#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
|