opts: error when lack of a mandatory parameter
[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 # Is this option mandatory?
29 readable writable var _mandatory: Bool
30
31 # Has this option been read?
32 readable var _read:Bool
33
34 # Current value of this option
35 writable var _value: nullable VALUE
36
37 # Current value of this option
38 fun value: VALUE do return _value.as(VALUE)
39
40 # Default value of this option
41 readable writable var _default_value: nullable VALUE
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 _names = new Array[String]
48 else
49 _names = names.to_a
50 end
51 _helptext = help
52 _mandatory = false
53 _read = false
54 _default_value = default
55 _value = default
56 end
57
58 # Add new aliases for this option
59 fun add_aliases(names: String...) do _names.add_all(names)
60
61 # An help text for this option with default settings
62 redef fun to_s do return pretty(2)
63
64 # A pretty print for this help
65 fun pretty(off: Int): String
66 do
67 var text = new Buffer.from(" ")
68 text.append(_names.join(", "))
69 text.append(" ")
70 var rest = off - text.length
71 if rest > 0 then text.append(" " * rest)
72 text.append(helptext)
73 #text.append(pretty_default)
74 return text.to_s
75 end
76
77 fun pretty_default: String
78 do
79 var dv = default_value
80 if dv != null then return " ({dv})"
81 return ""
82 end
83
84 # Consume parameters for this option
85 protected fun read_param(it: Iterator[String])
86 do
87 _read = true
88 end
89 end
90
91 class OptionText
92 super Option
93 init(text: String) do init_opt(text, null, null)
94
95 redef fun pretty(off) do return to_s
96
97 redef fun to_s do return helptext
98 end
99
100 class OptionBool
101 super Option
102 redef type VALUE: Bool
103
104 init(help: String, names: String...) do init_opt(help, false, names)
105
106 redef fun read_param(it)
107 do
108 super
109 value = true
110 end
111 end
112
113 class OptionCount
114 super Option
115 redef type VALUE: Int
116
117 init(help: String, names: String...) do init_opt(help, 0, names)
118
119 redef fun read_param(it)
120 do
121 super
122 value += 1
123 end
124 end
125
126 # Option with one parameter (mandatory by default)
127 abstract class OptionParameter
128 super Option
129 protected fun convert(str: String): VALUE is abstract
130
131 # Is the parameter mandatory?
132 readable writable var _parameter_mandatory: Bool
133
134 redef fun read_param(it)
135 do
136 super
137 if it.is_ok and it.item.first != '-' then
138 value = convert(it.item)
139 it.next
140 else
141 if _parameter_mandatory then
142 # FIXME exit(1) is not a good way of handling the error
143 stderr.write("Error: parameter expected for option {names.first}\n")
144 exit(1)
145 end
146 end
147 end
148
149 init init_opt(h, d, n)
150 do
151 super
152 _parameter_mandatory = true
153 end
154 end
155
156 class OptionString
157 super OptionParameter
158 redef type VALUE: nullable String
159
160 init(help: String, names: String...) do init_opt(help, null, names)
161
162 redef fun convert(str) do return str
163 end
164
165 class OptionEnum
166 super OptionParameter
167 redef type VALUE: Int
168 var _values: Array[String]
169
170 init(values: Array[String], help: String, default: Int, names: String...)
171 do
172 assert values.length > 0
173 _values = values.to_a
174 init_opt("{help} <{values.join(", ")}>", default, names)
175 end
176
177 redef fun convert(str)
178 do
179 var id = _values.index_of(str)
180 return id
181 end
182
183 fun value_name: String = _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 init_opt(help, default, names)
200
201 redef fun convert(str) do return str.to_i
202 end
203
204 class OptionArray
205 super OptionParameter
206 redef type VALUE: Array[String]
207
208 init(help: String, names: String...)
209 do
210 _values = new Array[String]
211 init_opt(help, _values, names)
212 end
213
214 var _values: Array[String]
215 redef fun convert(str)
216 do
217 _values.add(str)
218 return _values
219 end
220 end
221
222 class OptionContext
223 readable var _options: Array[Option]
224 readable var _rest: Array[String]
225
226 var _optmap: Map[String, Option]
227
228 fun usage
229 do
230 var lmax = 1
231 for i in _options do
232 var l = 3
233 for n in i.names do
234 l += n.length + 2
235 end
236 if lmax < l then lmax = l
237 end
238
239 for i in _options do
240 print(i.pretty(lmax))
241 end
242 end
243
244 # Parse ans assign options everywhere is the argument list
245 fun parse(argv: Collection[String])
246 do
247 var it = argv.iterator
248 parse_intern(it)
249 end
250
251 protected fun parse_intern(it: Iterator[String])
252 do
253 var parseargs = true
254 build
255 var rest = _rest
256
257 while parseargs and it.is_ok do
258 var str = it.item
259 if str == "--" then
260 it.next
261 rest.add_all(it.to_a)
262 parseargs = false
263 else
264 if _optmap.has_key(str) then
265 var opt = _optmap[str]
266 it.next
267 opt.read_param(it)
268 else
269 rest.add(it.item)
270 it.next
271 end
272 end
273 end
274
275 for opt in _options do
276 if opt.mandatory and not opt.read then
277 stderr.write("Error: mandatory option {opt.names.join(", ")} not found\n")
278 exit(1)
279 end
280 end
281 end
282
283 fun add_option(opts: Option...)
284 do
285 for opt in opts do
286 _options.add(opt)
287 end
288 end
289
290 init
291 do
292 _options = new Array[Option]
293 _optmap = new HashMap[String, Option]
294 _rest = new Array[String]
295 end
296
297 private fun build
298 do
299 for o in _options do
300 for n in o.names do
301 _optmap[n] = o
302 end
303 end
304 end
305 end