Home | History | Annotate | Line # | Download | only in cli
config.cpp revision 1.1.1.1
      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
     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
    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
    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