UNPKG

9.29 kBJavaScriptView Raw
1"use strict";
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 */
10var __importDefault = (this && this.__importDefault) || function (mod) {
11 return (mod && mod.__esModule) ? mod : { "default": mod };
12};
13Object.defineProperty(exports, "__esModule", { value: true });
14exports.Parser = void 0;
15const getopts_1 = __importDefault(require("getopts"));
16const 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 */
21class 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}
285exports.Parser = Parser;