You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
199 lines
6.4 KiB
199 lines
6.4 KiB
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') + '"'; |
|
} |
|
else { |
|
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); |
|
} |
|
|
|
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]); |
|
} |
|
else return x; |
|
})); |
|
}, []); |
|
}; |
|
|
|
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); |
|
var commented = false; |
|
|
|
if (!match) return []; |
|
if (!env) env = {}; |
|
if (!opts) opts = {}; |
|
return match.map(function (s, j) { |
|
if (commented) { |
|
return; |
|
} |
|
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; |
|
|
|
for (var i = 0, len = s.length; i < len; 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 (RegExp('^#$').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; |
|
|
|
function parseEnvVar() { |
|
i += 1; |
|
var varend, 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); |
|
} |
|
}) |
|
// finalize parsed aruments |
|
.reduce(function(prev, arg){ |
|
if (arg === undefined){ |
|
return prev; |
|
} |
|
return prev.concat(arg); |
|
},[]); |
|
|
|
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; |
|
} |
|
else return pre + r; |
|
} |
|
}
|
|
|