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