first commit

This commit is contained in:
Artem Anufrij
2026-10-07 18:56:27 +02:00
commit 2a8a60b725
7768 changed files with 1550851 additions and 0 deletions
+12
View File
@@ -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']
+24
View File
@@ -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.
+246
View File
@@ -0,0 +1,246 @@
# 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 ANSI-C quoting
```js
var parse = require('shell-quote/parse');
var xs = parse("echo $'it\\'s a \\n test'");
console.dir(xs);
```
output
```
[ 'echo', "it's a \n test" ]
```
## 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' ]
```
## parse with unquoted variable splitting
```js
var parse = require('shell-quote/parse');
var xs = parse('a $T', { T: 'c d' }, { splitUnquoted: true });
console.dir(xs);
```
output
```
[ 'a', 'c', 'd' ]
```
## 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`, a `pattern`/`comment`
containing line terminators, or a string containing line terminators
anywhere after a `{ comment }` throws a `TypeError`.
The output is POSIX shell (`sh`/`bash`) quoting.
It is not valid for Windows `cmd.exe` or PowerShell,
whose rules differ and, for `cmd.exe`,
are not solvable in the general case. On Windows,
do not build a shell command string from this output;
instead pass an argument array to a non-shell API such as
`child_process.execFile` or `spawn`
(or the `cross-spawn` package),
which does no shell parsing and needs no quoting.
Use the returned string verbatim as shell input.
It is already a complete, escaped shell word (or words);
do not wrap it in additional quotes or embed it in `eval '...'`.
Re-quoting the output (for example, placing it inside single quotes)
turns its backslash escapes into literal characters and corrupts the value.
## 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 `""`.
By default an expanded variable is a single token even when unquoted.
Pass `{ splitUnquoted: true }` to split an unquoted expansion into multiple tokens the way a shell performs field splitting,
using the default `IFS` (space, tab, newline).
Pass a string to use its characters as the `IFS` instead
(for example `{ splitUnquoted: ':' }`).
A quoted expansion (`"$VAR"`) is never split.
Bash [ANSI-C quoting](https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html)
(`$'...'`) is supported: escape sequences (such as `\n`, `\t`, `\\`, `\'`, `\"`,
`\xHH`, `\uHHHH`, `\UHHHHHHHH`, `\nnn`, and `\cx`) are decoded, an unrecognized
escape keeps its backslash, and the contents are never word-split and never
variable-expanded, so `${VAR}` inside `$'...'` stays literal. As in bash,
`$'...'` is not special inside double or single quotes, and a NUL ends the string.
Since the result is a JavaScript string, `\xHH` and `\nnn` produce the character
with that code (U+0000 to U+00FF), not a raw byte.
Only simple `$VARNAME` and `${VARNAME}` interpolation is supported.
Bash parameter expansion beyond a plain variable name is not evaluated:
forms such as array subscripts (`${arr[i]}`), length (`${#arr[@]}`),
and modifiers (`${var:-default}`, `${var/a/b}`)
are treated as an unknown variable and expand to `""`,
while arithmetic (`$((...))`) and command substitution (`$(...)`)
are not interpreted.
Whitespace inside `${...}` throws a `Bad substitution` error.
`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' ]
```
The here-document operators (`<<` and `<<-`) are emitted like any other operator,
followed by the delimiter word.
The here-document body is not recognized:
a newline is whitespace to `parse`,
so the lines after the command are parsed as ordinary tokens,
subject to quoting, comments, globs, and variable expansion.
# 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
[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
+28
View File
@@ -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
View File
@@ -0,0 +1,4 @@
'use strict';
exports.quote = require('./quote');
exports.parse = require('./parse');
+80
View File
@@ -0,0 +1,80 @@
{
"name": "shell-quote",
"description": "quote and parse shell commands",
"version": "1.12.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.5",
"@ljharb/eslint-config": "^22.2.5",
"@ljharb/tsconfig": "^0.3.2",
"auto-changelog": "^2.7.0",
"eslint": "^10.11.0",
"evalmd": "^0.0.22",
"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",
".nycrc",
"eslint.config.mjs",
"example",
"CHANGELOG.md",
"security.md",
"test"
]
},
"engines": {
"node": ">= 0.4"
}
}
+48
View File
@@ -0,0 +1,48 @@
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;
/** Field-splits an unquoted variable expansion, the way a shell does using `IFS`; quoted expansions are never split. `true` uses the default IFS (space, tab, newline); a string uses its characters as the IFS. An empty string, `false`, or omitting the option disables splitting. Defaults to false. */
splitUnquoted?: boolean | 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;
+382
View File
@@ -0,0 +1,382 @@
'use strict';
/**
* @import {
* ControlOperator,
* Env,
* GlobPattern,
* ParseEntry,
* } from './parse' */
// '<(' and '>(' are process substitution operators and
// can be parsed the same as control operator
var CONTROL = /** @type {const} */ ('(?:') + /** @type {const} */ ([
'\\|\\|',
'\\&(?:\\&|>>?)', // `&&`, `&>`, and `&>>`
';;\\&?', // `;;` and `;;&`
'[;|]\\&', // `;&` and `|&`
'\\<\\(',
'\\<\\<\\<',
'\\<\\<-',
'\\<\\<(?!\\()', // `<<(` stays `<` and `<(`, as zsh reads it; other shells reject it
'>>',
'>[&|(]', // `>&`, `>|`, and `>(`
'<[&>]', // `<&` and `<>`
'[&;()|<>]'
]).join(/** @type {const} */ ('|')) + /** @type {const} */ (')');
var controlRE = new RegExp('^' + CONTROL + '$');
var META = /** @type {const} */ ('|&;()<> \\t');
var SINGLE_QUOTE = /** @type {const} */ ('\'([^\']*?)\'');
// bash ANSI-C quoting, `$'...'`: a backslash escapes the next character, including `'`
var ANSI_C_BODY = '(?:\\\\[\\s\\S]|[^\\\\\'])*?';
var ANSI_C_QUOTE = '\\$\'' + ANSI_C_BODY + '\'';
var ansiCAt = new RegExp('\\$\'' + ANSI_C_BODY + '(?:(\')|\\\\?$)', 'g');
var ANSI_C_LETTERS = 'abeEfnrtv';
var ANSI_C_CHARS = '\x07\b\x1B\x1B\f\n\r\t\v';
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;
}
var ansiCEscape = /\\([0-7]{1,3}|x[\dA-Fa-f]{1,2}|u[\dA-Fa-f]{1,4}|U[\dA-Fa-f]{1,8}|c(?:\\\\|[\s\S])|[abeEfnrtv\\'"?])/g;
/**
* @param {string} m
* @param {string} escape
*/
function expandAnsiCEscape(m, escape) {
var kind = escape.charAt(0);
if (kind === 'c') {
var ctrl = escape.charAt(1);
return ctrl === '?' ? '\x7F' : String.fromCharCode(ctrl.charCodeAt(0) & 0x1F);
}
if (kind === 'x' || kind === 'u' || kind === 'U') {
var cp = parseInt(escape.slice(1), 16);
if (cp > 0x10FFFF) {
return m;
}
return String.fromCharCode.apply(null, cp > 0xFFFF ? [0xD7C0 + (cp >> 10), 0xDC00 + (cp & 0x3FF)] : [cp]);
}
if (kind >= '0' && kind <= '7') {
return String.fromCharCode(parseInt(escape, 8) & 0xFF);
}
var letter = ANSI_C_LETTERS.indexOf(escape);
return letter < 0 ? escape : ANSI_C_CHARS.charAt(letter);
}
/** @param {string} body */
function expandAnsiC(body) {
return body.replace(ansiCEscape, expandAnsiCEscape).split('\0')[0]; // like bash, a NUL ends the string
}
/**
* @param {string} s
* @param {number} i
*/
function closesAnsiC(s, i) {
ansiCAt.lastIndex = i;
return !!(/** @type {RegExpExecArray} */ (ansiCAt.exec(s)))[1];
}
/**
* @param {string} string
* @param {Env} [env]
* @param {{ escape?: string, splitUnquoted?: boolean | string }} [opts]
* @returns {ParseEntry[]}
*/
function parseInternal(string, env, opts) {
if (!opts) {
opts = {};
}
var BS = opts.escape || '\\';
var ifs = opts.splitUnquoted === true ? ' \t\n' : (typeof opts.splitUnquoted === 'string' ? opts.splitUnquoted : '');
var BAREWORD = '(\\' + BS + '[\'"$\\' + BS + META + ']|\\$\\$|\\$(?!' + ANSI_C_QUOTE.slice(2) + ')|[^\\s\'"$' + META + '])+';
var DOUBLE_QUOTE = '"(?:\\' + BS + '[\\s\\S]|[^"\\' + BS + '])*"';
var chunker = new RegExp([
'(' + CONTROL + ')', // control chars
'(' + ANSI_C_QUOTE + '|' + BAREWORD + '|' + DOUBLE_QUOTE + '|' + SINGLE_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 = '';
/** @type {string[]} */
var words = [];
var sawQuote = false;
/** @type {number | null} */
var pendingNw = null;
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));
}
// match braces by depth so a nested `${` keeps its inner `}` from ending the outer substitution
var depth = 1;
varend = i;
while (depth > 0 && varend < s.length) {
if (s.charAt(varend) === '{' && s.charAt(varend - 1) === '$') {
depth += 1;
} else if (s.charAt(varend) === '}') {
depth -= 1;
}
varend += 1;
}
if (depth !== 0) {
throw new Error('Bad substitution: ' + s.slice(i));
}
varend -= 1;
varname = s.slice(i, varend);
i = varend;
} else if ((/[*@#?$!-]/).test(char)) {
varname = char;
} 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);
}
function flushRun() {
if (pendingNw === null) {
return;
}
if (pendingNw === 0) {
if (out !== '') {
words[words.length] = out;
out = '';
}
} else {
words[words.length] = out;
out = '';
for (var fe = 1; fe < pendingNw; fe += 1) {
words[words.length] = '';
}
}
pendingNw = null;
}
for (i = 0; i < s.length; i++) {
var c = s.charAt(i);
if (ifs && c !== DS) {
flushRun();
}
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;
sawQuote = true;
} 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 && s.charAt(i + 1) === SQ && closesAnsiC(s, i)) {
flushRun();
sawQuote = true;
out += expandAnsiC(s.slice(i + 2, ansiCAt.lastIndex - 1));
i = ansiCAt.lastIndex - 1;
} else if (c === DS) {
var value = parseEnvVar();
if (!ifs) {
out += value;
} else {
for (var vi = 0; vi < value.length; vi += 1) {
var vc = value.charAt(vi);
if (ifs.indexOf(vc) < 0) {
flushRun();
out += vc;
} else if (pendingNw === null) {
pendingNw = vc === ' ' || vc === '\t' || vc === '\n' ? 0 : 1;
} else if (vc !== ' ' && vc !== '\t' && vc !== '\n') {
pendingNw += 1;
}
}
}
} else {
out += c;
}
}
if (isGlob) {
return /** @type {GlobPattern} */ ({ op: 'glob', pattern: out });
}
if (ifs) {
if (pendingNw !== null && pendingNw > 0) {
words[words.length] = out;
out = '';
for (var te = 1; te < pendingNw; te += 1) {
words[words.length] = '';
}
}
if (out !== '' || (sawQuote && words.length === 0)) {
words[words.length] = out;
}
return words;
}
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 {typeof 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[]} */ ([]));
};
+16
View File
@@ -0,0 +1,16 @@
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, `pattern`/`comment` values containing line terminators, or
* strings containing line terminators anywhere after a `comment`.
*
* @param args - Array of tokens to quote.
* @returns A shell-safe quoted string.
*/
declare function quote(args: readonly parse.ParseEntry[]): string;
export = quote;
+85
View File
@@ -0,0 +1,85 @@
'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 {typeof import('./quote')} */
module.exports = function quote(xs) {
var sawComment = false;
return xs.map(function (s) {
if (sawComment && typeof s === 'string' && LINE_TERMINATORS.test(s)) {
throw new TypeError('a token after a `comment` must not contain line terminators');
}
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');
}
if (s.pattern === '') {
return /** @type {const} */ ('\'\'');
}
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');
}
sawComment = true;
return '#' + s.comment;
}
throw new TypeError('unrecognized object token shape');
}
if ((/'/).test(s) && (/!/).test(s)) {
return "'" + s.replace(/'/g, "'\"'\"'") + "'";
}
if ((/["\s\\]/).test(s) && !(/'/).test(s)) {
return "'" + s + "'";
}
if ((/["'\s]/).test(s)) {
return '"' + s.replace(/(["\\$`])/g, '\\$1') + '"';
}
return String(s).replace(/([A-Za-z]:)?([#!"$&'()*,:;<=>?@[\\\]^`{|}~])/g, '$1\\$2');
}).join(' ');
};
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "@ljharb/tsconfig",
"compilerOptions": {
"target": "ES2021"
},
"exclude": [
"coverage",
"test"
]
}