Source file cmdliner_cline.ml
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
let err_multi_opt_name_def name a a' =
Cmdliner_base.err_multi_def
~kind:"option name" name Cmdliner_info.Arg.doc a a'
module Amap = Map.Make (Cmdliner_info.Arg)
type arg =
| O of (int * string * (string option)) list
| P of string list
type t = arg Amap.t
let get_arg cl a = try Amap.find a cl with Not_found -> assert false
let opt_arg cl a = match get_arg cl a with O l -> l | _ -> assert false
let pos_arg cl a = match get_arg cl a with P l -> l | _ -> assert false
let actual_args cl a = match get_arg cl a with
| P args -> args
| O l ->
let (_pos, name, value) =
name :: (match value with None -> [] | Some v -> [v])
in
List.concat (List.map extract_args l)
let arg_info_indexes args =
let rec loop optidx posidx cl = function
| [] -> optidx, posidx, cl
| a :: l ->
match Cmdliner_info.Arg.is_pos a with
| true -> loop optidx (a :: posidx) (Amap.add a (P []) cl) l
| false ->
let add t name = match Cmdliner_trie.add t name a with
| `New t -> t
| `Replaced (a', _) -> invalid_arg (err_multi_opt_name_def name a a')
in
let names = Cmdliner_info.Arg.opt_names a in
let optidx = List.fold_left add optidx names in
loop optidx posidx (Amap.add a (O []) cl) l
in
loop Cmdliner_trie.empty [] Amap.empty (Cmdliner_info.Arg.Set.elements args)
let is_opt s = String.length s > 1 && s.[0] = '-'
let is_short_opt s = String.length s = 2 && s.[0] = '-'
let parse_opt_arg s =
let l = String.length s in
if s.[1] <> '-' then
if l = 2 then s, None else
String.sub s 0 2, Some (String.sub s 2 (l - 2))
else try
let i = String.index s '=' in
String.sub s 0 i, Some (String.sub s (i + 1) (l - i - 1))
with Not_found -> s, None
let hint_matching_opt optidx s =
if String.length s <= 2 then [] else
let short_opt, long_opt =
if s.[1] <> '-'
then s, Printf.sprintf "-%s" s
else String.sub s 1 (String.length s - 1), s
in
let short_opt, _ = parse_opt_arg short_opt in
let long_opt, _ = parse_opt_arg long_opt in
let all = Cmdliner_trie.ambiguities optidx "-" in
match List.mem short_opt all, Cmdliner_base.suggest long_opt all with
| false, [] -> []
| false, l -> l
| true, [] -> [short_opt]
| true, l -> if List.mem short_opt l then l else short_opt :: l
let parse_opt_args ~peek_opts optidx cl args =
let rec loop errs k cl pargs = function
| [] -> List.rev errs, cl, List.rev pargs
| "--" :: args -> List.rev errs, cl, (List.rev_append pargs args)
| s :: args ->
if not (is_opt s) then loop errs (k + 1) cl (s :: pargs) args else
let name, value = parse_opt_arg s in
match Cmdliner_trie.find optidx name with
| `Ok a ->
let value, args = match value, Cmdliner_info.Arg.opt_kind a with
| Some v, Cmdliner_info.Arg.Flag when is_short_opt name ->
None, ("-" ^ v) :: args
| Some _, _ -> value, args
| None, Cmdliner_info.Arg.Flag -> value, args
| None, _ ->
match args with
| [] -> None, args
| v :: rest -> if is_opt v then None, args else Some v, rest
in
let arg = O ((k, name, value) :: opt_arg cl a) in
loop errs (k + 1) (Amap.add a arg cl) pargs args
| `Not_found when peek_opts -> loop errs (k + 1) cl pargs args
| `Not_found ->
let hints = hint_matching_opt optidx s in
let err = Cmdliner_base.err_unknown ~kind:"option" ~hints name in
loop (err :: errs) (k + 1) cl pargs args
| `Ambiguous ->
let ambs = Cmdliner_trie.ambiguities optidx name in
let ambs = List.sort compare ambs in
let err = Cmdliner_base.err_ambiguous ~kind:"option" name ~ambs in
loop (err :: errs) (k + 1) cl pargs args
in
let errs, cl, pargs = loop [] 0 cl [] args in
if errs = [] then Ok (cl, pargs) else
let err = String.concat "\n" errs in
Error (err, cl, pargs)
let take_range start stop l =
let rec loop i acc = function
| [] -> List.rev acc
| v :: vs ->
if i < start then loop (i + 1) acc vs else
if i <= stop then loop (i + 1) (v :: acc) vs else
List.rev acc
in
loop 0 [] l
let process_pos_args posidx cl pargs =
if pargs = [] then
let misses = List.filter Cmdliner_info.Arg.is_req posidx in
if misses = [] then Ok cl else
Error (Cmdliner_msg.err_pos_misses misses, cl)
else
let last = List.length pargs - 1 in
let pos rev k = if rev then last - k else k in
let rec loop misses cl max_spec = function
| [] -> misses, cl, max_spec
| a :: al ->
let apos = Cmdliner_info.Arg.pos_kind a in
let rev = Cmdliner_info.Arg.pos_rev apos in
let start = pos rev (Cmdliner_info.Arg.pos_start apos) in
let stop = match Cmdliner_info.Arg.pos_len apos with
| None -> pos rev last
| Some n -> pos rev (Cmdliner_info.Arg.pos_start apos + n - 1)
in
let start, stop = if rev then stop, start else start, stop in
let args = take_range start stop pargs in
let max_spec = max stop max_spec in
let cl = Amap.add a (P args) cl in
let misses = match Cmdliner_info.Arg.is_req a && args = [] with
| true -> a :: misses
| false -> misses
in
loop misses cl max_spec al
in
let misses, cl, max_spec = loop [] cl (-1) posidx in
if misses <> [] then Error (Cmdliner_msg.err_pos_misses misses, cl) else
if last <= max_spec then Ok cl else
let excess = take_range (max_spec + 1) last pargs in
Error (Cmdliner_msg.err_pos_excess excess, cl)
let create ?(peek_opts = false) al args =
let optidx, posidx, cl = arg_info_indexes al in
match parse_opt_args ~peek_opts optidx cl args with
| Ok (cl, _) when peek_opts -> Ok cl
| Ok (cl, pargs) -> process_pos_args posidx cl pargs
| Error (errs, cl, _) -> Error (errs, cl)
let deprecated_msgs cl =
let add i arg acc = match Cmdliner_info.Arg.deprecated i with
| None -> acc
| Some msg ->
let plural l = if List.length l > 1 then "s " else " " in
match arg with
| O [] | P [] -> acc
| O os ->
let plural = plural os in
let names = List.map (fun (_, n, _) -> n) os in
let names = String.concat " " (List.map Cmdliner_base.quote names) in
let msg = "option" :: plural :: names :: ": " :: msg :: [] in
String.concat "" msg :: acc
| P args ->
let plural = plural args in
let args = String.concat " " (List.map Cmdliner_base.quote args) in
let msg = "argument" :: plural :: args :: ": " :: msg :: [] in
String.concat "" msg :: acc
in
Amap.fold add cl []