diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml
index bba50e6..1735809 100644
--- a/.github/workflows/cmake-single-platform.yml
+++ b/.github/workflows/cmake-single-platform.yml
@@ -7,6 +7,7 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
+ workflow_dispatch:
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
@@ -22,6 +23,9 @@ jobs:
steps:
- uses: actions/checkout@v4
+ - name: Install Catch2
+ run: sudo apt-get install catch2
+
- name: Configure CMake
# Configure CMake in a 'build' subdirectory
run: cmake -B ${{github.workspace}}/build
@@ -33,5 +37,5 @@ jobs:
working-directory: ${{github.workspace}}/build
# Execute tests defined by the CMake configuration.
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
- run: ctest --test-dir build --rerun-failed --output-on-failure
+ run: ctest --rerun-failed --output-on-failure
diff --git a/.github/workflows/make-docs.yml b/.github/workflows/make-docs.yml
new file mode 100644
index 0000000..120cf92
--- /dev/null
+++ b/.github/workflows/make-docs.yml
@@ -0,0 +1,50 @@
+name: Build Doxide Docs
+
+on:
+ push:
+ branches: [ "main" ]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+jobs:
+ build-docs:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Repo
+ uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v6
+ with:
+ python-version: '3.10'
+
+ - name: Install Doxide and MkDocs Material
+ run: |
+ echo 'deb http://download.indii.org/deb resolute main' | sudo tee /etc/apt/sources.list.d/indii.org.list
+ curl -fsSL https://download.indii.org/deb/Release.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/indii.org.gpg > /dev/null
+ sudo apt update
+ sudo apt install doxide
+ pip install mkdocs mkdocs-material
+
+ - name: Build Doxide Documentation
+ run: |
+ doxide build
+
+ - name: Build MkDocs Site
+ run: |
+ mkdocs build
+
+ - name: Configure GitHub Pages
+ uses: actions/configure-pages@v5
+
+ - name: Upload Pages artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: ./site
+
+ - name: Deploy to GitHub Pages
+ uses: actions/deploy-pages@v4
diff --git a/README.md b/README.md
index 635f7f6..537b1d2 100644
--- a/README.md
+++ b/README.md
@@ -80,16 +80,6 @@ int main() {
}
```
-## Explaination
-
-### Philosophy of approach
-
-This header tries to avoid JSON manipulation as much as possible. It focuses
-on a bare bones approach to RO-Crates; modelling an RO-Crate a flat list of entities,
-where each entity is a flat list of properties. The library does not attempt to
-model the RO-Crate specification in a more complex way, such as modelling the
-relationships between entities.
-
## How-To
### Create an RO-Crate
@@ -158,6 +148,68 @@ write the RO-Crate to. This will write out the `ro-crate-metadata.json` file.
crate.writeOut("./routput/");o
```
+## Explaination
+
+These notes are mostly aimed at developers who want to understand the design of
+this library, but may be of interest to users as well.
+
+### Philosophy of approach
+
+This header tries to avoid JSON manipulation as much as possible. It focuses
+on a bare bones approach to RO-Crates; modelling an RO-Crate a flat list of entities,
+where each entity is a flat list of properties. The library does not attempt to
+model the RO-Crate specification in a more complex way, such as modelling the
+relationships between entities.
+
+### Entity lifecycle
+
+When an entity is created, it is not automatically added to the RO-Crate. The user
+must explicitly add the entity to the RO-Crate using the `addEntity` method.
+
+When an entity is added to the RO-Crate, it is stored in a map of entities.
+
+For maximum flexiblity, an already added entity may still be updated on the
+original entity object. The RO-Crate shares the same entity object, so any changes
+made to the original entity will be reflected in the RO-Crate.
+
+Worked example:
+
+```cpp
+ROCrate crate;
+
+Entity alice({"Person"});
+alice.set("name", "Alice");
+
+crate.addEntity("#alice", alice);
+
+/* Crate state:
+{
+ "#alice": {
+ "type": ["Person"],
+ "name": "Alice"
+ }
+}
+*/
+
+// Add an additional property to the crate entity
+Entity crateAlice = crate.getEntity("#alice");
+crateAlice.set("description", "One of hopefully many Contextual Entities");
+
+// Add an additional property to the original entity object
+alice.set("occupation", "Software Engineer");
+
+/* Crate state:
+{
+ "#alice": {
+ "type": ["Person"],
+ "name": "Alice",
+ "description": "One of hopefully many Contextual Entities",
+ "occupation": "Software Engineer"
+ }
+}
+*/
+```
+
## Reference
-Doxygen or similar reference documentation to follow.
+Reference documentation generated by [doxide](doxide.org) is available [here](http://esciencelab.org.uk/ro-crate-cpp/).
diff --git a/docs/javascripts/mathjax.js b/docs/javascripts/mathjax.js
new file mode 100644
index 0000000..080801e
--- /dev/null
+++ b/docs/javascripts/mathjax.js
@@ -0,0 +1,16 @@
+window.MathJax = {
+ tex: {
+ inlineMath: [["\\(", "\\)"]],
+ displayMath: [["\\[", "\\]"]],
+ processEscapes: true,
+ processEnvironments: true
+ },
+ options: {
+ ignoreHtmlClass: ".*|",
+ processHtmlClass: "arithmatex"
+ }
+};
+
+document$.subscribe(() => {
+ MathJax.typesetPromise()
+})
diff --git a/docs/javascripts/tablesort.js b/docs/javascripts/tablesort.js
new file mode 100644
index 0000000..6a5afcf
--- /dev/null
+++ b/docs/javascripts/tablesort.js
@@ -0,0 +1,6 @@
+document$.subscribe(function() {
+ var tables = document.querySelectorAll("article table:not([class])")
+ tables.forEach(function(table) {
+ new Tablesort(table)
+ })
+})
diff --git a/docs/overrides/partials/copyright.html b/docs/overrides/partials/copyright.html
new file mode 100644
index 0000000..473f830
--- /dev/null
+++ b/docs/overrides/partials/copyright.html
@@ -0,0 +1,17 @@
+
diff --git a/docs/stylesheets/doxide.css b/docs/stylesheets/doxide.css
new file mode 100644
index 0000000..e6a9ccf
--- /dev/null
+++ b/docs/stylesheets/doxide.css
@@ -0,0 +1,58 @@
+:root {
+ --md-admonition-icon--variable: url('data:image/svg+xml;charset=utf-8,');
+ --md-admonition-icon--function: url('data:image/svg+xml;charset=utf-8,');
+ --md-admonition-icon--typedef: url('data:image/svg+xml;charset=utf-8,');
+ --md-admonition-icon--concept: url('data:image/svg+xml;charset=utf-8,');
+ --md-admonition-icon--macro: url('data:image/svg+xml;charset=utf-8,');
+}
+
+.md-typeset .admonition.variable, .md-typeset details.variable,
+.md-typeset .admonition.function, .md-typeset details.function,
+.md-typeset .admonition.typedef, .md-typeset details.typedef,
+.md-typeset .admonition.concept, .md-typeset details.concept,
+.md-typeset .admonition.macro, .md-typeset details.macro {
+ border-color: var(--md-default-fg-color--lighter);
+}
+
+.md-typeset .variable > .admonition-title, .md-typeset .variable > summary,
+.md-typeset .function > .admonition-title, .md-typeset .function > summary,
+.md-typeset .typedef > .admonition-title, .md-typeset .typedef > summary,
+.md-typeset .concept > .admonition-title, .md-typeset .concept > summary,
+.md-typeset .macro > .admonition-title, .md-typeset .macro > summary {
+ background-color: var(--md-default-bg-color);
+}
+
+.md-typeset .variable > .admonition-title::before,
+.md-typeset .variable > summary::before {
+ background-color: var(--md-default-fg-color--light);
+ -webkit-mask-image: var(--md-admonition-icon--variable);
+ mask-image: var(--md-admonition-icon--variable);
+}
+
+.md-typeset .function > .admonition-title::before,
+.md-typeset .function > summary::before {
+ background-color: var(--md-default-fg-color--light);
+ -webkit-mask-image: var(--md-admonition-icon--function);
+ mask-image: var(--md-admonition-icon--function);
+}
+
+.md-typeset .typedef > .admonition-title::before,
+.md-typeset .typedef > summary::before {
+ background-color: var(--md-default-fg-color--light);
+ -webkit-mask-image: var(--md-admonition-icon--typedef);
+ mask-image: var(--md-admonition-icon--typedef);
+}
+
+.md-typeset .concept > .admonition-title::before,
+.md-typeset .concept > summary::before {
+ background-color: var(--md-default-fg-color--light);
+ -webkit-mask-image: var(--md-admonition-icon--concept);
+ mask-image: var(--md-admonition-icon--concept);
+}
+
+.md-typeset .macro > .admonition-title::before,
+.md-typeset .macro > summary::before {
+ background-color: var(--md-default-fg-color--light);
+ -webkit-mask-image: var(--md-admonition-icon--macro);
+ mask-image: var(--md-admonition-icon--macro);
+}
diff --git a/doxide.yaml b/doxide.yaml
new file mode 100644
index 0000000..e6815ed
--- /dev/null
+++ b/doxide.yaml
@@ -0,0 +1,4 @@
+title: ro-crate-cpp
+description: A header only library for creating RO-Crates in C++
+files:
+ - "include/ro-crate.hpp"
diff --git a/include/ro-crate.hpp b/include/ro-crate.hpp
index 0331ac0..3449cdc 100644
--- a/include/ro-crate.hpp
+++ b/include/ro-crate.hpp
@@ -26,35 +26,64 @@ namespace rocrate {
using Properties = std::map>;
+ /**
+ * Represents an entity in the RO-Crate.
+ * An entity can have multiple types and properties, where each property can have multiple values.
+ * Properties can be set as either literal values or references to other entities.
+ */
class Entity {
public:
Entity() = delete;
+
+ /**
+ * Constructs an Entity with the specified types.
+ *
+ * @param types A vector of strings representing the types of the entity.
+ * @throw std::invalid_argument if the types vector is empty.
+ */
explicit Entity(std::vector types);
~Entity() = default;
-
+
+ /**
+ * Sets a property-value pair for the entity.
+ *
+ * @param property The name of the property to set.
+ * @param value The value to assign to the property.
+ * @param valueType The type of the value (Literal or Reference).
+ * @throw std::invalid_argument if the property name or value is empty, or if attempting to set '@id' directly.
+ */
void set(
Property property,
Value value,
ValueType type = ValueType::Literal
);
+
+ /**
+ * Sets a property to reference another entity.
+ *
+ * @param property The name of the property to set.
+ * @param entity The entity to reference.
+ * @throw std::invalid_argument if the property name is empty.
+ * @throw std::runtime_error if the referenced entity does not have an '@id' property set.
+ */
void set(Property property, const Entity& entity);
private:
friend class ROCrate;
+ /**
+ * Assigns an '@id' to the entity.
+ *
+ * @param id The identifier to assign to the entity.
+ * @throw std::invalid_argument if the id is empty.
+ */
void assignId(const std::string& id);
std::shared_ptr properties_;
};
inline Entity::Entity(std::vector types)
: properties_(std::make_shared()) {
- /**
- * @brief Constructs an Entity with the specified types.
- *
- * @param types A vector of strings representing the types of the entity.
- * @throws std::invalid_argument if the types vector is empty.
- */
// Validate types (reject empty)
if ( types.empty() ) {
@@ -69,15 +98,6 @@ namespace rocrate {
}
inline void Entity::set(Property property, Value value, ValueType valueType) {
- /**
- * @brief Sets a property-value pair for the entity.
- *
- * @param property The name of the property to set.
- * @param value The value to assign to the property.
- * @param valueType The type of the value (Literal or Reference).
- * @throws std::invalid_argument if the property name or value is empty, or if attempting to set '@id' directly.
- */
-
// Validate property and value
if (property.empty())
throw std::invalid_argument("Property name cannot be empty.");
@@ -92,15 +112,6 @@ namespace rocrate {
}
inline void Entity::set(Property property, const Entity& entity) {
- /**
- * @brief Sets a property to reference another entity.
- *
- * @param property The name of the property to set.
- * @param entity The entity to reference.
- * @throws std::invalid_argument if the property name is empty.
- * @throws std::runtime_error if the referenced entity does not have an '@id' property set.
- */
-
// Validate property name
if (property.empty()) {
throw std::invalid_argument("Property name cannot be empty.");
@@ -116,13 +127,6 @@ namespace rocrate {
}
inline void Entity::assignId(const std::string& id) {
- /**
- * @brief Assigns an '@id' to the entity.
- *
- * @param id The identifier to assign to the entity.
- * @throws std::invalid_argument if the id is empty.
- */
-
// Validate id
if (id.empty()) {
throw std::invalid_argument("Entity ID cannot be empty.");
@@ -138,12 +142,47 @@ namespace rocrate {
using EntityRegister = std::map;
+ /**
+ * Represents an RO-Crate, a structured collection of entities and metadata.
+ *
+ * The ROCrate class manages a collection of entities, including a root metadata entity and a root dataset entity.
+ * It provides methods to add entities, retrieve entities by their identifiers, and manage the relationships between entities.
+ */
class ROCrate {
public:
+ /**
+ * Constructs an RO-Crate with a root metadata entity and a root dataset entity.
+ *
+ * The constructor initializes the RO-Crate with an empty entity register, creates the root metadata entity
+ * (ro-crate-metadata.json) and the root dataset entity, and adds the root dataset entity to the root metadata entity.
+ */
ROCrate();
+ /**
+ * Adds an entity to the RO-Crate's entity register with the specified id.
+ *
+ * @param id The identifier for the entity.
+ * @param entity The entity to add to the RO-Crate.
+ * @throw std::invalid_argument if the id is empty.
+ * @throw std::runtime_error if an entity with the given id already exists in the RO-Crate.
+ */
void addEntity(const std::string& id, Entity& entity);
+
+ /**
+ * Retrieves an entity from the RO-Crate's entity register by its id.
+ *
+ * @param id The identifier of the entity to retrieve.
+ * @return A reference to the entity with the specified id.
+ * @throw std::runtime_error if no entity with the given id is found in the RO-Crate.
+ */
Entity& getEntity(const std::string& id);
+
+ /**
+ * Serializes the RO-Crate to a JSON file at the specified path.
+ *
+ * @param path The file path where the RO-Crate JSON will be written.
+ * @throw std::runtime_error if there is an error writing to the file.
+ */
void writeOut(const std::string& path);
private:
@@ -164,13 +203,6 @@ namespace rocrate {
};
inline ROCrate::ROCrate() {
- /**
- * @brief Constructs an RO-Crate with a root metadata entity and a root dataset entity.
- *
- * The constructor initializes the RO-Crate with an empty entity register, creates the root metadata entity
- * (ro-crate-metadata.json) and the root dataset entity, and adds the root dataset entity to the root metadata entity.
- */
-
// Initialise the RO-Crate with an empty entity register
entities_ = {};
@@ -192,15 +224,6 @@ namespace rocrate {
}
inline void ROCrate::addEntity(const std::string& id, Entity& entity) {
- /**
- * @brief Adds an entity to the RO-Crate's entity register with the specified id.
- *
- * @param id The identifier for the entity.
- * @param entity The entity to add to the RO-Crate.
- * @throws std::invalid_argument if the id is empty.
- * @throws std::runtime_error if an entity with the given id already exists in the RO-Crate.
- */
-
// Validate the id (reject empty)
if (id.empty()) {
throw std::invalid_argument("Entity ID cannot be empty.");
@@ -219,14 +242,6 @@ namespace rocrate {
}
inline Entity& ROCrate::getEntity(const std::string& id) {
- /**
- * @brief Retrieves an entity from the RO-Crate's entity register by its id.
- *
- * @param id The identifier of the entity to retrieve.
- * @return A reference to the entity with the specified id.
- * @throws std::runtime_error if no entity with the given id is found in the RO-Crate.
- */
-
auto it = entities_.find(id);
if (it == entities_.end()) {
throw std::runtime_error("Entity with id '" + id + "' not found in the RO-Crate.");
@@ -234,14 +249,7 @@ namespace rocrate {
return it->second;
}
- inline void ROCrate::writeOut(const std::string& path) {
- /**
- * @brief Serializes the RO-Crate to a JSON file at the specified path.
- *
- * @param path The file path where the RO-Crate JSON will be written.
- * @throws std::runtime_error if there is an error writing to the file.
- */
-
+ inline void ROCrate::writeOut(const std::string& path) {
nlohmann::json outCrate = {
{"@context", "https://w3id.org/ro/crate/1.1/context"},
{"@graph", nlohmann::json::array()}
diff --git a/mkdocs.yaml b/mkdocs.yaml
new file mode 100644
index 0000000..312f354
--- /dev/null
+++ b/mkdocs.yaml
@@ -0,0 +1,45 @@
+site_name: ro-crate-cpp
+site_description: A header only library for creating RO-Crates in C++
+theme:
+ name: material
+ custom_dir: docs/overrides
+ features:
+ - navigation.indexes
+ palette:
+ # Palette toggle for light mode
+ - scheme: default
+ primary: red
+ accent: red
+ toggle:
+ icon: material/brightness-7
+ name: Switch to dark mode
+
+ # Palette toggle for dark mode
+ - scheme: slate
+ primary: red
+ accent: red
+ toggle:
+ icon: material/brightness-4
+ name: Switch to light mode
+
+markdown_extensions:
+ - def_list
+ - attr_list
+ - admonition
+ - pymdownx.details
+ - pymdownx.superfences
+ - pymdownx.arithmatex:
+ generic: true
+ - pymdownx.emoji:
+ emoji_index: !!python/name:material.extensions.emoji.twemoji
+ emoji_generator: !!python/name:material.extensions.emoji.to_svg
+plugins:
+ - search
+extra_css:
+ - stylesheets/doxide.css
+extra_javascript:
+ - javascripts/mathjax.js
+ - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
+ - https://cdn.jsdelivr.net/npm/tablesort@5.3.0/src/tablesort.min.js
+ - https://cdn.jsdelivr.net/npm/tablesort@5.3.0/src/sorts/tablesort.number.js
+ - javascripts/tablesort.js
diff --git a/vendor/json.hpp b/vendor/nlohmann/json.hpp
similarity index 100%
rename from vendor/json.hpp
rename to vendor/nlohmann/json.hpp