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