From 62bb1ffdee060819657161e260e75e3e1df017ac Mon Sep 17 00:00:00 2001 From: pommicket Date: Tue, 16 Sep 2025 17:08:19 -0400 Subject: C++ library fixes --- CMakeLists.txt | 13 +++- Makefile | 2 +- cpp/README.md | 5 ++ cpp/examples/all_functions.cpp | 139 +++++++++++++++++++++++++++++++++++++++++ cpp/examples/conf.pom | 17 +++++ cpp/pom.cpp | 83 +++++++++++++++--------- cpp/pom.hpp | 22 ++++++- examples/all_functions.c | 1 + pom.c | 1 + pre-commit.sh | 12 ++-- 10 files changed, 259 insertions(+), 36 deletions(-) create mode 100644 cpp/README.md create mode 100644 cpp/examples/all_functions.cpp create mode 100644 cpp/examples/conf.pom diff --git a/CMakeLists.txt b/CMakeLists.txt index ff27660..4a44c39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.0...3.31) project(pom) +option(LIBPOM_CXX "Build C++ library" ON) + if (MSVC) add_compile_options(/W4) else() @@ -33,7 +35,7 @@ target_link_libraries(example_all_functions pom) install(TARGETS pom pom-shared DESTINATION lib) install(FILES pom.h DESTINATION include) -if (BUILD_CXX) +if (LIBPOM_CXX) # C++ library add_library(pom++ STATIC cpp/pom.cpp) @@ -49,4 +51,13 @@ if (BUILD_CXX) target_include_directories(example++_read_conf PRIVATE cpp) target_link_libraries(example++_read_conf pom++) + # C++ Examples + add_executable(example++_all_functions cpp/examples/all_functions.cpp) + target_include_directories(example++_all_functions PRIVATE cpp) + target_link_libraries(example++_all_functions pom++) + + # Installation + install(TARGETS pom++ pom++-shared DESTINATION lib) + install(FILES cpp/pom.hpp DESTINATION include) + endif() diff --git a/Makefile b/Makefile index 0561492..0629ef3 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ PROFILE ?= Release BUILD_DIR ?= $(PROFILE) __build: mkdir -p $(BUILD_DIR) - P=`pwd` && cd $(BUILD_DIR) && cmake -DCMAKE_BUILD_TYPE=$(PROFILE) -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -DBUILD_CXX=yes $$P + P=`pwd` && cd $(BUILD_DIR) && cmake -DCMAKE_BUILD_TYPE=$(PROFILE) -DCMAKE_EXPORT_COMPILE_COMMANDS=1 $$P $(MAKE) -C $(BUILD_DIR) test: __build diff --git a/cpp/README.md b/cpp/README.md new file mode 100644 index 0000000..5367cfe --- /dev/null +++ b/cpp/README.md @@ -0,0 +1,5 @@ +# libpom++ + +C++ parser for the [POM configuration language](https://pom.computer). + +Requires at least C++17. diff --git a/cpp/examples/all_functions.cpp b/cpp/examples/all_functions.cpp new file mode 100644 index 0000000..326ae39 --- /dev/null +++ b/cpp/examples/all_functions.cpp @@ -0,0 +1,139 @@ +// Demonstrates almost all of libpom++'s API + +#include +#include +#include +#include + +class FdReader: public pom::Reader { +public: + explicit FdReader(const char *path) { + m_fd = open(path, O_RDONLY); + if (m_fd == -1) { + throw "file doesn't exist"; + } + } + ~FdReader() { close(m_fd); } + size_t read(char *buf, size_t size) { + size_t total_read = 0; + while (true) { + // must call read in a loop to fill buf up as much as possible! + // (read isn't guaranteed to read len bytes even if it could) + ssize_t ret = ::read(m_fd, buf, size); + if (ret < 0) { + // read error + throw "read error"; + } else if (ret == 0) { + // end-of-file + break; + } else { + total_read += ret; + buf += ret; + size -= ret; + } + } + return total_read; + } +private: + int m_fd; +}; + +int main(void) { + try { + // ordinary usage: load from a path + pom::Configuration conf("conf.pom"); + // get a key + std::optional indentation_type = conf.get("indentation-type"); + std::cout << "Indentation type: " << indentation_type.value_or("(none)") << "\n"; + + // load configuration with custom settings and a custom reader + pom::Settings settings = {}; + settings.set_error_language("fr"); // erreurs en français + FdReader reader("conf.pom"); + conf = pom::Configuration( + "conf.pom", // file name for error messages + reader, // object used to read file + &settings // settings + ); + + // nicer way of doing what we did above + std::cout << "Indentation type: " + << conf.get_or_default("indentation-type", "(none)") + << "\n"; + + // parse value as signed integer + std::optional tab_size = conf.get_int("tab-size"); + std::cout << "tab size: "; + if (tab_size.has_value()) + std::cout << tab_size.value(); + else + std::cout << "(none set)"; + std::cout << "\n"; + + // parse value as unsigned integer, use default of 2 + std::cout << "padding pixels: " + << conf.get_uint_or_default("padding-pixels", 2) + << "\n"; + + // parse value as double + std::cout << "font size: " + << conf.get_float_or_default("font-size", 12.5) + << "\n"; + + // parse value as boolean + std::cout << "show line numbers: " + << (conf.get_bool_or_default("show-line-numbers", true) ? "yes" : "no") + << "\n"; + + // extract section out of configuration + pom::Configuration file_extensions = conf.section("file-extensions"); + + // parse value as list + std::vector Cpp_extensions = file_extensions.get_list_or_default("Cpp", {}); + if (!Cpp_extensions.empty()) { + for (const std::string &extension: Cpp_extensions) + std::cout << "C++ extension: " << extension << "\n"; + } else { + printf("no extensions defined for C++\n"); + } + + // iterate over keys in section + pom::Configuration plugins = conf.section("plug-in"); + for (std::string &key: plugins.keys()) { + pom::Configuration plugin = plugins.section(key); + auto path = plugin.get_or_default("path", "(none)"); + bool enabled = plugin.get_bool_or_default("enabled", true); + // get location where key was defined + pom::Location location = plugins.location(key).value(); + + std::cout << location.file() << ":" << location.line() + << ": plug-in " << key + << " (path = " << path << + ", enabled = " << enabled << ")\n"; + } + + // load config from string + pom::Configuration overrides("", "tab-size = 12"); + + // merge configurations + conf.merge(overrides); + + // iterate over items (key-value pairs) in configuration + for (const auto &item: conf.items()) { + if (item->key().find('b') != std::string_view::npos) + std::cout << item->key() << ": " << item->value() << "\n"; + } + + // iterate over all the keys which haven't been accessed directly + for (const std::string &key: conf.unread_keys()) { + std::cout << "unrecognized key " << key << "\n"; + } + } catch (pom::Error &error) { // error from libpom++ + std::cerr << error; + return EXIT_FAILURE; + } catch (const char *error) { // error from FdReader + std::cerr << error << "\n"; + return EXIT_FAILURE; + } + return 0; +} diff --git a/cpp/examples/conf.pom b/cpp/examples/conf.pom new file mode 100644 index 0000000..82e1abd --- /dev/null +++ b/cpp/examples/conf.pom @@ -0,0 +1,17 @@ +indentation-type = tabs +show-line-numbers = yes +tab-size = 4 +font-size = "18" + +[file-extensions] +C = .c, .h +Cpp = .cpp, .hpp, .cc, .hh + +[plug-in.edit-over-ssh] +path = ~/misc/edit-over-ssh.so +enabled = yes + +[plug-in.wrap-text] +path = ~/misc/wrap_text_v3.5.7.so +enabled = no + diff --git a/cpp/pom.cpp b/cpp/pom.cpp index 7c2651c..ffa70a2 100644 --- a/cpp/pom.cpp +++ b/cpp/pom.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace pom { @@ -55,6 +56,7 @@ std::ostream &operator<<(std::ostream &o, Error &e) { } Configuration Configuration::section(std::string_view name) const { + if (!C) return {}; std::string name_str(name); const pom_conf *C_section = pom_conf_section( static_cast(C), name_str.c_str()); @@ -95,8 +97,8 @@ static size_t readable_read(void *udata, char *buf, size_t count) { return static_cast(udata)->read(buf, count); } -Configuration::Configuration(std::string_view filename, Reader &source, const Settings *settings) { - pom_settings C_settings = {}; +void Configuration::load(std::string_view filename, Reader &source, const Settings *settings) { + pom_settings C_settings = {}; if (settings) { settings->to_C(static_cast(&C_settings)); } @@ -108,8 +110,12 @@ Configuration::Configuration(std::string_view filename, Reader &source, const Se } } +Configuration::Configuration(std::string_view filename, Reader &source, const Settings *settings) { + load(filename, source, settings); +} + Configuration::Configuration(std::string_view path, const Settings *settings) { - pom_settings C_settings = {}; + pom_settings C_settings = {}; if (settings) { settings->to_C(static_cast(&C_settings)); } @@ -135,7 +141,7 @@ private: Configuration::Configuration(std::string_view filename, std::string_view string, const Settings *settings) { StringViewReader reader(string); - Configuration(filename, reader, settings); + load(filename, reader, settings); } class IStreamReader: public Reader { @@ -152,7 +158,7 @@ private: Configuration::Configuration(std::string_view filename, std::istream &stream, const Settings *settings) { IStreamReader reader(stream); - Configuration(filename, reader, settings); + load(filename, reader, settings); } void Configuration::merge(const Configuration &other) { @@ -183,6 +189,7 @@ std::string Configuration::get_or_default(std::string_view key, std::string_view } std::vector Configuration::unread_keys() const { + if (!C) return {}; std::vector unread; pom_unread_key_iter *iter = nullptr; const char *key; @@ -193,6 +200,7 @@ std::vector Configuration::unread_keys() const { } std::vector Configuration::keys() const { + if (!C) return {}; std::vector keys; pom_key_iter *iter = nullptr; const char *key; @@ -220,6 +228,7 @@ public: }; std::vector> Configuration::items() const { + if (!C) return {}; std::vector> items; pom_item_iter *iter = nullptr; const pom_item *item; @@ -230,6 +239,7 @@ std::vector> Configuration::items() const { } std::optional Configuration::get_int(std::string_view key) const { + if (!C) return {}; std::string key_str(key); if (!pom_conf_has(static_cast(C), key_str.c_str())) { return {}; @@ -242,15 +252,11 @@ std::optional Configuration::get_int(std::string_view key) const { } int64_t Configuration::get_int_or_default(std::string_view key, int64_t dflt) const { - std::string key_str(key); - int64_t value; - pom_error *error = pom_conf_get_int_or_default(static_cast(C), key_str.c_str(), &value, dflt); - if (error) - throw Error(error); - return value; + return get_int(key).value_or(dflt); } std::optional Configuration::get_uint(std::string_view key) const { + if (!C) return {}; std::string key_str(key); if (!pom_conf_has(static_cast(C), key_str.c_str())) { return {}; @@ -263,15 +269,11 @@ std::optional Configuration::get_uint(std::string_view key) const { } uint64_t Configuration::get_uint_or_default(std::string_view key, uint64_t dflt) const { - std::string key_str(key); - uint64_t value; - pom_error *error = pom_conf_get_uint_or_default(static_cast(C), key_str.c_str(), &value, dflt); - if (error) - throw Error(error); - return value; + return get_uint(key).value_or(dflt); } std::optional Configuration::get_float(std::string_view key) const { + if (!C) return {}; std::string key_str(key); if (!pom_conf_has(static_cast(C), key_str.c_str())) { return {}; @@ -284,15 +286,11 @@ std::optional Configuration::get_float(std::string_view key) const { } double Configuration::get_float_or_default(std::string_view key, double dflt) const { - std::string key_str(key); - double value; - pom_error *error = pom_conf_get_float_or_default(static_cast(C), key_str.c_str(), &value, dflt); - if (error) - throw Error(error); - return value; + return get_float(key).value_or(dflt); } std::optional Configuration::get_bool(std::string_view key) const { + if (!C) return {}; std::string key_str(key); if (!pom_conf_has(static_cast(C), key_str.c_str())) { return {}; @@ -305,15 +303,30 @@ std::optional Configuration::get_bool(std::string_view key) const { } bool Configuration::get_bool_or_default(std::string_view key, bool dflt) const { + return get_bool(key).value_or(dflt); +} + +std::optional> Configuration::get_list(std::string_view key) const { + if (!C) return {}; std::string key_str(key); - bool value; - pom_error *error = pom_conf_get_bool_or_default(static_cast(C), key_str.c_str(), &value, dflt); - if (error) - throw Error(error); - return value; + char **list = pom_conf_get_list(static_cast(C), key_str.c_str()); + std::vector vec; + for (size_t i = 0; list[i]; i++) + vec.emplace_back(list[i]); + free(list); + return vec; +} + +std::vector Configuration::get_list_or_default(std::string_view key, const std::vector &dflt) const { + auto list = get_list(key); + if (list.has_value()) + return list.value(); + else + return dflt; } Configuration &Configuration::operator=(const Configuration &other) { + pom_conf_free(static_cast(C)); C = static_cast(pom_conf_copy(static_cast(other.C))); return *this; } @@ -324,5 +337,19 @@ std::ostream &operator<<(std::ostream &o, const Configuration &conf) { return o; } +std::optional Configuration::location(std::string_view key) const { + if (!C) return {}; + const char *file; + uint64_t line; + std::string key_str(key); + if (pom_conf_location(static_cast(C), key_str.c_str(), &file, &line)) + return Location(file, line); + else + return {}; +} + +std::ostream &operator<<(std::ostream &o, const Location &location) { + return o << location.file() << ":" << location.line(); +} } // namespace pom diff --git a/cpp/pom.hpp b/cpp/pom.hpp index f9d7475..69a979d 100644 --- a/cpp/pom.hpp +++ b/cpp/pom.hpp @@ -53,11 +53,13 @@ public: } void set_error_language(std::string_view lang); private: + void check_version() const; void to_C(void *C) const; friend class Configuration; char m_error_lang[16] = {}; std::shared_ptr m_allocator; - uint32_t version = 1; // future-proofing + // to allow for future extensions without breaking backwards compatibility + const uint32_t version = 1; }; class Reader { @@ -78,6 +80,20 @@ public: virtual uint64_t line() const noexcept = 0; }; +class Location { +public: + inline std::string_view file() const { return m_file; } + inline uint64_t line() const { return m_line; } +private: + inline Location(std::string file, uint64_t line): + m_file(file), m_line(line) {} + friend class Configuration; + std::string m_file; + uint64_t m_line; + void *_reserved[4] = {}; +}; +std::ostream &operator<<(std::ostream &, const Location &); + class Configuration { public: Configuration(); @@ -88,6 +104,7 @@ public: Configuration(std::string_view path, const Settings *settings = nullptr); Configuration(std::string_view filename, std::string_view string, const Settings *settings = nullptr); ~Configuration(); + std::optional location(std::string_view key) const; std::optional get(std::string_view key) const; std::string get_or_default(std::string_view key, std::string_view dflt) const; std::optional get_int(std::string_view key) const; @@ -98,12 +115,15 @@ public: double get_float_or_default(std::string_view key, double dflt) const; std::optional get_bool(std::string_view key) const; bool get_bool_or_default(std::string_view key, bool dflt) const; + std::optional> get_list(std::string_view key) const; + std::vector get_list_or_default(std::string_view key, const std::vector &dflt) const; Configuration section(std::string_view name) const; std::vector unread_keys() const; std::vector keys() const; std::vector> items() const; void merge(const Configuration &other); private: + void load(std::string_view filename, Reader &source, const Settings *settings); explicit Configuration(void *c): C(c) {} void *C; }; diff --git a/examples/all_functions.c b/examples/all_functions.c index 18cce6f..413d114 100644 --- a/examples/all_functions.c +++ b/examples/all_functions.c @@ -150,6 +150,7 @@ int main(void) { printf("\t%s: %s\n", item->key, item->value); } + // iterate over all the keys which haven't been accessed directly pom_unread_key_iter *unread_iter = NULL; const char *unread; while ((unread = pom_conf_next_unread_key(copy, &unread_iter))) { diff --git a/pom.c b/pom.c index 5e98e4f..99102da 100644 --- a/pom.c +++ b/pom.c @@ -1270,6 +1270,7 @@ pom_load_path(const pom_settings *psettings, const char *path, pom_error **error void pom_conf_free(pom_conf *conf) { + if (!conf) return; conf_free(conf->main); } diff --git a/pre-commit.sh b/pre-commit.sh index 0510a5c..bde503b 100755 --- a/pre-commit.sh +++ b/pre-commit.sh @@ -5,9 +5,11 @@ which doxygen >/dev/null 2>/dev/null && { doxygen || exit 1; } make -j`nproc` test || exit 1 -if [ "$NO_TIDY" = '' ] && \ - which clang-tidy >/dev/null && \ - git diff --name-status HEAD -- pom.c | grep -q 'M\s*pom.c'; then - echo 'Running clang-tidy...' - clang-tidy pom.c || exit 1 +if [ "$NO_TIDY" = '' ] && which clang-tidy >/dev/null; then + for file in pom.c cpp/pom.cpp; do + if git diff --name-status HEAD -- $file | grep -q 'M\s*'"$file"; then + echo "clang-tidy $file..." + clang-tidy $file || exit 1 + fi + done fi -- cgit v1.2.3