VideoTools
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
gmapfile.cpp
Go to the documentation of this file.
1 //
2 // Copyright (C) 2017 Graeme Walker
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 // gmapfile.cpp
19 //
20 
21 #include "gdef.h"
22 #include "gmapfile.h"
23 #include "gstr.h"
24 #include "gconvert.h"
25 #include "gpath.h"
26 #include "gprocess.h"
27 #include "gdatetime.h"
28 #include "gdate.h"
29 #include "gtime.h"
30 #include "gfile.h"
31 #include "glog.h"
32 #include "gassert.h"
33 #include <algorithm> // std::find
34 #include <iterator>
35 #include <stdexcept>
36 
38  m_logging(true)
39 {
40 }
41 
42 G::MapFile::MapFile( const G::Path & path , bool utf8 ) :
43  m_logging(true)
44 {
45  if( path != Path() )
46  readFrom( path , utf8 ) ;
47 }
48 
49 G::MapFile::MapFile( std::istream & stream , bool utf8 ) :
50  m_logging(true)
51 {
52  G_LOG( "MapFile::read: start" ) ;
53  readFrom( stream , utf8 ) ;
54 }
55 
57  m_logging(true) ,
58  m_map(map)
59 {
60  m_keys.reserve( m_map.size() ) ;
61  for( StringMap::iterator p = m_map.begin() ; p != m_map.end() ; ++p )
62  m_keys.push_back( (*p).first ) ;
63 }
64 
65 G::MapFile::MapFile( const OptionMap & map , const std::string & yes ) :
66  m_logging(true)
67 {
68  for( OptionMap::const_iterator p = map.begin() ; p != map.end() ; )
69  {
70  std::string key = (*p).first ;
71  if( map.contains(key) ) // ie. ignore "--foo=off"
72  {
73  std::string value = (*p).second.valued() ? map.value(key) : yes ;
74  log( key , value ) ;
75  add( key , value ) ;
76  }
77  while( p != map.end() && (*p).first == key ) // since we used OptionMap::value()
78  ++p ;
79  }
80 }
81 
82 void G::MapFile::readFrom( const G::Path & path , bool utf8 )
83 {
84  std::ifstream stream( path.str().c_str() ) ;
85  if( !stream.good() )
86  throw std::runtime_error( std::string() + "cannot open \"" + path.str() + "\"" ) ;
87  G_LOG( "MapFile::read: reading [" << path.str() << "]" ) ;
88  readFrom( stream , utf8 ) ;
89 }
90 
91 void G::MapFile::readFrom( std::istream & ss , bool utf8 )
92 {
93  std::string line ;
94  while( ss.good() )
95  {
96  Str::readLineFrom( ss , "\n" , line ) ;
97  if( line.empty() )
98  continue ;
99  if( !ss )
100  break ;
101 
102  std::string::size_type pos_interesting = line.find_first_not_of(" \t\r#") ;
103  if( pos_interesting == std::string::npos )
104  continue ;
105 
106  std::string::size_type pos_hash = line.find("#") ;
107  if( pos_hash != std::string::npos && pos_hash < pos_interesting )
108  continue ;
109 
110  StringArray part ;
111  Str::splitIntoTokens( line , part , " =\t" ) ;
112  if( part.size() == 0U )
113  continue ;
114 
115  std::string value = part.size() == 1U ? std::string() : line.substr(part[0].length()+1U) ;
116  value = Str::trimmed( value , Str::ws() ) ;
117  if( value.length() >= 2U && value.at(0U) == '"' && value.at(value.length()-1U) == '"' )
118  value = value.substr(1U,value.length()-2U) ;
119 
120  std::string key = part[0] ;
121  log( key , value ) ;
122 
123  value = fromUtf( value , utf8 ) ;
124 
125  add( key , value ) ;
126  }
127 }
128 
129 void G::MapFile::check( const G::Path & path , bool utf8 )
130 {
131  MapFile tmp ;
132  tmp.m_logging = false ;
133  tmp.readFrom( path , utf8 ) ;
134 }
135 
136 void G::MapFile::log() const
137 {
138  for( StringArray::const_iterator p = m_keys.begin() ; p != m_keys.end() ; ++p )
139  log( (*(m_map.find(*p))).first , (*(m_map.find(*p))).second ) ;
140 }
141 
142 void G::MapFile::log( const std::string & key , const std::string & value ) const
143 {
144  log( m_logging , key , value ) ;
145 }
146 
147 void G::MapFile::log( bool logging , const std::string & key , const std::string & value )
148 {
149  if( logging )
150  G_LOG( "MapFile::item: " << key << "=[" <<
151  ( key.find("password") == std::string::npos ?
152  Str::printable(value) :
153  std::string("<not-logged>")
154  ) << "]" ) ;
155 }
156 
157 void G::MapFile::writeItem( std::ostream & stream , const std::string & key , bool utf8 ) const
158 {
159  std::string value = m_map.find(key) == m_map.end() ? std::string() : (*m_map.find(key)).second ;
160  writeItem( stream , key , value , utf8 ) ;
161 }
162 
163 void G::MapFile::writeItem( std::ostream & stream , const std::string & key , const std::string & value , bool utf8 )
164 {
165  log( true , key , value ) ;
166  const char * qq = value.find(' ') == std::string::npos ? "" : "\"" ;
167  stream << key << "=" << qq << toUtf(value,utf8) << qq << "\n" ;
168 }
169 
170 std::string G::MapFile::quote( const std::string & s )
171 {
172  return s.find_first_of(" \t") == std::string::npos ? s : (std::string()+"\""+s+"\"") ;
173 }
174 
175 void G::MapFile::editInto( const G::Path & path , bool make_backup , bool allow_read_error , bool allow_write_error , bool utf8 ) const
176 {
177  typedef std::list<std::string> List ;
178  List lines = read( path , allow_read_error ) ;
179  commentOut( lines ) ;
180  replace( lines , utf8 ) ;
181  if( make_backup ) backup( path ) ;
182  save( path , lines , allow_write_error ) ;
183 }
184 
185 G::MapFile::List G::MapFile::read( const G::Path & path , bool allow_read_error ) const
186 {
187  List line_list ;
188  std::ifstream file_in( path.str().c_str() ) ;
189  if( !file_in.good() && !allow_read_error )
190  throw std::runtime_error( std::string() + "cannot read \"" + path.str() + "\"" ) ;
191  while( file_in.good() )
192  {
193  std::string line = Str::readLineFrom( file_in , "\n" ) ;
194  if( !file_in ) break ;
195  line_list.push_back( line ) ;
196  }
197  return line_list ;
198 }
199 
200 void G::MapFile::commentOut( List & line_list ) const
201 {
202  for( List::iterator line_p = line_list.begin() ; line_p != line_list.end() ; ++line_p )
203  {
204  std::string & line = *line_p ;
205  if( line.empty() || line.at(0U) == '#' )
206  continue ;
207  line = std::string(1U,'#') + line ;
208  }
209 }
210 
211 void G::MapFile::replace( List & line_list , bool utf8 ) const
212 {
213  for( StringMap::const_iterator map_p = m_map.begin() ; map_p != m_map.end() ; ++map_p )
214  {
215  bool found = false ;
216  for( List::iterator line_p = line_list.begin() ; line_p != line_list.end() ; ++line_p )
217  {
218  std::string & line = (*line_p) ;
219  if( line.empty() ) continue ;
220  StringArray part ;
221  Str::splitIntoTokens( line , part , Str::ws()+"=#" ) ;
222  if( part.size() == 0U ) continue ;
223  if( part.at(0U) == (*map_p).first )
224  {
225  std::string value = toUtf( (*map_p).second , utf8 ) ;
226  line = Str::trimmed( (*map_p).first + " " + quote(value) , Str::ws() ) ;
227  found = true ;
228  break ;
229  }
230  }
231 
232  if( !found )
233  {
234  std::string value = toUtf( (*map_p).second , utf8 ) ;
235  line_list.push_back( Str::trimmed( (*map_p).first + " " + quote(value) , Str::ws() ) ) ;
236  }
237  }
238 }
239 
240 void G::MapFile::backup( const G::Path & path )
241 {
242  // ignore errors
243  DateTime::BrokenDownTime now = DateTime::local( DateTime::now() ) ;
244  std::string timestamp = Date(now).string(Date::yyyy_mm_dd) + Time(now).hhmmss() ;
245  Path backup( path.dirname() , path.basename() + "." + timestamp ) ;
246  Process::Umask umask( Process::Umask::Tightest ) ;
247  File::copy( path , backup , File::NoThrow() ) ;
248 }
249 
250 void G::MapFile::save( const G::Path & path , List & line_list , bool allow_write_error )
251 {
252  std::ofstream file_out( path.str().c_str() , std::ios_base::out | std::ios_base::trunc ) ;
253  std::copy( line_list.begin() , line_list.end() , std::ostream_iterator<std::string>(file_out,"\n") ) ;
254  file_out.close() ;
255  if( file_out.fail() && !allow_write_error )
256  throw std::runtime_error( std::string() + "cannot write \"" + path.str() + "\"" ) ;
257 }
258 
259 std::string G::MapFile::value( const std::string & key , const std::string & default_ ) const
260 {
261  StringMap::const_iterator p = m_map.find( key ) ;
262  std::string result = ( p == m_map.end() || (*p).second.empty() ) ? default_ : (*p).second ;
263  return result ;
264 }
265 
266 std::string G::MapFile::value( const std::string & key , const char * default_ ) const
267 {
268  return value( key , std::string(default_) ) ;
269 }
270 
271 std::string G::MapFile::mandatoryValue( const std::string & key ) const
272 {
273  if( m_map.find(key) == m_map.end() )
274  throw std::runtime_error( std::string() + "cannot find config item \"" + key + "\"" ) ;
275  return value( key ) ;
276 }
277 
278 G::Path G::MapFile::pathValue( const std::string & key ) const
279 {
280  return Path( mandatoryValue(key) ) ;
281 }
282 
283 G::Path G::MapFile::expandedPathValue( const std::string & key ) const
284 {
285  return Path( expand(mandatoryValue(key)) ) ;
286 }
287 
288 G::Path G::MapFile::pathValue( const std::string & key , const G::Path & default_ ) const
289 {
290  return Path( value(key,default_.str()) ) ;
291 }
292 
293 G::Path G::MapFile::expandedPathValue( const std::string & key , const G::Path & default_ ) const
294 {
295  return Path( expand(value(key,default_.str())) ) ;
296 }
297 
298 unsigned int G::MapFile::numericValue( const std::string & key , unsigned int default_ ) const
299 {
300  std::string s = value( key , std::string() ) ;
301  return !s.empty() && Str::isUInt(s) ? Str::toUInt( s ) : default_ ;
302 }
303 
304 bool G::MapFile::booleanValue( const std::string & key , bool default_ ) const
305 {
306  std::string s = value( key , default_ ? "Y" : "N" ) ;
307  return !s.empty() && ( s.at(0U) == 'y' || s.at(0U) == 'Y' ) ;
308 }
309 
310 void G::MapFile::remove( const std::string & key )
311 {
312  StringMap::iterator p = m_map.find( key ) ;
313  if( p != m_map.end() )
314  {
315  m_map.erase( p ) ;
316  G_ASSERT( std::find(m_keys.begin(),m_keys.end(),key) != m_keys.end() ) ;
317  m_keys.erase( std::find(m_keys.begin(),m_keys.end(),key) ) ;
318  }
319 }
320 
321 std::string G::MapFile::expand( const std::string & value_in ) const
322 {
323  std::string value( value_in ) ;
324  expand_( value ) ;
325  return value ;
326 }
327 
328 namespace
329 {
330  size_t find_single( std::string & s , char c , size_t start_pos )
331  {
332  char cc[] = { c , '\0' } ;
333  size_t pos = start_pos ;
334  for(;;)
335  {
336  pos = s.find( cc , pos ) ;
337  if( pos == std::string::npos )
338  {
339  break ; // not found
340  }
341  else if( (pos+1U) < s.length() && s.at(pos+1U) == c )
342  {
343  s.erase( pos , 1U ) ;
344  if( (pos+1U) == s.length() )
345  {
346  pos = std::string::npos ;
347  break ;
348  }
349  pos++ ;
350  }
351  else
352  {
353  break ; // found
354  }
355  }
356  return pos ;
357  }
358 }
359 
360 bool G::MapFile::expand_( std::string & value ) const
361 {
362  bool changed = false ;
363  size_t start = 0U ;
364  size_t end = 0U ;
365  size_t const npos = std::string::npos ;
366  while( end < value.length() )
367  {
368  start = find_single( value , '%' , end ) ;
369  if( start == npos ) break ;
370  end = value.find( "%" , start+1U ) ;
371  if( end == npos ) break ;
372  end++ ;
373  std::string key = value.substr( start+1U , end-start-2U ) ;
374  StringMap::const_iterator p = m_map.find( key ) ;
375  if( p != m_map.end() )
376  {
377  size_t old = end - start ;
378  size_t new_ = (*p).second.length() ;
379  value.replace( start , old , (*p).second ) ;
380  end += new_ ;
381  end -= old ;
382  changed = true ;
383  }
384  }
385  return changed ;
386 }
387 
388 void G::MapFile::add( const std::string & key , const std::string & value )
389 {
390  if( m_map.find(key) == m_map.end() )
391  m_keys.push_back( key ) ;
392  m_map[key] = value ;
393 }
394 
395 bool G::MapFile::contains( const std::string & key ) const
396 {
397  return m_map.find( key ) != m_map.end() ;
398 }
399 
401 {
402  return m_map ;
403 }
404 
406 {
407  return m_keys ;
408 }
409 
410 std::string G::MapFile::fromUtf( const std::string & value , bool utf8 )
411 {
412  if( G::is_windows() && utf8 )
413  {
414  std::string result ;
415  Convert::convert( result , Convert::utf8(value) , Convert::ThrowOnError() ) ;
416  return result ;
417  }
418  else
419  {
420  return value ;
421  }
422 }
423 
424 std::string G::MapFile::toUtf( const std::string & value , bool utf8 )
425 {
426  if( G::is_windows() && utf8 )
427  {
428  Convert::utf8 result ;
429  Convert::convert( result , value ) ;
430  return result.s ;
431  }
432  else
433  {
434  return value ;
435  }
436 }
437 
438 /// \file gmapfile.cpp
void editInto(const G::Path &path, bool make_backup=true, bool allow_read_error=false, bool allow_write_error=false, bool utf8=false) const
Edits an existing file so that its contents reflect this map.
Definition: gmapfile.cpp:175
std::string str() const
Returns the path string.
Definition: gpath.cpp:290
static std::string printable(const std::string &in, char escape= '\\')
Returns a printable represention of the given input string.
Definition: gstr.cpp:663
static void convert(utf8 &utf_out, const std::string &in_)
Converts between string types/encodings: ansi to utf8.
Definition: gconvert.cpp:44
static bool copy(const Path &from, const Path &to, const NoThrow &)
Copies a file. Returns false on error.
Definition: gfile.cpp:70
std::string value(const std::string &key, const std::string &default_=std::string()) const
Returns a string value from the map.
Definition: gmapfile.cpp:259
std::string basename() const
Returns the rightmost part of the path, ignoring "." parts.
Definition: gpath.cpp:310
const G::StringArray & keys() const
Returns a reference to the ordered list of keys.
Definition: gmapfile.cpp:405
static BrokenDownTime local(EpochTime epoch_time)
Converts from epoch time to local broken-down-time.
Definition: gdatetime.cpp:131
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:33
static void splitIntoTokens(const std::string &in, StringArray &out, const std::string &ws)
Splits the string into 'ws'-delimited tokens.
Definition: gstr.cpp:868
bool contains(const std::string &) const
Returns true if the map contains the given key, but ignoring un-valued() 'off' options.
Definition: goptionmap.h:122
const_iterator begin() const
Returns the begin iterator.
Definition: goptionmap.h:98
std::string value(const std::string &key) const
Returns the value of the valued() option identified by the given key.
Definition: goptionmap.h:144
Holds context information which convert() adds to the exception when it fails.
Definition: gconvert.h:70
void remove(const std::string &key)
Removes a value (if it exists).
Definition: gmapfile.cpp:310
static EpochTime now()
Returns the current epoch time.
A map-like container for command-line options and their values.
Definition: goptionmap.h:38
G::Path pathValue(const std::string &key) const
Returns a mandatory path value from the map. Throws if it does not exist.
Definition: gmapfile.cpp:278
bool contains(const std::string &key) const
Returns true if the map contains the given key.
Definition: gmapfile.cpp:395
const G::StringMap & map() const
Returns a reference to the internal map.
Definition: gmapfile.cpp:400
static unsigned int toUInt(const std::string &s)
Converts string 's' to an unsigned int.
Definition: gstr.cpp:450
void writeItem(std::ostream &, const std::string &key, bool utf8=false) const
Writes a single item from this map to the stream.
Definition: gmapfile.cpp:157
G::Path expandedPathValue(const std::string &key) const
Returns a mandatory path value from the map with expand(). Throws if it does not exist.
Definition: gmapfile.cpp:283
A class for reading and editing key=value files, supporting the creation of backup files...
Definition: gmapfile.h:43
unsigned int numericValue(const std::string &key, unsigned int default_) const
Returns a numeric value from the map.
Definition: gmapfile.cpp:298
void add(const std::string &key, const std::string &value)
Adds or updates a single item in the map.
Definition: gmapfile.cpp:388
A string wrapper that indicates UTF-8 encoding.
Definition: gconvert.h:63
std::string expand(const std::string &value) const
Does one-pass variable substitution for the given string.
Definition: gmapfile.cpp:321
const_iterator end() const
Returns the off-the-end iterator.
Definition: goptionmap.h:104
static std::string trimmed(const std::string &s, const std::string &ws)
Returns a trim()med version of s.
Definition: gstr.cpp:213
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:695
Path dirname() const
Returns the path without the rightmost part, ignoring "." parts.
Definition: gpath.cpp:318
MapFile()
Constructor for an empty map.
Definition: gmapfile.cpp:37
bool booleanValue(const std::string &key, bool default_) const
Returns a boolean value from the map.
Definition: gmapfile.cpp:304
std::map< std::string, std::string > StringMap
A std::map of std::strings.
Definition: gstrings.h:34
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:266
A Path object represents a file system path.
Definition: gpath.h:72
static std::string ws()
A convenience function returning standard whitespace characters.
Definition: gstr.cpp:1027
static void check(const G::Path &, bool utf8=false)
Throws if the file is invalid.
Definition: gmapfile.cpp:129
void log() const
Logs the contents.
Definition: gmapfile.cpp:136