diff --git a/.gitignore b/.gitignore index 25af6e7..0135b0a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +examples/examples.zip ro-crate-metadata .vscode* ro-crate-metadata_big* @@ -5,6 +6,7 @@ ro-crate-metadata_big* __pycache__/ *.py[cod] *$py.class +venv_mdp* # C extensions *.so diff --git a/Cargo.lock b/Cargo.lock index b028988..ff1d680 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", @@ -45,9 +45,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.7" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd2405b3ac1faab2990b74d728624cd9fd115651fcecc7c2d8daf01376275ba" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", @@ -59,9 +59,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anstyle-parse" @@ -93,15 +93,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -132,9 +132,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "block-buffer" @@ -147,9 +147,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" @@ -159,9 +159,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "bzip2" @@ -186,9 +186,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41" dependencies = [ "jobserver", "libc", @@ -202,9 +202,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ "android-tzdata", "iana-time-zone", @@ -212,7 +212,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.48.5", + "windows-targets 0.52.4", ] [[package]] @@ -227,9 +227,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.17" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80932e03c33999b9235edb8655bc9df3204adc9887c2f95b50cb1deb9fd54253" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -237,9 +237,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.17" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c0db58c659eef1c73e444d298c27322a1b52f6927d2ad470c0c0f96fa7b8fa" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", @@ -249,9 +249,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ "heck", "proc-macro2", @@ -261,9 +261,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "colorchoice" @@ -304,9 +304,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] @@ -374,9 +374,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] name = "flate2" @@ -394,21 +394,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -478,9 +463,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "libc", @@ -495,9 +480,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "h2" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -520,15 +505,9 @@ checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "hermit-abi" -version = "0.3.4" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hmac" @@ -541,9 +520,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -597,24 +576,11 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "iana-time-zone" -version = "0.1.59" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -645,9 +611,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.1.0" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", @@ -670,57 +636,51 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.67" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - [[package]] name = "libc" -version = "0.2.152" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "mime" @@ -730,18 +690,18 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", @@ -749,42 +709,20 @@ dependencies = [ ] [[package]] -name = "native-tls" -version = "0.2.11" +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "object" version = "0.32.2" @@ -800,50 +738,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "openssl" -version = "0.10.62" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" -dependencies = [ - "bitflags 2.4.1", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "password-hash" version = "0.4.2" @@ -875,9 +769,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -887,9 +781,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "powerfmt" @@ -905,9 +799,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.76" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -951,20 +845,11 @@ dependencies = [ "getrandom", ] -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "reqwest" -version = "0.11.23" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "base64", "bytes", @@ -975,21 +860,19 @@ dependencies = [ "http", "http-body", "hyper", - "hyper-tls", "ipnet", "js-sys", "log", "mime", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", - "tokio-native-tls", "tower-service", "url", "wasm-bindgen", @@ -1033,11 +916,11 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.30" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -1046,9 +929,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "same-file" @@ -1059,52 +942,20 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "security-framework" -version = "2.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "serde" -version = "1.0.195" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.195" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", @@ -1113,9 +964,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.111" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ "itoa", "ryu", @@ -1167,19 +1018,19 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" @@ -1189,15 +1040,21 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "2.0.48" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "system-configuration" version = "0.5.1" @@ -1221,24 +1078,24 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", "rustix", "windows-sys 0.52.0", ] [[package]] name = "time" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ "deranged", + "num-conv", "powerfmt", "serde", "time-core", @@ -1267,30 +1124,19 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.1" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "pin-project-lite", "socket2", "windows-sys 0.48.0", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.10" @@ -1344,9 +1190,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -1356,9 +1202,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] @@ -1382,19 +1228,13 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom", ] -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.4" @@ -1403,9 +1243,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -1428,9 +1268,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1438,9 +1278,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", @@ -1453,9 +1293,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.40" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -1465,9 +1305,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1475,9 +1315,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", @@ -1488,15 +1328,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" -version = "0.3.67" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -1539,7 +1379,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -1557,7 +1397,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -1577,17 +1417,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -1598,9 +1438,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -1610,9 +1450,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -1622,9 +1462,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -1634,9 +1474,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -1646,9 +1486,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" @@ -1658,9 +1498,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -1670,9 +1510,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "winreg" @@ -1725,9 +1565,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.10+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 12ca2f4..b34675a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ name = "ro-crate-rs" version = "0.1.0" edition = "2021" repository = "https://github.com/intbio-ncl/ro-crate-rs" +authors = ["Matt Burridge "] autoexamples = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -24,7 +25,7 @@ rand = "0.8" url = "2.2" zip = "0.6.6" walkdir = "2" -reqwest = { version = "0.11", features = ["blocking", "json"]} +reqwest = { version = "0.11", features = ["blocking", "json"], default-features = false} [dev-dependencies] tempfile = "3.9" diff --git a/README.md b/README.md index 293a977..8632e4b 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,12 @@ incorprate it into scrpits/ data pipelines as easily as possible. This also relies on you to have an understanding of the structure of an RO-Crate, but focuses more on the fact that some metadata is better than no metadata. +# Libraries + +1. [ro-crate-rs core](src/README,md) +2. [rocraters python bindings](python/README.md) +3. [cli tool](cli/README.md) + # Compatability RO-Crate v1.1 diff --git a/cli/src/args.rs b/cli/src/args.rs index bc76757..5add0dd 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -1,3 +1,7 @@ +//! Contains all the arguments for cli tool +//! +//! Built with clap + use clap::{Args, Parser, Subcommand}; use std::str::FromStr; @@ -221,8 +225,11 @@ pub struct RemoveFieldCommand { #[derive(Debug, Args)] pub struct ZipCrateCommand { + // Target crate #[clap(short,long,default_value_t=String::from("./"))] - pub target_folder: String, + pub target_crate: String, + // Copy and include external reachable data files + pub external: bool, } #[derive(Debug, Subcommand)] diff --git a/cli/src/main.rs b/cli/src/main.rs index 1b9d192..f1affbf 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,8 +1,10 @@ +//! Cli binding logic + use args::{ AddCommand, ContextType, CrateAction, DeleteCommand, ModifyCommand, PackageCommand, ReadCommand, }; use chrono::Utc; -use clap::{Args, Parser}; +use clap::Parser; use constraints::{DataType, DynamicEntity, Id, IdValue, License}; use data_entity::DataEntity; use read::{crate_path, read_crate}; @@ -171,13 +173,13 @@ fn main() { } } ReadCommand::Fields(read_fields_command) => { - let mut rocrate = open_and_load_crate(&read_fields_command.target_crate); + let rocrate = open_and_load_crate(&read_fields_command.target_crate); let values = get_field_values_with_count(&rocrate.graph, &read_fields_command.field); print_as_table(values, "Type".to_string(), "Count".to_string()) } ReadCommand::Value(read_value_command) => { - let mut rocrate = open_and_load_crate(&read_value_command.target_crate); + let rocrate = open_and_load_crate(&read_value_command.target_crate); let values = search_and_print_struct( &rocrate.graph, &read_value_command.value, @@ -190,13 +192,13 @@ fn main() { CrateAction::Package(package_command) => match package_command { PackageCommand::Zip(zip_command) => { let path: PathBuf; - if zip_command.target_folder == "./" { + if zip_command.target_crate == "./" { path = std::env::current_dir().unwrap(); } else { - path = crate_path(zip_command.target_folder.as_str()); + path = crate_path(zip_command.target_crate.as_str()); } println!("{:?}", path); - let _ = zip_crate(&path); + let _ = zip_crate(&path, true); } }, } @@ -205,7 +207,7 @@ fn main() { /// Input requires target_crate file string fn open_and_load_crate(input: &String) -> RoCrate { let target_crate = crate_path(&input); - match read_crate(target_crate) { + match read_crate(&target_crate, false) { Ok(ro_crate) => { // Process ro_crate if read successfully // ... @@ -343,7 +345,7 @@ fn add_entity(mut rocrate: RoCrate, input: &AddCommand) -> RoCrate { None }; - let mut data_entity = DataEntity { + let data_entity = DataEntity { id: input.id.to_string(), type_: datatype, dynamic_entity: entities, @@ -543,7 +545,7 @@ fn search_and_print_struct(object: &T, search_value: &str, locatio occurrences } -/// Method for searching based upon untyped serde value +/// Method for searching based upon untyped serde value fn search_and_print_recursive( json: &JsonValue, search_value: &str, @@ -552,7 +554,7 @@ fn search_and_print_recursive( ) -> isize { match json { JsonValue::Object(obj) => { - for (key, value) in obj { + for (_key, value) in obj { if value == search_value { occurrences += 1; if location { diff --git a/docs/ro-crate-schema.svg b/docs/ro-crate-schema.svg new file mode 100644 index 0000000..c2cec5f --- /dev/null +++ b/docs/ro-crate-schema.svg @@ -0,0 +1,5 @@ + + + + + CreateReadInitialisationrocrate.rsread.rsDeserialiseRoCratedatastructure context:RoCrateContextgraph: Vec<GraphVector>MetadataDescriptorRootDataEntityDataEntityContextualEntityFallbackValuewrite.rsro-crate-metadata.jsoncrate.zipWriteSerialisedata_entity.rscontextual_entity.rsroot.rsmetadata_descriptor.rsEmbedded ContextExtended ContextReference Contextmodify.rs(focused on dynamic_entites)contraints.rsschema.rsDeserialiseValidateFileStruct FieldData structureReferencesrelated toKeyDeserialise / serialiseSerde Lib \ No newline at end of file diff --git a/examples/create_and_write.py b/examples/create_and_write.py new file mode 100644 index 0000000..5a5315c --- /dev/null +++ b/examples/create_and_write.py @@ -0,0 +1,98 @@ +from rocraters import PyRoCrateContext, PyRoCrate, read, zip + +# Define context +context = PyRoCrateContext.from_string(" https://w3id.org/ro/crate/1.1/context") + +# Initialise empty crate +crate = PyRoCrate(context) + +# For an easy start, you can make a default crate! +default_crate = PyRoCrate.new_default() + +print(f"Example of a default crate \n {default_crate}") + +# Metadata descriptor +descriptor = { + "type": "CreativeWork", + "id": "ro-crate-metadata.json", + "conformsTo": {"id": "https://w3id.org/ro/crate/1.1"}, + "about": {"id": "./"} +} + +# Root data entity +root = { + "id": "./", + "type": "Dataset", + "datePublished": "2017", + "license": {"id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/"}, + "author": {"id": "#johndoe"} +} +# Data entity +data = { + "id": "output/data_file.txt", + "type": "Dataset", + "name": "Data file name" +} +# Contextual entity +contextual = { + "id": "#JohnDoe", + "type": "Person", +} + +failed_data = { + "id": "this_is_not_a_file.txt", + "type": "Dataset" +} + +# Update the RO-Crate object +crate.update_descriptor(descriptor) +crate.update_root(root) +crate.update_data(data) +crate.update_contextual(contextual) + +# This acts as an example of how if the file/ URI isn't valid as a potentially +# accessible data entity, it loads as a contextual entity +crate.update_data(failed_data) + +# Write crate +crate.write() + + +# Now that a new crate is written, we can open it again! +crate = read("ro-crate-metadata.json", True) + +# Update the data entity and make modification +data_target = crate.get_entity("output/data_file.txt") +data_target["description"] = "A text file dataset containing information" + +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}") + +# 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}") + +# To delete a key:value +data_target.pop("description") + +# We then update the crate the same way we make it +# The ID will be used to serach the crate and overwrites the object with an indentical "id" key +crate.update_data(data_target) + +# To delete an entity - this immediately updates the crate object +crate.delete_entity("#JaneDoe", True) + +crate.write() + +# Final example of modified crate +crate = read("ro-crate-metadata.json", True) +print(crate) + +# Zip the crate to get all data +zip("ro-crate-metadata.json", True) diff --git a/examples/create_and_write.rs b/examples/create_and_write.rs index 07b47bc..dcc271c 100644 --- a/examples/create_and_write.rs +++ b/examples/create_and_write.rs @@ -1,55 +1,68 @@ -use ro_crate_rs::ro_crate::read::{read_crate, CrateReadError}; -use ro_crate_rs::ro_crate::write::write_crate; -use ro_crate_rs::ro_crate::data_entity::DataEntity; use ro_crate_rs::ro_crate::constraints::*; use ro_crate_rs::ro_crate::contextual_entity::ContextualEntity; +use ro_crate_rs::ro_crate::data_entity::DataEntity; use ro_crate_rs::ro_crate::metadata_descriptor::MetadataDescriptor; +use ro_crate_rs::ro_crate::read::{read_crate, CrateReadError}; +use ro_crate_rs::ro_crate::rocrate::GraphVector; use ro_crate_rs::ro_crate::rocrate::{RoCrate, RoCrateContext}; use ro_crate_rs::ro_crate::root::RootDataEntity; +use ro_crate_rs::ro_crate::write::write_crate; use uuid::Uuid; -use ro_crate_rs::ro_crate::rocrate::GraphVector; + fn main() { - let mut i = 0; - while i < 5000 { - + while i < 500 { + // Create empty RoCrate let mut rocrate = RoCrate { - context: RoCrateContext::ReferenceContext("https://w3id.org/ro/crate/1.1/context".to_string()), + context: RoCrateContext::ReferenceContext( + "https://w3id.org/ro/crate/1.1/context".to_string(), + ), graph: Vec::new(), }; - + + // Set new MetadataDescriptor let description = MetadataDescriptor { id: "ro-crate-metadata.json".to_string(), - type_: DataType::Term("test".to_string()), - conforms_to: Id::Id(IdValue { id: "test".to_string() }), - about: Id::Id(IdValue { id: "teststests".to_string() }), - dynamic_entity: None + type_: DataType::Term("CreativeWork".to_string()), + conforms_to: Id::Id(IdValue { + id: "https://w3id.org/ro/crate/1.1".to_string(), + }), + about: Id::Id(IdValue { + id: "./".to_string(), + }), + dynamic_entity: None, }; - + + // Create new RootDataEntity let root_data_entity = RootDataEntity { id: "./".to_string(), - type_: DataType::Term("test_type".to_string()), - date_published: Option::Some("2012".to_string()), - license: Some(License::Id(Id::Id(IdValue {id: "test_id_123".to_string()}))), - dynamic_entity: None + type_: DataType::Term("Dataset".to_string()), + date_published: Option::Some("2024".to_string()), + license: Some(License::Id(Id::Id(IdValue { + id: "MIT LICENSE".to_string(), + }))), + dynamic_entity: None, }; - - let entity_types = Vec::from(["1".to_string(), "2".to_string()]); - + + // + let entity_types = Vec::from(["File".to_string(), "DigitalDocument".to_string()]); + let mut data_entity = DataEntity { - id: "test_data_entity".to_string(), + id: "output/data_entity.txt".to_string(), type_: DataType::TermArray(entity_types), dynamic_entity: None, }; - + let mut contextual_entity: ContextualEntity = ContextualEntity { - id: "test_contextual_entity".to_string(), + id: "#JohnDoe".to_string(), type_: DataType::Term("Person".to_string()), dynamic_entity: None, }; - rocrate.graph.push(GraphVector::MetadataDescriptor(description)); + rocrate + .graph + .push(GraphVector::MetadataDescriptor(description)); rocrate .graph .push(GraphVector::RootDataEntity(root_data_entity)); @@ -59,36 +72,30 @@ fn main() { .push(GraphVector::ContextualEntity(contextual_entity)); for _ in 0..100 { - let uuid = Uuid::new_v4(); // Generate a random UUID + let uuid = Uuid::new_v4(); // Generate a random UUID rocrate .graph - .push(GraphVector::ContextualEntity( - ContextualEntity { - id: uuid.to_string(), - type_: DataType::Term("Person".to_string()), - dynamic_entity: None, - } - )); // Print the UUID, or do something else with it + .push(GraphVector::ContextualEntity(ContextualEntity { + id: uuid.to_string(), + type_: DataType::Term("Person".to_string()), + dynamic_entity: None, + })); // Print the UUID, or do something else with it } write_crate(&rocrate, "ro-crate-metadata_3.json".to_string()); - - match read_crate("ro-crate-metadata_3.json".to_string()) { - Ok(mut rocrate) => { - - }, + let crate_name = crate_path("ro-crate-metadata_3.json"); + + match read_crate(&crate_name, false) { + Ok(mut rocrate) => {} Err(CrateReadError::IoError(err)) => { eprintln!("IO error occurred: {}", err); } Err(CrateReadError::JsonError(err)) => { eprintln!("JSON deserialization error occurred: {}", err); } + _ => {} } - i = i + 1; } - - - } diff --git a/examples/init.rs b/examples/init.rs deleted file mode 100644 index 6559a7d..0000000 --- a/examples/init.rs +++ /dev/null @@ -1,122 +0,0 @@ -use ro_crate_rs::ro_crate::constraints::{DataType, Id, License}; -use ro_crate_rs::ro_crate::contextual_entity::ContextualEntity; -use ro_crate_rs::ro_crate::data_entity::DataEntity; -use ro_crate_rs::ro_crate::modify::DynamicEntityManipulation; -use ro_crate_rs::ro_crate::root::{GraphVector, MetadataDescriptor, RoCrate, RootDataEntity}; -use ro_crate_rs::ro_crate::write::write_crate; -use uuid::Uuid; - -fn main() { - let mut rocrate = RoCrate { - context: "https://w3id.org/ro/crate/1.1/context".to_string(), - graph: Vec::new(), - }; - - let description = MetadataDescriptor { - id: "ro-crate-metadata.json".to_string(), - type_: "test".to_string(), - conforms_to: Id { - id: "test/test/test/test".to_string(), - }, - about: Id { - id: "testsdfsdfeste".to_string(), - }, - }; - - let root_data_entity = RootDataEntity { - id: "./".to_string(), - identifier: "root_test".to_string(), - type_: "test_type".to_string(), - date_published: "2012".to_string(), - license: Some(License::ContextualId(Id { - id: "test_id_123".to_string(), - })), - }; - - let entity_types = Vec::from(["1".to_string(), "2".to_string()]); - - let mut data_entity = DataEntity { - id: "test_data_entity".to_string(), - type_: DataType::TermArray(entity_types), - dynamic_entity: None, - }; - - let mut contextual_entity: ContextualEntity = ContextualEntity { - id: "test_contextual_entity".to_string(), - type_: DataType::Term("Person".to_string()), - dynamic_entity: None, - }; - - - //for _ in 0..1000000 { - // let uuid = Uuid::new_v4(); // Generate a random UUID - // rocrate - // .graph - // .push(GraphVector::ContextualEntity( - // ContextualEntity { - // id: uuid.to_string(), - // type_: DataType::Term("Person".to_string()), - // dynamic_entity: None, - // } - // )); // Print the UUID, or do something else with it - //} - - println!("Before adding: {}", contextual_entity); - - contextual_entity.add_string_value("first_name".to_string(), "Bob".to_string()); - contextual_entity.add_string_value("last_name".to_string(), "Jeffy".to_string()); - println!("After adding: {}", contextual_entity); - - data_entity.add_string_value("last_name".to_string(), "Jeffy".to_string()); - data_entity.add_id_value( - "software".to_string(), - Id { - id: "urlblahblah".to_string(), - }, - ); - - let part_ofs: Vec = Vec::from([ - Id { - id: "blahblah1 uri".to_string(), - }, - Id { - id: "blahblahblah2 uri".to_string(), - }, - Id { - id: "bolboboblbo uri".to_string(), - }, - ]); - - data_entity.add_id_vec_values("partOf".to_string(), part_ofs); - let entity_types2 = Vec::from(["1".to_string(), "2".to_string()]); - let mut data_entity2 = DataEntity { - id: "oh look a file".to_string(), - type_: DataType::TermArray(entity_types2), - dynamic_entity: None, - }; - - rocrate.graph.push(GraphVector::Descriptor(description)); - rocrate - .graph - .push(GraphVector::RootDataEntity(root_data_entity)); - rocrate.graph.push(GraphVector::DataEntity(data_entity)); - rocrate - .graph - .push(GraphVector::ContextualEntity(contextual_entity)); - rocrate.graph.push(GraphVector::DataEntity(data_entity2)); - - //println!("RO-Crate graph: {}", rocrate); - - rocrate.remove_by_id("test_contextual_entity"); - - let id_to_find = "test_data_entity"; - - let index = rocrate.find_id_index(id_to_find); - println!("Index: {:?}", index); - - rocrate.remove_dynamic_entity_from_index(id_to_find, "software"); - - //println!("Result: {:?}", rocrate); - - write_crate(&rocrate) -} diff --git a/examples/open_create_open.rs b/examples/open_create_open.rs deleted file mode 100644 index 05d5ef4..0000000 --- a/examples/open_create_open.rs +++ /dev/null @@ -1,109 +0,0 @@ -use rocrate::ro_crate::read::{read_crate, CrateReadError}; -use rocrate::ro_crate::write::write_crate; -use rocrate::ro_crate::data_entity::DataEntity; -use rocrate::ro_crate::constraints::*; -use rocrate::ro_crate::modify::*; -use uuid::Uuid; -use rocrate::ro_crate::rocrate::GraphVector; -use rocrate::ro_crate::contextual_entity::ContextualEntity; -use std::path::Path; - -fn main() { - - //let entity_types = Vec::from(["1".to_string(), "2".to_string()]); - //let mut data_entity = DataEntity { - // id: "test_data_entity".to_string(), - // type_: DataType::TermArray(entity_types), - // dynamic_entity: None, - //}; -// - //data_entity.add_id_value( - // "software".to_string(), - // Id::Id(IdValue { - // id: "urlblahblah".to_string(), - // }), - //); - //println!("{}", data_entity); -// - //data_entity.remove_matching_value("urlblahblah"); -// - //println!("{}", data_entity); - // - //let part_ofs: Vec = Vec::from([ - // Id::Id(IdValue { id: "blahblah1 uri".to_string() }), - // Id::Id(IdValue { id: "blahblahblah2 uri".to_string() }), - // Id::Id(IdValue { id: "bolboboblbo uri".to_string() }), - //]); - // -// - //data_entity.add_id_vec_values("partOf".to_string(), part_ofs); -// - // - //println!("{}", data_entity); - //data_entity.remove_matching_value("blahblahblah2 uri"); - //println!("{}", data_entity); - - let mut i = 0; - let path_1 = Path::new("ro-crate-metadata.json").to_path_buf(); - - while i < 1 { - match read_crate(path_1.clone()) { - - Ok(mut rocrate) => { - - let id = "results/multiqc/broadPeak/".to_string(); - - //let index = rocrate.find_id_index("#virify-inputs-virsorter_virome"); - - //println!("Index for target: {:#?}", index); - - //remove dynamic entity from index - //rocrate.remove_dynamic_entity_field(&id, "name"); - - for _ in 0..10000000 { - let uuid = Uuid::new_v4(); // Generate a random UUID - rocrate - .graph - .push(GraphVector::ContextualEntity( - ContextualEntity { - id: uuid.to_string(), - type_: DataType::Term("Person".to_string()), - dynamic_entity: None, - } - )); // Print the UUID, or do something else with it - } - - //println!("{}", rocrate); - - // remove id recursively from data_entity - //rocrate.remove_by_id(&id, true); - //let path = Path::new().to_path_buf(); - write_crate(&rocrate, "ro-crate-metadata_3.json".to_string()) - - }, - Err(CrateReadError::IoError(err)) => { - eprintln!("IO error occurred: {}", err); - } - Err(CrateReadError::JsonError(err)) => { - eprintln!("JSON deserialization error occurred: {}", err); - } - } - match read_crate(path_1.clone()) { - - Ok(mut rocrate) => { - let i = 1; - }, - Err(CrateReadError::IoError(err)) => { - eprintln!("IO error occurred: {}", err); - } - Err(CrateReadError::JsonError(err)) => { - eprintln!("JSON deserialization error occurred: {}", err); - } - } - - i = i + 1; - } - - - -} diff --git a/examples/output/data_entity.txt b/examples/output/data_entity.txt new file mode 100644 index 0000000..e69de29 diff --git a/examples/ro-crate-metadata.json b/examples/ro-crate-metadata.json index 05aac98..c7e9f6e 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,18 +14,34 @@ { "@id": "./", "@type": "Dataset", - "datePublished": "2024-02-23T14:38:51.132463984+00:00", + "datePublished": "2017", "license": { - "@id": "https://creativecommons.org/licenses/by-nc/4.0/deed.en" + "@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": "data_entity.txt", - "@type": "File" + "@id": "output/data_file.txt", + "@type": "Dataset", + "name": "Data file name" }, { - "@id": "https://orcid.org/0000-0002-1825-0097", + "@id": "#JohnDoe", "@type": "Person" + }, + { + "@id": "this_is_not_a_file.txt", + "@type": "Dataset" } ] } diff --git a/py-rocraters/Cargo.toml b/py-rocraters/Cargo.toml deleted file mode 100644 index 16a1f38..0000000 --- a/py-rocraters/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "py-rocraters" -version = "0.1.0" -edition = "2021" -authors = ["Matt Burridge"] -description = "A python library for interfacing with ro-crate-rs" -license = "Apache-2.0" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[lib] -name = "rocraters" -crate-type = ["cdylib"] - -[workspace] - -[dependencies] -pyo3 = "0.19.0" -ro-crate-rs = { path = "../"} -serde_json = "1.0" -chrono = "0.4" diff --git a/py-rocraters/pyproject.toml b/py-rocraters/pyproject.toml deleted file mode 100644 index 87a2be4..0000000 --- a/py-rocraters/pyproject.toml +++ /dev/null @@ -1,16 +0,0 @@ -[build-system] -requires = ["maturin>=1.4,<2.0"] -build-backend = "maturin" - -[project] -name = "rocraters" -requires-python = ">=3.8" -classifiers = [ - "Programming Language :: Rust", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", -] -dynamic = ["version"] - -[tool.maturin] -features = ["pyo3/extension-module"] diff --git a/py-rocraters/.github/workflows/CI.yml b/python/.github/workflows/CI.yml similarity index 100% rename from py-rocraters/.github/workflows/CI.yml rename to python/.github/workflows/CI.yml diff --git a/py-rocraters/.gitignore b/python/.gitignore similarity index 100% rename from py-rocraters/.gitignore rename to python/.gitignore diff --git a/py-rocraters/Cargo.lock b/python/Cargo.lock similarity index 75% rename from py-rocraters/Cargo.lock rename to python/Cargo.lock index 74177af..3e9194e 100644 --- a/py-rocraters/Cargo.lock +++ b/python/Cargo.lock @@ -45,15 +45,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -82,12 +82,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitflags" -version = "2.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" - [[package]] name = "block-buffer" version = "0.10.4" @@ -99,9 +93,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.3" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" @@ -111,9 +105,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "bzip2" @@ -138,10 +132,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9fa1897e4325be0d68d48df6aa1a71ac2ed4d27723887e7754192705350730" +checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41" dependencies = [ + "jobserver", "libc", ] @@ -153,9 +148,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.34" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ "android-tzdata", "iana-time-zone", @@ -163,7 +158,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.3", + "windows-targets 0.52.4", ] [[package]] @@ -254,9 +249,9 @@ dependencies = [ [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -267,22 +262,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" -[[package]] -name = "errno" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "fastrand" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" - [[package]] name = "flate2" version = "1.0.28" @@ -299,21 +278,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -383,9 +347,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "libc", @@ -400,9 +364,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "h2" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -424,10 +388,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] -name = "hermit-abi" -version = "0.3.6" +name = "heck" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hmac" @@ -440,9 +404,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -496,19 +460,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "iana-time-zone" version = "0.1.60" @@ -544,9 +495,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.3" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", @@ -554,9 +505,9 @@ dependencies = [ [[package]] name = "indoc" -version = "1.0.9" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "inout" @@ -575,24 +526,27 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] -name = "js-sys" -version = "0.3.68" +name = "jobserver" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +checksum = "f08474e32172238f2827bd160c67871cdb2801430f65c3979184dc362e3ca118" dependencies = [ - "wasm-bindgen", + "libc", ] [[package]] -name = "lazy_static" -version = "1.4.0" +name = "js-sys" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] [[package]] name = "libc" @@ -600,12 +554,6 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" -[[package]] -name = "linux-raw-sys" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" - [[package]] name = "lock_api" version = "0.4.11" @@ -618,21 +566,21 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memoffset" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] @@ -654,33 +602,15 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", "windows-sys 0.48.0", ] -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "num-conv" version = "0.1.0" @@ -696,16 +626,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "object" version = "0.32.2" @@ -721,50 +641,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "openssl" -version = "0.10.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" -dependencies = [ - "bitflags 2.4.2", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.50", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -819,9 +695,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -835,6 +711,12 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + [[package]] name = "powerfmt" version = "0.2.0" @@ -849,34 +731,25 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] -[[package]] -name = "py-rocraters" -version = "0.1.0" -dependencies = [ - "chrono", - "pyo3", - "ro-crate-rs", - "serde_json", -] - [[package]] name = "pyo3" -version = "0.19.2" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38" +checksum = "a7a8b1990bd018761768d5e608a13df8bd1ac5f678456e0f301bb93e5f3ea16b" dependencies = [ "cfg-if", "indoc", "libc", "memoffset", "parking_lot", + "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", @@ -885,9 +758,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.19.2" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5" +checksum = "650dca34d463b6cdbdb02b1d71bfd6eb6b6816afc708faebb3bac1380ff4aef7" dependencies = [ "once_cell", "target-lexicon", @@ -895,9 +768,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.19.2" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53cee42e77ebe256066ba8aa77eff722b3bb91f3419177cf4cd0f304d3284d9" +checksum = "09a7da8fc04a8a2084909b59f29e1b8474decac98b951d77b80b26dc45f046ad" dependencies = [ "libc", "pyo3-build-config", @@ -905,32 +778,34 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.19.2" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfeb4c99597e136528c6dd7d5e3de5434d1ceaf487436a3f03b2d56b6fc9efd1" +checksum = "4b8a199fce11ebb28e3569387228836ea98110e43a804a530a9fd83ade36d513" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "pyo3-macros-backend" -version = "0.19.2" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "947dc12175c254889edc0c02e399476c2f652b4b9ebd123aa655c224de259536" +checksum = "93fbbfd7eb553d10036513cb122b888dcd362a945a00b06c165f2ab480d4cc3b" dependencies = [ + "heck", "proc-macro2", + "pyo3-build-config", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -971,14 +846,14 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] name = "reqwest" -version = "0.11.24" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "base64", "bytes", @@ -989,23 +864,19 @@ dependencies = [ "http", "http-body", "hyper", - "hyper-tls", "ipnet", "js-sys", "log", "mime", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "system-configuration", "tokio", - "tokio-native-tls", "tower-service", "url", "wasm-bindgen", @@ -1030,32 +901,20 @@ dependencies = [ ] [[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - -[[package]] -name = "rustix" -version = "0.38.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +name = "rocraters-python" +version = "0.1.0" dependencies = [ - "bitflags 2.4.2", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", + "chrono", + "pyo3", + "ro-crate-rs", + "serde_json", ] [[package]] -name = "rustls-pemfile" -version = "1.0.4" +name = "rustc-demangle" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64", -] +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "ryu" @@ -1072,44 +931,12 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "security-framework" -version = "2.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "serde" version = "1.0.197" @@ -1127,14 +954,14 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ "itoa", "ryu", @@ -1186,18 +1013,18 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1208,20 +1035,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", @@ -1240,7 +1056,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ - "bitflags 1.3.2", + "bitflags", "core-foundation", "system-configuration-sys", ] @@ -1261,23 +1077,11 @@ version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" -[[package]] -name = "tempfile" -version = "3.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" -dependencies = [ - "cfg-if", - "fastrand", - "rustix", - "windows-sys 0.52.0", -] - [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "num-conv", @@ -1309,30 +1113,19 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.36.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "pin-project-lite", "socket2", "windows-sys 0.48.0", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.10" @@ -1407,9 +1200,9 @@ dependencies = [ [[package]] name = "unindent" -version = "0.1.11" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "url" @@ -1424,19 +1217,13 @@ dependencies = [ [[package]] name = "uuid" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom", ] -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.4" @@ -1445,9 +1232,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -1470,9 +1257,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1480,24 +1267,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.50", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -1507,9 +1294,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1517,28 +1304,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -1581,7 +1368,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.3", + "windows-targets 0.52.4", ] [[package]] @@ -1599,7 +1386,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.3", + "windows-targets 0.52.4", ] [[package]] @@ -1619,17 +1406,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm 0.52.3", - "windows_aarch64_msvc 0.52.3", - "windows_i686_gnu 0.52.3", - "windows_i686_msvc 0.52.3", - "windows_x86_64_gnu 0.52.3", - "windows_x86_64_gnullvm 0.52.3", - "windows_x86_64_msvc 0.52.3", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -1640,9 +1427,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -1652,9 +1439,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -1664,9 +1451,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -1676,9 +1463,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -1688,9 +1475,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" @@ -1700,9 +1487,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -1712,9 +1499,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "winreg" @@ -1767,9 +1554,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.10+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" dependencies = [ "cc", "pkg-config", diff --git a/python/Cargo.toml b/python/Cargo.toml new file mode 100644 index 0000000..17f6656 --- /dev/null +++ b/python/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "rocraters-python" +version = "0.1.0" +edition = "2021" +authors = ["Matt Burridge "] +description = "Lightweight Python library for RO-Crate manipulation implemented in Rust" +license = "Apache-2.0" +keywords = ["ro-crate", "rocrate", "research object", "rocraters"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "rocraters" +crate-type = ["cdylib"] + +[workspace] + +[dependencies] +pyo3 = "0.21.1" +serde_json = "1.0" +chrono = "0.4" + +[dependencies.ro-crate-rs] +path = "../" +version = "0" + diff --git a/py-rocraters/README.md b/python/README.md similarity index 87% rename from py-rocraters/README.md rename to python/README.md index 7f52193..af8e8b3 100644 --- a/py-rocraters/README.md +++ b/python/README.md @@ -2,6 +2,10 @@ pycrate is a python library that is built upon a rust backend for interfacing with [RO-Crates](https://www.researchobject.org/ro-crate/1.1/). It's designed to be maximally flexible with minimal onboarding, allowing you to incorprate it into scrpits/ data pipelines as easily as possible. This also relies on you to have an understanding of the structure of an RO-Crate, but focuses more on the fact that some metadata is better than no metadata. +# Build + +Built using PyO3 and maturin. Recommended to setup python venv, then install maturin (and remember maturin[patchelf]) + # Basic usage The RO-Crate specification defines an RO-Crate as a JSON-LD file, consisting of a context and a graph. As such, in python it is a dictionary containing a "context" key, with some form of vocab context (default is the RO-Crate context) and a "graph" key, which contains a list of json objects (dictionaries). @@ -77,7 +81,7 @@ To then read a `ro-crate-metadata.json` file and load it in as a structured obje from rocraters import read # Read RO-Crate at specified path -crate = read("ro-crate-metadata.json") +crate = read("ro-crate-metadata.json", True) ``` To zip the folder and all contained directories within the `ro-crate-metadata.json` directory: @@ -85,7 +89,7 @@ To zip the folder and all contained directories within the `ro-crate-metadata.js # new example from rocraters import zip -zip("./") +zip("ro-crate-metadata.json", True) ``` # Modifying a RO-Crate @@ -96,15 +100,16 @@ As per the libraries purpose, modification, ie the deletion, update and addition # Example based upon previously made crate from rocraters import read -crate = read("ro-crate-metadata.json") +crate = read("ro-crate-metadata.json", True) # Update the data entity and make modification data_target = crate.get_entity("data_file.txt") -data_target = target["description"] = "A text file dataset containing information" +data_target["description"] = "A text file dataset containing information" # Update the contextual entity and make modification -contextual_target = crate.get_contextual("#JohnDoe") +contextual_target = crate.get_entity("#JohnDoe") contextual_target.update({"id" : "#JaneDoe"}) +crate.update_contextual(contextual_target) # To delete a key:value data_target.pop("description") @@ -117,3 +122,8 @@ contextual_target.delete_entity("#JaneDoe") crate.update_data(data_target) crate.write() ``` + +# Custom compilation + +PyO3 is used to handle python bindings. Maturin is used as the build tool. + diff --git a/py-rocraters/pycrate.pyi b/python/pycrate.pyi similarity index 100% rename from py-rocraters/pycrate.pyi rename to python/pycrate.pyi diff --git a/python/pyproject.toml b/python/pyproject.toml new file mode 100644 index 0000000..26852cd --- /dev/null +++ b/python/pyproject.toml @@ -0,0 +1,26 @@ +[build-system] +requires = ["maturin>=1.4,<2.0"] +build-backend = "maturin" + +[project] +name = "rocraters" +description = "Lightweight Python library for RO-Crate manipulation implemented in Rust" +readme = "README.md" +keywords = ["ro-crate", "rocrate", "research object", "rocraters"] +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Development Status :: 2 - Pre-Alpha", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12" +] +dynamic = ["version"] + +[tool.maturin] +features = ["pyo3/extension-module"] + diff --git a/py-rocraters/src/lib.rs b/python/src/lib.rs similarity index 78% rename from py-rocraters/src/lib.rs rename to python/src/lib.rs index 1fccf9d..d163d8c 100644 --- a/py-rocraters/src/lib.rs +++ b/python/src/lib.rs @@ -1,20 +1,23 @@ +//! Python bindings for ro-crate-rs core + mod utils; extern crate chrono; +use ::rocraters::ro_crate::constraints::*; +use ::rocraters::ro_crate::metadata_descriptor::MetadataDescriptor; +use ::rocraters::ro_crate::root::RootDataEntity; use ::rocraters::ro_crate::{ self, rocrate::{ContextItem, GraphVector, RoCrate, RoCrateContext}, - write::{write_crate as rs_write_crate, zip_crate as rs_zip_crate} + write::{write_crate as rs_write_crate, zip_crate as rs_zip_crate}, }; +use chrono::prelude::*; +use pyo3::exceptions::PyIOError; use pyo3::{ prelude::*, types::{PyDict, PyList, PyString}, }; use std::collections::HashMap; use std::path::Path; -use ::rocraters::ro_crate::root::RootDataEntity; -use ::rocraters::ro_crate::metadata_descriptor::MetadataDescriptor; -use ::rocraters::ro_crate::constraints::*; -use chrono::prelude::*; /// PyO3 compatible wrapper around RoCrate struct #[pyclass] @@ -29,7 +32,7 @@ struct PyRoCrateContext { inner: RoCrateContext, } -/// CrateContext methods +/// CrateContext methods #[pymethods] impl PyRoCrateContext { /// Crates a RoCrate Context from just a string @@ -111,16 +114,15 @@ impl PyRoCrate { )), } } - - /// Update a data entity with new data + + /// Update a data entity with new data /// - /// Lazy update of data entity, finds id and overwrites the index. + /// Lazy update of data entity, finds id and overwrites the index. /// Strongly recommended to extract index data, modify, then rewrite the /// modified index data as the update. - fn update_data(&mut self, py: Python, py_obj: PyObject) -> PyResult<()>{ - + fn update_data(&mut self, py: Python, py_obj: PyObject) -> PyResult<()> { // Needs to check if data entity first - then parse as contextual if fail - // if data then append to partOf vec in root. + // if data then append to partOf vec in root. 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(); @@ -131,15 +133,14 @@ impl PyRoCrate { Ok(()) } - /// Update a contextual entity with new data + /// Update a contextual entity with new data /// - /// Lazy update of contextual entity, finds id and overwrites the index. + /// Lazy update of contextual entity, finds id and overwrites the index. /// Strongly recommended to extract index data, modify, then rewrite the /// modified index data as the update. - fn update_contextual(&mut self, py: Python, py_obj: PyObject) -> PyResult<()>{ - + fn update_contextual(&mut self, py: Python, py_obj: PyObject) -> PyResult<()> { // Needs to check if data entity first - then parse as contextual if fail - // if data then append to partOf vec in root. + // if data then append to partOf vec in root. 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(); @@ -150,15 +151,14 @@ impl PyRoCrate { Ok(()) } - /// Update a root entity with new data + /// Update a root entity with new data /// - /// Lazy update of root entity, finds id and overwrites the index. + /// Lazy update of root entity, finds id and overwrites the index. /// Strongly recommended to extract index data, modify, then rewrite the /// modified index data as the update. - fn update_root(&mut self, py: Python, py_obj: PyObject) -> PyResult<()>{ - + fn update_root(&mut self, py: Python, py_obj: PyObject) -> PyResult<()> { // Needs to check if data entity first - then parse as contextual if fail - // if data then append to partOf vec in root. + // if data then append to partOf vec in root. 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(); @@ -169,15 +169,14 @@ impl PyRoCrate { Ok(()) } - /// Update the metadata descriptor with new data + /// Update the metadata descriptor with new data /// - /// Lazy update of metadata descriptor, finds id and overwrites the index. + /// Lazy update of metadata descriptor, finds id and overwrites the index. /// Strongly recommended to extract index data, modify, then rewrite the /// modified index data as the update. - fn update_descriptor(&mut self, py: Python, py_obj: PyObject) -> PyResult<()>{ - + fn update_descriptor(&mut self, py: Python, py_obj: PyObject) -> PyResult<()> { // Needs to check if data entity first - then parse as contextual if fail - // if data then append to partOf vec in root. + // if data then append to partOf vec in root. let descriptor_wrapper: utils::MetadataDescriptorWrapper = py_obj.extract(py)?; let descriptor = descriptor_wrapper.0; // Access the inner DataEntity let id = descriptor.id.clone(); @@ -195,11 +194,12 @@ impl PyRoCrate { } /// Writes ro-crate back to ro-crate-metadata.json - fn write(&self) -> PyResult<()> { - rs_write_crate(&self.inner, "ro-crate-metadata.json".to_string()); + fn write(&self, file_path: Option) -> PyResult<()> { + let path = file_path.unwrap_or_else(|| "ro-crate-metadata.json".to_string()); + rs_write_crate(&self.inner, path); Ok(()) } - + /// Print's full crate fn __repr__(&self) -> PyResult { Ok(format!("PyRoCrate(data: '{:#?}')", self.inner)) @@ -207,7 +207,7 @@ impl PyRoCrate { } impl From for PyRoCrate { - /// Allows simple conversion into rust_struct on read + /// Allows simple conversion into rust_struct on read fn from(rust_struct: RoCrate) -> Self { PyRoCrate { inner: rust_struct } } @@ -215,54 +215,67 @@ impl From for PyRoCrate { /// Reads a crate into memory allowing manipulation #[pyfunction] -fn read(relative_path: &str) -> PyResult { +fn read(relative_path: &str, validity: bool) -> PyResult { let path = Path::new(relative_path).to_path_buf(); - let rocrate = ro_crate::read::read_crate(path).unwrap(); + let rocrate = ro_crate::read::read_crate(&path, validity) + .map_err(|e| PyIOError::new_err(format!("Failed to read crate: {:#?}", e)))?; Ok(PyRoCrate::from(rocrate)) } /// Targets a ro-crate and zips directory contents #[pyfunction] -fn zip(crate_folder: &str) -> PyResult<()>{ - let path = Path::new(crate_folder).to_path_buf(); - let _ = rs_zip_crate(&path); +fn zip(crate_path: &str, external: bool) -> PyResult<()> { + let path = Path::new(crate_path).to_path_buf(); + let _ = rs_zip_crate(&path, external); Ok(()) } - impl Default for PyRoCrate { /// Creates a new RoCrate with default requirements fn default() -> PyRoCrate { - let mut rocrate = PyRoCrate { inner: RoCrate { - context: RoCrateContext::ReferenceContext("https://w3id.org/ro/crate/1.1/context".to_string()), - graph: Vec::new() - } + context: RoCrateContext::ReferenceContext( + "https://w3id.org/ro/crate/1.1/context".to_string(), + ), + graph: Vec::new(), + }, }; let description = MetadataDescriptor { id: "ro-crate-metadata.json".to_string(), type_: DataType::Term("CreativeWork".to_string()), - conforms_to: Id::Id(IdValue { id: "https://w3id.org/ro/crate/1.1".to_string() }), - about: Id::Id(IdValue { id: "./".to_string() }), - dynamic_entity: None + conforms_to: Id::Id(IdValue { + id: "https://w3id.org/ro/crate/1.1".to_string(), + }), + about: Id::Id(IdValue { + id: "./".to_string(), + }), + dynamic_entity: None, }; let root_data_entity = RootDataEntity { id: "./".to_string(), type_: DataType::Term("Dataset".to_string()), date_published: Option::Some(Utc::now().to_rfc3339().to_string()), - license: Some(License::Id(Id::Id(IdValue {id: "https://creativecommons.org/licenses/by-nc/4.0/deed.en".to_string()}))), - dynamic_entity: None + license: Some(License::Id(Id::Id(IdValue { + id: "https://creativecommons.org/licenses/by-nc/4.0/deed.en".to_string(), + }))), + dynamic_entity: None, }; - rocrate.inner.graph.push(GraphVector::MetadataDescriptor(description)); - rocrate.inner.graph.push(GraphVector::RootDataEntity(root_data_entity)); + rocrate + .inner + .graph + .push(GraphVector::MetadataDescriptor(description)); + rocrate + .inner + .graph + .push(GraphVector::RootDataEntity(root_data_entity)); rocrate } } -/// A Python module implemented in Rust. +/// A lightweight Python library for Ro-Crate manipulation implemented in Rust. #[pymodule] fn rocraters(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; diff --git a/py-rocraters/src/utils.rs b/python/src/utils.rs similarity index 73% rename from py-rocraters/src/utils.rs rename to python/src/utils.rs index e50e065..3952a62 100644 --- a/py-rocraters/src/utils.rs +++ b/python/src/utils.rs @@ -1,14 +1,17 @@ -use pyo3::{ - prelude::*, - types::{PyBool, PyDict, PyFloat, PyList, PyString}, -}; +//! Utility functions for rust type conversions and python accessiblity + use ::rocraters::ro_crate::contextual_entity::ContextualEntity; use ::rocraters::ro_crate::data_entity::DataEntity; use ::rocraters::ro_crate::{ - constraints::{DataType, DynamicEntity, Id, License, IdValue}, + constraints::{DataType, DynamicEntity, Id, IdValue, License}, metadata_descriptor::MetadataDescriptor, root::RootDataEntity, }; +use pyo3::exceptions::PyTypeError; +use pyo3::{ + prelude::*, + types::{PyBool, PyDict, PyFloat, PyList, PyString}, +}; use serde_json::Value; use std::collections::HashMap; @@ -48,7 +51,7 @@ impl EntityTrait for ContextualEntity { /// Converts base entities (data and contextual) to python dicts pub fn base_entity_to_pydict(py: Python, entity: &T) -> PyResult { - let py_dict = PyDict::new(py); + let py_dict = PyDict::new_bound(py); // Now use the shared trait methods to access fields. py_dict.set_item("id", entity.id())?; @@ -60,9 +63,9 @@ pub fn base_entity_to_pydict(py: Python, entity: &T) -> PyResult DataType::TermArray(terms) => { let py_terms = terms .iter() - .map(|term| PyString::new(py, term)) + .map(|term| PyString::new_bound(py, term)) .collect::>(); - py_dict.set_item("type", PyList::new(py, &py_terms))?; + py_dict.set_item("type", PyList::new_bound(py, &py_terms))?; } } @@ -79,7 +82,7 @@ pub fn base_entity_to_pydict(py: Python, entity: &T) -> PyResult /// Converts root metadata entity to py dict pub fn root_entity_to_pydict(py: Python, entity: &RootDataEntity) -> PyResult { - let py_dict = PyDict::new(py); + let py_dict = PyDict::new_bound(py); py_dict.set_item("id", &entity.id)?; @@ -90,7 +93,7 @@ pub fn root_entity_to_pydict(py: Python, entity: &RootDataEntity) -> PyResult { let py_terms = terms .iter() - .map(|term| PyString::new(py, term)) + .map(|term| PyString::new_bound(py, term)) .collect::>(); py_dict.set_item("type", py_terms).unwrap(); } @@ -116,7 +119,7 @@ pub fn metadata_descriptor_to_pydict( py: Python, descriptor: &MetadataDescriptor, ) -> PyResult { - let py_dict = PyDict::new(py); + let py_dict = PyDict::new_bound(py); py_dict.set_item("id", &descriptor.id)?; @@ -127,7 +130,7 @@ pub fn metadata_descriptor_to_pydict( DataType::TermArray(terms) => { let py_terms = terms .iter() - .map(|term| PyString::new(py, term)) + .map(|term| PyString::new_bound(py, term)) .collect::>(); py_dict.set_item("type", py_terms).unwrap(); } @@ -157,16 +160,18 @@ pub fn convert_license_to_pyobject(py: Python, license_opt: &Option) -> match license_opt { Some(license) => match license { License::Id(id_enum) => match id_enum { - Id::Id(id_value) => PyString::new(py, &id_value.id).into_py(py), + Id::Id(id_value) => PyString::new_bound(py, &id_value.id).into_py(py), Id::IdArray(id_values) => { - let py_list = PyList::new( + let py_list = PyList::new_bound( py, - id_values.iter().map(|id_val| PyString::new(py, &id_val.id)), + id_values + .iter() + .map(|id_val| PyString::new_bound(py, &id_val.id)), ); py_list.into() } }, - License::Description(description) => PyString::new(py, description).into_py(py), + License::Description(description) => PyString::new_bound(py, description).into_py(py), }, None => py.None(), // Handle the None case } @@ -175,29 +180,32 @@ pub fn convert_license_to_pyobject(py: Python, license_opt: &Option) -> /// Converts dynamic entities into pyobjects for dict representation pub fn convert_dynamic_entity_to_pyobject(py: Python, value: &DynamicEntity) -> PyObject { match value { - DynamicEntity::EntityString(s) => PyString::new(py, s).into(), + DynamicEntity::EntityString(s) => PyString::new_bound(py, s).into(), DynamicEntity::EntityVecString(vec) => { - let py_list = PyList::new(py, vec.iter().map(|s| PyString::new(py, s))); + let py_list = PyList::new_bound(py, vec.iter().map(|s| PyString::new_bound(py, s))); py_list.into() } DynamicEntity::EntityId(id) => convert_id_to_pyobject(py, id).unwrap(), DynamicEntity::EntityIdVec(ids) => { - let py_list = PyList::new(py, ids.iter().map(|id| convert_id_to_pyobject(py, id).unwrap())); + let py_list = PyList::new_bound( + py, + ids.iter().map(|id| convert_id_to_pyobject(py, id).unwrap()), + ); py_list.into() } - DynamicEntity::EntityBool(b) => PyBool::new(py, *b).into(), + DynamicEntity::EntityBool(b) => PyBool::new_bound(py, *b).into_py(py), DynamicEntity::Entityi64(num) => (*num).into_py(py), - DynamicEntity::Entityf64(num) => PyFloat::new(py, *num).into(), + DynamicEntity::Entityf64(num) => PyFloat::new_bound(py, *num).into(), DynamicEntity::EntityVeci64(vec) => { - let py_list = PyList::new(py, vec.iter().map(|&num| (num).into_py(py))); + let py_list = PyList::new_bound(py, vec.iter().map(|&num| (num).into_py(py))); py_list.into() } DynamicEntity::EntityVecf64(vec) => { - let py_list = PyList::new(py, vec.iter().map(|&num| PyFloat::new(py, num))); + let py_list = PyList::new_bound(py, vec.iter().map(|&num| PyFloat::new_bound(py, num))); py_list.into() } DynamicEntity::EntityVec(vec) => { - let py_list = PyList::new( + let py_list = PyList::new_bound( py, vec.iter() .map(|entity| convert_dynamic_entity_to_pyobject(py, entity)), @@ -205,7 +213,7 @@ pub fn convert_dynamic_entity_to_pyobject(py: Python, value: &DynamicEntity) -> py_list.into() } DynamicEntity::EntityObject(map) => { - let py_dict = PyDict::new(py); + let py_dict = PyDict::new_bound(py); for (key, val) in map { py_dict .set_item(key, convert_dynamic_entity_to_pyobject(py, val)) @@ -214,10 +222,10 @@ pub fn convert_dynamic_entity_to_pyobject(py: Python, value: &DynamicEntity) -> py_dict.into() } DynamicEntity::EntityVecObject(vec) => { - let py_list = PyList::new( + let py_list = PyList::new_bound( py, vec.iter().map(|map| { - let py_dict = PyDict::new(py); + let py_dict = PyDict::new_bound(py); for (key, val) in map { py_dict .set_item(key, convert_dynamic_entity_to_pyobject(py, val)) @@ -241,20 +249,20 @@ pub fn convert_dynamic_entity_to_pyobject(py: Python, value: &DynamicEntity) -> // Function to handle conversion of serde_json::Value pub fn convert_serde_json_value_to_pyobject(py: Python, value: &Value) -> PyObject { match value { - Value::String(s) => PyString::new(py, s).into(), + Value::String(s) => PyString::new_bound(py, s).into(), Value::Number(num) => { if let Some(i) = num.as_i64() { i.into_py(py) } else if let Some(f) = num.as_f64() { - PyFloat::new(py, f).into() + PyFloat::new_bound(py, f).into() } else { - PyString::new(py, &num.to_string()).into() + PyString::new_bound(py, &num.to_string()).into() } } - Value::Bool(b) => PyBool::new(py, *b).into(), + Value::Bool(b) => PyBool::new_bound(py, *b).into_py(py), // Handle other serde_json::Value types as needed // ... - _ => PyString::new(py, "Unsupported serde_json::Value type").into(), + _ => PyString::new_bound(py, "Unsupported serde_json::Value type").into(), } } @@ -262,29 +270,28 @@ pub fn convert_serde_json_value_to_pyobject(py: Python, value: &Value) -> PyObje fn convert_id_to_pyobject(py: Python, id: &Id) -> PyResult { match id { Id::Id(id_value) => { - let py_dict = PyDict::new(py); - py_dict.set_item("id", PyString::new(py, &id_value.id))?; + let py_dict = PyDict::new_bound(py); + py_dict.set_item("id", PyString::new_bound(py, &id_value.id))?; Ok(py_dict.into_py(py)) } Id::IdArray(id_values) => { let dicts: Vec = id_values .iter() .map(|id_val| { - let py_dict = PyDict::new(py); + let py_dict = PyDict::new_bound(py); py_dict - .set_item("id", PyString::new(py, &id_val.id)) + .set_item("id", PyString::new_bound(py, &id_val.id)) .expect("Failed to set 'id' key"); py_dict.into_py(py) }) .collect(); - let py_list = PyList::new(py, &dicts); + let py_list = PyList::new_bound(py, &dicts); Ok(py_list.into_py(py)) } } } - //New type pattern for DataEntity pub struct DataEntityWrapper(pub DataEntity); impl<'source> FromPyObject<'source> for DataEntityWrapper { @@ -293,7 +300,11 @@ impl<'source> FromPyObject<'source> for DataEntityWrapper { let py_dict: &PyDict = obj.downcast()?; // Safely cast the PyAny to PyDict // Extract the "id" and "type_" fields explicitly - let id: String = py_dict.get_item("id").ok_or_else(|| PyErr::new::("Missing 'id'"))?.extract()?; + let id: String = match py_dict.get_item("id") { + Ok(str) => str.unwrap().to_string(), + Err(e) => return Err(e), + }; + let type_ = create_data_type_from_dict(py_dict)?; // Initialize an empty HashMap to hold dynamic_entity entries @@ -316,7 +327,11 @@ impl<'source> FromPyObject<'source> for DataEntityWrapper { None }; - Ok(DataEntityWrapper(DataEntity { id, type_, dynamic_entity })) + Ok(DataEntityWrapper(DataEntity { + id, + type_, + dynamic_entity, + })) } } @@ -328,7 +343,10 @@ impl<'source> FromPyObject<'source> for ContextualEntityWrapper { let py_dict: &PyDict = obj.downcast()?; // Safely cast the PyAny to PyDict // Extract the "id" and "type_" fields explicitly - let id: String = py_dict.get_item("id").ok_or_else(|| PyErr::new::("Missing 'id'"))?.extract()?; + let id: String = match py_dict.get_item("id") { + Ok(str) => str.unwrap().to_string(), + Err(e) => return Err(e), + }; let type_ = create_data_type_from_dict(py_dict)?; // Initialize an empty HashMap to hold dynamic_entity entries @@ -351,7 +369,11 @@ impl<'source> FromPyObject<'source> for ContextualEntityWrapper { None }; - Ok(ContextualEntityWrapper(ContextualEntity { id, type_, dynamic_entity })) + Ok(ContextualEntityWrapper(ContextualEntity { + id, + type_, + dynamic_entity, + })) } } @@ -362,22 +384,36 @@ impl<'source> FromPyObject<'source> for RootDataEntityWrapper { let py_dict: &PyDict = obj.downcast()?; // Safely cast the PyAny to PyDict // Extract the "id" and "type_" fields explicitly - let id: String = py_dict.get_item("id").ok_or_else(|| PyErr::new::("Missing 'id'"))?.extract()?; + let id: String = match py_dict.get_item("id") { + Ok(str) => str.unwrap().to_string(), + Err(e) => return Err(e), + }; let type_ = create_data_type_from_dict(py_dict)?; - let license = py_dict.get_item("license") - .map(|license_obj| convert_pyobject_to_license(py, license_obj)) - .transpose()?; // Converts Option> to PyResult> + let license = match py_dict.get_item("license") { + Ok(license_obj) => { + let license = convert_pyobject_to_license(py, license_obj.unwrap()); + license.ok() + } + Err(e) => return Err(e), + }; + + let date_published = match py_dict.get_item("datePublished") { + Ok(str) => Some(str.unwrap().to_string()), + Err(e) => return Err(e), + }; - let date_published = py_dict.get_item("datePublished").ok_or_else(|| PyErr::new::("Missing datePublished'"))?.extract()?; - // Initialize an empty HashMap to hold dynamic_entity entries let mut dynamic_entity_map: HashMap = HashMap::new(); // Iterate over the dictionary, excluding "id" and "type" keys for (key, value) in py_dict.into_iter() { let key_str: String = key.extract()?; // Extract key as String - if key_str != "id" && key_str != "type" && key_str != "datePublished" && key_str != "license" { + if key_str != "id" + && key_str != "type" + && key_str != "datePublished" + && key_str != "license" + { let dynamic_entity = convert_pyobject_to_dynamic_entity(py, value)?; // Convert value to DynamicEntity and insert into the map dynamic_entity_map.insert(key_str, dynamic_entity); @@ -391,7 +427,13 @@ impl<'source> FromPyObject<'source> for RootDataEntityWrapper { None }; - Ok(RootDataEntityWrapper(RootDataEntity { id, type_, date_published, license, dynamic_entity })) + Ok(RootDataEntityWrapper(RootDataEntity { + id, + type_, + date_published, + license, + dynamic_entity, + })) } } @@ -402,21 +444,21 @@ impl<'source> FromPyObject<'source> for MetadataDescriptorWrapper { let py_dict: &PyDict = obj.downcast()?; // Safely cast the PyAny to PyDict // Extract the "id" and "type_" fields explicitly - let id: String = py_dict.get_item("id").ok_or_else(|| PyErr::new::("Missing 'id'"))?.extract()?; + let id: String = match py_dict.get_item("id") { + Ok(str) => str.unwrap().to_string(), + Err(e) => return Err(e), + }; let type_ = create_data_type_from_dict(py_dict)?; - + // This won't work because it cant pick the key TODO - let conforms_to = if let Some(conforms_to_check) = py_dict.get_item("conformsTo") { - let mut value = conforms_to_check.extract()?; - convert_dict_to_id(py, value)? + let conforms_to = if let Ok(value) = py_dict.get_item("conformsTo") { + convert_dict_to_id(py, value.unwrap())? } else { todo!() }; - - let about = if let Some(about_check) = py_dict.get_item("about") { - let mut value = about_check.extract()?; - convert_dict_to_id(py, value)? + let about = if let Ok(about_check) = py_dict.get_item("about") { + convert_dict_to_id(py, about_check.unwrap())? } else { todo!() }; @@ -427,7 +469,8 @@ impl<'source> FromPyObject<'source> for MetadataDescriptorWrapper { // Iterate over the dictionary, excluding "id" and "type" keys for (key, value) in py_dict.into_iter() { let key_str: String = key.extract()?; // Extract key as String - if key_str != "id" && key_str != "type" && key_str != "conformsTo" && key_str != "about" { + if key_str != "id" && key_str != "type" && key_str != "conformsTo" && key_str != "about" + { let dynamic_entity = convert_pyobject_to_dynamic_entity(py, value)?; // Convert value to DynamicEntity and insert into the map dynamic_entity_map.insert(key_str, dynamic_entity); @@ -441,16 +484,24 @@ impl<'source> FromPyObject<'source> for MetadataDescriptorWrapper { None }; - Ok(MetadataDescriptorWrapper(MetadataDescriptor { id, type_, conforms_to, about, dynamic_entity })) + Ok(MetadataDescriptorWrapper(MetadataDescriptor { + id, + type_, + conforms_to, + about, + dynamic_entity, + })) } } fn create_data_type_from_dict(input: &PyDict) -> PyResult { - if let Some(value) = input.get_item("type") { - if let Ok(s) = value.extract::<&str>() { + if let Ok(value) = input.get_item("type") { + if let Ok(s) = value.unwrap().extract::<&str>() { Ok(DataType::Term(s.to_string())) - } else if let Ok(arr) = value.extract::>() { - Ok(DataType::TermArray(arr.into_iter().map(String::from).collect())) + } else if let Ok(arr) = value.unwrap().extract::>() { + Ok(DataType::TermArray( + arr.into_iter().map(String::from).collect(), + )) } else { Err(PyErr::new::( "The 'type' key must be associated with a string or a list of strings", @@ -463,29 +514,29 @@ fn create_data_type_from_dict(input: &PyDict) -> PyResult { } } - -fn convert_pyobject_to_license(py: Python, input: &PyAny) -> PyResult { +fn convert_pyobject_to_license(py: Python, input: &PyAny) -> Result { // Attempt to extract the input as an Id using the previously defined function - if let Ok(id) = convert_dict_to_id(py, input) { - return Ok(License::Id(id)); - } - - // If the above extraction fails, attempt to extract the input as a String - if let Ok(description) = input.extract::() { - return Ok(License::Description(description)); + match convert_dict_to_id(py, input) { + Ok(id) => return Ok(License::Id(id)), + Err(_) => { + // If it fails, then try to extract a description as a fallback + if let Ok(description) = input.extract::() { + return Ok(License::Description(description)); + } + // If both attempts fail, return a custom PyTypeError + return Err(PyTypeError::new_err("Input cannot be converted to License")); + } } - - // If neither extraction succeeds, return an error - Err(PyErr::new::( - "Input must be either a dictionary with an 'id' key (or a list of such dictionaries) or a string for a description", - )) } -fn convert_dict_to_id(py: Python, input: &PyAny) -> PyResult { +fn convert_dict_to_id(_py: Python, input: &PyAny) -> PyResult { // Check if input is a single object with "id" + // Converts to pydidct then checks id if let Ok(py_dict) = input.downcast::() { - if let Ok(id_str) = py_dict.get_item("id").unwrap().extract::<&str>() { - return Ok(Id::Id(IdValue { id: id_str.to_string() })); + if let Ok(id_str) = py_dict.get_item("id") { + return Ok(Id::Id(IdValue { + id: id_str.unwrap().to_string(), + })); } } @@ -494,8 +545,10 @@ fn convert_dict_to_id(py: Python, input: &PyAny) -> PyResult { let mut id_values: Vec = Vec::new(); for item in py_list { if let Ok(py_dict) = item.downcast::() { - if let Ok(id_str) = py_dict.get_item("id").unwrap().extract::<&str>() { - id_values.push(IdValue { id: id_str.to_string() }); + if let Ok(id_str) = py_dict.get_item("id") { + id_values.push(IdValue { + id: id_str.unwrap().to_string(), + }); } else { return Err(PyErr::new::( "List items must be dictionaries with an 'id' key", @@ -514,18 +567,6 @@ fn convert_dict_to_id(py: Python, input: &PyAny) -> PyResult { )) } -pub fn convert_pydict_to_dynamic_entity(py: Python, py_dict: &PyDict) -> PyResult> { - let mut map: HashMap = HashMap::new(); - - for (k, v) in py_dict.into_iter() { - let key: String = k.extract()?; - let value: DynamicEntity = convert_pyobject_to_dynamic_entity(py, v)?; - map.insert(key, value); - } - - Ok(map) -} - // converts a PyObject to any required dynamic entity fn convert_pyobject_to_dynamic_entity(py: Python, obj: &PyAny) -> PyResult { // String @@ -565,7 +606,7 @@ fn convert_pyobject_to_dynamic_entity(py: Python, obj: &PyAny) -> PyResult if let Ok(list) = obj.extract::<&PyList>() { let mut vec = Vec::new(); @@ -583,10 +624,10 @@ fn convert_pyobject_to_dynamic_entity(py: Python, obj: &PyAny) -> PyResult( + Err(PyErr::new::( "Data type unavailable", - )); + )) } } diff --git a/src/README.md b/src/README.md index 8d58f4f..cb85204 100644 --- a/src/README.md +++ b/src/README.md @@ -40,6 +40,10 @@ requirements of rust. These, by default, are instantiated as `None`. Fig 1 is an example of a base RO-Crate: ![](../docs/ro-crate-structure.png "Basic RO-Crate structure") +Fig 2 is a diagram describing how each file relates to one another with a brief +intro to the main structures involved. +![](../docs/ro-crate-schema.svg "Brief overview of ro-crate-rs core") + diff --git a/src/lib.rs b/src/lib.rs index c9d092b..b01921b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,49 @@ -pub mod ro_crate; - -// use pyo3::prelude::*; +//! # ro-crate-rs +//! +//! 'ro-crate-rs' is a rust library for defining RO-Crates +//! () for research data. It enables +//! the reading, creation, modification, writing and archiving of ro-crate-metadata.json +//! files. +//! +//! This is not meant to be used for rapid prototyping, but more so for defined +//! pipelines, regular workflows or cases where validation or performance is +//! required. For rapid prototyping, either use the Python bindings for this library +//! or the native python library for RO-Crates (). +//! +//! # Usage +//! +//! Create a basic crate with no entities: +//! +//! ```rust +//! let mut rocrate = RoCrate { +//! context: RoCrateContext::ReferenceContext( +//! "https://w3id.org/ro/crate/1.1/context".to_string(), +//! ), +//! graph: Vec::new(), +//! }; +//! +//! let description = MetadataDescriptor { +//! id: "ro-crate-metadata.json".to_string(), +//! type_: DataType::Term("CreativeWork".to_string()), +//! conforms_to: Id::Id(IdValue { +//! id: "https://w3id.org/ro/crate/1.1".to_string(), +//! }), +//! about: Id::Id(IdValue { +//! id: "./".to_string(), +//! }), +//! dynamic_entity: None, +//! }; +//! +//! let root_data_entity = RootDataEntity { +//! id: "./".to_string(), +//! type_: DataType::Term("Dataset".to_string()), +//! date_published: Option::Some("2024".to_string()), +//! license: Some(License::Id(Id::Id(IdValue { +//! id: "MIT LICENSE".to_string(), +//! }))), +//! dynamic_entity: None, +//! }; +//! ``` +//! -// /// Formats the sum of two numbers as string. -// #[pyfunction] -// fn sum_as_string(a: usize, b: usize) -> PyResult { -// Ok((a + b).to_string()) -// } - -// /// A Python module implemented in Rust. The name of this function must match -// /// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to -// /// import the module. -// #[pymodule] -// fn rocrate(_py: Python<'_>, m: &PyModule) -> PyResult<()> { -// m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; -// Ok(()) -// } +pub mod ro_crate; diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 9c14089..0000000 --- a/src/main.rs +++ /dev/null @@ -1,10 +0,0 @@ -use rocraters::ro_crate::schema::validate_crate_keys; - -fn main() { - - let path = rocraters::ro_crate::read::crate_path("examples/ro-crate-metadata.json"); - let rocrate = rocraters::ro_crate::read::read_crate(path).unwrap(); - let keys = validate_crate_keys(&rocrate); - - println!("These are the keys: {:?}", keys); -} diff --git a/src/ro_crate/constraints.rs b/src/ro_crate/constraints.rs index 2847c4a..73b5763 100644 --- a/src/ro_crate/constraints.rs +++ b/src/ro_crate/constraints.rs @@ -1,8 +1,16 @@ -use std::collections::HashMap; +//! Stuctures for contrained types within an RO-Crate. +//! +//! Focuses on MUST fields as definied by the specification, as well as defining +//! the available types for any additional fields added to an entity object +//! (DynamicEntity) use serde::{Deserialize, Serialize}; +use std::collections::HashMap; -/// Defines a basic ID structure +/// Defines a basic ID structure for crate +/// +/// Within programs functions as {id: String}, on serialisation will become +/// {"@id": "String"} #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct IdValue { #[serde(rename = "@id")] @@ -13,7 +21,9 @@ pub struct IdValue { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(untagged)] pub enum Id { + /// Direct ID value for values of {"@id": "id"} Id(IdValue), + /// Array of ID values for values such as [{"@id": "id1"}, {"@id": "id2"}] IdArray(Vec), } @@ -33,7 +43,9 @@ impl Id { #[derive(Serialize, Deserialize, Debug)] #[serde(untagged)] pub enum License { + /// Direct ID value for a single license contextual entity Id(Id), + /// Basic license without URI Description(String), } @@ -42,13 +54,18 @@ pub enum License { #[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum DataType { + /// Basic Datatype for entities with only one datatype Term(String), + /// Vector of datatypes for entities that can be described with multiple + /// datatypes TermArray(Vec), } /// Allow a vec of ids for modification and creation. /// Allow a new field of struct id thats not a vec /// Fallback suboptimal but catch all +/// +/// NOTE: Need to properly test but I don't think EntityIdVec is called anymore #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] #[serde(untagged)] pub enum DynamicEntity { diff --git a/src/ro_crate/contextual_entity.rs b/src/ro_crate/contextual_entity.rs index accbdeb..ffd5cd3 100644 --- a/src/ro_crate/contextual_entity.rs +++ b/src/ro_crate/contextual_entity.rs @@ -1,3 +1,8 @@ +//! Definition for contextual entity +//! +//! Contextual entity can be thought of a data object that supplements the +//! metadata of a data entity + use crate::ro_crate::constraints::*; use crate::ro_crate::modify::*; use serde::{ @@ -11,8 +16,13 @@ use std::fmt; /// or remote data file that is essential for crate undestanding #[derive(Debug)] 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 + /// node (e.g {"@id" : "_alice"}) pub id: String, + /// Data type for current contextual entity pub type_: DataType, + /// Optional additional metadata pub dynamic_entity: Option>, } diff --git a/src/ro_crate/data_entity.rs b/src/ro_crate/data_entity.rs index 759e8ef..bae0fc7 100644 --- a/src/ro_crate/data_entity.rs +++ b/src/ro_crate/data_entity.rs @@ -1,3 +1,8 @@ +//! Create a Data entity. +//! +//! A data entity is a computional research object such as a file that is +//! required to fully understand or reproduce a research outcome. + use crate::ro_crate::constraints::*; use crate::ro_crate::modify::*; use serde::{ @@ -15,10 +20,11 @@ use std::fmt; /// additional properties that may vary. #[derive(Debug)] pub struct DataEntity { + /// URI to data pub id: String, - + /// Defined type, if file MUST be type 'File' pub type_: DataType, - + /// Additional metadata pub dynamic_entity: Option>, } diff --git a/src/ro_crate/metadata_descriptor.rs b/src/ro_crate/metadata_descriptor.rs index 17e1b67..aea503a 100644 --- a/src/ro_crate/metadata_descriptor.rs +++ b/src/ro_crate/metadata_descriptor.rs @@ -1,3 +1,8 @@ +//! Create the metadata descriptor fo the object. +//! +//! This describes the crate as a whole, specifying it as a versioned crate and +//! the location of the root data entity + use crate::ro_crate::constraints::DynamicEntity; use crate::ro_crate::constraints::{DataType, Id}; use crate::ro_crate::modify::*; @@ -17,10 +22,16 @@ use std::marker::PhantomData; /// and a set of dynamic properties for descriptive extension. #[derive(Debug)] pub struct MetadataDescriptor { + /// ID that MUST be `ro-crate-metadata.json` pub id: String, + /// Type that MUST be `CreativeWork` pub type_: DataType, + /// SHOULD be a versioned permalink URI of the RO-Crate specification that the + /// crated crate conforms to pub conforms_to: Id, + /// Should reference the root data entity buy using `./` pub about: Id, + /// Optional dynamic entity for further population of key:value pairs pub dynamic_entity: Option>, } @@ -83,7 +94,7 @@ impl Serialize for MetadataDescriptor { } } -/// A custom serialization trait extending the standard `Serialize` trait. +/// A custom serialization trait extending the standard `Serialize` trait for MetadataDescriptor. /// /// `CustomSerialize` is designed to provide additional serialization methods /// for complex data structures, particularly those that include dynamic properties @@ -198,8 +209,7 @@ impl<'de> Deserialize<'de> for MetadataDescriptor { } } - -/// Tests +/// Tests #[cfg(test)] mod tests { use super::*; diff --git a/src/ro_crate/mod.rs b/src/ro_crate/mod.rs index 98f2b7b..9101c33 100644 --- a/src/ro_crate/mod.rs +++ b/src/ro_crate/mod.rs @@ -1,3 +1,9 @@ +//! Utility functions and structure for RO-Crate +//! +//! # Note +//! Serialisatoin and deserialisation of RO-Crates to json-ld files heavily leverages +//! the serde and serde-json library + pub mod constraints; pub mod contextual_entity; pub mod data_entity; @@ -6,5 +12,5 @@ pub mod modify; pub mod read; pub mod rocrate; pub mod root; +pub mod schema; pub mod write; -pub mod schema; \ No newline at end of file diff --git a/src/ro_crate/modify.rs b/src/ro_crate/modify.rs index 062f0aa..5c6255d 100644 --- a/src/ro_crate/modify.rs +++ b/src/ro_crate/modify.rs @@ -1,16 +1,34 @@ +//! Module for interacting with dynamic entities +//! +//! A dynamic entity is simply a field that is not a MUST within a defined +//! RO-Crate entity. It can be added, modified or deleted. + use crate::ro_crate::constraints::*; use serde::ser::SerializeMap; use serde::{Serialize, Serializer}; use serde_json::Value; use std::collections::HashMap; -/// A trait that enables dynamic entity modificaiton methods to be shared by different -/// Entity types. +/// A trait for manipulating dynamic entities within an RO-Crate object. +/// +/// Provides methods for adding, removing, and searching dynamic fields and values +/// within entities of an RO-Crate. These dynamic entities are often represented as +/// flexible structures, allowing for a varied and extensible set of metadata. /// -/// These methods encompass adding and removal, as well as recursive removal of IDs. +/// # Note +/// Additions need to be updated due to evolution of the DynamicEntity type. There +/// will be some redundency. pub trait DynamicEntityManipulation: Serialize { + /// Gets a mutable reference to the dynamic entity's underlying HashMap. fn dynamic_entity(&mut self) -> &mut Option>; + + /// Gets an immutable reference to the dynamic entity's underlying HashMap. fn dynamic_entity_immut(&self) -> &Option>; + + /// Adds dynamic fields to the entity + /// + /// # Arguments + /// * 'values' - A 'Hashmap' containing a key and a valid DynamicEntity type fn add_dynamic_fields(&mut self, values: HashMap) { for (key, value) in values { match value { @@ -20,6 +38,12 @@ pub trait DynamicEntityManipulation: Serialize { } } } + + /// Adds a string value to the dynamic entity. + /// + /// # Arguments + /// * `key` - The field name as a `String`. + /// * `value` - The string value to be added. fn add_string_value(&mut self, key: String, value: String) { // Use `entry` API for a more concise approach // This will automatically create the HashMap if it doesn't exist @@ -27,33 +51,64 @@ pub trait DynamicEntityManipulation: Serialize { .get_or_insert_with(HashMap::new) .insert(key, DynamicEntity::EntityString(value)); } + + /// Adds an identifier value to the dynamic entity. + /// + /// # Arguments + /// * `key` - The field name as a `String`. + /// * `value` - The `Id` value to be added. fn add_id_value(&mut self, key: String, value: Id) { self.dynamic_entity() .get_or_insert_with(HashMap::new) .insert(key, DynamicEntity::EntityId(value)); } + + /// Removes a field from the dynamic entity. + /// + /// # Arguments + /// * `key` - The name of the field to remove. fn remove_field(&mut self, key: &str) { if let Some(dynamic_entity) = self.dynamic_entity() { dynamic_entity.remove(key); } } - fn search_value(&self, search_value: &DynamicEntity) { + + /// Searches for a specific value within the dynamic entity. + /// + /// # Arguments + /// * `search_value` - The `DynamicEntity` value to search for. + /// + /// # Returns + /// `true` if the value is found, otherwise `false`. + fn search_value(&self, search_value: &DynamicEntity) -> bool { if let Some(dynamic_entity) = self.dynamic_entity_immut() { for (_key, value) in dynamic_entity.iter() { - println!("{:?}", value); if value == search_value { - println!("Value found: {:?}", value) + return true; } } } + return false; } - /// Finds keys within rocrate + + /// Finds keys within the RO-Crate matching a specified key or retrieves all keys. + /// + /// # Arguments + /// * `search_key` - A `String` specifying the key to search for. + /// * `get_all` - A boolean indicating whether to return all keys or only those matching `search_key`. + /// + /// # Returns + /// A vector of strings containing the keys found. fn search_keys(&self, search_key: &String, get_all: bool) -> Vec { let mut key_vec: Vec = Vec::new(); - - /// recursive function for traversing nested objects. - fn search_obj(object: &HashMap, search_key: &String, get_all: bool) -> Vec { + /// recursive function for traversing nested objects. + /// Should probably move out from nest + fn search_obj( + object: &HashMap, + search_key: &String, + get_all: bool, + ) -> Vec { let mut key_vec: Vec = Vec::new(); for (_key, value) in object.iter() { if get_all || _key == search_key { @@ -61,7 +116,7 @@ pub trait DynamicEntityManipulation: Serialize { } if let DynamicEntity::EntityObject(inner_object) = value { let inner_keys = search_obj(inner_object, search_key, get_all); - if inner_keys.len() != 0 { + if !inner_keys.is_empty() { key_vec.extend(inner_keys); } } @@ -71,25 +126,17 @@ pub trait DynamicEntityManipulation: Serialize { if let Some(dynamic_entity) = self.dynamic_entity_immut() { for (_key, value) in dynamic_entity.iter() { - //println!("{:?}, {:?}", _key, value); - match value { - DynamicEntity::EntityObject(object) => { - let obvec = search_obj(object, search_key, get_all); - if obvec.len() != 0 { - key_vec.extend(obvec); - }} - _ => () - } + if let DynamicEntity::EntityObject(object) = value { + let obvec = search_obj(object, search_key, get_all); + if !obvec.is_empty() { + key_vec.extend(obvec); + } + } if get_all { key_vec.push(_key.to_string()); } - if _key == search_key { - println!("Value found: {:?}", search_key) - } } - } - //println!("{:?}", key_vec); key_vec } @@ -100,6 +147,9 @@ pub trait DynamicEntityManipulation: Serialize { /// usage of ID which is expected to be 99% of recursive removal. Other types which could be regularly /// duplicated and non specific are ignored. /// + /// # Arguements + /// * 'target_id' - A string representing the id value to be removed + /// /// This is used as part of RoCrate implmentation for removing by ID /// /// Types that are not allowed to be recursively removed: @@ -107,11 +157,9 @@ pub trait DynamicEntityManipulation: Serialize { /// f64 /// i64 /// - /// It does not allow you to remove values from field sthat are defined as MUST within the Ro-Crate spec. - fn remove_matching_value(&mut self, target_value: &str) { + /// It does not allow you to remove values from fields that are defined as MUST within the Ro-Crate spec. + fn remove_matching_value(&mut self, target_id: &str) { if let Some(dynamic_entity) = self.dynamic_entity() { - println!("Removing values: {:?}", dynamic_entity); - let mut keys_to_remove = Vec::new(); // Collect keys where Fallback values need modification @@ -119,16 +167,14 @@ pub trait DynamicEntityManipulation: Serialize { let mut updates = Vec::new(); for (key, value) in dynamic_entity.iter() { - println!("Target key: {}", key); - println!("Target entity: {:?}", value); match value { - DynamicEntity::EntityString(s) if s == target_value => { + DynamicEntity::EntityString(s) if s == target_id => { keys_to_remove.push(key.clone()); } DynamicEntity::EntityId(Id::IdArray(id_values)) => { let filtered_values: Vec = id_values .iter() - .filter(|id_val| id_val.id != target_value) + .filter(|id_val| id_val.id != target_id) .cloned() .collect(); @@ -136,7 +182,7 @@ pub trait DynamicEntityManipulation: Serialize { updates.push((key.clone(), Id::IdArray(filtered_values))); } } - DynamicEntity::EntityId(Id::Id(id_value)) if id_value.id == target_value => { + DynamicEntity::EntityId(Id::Id(id_value)) if id_value.id == target_id => { keys_to_remove.push(key.clone()); } DynamicEntity::Fallback(fallback_values) => { @@ -150,11 +196,11 @@ pub trait DynamicEntityManipulation: Serialize { } } + // Potentially depreciated need to test for key in fallback_keys_to_modify { if let Some(DynamicEntity::Fallback(fallback_value)) = dynamic_entity.get_mut(&key) { - println!("Beginning fallback removal"); - remove_matching_value_from_json(fallback_value, target_value); + remove_matching_value_from_json(fallback_value, target_id); } } @@ -171,16 +217,90 @@ pub trait DynamicEntityManipulation: Serialize { } } } + /// Updates all occurrences of a specific ID with a new ID within the dynamic entity fields. + /// + /// This method traverses the dynamic entity fields of an object implementing `DynamicEntityManipulation`, + /// looking for any entity IDs that match `id_old` and replaces them with `id_new`. It supports updating + /// both single IDs and IDs within arrays. + /// + /// # Arguments + /// * `id_old` - A string slice representing the old ID to be replaced. + /// * `id_new` - A string slice representing the new ID to replace with. + /// + /// # Returns + /// Returns `Some(())` if at least one ID was updated, indicating that the operation made changes to the entity. + /// Returns `None` if no matching IDs were found or no updates were made. + /// + /// # Examples + /// Assuming `entity` is a member of the GraphVector Enum (e.g. GraphVector::DataEntity) + /// and contains an ID that matches `id_old`: + /// ``` + /// if entity.update_matching_id("old_id", "new_id").is_some() { + /// println!("ID updated successfully."); + /// } else { + /// println!("No matching ID found or no update needed."); + /// } + /// ``` + /// # Notes + /// This function is essential for maintaining referential integrity within the RO-Crate when IDs of entities + /// are changed. It ensures that all references to the updated entity reflect the new ID. + fn update_matching_id(&mut self, id_old: &str, id_new: &str) -> Option<()> { + let mut updated = false; + + if let Some(dynamic_entity) = &mut self.dynamic_entity() { + for (_key, value) in dynamic_entity.iter_mut() { + match value { + DynamicEntity::EntityId(Id::IdArray(ids)) => { + for id in ids { + if id.id == id_old { + id.id = id_new.to_string(); + updated = true; + } + } + } + DynamicEntity::EntityId(Id::Id(id)) => { + if id.id == id_old { + id.id = id_new.to_string(); + updated = true; + } + } + DynamicEntity::EntityIdVec(ids) => { + for _id in ids { + //if id.id == id_old { + // id.id = id_new.to_string(); + // updated = true; + //} + // TOOD: + } + } + _ => (), + } + } + } + + if updated { + Some(()) + } else { + None + } + } } /// Recursively removes matching values from fallback serailised json objects /// /// This allows for nested id's to be removed indefinitely from complicated json objects that don't conform /// to a statically defined DynamicEntity Type. Both single objects and array objects are searched. +/// +/// # Arguments +/// * `value` - A mutable reference to a serde_json `Value` that represents the JSON structure to be cleaned. +/// * `target_value` - The value to be searched for and removed from the JSON structure. +/// +/// # Notes +/// Potentially depreciated after DynamicEntity Type expansion +/// TODO: Need to test fn remove_matching_value_from_json(value: &mut Value, target_value: &str) { match value { Value::Object(obj) => { - println!("Fallback obj to remove: {:?}", obj); let keys_to_remove: Vec = obj .iter() .filter_map(|(k, v)| { @@ -192,10 +312,6 @@ fn remove_matching_value_from_json(value: &mut Value, target_value: &str) { }) .collect(); - if keys_to_remove.len() > 0 { - println!("SUCCESS! Keys to remove: {:?}", keys_to_remove); - } - for key in keys_to_remove { obj.remove(&key); } @@ -206,10 +322,8 @@ fn remove_matching_value_from_json(value: &mut Value, target_value: &str) { } Value::Array(arr) => { let mut i = 0; - println!("Fallback value arr to remove from arr: {:?}", arr); while i < arr.len() { if &arr[i] == target_value { - println!("SUCCESS! Removing specified key {:?}", arr[i]); arr.remove(i); } else { remove_matching_value_from_json(&mut arr[i], target_value); @@ -224,7 +338,10 @@ fn remove_matching_value_from_json(value: &mut Value, target_value: &str) { /// A trait for custom serialization of complex data structures. /// /// `CustomSerialize` extends the standard `Serialize` trait to provide additional -/// functionality for serializing data structures that contain both static and dynamic fields. +/// functionality for serializing data structures that contain both static and dynamic /// fields. +/// +/// # Note +/// This is utilised by both DataEntity and ContextualEntity pub trait CustomSerialize: Serialize { fn dynamic_entity(&self) -> Option<&HashMap>; fn id(&self) -> &String; diff --git a/src/ro_crate/read.rs b/src/ro_crate/read.rs index ed043a4..3f38868 100644 --- a/src/ro_crate/read.rs +++ b/src/ro_crate/read.rs @@ -1,33 +1,73 @@ +//! Allows RO-Crates (ro-crate-metadata.json) files to be read into the +//! RoCrate data structure + use crate::ro_crate::rocrate::RoCrate; +use crate::ro_crate::schema::validate_crate_keys; use serde_json; use std::fs; use std::io; use std::path::{Path, PathBuf}; -pub fn read_crate(crate_path: PathBuf) -> Result { - match fs::read_to_string(&crate_path) { - Ok(data) => serde_json::from_str(&data).map_err(CrateReadError::from), +/// Reads and deserializes an RO-Crate from a specified file path. +/// +/// This function attempts to load an RO-Crate from a JSON file located at `crate_path`. +/// If `valid` is `true`, it also validates the crate's keys against the RO-Crate schema. +/// +/// # Arguments +/// * `crate_path` - A reference to the `PathBuf` indicating the file path of the RO-Crate to read. +/// * `valid` - A boolean flag indicating whether to validate the crate's keys against the schema. +pub fn read_crate(crate_path: &PathBuf, valid: bool) -> Result { + match fs::read_to_string(crate_path) { + Ok(data) => match serde_json::from_str::(&data) { + Ok(rocrate) => { + if !valid || validate_crate_keys(&rocrate) { + Ok(rocrate) + } else { + //println!("{}", rocrate); + Err(CrateReadError::VocabNotValid("Vocab not valid".to_string())) + } + } + Err(e) => Err(CrateReadError::from(e)), + }, Err(e) => Err(CrateReadError::from(e)), } } - -pub fn crate_path(relative_path: &str) -> PathBuf { - Path::new(relative_path).to_path_buf() +/// Constructs a `PathBuf` from a given file path string. +/// +/// This utility function converts a string slice representing a path into a `PathBuf`, +/// facilitating file system operations with the path. +/// +/// # Arguments +/// * `path` - A string slice representing the path to be converted. +pub fn crate_path(path: &str) -> PathBuf { + Path::new(path).to_path_buf() } +/// Enumerates potential errors encountered while reading and validating an RO-Crate. +/// +/// This enum provides detailed categorization of errors that can occur during the process of +/// reading an RO-Crate from a file and optionally validating its keys against the schema. +/// +/// Variants: +/// - `IoError`: Encapsulates errors related to input/output operations, typically file reading issues. +/// - `JsonError`: Covers errors arising from parsing the crate's JSON content. +/// - `VocabNotValid`: Indicates that the crate's keys did not validate against the expected vocabulary, including a message detailing the issue. #[derive(Debug)] pub enum CrateReadError { IoError(io::Error), JsonError(serde_json::Error), + VocabNotValid(String), } impl From for CrateReadError { + /// Converts an `io::Error` into a `CrateReadError::IoError`. fn from(err: io::Error) -> CrateReadError { CrateReadError::IoError(err) } } impl From for CrateReadError { + /// Converts a `serde_json::Error` into a `CrateReadError::JsonError`. fn from(err: serde_json::Error) -> CrateReadError { CrateReadError::JsonError(err) } @@ -45,7 +85,7 @@ mod tests { fn test_read_crate_success() { let path = fixture_path("_ro-crate-metadata-minimal.json"); - let crate_result = read_crate(path); + let crate_result = read_crate(&path, false); assert!(crate_result.is_ok()); } @@ -53,7 +93,7 @@ mod tests { fn test_read_crate_file_not_found() { let path = fixture_path("non_existent_file.json"); - let crate_result = read_crate(path); + let crate_result = read_crate(&path, false); match crate_result { Err(CrateReadError::IoError(ref e)) if e.kind() == io::ErrorKind::NotFound => (), _ => panic!("Expected file not found error"), @@ -64,7 +104,7 @@ mod tests { fn test_read_crate_invalid_json() { let path = fixture_path("invalid.json"); - let crate_result = read_crate(path); + let crate_result = read_crate(&path, false); match crate_result { Err(CrateReadError::JsonError(_)) => (), _ => panic!("Expected JSON parsing error"), diff --git a/src/ro_crate/rocrate.rs b/src/ro_crate/rocrate.rs index b4f260b..27aa350 100644 --- a/src/ro_crate/rocrate.rs +++ b/src/ro_crate/rocrate.rs @@ -1,6 +1,16 @@ +//! Defines the RoCrate data structure +//! +//! Includes implementations for crate modification and defines both the +//! RoCrate, RoCrateContext and GraphVector. +//! +//! # Note +//! This should definitly be split up in future implementations + +use crate::ro_crate::constraints::{DynamicEntity, Id, IdValue}; use crate::ro_crate::contextual_entity::ContextualEntity; use crate::ro_crate::data_entity::DataEntity; use crate::ro_crate::metadata_descriptor::MetadataDescriptor; +use crate::ro_crate::modify::DynamicEntityManipulation; use crate::ro_crate::root::RootDataEntity; use serde::de::Error as SerdeError; use serde::ser::Serializer; @@ -11,44 +21,62 @@ use std::fmt; use std::path::Path; use url::Url; -use super::constraints::DynamicEntity; -use super::modify::DynamicEntityManipulation; - -//use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; - -// Toggle to allow URI validity check -// If true, increases parse time -const ID_VALIDATION: bool = true; - -/// Defines whether none conforming crates can be read and created -const STRICT_SPEC: bool = true; - -/// Primary struct for creating a Ro-Crate object. GraphVector -/// stores the crates graph array +/// Represents a Research Object Crate (RO-Crate) metadata structure. +/// +/// An RO-Crate is a lightweight approach to packaging research data +/// with their associated metadata in a machine-readable format. This struct +/// models the root of an RO-Crate JSON-LD document, containing both the +/// contextual information and the actual data entities (graph). #[derive(Serialize, Deserialize, Debug)] pub struct RoCrate { + /// JSON-LD context defining the terms used in the RO-Crate. + /// + /// This field specifies the context for interpreting the JSON-LD document, + /// often pointing to a remote JSON-LD context file or an inline definition + /// that maps terms to IRIs (Internationalized Resource Identifiers). #[serde(rename = "@context")] pub context: RoCrateContext, + /// The main content of the RO-Crate, represented as a graph of entities. + /// + /// This vector contains the entities (e.g., datasets, people, organizations) + /// involved in the research output. Each entity is described in a structured + /// format, allowing for easy machine processing and interoperability. #[serde(rename = "@graph")] pub graph: Vec, } -/// type for context to check types for MUST requirement +/// Defines the JSON-LD contexts in an RO-Crate, facilitating flexible context specification. +/// +/// This enum models the `@context` field's variability in RO-Crates, enabling the use of external URLs, +/// combination of contexts, or embedded definitions directly within the crate. It supports: #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] pub enum RoCrateContext { + /// A URI string for referencing external JSON-LD contexts (default should be + /// ro-crate context). ReferenceContext(String), + /// A combination of contexts for extended or customized vocabularies, represented as a list of items. ExtendedContext(Vec), + /// Directly embedded context definitions, ensuring crate portability by using a vector of hash maps for term definitions. EmbeddedContext(Vec>), } -/// Enables both a vector of context strings and a vector -/// containing defined embedded contexts (context_name: context_uri) +/// Represents elements in the `@context` of an RO-Crate, allowing for different ways to define terms. +/// +/// There are two types of items: +/// +/// - `ReferenceItem`: A URL string that links to an external context definition. It's like a reference to a standard set of terms used across different crates. +/// +/// - `EmbeddedContext`: A map containing definitions directly. This is for defining terms right within the crate, making it self-contained. +/// #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] pub enum ContextItem { + /// A URI string for referencing external JSON-LD contexts ReferenceItem(String), + /// Directly embedded context definitions, ensureing crate protability by using a vector of + /// hash maps for term definitions EmbeddedContext(HashMap), } @@ -82,10 +110,10 @@ impl RoCrate { } } - /// Recursively removes entities from the RO-Crate graph based on a matching `@id`. + /// Supports the removal process by looking for and removing related entities. /// - /// This method is from `remove_by_id` when recursive removal is needed. - /// It iterates through the graph and removes matching values from dynamic entities. + /// This function is called for deeper cleaning, making sure that any entity that + /// could be connected to the one being removed is also taken out if it matches the ID. fn remove_id_recursive(&mut self, id: &str) { for graph_vector in &mut self.graph { if let GraphVector::RootDataEntity(data_entity) = graph_vector { @@ -98,12 +126,59 @@ impl RoCrate { data_entity.remove_matching_value(id); } if let GraphVector::ContextualEntity(data_entity) = graph_vector { - println!("\n This is a contextual entity: {:?}", data_entity); data_entity.remove_matching_value(id); } } } + /// Updates the ID of an entity and any related entities within the crate. + /// + /// Looks through all entities, updating any that match `id_old` to `id_new`. If any entity is updated, + /// it returns a confirmation. This is useful for keeping the crate's links accurate if an entity's ID changes. + pub fn update_id_recursive(&mut self, id_old: &str, id_new: &str) -> Option<()> { + let mut any_updates = false; + + for graph_vector in &mut self.graph { + let updated = match graph_vector { + GraphVector::ContextualEntity(entity) => { + if entity.id == id_old { + entity.id = id_new.to_string(); + } + entity.update_matching_id(id_old, id_new) + } + GraphVector::DataEntity(entity) => { + if entity.id == id_old { + entity.id = id_new.to_string(); + } + entity.update_matching_id(id_old, id_new) + } + GraphVector::RootDataEntity(entity) => { + if entity.id == id_old { + entity.id = id_new.to_string(); + } + entity.update_matching_id(id_old, id_new) + } + GraphVector::MetadataDescriptor(descriptor) => { + if descriptor.id == id_old { + descriptor.id = id_new.to_string(); + } + descriptor.update_matching_id(id_old, id_new) + } + _ => None, + }; + + if updated.is_some() { + any_updates = true; + } + } + + if any_updates { + Some(()) + } else { + None + } + } + /// Finds the index of a particular entity in the RO-Crate graph based on its `@id`. /// /// Returns the index of the first entity that matches the given `@id`. @@ -120,12 +195,14 @@ impl RoCrate { _ => None, }) } - /// Finds ID based upon ID string input - pub fn find_id(&mut self, id:&str) -> Option<&GraphVector> { - self.find_id_index(id).and_then(|index| self.graph.get(index)) + /// Finds ID based upon ID string input and returns a reference to it. + /// + /// If it cannot find an entity, it will return None + pub fn find_id(&mut self, id: &str) -> Option<&GraphVector> { + self.find_id_index(id) + .and_then(|index| self.graph.get(index)) } - /// Removes a specific field from a dynamic entity within the RO-Crate graph. /// /// This method finds the entity by `id` and then removes the field specified by `key` @@ -134,7 +211,6 @@ impl RoCrate { let index = self.find_id_index(id); if let Some(index) = index { if let Some(graph_vector) = self.graph.get_mut(index) { - // Matching discrete to enable further custom type handling match graph_vector { GraphVector::MetadataDescriptor(descriptor) => { if let Some(dynamic_entity) = &mut descriptor.dynamic_entity { @@ -162,11 +238,11 @@ impl RoCrate { } } - /// Adds a specific dynamic entity field to the Ro-Crate Graph + /// Adds new information to an entity identified by ID. The new info is given as a + /// map (key-value pairs) and is added to the entity's dynamic_entity hashmap. pub fn add_dynamic_entity_field(&mut self, id: &str, values: HashMap) { if let Some(index) = self.find_id_index(id) { if let Some(graph_vector) = self.graph.get_mut(index) { - // Matching discrete to enable further custom type handling match graph_vector { GraphVector::MetadataDescriptor(descriptor) => { descriptor.add_dynamic_fields(values) @@ -180,7 +256,11 @@ impl RoCrate { } } - /// TODO + /// Searches for and returns values associated with a specific property key across all entities. + /// + /// This method scans every entity within the RO-Crate for a given property key and compiles a list of all unique values + /// associated with that key. If an entity contains the specified property, its value(s) are added to the return list. + /// This is useful for aggregating information from across the dataset that shares a common property. pub fn get_property_value(&self, key: String) -> Vec { let mut property_values: Vec = Vec::new(); for graph_vector in &self.graph { @@ -217,13 +297,20 @@ impl RoCrate { dedup_vec(&mut property_values); property_values } - - /// TODO + + /// Retrieves all distinct property values from dynamic entities within the RO-Crate. + /// + /// Unlike `get_property_value`, this method does not target a specific property key. Instead, it gathers + /// all values from all properties across every entity, providing a comprehensive overview of the data contained + /// within the crate. This method is particularly useful for extracting a dataset-wide summary or for auditing + /// the diversity of information stored. + /// + /// DOES NOT GET DEFINED STRUCT FIELDS pub fn get_all_property_values(&self) -> Vec { - // Empty string for function argument. + // Empty string for function argument. let mut property_values: Vec = Vec::new(); let key: String = String::new(); - + for graph_vector in &self.graph { match graph_vector { GraphVector::ContextualEntity(ref _entity) => { @@ -258,16 +345,21 @@ impl RoCrate { dedup_vec(&mut property_values); property_values } - + + /// Retrieves a list of context keys from the RO-Crate's context. + /// + /// This method examines the RO-Crate's context (either embedded directly in the crate or extended) + /// and compiles a list of all keys (properties or terms) defined. It's useful for understanding the + /// scope of metadata vocabularies used within the crate. pub fn get_context_items(&self) -> Vec { - let mut valid_context: Vec = Vec::new(); - + let mut valid_context: Vec = Vec::new(); + match &self.context { RoCrateContext::EmbeddedContext(context) => { for map in context { for (key, _value) in map { valid_context.push(key.to_string()); - } + } } } RoCrateContext::ExtendedContext(context) => { @@ -276,37 +368,120 @@ impl RoCrate { ContextItem::EmbeddedContext(context) => { for (key, _value) in context { valid_context.push(key.to_string()); - } + } } - _ => () + _ => (), } } } - _ =>() - } + RoCrateContext::ReferenceContext(context) => { + valid_context.push(context.to_string()); + } + } valid_context } /// TODO pub fn add_context(&self) {} - /// Overwrites a data object by it's id. If it doesn't exist, it will add to graph + /// Updates or adds a new data entity in the RO-Crate by ID. + /// + /// If an entity with the specified ID exists, it is overwritten with `new_data`. If no entity with the + /// given ID exists, `new_data` is added to the crate. This method ensures that any added or updated data + /// entity is correctly referenced in the root data entity's `hasPart` property if it is not already listed. pub fn overwite_by_id(&mut self, id: &str, new_data: GraphVector) { if let Some(index) = self.find_id_index(id) { - self.graph[index] = new_data + self.graph[index] = new_data; + if let GraphVector::DataEntity(_entity) = &self.graph[index] { + self.add_data_to_partof_root(id) + } } else { - self.graph.push(new_data) + self.graph.push(new_data); + if let Some(index) = self.find_id_index(id) { + if let GraphVector::DataEntity(_entity) = &self.graph[index] { + self.add_data_to_partof_root(id) + } + } } } + /// Ensures a data entity is included in the `hasPart` property of the root data entity. + /// + /// Before adding a new data entity, this method checks if the entity is already referenced in the root + /// data entity's `hasPart` property. If not, it adds a reference to ensure the data entity is correctly + /// part of the overall data structure of the RO-Crate. + pub fn add_data_to_partof_root(&mut self, target_id: &str) { + if let Some(index) = self.find_id_index("./") { + if let Some(GraphVector::RootDataEntity(root)) = self.graph.get_mut(index) { + let dynamic_entity = root.dynamic_entity.get_or_insert_with(HashMap::new); + + match dynamic_entity.get_mut("hasPart") { + Some(DynamicEntity::EntityId(Id::IdArray(ref mut id_array))) => { + // Check if the target_id is already present + if !id_array.iter().any(|id_value| id_value.id == target_id) { + id_array.push(IdValue { + id: target_id.to_owned(), + }); + } + } + _ => { + dynamic_entity.insert( + "hasPart".to_string(), + DynamicEntity::EntityId(Id::IdArray(vec![IdValue { + id: target_id.to_owned(), + }])), + ); + } + }; + }; + }; + } + + /// Retrieves a list of all entity IDs within the RO-Crate. + /// + /// This method compiles a list of the IDs of all entities contained within the RO-Crate. It is useful + /// for operations that need to process or reference every entity in the crate, such as data validation + /// or integrity checks. + pub fn get_all_ids(&self) -> Vec<&String> { + let mut id_vec: Vec<&String> = Vec::new(); + + for graph_vector in self.graph.iter() { + match graph_vector { + GraphVector::MetadataDescriptor(entity) => id_vec.push(&entity.id), + GraphVector::RootDataEntity(entity) => id_vec.push(&entity.id), + GraphVector::DataEntity(entity) => id_vec.push(&entity.id), + GraphVector::ContextualEntity(entity) => id_vec.push(&entity.id), + GraphVector::FallbackValue(_) => {} // other variants... + }; + } + id_vec + } } +/// Removes duplicates from a vector, leaving only unique elements. +/// +/// This function sorts the vector and then removes any consecutive duplicate elements, ensuring that +/// each element is unique. It requires the elements to implement the `Ord` trait to allow sorting. +/// +/// # Arguments +/// * `vec` - A mutable reference to the vector from which duplicates will be removed. +/// +/// # Examples +/// ``` +/// let mut numbers = vec![3, 1, 2, 3, 4, 2]; +/// dedup_vec(&mut numbers); +/// assert_eq!(numbers, vec![1, 2, 3, 4]); +/// ``` fn dedup_vec(vec: &mut Vec) { vec.sort(); vec.dedup(); } impl Default for RoCrate { + /// Provides a default instance of `RoCrate` with a predefined context URL and an empty graph. + /// + /// The context URL points to the standard RO-Crate JSON-LD context, setting up a new `RoCrate` with + /// the necessary context for interpreting the crate according to the RO-Crate specifications. fn default() -> Self { RoCrate { context: RoCrateContext::ReferenceContext(String::from( @@ -317,7 +492,17 @@ impl Default for RoCrate { } } -/// Formats RoCrate object for display +/// Implements the `Display` trait for `RoCrate` to enable pretty printing. +/// +/// This implementation provides a human-readable representation of an `RoCrate` instance, showing the +/// context and a summary of the graph content. It is useful for debugging purposes or when logging crate +/// information in a human-readable format. +/// +/// # Examples +/// ``` +/// let ro_crate = RoCrate::default(); +/// println!("{}", ro_crate); +/// // Outputs: RO-Crate: context="https://w3id.org/ro/crate/1.1/context", graph=[] impl fmt::Display for RoCrate { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( @@ -328,7 +513,21 @@ impl fmt::Display for RoCrate { } } -// Graph Vector enum for automatica type serialisation on ro-crate json loading +/// Represents the various types of entities contained within the graph of an RO-Crate. +/// +/// An RO-Crate organizes digital resources as a graph of interconnected entities, including +/// data entities, contextual entities, metadata descriptions, and root. This enum encapsulates +/// the different entity types that can be found in an RO-Crate's graph, allowing for flexible +/// serialization and handling of the graph's contents. +/// +/// Variants: +/// - `DataEntity`: Represents a data entity, which is a digital resource described by the crate. +/// - `ContextualEntity`: Represents a contextual entity that provides context for the data entities and the crate. +/// - `FallbackValue`: A generic value used when the specific type of an entity is unknown or not listed. +/// NOTE: this should never actualy be hit since everything should be handled. +/// +/// - `MetadataDescriptor`: Contains metadata about the crate itself or its entities. +/// - `RootDataEntity`: The root data entity, representing the crate's primary content. #[derive(Debug)] pub enum GraphVector { DataEntity(DataEntity), @@ -339,6 +538,11 @@ pub enum GraphVector { } impl Serialize for GraphVector { + /// Serializes the `GraphVector` enum into a format suitable for JSON representation. + /// + /// This custom implementation of `Serialize` ensures that each variant of `GraphVector` is + /// correctly serialized into JSON, adhering to the structure expected by consumers of RO-Crate metadata. + /// It matches on the enum variant and delegates serialization to the inner entity's own `Serialize` implementation. fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -354,16 +558,13 @@ impl Serialize for GraphVector { } } -/// Custom deserialization implementation for `GraphVector`. -/// -/// This implementation provides a tailored deserialization process for the `GraphVector` enum, -/// based on the `@id` field present in the JSON data. The `@id` field determines which specific -/// variant of `GraphVector` to instantiate - `MetadataDescriptor`, `RootDataEntity`, `DataEntity`, -/// or `ContextualEntity`. -/// -/// In cases where `ID_VALIDATION` is enabled, the method further distinguishes between `DataEntity` -/// and `ContextualEntity` based on the validity of the URL or file path in the `@id` field. impl<'de> Deserialize<'de> for GraphVector { + /// Custom deserialization implementation for `GraphVector`. + /// + /// This implementation provides a tailored deserialization process for the `GraphVector` enum, + /// based on the `@id` field present in the JSON data. The `@id` field determines which specific + /// variant of `GraphVector` to instantiate - `MetadataDescriptor`, `RootDataEntity`, `DataEntity`, + /// or `ContextualEntity`. fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, @@ -372,15 +573,7 @@ impl<'de> Deserialize<'de> for GraphVector { try_deserialize_into_graph_vector(&value) .or_else(|e| { - if STRICT_SPEC == true { - return Err(e); - } - println!( - "Error deserialising, falling back to Value: {} \n {} \n", - e, value - ); - // Explicitly return Ok with GraphVector variant - Ok(GraphVector::FallbackValue(value)) + return Err(e); }) .map_err(|e: SerdeJsonError| { // Use the error type from the deserializer's context @@ -388,6 +581,13 @@ impl<'de> Deserialize<'de> for GraphVector { }) } } + +/// Attempts to deserialize a JSON `Value` into a `GraphVector` variant. +/// +/// This function inspects the `@id` field within the given JSON `Value` to determine the type of entity it represents +/// and then deserializes the value into the corresponding `GraphVector` variant. It uses the entity's `@id` to distinguish +/// between different types of entities, such as metadata, root data entities, data entities, and contextual entities. + fn try_deserialize_into_graph_vector(value: &Value) -> Result { if let Some(id) = value.get("@id").and_then(Value::as_str) { match id { @@ -396,7 +596,7 @@ fn try_deserialize_into_graph_vector(value: &Value) -> Result RootDataEntity::deserialize(value).map(GraphVector::RootDataEntity), _ => { - if ID_VALIDATION == true && is_valid_url_or_path(id) { + if is_valid_url_or_path(id) { DataEntity::deserialize(value).map(GraphVector::DataEntity) } else { ContextualEntity::deserialize(value).map(GraphVector::ContextualEntity) @@ -419,7 +619,6 @@ fn is_valid_url_or_path(s: &str) -> bool { // Tests to make -// Test that ID_Validation correctly checks for valid URIs // Parses valid into dataEntity's if a file // Parses valid into contextual entities if a valid URL diff --git a/src/ro_crate/root.rs b/src/ro_crate/root.rs index 80ccbd2..4f532d1 100644 --- a/src/ro_crate/root.rs +++ b/src/ro_crate/root.rs @@ -1,3 +1,5 @@ +//! Defines the Root data entity for RO-Crates + use crate::ro_crate::constraints::DynamicEntity; use crate::ro_crate::constraints::{DataType, License}; use crate::ro_crate::modify::DynamicEntityManipulation; @@ -9,16 +11,32 @@ use serde::{ use std::collections::HashMap; use std::fmt; -// Root data entity +// The Root data entity struct. +// +// The root data entity is a dataset that represents the RO-Crate as a whole; a +// research object that incldues the data entities and related contextual entities. +// +// # Note +// Should update the type_ and id to follow requirements for future unless spec +// change. #[derive(Debug)] pub struct RootDataEntity { + // A string that SHOULD be ./ and MUST end with / pub id: String, + // A string that MUST be Dataset pub type_: DataType, + // MUST be a string in ISO 8601 Date format and SHOULD be specified to at least + // the precision of the day. pub date_published: Option, + // Should link to a contextual entity in the RO-Crate metadata file with a + // name and description. pub license: Option, + // Optional Hashmap to enable key/value pair addition depending on crate + // information. pub dynamic_entity: Option>, } +/// Implements the 'Display' trait for RootDataEntity to enable pretty printing impl fmt::Display for RootDataEntity { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( @@ -30,9 +48,11 @@ impl fmt::Display for RootDataEntity { } impl DynamicEntityManipulation for RootDataEntity { + /// Implements modification of dynamic entity fn dynamic_entity(&mut self) -> &mut Option> { &mut self.dynamic_entity } + /// Allows immutable dynamic entities to be used fn dynamic_entity_immut(&self) -> &Option> { &self.dynamic_entity } @@ -69,6 +89,7 @@ impl Serialize for RootDataEntity { } } +// Custom serailiser for root entity. pub trait CustomSerialize: Serialize { fn dynamic_entity(&self) -> Option<&HashMap>; fn id(&self) -> &String; diff --git a/src/ro_crate/schema.rs b/src/ro_crate/schema.rs index a8fe76f..6bdac52 100644 --- a/src/ro_crate/schema.rs +++ b/src/ro_crate/schema.rs @@ -1,11 +1,18 @@ -use reqwest; -use crate::ro_crate::constraints::{Id,IdValue,License}; +//! Validation logic for matching keys to valid context + +use crate::ro_crate::constraints::{Id, IdValue, License}; use crate::ro_crate::rocrate::RoCrate; -use std::collections::{HashSet, HashMap}; -use serde::{Serialize, Deserialize}; +use reqwest; +use serde::{Deserialize, Serialize}; +use std::collections::{HashMap, HashSet}; const ROCRATE_SCHEMA_1_1: &str = "https://www.researchobject.org/ro-crate/1.1/context.jsonld"; - + +/// Represents the JSON-LD context of the RO-Crate schema. +/// +/// This struct models the context information required for interpreting the JSON-LD encoded +/// RO-Crate metadata. It includes identifiers, names, version information, URLs, schema versions, +/// basis of the schema, licensing information, and the specific context definitions as a map. #[derive(Serialize, Deserialize, Debug)] pub struct RoCrateJSONLDContext { #[serde(rename = "@id")] @@ -19,22 +26,25 @@ pub struct RoCrateJSONLDContext { pub is_based_on: Id, pub license: License, #[serde(rename = "@context")] - pub context: HashMap + pub context: HashMap, } -/// Downloads ro-crate schema +/// Downloads the RO-Crate schema from a predefined URL. +/// +/// This function fetches the JSON-LD context defining the RO-Crate schema, attempting to parse it +/// into an `RoCrateJSONLDContext` struct. It is a synchronous operation and may block the thread +/// during the request and subsequent parsing. pub fn download_rocrate_schema() -> Result> { let res = reqwest::blocking::get(ROCRATE_SCHEMA_1_1)?.text()?; let context = serde_json::from_str(&res)?; Ok(context) } - /// Checks the graph and each entity for valid base schema vocab - /// - /// Looks at both current RO-Crate vocab, as well as any embedded vocabs defined. Does not traverse URI's - /// to determine if the key's are correct, so there may be aptly described properties that are failing validation - pub fn validate_crate_keys(rocrate: &RoCrate) -> bool { - +/// Validates that the keys in a given RO-Crate match those defined in the base schema vocabulary. +/// +/// This function checks the crate's properties against the official RO-Crate context and any embedded vocabularies. +/// It does not validate properties by dereferencing URIs but rather checks if the properties' keys are recognized. +pub fn validate_crate_keys(rocrate: &RoCrate) -> bool { match download_rocrate_schema() { Ok(crate_metadata) => { let crate_context: Vec = crate_metadata.context.keys().cloned().collect(); @@ -45,7 +55,7 @@ pub fn download_rocrate_schema() -> Result = crate_context.into_iter().collect(); let set2: HashSet<_> = custom_context.into_iter().collect(); let mut invalid_key: Vec<&String> = Vec::new(); - + for item in &vals { if !set1.contains(item) && !set2.contains(item) { invalid_key.push(&item); @@ -55,16 +65,9 @@ pub fn download_rocrate_schema() -> Result { - println!("Error downloading schema: {}", e); - false } + Err(_e) => false, } - -} \ No newline at end of file +} diff --git a/src/ro_crate/write.rs b/src/ro_crate/write.rs index 8777072..30ca3eb 100644 --- a/src/ro_crate/write.rs +++ b/src/ro_crate/write.rs @@ -1,13 +1,34 @@ -use crate::ro_crate::rocrate::RoCrate; +//! Module for writing RoCrate structures to file. +//! +//! Allows basic ro-crate-metadata.json file creation, as well as archiving +//! via zip. -use std::fs::File; -use std::io::{copy, Read, Write}; +use crate::ro_crate::read::read_crate; +use crate::ro_crate::rocrate::RoCrate; +use std::env; +use std::fmt; +use std::fs::{self, File}; +use std::io::{self, Write}; use std::path::{Path, PathBuf}; +use url::Url; use walkdir::WalkDir; -use zip::write::FileOptions; +use zip::{write::FileOptions, ZipWriter}; -/// Writes crate to a .json file. -/// Subpar error handling with eprintln! - need to update. +/// Serializes and writes an RO-Crate object to a JSON file. +/// +/// This function serializes the given `RoCrate` object into a pretty-printed JSON format and writes it +/// to a file with the specified `name`. The function uses basic error handling, printing error messages +/// to standard error without returning or propagating them, which is noted as an area for future improvement. +/// +/// +/// # Arguments +/// * `rocrate` - A reference to the `RoCrate` object to serialize. +/// * `name` - The name of the file to which the serialized JSON should be written. +/// +/// # Notes +/// Current error handling within this function is minimal, relying on printing to stderr. It is recommended +/// to update this function to return a `Result` type in future revisions for better error handling and integration +/// with calling code. pub fn write_crate(rocrate: &RoCrate, name: String) { match serde_json::to_string_pretty(&rocrate) { Ok(json_ld) => match File::create(name) { @@ -21,64 +42,407 @@ pub fn write_crate(rocrate: &RoCrate, name: String) { Err(e) => eprintln!("Serialization failed: {}", e), } } -/// Writes the crate directory to zip + +/// Serializes an RO-Crate object and writes it directly to a zip file. /// -/// Needs the root directory of the ro-crate for it to work, changing is a todo -pub fn zip_crate(crate_dir: &Path) -> Result<(), Box> { +/// This method allows for a modified RO-Crate to be efficiently serialized and saved into a zip archive +/// without overwriting the original data. It preserves file paths that are +/// relative or absolute in the original crate, whilst mapping the new relatives of the zip file. +/// The function also supports the potential remapping of all data entity IDs within the crate. +/// +/// # Arguments +/// * `rocrate` - A reference to the `RoCrate` object to serialize and save. +/// * `name` - The name under which the serialized crate will be stored in the zip file. +/// * `zip` - A mutable reference to the `ZipWriter` used for writing to the zip file. +/// * `options` - ZipFile options to use when creating the new file in the zip archive. +/// +/// # Returns +/// A `Result<(), ZipError>` indicating the success or failure of the operation. +fn write_crate_to_zip( + rocrate: &RoCrate, + name: String, + zip: &mut ZipWriter, + options: FileOptions, +) -> Result<(), ZipError> { + // Attempt to serialize the RoCrate object to a pretty JSON string + let json_ld = serde_json::to_string_pretty(&rocrate) + .map_err(|e| ZipError::ZipOperationError(e.to_string()))?; + + // Start a new file in the zip archive with the given name and options + zip.start_file(name, options) + .map_err(|e| ZipError::ZipOperationError(e.to_string()))?; + + // Write the serialized JSON data to the file in the zip archive + zip.write_all(json_ld.as_bytes()) + .map_err(|e| ZipError::ZipOperationError(e.to_string()))?; + + // If everything succeeded, return Ok(()) + Ok(()) +} + +/// Writes the contents of an RO-Crate directory to a zip file. +/// +/// This function compresses an entire RO-Crate directory, including all files within the directory structure, +/// into a single zip archive. It's designed to include every file present, without checking their relevance +/// to the crate's metadata, based on the principle that all files in the directory are part of the research +/// data or experiment. If external is true, it will grab and copy external data files +/// to a new `external` folder within the zip. This can increase storage costs, but allows +/// exhaustive capture of data state. +/// +/// # Arguments +/// * `crate_path` - The path to the RO-Crate file within crate to zip. +/// * `external` - A boolean flag indicating whether to apply special handling for external resources. +/// +/// # Returns +/// A `Result<(), ZipError>` reflecting the success or failure of the operation. +/// +/// # Notes +/// The function currently zips everything in the given directory, without analyzing the crate's metadata +/// to selectively include files. This approach ensures no potentially relevant data is omitted but may include +/// unnecessary files. Future versions might consider more selective zipping based on the crate's actual contents. +/// +/// # Examples +/// ``` +/// let crate_path = Path::new("/path/to/ro-crate-directory/ro-crate-metadata.json"); +/// zip_crate(crate_path, false)?; +/// ``` +pub fn zip_crate(crate_path: &Path, external: bool) -> Result<(), ZipError> { // TODO: add multile options for walking/compression e.g follow symbolic links etc. + let crate_abs = get_absolute_path(crate_path).unwrap(); + let root = crate_abs.parent().unwrap(); + + let zip_file_base_name = root + .file_name() + .ok_or(ZipError::FileNameNotFound)? + .to_str() + .ok_or(ZipError::FileNameConversionFailed)?; + + let zip_file_name = root.join(format!("{}.zip", zip_file_base_name)); + + let file = File::create(&zip_file_name).map_err(ZipError::IoError)?; + let mut zip = ZipWriter::new(file); + + // Can change this to deflated for standard compression + let options = FileOptions::default().compression_method(zip::CompressionMethod::Deflated); + + // Opens target crate ready for update + let mut rocrate = read_crate(&crate_abs.to_path_buf(), false).unwrap(); - let mut dir_vec: Vec = Vec::new(); + for entry in WalkDir::new(root) + .min_depth(0) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| e.path().is_file()) + // Consider only files, not directories + { + let path = entry.path(); - for entry in WalkDir::new(crate_dir).min_depth(0) { - let entry = entry?; - let path = entry.path().to_path_buf(); - dir_vec.push(path); + if path == zip_file_name || path == crate_abs { + continue; + } + + let relative_path = path.strip_prefix(root).map_err(ZipError::from)?; + + let relative_path_str = relative_path + .to_str() + .ok_or(ZipError::FileNameConversionFailed)?; + + let mut file = fs::File::open(path).map_err(ZipError::IoError)?; + zip.start_file(relative_path_str, options) + .map_err(|e| ZipError::ZipOperationError(e.to_string()))?; + io::copy(&mut file, &mut zip).map_err(ZipError::IoError)?; + + // Once copy the absolute path and relative path needs to be checked + let abs_path = get_absolute_path(path).unwrap(); + + // I need to update the rocrate with the relative paths of all the + update_zip_ids(&mut rocrate, abs_path, relative_path_str); + // absolute paths, } - let (file, dir_vec) = get_zip_name(dir_vec)?; + // TODO: Known issue, this zip external logic needs to be executed before + // you walk the directory, since this looks at the rocrate and determines + if external { + zip = zip_crate_external(&mut rocrate, &crate_abs, zip, options)? + } + let _ = write_crate_to_zip( + &rocrate, + "ro-crate-metadata.json".to_string(), + &mut zip, + options, + ); + + zip.finish() + .map_err(|e| ZipError::ZipOperationError(e.to_string()))?; + + Ok(()) +} + +#[derive(Debug)] +pub enum ZipError { + EmptyDirectoryVector, + FileNameNotFound, + FileNameConversionFailed, + PathError(std::path::StripPrefixError), + ZipOperationError(String), + IoError(io::Error), +} - let mut zip = zip::ZipWriter::new(file); +impl fmt::Display for ZipError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ZipError::EmptyDirectoryVector => write!(f, "Directory vector is empty"), + ZipError::FileNameNotFound => write!(f, "File name not found"), + ZipError::FileNameConversionFailed => write!(f, "Failed to convert file name"), + ZipError::ZipOperationError(ref msg) => write!(f, "Zip operation Error: {}", msg), + ZipError::PathError(ref err) => write!(f, "Path error: {}", err), + ZipError::IoError(ref err) => write!(f, "IO error: {}", err), + } + } +} - let options = FileOptions::default(); +/// Implements the standard Error trait for ZipError. +/// +/// This allows `ZipError` to integrate with Rust's error handling ecosystem, enabling it to be +/// returned and handled in contexts where a standard error type is expected. +impl std::error::Error for ZipError {} - for file_path in dir_vec.iter() { - // Get relative path - let relative_path = file_path.strip_prefix(crate_dir)?; - let relative_path_str = relative_path.to_str().ok_or("Failed to convert path")?; +/// Converts an `io::Error` into a `ZipError`. +/// +/// This is particularly useful when dealing with file I/O operations that may fail, +/// allowing these errors to be seamlessly converted and handled as `ZipError`s. +impl From for ZipError { + fn from(err: io::Error) -> ZipError { + ZipError::IoError(err) + } +} - println!("File path: {}", file_path.display()); - let file = File::open(file_path)?; +/// Converts a `std::path::StripPrefixError` into a `ZipError`. +/// +/// This conversion is necessary when manipulating file paths, especially when needing +/// to work with relative paths and encountering errors stripping prefixes from them. +impl From for ZipError { + fn from(err: std::path::StripPrefixError) -> ZipError { + ZipError::PathError(err) + } +} - if file_path.is_file() { - zip.start_file(relative_path_str, options)?; - let mut buffer = Vec::new(); - copy(&mut file.take(u64::MAX), &mut buffer)?; - zip.write_all(&buffer)?; - } else { - zip.add_directory(relative_path_str, options)?; +/// Packages an RO-Crate and its external files into a zip archive, updating IDs as necessary. +/// +/// This function is designed for RO-Crates that reference external files. It packages the crate +/// and any external files into a single zip archive, ensuring that all data entities, whether +/// internal or external to the crate directory, are included. Additionally, it updates the IDs +/// of packaged entities to reflect their new paths within the archive. +/// +/// # Arguments +/// * `rocrate` - A mutable reference to the `RoCrate` object being packaged. +/// * `crate_path` - The filesystem path to the directory containing the RO-Crate's metadata and data entities. +/// * `zip` - A `ZipWriter` for writing to the zip archive. +/// * `options` - `FileOptions` determining how files are added to the archive (e.g., compression level). +/// +/// # Returns +/// Returns a `Result` containing the updated `ZipWriter` on success, or a `ZipError` on failure, +/// encapsulating any errors that occurred during the operation. +pub fn zip_crate_external( + rocrate: &mut RoCrate, + crate_path: &Path, + mut zip: ZipWriter, + options: FileOptions, +) -> Result, ZipError> { + // Get all IDs for the target crate + let mut ids = rocrate.get_all_ids(); + + // Pop all non-urls + ids.retain(|id| is_not_url(id)); + let nonrels = get_nonrelative_paths(&ids, crate_path); + + // if nonrels is not empty, means data entities are external + // therefore we need to package them + if !nonrels.is_empty() { + for external in nonrels { + // norels = path to file, then we use external path to get folder then add basename + let file_name = external + .file_name() + .ok_or(ZipError::FileNameNotFound)? + .to_str() + .ok_or(ZipError::FileNameConversionFailed)?; + let zip_entry_name = format!("external/{}", file_name); + + let mut file = fs::File::open(&external).map_err(ZipError::IoError)?; + + zip.start_file(&zip_entry_name, options) + .map_err(|e| ZipError::ZipOperationError(e.to_string()))?; + + let copy_result = io::copy(&mut file, &mut zip).map_err(ZipError::IoError); + match copy_result { + Ok(_) => { + update_zip_ids(rocrate, external, &zip_entry_name); + } + Err(e) => return Err(e), + } } } - let _ = zip.finish(); + Ok(zip) +} +/// Updates the identifiers of entities within an RO-Crate to match their new paths in a zip archive. +/// +/// This function is essential when packaging an RO-Crate and its associated files into a zip archive. +/// It ensures that all references to data entities within the crate reflect their new locations within +/// the archive. The function handles various path formats, including extended-length paths on Windows. +/// +/// # Arguments +/// * `rocrate` - A mutable reference to the RO-Crate being updated. +/// * `id` - The original filesystem path of the entity. +/// * `zip_id` - The new identifier for the entity within the zip archive. +/// +/// # Note +/// The function includes specific checks for path anomalies, such as Windows extended-length path prefixes. +/// It currently focuses only on Windows extended-length path prefixes and needs to be updated in +/// line with new ids. +fn update_zip_ids(rocrate: &mut RoCrate, id: PathBuf, zip_id: &str) { + let id_str = id.to_str().unwrap_or_default(); - Ok(()) + // NOTE: this only really checks for extended length path failures - others may be present so this can + // be refactored when needed + // base update on direct match + if rocrate.update_id_recursive(id_str, zip_id).is_none() { + // if fail - check if the ID string contains the '\\?\' prefix + if id_str.starts_with(r"\\?\") { + let stripped_id = &id_str[4..]; + if let Some(_) = rocrate.update_id_recursive(stripped_id, zip_id) { + } else { + // if win extend length not an issue, check \\ stripping + if id_str.contains("\\\\") { + if let Some(_) = rocrate.update_id_recursive(stripped_id, zip_id) {} + } else { + if let Some(_) = rocrate.update_id_recursive(stripped_id, zip_id) {} + } + } + } else { + } + } else { + } } -fn get_zip_name( - mut dir_vec: Vec, -) -> Result<(File, Vec), Box> { - if dir_vec.is_empty() { - return Err("Directory vector is empty".into()); // Handle empty vector case +/// Identifies file paths that are not relative to the given RO-Crate directory. +/// +/// When preparing an RO-Crate for zipping, it's important to include all related files, even those +/// not stored within the crate's directory. This function helps identify such external files. +/// +/// # Arguments +/// * `ids` - A vector of strings representing the IDs (paths) to check. +/// * `crate_dir` - The base directory of the RO-Crate. +/// +/// # Returns +/// A vector of `PathBuf` objects representing files that are outside the crate's base directory. +fn get_nonrelative_paths(ids: &Vec<&String>, crate_dir: &Path) -> Vec { + let mut nonrels: Vec = Vec::new(); + + let rocrate_path = get_absolute_path(crate_dir).unwrap(); + let root_dir = rocrate_path.parent(); + // Extract the directory part of the path + if let Some(directory_path) = root_dir { + // Try to change the current working directory + let _ = env::set_current_dir(directory_path); + } else { } - let zip_name = dir_vec[0].file_name().ok_or("File name not found")?; - let new_zip_name = zip_name.to_str().ok_or("Failed to convert file name")?; + // Iterate over all the ids, check the paths are relative to crate. + // If not relative to crate and a file, then grab, add to extern folder + // and zip + for id in ids.iter() { + if id.starts_with('#') { + continue; + } + if let Some(path) = get_absolute_path(Path::new(id)) { + if path.exists() { + let nonrel = is_outside_base_folder(root_dir.unwrap(), &path); + if nonrel { + if id.starts_with(".") { + nonrels.push(id.into()); + } else { + nonrels.push(path); + } + } + } else { + } + } else { + continue; + } + } + nonrels +} +/// Converts a relative path to an absolute one, if possible. +/// +/// This utility function is useful for obtaining the absolute path representation of a file or directory. +/// +/// # Arguments +/// * `relative_path` - The path to be converted to its absolute form. +/// +/// # Returns +/// An `Option` containing the absolute path, if the conversion was successful; otherwise, `None`. +fn get_absolute_path(relative_path: &Path) -> Option { + match fs::canonicalize(relative_path) { + Ok(path) => Some(path), + Err(_e) => None, + } +} +/// Determines whether a given string is not a URL. +/// +/// This function checks if the provided string represents a file path rather than a URL. It's particularly +/// useful when filtering a list of identifiers to distinguish between web resources and local files. +/// +/// # Arguments +/// * `path` - The string to check. +/// +/// # Returns +/// `true` if the string is likely a file path; otherwise, `false`. +/// +/// # Examples +/// ``` +/// assert!(is_not_url("/path/to/file")); +/// assert!(!is_not_url("http://example.com")); +/// ``` +fn is_not_url(path: &str) -> bool { + // Check if the path is likely a Windows extended-length path + let is_extended_windows_path = path.starts_with(r"\\?\"); + + // Check if the path is likely a normal file path + let is_normal_file_path = path.starts_with(r"\\") // UNC path + || path.chars().next().map(|c| c.is_alphabetic() && path.chars().nth(1) == Some(':')).unwrap_or(false) // Drive letter, e.g., C:\ + || path.starts_with('/') // Unix-style path + || path.starts_with('.'); // Relative path + + // If it looks like a file path, return true early + if is_extended_windows_path || is_normal_file_path { + return true; + } - let file_name = PathBuf::from(format!("{}.zip", new_zip_name)); - let file = File::create(PathBuf::from(file_name))?; + Url::parse(path).is_err() +} - dir_vec.remove(0); - Ok((file, dir_vec)) +/// Checks if a given file path lies outside of a specified base folder. +/// +/// This function is critical in identifying external resources that need special handling when +/// preparing an RO-Crate for packaging or distribution. +/// +/// # Arguments +/// * `base_folder` - The base directory against which to compare. +/// * `file_path` - The path of the file to check. +/// +/// # Returns +/// `true` if the file is outside the base folder; otherwise, `false`. +/// +/// # Examples +/// ``` +/// let base_folder = Path::new("/path/to/base"); +/// let file_path = Path::new("/path/to/base/subdir/file"); +/// assert!(!is_outside_base_folder(base_folder, file_path)); +/// ``` +fn is_outside_base_folder(base_folder: &Path, file_path: &Path) -> bool { + // Compare the given file path with the base folder path + !file_path.starts_with(base_folder) } #[cfg(test)] @@ -96,7 +460,7 @@ mod write_crate_tests { #[test] fn test_write_crate_success() { let path = fixture_path("_ro-crate-metadata-minimal.json"); - let rocrate = read_crate(path).unwrap(); + let rocrate = read_crate(&path, false).unwrap(); let file_name = "test_rocrate_output.json"; // Call the function to write the crate to a file