Add web front-end and fix API issues
This commit is contained in:
1 parent
45ca4de0ad
commit
a7524412f0
2571 files changed
+86998
-5
No files matched your search
+12
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [ljharb]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: npm/shell-quote
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"all": true,
|
||||
"check-coverage": false,
|
||||
"reporter": ["text-summary", "text", "html", "json"],
|
||||
"lines": 86,
|
||||
"statements": 85.93,
|
||||
"functions": 82.43,
|
||||
"branches": 76.06,
|
||||
"exclude": [
|
||||
"coverage",
|
||||
"example",
|
||||
"test"
|
||||
]
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2013 James Halliday (mail@substack.net)
|
||||
|
||||
Permission is hereby granted, free of charge,
|
||||
to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify,
|
||||
merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
# shell-quote <sup>[![Version Badge][npm-version-svg]][package-url]</sup>
|
||||
|
||||
[![github actions][actions-image]][actions-url]
|
||||
[![coverage][codecov-image]][codecov-url]
|
||||
[![License][license-image]][license-url]
|
||||
[![Downloads][downloads-image]][downloads-url]
|
||||
|
||||
[![npm badge][npm-badge-png]][package-url]
|
||||
|
||||
Parse and quote shell commands.
|
||||
|
||||
# example
|
||||
|
||||
## quote
|
||||
|
||||
``` js
|
||||
var quote = require('shell-quote/quote');
|
||||
var s = quote([ 'a', 'b c d', '$f', '"g"' ]);
|
||||
console.log(s);
|
||||
```
|
||||
|
||||
output
|
||||
|
||||
```
|
||||
a 'b c d' \$f '"g"'
|
||||
```
|
||||
|
||||
## parse
|
||||
|
||||
``` js
|
||||
var parse = require('shell-quote/parse');
|
||||
var xs = parse('a "b c" \\$def \'it\\\'s great\'');
|
||||
console.dir(xs);
|
||||
```
|
||||
|
||||
output
|
||||
|
||||
```
|
||||
[ 'a', 'b c', '\\$def', 'it\'s great' ]
|
||||
```
|
||||
|
||||
## parse with an environment variable
|
||||
|
||||
``` js
|
||||
var parse = require('shell-quote/parse');
|
||||
var xs = parse('beep --boop="$PWD"', { PWD: '/home/robot' });
|
||||
console.dir(xs);
|
||||
```
|
||||
|
||||
output
|
||||
|
||||
```
|
||||
[ 'beep', '--boop=/home/robot' ]
|
||||
```
|
||||
|
||||
## parse with custom escape character
|
||||
|
||||
``` js
|
||||
var parse = require('shell-quote/parse');
|
||||
var xs = parse('beep ^--boop="$PWD"', { PWD: '/home/robot' }, { escape: '^' });
|
||||
console.dir(xs);
|
||||
```
|
||||
|
||||
output
|
||||
|
||||
```
|
||||
[ 'beep --boop=/home/robot' ]
|
||||
```
|
||||
|
||||
## parsing shell operators
|
||||
|
||||
``` js
|
||||
var parse = require('shell-quote/parse');
|
||||
var xs = parse('beep || boop > /byte');
|
||||
console.dir(xs);
|
||||
```
|
||||
|
||||
output:
|
||||
|
||||
```
|
||||
[ 'beep', { op: '||' }, 'boop', { op: '>' }, '/byte' ]
|
||||
```
|
||||
|
||||
## parsing shell comment
|
||||
|
||||
``` js
|
||||
var parse = require('shell-quote/parse');
|
||||
var xs = parse('beep > boop # > kaboom');
|
||||
console.dir(xs);
|
||||
```
|
||||
|
||||
output:
|
||||
|
||||
```
|
||||
[ 'beep', { op: '>' }, 'boop', { comment: '> kaboom' } ]
|
||||
```
|
||||
|
||||
# methods
|
||||
|
||||
``` js
|
||||
var quote = require('shell-quote/quote');
|
||||
var parse = require('shell-quote/parse');
|
||||
```
|
||||
|
||||
## quote(args)
|
||||
|
||||
Return a quoted string for the array `args` suitable for using in shell
|
||||
commands.
|
||||
|
||||
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={})
|
||||
|
||||
Return an array of arguments from the quoted string `cmd`.
|
||||
|
||||
Interpolate embedded bash-style `$VARNAME` and `${VARNAME}` variables with
|
||||
the `env` object which like bash will replace undefined variables with `""`.
|
||||
|
||||
`env` is usually an object but it can also be a function to perform lookups.
|
||||
When `env(key)` returns a string, its result will be output just like `env[key]`
|
||||
would. When `env(key)` returns an object, it will be inserted into the result
|
||||
array like the operator objects.
|
||||
|
||||
When a bash operator is encountered, the element in the array with be an object
|
||||
with an `"op"` key set to the operator string. For example:
|
||||
|
||||
```
|
||||
'beep || boop > /byte'
|
||||
```
|
||||
|
||||
parses as:
|
||||
|
||||
```
|
||||
[ 'beep', { op: '||' }, 'boop', { op: '>' }, '/byte' ]
|
||||
```
|
||||
|
||||
# install
|
||||
|
||||
With [npm](http://npmjs.org) do:
|
||||
|
||||
```
|
||||
npm install shell-quote
|
||||
```
|
||||
|
||||
# license
|
||||
|
||||
MIT
|
||||
|
||||
[package-url]: https://npmjs.org/package/shell-quote
|
||||
[npm-version-svg]: https://versionbadg.es/ljharb/shell-quote.svg
|
||||
[deps-svg]: https://david-dm.org/ljharb/shell-quote.svg
|
||||
[deps-url]: https://david-dm.org/ljharb/shell-quote
|
||||
[dev-deps-svg]: https://david-dm.org/ljharb/shell-quote/dev-status.svg
|
||||
[dev-deps-url]: https://david-dm.org/ljharb/shell-quote#info=devDependencies
|
||||
[npm-badge-png]: https://nodei.co/npm/shell-quote.png?downloads=true&stars=true
|
||||
[license-image]: https://img.shields.io/npm/l/shell-quote.svg
|
||||
[license-url]: LICENSE
|
||||
[downloads-image]: https://img.shields.io/npm/dm/shell-quote.svg
|
||||
[downloads-url]: https://npm-stat.com/charts.html?package=shell-quote
|
||||
[codecov-image]: https://codecov.io/gh/ljharb/shell-quote/branch/main/graphs/badge.svg
|
||||
[codecov-url]: https://app.codecov.io/gh/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
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import ljharb from '@ljharb/eslint-config/flat';
|
||||
|
||||
export default [
|
||||
...ljharb,
|
||||
{
|
||||
rules: {
|
||||
'array-bracket-newline': 'off',
|
||||
complexity: 'off',
|
||||
eqeqeq: 'warn',
|
||||
'func-style': ['error', 'declaration'],
|
||||
'max-depth': 'off',
|
||||
'max-lines-per-function': 'off',
|
||||
'max-statements': 'off',
|
||||
'multiline-comment-style': 'off',
|
||||
'no-extra-parens': 'off',
|
||||
'no-lonely-if': 'warn',
|
||||
'no-negated-condition': 'warn',
|
||||
'no-param-reassign': 'warn',
|
||||
'no-shadow': 'warn',
|
||||
'no-template-curly-in-string': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['example/**'],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
];
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import quote = require('./quote');
|
||||
import parse = require('./parse');
|
||||
|
||||
export { quote, parse };
|
||||
|
||||
export type ControlOperator = parse.ControlOperator;
|
||||
export type GlobPattern = parse.GlobPattern;
|
||||
export type Comment = parse.Comment;
|
||||
export type ParseEntry = parse.ParseEntry;
|
||||
export type ParseOptions = parse.ParseOptions;
|
||||
|
||||
type Join<T extends readonly string[], D extends string> = T extends readonly []
|
||||
? ''
|
||||
: T extends readonly [infer F extends string]
|
||||
? F
|
||||
: T extends readonly [infer F extends string, ...infer R extends string[]]
|
||||
? `${F}${D}${Join<R, D>}`
|
||||
: string;
|
||||
|
||||
declare global {
|
||||
interface ReadonlyArray<T> {
|
||||
join<This extends readonly string[], D extends string = ','>(this: This, separator?: D): Join<This, D>;
|
||||
}
|
||||
|
||||
interface Array<T> {
|
||||
join<This extends readonly string[], D extends string = ','>(this: This, separator?: D): Join<This, D>;
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
'use strict';
|
||||
|
||||
exports.quote = require('./quote');
|
||||
exports.parse = require('./parse');
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"name": "shell-quote",
|
||||
"description": "quote and parse shell commands",
|
||||
"version": "1.9.0",
|
||||
"author": {
|
||||
"name": "James Halliday",
|
||||
"email": "mail@substack.net",
|
||||
"url": "http://substack.net"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
},
|
||||
"bugs": "https://github.com/ljharb/shell-quote/issues",
|
||||
"homepage": "https://github.com/ljharb/shell-quote",
|
||||
"keywords": [
|
||||
"command",
|
||||
"parse",
|
||||
"quote",
|
||||
"shell"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/ljharb/shell-quote.git"
|
||||
},
|
||||
"scripts": {
|
||||
"prepack": "npmignore --auto --commentLines=autogenerated",
|
||||
"prepublish": "not-in-publish || npm run prepublishOnly",
|
||||
"prepublishOnly": "safe-publish-latest",
|
||||
"prelint": "evalmd README.md",
|
||||
"lint": "eslint --ext=js,mjs .",
|
||||
"postlint": "tsc -p . && attw -P",
|
||||
"pretest": "npm run lint",
|
||||
"tests-only": "nyc tape 'test/**/*.js'",
|
||||
"test": "npm run tests-only",
|
||||
"posttest": "npx npm@'>=10.2' audit --production",
|
||||
"version": "auto-changelog && git add CHANGELOG.md",
|
||||
"postversion": "auto-changelog && git add CHANGELOG.md && git commit --no-edit --amend && git tag -f \"v$(node -e \"console.log(require('./package.json').version)\")\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"@arethetypeswrong/cli": "^0.18.2",
|
||||
"@ljharb/eslint-config": "^22.2.3",
|
||||
"@ljharb/tsconfig": "^0.3.2",
|
||||
"auto-changelog": "^2.6.0",
|
||||
"eslint": "^10.5.0",
|
||||
"evalmd": "^0.0.19",
|
||||
"in-publish": "^2.0.1",
|
||||
"jiti": "^0.0.0",
|
||||
"npmignore": "^0.3.5",
|
||||
"nyc": "^10.3.2",
|
||||
"safe-publish-latest": "^2.0.0",
|
||||
"tape": "^5.10.2",
|
||||
"typescript": "next"
|
||||
},
|
||||
"auto-changelog": {
|
||||
"output": "CHANGELOG.md",
|
||||
"template": "keepachangelog",
|
||||
"unreleased": false,
|
||||
"commitLimit": false,
|
||||
"backfillLimit": false,
|
||||
"hideCredit": true,
|
||||
"startingVersion": "1.7.4"
|
||||
},
|
||||
"publishConfig": {
|
||||
"ignore": [
|
||||
".github/workflows",
|
||||
"example",
|
||||
"CHANGELOG.md"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
declare namespace parse {
|
||||
/** A shell control operator. */
|
||||
export interface ControlOperator {
|
||||
op: '||' | '&&' | ';;' | '|&' | '<(' | '<<<' | '>>' | '>&' | '<&' | '&' | ';' | '(' | ')' | '|' | '<' | '>';
|
||||
}
|
||||
|
||||
/** A glob pattern parsed from the shell command. */
|
||||
export interface GlobPattern {
|
||||
op: 'glob';
|
||||
pattern: string;
|
||||
}
|
||||
|
||||
/** A shell comment. */
|
||||
export interface Comment {
|
||||
comment: string;
|
||||
}
|
||||
|
||||
/** A parsed token returned by {@link parse}. */
|
||||
export type ParseEntry = string | ControlOperator | GlobPattern | Comment;
|
||||
|
||||
/** Options for the {@link parse} function. */
|
||||
export interface ParseOptions {
|
||||
/** Custom escape character. Defaults to `\\`. */
|
||||
escape?: string;
|
||||
}
|
||||
|
||||
export type Env =
|
||||
| Record<string, string | undefined>
|
||||
| ((key: string) => string | object | undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a shell command string into an array of tokens.
|
||||
*
|
||||
* @param s - The shell command string to parse.
|
||||
* @param env - Optional environment variables for expansion, either as an object of string values or a lookup function. When the lookup function returns an object, that object is inserted into the result verbatim.
|
||||
* @param opts - Optional parsing options.
|
||||
* @returns An array of parsed tokens, including any objects returned by an `env` lookup function.
|
||||
*/
|
||||
declare function parse<T extends string | object = never>(
|
||||
s: string,
|
||||
env?: parse.Env,
|
||||
opts?: parse.ParseOptions,
|
||||
): (parse.ParseEntry | T)[];
|
||||
|
||||
export = parse;
|
||||
+261
@@ -0,0 +1,261 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @import {
|
||||
* ControlOperator,
|
||||
* Env,
|
||||
* GlobPattern,
|
||||
* ParseEntry,
|
||||
* } from './parse' */
|
||||
|
||||
// '<(' is process substitution operator and
|
||||
// can be parsed the same as control operator
|
||||
var CONTROL = /** @type {const} */ ('(?:') + /** @type {const} */ ([
|
||||
'\\|\\|',
|
||||
'\\&\\&',
|
||||
';;',
|
||||
'\\|\\&',
|
||||
'\\<\\(',
|
||||
'\\<\\<\\<',
|
||||
'>>',
|
||||
'>\\&',
|
||||
'<\\&',
|
||||
'[&;()|<>]'
|
||||
]).join(/** @type {const} */ ('|')) + /** @type {const} */ (')');
|
||||
var controlRE = new RegExp('^' + CONTROL + '$');
|
||||
var META = /** @type {const} */ ('|&;()<> \\t');
|
||||
var SINGLE_QUOTE = /** @type {const} */ ('"((\\\\"|[^"])*?)"');
|
||||
var DOUBLE_QUOTE = /** @type {const} */ ('\'((\\\\\'|[^\'])*?)\'');
|
||||
var hash = /^#$/;
|
||||
|
||||
var SQ = /** @type {const} */ ("'");
|
||||
var DQ = /** @type {const} */ ('"');
|
||||
var DS = /** @type {const} */ ('$');
|
||||
|
||||
var TOKEN = '';
|
||||
var mult = /** @type {const} */ (0x100000000); // Math.pow(16, 8);
|
||||
for (var i = 0; i < 4; i++) {
|
||||
TOKEN += (mult * Math.random()).toString(16);
|
||||
}
|
||||
var startsWithToken = new RegExp('^' + TOKEN);
|
||||
|
||||
/**
|
||||
* @param {string} s
|
||||
* @param {RegExp} r
|
||||
*/
|
||||
function matchAll(s, r) {
|
||||
var origIndex = r.lastIndex;
|
||||
|
||||
var matches = [];
|
||||
var matchObj;
|
||||
|
||||
while ((matchObj = r.exec(s))) {
|
||||
matches[matches.length] = matchObj;
|
||||
if (r.lastIndex === matchObj.index) {
|
||||
r.lastIndex += 1;
|
||||
}
|
||||
}
|
||||
|
||||
r.lastIndex = origIndex;
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Env} env
|
||||
* @param {string} pre
|
||||
* @param {string} key
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} string
|
||||
* @param {Env} [env]
|
||||
* @param {{ escape?: string }} [opts]
|
||||
* @returns {ParseEntry[]}
|
||||
*/
|
||||
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 + ')+'
|
||||
].join('|'), 'g');
|
||||
|
||||
var matches = matchAll(string, chunker);
|
||||
|
||||
if (matches.length === 0) {
|
||||
return [];
|
||||
}
|
||||
if (!env) {
|
||||
env = {};
|
||||
}
|
||||
|
||||
var commented = false;
|
||||
|
||||
return matches.map(function (match) {
|
||||
var s = match[0];
|
||||
if (!s || commented) {
|
||||
return void undefined;
|
||||
}
|
||||
if (controlRE.test(s)) {
|
||||
return /** @type {ControlOperator} */ ({ 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")
|
||||
/** @type {string | boolean} */
|
||||
var quote = false;
|
||||
var esc = false;
|
||||
var out = '';
|
||||
var isGlob = false;
|
||||
/** @type {number} */
|
||||
var i;
|
||||
|
||||
function parseEnvVar() {
|
||||
i += 1;
|
||||
/** @type {number | RegExpMatchArray | null} */
|
||||
var varend;
|
||||
/** @type {string} */
|
||||
var varname;
|
||||
var char = s.charAt(i);
|
||||
|
||||
if (char === '{') {
|
||||
i += 1;
|
||||
if (s.charAt(i) === '}') {
|
||||
throw new Error('Bad substitution: ' + s.slice(i - 2, i + 1));
|
||||
}
|
||||
varend = s.indexOf('}', i);
|
||||
if (varend < 0) {
|
||||
throw new Error('Bad substitution: ' + s.slice(i));
|
||||
}
|
||||
varname = s.slice(i, varend);
|
||||
i = varend;
|
||||
} else if ((/[*@#?$!_-]/).test(char)) {
|
||||
varname = char;
|
||||
i += 1;
|
||||
} else {
|
||||
var slicedFromI = s.slice(i);
|
||||
varend = slicedFromI.match(/[^\w\d_]/);
|
||||
if (!varend) {
|
||||
varname = slicedFromI;
|
||||
i = s.length;
|
||||
} else {
|
||||
varname = slicedFromI.slice(0, varend.index);
|
||||
i += /** @type {number} */ (varend.index) - 1;
|
||||
}
|
||||
}
|
||||
return getVar(/** @type {NonNullable<typeof env>} */ (env), '', 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 (controlRE.test(c)) {
|
||||
return /** @type {ControlOperator} */ ({ op: s });
|
||||
} else if (hash.test(c)) {
|
||||
commented = true;
|
||||
var commentObj = { comment: string.slice(match.index + i + 1) };
|
||||
if (out.length) {
|
||||
return /** @type {const} */ ([out, commentObj]);
|
||||
}
|
||||
return /** @type {const} */ ([commentObj]);
|
||||
} else if (c === BS) {
|
||||
esc = true;
|
||||
} else if (c === DS) {
|
||||
out += parseEnvVar();
|
||||
} else {
|
||||
out += c;
|
||||
}
|
||||
}
|
||||
|
||||
if (isGlob) {
|
||||
return /** @type {GlobPattern} */ ({ op: 'glob', pattern: out });
|
||||
}
|
||||
|
||||
return out;
|
||||
}).reduce(function (prev, arg) { // finalize parsed arguments
|
||||
if (typeof arg === 'undefined') {
|
||||
return prev;
|
||||
}
|
||||
/** @type {ParseEntry[]} */ ([]).concat(arg).forEach(function (entry) {
|
||||
prev[prev.length] = entry;
|
||||
});
|
||||
return prev;
|
||||
}, /** @type {ParseEntry[]} */ ([]));
|
||||
}
|
||||
|
||||
/** @type {import('./parse')} */
|
||||
module.exports = function parse(s, env, opts) {
|
||||
var mapped = parseInternal(s, env, opts);
|
||||
if (typeof env !== 'function') {
|
||||
return mapped;
|
||||
}
|
||||
return mapped.reduce(function (acc, s) {
|
||||
if (typeof s === 'object') {
|
||||
acc[acc.length] = s;
|
||||
return acc;
|
||||
}
|
||||
var xs = s.split(RegExp('(' + TOKEN + '.*?' + TOKEN + ')', 'g'));
|
||||
if (xs.length === 1) {
|
||||
acc[acc.length] = xs[0];
|
||||
return acc;
|
||||
}
|
||||
xs.filter(Boolean).forEach(function (x) {
|
||||
acc[acc.length] = startsWithToken.test(x)
|
||||
? JSON.parse(x.split(TOKEN)[1])
|
||||
: x;
|
||||
});
|
||||
return acc;
|
||||
}, /** @type {ParseEntry[]} */ ([]));
|
||||
};
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import parse = require('./parse');
|
||||
|
||||
/**
|
||||
* Quotes an array of tokens into a shell-safe string.
|
||||
*
|
||||
* Accepts strings and the object shapes that {@link parse} emits. Throws a
|
||||
* `TypeError` for unrecognized object shapes, `op` values outside the
|
||||
* allowlist, or `pattern`/`comment` values containing line terminators.
|
||||
*
|
||||
* @param args - Array of tokens to quote.
|
||||
* @returns A shell-safe quoted string.
|
||||
*/
|
||||
declare function quote(args: readonly parse.ParseEntry[]): string;
|
||||
|
||||
export = quote;
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
'use strict';
|
||||
|
||||
/** @import { ControlOperator } from './parse' */
|
||||
|
||||
/** @type {ControlOperator['op'][]} */
|
||||
var OPS = /** @type {const} */ ([
|
||||
'||',
|
||||
'&&',
|
||||
';;',
|
||||
'|&',
|
||||
'<(',
|
||||
'<<<',
|
||||
'>>',
|
||||
'>&',
|
||||
'<&',
|
||||
'&',
|
||||
';',
|
||||
'(',
|
||||
')',
|
||||
'|',
|
||||
'<',
|
||||
'>'
|
||||
]);
|
||||
var LINE_TERMINATORS = /[\n\r\u2028\u2029]/;
|
||||
var GLOB_SHELL_SPECIAL = /[\s#!"$&'():;<=>@\\^`|]/g;
|
||||
|
||||
/** @type {import('./quote')} */
|
||||
module.exports = function quote(xs) {
|
||||
return xs.map(function (s) {
|
||||
if (s === '') {
|
||||
return /** @type {const} */ ('\'\'');
|
||||
}
|
||||
if (s && typeof s === 'object') {
|
||||
if ('op' in s && 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 ('op' in s && 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 ('comment' in s && 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');
|
||||
}
|
||||
if ((/["\s\\]/).test(s) && !(/'/).test(s)) {
|
||||
return "'" + s.replace(/(['])/g, '\\$1') + "'";
|
||||
}
|
||||
if ((/["'\s]/).test(s)) {
|
||||
return '"' + s.replace(/(["\\$`!])/g, '\\$1') + '"';
|
||||
}
|
||||
return String(s).replace(/([A-Za-z]:)?([#!"$&'()*,:;<=>?@[\\\]^`{|}~])/g, '$1\\$2');
|
||||
}).join(' ');
|
||||
};
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Only the latest major version is supported at any given time.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
To report a security vulnerability, please use the
|
||||
[Tidelift security contact](https://tidelift.com/security).
|
||||
Tidelift will coordinate the fix and disclosure.
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
var test = require('tape');
|
||||
var parse = require('../').parse;
|
||||
|
||||
test('comment', function (t) {
|
||||
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.end();
|
||||
});
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
'use strict';
|
||||
|
||||
var test = require('tape');
|
||||
var parse = require('../').parse;
|
||||
|
||||
test('expand environment variables', function (t) {
|
||||
t.same(parse('a $XYZ c', { XYZ: 'b' }), ['a', 'b', 'c']);
|
||||
t.same(parse('a${XYZ}c', { XYZ: 'b' }), ['abc']);
|
||||
t.same(parse('a${XYZ}c $XYZ', { XYZ: 'b' }), ['abc', 'b']);
|
||||
t.same(parse('"-$X-$Y-"', { X: 'a', Y: 'b' }), ['-a-b-']);
|
||||
t.same(parse("'-$X-$Y-'", { X: 'a', Y: 'b' }), ['-$X-$Y-']);
|
||||
t.same(parse('qrs"$zzz"wxy', { zzz: 'tuv' }), ['qrstuvwxy']);
|
||||
t.same(parse("qrs'$zzz'wxy", { zzz: 'tuv' }), ['qrs$zzzwxy']);
|
||||
t.same(parse('qrs${zzz}wxy'), ['qrswxy']);
|
||||
t.same(parse('qrs$wxy $'), ['qrs', '$']);
|
||||
t.same(parse('grep "xy$"'), ['grep', 'xy$']);
|
||||
t.same(parse('ab$x', { x: 'c' }), ['abc']);
|
||||
t.same(parse('ab\\$x', { x: 'c' }), ['ab$x']);
|
||||
t.same(parse('ab${x}def', { x: 'c' }), ['abcdef']);
|
||||
t.same(parse('ab\\${x}def', { x: 'c' }), ['ab${x}def']);
|
||||
t.same(parse('"ab\\${x}def"', { x: 'c' }), ['ab${x}def']);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
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) {
|
||||
t.same(parse('a $XYZ c', { XYZ: '"b"' }), ['a', '"b"', 'c']);
|
||||
t.same(parse('a $XYZ c', { XYZ: '$X', X: 5 }), ['a', '$X', 'c']);
|
||||
t.same(parse('a"$XYZ"c', { XYZ: "'xyz'" }), ["a'xyz'c"]);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('special shell parameters', function (t) {
|
||||
var chars = '*@#?-$!0_'.split('');
|
||||
t.plan(chars.length);
|
||||
|
||||
chars.forEach(function (c) {
|
||||
var env = {};
|
||||
env[c] = 'xxx';
|
||||
t.same(parse('a $' + c + ' c', env), ['a', 'xxx', 'c']);
|
||||
});
|
||||
});
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
var test = require('tape');
|
||||
var parse = require('../').parse;
|
||||
|
||||
function getEnv() {
|
||||
return 'xxx';
|
||||
}
|
||||
|
||||
function getEnvObj() {
|
||||
return { op: '@@' };
|
||||
}
|
||||
|
||||
test('functional env expansion', function (t) {
|
||||
t.plan(4);
|
||||
|
||||
t.same(parse('a $XYZ c', getEnv), ['a', 'xxx', 'c']);
|
||||
t.same(parse('a $XYZ c', getEnvObj), ['a', { op: '@@' }, 'c']);
|
||||
t.same(parse('a${XYZ}c', getEnvObj), ['a', { op: '@@' }, 'c']);
|
||||
t.same(parse('"a $XYZ c"', getEnvObj), ['a ', { op: '@@' }, ' c']);
|
||||
});
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
'use strict';
|
||||
|
||||
var test = require('tape');
|
||||
var parse = require('../').parse;
|
||||
|
||||
test('single operators', function (t) {
|
||||
t.same(parse('beep | boop'), ['beep', { op: '|' }, 'boop']);
|
||||
t.same(parse('beep|boop'), ['beep', { op: '|' }, 'boop']);
|
||||
t.same(parse('beep \\| boop'), ['beep', '|', 'boop']);
|
||||
t.same(parse('beep "|boop"'), ['beep', '|boop']);
|
||||
|
||||
t.same(parse('echo zing &'), ['echo', 'zing', { op: '&' }]);
|
||||
t.same(parse('echo zing&'), ['echo', 'zing', { op: '&' }]);
|
||||
t.same(parse('echo zing\\&'), ['echo', 'zing&']);
|
||||
t.same(parse('echo "zing\\&"'), ['echo', 'zing\\&']);
|
||||
|
||||
t.same(parse('beep;boop'), ['beep', { op: ';' }, 'boop']);
|
||||
t.same(parse('(beep;boop)'), [
|
||||
{ op: '(' }, 'beep', { op: ';' }, 'boop', { op: ')' }
|
||||
]);
|
||||
|
||||
t.same(parse('beep>boop'), ['beep', { op: '>' }, 'boop']);
|
||||
t.same(parse('beep 2>boop'), ['beep', '2', { op: '>' }, 'boop']);
|
||||
t.same(parse('beep<boop'), ['beep', { op: '<' }, 'boop']);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('double operators', function (t) {
|
||||
t.same(parse('beep || boop'), ['beep', { op: '||' }, 'boop']);
|
||||
t.same(parse('beep||boop'), ['beep', { op: '||' }, 'boop']);
|
||||
t.same(parse('beep ||boop'), ['beep', { op: '||' }, 'boop']);
|
||||
t.same(parse('beep|| boop'), ['beep', { op: '||' }, 'boop']);
|
||||
t.same(parse('beep || boop'), ['beep', { op: '||' }, 'boop']);
|
||||
|
||||
t.same(parse('beep && boop'), ['beep', { op: '&&' }, 'boop']);
|
||||
t.same(
|
||||
parse('beep && boop || byte'),
|
||||
['beep', { op: '&&' }, 'boop', { op: '||' }, 'byte']
|
||||
);
|
||||
t.same(
|
||||
parse('beep&&boop||byte'),
|
||||
['beep', { op: '&&' }, 'boop', { op: '||' }, 'byte']
|
||||
);
|
||||
t.same(
|
||||
parse('beep\\&\\&boop||byte'),
|
||||
['beep&&boop', { op: '||' }, 'byte']
|
||||
);
|
||||
t.same(
|
||||
parse('beep\\&&boop||byte'),
|
||||
['beep&', { op: '&' }, 'boop', { op: '||' }, 'byte']
|
||||
);
|
||||
t.same(
|
||||
parse('beep;;boop|&byte>>blip'),
|
||||
['beep', { op: ';;' }, 'boop', { op: '|&' }, 'byte', { op: '>>' }, 'blip']
|
||||
);
|
||||
|
||||
t.same(parse('beep 2>&1'), ['beep', '2', { op: '>&' }, '1']);
|
||||
|
||||
t.same(
|
||||
parse('beep<(boop)'),
|
||||
['beep', { op: '<(' }, 'boop', { op: ')' }]
|
||||
);
|
||||
t.same(
|
||||
parse('beep<<(boop)'),
|
||||
['beep', { op: '<' }, { op: '<(' }, 'boop', { op: ')' }]
|
||||
);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
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) {
|
||||
t.same(
|
||||
parse('tap test/*.test.js'),
|
||||
['tap', { op: 'glob', pattern: 'test/*.test.js' }]
|
||||
);
|
||||
|
||||
t.same(parse('tap "test/*.test.js"'), ['tap', 'test/*.test.js']);
|
||||
t.end();
|
||||
});
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
'use strict';
|
||||
|
||||
var test = require('tape');
|
||||
var parse = require('../').parse;
|
||||
|
||||
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']);
|
||||
t.same(
|
||||
parse('beep "boop" \'foo bar baz\' "it\'s \\"so\\" groovy"'),
|
||||
['beep', 'boop', 'foo bar baz', 'it\'s "so" groovy']
|
||||
);
|
||||
t.same(parse('a b\\ c d'), ['a', 'b c', 'd']);
|
||||
t.same(parse('\\$beep bo\\`op'), ['$beep', 'bo`op']);
|
||||
t.same(parse('echo "foo = \\"foo\\""'), ['echo', 'foo = "foo"']);
|
||||
t.same(parse(''), []);
|
||||
t.same(parse(' '), []);
|
||||
t.same(parse('\t'), []);
|
||||
t.same(parse('a"b c d"e'), ['ab c de']);
|
||||
t.same(parse('a\\ b"c d"\\ e f'), ['a bc d e', 'f']);
|
||||
t.same(parse('a\\ b"c d"\\ e\'f g\' h'), ['a bc d ef g', 'h']);
|
||||
t.same(parse("x \"bl'a\"'h'"), ['x', "bl'ah"]);
|
||||
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.deepEqual(parse('-x "" -y'), ['-x', '', '-y'], 'empty string is preserved');
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
test('parse stays linear in token count (GHSA-395f-4hp3-45gv)', function (t) {
|
||||
// the old concat-in-reduce finalizer was O(n^2): this many tokens took
|
||||
// ~minutes, so under the unfixed code this test hangs rather than passes
|
||||
var n = 2e5;
|
||||
var input = new Array(n + 1).join('x '); // avoid String#repeat for old engines
|
||||
|
||||
var words = parse(input);
|
||||
t.equal(words.length, n, 'every token is returned');
|
||||
t.equal(words[0], 'x', 'first token is correct');
|
||||
t.equal(words[n - 1], 'x', 'last token is correct');
|
||||
|
||||
var withEnv = parse(input, function () { return 'v'; });
|
||||
t.equal(withEnv.length, n, 'env-function path returns every token');
|
||||
|
||||
t.end();
|
||||
});
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
'use strict';
|
||||
|
||||
var test = require('tape');
|
||||
var quote = require('../').quote;
|
||||
|
||||
test('quote', function (t) {
|
||||
t.equal(quote(['a', 'b', 'c d']), 'a b \'c d\'');
|
||||
t.equal(
|
||||
quote(['a', 'b', "it's a \"neat thing\""]),
|
||||
'a b "it\'s a \\"neat thing\\""'
|
||||
);
|
||||
t.equal(
|
||||
quote(['$', '`', '\'']),
|
||||
'\\$ \\` "\'"'
|
||||
);
|
||||
t.equal(quote([]), '');
|
||||
t.equal(quote(['a\nb']), "'a\nb'");
|
||||
t.equal(quote([' #(){}*|][!']), "' #(){}*|][!'");
|
||||
t.equal(quote(["'#(){}*|][!"]), '"\'#(){}*|][\\!"');
|
||||
t.equal(quote(['X#(){}*|][!']), 'X\\#\\(\\)\\{\\}\\*\\|\\]\\[\\!');
|
||||
t.equal(quote(['a\n#\nb']), "'a\n#\nb'");
|
||||
t.equal(quote(['><;{}']), '\\>\\<\\;\\{\\}');
|
||||
t.equal(quote(['a', 1, true, false]), 'a 1 true false');
|
||||
t.equal(quote(['a', 1, null, undefined]), 'a 1 null undefined');
|
||||
t.equal(quote(['a\\x']), "'a\\x'");
|
||||
t.equal(quote(['a"b']), '\'a"b\'');
|
||||
t.equal(quote(['"a"b"']), '\'"a"b"\'');
|
||||
t.equal(quote(['a\\"b']), '\'a\\"b\'');
|
||||
t.equal(quote(['a\\b']), '\'a\\b\'');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('quote tilde (escapes leading ~ to prevent shell tilde-expansion)', function (t) {
|
||||
t.equal(quote(['~']), '\\~');
|
||||
t.equal(quote(['~/foo']), '\\~/foo');
|
||||
t.equal(quote(['~root']), '\\~root');
|
||||
t.equal(quote(['~root/x']), '\\~root/x');
|
||||
t.equal(quote(['~+']), '\\~+');
|
||||
t.equal(quote(['~-']), '\\~-');
|
||||
t.equal(quote(['a~b']), 'a\\~b');
|
||||
t.equal(quote(['x~']), 'x\\~');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('quote ops', function (t) {
|
||||
t.equal(quote(['a', { op: '|' }, 'b']), 'a \\| b');
|
||||
t.equal(
|
||||
quote(['a', { op: '&&' }, 'b', { op: ';' }, 'c']),
|
||||
'a \\&\\& b \\; c'
|
||||
);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('quote windows paths', { skip: 'breaking change, disabled until 2.x' }, function (t) {
|
||||
var path = 'C:\\projects\\node-shell-quote\\index.js';
|
||||
|
||||
t.equal(quote([path, 'b', 'c d']), 'C:\\projects\\node-shell-quote\\index.js b \'c d\'');
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test("chars for windows paths don't break out", function (t) {
|
||||
var x = '`:\\a\\b';
|
||||
t.equal(quote([x]), "'`:\\a\\b'");
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('empty strings', function (t) {
|
||||
t.equal(quote(['-x', '', 'y']), '-x \'\' y');
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
'use strict';
|
||||
|
||||
var test = require('tape');
|
||||
var parse = require('../').parse;
|
||||
|
||||
test('set env vars', function (t) {
|
||||
t.same(
|
||||
parse('ABC=444 x y z'),
|
||||
['ABC=444', 'x', 'y', 'z']
|
||||
);
|
||||
t.same(
|
||||
parse('ABC=3\\ 4\\ 5 x y z'),
|
||||
['ABC=3 4 5', 'x', 'y', 'z']
|
||||
);
|
||||
t.same(
|
||||
parse('X="7 8 9" printx'),
|
||||
['X=7 8 9', 'printx']
|
||||
);
|
||||
t.same(
|
||||
parse('X="7 8 9"; printx'),
|
||||
['X=7 8 9', { op: ';' }, 'printx']
|
||||
);
|
||||
t.same(
|
||||
parse('X="7 8 9"; printx', function () {
|
||||
t.fail('should not have matched any keys');
|
||||
}),
|
||||
['X=7 8 9', { op: ';' }, 'printx']
|
||||
);
|
||||
|
||||
t.end();
|
||||
});
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "@ljharb/tsconfig",
|
||||
"compilerOptions": {
|
||||
"target": "ES2021"
|
||||
},
|
||||
"exclude": [
|
||||
"coverage",
|
||||
"test"
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user