| 'use strict'; |
| |
| var utils = module.exports; |
| var path = require('path'); |
| |
| /** |
| * Module dependencies |
| */ |
| |
| var isWindows = require('is-windows')(); |
| var Snapdragon = require('snapdragon'); |
| utils.define = require('define-property'); |
| utils.diff = require('arr-diff'); |
| utils.extend = require('extend-shallow'); |
| utils.pick = require('object.pick'); |
| utils.typeOf = require('kind-of'); |
| utils.unique = require('array-unique'); |
| |
| /** |
| * Returns true if the given value is effectively an empty string |
| */ |
| |
| utils.isEmptyString = function(val) { |
| return String(val) === '' || String(val) === './'; |
| }; |
| |
| /** |
| * Returns true if the platform is windows, or `path.sep` is `\\`. |
| * This is defined as a function to allow `path.sep` to be set in unit tests, |
| * or by the user, if there is a reason to do so. |
| * @return {Boolean} |
| */ |
| |
| utils.isWindows = function() { |
| return path.sep === '\\' || isWindows === true; |
| }; |
| |
| /** |
| * Return the last element from an array |
| */ |
| |
| utils.last = function(arr, n) { |
| return arr[arr.length - (n || 1)]; |
| }; |
| |
| /** |
| * Get the `Snapdragon` instance to use |
| */ |
| |
| utils.instantiate = function(ast, options) { |
| var snapdragon; |
| // if an instance was created by `.parse`, use that instance |
| if (utils.typeOf(ast) === 'object' && ast.snapdragon) { |
| snapdragon = ast.snapdragon; |
| // if the user supplies an instance on options, use that instance |
| } else if (utils.typeOf(options) === 'object' && options.snapdragon) { |
| snapdragon = options.snapdragon; |
| // create a new instance |
| } else { |
| snapdragon = new Snapdragon(options); |
| } |
| |
| utils.define(snapdragon, 'parse', function(str, options) { |
| var parsed = Snapdragon.prototype.parse.call(this, str, options); |
| parsed.input = str; |
| |
| // escape unmatched brace/bracket/parens |
| var last = this.parser.stack.pop(); |
| if (last && this.options.strictErrors !== true) { |
| var open = last.nodes[0]; |
| var inner = last.nodes[1]; |
| if (last.type === 'bracket') { |
| if (inner.val.charAt(0) === '[') { |
| inner.val = '\\' + inner.val; |
| } |
| |
| } else { |
| open.val = '\\' + open.val; |
| var sibling = open.parent.nodes[1]; |
| if (sibling.type === 'star') { |
| sibling.loose = true; |
| } |
| } |
| } |
| |
| // add non-enumerable parser reference |
| utils.define(parsed, 'parser', this.parser); |
| return parsed; |
| }); |
| |
| return snapdragon; |
| }; |
| |
| /** |
| * Create the key to use for memoization. The key is generated |
| * by iterating over the options and concatenating key-value pairs |
| * to the pattern string. |
| */ |
| |
| utils.createKey = function(pattern, options) { |
| if (typeof options === 'undefined') { |
| return pattern; |
| } |
| var key = pattern; |
| for (var prop in options) { |
| if (options.hasOwnProperty(prop)) { |
| key += ';' + prop + '=' + String(options[prop]); |
| } |
| } |
| return key; |
| }; |
| |
| /** |
| * Cast `val` to an array |
| * @return {Array} |
| */ |
| |
| utils.arrayify = function(val) { |
| if (typeof val === 'string') return [val]; |
| return val ? (Array.isArray(val) ? val : [val]) : []; |
| }; |
| |
| /** |
| * Return true if `val` is a non-empty string |
| */ |
| |
| utils.isString = function(val) { |
| return typeof val === 'string'; |
| }; |
| |
| /** |
| * Return true if `val` is a non-empty string |
| */ |
| |
| utils.isRegex = function(val) { |
| return utils.typeOf(val) === 'regexp'; |
| }; |
| |
| /** |
| * Return true if `val` is a non-empty string |
| */ |
| |
| utils.isObject = function(val) { |
| return utils.typeOf(val) === 'object'; |
| }; |
| |
| /** |
| * Escape regex characters in the given string |
| */ |
| |
| utils.escapeRegex = function(str) { |
| return str.replace(/[-[\]{}()^$|*+?.\\/\s]/g, '\\$&'); |
| }; |
| |
| /** |
| * Combines duplicate characters in the provided `input` string. |
| * @param {String} `input` |
| * @returns {String} |
| */ |
| |
| utils.combineDupes = function(input, patterns) { |
| patterns = utils.arrayify(patterns).join('|').split('|'); |
| patterns = patterns.map(function(s) { |
| return s.replace(/\\?([+*\\/])/g, '\\$1'); |
| }); |
| var substr = patterns.join('|'); |
| var regex = new RegExp('(' + substr + ')(?=\\1)', 'g'); |
| return input.replace(regex, ''); |
| }; |
| |
| /** |
| * Returns true if the given `str` has special characters |
| */ |
| |
| utils.hasSpecialChars = function(str) { |
| return /(?:(?:(^|\/)[!.])|[*?+()|[\]{}]|[+@]\()/.test(str); |
| }; |
| |
| /** |
| * Normalize slashes in the given filepath. |
| * |
| * @param {String} `filepath` |
| * @return {String} |
| */ |
| |
| utils.toPosixPath = function(str) { |
| return str.replace(/\\+/g, '/'); |
| }; |
| |
| /** |
| * Strip backslashes before special characters in a string. |
| * |
| * @param {String} `str` |
| * @return {String} |
| */ |
| |
| utils.unescape = function(str) { |
| return utils.toPosixPath(str.replace(/\\(?=[*+?!.])/g, '')); |
| }; |
| |
| /** |
| * Strip the drive letter from a windows filepath |
| * @param {String} `fp` |
| * @return {String} |
| */ |
| |
| utils.stripDrive = function(fp) { |
| return utils.isWindows() ? fp.replace(/^[a-z]:[\\/]+?/i, '/') : fp; |
| }; |
| |
| /** |
| * Strip the prefix from a filepath |
| * @param {String} `fp` |
| * @return {String} |
| */ |
| |
| utils.stripPrefix = function(str) { |
| if (str.charAt(0) === '.' && (str.charAt(1) === '/' || str.charAt(1) === '\\')) { |
| return str.slice(2); |
| } |
| return str; |
| }; |
| |
| /** |
| * Returns true if `str` is a common character that doesn't need |
| * to be processed to be used for matching. |
| * @param {String} `str` |
| * @return {Boolean} |
| */ |
| |
| utils.isSimpleChar = function(str) { |
| return str.trim() === '' || str === '.'; |
| }; |
| |
| /** |
| * Returns true if the given str is an escaped or |
| * unescaped path character |
| */ |
| |
| utils.isSlash = function(str) { |
| return str === '/' || str === '\\/' || str === '\\' || str === '\\\\'; |
| }; |
| |
| /** |
| * Returns a function that returns true if the given |
| * pattern matches or contains a `filepath` |
| * |
| * @param {String} `pattern` |
| * @return {Function} |
| */ |
| |
| utils.matchPath = function(pattern, options) { |
| return (options && options.contains) |
| ? utils.containsPattern(pattern, options) |
| : utils.equalsPattern(pattern, options); |
| }; |
| |
| /** |
| * Returns true if the given (original) filepath or unixified path are equal |
| * to the given pattern. |
| */ |
| |
| utils._equals = function(filepath, unixPath, pattern) { |
| return pattern === filepath || pattern === unixPath; |
| }; |
| |
| /** |
| * Returns true if the given (original) filepath or unixified path contain |
| * the given pattern. |
| */ |
| |
| utils._contains = function(filepath, unixPath, pattern) { |
| return filepath.indexOf(pattern) !== -1 || unixPath.indexOf(pattern) !== -1; |
| }; |
| |
| /** |
| * Returns a function that returns true if the given |
| * pattern is the same as a given `filepath` |
| * |
| * @param {String} `pattern` |
| * @return {Function} |
| */ |
| |
| utils.equalsPattern = function(pattern, options) { |
| var unixify = utils.unixify(options); |
| options = options || {}; |
| |
| return function fn(filepath) { |
| var equal = utils._equals(filepath, unixify(filepath), pattern); |
| if (equal === true || options.nocase !== true) { |
| return equal; |
| } |
| var lower = filepath.toLowerCase(); |
| return utils._equals(lower, unixify(lower), pattern); |
| }; |
| }; |
| |
| /** |
| * Returns a function that returns true if the given |
| * pattern contains a `filepath` |
| * |
| * @param {String} `pattern` |
| * @return {Function} |
| */ |
| |
| utils.containsPattern = function(pattern, options) { |
| var unixify = utils.unixify(options); |
| options = options || {}; |
| |
| return function(filepath) { |
| var contains = utils._contains(filepath, unixify(filepath), pattern); |
| if (contains === true || options.nocase !== true) { |
| return contains; |
| } |
| var lower = filepath.toLowerCase(); |
| return utils._contains(lower, unixify(lower), pattern); |
| }; |
| }; |
| |
| /** |
| * Returns a function that returns true if the given |
| * regex matches the `filename` of a file path. |
| * |
| * @param {RegExp} `re` Matching regex |
| * @return {Function} |
| */ |
| |
| utils.matchBasename = function(re) { |
| return function(filepath) { |
| return re.test(filepath) || re.test(path.basename(filepath)); |
| }; |
| }; |
| |
| /** |
| * Returns the given value unchanced. |
| * @return {any} |
| */ |
| |
| utils.identity = function(val) { |
| return val; |
| }; |
| |
| /** |
| * Determines the filepath to return based on the provided options. |
| * @return {any} |
| */ |
| |
| utils.value = function(str, unixify, options) { |
| if (options && options.unixify === false) { |
| return str; |
| } |
| if (options && typeof options.unixify === 'function') { |
| return options.unixify(str); |
| } |
| return unixify(str); |
| }; |
| |
| /** |
| * Returns a function that normalizes slashes in a string to forward |
| * slashes, strips `./` from beginning of paths, and optionally unescapes |
| * special characters. |
| * @return {Function} |
| */ |
| |
| utils.unixify = function(options) { |
| var opts = options || {}; |
| return function(filepath) { |
| if (opts.stripPrefix !== false) { |
| filepath = utils.stripPrefix(filepath); |
| } |
| if (opts.unescape === true) { |
| filepath = utils.unescape(filepath); |
| } |
| if (opts.unixify === true || utils.isWindows()) { |
| filepath = utils.toPosixPath(filepath); |
| } |
| return filepath; |
| }; |
| }; |