@@ -121,15 +121,13 @@ type Pattern = ref object of RootObj
121
121
children: seq [Pattern]
122
122
gen_class(Pattern)
123
123
124
- type LeafPattern = ref object of Pattern
125
- # # Leaf/terminal node of a pattern tree.
126
- gen_class(LeafPattern)
124
+ type ChildPattern = ref object of Pattern
125
+ gen_class(ChildPattern)
127
126
128
- type BranchPattern = ref object of Pattern
129
- # # Branch/inner node of a pattern tree.
130
- gen_class(BranchPattern)
127
+ type ParentPattern = ref object of Pattern
128
+ gen_class(ParentPattern)
131
129
132
- type Argument = ref object of LeafPattern
130
+ type Argument = ref object of ChildPattern
133
131
gen_class(Argument)
134
132
135
133
proc argument(name: string , value = val()): Argument =
@@ -141,7 +139,7 @@ gen_class(Command)
141
139
proc command(name: string , value = val(false )): Command =
142
140
Command(m_name: name, value: value)
143
141
144
- type Option = ref object of LeafPattern
142
+ type Option = ref object of ChildPattern
145
143
short: string
146
144
long: string
147
145
argcount: int
@@ -155,32 +153,32 @@ proc option(short, long: string = nil, argcount = 0,
155
153
if value.kind == vkBool and value.bool_v == false and argcount > 0 :
156
154
result .value = val()
157
155
158
- type Required = ref object of BranchPattern
156
+ type Required = ref object of ParentPattern
159
157
gen_class(Required)
160
158
161
159
proc required(children: openarray [Pattern]) : Required =
162
160
Required(children: @ children)
163
161
164
- type Optional = ref object of BranchPattern
162
+ type Optional = ref object of ParentPattern
165
163
gen_class(Optional)
166
164
167
165
proc optional(children: openarray [Pattern]) : Optional =
168
166
Optional(children: @ children)
169
167
170
- type OptionsShortcut = ref object of Optional
168
+ type AnyOptions = ref object of Optional
171
169
# # Marker/placeholder for [options] shortcut.
172
- gen_class(OptionsShortcut )
170
+ gen_class(AnyOptions )
173
171
174
- proc options_shortcut (children: openarray [Pattern]) : OptionsShortcut =
175
- OptionsShortcut (children: @ children)
172
+ proc any_options (children: openarray [Pattern]) : AnyOptions =
173
+ AnyOptions (children: @ children)
176
174
177
- type OneOrMore = ref object of BranchPattern
175
+ type OneOrMore = ref object of ParentPattern
178
176
gen_class(OneOrMore)
179
177
180
178
proc one_or_more(children: openarray [Pattern]) : OneOrMore =
181
179
OneOrMore(children: @ children)
182
180
183
- type Either = ref object of BranchPattern
181
+ type Either = ref object of ParentPattern
184
182
gen_class(Either)
185
183
186
184
proc either(children: seq [Pattern]) : Either =
@@ -224,18 +222,17 @@ method fix_identities(self: Pattern, uniq: seq[Pattern]) =
224
222
method fix_identities(self: Pattern) =
225
223
self.fix_identities(self.flat([]).deduplicate())
226
224
227
- method transform(pattern: Pattern): Either =
228
- # # Expand pattern into an (almost) equivalent one, but with single Either.
229
- # #
230
- # # Example: ((-a | -b) (-c | -d)) => (-a -c | -a -d | -b -c | -b -d)
231
- # # Quirks: [-a] => (-a), (-a...) => (-a -a)
225
+ method either(self: Pattern): Either =
226
+ # # Transform pattern into an equivalent, with only top-level Either.
227
+ # Currently the pattern will not be equivalent, but more "narrow",
228
+ # although good enough to reason about list arguments.
232
229
var result : seq [seq [Pattern]] = @ []
233
- var groups: seq [seq [Pattern]] = @ [@ [pattern ]]
230
+ var groups: seq [seq [Pattern]] = @ [@ [self ]]
234
231
while groups.len > 0 :
235
232
var children = groups[0 ]
236
233
groups.delete()
237
234
var classes = children.map_it(string , it.class)
238
- var parents = [" Required" , " Optional" , " OptionsShortcut " ,
235
+ var parents = [" Required" , " Optional" , " AnyOptions " ,
239
236
" Either" , " OneOrMore" ]
240
237
if parents.any_it(it in classes):
241
238
var child: Pattern
@@ -259,7 +256,7 @@ method transform(pattern: Pattern): Either =
259
256
method fix_repeating_arguments(self: Pattern) =
260
257
# # Fix elements that should accumulate/increment values.
261
258
var either: seq [seq [Pattern]] = @ []
262
- for child in transform( self) .children:
259
+ for child in self.either .children:
263
260
either.add(@ (child.children))
264
261
for cas in either:
265
262
for e in cas:
@@ -280,17 +277,17 @@ method fix(self: Pattern) =
280
277
self.fix_repeating_arguments()
281
278
282
279
283
- method str(self: LeafPattern ): string =
280
+ method str(self: ChildPattern ): string =
284
281
" $#($#, $#)" .format(self.class, self.name.str, self.value.str)
285
282
286
- method flat(self: LeafPattern , types: openarray [string ]): seq [Pattern] =
283
+ method flat(self: ChildPattern , types: openarray [string ]): seq [Pattern] =
287
284
if types.len == 0 or self.class in types: @ [Pattern(self)] else : @ []
288
285
289
- method single_match(self: LeafPattern ,
286
+ method single_match(self: ChildPattern ,
290
287
left: seq [Pattern]) : SingleMatchResult =
291
288
assert false ; nil
292
289
293
- method match(self: LeafPattern , left: seq [Pattern],
290
+ method match(self: ChildPattern , left: seq [Pattern],
294
291
collected: seq [Pattern] = @ []) : MatchResult =
295
292
var m: SingleMatchResult
296
293
try :
@@ -320,10 +317,10 @@ method match(self: LeafPattern, left: seq[Pattern],
320
317
return (true , left2, collected & @ [match])
321
318
322
319
323
- method str(self: BranchPattern ): string =
320
+ method str(self: ParentPattern ): string =
324
321
" $#($#)" .format(self.class, self.children.str)
325
322
326
- method flat(self: BranchPattern , types: openarray [string ]): seq [Pattern] =
323
+ method flat(self: ParentPattern , types: openarray [string ]): seq [Pattern] =
327
324
if self.class in types:
328
325
return @ [Pattern(self)]
329
326
result = new_seq[Pattern]()
@@ -448,31 +445,27 @@ method match(self: Either, left: seq[Pattern],
448
445
return (false, left, collected)
449
446
450
447
451
- type Tokens = ref object
448
+ type TokenStream = ref object
452
449
tokens: seq[string]
453
450
error: ref Exception
454
451
455
- proc `@`(tokens: Tokens): var seq[string] = tokens.tokens
456
-
457
- proc tokens(source: seq[string],
458
- error: ref Exception = new_exception(DocoptExit, "")): Tokens =
459
- Tokens(tokens: source, error: error)
452
+ proc `@`(tokens: TokenStream): var seq[string] = tokens.tokens
460
453
461
- proc tokens_from_pattern (source: string): Tokens =
462
- var source = source.replacef(re"([\[\]\(\)\|]|\.\.\.)", r" $1 " )
463
- var tokens = source.split_inc(re"\s+|(\S*<.*?>)").filter_it(it.len > 0)
464
- tokens(tokens, new_exception(DocoptLanguageError, "") )
454
+ proc token_stream (source: seq[ string], error: ref Exception ): TokenStream =
455
+ TokenStream(tokens: source, error: error )
456
+ proc token_stream(source: string, error: ref Exception): TokenStream =
457
+ token_stream(source.split(), error )
465
458
466
- proc current(self: Tokens ): string =
459
+ proc current(self: TokenStream ): string =
467
460
if @self.len > 0:
468
461
result = @self[0]
469
462
470
- proc move(self: Tokens ): string =
463
+ proc move(self: TokenStream ): string =
471
464
result = self.current
472
465
@self.delete()
473
466
474
467
475
- proc parse_long(tokens: Tokens , options: var seq[Option]): seq[Pattern] =
468
+ proc parse_long(tokens: TokenStream , options: var seq[Option]): seq[Pattern] =
476
469
## long ::= '--' chars [ ( ' ' | '=' ) chars ] ;
477
470
var (long, eq, v) = tokens.move().partition("=")
478
471
assert long.starts_with("--")
@@ -502,7 +495,7 @@ proc parse_long(tokens: Tokens, options: var seq[Option]): seq[Pattern] =
502
495
raise tokens.error
503
496
else:
504
497
if value.kind == vkNone:
505
- if tokens.current in [ nil, "--"] :
498
+ if tokens.current == nil:
506
499
tokens.error.msg = "$# requires argument".format(o.long)
507
500
raise tokens.error
508
501
value = val(tokens.move())
@@ -511,7 +504,7 @@ proc parse_long(tokens: Tokens, options: var seq[Option]): seq[Pattern] =
511
504
@[Pattern(o)]
512
505
513
506
514
- proc parse_shorts(tokens: Tokens , options: var seq[Option]): seq[Pattern] =
507
+ proc parse_shorts(tokens: TokenStream , options: var seq[Option]): seq[Pattern] =
515
508
## shorts ::= '-' ( chars )* [ [ ' ' ] chars ] ;
516
509
var token = tokens.move()
517
510
assert token.starts_with("-") and not token.starts_with("--")
@@ -537,7 +530,7 @@ proc parse_shorts(tokens: Tokens, options: var seq[Option]): seq[Pattern] =
537
530
var value = val()
538
531
if o.argcount != 0:
539
532
if left == "":
540
- if tokens.current in [ nil, "--"] :
533
+ if tokens.current == nil:
541
534
tokens.error.msg = "$# requires argument".format(short)
542
535
raise tokens.error
543
536
value = val(tokens.move())
@@ -549,20 +542,23 @@ proc parse_shorts(tokens: Tokens, options: var seq[Option]): seq[Pattern] =
549
542
result.add(o)
550
543
551
544
552
- proc parse_expr(tokens: Tokens , options: var seq[Option]): seq[Pattern]
545
+ proc parse_expr(tokens: TokenStream , options: var seq[Option]): seq[Pattern]
553
546
554
547
proc parse_pattern(source: string, options: var seq[Option]): Required =
555
- var tokens = tokens_from_pattern(source)
548
+ var tokens = token_stream(
549
+ source.replacef(re"([\[\]\(\)\|]|\.\.\.)", r" $1 "),
550
+ new_exception(DocoptLanguageError, "")
551
+ )
556
552
var result = parse_expr(tokens, options)
557
553
if tokens.current != nil:
558
554
tokens.error.msg = "unexpected ending: '$#'".format(@tokens.join(" "))
559
555
raise tokens.error
560
556
required(result)
561
557
562
558
563
- proc parse_seq(tokens: Tokens , options: var seq[Option]): seq[Pattern]
559
+ proc parse_seq(tokens: TokenStream , options: var seq[Option]): seq[Pattern]
564
560
565
- proc parse_expr(tokens: Tokens , options: var seq[Option]): seq[Pattern] =
561
+ proc parse_expr(tokens: TokenStream , options: var seq[Option]): seq[Pattern] =
566
562
## expr ::= seq ( '|' seq )* ;
567
563
var sequ = parse_seq(tokens, options)
568
564
if tokens.current != "|":
@@ -576,9 +572,9 @@ proc parse_expr(tokens: Tokens, options: var seq[Option]): seq[Pattern] =
576
572
577
573
578
574
579
- proc parse_atom(tokens: Tokens , options: var seq[Option]): seq[Pattern]
575
+ proc parse_atom(tokens: TokenStream , options: var seq[Option]): seq[Pattern]
580
576
581
- proc parse_seq(tokens: Tokens , options: var seq[Option]): seq[Pattern] =
577
+ proc parse_seq(tokens: TokenStream , options: var seq[Option]): seq[Pattern] =
582
578
## seq ::= ( atom [ '...' ] )* ;
583
579
result = @[]
584
580
while tokens.current notin [nil, "]", ")", "|"]:
@@ -590,7 +586,7 @@ proc parse_seq(tokens: Tokens, options: var seq[Option]): seq[Pattern] =
590
586
result.add(atom)
591
587
592
588
593
- proc parse_atom(tokens: Tokens , options: var seq[Option]): seq[Pattern] =
589
+ proc parse_atom(tokens: TokenStream , options: var seq[Option]): seq[Pattern] =
594
590
## atom ::= '(' expr ')' | '[' expr ']' | 'options'
595
591
## | long | shorts | argument | command ;
596
592
var token = tokens.current
@@ -613,7 +609,7 @@ proc parse_atom(tokens: Tokens, options: var seq[Option]): seq[Pattern] =
613
609
return @[result]
614
610
elif token == "options":
615
611
discard tokens.move()
616
- return @[Pattern(options_shortcut ([]))]
612
+ return @[Pattern(any_options ([]))]
617
613
elif token.starts_with("--") and token != "--":
618
614
return parse_long(tokens, options)
619
615
elif token.starts_with("-") and token notin ["-", "--"]:
@@ -624,7 +620,7 @@ proc parse_atom(tokens: Tokens, options: var seq[Option]): seq[Pattern] =
624
620
return @[Pattern(command(tokens.move()))]
625
621
626
622
627
- proc parse_argv(tokens: Tokens , options: var seq[Option],
623
+ proc parse_argv(tokens: TokenStream , options: var seq[Option],
628
624
options_first = false): seq[Pattern] =
629
625
## Parse command-line argument vector.
630
626
##
@@ -646,29 +642,30 @@ proc parse_argv(tokens: Tokens, options: var seq[Option],
646
642
result.add(argument(nil, val(tokens.move())))
647
643
648
644
649
- proc parse_section(name: string, source: string): seq[string]
650
-
651
645
proc parse_defaults(doc: string): seq[Option] =
652
- result = @[]
653
- for ss in parse_section("options:", doc):
654
- # FIXME corner case "bla: options: --foo"
655
- var s = ss.partition(":").right # get rid of "options:"
656
- var split = ("\n" & s).split_inc(re"\n[\ \t]*(-\S+?)")
657
- for i in 1 .. split.len div 2:
658
- var s = split[i*2-1] & split[i*2]
659
- if s.starts_with("-"):
660
- result.add(option.option_parse(s))
646
+ var split = doc.split_inc(re"\n\ *(<\S+?>|-\S+?)")
647
+ result = new_seq[Option]()
648
+ for i in 1 .. split.len div 2:
649
+ var s = split[i*2-1] & split[i*2]
650
+ if s.starts_with("-"):
651
+ result.add(option.option_parse(s))
661
652
662
653
663
- proc parse_section(name: string, source: string): seq[string] =
664
- let pattern = re(r"^([^\n]*" & name & r"[^\n]*\n?(?:[ \t].*?(?:\n|$))*)",
665
- {reIgnoreCase, reMultiLine})
666
- @(source.find_all(pattern)).map_it(string, it.strip())
654
+ proc printable_usage(doc: string): string =
655
+ var usage_split = doc.split_inc(re"([Uu][Ss][Aa][Gg][Ee]:)")
656
+ if usage_split.len < 3:
657
+ raise new_exception(DocoptLanguageError,
658
+ "\"usage:\" (case-insensitive) not found.")
659
+ if usage_split.len > 3:
660
+ raise new_exception(DocoptLanguageError,
661
+ "More than one \"usage:\" (case-insensitive).")
662
+ usage_split.delete()
663
+ usage_split.join().split_inc(re"\n\s*\n")[0].strip()
667
664
668
665
669
- proc formal_usage(section : string): string =
670
- var section = section.partition(":").right # drop "usage:"
671
- var pu = section.split ()
666
+ proc formal_usage(printable_usage : string): string =
667
+ var pu = printable_usage.split()
668
+ pu.delete ()
672
669
var pu0 = pu[0]
673
670
pu.delete()
674
671
"( " & pu.map_it(string, if it == pu0: ") | (" else: it).join(" ") & " )"
@@ -688,25 +685,19 @@ proc docopt_exc(doc: string, argv: seq[string], help: bool, version: string,
688
685
options_first = false): Table[string, Value] =
689
686
690
687
var argv = (if argv.is_nil: command_line_params() else: argv)
691
-
692
- var usage_sections = parse_section("usage:", doc)
693
- if usage_sections.len == 0:
694
- raise new_exception(DocoptLanguageError,
695
- "\"usage:\" (case-insensitive) not found.")
696
- if usage_sections.len > 1:
697
- raise new_exception(DocoptLanguageError,
698
- "More than one \"usage:\" (case-insensitive).")
688
+
699
689
var docopt_exit = new_exception(DocoptExit, "")
700
- docopt_exit.usage = usage_sections[0]
690
+ docopt_exit.usage = printable_usage(doc)
701
691
702
692
var options = parse_defaults(doc)
703
693
var pattern = parse_pattern(formal_usage(docopt_exit.usage), options)
704
694
705
- var argvt = parse_argv(tokens(argv), options, options_first)
695
+ var argvt = parse_argv(token_stream(argv, docopt_exit), options,
696
+ options_first)
706
697
var pattern_options = pattern.flat(["Option"]).deduplicate()
707
- for options_shortcut in pattern.flat(["OptionsShortcut "]):
698
+ for any_options in pattern.flat(["AnyOptions "]):
708
699
var doc_options = parse_defaults(doc).deduplicate()
709
- options_shortcut .children = doc_options.filter_it(
700
+ any_options .children = doc_options.filter_it(
710
701
it notin pattern_options).map_it(Pattern, Pattern(it))
711
702
712
703
extras(help, version, argvt, doc)
0 commit comments