Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2
* vim: set ts=8 sts=2 et sw=2 tw=80:
3
* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "shell/jsoptparse.h"
8
9
#include <algorithm>
10
#include <stdarg.h>
11
12
#include "util/Unicode.h"
13
14
using namespace js;
15
using namespace js::cli;
16
using namespace js::cli::detail;
17
18
const char OptionParser::prognameMeta[] = "{progname}";
19
20
#define OPTION_CONVERT_IMPL(__cls) \
21
bool Option::is##__cls##Option() const { return kind == OptionKind##__cls; } \
22
__cls##Option* Option::as##__cls##Option() { \
23
MOZ_ASSERT(is##__cls##Option()); \
24
return static_cast<__cls##Option*>(this); \
25
} \
26
const __cls##Option* Option::as##__cls##Option() const { \
27
return const_cast<Option*>(this)->as##__cls##Option(); \
28
}
29
30
ValuedOption* Option::asValued() {
31
MOZ_ASSERT(isValued());
32
return static_cast<ValuedOption*>(this);
33
}
34
35
const ValuedOption* Option::asValued() const {
36
return const_cast<Option*>(this)->asValued();
37
}
38
39
OPTION_CONVERT_IMPL(Bool)
40
OPTION_CONVERT_IMPL(String)
41
OPTION_CONVERT_IMPL(Int)
42
OPTION_CONVERT_IMPL(MultiString)
43
44
void OptionParser::setArgTerminatesOptions(const char* name, bool enabled) {
45
findArgument(name)->setTerminatesOptions(enabled);
46
}
47
48
void OptionParser::setArgCapturesRest(const char* name) {
49
MOZ_ASSERT(restArgument == -1,
50
"only one argument may be set to capture the rest");
51
restArgument = findArgumentIndex(name);
52
MOZ_ASSERT(restArgument != -1,
53
"unknown argument name passed to setArgCapturesRest");
54
}
55
56
OptionParser::Result OptionParser::error(const char* fmt, ...) {
57
va_list args;
58
va_start(args, fmt);
59
fprintf(stderr, "Error: ");
60
vfprintf(stderr, fmt, args);
61
va_end(args);
62
fputs("\n\n", stderr);
63
return ParseError;
64
}
65
66
/* Quick and dirty paragraph printer. */
67
static void PrintParagraph(const char* text, unsigned startColno,
68
const unsigned limitColno, bool padFirstLine) {
69
unsigned colno = startColno;
70
unsigned indent = 0;
71
const char* it = text;
72
73
if (padFirstLine) {
74
printf("%*s", startColno, "");
75
}
76
77
/* Skip any leading spaces. */
78
while (*it != '\0' && unicode::IsSpace(*it)) {
79
++it;
80
}
81
82
while (*it != '\0') {
83
MOZ_ASSERT(!unicode::IsSpace(*it) || *it == '\n');
84
85
/* Delimit the current token. */
86
const char* limit = it;
87
while (!unicode::IsSpace(*limit) && *limit != '\0') {
88
++limit;
89
}
90
91
/*
92
* If the current token is longer than the available number of columns,
93
* then make a line break before printing the token.
94
*/
95
size_t tokLen = limit - it;
96
if (tokLen + colno >= limitColno) {
97
printf("\n%*s%.*s", startColno + indent, "", int(tokLen), it);
98
colno = startColno + tokLen;
99
} else {
100
printf("%.*s", int(tokLen), it);
101
colno += tokLen;
102
}
103
104
switch (*limit) {
105
case '\0':
106
return;
107
case ' ':
108
putchar(' ');
109
colno += 1;
110
it = limit;
111
while (*it == ' ') {
112
++it;
113
}
114
break;
115
case '\n':
116
/* |text| wants to force a newline here. */
117
printf("\n%*s", startColno, "");
118
colno = startColno;
119
it = limit + 1;
120
/* Could also have line-leading spaces. */
121
indent = 0;
122
while (*it == ' ') {
123
putchar(' ');
124
++colno;
125
++indent;
126
++it;
127
}
128
break;
129
default:
130
MOZ_CRASH("unhandled token splitting character in text");
131
}
132
}
133
}
134
135
static const char* OptionFlagsToFormatInfo(char shortflag, bool isValued,
136
size_t* length) {
137
static const char* const fmt[4] = {" -%c --%s ", " --%s ", " -%c --%s=%s ",
138
" --%s=%s "};
139
140
/* How mny chars w/o longflag? */
141
size_t lengths[4] = {strlen(fmt[0]) - 3, strlen(fmt[1]) - 3,
142
strlen(fmt[2]) - 5, strlen(fmt[3]) - 5};
143
int index = isValued ? 2 : 0;
144
if (!shortflag) {
145
index++;
146
}
147
148
*length = lengths[index];
149
return fmt[index];
150
}
151
152
OptionParser::Result OptionParser::printHelp(const char* progname) {
153
const char* prefixEnd = strstr(usage, prognameMeta);
154
if (prefixEnd) {
155
printf("%.*s%s%s\n", int(prefixEnd - usage), usage, progname,
156
prefixEnd + sizeof(prognameMeta) - 1);
157
} else {
158
puts(usage);
159
}
160
161
if (descr) {
162
putchar('\n');
163
PrintParagraph(descr, 2, descrWidth, true);
164
putchar('\n');
165
}
166
167
if (version) {
168
printf("\nVersion: %s\n\n", version);
169
}
170
171
if (!arguments.empty()) {
172
printf("Arguments:\n");
173
174
static const char fmt[] = " %s ";
175
size_t fmtChars = sizeof(fmt) - 2;
176
size_t lhsLen = 0;
177
for (Option* arg : arguments) {
178
lhsLen = std::max(lhsLen, strlen(arg->longflag) + fmtChars);
179
}
180
181
for (Option* arg : arguments) {
182
size_t chars = printf(fmt, arg->longflag);
183
for (; chars < lhsLen; ++chars) {
184
putchar(' ');
185
}
186
PrintParagraph(arg->help, lhsLen, helpWidth, false);
187
putchar('\n');
188
}
189
putchar('\n');
190
}
191
192
if (!options.empty()) {
193
printf("Options:\n");
194
195
/* Calculate sizes for column alignment. */
196
size_t lhsLen = 0;
197
for (Option* opt : options) {
198
size_t longflagLen = strlen(opt->longflag);
199
200
size_t fmtLen;
201
OptionFlagsToFormatInfo(opt->shortflag, opt->isValued(), &fmtLen);
202
203
size_t len = fmtLen + longflagLen;
204
if (opt->isValued()) {
205
len += strlen(opt->asValued()->metavar);
206
}
207
lhsLen = std::max(lhsLen, len);
208
}
209
210
/* Print option help text. */
211
for (Option* opt : options) {
212
size_t fmtLen;
213
const char* fmt =
214
OptionFlagsToFormatInfo(opt->shortflag, opt->isValued(), &fmtLen);
215
size_t chars;
216
if (opt->isValued()) {
217
if (opt->shortflag) {
218
chars = printf(fmt, opt->shortflag, opt->longflag,
219
opt->asValued()->metavar);
220
} else {
221
chars = printf(fmt, opt->longflag, opt->asValued()->metavar);
222
}
223
} else {
224
if (opt->shortflag) {
225
chars = printf(fmt, opt->shortflag, opt->longflag);
226
} else {
227
chars = printf(fmt, opt->longflag);
228
}
229
}
230
for (; chars < lhsLen; ++chars) {
231
putchar(' ');
232
}
233
PrintParagraph(opt->help, lhsLen, helpWidth, false);
234
putchar('\n');
235
}
236
}
237
238
return EarlyExit;
239
}
240
241
OptionParser::Result OptionParser::printVersion() {
242
MOZ_ASSERT(version);
243
printf("%s\n", version);
244
return EarlyExit;
245
}
246
247
OptionParser::Result OptionParser::extractValue(size_t argc, char** argv,
248
size_t* i, char** value) {
249
MOZ_ASSERT(*i < argc);
250
char* eq = strchr(argv[*i], '=');
251
if (eq) {
252
*value = eq + 1;
253
if (*value[0] == '\0') {
254
return error("A value is required for option %.*s", (int)(eq - argv[*i]),
255
argv[*i]);
256
}
257
return Okay;
258
}
259
260
if (argc == *i + 1) {
261
return error("Expected a value for option %s", argv[*i]);
262
}
263
264
*i += 1;
265
*value = argv[*i];
266
return Okay;
267
}
268
269
OptionParser::Result OptionParser::handleOption(Option* opt, size_t argc,
270
char** argv, size_t* i,
271
bool* optionsAllowed) {
272
if (opt->getTerminatesOptions()) {
273
*optionsAllowed = false;
274
}
275
276
switch (opt->kind) {
277
case OptionKindBool: {
278
if (opt == &helpOption) {
279
return printHelp(argv[0]);
280
}
281
if (opt == &versionOption) {
282
return printVersion();
283
}
284
opt->asBoolOption()->value = true;
285
return Okay;
286
}
287
/*
288
* Valued options are allowed to specify their values either via
289
* successive arguments or a single --longflag=value argument.
290
*/
291
case OptionKindString: {
292
char* value = nullptr;
293
if (Result r = extractValue(argc, argv, i, &value)) {
294
return r;
295
}
296
opt->asStringOption()->value = value;
297
return Okay;
298
}
299
case OptionKindInt: {
300
char* value = nullptr;
301
if (Result r = extractValue(argc, argv, i, &value)) {
302
return r;
303
}
304
opt->asIntOption()->value = atoi(value);
305
return Okay;
306
}
307
case OptionKindMultiString: {
308
char* value = nullptr;
309
if (Result r = extractValue(argc, argv, i, &value)) {
310
return r;
311
}
312
StringArg arg(value, *i);
313
return opt->asMultiStringOption()->strings.append(arg) ? Okay : Fail;
314
}
315
default:
316
MOZ_CRASH("unhandled option kind");
317
}
318
}
319
320
OptionParser::Result OptionParser::handleArg(size_t argc, char** argv,
321
size_t* i, bool* optionsAllowed) {
322
if (nextArgument >= arguments.length()) {
323
return error("Too many arguments provided");
324
}
325
326
Option* arg = arguments[nextArgument];
327
328
if (arg->getTerminatesOptions()) {
329
*optionsAllowed = false;
330
}
331
332
switch (arg->kind) {
333
case OptionKindString:
334
arg->asStringOption()->value = argv[*i];
335
nextArgument += 1;
336
return Okay;
337
case OptionKindMultiString: {
338
// Don't advance the next argument -- there can only be one (final)
339
// variadic argument.
340
StringArg value(argv[*i], *i);
341
return arg->asMultiStringOption()->strings.append(value) ? Okay : Fail;
342
}
343
default:
344
MOZ_CRASH("unhandled argument kind");
345
}
346
}
347
348
OptionParser::Result OptionParser::parseArgs(int inputArgc, char** argv) {
349
MOZ_ASSERT(inputArgc >= 0);
350
size_t argc = inputArgc;
351
// Permit a "no more options" capability, like |--| offers in many shell
352
// interfaces.
353
bool optionsAllowed = true;
354
355
for (size_t i = 1; i < argc; ++i) {
356
char* arg = argv[i];
357
Result r;
358
/* Note: solo dash option is actually a 'stdin' argument. */
359
if (arg[0] == '-' && arg[1] != '\0' && optionsAllowed) {
360
/* Option. */
361
Option* opt;
362
if (arg[1] == '-') {
363
if (arg[2] == '\0') {
364
/* End of options */
365
optionsAllowed = false;
366
nextArgument = restArgument;
367
continue;
368
} else {
369
/* Long option. */
370
opt = findOption(arg + 2);
371
if (!opt) {
372
return error("Invalid long option: %s", arg);
373
}
374
}
375
} else {
376
/* Short option */
377
if (arg[2] != '\0') {
378
return error("Short option followed by junk: %s", arg);
379
}
380
opt = findOption(arg[1]);
381
if (!opt) {
382
return error("Invalid short option: %s", arg);
383
}
384
}
385
386
r = handleOption(opt, argc, argv, &i, &optionsAllowed);
387
} else {
388
/* Argument. */
389
r = handleArg(argc, argv, &i, &optionsAllowed);
390
}
391
392
if (r != Okay) {
393
return r;
394
}
395
}
396
return Okay;
397
}
398
399
void OptionParser::setHelpOption(char shortflag, const char* longflag,
400
const char* help) {
401
helpOption.setFlagInfo(shortflag, longflag, help);
402
}
403
404
bool OptionParser::getHelpOption() const { return helpOption.value; }
405
406
bool OptionParser::getBoolOption(char shortflag) const {
407
return findOption(shortflag)->asBoolOption()->value;
408
}
409
410
int OptionParser::getIntOption(char shortflag) const {
411
return findOption(shortflag)->asIntOption()->value;
412
}
413
414
const char* OptionParser::getStringOption(char shortflag) const {
415
return findOption(shortflag)->asStringOption()->value;
416
}
417
418
MultiStringRange OptionParser::getMultiStringOption(char shortflag) const {
419
const MultiStringOption* mso = findOption(shortflag)->asMultiStringOption();
420
return MultiStringRange(mso->strings.begin(), mso->strings.end());
421
}
422
423
bool OptionParser::getBoolOption(const char* longflag) const {
424
return findOption(longflag)->asBoolOption()->value;
425
}
426
427
int OptionParser::getIntOption(const char* longflag) const {
428
return findOption(longflag)->asIntOption()->value;
429
}
430
431
const char* OptionParser::getStringOption(const char* longflag) const {
432
return findOption(longflag)->asStringOption()->value;
433
}
434
435
MultiStringRange OptionParser::getMultiStringOption(
436
const char* longflag) const {
437
const MultiStringOption* mso = findOption(longflag)->asMultiStringOption();
438
return MultiStringRange(mso->strings.begin(), mso->strings.end());
439
}
440
441
OptionParser::~OptionParser() {
442
for (Option* opt : options) {
443
js_delete<Option>(opt);
444
}
445
for (Option* arg : arguments) {
446
js_delete<Option>(arg);
447
}
448
}
449
450
Option* OptionParser::findOption(char shortflag) {
451
for (Option* opt : options) {
452
if (opt->shortflag == shortflag) {
453
return opt;
454
}
455
}
456
457
if (versionOption.shortflag == shortflag) {
458
return &versionOption;
459
}
460
461
return helpOption.shortflag == shortflag ? &helpOption : nullptr;
462
}
463
464
const Option* OptionParser::findOption(char shortflag) const {
465
return const_cast<OptionParser*>(this)->findOption(shortflag);
466
}
467
468
Option* OptionParser::findOption(const char* longflag) {
469
for (Option* opt : options) {
470
const char* target = opt->longflag;
471
if (opt->isValued()) {
472
size_t targetLen = strlen(target);
473
/* Permit a trailing equals sign on the longflag argument. */
474
for (size_t i = 0; i < targetLen; ++i) {
475
if (longflag[i] == '\0' || longflag[i] != target[i]) {
476
goto no_match;
477
}
478
}
479
if (longflag[targetLen] == '\0' || longflag[targetLen] == '=') {
480
return opt;
481
}
482
} else {
483
if (strcmp(target, longflag) == 0) {
484
return opt;
485
}
486
}
487
no_match:;
488
}
489
490
if (strcmp(versionOption.longflag, longflag) == 0) {
491
return &versionOption;
492
}
493
494
return strcmp(helpOption.longflag, longflag) ? nullptr : &helpOption;
495
}
496
497
const Option* OptionParser::findOption(const char* longflag) const {
498
return const_cast<OptionParser*>(this)->findOption(longflag);
499
}
500
501
/* Argument accessors */
502
503
int OptionParser::findArgumentIndex(const char* name) const {
504
for (Option* const* it = arguments.begin(); it != arguments.end(); ++it) {
505
const char* target = (*it)->longflag;
506
if (strcmp(target, name) == 0) {
507
return it - arguments.begin();
508
}
509
}
510
return -1;
511
}
512
513
Option* OptionParser::findArgument(const char* name) {
514
int index = findArgumentIndex(name);
515
return (index == -1) ? nullptr : arguments[index];
516
}
517
518
const Option* OptionParser::findArgument(const char* name) const {
519
int index = findArgumentIndex(name);
520
return (index == -1) ? nullptr : arguments[index];
521
}
522
523
const char* OptionParser::getStringArg(const char* name) const {
524
return findArgument(name)->asStringOption()->value;
525
}
526
527
MultiStringRange OptionParser::getMultiStringArg(const char* name) const {
528
const MultiStringOption* mso = findArgument(name)->asMultiStringOption();
529
return MultiStringRange(mso->strings.begin(), mso->strings.end());
530
}
531
532
/* Option builders */
533
534
bool OptionParser::addIntOption(char shortflag, const char* longflag,
535
const char* metavar, const char* help,
536
int defaultValue) {
537
if (!options.reserve(options.length() + 1)) {
538
return false;
539
}
540
IntOption* io =
541
js_new<IntOption>(shortflag, longflag, help, metavar, defaultValue);
542
if (!io) {
543
return false;
544
}
545
options.infallibleAppend(io);
546
return true;
547
}
548
549
bool OptionParser::addBoolOption(char shortflag, const char* longflag,
550
const char* help) {
551
if (!options.reserve(options.length() + 1)) {
552
return false;
553
}
554
BoolOption* bo = js_new<BoolOption>(shortflag, longflag, help);
555
if (!bo) {
556
return false;
557
}
558
options.infallibleAppend(bo);
559
return true;
560
}
561
562
bool OptionParser::addStringOption(char shortflag, const char* longflag,
563
const char* metavar, const char* help) {
564
if (!options.reserve(options.length() + 1)) {
565
return false;
566
}
567
StringOption* so = js_new<StringOption>(shortflag, longflag, help, metavar);
568
if (!so) {
569
return false;
570
}
571
options.infallibleAppend(so);
572
return true;
573
}
574
575
bool OptionParser::addMultiStringOption(char shortflag, const char* longflag,
576
const char* metavar, const char* help) {
577
if (!options.reserve(options.length() + 1)) {
578
return false;
579
}
580
MultiStringOption* mso =
581
js_new<MultiStringOption>(shortflag, longflag, help, metavar);
582
if (!mso) {
583
return false;
584
}
585
options.infallibleAppend(mso);
586
return true;
587
}
588
589
/* Argument builders */
590
591
bool OptionParser::addOptionalStringArg(const char* name, const char* help) {
592
if (!arguments.reserve(arguments.length() + 1)) {
593
return false;
594
}
595
StringOption* so = js_new<StringOption>(1, name, help, (const char*)nullptr);
596
if (!so) {
597
return false;
598
}
599
arguments.infallibleAppend(so);
600
return true;
601
}
602
603
bool OptionParser::addOptionalMultiStringArg(const char* name,
604
const char* help) {
605
MOZ_ASSERT_IF(!arguments.empty(), !arguments.back()->isVariadic());
606
if (!arguments.reserve(arguments.length() + 1)) {
607
return false;
608
}
609
MultiStringOption* mso =
610
js_new<MultiStringOption>(1, name, help, (const char*)nullptr);
611
if (!mso) {
612
return false;
613
}
614
arguments.infallibleAppend(mso);
615
return true;
616
}