initial version
This commit is contained in:
202
mrw/configfile.cpp
Normal file
202
mrw/configfile.cpp
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
/** @file
|
||||||
|
|
||||||
|
$Id$
|
||||||
|
|
||||||
|
$Date$
|
||||||
|
$Author$
|
||||||
|
|
||||||
|
@copy © Marc Wäckerlin
|
||||||
|
@license LGPL, see file <a href="license.html">COPYING</a>
|
||||||
|
|
||||||
|
$Log$
|
||||||
|
Revision 1.1 2005/01/07 00:31:38 marc
|
||||||
|
initial version
|
||||||
|
|
||||||
|
|
||||||
|
1 2 3 4 5 6 7 8
|
||||||
|
5678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <mrw/configfile.hpp>
|
||||||
|
#include <mrw/exception.hpp>
|
||||||
|
#include <mrw/string.hpp>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
mrw::ConfigFileReader::Value&
|
||||||
|
mrw::ConfigFileReader::operator()(const std::string& section,
|
||||||
|
const std::string& name,
|
||||||
|
const std::string& default_)
|
||||||
|
throw(std::bad_exception) {
|
||||||
|
_values[section].insert(std::make_pair(name, Value(default_)));
|
||||||
|
return _values[section].find(name)->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
mrw::ConfigFileReader& mrw::ConfigFileReader::load(const std::string& filename)
|
||||||
|
throw(std::exception) {
|
||||||
|
_filename = filename;
|
||||||
|
return reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
mrw::ConfigFileReader& mrw::ConfigFileReader::reload() throw(std::exception) {
|
||||||
|
return parse(readFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string mrw::ConfigFileReader::readFile() const throw(std::exception) {
|
||||||
|
std::string fileContents; // declaration as first allows optimization
|
||||||
|
std::ifstream file(_filename.c_str());
|
||||||
|
if (!file)
|
||||||
|
throw mrw::invalid_argument("Error file not found: '"+_filename+"'");
|
||||||
|
file.seekg(0, std::ios::end);
|
||||||
|
int size(file.tellg());
|
||||||
|
fileContents.resize(size); // make buffer long enough to store all
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
file.read(fileContents.begin().operator->(), size); // hack to get the buffer
|
||||||
|
if (!file.good() && file.eof())
|
||||||
|
throw mrw::invalid_argument("Error reading file: '"+_filename+"'");
|
||||||
|
return fileContents;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
using namespace std;
|
||||||
|
mrw::ConfigFileReader& mrw::ConfigFileReader::parse(const std::string& file)
|
||||||
|
throw(std::exception) {
|
||||||
|
_values.erase(_values.begin(), _values.end());
|
||||||
|
std::string section;
|
||||||
|
for (std::string::size_type pos(0);
|
||||||
|
(pos=file.find_first_of("[=#", pos))!=std::string::npos; ++pos) {
|
||||||
|
switch (file[pos]) {
|
||||||
|
case '[': _sections[section]=pos; section=parseSection(file, pos); break;
|
||||||
|
case '=': _values[section].insert(parseValue(file, pos)); break;
|
||||||
|
case '#': parseComment(file, pos); break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_sections[section]=file.size();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string mrw::ConfigFileReader::parseSection(const std::string& file,
|
||||||
|
std::string::size_type& pos)
|
||||||
|
const throw(std::exception) {
|
||||||
|
std::string::size_type start(pos);
|
||||||
|
if ((pos=file.find(']', pos))==std::string::npos)
|
||||||
|
throw mrw::invalid_argument
|
||||||
|
("section not terminated in file '"+_filename+"'\n"
|
||||||
|
"environment is: "+file.substr(start-10>0?start-10:0));
|
||||||
|
return file.substr(start+1, pos-start-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
mrw::ConfigFileReader::Values::mapped_type::value_type
|
||||||
|
mrw::ConfigFileReader::parseValue(const std::string& file,
|
||||||
|
std::string::size_type& pos)
|
||||||
|
const throw(std::exception) {
|
||||||
|
// find the begin of the variable name
|
||||||
|
std::string::size_type startName(file.rfind('\n', pos));
|
||||||
|
if (startName==std::string::npos) startName=0;
|
||||||
|
startName=file.find_first_not_of("\n\t ", startName);
|
||||||
|
if (startName==pos)
|
||||||
|
throw mrw::invalid_argument
|
||||||
|
("empty variable name in file '"+_filename+"'\n"
|
||||||
|
"environment is: "+file.substr(startName-10>0?startName-10:0, pos+10));
|
||||||
|
// find the end of the variable name
|
||||||
|
std::string::size_type endName(file.find_last_not_of("\n\t ", pos-1)+1);
|
||||||
|
// find the begin of the variable contents
|
||||||
|
std::string::size_type startVal(file.find_first_not_of("\n\t ", ++pos));
|
||||||
|
if (startVal==std::string::npos)
|
||||||
|
return std::make_pair // end of file, empty value
|
||||||
|
(file.substr(startName, endName-startName), Value("", pos, pos));
|
||||||
|
// find the end of the variable contents
|
||||||
|
std::string::size_type realStart(startVal); // doesn't cut the first quote
|
||||||
|
std::string::size_type endVal(file[startVal]=='"' ? // quoted with "
|
||||||
|
file.find('"', ++startVal) :
|
||||||
|
file[startVal]=='\'' ? // quoted with '
|
||||||
|
file.find('\'', ++startVal) :
|
||||||
|
file.find_first_of("[=#", startVal)); // normal
|
||||||
|
if (endVal!=std::string::npos && file[endVal]=='=')
|
||||||
|
endVal=file.rfind('\n', endVal); // jump before following variable name
|
||||||
|
endVal = file.find_last_not_of("\n\t ", // remove trailing white spaces
|
||||||
|
endVal!=std::string::npos?endVal-1:endVal)+1;
|
||||||
|
std::string::size_type realEnd(endVal!=std::string::npos && // quotes?
|
||||||
|
(file[realStart]=='"' && file[endVal]=='"' ||
|
||||||
|
file[realStart]=='\'' && file[endVal]=='\'')
|
||||||
|
? endVal+1 : endVal);
|
||||||
|
if (endVal<=startVal)
|
||||||
|
return std::make_pair // empty value
|
||||||
|
(file.substr(startName, endName-startName), Value("", pos, pos));
|
||||||
|
return std::make_pair // normal case
|
||||||
|
(file.substr(startName, endName-startName),
|
||||||
|
Value(file.substr(startVal, endVal-startVal), realStart, pos=realEnd));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string mrw::ConfigFileReader::parseComment(const std::string& file,
|
||||||
|
std::string::size_type& pos)
|
||||||
|
const throw(std::bad_exception) {
|
||||||
|
std::string::size_type start(pos);
|
||||||
|
pos = file.find('\n', pos);
|
||||||
|
return file.substr(start, pos-start);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GNU g++ prior to 3.4 does not implement covariant returns
|
||||||
|
#if __GNUC__ == 3 && __GNUC_MINOR__ < 4
|
||||||
|
mrw::ConfigFileReader&
|
||||||
|
#else
|
||||||
|
mrw::ConfigFileWriter&
|
||||||
|
#endif
|
||||||
|
mrw::ConfigFileWriter::reload() throw(std::exception) {
|
||||||
|
return parse(_file=readFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
mrw::ConfigFileWriter& mrw::ConfigFileWriter::save() throw(std::exception) {
|
||||||
|
if (_filename=="") return *this;
|
||||||
|
// work in changes
|
||||||
|
bool changed(false);
|
||||||
|
for (Values::iterator sec(_values.begin()); sec!=_values.end(); ++sec)
|
||||||
|
for (Values::mapped_type::iterator var(sec->second.begin());
|
||||||
|
var!=sec->second.end(); ++var)
|
||||||
|
if (var->second.changed) {
|
||||||
|
var->second.changed = false;
|
||||||
|
changed = true;
|
||||||
|
// check if we need quoting
|
||||||
|
std::string value(var->second.value);
|
||||||
|
if (value.find_first_of("[=#")!=std::string::npos)
|
||||||
|
if (value.find('"')==std::string::npos)
|
||||||
|
value = '"'+value+'"';
|
||||||
|
else if (value.find('\'')==std::string::npos)
|
||||||
|
value = '\''+value+'\'';
|
||||||
|
else
|
||||||
|
throw mrw::invalid_argument("Configuration value is not quotable: "
|
||||||
|
+value);
|
||||||
|
// recalculate all positions
|
||||||
|
if (var->second.start &&
|
||||||
|
value.size() != var->second.end-var->second.start) {
|
||||||
|
int diff(value.size()-var->second.end+var->second.start);
|
||||||
|
for (Values::iterator sec2(_values.begin());
|
||||||
|
sec2!=_values.end(); ++sec2)
|
||||||
|
for (Values::mapped_type::iterator var2(sec2->second.begin());
|
||||||
|
var2!=sec2->second.end(); ++var2)
|
||||||
|
if (var2->second.start>var->second.end) {
|
||||||
|
var2->second.start += diff;
|
||||||
|
var2->second.end += diff;
|
||||||
|
}
|
||||||
|
for (Sections::iterator sec2(_sections.begin());
|
||||||
|
sec2!=_sections.end(); ++sec2)
|
||||||
|
if (sec2->second>=var->second.end) sec2->second += diff;
|
||||||
|
}
|
||||||
|
// set the new value
|
||||||
|
if (var->second.start) // simple change
|
||||||
|
_file.replace(var->second.start, var->second.end-var->second.start,
|
||||||
|
value);
|
||||||
|
else if (_sections.find(sec->first)!=_sections.end()) // new value
|
||||||
|
_file.insert(_sections[sec->first], "\n"+var->first+" = "+value
|
||||||
|
+(_file[_sections[sec->first]]=='\n'?"":"\n"));
|
||||||
|
else { // even a new section
|
||||||
|
_file+="\n["+sec->first+"]\n"+var->first+" = "+value;
|
||||||
|
_sections[sec->first] = _file.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!changed) return *this;
|
||||||
|
// save to file
|
||||||
|
std::ofstream file(_filename.c_str());
|
||||||
|
if (!(file<<_file))
|
||||||
|
throw mrw::invalid_argument("Cannot write file '"+_filename+"'");
|
||||||
|
return *this;
|
||||||
|
}
|
362
mrw/configfile.hpp
Normal file
362
mrw/configfile.hpp
Normal file
@@ -0,0 +1,362 @@
|
|||||||
|
/** @file
|
||||||
|
|
||||||
|
$Id$
|
||||||
|
|
||||||
|
$Date$
|
||||||
|
$Author$
|
||||||
|
|
||||||
|
@copy © Marc Wäckerlin
|
||||||
|
@license LGPL, see file <a href="license.html">COPYING</a>
|
||||||
|
|
||||||
|
$Log$
|
||||||
|
Revision 1.1 2005/01/07 00:31:38 marc
|
||||||
|
initial version
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
#ifndef __MRW_CONFIGFILE_HPP__
|
||||||
|
#define __MRW_CONFIGFILE_HPP__
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
namespace mrw {
|
||||||
|
|
||||||
|
/** @defgroup config Configuration File Handler
|
||||||
|
|
||||||
|
Read configuration parameters from a file.
|
||||||
|
|
||||||
|
@section configSyntax Configuration File Syntax
|
||||||
|
|
||||||
|
@subsection configSyntaxSection Sections: [Section]
|
||||||
|
|
||||||
|
Syntax (regular expression):
|
||||||
|
<code>\\[([^[=#]*)\\]</code>
|
||||||
|
|
||||||
|
A configuration file consists of none or more sections. The
|
||||||
|
sections start with tag <code>[Section Name]</code>, where
|
||||||
|
<code>Section Name</code> is the name of the section.
|
||||||
|
The default section has an empty name.
|
||||||
|
|
||||||
|
@subsection configSyntaxVariables Variables: name = value
|
||||||
|
|
||||||
|
Syntax (regular expression):
|
||||||
|
<code>^[^[\\n\\t ]*([^[=#\\n]]+)[\\n\\t ]*=[\\n\\t ]*([^[=#]]*)[\\n\\t ]*</code>
|
||||||
|
or
|
||||||
|
<code>^[^[\\n\\t ]*([^[=#\\n]]+)[\\n\\t ]*=[\\n\\t ]*("[^"]]*")[\\n\\t ]*</code>
|
||||||
|
or
|
||||||
|
<code>^[^[\\n\\t ]*([^[=#\\n]]+)[\\n\\t ]*=[\\n\\t ]*('[^']]*')[\\n\\t ]*</code>
|
||||||
|
|
||||||
|
In the section, there are variable definitions in the form of
|
||||||
|
<code>name=value</code>, where <code>name</code> is the name of
|
||||||
|
the variable and <code>value</code> is it's contents. White
|
||||||
|
spaces at the begin or end of the name and the value are
|
||||||
|
stripped. Whitespaces inside a name or value remain
|
||||||
|
unchanged. Please note that a variable name starts at the begoin
|
||||||
|
of line and ends before the equal sign, while the contents
|
||||||
|
starts after the equal sign and ends before the next token
|
||||||
|
starts. If you need a token inside a value, or a variable must
|
||||||
|
start or end with white spaces, you can enclose it in either
|
||||||
|
<code>"</code> or <code>'</code> quotes. If a value is quoted
|
||||||
|
and the quoting is not terminated, then the contents is taken up
|
||||||
|
to the end of file. Please note that the same quote must not
|
||||||
|
occur inside the value. All possible tokens are: <code>[</code>,
|
||||||
|
<code>#</code> and <code>=</code>. All other characters are not
|
||||||
|
treated in a special way.
|
||||||
|
|
||||||
|
@subsection configSyntaxComments Comments: #
|
||||||
|
|
||||||
|
Comments start with <code>#</code> and end at the end of the
|
||||||
|
line.
|
||||||
|
|
||||||
|
@subsection configSyntaxSpecial Special Characters \\n, [, ], #, =, ", '
|
||||||
|
|
||||||
|
I have chosen a syntax that is downwards compatible with the
|
||||||
|
common Unix configuration file syntax, but with a grammar as
|
||||||
|
loose as possible and with as few restrictions as
|
||||||
|
possible. Special characters have only special meanings in
|
||||||
|
certain contexts. In other contexts, they can be used with no
|
||||||
|
restrictions.
|
||||||
|
|
||||||
|
The following characters are treated in a special way:
|
||||||
|
|
||||||
|
- <code>[</code> and <code>]</code> enclose a section name. A
|
||||||
|
section name may contain any arbitrary character, including new
|
||||||
|
line and white spaces, except a <code>]</code>.
|
||||||
|
|
||||||
|
- <code>\\n</code> (begin of line or file) and <code>=</code>
|
||||||
|
enclose a variable name. In other circumstances (except in
|
||||||
|
comments), a new line has no special meaning. A <code>=</code>
|
||||||
|
inside a value is allowed only if the value is quoted. A
|
||||||
|
variable name may contain any arbitrary character, including
|
||||||
|
white spaces, except a new line or <code>=</code>.
|
||||||
|
|
||||||
|
- <code>=</code> and one out of <code>[</code>, <code>#</code>
|
||||||
|
and the begin of a line that contains <code>=</code> enclose a
|
||||||
|
variable contents if the contents is not quoted. A variable's
|
||||||
|
contents may contain any arbitrary character, including new line
|
||||||
|
and white spaces, except a <code>]</code>, <code>#</code> or the
|
||||||
|
begin of a line that contains <code>=</code>.
|
||||||
|
|
||||||
|
- <code>"</code> or <code>'</code> may enclose a variables
|
||||||
|
contents. These characters have only a special meaning, if a
|
||||||
|
variable's contents starts with one of them. Then the variable's
|
||||||
|
contents may contain any arbitrary character, except the same
|
||||||
|
quote itself, which marks the end of the contents.
|
||||||
|
|
||||||
|
- <code>#</code> and <code>\\n</code> (end of line or file)
|
||||||
|
enclose a comment. A variable name may contain any arbitrary
|
||||||
|
character, including white spaces, except a new line.
|
||||||
|
|
||||||
|
@subsection configSyntaxRestrictions Restrictions
|
||||||
|
|
||||||
|
@attention Due to the actual syntax, it is impossible that a
|
||||||
|
variable's contents contains the following combination:
|
||||||
|
<code>"</code> @b and <code>'</code> @b and one out of
|
||||||
|
<code>[</code>, <code>=</code> and <code>#</code>
|
||||||
|
|
||||||
|
@attention If a variable's contents contains heading or trailing
|
||||||
|
white spaces or one out of <code>[</code>, <code>=</code> and
|
||||||
|
<code>#</code>, then it must be quoted either with
|
||||||
|
<code>"</code> or with <code>'</code>.
|
||||||
|
|
||||||
|
@section configFile Example File
|
||||||
|
|
||||||
|
@subsection configFileSamp1 Nice Example
|
||||||
|
|
||||||
|
@verbatim
|
||||||
|
[Section 1]
|
||||||
|
name1 = value1
|
||||||
|
name2 = value2
|
||||||
|
[Section 2]
|
||||||
|
name1 = value1
|
||||||
|
name2 = value2
|
||||||
|
@endverbatim
|
||||||
|
|
||||||
|
@subsection configFileSamp2 Enhanced Example
|
||||||
|
|
||||||
|
@verbatim
|
||||||
|
# this is in the global section, named ""
|
||||||
|
this is a variable name = this is a variable contents
|
||||||
|
|
||||||
|
[Global] # start of section "Global"
|
||||||
|
|
||||||
|
multi line text = this is a text
|
||||||
|
that is longer than one
|
||||||
|
line!
|
||||||
|
|
||||||
|
1234 = "var=17" # if you need an equal inside a text, enclose it
|
||||||
|
# either in " or in '
|
||||||
|
@endverbatim
|
||||||
|
|
||||||
|
@section configFileCode Code Sample
|
||||||
|
|
||||||
|
@code
|
||||||
|
try {
|
||||||
|
const char* home(getenv("HOME"));
|
||||||
|
mrw::ConfigFileReader config(home?home+"/.myprogram":".myprogram");
|
||||||
|
std::string userName(config("User Info", "Name"));
|
||||||
|
} catch (...) { // file read or evaluation error
|
||||||
|
}
|
||||||
|
@endcode
|
||||||
|
*/
|
||||||
|
//@{
|
||||||
|
|
||||||
|
/// Parse configuration file and access the contents.
|
||||||
|
/** Parse a configuration file that consists of sections containing
|
||||||
|
variables with values. This class does not store anything in the
|
||||||
|
file, even if variables have changed.
|
||||||
|
@see If you want %to save changes %to the file, use
|
||||||
|
@ref ConfigFileWriter.
|
||||||
|
@see For details, see @ref configSyntax. */
|
||||||
|
class ConfigFileReader {
|
||||||
|
public:
|
||||||
|
/// A configuration file value.
|
||||||
|
class Value {
|
||||||
|
public:
|
||||||
|
/// Get the value string.
|
||||||
|
/** Get the value string. Trailing and leading white spaces have
|
||||||
|
been truncated. If the variable was quoted, quotes have been
|
||||||
|
removed.
|
||||||
|
@return the variable's value as string */
|
||||||
|
operator const std::string&() const throw() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
/// Get the value string.
|
||||||
|
/** Alternative access to the variable's contents. Trailing and
|
||||||
|
leading white spaces have been truncated. If the variable
|
||||||
|
was quoted, quotes have been removed.
|
||||||
|
@return the variable's value as string */
|
||||||
|
const std::string& operator()() const throw() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
/// Assign a new value.
|
||||||
|
/** Assign a new value. The new value is lost if you use the
|
||||||
|
ConfigFileReader, but it is stored in the destructor, if you
|
||||||
|
use the ConfigFileWriter instead. */
|
||||||
|
Value& operator=(const std::string& newValue) throw() {
|
||||||
|
changed = true;
|
||||||
|
value = newValue;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
/// Compare the value to a string.
|
||||||
|
bool operator==(const std::string& o) const throw() {
|
||||||
|
return o==value;
|
||||||
|
}
|
||||||
|
/// Compare a string to the value.
|
||||||
|
friend bool operator==(const std::string& o, const Value& v) throw() {
|
||||||
|
return v==o;
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
friend class ConfigFileReader;
|
||||||
|
friend class ConfigFileWriter;
|
||||||
|
Value(const std::string& v,
|
||||||
|
std::string::size_type s, std::string::size_type e) throw():
|
||||||
|
value(v), changed(false), start(s), end(e) {
|
||||||
|
}
|
||||||
|
Value(const std::string& v) throw():
|
||||||
|
value(v), changed(true), start(0), end(0) {
|
||||||
|
}
|
||||||
|
std::string value;
|
||||||
|
bool changed;
|
||||||
|
std::string::size_type start;
|
||||||
|
std::string::size_type end;
|
||||||
|
private:
|
||||||
|
Value(); // not implemented, must be initialized
|
||||||
|
Value& operator=(const Value&); // not implemented, no assignment
|
||||||
|
};
|
||||||
|
/// Uninitialized construction.
|
||||||
|
/** Uninitialized construction. Use this constructor, if you want
|
||||||
|
to call @ref load() later. */
|
||||||
|
ConfigFileReader() throw() {}
|
||||||
|
/// Parse a file at construction.
|
||||||
|
/** This should be the normal case: load and parse the file at
|
||||||
|
construction.
|
||||||
|
@param filename the name of the file to read
|
||||||
|
@throw mrw::invalid_argument
|
||||||
|
- if the file does not exist
|
||||||
|
- if reading the file fails
|
||||||
|
- if there is an unterminated section name in the file
|
||||||
|
- if there is an empty variable name in the file */
|
||||||
|
ConfigFileReader(const std::string& filename) throw(std::exception):
|
||||||
|
_filename(filename) {
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
/// Copy construct from other ConfigFileReader.
|
||||||
|
ConfigFileReader(const ConfigFileReader& o) throw():
|
||||||
|
_filename(o._filename), _values(o._values) {
|
||||||
|
}
|
||||||
|
virtual ~ConfigFileReader() throw() {}
|
||||||
|
/// Copy from other ConfigFileReader.
|
||||||
|
ConfigFileReader& operator=(const ConfigFileReader& o) {
|
||||||
|
_filename = o._filename;
|
||||||
|
_values = o._values;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
/// Access a value read from the file.
|
||||||
|
/** Access a value read from the file.
|
||||||
|
@param section the section in the configuration file
|
||||||
|
@param name the variable name
|
||||||
|
@param default_ the default value that is set, if the variable is
|
||||||
|
not configured
|
||||||
|
@return the Value you are looking for */
|
||||||
|
Value& operator()(const std::string& section, const std::string& name,
|
||||||
|
const std::string& default_)
|
||||||
|
throw(std::bad_exception);
|
||||||
|
/// Load and parse a new file.
|
||||||
|
/** Load and parse a new file.
|
||||||
|
@throw mrw::invalid_argument
|
||||||
|
- if the file does not exist
|
||||||
|
- if reading the file fails
|
||||||
|
- if there is an unterminated section name in the file
|
||||||
|
- if there is an empty variable name in the file */
|
||||||
|
ConfigFileReader& load(const std::string& filename) throw(std::exception);
|
||||||
|
/// Reload the last file again.
|
||||||
|
/** Reload the last file again.
|
||||||
|
@throw mrw::invalid_argument
|
||||||
|
- if the file does not exist
|
||||||
|
- if reading the file fails
|
||||||
|
- if there is an unterminated section name in the file
|
||||||
|
- if there is an empty variable name in the file */
|
||||||
|
virtual ConfigFileReader& reload() throw(std::exception);
|
||||||
|
protected:
|
||||||
|
typedef std::map< std::string, std::map<std::string, Value> > Values;
|
||||||
|
typedef std::map<std::string, std::string::size_type> Sections;
|
||||||
|
std::string readFile() const throw(std::exception);
|
||||||
|
ConfigFileReader& parse(const std::string& file) throw(std::exception);
|
||||||
|
std::string parseSection(const std::string& file,
|
||||||
|
std::string::size_type& pos)
|
||||||
|
const throw(std::exception);
|
||||||
|
Values::mapped_type::value_type parseValue(const std::string& file,
|
||||||
|
std::string::size_type& pos)
|
||||||
|
const throw(std::exception);
|
||||||
|
std::string parseComment(const std::string& file,
|
||||||
|
std::string::size_type& pos)
|
||||||
|
const throw(std::bad_exception);
|
||||||
|
std::string _filename;
|
||||||
|
Values _values;
|
||||||
|
Sections _sections;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @brief Parse configuration file and offer read / write
|
||||||
|
access to the contents.
|
||||||
|
|
||||||
|
Parse a configuration file that consists of sections containing
|
||||||
|
variables with values. This class stores the file in the destructor
|
||||||
|
if variables have changed.
|
||||||
|
@see If you want %to leave the file untouched, use
|
||||||
|
@ref ConfigFileReader.
|
||||||
|
@see For details, see @ref configSyntax. */
|
||||||
|
class ConfigFileWriter: virtual public ConfigFileReader {
|
||||||
|
public:
|
||||||
|
/// Uninitialized construction.
|
||||||
|
/** @copydoc ConfigFileReader() */
|
||||||
|
ConfigFileWriter() throw() {}
|
||||||
|
/// Parse a file at construction.
|
||||||
|
/** @copydoc ConfigFileReader(const std::string& filename) */
|
||||||
|
ConfigFileWriter(const std::string& filename) throw(std::exception){
|
||||||
|
load(filename);
|
||||||
|
}
|
||||||
|
/// Copy construct from other ConfigFileWriter.
|
||||||
|
ConfigFileWriter(const ConfigFileWriter& o) throw():
|
||||||
|
ConfigFileReader(o) {
|
||||||
|
}
|
||||||
|
/// The file is stored at destruction.
|
||||||
|
virtual ~ConfigFileWriter() throw() {
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
/// Copy from other ConfigFileWriter.
|
||||||
|
ConfigFileWriter& operator=(const ConfigFileWriter& o) {
|
||||||
|
ConfigFileReader::operator=(o);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
// GNU g++ prior to 3.4 does not implement covariant returns
|
||||||
|
# if __GNUC__ == 3 && __GNUC_MINOR__ < 4
|
||||||
|
/// Reload the last file again.
|
||||||
|
/** @copydoc mrw::ConfigFileReader::reload()
|
||||||
|
@bug Bug in GNU g++ compiler:
|
||||||
|
return type should be <code>ConfigFileWriter&</code>,
|
||||||
|
but the compiler sais:<br>
|
||||||
|
<code>../mrw/configfile.hpp:295:
|
||||||
|
sorry, unimplemented: adjusting pointers for
|
||||||
|
covariant returns</code><br>
|
||||||
|
The problem is fixed since GNU g++ 3.4, so the signature
|
||||||
|
will be changed as soon as you upgrade to the new compiler
|
||||||
|
version. */
|
||||||
|
virtual ConfigFileReader& reload() throw(std::exception);
|
||||||
|
# else
|
||||||
|
/// Reload the last file again.
|
||||||
|
/** @copydoc mrw::ConfigFileReader::reload() */
|
||||||
|
virtual ConfigFileWriter& reload() throw(std::exception);
|
||||||
|
# endif
|
||||||
|
/// Saves changes back to the file.
|
||||||
|
/** All changed parameters are stored in the configuration
|
||||||
|
file. The rest of the file is left untouched. */
|
||||||
|
ConfigFileWriter& save() throw(std::exception);
|
||||||
|
protected:
|
||||||
|
std::string _file;
|
||||||
|
};
|
||||||
|
|
||||||
|
//@}
|
||||||
|
}
|
||||||
|
#endif
|
16
mrw/configfile.ini
Normal file
16
mrw/configfile.ini
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
xxx=yyy # this is a comment
|
||||||
|
[Section]
|
||||||
|
|
||||||
|
abc=
|
||||||
|
def=hallo welt
|
||||||
|
ghi=
|
||||||
|
jkl= mn
|
||||||
|
op qr
|
||||||
|
st
|
||||||
|
|
||||||
|
[Other Section]
|
||||||
|
|
||||||
|
1234="5678=90"
|
||||||
|
|
||||||
|
here we are = some contents # comments
|
||||||
|
here=
|
24
mrw/configfile.ini.result
Normal file
24
mrw/configfile.ini.result
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
xxx=0 # this is a comment
|
||||||
|
[Section]
|
||||||
|
|
||||||
|
abc=1
|
||||||
|
def="Und=Tschuess"
|
||||||
|
ghi=3
|
||||||
|
jkl= 4
|
||||||
|
|
||||||
|
|
||||||
|
guguseli zwei = dadaa
|
||||||
|
guguseli drei = dadaa
|
||||||
|
guguseli = dadaa
|
||||||
|
[Other Section]
|
||||||
|
|
||||||
|
1234=5
|
||||||
|
|
||||||
|
here we are = 6 # comments
|
||||||
|
here=7
|
||||||
|
yes = .
|
||||||
|
no no no = .
|
||||||
|
no no = .
|
||||||
|
[New Section]
|
||||||
|
a first one = sgadd
|
||||||
|
one more = .
|
3
mrw/configfile_check.sh
Executable file
3
mrw/configfile_check.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
test -z "`diff configfile2.ini configfile.ini.result`" && rm configfile2.ini
|
66
mrw/configfile_test.cpp
Normal file
66
mrw/configfile_test.cpp
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/** @file
|
||||||
|
|
||||||
|
$Id$
|
||||||
|
|
||||||
|
$Date$
|
||||||
|
$Author$
|
||||||
|
|
||||||
|
@copy © Marc Wäckerlin
|
||||||
|
@license LGPL, see file <a href="license.html">COPYING</a>
|
||||||
|
|
||||||
|
$Log$
|
||||||
|
Revision 1.1 2005/01/07 00:31:38 marc
|
||||||
|
initial version
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <mrw/configfile.hpp>
|
||||||
|
#include <mrw/file.hpp>
|
||||||
|
#include <cppunit/TestFixture.h>
|
||||||
|
#include <cppunit/ui/text/TestRunner.h>
|
||||||
|
#include <cppunit/extensions/HelperMacros.h>
|
||||||
|
#include <cppunit/extensions/TestFactoryRegistry.h>
|
||||||
|
|
||||||
|
class ConfigFileTest: public CppUnit::TestFixture {
|
||||||
|
public:
|
||||||
|
void CheckFile() {
|
||||||
|
mrw::File::copy("configfile.ini", "configfile2.ini");
|
||||||
|
mrw::ConfigFileWriter config("configfile2.ini");
|
||||||
|
CPPUNIT_ASSERT(config("", "xxx", ".")=="yyy");
|
||||||
|
CPPUNIT_ASSERT(config("Section", "abc", ".")=="");
|
||||||
|
CPPUNIT_ASSERT(config("Section", "def", ".")=="hallo welt");
|
||||||
|
CPPUNIT_ASSERT(config("Section", "ghi", ".")=="");
|
||||||
|
CPPUNIT_ASSERT(config("Section", "jkl", ".")=="mn\n op qr\n st");
|
||||||
|
CPPUNIT_ASSERT(config("Other Section", "1234", ".")=="5678=90");
|
||||||
|
CPPUNIT_ASSERT(config("Other Section", "here we are", ".")
|
||||||
|
=="some contents");
|
||||||
|
CPPUNIT_ASSERT(config("Other Section", "here", ".")=="");
|
||||||
|
config("", "xxx", ".")="0";
|
||||||
|
config("Section", "abc", ".")="1";
|
||||||
|
CPPUNIT_ASSERT(config("New Section", "a first one", "sgadd")=="sgadd");
|
||||||
|
config("Section", "def", ".")="Und=Tschuess";
|
||||||
|
config("Section", "ghi", ".")="3";
|
||||||
|
config("Section", "jkl", ".")="4";
|
||||||
|
config("Other Section", "1234", ".")="5";
|
||||||
|
config("Other Section", "here we are", ".")="6";
|
||||||
|
config("Other Section", "here", ".")="7";
|
||||||
|
CPPUNIT_ASSERT(config("Other Section", "no no", ".")==".");
|
||||||
|
CPPUNIT_ASSERT(config("Other Section", "no no no", ".")==".");
|
||||||
|
CPPUNIT_ASSERT(config("Other Section", "yes", ".")==".");
|
||||||
|
CPPUNIT_ASSERT(config("Section", "guguseli", "dadaa")=="dadaa");
|
||||||
|
CPPUNIT_ASSERT(config("Section", "guguseli zwei", "dadaa")=="dadaa");
|
||||||
|
CPPUNIT_ASSERT(config("Section", "guguseli drei", "dadaa")=="dadaa");
|
||||||
|
CPPUNIT_ASSERT(config("New Section", "one more", ".")==".");
|
||||||
|
}
|
||||||
|
CPPUNIT_TEST_SUITE(ConfigFileTest);
|
||||||
|
CPPUNIT_TEST(CheckFile);
|
||||||
|
CPPUNIT_TEST_SUITE_END();
|
||||||
|
};
|
||||||
|
CPPUNIT_TEST_SUITE_REGISTRATION(ConfigFileTest);
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
CppUnit::TextUi::TestRunner runner;
|
||||||
|
runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest());
|
||||||
|
return runner.run() ? 0 : 1;
|
||||||
|
}
|
47
mrw/file.hpp
Normal file
47
mrw/file.hpp
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/** @file
|
||||||
|
|
||||||
|
$Id$
|
||||||
|
|
||||||
|
$Date$
|
||||||
|
$Author$
|
||||||
|
|
||||||
|
@copy © Marc Wäckerlin
|
||||||
|
@license LGPL, see file <a href="license.html">COPYING</a>
|
||||||
|
|
||||||
|
$Log$
|
||||||
|
Revision 1.1 2005/01/07 00:31:38 marc
|
||||||
|
initial version
|
||||||
|
|
||||||
|
|
||||||
|
1 2 3 4 5 6 7 8
|
||||||
|
5678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||||
|
*/
|
||||||
|
#ifndef __MRW_FILE_HPP__
|
||||||
|
#define __MRW_FILE_HPP__
|
||||||
|
|
||||||
|
#include <mrw/exception.hpp>
|
||||||
|
#include <string>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
namespace mrw {
|
||||||
|
class File {
|
||||||
|
public:
|
||||||
|
static void copy(std::string from, std::string to) throw (std::exception) {
|
||||||
|
std::ifstream is(from.c_str());
|
||||||
|
if (!is)
|
||||||
|
throw mrw::invalid_argument("Cannot read file: '"+from+"'");
|
||||||
|
is.seekg(0, std::ios::end);
|
||||||
|
int size(is.tellg());
|
||||||
|
std::string contents(size, 0); // make buffer long enough to store all
|
||||||
|
is.seekg(0, std::ios::beg);
|
||||||
|
is.read(contents.begin().operator->(), size); // hack to get the buffer
|
||||||
|
if (!is.good() && is.eof())
|
||||||
|
throw mrw::invalid_argument("Cannot read file: '"+from+"'");
|
||||||
|
std::ofstream os(to.c_str());
|
||||||
|
os<<contents;
|
||||||
|
if (!os) throw mrw::invalid_argument("Cannot write file: '"+to+"'");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@@ -9,6 +9,9 @@
|
|||||||
@license LGPL, see file <a href="license.html">COPYING</a>
|
@license LGPL, see file <a href="license.html">COPYING</a>
|
||||||
|
|
||||||
$Log$
|
$Log$
|
||||||
|
Revision 1.2 2005/01/07 00:35:17 marc
|
||||||
|
initial version
|
||||||
|
|
||||||
Revision 1.1 2004/12/17 16:26:58 marc
|
Revision 1.1 2004/12/17 16:26:58 marc
|
||||||
initial version
|
initial version
|
||||||
|
|
||||||
@@ -24,7 +27,6 @@
|
|||||||
#include <cppunit/extensions/HelperMacros.h>
|
#include <cppunit/extensions/HelperMacros.h>
|
||||||
#include <cppunit/extensions/TestFactoryRegistry.h>
|
#include <cppunit/extensions/TestFactoryRegistry.h>
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
class TokenizerTest: public CppUnit::TestFixture {
|
class TokenizerTest: public CppUnit::TestFixture {
|
||||||
public:
|
public:
|
||||||
void CheckNonGreedy() {
|
void CheckNonGreedy() {
|
||||||
|
Reference in New Issue
Block a user