ALib C++ Library
Library Version: 2510 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
22 import ALib.EnumOps;
23 import ALib.Strings;
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{
59 auto c= subs.CharAtStart();
60 return c == '#'
61 || c == ';'
62 || ( c == '/' && subs.Length() > 1 && subs.CharAt(1) == '/' );
63}
64
65
66
67// #################################################################################################
68// Constructor/Destructor
69// #################################################################################################
76
78{
79 FileComments = nullptr;
80 FileName = nullptr;
81 entryTable .Reset();
82 Sections .Reset();
83 LinesWithReadErrors.Reset();
84 Allocator .Reset();
85}
86
88{
89 auto& section= Sections.emplace_back(Allocator);
90 section.Name.Allocate(Allocator, name );
91 return &section;
92}
93
95{
96 // search section
97 for(auto secIt= Sections.begin() ; secIt != Sections.end(); ++secIt )
98 {
99 // found?
100 if( secIt->Name.Equals(name))
101 {
102 auto* section= &*secIt;
103 // delete hashtable entries
104 for(auto entryIt= entryTable.begin() ; entryIt != entryTable.end(); ++entryIt )
105 {
106 if( entryIt->second.first == section )
107 {
108 entryIt= entryTable.erase( entryIt );
109 continue;
110 }
111 }
112
113 (void) Sections.erase(secIt);
114 return section;
115 }
116 }
117 return nullptr;
118}
119
121{
122 // search entry
123 for(auto entryIt= section->Entries.begin() ; entryIt != section->Entries.end(); ++entryIt )
124 {
125 // found?
126 if( entryIt->Name.Equals(name))
127 {
128 auto* entry= &*entryIt;
129 ALIB_ASSERT_RESULT_GREATER_THAN( entryTable.erase( EntryKey(section->Name, name)), 0 )
130 return entry;
131 }
132 }
133 return nullptr;
134}
135
137{
138 ALIB_ASSERT_ERROR( pName.IsNotEmpty(), "VARIABLES", "Empty Ini-File variable name given.")
139 Entry& newEntry= section->Entries.push_back(Entry());
140 newEntry.Name.Allocate(Allocator, pName);
141 entryTable.EmplaceUnique( EntryKey(section->Name, newEntry.Name),
142 std::make_pair(section, &newEntry) );
143 return &newEntry;
144}
145
146std::pair<IniFile::Section*, IniFile::Entry*> IniFile::SearchEntry (const String& sectionName, const String& name )
147{
148 ALIB_ASSERT_ERROR( sectionName.IsNotNull(), "VARIABLES", "Nulled section name given.")
149 auto it= entryTable.Find( EntryKey(sectionName, name ) );
150 if( it != entryTable.end() )
151 return it.Mapped();
152 return std::pair<Section*, Entry*>(nullptr, nullptr);
153}
154
156{
157 ALIB_ASSERT_ERROR(sectionName.IsNotNull(), "VARIABLES", "Nulled section name given.")
158 auto sIt= Sections.begin();
159 while( sIt != Sections.end() )
160 {
161 if( sIt->Name.Equals<CHK, lang::Case::Ignore>( sectionName ) )
162 return &*sIt;
163 ++sIt;
164 }
165 return nullptr;
166}
167
168std::pair<IniFile::Section*, bool>
170{
171 Section* s= SearchSection( sectionName );
172 if ( s != nullptr )
173 return std::make_pair( s, false );
174
175 return std::make_pair( CreateSection( sectionName ), true );
176}
177
178void IniFile::AddComments ( String& dest, const String& comments, const String& prefix )
179{
180 String2K buf;
181 Tokenizer tknzr( comments, '\n' );
182 while( tknzr.HasNext() )
183 {
184 auto tok= tknzr.Next().TrimEnd();
185 tok.ConsumeCharFromEnd('\r');
186 if( !startsWithCommentSymbol(tok) )
187 buf << prefix;
188 buf << tok << NEW_LINE;
189 }
190 dest.Allocate(Allocator, buf);
191}
192
194{
195 FileName.Allocate(Allocator, path);
196
197 //------------- open file -------------
198 ALIB_STRINGS_TO_NARROW(path, nPath, 256)
199 std::ifstream file( nPath );
200
201 if ( !file.is_open() )
202 {
203 int errNo= errno;
204
205 // file does not exist ?
206 if ( errNo == int(SystemErrors::enoent) )
207 return -1;
208
209 // other errors: throw
210 #if ALIB_CAMP
213 A_CHAR("INI-"), path );
214 throw e;
215 #else
216 throw std::runtime_error("ErrorOpeningFile");
217 #endif
218 }
219
220
221 if( Sections.empty() )
223
224 String256 actLine; ALIB_DBG( actLine .DbgDisableBufferReplacementWarning(); )
225 String4K actComments; ALIB_DBG( actComments.DbgDisableBufferReplacementWarning(); )
226 Section* actSection= &Sections.front();
227
228 String8 equalSignOrWhitespace('=');
229 equalSignOrWhitespace << DEFAULT_WHITESPACES;
230
231 StringReader reader;
232 reader.SetStream( &file );
233
234 #if ALIB_CAMP
235 auto writeBackAttribute= alib::BASECAMP.GetResource("CFGIniWB");
236 #else
237 String writeBackAttribute= A_CHAR("writeback");
238 #endif
239
240 //------------- read loop -------------
241 bool writebackFlag = false;
242 int qtyEntriesRead = 0;
243 int lineNo = 0;
244 bool fileHeaderRead = false;
245 while( !reader.IsEOF() )
246 {
247 reader.Read( actLine ); ++lineNo;
248
249 Substring lineTrimmed( actLine );
250 lineTrimmed.Trim();
251
252 // end of file header?
253 if ( !fileHeaderRead && lineTrimmed.IsEmpty() )
254 {
255 fileHeaderRead= true;
256 FileComments.Allocate(Allocator, actComments);
257 actComments.Reset();
258 }
259
260 // continued comment or empty section?
261 if ( lineTrimmed.IsEmpty() || startsWithCommentSymbol( lineTrimmed ) )
262 {
263 actComments._(actLine).NewLine();
264 continue;
265 }
266
267 // writeback flag?
268 if( lineTrimmed.Equals<NC,lang::Case::Ignore>(writeBackAttribute) )
269 {
270 writebackFlag= true;
271 continue;
272 }
273
274 // section line?
275 if ( lineTrimmed.ConsumeChar('[') )
276 {
277 fileHeaderRead= true;
278
279 if( !lineTrimmed.ConsumeCharFromEnd( ']' ) )
280 {
281 LinesWithReadErrors.emplace_back( lineNo );
282 // continue; // we tolerate missing section ends, but still mark this as a read error
283 }
284 lineTrimmed.TrimEnd();
285 actSection= SearchOrCreateSection( lineTrimmed ).first;
286 if( actSection->Comments.IsEmpty())
287 actSection->Comments.Allocate(Allocator, actComments);
288 actSection->WriteBack= writebackFlag;
289 writebackFlag= false;
290
291 actComments.Reset();
292 continue;
293 }
294
295 // Variable line(s)
296 {
297 String128 actName;
298 String4K actRawValue;
299
300 integer idx= lineTrimmed.IndexOfAny<lang::Inclusion::Include>( equalSignOrWhitespace );
301 if( idx < 0 )
302 {
303 actName << lineTrimmed;
304 lineTrimmed.Clear();
305 }
306 else
307 {
308 actName << lineTrimmed.Substring( 0, idx );
309 actName.TrimEnd();
310 lineTrimmed.ConsumeChars( idx );
311 actRawValue._(lineTrimmed);
312 }
313
314 // read continues as long as lines end with '\' (must not be '\\')
315 while ( lineTrimmed.CharAtEnd() == '\\'
316 && (lineTrimmed.Length() == 1 || lineTrimmed.CharAt<NC>( lineTrimmed.Length() -2 ) != '\\' ) )
317 {
318 actRawValue.NewLine();
319 reader.Read( actLine );
320 if ( reader.IsEOF() )
321 {
322 // last line of the file ended with '\' !
323 lineTrimmed.Clear();
324 break;
325 }
326 actLine.TrimEnd();
327 actRawValue << (actLine);
328 lineTrimmed= actLine;
329 }
330
331 // insert entry with raw and trimmed value
332 {
333 auto* entry= SearchEntry( actSection->Name, actName ).second;
334 if( entry == nullptr)
335 {
336 entry= CreateEntry( actSection, actName );
337 ++qtyEntriesRead;
338 }
339 else
340 {
341 ALIB_WARNING("VARIABLES",
342 "Variable \"{}\" was found twice in INI-file. First value will be discarded "
343 "on writing.", String(actName) )
344 }
345 entry->Comments.Allocate(Allocator, actComments );
346 entry->RawValue.Allocate(Allocator, actRawValue );
347 entry->WriteBack= writebackFlag;
348 writebackFlag= false;
349
350 // parse trimmed value
351 String4K trimmedValue;
352 Substring parser= actRawValue;
353 parser.Trim().ConsumeChar('=');
354 parser.TrimStart();
355 Tokenizer tknzr(parser, '\n', true);
356 while((parser= tknzr.Next()).IsNotNull())
357 {
358 parser.ConsumeCharFromEnd('\r');
359 if( parser.CharAtEnd() == '\\'
360 && parser.CharAt(parser.Length()-2) != '\\' )
361 parser.ConsumeCharFromEnd<NC>();
362 parser.TrimEnd();
363 if(!startsWithCommentSymbol(parser))
364 trimmedValue << parser;
365 }
366 entry->Value.Allocate(Allocator, trimmedValue );
367 }
368
369 } // variable line(s)
370
371 actComments.Reset();
372 } // !EOF
373 file.close();
374
375 return qtyEntriesRead;
376}
377
378void IniFile::Write(const PathString& pPath)
379{
380 #if ALIB_CAMP
381 auto writeBackAttribute= alib::BASECAMP.GetResource("CFGIniWB");
382 #else
383 String writeBackAttribute= A_CHAR("writeback");
384 #endif
385
386 NString256 path(pPath);
387 if( path.IsEmpty() )
388 path << FileName;
389 ALIB_ASSERT_ERROR( path.IsNotEmpty(), "VARIABLES",
390 "Given Path is empty and no known filename from previous Read() operation available.")
391
392 // open output file
393 std::ofstream outputFileStream( path.Terminate(), std::ios::binary );
394 if ( !outputFileStream.is_open() )
395 {
396 #if ALIB_CAMP
397 int errNo= errno;
400 throw e;
401 #else
402 throw std::runtime_error("ErrorWritingFile");
403 #endif
404 }
405
406 StringWriter writer;
407 writer.SetStream( &outputFileStream );
408
409 // write file header
410 if ( FileComments.IsNotEmpty() )
411 {
412 writer.Write( FileComments );
413 if (FileComments.CharAtEnd() != '\n')
414 writer.Write( NEW_LINE );
415 }
416
417 // loop over all sections
418 for ( Section& section : Sections )
419 {
420 if( section.Name.IsNotEmpty())
421 {
422 // write section comments, write back flag and name
423 if(section.Comments.CharAtStart() !='\n' && section.Comments.CharAtStart() !='\r')
424 writer.Write( NEW_LINE );
425 writer.Write( section.Comments );
426 if(section.WriteBack)
427 {
428 writer.Write( writeBackAttribute );
429 writer.Write( NEW_LINE );
430 }
431 writer.Write( NString256() << '[' << section.Name << ']' << NEW_LINE );
432 }
433
434 // variables
435 integer maxVarLength= 0;
436 for ( auto& entry : section.Entries )
437 maxVarLength= (std::max)( maxVarLength, entry.Name.Length() );
438
439 for ( auto& entry : section.Entries )
440 {
441 // write comments, write-back-flag, name
442 if( entry.Comments.IsNotEmpty())
443 {
444 if (entry.Comments.CharAtStart() != '\n' && entry.Comments.CharAtStart() != '\r')
445 writer.Write( NEW_LINE );
446 writer.Write( entry.Comments );
447 }
448
449 if(entry.WriteBack)
450 {
451 writer.Write( writeBackAttribute );
452 writer.Write( NEW_LINE );
453 }
454 writer.Write( entry.Name );
455
456 // write value
457 if( entry.NewValue.IsNull())
458 {
459 writer.Write( entry.RawValue );
460 if(!entry.NewValue.EndsWith(NEW_LINE))
461 writer.Write( NEW_LINE );
462 }
463 else
464 {
465 writer.Write( "=" );
466 int cntLine= 0;
467 integer maxValLength= 0;
468 Substring rest= entry.NewValue;
469 for(;;)
470 {
471 // write spaces
472 writer.WriteChars(' ', maxVarLength - ( cntLine == 0 ? entry.Name.Length() - 1
473 : -2 ));
474
475 Substring actual= rest.ConsumeToken( '\n' );
476 actual.ConsumeCharFromEnd('\r'); // in win case
477
478 // escape a trailing comment symbol
479 if( actual.CharAtStart() == '#' || actual.CharAtStart() == ';' )
480 writer.Write( "\\" );
481
482 writer.Write( actual );
483 if( rest.IsEmpty())
484 {
485 writer.Write( NEW_LINE );
486 break;
487 }
488
489 if( actual.Length() > maxValLength )
490 maxValLength= actual.Length() + 2;
491 writer.WriteChars(' ', maxValLength - actual.Length() );
492
493 writer.Write( "\\" );
494 writer.Write( NEW_LINE );
495
496 ++cntLine;
497 }
498 }
499 }
500 }
501
502 // close file
503 outputFileStream.close();
504}
505
506
507}} // namespace [alib::variables]
Exception & Add(const lang::CallerInfo &ci, TEnum type, TArgs &&... args)
constexpr const TChar * Terminate() const
Definition tastring.inl:682
TAString & _(const TAppendable &src)
TAString & TrimEnd(const TCString< TChar > &trimChars=CStringConstantsTraits< TChar >::DefaultWhitespaces())
constexpr integer Length() const
Definition string.inl:318
constexpr bool IsEmpty() const
Definition string.inl:367
TChar CharAtStart() const
Definition string.inl:440
constexpr bool IsNotNull() const
Definition string.inl:357
TChar CharAt(integer idx) const
Definition string.inl:421
constexpr bool IsNotEmpty() const
Definition string.inl:371
integer IndexOfAny(const TString &needles, integer startIdx=0) const
Definition string.inl:1019
TChar CharAtEnd() const
Definition string.inl:460
void Allocate(TAllocator &allocator, const TString< TChar > &copy)
Definition string.inl:1870
TString< TChar > Substring(integer regionStart, integer regionLength=MAX_LEN) const
Definition string.inl:386
bool Equals(const TString< TChar > &rhs) const
Definition string.inl:541
TSubstring & TrimStart(const TCString< TChar > &whiteSpaces=CStringConstantsTraits< TChar >::DefaultWhitespaces())
Definition substring.inl:76
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:98
ALIB_DLL void WriteChars(const nchar fillChar, integer count)
ALIB_DLL void Write(const NString &src)
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:77
ALIB_DLL Entry * DeleteEntry(Section *section, const String &name)
Definition inifile.cpp:120
ALIB_DLL Section * CreateSection(const String &name)
Definition inifile.cpp:87
ALIB_DLL void Write(const system::PathString &path=system::NULL_PATH)
Definition inifile.cpp:378
ALIB_DLL Entry * CreateEntry(Section *section, const String &name)
Definition inifile.cpp:136
ALIB_DLL void AddComments(String &dest, const String &comments, const String &prefix=A_CHAR("# "))
Definition inifile.cpp:178
system::PathString FileName
The file name.
Definition inifile.inl:232
bool startsWithCommentSymbol(String &subs)
Definition inifile.cpp:57
HashMap< MonoAllocator, EntryKey, std::pair< Section *, Entry * >, EntryKey::Hash, EntryKey::EqualTo > entryTable
Definition inifile.inl:217
List< MonoAllocator, integer > LinesWithReadErrors
Definition inifile.inl:239
ALIB_DLL std::pair< Section *, bool > SearchOrCreateSection(const String &sectionName)
Definition inifile.cpp:169
List< MonoAllocator, Section > Sections
The list of sections.
Definition inifile.inl:229
ALIB_DLL std::pair< Section *, Entry * > SearchEntry(const String &section, const String &name)
Definition inifile.cpp:146
ALIB_DLL integer Read(const system::CPathString &path)
Definition inifile.cpp:193
ALIB_DLL IniFile()
Default constructor.
Definition inifile.cpp:70
ALIB_DLL Section * DeleteSection(const String &name)
Definition inifile.cpp:94
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:155
String FileComments
The file header which will be written out as a comment lines with "# " prefixes.
Definition inifile.inl:235
#define ALIB_BOXING_VTABLE_DEFINE(TMapped, Identifier)
#define ALIB_CALLER_NULLED
Definition alib.inl:1010
#define A_CHAR(STR)
#define ALIB_STRINGS_TO_NARROW( src, dest, bufSize)
#define ALIB_WARNING(domain,...)
Definition alib.inl:1046
#define ALIB_ASSERT_RESULT_GREATER_THAN(func, value)
Definition alib.inl:1068
#define ALIB_DBG(...)
Definition alib.inl:836
#define ALIB_ASSERT_ERROR(cond, domain,...)
Definition alib.inl:1049
@ 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:391
@ ErrorOpeningFile
File not found when reading.
Definition inifile.inl:388
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>.
constexpr CString NEW_LINE
A zero-terminated string containing the new-line character sequence.
Definition cstring.inl:644
constexpr const String EMPTY_STRING
An empty string of the default character type.
Definition string.inl:2443
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:664
exceptions::Exception Exception
Type alias in namespace alib.
strings::compatibility::std::StringWriter StringWriter
Type alias in namespace alib.
strings::compatibility::std::StringReader StringReader
Type alias in namespace alib.
strings::TString< character > String
Type alias in namespace alib.
Definition string.inl:2381
NLocalString< 256 > NString256
Type alias name for TLocalString<nchar,256>.
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:165
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:145
List< MonoAllocator, 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