Initial commit
This commit is contained in:
153
node_modules/execa/lib/methods/template.js
generated
vendored
Normal file
153
node_modules/execa/lib/methods/template.js
generated
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
import {ChildProcess} from 'node:child_process';
|
||||
import isPlainObject from 'is-plain-obj';
|
||||
import {isUint8Array, uint8ArrayToString} from '../utils/uint-array.js';
|
||||
|
||||
// Check whether the template string syntax is being used
|
||||
export const isTemplateString = templates => Array.isArray(templates) && Array.isArray(templates.raw);
|
||||
|
||||
// Convert execa`file ...commandArguments` to execa(file, commandArguments)
|
||||
export const parseTemplates = (templates, expressions) => {
|
||||
let tokens = [];
|
||||
|
||||
for (const [index, template] of templates.entries()) {
|
||||
tokens = parseTemplate({
|
||||
templates,
|
||||
expressions,
|
||||
tokens,
|
||||
index,
|
||||
template,
|
||||
});
|
||||
}
|
||||
|
||||
if (tokens.length === 0) {
|
||||
throw new TypeError('Template script must not be empty');
|
||||
}
|
||||
|
||||
const [file, ...commandArguments] = tokens;
|
||||
return [file, commandArguments, {}];
|
||||
};
|
||||
|
||||
const parseTemplate = ({templates, expressions, tokens, index, template}) => {
|
||||
if (template === undefined) {
|
||||
throw new TypeError(`Invalid backslash sequence: ${templates.raw[index]}`);
|
||||
}
|
||||
|
||||
const {nextTokens, leadingWhitespaces, trailingWhitespaces} = splitByWhitespaces(template, templates.raw[index]);
|
||||
const newTokens = concatTokens(tokens, nextTokens, leadingWhitespaces);
|
||||
|
||||
if (index === expressions.length) {
|
||||
return newTokens;
|
||||
}
|
||||
|
||||
const expression = expressions[index];
|
||||
const expressionTokens = Array.isArray(expression)
|
||||
? expression.map(expression => parseExpression(expression))
|
||||
: [parseExpression(expression)];
|
||||
return concatTokens(newTokens, expressionTokens, trailingWhitespaces);
|
||||
};
|
||||
|
||||
// Like `string.split(/[ \t\r\n]+/)` except newlines and tabs are:
|
||||
// - ignored when input as a backslash sequence like: `echo foo\n bar`
|
||||
// - not ignored when input directly
|
||||
// The only way to distinguish those in JavaScript is to use a tagged template and compare:
|
||||
// - the first array argument, which does not escape backslash sequences
|
||||
// - its `raw` property, which escapes them
|
||||
const splitByWhitespaces = (template, rawTemplate) => {
|
||||
if (rawTemplate.length === 0) {
|
||||
return {nextTokens: [], leadingWhitespaces: false, trailingWhitespaces: false};
|
||||
}
|
||||
|
||||
const nextTokens = [];
|
||||
let templateStart = 0;
|
||||
const leadingWhitespaces = DELIMITERS.has(rawTemplate[0]);
|
||||
|
||||
for (
|
||||
let templateIndex = 0, rawIndex = 0;
|
||||
templateIndex < template.length;
|
||||
templateIndex += 1, rawIndex += 1
|
||||
) {
|
||||
const rawCharacter = rawTemplate[rawIndex];
|
||||
if (DELIMITERS.has(rawCharacter)) {
|
||||
if (templateStart !== templateIndex) {
|
||||
nextTokens.push(template.slice(templateStart, templateIndex));
|
||||
}
|
||||
|
||||
templateStart = templateIndex + 1;
|
||||
} else if (rawCharacter === '\\') {
|
||||
const nextRawCharacter = rawTemplate[rawIndex + 1];
|
||||
if (nextRawCharacter === '\n') {
|
||||
// Handles escaped newlines in templates
|
||||
templateIndex -= 1;
|
||||
rawIndex += 1;
|
||||
} else if (nextRawCharacter === 'u' && rawTemplate[rawIndex + 2] === '{') {
|
||||
rawIndex = rawTemplate.indexOf('}', rawIndex + 3);
|
||||
} else {
|
||||
rawIndex += ESCAPE_LENGTH[nextRawCharacter] ?? 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const trailingWhitespaces = templateStart === template.length;
|
||||
if (!trailingWhitespaces) {
|
||||
nextTokens.push(template.slice(templateStart));
|
||||
}
|
||||
|
||||
return {nextTokens, leadingWhitespaces, trailingWhitespaces};
|
||||
};
|
||||
|
||||
const DELIMITERS = new Set([' ', '\t', '\r', '\n']);
|
||||
|
||||
// Number of characters in backslash escape sequences: \0 \xXX or \uXXXX
|
||||
// \cX is allowed in RegExps but not in strings
|
||||
// Octal sequences are not allowed in strict mode
|
||||
const ESCAPE_LENGTH = {x: 3, u: 5};
|
||||
|
||||
const concatTokens = (tokens, nextTokens, isSeparated) => isSeparated
|
||||
|| tokens.length === 0
|
||||
|| nextTokens.length === 0
|
||||
? [...tokens, ...nextTokens]
|
||||
: [
|
||||
...tokens.slice(0, -1),
|
||||
`${tokens.at(-1)}${nextTokens[0]}`,
|
||||
...nextTokens.slice(1),
|
||||
];
|
||||
|
||||
// Handle `${expression}` inside the template string syntax
|
||||
const parseExpression = expression => {
|
||||
const typeOfExpression = typeof expression;
|
||||
|
||||
if (typeOfExpression === 'string') {
|
||||
return expression;
|
||||
}
|
||||
|
||||
if (typeOfExpression === 'number') {
|
||||
return String(expression);
|
||||
}
|
||||
|
||||
if (isPlainObject(expression) && ('stdout' in expression || 'isMaxBuffer' in expression)) {
|
||||
return getSubprocessResult(expression);
|
||||
}
|
||||
|
||||
if (expression instanceof ChildProcess || Object.prototype.toString.call(expression) === '[object Promise]') {
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
throw new TypeError('Unexpected subprocess in template expression. Please use ${await subprocess} instead of ${subprocess}.');
|
||||
}
|
||||
|
||||
throw new TypeError(`Unexpected "${typeOfExpression}" in template expression`);
|
||||
};
|
||||
|
||||
const getSubprocessResult = ({stdout}) => {
|
||||
if (typeof stdout === 'string') {
|
||||
return stdout;
|
||||
}
|
||||
|
||||
if (isUint8Array(stdout)) {
|
||||
return uint8ArrayToString(stdout);
|
||||
}
|
||||
|
||||
if (stdout === undefined) {
|
||||
throw new TypeError('Missing result.stdout in template expression. This is probably due to the previous subprocess\' "stdout" option.');
|
||||
}
|
||||
|
||||
throw new TypeError(`Unexpected "${typeof stdout}" stdout in template expression`);
|
||||
};
|
||||
Reference in New Issue
Block a user