shell-quote
Advanced tools
Comparing version 1.7.4 to 1.8.0
@@ -8,2 +8,14 @@ # Changelog | ||
## [v1.8.0](https://github.com/ljharb/shell-quote/compare/v1.7.4...v1.8.0) - 2023-01-30 | ||
### Commits | ||
- [New] extract `parse` and `quote` to their own deep imports [`553fdfc`](https://github.com/ljharb/shell-quote/commit/553fdfc32cc41b4c2f77e061b6957703958ca575) | ||
- [Tests] add `nyc` coverage [`fd7ddcd`](https://github.com/ljharb/shell-quote/commit/fd7ddcdd84bfef064c6d9a06b055a95531b26897) | ||
- [New] Add support for here strings (`<<<`) [`9802fb3`](https://github.com/ljharb/shell-quote/commit/9802fb37c7946e18c672b81122520dc296bde271) | ||
- [New] `parse`: Add syntax support for duplicating input file descriptors [`216b198`](https://github.com/ljharb/shell-quote/commit/216b19894f76b14d164c4c5a68f05a51b06336c4) | ||
- [Dev Deps] update `@ljharb/eslint-config`, `aud`, `tape` [`85f8e31`](https://github.com/ljharb/shell-quote/commit/85f8e31dd80e1dde63d58204b653e497a53857e6) | ||
- [Tests] add `evalmd` [`c5549fc`](https://github.com/ljharb/shell-quote/commit/c5549fcd82d70046bdc2b1c34184ae9f9d0191f9) | ||
- [actions] update checkout action [`62e9b49`](https://github.com/ljharb/shell-quote/commit/62e9b4958cfa2f9009b7069076612fe33528c1fb) | ||
## [v1.7.4](https://github.com/ljharb/shell-quote/compare/1.7.3...v1.7.4) - 2022-10-12 | ||
@@ -10,0 +22,0 @@ |
203
index.js
'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'); |
{ | ||
"name": "shell-quote", | ||
"description": "quote and parse shell commands", | ||
"version": "1.7.4", | ||
"version": "1.8.0", | ||
"author": { | ||
@@ -15,10 +15,12 @@ "name": "James Halliday", | ||
"devDependencies": { | ||
"@ljharb/eslint-config": "^21.0.0", | ||
"aud": "^2.0.1", | ||
"@ljharb/eslint-config": "^21.0.1", | ||
"aud": "^2.0.2", | ||
"auto-changelog": "^2.4.0", | ||
"eslint": "=8.8.0", | ||
"evalmd": "^0.0.19", | ||
"in-publish": "^2.0.1", | ||
"npmignore": "^0.3.0", | ||
"nyc": "^10.3.2", | ||
"safe-publish-latest": "^2.0.0", | ||
"tape": "^5.6.1" | ||
"tape": "^5.6.3" | ||
}, | ||
@@ -42,5 +44,6 @@ "homepage": "https://github.com/ljharb/shell-quote", | ||
"prepublishOnly": "safe-publish-latest", | ||
"prelint": "evalmd README.md", | ||
"lint": "eslint --ext=js,mjs .", | ||
"pretest": "npm run lint", | ||
"tests-only": "tape 'test/**/*.js'", | ||
"tests-only": "nyc tape 'test/**/*.js'", | ||
"test": "npm run tests-only", | ||
@@ -47,0 +50,0 @@ "posttest": "aud --production", |
@@ -17,3 +17,3 @@ # shell-quote <sup>[![Version Badge][npm-version-svg]][package-url]</sup> | ||
``` js | ||
var quote = require('shell-quote').quote; | ||
var quote = require('shell-quote/quote'); | ||
var s = quote([ 'a', 'b c d', '$f', '"g"' ]); | ||
@@ -32,3 +32,3 @@ console.log(s); | ||
``` js | ||
var parse = require('shell-quote').parse; | ||
var parse = require('shell-quote/parse'); | ||
var xs = parse('a "b c" \\$def \'it\\\'s great\''); | ||
@@ -47,3 +47,3 @@ console.dir(xs); | ||
``` js | ||
var parse = require('shell-quote').parse; | ||
var parse = require('shell-quote/parse'); | ||
var xs = parse('beep --boop="$PWD"', { PWD: '/home/robot' }); | ||
@@ -62,3 +62,3 @@ console.dir(xs); | ||
``` js | ||
var parse = require('shell-quote').parse; | ||
var parse = require('shell-quote/parse'); | ||
var xs = parse('beep --boop="$PWD"', { PWD: '/home/robot' }, { escape: '^' }); | ||
@@ -77,3 +77,3 @@ console.dir(xs); | ||
``` js | ||
var parse = require('shell-quote').parse; | ||
var parse = require('shell-quote/parse'); | ||
var xs = parse('beep || boop > /byte'); | ||
@@ -92,3 +92,3 @@ console.dir(xs); | ||
``` js | ||
var parse = require('shell-quote').parse; | ||
var parse = require('shell-quote/parse'); | ||
var xs = parse('beep > boop # > kaboom'); | ||
@@ -107,4 +107,4 @@ console.dir(xs); | ||
``` js | ||
var quote = require('shell-quote').quote; | ||
var parse = require('shell-quote').parse; | ||
var quote = require('shell-quote/quote'); | ||
var parse = require('shell-quote/parse'); | ||
``` | ||
@@ -111,0 +111,0 @@ |
@@ -26,2 +26,11 @@ 'use strict'; | ||
test('expand environment variables within here-strings', function (t) { | ||
t.same(parse('a <<< $x', { x: 'Joe' }), ['a', { op: '<<<' }, 'Joe']); | ||
t.same(parse('a <<< ${x}', { x: 'Joe' }), ['a', { op: '<<<' }, 'Joe']); | ||
t.same(parse('a <<< "$x"', { x: 'Joe' }), ['a', { op: '<<<' }, 'Joe']); | ||
t.same(parse('a <<< "${x}"', { x: 'Joe' }), ['a', { op: '<<<' }, 'Joe']); | ||
t.end(); | ||
}); | ||
test('environment variables with metacharacters', function (t) { | ||
@@ -28,0 +37,0 @@ t.same(parse('a $XYZ c', { XYZ: '"b"' }), ['a', '"b"', 'c']); |
@@ -72,2 +72,24 @@ 'use strict'; | ||
test('duplicating input file descriptors', function (t) { | ||
// duplicating stdout to file descriptor 3 | ||
t.same(parse('beep 3<&1'), ['beep', '3', { op: '<&' }, '1']); | ||
// duplicating stdout to file descriptor 0, i.e. stdin | ||
t.same(parse('beep <&1'), ['beep', { op: '<&' }, '1']); | ||
// closes stdin | ||
t.same(parse('beep <&-'), ['beep', { op: '<&' }, '-']); | ||
t.end(); | ||
}); | ||
test('here strings', function (t) { | ||
t.same(parse('cat <<< "hello world"'), ['cat', { op: '<<<' }, 'hello world']); | ||
t.same(parse('cat <<< hello'), ['cat', { op: '<<<' }, 'hello']); | ||
t.same(parse('cat<<<hello'), ['cat', { op: '<<<' }, 'hello']); | ||
t.same(parse('cat<<<"hello world"'), ['cat', { op: '<<<' }, 'hello world']); | ||
t.end(); | ||
}); | ||
test('glob patterns', function (t) { | ||
@@ -74,0 +96,0 @@ t.same( |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
42592
22
459
0
10