shell-quote
Advanced tools
+5
-6
| { | ||
| "name": "shell-quote", | ||
| "description": "quote and parse shell commands", | ||
| "version": "1.8.3", | ||
| "version": "1.8.4", | ||
| "author": { | ||
@@ -15,10 +15,9 @@ "name": "James Halliday", | ||
| "devDependencies": { | ||
| "@ljharb/eslint-config": "^21.1.1", | ||
| "auto-changelog": "^2.5.0", | ||
| "encoding": "^0.1.13", | ||
| "eslint": "=8.8.0", | ||
| "@ljharb/eslint-config": "^22.2.3", | ||
| "auto-changelog": "^2.5.1", | ||
| "eslint": "^8.57.1", | ||
| "evalmd": "^0.0.19", | ||
| "in-publish": "^2.0.1", | ||
| "jackspeak": "=2.1.1", | ||
| "npmignore": "^0.3.1", | ||
| "npmignore": "^0.3.5", | ||
| "nyc": "^10.3.2", | ||
@@ -25,0 +24,0 @@ "safe-publish-latest": "^2.0.0", |
+43
-1
| 'use strict'; | ||
| var OPS = [ | ||
| '||', | ||
| '&&', | ||
| ';;', | ||
| '|&', | ||
| '<(', | ||
| '<<<', | ||
| '>>', | ||
| '>&', | ||
| '<&', | ||
| '&', | ||
| ';', | ||
| '(', | ||
| ')', | ||
| '|', | ||
| '<', | ||
| '>' | ||
| ]; | ||
| var LINE_TERMINATORS = /[\n\r\u2028\u2029]/; | ||
| var GLOB_SHELL_SPECIAL = /[\s#!"$&'():;<=>@\\^`|]/g; | ||
| module.exports = function quote(xs) { | ||
@@ -9,3 +30,24 @@ return xs.map(function (s) { | ||
| if (s && typeof s === 'object') { | ||
| return s.op.replace(/(.)/g, '\\$1'); | ||
| if (s.op === 'glob') { | ||
| if (typeof s.pattern !== 'string') { | ||
| throw new TypeError('glob token requires a string `pattern`'); | ||
| } | ||
| if (LINE_TERMINATORS.test(s.pattern)) { | ||
| throw new TypeError('glob `pattern` must not contain line terminators'); | ||
| } | ||
| return s.pattern.replace(GLOB_SHELL_SPECIAL, '\\$&'); | ||
| } | ||
| if (typeof s.op === 'string') { | ||
| if (OPS.indexOf(s.op) < 0) { | ||
| throw new TypeError('invalid `op` value: ' + JSON.stringify(s.op)); | ||
| } | ||
| return s.op.replace(/[\s\S]/g, '\\$&'); | ||
| } | ||
| if (typeof s.comment === 'string') { | ||
| if (LINE_TERMINATORS.test(s.comment)) { | ||
| throw new TypeError('`comment` must not contain line terminators'); | ||
| } | ||
| return '#' + s.comment; | ||
| } | ||
| throw new TypeError('unrecognized object token shape'); | ||
| } | ||
@@ -12,0 +54,0 @@ if ((/["\s\\]/).test(s) && !(/'/).test(s)) { |
+8
-1
@@ -110,2 +110,9 @@ # shell-quote <sup>[![Version Badge][npm-version-svg]][package-url]</sup> | ||
| Each entry of `args` may be a string, or one of the object shapes that | ||
| `parse` emits: `{ op }` (where `op` is one of the control operators | ||
| `||`, `&&`, `;;`, `|&`, `<(`, `<<<`, `>>`, `>&`, `<&`, `&`, `;`, `(`, | ||
| `)`, `|`, `<`, `>`), `{ op: 'glob', pattern }`, or `{ comment }`. Any | ||
| other object shape, an unrecognized `op`, or a `pattern`/`comment` | ||
| containing line terminators throws a `TypeError`. | ||
| ## parse(cmd, env={}) | ||
@@ -161,3 +168,3 @@ | ||
| [codecov-url]: https://app.codecov.io/gh/ljharb/shell-quote/ | ||
| [actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/ljharb/shell-quote | ||
| [actions-image]: https://img.shields.io/github/check-runs/ljharb/shell-quote/main | ||
| [actions-url]: https://github.com/ljharb/shell-quote/actions |
+6
-0
@@ -43,3 +43,9 @@ 'use strict'; | ||
| t.same( | ||
| parse('2;b', {}, { escape: 'd' }), | ||
| [{ op: '2;b' }], | ||
| 'control char in unquoted context mid-token with regex-special escape returns op' | ||
| ); | ||
| t.end(); | ||
| }); |
+56
-0
@@ -61,1 +61,57 @@ 'use strict'; | ||
| }); | ||
| test('quote ops: allowlist', function (t) { | ||
| var ops = ['||', '&&', ';;', '|&', '<(', '<<<', '>>', '>&', '<&', '&', ';', '(', ')', '|', '<', '>']; | ||
| for (var i = 0; i < ops.length; i++) { | ||
| var op = ops[i]; | ||
| var expected = ''; | ||
| for (var j = 0; j < op.length; j++) { expected += '\\' + op.charAt(j); } | ||
| t.equal(quote([{ op: op }]), expected, 'op ' + op); | ||
| } | ||
| t.end(); | ||
| }); | ||
| test('quote ops: rejects line terminators (GHSA-w7jw-789q-3m8p)', function (t) { | ||
| t['throws'](function () { quote([{ op: ';\nid' }]); }, TypeError, 'newline in op'); | ||
| t['throws'](function () { quote([{ op: ';\rid' }]); }, TypeError, 'carriage return in op'); | ||
| t['throws'](function () { quote([{ op: ';\u2028id' }]); }, TypeError, 'U+2028 in op'); | ||
| t['throws'](function () { quote([{ op: ';\u2029id' }]); }, TypeError, 'U+2029 in op'); | ||
| t.end(); | ||
| }); | ||
| test('quote ops: rejects non-allowlisted values', function (t) { | ||
| t['throws'](function () { quote([{ op: '' }]); }, TypeError, 'empty op'); | ||
| t['throws'](function () { quote([{ op: 'foo' }]); }, TypeError, 'arbitrary string'); | ||
| t['throws'](function () { quote([{ op: '|||' }]); }, TypeError, 'near-miss'); | ||
| t['throws'](function () { quote([{ op: 42 }]); }, TypeError, 'non-string op'); | ||
| t.end(); | ||
| }); | ||
| test('quote glob pattern', function (t) { | ||
| t.equal(quote([{ op: 'glob', pattern: 'test/*.test.js' }]), 'test/*.test.js'); | ||
| t.equal(quote([{ op: 'glob', pattern: '?ab' }]), '?ab'); | ||
| t.equal(quote([{ op: 'glob', pattern: '[ab]c' }]), '[ab]c'); | ||
| t.equal(quote([{ op: 'glob', pattern: '{a,b}' }]), '{a,b}'); | ||
| t.equal(quote([{ op: 'glob', pattern: 'my dir/*.txt' }]), 'my\\ dir/*.txt'); | ||
| t.equal(quote([{ op: 'glob', pattern: 'a$b' }]), 'a\\$b'); | ||
| t['throws'](function () { quote([{ op: 'glob' }]); }, TypeError, 'missing pattern'); | ||
| t['throws'](function () { quote([{ op: 'glob', pattern: 'a\nb' }]); }, TypeError, 'newline in pattern'); | ||
| t['throws'](function () { quote([{ op: 'glob', pattern: 'a\u2028b' }]); }, TypeError, 'U+2028 in pattern'); | ||
| t.end(); | ||
| }); | ||
| test('quote comment', function (t) { | ||
| t.equal(quote(['echo', 'hi', { comment: ' a comment' }]), 'echo hi # a comment'); | ||
| t.equal(quote([{ comment: '' }]), '#'); | ||
| t['throws'](function () { quote([{ comment: 'a\nb' }]); }, TypeError, 'newline in comment'); | ||
| t['throws'](function () { quote([{ comment: 'a\rb' }]); }, TypeError, 'CR in comment'); | ||
| t['throws'](function () { quote([{ comment: 'a\u2028b' }]); }, TypeError, 'U+2028 in comment'); | ||
| t.end(); | ||
| }); | ||
| test('quote rejects unrecognized object shapes', function (t) { | ||
| t['throws'](function () { quote([{}]); }, TypeError, 'empty object'); | ||
| t['throws'](function () { quote([{ foo: 'bar' }]); }, TypeError, 'unknown key'); | ||
| t['throws'](function () { quote([{ op: null }]); }, TypeError, 'null op'); | ||
| t.end(); | ||
| }); |
-3
| #!/usr/bin/env python3 | ||
| import sys | ||
| print(sys.argv[1]) |
27884
17.44%10
-9.09%592
18.64%169
4.32%17
-5.56%