Skip to content
Snippets Groups Projects
Forked from jlab / hallc / analyzer_software / hcana
144 commits behind the upstream repository.
ConfigObject.cpp 10.00 KiB
//============================================================================//
// A class based on the support from ConfigParser and ConfigValue             //
// It provides a simple way to read text file as configuration file, and read //
// or modify a parameter in the inherited class                               //
// The Configure() function should be overloaded according to specialized     //
// requirements, and be called after the parameters being configured          //
//                                                                            //
// Chao Peng                                                                  //
// 10/31/2016                                                                 //
//============================================================================//

#include <fstream>
#include <iostream>
#include <iomanip>
#include <algorithm>
#include "ConfigObject.h"



//============================================================================//
// Constructor, Destructor                                                    //
//============================================================================//

// constructor
ConfigObject::ConfigObject(const std::string &splitter, const std::string &ignore, bool case_ins)
: split_chars(splitter), ignore_chars(ignore), case_insensitive(case_ins), __empty_value("")
{
    // set default replace bracket
    replace_pair = std::make_pair("{", "}");
}

// destructor
ConfigObject::~ConfigObject()
{
    // place holder
}


//============================================================================//
// Public Member Function                                                     //
//============================================================================//

// configure the cluster method
void ConfigObject::Configure(const std::string &path)
{
    // save the path
    config_path = path;

    // clear the map
    config_map.clear();

    // read configuration file in
    ReadConfigFile(path);
}

// clear all the loaded configuration values
void ConfigObject::ClearConfig()
{
    config_path = "";
    config_map.clear();
}

// read configuration file and build the configuration map
bool ConfigObject::ReadConfigFile(const std::string &path)
{
    ConfigParser c_parser;
    c_parser.SetSplitters(split_chars); // self-defined splitters

    if (c_parser.ReadFile(path)) {
        // current directory
        parserProcess(c_parser, path);
        return true;
    } else {
        std::cerr << "Cannot open configuration file "
                  << "\"" << path << "\""
                  << std::endl;
        return false;
    }
}

// read the configuration string directly
void ConfigObject::ReadConfigString(const std::string &content)
{
    ConfigParser c_parser;
    c_parser.SetSplitters(split_chars);

    c_parser.ReadBuffer(content.c_str());

    parserProcess(c_parser, "buffer_string");
}

// continue parse the terms
void ConfigObject::parserProcess(ConfigParser &c_parser, const std::string &source)
{
    std::string cur_dir = ConfigParser::decompose_path(source).dir;

    while (c_parser.ParseLine()) {
        // possible control words
        if (c_parser.NbofElements() == 1) {
            std::string control = c_parser.TakeFirst();
            size_t pos = control.find("{THIS_DIR}");
            if(pos != std::string::npos)
                control.replace(pos, 10, cur_dir);
            parseControl(control);
        // var_name and var_value
        } else if (c_parser.NbofElements() == 2) {
            std::string var_name, key, var_value;
            c_parser >> var_name >> var_value;
            size_t pos = var_value.find("{THIS_DIR}");
            if(pos != std::string::npos)
                var_value.replace(pos, 10, cur_dir);
            parseTerm(std::move(var_name), std::move(var_value));
        // unsupported format
        } else {
            std::cout << "Warning: Unsupported format at line "
                      << c_parser.LineNumber() << " from " << source << "\n"
                      << "\"" << c_parser.CurrentLine() << "\""
                      << std::endl;
        }
    }
}

// check if a certain term is configured
bool ConfigObject::HasKey(const std::string &var_name)
const
{
    auto key = formKey(var_name);

    if (config_map.find(key) != config_map.end())
        return true;
    return false;
}

// list all the existing configuration keys
void ConfigObject::ListKeys()
const
{
    for (auto &it : config_map) {
        std::cout << it.first << std::endl;
    }
}

// get all the existing configuration keys
std::vector<std::string> ConfigObject::GetKeyList()
const
{
    std::vector<std::string> res;

    for (auto &it : config_map) {
        res.push_back(it.first);
    }

    return res;
}

// save current configuration into a file
void ConfigObject::SaveConfig(const std::string &path)
const
{
    std::string save_path;

    if (path.empty()) 
        save_path = config_path;
    if(save_path.empty())
        save_path = "latest.conf";

    std::ofstream save(save_path);
    for (auto &it : config_map) {
        save << it.first
             << " " << split_chars.front() << " "
             << it.second
             << std::endl;
    }
}

