aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/config-file.cpp308
-rw-r--r--common/config-file.h131
-rw-r--r--common/module.mk1
3 files changed, 440 insertions, 0 deletions
diff --git a/common/config-file.cpp b/common/config-file.cpp
new file mode 100644
index 0000000000..f96a4dd2ce
--- /dev/null
+++ b/common/config-file.cpp
@@ -0,0 +1,308 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2005 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header$
+ *
+ */
+
+#include "stdafx.h"
+
+#include "common/config-file.h"
+#include "common/file.h"
+#include "common/util.h"
+
+#define MAXLINELEN 256
+
+namespace Common {
+
+static char *ltrim(char *t) {
+ while (isspace(*t))
+ t++;
+ return t;
+}
+
+static char *rtrim(char *t) {
+ int l = strlen(t) - 1;
+ while (l >= 0 && isspace(t[l]))
+ t[l--] = 0;
+ return t;
+}
+
+/**
+ * Check whether the given string is a valid section or key name.
+ * For that, it must only consist of letters, numbers, dashes and
+ * underscores. In particular, white space and "#", "=", "[", "]"
+ * are not valid!
+ */
+bool ConfigFile::isValidName(const Common::String &name) {
+ const char *p = name.c_str();
+ while (*p && (isalnum(*p) || *p == '-' || *p == '_'))
+ p++;
+ return *p == 0;
+}
+
+ConfigFile::ConfigFile() {
+}
+
+ConfigFile::~ConfigFile() {
+}
+
+void ConfigFile::clear() {
+ _sections.clear();
+}
+
+bool ConfigFile::loadFromFile(const String &filename) {
+ File file;
+ if (file.open(filename.c_str(), File::kFileReadMode))
+ return loadFromStream(file);
+ else
+ return false;
+}
+
+bool ConfigFile::loadFromStream(SeekableReadStream &stream) {
+ char buf[MAXLINELEN];
+ Section section;
+ KeyValue kv;
+ String comment;
+ int lineno = 0;
+
+ // TODO: Detect if a section occurs multiple times (or likewise, if
+ // a key occurs multiple times inside one section).
+
+ while (!stream.eos()) {
+ lineno++;
+ if (!stream.readLine(buf, MAXLINELEN))
+ break;
+
+ if (buf[0] == '#') {
+ // Accumulate comments here. Once we encounter either the start
+ // of a new section, or a key-value-pair, we associate the value
+ // of the 'comment' variable with that entity.
+ comment += buf;
+ comment += "\n";
+ } else if (buf[0] == '[') {
+ // It's a new section which begins here.
+ char *p = buf + 1;
+ // Get the section name, and check whether it's valid (that
+ // is, verify that it only consists of alphanumerics,
+ // dashes and underscores).
+ while (*p && (isalnum(*p) || *p == '-' || *p == '_'))
+ p++;
+
+ if (*p == '\0')
+ error("Config file buggy: missing ] in line %d", lineno);
+ else if (*p != ']')
+ error("Config file buggy: Invalid character '%c' occured in section name in line %d", *p, lineno);
+
+ *p = 0;
+
+ // Previous section is finished now, store it.
+ if (!section.name.isEmpty())
+ _sections.push_back(section);
+
+ section.name = buf + 1;
+ section.keys.clear();
+ section.comment = comment;
+ comment.clear();
+
+ assert(isValidName(section.name));
+ } else {
+ // Skip leading & trailing whitespaces
+ char *t = rtrim(ltrim(buf));
+
+ // Skip empty lines
+ if (*t == 0)
+ continue;
+
+ // If no section has been set, this config file is invalid!
+ if (section.name.isEmpty()) {
+ error("Config file buggy: Key/value pair found outside a section in line %d", lineno);
+ }
+
+ // Split string at '=' into 'key' and 'value'.
+ char *p = strchr(t, '=');
+ if (!p)
+ error("Config file buggy: Junk found in line line %d: '%s'", lineno, t);
+ *p = 0;
+
+ kv.key = rtrim(t);
+ kv.value = ltrim(p + 1);
+ kv.comment = comment;
+ comment.clear();
+
+ assert(isValidName(kv.key));
+
+ section.keys.push_back(kv);
+ }
+ }
+
+ return !stream.ioFailed();
+}
+
+bool ConfigFile::saveToFile(const String &filename) {
+ File file;
+ if (file.open(filename.c_str(), File::kFileWriteMode))
+ return saveToStream(file);
+ else
+ return false;
+}
+
+bool ConfigFile::saveToStream(WriteStream &stream) {
+ for (List<Section>::iterator i = _sections.begin(); i != _sections.end(); ++i) {
+ // Write out the section comment, if any
+ if (not i->comment.isEmpty()) {
+ stream.writeString(i->comment);
+ }
+
+ // Write out the section name
+ stream.writeByte('[');
+ stream.writeString(i->name);
+ stream.writeByte(']');
+ stream.writeByte('\n');
+
+ // Write out the key/value pairs
+ for (List<KeyValue>::iterator kv = i->keys.begin(); kv != i->keys.end(); ++kv) {
+ // Write out the comment, if any
+ if (not kv->comment.isEmpty()) {
+ stream.writeString(kv->comment);
+ }
+ // Write out the key/value pair
+ stream.writeString(kv->key);
+ stream.writeByte('=');
+ stream.writeString(kv->value);
+ stream.writeByte('\n');
+ }
+ }
+
+ return !stream.ioFailed();
+}
+
+
+void ConfigFile::removeSection(const String &section) {
+ assert(isValidName(section));
+ for (List<Section>::iterator i = _sections.begin(); i != _sections.end(); ++i) {
+ if (scumm_stricmp(section.c_str(), i->name.c_str())) {
+ _sections.erase(i);
+ return;
+ }
+ }
+}
+
+bool ConfigFile::hasSection(const String &section) const {
+ assert(isValidName(section));
+ const Section *s = getSection(section);
+ return s != 0;
+}
+
+void ConfigFile::renameSection(const String &oldName, const String &newName) {
+ assert(isValidName(oldName));
+ assert(isValidName(newName));
+
+ //Section *os = getSection(oldName);
+ Section *ns = getSection(newName);
+ if (ns) {
+ ns->name = newName;
+ }
+ // TODO: Check here whether there already is a section with the
+ // new name. Not sure how to cope with that case, we could:
+ // - simply remove the existing "newName" section
+ // - error out
+ // - merge the two sections "oldName" and "newName"
+}
+
+
+bool ConfigFile::hasKey(const String &key, const String &section) const {
+ assert(isValidName(key));
+ assert(isValidName(section));
+
+ const Section *s = getSection(section);
+ if (!s)
+ return false;
+ return s->hasKey(key);
+}
+
+void ConfigFile::removeKey(const String &key, const String &section) {
+ assert(isValidName(key));
+ assert(isValidName(section));
+
+ Section *s = getSection(section);
+ if (s)
+ return s->removeKey(key);
+}
+
+bool ConfigFile::getKey(const String &key, const String &section, String &value) const {
+ assert(isValidName(key));
+ assert(isValidName(section));
+
+ const Section *s = getSection(section);
+ if (!s)
+ return false;
+ const KeyValue *kv = s->getKey(key);
+ if (!kv)
+ return false;
+ value = kv->value;
+ return true;
+}
+
+void ConfigFile::setKey(const String &key, const String &section, const String &value) {
+ assert(isValidName(key));
+ assert(isValidName(section));
+ // TODO: Verify that value is valid, too. In particular, it shouldn't
+ // contain CR or LF...
+
+ Section *s = getSection(section);
+ if (!s) {
+ KeyValue newKV;
+ newKV.key = key;
+ newKV.value = value;
+
+ Section newSection;
+ newSection.name = section;
+ newSection.keys.push_back(newKV);
+
+ _sections.push_back(newSection);
+ } else {
+ KeyValue *kv = s->getKey(key);
+ if (!kv) {
+ KeyValue newKV;
+ newKV.key = key;
+ newKV.value = value;
+ s->keys.push_back(newKV);
+ } else
+ kv->value = value;
+ }
+}
+
+ConfigFile::Section *ConfigFile::getSection(const String &section) {
+ for (List<Section>::iterator i = _sections.begin(); i != _sections.end(); ++i) {
+ if (scumm_stricmp(section.c_str(), i->name.c_str())) {
+ return &(*i);
+ }
+ }
+ return 0;
+}
+
+const ConfigFile::Section *ConfigFile::getSection(const String &section) const {
+ for (List<Section>::const_iterator i = _sections.begin(); i != _sections.end(); ++i) {
+ if (scumm_stricmp(section.c_str(), i->name.c_str())) {
+ return &(*i);
+ }
+ }
+ return 0;
+}
+
+} // End of namespace Common
diff --git a/common/config-file.h b/common/config-file.h
new file mode 100644
index 0000000000..efea9f2ace
--- /dev/null
+++ b/common/config-file.h
@@ -0,0 +1,131 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2005 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Header$
+ *
+ */
+
+#ifndef COMMON_CONFIG_FILE_H
+#define COMMON_CONFIG_FILE_H
+
+#include "common/config-manager.h"
+#include "common/list.h"
+#include "common/map.h"
+#include "common/str.h"
+#include "common/stream.h"
+
+namespace Common {
+
+/**
+ * This class allows reading/writing INI style config files.
+ * It is used by the ConfigManager for storage, but can also
+ * be used by other code if it needs to read/write custom INI
+ * files.
+ *
+ * Lines starting with a '#' are ignored (i.e. treated as comments).
+ * Some effort is made to preserve comments, though.
+ *
+ * This class makes no attempts to provide fast access to key/value pairs.
+ * In particular, it stores all sections and k/v pairs in lists, not
+ * in dictionaries/maps. This makes it very easy to read/write the data
+ * from/to files, but of course is not appropriate for fast access.
+ * The main reason is that this class is indeed geared toward doing precisely
+ * that!
+ * If you need fast access to the game config, use higher level APIs, like the
+ * one provided by ConfigManager.
+ */
+class ConfigFile {
+public:
+ typedef Map<String, bool, IgnoreCaseComparator> StringSet;
+
+ struct KeyValue {
+ String key;
+ String value;
+ String comment;
+ };
+
+ /** A section in a config file. I.e. corresponds to something like this:
+ * [mySection]
+ * key=value
+ *
+ * Comments are also stored, to keep users happy who like editing their
+ * config files manually.
+ */
+ struct Section {
+ String name;
+ List<KeyValue> keys;
+ String comment;
+
+ bool hasKey(const String &key) const;
+ KeyValue* getKey(const String &key) const;
+ void setKey(const String &key, const String &value);
+ void removeKey(const String &key);
+ };
+
+public:
+ ConfigFile();
+ ~ConfigFile();
+
+ // TODO: Maybe add a copy constructor etc.?
+
+ /**
+ * Check whether the given string is a valid section or key name.
+ * For that, it must only consist of letters, numbers, dashes and
+ * underscores. In particular, white space and "#", "=", "[", "]"
+ * are not valid!
+ */
+ static bool isValidName(const Common::String &name);
+
+ /** Reset everything stored in this config file. */
+ void clear();
+
+ bool loadFromFile(const String &filename);
+ bool loadFromStream(SeekableReadStream &stream);
+ bool saveToFile(const String &filename);
+ bool saveToStream(WriteStream &stream);
+
+ bool hasSection(const String &section) const;
+ void removeSection(const String &section);
+ void renameSection(const String &oldName, const String &newName);
+
+ bool hasKey(const String &key, const String &section) const;
+ bool getKey(const String &key, const String &section, String &value) const;
+ void setKey(const String &key, const String &section, const String &value);
+ void removeKey(const String &key, const String &section);
+
+
+ void listSections(StringSet &set);
+ void listKeyValues(StringMap &kv);
+
+private:
+ List<Section> _sections;
+
+ Section *getSection(const String &section);
+ const Section *getSection(const String &section) const;
+};
+
+/*
+- ConfigMan owns a config file
+- allow direct access to that config file (for the launcher)
+- simplify and unify the regular ConfigMan API in exchange
+
+
+*/
+
+} // End of namespace Common
+
+#endif
diff --git a/common/module.mk b/common/module.mk
index 1a7b47a73e..885264702b 100644
--- a/common/module.mk
+++ b/common/module.mk
@@ -1,6 +1,7 @@
MODULE := common
MODULE_OBJS := \
+ common/config-file.o \
common/config-manager.o \
common/file.o \
common/md5.o \