src: remove useless comparisons on null
[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 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 var helptext: String
27
28 # Gathering errors during parsing
29 var errors: Array[String] = new Array[String]
30
31 # Is this option mandatory?
32 var mandatory: Bool = false is writable
33
34 # Is this option hidden from `usage`?
35 var hidden: Bool = false is writable
36
37 # Has this option been read?
38 var read: Bool = false is writable
39
40 # Current value of this option
41 var value: VALUE is writable
42
43 # Default value of this option
44 var default_value: VALUE is writable
45
46 # Create a new option
47 init(help: String, default: VALUE, names: nullable Array[String])
48 do
49 init_opt(help, default, names)
50 end
51
52 fun init_opt(help: String, default: VALUE, names: nullable Array[String])
53 do
54 if names == null then
55 self.names = new Array[String]
56 else
57 self.names = names.to_a
58 end
59 helptext = help
60 default_value = default
61 value = default
62 end
63
64 # Add new aliases for this option
65 fun add_aliases(names: String...) do names.add_all(names)
66
67 # An help text for this option with default settings
68 redef fun to_s do return pretty(2)
69
70 # A pretty print for this help
71 fun pretty(off: Int): String
72 do
73 var text = new FlatBuffer.from(" ")
74 text.append(names.join(", "))
75 text.append(" ")
76 var rest = off - text.length
77 if rest > 0 then text.append(" " * rest)
78 text.append(helptext)
79 #text.append(pretty_default)
80 return text.to_s
81 end
82
83 fun pretty_default: String
84 do
85 var dv = default_value
86 if dv != null then return " ({dv.to_s})"
87 return ""
88 end
89
90 # Consume parameters for this option
91 protected fun read_param(it: Iterator[String])
92 do
93 read = true
94 end
95 end
96
97 # Not really an option. Just add a line of text when displaying the usage
98 class OptionText
99 super Option
100 init(text: String) do super(text, null, null)
101
102 redef fun pretty(off) do return to_s
103
104 redef fun to_s do return helptext
105 end
106
107 # A boolean option, `true` when present, `false` if not
108 class OptionBool
109 super Option
110 redef type VALUE: Bool
111
112 init(help: String, names: String...) do super(help, false, names)
113
114 redef fun read_param(it)
115 do
116 super
117 value = true
118 end
119 end
120
121 # A count option. Count the number of time this option is present
122 class OptionCount
123 super Option
124 redef type VALUE: Int
125
126 init(help: String, names: String...) do super(help, 0, names)
127
128 redef fun read_param(it)
129 do
130 super
131 value += 1
132 end
133 end
134
135 # Option with one parameter (mandatory by default)
136 abstract class OptionParameter
137 super Option
138 protected fun convert(str: String): VALUE is abstract
139
140 # Is the parameter mandatory?
141 var parameter_mandatory: Bool = true is writable
142
143 redef fun read_param(it)
144 do
145 super
146 if it.is_ok and it.item.chars.first != '-' then
147 value = convert(it.item)
148 it.next
149 else
150 if parameter_mandatory then
151 errors.add("Parameter expected for option {names.first}.")
152 end
153 end
154 end
155 end
156
157 # An option with a String as parameter
158 class OptionString
159 super OptionParameter
160 redef type VALUE: nullable String
161
162 init(help: String, names: String...) do super(help, null, names)
163
164 redef fun convert(str) do return str
165 end
166
167 # An option with an enum as parameter
168 # In the code, declaring an option enum (-e) with an enum like `["zero", "one", "two"]
169 # In the command line, typing `myprog -e one` is giving 1 as value
170 class OptionEnum
171 super OptionParameter
172 redef type VALUE: Int
173 var values: Array[String]
174
175 init(values: Array[String], help: String, default: Int, names: String...)
176 do
177 assert values.length > 0
178 self.values = values.to_a
179 super("{help} <{values.join(", ")}>", default, names)
180 end
181
182 redef fun convert(str)
183 do
184 var id = values.index_of(str)
185 if id == -1 then
186 var e = "Unrecognized value for option {names.join(", ")}.\n"
187 e += "Expected values are: {values.join(", ")}."
188 errors.add(e)
189 end
190 return id
191 end
192
193 fun value_name: String do return values[value]
194
195 redef fun pretty_default
196 do
197 return " ({values[default_value]})"
198 end
199 end
200
201 # An option with an Int as parameter
202 class OptionInt
203 super OptionParameter
204 redef type VALUE: Int
205
206 init(help: String, default: Int, names: String...) do super(help, default, names)
207
208 redef fun convert(str) do return str.to_i
209 end
210
211 # An option with a Float as parameter
212 class OptionFloat
213 super OptionParameter
214 redef type VALUE: Float
215
216 init(help: String, default: Float, names: String...) do super(help, default, names)
217
218 redef fun convert(str) do return str.to_f
219 end
220
221 # An option with an array as parameter
222 # `myprog -optA arg1 -optA arg2` is giving an Array `["arg1", "arg2"]`
223 class OptionArray
224 super OptionParameter
225 redef type VALUE: Array[String]
226
227 init(help: String, names: String...)
228 do
229 values = new Array[String]
230 super(help, values, names)
231 end
232
233 private var values: Array[String]
234 redef fun convert(str)
235 do
236 values.add(str)
237 return values
238 end
239 end
240
241 # Context where the options process
242 class OptionContext
243 # Options present in the context
244 var options = new Array[Option]
245
246 # Rest of the options after `parse` is called
247 var rest = new Array[String]
248
249 # Errors found in the context after parsing
250 var errors = new Array[String]
251
252 private var optmap = new HashMap[String, Option]
253
254 # Add one or more options to the context
255 fun add_option(opts: Option...) do
256 options.add_all(opts)
257 end
258
259 # Display all the options available
260 fun usage
261 do
262 var lmax = 1
263 for i in options do
264 var l = 3
265 for n in i.names do
266 l += n.length + 2
267 end
268 if lmax < l then lmax = l
269 end
270
271 for i in options do
272 if not i.hidden then
273 print(i.pretty(lmax))
274 end
275 end
276 end
277
278 # Parse and assign options everywhere in the argument list
279 fun parse(argv: Collection[String])
280 do
281 var it = argv.iterator
282 parse_intern(it)
283 end
284
285 # Parse the command line
286 protected fun parse_intern(it: Iterator[String])
287 do
288 var parseargs = true
289 build
290 var rest = rest
291
292 while parseargs and it.is_ok do
293 var str = it.item
294 if str == "--" then
295 it.next
296 rest.add_all(it.to_a)
297 parseargs = false
298 else
299 # We're looking for packed short options
300 if str.chars.last_index_of('-') == 0 and str.length > 2 then
301 var next_called = false
302 for i in [1..str.length[ do
303 var short_opt = "-" + str.chars[i].to_s
304 if optmap.has_key(short_opt) then
305 var option = optmap[short_opt]
306 if option isa OptionParameter then
307 it.next
308 next_called = true
309 end
310 option.read_param(it)
311 end
312 end
313 if not next_called then it.next
314 else
315 if optmap.has_key(str) then
316 var opt = optmap[str]
317 it.next
318 opt.read_param(it)
319 else
320 rest.add(it.item)
321 it.next
322 end
323 end
324 end
325 end
326
327 for opt in options do
328 if opt.mandatory and not opt.read then
329 errors.add("Mandatory option {opt.names.join(", ")} not found.")
330 end
331 end
332 end
333
334 private fun build
335 do
336 for o in options do
337 for n in o.names do
338 optmap[n] = o
339 end
340 end
341 end
342
343 fun get_errors: Array[String]
344 do
345 var errors: Array[String] = new Array[String]
346 errors.add_all(errors)
347 for o in options do
348 for e in o.errors do
349 errors.add(e)
350 end
351 end
352 return errors
353 end
354 end