diff --git a/deps/inih/INIReader.h b/deps/inih/INIReader.h new file mode 100644 index 00000000..fbb5bc2c --- /dev/null +++ b/deps/inih/INIReader.h @@ -0,0 +1,441 @@ +// Read an INI file into easy-to-access name/value pairs. + +// inih and INIReader are released under the New BSD license (see LICENSE.txt). +// Go to the project home page for more info: +// +// https://github.com/benhoyt/inih +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#ifndef __INI_H__ +#define __INI_H__ + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Typedef for prototype of handler function. */ +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value); + +/* Typedef for prototype of fgets-style reader function. */ +typedef char* (*ini_reader)(char* str, int num, void* stream); + +/* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's configparser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), -1 on file open error, or -2 on memory allocation + error (only when INI_USE_STACK is zero). +*/ +int ini_parse(const char* filename, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes an ini_reader function pointer instead of + filename. Used for implementing custom or string-based I/O. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user); + +/* Nonzero to allow multi-line value parsing, in the style of Python's + configparser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +#define INI_ALLOW_MULTILINE 1 +#endif + +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of + the file. See http://code.google.com/p/inih/issues/detail?id=21 */ +#ifndef INI_ALLOW_BOM +#define INI_ALLOW_BOM 1 +#endif + +/* Nonzero to allow inline comments (with valid inline comment characters + specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match + Python 3.2+ configparser behaviour. */ +#ifndef INI_ALLOW_INLINE_COMMENTS +#define INI_ALLOW_INLINE_COMMENTS 1 +#endif +#ifndef INI_INLINE_COMMENT_PREFIXES +#define INI_INLINE_COMMENT_PREFIXES ";" +#endif + +/* Nonzero to use stack, zero to use heap (malloc/free). */ +#ifndef INI_USE_STACK +#define INI_USE_STACK 1 +#endif + +/* Stop parsing on first error (default is to keep parsing). */ +#ifndef INI_STOP_ON_FIRST_ERROR +#define INI_STOP_ON_FIRST_ERROR 0 +#endif + +/* Maximum line length for any line in INI file. */ +#ifndef INI_MAX_LINE +#define INI_MAX_LINE 200 +#endif + +#ifdef __cplusplus +} +#endif + +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include +#include + +#if !INI_USE_STACK +#include +#endif + +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* Strip whitespace chars off end of given string, in place. Return s. */ +inline static char* rstrip(char* s) +{ + char* p = s + strlen(s); + while (p > s && isspace((unsigned char)(*--p))) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +inline static char* lskip(const char* s) +{ + while (*s && isspace((unsigned char)(*s))) + s++; + return (char*)s; +} + +/* Return pointer to first char (of chars) or inline comment in given string, + or pointer to null at end of string if neither found. Inline comment must + be prefixed by a whitespace character to register as a comment. */ +inline static char* find_chars_or_comment(const char* s, const char* chars) +{ +#if INI_ALLOW_INLINE_COMMENTS + int was_space = 0; + while (*s && (!chars || !strchr(chars, *s)) && + !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { + was_space = isspace((unsigned char)(*s)); + s++; + } +#else + while (*s && (!chars || !strchr(chars, *s))) { + s++; + } +#endif + return (char*)s; +} + +/* Version of strncpy that ensures dest (size bytes) is null-terminated. */ +inline static char* strncpy0(char* dest, const char* src, size_t size) +{ + strncpy(dest, src, size); + dest[size - 1] = '\0'; + return dest; +} + +/* See documentation in header file. */ +inline int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ +#if INI_USE_STACK + char line[INI_MAX_LINE]; +#else + char* line; +#endif + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + +#if !INI_USE_STACK + line = (char*)malloc(INI_MAX_LINE); + if (!line) { + return -2; + } +#endif + + /* Scan through stream line by line */ + while (reader(line, INI_MAX_LINE, stream) != NULL) { + lineno++; + + start = line; +#if INI_ALLOW_BOM + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; + } +#endif + start = lskip(rstrip(start)); + + if (*start == ';' || *start == '#') { + /* Per Python configparser, allow both ; and # comments at the + start of a line */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { + +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(start, NULL); + if (*end) + *end = '\0'; + rstrip(start); +#endif + + /* Non-blank line with leading whitespace, treat as continuation + of previous name's value (as per Python configparser). */ + if (!handler(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = find_chars_or_comment(start + 1, "]"); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } + else if (*start) { + /* Not a comment, must be a name[=:]value pair */ + end = find_chars_or_comment(start, "=:"); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = lskip(end + 1); +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(value, NULL); + if (*end) + *end = '\0'; +#endif + rstrip(value); + + /* Valid name[=:]value pair found, call handler */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!handler(user, section, name, value) && !error) + error = lineno; + } + else if (!error) { + /* No '=' or ':' found on name[=:]value line */ + error = lineno; + } + } + +#if INI_STOP_ON_FIRST_ERROR + if (error) + break; +#endif + } + +#if !INI_USE_STACK + free(line); +#endif + + return error; +} + +/* See documentation in header file. */ +inline int ini_parse_file(FILE* file, ini_handler handler, void* user) +{ + return ini_parse_stream((ini_reader)fgets, file, handler, user); +} + +/* See documentation in header file. */ +inline int ini_parse(const char* filename, ini_handler handler, void* user) +{ + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) + return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} + +#endif /* __INI_H__ */ + + +#ifndef __INIREADER_H__ +#define __INIREADER_H__ + +#include +#include +#include + +// Read an INI file into easy-to-access name/value pairs. (Note that I've gone +// for simplicity here rather than speed, but it should be pretty decent.) +class INIReader +{ +public: + // Empty Constructor + INIReader() {}; + + // Construct INIReader and parse given filename. See ini.h for more info + // about the parsing. + INIReader(std::string filename); + + // Return the result of ini_parse(), i.e., 0 on success, line number of + // first error on parse error, or -1 on file open error. + int ParseError() const; + + // Return the list of sections found in ini file + const std::set& Sections() const; + + // Get a string value from INI file, returning default_value if not found. + std::string Get(std::string section, std::string name, + std::string default_value) const; + + // Get an integer (long) value from INI file, returning default_value if + // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2"). + long GetInteger(std::string section, std::string name, long default_value) const; + + // Get a real (floating point double) value from INI file, returning + // default_value if not found or not a valid floating point value + // according to strtod(). + double GetReal(std::string section, std::string name, double default_value) const; + + // Get a boolean value from INI file, returning default_value if not found or if + // not a valid true/false value. Valid true values are "true", "yes", "on", "1", + // and valid false values are "false", "no", "off", "0" (not case sensitive). + bool GetBoolean(std::string section, std::string name, bool default_value) const; + +protected: + int _error; + std::map _values; + std::set _sections; + static std::string MakeKey(std::string section, std::string name); + static int ValueHandler(void* user, const char* section, const char* name, + const char* value); +}; + +#endif // __INIREADER_H__ + + +#ifndef __INIREADER__ +#define __INIREADER__ + +#include +#include +#include + +using std::string; + +inline INIReader::INIReader(string filename) +{ + _error = ini_parse(filename.c_str(), ValueHandler, this); +} + +inline int INIReader::ParseError() const +{ + return _error; +} + +inline const std::set& INIReader::Sections() const +{ + return _sections; +} + +inline string INIReader::Get(string section, string name, string default_value) const +{ + string key = MakeKey(section, name); + return _values.count(key) ? _values.at(key) : default_value; +} + +inline long INIReader::GetInteger(string section, string name, long default_value) const +{ + string valstr = Get(section, name, ""); + const char* value = valstr.c_str(); + char* end; + // This parses "1234" (decimal) and also "0x4D2" (hex) + long n = strtol(value, &end, 0); + return end > value ? n : default_value; +} + +inline double INIReader::GetReal(string section, string name, double default_value) const +{ + string valstr = Get(section, name, ""); + const char* value = valstr.c_str(); + char* end; + double n = strtod(value, &end); + return end > value ? n : default_value; +} + +inline bool INIReader::GetBoolean(string section, string name, bool default_value) const +{ + string valstr = Get(section, name, ""); + // Convert to lower case to make string comparisons case-insensitive + std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower); + if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1") + return true; + else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0") + return false; + else + return default_value; +} + +inline string INIReader::MakeKey(string section, string name) +{ + string key = section + "=" + name; + // Convert to lower case to make section/name lookups case-insensitive + std::transform(key.begin(), key.end(), key.begin(), ::tolower); + return key; +} + +inline int INIReader::ValueHandler(void* user, const char* section, const char* name, + const char* value) +{ + INIReader* reader = (INIReader*)user; + string key = MakeKey(section, name); + if (reader->_values[key].size() > 0) + reader->_values[key] += "\n"; + reader->_values[key] += value; + reader->_sections.insert(section); + return 1; +} + +#endif // __INIREADER__ diff --git a/deps/inih/LICENSE.txt b/deps/inih/LICENSE.txt new file mode 100644 index 00000000..cb7ee2d0 --- /dev/null +++ b/deps/inih/LICENSE.txt @@ -0,0 +1,27 @@ + +The "inih" library is distributed under the New BSD license: + +Copyright (c) 2009, Ben Hoyt +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Ben Hoyt nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY BEN HOYT ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BEN HOYT BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/deps/inih/README.md b/deps/inih/README.md new file mode 100644 index 00000000..ab091a86 --- /dev/null +++ b/deps/inih/README.md @@ -0,0 +1,41 @@ +# inih +[![Build Status](https://travis-ci.org/jtilly/inih.svg?branch=master)](https://travis-ci.org/jtilly/inih) + +This is a header only C++ version of [inih](https://github.com/benhoyt/inih). + +**inih (INI Not Invented Here)** is a simple [.INI file](http://en.wikipedia.org/wiki/INI_file) parser written in C. It's only a couple of pages of code, and it was designed to be _small and simple_, so it's good for embedded systems. It's also more or less compatible with Python's [ConfigParser](http://docs.python.org/library/configparser.html) style of .INI files, including RFC 822-style multi-line syntax and `name: value` entries. + +## Usage + +All you need to do is to include `INIReader.h`. Consider the following example (`INIReaderTest.cpp`): + +```cpp +#include +#include "INIReader.h" + +int main() { + + INIReader reader("test.ini"); + + if (reader.ParseError() < 0) { + std::cout << "Can't load 'test.ini'\n"; + return 1; + } + std::cout << "Config loaded from 'test.ini': version=" + << reader.GetInteger("protocol", "version", -1) << ", name=" + << reader.Get("user", "name", "UNKNOWN") << ", email=" + << reader.Get("user", "email", "UNKNOWN") << ", pi=" + << reader.GetReal("user", "pi", -1) << ", active=" + << reader.GetBoolean("user", "active", true) << "\n"; + return 0; + +} +``` + +To compile and run: + +```sh +g++ INIReaderTest.cpp -o INIReaderTest.out +./INIReaderTest.out +# Config loaded from 'test.ini': version=6, name=Bob Smith, email=bob@smith.com, pi=3.14159, active=1 +``` diff --git a/deps/inih/test.ini b/deps/inih/test.ini new file mode 100644 index 00000000..000f58ae --- /dev/null +++ b/deps/inih/test.ini @@ -0,0 +1,12 @@ +; Test config file for ini_example.c and INIReaderTest.cpp + +[protocol] ; Protocol configuration +version=6 ; IPv6 + +[user] +name = Bob Smith ; Spaces around '=' are stripped +email = bob@smith.com ; And comments (like this) ignored +active = true ; Test a boolean +pi = 3.14159 ; Test a floating point number +multi = this is a ; test + multi-line value ; test diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 5595816f..05512b9e 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -46,6 +46,7 @@ target_include_directories( common PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/" "${CMAKE_CURRENT_SOURCE_DIR}/../../deps/asio/asio/include/" + "${CMAKE_CURRENT_SOURCE_DIR}/../../deps/" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/libraries/external/") diff --git a/src/common/Config/ConfigMgr.cpp b/src/common/Config/ConfigMgr.cpp index 2c921a9c..c4550f18 100644 --- a/src/common/Config/ConfigMgr.cpp +++ b/src/common/Config/ConfigMgr.cpp @@ -1,7 +1,7 @@ #include "ConfigMgr.h" - -#include -#include +#include +#include +#include /** * Loads an ini file and parses it @@ -13,15 +13,15 @@ bool Core::ConfigMgr::loadConfig( const std::string& configName ) std::stringstream configStream; // get global config - auto configDir = boost::filesystem::path( m_configFolderRoot ); + auto configDir = std::experimental::filesystem::path( m_configFolderRoot ); - if( !boost::filesystem::exists( configDir ) ) + if( !std::experimental::filesystem::exists( configDir ) ) { return false; } - auto globalConfig = boost::filesystem::path( configDir / m_globalConfigFile ); - if( !boost::filesystem::exists( globalConfig ) ) + auto globalConfig = std::experimental::filesystem::path( configDir / m_globalConfigFile ); + if( !std::experimental::filesystem::exists( globalConfig ) ) { if( !copyDefaultConfig( globalConfig.filename().string() ) ) return false; @@ -34,8 +34,8 @@ bool Core::ConfigMgr::loadConfig( const std::string& configName ) configStream << "\n\n"; // get local config - auto localConfig = boost::filesystem::path( configDir / configName ); - if( !boost::filesystem::exists( localConfig ) ) + auto localConfig = std::experimental::filesystem::path( configDir / configName ); + if( !std::experimental::filesystem::exists( localConfig ) ) { if( !copyDefaultConfig( localConfig.filename().string() ) ) return false; @@ -44,23 +44,28 @@ bool Core::ConfigMgr::loadConfig( const std::string& configName ) configStream << configFile.rdbuf(); // parse the tree and we're fuckin done - boost::property_tree::read_ini( configStream, m_propTree ); + //boost::property_tree::read_ini( configStream, m_propTree ); + + m_pInih = std::unique_ptr< INIReader >(new INIReader( localConfig ) ); + + if( m_pInih->ParseError() < 0 ) + return false; return true; } bool Core::ConfigMgr::copyDefaultConfig( const std::string& configName ) { - boost::filesystem::path configPath( m_configFolderRoot ); + std::experimental::filesystem::path configPath( m_configFolderRoot ); configPath /= configName; - if( !boost::filesystem::exists( configPath.string() + m_configDefaultSuffix ) ) + if( !std::experimental::filesystem::exists( configPath.string() + m_configDefaultSuffix ) ) { // no default file :( return false; } - boost::filesystem::copy_file( configPath.string() + m_configDefaultSuffix, configPath ); + std::experimental::filesystem::copy_file( configPath.string() + m_configDefaultSuffix, configPath ); return true; -} \ No newline at end of file +} diff --git a/src/common/Config/ConfigMgr.h b/src/common/Config/ConfigMgr.h index 82de18bf..d4b5b45d 100644 --- a/src/common/Config/ConfigMgr.h +++ b/src/common/Config/ConfigMgr.h @@ -1,7 +1,11 @@ #ifndef SAPPHIRE_CONFIGMGR_H #define SAPPHIRE_CONFIGMGR_H -#include +#include +#include +#include +#include +#include namespace Core { class ConfigMgr @@ -12,30 +16,48 @@ public: ~ConfigMgr() = default; bool loadConfig( const std::string& configName ); + + template struct always_false : std::false_type {}; template< class T > T getValue( const std::string& name, T defaultValue = T() ) { - try - { - return m_propTree.get< T >( name ); - } - catch( ... ) - { - return defaultValue; - } + if constexpr (std::is_same_v) + return m_pInih->GetInteger( "", name, defaultValue ); + else if constexpr (std::is_same_v) + return m_pInih->GetInteger( "", name, defaultValue ); + else if constexpr (std::is_same_v) + return m_pInih->GetInteger( "", name, defaultValue ); + else if constexpr (std::is_same_v) + return m_pInih->GetInteger( "", name, defaultValue ); + else if constexpr (std::is_same_v) + return m_pInih->GetInteger( "", name, defaultValue ); + else if constexpr (std::is_same_v) + return m_pInih->GetInteger( "", name, defaultValue ); + else if constexpr (std::is_same_v) + return m_pInih->GetInteger( "", name, defaultValue ); + else if constexpr (std::is_same_v) + return m_pInih->GetReal( "", name, defaultValue ); + else if constexpr (std::is_same_v) + return m_pInih->GetReal( "", name, defaultValue ); + else if constexpr (std::is_same_v) + return m_pInih->Get( "", name, defaultValue ); + else + static_assert(always_false::value, "non-exhaustive getter!"); } template< class T > void setValue( const std::string& name, T defaultValue = T() ) { - m_propTree.put( name, defaultValue ); + // TODO: reimplement this... + //m_propTree.put( name, defaultValue ); } private: bool copyDefaultConfig( const std::string& configName ); - boost::property_tree::ptree m_propTree; + std::unique_ptr< INIReader > m_pInih; + const std::string m_globalConfigFile = "global.ini"; const std::string m_configFolderRoot = "./config/"; const std::string m_configDefaultSuffix = ".default"; diff --git a/src/common/Util/Util.cpp b/src/common/Util/Util.cpp index 89f0a5f8..bbaae0c8 100644 --- a/src/common/Util/Util.cpp +++ b/src/common/Util/Util.cpp @@ -1,7 +1,5 @@ #include "Util.h" #include -#include -#include std::string Core::Util::binaryToHexString( uint8_t* pBinData, uint16_t size ) { @@ -9,7 +7,8 @@ std::string Core::Util::binaryToHexString( uint8_t* pBinData, uint16_t size ) for( uint32_t i = 0; i < size; i++ ) { - outStr += boost::str( boost::format( "%|02X|" ) % ( int32_t ) ( pBinData[ i ] & 0xFF ) ); +// TODO:: std::hex stream +// outStr += boost::str( boost::format( "%|02X|" ) % ( int32_t ) ( pBinData[ i ] & 0xFF ) ); } return outStr;