rex/src/config/Config.cpp

338 lines
13 KiB
C++

#include "Config.h"
/**
* @brief General exception class for the Conf class.
*
* This exception class can be thrown to indicate an error in the Conf class. It provides an error message describing the problem.
*/
class ConfigLoadException: public std::exception
{
public:
/**
* @brief Constructor that takes a C-style string error message.
*
* The string contents are copied upon construction. The caller is responsible for deleting the char*.
*
* @param message C-style string error message.
*/
explicit ConfigLoadException(const char* message):
msg_(message)
{}
/**
* @brief Constructor that takes a C++ STL string error message.
*
* @param message The error message.
*/
explicit ConfigLoadException(const std::string& message):
msg_(message)
{}
/**
* @brief Virtual destructor to allow for subclassing.
*/
virtual ~ConfigLoadException() throw (){}
/**
* @brief Returns a pointer to the error description.
*
* @return A pointer to a const char*. The underlying memory is in posession of the Exception object. Callers must not attempt to free the memory.
*/
virtual const char* what() const throw (){
return msg_.c_str();
}
protected:
/**
* @brief Error message.
*/
std::string msg_;
};
/**
* @brief Set the string value of a key
*
* This method sets the value of a key as a string in a member variable.
* It first retrieves the string value of the key using the `get_string` method.
* If the key is not found, a `ConfigLoadException` is thrown.
* If the key is found, the string value is interpolated using the `interpolate` method and then assigned to the member variable.
* The method logs the task in the log file using the `slog.log_task` method.
*
* @param keyname The name of the key to retrieve
* @param object_member The reference to the member variable to store the value
* @param filename The name of the configuration file
*
* @throws ConfigLoadException If the key is not found in the configuration file
*/
void Conf::set_object_s(std::string keyname, std::string & object_member, std::string filename )
{
std::string jval_s;
if ( this->get_string(jval_s, keyname) != 0) {
throw ConfigLoadException( "'" + keyname + "' string is not set in the config file supplied: " + filename );
} else {
interpolate(jval_s);
object_member = jval_s;
}
this->slog.log_task( E_DEBUG, "SET_PROPERTY", "'" + keyname + "': " + object_member );
}
/**
* @brief Set the string value of a key with a derived path
*
* This method sets the value of a key as a string in a member variable with a derived path.
* It first retrieves the string value of the key using the `get_string` method.
* If the key is not found, a `ConfigLoadException` is thrown.
* If the key is found, the string value is interpolated using the `interpolate` method and then passed to the `prepend_project_root` method to derive the path.
* The derived path is then assigned to the member variable.
* The method logs the task in the log file using the `slog.log_task` method.
*
* @param keyname The name of the key to retrieve
* @param object_member The reference to the member variable to store the value
* @param filename The name of the configuration file
*
* @throws ConfigLoadException If the key is not found in the configuration file
*/
void Conf::set_object_s_derivedpath(std::string keyname, std::string & object_member, std::string filename )
{
std::string jval_s;
if ( this->get_string(jval_s, keyname) != 0) {
throw ConfigLoadException( "'" + keyname + "' string is not set in the config file supplied: " + filename );
} else {
interpolate(jval_s);
object_member = prepend_project_root( jval_s );
}
this->slog.log_task( E_DEBUG, "SET_PROPERTY", "'" + keyname + "': " + object_member );
}
/**
* @brief Set the boolean value of a key
*
* This method sets the value of a key as a boolean in a member variable.
* It first retrieves the boolean value of the key using the `get_bool` method.
* If the key is not found, a `ConfigLoadException` is thrown.
* If the key is found, the boolean value is assigned to the member variable.
* The method logs the task in the log file using the `slog.log_task` method.
*
* @param keyname The name of the key to retrieve
* @param object_member The reference to the member variable to store the value
* @param filename The name of the configuration file
*
* @throws ConfigLoadException If the key is not found in the configuration file
*/
void Conf::set_object_b(std::string keyname, bool & object_member, std::string filename )
{
bool jval_b;
if ( this->get_bool(jval_b, keyname) != 0) {
throw ConfigLoadException( "'" + keyname + "' boolean is not set in the config file supplied: " + filename );
} else {
object_member = jval_b;
}
this->slog.log_task( E_DEBUG, "SET_PROPERTY", "'" + keyname + "' " + std::to_string(object_member));
}
void removeTrailingSlash(std::string &str) {
if (!str.empty() && str.back() == '/') {
str.pop_back();
}
}
/**
* @brief Prepend the project root to a relative path
*
* This method prepends the project root to a given relative path.
* The project root is stored as a member variable in the `Conf` object.
* The method concatenates the project root and the relative path separated by a forward slash (/) and returns the result.
*
* @param relative_path The relative path to prepend the project root to
*
* @return The concatenated path with the project root and the relative path
*/
std::string Conf::prepend_project_root( std::string relative_path)
{
removeTrailingSlash(relative_path);
return this->project_root + "/" + relative_path;
}
/**
* @brief Check if a path exists
*
* This method checks if a given path exists.
* If the path exists, a log entry is made in the log file using the `slog.log_task` method with log level `E_DEBUG`.
* If the path does not exist, a log entry is made in the log file with log level `E_FATAL` and a `ConfigLoadException` is thrown.
*
* @param keyname The name of the key to be used in the log entry
* @param path The path to be checked
*
* @throws ConfigLoadException If the path does not exist
*/
void Conf::checkPathExists( std::string keyname, const std::string &path ) {
if ( exists( path ) ) {
this->slog.log_task( E_DEBUG, "SANITY_CHECKS", "'" + keyname + "' exists ('" + path + "')" );
} else {
this->slog.log_task( E_FATAL, "SANITY_CHECKS", "'" + keyname + "' does not exist ('" + path + "')" );
throw ConfigLoadException("Path does not exist.");
}
}
/**
* @brief Load the shells
*
* This method loads the shell definitions from a file.
* The path to the shell definition file is stored as a member variable in the `Conf` object.
* The method first logs the task of loading the shells using the `slog.log_task` method.
* The method then tries to load the JSON file using the `load_json_file` method.
* If an exception occurs while loading the file, a log entry with log level `E_FATAL` is made and a `ConfigLoadException` is thrown.
* The method then retrieves the serialized value of the "shells" key using the `get_serialized` method.
* If the "shells" key is not found, a log entry with log level `E_FATAL` is made and a `ConfigLoadException` is thrown.
* The method then loops through the serialized values, loads each shell definition using the `load_root` method of the `Shell` class, and stores each shell in the `shells` vector.
* The method logs each loaded shell using the `slog.log_task` method.
*
* @throws ConfigLoadException If there is an error parsing the shell definition file
*/
void Conf::load_shells() {
this->slog.log_task( E_DEBUG, "SHELLS", "Loading shells..." );
try {
// load the test file.
this->load_json_file( this->shell_definitions_path );
} catch (std::exception& e) {
this->slog.log_task( E_FATAL, "SHELLS", "Unable to load shell definition file: '" + this->shell_definitions_path + "'. Error: " + e.what());
throw ConfigLoadException("Parsing error in shell definitions file.");
}
Json::Value jbuff;
if ( this-> get_serialized( jbuff, "shells" ) != 0 ) {
this->slog.log_task( E_FATAL, "SHELLS", "Parsing error: '" + this->shell_definitions_path + "'. Error: 'shells' key not found." );
throw ConfigLoadException("Parsing error in shell definitions file.");
}
Shell tmp_S = Shell( this->LOG_LEVEL );
for ( int index = 0; index < jbuff.size(); index++ )
{
tmp_S.load_root( jbuff[index] );
this->shells.push_back( tmp_S );
this->slog.log_task( E_DEBUG, "SHELLS", "Loaded shell: '" + tmp_S.name + "' (" + tmp_S.path + ")" );
}
}
/**
* @brief Get a shell by name
*
* This method retrieves a `Shell` object by its name from the `shells` vector.
* The method loops through the `shells` vector and compares each shell's name to the input name.
* If a match is found, the matching shell is returned.
* If no match is found, a `ConfigLoadException` is thrown.
*
* @param name The name of the shell to retrieve
*
* @return The `Shell` object corresponding to the input name
*
* @throws ConfigLoadException If the input name does not match any shell in the `shells` vector
*/
Shell Conf::get_shell_by_name( std::string name ) {
for (auto &shell: this->shells) {
if (shell.name == name) {
return shell;
}
}
throw ConfigLoadException("The shell specified ('" + name + "') is not defined in the shell definitions file.");
}
/**
* @class Conf
* @brief Loads the configuration for the application
*
* This class is responsible for loading the configuration for the application. It uses the `JSON_Loader` class to
* parse the JSON configuration file and stores the relevant information.
*
* @note Currently, the class only supports loading a single configuration file. However, it can be expanded to
* support loading multiple files or detecting when a directory path is supplied for the units_path or plan_path and
* importing all Tasks and Units.
*
* @param filename - The filename to load the configuration from.
* @param LOG_LEVEL - The log level to use for logging messages.
*/
Conf::Conf(std::string filename, int LOG_LEVEL ): JSON_Loader(LOG_LEVEL ), slog(LOG_LEVEL, "_conf_" )
{
this->LOG_LEVEL = LOG_LEVEL;
this->slog.log_task( E_DEBUG, "LOAD", "Loading configuration file: " + filename );
interpolate( filename );
try {
// load the test file.
this->load_json_file( filename );
} catch (std::exception& e) {
this->slog.log( E_FATAL, "Unable to load configuration file: '" + filename + "'. Error: " + e.what());
throw ConfigLoadException("Parsing error in configuration file.");
}
Json::Value jbuff;
if ( this->get_serialized( jbuff, "config" ) != 0) {
this->slog.log_task( E_FATAL, "LOAD", "Unable to locate 'config' object in configuration file: " + filename );
throw ConfigLoadException("Unable to locate 'config' object in configuration file.");
} else {
this->slog.log_task( E_DEBUG, "LOAD", "Found 'config' object in configuration file: " + filename );
this->json_root = jbuff;
}
set_object_s( "project_root", this->project_root, filename );
interpolate( project_root );
// convert to an absolute path after all the interpolation is done.
this->project_root = get_absolute_path( this->project_root );
set_object_s( "logs_path", this->logs_path, filename );
interpolate( this->logs_path );
// all other paths are relative to project_root
set_object_s_derivedpath( "units_path", this->units_path, filename );
interpolate( this->units_path );
set_object_s_derivedpath( "shells_path", this->shell_definitions_path, filename );
interpolate( this->shell_definitions_path );
// ensure these paths exists, with exception to the logs_path, which will be created at runtime
this->slog.log_task( E_DEBUG, "SANITY_CHECKS", "Checking for sanity..." );
checkPathExists( "project_root", this->project_root );
checkPathExists( "units_path", this->units_path );
checkPathExists( "shells_path", this->shell_definitions_path );
// shells are scoped beyond plan so they need to be considered part of config
load_shells();
this->slog.log_task( E_DEBUG, "LOAD", "CONFIGURATION LOADED." );
}
/**
* @brief Gets the path to the Unit definition file
*
* This function returns the path to the Unit definition file that was specified in the configuration file.
*
* @return The path to the Unit definition file.
*/
std::string Conf::get_units_path() { return this->units_path; }
/**
* @brief Gets the path to the logs directory
*
* This function returns the path to the logs directory that was specified in the configuration file.
*
* @return The path to the logs directory.
*/
std::string Conf::get_logs_path() { return this->logs_path; }
/**
* @brief Gets the project root directory
*
* This function returns the path to the project root directory that was specified in the configuration file.
*
* @return The path to the project root directory.
*/
std::string Conf::get_project_root() { return this->project_root; }