New ini-file feature: += appends RHS to LHS.
authorSteve Reinhardt <stever@eecs.umich.edu>
Wed, 22 Oct 2003 04:24:34 +0000 (21:24 -0700)
committerSteve Reinhardt <stever@eecs.umich.edu>
Wed, 22 Oct 2003 04:24:34 +0000 (21:24 -0700)
Added doxygen comments to inifile.hh.
Updated initest.  Some other minor cleanup.

base/inifile.cc:
    Add support for '+=' append operation.
    Factor common code from IniFile::load() and IniFile::add() into new Section::add().
    Rename ConfigTable to SectionTable (more descriptive).
    Fix bug in Section::dump().
base/inifile.hh:
    Add doxygen comments.
    Add support for '+=' append operation.
    Factor common code from IniFile::load() and IniFile::add() into new Section::add().
    Rename ConfigTable to SectionTable (more descriptive).
test/Makefile:
    initest needs cprintf.o now.
test/foo.ini:
    Add test of '+=' operator.
test/initest.cc:
    Bring this up-to-date.  Steal main loop from main.cc so we can test
    multiple .ini files and command-line assignments too.

--HG--
extra : convert_revision : 982521677fbf464e93aa93798ff7d9611826f17c

base/inifile.cc
base/inifile.hh
test/Makefile
test/foo.ini
test/initest.cc

