diff options
author | Andreas Kling <awesomekling@gmail.com> | 2019-04-15 02:22:08 +0200 |
---|---|---|
committer | Andreas Kling <awesomekling@gmail.com> | 2019-04-15 02:23:20 +0200 |
commit | bc5148354f6ad777ed9ba2988bd0547b16956102 (patch) | |
tree | e688f5f1ca70dd6da6a08527bf49a8021046bae1 | |
parent | 37c27e2e39e4f862574aca44076c5693c1befdad (diff) | |
download | serenity-bc5148354f6ad777ed9ba2988bd0547b16956102.zip |
LibCore: Add a CConfigFile class, a simple INI file parser.
You open the configuration for an app like so:
auto config = CConfigFile::get_for_app("MyApp");
This will then open ~/MyApp.ini and parse it for you.
Immediately start using it in Minesweeper to load the field size and mine
count from a config file.
-rw-r--r-- | AK/HashMap.h | 24 | ||||
-rw-r--r-- | Games/Minesweeper/Field.cpp | 7 | ||||
-rw-r--r-- | LibCore/CConfigFile.cpp | 195 | ||||
-rw-r--r-- | LibCore/CConfigFile.h | 45 | ||||
-rw-r--r-- | LibCore/Makefile | 1 |
5 files changed, 269 insertions, 3 deletions
diff --git a/AK/HashMap.h b/AK/HashMap.h index 952777ab34..df802f6702 100644 --- a/AK/HashMap.h +++ b/AK/HashMap.h @@ -1,8 +1,9 @@ #pragma once -#include "HashTable.h" -#include "StdLibExtras.h" -#include "kstdio.h" +#include <AK/HashTable.h> +#include <AK/StdLibExtras.h> +#include <AK/Vector.h> +#include <AK/kstdio.h> namespace AK { @@ -88,6 +89,23 @@ public: m_table.remove(it); } + V& ensure(const K& key) + { + auto it = find(key); + if (it == end()) + set(key, V()); + return find(key)->value; + } + + Vector<K> keys() const + { + Vector<K> list; + list.ensure_capacity(size()); + for (auto& it : *this) + list.unchecked_append(it.key); + return list; + } + private: HashTable<Entry, EntryTraits> m_table; }; diff --git a/Games/Minesweeper/Field.cpp b/Games/Minesweeper/Field.cpp index 853f1c975f..1480a445f8 100644 --- a/Games/Minesweeper/Field.cpp +++ b/Games/Minesweeper/Field.cpp @@ -2,6 +2,7 @@ #include <LibGUI/GButton.h> #include <LibGUI/GLabel.h> #include <AK/HashTable.h> +#include <LibCore/CConfigFile.h> #include <unistd.h> #include <time.h> @@ -30,6 +31,12 @@ Field::Field(GLabel& flag_label, GLabel& time_label, GButton& face_button, GWidg , m_flag_label(flag_label) , m_time_label(time_label) { + auto config = CConfigFile::get_for_app("Minesweeper"); + + m_mine_count = config->read_num_entry("Game", "MineCount", 10); + m_rows = config->read_num_entry("Game", "Rows", 9); + m_columns = config->read_num_entry("Game", "Columns", 9); + m_timer.on_timeout = [this] { m_time_label.set_text(String::format("%u", ++m_seconds_elapsed)); }; diff --git a/LibCore/CConfigFile.cpp b/LibCore/CConfigFile.cpp new file mode 100644 index 0000000000..e4355e6cab --- /dev/null +++ b/LibCore/CConfigFile.cpp @@ -0,0 +1,195 @@ +#include <LibCore/CConfigFile.h> +#include <LibCore/CFile.h> +#include <AK/StringBuilder.h> +#include <stdio.h> +#include <pwd.h> +#include <unistd.h> + +Retained<CConfigFile> CConfigFile::get_for_app(const String& app_name) +{ + String home_path; + if (auto* home_env = getenv("HOME")) { + home_path = home_env; + } else { + uid_t uid = getuid(); + if (auto* pwd = getpwuid(uid)) + home_path = pwd->pw_dir; + } + if (home_path.is_empty()) + home_path = String::format("/tmp"); + auto path = String::format("%s/%s.ini", home_path.characters(), app_name.characters()); + return adopt(*new CConfigFile(path)); +} + +CConfigFile::CConfigFile(const String& file_name) + : m_file_name(file_name) +{ + reparse(); +} + +CConfigFile::~CConfigFile() +{ + sync(); +} + +void CConfigFile::reparse() +{ + m_groups.clear(); + + CFile file(m_file_name); + if (!file.open(CIODevice::OpenMode::ReadOnly)) + return; + + HashMap<String, String>* current_group = nullptr; + + while (file.can_read_line()) { + auto line = file.read_line(BUFSIZ); + auto* cp = (const char*)line.pointer(); + + while(*cp && (*cp == ' ' || *cp == '\t' || *cp == '\n')) + ++cp; + + switch (*cp) { + case '\0':// EOL... + case '#': // Comment, skip entire line. + case ';': // -||- + continue; + case '[': { // Start of new group. + StringBuilder builder; + ++cp; // Skip the '[' + while (*cp && (*cp != ']')) + builder.append(*(cp++)); + current_group = &m_groups.ensure(builder.to_string()); + break; + } + default: { // Start of key{ + StringBuilder key_builder; + StringBuilder value_builder; + while (*cp && (*cp != '=')) + key_builder.append(*(cp++)); + ++cp; // Skip the '=' + while (*cp && (*cp != '\n')) + value_builder.append(*(cp++)); + if (!current_group) { + // We're not in a group yet, create one with the name ""... + current_group = &m_groups.ensure(""); + } + current_group->set(key_builder.to_string(), value_builder.to_string()); + } + } + } +} + +String CConfigFile::read_entry(const String& group, const String& key, const String& default_value) const +{ + if (!has_key(group, key)) { + const_cast<CConfigFile&>(*this).write_entry(group, key, default_value); + return default_value; + } + auto it = m_groups.find(group); + auto jt = it->value.find(key); + return jt->value; +} + +int CConfigFile::read_num_entry(const String& group, const String &key, int default_value) const +{ + if (!has_key(group, key)) { + const_cast<CConfigFile&>(*this).write_num_entry(group, key, default_value); + return default_value; + } + + bool ok; + int value = read_entry(group, key).to_uint(ok); + if (!ok) + return default_value; + return value; +} + +bool CConfigFile::read_bool_entry(const String& group, const String &key, bool default_value) const +{ + return read_entry(group, key, default_value ? "1" : "0") == "1"; +} + +void CConfigFile::write_entry(const String& group, const String& key, const String& value) +{ + m_groups.ensure(group).ensure(key) = value; + m_dirty = true; +} + +void CConfigFile::write_num_entry(const String& group, const String& key, int value) +{ + write_entry(group, key, String::format("%d", value)); +} + +bool CConfigFile::sync() +{ + if (!m_dirty) + return true; + + FILE *fp = fopen(m_file_name.characters(), "wb"); + if (!fp) + return false; + + for (auto& it : m_groups) { + fprintf(fp, "[%s]\n", it.key.characters()); + for (auto& jt : it.value) + fprintf(fp, "%s=%s\n", jt.key.characters(), jt.value.characters()); + fprintf(fp, "\n"); + } + + fclose(fp); + + m_dirty = false; + return true; +} + +void CConfigFile::dump() const +{ + for (auto& it : m_groups) { + printf("[%s]\n", it.key.characters()); + for (auto& jt : it.value) + printf("%s=%s\n", jt.key.characters(), jt.value.characters()); + printf("\n"); + } +} + +Vector<String> CConfigFile::groups() const +{ + return m_groups.keys(); +} + +Vector<String> CConfigFile::keys(const String& group) const +{ + auto it = m_groups.find(group); + if (it == m_groups.end()) + return { }; + return it->value.keys(); +} + +bool CConfigFile::has_key(const String& group, const String& key) const +{ + auto it = m_groups.find(group); + if (it == m_groups.end()) + return { }; + return it->value.contains(key); +} + +bool CConfigFile::has_group(const String& group) const +{ + return m_groups.contains(group); +} + +void CConfigFile::remove_group(const String& group) +{ + m_groups.remove(group); + m_dirty = true; +} + +void CConfigFile::remove_entry(const String& group, const String& key) +{ + auto it = m_groups.find(group); + if (it == m_groups.end()) + return; + it->value.remove(key); + m_dirty = true; +} diff --git a/LibCore/CConfigFile.h b/LibCore/CConfigFile.h new file mode 100644 index 0000000000..fa3f5d9f7c --- /dev/null +++ b/LibCore/CConfigFile.h @@ -0,0 +1,45 @@ +#pragma once + +#include <AK/Vector.h> +#include <AK/HashMap.h> +#include <AK/AKString.h> +#include <AK/Retainable.h> +#include <AK/RetainPtr.h> + +class CConfigFile : public Retainable<CConfigFile> { +public: + static Retained<CConfigFile> get_for_app(const String& app_name); + ~CConfigFile(); + + bool has_group(const String&) const; + bool has_key(const String& group, const String& key) const; + + Vector<String> groups() const; + Vector<String> keys(const String& group) const; + + String read_entry(const String& group, const String& key, const String& default_vaule = String()) const; + int read_num_entry(const String& group, const String& key, int default_value = 0) const; + bool read_bool_entry(const String& group, const String& key, bool default_value = false) const; + + void write_entry(const String& group, const String& key, const String &value); + void write_num_entry(const String& group, const String& key, int value); + void write_bool_entry(const String& group, const String& key, bool value); + + void dump() const; + + bool is_dirty() const { return m_dirty; } + + bool sync(); + + void remove_group(const String& group); + void remove_entry(const String& group, const String& key); + +private: + explicit CConfigFile(const String& file_name); + + void reparse(); + + String m_file_name; + HashMap<String, HashMap<String, String>> m_groups; + bool m_dirty { false }; +}; diff --git a/LibCore/Makefile b/LibCore/Makefile index 6e5dda7f5a..8331bb64bd 100644 --- a/LibCore/Makefile +++ b/LibCore/Makefile @@ -13,6 +13,7 @@ OBJS = \ CObject.o \ CTimer.o \ CEventLoop.o \ + CConfigFile.o \ CEvent.o LIBRARY = libcore.a |