docs(readme): simplify project overview
This commit is contained in:
273
README.md
273
README.md
@@ -1,22 +1,10 @@
|
|||||||
# iKvxx
|
# iKvxx
|
||||||
|
|
||||||
iKvxx is a modern C++17 binding for [iKv](https://dock-it.dev/iDENTITY-Technology/iKv).
|
Modern C++17 bindings for [iKv](https://dock-it.dev/iDENTITY-Technology/iKv), with RAII ownership and a JsonCpp-style `Value` API.
|
||||||
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
|
For installation, the complete API reference, format details, binary layout, testing, and more examples, see the **[iKvxx Wiki](https://dock-it.dev/iDENTITY-Technology/iKvxx/wiki)**.
|
||||||
|
|
||||||
- Parse iKv text from strings or files
|
## Simple syntax
|
||||||
- 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
|
```ikv
|
||||||
ikv2 "player_save"
|
ikv2 "player_save"
|
||||||
@@ -24,7 +12,7 @@ ikv2 "player_save"
|
|||||||
"title" "iKvxx demo"
|
"title" "iKvxx demo"
|
||||||
"version" 2
|
"version" 2
|
||||||
"player" {
|
"player" {
|
||||||
"name" "jondoe"
|
"name" "Ada"
|
||||||
"alive" true
|
"alive" true
|
||||||
"speed" 12.5
|
"speed" 12.5
|
||||||
}
|
}
|
||||||
@@ -48,7 +36,7 @@ int main()
|
|||||||
root["version"] = 2;
|
root["version"] = 2;
|
||||||
|
|
||||||
auto player = root.makeObject("player");
|
auto player = root.makeObject("player");
|
||||||
player["name"] = "jondoe";
|
player["name"] = "Ada";
|
||||||
player["alive"] = true;
|
player["alive"] = true;
|
||||||
player["speed"] = 12.5;
|
player["speed"] = 12.5;
|
||||||
|
|
||||||
@@ -58,255 +46,14 @@ int main()
|
|||||||
inventory.append("map");
|
inventory.append("map");
|
||||||
|
|
||||||
root.write("player_save.ikv");
|
root.write("player_save.ikv");
|
||||||
|
|
||||||
|
auto loaded = ikv::Value::load("player_save.ikv");
|
||||||
|
return loaded["player"]["alive"].asBool() ? 0 : 1;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
CTest runs every iKvxx test group separately and also includes the complete upstream
|
|
||||||
iKv suite. The binding tests cover 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. The Gitea Actions
|
|
||||||
workflow follows iKv's Ubuntu build-and-test structure, with one CMake build job and
|
|
||||||
one job that runs the complete test suite.
|
|
||||||
|
|
||||||
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
|
## License
|
||||||
|
|
||||||
This repository ships with the license text in [`LICENSE`](./LICENSE):
|
This project is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International license](./LICENSE).
|
||||||
|
|
||||||
- Creative Commons Attribution-ShareAlike 4.0 International (`CC BY-SA 4.0`)
|
The iKv submodule includes its own [license file](./third_party/iKv/LICENSE).
|
||||||
|
|
||||||
The iKv submodule includes its own license file at
|
|
||||||
[`third_party/iKv/LICENSE`](./third_party/iKv/LICENSE).
|
|
||||||
|
|||||||
Reference in New Issue
Block a user