ALib C++ Framework
by
Library Version: 2511 R0
Documentation generated by doxygen
Loading...
Searching...
No Matches
inifile.cpp
1//##################################################################################################
2// ALib C++ Framework
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 "Configuration MACRO 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.SectionPointer == 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 Handle{section, &newEntry} );
127 return &newEntry;
128}
129
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 Handle{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
172int IniFile::Read(const CPathString& path) {
173 FileName.Allocate(Allocator, path);
174
175 //------------------------------------------- open file ------------------------------------------
176 ALIB_STRINGS_TO_NARROW(path, nPath, 256)
177 std::ifstream file;
178 #if !ALIB_CAMP
179 file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
180 #endif
181 errno= 0;
182 file.open( nPath );
183
184 #if ALIB_CAMP
185 if ( !file.is_open() || errno ) {
188 A_CHAR("INI-"), path );
189 throw e;
190 }
191 #endif
192
193
194 if( Sections.empty() )
196
197 String256 actLine; ALIB_DBG( actLine .DbgDisableBufferReplacementWarning(); )
198 String4K actComments; ALIB_DBG( actComments.DbgDisableBufferReplacementWarning(); )
199 Section* actSection= &Sections.front();
200
201 String8 equalSignOrWhitespace('=');
202 equalSignOrWhitespace << DEFAULT_WHITESPACES;
203
204 IStreamReader reader;
205 reader.SetStream( &file );
206
207 #if ALIB_CAMP
208 auto writeBackAttribute= alib::BASECAMP.GetResource("CFGIniWB");
209 #else
210 String writeBackAttribute= A_CHAR("writeback");
211 #endif
212
213 //------------------------------------------- read loop ------------------------------------------
214 bool writebackFlag = false;
215 int qtyEntriesRead = 0;
216 int lineNo = 0;
217 bool fileHeaderRead = false;
218 while( !reader.IsEOF() ) {
219 reader.Read(actLine); ++lineNo;
220
221 Substring lineTrimmed( actLine );
222 lineTrimmed.Trim();
223
224 // end of file header?
225 if ( !fileHeaderRead && lineTrimmed.IsEmpty() ) {
226 fileHeaderRead= true;
227 if (actComments.IsNotEmpty())
228 FileComments.Allocate(Allocator, actComments);
229 actComments.Reset();
230 }
231
232 // continued comment or empty section?
233 if ( lineTrimmed.IsEmpty() || startsWithCommentSymbol( lineTrimmed ) ) {
234 actComments._(actLine).NewLine();
235 continue;
236 }
237
238 // writeback flag?
239 if( lineTrimmed.Equals<NC,lang::Case::Ignore>(writeBackAttribute) ) {
240 writebackFlag= true;
241 continue;
242 }
243
244 // section line?
245 if ( lineTrimmed.ConsumeChar('[') ) {
246 fileHeaderRead= true;
247
248 if( !lineTrimmed.ConsumeCharFromEnd( ']' ) ) {
249 LinesWithReadErrors.emplace_back( lineNo );
250 // continue; // we tolerate missing section ends, but still mark this as a read error
251 }
252 lineTrimmed.TrimEnd();
253 actSection= SearchOrCreateSection( lineTrimmed ).first;
254 if( actSection->Comments.IsEmpty())
255 actSection->Comments.Allocate(Allocator, actComments);
256 actSection->WriteBack= writebackFlag;
257 writebackFlag= false;
258
259 actComments.Reset();
260 continue;
261 }
262
263 // Variable line(s)
264 {
265 String128 actName;
266 String4K actRawValue;
268
269 integer idx= lineTrimmed.IndexOfAny<lang::Inclusion::Include>( equalSignOrWhitespace );
270 if( idx < 0 ) {
271 actName << lineTrimmed;
272 lineTrimmed.Clear();
273 } else {
274 actName << lineTrimmed.Substring( 0, idx );
275 actName.TrimEnd();
276 lineTrimmed.ConsumeChars( idx );
277 actRawValue._(lineTrimmed);
278 }
279
280 // read continues as long as lines end with '\' (must not be '\\')
281 while ( lineTrimmed.CharAtEnd() == '\\'
282 && (lineTrimmed.Length() == 1 || lineTrimmed.CharAt<NC>( lineTrimmed.Length() -2 ) != '\\' ) )
283 {
284 actRawValue.NewLine();
285 reader.Read(actLine);
286 if ( reader.IsEOF() ) {
287 // last line of the file ended with '\' !
288 lineTrimmed.Clear();
289 break;
290 }
291 actLine.TrimEnd();
292 actRawValue << (actLine);
293 lineTrimmed= actLine;
294 }
295
296 // insert entry with raw and trimmed value
297 {
298 auto* entry= SearchEntry( actSection->Name, actName ).EntryPointer;
299 if( entry == nullptr) {
300 entry= CreateEntry( actSection, actName );
301 ++qtyEntriesRead;
302 } else {
303 // todo: This must not be an ALib warning, but instead has to be attached
304 // or returned as a list of warnings, that can be displayed to the
305 // end-user in any way
306 ALIB_WARNING("VARIABLES",
307 "Variable \"{}\" was found twice in INI-file. First value will be discarded "
308 "on writing.\n"
309 " Previous occurrence @ {}:{}\n"
310 " Current occurrence @ {}:{}"
311 , String(actName), path, entry->LineNo, path, lineNo )
312 }
313 entry->Comments.Allocate(Allocator, actComments );
314 entry->RawValue.Allocate(Allocator, actRawValue );
315 entry->WriteBack= writebackFlag;
316 entry->LineNo= lineNo;
317 writebackFlag= false;
318
319 // parse trimmed value
320 String4K trimmedValue;
321 Substring parser= actRawValue;
322 parser.Trim().ConsumeChar('=');
323 parser.TrimStart();
324 Tokenizer tknzr(parser, '\n', true);
325 while((parser= tknzr.Next()).IsNotNull()) {
326 parser.ConsumeCharFromEnd('\r');
327 if( parser.CharAtEnd() == '\\'
328 && parser.CharAt(parser.Length()-2) != '\\' )
329 parser.ConsumeCharFromEnd<NC>();
330 parser.TrimEnd();
331 if(!startsWithCommentSymbol(parser))
332 trimmedValue << parser;
333 }
334 entry->Value.Allocate(Allocator, trimmedValue );
335 }
336
337 } // variable line(s)
338
339 actComments.Reset();
340 } // !EOF
341 file.close();
342
343 return qtyEntriesRead;
344}
345
346void IniFile::Write(const PathString& pPath) {
347 #if ALIB_CAMP
348 auto writeBackAttribute= alib::BASECAMP.GetResource("CFGIniWB");
349 #else
350 String writeBackAttribute= A_CHAR("writeback");
351 #endif
352
353 NString256 path(pPath);
354 if( path.IsEmpty() )
355 path << FileName;
356 ALIB_ASSERT_ERROR( path.IsNotEmpty(), "VARIABLES",
357 "Given Path is empty and no known filename from previous Read() operation available.")
358
359 // open output file
360 std::ofstream file;
361 #if !ALIB_CAMP
362 file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
363 #endif
364 errno= 0;
365 file.open( path.Terminate(), std::ios::binary );
366 #if ALIB_CAMP
367 if ( !file.is_open() || errno ) {
370 "INI-", FileName );
371 throw e;
372 }
373 #endif
374
375 OStreamWriter writer( file );
376
377 // write file header
378 if ( FileComments.IsNotEmpty() ) {
379 writer.Write( FileComments );
380 if (FileComments.CharAtEnd() != '\n')
381 writer.Write( NEW_LINE );
382 }
383
384 // loop over all sections
385 for ( Section& section : Sections ) {
386 if( section.Name.IsNotEmpty()) {
387 // write section comments, write back flag and name
388 if(section.Comments.CharAtStart() !='\n' && section.Comments.CharAtStart() !='\r')
389 writer.Write( NEW_LINE );
390 writer.Write( section.Comments );
391 if(section.WriteBack) {
392 writer.Write( writeBackAttribute );
393 writer.Write( NEW_LINE );
394 }
395 writer.Write( NString256() << '[' << section.Name << ']' << NEW_LINE );
396 }
397
398 // variables
399 integer maxVarLength= 0;
400 for ( auto& entry : section.Entries )
401 maxVarLength= (std::max)( maxVarLength, entry.Name.Length() );
402
403 for ( auto& entry : section.Entries ) {
404 // write comments, write-back-flag, name
405 if( entry.Comments.IsNotEmpty()) {
406 if (entry.Comments.CharAtStart() != '\n' && entry.Comments.CharAtStart() != '\r')
407 writer.Write( NEW_LINE );
408 writer.Write( entry.Comments );
409 }
410
411 if(entry.WriteBack) {
412 writer.Write( writeBackAttribute );
413 writer.Write( NEW_LINE );
414 }
415 writer.Write( entry.Name );
416
417 // write value
418 if( entry.NewValue.IsNull()) {
419 writer.Write( entry.RawValue );
420 if(!entry.NewValue.EndsWith(NEW_LINE))
421 writer.Write( NEW_LINE );
422 } else {
423 writer.Write( "=" );
424 int cntLine= 0;
425 integer maxValLength= 0;
426 Substring rest= entry.NewValue;
427 for(;;) {
428 // write spaces
429 writer.Fill(' ', maxVarLength - ( cntLine == 0 ? entry.Name.Length() - 1
430 : -2 ));
431
432 Substring actual= rest.ConsumeToken( '\n' );
433 actual.ConsumeCharFromEnd('\r'); // in win case
434
435 // escape a trailing comment symbol
436 if( actual.CharAtStart() == '#' || actual.CharAtStart() == ';' )
437 writer.Write( "\\" );
438
439 writer.Write( actual );
440 if( rest.IsEmpty()) {
441 writer.Write( NEW_LINE );
442 break;
443 }
444
445 if( actual.Length() > maxValLength )
446 maxValLength= actual.Length() + 2;
447 writer.Fill(' ', maxValLength - actual.Length() );
448
449 writer.Write( "\\" );
450 writer.Write( NEW_LINE );
451
452 ++cntLine;
453 } } } }
454
455 // close file
456 file.close();
457}
458
459
460}} // namespace [alib::variables]
#define ALIB_CALLER_NULLED
Definition alib.inl:1105
#define A_CHAR(STR)
Definition alib.inl:1325
#define ALIB_WARNING(domain,...)
Definition alib.inl:1141
#define ALIB_ASSERT_RESULT_GREATER_THAN(func, value)
Definition alib.inl:1163
#define ALIB_DBG(...)
Definition alib.inl:931
#define ALIB_ASSERT_ERROR(cond, domain,...)
Definition alib.inl:1144
#define ALIB_BOXING_VTABLE_DEFINE(TMapped, Identifier)
Exception & Add(const lang::CallerInfo &ci, TEnum type, TArgs &&... args)
constexpr const TChar * Terminate() const
Definition tastring.inl:620
TAString & _(const TAppendable &src)
void DbgDisableBufferReplacementWarning()
Definition tastring.inl:241
TAString & TrimEnd(const TCString< TChar > &trimChars=CStringConstantsTraits< TChar >::DefaultWhitespaces())
constexpr integer Length() const
Definition string.inl:304
constexpr bool IsEmpty() const
Definition string.inl:353
TChar CharAtStart() const
Definition string.inl:421
constexpr bool IsNotNull() const
Definition string.inl:343
void Allocate(TAllocator &allocator, const TString< TChar > &copy)
Definition string.inl:1734
TChar CharAt(integer idx) const
Definition string.inl:403
constexpr bool IsNotEmpty() const
Definition string.inl:357
integer IndexOfAny(const TString &needles, integer startIdx=0) const
Definition string.inl:961
TChar CharAtEnd() const
Definition string.inl:440
TString< TChar > Substring(integer regionStart, integer regionLength=MAX_LEN) const
Definition string.inl:372
bool Equals(const TString< TChar > &rhs) const
Definition string.inl:519
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)
TSubstring< TChar > & Next(lang::Whitespaces trimming=lang::Whitespaces::Trim, TChar newDelim='\0')
Definition tokenizer.cpp:26
void Reset()
Clears all data, resets the internal mono allocator.
Definition inifile.cpp:75
Entry * DeleteEntry(Section *section, const String &name)
Definition inifile.cpp:109
Section * CreateSection(const String &name)
Definition inifile.cpp:84
void Write(const system::PathString &path=system::NULL_PATH)
Definition inifile.cpp:346
Entry * CreateEntry(Section *section, const String &name)
Definition inifile.cpp:121
void AddComments(String &dest, const String &comments, const String &prefix=A_CHAR("# "))
Definition inifile.cpp:159
HashMap< MonoAllocator, EntryKey, Handle, EntryKey::Hash, EntryKey::EqualTo > entryTable
Definition inifile.inl:220
ListMA< integer > LinesWithReadErrors
Definition inifile.inl:242
system::PathString FileName
The file name.
Definition inifile.inl:235
bool startsWithCommentSymbol(String &subs)
Definition inifile.cpp:57
Handle SearchEntry(const String &section, const String &name)
Definition inifile.cpp:130
int Read(const system::CPathString &path)
Definition inifile.cpp:172
std::pair< Section *, bool > SearchOrCreateSection(const String &sectionName)
Definition inifile.cpp:151
ListMA< Section > Sections
The list of sections.
Definition inifile.inl:232
IniFile()
Default constructor.
Definition inifile.cpp:69
Section * DeleteSection(const String &name)
Definition inifile.cpp:90
MonoAllocator Allocator
A monotonic allocator used for allocating sections and entries.
Definition inifile.inl:122
Section * SearchSection(const String &sectionName)
Definition inifile.cpp:139
String FileComments
The file header which will be written out as comment lines with "# " prefixes.
Definition inifile.inl:238
Exception CreateExceptionFromSystemError(const CallerInfo &ci, std::error_code errorCode)
@ 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
@ ErrorWritingFile
An error occurred writing the file .
Definition inifile.inl:385
@ ErrorOpeningFile
File not found when reading.
Definition inifile.inl:382
strings::compatibility::std::IStreamReader IStreamReader
Type alias in namespace alib.
strings::util::TTokenizer< character > Tokenizer
Type alias in namespace alib.
LocalString< 8 > String8
Type alias name for #"TLocalString;TLocalString<character,8>".
constexpr CString NEW_LINE
A zero-terminated string containing the new-line character sequence.
Definition cstring.inl:540
constexpr const String EMPTY_STRING
An empty string of the default character type.
Definition string.inl:2234
lang::integer integer
Type alias in namespace alib.
Definition integers.inl:149
LocalString< 4096 > String4K
Type alias name for #"TLocalString;TLocalString<character,4096>".
strings::TString< character > String
Type alias in namespace alib.
Definition string.inl:2172
strings::TSubstring< character > Substring
Type alias in namespace alib.
camp::Basecamp BASECAMP
The singleton instance of ALib Camp class #"Basecamp".
Definition basecamp.cpp:80
exceptions::Exception Exception
Type alias in namespace alib.
constexpr CString DEFAULT_WHITESPACES
A zero-terminated string of default whitespace characters.
Definition cstring.inl:560
LocalString< 128 > String128
Type alias name for #"TLocalString;TLocalString<character,128>".
NLocalString< 256 > NString256
Type alias name for #"TLocalString;TLocalString<nchar,256>".
LocalString< 256 > String256
Type alias name for #"TLocalString;TLocalString<character,256>".
LocalString< 2048 > String2K
Type alias name for #"TLocalString;TLocalString<character,2048>".
strings::compatibility::std::OStreamWriter< TChar, TAllocator, TSynced, TTargetLF > OStreamWriter
Type alias in namespace alib.
#define ALIB_STRINGS_TO_NARROW( src, dest, bufSize)
See sibling type #"NC".
Definition chk_nc.inl:33
Hash functor for nodes hashed in field #"entryTable". Ignores letter case.
Definition inifile.inl:171
An entry in a #"IniFile::Section".
Definition inifile.inl:128
String Name
The entry's name.
Definition inifile.inl:129
String Comments
The entry's comments.
Definition inifile.inl:130
A pair of pointers to a section and an entry in the section.
Definition inifile.inl:162
Entry * EntryPointer
Pointer to the entry in the #"SectionPointer".
Definition inifile.inl:164
A section of the INI-file.
Definition inifile.inl:145
ListMA< Entry, Recycling::None > Entries
The list of variables of the section.
Definition inifile.inl:157
String Name
The name of the section.
Definition inifile.inl:155