From a2c9adca9a19ff0bd6672fd7c27df13c554cf807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Thu, 13 Aug 2026 16:03:04 -0300 Subject: [PATCH] Optimize allocations in project saving and editor rendering Refactor IniFile storage to keep keys and values in unified SmallVector-backed records, avoiding parallel vector growth and allocations for common small configurations. Use std::string_view by value throughout the IniFile input API to prevent temporary std::string allocations for keys, value names, paths, and comments. Write serialized components directly to streams instead of constructing a temporary string for every line. Return ProjectBuildConfiguration by const reference to avoid copying it during project saves, and use inline storage for DocumentView visual lines to avoid heap allocations while rendering the code editor minimap. Add IniFile tests covering parsing, mutation, ordered serialization, and a byte-for-byte ecode project-state round trip. --- include/eepp/system/inifile.hpp | 110 +++++----- include/eepp/ui/doc/documentview.hpp | 2 +- src/eepp/system/inifile.cpp | 269 ++++++++++--------------- src/tests/unit_tests/inifile_tests.cpp | 107 ++++++++++ src/tools/ecode/projectbuild.cpp | 2 +- src/tools/ecode/projectbuild.hpp | 2 +- 6 files changed, 264 insertions(+), 228 deletions(-) create mode 100644 src/tests/unit_tests/inifile_tests.cpp diff --git a/include/eepp/system/inifile.hpp b/include/eepp/system/inifile.hpp index 97958da45..d1d5e055a 100644 --- a/include/eepp/system/inifile.hpp +++ b/include/eepp/system/inifile.hpp @@ -16,12 +16,12 @@ #ifndef EE_SYSTEM_INIFILE_HPP #define EE_SYSTEM_INIFILE_HPP +#include #include #include #include #include #include -#include namespace EE { namespace System { @@ -32,13 +32,13 @@ class EE_API IniFile { enum errors { noID = -1 }; /** Initialize and load the ini file from path */ - IniFile( const std::string& iniPath = "", bool autoLoad = true ); + IniFile( std::string_view iniPath = {}, bool autoLoad = true ); /** Initialize and load the ini file from memory */ IniFile( const Uint8* RAWData, const Uint32& size, bool autoLoad = true ); /** Initialize and load the ini file from a pack file */ - IniFile( Pack* Pack, const std::string& iniPackPath, bool autoLoad = true ); + IniFile( Pack* Pack, std::string_view iniPackPath, bool autoLoad = true ); /** Initialize and load the ini file from a stream */ IniFile( IOStream& stream, bool autoLoad = true ); @@ -46,19 +46,19 @@ class EE_API IniFile { virtual ~IniFile() {} /** Loads an ini file from path */ - bool loadFromFile( const std::string& iniPath ); + bool loadFromFile( std::string_view iniPath ); /** Loads an ini file from memory */ bool loadFromMemory( const Uint8* RAWData, const Uint32& size ); /** Loads an ini file from a pack file */ - bool loadFromPack( Pack* Pack, const std::string& iniPackPath ); + bool loadFromPack( Pack* Pack, std::string_view iniPackPath ); /** Loads an ini file from a stream */ bool loadFromStream( IOStream& stream ); /** Sets mPath of ini file to read and write from. */ - void path( const std::string& newPath ) { mPath = newPath; } + void path( std::string_view newPath ) { mPath.assign( newPath ); } /** @return The ini file path */ const std::string& path() const { return mPath; } @@ -77,22 +77,16 @@ class EE_API IniFile { void clear(); /** @return index of specified key, or noID if not found. */ - long findKey( const std::string& keyname ) const; - - long findKey( const std::string_view& keyname ) const; + long findKey( std::string_view keyname ) const; /** @return index of specified value, in the specified key, or noID if not found. */ - long findValue( unsigned const keyID, const std::string& valuename ) const; - - long findValue( unsigned const keyID, const std::string_view& valuename ) const; + long findValue( unsigned const keyID, std::string_view valuename ) const; /** @return number of Keys currently in the ini. */ - unsigned getNumKeys() const { return (unsigned int)mNames.size(); } + unsigned getNumKeys() const { return (unsigned int)mKeys.size(); } /** Add a key name. */ - unsigned addKeyName( const std::string& keyname ); - - unsigned addKeyName( const std::string_view& keyname ); + unsigned addKeyName( std::string_view keyname ); /** @return key Names by index. */ std::string getKeyName( unsigned const keyID ) const; @@ -101,45 +95,45 @@ class EE_API IniFile { unsigned getNumValues( unsigned const keyID ); /** @return number of values stored for specified key from its name. */ - unsigned getNumValues( const std::string& keyname ); + unsigned getNumValues( std::string_view keyname ); /** @return value name by index for a given keyname or keyID. */ std::string getValueName( unsigned const keyID, unsigned const valueID ) const; /** @return A value name from keyname and valueId */ - std::string getValueName( const std::string& keyname, unsigned const valueID ) const; + std::string getValueName( std::string_view keyname, unsigned const valueID ) const; /** Gets value of [keyname] valuename =. ** Overloaded to return std::string, int, and double. ** @return defValue if key/value not found otherwise the value obtained. */ std::string getValue( unsigned const keyID, unsigned const valueID, - const std::string& defValue = "" ) const; + std::string_view defValue = {} ) const; /** Gets a value from a keyname and valuename */ - std::string getValue( const std::string& keyname, const std::string& valuename, - const std::string& defValue = "" ) const; + std::string getValue( std::string_view keyname, std::string_view valuename, + std::string_view defValue = {} ) const; /** Gets the value as integer */ - int getValueI( const std::string& keyname, const std::string& valuename, + int getValueI( std::string_view keyname, std::string_view valuename, int const defValue = 0 ) const; /** Gets the value as an unsigned long */ - unsigned long getValueU( const std::string& keyname, const std::string& valuename, + unsigned long getValueU( std::string_view keyname, std::string_view valuename, unsigned long const defValue = 0 ) const; /** Gets the value as boolean */ - bool getValueB( const std::string& keyname, const std::string& valuename, + bool getValueB( std::string_view keyname, std::string_view valuename, bool const defValue = false ) const; /** Gets the value as double */ - double getValueF( const std::string& keyname, const std::string& valuename, + double getValueF( std::string_view keyname, std::string_view valuename, double const defValue = 0.0 ) const; /** Sets value of [keyname] valuename =. ** Specify the optional parameter as false (0) if you do not want it to create ** the key if it doesn't exist. @return true if data entered, false otherwise. ** Overloaded to accept std::string, int, and double. */ - bool setValue( unsigned const keyID, unsigned const valueID, const std::string& value ); + bool setValue( unsigned const keyID, unsigned const valueID, std::string_view value ); /** Sets the value from a keyname and a valuename * @param keyname The key name @@ -147,11 +141,8 @@ class EE_API IniFile { * @param value The value to assign * @param create If true it will create the keyname if doesn't exists */ - bool setValue( const std::string& keyname, const std::string& valuename, - const std::string& value, bool create = true ); - - bool setValue( const std::string_view& keyname, const std::string_view& valuename, - const std::string_view& value, bool create = true ); + bool setValue( std::string_view keyname, std::string_view valuename, std::string_view value, + bool create = true ); /** Sets a integer value from a keyname and a valuename * @param keyname The key name @@ -159,7 +150,7 @@ class EE_API IniFile { * @param value The value to assign * @param create If true it will create the keyname if doesn't exists */ - bool setValueI( const std::string& keyname, const std::string& valuename, int const value, + bool setValueI( std::string_view keyname, std::string_view valuename, int const value, bool create = true ); /** Sets a unsigned long value from a keyname and a valuename @@ -168,8 +159,8 @@ class EE_API IniFile { * @param value The value to assign * @param create If true it will create the keyname if doesn't exists */ - bool setValueU( const std::string& keyname, const std::string& valuename, - unsigned long const value, bool create = true ); + bool setValueU( std::string_view keyname, std::string_view valuename, unsigned long const value, + bool create = true ); /** Sets a boolean value from a keyname and a valuename * @param keyname The key name @@ -177,7 +168,7 @@ class EE_API IniFile { * @param value The value to assign * @param create If true it will create the keyname if doesn't exists */ - bool setValueB( const std::string& keyname, const std::string& valuename, bool const value, + bool setValueB( std::string_view keyname, std::string_view valuename, bool const value, bool create = true ) { return setValueI( keyname, valuename, int( value ), create ); } @@ -188,19 +179,19 @@ class EE_API IniFile { * @param value The value to assign * @param create If true it will create the keyname if doesn't exists */ - bool setValueF( const std::string& keyname, const std::string& valuename, double const value, + bool setValueF( std::string_view keyname, std::string_view valuename, double const value, bool create = true ); /** Sets a formatted value from a keyname and a valuename */ - bool setValueV( const std::string& keyname, const std::string& valuename, char* format, ... ); + bool setValueV( std::string_view keyname, std::string_view valuename, char* format, ... ); /** Deletes specified value. ** @return true if value existed and deleted, false otherwise. */ - bool deleteValue( const std::string& keyname, const std::string& valuename ); + bool deleteValue( std::string_view keyname, std::string_view valuename ); /** Deletes specified key and all values contained within. ** @return true if key existed and deleted, false otherwise. */ - bool deleteKey( const std::string& keyname ); + bool deleteKey( std::string_view keyname ); /** Header comment functions. ** Header comments are those comments before the first key. @@ -208,7 +199,7 @@ class EE_API IniFile { unsigned numHeaderComments() { return (unsigned int)mComments.size(); } /** Add a header comment. */ - void addHeaderComment( const std::string& comment ); + void addHeaderComment( std::string_view comment ); /** Return a header comment. */ std::string getHeaderComment( unsigned const commentID ) const; @@ -221,12 +212,12 @@ class EE_API IniFile { std::map getKeyMap( const unsigned& keyID ) const; - std::map getKeyMap( const std::string& keyname ) const; + std::map getKeyMap( std::string_view keyname ) const; std::unordered_map getKeyUnorderedMap( const unsigned& keyID ) const; std::unordered_map - getKeyUnorderedMap( const std::string& keyname ) const; + getKeyUnorderedMap( std::string_view keyname ) const; /** Key comment functions. ** Key comments are those comments within a key. Any comments @@ -235,44 +226,45 @@ class EE_API IniFile { ** the IniFile::writeFile() is called. ** Number of key comments. */ unsigned getNumKeyComments( unsigned const keyID ) const; - unsigned getNumKeyComments( const std::string& keyname ) const; + unsigned getNumKeyComments( std::string_view keyname ) const; /** Add a key comment.*/ - bool addKeyComment( unsigned const keyID, const std::string& comment ); - bool addKeyComment( unsigned const keyID, const std::string_view& comment ); - bool addKeyComment( const std::string& keyname, const std::string& comment ); - bool addKeyComment( const std::string_view& keyname, const std::string_view& comment ); + bool addKeyComment( unsigned const keyID, std::string_view comment ); + bool addKeyComment( std::string_view keyname, std::string_view comment ); /** Return a key comment. */ std::string getKeyComment( unsigned const keyID, unsigned const commentID ) const; - std::string getKeyComment( const std::string& keyname, unsigned const commentID ) const; + std::string getKeyComment( std::string_view keyname, unsigned const commentID ) const; /** Delete a key comment. */ bool deleteKeyComment( unsigned const keyID, unsigned const commentID ); - bool deleteKeyComment( const std::string& keyname, unsigned const commentID ); + bool deleteKeyComment( std::string_view keyname, unsigned const commentID ); /** Delete all comments for a key. */ bool deleteKeyComments( unsigned const keyID ); - bool deleteKeyComments( const std::string& keyname ); + bool deleteKeyComments( std::string_view keyname ); bool iniParsed() { return mIniRead; } - bool keyExists( const std::string& keyname ) const; + bool keyExists( std::string_view keyname ) const; - bool keyValueExists( const std::string& keyname, const std::string& valuename ) const; + bool keyValueExists( std::string_view keyname, std::string_view valuename ) const; private: bool mIniRead{ false }; std::string mPath; std::string mBuffer; - struct key { - std::vector names; - std::vector values; - std::vector comments; + struct value { + std::string name; + std::string data; }; - std::vector mKeys; - std::vector mNames; - std::vector mComments; + struct key { + std::string name; + SmallVector values; + SmallVector comments; + }; + SmallVector mKeys; + SmallVector mComments; }; }} // namespace EE::System diff --git a/include/eepp/ui/doc/documentview.hpp b/include/eepp/ui/doc/documentview.hpp index 29d3f0441..9ed5b720a 100644 --- a/include/eepp/ui/doc/documentview.hpp +++ b/include/eepp/ui/doc/documentview.hpp @@ -37,7 +37,7 @@ class EE_API DocumentView { struct VisibleLineInfo { VisibleIndex visibleIndex{ VisibleIndex::invalid }; Float paddingStart{ 0 }; - std::vector visualLines; + SmallVector visualLines; }; struct VisibleLineRange { diff --git a/src/eepp/system/inifile.cpp b/src/eepp/system/inifile.cpp index e1366636a..c4f0c2b37 100644 --- a/src/eepp/system/inifile.cpp +++ b/src/eepp/system/inifile.cpp @@ -14,7 +14,7 @@ namespace EE { namespace System { -IniFile::IniFile( const std::string& iniPath, bool autoLoad ) { +IniFile::IniFile( std::string_view iniPath, bool autoLoad ) { if ( autoLoad ) loadFromFile( iniPath ); else @@ -26,7 +26,7 @@ IniFile::IniFile( const Uint8* RAWData, const Uint32& size, bool autoLoad ) { loadFromMemory( RAWData, size ); } -IniFile::IniFile( Pack* Pack, const std::string& iniPackPath, bool autoLoad ) { +IniFile::IniFile( Pack* Pack, std::string_view iniPackPath, bool autoLoad ) { if ( autoLoad ) loadFromPack( Pack, iniPackPath ); } @@ -36,11 +36,12 @@ IniFile::IniFile( IOStream& stream, bool autoLoad ) { loadFromStream( stream ); } -bool IniFile::loadFromPack( Pack* Pack, const std::string& iniPackPath ) { - if ( NULL != Pack && Pack->isOpen() && -1 != Pack->exists( iniPackPath ) ) { +bool IniFile::loadFromPack( Pack* Pack, std::string_view iniPackPath ) { + std::string path( iniPackPath ); + if ( NULL != Pack && Pack->isOpen() && -1 != Pack->exists( path ) ) { ScopedBuffer buffer; - Pack->extractFileToMemory( iniPackPath, buffer ); + Pack->extractFileToMemory( path, buffer ); return loadFromMemory( buffer.get(), buffer.length() ); } @@ -66,14 +67,14 @@ bool IniFile::loadFromMemory( const Uint8* RAWData, const Uint32& size ) { return loadFromStream( f ); } -bool IniFile::loadFromFile( const std::string& iniPath ) { +bool IniFile::loadFromFile( std::string_view iniPath ) { path( iniPath ); - if ( FileSystem::fileExists( iniPath ) ) { + if ( FileSystem::fileExists( mPath ) ) { IOStreamFile f( mPath ); return loadFromStream( f ); } else if ( PackRegistry::instance()->isFallbackToPacksActive() ) { - std::string tPath( iniPath ); + std::string tPath( mPath ); Pack* tPack = PackRegistry::instance()->exists( tPath ); @@ -149,7 +150,7 @@ bool IniFile::readFile() { break; case ';': case '#': - if ( !mNames.size() ) + if ( mKeys.empty() ) addHeaderComment( std::string{ line.substr( pLeft + 1 ) } ); else addKeyComment( keyname, std::string{ line.substr( pLeft + 1 ) } ); @@ -158,7 +159,7 @@ bool IniFile::readFile() { } } - if ( mNames.size() ) { + if ( !mKeys.empty() ) { mIniRead = true; return true; @@ -176,132 +177,113 @@ bool IniFile::writeStream( IOStream& stream ) { if ( !stream.isOpen() ) return false; - unsigned commentID, keyID, valueID; - std::string str; + static constexpr char commentPrefix = ';'; + static constexpr char keyPrefix = '['; + static constexpr char keySuffix[] = "]\n"; + static constexpr char valueSeparator = '='; + static constexpr char newline = '\n'; // Write header mComments. - for ( commentID = 0; commentID < mComments.size(); ++commentID ) { - str = ';' + mComments[commentID] + '\n'; - stream.write( str.c_str(), str.size() ); + for ( const auto& comment : mComments ) { + stream.write( &commentPrefix, 1 ); + stream.write( comment.data(), comment.size() ); + stream.write( &newline, 1 ); } - if ( mComments.size() ) { - str = "\n"; - stream.write( str.c_str(), str.size() ); - } + if ( !mComments.empty() ) + stream.write( &newline, 1 ); // Write Keys and values. - for ( keyID = 0; keyID < mKeys.size(); ++keyID ) { - str = '[' + mNames[keyID] + ']' + '\n'; - stream.write( str.c_str(), str.size() ); + for ( const auto& key : mKeys ) { + stream.write( &keyPrefix, 1 ); + stream.write( key.name.data(), key.name.size() ); + stream.write( keySuffix, sizeof( keySuffix ) - 1 ); // Comments. - for ( commentID = 0; commentID < mKeys[keyID].comments.size(); ++commentID ) { - str = ';' + mKeys[keyID].comments[commentID] + '\n'; - stream.write( str.c_str(), str.size() ); + for ( const auto& comment : key.comments ) { + stream.write( &commentPrefix, 1 ); + stream.write( comment.data(), comment.size() ); + stream.write( &newline, 1 ); } // Values. - for ( valueID = 0; valueID < mKeys[keyID].names.size(); ++valueID ) { - str = mKeys[keyID].names[valueID] + '=' + mKeys[keyID].values[valueID] + '\n'; - stream.write( str.c_str(), str.size() ); + for ( const auto& value : key.values ) { + stream.write( value.name.data(), value.name.size() ); + stream.write( &valueSeparator, 1 ); + stream.write( value.data.data(), value.data.size() ); + stream.write( &newline, 1 ); } - str = "\n"; - stream.write( str.c_str(), str.size() ); + stream.write( &newline, 1 ); } return true; } -long IniFile::findKey( const std::string& keyname ) const { - for ( unsigned keyID = 0; keyID < mNames.size(); ++keyID ) - if ( mNames[keyID] == keyname ) +long IniFile::findKey( std::string_view keyname ) const { + for ( unsigned keyID = 0; keyID < mKeys.size(); ++keyID ) + if ( mKeys[keyID].name == keyname ) return long( keyID ); return noID; } -long IniFile::findKey( const std::string_view& keyname ) const { - for ( unsigned keyID = 0; keyID < mNames.size(); ++keyID ) - if ( mNames[keyID] == keyname ) - return long( keyID ); - return noID; -} - -long IniFile::findValue( unsigned const keyID, const std::string& valuename ) const { +long IniFile::findValue( const unsigned int keyID, std::string_view valuename ) const { if ( !mKeys.size() || keyID >= mKeys.size() ) return noID; - for ( unsigned valueID = 0; valueID < mKeys[keyID].names.size(); ++valueID ) - if ( mKeys[keyID].names[valueID] == valuename ) + for ( unsigned valueID = 0; valueID < mKeys[keyID].values.size(); ++valueID ) + if ( mKeys[keyID].values[valueID].name == valuename ) return long( valueID ); return noID; } -long IniFile::findValue( const unsigned int keyID, const std::string_view& valuename ) const { - if ( !mKeys.size() || keyID >= mKeys.size() ) - return noID; - - for ( unsigned valueID = 0; valueID < mKeys[keyID].names.size(); ++valueID ) - if ( mKeys[keyID].names[valueID] == valuename ) - return long( valueID ); - return noID; -} - -unsigned IniFile::addKeyName( const std::string& keyname ) { - mNames.resize( mNames.size() + 1, keyname ); - mKeys.resize( mKeys.size() + 1 ); - return (unsigned int)( mNames.size() - 1 ); -} - -unsigned int IniFile::addKeyName( const std::string_view& keyname ) { - mNames.resize( mNames.size() + 1, std::string{ keyname } ); - mKeys.resize( mKeys.size() + 1 ); - return (unsigned int)( mNames.size() - 1 ); +unsigned int IniFile::addKeyName( std::string_view keyname ) { + mKeys.push_back( { std::string{ keyname }, {}, {} } ); + return (unsigned int)( mKeys.size() - 1 ); } std::string IniFile::getKeyName( unsigned const keyID ) const { - if ( keyID < mNames.size() ) - return mNames[keyID]; + if ( keyID < mKeys.size() ) + return mKeys[keyID].name; else return ""; } unsigned IniFile::getNumValues( unsigned const keyID ) { if ( keyID < mKeys.size() ) - return (unsigned int)mKeys[keyID].names.size(); + return (unsigned int)mKeys[keyID].values.size(); return 0; } -unsigned IniFile::getNumValues( const std::string& keyname ) { +unsigned IniFile::getNumValues( std::string_view keyname ) { long keyID = findKey( keyname ); if ( keyID == noID ) return 0; - return (unsigned int)mKeys[keyID].names.size(); + return (unsigned int)mKeys[keyID].values.size(); } std::string IniFile::getValueName( unsigned const keyID, unsigned const valueID ) const { - if ( keyID < mKeys.size() && valueID < mKeys[keyID].names.size() ) - return mKeys[keyID].names[valueID]; + if ( keyID < mKeys.size() && valueID < mKeys[keyID].values.size() ) + return mKeys[keyID].values[valueID].name; return ""; } -std::string IniFile::getValueName( const std::string& keyname, unsigned const valueID ) const { +std::string IniFile::getValueName( std::string_view keyname, unsigned const valueID ) const { long keyID = findKey( keyname ); if ( keyID == noID ) return ""; return getValueName( keyID, valueID ); } -bool IniFile::setValue( unsigned const keyID, unsigned const valueID, const std::string& value ) { - if ( keyID < mKeys.size() && valueID < mKeys[keyID].names.size() ) - mKeys[keyID].values[valueID] = value; +bool IniFile::setValue( unsigned const keyID, unsigned const valueID, std::string_view value ) { + if ( keyID < mKeys.size() && valueID < mKeys[keyID].values.size() ) + mKeys[keyID].values[valueID].data.assign( value ); return false; } -bool IniFile::setValue( const std::string& keyname, const std::string& valuename, - const std::string& value, bool create ) { +bool IniFile::setValue( std::string_view keyname, std::string_view valuename, + std::string_view value, bool create ) { long keyID = findKey( keyname ); if ( keyID == noID ) { if ( create ) @@ -314,37 +296,14 @@ bool IniFile::setValue( const std::string& keyname, const std::string& valuename if ( valueID == noID ) { if ( !create ) return false; - mKeys[keyID].names.resize( mKeys[keyID].names.size() + 1, valuename ); - mKeys[keyID].values.resize( mKeys[keyID].values.size() + 1, value ); + mKeys[keyID].values.push_back( { std::string{ valuename }, std::string{ value } } ); } else - mKeys[keyID].values[valueID] = value; + mKeys[keyID].values[valueID].data.assign( value ); return true; } -bool IniFile::setValue( const std::string_view& keyname, const std::string_view& valuename, - const std::string_view& value, bool create ) { - long keyID = findKey( keyname ); - if ( keyID == noID ) { - if ( create ) - keyID = long( addKeyName( std::string{ keyname } ) ); - else - return false; - } - - long valueID = findValue( unsigned( keyID ), valuename ); - if ( valueID == noID ) { - if ( !create ) - return false; - mKeys[keyID].names.resize( mKeys[keyID].names.size() + 1, std::string{ valuename } ); - mKeys[keyID].values.resize( mKeys[keyID].values.size() + 1, std::string{ value } ); - } else - mKeys[keyID].values[valueID] = value; - - return true; -} - -bool IniFile::setValueI( const std::string& keyname, const std::string& valuename, int const value, +bool IniFile::setValueI( std::string_view keyname, std::string_view valuename, int const value, bool create ) { char svalue[MAX_VALUEDATA]; @@ -352,7 +311,7 @@ bool IniFile::setValueI( const std::string& keyname, const std::string& valuenam return setValue( keyname, valuename, svalue, create ); } -bool IniFile::setValueU( const std::string& keyname, const std::string& valuename, +bool IniFile::setValueU( std::string_view keyname, std::string_view valuename, const unsigned long value, bool create ) { char svalue[MAX_VALUEDATA]; @@ -360,16 +319,15 @@ bool IniFile::setValueU( const std::string& keyname, const std::string& valuenam return setValue( keyname, valuename, svalue, create ); } -bool IniFile::setValueF( const std::string& keyname, const std::string& valuename, - double const value, bool create ) { +bool IniFile::setValueF( std::string_view keyname, std::string_view valuename, double const value, + bool create ) { char svalue[MAX_VALUEDATA]; String::formatBuffer( svalue, MAX_VALUEDATA, "%f", value ); return setValue( keyname, valuename, svalue, create ); } -bool IniFile::setValueV( const std::string& keyname, const std::string& valuename, char* format, - ... ) { +bool IniFile::setValueV( std::string_view keyname, std::string_view valuename, char* format, ... ) { va_list args; char value[MAX_VALUEDATA]; @@ -384,26 +342,26 @@ bool IniFile::setValueV( const std::string& keyname, const std::string& valuenam } std::string IniFile::getValue( unsigned const keyID, unsigned const valueID, - const std::string& defValue ) const { - if ( keyID < mKeys.size() && valueID < mKeys[keyID].names.size() ) - return mKeys[keyID].values[valueID]; - return defValue; + std::string_view defValue ) const { + if ( keyID < mKeys.size() && valueID < mKeys[keyID].values.size() ) + return mKeys[keyID].values[valueID].data; + return std::string{ defValue }; } -std::string IniFile::getValue( const std::string& keyname, const std::string& valuename, - const std::string& defValue ) const { +std::string IniFile::getValue( std::string_view keyname, std::string_view valuename, + std::string_view defValue ) const { long keyID = findKey( keyname ); if ( keyID == noID ) - return defValue; + return std::string{ defValue }; long valueID = findValue( unsigned( keyID ), valuename ); if ( valueID == noID ) - return defValue; + return std::string{ defValue }; - return mKeys[keyID].values[valueID]; + return mKeys[keyID].values[valueID].data; } -int IniFile::getValueI( const std::string& keyname, const std::string& valuename, +int IniFile::getValueI( std::string_view keyname, std::string_view valuename, int const defValue ) const { char svalue[MAX_VALUEDATA]; @@ -411,7 +369,7 @@ int IniFile::getValueI( const std::string& keyname, const std::string& valuename return atoi( getValue( keyname, valuename, svalue ).c_str() ); } -unsigned long IniFile::getValueU( const std::string& keyname, const std::string& valuename, +unsigned long IniFile::getValueU( std::string_view keyname, std::string_view valuename, const unsigned long defValue ) const { char svalue[MAX_VALUEDATA]; @@ -419,14 +377,14 @@ unsigned long IniFile::getValueU( const std::string& keyname, const std::string& return atoi( getValue( keyname, valuename, svalue ).c_str() ); } -bool IniFile::getValueB( const std::string& keyname, const std::string& valuename, +bool IniFile::getValueB( std::string_view keyname, std::string_view valuename, const bool defValue ) const { std::string val = getValue( keyname, valuename, defValue ? "1" : "0" ); char fist = !val.empty() ? val[0] : '0'; return fist == '1' || fist == 't' || fist == 'y' || fist == 'T' || fist == 'Y'; } -double IniFile::getValueF( const std::string& keyname, const std::string& valuename, +double IniFile::getValueF( std::string_view keyname, std::string_view valuename, double const defValue ) const { char svalue[MAX_VALUEDATA]; @@ -434,7 +392,7 @@ double IniFile::getValueF( const std::string& keyname, const std::string& valuen return atof( getValue( keyname, valuename, svalue ).c_str() ); } -bool IniFile::deleteValue( const std::string& keyname, const std::string& valuename ) { +bool IniFile::deleteValue( std::string_view keyname, std::string_view valuename ) { long keyID = findKey( keyname ); if ( keyID == noID ) return false; @@ -443,37 +401,30 @@ bool IniFile::deleteValue( const std::string& keyname, const std::string& valuen if ( valueID == noID ) return false; - // This looks strange, but is necessary. - std::vector::iterator npos = mKeys[keyID].names.begin() + valueID; - std::vector::iterator vpos = mKeys[keyID].values.begin() + valueID; - mKeys[keyID].names.erase( npos, npos + 1 ); - mKeys[keyID].values.erase( vpos, vpos + 1 ); + auto pos = mKeys[keyID].values.begin() + valueID; + mKeys[keyID].values.erase( pos ); return true; } -bool IniFile::deleteKey( const std::string& keyname ) { +bool IniFile::deleteKey( std::string_view keyname ) { long keyID = findKey( keyname ); if ( keyID == noID ) return false; - std::vector::iterator npos = mNames.begin() + keyID; - std::vector::iterator kpos = mKeys.begin() + keyID; - mNames.erase( npos, npos + 1 ); - mKeys.erase( kpos, kpos + 1 ); + mKeys.erase( mKeys.begin() + keyID ); return true; } void IniFile::clear() { mIniRead = false; - mNames.clear(); mKeys.clear(); mComments.clear(); } -void IniFile::addHeaderComment( const std::string& comment ) { - mComments.resize( mComments.size() + 1, comment ); +void IniFile::addHeaderComment( std::string_view comment ) { + mComments.emplace_back( comment ); } std::string IniFile::getHeaderComment( unsigned const commentID ) const { @@ -484,7 +435,7 @@ std::string IniFile::getHeaderComment( unsigned const commentID ) const { bool IniFile::deleteHeaderComment( unsigned commentID ) { if ( commentID < mComments.size() ) { - std::vector::iterator cpos = mComments.begin() + commentID; + auto cpos = mComments.begin() + commentID; mComments.erase( cpos, cpos + 1 ); return true; } @@ -494,15 +445,15 @@ bool IniFile::deleteHeaderComment( unsigned commentID ) { std::map IniFile::getKeyMap( const unsigned& keyID ) const { std::map map; if ( keyID < mKeys.size() ) { - for ( size_t i = 0; i < mKeys[keyID].names.size(); i++ ) { - map[mKeys[keyID].names[i]] = mKeys[keyID].values[i]; + for ( const auto& value : mKeys[keyID].values ) { + map[value.name] = value.data; } return map; } return {}; } -std::map IniFile::getKeyMap( const std::string& keyname ) const { +std::map IniFile::getKeyMap( std::string_view keyname ) const { long keyID = findKey( keyname ); if ( keyID != noID ) return getKeyMap( keyID ); @@ -513,8 +464,9 @@ std::unordered_map IniFile::getKeyUnorderedMap( const unsigned& keyID ) const { std::unordered_map map; if ( keyID < mKeys.size() ) { - for ( size_t i = 0; i < mKeys[keyID].names.size(); i++ ) { - map[mKeys[keyID].names[i]] = mKeys[keyID].values[i]; + map.reserve( mKeys[keyID].values.size() ); + for ( const auto& value : mKeys[keyID].values ) { + map.emplace( value.name, value.data ); } return map; } @@ -522,7 +474,7 @@ IniFile::getKeyUnorderedMap( const unsigned& keyID ) const { } std::unordered_map -IniFile::getKeyUnorderedMap( const std::string& keyname ) const { +IniFile::getKeyUnorderedMap( std::string_view keyname ) const { long keyID = findKey( keyname ); if ( keyID != noID ) return getKeyUnorderedMap( keyID ); @@ -535,37 +487,22 @@ unsigned IniFile::getNumKeyComments( unsigned const keyID ) const { return 0; } -unsigned IniFile::getNumKeyComments( const std::string& keyname ) const { +unsigned IniFile::getNumKeyComments( std::string_view keyname ) const { long keyID = findKey( keyname ); if ( keyID == noID ) return 0; return (unsigned int)mKeys[keyID].comments.size(); } -bool IniFile::addKeyComment( unsigned const keyID, const std::string& comment ) { +bool IniFile::addKeyComment( unsigned const keyID, std::string_view comment ) { if ( keyID < mKeys.size() ) { - mKeys[keyID].comments.resize( mKeys[keyID].comments.size() + 1, comment ); + mKeys[keyID].comments.emplace_back( comment ); return true; } return false; } -bool IniFile::addKeyComment( unsigned const keyID, const std::string_view& comment ) { - if ( keyID < mKeys.size() ) { - mKeys[keyID].comments.resize( mKeys[keyID].comments.size() + 1, std::string{ comment } ); - return true; - } - return false; -} - -bool IniFile::addKeyComment( const std::string& keyname, const std::string& comment ) { - long keyID = findKey( keyname ); - if ( keyID == noID ) - return false; - return addKeyComment( unsigned( keyID ), comment ); -} - -bool IniFile::addKeyComment( const std::string_view& keyname, const std::string_view& comment ) { +bool IniFile::addKeyComment( std::string_view keyname, std::string_view comment ) { long keyID = findKey( keyname ); if ( keyID == noID ) return false; @@ -578,7 +515,7 @@ std::string IniFile::getKeyComment( unsigned const keyID, unsigned const comment return ""; } -std::string IniFile::getKeyComment( const std::string& keyname, unsigned const commentID ) const { +std::string IniFile::getKeyComment( std::string_view keyname, unsigned const commentID ) const { long keyID = findKey( keyname ); if ( keyID == noID ) return ""; @@ -587,14 +524,14 @@ std::string IniFile::getKeyComment( const std::string& keyname, unsigned const c bool IniFile::deleteKeyComment( unsigned const keyID, unsigned const commentID ) { if ( keyID < mKeys.size() && commentID < mKeys[keyID].comments.size() ) { - std::vector::iterator cpos = mKeys[keyID].comments.begin() + commentID; + auto cpos = mKeys[keyID].comments.begin() + commentID; mKeys[keyID].comments.erase( cpos, cpos + 1 ); return true; } return false; } -bool IniFile::deleteKeyComment( const std::string& keyname, unsigned const commentID ) { +bool IniFile::deleteKeyComment( std::string_view keyname, unsigned const commentID ) { long keyID = findKey( keyname ); if ( keyID == noID ) return false; @@ -609,18 +546,18 @@ bool IniFile::deleteKeyComments( unsigned const keyID ) { return false; } -bool IniFile::deleteKeyComments( const std::string& keyname ) { +bool IniFile::deleteKeyComments( std::string_view keyname ) { long keyID = findKey( keyname ); if ( keyID == noID ) return false; return deleteKeyComments( unsigned( keyID ) ); } -bool IniFile::keyExists( const std::string& keyname ) const { +bool IniFile::keyExists( std::string_view keyname ) const { return findKey( keyname ) != noID; } -bool IniFile::keyValueExists( const std::string& keyname, const std::string& valuename ) const { +bool IniFile::keyValueExists( std::string_view keyname, std::string_view valuename ) const { long keyID = findKey( keyname ); if ( keyID == noID ) return false; diff --git a/src/tests/unit_tests/inifile_tests.cpp b/src/tests/unit_tests/inifile_tests.cpp new file mode 100644 index 000000000..48e1aeb84 --- /dev/null +++ b/src/tests/unit_tests/inifile_tests.cpp @@ -0,0 +1,107 @@ +#include "utest.hpp" + +#include +#include + +using namespace EE::System; + +UTEST( IniFile, preservesInsertionOrderAndSerializes ) { + IniFile ini( "", false ); + ini.addHeaderComment( "header" ); + ini.setValue( std::string_view{ "project" }, std::string_view{ "path" }, + std::string_view{ "/tmp/project" } ); + ini.setValue( std::string_view{ "project" }, std::string_view{ "session" }, + std::string_view{ "open" } ); + ini.addKeyComment( std::string_view{ "project" }, std::string_view{ "state" } ); + ini.setValueI( "editor", "tab_width", 4 ); + + IOStreamString stream; + EXPECT_TRUE( ini.writeStream( stream ) ); + EXPECT_STDSTREQ( ";header\n\n[project]\n;state\npath=/tmp/project\nsession=open\n\n" + "[editor]\ntab_width=4\n\n", + stream.getStream() ); + + EXPECT_EQ( 2u, ini.getNumKeys() ); + EXPECT_STDSTREQ( "project", ini.getKeyName( 0 ) ); + EXPECT_STDSTREQ( "session", ini.getValueName( 0, 1 ) ); + EXPECT_STDSTREQ( "open", ini.getValue( 0, 1 ) ); +} + +UTEST( IniFile, parsesUpdatesAndDeletesValues ) { + const std::string source = + "[project]\npath=/tmp/project\nsession=open\n\n[editor]\ntab_width=4\n"; + IniFile ini( reinterpret_cast( source.data() ), + static_cast( source.size() ) ); + + EXPECT_TRUE( ini.iniParsed() ); + EXPECT_TRUE( ini.setValue( std::string_view{ "project" }, std::string_view{ "session" }, + std::string_view{ "closed" } ) ); + EXPECT_TRUE( ini.deleteValue( "project", "path" ) ); + EXPECT_EQ( 1u, ini.getNumValues( "project" ) ); + EXPECT_STDSTREQ( "closed", ini.getValue( "project", "session" ) ); + EXPECT_TRUE( ini.deleteKey( "editor" ) ); + EXPECT_EQ( 1u, ini.getNumKeys() ); +} + +UTEST( IniFile, ecodeProjectStateRoundTripsByteForByte ) { + IniFile project( "/tmp/project.cfg", false ); + auto set = [&project]( std::string_view key, std::string_view name, std::string_view data ) { + return project.setValue( key, name, data ); + }; + + EXPECT_TRUE( set( "path", "folder_path", "/home/user/projects/eepp/" ) ); + project.setValueB( "project_tree", "show_hidden_files", true ); + project.setValueB( "document", "use_global_settings", false ); + EXPECT_TRUE( set( "document", "h_ext_language_type", "cpp" ) ); + project.setValueB( "document", "trim_trailing_whitespaces", true ); + project.setValueB( "document", "force_new_line_at_end_of_file", true ); + project.setValueB( "document", "auto_detect_indent_type", false ); + EXPECT_TRUE( set( "document", "auto_indent", "keep-indentation" ) ); + project.setValueB( "document", "write_bom", false ); + project.setValueI( "document", "indent_width", 4 ); + project.setValueB( "document", "indent_spaces", false ); + EXPECT_TRUE( set( "document", "line_endings", "LF" ) ); + project.setValueI( "document", "tab_width", 4 ); + project.setValueI( "document", "line_breaking_column", 100 ); + EXPECT_TRUE( set( "build", "build_name", "eepp-linux-ninja" ) ); + EXPECT_TRUE( set( "build", "build_type", "release" ) ); + EXPECT_TRUE( set( "build", "run_name", "ecode" ) ); + EXPECT_TRUE( set( + "nodes", "documents", + R"({"type":"splitter","first":{"type":"tabwidget","files":[{"type":"editor","path":"/home/user/projects/eepp/src/tools/ecode/appconfig.cpp"}]},"last":{"type":"tabwidget","files":[]}})" ) ); + EXPECT_TRUE( set( "languages_extensions", ".h", "cpp" ) ); + EXPECT_TRUE( set( "languages_extensions", ".inl", "cpp" ) ); + + const std::string expected = + "[path]\nfolder_path=/home/user/projects/eepp/\n\n" + "[project_tree]\nshow_hidden_files=1\n\n" + "[document]\nuse_global_settings=0\nh_ext_language_type=cpp\n" + "trim_trailing_whitespaces=1\nforce_new_line_at_end_of_file=1\n" + "auto_detect_indent_type=0\nauto_indent=keep-indentation\nwrite_bom=0\n" + "indent_width=4\nindent_spaces=0\nline_endings=LF\ntab_width=4\n" + "line_breaking_column=100\n\n" + "[build]\nbuild_name=eepp-linux-ninja\nbuild_type=release\nrun_name=ecode\n\n" + "[nodes]\ndocuments={\"type\":\"splitter\",\"first\":{\"type\":\"tabwidget\"," + "\"files\":[{\"type\":\"editor\",\"path\":\"/home/user/projects/eepp/src/tools/" + "ecode/appconfig.cpp\"}]},\"last\":{\"type\":\"tabwidget\",\"files\":[]}}\n\n" + "[languages_extensions]\n.h=cpp\n.inl=cpp\n\n"; + + IOStreamString firstWrite; + EXPECT_TRUE( project.writeStream( firstWrite ) ); + EXPECT_STDSTREQ( expected, firstWrite.getStream() ); + + IniFile reloaded( reinterpret_cast( expected.data() ), + static_cast( expected.size() ) ); + EXPECT_TRUE( reloaded.iniParsed() ); + EXPECT_STDSTREQ( "/home/user/projects/eepp/", reloaded.getValue( "path", "folder_path" ) ); + EXPECT_EQ( 100, reloaded.getValueI( "document", "line_breaking_column" ) ); + EXPECT_TRUE( reloaded.getValueB( "project_tree", "show_hidden_files" ) ); + EXPECT_FALSE( reloaded.setValue( std::string_view{ "missing" }, std::string_view{ "value" }, + std::string_view{ "data" }, false ) ); + EXPECT_TRUE( reloaded.setValue( std::string_view{ "build" }, std::string_view{ "run_name" }, + std::string_view{ "ecode" }, false ) ); + + IOStreamString secondWrite; + EXPECT_TRUE( reloaded.writeStream( secondWrite ) ); + EXPECT_STDSTREQ( expected, secondWrite.getStream() ); +} diff --git a/src/tools/ecode/projectbuild.cpp b/src/tools/ecode/projectbuild.cpp index 8232866e8..f06dc239f 100644 --- a/src/tools/ecode/projectbuild.cpp +++ b/src/tools/ecode/projectbuild.cpp @@ -735,7 +735,7 @@ void ProjectBuildManager::cancelRun() { mProcessRun->kill(); } -ProjectBuildConfiguration ProjectBuildManager::getConfig() const { +const ProjectBuildConfiguration& ProjectBuildManager::getConfig() const { return mConfig; } diff --git a/src/tools/ecode/projectbuild.hpp b/src/tools/ecode/projectbuild.hpp index 27001d456..9f1f23666 100644 --- a/src/tools/ecode/projectbuild.hpp +++ b/src/tools/ecode/projectbuild.hpp @@ -326,7 +326,7 @@ class ProjectBuildManager { void cancelRun(); - ProjectBuildConfiguration getConfig() const; + const ProjectBuildConfiguration& getConfig() const; void setConfig( const ProjectBuildConfiguration& config );