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