b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame^] | 1 | #ifndef _UCI_CXX_HPP |
| 2 | #define _UCI_CXX_HPP |
| 3 | |
| 4 | #include <uci.h> |
| 5 | #include <memory> |
| 6 | #include <mutex> |
| 7 | #include <stdexcept> |
| 8 | #include <string> |
| 9 | #include <string_view> |
| 10 | |
| 11 | namespace uci { |
| 12 | |
| 13 | template <class T> |
| 14 | class iterator { // like uci_foreach_element_safe. |
| 15 | |
| 16 | private: |
| 17 | const uci_ptr& _ptr; |
| 18 | |
| 19 | uci_element* _it = nullptr; |
| 20 | |
| 21 | uci_element* _next = nullptr; |
| 22 | |
| 23 | // wrapper for clang-tidy |
| 24 | inline auto _list_to_element(const uci_list* cur) -> uci_element* |
| 25 | { |
| 26 | // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic,cppcoreguidelines-pro-type-cstyle-cast) |
| 27 | return list_to_element(cur); // macro casting container=pointer-offset. |
| 28 | } |
| 29 | |
| 30 | public: |
| 31 | inline explicit iterator(const uci_ptr& ptr, const uci_list* cur) |
| 32 | : _ptr{ptr}, _it{_list_to_element(cur)} |
| 33 | { |
| 34 | _next = _list_to_element(_it->list.next); |
| 35 | } |
| 36 | |
| 37 | inline iterator(iterator&&) noexcept = default; |
| 38 | |
| 39 | inline iterator(const iterator&) = delete; |
| 40 | |
| 41 | inline auto operator=(const iterator&) -> iterator& = delete; |
| 42 | |
| 43 | inline auto operator=(iterator &&) -> iterator& = delete; |
| 44 | |
| 45 | auto operator*() -> T |
| 46 | { |
| 47 | return T{_ptr, _it}; |
| 48 | } |
| 49 | |
| 50 | inline auto operator!=(const iterator& rhs) -> bool |
| 51 | { |
| 52 | return (&_it->list != &rhs._it->list); |
| 53 | } |
| 54 | |
| 55 | inline auto operator++() -> iterator& |
| 56 | { |
| 57 | _it = _next; |
| 58 | _next = _list_to_element(_next->list.next); |
| 59 | return *this; |
| 60 | } |
| 61 | |
| 62 | inline ~iterator() = default; |
| 63 | }; |
| 64 | |
| 65 | class locked_context { |
| 66 | private: |
| 67 | static std::mutex inuse; |
| 68 | |
| 69 | public: |
| 70 | inline locked_context() |
| 71 | { |
| 72 | inuse.lock(); |
| 73 | } |
| 74 | |
| 75 | inline locked_context(locked_context&&) noexcept = default; |
| 76 | |
| 77 | inline locked_context(const locked_context&) = delete; |
| 78 | |
| 79 | inline auto operator=(const locked_context&) -> locked_context& = delete; |
| 80 | |
| 81 | inline auto operator=(locked_context &&) -> locked_context& = delete; |
| 82 | |
| 83 | // NOLINTNEXTLINE(readability-convert-member-functions-to-static) |
| 84 | inline auto get() -> uci_context* // is member to enforce inuse |
| 85 | { |
| 86 | static auto free_ctx = [](uci_context* ctx) { uci_free_context(ctx); }; |
| 87 | static std::unique_ptr<uci_context, decltype(free_ctx)> lazy_ctx{uci_alloc_context(), |
| 88 | free_ctx}; |
| 89 | |
| 90 | if (!lazy_ctx) { // it could be available on a later call: |
| 91 | lazy_ctx.reset(uci_alloc_context()); |
| 92 | if (!lazy_ctx) { |
| 93 | throw std::runtime_error("uci error: cannot allocate context"); |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | return lazy_ctx.get(); |
| 98 | } |
| 99 | |
| 100 | inline ~locked_context() |
| 101 | { |
| 102 | inuse.unlock(); |
| 103 | } |
| 104 | }; |
| 105 | |
| 106 | template <class T> |
| 107 | class element { |
| 108 | private: |
| 109 | uci_list* _begin = nullptr; |
| 110 | |
| 111 | uci_list* _end = nullptr; |
| 112 | |
| 113 | uci_ptr _ptr{}; |
| 114 | |
| 115 | protected: |
| 116 | [[nodiscard]] inline auto ptr() -> uci_ptr& |
| 117 | { |
| 118 | return _ptr; |
| 119 | } |
| 120 | |
| 121 | [[nodiscard]] inline auto ptr() const -> const uci_ptr& |
| 122 | { |
| 123 | return _ptr; |
| 124 | } |
| 125 | |
| 126 | void init_begin_end(uci_list* begin, uci_list* end) |
| 127 | { |
| 128 | _begin = begin; |
| 129 | _end = end; |
| 130 | } |
| 131 | |
| 132 | inline explicit element(const uci_ptr& pre, uci_element* last) : _ptr{pre} |
| 133 | { |
| 134 | _ptr.last = last; |
| 135 | } |
| 136 | |
| 137 | inline explicit element() = default; |
| 138 | |
| 139 | public: |
| 140 | inline element(element&&) noexcept = default; |
| 141 | |
| 142 | inline element(const element&) = delete; |
| 143 | |
| 144 | inline auto operator=(const element&) -> element& = delete; |
| 145 | |
| 146 | inline auto operator=(element &&) -> element& = delete; |
| 147 | |
| 148 | auto operator[](std::string_view key) const -> T; |
| 149 | |
| 150 | [[nodiscard]] inline auto name() const -> std::string |
| 151 | { |
| 152 | return _ptr.last->name; |
| 153 | } |
| 154 | |
| 155 | void rename(const char* value) const; |
| 156 | |
| 157 | void commit() const; |
| 158 | |
| 159 | [[nodiscard]] inline auto begin() const -> iterator<T> |
| 160 | { |
| 161 | return iterator<T>{_ptr, _begin}; |
| 162 | } |
| 163 | |
| 164 | [[nodiscard]] inline auto end() const -> iterator<T> |
| 165 | { |
| 166 | return iterator<T>{_ptr, _end}; |
| 167 | } |
| 168 | |
| 169 | inline ~element() = default; |
| 170 | }; |
| 171 | |
| 172 | class section; |
| 173 | |
| 174 | class option; |
| 175 | |
| 176 | class item; |
| 177 | |
| 178 | class package : public element<section> { |
| 179 | public: |
| 180 | inline package(const uci_ptr& pre, uci_element* last) : element{pre, last} |
| 181 | { |
| 182 | // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic,cppcoreguidelines-pro-type-cstyle-cast) |
| 183 | ptr().p = uci_to_package(ptr().last); // macro casting pointer-offset. |
| 184 | ptr().package = ptr().last->name; |
| 185 | |
| 186 | auto* end = &ptr().p->sections; |
| 187 | auto* begin = end->next; |
| 188 | init_begin_end(begin, end); |
| 189 | } |
| 190 | |
| 191 | explicit package(const char* name); |
| 192 | |
| 193 | auto set(const char* key, const char* type) const -> section; |
| 194 | }; |
| 195 | |
| 196 | class section : public element<option> { |
| 197 | public: |
| 198 | inline section(const uci_ptr& pre, uci_element* last) : element{pre, last} |
| 199 | { |
| 200 | // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic,cppcoreguidelines-pro-type-cstyle-cast) |
| 201 | ptr().s = uci_to_section(ptr().last); // macro casting pointer-offset. |
| 202 | ptr().section = ptr().last->name; |
| 203 | |
| 204 | auto* end = &ptr().s->options; |
| 205 | auto* begin = end->next; |
| 206 | init_begin_end(begin, end); |
| 207 | } |
| 208 | |
| 209 | auto set(const char* key, const char* value) const -> option; |
| 210 | |
| 211 | void del(); |
| 212 | |
| 213 | [[nodiscard]] inline auto anonymous() const -> bool |
| 214 | { |
| 215 | return ptr().s->anonymous; |
| 216 | } |
| 217 | |
| 218 | [[nodiscard]] inline auto type() const -> std::string |
| 219 | { |
| 220 | return ptr().s->type; |
| 221 | } |
| 222 | }; |
| 223 | |
| 224 | class option : public element<item> { |
| 225 | public: |
| 226 | inline option(const uci_ptr& pre, uci_element* last) : element{pre, last} |
| 227 | { |
| 228 | // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic,cppcoreguidelines-pro-type-cstyle-cast) |
| 229 | ptr().o = uci_to_option(ptr().last); // macro casting pointer-offset. |
| 230 | ptr().option = ptr().last->name; |
| 231 | |
| 232 | if (ptr().o->type == UCI_TYPE_LIST) { // use union ptr().o->v as list: |
| 233 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) |
| 234 | auto* end = &ptr().o->v.list; |
| 235 | auto* begin = end->next; |
| 236 | init_begin_end(begin, end); |
| 237 | } |
| 238 | else { |
| 239 | auto* begin = &ptr().last->list; |
| 240 | auto* end = begin->next; |
| 241 | init_begin_end(begin, end); |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | void del(); |
| 246 | |
| 247 | [[nodiscard]] inline auto type() const -> std::string |
| 248 | { |
| 249 | return (ptr().o->type == UCI_TYPE_LIST ? "list" : "option"); |
| 250 | } |
| 251 | }; |
| 252 | |
| 253 | class item : public element<item> { |
| 254 | public: |
| 255 | inline item(const uci_ptr& pre, uci_element* last) : element{pre, last} |
| 256 | { |
| 257 | ptr().value = ptr().last->name; |
| 258 | } |
| 259 | |
| 260 | [[nodiscard]] inline auto type() const -> std::string |
| 261 | { |
| 262 | return (ptr().o->type == UCI_TYPE_LIST ? "list" : "option"); |
| 263 | } |
| 264 | |
| 265 | [[nodiscard]] inline auto name() const -> std::string |
| 266 | { |
| 267 | return (ptr().last->type == UCI_TYPE_ITEM |
| 268 | ? ptr().last->name |
| 269 | : |
| 270 | // else: use union ptr().o->v as string: |
| 271 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) |
| 272 | ptr().o->v.string); |
| 273 | } |
| 274 | |
| 275 | inline explicit operator bool() const |
| 276 | { |
| 277 | const auto x = std::string_view{name()}; |
| 278 | |
| 279 | if (x == "0" || x == "off" || x == "false" || x == "no" || x == "disabled") { |
| 280 | return false; |
| 281 | } |
| 282 | |
| 283 | if (x == "1" || x == "on" || x == "true" || x == "yes" || x == "enabled") { |
| 284 | return true; |
| 285 | } |
| 286 | |
| 287 | auto errmsg = std::string{"uci_error: item is not bool "} + name(); |
| 288 | throw std::runtime_error(errmsg); |
| 289 | } |
| 290 | |
| 291 | void rename(const char* value) const; |
| 292 | }; |
| 293 | |
| 294 | // ------------------------- implementation: ---------------------------------- |
| 295 | |
| 296 | std::mutex locked_context::inuse{}; |
| 297 | |
| 298 | inline auto uci_error(uci_context* ctx, const char* prefix = nullptr) -> std::runtime_error |
| 299 | { |
| 300 | char* errmsg = nullptr; |
| 301 | uci_get_errorstr(ctx, &errmsg, prefix); |
| 302 | |
| 303 | std::unique_ptr<char, decltype(&std::free)> auto_free{errmsg, std::free}; |
| 304 | return std::runtime_error{errmsg}; |
| 305 | } |
| 306 | |
| 307 | template <class T> |
| 308 | auto element<T>::operator[](std::string_view key) const -> T |
| 309 | { |
| 310 | for (auto elmt : *this) { |
| 311 | if (elmt.name() == key) { |
| 312 | return elmt; |
| 313 | } |
| 314 | } |
| 315 | |
| 316 | auto errmsg = std::string{"uci error: cannot find "}.append(key); |
| 317 | throw uci_error(locked_context{}.get(), errmsg.c_str()); |
| 318 | } |
| 319 | |
| 320 | template <class T> |
| 321 | void element<T>::rename(const char* value) const |
| 322 | { |
| 323 | if (value == name()) { |
| 324 | return; |
| 325 | } |
| 326 | |
| 327 | auto ctx = locked_context{}; |
| 328 | auto tmp_ptr = uci_ptr{_ptr}; |
| 329 | tmp_ptr.value = value; |
| 330 | if (uci_rename(ctx.get(), &tmp_ptr) != 0) { |
| 331 | auto errmsg = std::string{"uci error: cannot rename "}.append(name()); |
| 332 | throw uci_error(ctx.get(), errmsg.c_str()); |
| 333 | } |
| 334 | } |
| 335 | |
| 336 | template <class T> |
| 337 | void element<T>::commit() const |
| 338 | { |
| 339 | auto ctx = locked_context{}; |
| 340 | // TODO(pst) use when possible: |
| 341 | // if (uci_commit(ctx.get(), &_ptr.p, true) != 0) { |
| 342 | // auto errmsg = std::string{"uci error: cannot commit "} + _ptr.package; |
| 343 | // throw uci_error(ctx.get(), errmsg.c_str()); |
| 344 | // } |
| 345 | auto err = uci_save(ctx.get(), _ptr.p); |
| 346 | if (err == 0) { |
| 347 | uci_package* tmp_pkg = nullptr; |
| 348 | uci_context* tmp_ctx = uci_alloc_context(); |
| 349 | err = (tmp_ctx == nullptr ? 1 : 0); |
| 350 | if (err == 0) { |
| 351 | err = uci_load(tmp_ctx, _ptr.package, &tmp_pkg); |
| 352 | } |
| 353 | if (err == 0) { |
| 354 | err = uci_commit(tmp_ctx, &tmp_pkg, false); |
| 355 | } |
| 356 | if (err == 0) { |
| 357 | err = uci_unload(tmp_ctx, tmp_pkg); |
| 358 | } |
| 359 | if (tmp_ctx != nullptr) { |
| 360 | uci_free_context(tmp_ctx); |
| 361 | } |
| 362 | } |
| 363 | |
| 364 | if (err != 0) { |
| 365 | auto errmsg = std::string{"uci error: cannot commit "} + _ptr.package; |
| 366 | throw uci_error(ctx.get(), errmsg.c_str()); |
| 367 | } |
| 368 | } |
| 369 | |
| 370 | package::package(const char* name) |
| 371 | { |
| 372 | auto ctx = locked_context{}; |
| 373 | |
| 374 | auto* pkg = uci_lookup_package(ctx.get(), name); |
| 375 | if (pkg == nullptr) { |
| 376 | if (uci_load(ctx.get(), name, &pkg) != 0) { |
| 377 | auto errmsg = std::string{"uci error: cannot load package "} + name; |
| 378 | throw uci_error(ctx.get(), errmsg.c_str()); |
| 379 | } |
| 380 | } |
| 381 | |
| 382 | ptr().package = name; |
| 383 | ptr().p = pkg; |
| 384 | ptr().last = &pkg->e; |
| 385 | |
| 386 | auto* end = &ptr().p->sections; |
| 387 | auto* begin = end->next; |
| 388 | init_begin_end(begin, end); |
| 389 | } |
| 390 | |
| 391 | auto package::set(const char* key, const char* type) const -> section |
| 392 | { |
| 393 | auto ctx = locked_context{}; |
| 394 | |
| 395 | auto tmp_ptr = uci_ptr{ptr()}; |
| 396 | tmp_ptr.section = key; |
| 397 | tmp_ptr.value = type; |
| 398 | if (uci_set(ctx.get(), &tmp_ptr) != 0) { |
| 399 | auto errmsg = std::string{"uci error: cannot set section "} + type + "'" + key + |
| 400 | "' in package " + name(); |
| 401 | throw uci_error(ctx.get(), errmsg.c_str()); |
| 402 | } |
| 403 | |
| 404 | return section{ptr(), tmp_ptr.last}; |
| 405 | } |
| 406 | |
| 407 | auto section::set(const char* key, const char* value) const -> option |
| 408 | { |
| 409 | auto ctx = locked_context{}; |
| 410 | |
| 411 | auto tmp_ptr = uci_ptr{ptr()}; |
| 412 | tmp_ptr.option = key; |
| 413 | tmp_ptr.value = value; |
| 414 | if (uci_set(ctx.get(), &tmp_ptr) != 0) { |
| 415 | auto errmsg = std::string{"uci error: cannot set option "} + key + "'" + value + |
| 416 | "' in package " + name(); |
| 417 | throw uci_error(ctx.get(), errmsg.c_str()); |
| 418 | } |
| 419 | |
| 420 | return option{ptr(), tmp_ptr.last}; |
| 421 | } |
| 422 | |
| 423 | void section::del() |
| 424 | { |
| 425 | auto ctx = locked_context{}; |
| 426 | if (uci_delete(ctx.get(), &ptr()) != 0) { |
| 427 | auto errmsg = std::string{"uci error: cannot delete section "} + name(); |
| 428 | throw uci_error(ctx.get(), errmsg.c_str()); |
| 429 | } |
| 430 | } |
| 431 | |
| 432 | void option::del() |
| 433 | { |
| 434 | auto ctx = locked_context{}; |
| 435 | if (uci_delete(ctx.get(), &ptr()) != 0) { |
| 436 | auto errmsg = std::string{"uci error: cannot delete option "} + name(); |
| 437 | throw uci_error(ctx.get(), errmsg.c_str()); |
| 438 | } |
| 439 | } |
| 440 | |
| 441 | void item::rename(const char* value) const |
| 442 | { |
| 443 | if (value == name()) { |
| 444 | return; |
| 445 | } |
| 446 | |
| 447 | auto ctx = locked_context{}; |
| 448 | auto tmp_ptr = uci_ptr{ptr()}; |
| 449 | |
| 450 | if (tmp_ptr.last->type != UCI_TYPE_ITEM) { |
| 451 | tmp_ptr.value = value; |
| 452 | if (uci_set(ctx.get(), &tmp_ptr) != 0) { |
| 453 | auto errmsg = std::string{"uci error: cannot rename item "} + name(); |
| 454 | throw uci_error(ctx.get(), errmsg.c_str()); |
| 455 | } |
| 456 | return; |
| 457 | } // else: |
| 458 | |
| 459 | tmp_ptr.value = tmp_ptr.last->name; |
| 460 | if (uci_del_list(ctx.get(), &tmp_ptr) != 0) { |
| 461 | auto errmsg = std::string{"uci error: cannot rename (del) "} + name(); |
| 462 | throw uci_error(ctx.get(), errmsg.c_str()); |
| 463 | } |
| 464 | |
| 465 | tmp_ptr.value = value; |
| 466 | if (uci_add_list(ctx.get(), &tmp_ptr) != 0) { |
| 467 | auto errmsg = std::string{"uci error: cannot rename (add) "} + value; |
| 468 | throw uci_error(ctx.get(), errmsg.c_str()); |
| 469 | } |
| 470 | } |
| 471 | |
| 472 | } // namespace uci |
| 473 | |
| 474 | #endif |