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