Line data Source code
1 : /*
2 : * License:
3 : * Copyright (c) 2006-2019 Made to Order Software Corp. All Rights Reserved
4 : *
5 : * https://snapwebsites.org/
6 : * contact@m2osw.com
7 : *
8 : * This program is free software; you can redistribute it and/or modify
9 : * it under the terms of the GNU General Public License as published by
10 : * the Free Software Foundation; either version 2 of the License, or
11 : * (at your option) any later version.
12 : *
13 : * This program is distributed in the hope that it will be useful,
14 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : * GNU General Public License for more details.
17 : *
18 : * You should have received a copy of the GNU General Public License along
19 : * with this program; if not, write to the Free Software Foundation, Inc.,
20 : * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 : *
22 : * Authors:
23 : * Alexis Wilke alexis@m2osw.com
24 : * Doug Barbieri doug@m2osw.com
25 : */
26 :
27 : /** \file
28 : * \brief Advanced getopt data access implementation.
29 : *
30 : * The advgetopt class has many function used to access the data in the
31 : * class. These functions are gathered here.
32 : *
33 : * This file is covered by the following tests:
34 : *
35 : * \li options_parser
36 : * \li invalid_options_parser
37 : * \li valid_options_files
38 : * \li invalid_options_files
39 : */
40 :
41 : // self
42 : //
43 : #include "advgetopt/advgetopt.h"
44 :
45 :
46 : // advgetopt lib
47 : //
48 : #include "advgetopt/conf_file.h"
49 : #include "advgetopt/exception.h"
50 : #include "advgetopt/log.h"
51 :
52 :
53 : // last include
54 : //
55 : #include <snapdev/poison.h>
56 :
57 :
58 :
59 :
60 : namespace advgetopt
61 : {
62 :
63 :
64 :
65 :
66 :
67 :
68 :
69 : /** \brief Reset all the options.
70 : *
71 : * This function goes through the list of options and mark them all as
72 : * undefined. This is useful if you want to reuse a getopt object.
73 : *
74 : * The effect is that all calls to is_defined() made afterward return false
75 : * until new arguments get parsed.
76 : */
77 2 : void getopt::reset()
78 : {
79 18 : for(auto & opt : f_options_by_name)
80 : {
81 16 : opt.second->reset();
82 : }
83 2 : }
84 :
85 :
86 : /** \brief Parse the options to option_info objects.
87 : *
88 : * This function transforms an array of options in a vector of option_info
89 : * objects.
90 : *
91 : * \param[in] opts An array of options to be parsed.
92 : * \param[in] ignore_duplicates Whether to ignore potential duplicates.
93 : */
94 414 : void getopt::parse_options_info(option const * opts, bool ignore_duplicates)
95 : {
96 414 : if(opts == nullptr)
97 : {
98 64 : return;
99 : }
100 :
101 956 : for(
102 1306 : ; (opts->f_flags & GETOPT_FLAG_END) == 0
103 : ; ++opts)
104 : {
105 964 : if(opts->f_name == nullptr
106 963 : || opts->f_name[0] == '\0')
107 : {
108 2 : throw getopt_exception_logic("option long name missing or empty.");
109 : }
110 962 : short_name_t const one_char(string_to_short_name(opts->f_name));
111 962 : if(one_char != NO_SHORT_NAME)
112 : {
113 1 : throw getopt_exception_logic("a long name option must be at least 2 characters.");
114 : }
115 :
116 961 : if(get_option(opts->f_name, true) != nullptr)
117 : {
118 2 : if(ignore_duplicates)
119 : {
120 1 : continue;
121 : }
122 : throw getopt_exception_logic(
123 : std::string("option named \"")
124 2 : + opts->f_name
125 3 : + "\" found twice.");
126 : }
127 959 : short_name_t short_name(opts->f_short_name);
128 959 : if(get_option(short_name, true) != nullptr)
129 : {
130 2 : if(ignore_duplicates)
131 : {
132 1 : short_name = U'\0';
133 : }
134 : else
135 : {
136 : throw getopt_exception_logic(
137 : "option with short name \""
138 2 : + short_name_to_string(opts->f_short_name)
139 3 : + "\" found twice.");
140 : }
141 : }
142 :
143 : option_info::pointer_t o(std::make_shared<option_info>(
144 : opts->f_name
145 1915 : , short_name));
146 :
147 957 : o->add_flag(opts->f_flags);
148 957 : o->set_default(opts->f_default);
149 957 : o->set_help(opts->f_help);
150 957 : o->set_multiple_separators(opts->f_multiple_separators);
151 :
152 957 : if(opts->f_validator != nullptr)
153 : {
154 6 : o->set_validator(opts->f_validator);
155 : }
156 :
157 957 : if(o->is_default_option())
158 : {
159 42 : if(f_default_option != nullptr)
160 : {
161 1 : throw getopt_exception_logic("two default options found after check of long names duplication.");
162 : }
163 41 : if(o->has_flag(GETOPT_FLAG_FLAG))
164 : {
165 1 : throw getopt_exception_logic("a default option must accept parameters, it can't be a GETOPT_FLAG_FLAG.");
166 : }
167 :
168 40 : f_default_option = o;
169 : }
170 :
171 955 : f_options_by_name[o->get_name()] = o;
172 955 : if(short_name != NO_SHORT_NAME)
173 : {
174 544 : f_options_by_short_name[short_name] = o;
175 : }
176 : }
177 : }
178 :
179 :
180 : /** \brief Check for a file with option definitions.
181 : *
182 : * This function tries to read a file of options for this application.
183 : * These are similar to the option structure, only it is defined in a
184 : * file.
185 : *
186 : * The format of the file is like so:
187 : *
188 : * \li Option names are defined on a line by themselves between square brackets.
189 : * \li Parameters of that option are defined below as a `name=<value>`.
190 : *
191 : * Example:
192 : *
193 : * \code
194 : * [<command-name>]
195 : * short_name=<character>
196 : * default=<default value>
197 : * help=<help sentence>
198 : * validator=<validator name>[(<param>)]|/<regex>/<flags>
199 : * alias=<name of aliased option>
200 : * allowed=command-line,environment-variable,configuration-file
201 : * show-usage-on-error
202 : * no-arguments|multiple
203 : * required
204 : * \endcode
205 : */
206 320 : void getopt::parse_options_from_file()
207 : {
208 329 : std::string filename;
209 :
210 320 : if(f_options_environment.f_project_name == nullptr
211 316 : || f_options_environment.f_project_name[0] == '\0')
212 : {
213 6 : return;
214 : }
215 :
216 314 : if(f_options_environment.f_options_files_directory == nullptr
217 73 : || f_options_environment.f_options_files_directory[0] == '\0')
218 : {
219 243 : filename = "/usr/share/advgetopt/options";
220 : }
221 : else
222 : {
223 71 : filename = f_options_environment.f_options_files_directory;
224 71 : if(filename.back() != '/')
225 : {
226 71 : filename += '/';
227 : }
228 : }
229 314 : filename += f_options_environment.f_project_name;
230 314 : filename += ".ini";
231 :
232 : conf_file_setup conf_setup(filename
233 : , line_continuation_t::line_continuation_unix
234 : , ASSIGNMENT_OPERATOR_EQUAL
235 : , COMMENT_INI | COMMENT_SHELL
236 323 : , SECTION_OPERATOR_INI_FILE | SECTION_OPERATOR_ONE_SECTION);
237 314 : if(!conf_setup.is_valid())
238 : {
239 305 : return;
240 : }
241 :
242 18 : conf_file::pointer_t conf(conf_file::get_conf_file(conf_setup));
243 18 : conf_file::sections_t const & sections(conf->get_sections());
244 22 : for(auto & section_name : sections)
245 : {
246 16 : std::string::size_type pos(section_name.find("::"));
247 16 : if(pos != std::string::npos)
248 : {
249 : // this should never happen since we use the
250 : // SECTION_OPERATOR_ONE_SECTION flag
251 : //
252 : throw getopt_exception_logic( // LCOV_EXCL_LINE
253 : "section \"" // LCOV_EXCL_LINE
254 : + section_name // LCOV_EXCL_LINE
255 : + "\" includes a section separator (::) in \"" // LCOV_EXCL_LINE
256 : + filename // LCOV_EXCL_LINE
257 : + "\". We only support one level."); // LCOV_EXCL_LINE
258 : }
259 :
260 32 : std::string const parameter_name(section_name);
261 32 : std::string const short_name(unquote(conf->get_parameter(parameter_name + "::shortname")));
262 16 : if(short_name.length() > 1)
263 : {
264 : throw getopt_exception_logic(
265 : "option \""
266 2 : + section_name
267 2 : + "\" has an invalid short name in \""
268 2 : + filename
269 3 : + "\", it can't be more than one character.");
270 : }
271 15 : short_name_t sn('\0');
272 15 : if(short_name.length() == 1)
273 : {
274 13 : sn = short_name[0];
275 : }
276 :
277 30 : option_info::pointer_t opt(std::make_shared<option_info>(parameter_name, sn));
278 :
279 30 : std::string const default_name(parameter_name + "::default");
280 15 : if(conf->has_parameter(default_name))
281 : {
282 9 : opt->set_default(unquote(conf->get_parameter(default_name)));
283 : }
284 :
285 15 : opt->set_help(unquote(conf->get_parameter(parameter_name + "::help")));
286 :
287 30 : std::string const validator_name_and_params(conf->get_parameter(parameter_name + "::validator"));
288 15 : opt->set_validator(validator_name_and_params);
289 :
290 28 : std::string const alias_name(parameter_name + "::alias");
291 14 : if(conf->has_parameter(alias_name))
292 : {
293 6 : if(!opt->get_help().empty())
294 : {
295 : throw getopt_exception_logic(
296 : "option \""
297 2 : + section_name
298 2 : + "\" is an alias and as such it can't include a help=... parameter in \""
299 2 : + filename
300 3 : + "\".");
301 : }
302 5 : opt->set_help(unquote(conf->get_parameter(alias_name)));
303 5 : opt->add_flag(GETOPT_FLAG_ALIAS);
304 : }
305 :
306 26 : std::string const allowed_name(parameter_name + "::allowed");
307 13 : if(conf->has_parameter(allowed_name))
308 : {
309 26 : std::string const allowed_list(conf->get_parameter(allowed_name));
310 26 : string_list_t allowed;
311 13 : split_string(allowed_list, allowed, {","});
312 32 : for(auto const & a : allowed)
313 : {
314 19 : if(a == "command-line")
315 : {
316 10 : opt->add_flag(GETOPT_FLAG_COMMAND_LINE);
317 : }
318 9 : else if(a == "environment-variable")
319 : {
320 6 : opt->add_flag(GETOPT_FLAG_ENVIRONMENT_VARIABLE);
321 : }
322 3 : else if(a == "configuration-file")
323 : {
324 3 : opt->add_flag(GETOPT_FLAG_CONFIGURATION_FILE);
325 : }
326 : }
327 : }
328 :
329 13 : if(conf->has_parameter(parameter_name + "::show-usage-on-error"))
330 : {
331 1 : opt->add_flag(GETOPT_FLAG_SHOW_USAGE_ON_ERROR);
332 : }
333 :
334 13 : if(conf->has_parameter(parameter_name + "::no-arguments"))
335 : {
336 4 : opt->add_flag(GETOPT_FLAG_FLAG);
337 : }
338 :
339 13 : if(conf->has_parameter(parameter_name + "::multiple"))
340 : {
341 1 : opt->add_flag(GETOPT_FLAG_MULTIPLE);
342 : }
343 :
344 13 : if(conf->has_parameter(parameter_name + "::required"))
345 : {
346 5 : opt->add_flag(GETOPT_FLAG_REQUIRED);
347 : }
348 :
349 13 : f_options_by_name[parameter_name] = opt;
350 13 : if(sn != NO_SHORT_NAME)
351 : {
352 11 : f_options_by_short_name[sn] = opt;
353 : }
354 : }
355 : }
356 :
357 :
358 : /** \brief Link options marked as a GETOPT_FLAG_ALIAS.
359 : *
360 : * After we defined all the options, go through the list again to find
361 : * aliases and link them with their corresponding alias option.
362 : *
363 : * \exception getopt_exception_invalid
364 : * All aliases must exist or this exception is raised.
365 : */
366 244 : void getopt::link_aliases()
367 : {
368 964 : for(auto & c : f_options_by_name)
369 : {
370 724 : if(c.second->has_flag(GETOPT_FLAG_ALIAS))
371 : {
372 22 : std::string const & alias_name(c.second->get_help());
373 22 : if(alias_name.empty())
374 : {
375 : throw getopt_exception_logic(
376 : "the default value of your alias cannot be an empty string for \""
377 4 : + c.first
378 6 : + "\".");
379 : }
380 :
381 : // we have to use the `true` flag in this get_option() because
382 : // aliases may not yet be defined
383 : //
384 40 : option_info::pointer_t alias(get_option(alias_name, true));
385 20 : if(alias == nullptr)
386 : {
387 : throw getopt_exception_logic(
388 : "no option named \""
389 2 : + alias_name
390 2 : + "\" to satisfy the alias of \""
391 3 : + c.first
392 3 : + "\".");
393 : }
394 :
395 19 : flag_t const expected_flags(c.second->get_flags() & ~GETOPT_FLAG_ALIAS);
396 19 : if(alias->get_flags() != expected_flags)
397 : {
398 2 : std::stringstream ss;
399 1 : ss << std::hex
400 1 : << "the flags of alias \""
401 2 : << c.first
402 1 : << "\" (0x"
403 1 : << expected_flags
404 1 : << ") are different than the flags of \""
405 1 : << alias_name
406 1 : << "\" (0x"
407 2 : << alias->get_flags()
408 1 : << ").";
409 1 : throw getopt_exception_logic(ss.str());
410 : }
411 :
412 18 : c.second->set_alias_destination(alias);
413 : }
414 : }
415 240 : }
416 :
417 :
418 : /** \brief Assign a short name to an option.
419 : *
420 : * This function allows for dynamically assigning a short name to an option.
421 : * This is useful for cases where a certain number of options may be added
422 : * dynamically and may share the same short name or similar situation.
423 : *
424 : * On our end we like to add `-c` as the short name of the `--config-dir`
425 : * command line or environment variable option. However, some of our tools
426 : * use `-c` for other reason (i.e. our `cxpath` tool uses `-c` for its
427 : * `--compile` option.) So we do not want to have it has a default in
428 : * that option. Instead we assign it afterward.
429 : *
430 : * **IMPORTANT:** To make this call useful, make sure to make it before
431 : * you call the parse functions. Setting the short name after the parsing
432 : * was done is going to be useless.
433 : *
434 : * \note
435 : * This function requires you to make use of the constructor without the
436 : * `argc` and `argv` parameters, add the short name, then run all the
437 : * parsing.
438 : *
439 : * \note
440 : * The function calls the option_info::set_short_name() function which
441 : * may raise an exception if the option already has a short name (or if
442 : * you inadvertendly passed NO_SHORT_NAME.)
443 : *
444 : * \exception getopt_exception_logic
445 : * The same short name cannot be used more than once. This exception is
446 : * raised if it is discovered that another option already makes use of
447 : * this short name. This exception is also raised if the \p name
448 : * parameter does not reference an existing option.
449 : *
450 : * \param[in] name The name of the option which is to receive a short name.
451 : * \param[in] short_name The short name to assigned to the \p name option.
452 : */
453 7 : void getopt::set_short_name(std::string const & name, short_name_t short_name)
454 : {
455 7 : auto it(f_options_by_short_name.find(short_name));
456 7 : if(it != f_options_by_short_name.end())
457 : {
458 : throw getopt_exception_logic(
459 : "found another option (\""
460 2 : + it->second->get_name()
461 2 : + "\") with short name '"
462 4 : + short_name_to_string(short_name)
463 3 : + "'.");
464 : }
465 :
466 6 : auto opt(f_options_by_name.find(name));
467 6 : if(opt == f_options_by_name.end())
468 : {
469 : throw getopt_exception_logic(
470 : "option with name \""
471 2 : + name
472 3 : + "\" not found.");
473 : }
474 :
475 5 : opt->second->set_short_name(short_name);
476 :
477 3 : f_options_by_short_name[short_name] = opt->second;
478 3 : }
479 :
480 :
481 :
482 :
483 6 : } // namespace advgetopt
484 : // vim: ts=4 sw=4 et
|