'use strict'; var chars = require('./chars'); var utils = require('./utils'); /** * Expose `Glob` */ var Glob = module.exports = function Glob(pattern, options) { if (!(this instanceof Glob)) { return new Glob(pattern, options); } this.options = options || {}; this.pattern = pattern; this.history = []; this.tokens = {}; this.init(pattern); }; /** * Initialize defaults */ Glob.prototype.init = function(pattern) { this.orig = pattern; this.negated = this.isNegated(); this.options.track = this.options.track || false; this.options.makeRe = true; }; /** * Push a change into `glob.history`. Useful * for debugging. */ Glob.prototype.track = function(msg) { if (this.options.track) { this.history.push({msg: msg, pattern: this.pattern}); } }; /** * Return true if `glob.pattern` was negated * with `!`, also remove the `!` from the pattern. * * @return {Boolean} */ Glob.prototype.isNegated = function() { if (this.pattern.charCodeAt(0) === 33 /* '!' */) { this.pattern = this.pattern.slice(1); return true; } return false; }; /** * Expand braces in the given glob pattern. * * We only need to use the [braces] lib when * patterns are nested. */ Glob.prototype.braces = function() { if (this.options.nobraces !== true && this.options.nobrace !== true) { // naive/fast check for imbalanced characters var a = this.pattern.match(/[\{\(\[]/g); var b = this.pattern.match(/[\}\)\]]/g); // if imbalanced, don't optimize the pattern if (a && b && (a.length !== b.length)) { this.options.makeRe = false; } // expand brace patterns and join the resulting array var expanded = utils.braces(this.pattern, this.options); this.pattern = expanded.join('|'); } }; /** * Expand bracket expressions in `glob.pattern` */ Glob.prototype.brackets = function() { if (this.options.nobrackets !== true) { this.pattern = utils.brackets(this.pattern); } }; /** * Expand bracket expressions in `glob.pattern` */ Glob.prototype.extglob = function() { if (this.options.noextglob === true) return; if (utils.isExtglob(this.pattern)) { this.pattern = utils.extglob(this.pattern, {escape: true}); } }; /** * Parse the given pattern */ Glob.prototype.parse = function(pattern) { this.tokens = utils.parseGlob(pattern || this.pattern, true); return this.tokens; }; /** * Replace `a` with `b`. Also tracks the change before and * after each replacement. This is disabled by default, but * can be enabled by setting `options.track` to true. * * Also, when the pattern is a string, `.split()` is used, * because it's much faster than replace. * * @param {RegExp|String} `a` * @param {String} `b` * @param {Boolean} `escape` When `true`, escapes `*` and `?` in the replacement. * @return {String} */ Glob.prototype._replace = function(a, b, escape) { this.track('before (find): "' + a + '" (replace with): "' + b + '"'); if (escape) b = esc(b); if (a && b && typeof a === 'string') { this.pattern = this.pattern.split(a).join(b); } else { this.pattern = this.pattern.replace(a, b); } this.track('after'); }; /** * Escape special characters in the given string. * * @param {String} `str` Glob pattern * @return {String} */ Glob.prototype.escape = function(str) { this.track('before escape: '); var re = /["\\](['"]?[^"'\\]['"]?)/g; this.pattern = str.replace(re, function($0, $1) { var o = chars.ESC; var ch = o && o[$1]; if (ch) { return ch; } if (/[a-z]/i.test($0)) { return $0.split('\\').join(''); } return $0; }); this.track('after escape: '); }; /** * Unescape special characters in the given string. * * @param {String} `str` * @return {String} */ Glob.prototype.unescape = function(str) { var re = /__([A-Z]+)_([A-Z]+)__/g; this.pattern = str.replace(re, function($0, $1) { return chars[$1][$0]; }); this.pattern = unesc(this.pattern); }; /** * Escape/unescape utils */ function esc(str) { str = str.split('?').join('%~'); str = str.split('*').join('%%'); return str; } function unesc(str) { str = str.split('%~').join('?'); str = str.split('%%').join('*'); return str; }