shell-quote
Advanced tools
Comparing version 1.8.0 to 1.8.1
@@ -8,2 +8,19 @@ # Changelog | ||
## [v1.8.1](https://github.com/ljharb/shell-quote/compare/v1.8.0...v1.8.1) - 2023-04-07 | ||
### Fixed | ||
- [Fix] `parse`: preserve whitespace in comments [`#6`](https://github.com/ljharb/shell-quote/issues/6) | ||
- [Fix] properly support the `escape` option [`#5`](https://github.com/ljharb/shell-quote/issues/5) | ||
### Commits | ||
- [Refactor] `parse`: hoist `getVar` to module level [`b42ac73`](https://github.com/ljharb/shell-quote/commit/b42ac73e39e566cfc355a4addc4bd2df2652556c) | ||
- [Refactor] hoist some vars to module level [`8f0c5c3`](https://github.com/ljharb/shell-quote/commit/8f0c5c3c9df3a10e32f1972636675af6fffef998) | ||
- [Refactor] `parse`: use `slice` over `substr`, cache some values [`fcb2e1a`](https://github.com/ljharb/shell-quote/commit/fcb2e1acd5312a1a1a4e6c66ec688aab383023b5) | ||
- [Refactor] `parse`: a bit of cleanup [`6780ec5`](https://github.com/ljharb/shell-quote/commit/6780ec5194e36e2a696bfbaaf85169682a333321) | ||
- [Refactor] `parse`: tweak the regex to not match nothing [`227d474`](https://github.com/ljharb/shell-quote/commit/227d4742a006e81ec3fde1eee103731a6f7ea920) | ||
- [Tests] increase coverage [`a66de94`](https://github.com/ljharb/shell-quote/commit/a66de943555e49fbb1b657cbe3c5b2c703ae507d) | ||
- [Refactor] `parse`: avoid shadowing a function arg [`1d58679`](https://github.com/ljharb/shell-quote/commit/1d5867907ecbf553556fe6ad790b6d6658aedba3) | ||
## [v1.8.0](https://github.com/ljharb/shell-quote/compare/v1.7.4...v1.8.0) - 2023-01-30 | ||
@@ -10,0 +27,0 @@ |
{ | ||
"name": "shell-quote", | ||
"description": "quote and parse shell commands", | ||
"version": "1.8.0", | ||
"version": "1.8.1", | ||
"author": { | ||
@@ -6,0 +6,0 @@ "name": "James Halliday", |
140
parse.js
@@ -6,22 +6,77 @@ 'use strict'; | ||
var CONTROL = '(?:' + [ | ||
'\\|\\|', '\\&\\&', ';;', '\\|\\&', '\\<\\(', '\\<\\<\\<', '>>', '>\\&', '<\\&', '[&;()|<>]' | ||
'\\|\\|', | ||
'\\&\\&', | ||
';;', | ||
'\\|\\&', | ||
'\\<\\(', | ||
'\\<\\<\\<', | ||
'>>', | ||
'>\\&', | ||
'<\\&', | ||
'[&;()|<>]' | ||
].join('|') + ')'; | ||
var controlRE = new RegExp('^' + CONTROL + '$'); | ||
var META = '|&;()<> \\t'; | ||
var BAREWORD = '(\\\\[\'"' + META + ']|[^\\s\'"' + META + '])+'; | ||
var SINGLE_QUOTE = '"((\\\\"|[^"])*?)"'; | ||
var DOUBLE_QUOTE = '\'((\\\\\'|[^\'])*?)\''; | ||
var hash = /^#$/; | ||
var SQ = "'"; | ||
var DQ = '"'; | ||
var DS = '$'; | ||
var TOKEN = ''; | ||
var mult = 0x100000000; // Math.pow(16, 8); | ||
for (var i = 0; i < 4; i++) { | ||
TOKEN += (Math.pow(16, 8) * Math.random()).toString(16); | ||
TOKEN += (mult * Math.random()).toString(16); | ||
} | ||
var startsWithToken = new RegExp('^' + TOKEN); | ||
function parseInternal(s, env, opts) { | ||
function matchAll(s, r) { | ||
var origIndex = r.lastIndex; | ||
var matches = []; | ||
var matchObj; | ||
while ((matchObj = r.exec(s))) { | ||
matches.push(matchObj); | ||
if (r.lastIndex === matchObj.index) { | ||
r.lastIndex += 1; | ||
} | ||
} | ||
r.lastIndex = origIndex; | ||
return matches; | ||
} | ||
function getVar(env, pre, key) { | ||
var r = typeof env === 'function' ? env(key) : env[key]; | ||
if (typeof r === 'undefined' && key != '') { | ||
r = ''; | ||
} else if (typeof r === 'undefined') { | ||
r = '$'; | ||
} | ||
if (typeof r === 'object') { | ||
return pre + TOKEN + JSON.stringify(r) + TOKEN; | ||
} | ||
return pre + r; | ||
} | ||
function parseInternal(string, env, opts) { | ||
if (!opts) { | ||
opts = {}; | ||
} | ||
var BS = opts.escape || '\\'; | ||
var BAREWORD = '(\\' + BS + '[\'"' + META + ']|[^\\s\'"' + META + '])+'; | ||
var chunker = new RegExp([ | ||
'(' + CONTROL + ')', // control chars | ||
'(' + BAREWORD + '|' + SINGLE_QUOTE + '|' + DOUBLE_QUOTE + ')*' | ||
'(' + BAREWORD + '|' + SINGLE_QUOTE + '|' + DOUBLE_QUOTE + ')+' | ||
].join('|'), 'g'); | ||
var match = s.match(chunker).filter(Boolean); | ||
if (!match) { | ||
var matches = matchAll(string, chunker); | ||
if (matches.length === 0) { | ||
return []; | ||
@@ -32,27 +87,11 @@ } | ||
} | ||
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 matches.map(function (match) { | ||
var s = match[0]; | ||
if (!s || commented) { | ||
return void undefined; | ||
} | ||
if (RegExp('^' + CONTROL + '$').test(s)) { | ||
if (controlRE.test(s)) { | ||
return { op: s }; | ||
@@ -72,6 +111,2 @@ } | ||
// "allonetoken") | ||
var SQ = "'"; | ||
var DQ = '"'; | ||
var DS = '$'; | ||
var BS = opts.escape || '\\'; | ||
var quote = false; | ||
@@ -87,28 +122,30 @@ var esc = false; | ||
var varname; | ||
// debugger | ||
if (s.charAt(i) === '{') { | ||
var char = s.charAt(i); | ||
if (char === '{') { | ||
i += 1; | ||
if (s.charAt(i) === '}') { | ||
throw new Error('Bad substitution: ' + s.substr(i - 2, 3)); | ||
throw new Error('Bad substitution: ' + s.slice(i - 2, i + 1)); | ||
} | ||
varend = s.indexOf('}', i); | ||
if (varend < 0) { | ||
throw new Error('Bad substitution: ' + s.substr(i)); | ||
throw new Error('Bad substitution: ' + s.slice(i)); | ||
} | ||
varname = s.substr(i, varend - i); | ||
varname = s.slice(i, varend); | ||
i = varend; | ||
} else if ((/[*@#?$!_-]/).test(s.charAt(i))) { | ||
varname = s.charAt(i); | ||
} else if ((/[*@#?$!_-]/).test(char)) { | ||
varname = char; | ||
i += 1; | ||
} else { | ||
varend = s.substr(i).match(/[^\w\d_]/); | ||
var slicedFromI = s.slice(i); | ||
varend = slicedFromI.match(/[^\w\d_]/); | ||
if (!varend) { | ||
varname = s.substr(i); | ||
varname = slicedFromI; | ||
i = s.length; | ||
} else { | ||
varname = s.substr(i, varend.index); | ||
varname = slicedFromI.slice(0, varend.index); | ||
i += varend.index - 1; | ||
} | ||
} | ||
return getVar(null, '', varname); | ||
return getVar(env, '', varname); | ||
} | ||
@@ -144,10 +181,11 @@ | ||
quote = c; | ||
} else if (RegExp('^' + CONTROL + '$').test(c)) { | ||
} else if (controlRE.test(c)) { | ||
return { op: s }; | ||
} else if ((/^#$/).test(c)) { | ||
} else if (hash.test(c)) { | ||
commented = true; | ||
var commentObj = { comment: string.slice(match.index + i + 1) }; | ||
if (out.length) { | ||
return [out, { comment: s.slice(i + 1) + match.slice(j + 1).join(' ') }]; | ||
return [out, commentObj]; | ||
} | ||
return [{ comment: s.slice(i + 1) + match.slice(j + 1).join(' ') }]; | ||
return [commentObj]; | ||
} else if (c === BS) { | ||
@@ -167,7 +205,5 @@ esc = true; | ||
return out; | ||
}).reduce(function (prev, arg) { // finalize parsed aruments | ||
if (arg === undefined) { | ||
return prev; | ||
} | ||
return prev.concat(arg); | ||
}).reduce(function (prev, arg) { // finalize parsed arguments | ||
// TODO: replace this whole reduce with a concat | ||
return typeof arg === 'undefined' ? prev : prev.concat(arg); | ||
}, []); | ||
@@ -190,3 +226,3 @@ } | ||
return acc.concat(xs.filter(Boolean).map(function (x) { | ||
if (RegExp('^' + TOKEN).test(x)) { | ||
if (startsWithToken.test(x)) { | ||
return JSON.parse(x.split(TOKEN)[1]); | ||
@@ -193,0 +229,0 @@ } |
@@ -60,3 +60,3 @@ # shell-quote <sup>[![Version Badge][npm-version-svg]][package-url]</sup> | ||
var parse = require('shell-quote/parse'); | ||
var xs = parse('beep --boop="$PWD"', { PWD: '/home/robot' }, { escape: '^' }); | ||
var xs = parse('beep ^--boop="$PWD"', { PWD: '/home/robot' }, { escape: '^' }); | ||
console.dir(xs); | ||
@@ -68,3 +68,3 @@ ``` | ||
``` | ||
[ 'beep', '--boop=/home/robot' ] | ||
[ 'beep --boop=/home/robot' ] | ||
``` | ||
@@ -71,0 +71,0 @@ |
@@ -9,9 +9,9 @@ 'use strict'; | ||
t.same(parse('beep #boop'), ['beep', { comment: 'boop' }]); | ||
t.same(parse('beep # boop'), ['beep', { comment: 'boop' }]); | ||
t.same(parse('beep # > boop'), ['beep', { comment: '> boop' }]); | ||
t.same(parse('beep # "> boop"'), ['beep', { comment: '"> boop"' }]); | ||
t.same(parse('beep # boop'), ['beep', { comment: ' boop' }]); | ||
t.same(parse('beep # > boop'), ['beep', { comment: ' > boop' }]); | ||
t.same(parse('beep # "> boop"'), ['beep', { comment: ' "> boop"' }]); | ||
t.same(parse('beep "#"'), ['beep', '#']); | ||
t.same(parse('beep #"#"#'), ['beep', { comment: '"#"#' }]); | ||
t.same(parse('beep > boop # > foo'), ['beep', { op: '>' }, 'boop', { comment: '> foo' }]); | ||
t.same(parse('beep > boop # > foo'), ['beep', { op: '>' }, 'boop', { comment: ' > foo' }]); | ||
t.end(); | ||
}); |
@@ -7,2 +7,15 @@ 'use strict'; | ||
test('parse shell commands', function (t) { | ||
t.same(parse(''), [], 'parses an empty string'); | ||
t['throws']( | ||
function () { parse('${}'); }, | ||
Error, | ||
'empty substitution throws' | ||
); | ||
t['throws']( | ||
function () { parse('${'); }, | ||
Error, | ||
'incomplete substitution throws' | ||
); | ||
t.same(parse('a \'b\' "c"'), ['a', 'b', 'c']); | ||
@@ -24,4 +37,8 @@ t.same( | ||
t.same(parse("x bl^'a^'h'", {}, { escape: '^' }), ['x', "bl'a'h"]); | ||
t.same(parse('abcH def', {}, { escape: 'H' }), ['abc def']); | ||
t.deepEqual(parse('# abc def ghi'), [{ comment: ' abc def ghi' }], 'start-of-line comment content is unparsed'); | ||
t.deepEqual(parse('xyz # abc def ghi'), ['xyz', { comment: ' abc def ghi' }], 'comment content is unparsed'); | ||
t.end(); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
44964
500