globToRegExp.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. function globToRegExp(glob) {
  7. // * [^\\\/]*
  8. // /**/ /.+/
  9. // ^* \./.+ (concord special)
  10. // ? [^\\\/]
  11. // [!...] [^...]
  12. // [^...] [^...]
  13. // / [\\\/]
  14. // {...,...} (...|...)
  15. // ?(...|...) (...|...)?
  16. // +(...|...) (...|...)+
  17. // *(...|...) (...|...)*
  18. // @(...|...) (...|...)
  19. if (/^\(.+\)$/.test(glob)) {
  20. // allow to pass an RegExp in brackets
  21. return new RegExp(glob.substr(1, glob.length - 2));
  22. }
  23. const tokens = tokenize(glob);
  24. const process = createRoot();
  25. const regExpStr = tokens.map(process).join("");
  26. return new RegExp("^" + regExpStr + "$");
  27. }
  28. const SIMPLE_TOKENS = {
  29. "@(": "one",
  30. "?(": "zero-one",
  31. "+(": "one-many",
  32. "*(": "zero-many",
  33. "|": "segment-sep",
  34. "/**/": "any-path-segments",
  35. "**": "any-path",
  36. "*": "any-path-segment",
  37. "?": "any-char",
  38. "{": "or",
  39. "/": "path-sep",
  40. ",": "comma",
  41. ")": "closing-segment",
  42. "}": "closing-or"
  43. };
  44. function tokenize(glob) {
  45. return glob
  46. .split(
  47. /([@?+*]\(|\/\*\*\/|\*\*|[?*]|\[[!^]?(?:[^\]\\]|\\.)+\]|\{|,|\/|[|)}])/g
  48. )
  49. .map(item => {
  50. if (!item) return null;
  51. const t = SIMPLE_TOKENS[item];
  52. if (t) {
  53. return {
  54. type: t
  55. };
  56. }
  57. if (item[0] === "[") {
  58. if (item[1] === "^" || item[1] === "!") {
  59. return {
  60. type: "inverted-char-set",
  61. value: item.substr(2, item.length - 3)
  62. };
  63. } else {
  64. return {
  65. type: "char-set",
  66. value: item.substr(1, item.length - 2)
  67. };
  68. }
  69. }
  70. return {
  71. type: "string",
  72. value: item
  73. };
  74. })
  75. .filter(Boolean)
  76. .concat({
  77. type: "end"
  78. });
  79. }
  80. function createRoot() {
  81. const inOr = [];
  82. const process = createSeqment();
  83. let initial = true;
  84. return function(token) {
  85. switch (token.type) {
  86. case "or":
  87. inOr.push(initial);
  88. return "(";
  89. case "comma":
  90. if (inOr.length) {
  91. initial = inOr[inOr.length - 1];
  92. return "|";
  93. } else {
  94. return process(
  95. {
  96. type: "string",
  97. value: ","
  98. },
  99. initial
  100. );
  101. }
  102. case "closing-or":
  103. if (inOr.length === 0) throw new Error("Unmatched '}'");
  104. inOr.pop();
  105. return ")";
  106. case "end":
  107. if (inOr.length) throw new Error("Unmatched '{'");
  108. return process(token, initial);
  109. default: {
  110. const result = process(token, initial);
  111. initial = false;
  112. return result;
  113. }
  114. }
  115. };
  116. }
  117. function createSeqment() {
  118. const inSeqment = [];
  119. const process = createSimple();
  120. return function(token, initial) {
  121. switch (token.type) {
  122. case "one":
  123. case "one-many":
  124. case "zero-many":
  125. case "zero-one":
  126. inSeqment.push(token.type);
  127. return "(";
  128. case "segment-sep":
  129. if (inSeqment.length) {
  130. return "|";
  131. } else {
  132. return process(
  133. {
  134. type: "string",
  135. value: "|"
  136. },
  137. initial
  138. );
  139. }
  140. case "closing-segment": {
  141. const segment = inSeqment.pop();
  142. switch (segment) {
  143. case "one":
  144. return ")";
  145. case "one-many":
  146. return ")+";
  147. case "zero-many":
  148. return ")*";
  149. case "zero-one":
  150. return ")?";
  151. }
  152. throw new Error("Unexcepted segment " + segment);
  153. }
  154. case "end":
  155. if (inSeqment.length > 0) {
  156. throw new Error("Unmatched segment, missing ')'");
  157. }
  158. return process(token, initial);
  159. default:
  160. return process(token, initial);
  161. }
  162. };
  163. }
  164. function createSimple() {
  165. return function(token, initial) {
  166. switch (token.type) {
  167. case "path-sep":
  168. return "[\\\\/]+";
  169. case "any-path-segments":
  170. return "[\\\\/]+(?:(.+)[\\\\/]+)?";
  171. case "any-path":
  172. return "(.*)";
  173. case "any-path-segment":
  174. if (initial) {
  175. return "\\.[\\\\/]+(?:.*[\\\\/]+)?([^\\\\/]+)";
  176. } else {
  177. return "([^\\\\/]*)";
  178. }
  179. case "any-char":
  180. return "[^\\\\/]";
  181. case "inverted-char-set":
  182. return "[^" + token.value + "]";
  183. case "char-set":
  184. return "[" + token.value + "]";
  185. case "string":
  186. return token.value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
  187. case "end":
  188. return "";
  189. default:
  190. throw new Error("Unsupported token '" + token.type + "'");
  191. }
  192. };
  193. }
  194. exports.globToRegExp = globToRegExp;