1 | import {Buffer} from 'node:buffer';
|
2 | import {ChildProcess} from 'node:child_process';
|
3 |
|
4 | const normalizeArgs = (file, args = []) => {
|
5 | if (!Array.isArray(args)) {
|
6 | return [file];
|
7 | }
|
8 |
|
9 | return [file, ...args];
|
10 | };
|
11 |
|
12 | const NO_ESCAPE_REGEXP = /^[\w.-]+$/;
|
13 |
|
14 | const escapeArg = arg => {
|
15 | if (typeof arg !== 'string' || NO_ESCAPE_REGEXP.test(arg)) {
|
16 | return arg;
|
17 | }
|
18 |
|
19 | return `"${arg.replaceAll('"', '\\"')}"`;
|
20 | };
|
21 |
|
22 | export const joinCommand = (file, args) => normalizeArgs(file, args).join(' ');
|
23 |
|
24 | export const getEscapedCommand = (file, args) => normalizeArgs(file, args).map(arg => escapeArg(arg)).join(' ');
|
25 |
|
26 | const SPACES_REGEXP = / +/g;
|
27 |
|
28 |
|
29 | export const parseCommand = command => {
|
30 | const tokens = [];
|
31 | for (const token of command.trim().split(SPACES_REGEXP)) {
|
32 |
|
33 | const previousToken = tokens.at(-1);
|
34 | if (previousToken && previousToken.endsWith('\\')) {
|
35 |
|
36 | tokens[tokens.length - 1] = `${previousToken.slice(0, -1)} ${token}`;
|
37 | } else {
|
38 | tokens.push(token);
|
39 | }
|
40 | }
|
41 |
|
42 | return tokens;
|
43 | };
|
44 |
|
45 | const parseExpression = expression => {
|
46 | const typeOfExpression = typeof expression;
|
47 |
|
48 | if (typeOfExpression === 'string') {
|
49 | return expression;
|
50 | }
|
51 |
|
52 | if (typeOfExpression === 'number') {
|
53 | return String(expression);
|
54 | }
|
55 |
|
56 | if (
|
57 | typeOfExpression === 'object'
|
58 | && expression !== null
|
59 | && !(expression instanceof ChildProcess)
|
60 | && 'stdout' in expression
|
61 | ) {
|
62 | const typeOfStdout = typeof expression.stdout;
|
63 |
|
64 | if (typeOfStdout === 'string') {
|
65 | return expression.stdout;
|
66 | }
|
67 |
|
68 | if (Buffer.isBuffer(expression.stdout)) {
|
69 | return expression.stdout.toString();
|
70 | }
|
71 |
|
72 | throw new TypeError(`Unexpected "${typeOfStdout}" stdout in template expression`);
|
73 | }
|
74 |
|
75 | throw new TypeError(`Unexpected "${typeOfExpression}" in template expression`);
|
76 | };
|
77 |
|
78 | const concatTokens = (tokens, nextTokens, isNew) => isNew || tokens.length === 0 || nextTokens.length === 0
|
79 | ? [...tokens, ...nextTokens]
|
80 | : [
|
81 | ...tokens.slice(0, -1),
|
82 | `${tokens.at(-1)}${nextTokens[0]}`,
|
83 | ...nextTokens.slice(1),
|
84 | ];
|
85 |
|
86 | const parseTemplate = ({templates, expressions, tokens, index, template}) => {
|
87 | const templateString = template ?? templates.raw[index];
|
88 | const templateTokens = templateString.split(SPACES_REGEXP).filter(Boolean);
|
89 | const newTokens = concatTokens(
|
90 | tokens,
|
91 | templateTokens,
|
92 | templateString.startsWith(' '),
|
93 | );
|
94 |
|
95 | if (index === expressions.length) {
|
96 | return newTokens;
|
97 | }
|
98 |
|
99 | const expression = expressions[index];
|
100 | const expressionTokens = Array.isArray(expression)
|
101 | ? expression.map(expression => parseExpression(expression))
|
102 | : [parseExpression(expression)];
|
103 | return concatTokens(
|
104 | newTokens,
|
105 | expressionTokens,
|
106 | templateString.endsWith(' '),
|
107 | );
|
108 | };
|
109 |
|
110 | export const parseTemplates = (templates, expressions) => {
|
111 | let tokens = [];
|
112 |
|
113 | for (const [index, template] of templates.entries()) {
|
114 | tokens = parseTemplate({templates, expressions, tokens, index, template});
|
115 | }
|
116 |
|
117 | return tokens;
|
118 | };
|
119 |
|