diff options
-rw-r--r-- | common/config-file.cpp | 308 | ||||
-rw-r--r-- | common/config-file.h | 131 | ||||
-rw-r--r-- | common/module.mk | 1 |
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 §ion) { + 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 §ion) 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 §ion) 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 §ion) { + assert(isValidName(key)); + assert(isValidName(section)); + + Section *s = getSection(section); + if (s) + return s->removeKey(key); +} + +bool ConfigFile::getKey(const String &key, const String §ion, 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 §ion, 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 §ion) { + 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 §ion) 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 §ion) const; + void removeSection(const String §ion); + void renameSection(const String &oldName, const String &newName); + + bool hasKey(const String &key, const String §ion) const; + bool getKey(const String &key, const String §ion, String &value) const; + void setKey(const String &key, const String §ion, const String &value); + void removeKey(const String &key, const String §ion); + + + void listSections(StringSet &set); + void listKeyValues(StringMap &kv); + +private: + List<Section> _sections; + + Section *getSection(const String §ion); + const Section *getSection(const String §ion) 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 \ |