| #ifndef _UCI_CXX_HPP |
| #define _UCI_CXX_HPP |
| |
| #include <uci.h> |
| #include <memory> |
| #include <mutex> |
| #include <stdexcept> |
| #include <string> |
| #include <string_view> |
| |
| namespace uci { |
| |
| template <class T> |
| class iterator { // like uci_foreach_element_safe. |
| |
| private: |
| const uci_ptr& _ptr; |
| |
| uci_element* _it = nullptr; |
| |
| uci_element* _next = nullptr; |
| |
| // wrapper for clang-tidy |
| inline auto _list_to_element(const uci_list* cur) -> uci_element* |
| { |
| // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic,cppcoreguidelines-pro-type-cstyle-cast) |
| return list_to_element(cur); // macro casting container=pointer-offset. |
| } |
| |
| public: |
| inline explicit iterator(const uci_ptr& ptr, const uci_list* cur) |
| : _ptr{ptr}, _it{_list_to_element(cur)} |
| { |
| _next = _list_to_element(_it->list.next); |
| } |
| |
| inline iterator(iterator&&) noexcept = default; |
| |
| inline iterator(const iterator&) = delete; |
| |
| inline auto operator=(const iterator&) -> iterator& = delete; |
| |
| inline auto operator=(iterator &&) -> iterator& = delete; |
| |
| auto operator*() -> T |
| { |
| return T{_ptr, _it}; |
| } |
| |
| inline auto operator!=(const iterator& rhs) -> bool |
| { |
| return (&_it->list != &rhs._it->list); |
| } |
| |
| inline auto operator++() -> iterator& |
| { |
| _it = _next; |
| _next = _list_to_element(_next->list.next); |
| return *this; |
| } |
| |
| inline ~iterator() = default; |
| }; |
| |
| class locked_context { |
| private: |
| static std::mutex inuse; |
| |
| public: |
| inline locked_context() |
| { |
| inuse.lock(); |
| } |
| |
| inline locked_context(locked_context&&) noexcept = default; |
| |
| inline locked_context(const locked_context&) = delete; |
| |
| inline auto operator=(const locked_context&) -> locked_context& = delete; |
| |
| inline auto operator=(locked_context &&) -> locked_context& = delete; |
| |
| // NOLINTNEXTLINE(readability-convert-member-functions-to-static) |
| inline auto get() -> uci_context* // is member to enforce inuse |
| { |
| static auto free_ctx = [](uci_context* ctx) { uci_free_context(ctx); }; |
| static std::unique_ptr<uci_context, decltype(free_ctx)> lazy_ctx{uci_alloc_context(), |
| free_ctx}; |
| |
| if (!lazy_ctx) { // it could be available on a later call: |
| lazy_ctx.reset(uci_alloc_context()); |
| if (!lazy_ctx) { |
| throw std::runtime_error("uci error: cannot allocate context"); |
| } |
| } |
| |
| return lazy_ctx.get(); |
| } |
| |
| inline ~locked_context() |
| { |
| inuse.unlock(); |
| } |
| }; |
| |
| template <class T> |
| class element { |
| private: |
| uci_list* _begin = nullptr; |
| |
| uci_list* _end = nullptr; |
| |
| uci_ptr _ptr{}; |
| |
| protected: |
| [[nodiscard]] inline auto ptr() -> uci_ptr& |
| { |
| return _ptr; |
| } |
| |
| [[nodiscard]] inline auto ptr() const -> const uci_ptr& |
| { |
| return _ptr; |
| } |
| |
| void init_begin_end(uci_list* begin, uci_list* end) |
| { |
| _begin = begin; |
| _end = end; |
| } |
| |
| inline explicit element(const uci_ptr& pre, uci_element* last) : _ptr{pre} |
| { |
| _ptr.last = last; |
| } |
| |
| inline explicit element() = default; |
| |
| public: |
| inline element(element&&) noexcept = default; |
| |
| inline element(const element&) = delete; |
| |
| inline auto operator=(const element&) -> element& = delete; |
| |
| inline auto operator=(element &&) -> element& = delete; |
| |
| auto operator[](std::string_view key) const -> T; |
| |
| [[nodiscard]] inline auto name() const -> std::string |
| { |
| return _ptr.last->name; |
| } |
| |
| void rename(const char* value) const; |
| |
| void commit() const; |
| |
| [[nodiscard]] inline auto begin() const -> iterator<T> |
| { |
| return iterator<T>{_ptr, _begin}; |
| } |
| |
| [[nodiscard]] inline auto end() const -> iterator<T> |
| { |
| return iterator<T>{_ptr, _end}; |
| } |
| |
| inline ~element() = default; |
| }; |
| |
| class section; |
| |
| class option; |
| |
| class item; |
| |
| class package : public element<section> { |
| public: |
| inline package(const uci_ptr& pre, uci_element* last) : element{pre, last} |
| { |
| // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic,cppcoreguidelines-pro-type-cstyle-cast) |
| ptr().p = uci_to_package(ptr().last); // macro casting pointer-offset. |
| ptr().package = ptr().last->name; |
| |
| auto* end = &ptr().p->sections; |
| auto* begin = end->next; |
| init_begin_end(begin, end); |
| } |
| |
| explicit package(const char* name); |
| |
| auto set(const char* key, const char* type) const -> section; |
| }; |
| |
| class section : public element<option> { |
| public: |
| inline section(const uci_ptr& pre, uci_element* last) : element{pre, last} |
| { |
| // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic,cppcoreguidelines-pro-type-cstyle-cast) |
| ptr().s = uci_to_section(ptr().last); // macro casting pointer-offset. |
| ptr().section = ptr().last->name; |
| |
| auto* end = &ptr().s->options; |
| auto* begin = end->next; |
| init_begin_end(begin, end); |
| } |
| |
| auto set(const char* key, const char* value) const -> option; |
| |
| void del(); |
| |
| [[nodiscard]] inline auto anonymous() const -> bool |
| { |
| return ptr().s->anonymous; |
| } |
| |
| [[nodiscard]] inline auto type() const -> std::string |
| { |
| return ptr().s->type; |
| } |
| }; |
| |
| class option : public element<item> { |
| public: |
| inline option(const uci_ptr& pre, uci_element* last) : element{pre, last} |
| { |
| // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic,cppcoreguidelines-pro-type-cstyle-cast) |
| ptr().o = uci_to_option(ptr().last); // macro casting pointer-offset. |
| ptr().option = ptr().last->name; |
| |
| if (ptr().o->type == UCI_TYPE_LIST) { // use union ptr().o->v as list: |
| // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) |
| auto* end = &ptr().o->v.list; |
| auto* begin = end->next; |
| init_begin_end(begin, end); |
| } |
| else { |
| auto* begin = &ptr().last->list; |
| auto* end = begin->next; |
| init_begin_end(begin, end); |
| } |
| } |
| |
| void del(); |
| |
| [[nodiscard]] inline auto type() const -> std::string |
| { |
| return (ptr().o->type == UCI_TYPE_LIST ? "list" : "option"); |
| } |
| }; |
| |
| class item : public element<item> { |
| public: |
| inline item(const uci_ptr& pre, uci_element* last) : element{pre, last} |
| { |
| ptr().value = ptr().last->name; |
| } |
| |
| [[nodiscard]] inline auto type() const -> std::string |
| { |
| return (ptr().o->type == UCI_TYPE_LIST ? "list" : "option"); |
| } |
| |
| [[nodiscard]] inline auto name() const -> std::string |
| { |
| return (ptr().last->type == UCI_TYPE_ITEM |
| ? ptr().last->name |
| : |
| // else: use union ptr().o->v as string: |
| // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) |
| ptr().o->v.string); |
| } |
| |
| inline explicit operator bool() const |
| { |
| const auto x = std::string_view{name()}; |
| |
| if (x == "0" || x == "off" || x == "false" || x == "no" || x == "disabled") { |
| return false; |
| } |
| |
| if (x == "1" || x == "on" || x == "true" || x == "yes" || x == "enabled") { |
| return true; |
| } |
| |
| auto errmsg = std::string{"uci_error: item is not bool "} + name(); |
| throw std::runtime_error(errmsg); |
| } |
| |
| void rename(const char* value) const; |
| }; |
| |
| // ------------------------- implementation: ---------------------------------- |
| |
| std::mutex locked_context::inuse{}; |
| |
| inline auto uci_error(uci_context* ctx, const char* prefix = nullptr) -> std::runtime_error |
| { |
| char* errmsg = nullptr; |
| uci_get_errorstr(ctx, &errmsg, prefix); |
| |
| std::unique_ptr<char, decltype(&std::free)> auto_free{errmsg, std::free}; |
| return std::runtime_error{errmsg}; |
| } |
| |
| template <class T> |
| auto element<T>::operator[](std::string_view key) const -> T |
| { |
| for (auto elmt : *this) { |
| if (elmt.name() == key) { |
| return elmt; |
| } |
| } |
| |
| auto errmsg = std::string{"uci error: cannot find "}.append(key); |
| throw uci_error(locked_context{}.get(), errmsg.c_str()); |
| } |
| |
| template <class T> |
| void element<T>::rename(const char* value) const |
| { |
| if (value == name()) { |
| return; |
| } |
| |
| auto ctx = locked_context{}; |
| auto tmp_ptr = uci_ptr{_ptr}; |
| tmp_ptr.value = value; |
| if (uci_rename(ctx.get(), &tmp_ptr) != 0) { |
| auto errmsg = std::string{"uci error: cannot rename "}.append(name()); |
| throw uci_error(ctx.get(), errmsg.c_str()); |
| } |
| } |
| |
| template <class T> |
| void element<T>::commit() const |
| { |
| auto ctx = locked_context{}; |
| // TODO(pst) use when possible: |
| // if (uci_commit(ctx.get(), &_ptr.p, true) != 0) { |
| // auto errmsg = std::string{"uci error: cannot commit "} + _ptr.package; |
| // throw uci_error(ctx.get(), errmsg.c_str()); |
| // } |
| auto err = uci_save(ctx.get(), _ptr.p); |
| if (err == 0) { |
| uci_package* tmp_pkg = nullptr; |
| uci_context* tmp_ctx = uci_alloc_context(); |
| err = (tmp_ctx == nullptr ? 1 : 0); |
| if (err == 0) { |
| err = uci_load(tmp_ctx, _ptr.package, &tmp_pkg); |
| } |
| if (err == 0) { |
| err = uci_commit(tmp_ctx, &tmp_pkg, false); |
| } |
| if (err == 0) { |
| err = uci_unload(tmp_ctx, tmp_pkg); |
| } |
| if (tmp_ctx != nullptr) { |
| uci_free_context(tmp_ctx); |
| } |
| } |
| |
| if (err != 0) { |
| auto errmsg = std::string{"uci error: cannot commit "} + _ptr.package; |
| throw uci_error(ctx.get(), errmsg.c_str()); |
| } |
| } |
| |
| package::package(const char* name) |
| { |
| auto ctx = locked_context{}; |
| |
| auto* pkg = uci_lookup_package(ctx.get(), name); |
| if (pkg == nullptr) { |
| if (uci_load(ctx.get(), name, &pkg) != 0) { |
| auto errmsg = std::string{"uci error: cannot load package "} + name; |
| throw uci_error(ctx.get(), errmsg.c_str()); |
| } |
| } |
| |
| ptr().package = name; |
| ptr().p = pkg; |
| ptr().last = &pkg->e; |
| |
| auto* end = &ptr().p->sections; |
| auto* begin = end->next; |
| init_begin_end(begin, end); |
| } |
| |
| auto package::set(const char* key, const char* type) const -> section |
| { |
| auto ctx = locked_context{}; |
| |
| auto tmp_ptr = uci_ptr{ptr()}; |
| tmp_ptr.section = key; |
| tmp_ptr.value = type; |
| if (uci_set(ctx.get(), &tmp_ptr) != 0) { |
| auto errmsg = std::string{"uci error: cannot set section "} + type + "'" + key + |
| "' in package " + name(); |
| throw uci_error(ctx.get(), errmsg.c_str()); |
| } |
| |
| return section{ptr(), tmp_ptr.last}; |
| } |
| |
| auto section::set(const char* key, const char* value) const -> option |
| { |
| auto ctx = locked_context{}; |
| |
| auto tmp_ptr = uci_ptr{ptr()}; |
| tmp_ptr.option = key; |
| tmp_ptr.value = value; |
| if (uci_set(ctx.get(), &tmp_ptr) != 0) { |
| auto errmsg = std::string{"uci error: cannot set option "} + key + "'" + value + |
| "' in package " + name(); |
| throw uci_error(ctx.get(), errmsg.c_str()); |
| } |
| |
| return option{ptr(), tmp_ptr.last}; |
| } |
| |
| void section::del() |
| { |
| auto ctx = locked_context{}; |
| if (uci_delete(ctx.get(), &ptr()) != 0) { |
| auto errmsg = std::string{"uci error: cannot delete section "} + name(); |
| throw uci_error(ctx.get(), errmsg.c_str()); |
| } |
| } |
| |
| void option::del() |
| { |
| auto ctx = locked_context{}; |
| if (uci_delete(ctx.get(), &ptr()) != 0) { |
| auto errmsg = std::string{"uci error: cannot delete option "} + name(); |
| throw uci_error(ctx.get(), errmsg.c_str()); |
| } |
| } |
| |
| void item::rename(const char* value) const |
| { |
| if (value == name()) { |
| return; |
| } |
| |
| auto ctx = locked_context{}; |
| auto tmp_ptr = uci_ptr{ptr()}; |
| |
| if (tmp_ptr.last->type != UCI_TYPE_ITEM) { |
| tmp_ptr.value = value; |
| if (uci_set(ctx.get(), &tmp_ptr) != 0) { |
| auto errmsg = std::string{"uci error: cannot rename item "} + name(); |
| throw uci_error(ctx.get(), errmsg.c_str()); |
| } |
| return; |
| } // else: |
| |
| tmp_ptr.value = tmp_ptr.last->name; |
| if (uci_del_list(ctx.get(), &tmp_ptr) != 0) { |
| auto errmsg = std::string{"uci error: cannot rename (del) "} + name(); |
| throw uci_error(ctx.get(), errmsg.c_str()); |
| } |
| |
| tmp_ptr.value = value; |
| if (uci_add_list(ctx.get(), &tmp_ptr) != 0) { |
| auto errmsg = std::string{"uci error: cannot rename (add) "} + value; |
| throw uci_error(ctx.get(), errmsg.c_str()); |
| } |
| } |
| |
| } // namespace uci |
| |
| #endif |