nitrpg: Move `nitrpg` to its own repository
[nit.git] / lib / config / config.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Configuration options for nit tools and apps
16 #
17 # This module provides basic services for options handling in your Nit programs.
18 #
19 # ## Basic configuration holder
20 #
21 # The `Config` class can be used as a simple option holder and processor:
22 #
23 # ~~~
24 # import config
25 #
26 # # Create a new config option
27 # var opt_my = new OptionString("My option", "--my")
28 #
29 # # Create the config and add the option
30 # var config = new Config
31 # config.add_option(opt_my)
32 #
33 # # Parse the program arguments, usually `args`
34 # config.parse_options(["--my", "myOption", "arg1", "arg2"])
35 #
36 # # Access the options and args
37 # assert opt_my.value == "myOption"
38 # assert config.args == ["arg1", "arg2"]
39 # ~~~
40 #
41 # ## Custom configuration class
42 #
43 # Instead of using basic `Config` instances, it is better to define new sublcasses
44 # to store options and define custom services.
45 #
46 # ~~~
47 # import config
48 #
49 # class MyConfig
50 # super Config
51 #
52 # var opt_my = new OptionString("My option", "--my")
53 #
54 # init do
55 # super
56 # tool_description = "Usage: MyExample [OPTION]... [ARGS]..."
57 # add_option(opt_my)
58 # end
59 #
60 # fun my: String do return opt_my.value or else "Default value"
61 # end
62 #
63 # var config = new MyConfig
64 # config.parse_options(["--my", "myOption", "arg1", "arg2"])
65 #
66 # assert config.my == "myOption"
67 # assert config.args == ["arg1", "arg2"]
68 # ~~~
69 #
70 # We define the `my` method to provide an elegant shortcut to `opt_my.value`
71 # and define the default value if the option was not set by the user.
72 #
73 # The `tool_description` attribute is used to set the `usage` header printed when
74 # the user request the `help` message.
75 #
76 # ~~~
77 # config.parse_options(["-h"])
78 # if config.help then
79 # config.usage
80 # exit 0
81 # end
82 # ~~~
83 #
84 # This will display the tool usage like this:
85 #
86 # ~~~raw
87 # Usage: MyExample [OPTION]... [ARGS]...
88 # -h, --help Show this help message
89 # --my My option
90 # ~~~
91 #
92 # ## Configuration with `ini` file
93 #
94 # The `IniConfig` class extends `Config` to add an easy way to link your
95 # configuration to an ini file.
96 #
97 # ~~~
98 # class MyIniConfig
99 # super IniConfig
100 #
101 # var opt_my = new OptionString("My option", "--my")
102 #
103 # init do
104 # super
105 # tool_description = "Usage: MyExample [OPTION]... [ARGS]..."
106 # opts.add_option(opt_my)
107 # end
108 #
109 # fun my: String do return opt_my.value or else ini["my"] or else "Default"
110 # end
111 # ~~~
112 #
113 # This time, we define the `my` method to return the option value or the ini
114 # if no option was passed. Finally, if no ini value can be found, we return the
115 # default value.
116 #
117 # By default, `IniConfig` looks at a `config.ini` file in the execution directory.
118 # This can be overrided in multiple ways.
119 #
120 # First by the app user by setting the `--config` option:
121 #
122 # ~~~
123 # var config = new MyIniConfig
124 # config.parse_options(["--config", "my_config.ini"])
125 #
126 # assert config.config_file == "my_config.ini"
127 # ~~~
128 #
129 # Default config file can also be changed by the library client through the
130 # `default_config_file` attribute:
131 #
132 # ~~~
133 # config = new MyIniConfig
134 # config.default_config_file = "my_config.ini"
135 # config.parse_options(["arg"])
136 #
137 # assert config.config_file == "my_config.ini"
138 # ~~~
139 #
140 # Or by the library developper in the custom config class:
141 #
142 # ~~~
143 # class MyCustomIniConfig
144 # super IniConfig
145 #
146 # redef var default_config_file = "my_config.ini"
147 # end
148 #
149 # var config = new MyCustomIniConfig
150 # config.parse_options(["arg"])
151 #
152 # assert config.config_file == "my_config.ini"
153 # ~~~
154 module config
155
156 import ini
157 import opts
158
159 # Basic configuration class
160 #
161 # ~~~
162 # import config
163 #
164 # class MyConfig
165 # super Config
166 #
167 # var opt_my = new OptionString("My option", "--my")
168 #
169 # init do
170 # super
171 # tool_description = "Usage: MyExample [OPTION]... [ARGS]..."
172 # opts.add_option(opt_my)
173 # end
174 #
175 # fun my: String do return opt_my.value or else "Default value"
176 # end
177 #
178 # var config = new MyConfig
179 # config.parse_options(["--my", "hello", "arg1", "arg2"])
180 # assert config.my == "hello"
181 # assert config.args == ["arg1", "arg2"]
182 # ~~~
183 class Config
184
185 # Context used to store and parse options
186 var opts = new OptionContext
187
188 # Help option
189 var opt_help = new OptionBool("Show this help message", "-h", "-?", "--help")
190
191 # Option --stub-man
192 var opt_stub_man = new OptionBool("Generate a stub manpage in markdown format", "--stub-man")
193
194 # Redefine this init to add your options
195 init do
196 add_option(opt_help, opt_stub_man)
197 opt_stub_man.hidden = true
198 end
199
200 # Add an option to `self`
201 #
202 # Shortcut to `opts.add_option`.
203 fun add_option(opt: Option...) do opts.add_option(opt...)
204
205 # Initialize `self` options from `args`
206 fun parse_options(args: Collection[String]) do
207 opts.parse(args)
208
209 if opt_stub_man.value then
210 stub_man_options
211 exit 0
212 end
213 end
214
215 # Return the remaining args once options are parsed by `from_args`
216 fun args: Array[String] do return opts.rest
217
218 # Name, usage and synopsis of the tool.
219 # It is mainly used in `usage`.
220 # Should be correctly set by the client before calling `usage`
221 # A multi-line string is recommended.
222 #
223 # eg. `"Usage: tool [OPTION]... [FILE]...\nDo some things."`
224 var tool_description: String = "Usage: [OPTION]... [ARG]..." is writable
225
226 # Was the `--help` option requested?
227 fun help: Bool do return opt_help.value
228
229 # Display `tool_description` and options usage in console
230 fun usage do
231 print tool_description
232 print "\nOptions:"
233 opts.usage
234 end
235
236 # Generate a manpage stub from `self`
237 fun stub_man_options do
238 var lines = tool_description.split("\n")
239 var name = sys.program_name.basename
240 var syn = lines.shift
241 print "# NAME"
242 print ""
243 if lines.not_empty then
244 printn "{name} - "
245 print lines.join("\n")
246 print ""
247 end
248 print "# SYNOPSIS"
249 print ""
250 print syn.replace("Usage: ", "")
251 print ""
252 print "# OPTIONS"
253 for o in opts.options do
254 if o.hidden then continue
255
256 var first = true
257 print ""
258 printn "### "
259 for n in o.names do
260 if first then first = false else printn ", "
261 printn "`{n}`"
262 end
263 print ""
264 print "{o.helptext}."
265 end
266 exit 0
267 end
268 end
269
270 # Configuration class based on a INI file.
271 #
272 # ~~~
273 # class MyIniConfig
274 # super IniConfig
275 #
276 # var opt_my = new OptionString("My option", "--my")
277 #
278 # init do
279 # super
280 # tool_description = "Usage: MyExample [OPTION]... [ARGS]..."
281 # opts.add_option(opt_my)
282 # end
283 #
284 # fun my: String do return opt_my.value or else ini["my"] or else "Default"
285 # end
286 #
287 # var config = new MyIniConfig
288 # config.default_config_file = "my_config.ini"
289 # config.parse_options(args)
290 #
291 # if config.help then
292 # config.usage
293 # exit 0
294 # end
295 #
296 # assert config.my == "Default"
297 # ~~~
298 class IniConfig
299 super Config
300
301 # Config tree used to store config options
302 var ini: ConfigTree is noinit
303
304 # Path to app config file
305 var opt_config = new OptionString("Path to config file", "--config")
306
307 init do
308 super
309 opts.add_option(opt_config)
310 end
311
312 redef fun parse_options(args) do
313 super
314 ini = new ConfigTree(config_file)
315 end
316
317 # Default config file path
318 var default_config_file = "config.ini" is writable
319
320 # Return the config file path from options or the default
321 fun config_file: String do return opt_config.value or else default_config_file
322 end