first commit
This commit is contained in:
+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']
|
||||
+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.
|
||||
+246
@@ -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
@@ -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');
|
||||
+80
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "@ljharb/tsconfig",
|
||||
"compilerOptions": {
|
||||
"target": "ES2021"
|
||||
},
|
||||
"exclude": [
|
||||
"coverage",
|
||||
"test"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user