|
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; |
|
}; |
|
|
|
|