index d5436fba8de40234e74446b350ece91369cea1c1..7fd2f55684d9746fc64aac6125826b80169b8825 100644 (file)
@@ -59,8 +59,8 @@ IniFile::IniFile()
 
 IniFile::~IniFile()
 {
-    ConfigTable::iterator i = table.begin();
-    ConfigTable::iterator end = table.end();
+    SectionTable::iterator i = table.begin();
+    SectionTable::iterator end = table.end();
 
     while (i != end) {
         delete (*i).second;
@@ -75,6 +75,16 @@ IniFile::loadCPP(const string &file, vector<char *> &cppArgs)
 {
     int fd[2];
 
+    // Open the file just to verify that we can.  Otherwise if the
+    // file doesn't exist or has bad permissions the user will get
+    // confusing errors from cpp/g++.
+    ifstream tmpf(file.c_str());
+
+    if (!tmpf.is_open())
+        return false;
+
+    tmpf.close();
+
 #ifdef CPP_PIPE
     if (pipe(fd) == -1)
         return false;
@@ -183,7 +193,8 @@ IniFile::Entry::getValue() const
 
 void
 IniFile::Section::addEntry(const std::string &entryName,
-                           const std::string &value)
+                           const std::string &value,
+                           bool append)
 {
     EntryTable::iterator ei = table.find(entryName);
 
@@ -191,6 +202,10 @@ IniFile::Section::addEntry(const std::string &entryName,
         // new entry
         table[entryName] = new Entry(value);
     }
+    else if (append) {
+        // append new reult to old entry
+        ei->second->appendValue(value);
+    }
     else {
         // override old entry
         ei->second->setValue(value);
@@ -198,6 +213,27 @@ IniFile::Section::addEntry(const std::string &entryName,
 }
 
 
+bool
+IniFile::Section::add(const std::string &assignment)
+{
+    string::size_type offset = assignment.find('=');
+    if (offset == string::npos)  // no '=' found
+        return false;
+
+    // if "+=" rather than just "=" then append value
+    bool append = (assignment[offset-1] == '+');
+
+    string entryName = assignment.substr(0, append ? offset-1 : offset);
+    string value = assignment.substr(offset + 1);
+
+    eat_white(entryName);
+    eat_white(value);
+
+    addEntry(entryName, value, append);
+    return true;
+}
+
+
 IniFile::Entry *
 IniFile::Section::findEntry(const std::string &entryName) const
 {
@@ -212,10 +248,10 @@ IniFile::Section::findEntry(const std::string &entryName) const
 IniFile::Section *
 IniFile::addSection(const string &sectionName)
 {
-    ConfigTable::iterator ci = table.find(sectionName);
+    SectionTable::iterator i = table.find(sectionName);
 
-    if (ci != table.end()) {
-        return ci->second;
+    if (i != table.end()) {
+        return i->second;
     }
     else {
         // new entry
@@ -229,9 +265,9 @@ IniFile::addSection(const string &sectionName)
 IniFile::Section *
 IniFile::findSection(const string &sectionName) const
 {
-    ConfigTable::const_iterator ci = table.find(sectionName);
+    SectionTable::const_iterator i = table.find(sectionName);
 
-    return (ci == table.end()) ? NULL : ci->second;
+    return (i == table.end()) ? NULL : i->second;
 }
 
 
@@ -248,21 +284,10 @@ IniFile::add(const string &str)
     string sectionName = str.substr(0, offset);
     string rest = str.substr(offset + 1);
 
-    offset = rest.find('=');
-    if (offset == string::npos)  // no '='found
-        return false;
-
-    string entryName = rest.substr(0, offset);
-    string value = rest.substr(offset + 1);
-
     eat_white(sectionName);
-    eat_white(entryName);
-    eat_white(value);
-
     Section *s = addSection(sectionName);
-    s->addEntry(entryName, value);
 
-    return true;
+    return s->add(rest);
 }
 
 bool
@@ -294,14 +319,8 @@ IniFile::load(istream &f)
         if (section == NULL)
             continue;
 
-        string::size_type offset = line.find('=');
-        string entryName = line.substr(0, offset);
-        string value = line.substr(offset + 1);
-
-        eat_white(entryName);
-        eat_white(value);
-
-        section->addEntry(entryName, value);
+        if (!section->add(line))
+            return false;
     }
 
     return true;
@@ -387,10 +406,10 @@ IniFile::printUnreferenced()
 {
     bool unref = false;
 
-    for (ConfigTable::iterator ci = table.begin();
-         ci != table.end(); ++ci) {
-        const string &sectionName = ci->first;
-        Section *section = ci->second;
+    for (SectionTable::iterator i = table.begin();
+         i != table.end(); ++i) {
+        const string &sectionName = i->first;
+        Section *section = i->second;
 
         if (!section->isReferenced()) {
             if (section->findEntry("unref_section_ok") == NULL) {
@@ -416,15 +435,15 @@ IniFile::Section::dump(const string &sectionName)
     for (EntryTable::iterator ei = table.begin();
          ei != table.end(); ++ei) {
         cout << sectionName << ": " << (*ei).first << " => "
-             << (*ei).second << "\n";
+             << (*ei).second->getValue() << "\n";
     }
 }
 
 void
 IniFile::dump()
 {
-    for (ConfigTable::iterator ci = table.begin();
-         ci != table.end(); ++ci) {
-        ci->second->dump(ci->first);
+    for (SectionTable::iterator i = table.begin();
+         i != table.end(); ++i) {
+        i->second->dump(i->first);
     }
 }
index 919732e1e6c22eee90a3adf38c967d5c8dc6f434..3a82f2d4dab88e51e70113c0eade2c771e7461ba 100644 (file)
 
 #include "base/hashmap.hh"
 
+///
+/// @file
+/// Declaration of IniFile object.
+///
+
+///
+/// This class represents the contents of a ".ini" file.
+///
+/// It's basically a two level lookup table: a set of named sections,
+/// where each section is a set of key/value pairs.  Section names,
+/// keys, and values are all uninterpreted strings.
+///
 class IniFile
 {
   protected:
+
+    ///
+    /// A single key/value pair.
+    ///
     class Entry
     {
-        std::string    value;
-        mutable bool   referenced;
+        std::string    value;          ///< The entry value.
+        mutable bool   referenced;     ///< Has this entry been used?
 
       public:
+        /// Constructor.
         Entry(const std::string &v)
             : value(v), referenced(false)
         {
         }
 
+        /// Has this entry been used?
         bool isReferenced() { return referenced; }
 
+        /// Fetch the value.
         const std::string &getValue() const;
 
+        /// Set the value.
         void setValue(const std::string &v) { value = v; }
+
+        /// Append the given string to the value.  A space is inserted
+        /// between the existing value and the new value.  Since this
+        /// operation is typically used with values that are
+        /// space-separated lists of tokens, this keeps the tokens
+        /// separate.
+        void appendValue(const std::string &v) { value += " "; value += v; }
     };
 
+    ///
+    /// A section.
+    ///
     class Section
     {
+        /// EntryTable type.  Map of strings to Entry object pointers.
         typedef m5::hash_map<std::string, Entry *> EntryTable;
 
-        EntryTable     table;
-        mutable bool   referenced;
+        EntryTable     table;          ///< Table of entries.
+        mutable bool   referenced;     ///< Has this section been used?
 
       public:
+        /// Constructor.
         Section()
             : table(), referenced(false)
         {
         }
 
+        /// Has this section been used?
         bool isReferenced() { return referenced; }
 
-        void addEntry(const std::string &entryName, const std::string &value);
+        /// Add an entry to the table.  If an entry with the same name
+        /// already exists, the 'append' parameter is checked If true,
+        /// the new value will be appended to the existing entry.  If
+        /// false, the new value will replace the existing entry.
+        void addEntry(const std::string &entryName, const std::string &value,
+                      bool append);
+
+        /// Add an entry to the table given a string assigment.
+        /// Assignment should be of the form "param=value" or
+        /// "param+=value" (for append).  This funciton parses the
+        /// assignment statment and calls addEntry().
+        /// @retval True for success, false if parse error.
+        bool add(const std::string &assignment);
+
+        /// Find the entry with the given name.
+        /// @retval Pointer to the entry object, or NULL if none.
         Entry *findEntry(const std::string &entryName) const;
 
+        /// Print the unreferenced entries in this section to cerr.
+        /// Messages can be suppressed using "unref_section_ok" and
+        /// "unref_entries_ok".
+        /// @param sectionName Name of this section, for use in output message.
+        /// @retval True if any entries were printed.
         bool printUnreferenced(const std::string &sectionName);
+
+        /// Print the contents of this section to cout (for debugging).
         void dump(const std::string &sectionName);
     };
 
-    typedef m5::hash_map<std::string, Section *> ConfigTable;
+    /// SectionTable type.  Map of strings to Section object pointers.
+    typedef m5::hash_map<std::string, Section *> SectionTable;
 
   protected:
-    ConfigTable table;
+    /// Hash of section names to Section object pointers.
+    SectionTable table;
 
+    /// Look up section with the given name, creating a new section if
+    /// not found.
+    /// @retval Pointer to section object.
     Section *addSection(const std::string &sectionName);
+
+    /// Look up section with the given name.
+    /// @retval Pointer to section object, or NULL if not found.
     Section *findSection(const std::string &sectionName) const;
 
+    /// Load parameter settings from given istream.  This is a helper
+    /// function for load(string) and loadCPP(), which open a file
+    /// and then pass it here.
+    /// @retval True if successful, false if errors were encountered.
     bool load(std::istream &f);
 
   public:
+    /// Constructor.
     IniFile();
+
+    /// Destructor.
     ~IniFile();
 
+    /// Load the specified file, passing it through the C preprocessor.
+    /// Parameter settings found in the file will be merged with any
+    /// already defined in this object.
+    /// @param file The path of the file to load.
+    /// @param cppFlags Vector of extra flags to pass to cpp.
+    /// @retval True if successful, false if errors were encountered.
     bool loadCPP(const std::string &file, std::vector<char *> &cppFlags);
+
+    /// Load the specified file.
+    /// Parameter settings found in the file will be merged with any
+    /// already defined in this object.
+    /// @param file The path of the file to load.
+    /// @retval True if successful, false if errors were encountered.
     bool load(const std::string &file);
 
+    /// Take string of the form "<section>:<parameter>=<value>" or
+    /// "<section>:<parameter>+=<value>" and add to database.
+    /// @retval True if successful, false if parse error.
     bool add(const std::string &s);
 
+    /// Find value corresponding to given section and entry names.
+    /// Value is returned by reference in 'value' param.
+    /// @retval True if found, false if not.
     bool find(const std::string &section, const std::string &entry,
               std::string &value) const;
+
+    /// Find value corresponding to given section and entry names,
+    /// following "default" links to other sections where possible.
+    /// Value is returned by reference in 'value' param.
+    /// @retval True if found, false if not.
     bool findDefault(const std::string &section, const std::string &entry,
                      std::string &value) const;
 
+    /// Print unreferenced entries in object.  Iteratively calls
+    /// printUnreferend() on all the constituent sections.
     bool printUnreferenced();
 
+    /// Dump contents to cout.  For debugging.
     void dump();
 };
 
index c95f8cb8d4b621bf6b7da5264c0926a3ae8b4609..1502bca3d02e2e28c0f53790cddcb86385c2dc3e 100644 (file)
@@ -37,7 +37,7 @@ circletest: circletest.o circlebuf.o
 cprintftest: cprintftest.o cprintf.o
        $(CXX) $(LFLAGS) -o $@ $^
 
-initest: initest.o str.o inifile.o
+initest: initest.o str.o inifile.o cprintf.o
        $(CXX) $(LFLAGS) -o $@ $^
 
 lrutest: lru_test.o
index aa4305f123dee301ea1057a53ae858106ec33c44..534a4e00135c54ae973ea401fdfe1e432fff7872 100644 (file)
@@ -5,3 +5,6 @@ Foo2=384
 
 [General]
 Test3=89
+
+[Junk]
+Test4+=mia
index 51089a46b86ad40fa064fd8edfe94c6138de006f..818c64da7572a14b1b528f24804cc8ca524329d9 100644 (file)
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include <iostream.h>
-#include <fstream.h>
-#include <unistd.h>
-
+#include <iostream>
+#include <fstream>
 #include <string>
 #include <vector>
 
 #include "base/inifile.hh"
+#include "base/cprintf.hh"
+
+using namespace std;
 
 char *progname;
 
 void
 usage()
 {
-  cout << "Usage: " << progname << " <ini file>\n";
-  exit(1);
+    cout << "Usage: " << progname << " <ini file>\n";
+    exit(1);
 }
 
 #if 0
-    char *defines = getenv("CONFIG_DEFINES");
-    if (defines) {
-      char *c = defines;
-      while ((c = strchr(c, ' ')) != NULL) {
+char *defines = getenv("CONFIG_DEFINES");
+if (defines) {
+    char *c = defines;
+    while ((c = strchr(c, ' ')) != NULL) {
         *c++ = '\0';
         count++;
-      }
-      count++;
     }
+    count++;
+}
 
 #endif
 
 int
 main(int argc, char *argv[])
 {
-  IniFile config;
-
-  progname = argv[0];
-
-  int cpp_options_count = 0;
-  char **cpp_options = new char*[argc * 2];
-  char **cur_opt = cpp_options;
-
-  int ch;
-  while ((ch = getopt(argc, argv, "D:")) != -1) {
-    switch (ch) {
-    case 'D':
-      *cur_opt++ = "-D";
-      *cur_opt++ = optarg;
-      cpp_options_count += 2;
-      break;
-
-    default:
-      usage();
+    IniFile simConfigDB;
+
+    progname = argv[0];
+
+    vector<char *> cppArgs;
+
+    vector<char *> cpp_options;
+    cpp_options.reserve(argc * 2);
+
+    for (int i = 1; i < argc; ++i) {
+        char *arg_str = argv[i];
+
+        // if arg starts with '-', parse as option,
+        // else treat it as a configuration file name and load it
+        if (arg_str[0] == '-') {
+
+            // switch on second char
+            switch (arg_str[1]) {
+              case 'D':
+              case 'U':
+              case 'I':
+                // cpp options: record & pass to cpp.  Note that these
+                // cannot have spaces, i.e., '-Dname=val' is OK, but
+                // '-D name=val' is not.  I don't consider this a
+                // problem, since even though gnu cpp accepts the
+                // latter, other cpp implementations do not (Tru64,
+                // for one).
+                cppArgs.push_back(arg_str);
+                break;
+
+              case '-':
+                // command-line configuration parameter:
+                // '--<section>:<parameter>=<value>'
+
+                if (!simConfigDB.add(arg_str + 2)) {
+                    // parse error
+                    ccprintf(cerr,
+                             "Could not parse configuration argument '%s'\n"
+                             "Expecting --<section>:<parameter>=<value>\n",
+                             arg_str);
+                    exit(0);
+                }
+                break;
+
+              default:
+                usage();
+            }
+        }
+        else {
+            // no '-', treat as config file name
+
+            if (!simConfigDB.loadCPP(arg_str, cppArgs)) {
+                cprintf("Error processing file %s\n", arg_str);
+                exit(1);
+            }
+        }
     }
-  }
-
-  argc -= optind;
-  argv += optind;
 
-  if (argc != 1)
-    usage();
-
-  string file = argv[0];
-
-  if (!config.loadCPP(file, cpp_options, cpp_options_count)) {
-    cout << "File not found!\n";
-    exit(1);
-  }
+    string value;
 
-  string value;
 #define FIND(C, E) \
-  if (config.find(C, E, value)) \
+  if (simConfigDB.find(C, E, value)) \
     cout << ">" << value << "<\n"; \
   else \
     cout << "Not Found!\n"
 
-  FIND("General", "Test2");
-  FIND("Junk", "Test3");
-  FIND("Junk", "Test4");
-  FIND("General", "Test1");
-  FIND("Junk2", "test3");
-  FIND("General", "Test3");
+    FIND("General", "Test2");
+    FIND("Junk", "Test3");
+    FIND("Junk", "Test4");
+    FIND("General", "Test1");
+    FIND("Junk2", "test3");
+    FIND("General", "Test3");
 
-  cout << "\n";
+    cout << "\n";
 
-  config.dump();
+    simConfigDB.dump();
 
-  return 0;
+    return 0;
 }