have conftree preserve comments and ordering

This commit is contained in:
dockes 2007-08-03 07:50:49 +00:00
parent 767e4c01c8
commit 3ef569c5db
3 changed files with 250 additions and 99 deletions

View File

@ -74,7 +74,7 @@ trutf8iter.o : utf8iter.cpp utf8iter.h
CONFTREE_OBJS= trconftree.o ../lib/pathut.o ../lib/smallut.o ../lib/conftree.o CONFTREE_OBJS= trconftree.o ../lib/pathut.o ../lib/smallut.o ../lib/conftree.o
trconftree : $(CONFTREE_OBJS) trconftree : $(CONFTREE_OBJS) $(BIGLIB)
$(CXX) $(ALL_CXXFLAGS) -o trconftree $(CONFTREE_OBJS) $(CXX) $(ALL_CXXFLAGS) -o trconftree $(CONFTREE_OBJS)
trconftree.o : conftree.cpp trconftree.o : conftree.cpp
$(CXX) $(ALL_CXXFLAGS) -DTEST_CONFTREE -c -o trconftree.o \ $(CXX) $(ALL_CXXFLAGS) -DTEST_CONFTREE -c -o trconftree.o \

View File

@ -1,5 +1,5 @@
#ifndef lint #ifndef lint
static char rcsid [] = "@(#$Id: conftree.cpp,v 1.8 2006-12-14 13:53:43 dockes Exp $ (C) 2003 J.F.Dockes"; static char rcsid [] = "@(#$Id: conftree.cpp,v 1.9 2007-08-03 07:50:49 dockes Exp $ (C) 2003 J.F.Dockes";
#endif #endif
/* /*
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
@ -29,6 +29,7 @@ static char rcsid [] = "@(#$Id: conftree.cpp,v 1.8 2006-12-14 13:53:43 dockes Ex
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <algorithm> #include <algorithm>
#include <iostream>
#include "conftree.h" #include "conftree.h"
#include "pathut.h" #include "pathut.h"
@ -43,7 +44,6 @@ using std::list;
#define MIN(A,B) ((A)<(B) ? (A) : (B)) #define MIN(A,B) ((A)<(B) ? (A) : (B))
#endif #endif
#define LL 1024 #define LL 1024
void ConfSimple::parseinput(istream &input) void ConfSimple::parseinput(istream &input)
{ {
@ -78,8 +78,10 @@ void ConfSimple::parseinput(istream &input)
// Note that we trim whitespace before checking for backslash-eol // Note that we trim whitespace before checking for backslash-eol
// This avoids invisible problems. // This avoids invisible problems.
trimstring(line); trimstring(line);
if (line.empty()) if (line.empty()) {
m_order.push_back(ConfLine(ConfLine::CFL_COMMENT, line));
continue; continue;
}
if (line[line.length() - 1] == '\\') { if (line[line.length() - 1] == '\\') {
line.erase(line.length() - 1); line.erase(line.length() - 1);
appending = true; appending = true;
@ -93,13 +95,20 @@ void ConfSimple::parseinput(istream &input)
submapkey = path_tildexpand(line); submapkey = path_tildexpand(line);
else else
submapkey = line; submapkey = line;
// No need for adding sk to order, will be done with first
// variable insert. Also means that empty section are
// expandable (won't be output when rewriting)
// Another option would be to add the subsec to m_order here
// and not do it inside i_set() if init is true
continue; continue;
} }
// Look for first equal sign // Look for first equal sign
string::size_type eqpos = line.find("="); string::size_type eqpos = line.find("=");
if (eqpos == string::npos) if (eqpos == string::npos) {
m_order.push_back(ConfLine(ConfLine::CFL_COMMENT, line));
continue; continue;
}
// Compute name and value, trim white space // Compute name and value, trim white space
string nm, val; string nm, val;
@ -108,48 +117,33 @@ void ConfSimple::parseinput(istream &input)
val = line.substr(eqpos+1, string::npos); val = line.substr(eqpos+1, string::npos);
trimstring(val); trimstring(val);
if (nm.length() == 0) if (nm.length() == 0) {
m_order.push_back(ConfLine(ConfLine::CFL_COMMENT, line));
continue; continue;
map<string, map<string, string> >::iterator s;
s = submaps.find(submapkey);
if (s != submaps.end()) {
// submap already exists
map<string, string> &sm = s->second;
sm[nm] = val;
} else {
map<string, string> newmap;
newmap[nm] = val;
submaps[submapkey] = newmap;
} }
i_set(nm, val, submapkey, true);
} }
} }
ConfSimple::ConfSimple(int readonly, bool tildexp) ConfSimple::ConfSimple(int readonly, bool tildexp)
: dotildexpand(tildexp), m_data(0)
{ {
data = 0;
dotildexpand = tildexp;
status = readonly ? STATUS_RO : STATUS_RW; status = readonly ? STATUS_RO : STATUS_RW;
} }
ConfSimple::ConfSimple(string *d, int readonly, bool tildexp) ConfSimple::ConfSimple(string *d, int readonly, bool tildexp)
: dotildexpand(tildexp), m_data(d)
{ {
data = d;
dotildexpand = tildexp;
status = readonly ? STATUS_RO : STATUS_RW; status = readonly ? STATUS_RO : STATUS_RW;
stringstream input(*d, ios::in); stringstream input(*d, ios::in);
parseinput(input); parseinput(input);
} }
ConfSimple::ConfSimple(const char *fname, int readonly, bool tildexp) ConfSimple::ConfSimple(const char *fname, int readonly, bool tildexp)
: dotildexpand(tildexp), m_filename(fname), m_data(0)
{ {
data = 0;
filename = string(fname);
dotildexpand = tildexp;
status = readonly ? STATUS_RO : STATUS_RW; status = readonly ? STATUS_RO : STATUS_RW;
ifstream input; ifstream input;
@ -180,7 +174,6 @@ ConfSimple::ConfSimple(const char *fname, int readonly, bool tildexp)
return; return;
} }
// Parse
parseinput(input); parseinput(input);
} }
@ -200,7 +193,7 @@ int ConfSimple::get(const string &nm, string &value, const string &sk)
// Find submap // Find submap
map<string, map<string, string> >::iterator ss; map<string, map<string, string> >::iterator ss;
if ((ss = submaps.find(sk)) == submaps.end()) if ((ss = m_submaps.find(sk)) == m_submaps.end())
return 0; return 0;
// Find named value // Find named value
@ -211,13 +204,15 @@ int ConfSimple::get(const string &nm, string &value, const string &sk)
return 1; return 1;
} }
static ConfSimple::WalkerCode swalker(void *f, const string &nm, // Code to appropriately output a subkey (nm=="") or variable line
// Splits long lines
static ConfSimple::WalkerCode varprinter(void *f, const string &nm,
const string &value) const string &value)
{ {
ostream *output = (ostream *)f; ostream *output = (ostream *)f;
if (nm.empty()) if (nm.empty()) {
*output << "\n[" << value << "]\n"; *output << "\n[" << value << "]\n";
else { } else {
string value1; string value1;
if (value.length() < 60) { if (value.length() < 60) {
value1 = value; value1 = value;
@ -236,46 +231,90 @@ static ConfSimple::WalkerCode swalker(void *f, const string &nm,
return ConfSimple::WALK_CONTINUE; return ConfSimple::WALK_CONTINUE;
} }
// Set variable and rewrite data
int ConfSimple::set(const std::string &nm, const std::string &value, int ConfSimple::set(const std::string &nm, const std::string &value,
const string &sk) const string &sk)
{ {
if (status != STATUS_RW) if (status != STATUS_RW)
return 0; return 0;
// Preprocess value: we don't want nl's in there, and we want to keep if (!i_set(nm, value, sk))
// lines to a reasonable length return 0;
return write();
}
// Internal set variable: no rw checking or file rewriting. If init is
// set, we're doing initial parsing, else we are changing a parsed
// tree (changes the way we update the order data)
int ConfSimple::i_set(const std::string &nm, const std::string &value,
const string &sk, bool init)
{
// Values must not have embedded newlines
if (value.find_first_of("\n\r") != string::npos) { if (value.find_first_of("\n\r") != string::npos) {
return 0; return 0;
} }
bool existing = false;
map<string, map<string, string> >::iterator ss; map<string, map<string, string> >::iterator ss;
if ((ss = submaps.find(sk)) == submaps.end()) { if ((ss = m_submaps.find(sk)) == m_submaps.end()) {
map<string, string> submap; map<string, string> submap;
submap[nm] = value; submap[nm] = value;
submaps[sk] = submap; m_submaps[sk] = submap;
if (!sk.empty())
m_order.push_back(ConfLine(ConfLine::CFL_SK, sk));
// The var insert will be at the end, need not search for the
// right place
init = true;
} else {
map<string, string>::iterator it;
it = ss->second.find(nm);
if (it == ss->second.end()) {
ss->second.insert(pair<string,string>(nm, value));
} else {
it->second = value;
existing = true;
}
}
// If the variable already existed, no need to change the order data
if (existing)
return 1;
// Add the new variable at the end of its submap in the order data.
if (init) {
// During the initial construction, insert at end
m_order.push_back(ConfLine(ConfLine::CFL_VAR, nm));
return 1;
}
list<ConfLine>::iterator start, fin;
if (sk.empty()) {
start = m_order.begin();
} else { } else {
ss->second[nm] = value; start = find(m_order.begin(), m_order.end(),
} ConfLine(ConfLine::CFL_SK, sk));
if (start == m_order.end()) {
if (filename.length()) { // This is not logically possible. The subkey must
ofstream output(filename.c_str(), ios::out|ios::trunc); // exist. We're doomed
if (!output.is_open()) std::cerr << "Logical failure during configuration variable "
return 0; "insertion" << endl;
if (sortwalk(swalker, &output) != WALK_CONTINUE) { abort();
return 0;
} }
return 1;
} else if (data) {
ostringstream output(*data, ios::out | ios::trunc);
if (sortwalk(swalker, &output) != WALK_CONTINUE) {
return 0;
}
return 1;
} else {
// No backing store, no writing
return 1;
} }
fin = m_order.end();
if (start != m_order.end()) {
start++;
for (list<ConfLine>::iterator it = start; it != m_order.end(); it++) {
if (it->m_kind == ConfLine::CFL_SK) {
fin = it;
break;
}
}
}
m_order.insert(fin, ConfLine(ConfLine::CFL_VAR, nm));
return 1;
} }
int ConfSimple::erase(const string &nm, const string &sk) int ConfSimple::erase(const string &nm, const string &sk)
@ -284,30 +323,13 @@ int ConfSimple::erase(const string &nm, const string &sk)
return 0; return 0;
map<string, map<string, string> >::iterator ss; map<string, map<string, string> >::iterator ss;
if ((ss = submaps.find(sk)) == submaps.end()) { if ((ss = m_submaps.find(sk)) == m_submaps.end()) {
return 0; return 0;
} }
ss->second.erase(nm); ss->second.erase(nm);
if (filename.length()) { return write();
ofstream output(filename.c_str(), ios::out|ios::trunc);
if (!output.is_open())
return 0;
if (sortwalk(swalker, &output) != WALK_CONTINUE) {
return 0;
}
return 1;
} else if (data) {
ostringstream output(*data, ios::out | ios::trunc);
if (sortwalk(swalker, &output) != WALK_CONTINUE) {
return 0;
}
return 1;
} else {
return 1;
}
} }
int ConfSimple::set(const char *nm, const char *value, const char *sk) int ConfSimple::set(const char *nm, const char *value, const char *sk)
@ -323,8 +345,8 @@ ConfSimple::sortwalk(WalkerCode (*walker)(void *,const string&,const string&),
if (!ok()) if (!ok())
return WALK_STOP; return WALK_STOP;
// For all submaps: // For all submaps:
for (map<string, map<string, string> >::iterator sit = submaps.begin(); for (map<string, map<string, string> >::iterator sit = m_submaps.begin();
sit != submaps.end(); sit++) { sit != m_submaps.end(); sit++) {
// Possibly emit submap name: // Possibly emit submap name:
if (!sit->first.empty() && walker(clidata, "", sit->first.c_str()) if (!sit->first.empty() && walker(clidata, "", sit->first.c_str())
@ -342,12 +364,60 @@ ConfSimple::sortwalk(WalkerCode (*walker)(void *,const string&,const string&),
return WALK_CONTINUE; return WALK_CONTINUE;
} }
#include <iostream> bool ConfSimple::write()
{
if (m_filename.length()) {
ofstream output(m_filename.c_str(), ios::out|ios::trunc);
if (!output.is_open())
return 0;
return write(output);
} else if (m_data) {
ostringstream output(*m_data, ios::out | ios::trunc);
return write(output);
} else {
// No backing store, no writing
return 1;
}
}
bool ConfSimple::write(ostream& out)
{
if (!ok())
return false;
string sk;
for (list<ConfLine>::const_iterator it = m_order.begin();
it != m_order.end(); it++) {
switch(it->m_kind) {
case ConfLine::CFL_COMMENT:
out << it->m_data << endl;
if (!out.good())
return false;
break;
case ConfLine::CFL_SK:
sk = it->m_data;
out << "[" << it->m_data << "]" << endl;
if (!out.good())
return false;
break;
case ConfLine::CFL_VAR:
string value;
// As erase() doesnt update m_order we can find unexisting
// variables, and must not output anything for them
if (get(it->m_data, value, sk)) {
varprinter(&out, it->m_data, value);
if (!out.good())
return false;
}
}
}
return true;
}
void ConfSimple::listall() void ConfSimple::listall()
{ {
if (!ok()) if (!ok())
return; return;
sortwalk(swalker, &std::cout); write(std::cout);
} }
list<string> ConfSimple::getNames(const string &sk) list<string> ConfSimple::getNames(const string &sk)
@ -356,7 +426,7 @@ list<string> ConfSimple::getNames(const string &sk)
if (!ok()) if (!ok())
return mylist; return mylist;
map<string, map<string, string> >::iterator ss; map<string, map<string, string> >::iterator ss;
if ((ss = submaps.find(sk)) == submaps.end()) { if ((ss = m_submaps.find(sk)) == m_submaps.end()) {
return mylist; return mylist;
} }
map<string, string>::const_iterator it; map<string, string>::const_iterator it;
@ -368,6 +438,18 @@ list<string> ConfSimple::getNames(const string &sk)
return mylist; return mylist;
} }
list<string> ConfSimple::getSubKeys()
{
std::list<string> mylist;
if (!ok())
return mylist;
map<string, map<string, string> >::iterator ss;
for (ss = m_submaps.begin(); ss != m_submaps.end(); ss++) {
mylist.push_back(ss->first);
}
return mylist;
}
// ////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////
// ConfTree Methods: conftree interpret keys like a hierarchical file tree // ConfTree Methods: conftree interpret keys like a hierarchical file tree
// ////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////
@ -460,6 +542,7 @@ static char usage [] =
"testconftree [opts] filename\n" "testconftree [opts] filename\n"
"[-w] : read/write test.\n" "[-w] : read/write test.\n"
"[-s] : string parsing test. Filename must hold parm 'strings'\n" "[-s] : string parsing test. Filename must hold parm 'strings'\n"
"[-a] nm value sect : add nm,value in 'sect' which can be ''\n"
"[-q] nm sect : subsection test: look for nm in 'sect' which can be ''\n" "[-q] nm sect : subsection test: look for nm in 'sect' which can be ''\n"
"[-d] nm sect : delete nm in 'sect' which can be ''\n" "[-d] nm sect : delete nm in 'sect' which can be ''\n"
"[-S] : string io test. No filename in this case\n" "[-S] : string io test. No filename in this case\n"
@ -478,11 +561,13 @@ static int op_flags;
#define OPT_S 0x10 #define OPT_S 0x10
#define OPT_d 0x20 #define OPT_d 0x20
#define OPT_V 0x40 #define OPT_V 0x40
#define OPT_a 0x80
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
const char *nm = 0; const char *nm = 0;
const char *sub = 0; const char *sub = 0;
const char *value = 0;
thisprog = argv[0]; thisprog = argv[0];
argc--; argv++; argc--; argv++;
@ -494,13 +579,21 @@ int main(int argc, char **argv)
Usage(); Usage();
while (**argv) while (**argv)
switch (*(*argv)++) { switch (*(*argv)++) {
case 'd': case 'd':
op_flags |= OPT_d; op_flags |= OPT_d;
if (argc < 3) if (argc < 3)
Usage(); Usage();
nm = *(++argv);argc--; nm = *(++argv);argc--;
sub = *(++argv);argc--; sub = *(++argv);argc--;
goto b1; goto b1;
case 'a':
op_flags |= OPT_a;
if (argc < 4)
Usage();
nm = *(++argv);argc--;
value = *(++argv);argc--;
sub = *(++argv);argc--;
goto b1;
case 'q': case 'q':
op_flags |= OPT_q; op_flags |= OPT_q;
if (argc < 3) if (argc < 3)
@ -556,13 +649,18 @@ int main(int argc, char **argv)
} }
char spid[100]; char spid[100];
sprintf(spid, "%d", getpid()); sprintf(spid, "%d", getpid());
parms.set("mypid", spid); if (!parms.set("mypid", spid)) {
cerr << "Set mypid failed" << endl;
}
ostringstream ost;; ostringstream ost;;
ost << "mypid" << getpid(); ost << "mypid" << getpid();
parms.set(ost.str(), spid, ""); if (!parms.set(ost.str(), spid, "")) {
cerr << "Set mypid failed (2)" << endl;
parms.set("unstring", "Une jolie phrase pour essayer"); }
if (!parms.set("unstring", "Une jolie phrase pour essayer")) {
cerr << "Set unstring failed" << endl;
}
} else if (op_flags & OPT_q) { } else if (op_flags & OPT_q) {
ConfTree parms(filename, 0); ConfTree parms(filename, 0);
if (parms.getStatus() == ConfSimple::STATUS_ERROR) { if (parms.getStatus() == ConfSimple::STATUS_ERROR) {
@ -576,6 +674,17 @@ int main(int argc, char **argv)
} }
printf("%s : '%s' = '%s'\n", sub, nm, value.c_str()); printf("%s : '%s' = '%s'\n", sub, nm, value.c_str());
exit(0); exit(0);
} else if (op_flags & OPT_a) {
ConfTree parms(filename, 0);
if (parms.getStatus() == ConfSimple::STATUS_ERROR) {
fprintf(stderr, "Error opening or parsing file\n");
exit(1);
}
if (!parms.set(nm, value, sub)) {
fprintf(stderr, "Set error\n");
exit(1);
}
exit(0);
} else if (op_flags & OPT_d) { } else if (op_flags & OPT_d) {
ConfTree parms(filename, 0); ConfTree parms(filename, 0);
if (parms.getStatus() != ConfSimple::STATUS_RW) { if (parms.getStatus() != ConfSimple::STATUS_RW) {

View File

@ -23,7 +23,9 @@
* Configuration files have lines like 'name = value', and/or like '[subkey]' * Configuration files have lines like 'name = value', and/or like '[subkey]'
* *
* Lines like '[subkeyname]' in the file define subsections, with independant * Lines like '[subkeyname]' in the file define subsections, with independant
* configuration namespaces. * configuration namespaces. Only subsections holding at least one variable are
* significant (empty subsections may be deleted during an update, but
not always).
* *
* Whitespace around name and value is insignificant. * Whitespace around name and value is insignificant.
* *
@ -48,6 +50,7 @@
#include <iostream> #include <iostream>
#else #else
#include <istream> #include <istream>
#include <ostream>
#endif #endif
#ifndef NO_NAMESPACES #ifndef NO_NAMESPACES
@ -55,10 +58,28 @@ using std::string;
using std::list; using std::list;
using std::map; using std::map;
using std::istream; using std::istream;
using std::ostream;
#endif // NO_NAMESPACES #endif // NO_NAMESPACES
#include "pathut.h" #include "pathut.h"
/** Internal class used for storing presentation information */
class ConfLine {
public:
enum Kind {CFL_COMMENT, CFL_SK, CFL_VAR};
Kind m_kind;
string m_data;
ConfLine(Kind k, const string& d)
: m_kind(k), m_data(d)
{
}
bool operator==(const ConfLine& o)
{
return o.m_kind == m_kind && o.m_data == m_data;
}
};
/** /**
* Manages a simple configuration file with subsections. * Manages a simple configuration file with subsections.
*/ */
@ -135,29 +156,37 @@ public:
*/ */
virtual list<string> getNames(const string &sk); virtual list<string> getNames(const string &sk);
virtual string getFilename() {return filename;} /**
* Return all subkeys
*/
virtual list<string> getSubKeys();
virtual string getFilename() {return m_filename;}
/** /**
* Copy constructor. Expensive but less so than a full rebuild * Copy constructor. Expensive but less so than a full rebuild
*/ */
ConfSimple(const ConfSimple &rhs) : data(0) { ConfSimple(const ConfSimple &rhs)
: m_data(0)
{
if ((status = rhs.status) == STATUS_ERROR) if ((status = rhs.status) == STATUS_ERROR)
return; return;
filename = rhs.filename; m_filename = rhs.m_filename;
// Note: we just share the pointer, this doesnt belong to us // Note: we just share the pointer, this doesnt belong to us
data = rhs.data; m_data = rhs.m_data;
submaps = rhs.submaps; m_submaps = rhs.m_submaps;
} }
/** /**
* Assignement. This is expensive * Assignement. This is expensive
*/ */
ConfSimple& operator=(const ConfSimple &rhs) { ConfSimple& operator=(const ConfSimple &rhs)
{
if (this != &rhs && (status = rhs.status) != STATUS_ERROR) { if (this != &rhs && (status = rhs.status) != STATUS_ERROR) {
filename = rhs.filename; m_filename = rhs.m_filename;
// Note: we don't own data. Just share the pointer // Note: we don't own data. Just share the pointer
data = rhs.data; m_data = rhs.m_data;
submaps = rhs.submaps; m_submaps = rhs.m_submaps;
} }
return *this; return *this;
} }
@ -166,11 +195,24 @@ protected:
bool dotildexpand; bool dotildexpand;
StatusCode status; StatusCode status;
private: private:
string filename; // set if we're working with a file // Set if we're working with a file
string *data; // set if we're working with an in-memory string string m_filename;
map<string, map<string, string> > submaps; // Set if we're working with an in-memory string
string *m_data;
// Configuration data submaps (one per subkey, the main data has a
// null subkey)
map<string, map<string, string> > m_submaps;
// Presentation data. We keep the comments, empty lines and
// variable and subkey ordering information in there (for
// rewriting the file while keeping hand-edited information)
list<ConfLine> m_order;
void parseinput(istream &input); void parseinput(istream& input);
bool write();
bool write(ostream& out);
// Internal version of set: no RW checking
virtual int i_set(const string &nm, const string &val,
const string &sk, bool init = false);
}; };
/** /**