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 :
34 : // self
35 : //
36 : #include "advgetopt/advgetopt.h"
37 :
38 : // advgetopt lib
39 : //
40 : #include "advgetopt/conf_file.h"
41 :
42 :
43 : // cppthread lib
44 : //
45 : #include <cppthread/log.h>
46 :
47 : // boost lib
48 : //
49 : #include <boost/algorithm/string/join.hpp>
50 : #include <boost/algorithm/string/replace.hpp>
51 :
52 :
53 : // last include
54 : //
55 : #include <snapdev/poison.h>
56 :
57 :
58 : namespace advgetopt
59 : {
60 :
61 :
62 :
63 :
64 : /** \brief Generate a list of configuration filenames.
65 : *
66 : * This function goes through the list of filenames and directories and
67 : * generates a complete list of all the configuration files that the
68 : * system will load when you call the parse_configuration_files()
69 : * function.
70 : *
71 : * Set the flag \p exists to true if you only want the name of files
72 : * that currently exists.
73 : *
74 : * The \p writable file means that we only want files under the
75 : * \<project-name>.d folder and the user configuration folder.
76 : *
77 : * \param[in] exists Remove files that do not exist from the list.
78 : * \param[in] writable Only return files we consider writable.
79 : *
80 : * \return The list of configuration filenames.
81 : */
82 334 : string_list_t getopt::get_configuration_filenames(bool exists, bool writable) const
83 : {
84 668 : string_list_t result;
85 :
86 334 : if(f_options_environment.f_configuration_files != nullptr)
87 : {
88 : // load options from configuration files specified as is by caller
89 : //
90 453 : for(char const * const * configuration_files(f_options_environment.f_configuration_files)
91 453 : ; *configuration_files != nullptr
92 : ; ++configuration_files)
93 : {
94 351 : char const * filename(*configuration_files);
95 351 : if(*filename != '\0')
96 : {
97 702 : std::string const user_filename(handle_user_directory(filename));
98 351 : if(user_filename == filename)
99 : {
100 344 : if(!writable)
101 : {
102 202 : result.push_back(user_filename);
103 : }
104 :
105 688 : string_list_t const with_project_name(insert_group_name(user_filename, f_options_environment.f_group_name, f_options_environment.f_project_name));
106 344 : if(!with_project_name.empty())
107 : {
108 344 : result.insert(
109 688 : result.end()
110 : , with_project_name.begin()
111 1032 : , with_project_name.end());
112 : }
113 : }
114 : else
115 : {
116 7 : result.push_back(user_filename);
117 : }
118 : }
119 : }
120 : }
121 :
122 334 : if(f_options_environment.f_configuration_filename != nullptr)
123 : {
124 316 : string_list_t directories;
125 158 : if(has_flag(GETOPT_ENVIRONMENT_FLAG_SYSTEM_PARAMETERS))
126 : {
127 11 : if(is_defined("config-dir"))
128 : {
129 2 : size_t const max(size("config-dir"));
130 6 : for(size_t idx(0); idx < max; ++idx)
131 : {
132 4 : directories.push_back(get_string("config-dir", idx));
133 : }
134 : }
135 : }
136 :
137 158 : if(f_options_environment.f_configuration_directories != nullptr)
138 : {
139 260 : for(char const * const * configuration_directories(f_options_environment.f_configuration_directories)
140 260 : ; *configuration_directories != nullptr
141 : ; ++configuration_directories)
142 : {
143 204 : directories.push_back(*configuration_directories);
144 : }
145 : }
146 :
147 316 : std::string const filename(f_options_environment.f_configuration_filename);
148 :
149 366 : for(auto directory : directories)
150 : {
151 208 : if(!directory.empty())
152 : {
153 416 : std::string const full_filename(directory + ("/" + filename));
154 416 : std::string const user_filename(handle_user_directory(full_filename));
155 208 : if(user_filename == full_filename)
156 : {
157 161 : if(!writable)
158 : {
159 101 : result.push_back(user_filename);
160 : }
161 :
162 322 : string_list_t const with_project_name(insert_group_name(user_filename, f_options_environment.f_group_name, f_options_environment.f_project_name));
163 161 : if(!with_project_name.empty())
164 : {
165 161 : result.insert(
166 322 : result.end()
167 : , with_project_name.begin()
168 483 : , with_project_name.end());
169 : }
170 : }
171 : else
172 : {
173 47 : result.push_back(user_filename);
174 : }
175 : }
176 : }
177 : }
178 :
179 334 : if(!exists)
180 : {
181 286 : return result;
182 : }
183 :
184 96 : string_list_t existing_files;
185 48 : int const mode(R_OK | (writable ? W_OK : 0));
186 404 : for(auto r : result)
187 : {
188 356 : if(access(r.c_str(), mode) == 0)
189 : {
190 26 : existing_files.push_back(r);
191 : }
192 : }
193 48 : return existing_files;
194 : }
195 :
196 :
197 : /** \brief This function checks for arguments in configuration files.
198 : *
199 : * Each configuration file is checked one after another. Each file that is
200 : * defined is loaded and each line is viewed as an option. If valid, it is
201 : * added to the resulting getopt list of options.
202 : *
203 : * Note that it is an error to define a command in a configuration file. If
204 : * that happens, an error occurs and the process stops. Technically this is
205 : * defined with the GETOPT_FLAG_CONFIGURATION_FILE flag in your opt table.
206 : *
207 : * The list of files is checked from beginning to end. So if a later file
208 : * changes an option of an earlier file, it is the one effective.
209 : *
210 : * The configuration file loader supports a project name as defined in the
211 : * get_project_name() function. It allows for a sub-directory to
212 : * be inserted between the path and the basename of the configuration
213 : * file. This allows for a file to be search in an extra sub-directory
214 : * so one can avoid changing the original definitions and only use
215 : * configuration files in the sub-directory. The path looks like this
216 : * when a project name is specified:
217 : *
218 : * \code
219 : * <path>/<project name>.d/<basename>
220 : * \endcode
221 : *
222 : * Notice that we add a ".d" as usual in other projects under Linux.
223 : *
224 : * \exception getopt_exception_invalid
225 : * This function generates the getopt_exception_invalid exception whenever
226 : * something invalid is found in the list of options passed as the \p opts
227 : * parameter.
228 : *
229 : * \exception getopt_exception_default
230 : * The function detects whether two options are marked as the default
231 : * option (the one receiving parameters that are not used by another command
232 : * or match a command.) This exception is raised when such is detected.
233 : *
234 : * \sa process_configuration_file()
235 : */
236 240 : void getopt::parse_configuration_files()
237 : {
238 480 : string_list_t const filenames(get_configuration_filenames(false, false));
239 :
240 462 : for(auto f : filenames)
241 : {
242 222 : process_configuration_file(f);
243 : }
244 240 : }
245 :
246 :
247 : /** \brief Parse one specific configuration file and process the results.
248 : *
249 : * This function reads one specific configuration file using a conf_file
250 : * object and then goes through the resulting arguments and add them to
251 : * the options of this getopt object.
252 : *
253 : * The options found in the configuration file must match an option by
254 : * its long name. In a configuration file, it is not allowed to have an
255 : * option which name is only one character.
256 : *
257 : * \note
258 : * If the filename points to a file which can't be read or does not exist,
259 : * then nothing happens and the function returns without an error.
260 : *
261 : * \todo
262 : * Extend the support by having the various flags that the conf_file
263 : * class supports appear in the list of configuration filenames.
264 : *
265 : * \param[in] filename The name of the configuration file to check out.
266 : *
267 : * \sa parse_configuration_files()
268 : */
269 231 : void getopt::process_configuration_file(std::string const & filename)
270 : {
271 461 : conf_file_setup conf_setup(filename);
272 231 : if(!conf_setup.is_valid())
273 : {
274 : // a non-existant file is considered valid now so this should never
275 : // happen; later we may use the flag if we find errors in the file
276 : //
277 : return; // LCOV_EXCL_LINE
278 : }
279 461 : conf_file::pointer_t conf(conf_file::get_conf_file(conf_setup));
280 :
281 461 : conf_file::sections_t sections(conf->get_sections());
282 231 : if(!sections.empty())
283 : {
284 9 : std::string const name(CONFIGURATION_SECTIONS);
285 9 : option_info::pointer_t configuration_sections(get_option(name));
286 5 : if(configuration_sections == nullptr)
287 : {
288 2 : configuration_sections = std::make_shared<option_info>(name);
289 2 : configuration_sections->add_flag(
290 : GETOPT_FLAG_MULTIPLE
291 : | GETOPT_FLAG_CONFIGURATION_FILE
292 : );
293 2 : f_options_by_name[configuration_sections->get_name()] = configuration_sections;
294 : }
295 3 : else if(!configuration_sections->has_flag(GETOPT_FLAG_MULTIPLE))
296 : {
297 2 : cppthread::log << cppthread::log_level_t::error
298 1 : << "option \""
299 1 : << name
300 1 : << "\" must have GETOPT_FLAG_MULTIPLE set."
301 1 : << cppthread::end;
302 1 : return;
303 : }
304 13 : for(auto s : sections)
305 : {
306 9 : if(!configuration_sections->has_value(s))
307 : {
308 5 : configuration_sections->add_value(s);
309 : }
310 : }
311 : }
312 :
313 309 : for(auto const & param : conf->get_parameters())
314 : {
315 : // in configuration files we only allow long arguments
316 : //
317 154 : option_info::pointer_t opt(get_option(param.first));
318 79 : if(opt == nullptr)
319 : {
320 13 : if(!has_flag(GETOPT_ENVIRONMENT_FLAG_DYNAMIC_PARAMETERS)
321 5 : || param.first.length() == 1)
322 : {
323 6 : cppthread::log << cppthread::log_level_t::error
324 3 : << "unknown option \""
325 6 : << boost::replace_all_copy(param.first, "-", "_")
326 3 : << "\" found in configuration file \""
327 3 : << filename
328 3 : << "\"."
329 3 : << cppthread::end;
330 3 : continue;
331 : }
332 : else
333 : {
334 : // add a new parameter dynamically
335 : //
336 2 : opt = std::make_shared<option_info>(param.first);
337 :
338 2 : opt->set_flags(GETOPT_FLAG_CONFIGURATION_FILE | GETOPT_FLAG_DYNAMIC);
339 :
340 : // consider the first definition as the default
341 : // (which is likely in our environment)
342 : //
343 2 : opt->set_default(param.second);
344 :
345 2 : f_options_by_name[opt->get_name()] = opt;
346 : }
347 : }
348 : else
349 : {
350 75 : if(!opt->has_flag(GETOPT_FLAG_CONFIGURATION_FILE))
351 : {
352 : // in configuration files we are expected to use '_' so
353 : // print an error with such
354 : //
355 2 : cppthread::log << cppthread::log_level_t::error
356 1 : << "option \""
357 2 : << boost::replace_all_copy(param.first, "-", "_")
358 1 : << "\" is not supported in configuration files (found in \""
359 1 : << filename
360 1 : << "\")."
361 1 : << cppthread::end;
362 1 : continue;
363 : }
364 : }
365 :
366 75 : if(opt != nullptr)
367 : {
368 75 : add_option_from_string(opt, param.second, filename);
369 : }
370 : }
371 : }
372 :
373 :
374 :
375 :
376 :
377 :
378 6 : } // namespace advgetopt
379 : // vim: ts=4 sw=4 et
|