1 | import {
|
2 | BuidlerArguments,
|
3 | BuidlerParamDefinitions,
|
4 | ParamDefinition,
|
5 | ParamDefinitionsMap,
|
6 | TaskArguments,
|
7 | TaskDefinition,
|
8 | } from "../../types";
|
9 | import { BuidlerError } from "../core/errors";
|
10 | import { ERRORS } from "../core/errors-list";
|
11 |
|
12 | export class ArgumentsParser {
|
13 | public static readonly PARAM_PREFIX = "--";
|
14 |
|
15 | public static paramNameToCLA(paramName: string): string {
|
16 | return (
|
17 | ArgumentsParser.PARAM_PREFIX +
|
18 | paramName
|
19 | .split(/(?=[A-Z])/g)
|
20 | .map((s) => s.toLowerCase())
|
21 | .join("-")
|
22 | );
|
23 | }
|
24 |
|
25 | public static cLAToParamName(cLA: string): string {
|
26 | if (cLA.toLowerCase() !== cLA) {
|
27 | throw new BuidlerError(ERRORS.ARGUMENTS.PARAM_NAME_INVALID_CASING, {
|
28 | param: cLA,
|
29 | });
|
30 | }
|
31 |
|
32 | const parts = cLA.slice(ArgumentsParser.PARAM_PREFIX.length).split("-");
|
33 |
|
34 | return (
|
35 | parts[0] +
|
36 | parts
|
37 | .slice(1)
|
38 | .map((s) => s[0].toUpperCase() + s.slice(1))
|
39 | .join("")
|
40 | );
|
41 | }
|
42 |
|
43 | public parseBuidlerArguments(
|
44 | buidlerParamDefinitions: BuidlerParamDefinitions,
|
45 | envVariableArguments: BuidlerArguments,
|
46 | rawCLAs: string[]
|
47 | ): {
|
48 | buidlerArguments: BuidlerArguments;
|
49 | taskName?: string;
|
50 | unparsedCLAs: string[];
|
51 | } {
|
52 | const buidlerArguments: Partial<BuidlerArguments> = {};
|
53 | let taskName: string | undefined;
|
54 | const unparsedCLAs: string[] = [];
|
55 |
|
56 | for (let i = 0; i < rawCLAs.length; i++) {
|
57 | const arg = rawCLAs[i];
|
58 |
|
59 | if (taskName === undefined) {
|
60 | if (!this._hasCLAParamNameFormat(arg)) {
|
61 | taskName = arg;
|
62 | continue;
|
63 | }
|
64 |
|
65 | if (!this._isCLAParamName(arg, buidlerParamDefinitions)) {
|
66 | throw new BuidlerError(
|
67 | ERRORS.ARGUMENTS.UNRECOGNIZED_COMMAND_LINE_ARG,
|
68 | { argument: arg }
|
69 | );
|
70 | }
|
71 |
|
72 | i = this._parseArgumentAt(
|
73 | rawCLAs,
|
74 | i,
|
75 | buidlerParamDefinitions,
|
76 | buidlerArguments
|
77 | );
|
78 | } else {
|
79 | if (!this._isCLAParamName(arg, buidlerParamDefinitions)) {
|
80 | unparsedCLAs.push(arg);
|
81 | continue;
|
82 | }
|
83 |
|
84 | i = this._parseArgumentAt(
|
85 | rawCLAs,
|
86 | i,
|
87 | buidlerParamDefinitions,
|
88 | buidlerArguments
|
89 | );
|
90 | }
|
91 | }
|
92 |
|
93 | return {
|
94 | buidlerArguments: this._addBuidlerDefaultArguments(
|
95 | buidlerParamDefinitions,
|
96 | envVariableArguments,
|
97 | buidlerArguments
|
98 | ),
|
99 | taskName,
|
100 | unparsedCLAs,
|
101 | };
|
102 | }
|
103 |
|
104 | public parseTaskArguments(
|
105 | taskDefinition: TaskDefinition,
|
106 | rawCLAs: string[]
|
107 | ): TaskArguments {
|
108 | const {
|
109 | paramArguments,
|
110 | rawPositionalArguments,
|
111 | } = this._parseTaskParamArguments(taskDefinition, rawCLAs);
|
112 |
|
113 | const positionalArguments = this._parsePositionalParamArgs(
|
114 | rawPositionalArguments,
|
115 | taskDefinition.positionalParamDefinitions
|
116 | );
|
117 |
|
118 | return { ...paramArguments, ...positionalArguments };
|
119 | }
|
120 |
|
121 | private _parseTaskParamArguments(
|
122 | taskDefinition: TaskDefinition,
|
123 | rawCLAs: string[]
|
124 | ) {
|
125 | const paramArguments = {};
|
126 | const rawPositionalArguments: string[] = [];
|
127 |
|
128 | for (let i = 0; i < rawCLAs.length; i++) {
|
129 | const arg = rawCLAs[i];
|
130 |
|
131 | if (!this._hasCLAParamNameFormat(arg)) {
|
132 | rawPositionalArguments.push(arg);
|
133 | continue;
|
134 | }
|
135 |
|
136 | if (!this._isCLAParamName(arg, taskDefinition.paramDefinitions)) {
|
137 | throw new BuidlerError(ERRORS.ARGUMENTS.UNRECOGNIZED_PARAM_NAME, {
|
138 | param: arg,
|
139 | });
|
140 | }
|
141 |
|
142 | i = this._parseArgumentAt(
|
143 | rawCLAs,
|
144 | i,
|
145 | taskDefinition.paramDefinitions,
|
146 | paramArguments
|
147 | );
|
148 | }
|
149 |
|
150 | this._addTaskDefaultArguments(taskDefinition, paramArguments);
|
151 |
|
152 | return { paramArguments, rawPositionalArguments };
|
153 | }
|
154 |
|
155 | private _addBuidlerDefaultArguments(
|
156 | buidlerParamDefinitions: BuidlerParamDefinitions,
|
157 | envVariableArguments: BuidlerArguments,
|
158 | buidlerArguments: Partial<BuidlerArguments>
|
159 | ): BuidlerArguments {
|
160 | return {
|
161 | ...envVariableArguments,
|
162 | ...buidlerArguments,
|
163 | };
|
164 | }
|
165 |
|
166 | private _addTaskDefaultArguments(
|
167 | taskDefinition: TaskDefinition,
|
168 | taskArguments: TaskArguments
|
169 | ) {
|
170 | for (const paramName of Object.keys(taskDefinition.paramDefinitions)) {
|
171 | const definition = taskDefinition.paramDefinitions[paramName];
|
172 |
|
173 | if (taskArguments[paramName] !== undefined) {
|
174 | continue;
|
175 | }
|
176 | if (!definition.isOptional) {
|
177 | throw new BuidlerError(ERRORS.ARGUMENTS.MISSING_TASK_ARGUMENT, {
|
178 | param: ArgumentsParser.paramNameToCLA(paramName),
|
179 | });
|
180 | }
|
181 |
|
182 | taskArguments[paramName] = definition.defaultValue;
|
183 | }
|
184 | }
|
185 |
|
186 | private _isCLAParamName(str: string, paramDefinitions: ParamDefinitionsMap) {
|
187 | if (!this._hasCLAParamNameFormat(str)) {
|
188 | return false;
|
189 | }
|
190 |
|
191 | const name = ArgumentsParser.cLAToParamName(str);
|
192 | return paramDefinitions[name] !== undefined;
|
193 | }
|
194 |
|
195 | private _hasCLAParamNameFormat(str: string) {
|
196 | return str.startsWith(ArgumentsParser.PARAM_PREFIX);
|
197 | }
|
198 |
|
199 | private _parseArgumentAt(
|
200 | rawCLAs: string[],
|
201 | index: number,
|
202 | paramDefinitions: ParamDefinitionsMap,
|
203 | parsedArguments: TaskArguments
|
204 | ) {
|
205 | const claArg = rawCLAs[index];
|
206 | const paramName = ArgumentsParser.cLAToParamName(claArg);
|
207 | const definition = paramDefinitions[paramName];
|
208 |
|
209 | if (parsedArguments[paramName] !== undefined) {
|
210 | throw new BuidlerError(ERRORS.ARGUMENTS.REPEATED_PARAM, {
|
211 | param: claArg,
|
212 | });
|
213 | }
|
214 |
|
215 | if (definition.isFlag) {
|
216 | parsedArguments[paramName] = true;
|
217 | } else {
|
218 | index++;
|
219 | const value = rawCLAs[index];
|
220 |
|
221 | if (value === undefined) {
|
222 | throw new BuidlerError(ERRORS.ARGUMENTS.MISSING_TASK_ARGUMENT, {
|
223 | param: ArgumentsParser.paramNameToCLA(paramName),
|
224 | });
|
225 | }
|
226 |
|
227 | parsedArguments[paramName] = definition.type.parse(paramName, value);
|
228 | }
|
229 |
|
230 | return index;
|
231 | }
|
232 |
|
233 | private _parsePositionalParamArgs(
|
234 | rawPositionalParamArgs: string[],
|
235 | positionalParamDefinitions: Array<ParamDefinition<any>>
|
236 | ): TaskArguments {
|
237 | const args: TaskArguments = {};
|
238 |
|
239 | for (let i = 0; i < positionalParamDefinitions.length; i++) {
|
240 | const definition = positionalParamDefinitions[i];
|
241 |
|
242 | const rawArg = rawPositionalParamArgs[i];
|
243 |
|
244 | if (rawArg === undefined) {
|
245 | if (!definition.isOptional) {
|
246 | throw new BuidlerError(ERRORS.ARGUMENTS.MISSING_POSITIONAL_ARG, {
|
247 | param: definition.name,
|
248 | });
|
249 | }
|
250 |
|
251 | args[definition.name] = definition.defaultValue;
|
252 | } else if (!definition.isVariadic) {
|
253 | args[definition.name] = definition.type.parse(definition.name, rawArg);
|
254 | } else {
|
255 | args[definition.name] = rawPositionalParamArgs
|
256 | .slice(i)
|
257 | .map((raw) => definition.type.parse(definition.name, raw));
|
258 | }
|
259 | }
|
260 |
|
261 | const lastDefinition =
|
262 | positionalParamDefinitions[positionalParamDefinitions.length - 1];
|
263 |
|
264 | const hasVariadicParam =
|
265 | lastDefinition !== undefined && lastDefinition.isVariadic;
|
266 |
|
267 | if (
|
268 | !hasVariadicParam &&
|
269 | rawPositionalParamArgs.length > positionalParamDefinitions.length
|
270 | ) {
|
271 | throw new BuidlerError(ERRORS.ARGUMENTS.UNRECOGNIZED_POSITIONAL_ARG, {
|
272 | argument: rawPositionalParamArgs[positionalParamDefinitions.length],
|
273 | });
|
274 | }
|
275 |
|
276 | return args;
|
277 | }
|
278 | }
|