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