stdlib/strings: Moved Buffer to FlatBuffer, Buffer is now abstract.
[nit.git] / lib / opts.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2008 Floréal Morandat <morandat@lirmm.fr>
4 # Copyright 2008 Jean Privat <jean@pryen.org>
5 #
6 # This file is free software, which comes along with NIT. This software is
7 # distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
8 # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
9 # PARTICULAR PURPOSE. You can modify it is you want, provided this header
10 # is kept unaltered, and a notification of the changes is added.
11 # You are allowed to redistribute it and sell it, alone or is a part of
12 # another product.
13
14 # Management of options on the command line
15 module opts
16
17 # Super class of all option's class
18 abstract class Option
19 # Names for the option (including long and short ones)
20 readable var _names: Array[String]
21
22 # Type of the value of the option
23 type VALUE: nullable Object
24
25 # Human readable description of the option
26 readable var _helptext: String
27
28 # Gathering errors during parsing
29 readable var _errors: Array[String]
30
31 # Is this option mandatory?
32 readable writable var _mandatory: Bool
33
34 # Has this option been read?
35 readable var _read:Bool
36
37 # Current value of this option
38 writable var _value: nullable VALUE
39
40 # Current value of this option
41 fun value: VALUE do return _value.as(VALUE)
42
43 # Default value of this option
44 readable writable var _default_value: nullable VALUE
45
46 # Create a new option
47 init init_opt(help: String, default: nullable VALUE, names: nullable Array[String])
48 do
49 if names == null then
50 _names = new Array[String]
51 else
52 _names = names.to_a
53 end
54 _helptext = help
55 _mandatory = false
56 _read = false
57 _default_value = default
58 _value = default
59 _errors = new Array[String]
60 end
61
62 # Add new aliases for this option
63 fun add_aliases(names: String...) do _names.add_all(names)
64
65 # An help text for this option with default settings
66 redef fun to_s do return pretty(2)
67
68 # A pretty print for this help
69 fun pretty(off: Int): String
70 do
71 var text = new FlatBuffer.from(" ")
72 text.append(_names.join(", "))
73 text.append(" ")
74 var rest = off - text.length
75 if rest > 0 then text.append(" " * rest)
76 text.append(helptext)
77 #text.append(pretty_default)
78 return text.to_s
79 end
80
81 fun pretty_default: String
82 do
83 var dv = default_value
84 if dv != null then return " ({dv})"
85 return ""
86 end
87
88 # Consume parameters for this option
89 protected fun read_param(it: Iterator[String])
90 do
91 _read = true
92 end
93 end
94
95 class OptionText
96 super Option
97 init(text: String) do init_opt(text, null, null)
98
99 redef fun pretty(off) do return to_s
100
101 redef fun to_s do return helptext
102 end
103
104 class OptionBool
105 super Option
106 redef type VALUE: Bool
107
108 init(help: String, names: String...) do init_opt(help, false, names)
109
110 redef fun read_param(it)
111 do
112 super
113 value = true
114 end
115 end
116
117 class OptionCount
118 super Option
119 redef type VALUE: Int
120
121 init(help: String, names: String...) do init_opt(help, 0, names)
122
123 redef fun read_param(it)
124 do
125 super
126 value += 1
127 end
128 end
129
130 # Option with one parameter (mandatory by default)
131 abstract class OptionParameter
132 super Option
133 protected fun convert(str: String): VALUE is abstract
134
135 # Is the parameter mandatory?
136 readable writable var _parameter_mandatory: Bool
137
138 redef fun read_param(it)
139 do
140 super
141 if it.is_ok and it.item.chars.first != '-' then
142 value = convert(it.item)
143 it.next
144 else
145 if _parameter_mandatory then
146 _errors.add("Parameter expected for option {names.first}.")
147 end
148 end
149 end
150
151 init init_opt(h, d, n)
152 do
153 super
154 _parameter_mandatory = true
155 end
156 end
157
158 class OptionString
159 super OptionParameter
160 redef type VALUE: nullable String
161
162 init(help: String, names: String...) do init_opt(help, null, names)
163
164 redef fun convert(str) do return str
165 end
166
167 class OptionEnum
168 super OptionParameter
169 redef type VALUE: Int
170 var _values: Array[String]
171
172 init(values: Array[String], help: String, default: Int, names: String...)
173 do
174 assert values.length > 0
175 _values = values.to_a
176 init_opt("{help} <{values.join(", ")}>", default, names)
177 end
178
179 redef fun convert(str)
180 do
181 var id = _values.index_of(str)
182 if id == -1 then
183 var e = "Unrecognized value for option {_names.join(", ")}.\n"
184 e += "Expected values are: {_values.join(", ")}."
185 _errors.add(e)
186 end
187 return id
188 end
189
190 fun value_name: String = _values[value]
191
192 redef fun pretty_default
193 do
194 if default_value != null then
195 return " ({_values[default_value.as(not null)]})"
196 else
197 return ""
198 end
199 end
200 end
201
202 class OptionInt
203 super OptionParameter
204 redef type VALUE: Int
205
206 init(help: String, default: Int, names: String...) do init_opt(help, default, names)
207
208 redef fun convert(str) do return str.to_i
209 end
210
211 class OptionArray
212 super OptionParameter
213 redef type VALUE: Array[String]
214
215 init(help: String, names: String...)
216 do
217 _values = new Array[String]
218 init_opt(help, _values, names)
219 end
220
221 var _values: Array[String]
222 redef fun convert(str)
223 do
224 _values.add(str)
225 return _values
226 end
227 end
228
229 class OptionContext
230 readable var _options: Array[Option]
231 readable var _rest: Array[String]
232 readable var _errors: Array[String]
233
234 var _optmap: Map[String, Option]
235
236 fun usage
237 do
238 var lmax = 1
239 for i in _options do
240 var l = 3
241 for n in i.names do
242 l += n.length + 2
243 end
244 if lmax < l then lmax = l
245 end
246
247 for i in _options do
248 print(i.pretty(lmax))
249 end
250 end
251
252 # Parse ans assign options everywhere is the argument list
253 fun parse(argv: Collection[String])
254 do
255 var it = argv.iterator
256 parse_intern(it)
257 end
258
259 protected fun parse_intern(it: Iterator[String])
260 do
261 var parseargs = true
262 build
263 var rest = _rest
264
265 while parseargs and it.is_ok do
266 var str = it.item
267 if str == "--" then
268 it.next
269 rest.add_all(it.to_a)
270 parseargs = false
271 else
272 # We're looking for packed short options
273 if str.last_index_of('-') == 0 and str.length > 2 then
274 var next_called = false
275 for i in [1..str.length] do
276 var short_opt = "-" + str.chars[i].to_s
277 if _optmap.has_key(short_opt) then
278 var option = _optmap[short_opt]
279 if option isa OptionParameter then
280 it.next
281 next_called = true
282 end
283 option.read_param(it)
284 end
285 end
286 if not next_called then it.next
287 else
288 if _optmap.has_key(str) then
289 var opt = _optmap[str]
290 it.next
291 opt.read_param(it)
292 else
293 rest.add(it.item)
294 it.next
295 end
296 end
297 end
298 end
299
300 for opt in _options do
301 if opt.mandatory and not opt.read then
302 _errors.add("Mandatory option {opt.names.join(", ")} not found.")
303 end
304 end
305 end
306
307 fun add_option(opts: Option...)
308 do
309 for opt in opts do
310 _options.add(opt)
311 end
312 end
313
314 init
315 do
316 _options = new Array[Option]
317 _optmap = new HashMap[String, Option]
318 _rest = new Array[String]
319 _errors = new Array[String]
320 end
321
322 private fun build
323 do
324 for o in _options do
325 for n in o.names do
326 _optmap[n] = o
327 end
328 end
329 end
330
331 fun get_errors: Array[String]
332 do
333 var errors: Array[String] = new Array[String]
334
335 errors.add_all(_errors)
336
337 for o in _options do
338 for e in o.errors do
339 errors.add(e)
340 end
341 end
342
343 return errors
344 end
345 end