xref: /netbsd-src/external/bsd/kyua-cli/dist/cli/config.cpp (revision 6b3a42af15b5e090c339512c790dd68f3d11a9d8)
1 // Copyright 2011 Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 //   notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 //   notice, this list of conditions and the following disclaimer in the
12 //   documentation and/or other materials provided with the distribution.
13 // * Neither the name of Google Inc. nor the names of its contributors
14 //   may be used to endorse or promote products derived from this software
15 //   without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 
29 #include "cli/config.hpp"
30 
31 #include "cli/common.hpp"
32 #include "engine/config.hpp"
33 #include "engine/exceptions.hpp"
34 #include "utils/cmdline/parser.ipp"
35 #include "utils/config/tree.ipp"
36 #include "utils/format/macros.hpp"
37 #include "utils/fs/exceptions.hpp"
38 #include "utils/fs/operations.hpp"
39 #include "utils/fs/path.hpp"
40 #include "utils/env.hpp"
41 #include "utils/logging/macros.hpp"
42 #include "utils/optional.ipp"
43 
44 namespace cmdline = utils::cmdline;
45 namespace config = utils::config;
46 namespace fs = utils::fs;
47 
48 using utils::optional;
49 
50 
51 namespace {
52 
53 
54 /// Basename of the configuration file.
55 static const char* config_basename = "kyua.conf";
56 
57 
58 /// Magic string to disable loading of configuration files.
59 static const char* none_config = "none";
60 
61 
62 /// Textual description of the default configuration files.
63 ///
64 /// This is just an auxiliary string required to define the option below, which
65 /// requires a pointer to a static C string.
66 ///
67 /// \todo If the user overrides the KYUA_CONFDIR environment variable, we don't
68 /// reflect this fact here.  We don't want to query the variable during program
69 /// initialization due to the side-effects it may have.  Therefore, fixing this
70 /// is tricky as it may require a whole rethink of this module.
71 static const std::string config_lookup_names =
72     (fs::path("~/.kyua") / config_basename).str() + " or " +
73     (fs::path(KYUA_CONFDIR) / config_basename).str();
74 
75 
76 /// Loads the configuration file for this session, if any.
77 ///
78 /// This is a helper function that does not apply user-specified overrides.  See
79 /// the documentation for cli::load_config() for more details.
80 ///
81 /// \param cmdline The parsed command line.
82 ///
83 /// \return The loaded configuration file, or the configuration defaults if the
84 /// loading is disabled.
85 ///
86 /// \throw engine::error If the parsing of the configuration file fails.
87 ///     TODO(jmmv): I'm not sure if this is the raised exception.  And even if
88 ///     it is, we should make it more accurate.
89 config::tree
load_config_file(const cmdline::parsed_cmdline & cmdline)90 load_config_file(const cmdline::parsed_cmdline& cmdline)
91 {
92     // TODO(jmmv): We should really be able to use cmdline.has_option here to
93     // detect whether the option was provided or not instead of checking against
94     // the default value.
95     const fs::path filename = cmdline.get_option< cmdline::path_option >(
96         cli::config_option.long_name());
97     if (filename.str() == none_config) {
98         LD("Configuration loading disabled; using defaults");
99         return engine::default_config();
100     } else if (filename.str() != cli::config_option.default_value())
101         return engine::load_config(filename);
102 
103     const optional< fs::path > home = cli::get_home();
104     if (home) {
105         const fs::path path = home.get() / ".kyua" / config_basename;
106         try {
107             if (fs::exists(path))
108                 return engine::load_config(path);
109         } catch (const fs::error& e) {
110             // Fall through.  If we fail to load the user-specific configuration
111             // file because it cannot be openend, we try to load the system-wide
112             // one.
113             LW(F("Failed to load user-specific configuration file '%s': %s") %
114                path % e.what());
115         }
116     }
117 
118     const fs::path confdir(utils::getenv_with_default(
119         "KYUA_CONFDIR", KYUA_CONFDIR));
120 
121     const fs::path path = confdir / config_basename;
122     if (fs::exists(path)) {
123         return engine::load_config(path);
124     } else {
125         return engine::default_config();
126     }
127 }
128 
129 
130 /// Loads the configuration file for this session, if any.
131 ///
132 /// This is a helper function for cli::load_config() that attempts to load the
133 /// configuration unconditionally.
134 ///
135 /// \param cmdline The parsed command line.
136 ///
137 /// \return The loaded configuration file data.
138 ///
139 /// \throw engine::error If the parsing of the configuration file fails.
140 static config::tree
load_required_config(const cmdline::parsed_cmdline & cmdline)141 load_required_config(const cmdline::parsed_cmdline& cmdline)
142 {
143     config::tree user_config = load_config_file(cmdline);
144 
145     if (cmdline.has_option(cli::variable_option.long_name())) {
146         typedef std::pair< std::string, std::string > override_pair;
147 
148         const std::vector< override_pair >& overrides =
149             cmdline.get_multi_option< cmdline::property_option >(
150                 cli::variable_option.long_name());
151 
152         for (std::vector< override_pair >::const_iterator
153                  iter = overrides.begin(); iter != overrides.end(); iter++) {
154             try {
155                 user_config.set_string((*iter).first, (*iter).second);
156             } catch (const config::error& e) {
157                 // TODO(jmmv): Raising this type from here is obviously the
158                 // wrong thing to do.
159                 throw engine::error(e.what());
160             }
161         }
162     }
163 
164     return user_config;
165 }
166 
167 
168 }  // anonymous namespace
169 
170 
171 /// Standard definition of the option to specify a configuration file.
172 ///
173 /// You must use load_config() to load a configuration file while honoring the
174 /// value of this flag.
175 const cmdline::path_option cli::config_option(
176     'c', "config",
177     (std::string("Path to the configuration file; '") + none_config +
178      "' to disable loading").c_str(),
179     "file", config_lookup_names.c_str());
180 
181 
182 /// Standard definition of the option to specify a configuration variable.
183 const cmdline::property_option cli::variable_option(
184     'v', "variable",
185     "Overrides a particular configuration variable",
186     "K=V");
187 
188 
189 /// Loads the configuration file for this session, if any.
190 ///
191 /// The algorithm implemented here is as follows:
192 /// 1) If ~/.kyua/kyua.conf exists, load it.
193 /// 2) Otherwise, if sysconfdir/kyua.conf exists, load it.
194 /// 3) Otherwise, use the built-in settings.
195 /// 4) Lastly, apply any user-provided overrides.
196 ///
197 /// \param cmdline The parsed command line.
198 /// \param required Whether the loading of the configuration file must succeed.
199 ///     Some commands should run regardless, and therefore we need to set this
200 ///     to false for those commands.
201 ///
202 /// \return The loaded configuration file data.  If required was set to false,
203 /// this might be the default configuration data if the requested file could not
204 /// be properly loaded.
205 ///
206 /// \throw engine::error If the parsing of the configuration file fails.
207 config::tree
load_config(const cmdline::parsed_cmdline & cmdline,const bool required)208 cli::load_config(const cmdline::parsed_cmdline& cmdline,
209                  const bool required)
210 {
211     try {
212         return load_required_config(cmdline);
213     } catch (const engine::error& e) {
214         if (required) {
215             throw;
216         } else {
217             LW(F("Ignoring failure to load configuration because the requested "
218                  "command should not fail: %s") % e.what());
219             return engine::default_config();
220         }
221     }
222 }
223