├── .travis.yml ├── README.md ├── index.js ├── package.json └── test.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 4 | - "0.10" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Glob To Regular Expression 2 | 3 | [![Build Status](https://travis-ci.org/fitzgen/glob-to-regexp.png?branch=master)](https://travis-ci.org/fitzgen/glob-to-regexp) 4 | 5 | Turn a \*-wildcard style glob (`"*.min.js"`) into a regular expression 6 | (`/^.*\.min\.js$/`)! 7 | 8 | To match bash-like globs, eg. `?` for any single-character match, `[a-z]` for 9 | character ranges, and `{*.html, *.js}` for multiple alternatives, call with 10 | `{ extended: true }`. 11 | 12 | To obey [globstars `**`](https://github.com/isaacs/node-glob#glob-primer) rules set option `{globstar: true}`. 13 | NOTE: This changes the behavior of `*` when `globstar` is `true` as shown below: 14 | When `{globstar: true}`: `/foo/**` will match any string that starts with `/foo/` 15 | like `/foo/index.htm`, `/foo/bar/baz.txt`, etc. Also, `/foo/**/*.txt` will match 16 | any string that starts with `/foo/` and ends with `.txt` like `/foo/bar.txt`, 17 | `/foo/bar/baz.txt`, etc. 18 | Whereas `/foo/*` (single `*`, not a globstar) will match strings that start with 19 | `/foo/` like `/foo/index.htm`, `/foo/baz.txt` but will not match strings that 20 | contain a `/` to the right like `/foo/bar/baz.txt`, `/foo/bar/baz/qux.dat`, etc. 21 | 22 | Set flags on the resulting `RegExp` object by adding the `flags` property to the option object, eg `{ flags: "i" }` for ignoring case. 23 | 24 | ## Install 25 | 26 | npm install glob-to-regexp 27 | 28 | ## Usage 29 | ```js 30 | var globToRegExp = require('glob-to-regexp'); 31 | var re = globToRegExp("p*uck"); 32 | re.test("pot luck"); // true 33 | re.test("pluck"); // true 34 | re.test("puck"); // true 35 | 36 | re = globToRegExp("*.min.js"); 37 | re.test("http://example.com/jquery.min.js"); // true 38 | re.test("http://example.com/jquery.min.js.map"); // false 39 | 40 | re = globToRegExp("*/www/*.js"); 41 | re.test("http://example.com/www/app.js"); // true 42 | re.test("http://example.com/www/lib/factory-proxy-model-observer.js"); // true 43 | 44 | // Extended globs 45 | re = globToRegExp("*/www/{*.js,*.html}", { extended: true }); 46 | re.test("http://example.com/www/app.js"); // true 47 | re.test("http://example.com/www/index.html"); // true 48 | ``` 49 | 50 | ## License 51 | 52 | Copyright (c) 2013, Nick Fitzgerald 53 | 54 | All rights reserved. 55 | 56 | Redistribution and use in source and binary forms, with or without modification, 57 | are permitted provided that the following conditions are met: 58 | 59 | * Redistributions of source code must retain the above copyright notice, this 60 | list of conditions and the following disclaimer. 61 | 62 | * Redistributions in binary form must reproduce the above copyright notice, this 63 | list of conditions and the following disclaimer in the documentation and/or 64 | other materials provided with the distribution. 65 | 66 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 67 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 68 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 69 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 70 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 71 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 72 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 73 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 74 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 75 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 76 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = function (glob, opts) { 2 | if (typeof glob !== 'string') { 3 | throw new TypeError('Expected a string'); 4 | } 5 | 6 | var str = String(glob); 7 | 8 | // The regexp we are building, as a string. 9 | var reStr = ""; 10 | 11 | // Whether we are matching so called "extended" globs (like bash) and should 12 | // support single character matching, matching ranges of characters, group 13 | // matching, etc. 14 | var extended = opts ? !!opts.extended : false; 15 | 16 | // When globstar is _false_ (default), '/foo/*' is translated a regexp like 17 | // '^\/foo\/.*$' which will match any string beginning with '/foo/' 18 | // When globstar is _true_, '/foo/*' is translated to regexp like 19 | // '^\/foo\/[^/]*$' which will match any string beginning with '/foo/' BUT 20 | // which does not have a '/' to the right of it. 21 | // E.g. with '/foo/*' these will match: '/foo/bar', '/foo/bar.txt' but 22 | // these will not '/foo/bar/baz', '/foo/bar/baz.txt' 23 | // Lastely, when globstar is _true_, '/foo/**' is equivelant to '/foo/*' when 24 | // globstar is _false_ 25 | var globstar = opts ? !!opts.globstar : false; 26 | 27 | // If we are doing extended matching, this boolean is true when we are inside 28 | // a group (eg {*.html,*.js}), and false otherwise. 29 | var inGroup = false; 30 | 31 | // RegExp flags (eg "i" ) to pass in to RegExp constructor. 32 | var flags = opts && typeof( opts.flags ) === "string" ? opts.flags : ""; 33 | 34 | var c; 35 | for (var i = 0, len = str.length; i < len; i++) { 36 | c = str[i]; 37 | 38 | switch (c) { 39 | case "/": 40 | case "$": 41 | case "^": 42 | case "+": 43 | case ".": 44 | case "(": 45 | case ")": 46 | case "=": 47 | case "!": 48 | case "|": 49 | reStr += "\\" + c; 50 | break; 51 | 52 | case "?": 53 | if (extended) { 54 | reStr += "."; 55 | break; 56 | } 57 | 58 | case "[": 59 | case "]": 60 | if (extended) { 61 | reStr += c; 62 | break; 63 | } 64 | 65 | case "{": 66 | if (extended) { 67 | inGroup = true; 68 | reStr += "("; 69 | break; 70 | } 71 | 72 | case "}": 73 | if (extended) { 74 | inGroup = false; 75 | reStr += ")"; 76 | break; 77 | } 78 | 79 | case ",": 80 | if (inGroup) { 81 | reStr += "|"; 82 | break; 83 | } 84 | reStr += "\\" + c; 85 | break; 86 | 87 | case "*": 88 | // Move over all consecutive "*"'s. 89 | // Also store the previous and next characters 90 | var prevChar = str[i - 1]; 91 | var starCount = 1; 92 | while(str[i + 1] === "*") { 93 | starCount++; 94 | i++; 95 | } 96 | var nextChar = str[i + 1]; 97 | 98 | if (!globstar) { 99 | // globstar is disabled, so treat any number of "*" as one 100 | reStr += ".*"; 101 | } else { 102 | // globstar is enabled, so determine if this is a globstar segment 103 | var isGlobstar = starCount > 1 // multiple "*"'s 104 | && (prevChar === "/" || prevChar === undefined) // from the start of the segment 105 | && (nextChar === "/" || nextChar === undefined) // to the end of the segment 106 | 107 | if (isGlobstar) { 108 | // it's a globstar, so match zero or more path segments 109 | reStr += "((?:[^/]*(?:\/|$))*)"; 110 | i++; // move over the "/" 111 | } else { 112 | // it's not a globstar, so only match one path segment 113 | reStr += "([^/]*)"; 114 | } 115 | } 116 | break; 117 | 118 | default: 119 | reStr += c; 120 | } 121 | } 122 | 123 | // When regexp 'g' flag is specified don't 124 | // constrain the regular expression with ^ & $ 125 | if (!flags || !~flags.indexOf('g')) { 126 | reStr = "^" + reStr + "$"; 127 | } 128 | 129 | return new RegExp(reStr, flags); 130 | }; 131 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glob-to-regexp", 3 | "version": "0.4.1", 4 | "description": "Convert globs to regular expressions", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node test.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/fitzgen/glob-to-regexp.git" 12 | }, 13 | "keywords": [ 14 | "regexp", 15 | "glob", 16 | "regexps", 17 | "regular expressions", 18 | "regular expression", 19 | "wildcard" 20 | ], 21 | "author": "Nick Fitzgerald ", 22 | "license": "BSD-2-Clause" 23 | } 24 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var globToRegexp = require("./index.js"); 2 | var assert = require("assert"); 3 | 4 | function assertMatch(glob, str, opts) { 5 | //console.log(glob, globToRegexp(glob, opts)); 6 | assert.ok(globToRegexp(glob, opts).test(str)); 7 | } 8 | 9 | function assertNotMatch(glob, str, opts) { 10 | //console.log(glob, globToRegexp(glob, opts)); 11 | assert.equal(false, globToRegexp(glob, opts).test(str)); 12 | } 13 | 14 | function test(globstar) { 15 | // Match everything 16 | assertMatch("*", "foo"); 17 | assertMatch("*", "foo", { flags: 'g' }); 18 | 19 | // Match the end 20 | assertMatch("f*", "foo"); 21 | assertMatch("f*", "foo", { flags: 'g' }); 22 | 23 | // Match the start 24 | assertMatch("*o", "foo"); 25 | assertMatch("*o", "foo", { flags: 'g' }); 26 | 27 | // Match the middle 28 | assertMatch("f*uck", "firetruck"); 29 | assertMatch("f*uck", "firetruck", { flags: 'g' }); 30 | 31 | // Don't match without Regexp 'g' 32 | assertNotMatch("uc", "firetruck"); 33 | // Match anywhere with RegExp 'g' 34 | assertMatch("uc", "firetruck", { flags: 'g' }); 35 | 36 | // Match zero characters 37 | assertMatch("f*uck", "fuck"); 38 | assertMatch("f*uck", "fuck", { flags: 'g' }); 39 | 40 | // More complex matches 41 | assertMatch("*.min.js", "http://example.com/jquery.min.js", {globstar: false}); 42 | assertMatch("*.min.*", "http://example.com/jquery.min.js", {globstar: false}); 43 | assertMatch("*/js/*.js", "http://example.com/js/jquery.min.js", {globstar: false}); 44 | 45 | // More complex matches with RegExp 'g' flag (complex regression) 46 | assertMatch("*.min.*", "http://example.com/jquery.min.js", { flags: 'g' }); 47 | assertMatch("*.min.js", "http://example.com/jquery.min.js", { flags: 'g' }); 48 | assertMatch("*/js/*.js", "http://example.com/js/jquery.min.js", { flags: 'g' }); 49 | 50 | // Test string "\\\\/$^+?.()=!|{},[].*" represents \\/$^+?.()=!|{},[].* 51 | // The equivalent regex is: /^\\\/\$\^\+\?\.\(\)\=\!\|\{\}\,\[\]\..*$/ 52 | // Both glob and regex match: \/$^+?.()=!|{},[].* 53 | var testStr = "\\\\/$^+?.()=!|{},[].*"; 54 | var targetStr = "\\/$^+?.()=!|{},[].*"; 55 | assertMatch(testStr, targetStr); 56 | assertMatch(testStr, targetStr, { flags: 'g' }); 57 | 58 | // Equivalent matches without/with using RegExp 'g' 59 | assertNotMatch(".min.", "http://example.com/jquery.min.js"); 60 | assertMatch("*.min.*", "http://example.com/jquery.min.js"); 61 | assertMatch(".min.", "http://example.com/jquery.min.js", { flags: 'g' }); 62 | 63 | assertNotMatch("http:", "http://example.com/jquery.min.js"); 64 | assertMatch("http:*", "http://example.com/jquery.min.js"); 65 | assertMatch("http:", "http://example.com/jquery.min.js", { flags: 'g' }); 66 | 67 | assertNotMatch("min.js", "http://example.com/jquery.min.js"); 68 | assertMatch("*.min.js", "http://example.com/jquery.min.js"); 69 | assertMatch("min.js", "http://example.com/jquery.min.js", { flags: 'g' }); 70 | 71 | // Match anywhere (globally) using RegExp 'g' 72 | assertMatch("min", "http://example.com/jquery.min.js", { flags: 'g' }); 73 | assertMatch("/js/", "http://example.com/js/jquery.min.js", { flags: 'g' }); 74 | 75 | assertNotMatch("/js*jq*.js", "http://example.com/js/jquery.min.js"); 76 | assertMatch("/js*jq*.js", "http://example.com/js/jquery.min.js", { flags: 'g' }); 77 | 78 | // Extended mode 79 | 80 | // ?: Match one character, no more and no less 81 | assertMatch("f?o", "foo", { extended: true }); 82 | assertNotMatch("f?o", "fooo", { extended: true }); 83 | assertNotMatch("f?oo", "foo", { extended: true }); 84 | 85 | // ?: Match one character with RegExp 'g' 86 | assertMatch("f?o", "foo", { extended: true, globstar: globstar, flags: 'g' }); 87 | assertMatch("f?o", "fooo", { extended: true, globstar: globstar, flags: 'g' }); 88 | assertMatch("f?o?", "fooo", { extended: true, globstar: globstar, flags: 'g' }); 89 | assertNotMatch("?fo", "fooo", { extended: true, globstar: globstar, flags: 'g' }); 90 | assertNotMatch("f?oo", "foo", { extended: true, globstar: globstar, flags: 'g' }); 91 | assertNotMatch("foo?", "foo", { extended: true, globstar: globstar, flags: 'g' }); 92 | 93 | // []: Match a character range 94 | assertMatch("fo[oz]", "foo", { extended: true }); 95 | assertMatch("fo[oz]", "foz", { extended: true }); 96 | assertNotMatch("fo[oz]", "fog", { extended: true }); 97 | 98 | // []: Match a character range and RegExp 'g' (regresion) 99 | assertMatch("fo[oz]", "foo", { extended: true, globstar: globstar, flags: 'g' }); 100 | assertMatch("fo[oz]", "foz", { extended: true, globstar: globstar, flags: 'g' }); 101 | assertNotMatch("fo[oz]", "fog", { extended: true, globstar: globstar, flags: 'g' }); 102 | 103 | // {}: Match a choice of different substrings 104 | assertMatch("foo{bar,baaz}", "foobaaz", { extended: true }); 105 | assertMatch("foo{bar,baaz}", "foobar", { extended: true }); 106 | assertNotMatch("foo{bar,baaz}", "foobuzz", { extended: true }); 107 | assertMatch("foo{bar,b*z}", "foobuzz", { extended: true }); 108 | 109 | // {}: Match a choice of different substrings and RegExp 'g' (regression) 110 | assertMatch("foo{bar,baaz}", "foobaaz", { extended: true, globstar: globstar, flags: 'g' }); 111 | assertMatch("foo{bar,baaz}", "foobar", { extended: true, globstar: globstar, flags: 'g' }); 112 | assertNotMatch("foo{bar,baaz}", "foobuzz", { extended: true, globstar: globstar, flags: 'g' }); 113 | assertMatch("foo{bar,b*z}", "foobuzz", { extended: true, globstar: globstar, flags: 'g' }); 114 | 115 | // More complex extended matches 116 | assertMatch("http://?o[oz].b*z.com/{*.js,*.html}", 117 | "http://foo.baaz.com/jquery.min.js", 118 | { extended: true }); 119 | assertMatch("http://?o[oz].b*z.com/{*.js,*.html}", 120 | "http://moz.buzz.com/index.html", 121 | { extended: true }); 122 | assertNotMatch("http://?o[oz].b*z.com/{*.js,*.html}", 123 | "http://moz.buzz.com/index.htm", 124 | { extended: true }); 125 | assertNotMatch("http://?o[oz].b*z.com/{*.js,*.html}", 126 | "http://moz.bar.com/index.html", 127 | { extended: true }); 128 | assertNotMatch("http://?o[oz].b*z.com/{*.js,*.html}", 129 | "http://flozz.buzz.com/index.html", 130 | { extended: true }); 131 | 132 | // More complex extended matches and RegExp 'g' (regresion) 133 | assertMatch("http://?o[oz].b*z.com/{*.js,*.html}", 134 | "http://foo.baaz.com/jquery.min.js", 135 | { extended: true, globstar: globstar, flags: 'g' }); 136 | assertMatch("http://?o[oz].b*z.com/{*.js,*.html}", 137 | "http://moz.buzz.com/index.html", 138 | { extended: true, globstar: globstar, flags: 'g' }); 139 | assertNotMatch("http://?o[oz].b*z.com/{*.js,*.html}", 140 | "http://moz.buzz.com/index.htm", 141 | { extended: true, globstar: globstar, flags: 'g' }); 142 | assertNotMatch("http://?o[oz].b*z.com/{*.js,*.html}", 143 | "http://moz.bar.com/index.html", 144 | { extended: true, globstar: globstar, flags: 'g' }); 145 | assertNotMatch("http://?o[oz].b*z.com/{*.js,*.html}", 146 | "http://flozz.buzz.com/index.html", 147 | { extended: true, globstar: globstar, flags: 'g' }); 148 | 149 | // globstar 150 | assertMatch("http://foo.com/**/{*.js,*.html}", 151 | "http://foo.com/bar/jquery.min.js", 152 | { extended: true, globstar: globstar, flags: 'g' }); 153 | assertMatch("http://foo.com/**/{*.js,*.html}", 154 | "http://foo.com/bar/baz/jquery.min.js", 155 | { extended: true, globstar: globstar, flags: 'g' }); 156 | assertMatch("http://foo.com/**", 157 | "http://foo.com/bar/baz/jquery.min.js", 158 | { extended: true, globstar: globstar, flags: 'g' }); 159 | 160 | // Remaining special chars should still match themselves 161 | // Test string "\\\\/$^+.()=!|,.*" represents \\/$^+.()=!|,.* 162 | // The equivalent regex is: /^\\\/\$\^\+\.\(\)\=\!\|\,\..*$/ 163 | // Both glob and regex match: \/$^+.()=!|,.* 164 | var testExtStr = "\\\\/$^+.()=!|,.*"; 165 | var targetExtStr = "\\/$^+.()=!|,.*"; 166 | assertMatch(testExtStr, targetExtStr, { extended: true }); 167 | assertMatch(testExtStr, targetExtStr, { extended: true, globstar: globstar, flags: 'g' }); 168 | } 169 | 170 | // regression 171 | // globstar false 172 | test(false) 173 | // globstar true 174 | test(true); 175 | 176 | // globstar specific tests 177 | assertMatch("/foo/*", "/foo/bar.txt", {globstar: true }); 178 | assertMatch("/foo/**", "/foo/baz.txt", {globstar: true }); 179 | assertMatch("/foo/**", "/foo/bar/baz.txt", {globstar: true }); 180 | assertMatch("/foo/*/*.txt", "/foo/bar/baz.txt", {globstar: true }); 181 | assertMatch("/foo/**/*.txt", "/foo/bar/baz.txt", {globstar: true }); 182 | assertMatch("/foo/**/*.txt", "/foo/bar/baz/qux.txt", {globstar: true }); 183 | assertMatch("/foo/**/bar.txt", "/foo/bar.txt", {globstar: true }); 184 | assertMatch("/foo/**/**/bar.txt", "/foo/bar.txt", {globstar: true }); 185 | assertMatch("/foo/**/*/baz.txt", "/foo/bar/baz.txt", {globstar: true }); 186 | assertMatch("/foo/**/*.txt", "/foo/bar.txt", {globstar: true }); 187 | assertMatch("/foo/**/**/*.txt", "/foo/bar.txt", {globstar: true }); 188 | assertMatch("/foo/**/*/*.txt", "/foo/bar/baz.txt", {globstar: true }); 189 | assertMatch("**/*.txt", "/foo/bar/baz/qux.txt", {globstar: true }); 190 | assertMatch("**/foo.txt", "foo.txt", {globstar: true }); 191 | assertMatch("**/*.txt", "foo.txt", {globstar: true }); 192 | 193 | assertNotMatch("/foo/*", "/foo/bar/baz.txt", {globstar: true }); 194 | assertNotMatch("/foo/*.txt", "/foo/bar/baz.txt", {globstar: true }); 195 | assertNotMatch("/foo/*/*.txt", "/foo/bar/baz/qux.txt", {globstar: true }); 196 | assertNotMatch("/foo/*/bar.txt", "/foo/bar.txt", {globstar: true }); 197 | assertNotMatch("/foo/*/*/baz.txt", "/foo/bar/baz.txt", {globstar: true }); 198 | assertNotMatch("/foo/**.txt", "/foo/bar/baz/qux.txt", {globstar: true }); 199 | assertNotMatch("/foo/bar**/*.txt", "/foo/bar/baz/qux.txt", {globstar: true }); 200 | assertNotMatch("/foo/bar**", "/foo/bar/baz.txt", {globstar: true }); 201 | assertNotMatch("**/.txt", "/foo/bar/baz/qux.txt", {globstar: true }); 202 | assertNotMatch("*/*.txt", "/foo/bar/baz/qux.txt", {globstar: true }); 203 | assertNotMatch("*/*.txt", "foo.txt", {globstar: true }); 204 | 205 | assertNotMatch("http://foo.com/*", 206 | "http://foo.com/bar/baz/jquery.min.js", 207 | { extended: true, globstar: true }); 208 | assertNotMatch("http://foo.com/*", 209 | "http://foo.com/bar/baz/jquery.min.js", 210 | { globstar: true }); 211 | 212 | assertMatch("http://foo.com/*", 213 | "http://foo.com/bar/baz/jquery.min.js", 214 | { globstar: false }); 215 | assertMatch("http://foo.com/**", 216 | "http://foo.com/bar/baz/jquery.min.js", 217 | { globstar: true }); 218 | 219 | assertMatch("http://foo.com/*/*/jquery.min.js", 220 | "http://foo.com/bar/baz/jquery.min.js", 221 | { globstar: true }); 222 | assertMatch("http://foo.com/**/jquery.min.js", 223 | "http://foo.com/bar/baz/jquery.min.js", 224 | { globstar: true }); 225 | assertMatch("http://foo.com/*/*/jquery.min.js", 226 | "http://foo.com/bar/baz/jquery.min.js", 227 | { globstar: false }); 228 | assertMatch("http://foo.com/*/jquery.min.js", 229 | "http://foo.com/bar/baz/jquery.min.js", 230 | { globstar: false }); 231 | assertNotMatch("http://foo.com/*/jquery.min.js", 232 | "http://foo.com/bar/baz/jquery.min.js", 233 | { globstar: true }); 234 | 235 | console.log("Ok!"); 236 | --------------------------------------------------------------------------------