Page MenuHomePhorge

Configuration.h
No OneTemporary

Size
40 KB
Referenced Files
None
Subscribers
None

Configuration.h

#ifndef ECHO_CONFIGURATION_H
#define ECHO_CONFIGURATION_H
#include <echo/Util/StringUtils.h>
#include <map>
#include <list>
namespace Echo
{
class FunctionBinder;
class FileSystem;
class File;
namespace Parser
{
class CalculatorWithVariables;
class VariableSubstitutor;
}
/**
* Configuration objects are name mapped values.
* You can load mapped configuration options from a file or set them using the various Set() methods.
* Configuration objects can also take a FunctionBinder object and parse a file like a script. This can be useful
* if you want to load a configuration in and perform various function calls during loading. The function calls
* need to be set up in the FunctionBinder for this to work.
*
* The option values are stored as strings and will be converted to the requested type (or at least it will be
* attempted) when calling Get<T>().
*
* Options can have multiple values and they can be indexed from a zero base in the order they were added. In
* the case of loading options from a file, if there are multiple of the same option then they will be indexed
* in the order they appears in the file.
*
* Sections
*
* Configuration objects also support sections. Sections allow you to break up options into related groups.
* Accessing options within sections is accomplished through the GetFromSection() and similar methods. These
* are designed to take a section "path" which are section names separated by `.` for example:
*
* Colour textColour = GetFromSection<Colour>("gui","text.colour",Colours::BLACK);
*
* ```
* [gui]
* text.colour=#aadf63
* ```
*
* Here are a few notes on using sections including some things to consider:
*
* - An empty section name refers to no section, that includes [] in a file and using GetFromSection() passing
* an empty section name.
* - Sub sections can be accessed from higher levels using `.` for example "gui.fonts" would refer to a section
* named "fonts" in the "gui" section from the root
* - It is possible to create a section tree, either by loading a file or programatically, however the tree
* structure is currently not preserved when writing to a file.
* - Sections, by default, will inherit the parent's variables. This can be controlled by setting sections.inherit
* (which is a bool) before the section is first declared in the file.
*/
class Configuration
{
public:
typedef std::vector<std::string> ConfigurationValueList;
typedef std::pair< std::string, ConfigurationValueList > ConfigurationPair;
typedef std::map< std::string, ConfigurationValueList > ConfigurationMap;
typedef std::map< std::string, std::string > SingleValueConfigurationMap;
typedef std::map< std::string, Configuration > SectionMap;
typedef std::pair< std::string, Configuration > SectionPair;
/**
* Constructor.
* @param fileSystem FileSystem to use to load files. Can be null but LoadFile() will not be available.
* @param functionBinder If a valid pointer, the function binder can be used while loading to call each line of the
* file as a function.
*/
Configuration(shared_ptr<FileSystem> fileSystem=shared_ptr<FileSystem>(), shared_ptr<FunctionBinder> functionBinder = shared_ptr<FunctionBinder>());
Configuration(const Configuration& other);
Configuration(Configuration&& other);
Configuration& operator=(const Configuration& other);
Configuration& operator=(Configuration&& other);
~Configuration();
/**
* Load options from a file.
* Configuration files can also specify other configuration files to load too using "include=filename[,optional]" option. The
* function will attempt to load relative files first. You can specify ",optional" if the file is considered optional which
* prevents the LoadFile() call from failing.
* @note Non-key-value-pair lines are stored with an empty key by default. If you need to save some memory you can change this
* behaviour with SetStoreLinesOnLoad(false).
* @param fileName name of the configuration file to attempt to open and load.
* @param parseLineThroughFunctionBinder if true and a function binder has been provided, will pass each line to the
* function binder to perform a function call. The function binder must be configured in advanced for this to work.
* @return true upon successful load, false if the passed in File object is not opened.
*/
bool LoadFile(const std::string& fileName, bool parseLineThroughFunctionBinder = false);
/**
* Parse options from a vector of strings.
* If you already have the file contents loaded you can use this method.
* @see LoadFile().
* @param parentFileName The parent file name that include lines can be relative to.
* @return true upon successful parse, false if parsing the string failed usually a result of an include failure.
*/
bool ParseLines(const std::vector<std::string>& lines, bool parseLineThroughFunctionBinder = false, std::string parentFileName = "");
/**
* Parse options from a string.
* The string will be split into lines.
* @see LoadFile().
* @param parentFileName The parent file name that include lines can be relative to.
* @return true upon successful parse, false if parsing the string failed usually a result of an include failure.
*/
bool ParseString(const std::string& content, bool parseLineThroughFunctionBinder = false, std::string parentFileName = "");
/**
* Version of LoadFile() that supports loading into a section.
*/
bool LoadFile(const std::string& section, const std::string& fileName, bool parseLineThroughFunctionBinder = false);
/**
* Version of ParseLines() that supports sections.
*/
bool ParseLines(const std::string& section, const std::vector<std::string>& lines, bool parseLineThroughFunctionBinder = false, std::string parentFileName = "");
/**
* Version of ParseString() that supports sections.
*/
bool ParseString(const std::string& section, const std::string& content, bool parseLineThroughFunctionBinder = false, std::string parentFileName = "");
/**
* Write configuration to a file.
* @note The Configuration object must have a FileSystem object in order for this method to succeed.
* @note To include a line such as a comment or function call add it to the configuration object
* with an empty key. For example: Add("","#your line).
* @note Lines are read in the order they are added (for index purposes), but written all at once so if you have a
* configuration file:
* a=b
* AFunctionCall()
* c=d
* AFunctionCall2()
* If the file is loaded then written the output will be:
* AFunctionCall()
* AFunctionCall2()
* a=b
* c=d
* This is due to internal sorting of values. This should be considered when overwriting a file where order is
* important, such as when substitution is used. Sections are written in blocks but the section order may change.
* @param fileName the name of the file to write. This will be in write mode, if you wish to append use the
* WriteFile(File&) overload.
* @note It is recommended to only use includes in files you will not write back as an include to load to due to
* ordering and the loss of include information (as the include is merged with the object it is included from).
* @note Sub sections are flattened during a write and each sub section becomes a section of the parent. This
* is because Configuration objects can load multiple files into the same object which is how includes are
* processed.
* @return true if the configuration was written successfully, otherwise false.
*/
bool WriteFile(const std::string& fileName) const;
/**
* Overload of WriteFile that takes an already open File object.
* @param sectionPath Used to provide a section path prefix for this write object to exist in.
*/
bool WriteFile(File& file, std::string sectionPath = "") const;
/**
* A variation of WriteFile that writes to a string instead of a file.
*
*/
bool WriteToString(std::string& output) const;
/**
* Parses a file as a simple script where each line is passed to the function binder.
* @param file Already open file to processed.
* @return false if there isn't a function binder or if the file is not opened, otherwise true.
*/
bool ParseConfigurationScript(File& file);
/**
* Get the number of options with the specified name.
* This will return the number of times a given option is specified, for example if you query with "person" and there is at least one
* line in the configuration file with "person" then the value returned will be 1 or more. The value represents the maxIndex+1 for
* that option.
* @param optionName
* @return the number of entries for the specified option name.
*/
size_t GetNumberOfOptionsNamed(const std::string& optionName) const;
/**
* Return whether an option exists.
* @param optionName
* @return true if the specified option exists, otherwise false.
*/
bool HasOption(const std::string& optionName) const;
/**
* Get a configuration option value.
* Use this version of Get() if you don't care if the option is available or not.
* @param optionName The name of the option to use as a key for look-up.
* @param defaultValue If the configuration option cannot be found, or conversion fails, this will be the value returned.
* @return The configuration option if found, else the default value.
*/
template< typename T >
T Get(const std::string& optionName, const T& defaultValue) const
{
T outObject;
Get(optionName, outObject, defaultValue, 0, true, false);
return outObject;
}
template<size_t size>
std::string Get(const std::string& optionName, const char (&defaultValue)[size]) const
{
return Get(optionName, std::string(defaultValue));
}
template< typename T >
T GetNoCalc(const std::string& optionName, const T& defaultValue) const
{
T outObject;
Get(optionName, outObject, defaultValue, 0, true, true);
return outObject;
}
template<size_t size>
std::string GetNoCalc(const std::string& optionName, const char (&defaultValue)[size]) const
{
return GetNoCalc(optionName, std::string(defaultValue));
}
/**
* Get a configuration option value at the specified index.
* Use this version of Get() if you don't care if the option is available or not.
* @param configurationName name of the option.
* @param defaultValue if the configuration option cannot be found or conversion fails this will be the value of outObject.
* @param index The index of the value.
* @return The configuration option or the default value.
*/
template< typename T >
T GetAtIndex(const std::string& optionName, const T& defaultValue, size_t index, bool suppressWarning = false, bool substituteOnly = false) const
{
T outObject;
Get(optionName, outObject, defaultValue, index, suppressWarning, substituteOnly);
return outObject;
}
/**
* Get a configuration option value.
* @param optionName name of the option.
* @param outObject The object to assign the value.
* @param defaultValue if the configuration option cannot be found or conversion fails this will be the value of outObject.
* @param index index of the configuration value if there are multiple of the same line.
* @param suppressWarning if true will suppress the option not found warning.
* @return true if successful, false if the default value was applied which may occur from the option not found or a conversion failure.
*/
template< typename T >
bool Get(const std::string& optionName, T& outObject, const T& defaultValue, size_t index = 0, bool suppressWarning = false, bool substituteOnly = false) const
{
ConfigurationMap::const_iterator it = mConfigurationOptions.find(optionName);
if(it==mConfigurationOptions.end())
{
if(mSectionsInheritVariables && mParent)
{
return mParent->Get<T>(optionName,outObject,defaultValue,index,suppressWarning,substituteOnly);
}
if(!suppressWarning)
{
ECHO_LOG_WARNING("Option '" << optionName << "' not found.");
}
outObject = defaultValue;
return false;
}
if(index>=it->second.size())
{
if(!suppressWarning)
{
ECHO_LOG_WARNING("Option not found for '" << optionName << "' at index " << index);
}
outObject = defaultValue;
return false;
}
// Get a variable replacement first so you can do things like:
// x = 10
// y = 2
// z = $y * x
std::string parsed = ParseVariableSubstitutions(it->second[index]);
optional<double> result;
if(!substituteOnly)
{
result = ParseCalculatorValues(parsed);
}
if(result)
{
std::stringstream ss;
ss << std::setprecision(mStreamPrecision) << result.value();
ss >> std::setprecision(mStreamPrecision) >> outObject;
if((ss.fail()))
{
outObject = defaultValue;
return false;
}
}else
{
std::stringstream ss(parsed);
ss >> std::setprecision(mStreamPrecision) >> outObject;
if((ss.fail()))
{
outObject = defaultValue;
return false;
}
}
return true;
}
template<size_t size>
bool Get(const std::string& optionName, std::string& outObject, const char (&defaultValue)[size], size_t index = 0, bool suppressWarning = false, bool substituteOnly = false) const
{
return Get(optionName, outObject, std::string(defaultValue), index, suppressWarning, substituteOnly);
}
/**
* Like GetOrFail() except it doesn't take a default value so the outObject is untouched if this method returns false.
*/
template< typename T >
bool GetOrFail(const std::string& optionName, T& outObject, size_t index = 0, bool suppressWarning = false, bool substituteOnly = false) const
{
ConfigurationMap::const_iterator it = mConfigurationOptions.find(optionName);
if(it==mConfigurationOptions.end())
{
if(mSectionsInheritVariables && mParent)
{
return mParent->GetOrFail<T>(optionName,outObject,index,suppressWarning,substituteOnly);
}
if(!suppressWarning)
{
ECHO_LOG_WARNING("Option '" << optionName << "' not found.");
}
return false;
}
if(index>=it->second.size())
{
if(!suppressWarning)
{
ECHO_LOG_WARNING("Option not found for '" << optionName << "' at index " << index);
}
return false;
}
std::string parsed = ParseVariableSubstitutions(it->second[index]);
optional<double> result;
if(!substituteOnly)
{
result = ParseCalculatorValues(parsed);
}
if(result)
{
std::stringstream ss;
ss << std::setprecision(mStreamPrecision) << result.value();
ss >> std::setprecision(mStreamPrecision) >> outObject;
if((ss.fail()))
{
return false;
}
}else
{
std::stringstream ss(parsed);
ss >> std::setprecision(mStreamPrecision) >> outObject;
if((ss.fail()))
{
return false;
}
}
return true;
}
/**
* Get a configuration option and convert it into multiple parameters.
* @param optionName name of the option.
* @param outObjects The target objects to assign the converted values.
* @param index index of the configuration value if there are multiple of the same line.
* @param suppressWarning if true will suppress the option not found warning.
* @return true if successful, false if the default value was applied which may occur from the option not found or a conversion failure.
*/
template< typename ...Types >
bool Get(const std::string& optionName, const std::string& separators, Size index, Types&... outObjects) const
{
const bool suppressWarning = false;
const bool substituteOnly = false;
ConfigurationMap::const_iterator it = mConfigurationOptions.find(optionName);
if(it==mConfigurationOptions.end())
{
if(mSectionsInheritVariables && mParent)
{
return mParent->Get<Types...>(optionName, separators, index, outObjects...);
}
if(!suppressWarning)
{
ECHO_LOG_WARNING("Option '" << optionName << "' not found.");
}
return false;
}
if(index >= it->second.size())
{
if(!suppressWarning)
{
ECHO_LOG_WARNING("Option not found for '" << optionName << "' at index " << index);
}
return false;
}
std::string parsed = ParseVariableSubstitutions(it->second[index]);
std::vector< std::string > split;
Utils::String::Split(parsed, separators, split);
if(!substituteOnly)
{
// Run each split value through the calculator
for(std::string& calculated : split)
{
optional<double> result = ParseCalculatorValues(parsed);
if(result)
{
// There is a convert for dobule so we ignore the return
Utils::String::ToString(*result, calculated, mStreamPrecision);
}
}
}
return Utils::String::FromStringMultiple(split, outObjects...);
}
/**
* Get all of the option values in a vector using the specified key.
* This method will attempt to convert all of the values of all entries that have the specified key to the specified type.
* If any of the conversions fail the method will return an empty list.
* If the key isn't found the method will return an empty list.
* @param optionName The name, or key, to look up.
* @param suppressWarning if true any warnings will be suppressed
* @return An empty vector if the option name isn't found or if a conversion fails, otherwise a vector containing the converted values.
*/
template< typename T >
std::vector<T> GetAll(const std::string& optionName, bool suppressWarning = false, bool substituteOnly = false) const
{
ConfigurationMap::const_iterator it = mConfigurationOptions.find(optionName);
if(it==mConfigurationOptions.end())
{
if(mSectionsInheritVariables && mParent)
{
return mParent->GetAll<T>(optionName,suppressWarning,substituteOnly);
}
if(!suppressWarning)
{
ECHO_LOG_WARNING("Option '" << optionName << "' not found.");
}
return std::vector<T>();
}
std::vector<T> outVector;
Size numberOfFailedConversions = 0;
Size numberOfOptions = GetNumberOfOptionsNamed(optionName);
for(Size i = 0; i < numberOfOptions; ++i)
{
T converted;
if(GetOrFail(optionName, converted, i, suppressWarning, substituteOnly))
{
outVector.push_back(converted);
}else
{
// I've decided to continue so we can report all errors if suppressWarning is false
++numberOfFailedConversions;
}
}
if(numberOfFailedConversions>0)
{
if(!suppressWarning)
{
ECHO_LOG_WARNING("Failed to convert " << numberOfFailedConversions << " entries to " << typeid(T).name());
}
return std::vector<T>();
}
return outVector;
}
/**
* A version of the above method because strings do not need to be converted.
* We cannot overload this because the only thing that differs is the return type.
*/
std::vector<std::string> GetAllAsStrings(const std::string& optionName, bool suppressWarning = false) const;
/**
* Get all option names
* This only returns for the current object, no children or parents are queried.
*/
std::vector< std::string > GetAllOptionNames() const;
/**
* Set a configuration option value.
* Use this version of Get() if you don't care if the option is available or not.
* @param optionName name of the option.
* @param optionValue The value to set option.
* @return The configuration option or the default value.
*/
template< typename T >
bool Set(const std::string& optionName, const T& optionValue, bool overwriteExisting = true)
{
std::stringstream ss;
ss << std::setprecision(mStreamPrecision) << optionValue;
if(ss.fail())
{
return false;
}
return Set(optionName,ss.str(),overwriteExisting);
}
/**
* Set an option's value by name with the option to overwrite existing values.
* @param optionName The option's name, used to reference the option.
* @param optionValue The value of the option.
* @param overwriteExisting if true any options with the same name will be overwritten.
* @return If overwriteExisting is false and the option exists the method will return false,
* otherwise true.
*/
bool Set(const std::string& optionName, const std::string& optionValue, bool overwriteExisting = true);
/**
* Add an option and attempt to automatically convert to a storage string.
* This method appends the value to the option name and never overwrites.
* The type should be convertible using a stringstream.
* @param optionName the name of the option.
* @param optionValue The value.
* @return true if the option is added, false if conversion failed.
*/
template< typename T >
bool Add(const std::string& optionName, const T& optionValue)
{
std::stringstream ss;
ss << std::setprecision(mStreamPrecision) << optionValue;
if(ss.fail())
{
return false;
}
Add(optionName,ss.str());
return true;
}
/**
* Add an option.
* This method appends the value to the option name and never overwrites.
* @param optionName the option name
* @param optionValue
*/
void Add(const std::string& optionName, const std::string& optionValue);
/**
* Add a "line" to the configuration object.
* Lines are added as configuration options with empty keys so you can access them. Normally you won't
* bother using lines unless you are planning to write the configuration file out.
* Lines are written in the order they are added.
* @param line The contents of a line to include in the configuration file.
*/
void AddLine(const std::string& line)
{
Add("",line);
}
/**
* Add multiple options at once using a string-string map.
* @param optionsMap The options.
*/
void AddOptions(const SingleValueConfigurationMap& optionsMap);
/**
* Remove an option by name.
* @param optionName
* @return false if the option doesn't exist, otherwise true if the option was found and removed.
*/
bool Remove(const std::string& optionName);
/**
* Remove an option by name at index.
* @param optionName Option name
* @param index Index of option to remove
* @return false if the option or index doesn't exist, otherwise true if the option at index was found and removed.
*/
bool RemoveAtIndex(const std::string& optionName, Size index);
/**
* Clear all values previously added/set, including all subsections.
*/
void Clear();
/**
* Version of GetNumberOfOptionsNamed() that supports sections.
*/
size_t GetNumberOfOptionsNamedInSection(const std::string& section, const std::string& optionName) const;
/**
* Version of HasOption() that supports sections.
*/
bool HasOptionInSection(const std::string& section, const std::string& optionName) const;
/**
* Version of Get() supporting section access.
*/
template< typename T >
T GetFromSection(const std::string& section, const std::string& optionName, const T& defaultValue, bool suppressWarning = false, bool substituteOnly = false) const
{
T outObject;
GetFromSection(section, optionName, outObject, defaultValue, 0, suppressWarning, substituteOnly);
return outObject;
}
template<size_t size>
std::string GetFromSection(const std::string& section, const std::string& optionName, const char (&defaultValue)[size], bool suppressWarning = false, bool substituteOnly = false) const
{
return GetFromSection(section, optionName, std::string(defaultValue), suppressWarning, substituteOnly);
}
/**
* Version of GetAtIndex() that supports sections
*/
template< typename T >
T GetFromSectionAtIndex(const std::string& section, const std::string& optionName, const T& defaultValue, size_t index, bool suppressWarning = false, bool substituteOnly = false) const
{
T outObject;
GetFromSection(section, optionName, outObject, defaultValue, index, suppressWarning, substituteOnly);
return outObject;
}
/**
* Version of Get() that supports sections
*/
template< typename T >
bool GetFromSection(const std::string& section, const std::string& optionName, T& outObject, const T& defaultValue, size_t index = 0, bool suppressWarning = false, bool substituteOnly = false) const
{
if(section.empty())
{
return Get(optionName, outObject, defaultValue, index, suppressWarning, substituteOnly);
}
std::string subsections;
const Configuration* thisSection = GetSection(section,subsections);
if(!thisSection)
{
ECHO_LOG_WARNING("Unable to find section " << thisSection << " could not look up " << optionName);
return false;
}
// When a separator isn't found subsections will be empty causing a normal lookup to occur.
return thisSection->GetFromSection<T>(subsections, optionName, outObject, defaultValue, index, suppressWarning, substituteOnly);
}
/**
* Version of GetOrFail() that supports sections
*/
template< typename T >
bool GetFromSectionOrFail(const std::string& section, const std::string& optionName, T& outObject, size_t index = 0, bool suppressWarning = false, bool substituteOnly = false) const
{
if(section.empty())
{
return GetOrFail<T>(optionName, outObject, index, suppressWarning, substituteOnly);
}
std::string subsections;
const Configuration* thisSection = GetSection(section,subsections);
if(!thisSection)
{
ECHO_LOG_WARNING("Unable to find section " << thisSection << " could not look up " << optionName);
return false;
}
// When a separator isn't found subsections will be empty causing a normal lookup to occur.
return thisSection->GetFromSectionOrFail<T>(subsections, optionName, outObject, index, suppressWarning, substituteOnly);
}
/**
* Version of GetAll() that supports sections
*/
template< typename T >
std::vector<T> GetAllFromSection(const std::string& section, const std::string& optionName, bool suppressWarning = false, bool substituteOnly = false) const
{
if(section.empty())
{
return GetAll<T>(optionName, suppressWarning, substituteOnly);
}
std::string subsections;
const Configuration* thisSection = GetSection(section,subsections);
if(!thisSection)
{
ECHO_LOG_WARNING("Unable to find section " << thisSection << " could not look up " << optionName);
return false;
}
// When a separator isn't found subsections will be empty causing a normal lookup to occur.
return thisSection->GetAllFromSection<T>(subsections, optionName, suppressWarning, substituteOnly);
}
/**
* Version of GetAllAsStrings() that supports sections
*/
std::vector<std::string> GetAllFromSectionAsStrings(const std::string& section, const std::string& optionName, bool suppressWarning = false) const;
/**
* Version of Set() that supports sections.
*/
template< typename T >
bool SetInSection(const std::string& section, const std::string& optionName, const T& optionValue, bool overwriteExisting = true)
{
if(section.empty())
{
return Set<T>(optionName, optionValue, overwriteExisting);
}
std::string subsections;
Configuration& thisSection = GetOrCreateSection(section,subsections);
return thisSection.SetInSection<T>(subsections, optionName, optionValue, overwriteExisting);
}
/**
* Version of Add() that supports sections.
*/
template< typename T >
bool AddToSection(const std::string& section, const std::string& optionName, const T& optionValue)
{
if(section.empty())
{
return Add<T>(optionName, optionValue);
}
std::string subsections;
Configuration& thisSection = GetOrCreateSection(section,subsections);
return thisSection.AddToSection<T>(subsections, optionName, optionValue);
}
/**
* Version of AddLine() that supports sections.
*/
void AddLineToSection(const std::string& section, const std::string& line)
{
AddToSection(section,"",line);
}
/**
* Version of AddOptions() that supports sections
*/
void AddOptionsToSection(const std::string& section, const SingleValueConfigurationMap& optionsMap);
/**
* Version of Remove() that supports sections.
*/
bool RemoveFromSection(const std::string& section, const std::string& optionName);
/**
* Remove option by name at index in section.
* @param sectionName Section name. If empty it will call remove on this object.
* @param optionName Option name
* @param index Index of option to remove
* @return false if the option or index doesn't exist, otherwise true if the option at index was found and removed.
*/
bool RemoveFromSectionAtIndex(const std::string& sectionName, const std::string& optionName, Size index);
/**
* Clear all values previously added/set in the specified section.
* If section is empty the object is cleared, including all subsections.
*/
void ClearSection(const std::string& section);
void SetFunctionBinder(shared_ptr<FunctionBinder> functionBinder) {mFunctionBinder=functionBinder;}
shared_ptr<FunctionBinder> GetFunctionBinder() const {return mFunctionBinder;}
void SetFileSystem(shared_ptr<FileSystem> fileSystem) {mFileSystem=fileSystem;}
shared_ptr<FileSystem> GetFileSystem() const {return mFileSystem;}
/**
* Set whether to store non-key-value-pair lines on file load.
*/
void SetStoreLinesOnLoad(bool store){mStoreLinesOnLoad=store;}
/**
* Get whether to store non-key-value-pair lines on file load.
*/
bool GetStoreLinesOnLoad() const {return mStoreLinesOnLoad;}
/**
* Set whether or not values set rather than add when parsing.
* @note This mode can be changed inline with configuration.overwrite=true|false[,persist]
*/
void SetParsingSetsNotAdd(bool setNotAdd){mParsingSetsNotAdd=setNotAdd;}
/**
* Get whether or not values set rather than add when parsing.
*/
bool GetParsingSetsNotAdd() const {return mParsingSetsNotAdd;}
/**
* Set whether or not to trim line endings of whitespace from the end (right) of the stright.
* @note This is on by default.
* @note This can be used to force trimming \t,\r,\n and space.
* @note This does not use the locale.
* @note This mode can be changed inline with configuration.trim.lineendings=true|false[,persist]
*/
void SetParsingTrimsLineEndings(bool trim){mParsingTrimsLineEndings=trim;}
/**
* Get whether or not parsing trims line endings.
*/
bool GetParsingTrimsLineEndings() const {return mParsingTrimsLineEndings;}
/**
* Sets the precision of the streams for reading and writing values.
* This affects any future reads and writes.
* @param streamPrecision The stream precision, see std::setprecision()
*/
void SetStreamPrecision(Size streamPrecision)
{
mStreamPrecision = streamPrecision;
}
/**
* Get the current precision used for stream operations.
* @see SetStreamPrecision()
*/
Size GetStreamPrecision() const {return mStreamPrecision;}
Configuration* GetSection(const std::string& sectonPath);
const Configuration* GetSection(const std::string& sectonPath) const;
/**
* Named section.
* Because it is nicer to reference mName or mSection than first and second from a std::pair
*/
struct NamedSection
{
std::string mName;
const Configuration* mSection;
};
/**
* Get all sections with their names
* @return a vector of NamedSections in the order they were created.
*/
std::vector< NamedSection > GetAllSections() const;
/**
* Get all section names
* @return a vector of section names in the order the sections were created.
*/
std::vector< std::string > GetAllSectionNames() const;
/**
* Get the or create a Configuration section.
* @note Only the first section defined in the path is created. That is, if a subscection is specified
* "section.subsection" it will be ignored and only the "section" will be used.
* @note The parent object owns the section, so any references held after this call must be released
* before the parent is destroyed.
* @param section sectionPath
* @return Configuration& the section.
*/
Configuration& GetOrCreateSection(const std::string& sectonPath);
void SetSectionsInheritVariables(bool sectionsInheritVariables);
bool GetSectionsInheritVariables() const;
template< typename T >
bool ForEach(const std::string& optionName, function<bool(const T&)> operation, bool suppressWarning = false, bool substituteOnly = false) const
{
ConfigurationMap::const_iterator it = mConfigurationOptions.find(optionName);
if(it==mConfigurationOptions.end())
{
if(mSectionsInheritVariables && mParent)
{
return mParent->ForEach<T>(optionName,operation,suppressWarning,substituteOnly);
}
if(!suppressWarning)
{
ECHO_LOG_WARNING("Option '" << optionName << "' not found.");
}
return false;
}
ConfigurationValueList::const_iterator itList = it->second.begin();
ConfigurationValueList::const_iterator itListEnd = it->second.end();
while(itList!=itListEnd)
{
T converted;
std::string parsed = ParseVariableSubstitutions(*itList);
optional<double> result;
if(!substituteOnly)
{
result = ParseCalculatorValues(parsed);
}
if(result)
{
std::stringstream ss;
ss << std::setprecision(mStreamPrecision) << result.value();
ss >> std::setprecision(mStreamPrecision) >> converted;
if((ss.fail()))
{
if(!suppressWarning)
{
ECHO_LOG_WARNING("Failed to convert option '" << optionName << "' with data '" << (parsed) << "'");
}
return false;
}
}else
{
std::stringstream ss(parsed);
ss >> std::setprecision(mStreamPrecision) >> converted;
if((ss.fail()))
{
if(!suppressWarning)
{
ECHO_LOG_WARNING("Failed to convert option '" << optionName << "' with data '" << (parsed) << "'");
}
return false;
}
}
if(!operation(converted))
{
if(!suppressWarning)
{
ECHO_LOG_WARNING("Operation callback failed for '" << optionName << "' with data '" << (parsed) << "'");
return false;
}
}
itList++;
}
return true;
}
/**
* Version of ForEach() that takes a callback with a variable number of parameters where the parameters are converted after
* splitting each value with the separators.
* @see Utils::String::Split()
* Useage:
* @param optionName
* @param separators
* @param operation
* @return true
* @return false
*/
template< typename ...Types, typename Callable>
bool ForEachSplit(const std::string& optionName, const std::string& separators, Callable operation) const
{
bool suppressWarning = false;
bool substituteOnly = false;
ConfigurationMap::const_iterator it = mConfigurationOptions.find(optionName);
if(it==mConfigurationOptions.end())
{
if(mSectionsInheritVariables && mParent)
{
return mParent->ForEachSplit<Types...>(optionName,separators,operation);
}
if(!suppressWarning)
{
ECHO_LOG_WARNING("Option '" << optionName << "' not found.");
}
return false;
}
ConfigurationValueList::const_iterator itList = it->second.begin();
ConfigurationValueList::const_iterator itListEnd = it->second.end();
while(itList!=itListEnd)
{
std::string parsed = ParseVariableSubstitutions(*itList);
std::vector< std::string > split;
Utils::String::Split(parsed, separators, split);
if(!substituteOnly)
{
// Run each split value through the calculator
for(std::string& calculated : split)
{
optional<double> result = ParseCalculatorValues(parsed);
if(result)
{
// There is a convert for dobule so we ignore the return
Utils::String::ToString(*result, calculated, mStreamPrecision);
}
}
}
std::tuple<Types...> values;
bool converted = std::apply( [&](Types&... params) {
return Utils::String::FromStringMultiple(split, params...);
}, values);
if(!converted)
{
return false;
}
if(!std::apply(operation, values))
{
if(!suppressWarning)
{
ECHO_LOG_WARNING("Operation callback failed for '" << optionName);
return false;
}
}
itList++;
}
return true;
}
template< typename T >
bool ForEachInSection(const std::string& optionName, const std::string& sectionName, function<bool(const T&)> operation , bool suppressWarning = false, bool substituteOnly = false) const
{
const Configuration* section = GetSection(sectionName);
if(!section)
{
if(!suppressWarning)
{
ECHO_LOG_WARNING("Could not find section '" << sectionName << "'");
}
return false;
}
return section->ForEach<T>(optionName, operation, suppressWarning, substituteOnly);
}
/**
* Get the Parent configuration object.
* This will be null if it is the root object.
* @return Pointer to the parent Configuration object or nullptr
*/
const Configuration* GetParent() const;
Configuration* GetParent();
/**
* Check if this configuration object has a parent.
* @return true if this is a section that has a parent, otherwise false for the root object
*/
bool HasParent() const;
/**
* Set the Substituion Pattern for variable substitution.
* The default is to permit typical code variables after $ or ${variables.with-special.separators}.
* @note This needs to be a valid regex pattern. No validation is performed.
*/
void SetSubstituionPattern(std::string pattern);
private:
void SetParent(Configuration* parent);
Configuration* GetSection(const std::string& sectonPath, std::string& subsections);
const Configuration* GetSection(const std::string& sectonPath, std::string& subsections) const;
Configuration& GetOrCreateSection(const std::string& sectonPath, std::string& subsections);
void RegisterOptionWithParsers(const std::string& optionName);
void RegisterOptionWithParsers(const std::string& section, const std::string& optionName);
std::string ParseVariableSubstitutions(const std::string& input) const;
optional<double> ParseCalculatorValues(const std::string& input) const;
ConfigurationMap mConfigurationOptions;
Configuration* mParent;
SectionMap mSections;
std::list< std::string > mSectionOrder;
shared_ptr<FileSystem> mFileSystem;
shared_ptr<FunctionBinder> mFunctionBinder;
shared_ptr<Parser::CalculatorWithVariables> mParser;
shared_ptr<Parser::VariableSubstitutor> mVariableSubstitutor;
std::string mSubstitutionPattern;
Size mFileDepth;
Size mStreamPrecision;
bool mStoreLinesOnLoad;
bool mSectionsInheritVariables;
bool mParsingSetsNotAdd;
bool mParsingTrimsLineEndings;
};
/**
* Specialisation of Get() for bool to support converting "1", "0", "true" and "false".
*/
template<>
bool Configuration::Get<bool>(const std::string& optionName, bool& outObject, const bool& defaultValue, size_t index, bool suppressWarning, bool substituteOnly) const;
/**
* Specialisation of Get() for std::string to prevent the string ending at whitespae.
*/
template<>
bool Configuration::Get(const std::string& configurationName, std::string& outObject, const std::string& defaultValue, size_t index, bool suppressWarning, bool substituteOnly) const;
/**
* Specialisation of GetOrFail() for std::string to prevent the string ending at whitespae.
*/
template<>
bool Configuration::GetOrFail<std::string>(const std::string& optionName, std::string& outObject, size_t index, bool suppressWarning, bool substituteOnly) const;
/**
* Specialisation of ForEach() for std::string to prevent the string ending at whitespae.
*/
template<>
bool Configuration::ForEach<std::string>(const std::string& optionName, function<bool(const std::string&)> operation, bool suppressWarning, bool substituteOnly) const;
}
#endif

File Metadata

Mime Type
text/x-c++
Expires
Mon, May 19, 12:39 PM (17 h, 15 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
76972
Default Alt Text
Configuration.h (40 KB)

Event Timeline