-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcli.toit
More file actions
1605 lines (1322 loc) · 49.8 KB
/
cli.toit
File metadata and controls
1605 lines (1322 loc) · 49.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (C) 2022 Toitware ApS. All rights reserved.
// Use of this source code is governed by an MIT-style license that can be
// found in the package's LICENSE file.
import fs
import host.directory
import host.file
import host.pipe
import io
import log
import uuid show Uuid
import system
import .cache
import .completion_
import .completion-scripts_
import .config
import .help-generator_
import .parser_
import .path_
import .utils_
import .ui
export Ui
export Cache FileStore DirectoryStore
export Config
/**
An object giving access to common operations for CLI programs.
If no ui is given uses $Ui.human.
*/
interface Cli:
constructor
name/string
--ui/Ui?=null
--cache/Cache?=null
--config/Config?=null:
if not ui: ui = Ui.human
return Cli_ name --ui=ui --cache=cache --config=config
/**
The name of the application.
Used to find configurations and caches.
*/
name -> string
/**
The UI object to use for this application.
Output should be written to this object.
*/
ui -> Ui
/** The cache object for this application. */
cache -> Cache
/** The configuration object for this application. */
config -> Config
/**
Returns a new UI object based on the given arguments.
All non-null arguments are used to create the UI object. If an argument is null, the
current value is used.
*/
with -> Cli
--name/string?=null
--ui/Ui?=null
--cache/Cache?=null
--config/Config?=null
/**
An object giving access to common operations for CLI programs.
*/
class Cli_ implements Cli:
/**
The name of the application.
Used to find configurations and caches.
*/
name/string
cache_/Cache? := null
config_/Config? := null
/**
The UI object to use for this application.
Output should be written to this object.
*/
ui/Ui
constructor .name --.ui --cache/Cache? --config/Config?:
cache_ = cache
config_ = config
/** The cache object for this application. */
cache -> Cache:
if not cache_: cache_ = Cache --app-name=name
return cache_
/** The configuration object for this application. */
config -> Config:
if not config_: config_ = Config --app-name=name
return config_
with -> Cli
--name/string?=null
--ui/Ui?=null
--cache/Cache?=null
--config/Config?=null:
return Cli_
name or this.name
--ui=ui or this.ui
--cache=cache or cache_
--config=config or config_
/**
A command.
The main program is a command, and so are all subcommands.
*/
class Command:
/**
The name of the command.
The name of the root command is used as application name for the $Cli.
*/
name/string
/**
The usage string of this command.
Usually constructed from the name and the arguments of the command. However, in
some cases, a different (shorter) usage string is desired.
*/
usage_/string?
/** A short (one line) description of the command. */
short-help_/string?
/** A longer description of the command. */
help_/string?
/** Examples of the command. */
examples_/List
/** Aliases of the command. */
aliases_/List
/** Options to the command. */
options_/List := ?
/** The rest arguments. */
rest_/List
/** Whether this command should show up in the help. */
is-hidden_/bool
/**
Subcommands.
Use $add to add new subcommands.
*/
subcommands_/List
/**
The function to invoke when this command is executed.
May be null, in which case at least one subcommand must be specified.
*/
run-callback_/Lambda?
/**
Constructs a new command.
The $name is only optional for the root command, which represents the program. All
subcommands must have a name.
The $usage is usually constructed from the name and the arguments of the command, but can
be provided explicitly if a different usage string is desired.
The $help is a longer description of the command that can span multiple lines. Use
indented lines to continue paragraphs (just like toitdoc). The first paragraph of the
$help is used as short help, and should have meaningful content on its own.
The $run callback is invoked when the command is executed. It is given an
$Invocation object. If $run is null, then at least one subcommand must be added
to this command.
*/
constructor name --usage/string?=null --help/string?=null --examples/List=[] \
--aliases/List=[] --options/List=[] --rest/List=[] --subcommands/List=[] --hidden/bool=false \
--run/Lambda?=null:
return Command.private name --usage=usage --help=help --examples=examples \
--aliases=aliases --options=options --rest=rest --subcommands=subcommands --hidden=hidden \
--run=run
/**
Deprecated. Use '--help' instead of '--short-help'.
*/
constructor name --usage/string?=null --short-help/string --examples/List=[] \
--aliases/List=[] --options/List=[] --rest/List=[] --subcommands/List=[] --hidden/bool=false \
--run/Lambda?=null:
return Command.private name --usage=usage --short-help=short-help --examples=examples \
--aliases=aliases --options=options --rest=rest --subcommands=subcommands --hidden=hidden \
--run=run
/**
Deprecated. Use '--help' instead of '--long-help'.
*/
constructor name --usage/string?=null --long-help/string --examples/List=[] \
--aliases/List=[] --options/List=[] --rest/List=[] --subcommands/List=[] --hidden/bool=false \
--run/Lambda?=null:
return Command.private name --usage=usage --help=long-help --examples=examples \
--aliases=aliases --options=options --rest=rest --subcommands=subcommands --hidden=hidden \
--run=run
/**
Deprecated. Use '--help' with a meaningful first paragraph instead of '--short-help' and '--long-help'.
*/
constructor name --usage/string?=null --short-help/string --long-help/string --examples/List=[] \
--aliases/List=[] --options/List=[] --rest/List=[] --subcommands/List=[] --hidden/bool=false \
--run/Lambda?=null:
return Command.private name --usage=usage --short-help=short-help --help=long-help --examples=examples \
--aliases=aliases --options=options --rest=rest --subcommands=subcommands --hidden=hidden \
--run=run
constructor.private .name --usage/string?=null --short-help/string?=null --help/string?=null --examples/List=[] \
--aliases/List=[] --options/List=[] --rest/List=[] --subcommands/List=[] --hidden/bool=false \
--run/Lambda?=null:
usage_ = usage
short-help_ = short-help
help_ = help
examples_ = examples
aliases_ = aliases
options_ = options
rest_ = rest
subcommands_ = subcommands
run-callback_ = run
is-hidden_ = hidden
if not subcommands.is-empty and not rest.is-empty:
throw "Cannot have both subcommands and rest arguments."
if run and not subcommands.is-empty:
throw "Cannot have both a run callback and subcommands."
hash-code -> int:
return name.hash-code
/**
Adds a subcommand to this command.
Subcommands can also be provided in the constructor.
It is an error to add a subcommand to a command that has rest arguments.
It is an error to add a subcommand to a command that has a run callback.
*/
add command/Command:
if not rest_.is-empty:
throw "Cannot add subcommands to a command with rest arguments."
if run-callback_:
throw "Cannot add subcommands to a command with a run callback."
subcommands_.add command
/** Returns the help string of this command. */
help --invoked-command/string=system.program-name -> string:
path := Path this --invoked-command=invoked-command
generator := HelpGenerator path
generator.build-all
return generator.to-string
/**
The short help string of this command.
This is the first paragraph of the help string.
*/
short-help -> string:
if help := short-help_:
return help
if long-help := help_:
// Take the first paragraph (potentially multiple lines) of the long help.
paragraph-index := long-help.index-of "\n\n"
if paragraph-index == -1:
return long-help.trim
return long-help[..paragraph-index].trim
return ""
/** Returns the usage string of this command. */
usage --invoked-command/string=system.program-name -> string:
path := Path this --invoked-command=invoked-command
generator := HelpGenerator path
generator.build-usage --as-section=false
return generator.to-string
/**
Runs this command.
Parses the given $arguments and then invokes the command or one of its subcommands
with the $Invocation output.
The $invoked-command is used only for the usage message in case of an
error. It defaults to $system.program-name.
If no $cli is given, the arguments are parsed for `--verbose`, `--verbosity-level` and
`--output-format` to create the appropriate UI object. If a $cli object is given,
then these arguments are ignored.
The $add-ui-help flag is used to determine whether to include help for `--verbose`, ...
in the help output. By default it is active if no $cli is provided.
*/
run arguments/List -> none
--invoked-command=system.program-name
--cli/Cli?=null
--add-ui-help/bool=(not cli)
--add-completion/bool=true:
if add-completion:
add-completion-command_ --program-path=invoked-command
// Handle __complete requests before any other processing.
if add-completion and not arguments.is-empty and arguments[0] == "__complete":
if add-ui-help: add-ui-options_
completion-args := arguments[1..]
if not completion-args.is-empty and completion-args[0] == "--":
completion-args = completion-args[1..]
result := complete_ this completion-args
result.candidates.do: | candidate/CompletionCandidate_ |
print candidate.to-string
print ":$result.directive"
return
if not cli:
ui := create-ui-from-args_ arguments
log.set-default (ui.logger --name=name)
if add-ui-help:
add-ui-options_
cli = Cli_ name --ui=ui --cache=null --config=null
parser := Parser_ --invoked-command=invoked-command
parser.parse this arguments: | path/Path parameters/Parameters |
invocation := Invocation.private_ cli path.commands parameters
invocation.command.run-callback_.call invocation
add-completion-command_ --program-path/string:
// Don't add if the user already has a "completion" subcommand.
if find-subcommand_ "completion": return
// Can't add subcommands to a command with rest args or a run callback
// that already has subcommands handled.
if run-callback_: return
prog-name := basename_ program-path
completion-command := Command "completion"
--help="""
Generate shell completion scripts.
To enable completions, add the appropriate command to your shell
configuration:
Bash (~/.bashrc):
source <($program-path completion bash)
Zsh (~/.zshrc):
source <($program-path completion zsh)
Fish (~/.config/fish/config.fish):
$program-path completion fish | source
Alternatively, install the script to the system completion directory
so it loads automatically for all sessions:
Bash:
$program-path completion bash > /etc/bash_completion.d/$prog-name
Zsh:
$program-path completion zsh > \$fpath[1]/_$prog-name
Fish:
$program-path completion fish > ~/.config/fish/completions/$(prog-name).fish
PowerShell:
$program-path completion powershell >> \$PROFILE"""
--rest=[
OptionEnum "shell" ["bash", "zsh", "fish", "powershell"]
--help="The shell to generate completions for."
--required,
]
--run=:: | invocation/Invocation |
shell := invocation["shell"]
script/string := ?
if shell == "bash":
script = bash-completion-script_ --program-path=program-path
else if shell == "zsh":
script = zsh-completion-script_ --program-path=program-path
else if shell == "fish":
script = fish-completion-script_ --program-path=program-path
else if shell == "powershell":
script = powershell-completion-script_ --program-path=program-path
else:
unreachable
print script
subcommands_.add completion-command
add-ui-options_:
has-output-format-option := false
has-verbose-flag := false
has-verbosity-level-option := false
options_.do: | option/Option |
if option.name == "output-format": has-output-format-option = true
if option.name == "verbose": has-verbose-flag = true
if option.name == "verbosity-level": has-verbosity-level-option = true
is-copied := false
if not has-output-format-option:
options_ = options_.copy
is-copied = true
option := OptionEnum "output-format"
["human", "plain", "json"]
--help="Specify the format used when printing to the console."
--default="human"
options_.add option
if not has-verbose-flag:
if not is-copied:
options_ = options_.copy
is-copied = true
option := Flag "verbose"
--help="Enable verbose output. Shorthand for --verbosity-level=verbose."
--default=false
options_.add option
if not has-verbosity-level-option:
if not is-copied:
options_ = options_.copy
is-copied = true
option := OptionEnum "verbosity-level"
["debug", "info", "verbose", "quiet", "silent"]
--help="Specify the verbosity level."
--default="info"
options_.add option
/**
Returns a shell completion script for this command.
The $shell must be one of "bash", "zsh", or "fish".
The $program-path is the path to the executable. The basename of this path
is used to register the completion with the shell.
*/
completion-script --shell/string --program-path/string=name -> string:
if shell == "bash":
return bash-completion-script_ --program-path=program-path
if shell == "zsh":
return zsh-completion-script_ --program-path=program-path
if shell == "fish":
return fish-completion-script_ --program-path=program-path
throw "Unknown shell: $shell. Supported shells: bash, zsh, fish."
/**
Checks this command and all subcommands for errors.
If an error is found, an exception is thrown with a message describing the error.
Typically, a call to this method is added to the program's main function in an
assert block.
*/
check --invoked-command=system.program-name:
path := Path this --invoked-command=invoked-command
check_ --path=path --invoked-command=invoked-command
are-prefix-of-each-other_ str1/string str2/string -> bool:
m := min str1.size str2.size
return str1[..m] == str2[..m]
/**
Checks this command and all subcommands.
The $path, a list of strings, provides the sequence that was used to reach this command.
The $outer-long-options and $outer-short-options are the options that are
available through supercommands.
*/
check_ --path/Path --invoked-command/string --outer-long-options/Set={} --outer-short-options/Set={}:
examples_.do: it as Example
aliases_.do: it as string
long-options := {}
short-options := {}
options_.do: | option/Option |
if long-options.contains option.name:
throw "Ambiguous option of '$path.to-string': --$option.name."
if outer-long-options.contains option.name:
throw "Ambiguous option of '$path.to-string': --$option.name conflicts with global option."
long-options.add option.name
if option.short-name:
if (short-options.any: are-prefix-of-each-other_ it option.short-name):
throw "Ambiguous option of '$path.to-string': -$option.short-name."
if (outer-short-options.any: are-prefix-of-each-other_ it option.short-name):
throw "Ambiguous option of '$path.to-string': -$option.short-name conflicts with global option."
short-options.add option.short-name
have-seen-optional-rest := false
for i := 0; i < rest_.size; i++:
option/Option := rest_[i]
if option.is-multi and not i == rest_.size - 1:
throw "Multi-option '$option.name' of '$path.to-string' must be the last rest argument."
if long-options.contains option.name:
throw "Rest name '$option.name' of '$path.to-string' already used."
if outer-long-options.contains option.name:
throw "Rest name '$option.name' of '$path.to-string' already a global option."
if have-seen-optional-rest and option.is-required:
throw "Required rest argument '$option.name' of '$path.to-string' cannot follow optional rest argument."
if option.is-hidden:
throw "Rest argument '$option.name' of '$path.to-string' cannot be hidden."
have-seen-optional-rest = not option.is-required
long-options.add option.name
if not long-options.is-empty:
// Make a copy first.
outer-long-options = outer-long-options.map: it
outer-long-options.add-all long-options
if not short-options.is-empty:
// Make a copy first.
outer-short-options = outer-short-options.map: it
outer-short-options.add-all short-options
subnames := {}
subcommands_.do: | command/Command |
names := [command.name] + command.aliases_
names.do: | name/string? |
if subnames.contains name:
throw "Ambiguous subcommand of '$path.to-string': '$name'."
subnames.add name
command.check_
--path=(path + command)
--invoked-command=invoked-command
--outer-long-options=outer-long-options
--outer-short-options=outer-short-options
// We allow a command with a run callback if all subcommands are hidden.
// As such, we could also allow commands without either. If desired, it should be
// safe to remove the following check.
if subcommands_.is-empty and not run-callback_:
throw "Command '$path.to-string' has no subcommands and no run callback."
if not examples_.is-empty:
generator := HelpGenerator path
examples_.do: | example/Example |
generator.build-example_ example --example-path=path
find-subcommand_ name/string -> Command?:
subcommands_.do: | command/Command |
if command.name == name or command.aliases_.contains name:
return command
return null
/**
A completion candidate returned by completion callbacks.
Contains a $value and an optional $description. The $description is shown
alongside the value in shells that support it (zsh, fish).
*/
class CompletionCandidate:
/** The completion value. */
value/string
/**
An optional description shown alongside the value.
For example, if the value is a UUID, the description could be the
human-readable name of the entity.
*/
description/string?
/**
Creates a completion candidate with the given $value and optional $description.
*/
constructor .value --.description=null:
/**
Context provided to completion callbacks.
Contains the prefix being completed, the option being completed, the
current command, and the options that have already been seen.
*/
class CompletionContext:
/**
The text the user has typed so far for the value being completed.
*/
prefix/string
/**
The option whose value is being completed.
*/
option/Option
/**
The command that is currently being completed.
*/
command/Command
/**
A map from option name to a list of values that have been provided
for that option so far.
*/
seen-options/Map
constructor.private_ --.prefix --.option --.command --.seen-options:
/**
An option to a command.
Options are used for any input from the command line to the program. They must have unique names,
so that they can be identified in the $Invocation output.
Non-rest options can be used with '--$name' or '-$short-name' (if provided). Rest options are positional
and their name is not exposed to the user except for the help.
*/
abstract class Option:
name/string
short-name/string?
help/string?
is-required/bool
is-hidden/bool
is-multi/bool
should-split-commas/bool
completion-callback_/Lambda?
/** Deprecated. Use '--help' instead of '--short-help'. */
constructor name/string
--default/string?=null
--type/string="string"
--short-name/string?=null
--short-help/string
--required/bool=false
--hidden/bool=false
--multi/bool=false
--split-commas/bool=false
--completion/Lambda?=null:
return OptionString name
--default=default
--type=type
--short-name=short-name
--help=short-help
--required=required
--hidden=hidden
--multi=multi
--split-commas=split-commas
--completion=completion
/** An alias for $OptionString. */
constructor name/string
--default/string?=null
--type/string="string"
--short-name/string?=null
--help/string?=null
--required/bool=false
--hidden/bool=false
--multi/bool=false
--split-commas/bool=false
--completion/Lambda?=null:
return OptionString name
--default=default
--type=type
--short-name=short-name
--help=help
--required=required
--hidden=hidden
--multi=multi
--split-commas=split-commas
--completion=completion
/** Deprecated. Use $help instead. */
short-help -> string?: return help
/**
Creates an option with the given $name.
This constructor is intended to be called from subclasses. Also see $Option.constructor
which is an alias for the $OptionString constructor.
The $name sets the name of the option. It must be unique among all options of a command.
It is also used to extract the parsed value from the $Invocation object. For multi-word
options kebab case ('foo-bar') is recommended. The constructor automatically converts
snake case ('foo_bar') to kebab case. This also means, that it's not possible to
have two options that only differ in their case (kebab and snake).
The $short-name is optional and will normally be a single-character string when provided.
The $help is optional and is used for help output. It should be a full sentence, starting
with a capital letter and ending with a period.
If $required is true, then the option must be provided. Otherwise, it is optional.
If $hidden is true, then the option is not shown in help output. Rest arguments must not be
hidden.
If $multi is true, then the option can be provided multiple times. The parsed value will
be a list of strings.
If $split-commas is true, then $multi must be true too. Values given to this option are then
split on commas. For example, `--option a,b,c` will result in the list `["a", "b", "c"]`.
*/
constructor.from-subclass .name --.short-name --help/string? --required --hidden --multi --split-commas --completion/Lambda?=null:
this.help = help
name = to-kebab name
is-required = required
is-hidden = hidden
is-multi = multi
should-split-commas = split-commas
completion-callback_ = completion
if name.contains "=" or name.starts-with "no-": throw "Invalid option name: $name"
if short-name and not is-alpha-num-string_ short-name:
throw "Invalid short option name: '$short-name'"
if split-commas and not multi:
throw "--split-commas is only valid for multi options."
if is-hidden and is-required:
throw "Option can't be hidden and required."
/** Deprecated. Use --help instead of '--short-help'. */
constructor.from-subclass .name --.short-name --short-help/string --required --hidden --multi --split-commas --completion/Lambda?=null:
help = short-help
name = to-kebab name
is-required = required
is-hidden = hidden
is-multi = multi
should-split-commas = split-commas
completion-callback_ = completion
if name.contains "=" or name.starts-with "no-": throw "Invalid option name: $name"
if short-name and not is-alpha-num-string_ short-name:
throw "Invalid short option name: '$short-name'"
if split-commas and not multi:
throw "--split_commas is only valid for multi options."
if is-hidden and is-required:
throw "Option can't be hidden and required."
static is-alpha-num-string_ str/string -> bool:
if str.size < 1: return false
str.do --runes: | c |
if not ('a' <= c <= 'z' or 'A' <= c <= 'Z' or '0' <= c <= '9'):
return false
return true
/**
The default value of this option, as a string.
This output is used in the help output.
*/
default-as-string -> string?:
default-value := default
if default-value != null: return default-value.stringify
return null
/** The default value of this option. */
abstract default -> any
/** The type of the option. This property is only used in help output. */
abstract type -> string
/** Whether this option is a flag. */
abstract is-flag -> bool
/**
Parses the given $str and returns the parsed value.
If $for-help-example is true, only performs validation that is valid for examples.
For example, a FileOption would not check that the file exists.
*/
abstract parse str/string --for-help-example/bool=false -> any
/**
Returns the default completion candidates for this option.
Subclasses override this to provide type-specific completions.
For example, $OptionEnum returns its $OptionEnum.values list.
*/
abstract options-for-completion -> List
/**
Returns the completion directive for this option, or null.
If non-null, the completion engine uses this directive instead of computing
one from the candidates. Subclasses like $OptionPath override this to
request file or directory completion from the shell.
*/
completion-directive -> int?:
return null
/**
Returns completion candidates for this option's value.
If a completion callback was provided via `--completion` in the constructor, it is
called with the given $context and must return a list of $CompletionCandidate
objects. Otherwise, the default completions from $options-for-completion are
wrapped as candidates without descriptions.
*/
complete context/CompletionContext -> List:
if completion-callback_: return completion-callback_.call context
return options-for-completion.map: CompletionCandidate it
/**
A string option.
*/
class OptionString extends Option:
default/string?
type/string
/**
Creates a new string option.
The $default value is null.
The $type is set to 'string', but can be changed to something else. The $type is
only used for help output.
See $Option.constructor for the other parameters.
*/
constructor name/string
--.default=null
--.type="string"
--short-name/string?=null
--help/string?=null
--required/bool=false
--hidden/bool=false
--multi/bool=false
--split-commas/bool=false
--completion/Lambda?=null:
if multi and default: throw "Multi option can't have default value."
if required and default: throw "Option can't have default value and be required."
super.from-subclass name --short-name=short-name --help=help \
--required=required --hidden=hidden --multi=multi \
--split-commas=split-commas --completion=completion
/** Deprecated. Use '--help' instead of '--short-help'. */
constructor name/string
--.default=null
--.type="string"
--short-name/string?=null
--short-help/string?
--required/bool=false
--hidden/bool=false
--multi/bool=false
--split-commas/bool=false
--completion/Lambda?=null:
if multi and default: throw "Multi option can't have default value."
if required and default: throw "Option can't have default value and be required."
super.from-subclass name --short-name=short-name --help=short-help \
--required=required --hidden=hidden --multi=multi \
--split-commas=split-commas --completion=completion
is-flag: return false
options-for-completion -> List: return []
parse str/string --for-help-example/bool=false -> string:
return str
/**
An option that must be one of a set of $values.
The $parse function ensures that the value is one of the $values.
The $type defaults to a string enumerating the $values separated by '|'. For
example `OptionEnum("color", ["red", "green", "blue"])` would have a type of
"red|green|blue".
*/
class OptionEnum extends Option:
default/string? := null
values/List
type/string
/**
Creates a new enum option.
The $values list provides the list of valid values for this option.
The $default value is null.
The $type defaults to a string joining all $values with a '|'.
See $Option.constructor for the other parameters.
*/
constructor name/string .values/List
--.default=null
--.type=(values.join "|")
--short-name/string?=null
--help/string?=null
--required/bool=false
--hidden/bool=false
--multi/bool=false
--split-commas/bool=false
--completion/Lambda?=null:
if multi and default: throw "Multi option can't have default value."
if required and default: throw "Option can't have default value and be required."
super.from-subclass name --short-name=short-name --help=help \
--required=required --hidden=hidden --multi=multi \
--split-commas=split-commas --completion=completion
if default and not values.contains default:
throw "Default value of '$name' is not a valid value: $default"
/** Deprecated. Use '--help' instead of '--short-help'. */
constructor name/string .values/List
--.default=null
--.type=(values.join "|")
--short-name/string?=null
--short-help/string?
--required/bool=false
--hidden/bool=false
--multi/bool=false
--split-commas/bool=false
--completion/Lambda?=null:
if multi and default: throw "Multi option can't have default value."
if required and default: throw "Option can't have default value and be required."
super.from-subclass name --short-name=short-name --help=short-help \
--required=required --hidden=hidden --multi=multi \
--split-commas=split-commas --completion=completion
if default and not values.contains default:
throw "Default value of '$name' is not a valid value: $default"
is-flag: return false
options-for-completion -> List: return values
parse str/string --for-help-example/bool=false -> string:
if not values.contains str:
throw "Invalid value for option '$name': '$str'. Valid values are: $(values.join ", ")."
return str
/**
An option that must be an integer value.
The $parse function ensures that the value is an integer.
*/
class OptionInt extends Option:
default/int?
type/string
/**
Creates a new integer option.
The $default value is null.
The $type defaults to "int".
See $Option.constructor for the other parameters.
*/
constructor name/string
--.default=null
--.type="int"
--short-name/string?=null
--help/string?=null
--required/bool=false
--hidden/bool=false
--multi/bool=false
--split-commas/bool=false
--completion/Lambda?=null:
if multi and default: throw "Multi option can't have default value."
if required and default: throw "Option can't have default value and be required."
super.from-subclass name --short-name=short-name --help=help \
--required=required --hidden=hidden --multi=multi \
--split-commas=split-commas --completion=completion
/** Deprecated. Use '--help' instead of '--short-help'. */
constructor name/string
--.default=null
--.type="int"
--short-name/string?=null
--short-help/string?
--required/bool=false
--hidden/bool=false
--multi/bool=false
--split-commas/bool=false
--completion/Lambda?=null:
if multi and default: throw "Multi option can't have default value."
if required and default: throw "Option can't have default value and be required."
super.from-subclass name --short-name=short-name --help=short-help \
--required=required --hidden=hidden --multi=multi \
--split-commas=split-commas --completion=completion
is-flag: return false
options-for-completion -> List: return []
parse str/string --for-help-example/bool=false -> int:
return int.parse str --if-error=:
throw "Invalid integer value for option '$name': '$str'."
/**
An option for patterns.
Patterns are an extension to enums: they allow to specify a prefix to a value.
For example, a pattern `"interval:<duration>"` would allow values like
`"interval:1h"`, `"interval:30m"`, etc.
Both '=' and ':' are allowed as separators between the prefix and the value.
*/
class OptionPatterns extends Option:
default/string?
patterns/List
type/string
constructor name/string .patterns/List
--.default=null
--.type=(patterns.join "|")
--short-name/string?=null
--help/string?=null
--required/bool=false
--hidden/bool=false
--multi/bool=false
--split-commas/bool=false
--completion/Lambda?=null: