1 | ;
|
2 | /*
|
3 | * @adonisjs/ace
|
4 | *
|
5 | * (c) Harminder Virk <virk@adonisjs.com>
|
6 | *
|
7 | * For the full copyright and license information, please view the LICENSE
|
8 | * file that was distributed with this source code.
|
9 | */
|
10 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
11 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
12 | };
|
13 | Object.defineProperty(exports, "__esModule", { value: true });
|
14 | exports.Parser = void 0;
|
15 | const getopts_1 = __importDefault(require("getopts"));
|
16 | const Exceptions_1 = require("../Exceptions");
|
17 | /**
|
18 | * The job of the parser is to parse the command line values by taking
|
19 | * the command `args`, `flags` and `globalFlags` into account.
|
20 | */
|
21 | class Parser {
|
22 | constructor(registeredFlags) {
|
23 | this.registeredFlags = registeredFlags;
|
24 | }
|
25 | /**
|
26 | * Validate all the flags against the flags registered by the command
|
27 | * or as global flags and disallow unknown flags.
|
28 | */
|
29 | scanForUnknownFlags(parsed, flagsAndAliases) {
|
30 | Object.keys(parsed).forEach((key) => {
|
31 | if (key === '_') {
|
32 | return;
|
33 | }
|
34 | const hasFlag = flagsAndAliases.find((value) => value === key);
|
35 | if (!hasFlag) {
|
36 | throw Exceptions_1.UnknownFlagException.invoke(key);
|
37 | }
|
38 | });
|
39 | }
|
40 | /**
|
41 | * Processes ace command flag to set the options for `getopts`.
|
42 | * We just define the `alias` with getopts coz their default,
|
43 | * string and boolean options produces the behavior we don't
|
44 | * want.
|
45 | */
|
46 | preProcessFlag(flag, options) {
|
47 | /**
|
48 | * Register alias (when exists)
|
49 | */
|
50 | if (flag.alias) {
|
51 | options.alias[flag.alias] = flag.name;
|
52 | }
|
53 | }
|
54 | /**
|
55 | * Casts a flag value to a boolean. The casting logic is driven
|
56 | * by the behavior of "getopts"
|
57 | */
|
58 | castToBoolean(value) {
|
59 | if (typeof value === 'boolean') {
|
60 | return value;
|
61 | }
|
62 | if (value === 'true' || value === '=true') {
|
63 | return true;
|
64 | }
|
65 | return undefined;
|
66 | }
|
67 | /**
|
68 | * Cast the value to a string. The casting logic is driven
|
69 | * by the behavior of "getopts"
|
70 | *
|
71 | * - Convert numbers to string
|
72 | * - Do not convert boolean to a string, since a flag without a value
|
73 | * gets a boolean value, which is invalid
|
74 | */
|
75 | castToString(value) {
|
76 | if (typeof value === 'number') {
|
77 | value = String(value);
|
78 | }
|
79 | if (typeof value === 'string' && value.trim()) {
|
80 | return value;
|
81 | }
|
82 | return undefined;
|
83 | }
|
84 | /**
|
85 | * Cast value to an array of string. The casting logic is driven
|
86 | * by the behavior of "getopts"
|
87 | *
|
88 | * - Numeric values are converted to string of array
|
89 | * - A string value is splitted by comma and trimmed.
|
90 | * - An array is casted to an array of string values
|
91 | */
|
92 | castToArray(value) {
|
93 | if (typeof value === 'number') {
|
94 | value = String(value);
|
95 | }
|
96 | if (typeof value === 'string') {
|
97 | return value.split(',').filter((prop) => prop.trim());
|
98 | }
|
99 | if (Array.isArray(value)) {
|
100 | /**
|
101 | * This will also convert numeric values to a string. The behavior
|
102 | * is same as string flag type.
|
103 | */
|
104 | return value.map((prop) => String(prop));
|
105 | }
|
106 | return undefined;
|
107 | }
|
108 | /**
|
109 | * Cast value to an array of numbers. The casting logic is driven
|
110 | * by the behavior of "getopts".
|
111 | *
|
112 | * - Numeric values are wrapped to an array.
|
113 | * - String is splitted by comma and each value is casted to a number
|
114 | * - Each array value is casted to a number.
|
115 | */
|
116 | castToNumArray(value) {
|
117 | if (typeof value === 'number') {
|
118 | return [value];
|
119 | }
|
120 | if (typeof value === 'string') {
|
121 | return value.split(',').map((one) => Number(one));
|
122 | }
|
123 | if (Array.isArray(value)) {
|
124 | return value.map((prop) => Number(prop));
|
125 | }
|
126 | return undefined;
|
127 | }
|
128 | /**
|
129 | * Cast value to a number. The casting logic is driven
|
130 | * by the behavior of "getopts"
|
131 | *
|
132 | * - Boolean values are not allowed
|
133 | * - A string is converted to a number
|
134 | */
|
135 | castToNumer(value) {
|
136 | if (typeof value === 'number') {
|
137 | return value;
|
138 | }
|
139 | if (typeof value === 'string') {
|
140 | // Possibility of NaN here
|
141 | return Number(value);
|
142 | }
|
143 | return undefined;
|
144 | }
|
145 | /**
|
146 | * Casts value of a flag to it's expected data type. These values
|
147 | * are then later validated to ensure that casting was successful.
|
148 | */
|
149 | processFlag(flag, parsed, command) {
|
150 | let value = parsed[flag.name];
|
151 | /**
|
152 | * Check for the value with the alias, if it undefined
|
153 | * by the name
|
154 | */
|
155 | if (value === undefined && flag.alias) {
|
156 | value = parsed[flag.alias];
|
157 | }
|
158 | /**
|
159 | * Still undefined??
|
160 | *
|
161 | * It is fine. Flags are optional anyways
|
162 | */
|
163 | if (value === undefined) {
|
164 | return;
|
165 | }
|
166 | /**
|
167 | * Handle boolean values. It should be a valid boolean
|
168 | * data type or a string value of `'true'`.
|
169 | */
|
170 | if (flag.type === 'boolean') {
|
171 | value = this.castToBoolean(value);
|
172 | if (value === undefined) {
|
173 | throw Exceptions_1.InvalidFlagException.invoke(flag.name, flag.type, command);
|
174 | }
|
175 | }
|
176 | /**
|
177 | * Handle string value. It should be a valid and not empty.
|
178 | * Either remove the flag or provide a value
|
179 | */
|
180 | if (flag.type === 'string') {
|
181 | value = this.castToString(value);
|
182 | if (value === undefined) {
|
183 | throw Exceptions_1.InvalidFlagException.invoke(flag.name, flag.type, command);
|
184 | }
|
185 | }
|
186 | /**
|
187 | * Handle numeric values. The flag should have a value and
|
188 | * a valid number.
|
189 | */
|
190 | if (flag.type === 'number') {
|
191 | value = this.castToNumer(value);
|
192 | if (value === undefined || isNaN(value)) {
|
193 | throw Exceptions_1.InvalidFlagException.invoke(flag.name, flag.type, command);
|
194 | }
|
195 | }
|
196 | /**
|
197 | * Parse the value to be an array of strings
|
198 | */
|
199 | if (flag.type === 'array') {
|
200 | value = this.castToArray(value);
|
201 | if (!value || !value.length) {
|
202 | throw Exceptions_1.InvalidFlagException.invoke(flag.name, flag.type, command);
|
203 | }
|
204 | }
|
205 | /**
|
206 | * Parse the value to be an array of numbers
|
207 | */
|
208 | if (flag.type === 'numArray') {
|
209 | value = this.castToNumArray(value);
|
210 | if (!value || !value.length) {
|
211 | throw Exceptions_1.InvalidFlagException.invoke(flag.name, flag.type, command);
|
212 | }
|
213 | /**
|
214 | * Find if array has NaN values
|
215 | */
|
216 | if (value.findIndex((one) => isNaN(one)) > -1) {
|
217 | throw Exceptions_1.InvalidFlagException.invoke(flag.name, flag.type, command);
|
218 | }
|
219 | }
|
220 | parsed[flag.name] = value;
|
221 | if (flag.alias) {
|
222 | parsed[flag.alias] = value;
|
223 | }
|
224 | }
|
225 | /**
|
226 | * Validates the value to ensure that values are defined for
|
227 | * required arguments.
|
228 | */
|
229 | validateArg(arg, index, parsed, command) {
|
230 | const value = parsed._[index];
|
231 | if (value === undefined && arg.required) {
|
232 | throw Exceptions_1.MissingArgumentException.invoke(arg.name, command);
|
233 | }
|
234 | }
|
235 | /**
|
236 | * Parses argv and executes the command and global flags handlers
|
237 | */
|
238 | parse(argv, command) {
|
239 | let options = { alias: {}, boolean: [], default: {}, string: [] };
|
240 | const flagsAndAliases = [];
|
241 | const globalFlags = Object.keys(this.registeredFlags).map((name) => this.registeredFlags[name]);
|
242 | /**
|
243 | * Build options from global flags
|
244 | */
|
245 | globalFlags.forEach((flag) => {
|
246 | this.preProcessFlag(flag, options);
|
247 | flagsAndAliases.push(flag.name);
|
248 | flag.alias && flagsAndAliases.push(flag.alias);
|
249 | });
|
250 | /**
|
251 | * Build options from command flags
|
252 | */
|
253 | if (command) {
|
254 | command.flags.forEach((flag) => {
|
255 | this.preProcessFlag(flag, options);
|
256 | flagsAndAliases.push(flag.name);
|
257 | flag.alias && flagsAndAliases.push(flag.alias);
|
258 | });
|
259 | }
|
260 | /**
|
261 | * Parsing argv with the previously built options
|
262 | */
|
263 | const parsed = getopts_1.default(argv, options);
|
264 | /**
|
265 | * Scan and report unknown flag as exception
|
266 | */
|
267 | this.scanForUnknownFlags(parsed, flagsAndAliases);
|
268 | /**
|
269 | * Validating global flags (if any)
|
270 | */
|
271 | globalFlags.forEach((flag) => {
|
272 | this.processFlag(flag, parsed);
|
273 | });
|
274 | /**
|
275 | * Validating command flags (if command is defined)
|
276 | */
|
277 | if (command) {
|
278 | command.flags.forEach((flag) => {
|
279 | this.processFlag(flag, parsed);
|
280 | });
|
281 | }
|
282 | return parsed;
|
283 | }
|
284 | }
|
285 | exports.Parser = Parser;
|