feat(bindings): add modern C++ interface for iKv
Some checks failed
C++ bindings / gcc / Debug (push) Failing after 10s
C++ bindings / clang / Debug (push) Failing after 17s
C++ bindings / clang / Release (push) Failing after 10s
C++ bindings / gcc / Release (push) Failing after 16s

This commit is contained in:
2026-06-17 18:57:54 -05:00
parent a56451a01f
commit e651ae1575
11 changed files with 1325 additions and 29 deletions

309
README.md
View File

@@ -1,3 +1,310 @@
# iKvxx
C++ bindings for iKv files
iKvxx is a modern C++17 binding for [iKv](https://dock-it.dev/iDENTITY-Technology/iKv).
It provides automatic resource management, exceptions, type-safe accessors, and a
compact `Value` API modeled after JsonCpp while preserving iKv text and binary format
compatibility.
## What it does
- Parse iKv text from strings or files
- Parse iKv binary data from memory or files
- Build object and array trees with a JsonCpp-style API
- Read and write both `iKv1` and `iKv2`
- Write the current `iKv2` format by default
- Manage the underlying C tree automatically with RAII
- Report parse, type, bounds, allocation, and I/O failures with exceptions
- Provide a reusable `ikvxx::ikvxx` CMake target and installable package
## Syntax preview
```ikv
ikv2 "player_save"
{
"title" "iKvxx demo"
"version" 2
"player" {
"name" "jondoe"
"alive" true
"speed" 12.5
}
"inventory" [
"wrench"
"battery"
"map"
]
}
```
## Library example
```cpp
#include <ikvxx/ikvxx.hpp>
int main()
{
ikv::Value root(ikv::objectValue, "player_save");
root["title"] = "iKvxx demo";
root["version"] = 2;
auto player = root.makeObject("player");
player["name"] = "jondoe";
player["alive"] = true;
player["speed"] = 12.5;
auto inventory = root.makeArray("inventory", ikv::stringValue);
inventory.append("wrench");
inventory.append("battery");
inventory.append("map");
root.write("player_save.ikv");
}
```
The root and all child handles release their underlying iKv tree automatically. No
manual `ikv_free` call is required.
## Reading values
Use `load` for text files and the familiar `operator[]` syntax for object and array
access:
```cpp
#include <ikvxx/ikvxx.hpp>
#include <iostream>
int main()
{
auto root = ikv::Value::load("player_save.ikv");
std::cout << root["title"].asString() << '\n';
std::cout << root["version"].asInt() << '\n';
std::cout << root["player"]["speed"].asDouble() << '\n';
auto inventory = root["inventory"];
for (ikv::Value::ArrayIndex i = 0; i < inventory.size(); ++i)
std::cout << inventory[i].asString() << '\n';
}
```
Missing object members produce a null `Value`. Invalid conversions and out-of-range
array indexes throw a standard exception:
```cpp
if (!root.isMember("difficulty"))
root["difficulty"] = "normal";
if (root["optional"].isNull())
root["optional"] = false;
try
{
int value = root["title"].asInt();
}
catch (const ikv::Error& error)
{
// "title" is a string, not an integer.
}
```
## Parsing text
`Value::parse` auto-detects `iKv1` and `iKv2` input:
```cpp
const std::string text = R"(
ikv2 "settings"
{
"fullscreen" true
"width" 1920
"height" 1080
}
)";
auto settings = ikv::Value::parse(text);
bool fullscreen = settings["fullscreen"].asBool();
```
## Arrays
Arrays may be untyped or restricted to one iKv value type. Typed arrays reject values
of the wrong type:
```cpp
ikv::Value root;
auto scores = root.makeArray("scores", ikv::intValue);
scores.append(10);
scores.append(25);
scores.append(100);
auto mixed = root.makeArray("mixed");
mixed.append("ready");
mixed.append(42);
mixed.append(true);
auto players = root.makeArray("players", ikv::objectValue);
auto first = players.appendObject();
first["name"] = "Ada";
first["score"] = 100;
```
## Binary data
Binary iKv can be written to a file or encoded directly into a byte vector:
```cpp
ikv::Value root;
root["answer"] = 42;
root.writeBinary("data.ikvb");
auto from_file = ikv::Value::loadBinary("data.ikvb");
std::vector<std::uint8_t> bytes = root.toBinary();
auto from_memory = ikv::Value::fromBinary(bytes.data(), bytes.size());
```
Pass an explicit version when compatibility with older data is required:
```cpp
root.write("legacy.ikv", ikv::Version::v1);
root.writeBinary("legacy.ikvb", ikv::Version::v1);
auto legacy_bytes = root.toBinary(ikv::Version::v1);
```
## Refreshing a tree
An existing root can be updated in place from a text or binary file. Other `Value`
handles that share that root observe the refreshed data:
```cpp
auto settings = ikv::Value::load("settings.ikv");
auto shared = settings;
settings.refresh("updated-settings.ikv");
// shared now refers to the refreshed tree as well.
```
`refresh` is only valid on a mutable root value. If refresh fails, the existing tree
is preserved.
## Build
Clone with the iKv submodule and build with CMake:
```sh
git clone --recurse-submodules https://dock-it.dev/iDENTITY-Technology/iKvxx.git
cd iKvxx
cmake -S . -B build
cmake --build build
```
If the repository was cloned without submodules:
```sh
git submodule update --init --recursive
```
This produces the reusable static library targets:
- `ikvxx`
- `ikvxx::ikvxx` for CMake consumers
### CMake consumer usage
When iKvxx is included directly in another source tree:
```cmake
add_subdirectory(path/to/iKvxx)
target_link_libraries(your_target PRIVATE ikvxx::ikvxx)
```
The library can also be installed and consumed with `find_package`:
```sh
cmake --install build --prefix /path/to/ikvxx-install
```
```cmake
find_package(ikvxx 0.1 REQUIRED CONFIG)
target_link_libraries(your_target PRIVATE ikvxx::ikvxx)
```
Point `CMAKE_PREFIX_PATH` at the selected installation prefix when it is not in a
standard system location.
## Tests
Build and run the binding tests with CTest:
```sh
cmake -S . -B build -DIKVXX_BUILD_TESTS=ON
cmake --build build
ctest --test-dir build --output-on-failure
```
The test suite covers construction, every value type, object and array access,
assignment and append overloads, conversion failures, bounds checks, text and binary
I/O, format versions, refresh behavior, and shared ownership. Gitea Actions runs the
suite on Ubuntu with GCC and Clang in both Debug and Release configurations.
On Windows, the complete configure, build, and test sequence can be run with:
```bat
tests\run.bat
tests\run.bat Debug
```
## Project layout
- `include/ikvxx/ikvxx.hpp`: public C++ API
- `src/value.cpp`: C++ binding implementation
- `tests/value_tests.cpp`: unit and integration tests
- `third_party/iKv`: pinned iKv Git submodule
- `cmake/ikvxxConfig.cmake.in`: installed CMake package configuration
- `.gitea/workflows/bindings.yml`: Ubuntu compiler and build matrix
## Format behavior
- Generic parse APIs auto-detect `iKv1` and `iKv2`
- Generic write APIs default to `iKv2`
- Text and binary files remain compatible with the underlying iKv C library
- Explicit `ikv::Version::v1` and `ikv::Version::v2` output is supported
- `iKv2` binary input retains iKv's indexed, lazy root lookup behavior
## Public API
The public interface is declared in
[`include/ikvxx/ikvxx.hpp`](./include/ikvxx/ikvxx.hpp). Key areas include:
- `ikv::Value` construction and automatic lifetime management
- JsonCpp-style object lookup with `operator[]`
- Array indexing and append operations
- Runtime type inspection with `isString`, `isInt`, `isObject`, and related methods
- Typed scalar access with `asString`, `asInt`, `asInt64`, `asDouble`, and `asBool`
- Text parsing, loading, writing, and in-place refresh
- Binary memory and file I/O
- Explicit format-version selection
## API constraints
iKvxx intentionally exposes errors where the underlying iKv model cannot safely
provide JsonCpp behavior:
- Roots must be objects or arrays; scalar roots are not supported by iKv
- Arrays of arrays are not supported by the current iKv C API
- Existing array elements cannot be replaced in place
- Copied `Value` handles share ownership of the same tree
- Structural assignment between two `Value` objects is disabled because the public C
API cannot clone arbitrary subtrees
- A child handle must not be retained across replacement of that child or a root
refresh; acquire the child again after modifying the structure
## License
This repository ships with the license text in [`LICENSE`](./LICENSE):
- Creative Commons Attribution-ShareAlike 4.0 International (`CC BY-SA 4.0`)
The iKv submodule includes its own license file at
[`third_party/iKv/LICENSE`](./third_party/iKv/LICENSE).