From e4996722f0d61c23dcb21a33a3a44882a3322522 Mon Sep 17 00:00:00 2001 From: Matt Burridge <81111055+mburridge96@users.noreply.github.com> Date: Wed, 20 Nov 2024 11:54:15 +0000 Subject: [PATCH] working updates for graph-vector refactor --- cli/README.md | 253 +--------------------------- cli/src/main.rs | 19 +-- examples/create_and_write.py | 19 ++- examples/ro-crate-metadata.json | 16 +- python/src/lib.rs | 29 +++- src/ro_crate/contextual_entity.rs | 2 +- src/ro_crate/data_entity.rs | 2 +- src/ro_crate/graph_vector.rs | 2 +- src/ro_crate/metadata_descriptor.rs | 2 +- src/ro_crate/rocrate.rs | 2 + src/ro_crate/root.rs | 2 +- 11 files changed, 52 insertions(+), 296 deletions(-) diff --git a/cli/README.md b/cli/README.md index 08b3661..a2b3175 100644 --- a/cli/README.md +++ b/cli/README.md @@ -4,255 +4,4 @@ The ro-crate-rs cli tool can be used to immediately interface with RO-Crates wit # Basics -This cli allows basic interaction with an RO-Crate, such as reading, writing, updating and so on. - -## Initial commands - -```bash -Usage: rocrate - -Commands: - init Initialise a new empty Ro-Crate - add Add an entity to an Ro-Crate - delete Delete an entity in an Ro-Crate - modify Modify a particular entity within an Ro-Crate (includes Root and Descriptor) - read Read the crate and display - package Allows you to package crate into different formats - help Print this message or the help of the given subcommand(s) - -Options: - -h, --help Print help - -V, --version Print version -``` - -## Init command - -```bash -Initialise a new empty Ro-Crate - -Usage: rocrate init [OPTIONS] - -Options: - -d, --default Default ro-crate initialisation using latest spec - -c, --context-type Type of context: - 1 - Reference Context: Basic context type with minimal fields. - 2 - Extended Context: Includes additional metadata fields. - 3 - Embedded Context: Contains embedded data for richer context. - -m, --minimal Initialise with default minimal entites or leave empty - -h, --help Print help -``` - -## Add command -```bash -Add an entity to an Ro-Crate - -Usage: rocrate add [OPTIONS] [DATATYPE]... - -Arguments: - Input ID - [DATATYPE]... Input Datatype. This can be a single datatype, or a list seperated by ',' (e.g type1,type2) - -Options: - -t, --target-crate Target crate [default: ro-crate-metadata.json] - -e, --extra-information Extra information. Allows you to input Custom fields and values - -h, --help Print help -``` -## Delete command -```bash -Delete an entity in an Ro-Crate - -Usage: rocrate delete [OPTIONS] - -Arguments: - Input ID to delete - -Options: - -t, --target-crate Target crate [default: ro-crate-metadata.json] - -r, --recursive Recursive deletion. Allows you to delete all occurances of the ID linked in other research objects - -h, --help Print help -``` - -## Modify command -```bash -Modify a particular entity within an Ro-Crate (includes Root and Descriptor) - -Usage: rocrate modify - -Commands: - add-string Add a string value to an entity - add-id-value Add a ID to an entity - add-id-vec-values Add a list of ID's to an entity - add-multiple Add multiple new fields to entity - Useful for large crates - remove-field Remove a specific field from an entity - help Print this message or the help of the given subcommand(s) - -Options: - -h, --help Print help -``` -### add-string -```bash -Add a string value to an entity - -Usage: rocrate modify add-string [OPTIONS] - -Arguments: - Input ID to add to - Key to add - Value to add - -Options: - -t, --target-crate Target crate [default: ro-crate-metadata.json] - -h, --help Print help -``` -### add-id-value -```bash -Add a ID to an entity - -Usage: rocrate modify add-id-value [OPTIONS] - -Arguments: - Input ID to target - Key to add - ID value to add - -Options: - -t, --target-crate Target crate [default: ro-crate-metadata.json] - -h, --help Print help -``` -### add-id-vec-values -```bash -Add a list of ID's to an entity - -Usage: rocrate modify add-id-vec-values [OPTIONS] [VALUES]... - -Arguments: - Input ID to target - Key to add - [VALUES]... ID values to add. This can be a single id, or a list seperated by ',' (e.g id1,id2) - -Options: - -t, --target-crate Target crate [default: ro-crate-metadata.json] - -h, --help Print help - -``` -### add-multiple -```bash -Add multiple new fields to entity - Useful for large crates - -Usage: rocrate modify add-multiple [OPTIONS] - -Arguments: - Input ID to target - -Options: - -t, --target-crate Target crate [default: ro-crate-metadata.json] - -h, --help Print help -``` -### remove-field -```bash -Remove a specific field from an entity - -Usage: rocrate modify remove-field [OPTIONS] - -Arguments: - Input ID to delete - Field to delete - -Options: - -t, --target-crate Target crate [default: ro-crate-metadata.json] - -h, --help Print help -``` - -## Read command -```bash -Read the crate and display - -Usage: rocrate read - -Commands: - crate Read full crate - entity Read entity of crate - fields Read all of one field in crate - value Read entity containing specific value - help Print this message or the help of the given subcommand(s) - -Options: - -h, --help Print help -``` - -### crate -```bash -Read full crate - -Usage: rocrate read crate [OPTIONS] - -Options: - -t, --target-crate Target crate [default: ro-crate-metadata.json] - -r, --raw-struct Raw struct data - -h, --help Print help -``` -### entity -```bash -Read entity of crate - -Usage: rocrate read entity [OPTIONS] - -Arguments: - Entity ID to search - -Options: - -t, --target-crate Target crate [default: ro-crate-metadata.json] - -r, --raw-struct Raw struct data - -h, --help Print help -``` -### fields -```bash -Read all of one field in crate - -Usage: rocrate read fields [OPTIONS] - -Arguments: - Field to search for - -Options: - -t, --target-crate Target crate [default: ro-crate-metadata.json] - -h, --help Print help -``` -### value -```bash -Read entity containing specific value - -Usage: rocrate read value [OPTIONS] - -Arguments: - Field to search for - -Options: - -t, --target-crate Target crate [default: ro-crate-metadata.json] - -l, --location Show the object location - -h, --help Print help -``` -## Package command -```bash -Allows you to package crate into different formats - -Usage: rocrate package - -Commands: - zip Read full crate - help Print this message or the help of the given subcommand(s) - -Options: - -h, --help Print help -``` -### zip -```bash -Read full crate - -Usage: rocrate package zip [OPTIONS] - -Options: - -t, --target-folder [default: ./] - -h, --help Print help -``` - +This cli allows basic interaction with an RO-Crate, such as reading, writing, updating, validating and so on. diff --git a/cli/src/main.rs b/cli/src/main.rs index 74b44a0..8cc6eef 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -223,13 +223,8 @@ fn main() { fn open_and_load_crate(input: &str) -> RoCrate { let target_crate = crate_path(input); match read_crate(&target_crate, 1) { - Ok(ro_crate) => { - // Process ro_crate if read successfully - // ... - ro_crate - } + Ok(ro_crate) => ro_crate, Err(e) => { - // Handle the error eprintln!("Error processing crate: {:?}", e); std::process::exit(1) } @@ -251,7 +246,7 @@ fn create_rocrate_with_context(context_type: ContextType) -> RoCrate { // Get the primary context (probably ro-crate spec) println!("Please primary context (default to ro-crate spac (1)):"); - io::stdout().flush().unwrap(); // Make sure 'Enter key' is printed before input + io::stdout().flush().unwrap(); let mut answer = String::new(); io::stdin().read_line(&mut answer).unwrap(); @@ -302,12 +297,12 @@ fn loop_input(mut key_value_pairs: HashMap) -> HashMap u8 { println!("6 - A boolean (true/false)"); let mut answer = String::new(); - io::stdout().flush().unwrap(); // Ensure prompt is displayed before input + io::stdout().flush().unwrap(); io::stdin().read_line(&mut answer).unwrap(); match answer.trim().parse::() { @@ -522,7 +517,7 @@ fn get_field_values_with_count( fn collect_field_values_recursive( json: &JsonValue, field_name: &str, - collected_values: &mut HashMap<(String, String), isize>, // (id, value) -> count + collected_values: &mut HashMap<(String, String), isize>, ) { match json { JsonValue::Object(obj) => { @@ -536,7 +531,7 @@ fn collect_field_values_recursive( for (key, value) in obj { if key == field_name { let value_str = value.to_string(); - let key = (current_id.clone(), value_str); // Store as tuple (id, type) + let key = (current_id.clone(), value_str); *collected_values.entry(key).or_insert(0) += 1; } // Continue recursive search within the object diff --git a/examples/create_and_write.py b/examples/create_and_write.py index 0656e5e..6ddcb15 100644 --- a/examples/create_and_write.py +++ b/examples/create_and_write.py @@ -24,8 +24,10 @@ "id": "./", "type": "Dataset", "datePublished": "2017", + "name": "Example", + "description": "Example Ro-Crate for using python library", "license": {"id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/"}, - "author": {"id": "#johndoe"}, + "author": {"id": "#JohnDoe"}, } # Data entity data = {"id": "output/data_file.txt", "type": "Dataset", "name": "Data file name"} @@ -52,24 +54,26 @@ # Now that a new crate is written, we can open it again! -crate = read("ro-crate-metadata.json", 1) +crate = read("ro-crate-metadata.json", 0) +# print(f"Example of crate created and read in \n {crate}") # Update the data entity and make modification data_target = crate.get_entity("output/data_file.txt") +# print(data_target) data_target["description"] = "A text file dataset containing information" -print(f"This is the loaded and modified data_file entity \n {data_target}") +# print(f"This is the loaded and modified data_file entity \n {data_target}") crate.update_data(data_target) -print(f"This is now the updated, in memory, crate: \n {crate}") +# print(f"This is now the updated, in memory, crate: \n {crate}") # Update the contextual entity and make modification contextual_target = crate.get_entity("#JohnDoe") contextual_target.update({"id": "#JaneDoe"}) crate.update_contextual(contextual_target) -print(f"Example of a modified entity id that will save as a new entity: \n {crate}") +# print(f"Example of a modified entity id that will save as a new entity: \n {crate}") # To delete a key:value data_target.pop("description") @@ -80,12 +84,13 @@ # To delete an entity - this immediately updates the crate object crate.delete_entity("#JaneDoe", True) +crate.replace_id("#JohnDoe", "#JaneDoe") +# print(crate) crate.write() # Final example of modified crate crate = read("ro-crate-metadata.json", 1) -print(crate) # Zip the crate to get all data -zip("ro-crate-metadata.json", 1) +# zip("ro-crate-metadata.json", True, 2) diff --git a/examples/ro-crate-metadata.json b/examples/ro-crate-metadata.json index c7e9f6e..a6f16d1 100644 --- a/examples/ro-crate-metadata.json +++ b/examples/ro-crate-metadata.json @@ -1,5 +1,5 @@ { - "@context": " https://w3id.org/ro/crate/1.1/context", + "@context": "https://w3id.org/ro/crate/1.1/context", "@graph": [ { "@id": "ro-crate-metadata.json", @@ -14,20 +14,14 @@ { "@id": "./", "@type": "Dataset", + "name": "Example", + "description": "Example Ro-Crate for using python library", "datePublished": "2017", "license": { "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/" }, - "hasPart": [ - { - "@id": "output/data_file.txt" - }, - { - "@id": "this_is_not_a_file.txt" - } - ], "author": { - "@id": "#johndoe" + "@id": "#JaneDoe" } }, { @@ -36,7 +30,7 @@ "name": "Data file name" }, { - "@id": "#JohnDoe", + "@id": "#JaneDoe", "@type": "Person" }, { diff --git a/python/src/lib.rs b/python/src/lib.rs index 2069a59..b4ba320 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -127,9 +127,12 @@ impl PyRoCrate { let data_entity_wrapper: utils::DataEntityWrapper = py_obj.extract(py)?; let data_entity = data_entity_wrapper.0; // Access the inner DataEntity let id = data_entity.id.clone(); - let update = GraphVector::DataEntity(data_entity); + let update = GraphVector::DataEntity(data_entity.clone()); - self.inner.overwrite_by_id(&id, update); + if !self.inner.overwrite_by_id(&id, update) { + let add = GraphVector::DataEntity(data_entity); + self.inner.graph.push(add) + }; Ok(()) } @@ -145,9 +148,12 @@ impl PyRoCrate { let contextual_entity_wrapper: utils::ContextualEntityWrapper = py_obj.extract(py)?; let contextual_entity = contextual_entity_wrapper.0; // Access the inner DataEntity let id = contextual_entity.id.clone(); - let update = GraphVector::ContextualEntity(contextual_entity); + let update = GraphVector::ContextualEntity(contextual_entity.clone()); - self.inner.overwrite_by_id(&id, update); + if !self.inner.overwrite_by_id(&id, update) { + let add = GraphVector::ContextualEntity(contextual_entity); + self.inner.graph.push(add); + }; Ok(()) } @@ -163,9 +169,12 @@ impl PyRoCrate { let root_entity_wrapper: utils::RootDataEntityWrapper = py_obj.extract(py)?; let root_entity = root_entity_wrapper.0; // Access the inner DataEntity let id = root_entity.id.clone(); - let update = GraphVector::RootDataEntity(root_entity); + let update = GraphVector::RootDataEntity(root_entity.clone()); - self.inner.overwrite_by_id(&id, update); + if !self.inner.overwrite_by_id(&id, update) { + let add = GraphVector::RootDataEntity(root_entity); + self.inner.graph.push(add); + }; Ok(()) } @@ -181,10 +190,12 @@ impl PyRoCrate { let descriptor_wrapper: utils::MetadataDescriptorWrapper = py_obj.extract(py)?; let descriptor = descriptor_wrapper.0; // Access the inner DataEntity let id = descriptor.id.clone(); - let update = GraphVector::MetadataDescriptor(descriptor); - - self.inner.overwrite_by_id(&id, update); + let update = GraphVector::MetadataDescriptor(descriptor.clone()); + if !self.inner.overwrite_by_id(&id, update) { + let add = GraphVector::MetadataDescriptor(descriptor); + self.inner.graph.push(add); + } Ok(()) } diff --git a/src/ro_crate/contextual_entity.rs b/src/ro_crate/contextual_entity.rs index cd6fe88..5e43eed 100644 --- a/src/ro_crate/contextual_entity.rs +++ b/src/ro_crate/contextual_entity.rs @@ -14,7 +14,7 @@ use std::fmt; /// Defines a contextual entity - an entity without a local /// or remote data file that is essential for crate undestanding -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ContextualEntity { /// Can be a URI but must be some form of local unique identifier for the current /// crate. If a local, should prefix with # (e.g {"@id": "#alice"}) or a blank diff --git a/src/ro_crate/data_entity.rs b/src/ro_crate/data_entity.rs index bded47b..dcf4350 100644 --- a/src/ro_crate/data_entity.rs +++ b/src/ro_crate/data_entity.rs @@ -18,7 +18,7 @@ use std::fmt; /// a specific type (`type_`), and a set of dynamic properties (`dynamic_entity`). /// This struct is used to handle Data entities that have a predefined structure along with /// additional properties that may vary. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct DataEntity { /// URI to data pub id: String, diff --git a/src/ro_crate/graph_vector.rs b/src/ro_crate/graph_vector.rs index c2357a4..8c837fb 100644 --- a/src/ro_crate/graph_vector.rs +++ b/src/ro_crate/graph_vector.rs @@ -24,7 +24,7 @@ use url::Url; /// - `ContextualEntity`: Represents a contextual entity that provides context for the data entities and the crate. /// - `MetadataDescriptor`: Contains metadata about the crate itself or its entities. /// - `RootDataEntity`: The root data entity, representing the crate's primary content. -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum GraphVector { DataEntity(DataEntity), ContextualEntity(ContextualEntity), diff --git a/src/ro_crate/metadata_descriptor.rs b/src/ro_crate/metadata_descriptor.rs index c6751fc..ee3555b 100644 --- a/src/ro_crate/metadata_descriptor.rs +++ b/src/ro_crate/metadata_descriptor.rs @@ -19,7 +19,7 @@ use std::fmt; /// `MetadataDescriptor` is designed to encapsulate metadata information for an ro-crate. /// It includes the fields that MUST be in included - `id`, `type_`, `conforms_to`, `about`, /// and a set of dynamic properties for descriptive extension. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct MetadataDescriptor { /// ID that MUST be `ro-crate-metadata.json` pub id: String, diff --git a/src/ro_crate/rocrate.rs b/src/ro_crate/rocrate.rs index 783e59e..cb5193f 100644 --- a/src/ro_crate/rocrate.rs +++ b/src/ro_crate/rocrate.rs @@ -279,6 +279,8 @@ impl RoCrate { } pub fn overwrite_by_id(&mut self, id: &str, entity: GraphVector) -> bool { + println!("id: {}", id); + println!("Entity: {:?}", entity); if let Some(index) = self.find_entity_index(id) { self.graph[index] = entity; true diff --git a/src/ro_crate/root.rs b/src/ro_crate/root.rs index 68c0958..fb731b3 100644 --- a/src/ro_crate/root.rs +++ b/src/ro_crate/root.rs @@ -21,7 +21,7 @@ use super::constraints::Id; // # Note // Should update the type_ and id to follow requirements for future unless spec // change. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct RootDataEntity { // A string that SHOULD be ./ and MUST end with / pub id: String,