utils.js
1 'use strict'; 2 3 var utils = module.exports; 4 var path = require('path'); 5 6 /** 7 * Module dependencies 8 */ 9 10 var Snapdragon = require('snapdragon'); 11 utils.define = require('define-property'); 12 utils.diff = require('arr-diff'); 13 utils.extend = require('extend-shallow'); 14 utils.pick = require('object.pick'); 15 utils.typeOf = require('kind-of'); 16 utils.unique = require('array-unique'); 17 18 /** 19 * Returns true if the platform is windows, or `path.sep` is `\\`. 20 * This is defined as a function to allow `path.sep` to be set in unit tests, 21 * or by the user, if there is a reason to do so. 22 * @return {Boolean} 23 */ 24 25 utils.isWindows = function() { 26 return path.sep === '\\' || process.platform === 'win32'; 27 }; 28 29 /** 30 * Get the `Snapdragon` instance to use 31 */ 32 33 utils.instantiate = function(ast, options) { 34 var snapdragon; 35 // if an instance was created by `.parse`, use that instance 36 if (utils.typeOf(ast) === 'object' && ast.snapdragon) { 37 snapdragon = ast.snapdragon; 38 // if the user supplies an instance on options, use that instance 39 } else if (utils.typeOf(options) === 'object' && options.snapdragon) { 40 snapdragon = options.snapdragon; 41 // create a new instance 42 } else { 43 snapdragon = new Snapdragon(options); 44 } 45 46 utils.define(snapdragon, 'parse', function(str, options) { 47 var parsed = Snapdragon.prototype.parse.apply(this, arguments); 48 parsed.input = str; 49 50 // escape unmatched brace/bracket/parens 51 var last = this.parser.stack.pop(); 52 if (last && this.options.strictErrors !== true) { 53 var open = last.nodes[0]; 54 var inner = last.nodes[1]; 55 if (last.type === 'bracket') { 56 if (inner.val.charAt(0) === '[') { 57 inner.val = '\\' + inner.val; 58 } 59 60 } else { 61 open.val = '\\' + open.val; 62 var sibling = open.parent.nodes[1]; 63 if (sibling.type === 'star') { 64 sibling.loose = true; 65 } 66 } 67 } 68 69 // add non-enumerable parser reference 70 utils.define(parsed, 'parser', this.parser); 71 return parsed; 72 }); 73 74 return snapdragon; 75 }; 76 77 /** 78 * Create the key to use for memoization. The key is generated 79 * by iterating over the options and concatenating key-value pairs 80 * to the pattern string. 81 */ 82 83 utils.createKey = function(pattern, options) { 84 if (utils.typeOf(options) !== 'object') { 85 return pattern; 86 } 87 var val = pattern; 88 var keys = Object.keys(options); 89 for (var i = 0; i < keys.length; i++) { 90 var key = keys[i]; 91 val += ';' + key + '=' + String(options[key]); 92 } 93 return val; 94 }; 95 96 /** 97 * Cast `val` to an array 98 * @return {Array} 99 */ 100 101 utils.arrayify = function(val) { 102 if (typeof val === 'string') return [val]; 103 return val ? (Array.isArray(val) ? val : [val]) : []; 104 }; 105 106 /** 107 * Return true if `val` is a non-empty string 108 */ 109 110 utils.isString = function(val) { 111 return typeof val === 'string'; 112 }; 113 114 /** 115 * Return true if `val` is a non-empty string 116 */ 117 118 utils.isObject = function(val) { 119 return utils.typeOf(val) === 'object'; 120 }; 121 122 /** 123 * Returns true if the given `str` has special characters 124 */ 125 126 utils.hasSpecialChars = function(str) { 127 return /(?:(?:(^|\/)[!.])|[*?+()|\[\]{}]|[+@]\()/.test(str); 128 }; 129 130 /** 131 * Escape regex characters in the given string 132 */ 133 134 utils.escapeRegex = function(str) { 135 return str.replace(/[-[\]{}()^$|*+?.\\\/\s]/g, '\\$&'); 136 }; 137 138 /** 139 * Normalize slashes in the given filepath. 140 * 141 * @param {String} `filepath` 142 * @return {String} 143 */ 144 145 utils.toPosixPath = function(str) { 146 return str.replace(/\\+/g, '/'); 147 }; 148 149 /** 150 * Strip backslashes before special characters in a string. 151 * 152 * @param {String} `str` 153 * @return {String} 154 */ 155 156 utils.unescape = function(str) { 157 return utils.toPosixPath(str.replace(/\\(?=[*+?!.])/g, '')); 158 }; 159 160 /** 161 * Strip the prefix from a filepath 162 * @param {String} `fp` 163 * @return {String} 164 */ 165 166 utils.stripPrefix = function(str) { 167 if (str.charAt(0) !== '.') { 168 return str; 169 } 170 var ch = str.charAt(1); 171 if (utils.isSlash(ch)) { 172 return str.slice(2); 173 } 174 return str; 175 }; 176 177 /** 178 * Returns true if the given str is an escaped or 179 * unescaped path character 180 */ 181 182 utils.isSlash = function(str) { 183 return str === '/' || str === '\\/' || str === '\\' || str === '\\\\'; 184 }; 185 186 /** 187 * Returns a function that returns true if the given 188 * pattern matches or contains a `filepath` 189 * 190 * @param {String} `pattern` 191 * @return {Function} 192 */ 193 194 utils.matchPath = function(pattern, options) { 195 return (options && options.contains) 196 ? utils.containsPattern(pattern, options) 197 : utils.equalsPattern(pattern, options); 198 }; 199 200 /** 201 * Returns true if the given (original) filepath or unixified path are equal 202 * to the given pattern. 203 */ 204 205 utils._equals = function(filepath, unixPath, pattern) { 206 return pattern === filepath || pattern === unixPath; 207 }; 208 209 /** 210 * Returns true if the given (original) filepath or unixified path contain 211 * the given pattern. 212 */ 213 214 utils._contains = function(filepath, unixPath, pattern) { 215 return filepath.indexOf(pattern) !== -1 || unixPath.indexOf(pattern) !== -1; 216 }; 217 218 /** 219 * Returns a function that returns true if the given 220 * pattern is the same as a given `filepath` 221 * 222 * @param {String} `pattern` 223 * @return {Function} 224 */ 225 226 utils.equalsPattern = function(pattern, options) { 227 var unixify = utils.unixify(options); 228 options = options || {}; 229 230 return function fn(filepath) { 231 var equal = utils._equals(filepath, unixify(filepath), pattern); 232 if (equal === true || options.nocase !== true) { 233 return equal; 234 } 235 var lower = filepath.toLowerCase(); 236 return utils._equals(lower, unixify(lower), pattern); 237 }; 238 }; 239 240 /** 241 * Returns a function that returns true if the given 242 * pattern contains a `filepath` 243 * 244 * @param {String} `pattern` 245 * @return {Function} 246 */ 247 248 utils.containsPattern = function(pattern, options) { 249 var unixify = utils.unixify(options); 250 options = options || {}; 251 252 return function(filepath) { 253 var contains = utils._contains(filepath, unixify(filepath), pattern); 254 if (contains === true || options.nocase !== true) { 255 return contains; 256 } 257 var lower = filepath.toLowerCase(); 258 return utils._contains(lower, unixify(lower), pattern); 259 }; 260 }; 261 262 /** 263 * Returns a function that returns true if the given 264 * regex matches the `filename` of a file path. 265 * 266 * @param {RegExp} `re` Matching regex 267 * @return {Function} 268 */ 269 270 utils.matchBasename = function(re) { 271 return function(filepath) { 272 return re.test(path.basename(filepath)); 273 }; 274 }; 275 276 /** 277 * Determines the filepath to return based on the provided options. 278 * @return {any} 279 */ 280 281 utils.value = function(str, unixify, options) { 282 if (options && options.unixify === false) { 283 return str; 284 } 285 return unixify(str); 286 }; 287 288 /** 289 * Returns a function that normalizes slashes in a string to forward 290 * slashes, strips `./` from beginning of paths, and optionally unescapes 291 * special characters. 292 * @return {Function} 293 */ 294 295 utils.unixify = function(options) { 296 options = options || {}; 297 return function(filepath) { 298 if (utils.isWindows() || options.unixify === true) { 299 filepath = utils.toPosixPath(filepath); 300 } 301 if (options.stripPrefix !== false) { 302 filepath = utils.stripPrefix(filepath); 303 } 304 if (options.unescape === true) { 305 filepath = utils.unescape(filepath); 306 } 307 return filepath; 308 }; 309 };