// get configuration value by its name/key
ConfigValue ConfigObject::GetConfigValue(const std::string &var_name)
const
{
    // convert to lower case and remove uninterested characters
    auto key = formKey(var_name);

    auto it = config_map.find(key);
    if (it == config_map.end()) {
        return __empty_value;
    } else {
        ConfigValue result(it->second);
        reform(result._value, replace_pair.first, replace_pair.second);
        return result;
    }
}

// set configuration value by its name/key
void ConfigObject::SetConfigValue(const std::string &var_name, const ConfigValue &c_value)
{
    // convert to lower case and remove uninterested characters
    auto key = formKey(var_name);

    config_map[key] = c_value;
}


// get configuration value from the map
// if no such config value exists, it will fill the default value in
ConfigValue ConfigObject::GetConfigValue(const std::string &var_name, const ConfigValue &def_value, bool verbose)
{
    auto key = formKey(var_name);

    auto it = config_map.find(key);
    if (it == config_map.end()) {
        if (def_value.IsEmpty())
            return __empty_value;

        if (verbose) {
            std::cout << var_name << " (key: " << key << ")"
                      << " not defined in configuration file, set to default value "
                      << def_value
                      << std::endl;
        }
        config_map[key] = def_value;
        return def_value;
    }

    ConfigValue result(it->second);
    reform(result._value, replace_pair.first, replace_pair.second);
    return result;
}



//============================================================================//
// Protected Member Function                                                  //
//============================================================================//

// build the key
std::string ConfigObject::formKey(const std::string &rawKey)
const
{
    std::string key = ConfigParser::str_remove(rawKey, ignore_chars);
    if (case_insensitive) {
        key = ConfigParser::str_lower(key);
    }
    return key;
}

// replace the contents inside replace_pair with the configuration value
void ConfigObject::reform(std::string &input, const std::string &op, const std::string &cl)
const
{
    // loop until no pair found
    while (true) {
        auto rpair = ConfigParser::find_pair(input, op, cl);
        if (rpair.first != std::string::npos && rpair.second != std::string::npos) {
            // get content inside the bracket
            std::string var = input.substr(rpair.first + op.size(),
                                           rpair.second - op.size() - rpair.first);
            // deal with nested structure
            reform(var, op, cl);

            // replace content
            std::string val;

            // environment variable
            if (rpair.first > 0 && input.at(rpair.first - 1) == '$') {
                val = std::getenv(var.c_str());
                // replace $ mark also
                rpair.first--;
            // ConfigObject variable
            } else {
                val = GetConfigValue(var)._value;
            }

            // replace variable with configuration value
            input.replace(rpair.first, rpair.second - rpair.first + cl.size(), val);
        } else {
            // no pair found any more
            return;
        }
    }
}




//============================================================================//
// Private Member Function                                                    //
//============================================================================//

// parse the control word and respond
void ConfigObject::parseControl(const std::string &word)
{
    if (ConfigParser::str_upper(word.substr(0, 7)) == "INCLUDE") {
        // need the most outer pair
        auto p = ConfigParser::find_pair(word, "(", ")");
        // not find pair
        if (p.first == std::string::npos || p.second == std::string::npos) {
            std::cout << "Unsupported control word format: " << word << "."
                      << "Expected: INCLUDE(path)"
                      << std::endl;
            return;
        }

        int begin = p.first + 1;
        int length = p.second - begin;

        std::string new_path = word.substr(begin, length);
        reform(new_path, replace_pair.first, replace_pair.second);

        ReadConfigFile(new_path);
    }
    else {
        std::cout << "Unsupported control word: " << word << std::endl;
    }
}

// parse the configuration term
void ConfigObject::parseTerm(std::string &&var_name, std::string &&var_value)
{
    // convert to lower case and remove uninterested characters
    auto key = formKey(var_name);

    if (key.back() == '+') {
        key.pop_back();
        auto it = config_map.find(key);
        if (it != config_map.end())
            it->second += var_value;
        else
            config_map[key] = var_value;
    } else {
        config_map[key] = var_value;
    }
}