blob: 4598890d9ba8f8979ac834167ec1f32afd780240 [file] [log] [blame]
b.liue9582032025-04-17 19:18:16 +08001#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
11namespace uci {
12
13template <class T>
14class 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
65class 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
106template <class T>
107class 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
172class section;
173
174class option;
175
176class item;
177
178class 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
196class 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
224class 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
253class 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
296std::mutex locked_context::inuse{};
297
298inline 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
307template <class T>
308auto 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
320template <class T>
321void 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
336template <class T>
337void 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
370package::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
391auto 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
407auto 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
423void 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
432void 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
441void 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