1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | function throwError(exitCode, message, helpText) {
|
9 | const error = new Error(
|
10 | helpText ? `${message}\n\n---\n\n${helpText}` : message
|
11 | );
|
12 | error.exitCode = exitCode;
|
13 | throw error;
|
14 | }
|
15 |
|
16 | function formatOption(option) {
|
17 | let text = ' ';
|
18 | text += option.abbr ? `-${option.abbr}, ` : ' ';
|
19 | text += `--${option.flag ? '(no-)' : ''}${option.full}`;
|
20 | if (option.choices) {
|
21 | text += `=${option.choices.join('|')}`;
|
22 | } else if (option.metavar) {
|
23 | text += `=${option.metavar}`;
|
24 | }
|
25 | if (option.list) {
|
26 | text += ' ...';
|
27 | }
|
28 | if (option.defaultHelp || option.default !== undefined || option.help) {
|
29 | text += ' ';
|
30 | if (text.length < 32) {
|
31 | text += ' '.repeat(32 - text.length);
|
32 | }
|
33 | const textLength = text.length;
|
34 | if (option.help) {
|
35 | text += option.help;
|
36 | }
|
37 | if (option.defaultHelp || option.default !== undefined) {
|
38 | if (option.help) {
|
39 | text += '\n';
|
40 | }
|
41 | text += `${' '.repeat(textLength)}(default: ${option.defaultHelp || option.default})`;
|
42 | }
|
43 | }
|
44 |
|
45 | return text;
|
46 | }
|
47 |
|
48 | function getHelpText(options) {
|
49 | const opts = Object.keys(options)
|
50 | .map(k => options[k])
|
51 | .sort((a,b) => a.display_index - b.display_index);
|
52 |
|
53 | const text = `
|
54 | Usage: jscodeshift [OPTION]... PATH...
|
55 | or: jscodeshift [OPTION]... -t TRANSFORM_PATH PATH...
|
56 | or: jscodeshift [OPTION]... -t URL PATH...
|
57 | or: jscodeshift [OPTION]... --stdin < file_list.txt
|
58 |
|
59 | Apply transform logic in TRANSFORM_PATH (recursively) to every PATH.
|
60 | If --stdin is set, each line of the standard input is used as a path.
|
61 |
|
62 | Options:
|
63 | "..." behind an option means that it can be supplied multiple times.
|
64 | All options are also passed to the transformer, which means you can supply custom options that are not listed here.
|
65 |
|
66 | ${opts.map(formatOption).join('\n')}
|
67 | `;
|
68 | return text.trimLeft();
|
69 | }
|
70 |
|
71 | function validateOptions(parsedOptions, options) {
|
72 | const errors = [];
|
73 | for (const optionName in options) {
|
74 | const option = options[optionName];
|
75 | if (option.choices && !option.choices.includes(parsedOptions[optionName])) {
|
76 | errors.push(
|
77 | `Error: --${option.full} must be one of the values ${option.choices.join(',')}`
|
78 | );
|
79 | }
|
80 | }
|
81 | if (errors.length > 0) {
|
82 | throwError(
|
83 | 1,
|
84 | errors.join('\n'),
|
85 | getHelpText(options)
|
86 | );
|
87 | }
|
88 | }
|
89 |
|
90 | function prepareOptions(options) {
|
91 | options.help = {
|
92 | display_index: 5,
|
93 | abbr: 'h',
|
94 | help: 'print this help and exit',
|
95 | callback() {
|
96 | return getHelpText(options);
|
97 | },
|
98 | };
|
99 |
|
100 | const preparedOptions = {};
|
101 |
|
102 | for (const optionName of Object.keys(options)) {
|
103 | const option = options[optionName];
|
104 | if (!option.full) {
|
105 | option.full = optionName;
|
106 | }
|
107 | option.key = optionName;
|
108 |
|
109 | preparedOptions['--'+option.full] = option;
|
110 | if (option.abbr) {
|
111 | preparedOptions['-'+option.abbr] = option;
|
112 | }
|
113 | if (option.flag) {
|
114 | preparedOptions['--no-'+option.full] = option;
|
115 | }
|
116 | }
|
117 |
|
118 | return preparedOptions;
|
119 | }
|
120 |
|
121 | function isOption(value) {
|
122 | return /^--?/.test(value);
|
123 | }
|
124 |
|
125 | function parse(options, args=process.argv.slice(2)) {
|
126 | const missingValue = Symbol();
|
127 | const preparedOptions = prepareOptions(options);
|
128 |
|
129 | const parsedOptions = {};
|
130 | const positionalArguments = [];
|
131 |
|
132 | for (const optionName in options) {
|
133 | const option = options[optionName];
|
134 | if (option.default !== undefined) {
|
135 | parsedOptions[optionName] = option.default;
|
136 | } else if (option.list) {
|
137 | parsedOptions[optionName] = [];
|
138 | }
|
139 | }
|
140 |
|
141 | for (let i = 0; i < args.length; i++) {
|
142 | const arg = args[i];
|
143 | if (isOption(arg)) {
|
144 | let optionName = arg;
|
145 | let value = null;
|
146 | let option = null;
|
147 | if (optionName.includes('=')) {
|
148 | const index = arg.indexOf('=');
|
149 | optionName = arg.slice(0, index);
|
150 | value = arg.slice(index+1);
|
151 | }
|
152 | if (preparedOptions.hasOwnProperty(optionName)) {
|
153 | option = preparedOptions[optionName];
|
154 | } else {
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 | const realOptionName = optionName.replace(/^--?(no-)?/, '');
|
164 | const isList = parsedOptions.hasOwnProperty(realOptionName) &&
|
165 | parsedOptions[realOptionName] !== true;
|
166 | option = {
|
167 | key: realOptionName,
|
168 | full: realOptionName,
|
169 | flag: !parsedOptions.hasOwnProperty(realOptionName) &&
|
170 | value === null &&
|
171 | isOption(args[i+1]),
|
172 | list: isList,
|
173 | process(value) {
|
174 |
|
175 | try {
|
176 | return JSON.parse(value);
|
177 | } catch(_e) {}
|
178 | return value;
|
179 | },
|
180 | };
|
181 |
|
182 | if (isList) {
|
183 | const currentValue = parsedOptions[realOptionName];
|
184 | if (!Array.isArray(currentValue)) {
|
185 | parsedOptions[realOptionName] = currentValue === true ?
|
186 | [] :
|
187 | [currentValue];
|
188 | }
|
189 | }
|
190 | }
|
191 |
|
192 | if (option.callback) {
|
193 | throwError(0, option.callback());
|
194 | } else if (option.flag) {
|
195 | if (optionName.startsWith('--no-')) {
|
196 | value = false;
|
197 | } else if (value !== null) {
|
198 | value = value === '1';
|
199 | } else {
|
200 | value = true;
|
201 | }
|
202 | parsedOptions[option.key] = value;
|
203 | } else {
|
204 | if (value === null && i < args.length - 1 && !isOption(args[i+1])) {
|
205 |
|
206 | value = args[i+1];
|
207 | i += 1;
|
208 | }
|
209 | if (value !== null) {
|
210 | if (option.process) {
|
211 | value = option.process(value);
|
212 | }
|
213 | if (option.list) {
|
214 | parsedOptions[option.key].push(value);
|
215 | } else {
|
216 | parsedOptions[option.key] = value;
|
217 | }
|
218 | } else {
|
219 | parsedOptions[option.key] = missingValue;
|
220 | }
|
221 | }
|
222 | } else {
|
223 | positionalArguments.push(/^\d+$/.test(arg) ? Number(arg) : arg);
|
224 | }
|
225 | }
|
226 |
|
227 | for (const optionName in parsedOptions) {
|
228 | if (parsedOptions[optionName] === missingValue) {
|
229 | throwError(
|
230 | 1,
|
231 | `Missing value: --${options[optionName].full} requires a value`,
|
232 | getHelpText(options)
|
233 | );
|
234 | }
|
235 | }
|
236 |
|
237 | const result = {
|
238 | positionalArguments,
|
239 | options: parsedOptions,
|
240 | };
|
241 |
|
242 | validateOptions(parsedOptions, options);
|
243 |
|
244 | return result;
|
245 | }
|
246 |
|
247 | module.exports = {
|
248 | |
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 | options(options) {
|
264 | return {
|
265 | parse(args) {
|
266 | return parse(options, args);
|
267 | },
|
268 | getHelpText() {
|
269 | return getHelpText(options);
|
270 | },
|
271 | };
|
272 | },
|
273 | };
|