diff options
| author | Lexi Winter <lexi@le-fay.org> | 2025-06-21 17:18:57 +0100 |
|---|---|---|
| committer | Lexi Winter <lexi@le-fay.org> | 2025-06-21 17:18:57 +0100 |
| commit | d27d1302d1fa1b96bf8f53f17fce947f19d21330 (patch) | |
| tree | f56ef29d7816c3b574b3359c746355a44e3b0819 | |
| parent | 75c6b5fee029ec95e7e45e18525e3e78b9616f48 (diff) | |
| download | nihil-d27d1302d1fa1b96bf8f53f17fce947f19d21330.tar.gz nihil-d27d1302d1fa1b96bf8f53f17fce947f19d21330.tar.bz2 | |
add nihil.config (incomplete)
| -rw-r--r-- | CMakeLists.txt | 19 | ||||
| -rw-r--r-- | nihil.config/CMakeLists.txt | 11 | ||||
| -rw-r--r-- | nihil.config/error.ccm | 26 | ||||
| -rw-r--r-- | nihil.config/nihil.config.ccm | 12 | ||||
| -rw-r--r-- | nihil.config/option.ccm | 84 | ||||
| -rw-r--r-- | nihil.config/store.ccm | 183 | ||||
| -rw-r--r-- | nihil.config/string.ccm | 54 | ||||
| -rw-r--r-- | nihil.ucl/CMakeLists.txt | 13 | ||||
| -rw-r--r-- | nihil.ucl/nihil.ucl.ccm | 9 | ||||
| -rw-r--r-- | nihil/CMakeLists.txt | 5 | ||||
| -rw-r--r-- | nihil/tests/CMakeLists.txt (renamed from tests/CMakeLists.txt) | 0 | ||||
| -rw-r--r-- | nihil/tests/command_map.cc (renamed from tests/command_map.cc) | 0 | ||||
| -rw-r--r-- | nihil/tests/ctype.cc (renamed from tests/ctype.cc) | 0 | ||||
| -rw-r--r-- | nihil/tests/fd.cc (renamed from tests/fd.cc) | 0 | ||||
| -rw-r--r-- | nihil/tests/generator.cc (renamed from tests/generator.cc) | 0 | ||||
| -rw-r--r-- | nihil/tests/generic_error.cc (renamed from tests/generic_error.cc) | 0 | ||||
| -rw-r--r-- | nihil/tests/getenv.cc (renamed from tests/getenv.cc) | 0 | ||||
| -rw-r--r-- | nihil/tests/guard.cc (renamed from tests/guard.cc) | 0 | ||||
| -rw-r--r-- | nihil/tests/next_word.cc (renamed from tests/next_word.cc) | 0 | ||||
| -rw-r--r-- | nihil/tests/skipws.cc (renamed from tests/skipws.cc) | 0 | ||||
| -rw-r--r-- | nihil/tests/spawn.cc (renamed from tests/spawn.cc) | 0 | ||||
| -rw-r--r-- | nihil/tests/tabulate.cc (renamed from tests/tabulate.cc) | 0 |
22 files changed, 414 insertions, 2 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 9df3b46..457f603 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,8 +4,14 @@ cmake_minimum_required(VERSION 3.28) project(libexi) +option(NIHIL_CONFIG "Build the nihil.config library" ON) +option(NIHIL_UCL "Build the nihil.ucl library" ON) +option(NIHIL_TESTS "Build nihil's unit tests" ON) + set(CMAKE_CXX_STANDARD 26) +find_package(PkgConfig REQUIRED) + add_compile_options(-W) add_compile_options(-Wall) add_compile_options(-Wextra) @@ -13,6 +19,15 @@ add_compile_options(-Werror) add_compile_options(-Wpedantic) add_subdirectory(nihil) -add_subdirectory(tests) -enable_testing() +if(NIHIL_UCL) + add_subdirectory(nihil.ucl) +endif() + +if(NIHIL_CONFIG) + add_subdirectory(nihil.config) +endif() + +if(NIHIL_TESTS) + enable_testing() +endif() diff --git a/nihil.config/CMakeLists.txt b/nihil.config/CMakeLists.txt new file mode 100644 index 0000000..d96b116 --- /dev/null +++ b/nihil.config/CMakeLists.txt @@ -0,0 +1,11 @@ +# This source code is released into the public domain. + +add_library(nihil.config STATIC) +target_sources(nihil.config PUBLIC + FILE_SET modules TYPE CXX_MODULES FILES + nihil.config.ccm + error.ccm + store.ccm + option.ccm + string.ccm) +target_link_libraries(nihil.config PUBLIC nihil nihil.ucl) diff --git a/nihil.config/error.ccm b/nihil.config/error.ccm new file mode 100644 index 0000000..0da91cb --- /dev/null +++ b/nihil.config/error.ccm @@ -0,0 +1,26 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <format> +#include <utility> + +export module nihil.config:error; + +import nihil; + +namespace nihil::config { + +/* + * Exception thrown when an issue occurs with the configuration. + */ +export struct error : generic_error { + template<typename... Args> + error(std::format_string<Args...> fmt, Args &&...args) + : generic_error(fmt, std::forward<Args>(args)...) + {} +}; + +} // namespace nihil::config diff --git a/nihil.config/nihil.config.ccm b/nihil.config/nihil.config.ccm new file mode 100644 index 0000000..0b12885 --- /dev/null +++ b/nihil.config/nihil.config.ccm @@ -0,0 +1,12 @@ +/* + * This source code is released into the public domain. + */ + +module; + +export module nihil.config; + +export import :error; +export import :option; +export import :store; +export import :string; diff --git a/nihil.config/option.ccm b/nihil.config/option.ccm new file mode 100644 index 0000000..207eb65 --- /dev/null +++ b/nihil.config/option.ccm @@ -0,0 +1,84 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <string> + +#include <ucl++.h> + +export module nihil.config:option; + +import nihil; +import :error; + +namespace nihil::config { + +/* + * Base class for options; this is what config_store interacts with. + */ + +export struct option +{ + // Short name of this option. + auto name(this option const &self) noexcept -> std::string_view + { + return self._name; + } + + // Human-readable description of this option. + auto description(this option const &self) noexcept -> std::string_view + { + return self._description; + } + + // If true, this option is set to its default value. + auto is_default(this option const &self) noexcept -> bool + { + return self._is_default; + } + + // Get or set this option as a string. + auto string(this option const &self) -> std::string + { + return self.get_string(); + } + + void string(this option &self, std::string_view value) + { + self.set_string(value); + self._is_default = false; + } + + /* + * Add this option to a UCL object. This is used when writing the + * configuration file. + */ + virtual void add_to_ucl(ucl_object_t *) const = 0; + + // Not copyable or movable. + option(option const &) = delete; + auto operator=(option const &) -> option& = delete; + +protected: + option(std::string_view name, + std::string_view description) + : _name(name) + , _description(description) + { + } + + /* + * Get or set this option as a string. + */ + virtual auto get_string() const -> std::string = 0; + virtual auto set_string(std::string_view) -> void = 0; + +private: + std::string _name; + std::string _description; + bool _is_default = true; +}; + +} // namespace nihil diff --git a/nihil.config/store.ccm b/nihil.config/store.ccm new file mode 100644 index 0000000..7ed4ccb --- /dev/null +++ b/nihil.config/store.ccm @@ -0,0 +1,183 @@ +/* + * This source code is released into the public domain. + */ + +module; + +/* + * The configuration store. There should only be one of these. + */ + +#include <coroutine> +#include <format> +#include <map> + +export module nihil.config:store; + +import nihil; +import :error; +import :option; + +namespace nihil::config { + +// Exception thrown on an attempt to fetch an undefined option. +export struct unknown_option final : error { + std::string varname; + + unknown_option(std::string_view varname_) + : error("unknown configuration variable '{}'", varname_) + , varname(varname_) + {} +}; + +export struct store final { + /* + * Get the global config store. + */ + static auto get() -> store& { + if (instance == nullptr) + instance = new store; + + return *instance; + } + + /* + * Initialise the global config store. + */ +#if 0 + void init(context const &ctx) { + std::string config_text; + + // Load the configuration text. + auto config_path = ctx.dbdir / "config.ucl"; + try { + read_file(config_path, std::back_inserter(config_text)); + } catch (io_error const &exc) { + // Ignore ENOENT, it simply means we haven't created the + // config file yet, so default values will be used. + if (exc.error == std::errc::no_such_file_or_directory) + return; + throw; + } + + // Parse the UCL. + + std::string err; + auto uclconfig = ucl::Ucl::parse(config_text, err); + + if (!uclconfig) + throw error("{0}: {1}", config_path, err); + + auto const &cfg = get(); + for (auto const &uclvalue : uclconfig) { + auto &value = cfg.fetch(uclvalue.key()); + + switch (uclvalue.type()) { + case UCL_INT: + value.integer(uclvalue.int_value()); + break; + case UCL_STRING: + value.string(uclvalue.string_value()); + break; + default: + throw error( + "INTERNAL ERROR: unknown value type {0}", + static_cast<int>(uclvalue.type())); + } + } + } +#endif + + /* + * Register a new value with the config store. + */ + auto register_option(this store &self, option *object) -> void + { + auto [it, okay] = self.options.insert( + std::pair{object->name(), object}); + + if (okay) + return; + + throw error("INTERNAL ERROR: attempt to register " + "duplicate config value '{0}'", + object->name()); + } + + /* + * Fetch an existing value in the config store. + */ + auto fetch(this store const &self, std::string_view name) + -> option & + { + if (auto it = self.options.find(name); it != self.options.end()) + return *it->second; + + throw unknown_option(name); + } + + /* + * Fetch all values in the configuration store. + */ + auto all(this auto &&self) -> nihil::generator<option const &> + { + for (auto &&it : self.options) + co_yield *it.second; + } + + /* + * Write all config values (except defaults) to disk. + */ +#if 0 + void store::write_all(this store const &self, context const &ctx) { + // The UCL C++ API doesn't seem to support creating new objects + // from scratch, so we use the C API here. We should probably + // provider a better wrapper for this. + + auto ucl = ::ucl_object_typed_new(UCL_OBJECT); + auto ucl_guard = guard([ucl] { ::ucl_object_unref(ucl); }); + + // Add all the options to the UCL object. + for (auto const &option : self.fetch_all()) { + if (option.is_default) + continue; + + option.add_to_ucl(ucl); + } + + // Dump the UCL object to a string. + auto *ucl_c_text = reinterpret_cast<char *>( + ::ucl_object_emit(ucl, UCL_EMIT_CONFIG)); + //NOLINTNEXTLINE(cppcoreguidelines-no-malloc) + auto ucl_text_guard = guard([ucl_c_text] { ::free(ucl_c_text); }); + std::string ucl_text(ucl_c_text); + + // Write the object to a file. + auto config_path = ctx.dbdir / "config.ucl"; + + try { + safe_write_file(config_path, ucl_text); + } catch (io_error const &exc) { + throw error("{}", exc.what()); + } + } +#endif + + // Not movable or copyable. + store(store const &) = delete; + store(store &&) = delete; + store& operator=(store const &) = delete; + store& operator=(store &&) = delete; + +private: + /* + * The global configuration store, created by init() and accessed via + * get(). + */ + inline static store *instance = nullptr; + + std::map<std::string_view, option *> options; + store() = default; +}; + +} // namespace nihil::config diff --git a/nihil.config/string.ccm b/nihil.config/string.ccm new file mode 100644 index 0000000..f3273c3 --- /dev/null +++ b/nihil.config/string.ccm @@ -0,0 +1,54 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <stdexcept> +#include <string> + +#include <ucl++.h> + +export module nihil.config:string; + +import nihil; +import :option; +import :store; + +namespace nihil::config { + +struct string final : option +{ + string(std::string &storage, + std::string_view name, + std::string_view description) noexcept + : option(name, description) + , _storage(storage) + { + store::get().register_option(this); + } + + auto get_string() const -> std::string override + { + return _storage; + }; + + auto set_string(std::string_view new_value) -> void override + { + _storage = new_value; + } + + auto add_to_ucl(ucl_object_t *ucl) const -> void override + { + auto ucl_value = ucl_object_fromstring_common( + _storage.data(), _storage.size(), + UCL_STRING_RAW); + ucl_object_insert_key(ucl, ucl_value, + name().data(), name().size(), true); + } + +private: + std::string &_storage; +}; + +} // namespace nihil::config diff --git a/nihil.ucl/CMakeLists.txt b/nihil.ucl/CMakeLists.txt new file mode 100644 index 0000000..59021aa --- /dev/null +++ b/nihil.ucl/CMakeLists.txt @@ -0,0 +1,13 @@ +# This source code is released into the public domain. + +pkg_check_modules(LIBUCL REQUIRED libucl) + +add_library(nihil.ucl STATIC) +target_sources(nihil.ucl PUBLIC + FILE_SET modules TYPE CXX_MODULES FILES + nihil.ucl.ccm) +target_compile_options(nihil.ucl PUBLIC ${LIBUCL_CFLAGS_OTHER}) +target_include_directories(nihil.ucl PUBLIC ${LIBUCL_INCLUDE_DIRS}) +target_link_libraries(nihil.ucl PUBLIC ${LIBUCL_LIBRARIES}) +target_link_directories(nihil.ucl PUBLIC ${LIBUCL_LIBRARY_DIRS}) + diff --git a/nihil.ucl/nihil.ucl.ccm b/nihil.ucl/nihil.ucl.ccm new file mode 100644 index 0000000..23151e0 --- /dev/null +++ b/nihil.ucl/nihil.ucl.ccm @@ -0,0 +1,9 @@ +/* + * This source code is released into the public domain. + */ + +module; + +export module nihil.ucl; + +// TODO: Implement. diff --git a/nihil/CMakeLists.txt b/nihil/CMakeLists.txt index 81f11c6..72f7e22 100644 --- a/nihil/CMakeLists.txt +++ b/nihil/CMakeLists.txt @@ -20,3 +20,8 @@ target_sources(nihil PUBLIC spawn.ccm tabulate.ccm usage_error.ccm) + +if(NIHIL_TESTS) + add_subdirectory(tests) + enable_testing() +endif() diff --git a/tests/CMakeLists.txt b/nihil/tests/CMakeLists.txt index abeab88..abeab88 100644 --- a/tests/CMakeLists.txt +++ b/nihil/tests/CMakeLists.txt diff --git a/tests/command_map.cc b/nihil/tests/command_map.cc index 75b6f0d..75b6f0d 100644 --- a/tests/command_map.cc +++ b/nihil/tests/command_map.cc diff --git a/tests/ctype.cc b/nihil/tests/ctype.cc index 87f5103..87f5103 100644 --- a/tests/ctype.cc +++ b/nihil/tests/ctype.cc diff --git a/tests/fd.cc b/nihil/tests/fd.cc index fbf353e..fbf353e 100644 --- a/tests/fd.cc +++ b/nihil/tests/fd.cc diff --git a/tests/generator.cc b/nihil/tests/generator.cc index 8657756..8657756 100644 --- a/tests/generator.cc +++ b/nihil/tests/generator.cc diff --git a/tests/generic_error.cc b/nihil/tests/generic_error.cc index b213af9..b213af9 100644 --- a/tests/generic_error.cc +++ b/nihil/tests/generic_error.cc diff --git a/tests/getenv.cc b/nihil/tests/getenv.cc index adfa84f..adfa84f 100644 --- a/tests/getenv.cc +++ b/nihil/tests/getenv.cc diff --git a/tests/guard.cc b/nihil/tests/guard.cc index f88aa9b..f88aa9b 100644 --- a/tests/guard.cc +++ b/nihil/tests/guard.cc diff --git a/tests/next_word.cc b/nihil/tests/next_word.cc index 4055485..4055485 100644 --- a/tests/next_word.cc +++ b/nihil/tests/next_word.cc diff --git a/tests/skipws.cc b/nihil/tests/skipws.cc index 2159e2e..2159e2e 100644 --- a/tests/skipws.cc +++ b/nihil/tests/skipws.cc diff --git a/tests/spawn.cc b/nihil/tests/spawn.cc index 455223e..455223e 100644 --- a/tests/spawn.cc +++ b/nihil/tests/spawn.cc diff --git a/tests/tabulate.cc b/nihil/tests/tabulate.cc index 84f8b33..84f8b33 100644 --- a/tests/tabulate.cc +++ b/nihil/tests/tabulate.cc |
