| import {Buffer} from 'node:buffer';
|
| import {ChildProcess} from 'node:child_process';
|
|
|
| const normalizeArgs = (file, args = []) => {
|
| if (!Array.isArray(args)) {
|
| return [file];
|
| }
|
|
|
| return [file, ...args];
|
| };
|
|
|
| const NO_ESCAPE_REGEXP = /^[\w.-]+$/;
|
|
|
| const escapeArg = arg => {
|
| if (typeof arg !== 'string' || NO_ESCAPE_REGEXP.test(arg)) {
|
| return arg;
|
| }
|
|
|
| return `"${arg.replaceAll('"', '\\"')}"`;
|
| };
|
|
|
| export const joinCommand = (file, args) => normalizeArgs(file, args).join(' ');
|
|
|
| export const getEscapedCommand = (file, args) => normalizeArgs(file, args).map(arg => escapeArg(arg)).join(' ');
|
|
|
| const SPACES_REGEXP = / +/g;
|
|
|
|
|
| export const parseCommand = command => {
|
| const tokens = [];
|
| for (const token of command.trim().split(SPACES_REGEXP)) {
|
|
|
| const previousToken = tokens.at(-1);
|
| if (previousToken && previousToken.endsWith('\\')) {
|
|
|
| tokens[tokens.length - 1] = `${previousToken.slice(0, -1)} ${token}`;
|
| } else {
|
| tokens.push(token);
|
| }
|
| }
|
|
|
| return tokens;
|
| };
|
|
|
| const parseExpression = expression => {
|
| const typeOfExpression = typeof expression;
|
|
|
| if (typeOfExpression === 'string') {
|
| return expression;
|
| }
|
|
|
| if (typeOfExpression === 'number') {
|
| return String(expression);
|
| }
|
|
|
| if (
|
| typeOfExpression === 'object'
|
| && expression !== null
|
| && !(expression instanceof ChildProcess)
|
| && 'stdout' in expression
|
| ) {
|
| const typeOfStdout = typeof expression.stdout;
|
|
|
| if (typeOfStdout === 'string') {
|
| return expression.stdout;
|
| }
|
|
|
| if (Buffer.isBuffer(expression.stdout)) {
|
| return expression.stdout.toString();
|
| }
|
|
|
| throw new TypeError(`Unexpected "${typeOfStdout}" stdout in template expression`);
|
| }
|
|
|
| throw new TypeError(`Unexpected "${typeOfExpression}" in template expression`);
|
| };
|
|
|
| const concatTokens = (tokens, nextTokens, isNew) => isNew || tokens.length === 0 || nextTokens.length === 0
|
| ? [...tokens, ...nextTokens]
|
| : [
|
| ...tokens.slice(0, -1),
|
| `${tokens.at(-1)}${nextTokens[0]}`,
|
| ...nextTokens.slice(1),
|
| ];
|
|
|
| const parseTemplate = ({templates, expressions, tokens, index, template}) => {
|
| const templateString = template ?? templates.raw[index];
|
| const templateTokens = templateString.split(SPACES_REGEXP).filter(Boolean);
|
| const newTokens = concatTokens(
|
| tokens,
|
| templateTokens,
|
| templateString.startsWith(' '),
|
| );
|
|
|
| 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,
|
| templateString.endsWith(' '),
|
| );
|
| };
|
|
|
| export const parseTemplates = (templates, expressions) => {
|
| let tokens = [];
|
|
|
| for (const [index, template] of templates.entries()) {
|
| tokens = parseTemplate({templates, expressions, tokens, index, template});
|
| }
|
|
|
| return tokens;
|
| };
|
|
|
|
|