E-MailRelay
gmapfile.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file gmapfile.cpp
19///
20
21#include "gdef.h"
22#include "gmapfile.h"
23#include "gstr.h"
24#include "gpath.h"
25#include "gprocess.h"
26#include "gdatetime.h"
27#include "gdate.h"
28#include "gtime.h"
29#include "gfile.h"
30#include "glog.h"
31#include "gassert.h"
32#include <algorithm> // std::find
33#include <iterator>
34#include <stdexcept>
35#include <array>
36
38= default;
39
40G::MapFile::MapFile( const G::Path & path , const std::string & kind ) :
41 m_kind(kind)
42{
43 if( !path.empty() )
44 {
45 m_path = path ;
46 readFrom( path , kind ) ;
47 }
48}
49
50G::MapFile::MapFile( std::istream & stream )
51{
52 readFrom( stream ) ;
53}
54
56 m_map(map)
57{
58 m_keys.reserve( m_map.size() ) ;
59 for( auto & p : m_map )
60 m_keys.push_back( p.first ) ;
61}
62
63G::MapFile::MapFile( const OptionMap & map , const std::string & yes )
64{
65 for( auto p = map.begin() ; p != map.end() ; )
66 {
67 std::string key = (*p).first ;
68 if( !(*p).second.isOff() )
69 {
70 std::string value = (*p).second.isOn() ? yes : map.value(key) ;
71 add( key , value ) ;
72 }
73 while( p != map.end() && (*p).first == key ) // since we used OptionMap::value() to get them all
74 ++p ;
75 }
76}
77
78void G::MapFile::readFrom( const G::Path & path , const std::string & kind )
79{
80 std::ifstream stream ;
81 File::open( stream , path , File::Text() ) ;
82 if( !stream.good() )
83 throw readError( path , kind ) ;
84 G_LOG( "MapFile::read: reading [" << path.str() << "]" ) ;
85 readFrom( stream ) ;
86 if( stream.bad() ) // eg. EISDIR
87 throw readError( path , kind ) ;
88}
89
90void G::MapFile::readFrom( std::istream & stream )
91{
92 std::string line ;
93 while( stream.good() )
94 {
95 Str::readLineFrom( stream , "\n" , line ) ;
96 Str::trimRight( line , {"\r",1U} ) ;
97 if( line.empty() )
98 continue ;
99 if( !stream )
100 break ;
101 if( ignore(line) )
102 continue ;
103
104 // no escaping here -- just strip quotes if the value starts and ends with them
105 std::string key ;
106 std::string value ;
107 {
108 StringArray parts ;
109 Str::splitIntoTokens( line , parts , " =\t" ) ;
110 if( parts.empty() )
111 continue ;
112 key = parts[0] ;
113
114 value = Str::tail( line , line.find(key)+key.size() , std::string() ) ;
115 Str::trimLeft( value , {" =\t",3U} ) ;
116 Str::trimRight( value , Str::ws() ) ;
117
118 if( value.length() >= 2U && value.at(0U) == '"' && value.at(value.length()-1U) == '"' )
119 value = value.substr(1U,value.length()-2U) ;
120 }
121 add( key , value ) ;
122 }
123}
124
125bool G::MapFile::ignore( const std::string & line ) const
126{
127 std::string::size_type pos_interesting = line.find_first_not_of(" \t\r#") ;
128 if( pos_interesting == std::string::npos )
129 return true ;
130
131 std::string::size_type pos_hash = line.find('#') ;
132 if( pos_hash != std::string::npos && pos_hash < pos_interesting )
133 return true ;
134
135 return false ;
136}
137
138void G::MapFile::check( const G::Path & path , const std::string & kind )
139{
140 MapFile tmp ;
141 tmp.readFrom( path , kind ) ;
142}
143
144void G::MapFile::log( const std::string & prefix_in ) const
145{
146 std::string prefix = prefix_in.empty() ? std::string() : ( prefix_in + ": " ) ;
147 for( const auto & key : m_keys )
148 {
149 auto p = m_map.find( key ) ;
150 if( p == m_map.end() ) continue ;
151 std::string value = (*p).second ;
152 G_LOG( "MapFile::item: " << prefix << key << "=[" <<
153 ( Str::ifind(key,"password") == std::string::npos ?
154 Str::printable(value) :
155 std::string("<not-logged>")
156 ) << "]" ) ;
157 }
158}
159
160void G::MapFile::writeItem( std::ostream & stream , const std::string & key ) const
161{
162 std::string value = m_map.find(key) == m_map.end() ? std::string() : (*m_map.find(key)).second ;
163 writeItem( stream , key , value ) ;
164}
165
166void G::MapFile::writeItem( std::ostream & stream , const std::string & key , const std::string & value )
167{
168 const char * qq = value.find(' ') == std::string::npos ? "" : "\"" ;
169 stream << key << "=" << qq << value << qq << "\n" ;
170}
171
172std::string G::MapFile::quote( const std::string & s )
173{
174 return s.find_first_of(" \t") == std::string::npos ? s : ("\""+s+"\"") ;
175}
176
177void G::MapFile::editInto( const G::Path & path , bool make_backup ,
178 bool allow_read_error , bool allow_write_error ) const
179{
180 List lines = read( path , m_kind , allow_read_error ) ;
181 commentOut( lines ) ;
182 replace( lines ) ;
183 if( make_backup ) backup( path ) ;
184 save( path , lines , allow_write_error ) ;
185}
186
187G::MapFile::List G::MapFile::read( const G::Path & path , const std::string & kind , bool allow_read_error ) const
188{
189 List line_list ;
190 std::ifstream file_in ;
191 File::open( file_in , path , File::Text() ) ;
192 if( !file_in.good() && !allow_read_error )
193 throw readError( path , kind ) ;
194 while( file_in.good() )
195 {
196 std::string line = Str::readLineFrom( file_in , "\n" ) ;
197 Str::trimRight( line , {"\r",1U} ) ;
198 if( !file_in ) break ;
199 line_list.push_back( line ) ;
200 }
201 return line_list ;
202}
203
204void G::MapFile::commentOut( List & line_list ) const
205{
206 for( auto & line : line_list )
207 {
208 if( line.empty() || line.at(0U) == '#' )
209 continue ;
210 line.insert( 0U , 1U , '#' ) ;
211 }
212}
213
214void G::MapFile::replace( List & line_list ) const
215{
216 for( const auto & map_item : m_map )
217 {
218 bool found = false ;
219 for( auto & line : line_list )
220 {
221 if( line.empty() ) continue ;
222 StringArray parts ;
223 Str::splitIntoTokens( line , parts , " \r\n\t=#" ) ;
224 if( parts.empty() ) continue ;
225 if( parts.at(0U) == map_item.first )
226 {
227 std::string value = map_item.second ;
228 line = Str::trimmed( map_item.first + " " + quote(value) , Str::ws() ) ;
229 found = true ;
230 break ;
231 }
232 }
233
234 if( !found )
235 {
236 std::string value = map_item.second ;
237 line_list.push_back( Str::trimmed( map_item.first + " " + quote(value) , Str::ws() ) ) ;
238 }
239 }
240}
241
242void G::MapFile::backup( const G::Path & path )
243{
244 // ignore errors
245 BrokenDownTime now = SystemTime::now().local() ;
246 std::string timestamp = Date(now).str(Date::Format::yyyy_mm_dd) + Time(now).hhmmss() ;
247 Path backup( path.dirname() , path.basename() + "." + timestamp ) ;
248 Process::Umask umask( Process::Umask::Mode::Tightest ) ;
249 File::copy( path , backup , std::nothrow ) ;
250}
251
252void G::MapFile::save( const G::Path & path , List & line_list , bool allow_write_error )
253{
254 std::ofstream file_out ;
255 File::open( file_out , path , File::Text() ) ;
256 std::copy( line_list.begin() , line_list.end() , std::ostream_iterator<std::string>(file_out,"\n") ) ;
257 file_out.close() ;
258 if( file_out.fail() && !allow_write_error )
259 throw writeError( path ) ;
260}
261
262bool G::MapFile::booleanValue( const std::string & key , bool default_ ) const
263{
264 auto p = m_map.find( key ) ;
265 if( p == m_map.end() )
266 {
267 return default_ ;
268 }
269 else if( (*p).second.empty() )
270 {
271 return true ;
272 }
273 else
274 {
275 return G::Str::isPositive( (*p).second ) ;
276 }
277}
278
279std::string G::MapFile::value( const std::string & key , const std::string & default_ ) const
280{
281 auto p = m_map.find( key ) ;
282 std::string result = ( p == m_map.end() || (*p).second.empty() ) ? default_ : (*p).second ;
283 return result ;
284}
285
286std::string G::MapFile::value( const std::string & key , const char * default_ ) const
287{
288 return value( key , std::string(default_) ) ;
289}
290
291std::string G::MapFile::mandatoryValue( const std::string & key ) const
292{
293 if( m_map.find(key) == m_map.end() )
294 throw missingValueError( m_path , m_kind , key ) ;
295 return value( key ) ;
296}
297
298G::Path G::MapFile::pathValue( const std::string & key ) const
299{
300 return Path( mandatoryValue(key) ) ;
301}
302
303G::Path G::MapFile::expandedPathValue( const std::string & key ) const
304{
305 return Path( expand(mandatoryValue(key)) ) ;
306}
307
308G::Path G::MapFile::pathValue( const std::string & key , const G::Path & default_ ) const
309{
310 return Path( value(key,default_.str()) ) ;
311}
312
313G::Path G::MapFile::expandedPathValue( const std::string & key , const G::Path & default_ ) const
314{
315 return Path( expand(value(key,default_.str())) ) ;
316}
317
318unsigned int G::MapFile::numericValue( const std::string & key , unsigned int default_ ) const
319{
320 std::string s = value( key , std::string() ) ;
321 return !s.empty() && Str::isUInt(s) ? Str::toUInt( s ) : default_ ;
322}
323
324void G::MapFile::remove( const std::string & key )
325{
326 auto p = m_map.find( key ) ;
327 if( p != m_map.end() )
328 {
329 m_map.erase( p ) ;
330 G_ASSERT( std::find(m_keys.begin(),m_keys.end(),key) != m_keys.end() ) ;
331 m_keys.erase( std::find(m_keys.begin(),m_keys.end(),key) ) ;
332 }
333}
334
335std::string G::MapFile::expand( const std::string & value_in ) const
336{
337 std::string value( value_in ) ;
338 expand_( value ) ;
339 return value ;
340}
341
342namespace G
343{
344 namespace MapFileImp
345 {
346 std::size_t find_single( std::string & s , char c , std::size_t start_pos )
347 {
348 std::array<char,2U> cc {{ c , '\0' }} ;
349 std::size_t pos = start_pos ;
350 for(;;)
351 {
352 pos = s.find( &cc[0] , pos ) ;
353 if( pos == std::string::npos )
354 {
355 break ; // not found
356 }
357 else if( (pos+1U) < s.length() && s.at(pos+1U) == c )
358 {
359 s.erase( pos , 1U ) ;
360 if( (pos+1U) == s.length() )
361 {
362 pos = std::string::npos ;
363 break ;
364 }
365 pos++ ;
366 }
367 else
368 {
369 break ; // found
370 }
371 }
372 return pos ;
373 }
374 }
375}
376
377bool G::MapFile::expand_( std::string & value ) const
378{
379 bool changed = false ;
380 std::size_t start = 0U ;
381 std::size_t end = 0U ;
382 std::size_t const npos = std::string::npos ;
383 while( end < value.length() )
384 {
385 start = MapFileImp::find_single( value , '%' , end ) ;
386 if( start == npos ) break ;
387 end = value.find( '%' , start+1U ) ;
388 if( end == npos ) break ;
389 end++ ;
390 std::string key = value.substr( start+1U , end-start-2U ) ;
391 auto p = m_map.find( key ) ;
392 if( p != m_map.end() )
393 {
394 std::size_t old = end - start ;
395 std::size_t new_ = (*p).second.length() ;
396 value.replace( start , old , (*p).second ) ;
397 end += new_ ;
398 end -= old ;
399 changed = true ;
400 }
401 }
402 return changed ;
403}
404
405void G::MapFile::add( const std::string & key , const std::string & value , bool clear )
406{
407 if( m_map.find(key) == m_map.end() )
408 {
409 m_keys.push_back( key ) ;
410 m_map[key] = value ;
411 }
412 else if( clear )
413 {
414 m_map[key] = value ;
415 }
416 else
417 {
418 m_map[key].append( 1U , ',' ) ;
419 m_map[key].append( value ) ;
420 }
421}
422
423bool G::MapFile::contains( const std::string & key ) const
424{
425 return m_map.find( key ) != m_map.end() ;
426}
427
429{
430 return m_map ;
431}
432
434{
435 return m_keys ;
436}
437
438std::string G::MapFile::ekind( const std::string & kind_in )
439{
440 return kind_in.empty() ? std::string("map") : kind_in ;
441}
442
443std::string G::MapFile::epath( const G::Path & path_in )
444{
445 return path_in.empty() ? std::string() : (" ["+path_in.str()+"]") ;
446}
447
448G::MapFile::Error G::MapFile::readError( const Path & path , const std::string & kind_in )
449{
450 std::string description = "cannot read " + ekind(kind_in) + " file" + epath(path) ;
451 return Error( description ) ;
452}
453
454G::MapFile::Error G::MapFile::writeError( const Path & path , const std::string & kind_in )
455{
456 std::string description = "cannot create " + ekind(kind_in) + " file" + epath(path) ;
457 return Error( description ) ;
458}
459
460G::MapFile::Error G::MapFile::missingValueError( const Path & path , const std::string & kind ,
461 const std::string & key )
462{
463 std::string description = "no item [" + key + "] in " + ekind(kind) + " file" + epath(path) ;
464 return Error( description ) ;
465}
466
std::string str(const char *fmt) const
Returns the formatted date, with the same restrictions as format().
Definition: gdatetime.cpp:190
An overload discriminator for G::File::open().
Definition: gfile.h:62
static void open(std::ofstream &, const Path &)
Calls open() on the given output file stream.
Definition: gfile_unix.cpp:55
static bool copy(const Path &from, const Path &to, std::nothrow_t)
Copies a file. Returns false on error.
Definition: gfile.cpp:83
A class for reading, writing and editing key=value files, supporting variable expansion of percent-ke...
Definition: gmapfile.h:57
static void check(const G::Path &, const std::string &kind=std::string())
Throws if the file is invalid.
Definition: gmapfile.cpp:138
std::string expand(const std::string &value) const
Does one-pass variable substitution for the given string.
Definition: gmapfile.cpp:335
void editInto(const G::Path &path, bool make_backup, bool allow_read_error, bool allow_write_error) const
Edits an existing file so that its contents reflect this map.
Definition: gmapfile.cpp:177
bool booleanValue(const std::string &key, bool default_) const
Returns a boolean value from the map.
Definition: gmapfile.cpp:262
unsigned int numericValue(const std::string &key, unsigned int default_) const
Returns a numeric value from the map.
Definition: gmapfile.cpp:318
void add(const std::string &key, const std::string &value, bool clear=false)
Adds or updates a single item in the map.
Definition: gmapfile.cpp:405
const G::StringMap & map() const
Returns a reference to the internal map.
Definition: gmapfile.cpp:428
void remove(const std::string &key)
Removes a value (if it exists).
Definition: gmapfile.cpp:324
G::Path pathValue(const std::string &key) const
Returns a mandatory path value from the map.
Definition: gmapfile.cpp:298
MapFile()
Constructor for an empty map.
bool contains(const std::string &key) const
Returns true if the map contains the given key.
Definition: gmapfile.cpp:423
G::Path expandedPathValue(const std::string &key) const
Returns a mandatory path value from the map with expand().
Definition: gmapfile.cpp:303
const G::StringArray & keys() const
Returns a reference to the internal ordered list of keys.
Definition: gmapfile.cpp:433
void log(const std::string &prefix=std::string()) const
Logs the contents.
Definition: gmapfile.cpp:144
void writeItem(std::ostream &, const std::string &key) const
Writes a single item from this map to the stream.
Definition: gmapfile.cpp:160
std::string value(const std::string &key, const std::string &default_=std::string()) const
Returns a string value from the map.
Definition: gmapfile.cpp:279
A multimap-like container for command-line options and their values.
Definition: goptionmap.h:42
A Path object represents a file system path.
Definition: gpath.h:72
std::string basename() const
Returns the rightmost part of the path, ignoring "." parts.
Definition: gpath.cpp:328
Path dirname() const
Returns the path without the rightmost part, ignoring "." parts.
Definition: gpath.cpp:336
std::string str() const
Returns the path string.
Definition: gpath.h:215
bool empty() const noexcept
Returns true if size() is zero.
Definition: gpath.h:203
static bool isPositive(const std::string &)
Returns true if the string has a positive meaning, such as "1", "true", "yes".
Definition: gstr.cpp:1356
static string_view ws()
Returns a string of standard whitespace characters.
Definition: gstr.cpp:1255
static std::string tail(const std::string &in, std::size_t pos, const std::string &default_=std::string())
Returns the last part of the string after the given position.
Definition: gstr.cpp:1287
static std::string & trimLeft(std::string &s, string_view ws, std::size_t limit=0U)
Trims the lhs of s, taking off up to 'limit' of the 'ws' characters.
Definition: gstr.cpp:335
static void splitIntoTokens(const std::string &in, StringArray &out, string_view ws, char esc='\0')
Splits the string into 'ws'-delimited tokens.
Definition: gstr.cpp:1073
static std::string & trimRight(std::string &s, string_view ws, std::size_t limit=0U)
Trims the rhs of s, taking off up to 'limit' of the 'ws' characters.
Definition: gstr.cpp:347
static std::string printable(const std::string &in, char escape='\\')
Returns a printable representation of the given input string, using chacter code ranges 0x20 to 0x7e ...
Definition: gstr.cpp:885
static unsigned int toUInt(const std::string &s)
Converts string 's' to an unsigned int.
Definition: gstr.cpp:604
static bool isUInt(const std::string &s)
Returns true if the string can be converted into an unsigned integer without throwing an exception.
Definition: gstr.cpp:444
static std::string trimmed(const std::string &s, string_view ws)
Returns a trim()med version of s.
Definition: gstr.cpp:364
static std::string readLineFrom(std::istream &stream, const std::string &eol=std::string())
Reads a line from the stream using the given line terminator.
Definition: gstr.cpp:924
static std::size_t ifind(const std::string &s, const std::string &key, std::size_t pos=0U)
Returns the position of the key in 's' using a Latin-1 case-insensitive search.
Definition: gstr.cpp:1442
static SystemTime now()
Factory function for the current time.
Definition: gdatetime.cpp:260
BrokenDownTime local() const
Returns the locale-dependent local broken-down time.
Definition: gdatetime.cpp:286
Low-level classes.
Definition: galign.h:28
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:31
std::map< std::string, std::string > StringMap
A std::map of std::strings.
Definition: gstrings.h:32
Exception class for G::MapFile.
Definition: gmapfile.h:60