ALib C++ Library
Library Version: 2511 R0
Documentation generated by doxygen
Loading...
Searching...
No Matches
inifile.cpp
1//##################################################################################################
2// ALib C++ Library
3//
4// Copyright 2013-2025 A-Worx GmbH, Germany
5// Published under 'Boost Software License' (a free software license, see LICENSE.txt)
6//##################################################################################################
7#include "alib_precompile.hpp"
8#if !defined(ALIB_C20_MODULES) || ((ALIB_C20_MODULES != 0) && (ALIB_C20_MODULES != 1))
9# error "Symbol ALIB_C20_MODULES has to be given to the compiler as either 0 or 1"
10#endif
11#if ALIB_C20_MODULES
12 module;
13#endif
14//========================================= Global Fragment ========================================
18#include <fstream>
19//============================================== Module ============================================
20#if ALIB_C20_MODULES
21 module ALib.Variables.IniFile;
22 import ALib.EnumOps;
23 import ALib.Strings;
24 import ALib.Strings.Tokenizer;
25 import ALib.Strings.StdIOStream;
26 import ALib.Boxing;
27 import ALib.EnumRecords;
28# if ALIB_EXCEPTIONS
29 import ALib.Exceptions;
30# endif
31 import ALib.System;
32 import ALib.Variables;
33 import ALib.Camp.Base;
34#else
35# include "ALib.Strings.H"
38# include "ALib.Boxing.H"
39# include "ALib.Exceptions.H"
40# include "ALib.System.H"
41# include "ALib.Variables.H"
42# include "ALib.Camp.Base.H"
44#endif
45//========================================== Implementation ========================================
46#if ALIB_CAMP
48#endif
49
50using namespace alib::system;
51
52namespace alib { namespace variables {
53
54//##################################################################################################
55// helpers
56//##################################################################################################
58 auto c= subs.CharAtStart();
59 return c == '#'
60 || c == ';'
61 || ( c == '/' && subs.Length() > 1 && subs.CharAt(1) == '/' );
62}
63
64
65
66//##################################################################################################
67// Constructor/Destructor
68//##################################################################################################
74
76 FileComments = nullptr;
77 FileName = nullptr;
78 entryTable .Reset();
79 Sections .Reset();
80 LinesWithReadErrors.Reset();
81 Allocator .Reset();
82}
83
85 auto& section= Sections.emplace_back(Allocator);
86 section.Name.Allocate(Allocator, name );
87 return &section;
88}
89
91 // search section
92 for(auto secIt= Sections.begin() ; secIt != Sections.end(); ++secIt ) {
93 // found?
94 if( secIt->Name.Equals(name)) {
95 auto* section= &*secIt;
96 // delete hashtable entries
97 for(auto entryIt= entryTable.begin() ; entryIt != entryTable.end(); ++entryIt ) {
98 if( entryIt->second.first == section ) {
99 entryIt= entryTable.erase( entryIt );
100 continue;
101 } }
102
103 (void) Sections.erase(secIt);
104 return section;
105 } }
106 return nullptr;
107}
108
110 // search entry
111 for(auto entryIt= section->Entries.begin() ; entryIt != section->Entries.end(); ++entryIt ) {
112 // found?
113 if( entryIt->Name.Equals(name)) {
114 auto* entry= &*entryIt;
115 ALIB_ASSERT_RESULT_GREATER_THAN( entryTable.erase( EntryKey(section->Name, name)), 0 )
116 return entry;
117 } }
118 return nullptr;
119}
120
122 ALIB_ASSERT_ERROR( pName.IsNotEmpty(), "VARIABLES", "Empty Ini-File variable name given.")
123 Entry& newEntry= section->Entries.push_back(Entry());
124 newEntry.Name.Allocate(Allocator, pName);
125 entryTable.EmplaceUnique( EntryKey(section->Name, newEntry.Name),
126 std::make_pair(section, &newEntry) );
127 return &newEntry;
128}
129
130std::pair<IniFile::Section*, IniFile::Entry*> IniFile::SearchEntry (const String& sectionName,
131 const String& name ) {
132 ALIB_ASSERT_ERROR( sectionName.IsNotNull(), "VARIABLES", "Nulled section name given.")
133 auto it= entryTable.Find( EntryKey(sectionName, name ) );
134 if( it != entryTable.end() )
135 return it.Mapped();
136 return std::pair<Section*, Entry*>(nullptr, nullptr);
137}
138
140 ALIB_ASSERT_ERROR(sectionName.IsNotNull(), "VARIABLES", "Nulled section name given.")
141 auto sIt= Sections.begin();
142 while( sIt != Sections.end() ) {
143 if( sIt->Name.Equals<CHK, lang::Case::Ignore>( sectionName ) )
144 return &*sIt;
145 ++sIt;
146 }
147 return nullptr;
148}
149
150std::pair<IniFile::Section*, bool>
152 Section* s= SearchSection( sectionName );
153 if ( s != nullptr )
154 return std::make_pair( s, false );
155
156 return std::make_pair( CreateSection( sectionName ), true );
157}
158
159void IniFile::AddComments ( String& dest, const String& comments, const String& prefix ) {
160 String2K buf;
161 Tokenizer tknzr( comments, '\n' );
162 while( tknzr.HasNext() ) {
163 auto tok= tknzr.Next().TrimEnd();
164 tok.ConsumeCharFromEnd('\r');
165 if( !startsWithCommentSymbol(tok) )
166 buf << prefix;
167 buf << tok << NEW_LINE;
168 }
169 dest.Allocate(Allocator, buf);
170}
171
173 FileName.Allocate(Allocator, path);
174
175 //------------------------------------------- open file ------------------------------------------
176 ALIB_STRINGS_TO_NARROW(path, nPath, 256)
177 std::ifstream file( nPath );
178
179 if ( !file.is_open() ) {
180 int errNo= errno;
181
182 // file does not exist ?
183 if ( errNo == int(SystemErrors::enoent) )
184 return -1;
185
186 // other errors: throw
187 #if ALIB_CAMP
190 A_CHAR("INI-"), path );
191 throw e;
192 #else
193 throw std::runtime_error("ErrorOpeningFile");
194 #endif
195 }
196
197
198 if( Sections.empty() )
200
201 String256 actLine; ALIB_DBG( actLine .DbgDisableBufferReplacementWarning(); )
202 String4K actComments; ALIB_DBG( actComments.DbgDisableBufferReplacementWarning(); )
203 Section* actSection= &Sections.front();
204
205 String8 equalSignOrWhitespace('=');
206 equalSignOrWhitespace << DEFAULT_WHITESPACES;
207
208 IStreamReader reader;
209 reader.SetStream( &file );
210
211 #if ALIB_CAMP
212 auto writeBackAttribute= alib::BASECAMP.GetResource("CFGIniWB");
213 #else
214 String writeBackAttribute= A_CHAR("writeback");
215 #endif
216
217 //------------------------------------------- read loop ------------------------------------------
218 bool writebackFlag = false;
219 int qtyEntriesRead = 0;
220 int lineNo = 0;
221 bool fileHeaderRead = false;
222 while( !reader.IsEOF() ) {
223 reader.Read(actLine); ++lineNo;
224
225 Substring lineTrimmed( actLine );
226 lineTrimmed.Trim();
227
228 // end of file header?
229 if ( !fileHeaderRead && lineTrimmed.IsEmpty() ) {
230 fileHeaderRead= true;
231 FileComments.Allocate(Allocator, actComments);
232 actComments.Reset();
233 }
234
235 // continued comment or empty section?
236 if ( lineTrimmed.IsEmpty() || startsWithCommentSymbol( lineTrimmed ) ) {
237 actComments._(actLine).NewLine();
238 continue;
239 }
240
241 // writeback flag?
242 if( lineTrimmed.Equals<NC,lang::Case::Ignore>(writeBackAttribute) ) {
243 writebackFlag= true;
244 continue;
245 }
246
247 // section line?
248 if ( lineTrimmed.ConsumeChar('[') ) {
249 fileHeaderRead= true;
250
251 if( !lineTrimmed.ConsumeCharFromEnd( ']' ) ) {
252 LinesWithReadErrors.emplace_back( lineNo );
253 // continue; // we tolerate missing section ends, but still mark this as a read error
254 }
255 lineTrimmed.TrimEnd();
256 actSection= SearchOrCreateSection( lineTrimmed ).first;
257 if( actSection->Comments.IsEmpty())
258 actSection->Comments.Allocate(Allocator, actComments);
259 actSection->WriteBack= writebackFlag;
260 writebackFlag= false;
261
262 actComments.Reset();
263 continue;
264 }
265
266 // Variable line(s)
267 {
268 String128 actName;
269 String4K actRawValue;
271
272 integer idx= lineTrimmed.IndexOfAny<lang::Inclusion::Include>( equalSignOrWhitespace );
273 if( idx < 0 ) {
274 actName << lineTrimmed;
275 lineTrimmed.Clear();
276 } else {
277 actName << lineTrimmed.Substring( 0, idx );
278 actName.TrimEnd();
279 lineTrimmed.ConsumeChars( idx );
280 actRawValue._(lineTrimmed);
281 }
282
283 // read continues as long as lines end with '\' (must not be '\\')
284 while ( lineTrimmed.CharAtEnd() == '\\'
285 && (lineTrimmed.Length() == 1 || lineTrimmed.CharAt<NC>( lineTrimmed.Length() -2 ) != '\\' ) )
286 {
287 actRawValue.NewLine();
288 reader.Read(actLine);
289 if ( reader.IsEOF() ) {
290 // last line of the file ended with '\' !
291 lineTrimmed.Clear();
292 break;
293 }
294 actLine.TrimEnd();
295 actRawValue << (actLine);
296 lineTrimmed= actLine;
297 }
298
299 // insert entry with raw and trimmed value
300 {
301 auto* entry= SearchEntry( actSection->Name, actName ).second;
302 if( entry == nullptr) {
303 entry= CreateEntry( actSection, actName );
304 ++qtyEntriesRead;
305 } else {
306 ALIB_WARNING("VARIABLES",
307 "Variable \"{}\" was found twice in INI-file. First value will be discarded "
308 "on writing.", String(actName) )
309 }
310 entry->Comments.Allocate(Allocator, actComments );
311 entry->RawValue.Allocate(Allocator, actRawValue );
312 entry->WriteBack= writebackFlag;
313 writebackFlag= false;
314
315 // parse trimmed value
316 String4K trimmedValue;
317 Substring parser= actRawValue;
318 parser.Trim().ConsumeChar('=');
319 parser.TrimStart();
320 Tokenizer tknzr(parser, '\n', true);
321 while((parser= tknzr.Next()).IsNotNull()) {
322 parser.ConsumeCharFromEnd('\r');
323 if( parser.CharAtEnd() == '\\'
324 && parser.CharAt(parser.Length()-2) != '\\' )
325 parser.ConsumeCharFromEnd<NC>();
326 parser.TrimEnd();
327 if(!startsWithCommentSymbol(parser))
328 trimmedValue << parser;
329 }
330 entry->Value.Allocate(Allocator, trimmedValue );
331 }
332
333 } // variable line(s)
334
335 actComments.Reset();
336 } // !EOF
337 file.close();
338
339 return qtyEntriesRead;
340}
341
342void IniFile::Write(const PathString& pPath) {
343 #if ALIB_CAMP
344 auto writeBackAttribute= alib::BASECAMP.GetResource("CFGIniWB");
345 #else
346 String writeBackAttribute= A_CHAR("writeback");
347 #endif
348
349 NString256 path(pPath);
350 if( path.IsEmpty() )
351 path << FileName;
352 ALIB_ASSERT_ERROR( path.IsNotEmpty(), "VARIABLES",
353 "Given Path is empty and no known filename from previous Read() operation available.")
354
355 // open output file
356 std::ofstream outputFileStream( path.Terminate(), std::ios::binary );
357 if ( !outputFileStream.is_open() ) {
358 #if ALIB_CAMP
359 int errNo= errno;
362 throw e;
363 #else
364 throw std::runtime_error("ErrorWritingFile");
365 #endif
366 }
367
368 OStreamWriter writer( outputFileStream );
369
370 // write file header
371 if ( FileComments.IsNotEmpty() ) {
372 writer.Write( FileComments );
373 if (FileComments.CharAtEnd() != '\n')
374 writer.Write( NEW_LINE );
375 }
376
377 // loop over all sections
378 for ( Section& section : Sections ) {
379 if( section.Name.IsNotEmpty()) {
380 // write section comments, write back flag and name
381 if(section.Comments.CharAtStart() !='\n' && section.Comments.CharAtStart() !='\r')
382 writer.Write( NEW_LINE );
383 writer.Write( section.Comments );
384 if(section.WriteBack) {
385 writer.Write( writeBackAttribute );
386 writer.Write( NEW_LINE );
387 }
388 writer.Write( NString256() << '[' << section.Name << ']' << NEW_LINE );
389 }
390
391 // variables
392 integer maxVarLength= 0;
393 for ( auto& entry : section.Entries )
394 maxVarLength= (std::max)( maxVarLength, entry.Name.Length() );
395
396 for ( auto& entry : section.Entries ) {
397 // write comments, write-back-flag, name
398 if( entry.Comments.IsNotEmpty()) {
399 if (entry.Comments.CharAtStart() != '\n' && entry.Comments.CharAtStart() != '\r')
400 writer.Write( NEW_LINE );
401 writer.Write( entry.Comments );
402 }
403
404 if(entry.WriteBack) {
405 writer.Write( writeBackAttribute );
406 writer.Write( NEW_LINE );
407 }
408 writer.Write( entry.Name );
409
410 // write value
411 if( entry.NewValue.IsNull()) {
412 writer.Write( entry.RawValue );
413 if(!entry.NewValue.EndsWith(NEW_LINE))
414 writer.Write( NEW_LINE );
415 } else {
416 writer.Write( "=" );
417 int cntLine= 0;
418 integer maxValLength= 0;
419 Substring rest= entry.NewValue;
420 for(;;) {
421 // write spaces
422 writer.Fill(' ', maxVarLength - ( cntLine == 0 ? entry.Name.Length() - 1
423 : -2 ));
424
425 Substring actual= rest.ConsumeToken( '\n' );
426 actual.ConsumeCharFromEnd('\r'); // in win case
427
428 // escape a trailing comment symbol
429 if( actual.CharAtStart() == '#' || actual.CharAtStart() == ';' )
430 writer.Write( "\\" );
431
432 writer.Write( actual );
433 if( rest.IsEmpty()) {
434 writer.Write( NEW_LINE );
435 break;
436 }
437
438 if( actual.Length() > maxValLength )
439 maxValLength= actual.Length() + 2;
440 writer.Fill(' ', maxValLength - actual.Length() );
441
442 writer.Write( "\\" );
443 writer.Write( NEW_LINE );
444
445 ++cntLine;
446 } } } }
447
448 // close file
449 outputFileStream.close();
450}
451
452
453}} // namespace [alib::variables]
Exception & Add(const lang::CallerInfo &ci, TEnum type, TArgs &&... args)
constexpr const TChar * Terminate() const
Definition tastring.inl:660
TAString & _(const TAppendable &src)
void DbgDisableBufferReplacementWarning()
Definition tastring.inl:244
TAString & TrimEnd(const TCString< TChar > &trimChars=CStringConstantsTraits< TChar >::DefaultWhitespaces())
constexpr integer Length() const
Definition string.inl:316
constexpr bool IsEmpty() const
Definition string.inl:365
TChar CharAtStart() const
Definition string.inl:433
constexpr bool IsNotNull() const
Definition string.inl:355
TChar CharAt(integer idx) const
Definition string.inl:415
constexpr bool IsNotEmpty() const
Definition string.inl:369
integer IndexOfAny(const TString &needles, integer startIdx=0) const
Definition string.inl:973
TChar CharAtEnd() const
Definition string.inl:452
void Allocate(TAllocator &allocator, const TString< TChar > &copy)
Definition string.inl:1745
TString< TChar > Substring(integer regionStart, integer regionLength=MAX_LEN) const
Definition string.inl:384
bool Equals(const TString< TChar > &rhs) const
Definition string.inl:531
TSubstring & TrimStart(const TCString< TChar > &whiteSpaces=CStringConstantsTraits< TChar >::DefaultWhitespaces())
Definition substring.inl:70
bool ConsumeCharFromEnd(TChar consumable)
integer ConsumeChars(integer regionLength, TSubstring *target=nullptr)
TSubstring & Trim(const TCString< TChar > &whiteSpaces=CStringConstantsTraits< TChar >::DefaultWhitespaces())
TString< TChar > ConsumeToken(TChar separator=',', lang::Inclusion includeSeparator=lang::Inclusion::Include)
TSubstring & TrimEnd(const TCString< TChar > &whiteSpaces=CStringConstantsTraits< TChar >::DefaultWhitespaces())
Definition substring.inl:90
void Write(const NString &src, integer *printedWidth=nullptr)
void Fill(const TChar fillChar, integer count)
ALIB_DLL TSubstring< TChar > & Next(lang::Whitespaces trimming=lang::Whitespaces::Trim, TChar newDelim='\0')
Definition tokenizer.cpp:26
ALIB_DLL void Reset()
Clears all data, resets the internal mono allocator.
Definition inifile.cpp:75
ALIB_DLL Entry * DeleteEntry(Section *section, const String &name)
Definition inifile.cpp:109
ALIB_DLL Section * CreateSection(const String &name)
Definition inifile.cpp:84
ALIB_DLL void Write(const system::PathString &path=system::NULL_PATH)
Definition inifile.cpp:342
ALIB_DLL Entry * CreateEntry(Section *section, const String &name)
Definition inifile.cpp:121
ALIB_DLL void AddComments(String &dest, const String &comments, const String &prefix=A_CHAR("# "))
Definition inifile.cpp:159
ListMA< integer > LinesWithReadErrors
Definition inifile.inl:235
system::PathString FileName
The file name.
Definition inifile.inl:228
bool startsWithCommentSymbol(String &subs)
Definition inifile.cpp:57
HashMap< MonoAllocator, EntryKey, std::pair< Section *, Entry * >, EntryKey::Hash, EntryKey::EqualTo > entryTable
Definition inifile.inl:213
ALIB_DLL std::pair< Section *, bool > SearchOrCreateSection(const String &sectionName)
Definition inifile.cpp:151
ListMA< Section > Sections
The list of sections.
Definition inifile.inl:225
ALIB_DLL std::pair< Section *, Entry * > SearchEntry(const String &section, const String &name)
Definition inifile.cpp:130
ALIB_DLL integer Read(const system::CPathString &path)
Definition inifile.cpp:172
ALIB_DLL IniFile()
Default constructor.
Definition inifile.cpp:69
ALIB_DLL Section * DeleteSection(const String &name)
Definition inifile.cpp:90
MonoAllocator Allocator
A monotonic allocator used for allocating sections and entries.
Definition inifile.inl:122
ALIB_DLL Section * SearchSection(const String &sectionName)
Definition inifile.cpp:139
String FileComments
The file header which will be written out as a comment lines with "# " prefixes.
Definition inifile.inl:231
#define ALIB_BOXING_VTABLE_DEFINE(TMapped, Identifier)
#define ALIB_CALLER_NULLED
Definition alib.inl:1027
#define A_CHAR(STR)
#define ALIB_STRINGS_TO_NARROW( src, dest, bufSize)
#define ALIB_WARNING(domain,...)
Definition alib.inl:1063
#define ALIB_ASSERT_RESULT_GREATER_THAN(func, value)
Definition alib.inl:1085
#define ALIB_DBG(...)
Definition alib.inl:853
#define ALIB_ASSERT_ERROR(cond, domain,...)
Definition alib.inl:1066
@ Include
Chooses inclusion.
strings::TCString< PathCharType > CPathString
The string-type used with this ALib Module.
Definition path.inl:36
strings::TString< PathCharType > PathString
The string-type used with this ALib Module.
Definition path.inl:33
Exception CreateSystemException(const CallerInfo &ci, int errNo)
@ ErrorWritingFile
An error occurred writing the file .
Definition inifile.inl:381
@ ErrorOpeningFile
File not found when reading.
Definition inifile.inl:378
LocalString< 256 > String256
Type alias name for TLocalString<character,256>.
strings::util::TTokenizer< character > Tokenizer
Type alias in namespace alib.
LocalString< 4096 > String4K
Type alias name for TLocalString<character,4096>.
camp::Basecamp BASECAMP
The singleton instance of ALib Camp class Basecamp.
Definition basecamp.cpp:81
LocalString< 128 > String128
Type alias name for TLocalString<character,128>.
strings::compatibility::std::IStreamReader IStreamReader
Type alias in namespace alib.
constexpr CString NEW_LINE
A zero-terminated string containing the new-line character sequence.
Definition cstring.inl:616
constexpr const String EMPTY_STRING
An empty string of the default character type.
Definition string.inl:2251
lang::integer integer
Type alias in namespace alib.
Definition integers.inl:149
LocalString< 8 > String8
Type alias name for TLocalString<character,8>.
constexpr CString DEFAULT_WHITESPACES
A zero-terminated string of default whitespace characters.
Definition cstring.inl:636
exceptions::Exception Exception
Type alias in namespace alib.
strings::TString< character > String
Type alias in namespace alib.
Definition string.inl:2189
NLocalString< 256 > NString256
Type alias name for TLocalString<nchar,256>.
strings::compatibility::std::OStreamWriter< TChar, TAllocator, TSynced, TTargetLF > OStreamWriter
Type alias in namespace alib.
LocalString< 2048 > String2K
Type alias name for TLocalString<character,2048>.
strings::TSubstring< character > Substring
Type alias in namespace alib.
See sibling type NC.
Definition chk_nc.inl:33
Hash functor for nodes hashed in field entryTable. Ignores letter case.
Definition inifile.inl:164
An entry in a Section.
Definition inifile.inl:128
String Name
The entry's name.
Definition inifile.inl:129
A section of the INI-file.
Definition inifile.inl:144
ListMA< Entry, Recycling::None > Entries
The list of variables of the section.
Definition inifile.inl:156
String Name
The name of the section.
Definition inifile.inl:154