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