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