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