-
-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[New] extract
parse
and quote
to their own deep imports
- Loading branch information
Showing
4 changed files
with
216 additions
and
209 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,203 +1,4 @@ | ||
'use strict'; | ||
|
||
exports.quote = function (xs) { | ||
return xs.map(function (s) { | ||
if (s && typeof s === 'object') { | ||
return s.op.replace(/(.)/g, '\\$1'); | ||
} else if ((/["\s]/).test(s) && !(/'/).test(s)) { | ||
return "'" + s.replace(/(['\\])/g, '\\$1') + "'"; | ||
} else if ((/["'\s]/).test(s)) { | ||
return '"' + s.replace(/(["\\$`!])/g, '\\$1') + '"'; | ||
} | ||
return String(s).replace(/([A-Za-z]:)?([#!"$&'()*,:;<=>?@[\\\]^`{|}])/g, '$1\\$2'); | ||
}).join(' '); | ||
}; | ||
|
||
// '<(' is process substitution operator and | ||
// can be parsed the same as control operator | ||
var CONTROL = '(?:' + [ | ||
'\\|\\|', '\\&\\&', ';;', '\\|\\&', '\\<\\(', '>>', '>\\&', '[&;()|<>]' | ||
].join('|') + ')'; | ||
var META = '|&;()<> \\t'; | ||
var BAREWORD = '(\\\\[\'"' + META + ']|[^\\s\'"' + META + '])+'; | ||
var SINGLE_QUOTE = '"((\\\\"|[^"])*?)"'; | ||
var DOUBLE_QUOTE = '\'((\\\\\'|[^\'])*?)\''; | ||
|
||
var TOKEN = ''; | ||
for (var i = 0; i < 4; i++) { | ||
TOKEN += (Math.pow(16, 8) * Math.random()).toString(16); | ||
} | ||
|
||
function parse(s, env, opts) { | ||
var chunker = new RegExp([ | ||
'(' + CONTROL + ')', // control chars | ||
'(' + BAREWORD + '|' + SINGLE_QUOTE + '|' + DOUBLE_QUOTE + ')*' | ||
].join('|'), 'g'); | ||
var match = s.match(chunker).filter(Boolean); | ||
|
||
if (!match) { | ||
return []; | ||
} | ||
if (!env) { | ||
env = {}; | ||
} | ||
if (!opts) { | ||
opts = {}; | ||
} | ||
|
||
var commented = false; | ||
|
||
function getVar(_, pre, key) { | ||
var r = typeof env === 'function' ? env(key) : env[key]; | ||
if (r === undefined && key != '') { | ||
r = ''; | ||
} else if (r === undefined) { | ||
r = '$'; | ||
} | ||
|
||
if (typeof r === 'object') { | ||
return pre + TOKEN + JSON.stringify(r) + TOKEN; | ||
} | ||
return pre + r; | ||
} | ||
|
||
return match.map(function (s, j) { | ||
if (commented) { | ||
return void undefined; | ||
} | ||
if (RegExp('^' + CONTROL + '$').test(s)) { | ||
return { op: s }; | ||
} | ||
|
||
// Hand-written scanner/parser for Bash quoting rules: | ||
// | ||
// 1. inside single quotes, all characters are printed literally. | ||
// 2. inside double quotes, all characters are printed literally | ||
// except variables prefixed by '$' and backslashes followed by | ||
// either a double quote or another backslash. | ||
// 3. outside of any quotes, backslashes are treated as escape | ||
// characters and not printed (unless they are themselves escaped) | ||
// 4. quote context can switch mid-token if there is no whitespace | ||
// between the two quote contexts (e.g. all'one'"token" parses as | ||
// "allonetoken") | ||
var SQ = "'"; | ||
var DQ = '"'; | ||
var DS = '$'; | ||
var BS = opts.escape || '\\'; | ||
var quote = false; | ||
var esc = false; | ||
var out = ''; | ||
var isGlob = false; | ||
var i; | ||
|
||
function parseEnvVar() { | ||
i += 1; | ||
var varend; | ||
var varname; | ||
// debugger | ||
if (s.charAt(i) === '{') { | ||
i += 1; | ||
if (s.charAt(i) === '}') { | ||
throw new Error('Bad substitution: ' + s.substr(i - 2, 3)); | ||
} | ||
varend = s.indexOf('}', i); | ||
if (varend < 0) { | ||
throw new Error('Bad substitution: ' + s.substr(i)); | ||
} | ||
varname = s.substr(i, varend - i); | ||
i = varend; | ||
} else if ((/[*@#?$!_-]/).test(s.charAt(i))) { | ||
varname = s.charAt(i); | ||
i += 1; | ||
} else { | ||
varend = s.substr(i).match(/[^\w\d_]/); | ||
if (!varend) { | ||
varname = s.substr(i); | ||
i = s.length; | ||
} else { | ||
varname = s.substr(i, varend.index); | ||
i += varend.index - 1; | ||
} | ||
} | ||
return getVar(null, '', varname); | ||
} | ||
|
||
for (i = 0; i < s.length; i++) { | ||
var c = s.charAt(i); | ||
isGlob = isGlob || (!quote && (c === '*' || c === '?')); | ||
if (esc) { | ||
out += c; | ||
esc = false; | ||
} else if (quote) { | ||
if (c === quote) { | ||
quote = false; | ||
} else if (quote == SQ) { | ||
out += c; | ||
} else { // Double quote | ||
if (c === BS) { | ||
i += 1; | ||
c = s.charAt(i); | ||
if (c === DQ || c === BS || c === DS) { | ||
out += c; | ||
} else { | ||
out += BS + c; | ||
} | ||
} else if (c === DS) { | ||
out += parseEnvVar(); | ||
} else { | ||
out += c; | ||
} | ||
} | ||
} else if (c === DQ || c === SQ) { | ||
quote = c; | ||
} else if (RegExp('^' + CONTROL + '$').test(c)) { | ||
return { op: s }; | ||
} else if ((/^#$/).test(c)) { | ||
commented = true; | ||
if (out.length) { | ||
return [out, { comment: s.slice(i + 1) + match.slice(j + 1).join(' ') }]; | ||
} | ||
return [{ comment: s.slice(i + 1) + match.slice(j + 1).join(' ') }]; | ||
} else if (c === BS) { | ||
esc = true; | ||
} else if (c === DS) { | ||
out += parseEnvVar(); | ||
} else { | ||
out += c; | ||
} | ||
} | ||
|
||
if (isGlob) { | ||
return { op: 'glob', pattern: out }; | ||
} | ||
|
||
return out; | ||
}).reduce(function (prev, arg) { // finalize parsed aruments | ||
if (arg === undefined) { | ||
return prev; | ||
} | ||
return prev.concat(arg); | ||
}, []); | ||
} | ||
|
||
exports.parse = function (s, env, opts) { | ||
var mapped = parse(s, env, opts); | ||
if (typeof env !== 'function') { | ||
return mapped; | ||
} | ||
return mapped.reduce(function (acc, s) { | ||
if (typeof s === 'object') { | ||
return acc.concat(s); | ||
} | ||
var xs = s.split(RegExp('(' + TOKEN + '.*?' + TOKEN + ')', 'g')); | ||
if (xs.length === 1) { | ||
return acc.concat(xs[0]); | ||
} | ||
return acc.concat(xs.filter(Boolean).map(function (x) { | ||
if (RegExp('^' + TOKEN).test(x)) { | ||
return JSON.parse(x.split(TOKEN)[1]); | ||
} | ||
return x; | ||
})); | ||
}, []); | ||
}; | ||
exports.quote = require('./quote'); | ||
exports.parse = require('./parse'); |
Oops, something went wrong.