11#include <promeki/config.h>
12#if PROMEKI_ENABLE_CORE
32PROMEKI_NAMESPACE_BEGIN
41enum class SpecValidation {
122template <CompiledString Name>
class VariantDatabase {
125 using ID =
typename StringRegistry<Name>::Item;
128 using SpecMap = ::promeki::Map<ID, VariantSpec>;
156 static ID declareID(
const String &name,
const VariantSpec &spec) {
161 ID
id = ID::fromId(StringRegistry<Name>::instance().findOrCreateStrict(name));
162 specRegistry().insert(
id.
id(), spec);
171 static const VariantSpec *spec(ID
id) {
return specRegistry().find(
id.
id()); }
189 static const VariantSpec *specFor(
const String &name) {
190 ID
id = ID::find(name);
191 if (!
id.isValid())
return nullptr;
199 static Map<uint64_t, VariantSpec> registeredSpecs() {
return specRegistry().all(); }
211 static VariantDatabase fromSpecs(
const SpecMap &specs) {
213 Data *d = db._d.modify();
214 for (
auto it = specs.cbegin(); it != specs.cend(); ++it) {
215 const Variant &def = it->second.defaultValue();
216 if (def.isValid()) d->data.insert(it->first.id(), def);
243 static int writeSpecMapHelp(TextStream &stream,
const SpecMap &specs,
244 const StringList &skipKeys = StringList()) {
248 for (
auto it = specs.cbegin(); it != specs.cend(); ++it) {
249 const String &n = it->first.name();
250 if (skipKeys.contains(n))
continue;
253 names = names.sort();
254 if (names.isEmpty())
return 0;
259 List<String> details;
260 details.resize(names.size());
262 int detailsWidth = 0;
263 for (
size_t i = 0; i < names.size(); ++i) {
265 auto it = specs.find(
id);
266 if (it == specs.end())
continue;
267 int nw =
static_cast<int>(names[i].size());
268 if (nw > nameWidth) nameWidth = nw;
269 details[i] = it->second.detailsString();
270 int dw =
static_cast<int>(details[i].size());
271 if (dw > detailsWidth) detailsWidth = dw;
280 const int prefixWidth = 2 + nameWidth + 2 + detailsWidth;
281 int maxLineWidth = 0;
282 for (
size_t i = 0; i < names.size(); ++i) {
284 auto it = specs.find(
id);
285 if (it == specs.end())
continue;
287 String nameCol = names[i];
288 while (
static_cast<int>(nameCol.size()) < nameWidth) {
291 String detailsCol = details[i];
292 while (
static_cast<int>(detailsCol.size()) < detailsWidth) {
295 stream <<
" " << nameCol <<
" " << detailsCol;
296 const String &desc = it->second.description();
297 int lineWidth = prefixWidth;
298 if (!desc.isEmpty()) {
299 stream <<
" " << desc;
300 lineWidth += 2 +
static_cast<int>(desc.size());
303 if (lineWidth > maxLineWidth) maxLineWidth = lineWidth;
313 VariantDatabase() =
default;
353 Error setFromJson(ID
id,
const Variant &value) {
354 const VariantSpec *sp = spec(
id);
366 Variant coerced = sp->coerce(value, &ce);
376 set(
id, std::move(coerced), &err);
380 set(
id, value, &err);
400 static VariantDatabase fromJson(
const JsonObject &json) {
402 json.forEach([&db](
const String &key,
const Variant &val) { db.setFromJson(ID(key), val); });
419 void setValidation(SpecValidation mode) { _d.modify()->validation = mode; }
425 SpecValidation validation()
const {
return _d->validation; }
451 bool set(ID
id,
const Variant &value, Error *err =
nullptr) {
452 if (!validateOnSet(
id, value, err))
return false;
453 _d.modify()->data.insert(
id.
id(), value);
464 bool set(ID
id, Variant &&value, Error *err =
nullptr) {
465 if (!validateOnSet(
id, value, err))
return false;
466 _d.modify()->data.insert(
id.
id(), std::move(value));
483 bool setIfMissing(ID
id,
const Variant &value) {
484 if (_d->data.contains(
id.id()))
return false;
485 _d.modify()->data.insert(
id.
id(), value);
496 bool setIfMissing(ID
id, Variant &&value) {
497 if (_d->data.contains(
id.id()))
return false;
498 _d.modify()->data.insert(
id.
id(), std::move(value));
508 Variant get(ID
id,
const Variant &defaultValue = Variant())
const {
509 auto it = _d->data.find(
id.
id());
510 if (it == _d->data.end())
return defaultValue;
527 template <
typename T> T getAs(ID
id,
const T &defaultValue = T{}, Error *err =
nullptr)
const {
528 auto it = _d->data.find(
id.
id());
529 if (it == _d->data.end()) {
530 if (err) *err = Error::IdNotFound;
534 T result = it->second.template get<T>(&e);
536 if (err) *err = Error::ConversionFailed;
539 if (err) *err = Error::Ok;
608 template <
typename Resolver>
609 String format(
const String &tmpl, Resolver &&resolver, Error *err =
nullptr)
const {
610 if (err !=
nullptr) *err = Error::Ok;
612 out.reserve(tmpl.byteCount());
613 const char *src = tmpl.cstr();
614 const size_t len = tmpl.byteCount();
615 bool sawUnresolved =
false;
620 if (i + 1 < len && src[i + 1] ==
'{') {
626 while (end < len && src[end] !=
'}') ++end;
628 out += String(src + i, len - i);
631 const char *bodyData = src + i + 1;
632 const size_t bodyLen = end - (i + 1);
633 size_t colon = bodyLen;
634 for (
size_t p = 0; p < bodyLen; ++p) {
635 if (bodyData[p] ==
':') {
640 const size_t keyLen = colon;
641 const size_t specOff = (colon == bodyLen) ? bodyLen : colon + 1;
642 String keyName(bodyData, keyLen);
643 String specStr(bodyData + specOff, bodyLen - specOff);
649 size_t sep = keyName.byteCount();
650 const char *kn = keyName.cstr();
651 for (
size_t p = 0; p < keyName.byteCount(); ++p) {
652 if (kn[p] ==
'.' || kn[p] ==
'[') {
657 String headKey = (sep == keyName.byteCount()) ? keyName
660 if (sep < keyName.byteCount()) {
662 size_t start = (kn[sep] ==
'.') ? sep + 1 : sep;
663 tailPath = String(kn + start, keyName.byteCount() - start);
665 ID
id = ID::find(headKey);
666 auto it =
id.isValid() ? _d->data.find(
id.
id()) : _d->data.end();
667 if (it != _d->data.end()) {
668 Variant target = it->second;
669 if (!tailPath.isEmpty()) {
671 target = promekiResolveVariantPath(target, tailPath, &pe);
672 if (pe.isError() || !target.isValid()) {
673 sawUnresolved =
true;
675 out += String(bodyData, keyLen);
681 out += target.format(specStr);
683 Optional<String> resolved;
684 if constexpr (!std::is_same_v<std::decay_t<Resolver>, std::nullptr_t>) {
685 resolved = resolver(keyName, specStr);
687 if (resolved.hasValue()) {
690 sawUnresolved =
true;
692 out += String(bodyData, keyLen);
697 }
else if (c ==
'}') {
698 if (i + 1 < len && src[i + 1] ==
'}') {
710 if (sawUnresolved && err !=
nullptr) *err = Error::IdNotFound;
725 String format(
const String &tmpl, Error *err =
nullptr)
const {
return format(tmpl,
nullptr, err); }
732 bool contains(ID
id)
const {
return _d->data.contains(
id.
id()); }
740 if (!_d->data.contains(
id.id()))
return false;
741 return _d.modify()->data.remove(
id.
id());
748 size_t size()
const {
return _d->data.size(); }
754 bool isEmpty()
const {
return _d->data.isEmpty(); }
760 if (_d->data.isEmpty())
return;
761 _d.modify()->data.clear();
768 List<ID> ids()
const {
770 for (
auto it = _d->data.cbegin(); it != _d->data.cend(); ++it) {
771 ret.pushToBack(ID::fromId(it->first));
797 StringList unknownKeys(
const SpecMap &extraSpecs = SpecMap())
const {
799 for (
auto it = _d->data.cbegin(); it != _d->data.cend(); ++it) {
800 ID
id = ID::fromId(it->first);
801 if (extraSpecs.find(
id) != extraSpecs.end())
continue;
802 if (spec(
id) !=
nullptr)
continue;
803 out.pushToBack(
id.name());
818 template <
typename Func>
void forEach(Func &&func)
const {
819 for (
auto it = _d->data.cbegin(); it != _d->data.cend(); ++it) {
820 func(ID::fromId(it->first), it->second);
832 void merge(
const VariantDatabase &other) {
833 other.forEach([
this](ID
id,
const Variant &val) { set(
id, val); });
844 VariantDatabase extract(
const List<ID> &idList)
const {
845 VariantDatabase result;
846 Data *r = result._d.modify();
847 for (
size_t i = 0; i < idList.size(); ++i) {
848 auto it = _d->data.find(idList[i].
id());
849 if (it != _d->data.end()) {
850 r->data.insert(it->first, it->second);
875 bool operator==(
const VariantDatabase &other)
const {
876 if (_d == other._d)
return true;
877 return _d->data == other._d->data;
881 bool operator!=(
const VariantDatabase &other)
const {
return !(*
this == other); }
896 JsonObject toJson()
const {
898 for (
auto it = _d->data.cbegin(); it != _d->data.cend(); ++it) {
899 String name = StringRegistry<Name>::instance().name(it->first);
900 json.setFromVariant(name, it->second);
917 void writeTo(DataStream &stream)
const {
918 stream << static_cast<uint32_t>(_d->data.size());
919 for (
auto it = _d->data.cbegin(); it != _d->data.cend(); ++it) {
920 String name = StringRegistry<Name>::instance().name(it->first);
921 stream << name << it->second;
934 void readFrom(DataStream &stream) {
935 _d.modify()->data.clear();
938 for (uint32_t i = 0; i < count && stream.status() == DataStream::Ok; ++i) {
941 stream >> name >> value;
942 if (stream.status() == DataStream::Ok) {
943 set(ID(name), std::move(value));
963 void writeTo(TextStream &stream)
const {
964 for (
auto it = _d->data.cbegin(); it != _d->data.cend(); ++it) {
965 String name = StringRegistry<Name>::instance().name(it->first);
966 stream << name <<
" = " << it->second << endl;
982 PROMEKI_SHARED_FINAL(Data)
983 Map<uint64_t, Variant> data;
984 SpecValidation validation = SpecValidation::Strict;
986 SharedPtr<Data> _d = SharedPtr<Data>::create();
995 struct SpecRegistry {
996 static SpecRegistry &instance() {
997 static SpecRegistry reg;
1001 void insert(uint64_t
id,
const VariantSpec &spec) {
1002 ReadWriteLock::WriteLocker lock(_lock);
1003 _specs.insert(
id, spec);
1006 const VariantSpec *find(uint64_t
id)
const {
1007 ReadWriteLock::ReadLocker lock(_lock);
1008 auto it = _specs.find(
id);
1009 if (it == _specs.end())
return nullptr;
1013 Map<uint64_t, VariantSpec> all()
const {
1014 ReadWriteLock::ReadLocker lock(_lock);
1019 SpecRegistry() =
default;
1020 mutable ReadWriteLock _lock;
1021 Map<uint64_t, VariantSpec> _specs;
1024 static SpecRegistry &specRegistry() {
return SpecRegistry::instance(); }
1042 bool validateOnSet(ID
id,
const Variant &value, Error *err =
nullptr) {
1043 if (err) *err = Error::Ok;
1044 const SpecValidation mode = _d->validation;
1045 if (mode == SpecValidation::None)
return true;
1046 const VariantSpec *s = specRegistry().find(
id.
id());
1047 if (!s)
return true;
1049 if (!s->validate(value, &verr)) {
1050 if (mode == SpecValidation::Warn) {
1051 promekiWarn(
"VariantDatabase: value for '%s' fails spec (%s)",
id.name().cstr(),
1052 verr.name().cstr());
1055 if (err) *err = verr;
1068template <CompiledString Name> DataStream &operator<<(DataStream &stream,
const VariantDatabase<Name> &db) {
1079template <CompiledString Name> DataStream &operator>>(DataStream &stream, VariantDatabase<Name> &db) {
1080 db.readFrom(stream);
1090template <CompiledString Name> TextStream &operator<<(TextStream &stream,
const VariantDatabase<Name> &db) {
1095PROMEKI_NAMESPACE_END
1132#define PROMEKI_DECLARE_ID(Name, ...) \
1133 static constexpr ID Name = ID::literal(#Name); \
1134 [[maybe_unused]] static inline const ID PROMEKI_CONCAT(_promeki_spec_reg_, Name) = declareID(#Name, __VA_ARGS__)