From 6d75248bde1b26570e8b24b607be8481c0cfad09ef794d0b770e470073304a21 Mon Sep 17 00:00:00 2001 From: Alex Wied <2+alex@noreply.code.graphofliberty.org> Date: Wed, 2 Sep 2026 23:05:29 -0400 Subject: [PATCH] . --- Cargo.lock | 271 +++++++-------- Cargo.toml | 2 +- graph/src/category.rs | 4 +- graph/src/class.rs | 161 ++++++--- graph/src/curie.rs | 31 +- graph/src/error.rs | 4 +- graph/src/helpers.rs | 6 +- graph/src/indexer.rs | 116 ++++--- graph/src/language.rs | 56 ++-- graph/src/lib.rs | 8 +- graph/src/ontology.rs | 89 ++--- graph/src/vocab.rs | 2 +- inference/build.rs | 2 +- inference/src/error.rs | 6 +- inference/src/lib.rs | 2 +- inference/src/logic.rs | 35 +- inference/src/main.rs | 12 +- inference/src/service.rs | 122 ++++--- publish/Cargo.toml | 1 - publish/src/app.rs | 572 +++++++++++++++++++++----------- publish/src/args.rs | 2 +- publish/src/error.rs | 12 +- publish/src/main.rs | 87 +++-- publish/src/navigator.rs | 2 +- publish/src/tasks.rs | 104 +++--- publish/src/widget/iri_input.rs | 8 +- search/src/index.rs | 2 +- search/src/lib.rs | 2 +- search/src/schema.rs | 2 +- search/src/subtitles.rs | 2 +- 30 files changed, 1075 insertions(+), 650 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 284e20d..cec6685 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -359,9 +359,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.18.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e" +checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e" dependencies = [ "aws-lc-sys", "zeroize", @@ -369,9 +369,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.44.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483" +checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27" dependencies = [ "cc", "cmake", @@ -511,11 +511,11 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.4" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ - "generic-array", + "hybrid-array", ] [[package]] @@ -677,12 +677,12 @@ checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] name = "chacha20" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" dependencies = [ "cfg-if", - "cpufeatures 0.3.0", + "cpufeatures", "rand_core 0.10.1", ] @@ -829,6 +829,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "core-foundation" version = "0.9.4" @@ -914,18 +920,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.17" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" dependencies = [ "libc", ] @@ -993,33 +990,11 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.7" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "csv" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" -dependencies = [ - "csv-core", - "itoa", - "ryu", - "serde_core", -] - -[[package]] -name = "csv-core" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" -dependencies = [ - "memchr", + "hybrid-array", ] [[package]] @@ -1102,11 +1077,12 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.7" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer", + "const-oid", "crypto-common", ] @@ -1557,16 +1533,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - [[package]] name = "gethostname" version = "1.1.0" @@ -1661,7 +1627,6 @@ version = "0.1.0" dependencies = [ "clap", "color-eyre", - "csv", "gl-graph", "gl-inference", "gl-search", @@ -1706,9 +1671,9 @@ dependencies = [ [[package]] name = "glam" -version = "0.33.5" +version = "0.33.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c426daf70099baff33fac0ba85151c42424861c56828cef9ba02dacb78eb6b26" +checksum = "21fef0953c54fd3de2f44b743fbf77e044c81a25faee03636dfccc0d35135e23" [[package]] name = "glob" @@ -1855,9 +1820,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" [[package]] name = "hex" @@ -1923,10 +1888,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] -name = "hyper" -version = "1.11.0" +name = "hybrid-array" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" dependencies = [ "atomic-waker", "bytes", @@ -1998,7 +1972,7 @@ dependencies = [ [[package]] name = "iced" version = "0.15.0-dev" -source = "git+https://github.com/iced-rs/iced.git?branch=master#3de451447bd28217bb535632867550908e29d5d0" +source = "git+https://github.com/iced-rs/iced.git?branch=master#f8127c84dddf0b9e32c5e7dbb822911652f72665" dependencies = [ "iced_core", "iced_debug", @@ -2013,7 +1987,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.15.0-dev" -source = "git+https://github.com/iced-rs/iced.git?branch=master#3de451447bd28217bb535632867550908e29d5d0" +source = "git+https://github.com/iced-rs/iced.git?branch=master#f8127c84dddf0b9e32c5e7dbb822911652f72665" dependencies = [ "bitflags 2.13.1", "bytes", @@ -2032,7 +2006,7 @@ dependencies = [ [[package]] name = "iced_debug" version = "0.15.0-dev" -source = "git+https://github.com/iced-rs/iced.git?branch=master#3de451447bd28217bb535632867550908e29d5d0" +source = "git+https://github.com/iced-rs/iced.git?branch=master#f8127c84dddf0b9e32c5e7dbb822911652f72665" dependencies = [ "iced_core", "iced_futures", @@ -2042,7 +2016,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.15.0-dev" -source = "git+https://github.com/iced-rs/iced.git?branch=master#3de451447bd28217bb535632867550908e29d5d0" +source = "git+https://github.com/iced-rs/iced.git?branch=master#f8127c84dddf0b9e32c5e7dbb822911652f72665" dependencies = [ "futures", "iced_core", @@ -2056,7 +2030,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.15.0-dev" -source = "git+https://github.com/iced-rs/iced.git?branch=master#3de451447bd28217bb535632867550908e29d5d0" +source = "git+https://github.com/iced-rs/iced.git?branch=master#f8127c84dddf0b9e32c5e7dbb822911652f72665" dependencies = [ "bitflags 2.13.1", "bytemuck", @@ -2073,7 +2047,7 @@ dependencies = [ [[package]] name = "iced_program" version = "0.15.0-dev" -source = "git+https://github.com/iced-rs/iced.git?branch=master#3de451447bd28217bb535632867550908e29d5d0" +source = "git+https://github.com/iced-rs/iced.git?branch=master#f8127c84dddf0b9e32c5e7dbb822911652f72665" dependencies = [ "iced_graphics", "iced_runtime", @@ -2082,7 +2056,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.15.0-dev" -source = "git+https://github.com/iced-rs/iced.git?branch=master#3de451447bd28217bb535632867550908e29d5d0" +source = "git+https://github.com/iced-rs/iced.git?branch=master#f8127c84dddf0b9e32c5e7dbb822911652f72665" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2094,18 +2068,19 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.15.0-dev" -source = "git+https://github.com/iced-rs/iced.git?branch=master#3de451447bd28217bb535632867550908e29d5d0" +source = "git+https://github.com/iced-rs/iced.git?branch=master#f8127c84dddf0b9e32c5e7dbb822911652f72665" dependencies = [ "bytes", "iced_core", "iced_futures", + "sipper", "thiserror 2.0.20", ] [[package]] name = "iced_tiny_skia" version = "0.15.0-dev" -source = "git+https://github.com/iced-rs/iced.git?branch=master#3de451447bd28217bb535632867550908e29d5d0" +source = "git+https://github.com/iced-rs/iced.git?branch=master#f8127c84dddf0b9e32c5e7dbb822911652f72665" dependencies = [ "bytemuck", "cosmic-text", @@ -2121,7 +2096,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.15.0-dev" -source = "git+https://github.com/iced-rs/iced.git?branch=master#3de451447bd28217bb535632867550908e29d5d0" +source = "git+https://github.com/iced-rs/iced.git?branch=master#f8127c84dddf0b9e32c5e7dbb822911652f72665" dependencies = [ "bitflags 2.13.1", "bytemuck", @@ -2138,7 +2113,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.15.0-dev" -source = "git+https://github.com/iced-rs/iced.git?branch=master#3de451447bd28217bb535632867550908e29d5d0" +source = "git+https://github.com/iced-rs/iced.git?branch=master#f8127c84dddf0b9e32c5e7dbb822911652f72665" dependencies = [ "iced_renderer", "iced_runtime", @@ -2152,7 +2127,7 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.15.0-dev" -source = "git+https://github.com/iced-rs/iced.git?branch=master#3de451447bd28217bb535632867550908e29d5d0" +source = "git+https://github.com/iced-rs/iced.git?branch=master#f8127c84dddf0b9e32c5e7dbb822911652f72665" dependencies = [ "arboard", "iced_debug", @@ -2287,9 +2262,9 @@ checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" -version = "2.14.0" +version = "2.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" dependencies = [ "equivalent", "hashbrown 0.17.1", @@ -2459,7 +2434,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "ldp" -version = "0.0.0" +version = "0.1.0" dependencies = [ "async-trait", "base64 0.23.1", @@ -2469,6 +2444,7 @@ dependencies = [ "oxigraph", "parse_link_header", "reqwest-middleware", + "sfv", "slotmap", "thiserror 2.0.20", "tracing", @@ -2504,9 +2480,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.20" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" +checksum = "8d8f1ea3f21fd3405dcaf6c9b5c1630af9afc422d9073ea39c5f6d6c772e08ed" dependencies = [ "bitflags 2.13.1", "libc", @@ -2606,9 +2582,9 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "md-5" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" dependencies = [ "cfg-if", "digest", @@ -2670,9 +2646,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" dependencies = [ "libc", "wasi", @@ -3293,15 +3269,15 @@ dependencies = [ [[package]] name = "owo-colors" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" +checksum = "13c45bb4a6ae1280ec0803b1ef9d3455eb50f01efbbe1447ab020f1d54fba9d8" [[package]] name = "oxigraph" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d945cbe78640ab719a73697b9bd250054ea82c60470754efea79c21d5fc1cd1" +checksum = "7f6632dd080bd07676f06e70254ff5862c9943e9483d700fa7258df73a3f9a6a" dependencies = [ "dashmap", "getrandom 0.3.4", @@ -3337,9 +3313,9 @@ checksum = "54b4ed3a7192fa19f5f48f99871f2755047fabefd7f222f12a1df1773796a102" [[package]] name = "oxjsonld" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e1380a504a8571763f13b8bcad629ff90d0300d47d8a519f3c6c599625840e" +checksum = "86a3e89e005662e60327027f45ec7cefd0472404e01831b5d83a3ac522cfabe0" dependencies = [ "json-event-parser", "oxiri", @@ -3350,9 +3326,9 @@ dependencies = [ [[package]] name = "oxrdf" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0afd5c28e4a399c57ee2bc3accd40c7b671fdc7b6537499f14e95b265af7d7e0" +checksum = "4be15186205eb60ddbdc2fb47cb25df472c7f399bbe119b68c0972b6b6dd46ea" dependencies = [ "hex", "oxilangtag", @@ -3365,9 +3341,9 @@ dependencies = [ [[package]] name = "oxrdfio" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "696223589ecbcab06b1a5df9b527dc25b0c656160cca38752fdb3878a5d3dd03" +checksum = "17b3fe3bb34179c376ab04f2a1b32616b0babab574b3b7eef64d587e60d5e1db" dependencies = [ "oxjsonld", "oxrdf", @@ -3378,9 +3354,9 @@ dependencies = [ [[package]] name = "oxrdfxml" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd5516ae083d09bc57ec65ed5ee97701481725de6ffaa83d968ab42a96157ba1" +checksum = "71853d12051879f3e424c6864e978f5f5cbecd8e86a9817adbccaefccac9d61d" dependencies = [ "oxilangtag", "oxiri", @@ -3391,9 +3367,9 @@ dependencies = [ [[package]] name = "oxrocksdb-sys" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6977c1d1827fcbf14480e768e4dede0b39c2961f6028497d012f9c04bcda91c6" +checksum = "01fcf5ae1e1295a612bef72fc64cd81e1ac3d1bd3243ef84d330f2962a8335e1" dependencies = [ "bindgen", "cc", @@ -3401,18 +3377,18 @@ dependencies = [ [[package]] name = "oxsdatatypes" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06fa874d87eae638daae9b4e3198864fe2cce68589f227c0b2cf5b62b1530516" +checksum = "c17c2ae62ac34fda465f6331105906c0e17b4e088dac3249e1f229b962062503" dependencies = [ "thiserror 2.0.20", ] [[package]] name = "oxttl" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f03fd471bd54c23d76631c0a2677aa4bb308d905f6e491ee35dcb0732b7c5c6c" +checksum = "b1d0fe19fa62a6a102f85b052bbd86e3cdc3360cd6a338d01bab1a6743bf1d8e" dependencies = [ "memchr", "oxilangtag", @@ -3992,6 +3968,26 @@ dependencies = [ "bitflags 2.13.1", ] +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + [[package]] name = "regex" version = "1.13.1" @@ -4270,12 +4266,6 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - [[package]] name = "ryu-js" version = "1.0.3" @@ -4426,24 +4416,35 @@ dependencies = [ ] [[package]] -name = "sha1" -version = "0.10.7" +name = "sfv" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +checksum = "890ef0e5532daf21fdaa3dbaf991417187f3a87ab69231fe69be942495746480" +dependencies = [ + "base64 0.22.1", + "indexmap", + "ref-cast", +] + +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", + "cpufeatures", "digest", ] [[package]] name = "sha2" -version = "0.10.9" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", + "cpufeatures", "digest", ] @@ -4500,6 +4501,16 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" +[[package]] +name = "sipper" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bccb4192828b3d9a08e0b5a73f17795080dfb278b50190216e3ae2132cf4f95" +dependencies = [ + "futures", + "pin-project-lite", +] + [[package]] name = "sketches-ddsketch" version = "0.4.1" @@ -4546,9 +4557,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.2" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" dependencies = [ "serde", ] @@ -4636,9 +4647,9 @@ dependencies = [ [[package]] name = "sparesults" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e13362decdcbeb3fefadff1631238c835c112027221e83d217cc5779f5a81c2" +checksum = "5dfa99dba6f5155ef4fdd75b78f794ea0c729e17b07d84eb88ae3c1278d1758d" dependencies = [ "json-event-parser", "memchr", @@ -4649,9 +4660,9 @@ dependencies = [ [[package]] name = "spareval" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8eab80b96dc836e4cf8fda72daa67ccc0244dc5303be5d4566e8fd04adbdde5" +checksum = "85b51144836ff9ca2f792b7a2422c4170ff04460d86cd8f2f764cf53e339cbec" dependencies = [ "hex", "json-event-parser", @@ -4672,9 +4683,9 @@ dependencies = [ [[package]] name = "spargebra" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46715eb957d1fe960cbbc0b713da8f78e2cb19df315b48fb09c9467a1c84f656" +checksum = "bef3c186ffb9bf2dde7c5edd5528583f309fcb819b5773cf0e313194a990304f" dependencies = [ "oxilangtag", "oxiri", @@ -4686,9 +4697,9 @@ dependencies = [ [[package]] name = "sparopt" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aed7a854b8ea67618f8ce69aabb0e904d7704226060e9d5a2930eb3136c3fa3b" +checksum = "973ee503ab3d6c3de66b4b459233cd5b22b44dad0fb7b8d0931daf73d2ede935" dependencies = [ "oxrdf", "rand 0.9.5", @@ -5602,9 +5613,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.25.0" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f053576934f05a761a402421fbbe3d425d9366f75f978806a037b3ca481abecc" +checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" dependencies = [ "getrandom 0.4.3", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index efc2995..8591b15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ clap = { version = "4.6", features = ["derive"] } color-eyre = "0.6" futures = "0.3" http = "1.5" -iced = { git = "https://github.com/iced-rs/iced.git", branch = "master", features = ["advanced", "tokio"] } +iced = { git = "https://github.com/iced-rs/iced.git", branch = "master", features = ["advanced", "sipper", "tokio"] } num-traits = "0.2" oxigraph = { version = "0.5", features = ["rdf-12"] } oxilangtag = "0.1" diff --git a/graph/src/category.rs b/graph/src/category.rs index a196812..1846abe 100644 --- a/graph/src/category.rs +++ b/graph/src/category.rs @@ -1,5 +1,5 @@ -use oxigraph::model::NamedNodeRef; use crate::vocab; +use oxigraph::model::NamedNodeRef; pub enum Category { AudioBook = 0, @@ -19,4 +19,4 @@ impl Category { _ => None, } } -} \ No newline at end of file +} diff --git a/graph/src/class.rs b/graph/src/class.rs index 24a537a..77655dd 100644 --- a/graph/src/class.rs +++ b/graph/src/class.rs @@ -1,9 +1,9 @@ -use std::fmt::Display; -use oxigraph::model::{Graph, Literal, LiteralRef, NamedNode, NamedNodeRef, NamedOrBlankNode, Term, TermRef, Triple}; -use oxilangtag::LanguageTag; -use url::Url; -use gl_search::Schema; use crate::vocab; +use gl_search::Schema; +use oxigraph::model::{Graph, LiteralRef, NamedNode, NamedNodeRef, Triple}; +use oxilangtag::LanguageTag; +use std::fmt::Display; +use url::Url; #[derive(Clone, Debug, Eq, PartialEq)] pub enum Class { @@ -83,33 +83,61 @@ impl Class { pub fn fields(&self, language: LanguageTag) -> Vec<(gl_search::Field, String)> { match self { Class::RdfProperty => vec![ - (Schema::field("label", Some(language.primary_language())), String::from("Label")), - (Schema::field("definition", Some(language.primary_language())), String::from("Definition")), + ( + Schema::field("label", Some(language.primary_language())), + String::from("Label"), + ), + ( + Schema::field("definition", Some(language.primary_language())), + String::from("Definition"), + ), ], Class::RdfsClass => vec![ - (Schema::field("label", Some(language.primary_language())), String::from("Label")), - (Schema::field("definition", Some(language.primary_language())), String::from("Definition")), + ( + Schema::field("label", Some(language.primary_language())), + String::from("Label"), + ), + ( + Schema::field("definition", Some(language.primary_language())), + String::from("Definition"), + ), ], Class::SkosConcept => vec![ - (Schema::field("label", Some(language.primary_language())), String::from("Label")), - (Schema::field("definition", Some(language.primary_language())), String::from("Definition")), - ], - Class::Work => vec![ - (Schema::field("title", Some(language.primary_language())), String::from("Title")), + ( + Schema::field("label", Some(language.primary_language())), + String::from("Label"), + ), + ( + Schema::field("definition", Some(language.primary_language())), + String::from("Definition"), + ), ], + Class::Work => vec![( + Schema::field("title", Some(language.primary_language())), + String::from("Title"), + )], Class::Person => vec![ - (Schema::field("given_name", Some(language.primary_language())), String::from("Given Name")), - (Schema::field("surname", Some(language.primary_language())), String::from("Surname")), - ], - Class::CorporateBody => vec![ - (Schema::field("corporate_body", Some(language.primary_language())), String::from("Corporate Body")), - ], - Class::Expression => vec![ - (Schema::field("title", Some(language.primary_language())), String::from("Title")), - ], - Class::Manifestation => vec![ - (Schema::field("title", Some(language.primary_language())), String::from("Title")), + ( + Schema::field("given_name", Some(language.primary_language())), + String::from("Given Name"), + ), + ( + Schema::field("surname", Some(language.primary_language())), + String::from("Surname"), + ), ], + Class::CorporateBody => vec![( + Schema::field("corporate_body", Some(language.primary_language())), + String::from("Corporate Body"), + )], + Class::Expression => vec![( + Schema::field("title", Some(language.primary_language())), + String::from("Title"), + )], + Class::Manifestation => vec![( + Schema::field("title", Some(language.primary_language())), + String::from("Title"), + )], } } @@ -117,16 +145,23 @@ impl Class { match self { Class::Work => Some(Url::parse("http://fedora.quill.lan/rest/#Work").unwrap()), Class::Person => Some(Url::parse("http://fedora.quill.lan/rest/#Agent").unwrap()), - Class::CorporateBody => Some(Url::parse("http://fedora.quill.lan/rest/#Agent").unwrap()), - Class::Expression => Some(Url::parse("http://fedora.quill.lan/rest/#Expression").unwrap()), - Class::Manifestation => Some(Url::parse("http://fedora.quill.lan/rest/#Manifestation").unwrap()), + Class::CorporateBody => { + Some(Url::parse("http://fedora.quill.lan/rest/#Agent").unwrap()) + } + Class::Expression => { + Some(Url::parse("http://fedora.quill.lan/rest/#Expression").unwrap()) + } + Class::Manifestation => { + Some(Url::parse("http://fedora.quill.lan/rest/#Manifestation").unwrap()) + } _ => None, } } pub fn template(&self, subject: NamedNode) -> Graph { let subject = subject.as_ref(); - let empty_english_string_literal = LiteralRef::new_language_tagged_literal_unchecked("", "en"); + let empty_english_string_literal = + LiteralRef::new_language_tagged_literal_unchecked("", "en"); let empty_string_literal = LiteralRef::new_simple_literal(""); let empty_node = NamedNodeRef::new_unchecked(""); let hash_node = NamedNodeRef::new_unchecked("#"); @@ -136,42 +171,86 @@ impl Class { Class::RdfsClass => Graph::new(), Class::SkosConcept => Graph::new(), Class::Work => Graph::from_iter([ - Triple::new(subject, vocab::rdawd::TITLE_OF_WORK, empty_english_string_literal), + Triple::new( + subject, + vocab::rdawd::TITLE_OF_WORK, + empty_english_string_literal, + ), Triple::new(subject, vocab::rdawo::IDENTIFIER_FOR_WORK, empty_node), Triple::new(subject, vocab::rdawo::EXPRESSION_OF_WORK, empty_node), ]), Class::Person => Graph::from_iter([ - Triple::new(subject, vocab::rdaad::GIVEN_NAME, empty_english_string_literal), + Triple::new( + subject, + vocab::rdaad::GIVEN_NAME, + empty_english_string_literal, + ), Triple::new(subject, vocab::rdaad::SURNAME, empty_english_string_literal), Triple::new(subject, vocab::rdaao::IDENTIFIER_FOR_PERSON, hash_node), Triple::new(hash_node, vocab::rdano::SCHEME_OF_NOMEN, empty_node), Triple::new(hash_node, vocab::rdand::NOMEN_STRING, empty_string_literal), ]), Class::CorporateBody => Graph::from_iter([ - Triple::new(subject, vocab::rdaao::IDENTIFIER_FOR_CORPORATE_BODY, hash_node), - Triple::new(subject, vocab::rdaad::NAME_OF_CORPORATE_BODY, empty_english_string_literal), + Triple::new( + subject, + vocab::rdaao::IDENTIFIER_FOR_CORPORATE_BODY, + hash_node, + ), + Triple::new( + subject, + vocab::rdaad::NAME_OF_CORPORATE_BODY, + empty_english_string_literal, + ), Triple::new(hash_node, vocab::rdano::SCHEME_OF_NOMEN, empty_node), Triple::new(hash_node, vocab::rdand::NOMEN_STRING, empty_string_literal), ]), Class::Expression => Graph::from_iter([ Triple::new(subject, vocab::rdaeo::WORK_EXPRESSED, empty_node), - Triple::new(subject, vocab::rdaeo::LANGUAGE_OF_EXPRESSION, vocab::lclang::ENGLISH), - Triple::new(subject, vocab::rdaeo::TITLE_OF_EXPRESSION, empty_english_string_literal), + Triple::new( + subject, + vocab::rdaeo::LANGUAGE_OF_EXPRESSION, + vocab::lclang::ENGLISH, + ), + Triple::new( + subject, + vocab::rdaeo::TITLE_OF_EXPRESSION, + empty_english_string_literal, + ), Triple::new(subject, vocab::rdaeo::CONTENT_TYPE, empty_node), - Triple::new(subject, vocab::rdaeo::MANIFESTATION_OF_EXPRESSION, empty_node), + Triple::new( + subject, + vocab::rdaeo::MANIFESTATION_OF_EXPRESSION, + empty_node, + ), ]), Class::Manifestation => Graph::from_iter([ - Triple::new(subject, vocab::rdamd::TITLE_OF_MANIFESTATION, empty_english_string_literal), - Triple::new(subject, vocab::rdamd::UNIFORM_RESOURCE_LOCATOR, empty_string_literal), - Triple::new(subject, vocab::rdamo::CARRIER_TYPE, vocab::rdact::ONLINE_RESOURCE), + Triple::new( + subject, + vocab::rdamd::TITLE_OF_MANIFESTATION, + empty_english_string_literal, + ), + Triple::new( + subject, + vocab::rdamd::UNIFORM_RESOURCE_LOCATOR, + empty_string_literal, + ), + Triple::new( + subject, + vocab::rdamo::CARRIER_TYPE, + vocab::rdact::ONLINE_RESOURCE, + ), Triple::new(subject, vocab::rdamo::MEDIA_TYPE, empty_node), Triple::new(subject, vocab::rdamo::FILE_TYPE, empty_node), Triple::new(subject, vocab::rdamo::CATEGORY_OF_MANIFESTATION, empty_node), Triple::new(subject, vocab::rdamo::EXPRESSION_MANIFESTED, empty_node), - Triple::new(subject, vocab::rdamo::IDENTIFIER_FOR_MANIFESTATION, hash_node), + Triple::new( + subject, + vocab::rdamo::IDENTIFIER_FOR_MANIFESTATION, + hash_node, + ), Triple::new(hash_node, vocab::rdano::SCHEME_OF_NOMEN, empty_node), Triple::new(hash_node, vocab::rdand::NOMEN_STRING, empty_string_literal), ]), } } -} \ No newline at end of file +} diff --git a/graph/src/curie.rs b/graph/src/curie.rs index 5962e34..b920ed3 100644 --- a/graph/src/curie.rs +++ b/graph/src/curie.rs @@ -81,19 +81,24 @@ impl CurieHelper { } pub fn expand(&self, base: Option<&Url>, abbreviated_iri: &str) -> Option { - let absolute_iri = base.and_then(|base| { - base.join(abbreviated_iri).ok().map(|absolute| absolute.as_str().to_string()) - }).unwrap_or_else(|| abbreviated_iri.to_string()); - - absolute_iri.split_once(':') - .and_then(|(prefix, name)| { - if prefix.is_empty() && let Some(base) = base { - Some(format!("{base}{name}")) - } else { - self.prefixes - .get(prefix) - .map(|base| format!("{base}{name}")) - } + let absolute_iri = base + .and_then(|base| { + base.join(abbreviated_iri) + .ok() + .map(|absolute| absolute.as_str().to_string()) }) + .unwrap_or_else(|| abbreviated_iri.to_string()); + + absolute_iri.split_once(':').and_then(|(prefix, name)| { + if prefix.is_empty() + && let Some(base) = base + { + Some(format!("{base}{name}")) + } else { + self.prefixes + .get(prefix) + .map(|base| format!("{base}{name}")) + } + }) } } diff --git a/graph/src/error.rs b/graph/src/error.rs index f3ac0a4..bcf53de 100644 --- a/graph/src/error.rs +++ b/graph/src/error.rs @@ -24,10 +24,10 @@ pub enum Error { #[error(transparent)] UpdateEvaluation(#[from] oxigraph::sparql::UpdateEvaluationError), - + #[error(transparent)] TonicTransport(#[from] gl_inference::tonic::transport::Error), - + #[error(transparent)] TonicStatus(#[from] gl_inference::tonic::Status), diff --git a/graph/src/helpers.rs b/graph/src/helpers.rs index faaf8cc..a7fab98 100644 --- a/graph/src/helpers.rs +++ b/graph/src/helpers.rs @@ -1,6 +1,6 @@ -use oxigraph::model::{NamedNode, Term, TermRef}; -use oxigraph::model::vocab::xsd; use crate::vocab::rdf; +use oxigraph::model::vocab::xsd; +use oxigraph::model::{NamedNode, Term, TermRef}; pub(crate) fn term_to_named_node(term: &Term) -> Option<&NamedNode> { if let Term::NamedNode(node) = term { @@ -34,4 +34,4 @@ pub(crate) fn term_ref_as_str(term: TermRef<'_>) -> Option<&str> { } else { None } -} \ No newline at end of file +} diff --git a/graph/src/indexer.rs b/graph/src/indexer.rs index c7c312b..c267be3 100644 --- a/graph/src/indexer.rs +++ b/graph/src/indexer.rs @@ -1,12 +1,12 @@ -use rayon::iter::ParallelIterator; -use std::collections::{HashMap, HashSet}; -use oxigraph::model::{Graph, NamedNode, TermRef, NamedOrBlankNodeRef, NamedNodeRef}; -use rayon::iter::IntoParallelRefIterator; -use gl_search::{Field, OwnedValue, Schema}; use crate::class::Class; -use crate::{helpers, vocab, CurieHelper}; use crate::language::LanguageCondition; use crate::ontology::ResourceDescription; +use crate::{CurieHelper, helpers, vocab}; +use gl_search::{Field, OwnedValue, Schema}; +use oxigraph::model::{Graph, NamedNode, NamedNodeRef, NamedOrBlankNodeRef, TermRef}; +use rayon::iter::IntoParallelRefIterator; +use rayon::iter::ParallelIterator; +use std::collections::{HashMap, HashSet}; pub struct Indexer<'a> { language: LanguageCondition, @@ -21,8 +21,14 @@ impl<'a> Indexer<'a> { } } - fn extract_string(&self, graph: &Graph, subject: NamedNodeRef, predicate: NamedNodeRef) -> OwnedValue { - graph.object_for_subject_predicate(subject, predicate) + fn extract_string( + &self, + graph: &Graph, + subject: NamedNodeRef, + predicate: NamedNodeRef, + ) -> OwnedValue { + graph + .object_for_subject_predicate(subject, predicate) .filter(|term| self.language.primary_matches_term(*term)) .and_then(helpers::term_ref_as_str) .map(String::from) @@ -44,52 +50,46 @@ impl<'a> Indexer<'a> { } fn corporate_body(&self, graph: &Graph, subject: NamedNodeRef) -> HashMap { - HashMap::from_iter([ - ( - Schema::field("corporate_name", self.language.primary_language()), - self.extract_string(graph, subject, vocab::rdaad::NAME_OF_CORPORATE_BODY), - ), - ]) + HashMap::from_iter([( + Schema::field("corporate_name", self.language.primary_language()), + self.extract_string(graph, subject, vocab::rdaad::NAME_OF_CORPORATE_BODY), + )]) } fn work(&self, graph: &Graph, subject: NamedNodeRef) -> HashMap { - HashMap::from_iter([ - ( - Schema::field("title", self.language.primary_language()), - self.extract_string(graph, subject, vocab::rdawd::TITLE_OF_WORK), - ), - ]) + HashMap::from_iter([( + Schema::field("title", self.language.primary_language()), + self.extract_string(graph, subject, vocab::rdawd::TITLE_OF_WORK), + )]) } fn expression(&self, graph: &Graph, subject: NamedNodeRef) -> HashMap { - HashMap::from_iter([ - ( - Schema::field("title", self.language.primary_language()), - self.extract_string(graph, subject, vocab::rdaed::TITLE_OF_EXPRESSION), - ), - ]) + HashMap::from_iter([( + Schema::field("title", self.language.primary_language()), + self.extract_string(graph, subject, vocab::rdaed::TITLE_OF_EXPRESSION), + )]) } fn manifestation(&self, graph: &Graph, subject: NamedNodeRef) -> HashMap { - HashMap::from_iter([ - ( - Schema::field("title", self.language.primary_language()), - self.extract_string(graph, subject, vocab::rdamd::TITLE_OF_MANIFESTATION), - ), - ]) + HashMap::from_iter([( + Schema::field("title", self.language.primary_language()), + self.extract_string(graph, subject, vocab::rdamd::TITLE_OF_MANIFESTATION), + )]) } pub fn graph(&self, graph: &Graph) -> Vec> { let mut entities_and_types = HashSet::new(); let triples = graph.triples_for_predicate(vocab::rdf::TYPE); for triple in triples { - if let NamedOrBlankNodeRef::NamedNode(subject) = triple.subject && - let TermRef::NamedNode(class) = triple.object { + if let NamedOrBlankNodeRef::NamedNode(subject) = triple.subject + && let TermRef::NamedNode(class) = triple.object + { entities_and_types.insert((subject, class)); } } - entities_and_types.par_iter() + entities_and_types + .par_iter() .filter_map(|(subject, class)| { Class::try_from_named_node(*class).and_then(|class| { match class { @@ -101,11 +101,18 @@ impl<'a> Indexer<'a> { Class::CorporateBody => Some(self.corporate_body(graph, *subject)), Class::Expression => Some(self.expression(graph, *subject)), Class::Manifestation => Some(self.manifestation(graph, *subject)), - }.map(|mut document| { - document.insert(Schema::discriminant_field(), OwnedValue::from(class as u64)); - document.insert(Schema::iri_field(), OwnedValue::Str(subject.as_str().to_string())); + } + .map(|mut document| { + document + .insert(Schema::discriminant_field(), OwnedValue::from(class as u64)); + document.insert( + Schema::iri_field(), + OwnedValue::Str(subject.as_str().to_string()), + ); - let curie = self.curie_helper.abbreviate(None, subject.as_str()) + let curie = self + .curie_helper + .abbreviate(None, subject.as_str()) .map(OwnedValue::Str) .unwrap_or(OwnedValue::Null); document.insert(Schema::curie_field(), curie); @@ -113,18 +120,24 @@ impl<'a> Indexer<'a> { document }) }) - }).collect() + }) + .collect() } - pub fn ontology(&self, entities: HashMap) -> Vec> { + pub fn ontology( + &self, + entities: HashMap, + ) -> Vec> { let label_field = Schema::field("label", self.language.primary_language()); let definition_field = Schema::field("definition", self.language.primary_language()); - entities.iter() + entities + .iter() .map(|(subject, description)| { let mut document = HashMap::with_capacity(4); - let discriminant = description.class + let discriminant = description + .class .as_ref() .and_then(|node| Class::try_from_named_node(node.as_ref())) .map(|doc_type| doc_type as u64) @@ -132,7 +145,9 @@ impl<'a> Indexer<'a> { .unwrap_or(OwnedValue::Null); document.insert(Schema::discriminant_field(), discriminant); - let curie = self.curie_helper.abbreviate(None, subject.as_str()) + let curie = self + .curie_helper + .abbreviate(None, subject.as_str()) .map(OwnedValue::Str) .unwrap_or(OwnedValue::Null); document.insert(Schema::curie_field(), curie); @@ -140,17 +155,22 @@ impl<'a> Indexer<'a> { let subject = OwnedValue::from(subject.as_str()); document.insert(Schema::iri_field(), subject); - let label = description.label.clone() + let label = description + .label + .clone() .map(OwnedValue::from) .unwrap_or(OwnedValue::Null); document.insert(label_field, label); - let definition = description.description.clone() + let definition = description + .description + .clone() .map(OwnedValue::from) .unwrap_or(OwnedValue::Null); document.insert(definition_field, definition); document - }).collect() + }) + .collect() } -} \ No newline at end of file +} diff --git a/graph/src/language.rs b/graph/src/language.rs index 1578233..e5e674f 100644 --- a/graph/src/language.rs +++ b/graph/src/language.rs @@ -1,16 +1,14 @@ use oxigraph::model::{Literal, TermRef}; use oxilangtag::LanguageTag; -use std::sync::LazyLock; use spargebra::algebra::{Expression, Function, GraphPattern}; use spargebra::term::Variable; +use std::sync::LazyLock; -pub static ENGLISH_TAG: LazyLock> = LazyLock::new(|| { - LanguageTag::parse("en".to_string()).unwrap() -}); +pub static ENGLISH_TAG: LazyLock> = + LazyLock::new(|| LanguageTag::parse("en".to_string()).unwrap()); -pub static ENGLISH_OR_UNTAGGED: LazyLock = LazyLock::new(|| { - LanguageCondition::ExactMatchOrUntagged(ENGLISH_TAG.clone()) -}); +pub static ENGLISH_OR_UNTAGGED: LazyLock = + LazyLock::new(|| LanguageCondition::ExactMatchOrUntagged(ENGLISH_TAG.clone())); #[derive(Clone, Debug)] pub enum LanguageCondition { @@ -54,20 +52,23 @@ impl LanguageCondition { pub fn filter(&self, variable: impl Into) -> String { let variable = variable.into(); - let exact = |language: &LanguageTag| Expression::FunctionCall( - Function::LangMatches, vec![ - Expression::FunctionCall(Function::Lang, vec![ - Expression::Variable(Variable::new_unchecked(&variable)) - ]), - Expression::Literal(Literal::new_simple_literal(language.to_string())) - ], - ); - - let untagged = Expression::Not( - Box::new(Expression::FunctionCall(Function::HasLang, vec![ - Expression::Variable(Variable::new_unchecked(&variable)) - ])) - ); + let exact = |language: &LanguageTag| { + Expression::FunctionCall( + Function::LangMatches, + vec![ + Expression::FunctionCall( + Function::Lang, + vec![Expression::Variable(Variable::new_unchecked(&variable))], + ), + Expression::Literal(Literal::new_simple_literal(language.to_string())), + ], + ) + }; + + let untagged = Expression::Not(Box::new(Expression::FunctionCall( + Function::HasLang, + vec![Expression::Variable(Variable::new_unchecked(&variable))], + ))); match self { Self::ExactMatchOnly(language) => Some(exact(language)), @@ -77,9 +78,14 @@ impl LanguageCondition { )), Self::UntaggedOnly => Some(untagged), Self::AnyOrNone => None, - }.map(|expr| GraphPattern::Filter { - expr, - inner: Box::new(GraphPattern::Bgp { patterns: vec![] }), - }.to_string()).unwrap_or_default() + } + .map(|expr| { + GraphPattern::Filter { + expr, + inner: Box::new(GraphPattern::Bgp { patterns: vec![] }), + } + .to_string() + }) + .unwrap_or_default() } } diff --git a/graph/src/lib.rs b/graph/src/lib.rs index 4a64274..f98013d 100644 --- a/graph/src/lib.rs +++ b/graph/src/lib.rs @@ -1,13 +1,13 @@ mod curie; mod error; -pub mod vocab; -pub mod class; pub mod category; -pub mod language; +pub mod class; mod helpers; pub mod indexer; +pub mod language; pub mod ontology; +pub mod vocab; pub use curie::{CurieHelper, PREFIXES}; -pub use error::{Error, Result}; \ No newline at end of file +pub use error::{Error, Result}; diff --git a/graph/src/ontology.rs b/graph/src/ontology.rs index 4eb7538..d981188 100644 --- a/graph/src/ontology.rs +++ b/graph/src/ontology.rs @@ -1,17 +1,19 @@ -use std::collections::{HashMap, HashSet}; -use std::fmt::Debug; -use std::str::FromStr; -use oxigraph::io::{RdfFormat, RdfParser, RdfSerializer}; -use oxigraph::model::{Graph, NamedNode, Triple}; -use oxigraph::sparql::results::{QueryResultsFormat, QueryResultsParser, SliceQueryResultsParserOutput}; -use spargebra::algebra::GraphPattern; -use spargebra::term::{GroundTerm, NamedNodePattern, TermPattern, TriplePattern, Variable}; -use gl_inference::proto::ontology_client::OntologyClient; -use gl_inference::proto::OntologyQueryRequest; -use gl_inference::tonic::transport::{Channel, Endpoint}; use crate::helpers::{term_as_str, term_to_named_node}; use crate::language::LanguageCondition; use crate::vocab; +use gl_inference::proto::OntologyQueryRequest; +use gl_inference::proto::ontology_client::OntologyClient; +use gl_inference::tonic::transport::{Channel, Endpoint}; +use oxigraph::io::{RdfFormat, RdfParser, RdfSerializer}; +use oxigraph::model::{Graph, NamedNode, Triple}; +use oxigraph::sparql::results::{ + QueryResultsFormat, QueryResultsParser, SliceQueryResultsParserOutput, +}; +use spargebra::algebra::GraphPattern; +use spargebra::term::{GroundTerm, NamedNodePattern, TermPattern, TriplePattern, Variable}; +use std::collections::{HashMap, HashSet}; +use std::fmt::Debug; +use std::str::FromStr; #[derive(Clone, Debug, Default)] pub struct ResourceDescription { @@ -45,10 +47,7 @@ impl OntologyBuilder { .keep_alive_timeout(std::time::Duration::from_secs(20)) .keep_alive_while_idle(true); - Ok(Self { - endpoint, - language, - }) + Ok(Self { endpoint, language }) } pub async fn connect(self) -> crate::Result { @@ -78,8 +77,8 @@ impl Debug for ConnectedOntology { impl ConnectedOntology { pub async fn run_inference(&mut self, graph: &Graph) -> crate::Result { let mut output_buffer = Vec::new(); - let mut serializer = RdfSerializer::from_format(RdfFormat::Turtle) - .for_writer(output_buffer); + let mut serializer = + RdfSerializer::from_format(RdfFormat::Turtle).for_writer(output_buffer); for triple in graph { serializer.serialize_triple(triple)?; } @@ -110,7 +109,8 @@ SELECT ?subject ?class WHERE { VALUES ?class { rdf:Property rdfs:Class } ?subject a ?class ; gl:readOnly "true"^^xsd:boolean . -}"#.to_owned(); +}"# + .to_owned(); let request = OntologyQueryRequest { sparql_query: Some(sparql_query), @@ -129,13 +129,11 @@ SELECT ?subject ?class WHERE { .and_then(term_to_named_node) .cloned(); - let class = solution - .get("class") - .and_then(term_to_named_node) - .cloned(); + let class = solution.get("class").and_then(term_to_named_node).cloned(); - if let Some(subject) = subject && - let Some(class) = class { + if let Some(subject) = subject + && let Some(class) = class + { let entry = match class.as_ref() { vocab::rdf::PROPERTY => ReadOnlyEntity::Property(subject), vocab::rdfs::CLASS => ReadOnlyEntity::Class(subject), @@ -148,8 +146,12 @@ SELECT ?subject ?class WHERE { Ok(results) } - fn selector_to_sparql_pattern(selector: ResourceSelector, nodes: impl IntoIterator) -> String { - let bindings = nodes.into_iter() + fn selector_to_sparql_pattern( + selector: ResourceSelector, + nodes: impl IntoIterator, + ) -> String { + let bindings = nodes + .into_iter() .map(GroundTerm::NamedNode) .map(Some) .map(|item| vec![item]) @@ -181,16 +183,22 @@ SELECT ?subject ?class WHERE { GraphPattern::Join { left: Box::new(basic_pattern), right: Box::new(values_pattern), - }.to_string() + } + .to_string() } - pub async fn resource_descriptions(&mut self, selector: ResourceSelector, nodes: impl IntoIterator) -> crate::Result> { + pub async fn resource_descriptions( + &mut self, + selector: ResourceSelector, + nodes: impl IntoIterator, + ) -> crate::Result> { let pattern = Self::selector_to_sparql_pattern(selector, nodes); let label_filter = self.language.filter("label"); let comment_filter = self.language.filter("comment"); let definition_filter = self.language.filter("definition"); - let sparql_query = format!(r#"PREFIX rdf: + let sparql_query = format!( + r#"PREFIX rdf: PREFIX rdfs: PREFIX skos: @@ -207,7 +215,8 @@ SELECT ?subject ?label ?description ?class WHERE {{ {definition_filter} }} BIND(COALESCE(?definition, ?comment) AS ?description) -}}"#); +}}"# + ); let request = OntologyQueryRequest { sparql_query: Some(sparql_query), @@ -221,10 +230,7 @@ SELECT ?subject ?label ?description ?class WHERE {{ let mut results = HashMap::new(); if let SliceQueryResultsParserOutput::Solutions(solutions) = parser_output { for solution in solutions.filter_map(Result::ok) { - let class = solution - .get("class") - .and_then(term_to_named_node) - .cloned(); + let class = solution.get("class").and_then(term_to_named_node).cloned(); let subject = solution .get("subject") @@ -242,14 +248,17 @@ SELECT ?subject ?label ?description ?class WHERE {{ .and_then(term_as_str) .map(String::from); - results.insert(subject, ResourceDescription { - label, - description, - class, - }); + results.insert( + subject, + ResourceDescription { + label, + description, + class, + }, + ); } } } Ok(results) } -} \ No newline at end of file +} diff --git a/graph/src/vocab.rs b/graph/src/vocab.rs index 64d3a8e..3abb1e0 100644 --- a/graph/src/vocab.rs +++ b/graph/src/vocab.rs @@ -171,4 +171,4 @@ pub mod skos { pub const CONCEPT: NamedNodeRef = NamedNodeRef::new_unchecked("http://www.w3.org/2004/02/skos/core#Concept"); -} \ No newline at end of file +} diff --git a/inference/build.rs b/inference/build.rs index 30d049d..f758712 100644 --- a/inference/build.rs +++ b/inference/build.rs @@ -4,4 +4,4 @@ fn main() { println!("cargo::rerun-if-changed=build.rs"); println!("cargo::rerun-if-changed=proto/inference.proto"); -} \ No newline at end of file +} diff --git a/inference/src/error.rs b/inference/src/error.rs index 57aa81f..3bb86bf 100644 --- a/inference/src/error.rs +++ b/inference/src/error.rs @@ -9,13 +9,13 @@ pub enum Error { #[error(transparent)] SparqlSyntax(#[from] oxigraph::sparql::SparqlSyntaxError), - + #[error(transparent)] QueryEvaluation(#[from] oxigraph::sparql::QueryEvaluationError), - + #[error(transparent)] UpdateEvaluation(#[from] oxigraph::sparql::UpdateEvaluationError), #[error(transparent)] Storage(#[from] oxigraph::store::StorageError), -} \ No newline at end of file +} diff --git a/inference/src/lib.rs b/inference/src/lib.rs index 52a8864..8cb4d6e 100644 --- a/inference/src/lib.rs +++ b/inference/src/lib.rs @@ -2,4 +2,4 @@ pub mod proto { tonic::include_proto!("org.graphofliberty.inference"); } -pub use tonic; \ No newline at end of file +pub use tonic; diff --git a/inference/src/logic.rs b/inference/src/logic.rs index 8e29619..b762ff3 100644 --- a/inference/src/logic.rs +++ b/inference/src/logic.rs @@ -1,5 +1,5 @@ use oxigraph::model::GraphNameRef; -use oxigraph::sparql::{SparqlEvaluator}; +use oxigraph::sparql::SparqlEvaluator; use oxigraph::store::Store; use tracing::debug_span; @@ -30,24 +30,43 @@ const DOMAIN_UPDATE: &str = r#"INSERT { ?x ?p ?y . }"#; -fn run_update(query_name: &str, query: &str, store: &Store, graph_name: GraphNameRef<'_>) -> crate::error::Result<()> { +fn run_update( + query_name: &str, + query: &str, + store: &Store, + graph_name: GraphNameRef<'_>, +) -> crate::error::Result<()> { let _span = debug_span!("Update", name = query_name).entered(); SparqlEvaluator::new() .with_prefix("rdfs", RDF_SCHEMA_PREFIX)? .with_prefix("gl", GL_PREFIX)? .parse_update(&format!("WITH {graph_name} {query}"))? - .on_store(store).execute()?; + .on_store(store) + .execute()?; Ok(()) } -pub(crate) fn infer(inferences: usize, store: &Store, graph_name: GraphNameRef<'_>) -> crate::error::Result { - let old_count = store.quads_for_pattern(None, None, None, Some(graph_name)).count(); - run_update("rdfs:subPropertyOf", SUB_PROPERTY_OF_UPDATE, store, graph_name)?; +pub(crate) fn infer( + inferences: usize, + store: &Store, + graph_name: GraphNameRef<'_>, +) -> crate::error::Result { + let old_count = store + .quads_for_pattern(None, None, None, Some(graph_name)) + .count(); + run_update( + "rdfs:subPropertyOf", + SUB_PROPERTY_OF_UPDATE, + store, + graph_name, + )?; run_update("rdfs:subClassOf", SUB_CLASS_OF_UPDATE, store, graph_name)?; run_update("rdfs:domain", DOMAIN_UPDATE, store, graph_name)?; - let new_count = store.quads_for_pattern(None, None, None, Some(graph_name)).count(); + let new_count = store + .quads_for_pattern(None, None, None, Some(graph_name)) + .count(); let new_inferences_total = inferences + (new_count - old_count); if new_count > old_count { @@ -55,4 +74,4 @@ pub(crate) fn infer(inferences: usize, store: &Store, graph_name: GraphNameRef<' } else { Ok(new_inferences_total) } -} \ No newline at end of file +} diff --git a/inference/src/main.rs b/inference/src/main.rs index 2b2dd7d..ef9231e 100644 --- a/inference/src/main.rs +++ b/inference/src/main.rs @@ -1,14 +1,14 @@ +use crate::service::OntologyService; +use gl_inference::proto::ontology_server::OntologyServer; use tonic::transport::Server; -use tracing_subscriber::{fmt, EnvFilter}; use tracing_subscriber::fmt::format::FmtSpan; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; -use gl_inference::proto::ontology_server::OntologyServer; -use crate::service::OntologyService; +use tracing_subscriber::{EnvFilter, fmt}; -mod service; -mod logic; mod error; +mod logic; +mod service; #[tokio::main] async fn main() -> color_eyre::Result<()> { @@ -35,4 +35,4 @@ async fn main() -> color_eyre::Result<()> { .await?; Ok(()) -} \ No newline at end of file +} diff --git a/inference/src/service.rs b/inference/src/service.rs index 247e7de..097549f 100644 --- a/inference/src/service.rs +++ b/inference/src/service.rs @@ -1,13 +1,16 @@ +use gl_inference::proto::ontology_server::Ontology; +use gl_inference::proto::{ + OntologyClearResponse, OntologyLoadRequest, OntologyLoadResponse, OntologyQueryRequest, + OntologyQueryResponse, +}; use num_traits::ToPrimitive; use oxigraph::io::{RdfFormat, RdfParser, RdfSerializer}; use oxigraph::model::{BlankNode, Dataset, GraphName, GraphNameRef, NamedNode, NamedNodeRef}; -use oxigraph::sparql::{QueryResults, SparqlEvaluator}; use oxigraph::sparql::results::{QueryResultsFormat, QueryResultsSerializer}; +use oxigraph::sparql::{QueryResults, SparqlEvaluator}; use oxigraph::store::Store; use tonic::{Request, Response, Status}; use tracing::{debug, debug_span, field}; -use gl_inference::proto::ontology_server::Ontology; -use gl_inference::proto::{OntologyClearResponse, OntologyLoadRequest, OntologyLoadResponse, OntologyQueryRequest, OntologyQueryResponse}; const ONTOLOGY_GRAPH: GraphNameRef = GraphNameRef::NamedNode(NamedNodeRef::new_unchecked( "https://graphofliberty.org/ontology", @@ -27,29 +30,40 @@ impl OntologyService { #[tonic::async_trait] impl Ontology for OntologyService { - async fn load(&self, request: Request) -> Result, Status> { - let span = debug_span!("Load Ontology", old_size = field::Empty, input_size = field::Empty, inferences = field::Empty, new_size = field::Empty).entered(); + async fn load( + &self, + request: Request, + ) -> Result, Status> { + let span = debug_span!( + "Load Ontology", + old_size = field::Empty, + input_size = field::Empty, + inferences = field::Empty, + new_size = field::Empty + ) + .entered(); let request = request.get_ref(); - let old_size = self.ontology.len() + let old_size = self + .ontology + .len() .map_err(|err| Status::internal(err.to_string()))? .to_u64() .unwrap_or(u64::MAX); let path = std::path::Path::new(&request.path); let store = Store::open_read_only(path).unwrap(); - let input_size = store.len() + let input_size = store + .len() .map_err(|err| Status::internal(err.to_string()))? .to_u64() .unwrap_or(u64::MAX); - let quads = store.iter() - .filter_map(Result::ok) - .map(|mut quad| { - quad.graph_name = ONTOLOGY_GRAPH.into_owned(); - quad - }); + let quads = store.iter().filter_map(Result::ok).map(|mut quad| { + quad.graph_name = ONTOLOGY_GRAPH.into_owned(); + quad + }); self.ontology.extend(quads).unwrap(); let inferences = if request.infer { @@ -57,9 +71,13 @@ impl Ontology for OntologyService { .map_err(|err| Status::internal(err.to_string()))? .to_u64() .unwrap_or(u64::MAX) - } else { 0 }; + } else { + 0 + }; - let new_size = self.ontology.len() + let new_size = self + .ontology + .len() .map_err(|err| Status::internal(err.to_string()))? .to_u64() .unwrap_or(u64::MAX); @@ -79,27 +97,34 @@ impl Ontology for OntologyService { Ok(Response::new(response)) } - async fn clear(&self, _request: Request<()>) -> Result, Status> { + async fn clear( + &self, + _request: Request<()>, + ) -> Result, Status> { let span = debug_span!("Clear Ontology", size = field::Empty).entered(); - let size = self.ontology.len() + let size = self + .ontology + .len() .map_err(|err| Status::internal(err.to_string()))? .to_u64() .unwrap_or(u64::MAX); - self.ontology.clear() + self.ontology + .clear() .map_err(|err| Status::internal(err.to_string()))?; - let response = OntologyClearResponse { - size, - }; + let response = OntologyClearResponse { size }; span.record("size", response.size); Ok(Response::new(response)) } - async fn query(&self, request: Request) -> Result, Status> { + async fn query( + &self, + request: Request, + ) -> Result, Status> { let span = debug_span!("Query", inferences = field::Empty).entered(); let request = request.get_ref(); @@ -108,16 +133,21 @@ impl Ontology for OntologyService { let provided_dataset; let graph_name = if let Some(turtle) = &request.turtle { let random_graph_identifier = BlankNode::default(); - let graph_name = GraphName::NamedNode(NamedNode::new_unchecked(format!("https://graphofliberty.org/inference/{}", random_graph_identifier.as_str()))); + let graph_name = GraphName::NamedNode(NamedNode::new_unchecked(format!( + "https://graphofliberty.org/inference/{}", + random_graph_identifier.as_str() + ))); provided_dataset = RdfParser::from_format(RdfFormat::Turtle) .for_slice(&turtle) .filter_map(Result::ok) .map(|mut quad| { quad.graph_name = graph_name.clone(); quad - }).collect::(); + }) + .collect::(); - self.ontology.extend(&provided_dataset) + self.ontology + .extend(&provided_dataset) .map_err(|err| Status::internal(err.to_string()))?; let inferences = crate::logic::infer(0, &self.ontology, graph_name.as_ref()) @@ -135,34 +165,42 @@ impl Ontology for OntologyService { let mut evaluator = SparqlEvaluator::new(); for (name, iri) in &request.prefixes { - evaluator = evaluator.with_prefix(name, iri) + evaluator = evaluator + .with_prefix(name, iri) .map_err(|err| Status::internal(err.to_string()))?; } if let Some(base) = &request.base { - evaluator = evaluator.with_base_iri(base) + evaluator = evaluator + .with_base_iri(base) .map_err(|err| Status::internal(err.to_string()))?; } - let mut query = evaluator.parse_query(sparql_query) + let mut query = evaluator + .parse_query(sparql_query) .map_err(|err| Status::internal(err.to_string()))?; - query.dataset_mut().set_default_graph(vec![graph_name.clone()]); + query + .dataset_mut() + .set_default_graph(vec![graph_name.clone()]); - let results = query.on_store(&self.ontology) + let results = query + .on_store(&self.ontology) .execute() .map_err(|err| Status::internal(err.to_string()))?; match results { QueryResults::Boolean(result) => { let serializer = QueryResultsSerializer::from_format(QueryResultsFormat::Json); - output_buffer = serializer.serialize_boolean_to_writer(output_buffer, result) + output_buffer = serializer + .serialize_boolean_to_writer(output_buffer, result) .map_err(|err| Status::internal(err.to_string()))?; } QueryResults::Solutions(solutions) => { let serializer = QueryResultsSerializer::from_format(QueryResultsFormat::Json); let variables = Vec::from_iter(solutions.variables().iter().cloned()); - let mut writer = serializer.serialize_solutions_to_writer(output_buffer, variables)?; + let mut writer = + serializer.serialize_solutions_to_writer(output_buffer, variables)?; for solution in solutions.filter_map(Result::ok) { writer.serialize(&solution)?; } @@ -171,11 +209,13 @@ impl Ontology for OntologyService { QueryResults::Graph(graph) => { let mut serializer = RdfSerializer::from_format(RdfFormat::Turtle); for (name, iri) in &request.prefixes { - serializer = serializer.with_prefix(name, iri) + serializer = serializer + .with_prefix(name, iri) .map_err(|err| Status::internal(err.to_string()))?; } if let Some(base) = &request.base { - serializer = serializer.with_base_iri(base) + serializer = serializer + .with_base_iri(base) .map_err(|err| Status::internal(err.to_string()))?; } @@ -189,16 +229,19 @@ impl Ontology for OntologyService { } else { let mut serializer = RdfSerializer::from_format(RdfFormat::Turtle); for (name, iri) in &request.prefixes { - serializer = serializer.with_prefix(name, iri) + serializer = serializer + .with_prefix(name, iri) .map_err(|err| Status::internal(err.to_string()))?; } if let Some(base) = &request.base { - serializer = serializer.with_base_iri(base) + serializer = serializer + .with_base_iri(base) .map_err(|err| Status::internal(err.to_string()))?; } let mut serializer = serializer.for_writer(output_buffer); - let resulting_dataset = self.ontology + let resulting_dataset = self + .ontology .quads_for_pattern(None, None, None, Some(graph_name.as_ref())) .filter_map(Result::ok); for quad in resulting_dataset { @@ -212,10 +255,11 @@ impl Ontology for OntologyService { response.results = String::from_utf8_lossy(&output_buffer).to_string(); if request.turtle.is_some() { - self.ontology.clear_graph(graph_name.as_ref()) + self.ontology + .clear_graph(graph_name.as_ref()) .map_err(|err| Status::internal(err.to_string()))?; } Ok(Response::new(response)) } -} \ No newline at end of file +} diff --git a/publish/Cargo.toml b/publish/Cargo.toml index 3f34199..c237183 100644 --- a/publish/Cargo.toml +++ b/publish/Cargo.toml @@ -10,7 +10,6 @@ gl-search.workspace = true clap.workspace = true color-eyre.workspace = true -csv = "1.4" http.workspace = true iced.workspace = true ldp.workspace = true diff --git a/publish/src/app.rs b/publish/src/app.rs index a9db664..eda07a9 100644 --- a/publish/src/app.rs +++ b/publish/src/app.rs @@ -1,33 +1,46 @@ -use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; -use std::ops::Sub; use crate::navigator::Navigator; +use crate::rdf::conversion::term_to_named_node; use crate::rdf::term_helper::{TermHelper, TermHelperMut}; +use crate::tasks; use crate::widget::iri_input::iri_input; use crate::widget::navigation_area::navigation_area; -use gl_graph::{language, CurieHelper, vocab}; +use gl_graph::class::Class; +use gl_graph::ontology::{ConnectedOntology, OntologyBuilder, ReadOnlyEntity, ResourceDescription}; +use gl_graph::{CurieHelper, language, vocab}; +use gl_search::tantivy::schema::Value; use gl_search::{Schema, SearchIndex}; use http::StatusCode; +use iced::advanced::text::Wrapping; use iced::alignment::Horizontal; use iced::keyboard::{Event, key}; +use iced::task::sipper; use iced::widget::button::Style; -use iced::widget::{button, center, column, combo_box, container, grid, mouse_area, opaque, operation, pick_list, row, scrollable, space, stack, table, text, text_input, toggler}; -use iced::window::Settings; -use iced::{color, keyboard, window, Background, Color, Element, Font, Length, Subscription, Task, font}; -use iced::advanced::text::Wrapping; use iced::widget::grid::Sizing; +use iced::widget::{ + button, center, column, combo_box, container, grid, mouse_area, opaque, operation, pick_list, + row, scrollable, space, stack, table, text, text_input, toggler, +}; +use iced::window::Settings; +use iced::{ + Background, Color, Element, Font, Length, Subscription, Task, color, font, keyboard, window, +}; use ldp::middleware::BasicAuthMiddleware; use ldp::model::{KeyedDataset, QuadKey}; use ldp::reqwest::{Client, Url}; use ldp::reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; use ldp::{RdfSource, RdfSourceUpdateResponse, ResourceRequestBuilder, SerializationOptions}; use oxigraph::io::RdfFormat; -use oxigraph::model::{BaseDirection, Graph, NamedNode, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Quad, Term, TermRef, Triple, TripleRef}; +use oxigraph::model::{ + BaseDirection, Graph, NamedNode, NamedNodeRef, NamedOrBlankNode, NamedOrBlankNodeRef, Quad, + Term, TermRef, Triple, TripleRef, +}; +use rfd::FileHandle; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::ops::Sub; +use std::path::PathBuf; +use tokio::io::AsyncWriteExt; +use tonic::codegen::tokio_stream::StreamExt; use tracing::{debug, error, trace}; -use gl_graph::class::Class; -use gl_graph::ontology::{ConnectedOntology, OntologyBuilder, ReadOnlyEntity, ResourceDescription}; -use gl_search::tantivy::schema::Value; -use crate::rdf::conversion::term_to_named_node; -use crate::tasks; #[derive(Clone, Debug)] pub(crate) enum Message { @@ -68,6 +81,9 @@ pub(crate) enum Message { ShowOverwriteConfirmationModal, HideOverwriteConfirmationModal, ConfirmOverwrite, + DownloadResource, + DownloadLocationSelected(Option), + DownloadProgress((usize, Option)), NewDocument(Class, Option), NavigateToPredicate(QuadKey), NavigateToObject(QuadKey), @@ -102,6 +118,11 @@ struct SearchState { results: Vec>, } +enum DownloadState { + Idle, + InProgress(FileHandle, usize), +} + pub(crate) struct Publisher { http_client: ClientWithMiddleware, curie_helper: CurieHelper, @@ -116,6 +137,7 @@ pub(crate) struct Publisher { url_input_valid: bool, navigator: Navigator, document: RdfSource>, + download_state: DownloadState, inferred_triples: Graph, hovered_row: Option, search_state: Option, @@ -129,11 +151,14 @@ pub(crate) struct Publisher { impl Publisher { fn is_read_only<'a>(&'a self, triple: impl Into>) -> bool { let triple = triple.into(); - if triple.predicate == vocab::rdf::TYPE && - let TermRef::NamedNode(node) = triple.object { - self.read_only_entities.contains(&ReadOnlyEntity::Class(node.into_owned())) + if triple.predicate == vocab::rdf::TYPE + && let TermRef::NamedNode(node) = triple.object + { + self.read_only_entities + .contains(&ReadOnlyEntity::Class(node.into_owned())) } else { - self.read_only_entities.contains(&ReadOnlyEntity::Property(triple.predicate.into_owned())) + self.read_only_entities + .contains(&ReadOnlyEntity::Property(triple.predicate.into_owned())) } } @@ -174,6 +199,7 @@ impl Publisher { url_input_valid: true, navigator, document, + download_state: DownloadState::Idle, inferred_triples: Graph::new(), hovered_row: None, search_state: None, @@ -193,8 +219,9 @@ impl Publisher { match message { Message::ConnectToOntologyService(endpoint) => { - let builder = OntologyBuilder::from_string(&endpoint, language::ENGLISH_OR_UNTAGGED.clone()) - .expect("Unable to parse endpoint of ontology service"); + let builder = + OntologyBuilder::from_string(&endpoint, language::ENGLISH_OR_UNTAGGED.clone()) + .expect("Unable to parse endpoint of ontology service"); task = tasks::connect_to_ontology_service(builder) .chain(Task::done(Message::PopulateCaches)); @@ -204,9 +231,9 @@ impl Publisher { } Message::PopulateCaches => { if let Some(ontology) = &mut self.ontology { - let classes = Class::ALL.iter().map(|class| { - class.to_named_node().into_owned() - }); + let classes = Class::ALL + .iter() + .map(|class| class.to_named_node().into_owned()); task = Task::batch([ tasks::list_datatypes(ontology.clone()), @@ -216,18 +243,22 @@ impl Publisher { } } Message::CacheDatatypes(datatypes) => { - self.abbreviated_datatypes = datatypes.keys() + self.abbreviated_datatypes = datatypes + .keys() .map(|node| { - self.curie_helper.abbreviate(None, node.as_str()) + self.curie_helper + .abbreviate(None, node.as_str()) .unwrap_or(node.as_str().to_string()) - }).collect(); + }) + .collect(); } Message::CacheReadOnlyEntities(entities) => { self.read_only_entities = entities; } Message::LookupResourceDescriptions(nodes) => { let provided_nodes: HashSet = HashSet::from_iter(nodes); - let nodes_with_known_descriptions = HashSet::from_iter(self.resource_descriptions.keys().cloned()); + let nodes_with_known_descriptions = + HashSet::from_iter(self.resource_descriptions.keys().cloned()); let nodes_to_look_up = provided_nodes.sub(&nodes_with_known_descriptions); if let Some(ontology) = &mut self.ontology { @@ -240,10 +271,18 @@ impl Publisher { Message::WindowClosed(id) => { if self.window_id == id { task = iced::exit(); - } else if let Some(search_state) = &self.search_state && search_state.window_id == id { + } else if let Some(search_state) = &self.search_state + && search_state.window_id == id + { self.search_state = None; - } else if let Some(infrence_window_id) = self.inference_window_id && infrence_window_id == id { + } else if let Some(infrence_window_id) = self.inference_window_id + && infrence_window_id == id + { self.inference_window_id = None; + } else if let Some(read_only_window_id) = self.read_only_window_id + && read_only_window_id == id + { + self.read_only_window_id = None; } } Message::URLInputChanged(value) => { @@ -259,47 +298,49 @@ impl Publisher { } } Message::NavigateTo(url) => { - task = Task::done(Message::ResetState) - .chain(Task::done(Message::FetchDocument(url))); + task = + Task::done(Message::ResetState).chain(Task::done(Message::FetchDocument(url))); } Message::FetchDocument(url) => { let client = self.http_client.clone(); - task = Task::future(async { - let request = ResourceRequestBuilder::with_client_and_url(client, url) - .accept_rdf_format(RdfFormat::Turtle) - .follow_described_by(true) - .build(); + task = Task::perform( + async { + let request = ResourceRequestBuilder::with_client_and_url(client, url) + .accept_rdf_format(RdfFormat::Turtle) + .follow_described_by(true) + .build(); - match request.send().await { - Ok(resource) => match resource.into_rdf_source().await { - Ok(rdf_source) => Message::LoadDocument(rdf_source), - Err(err) => { - Message::ShowError(format!("Unable to parse response: {err}")) - } - }, - Err(ldp::Error::Reqwest(err)) => { - if err.status() == Some(StatusCode::NOT_FOUND) { - Message::ResourceNotFound - } else { - Message::ShowError(err.to_string()) - } + let response = request.send().await?; + response.into_rdf_source().await + }, + |result| match result { + Ok(document) => Message::LoadDocument(document), + Err(ldp::Error::Reqwest(err)) + if err.status() == Some(StatusCode::NOT_FOUND) => + { + Message::ResourceNotFound } - Err(err) => Message::ShowError(format!("Request failed: {err}")), - } - }); + Err(err) => Message::ShowError(err.to_string()), + }, + ); } Message::ResourceNotFound => { - self.resource_not_found = true; task = Task::done(Message::ShowNewDocumentButtons); + self.resource_not_found = true; } Message::LoadDocument(document) => { - let nodes_to_look_up = document.dataset().quads.iter().flat_map(|(_, quad)| { - let mut nodes = vec![ quad.predicate.clone() ]; - if let Some(object) = term_to_named_node(&quad.object) { - nodes.push(object.clone()); - } - nodes - }).collect(); + let nodes_to_look_up = document + .dataset() + .quads + .iter() + .flat_map(|(_, quad)| { + let mut nodes = vec![quad.predicate.clone()]; + if let Some(object) = term_to_named_node(&quad.object) { + nodes.push(object.clone()); + } + nodes + }) + .collect(); let add_row_tasks = document.dataset().quads.iter().map(|(key, quad)| { let datatype_state = combo_box::State::new(self.abbreviated_datatypes.clone()); @@ -374,9 +415,10 @@ impl Publisher { task = window_task.then(|_| operation::focus("query")); } - if let Some(selected_entity_class) = selected_entity_class && - let Some(class) = Class::try_from_named_node(selected_entity_class) { - task = task.chain(Task::done(Message::UpdateQueryClass(class))) + if let Some(selected_entity_class) = selected_entity_class + && let Some(class) = Class::try_from_named_node(selected_entity_class) + { + task = task.chain(Task::done(Message::UpdateQueryClass(class))) } } Message::UpdateQueryClass(type_) => { @@ -387,7 +429,8 @@ impl Publisher { } Message::QueryUpdated(new_query) => { if let Some(search_state) = &mut self.search_state { - let category_id = search_state.entity_class_selection + let category_id = search_state + .entity_class_selection .as_ref() .map(|selection| selection.to_owned() as u64); @@ -407,7 +450,9 @@ impl Publisher { } Message::SearchResultClicked(node) => { if let Some(search_state) = &self.search_state { - task = task.chain(Task::done(Message::LookupResourceDescriptions(HashSet::from_iter([node.clone()])))); + task = task.chain(Task::done(Message::LookupResourceDescriptions( + HashSet::from_iter([node.clone()]), + ))); match search_state.action { SearchResultClickAction::URLInput => { @@ -497,15 +542,18 @@ impl Publisher { Message::SaveGraph(overwrite) => { let client = self.http_client.clone(); let read_only_entities = self.read_only_entities.clone(); - let options = SerializationOptions::from_format(RdfFormat::Turtle) - .with_filter(move |triple| { - if triple.predicate == vocab::rdf::TYPE && - let TermRef::NamedNode(node) = triple.object { + let options = SerializationOptions::from_format(RdfFormat::Turtle).with_filter( + move |triple| { + if triple.predicate == vocab::rdf::TYPE + && let TermRef::NamedNode(node) = triple.object + { !read_only_entities.contains(&ReadOnlyEntity::Class(node.into_owned())) } else { - !read_only_entities.contains(&ReadOnlyEntity::Property(triple.predicate.into_owned())) + !read_only_entities + .contains(&ReadOnlyEntity::Property(triple.predicate.into_owned())) } - }); + }, + ); let request = self .document .to_update(options) @@ -531,6 +579,58 @@ impl Publisher { task = Task::done(Message::SaveGraph(true)) .chain(Task::done(Message::HideOverwriteConfirmationModal)); } + Message::DownloadResource => { + let suggested_file_name = self.document.origin_file_name().unwrap_or_default(); + + let destination_selector = + rfd::AsyncFileDialog::new().set_file_name(suggested_file_name); + + task = Task::perform(destination_selector.save_file(), |result| { + Message::DownloadLocationSelected(result.map(|fh| fh.path().to_owned())) + }); + } + Message::DownloadLocationSelected(None) => { + debug!("canceled"); + } + Message::DownloadLocationSelected(Some(path)) => { + if let Ok(url) = Url::parse(&self.url_input) { + let client = self.http_client.clone(); + task = Task::sip( + sipper(async move |mut sender| { + let request = ResourceRequestBuilder::with_client_and_url(client, url) + .follow_described_by(false) + .build(); + let resource = request.send().await?; + + let mut handle = tokio::fs::File::create(path).await?; + let mut progress = 0usize; + let total_size = resource.size(); + + let mut stream = resource.into_stream(); + while let Some(chunk_result) = stream.next().await { + let chunk = chunk_result?; + handle.write_all(&chunk).await?; + progress += chunk.len(); + sender.send((progress, total_size)).await; + } + + Ok(()) + }), + Message::DownloadProgress, + |result: crate::error::Result<()>| { + if let Err(err) = result { + Message::ShowError(err.to_string()) + } else { + Message::None + } + }, + ); + } + } + Message::DownloadProgress((bytes_written, Some(bytes_total))) => { + let percentage = bytes_written as f32 / bytes_total as f32; + debug!(bytes_written, bytes_total, percentage); + } Message::ShowNewDocumentButtons => { self.show_new_document_buttons = true; } @@ -549,8 +649,9 @@ impl Publisher { let quad = document.quad_from_triple(triple.into_owned()); document.dataset_mut().quads.insert(quad); - if triple.predicate == vocab::rdf::TYPE && - let TermRef::NamedNode(node) = triple.object { + if triple.predicate == vocab::rdf::TYPE + && let TermRef::NamedNode(node) = triple.object + { nodes_to_look_up.insert(node.into_owned()); } else { nodes_to_look_up.insert(triple.predicate.into_owned()); @@ -581,7 +682,9 @@ impl Publisher { Url::parse(node.as_str()).ok() } else if let Term::Literal(literal) = &quad.object { Url::parse(literal.value()).ok() - } else { None }; + } else { + None + }; if let Some(url) = url { task = Task::done(Message::URLInputChanged(url.to_string())) @@ -601,7 +704,8 @@ impl Publisher { } Message::RunInference => { if let Some(ontology) = &mut self.ontology { - let graph = self.document + let graph = self + .document .dataset() .quads .iter() @@ -613,14 +717,12 @@ impl Publisher { } Message::SetInferredTriples(graph) => { if let Some(ontology) = &mut self.ontology { - let predicates = graph.iter() - .map(|triple| triple.predicate.into_owned()); + let predicates = graph.iter().map(|triple| triple.predicate.into_owned()); - let objects = graph.iter() - .filter_map(|triple| match triple.object { - TermRef::NamedNode(node) => Some(node.into_owned()), - _ => None, - }); + let objects = graph.iter().filter_map(|triple| match triple.object { + TermRef::NamedNode(node) => Some(node.into_owned()), + _ => None, + }); let mut nodes_to_look_up: HashSet = HashSet::from_iter(predicates); nodes_to_look_up.extend(objects); @@ -664,11 +766,17 @@ impl Publisher { } pub(crate) fn title(&self, window: window::Id) -> String { - if let Some(inference_window_id) = self.inference_window_id && window == inference_window_id { + if let Some(inference_window_id) = self.inference_window_id + && window == inference_window_id + { "Graph of Liberty Publisher - Inferences".to_string() - } else if let Some(search_state) = &self.search_state && window == search_state.window_id { + } else if let Some(search_state) = &self.search_state + && window == search_state.window_id + { "Graph of Liberty Publisher - Search".to_string() - } else if let Some(read_only_window_id) = &self.read_only_window_id { + } else if let Some(read_only_window_id) = self.read_only_window_id + && window == read_only_window_id + { "Graph of Liberty Publisher - Read Only Triples".to_string() } else { "Graph of Liberty Publisher".to_string() @@ -682,43 +790,64 @@ impl Publisher { ]) } - fn view_inferred_class_table<'a>(&'a self, triples: impl IntoIterator>) -> Element<'a, Message> { - let bold = |t| text(t).font(Font { weight: font::Weight::Bold, ..Font::default() }); + fn view_inferred_class_table<'a>( + &'a self, + triples: impl IntoIterator>, + ) -> Element<'a, Message> { + let bold = |t| { + text(t).font(Font { + weight: font::Weight::Bold, + ..Font::default() + }) + }; let subject_column = table::column(bold("Subject"), |(subject, _): (NamedNodeRef, _)| { - text(self.curie_helper - .abbreviate(Some(self.document.origin()), subject.as_str()) - .unwrap_or_else(|| subject.as_str().to_string())) + text( + self.curie_helper + .abbreviate(Some(self.document.origin()), subject.as_str()) + .unwrap_or_else(|| subject.as_str().to_string()), + ) }); - let class_column = table::column(bold("Classes"), |(_, classes): (_, BTreeSet)| { - let textual_classes = classes.iter() - .map(|class| { - let curie = self.curie_helper - .abbreviate(Some(self.document.origin()), class.as_str()) - .unwrap_or_else(|| class.as_str().to_string()); + let class_column = table::column( + bold("Classes"), + |(_, classes): (_, BTreeSet)| { + let textual_classes = classes + .iter() + .map(|class| { + let curie = self + .curie_helper + .abbreviate(Some(self.document.origin()), class.as_str()) + .unwrap_or_else(|| class.as_str().to_string()); - let label = self.resource_descriptions - .get(&class.into_owned()) - .and_then(|description| description.label.clone()); + let label = self + .resource_descriptions + .get(&class.into_owned()) + .and_then(|description| description.label.clone()); - if let Some(label) = label { - format!("{curie} ({label})") - } else { - curie - } - }).collect::>() - .join("\n"); + if let Some(label) = label { + format!("{curie} ({label})") + } else { + curie + } + }) + .collect::>() + .join("\n"); - text(textual_classes) - }); + text(textual_classes) + }, + ); let mut inferred_classes: BTreeMap> = BTreeMap::new(); for triple in triples { - if let NamedOrBlankNodeRef::NamedNode(subject) = triple.subject && - let TermRef::NamedNode(class) = triple.object { - inferred_classes.entry(subject) - .and_modify(|classes| { classes.insert(class); }) + if let NamedOrBlankNodeRef::NamedNode(subject) = triple.subject + && let TermRef::NamedNode(class) = triple.object + { + inferred_classes + .entry(subject) + .and_modify(|classes| { + classes.insert(class); + }) .or_insert(BTreeSet::from_iter([class])); } } @@ -726,14 +855,24 @@ impl Publisher { table([subject_column, class_column], inferred_classes).into() } - fn view_triple_table<'a>(&'a self, triples: impl IntoIterator>) -> Element<'a, Message> { - let bold = |t| text(t).font(Font { weight: font::Weight::Bold, ..Font::default() }); + fn view_triple_table<'a>( + &'a self, + triples: impl IntoIterator>, + ) -> Element<'a, Message> { + let bold = |t| { + text(t).font(Font { + weight: font::Weight::Bold, + ..Font::default() + }) + }; let subject_column = table::column(bold("Subject"), |triple: TripleRef| { let curie = if let NamedOrBlankNodeRef::NamedNode(subject) = triple.subject { - Some(self.curie_helper - .abbreviate(Some(self.document.origin()), subject.as_str()) - .unwrap_or_else(|| subject.as_str().to_string())) + Some( + self.curie_helper + .abbreviate(Some(self.document.origin()), subject.as_str()) + .unwrap_or_else(|| subject.as_str().to_string()), + ) } else { None }; @@ -742,11 +881,13 @@ impl Publisher { }); let predicate_column = table::column(bold("Predicate"), |triple: TripleRef| { - let curie = self.curie_helper + let curie = self + .curie_helper .abbreviate(None, triple.predicate.as_str()) .unwrap_or_else(|| triple.predicate.as_str().to_string()); - let label = self.resource_descriptions + let label = self + .resource_descriptions .get(&triple.predicate.into_owned()) .and_then(|description| description.label.clone()); @@ -757,14 +898,16 @@ impl Publisher { } }); - let object_column = table::column(bold("Object"), |triple: TripleRef| { - match triple.object { + let object_column = + table::column(bold("Object"), |triple: TripleRef| match triple.object { TermRef::NamedNode(node) => { - let curie = self.curie_helper + let curie = self + .curie_helper .abbreviate(Some(self.document.origin()), node.as_str()) .unwrap_or_else(|| node.as_str().to_string()); - let label = self.resource_descriptions + let label = self + .resource_descriptions .get(&node.into_owned()) .and_then(|description| description.label.clone()); @@ -775,8 +918,7 @@ impl Publisher { } } _ => text(triple.object.to_string()), - } - }); + }); table([subject_column, predicate_column, object_column], triples).into() } @@ -790,45 +932,55 @@ impl Publisher { search_state.entity_class_selection.as_ref(), Class::ALL, ToString::to_string, - ).on_select(Message::UpdateQueryClass); + ) + .on_select(Message::UpdateQueryClass); - let mut columns = vec![table::column(text("CURIE"), |document: &HashMap| { - let iri = document.get(&Schema::iri_field()) - .and_then(|value| value.as_str()) - .unwrap_or_default(); + let mut columns = vec![table::column( + text("CURIE"), + |document: &HashMap| { + let iri = document + .get(&Schema::iri_field()) + .and_then(|value| value.as_str()) + .unwrap_or_default(); - let abbreviated_iri = document.get(&Schema::curie_field()) - .and_then(|value| value.as_str()) - .unwrap_or_default(); + let abbreviated_iri = document + .get(&Schema::curie_field()) + .and_then(|value| value.as_str()) + .unwrap_or_default(); - button(text(abbreviated_iri).wrapping(Wrapping::Word)) - .on_press(Message::SearchResultClicked(NamedNode::new_unchecked(iri))) - .style(button::text) - })]; + button(text(abbreviated_iri).wrapping(Wrapping::Word)) + .on_press(Message::SearchResultClicked(NamedNode::new_unchecked(iri))) + .style(button::text) + }, + )]; - let fields = search_state.entity_class_selection + let fields = search_state + .entity_class_selection .as_ref() - .map(|class| { - class.fields(language::ENGLISH_TAG.clone()) - }).unwrap_or_default(); + .map(|class| class.fields(language::ENGLISH_TAG.clone())) + .unwrap_or_default(); for (field, label) in fields { columns.push( - table::column(text(label), move |document: &HashMap| { - let iri = document.get(&Schema::iri_field()) - .and_then(|value| value.as_str()) - .unwrap_or_default(); + table::column( + text(label), + move |document: &HashMap| { + let iri = document + .get(&Schema::iri_field()) + .and_then(|value| value.as_str()) + .unwrap_or_default(); - let value = document - .get(&field) - .and_then(|value| value.as_str()) - .unwrap_or_default(); + let value = document + .get(&field) + .and_then(|value| value.as_str()) + .unwrap_or_default(); - button(text(value).wrapping(Wrapping::Word)) - .on_press(Message::SearchResultClicked(NamedNode::new_unchecked(iri))) - .style(button::text) - }) - .width(Length::Fixed(256.0)), + button(text(value).wrapping(Wrapping::Word)) + .on_press(Message::SearchResultClicked(NamedNode::new_unchecked(iri))) + .style(button::text) + }, + ) + .width(Length::Fixed(256.0)), ); } @@ -842,24 +994,26 @@ impl Publisher { } fn view_inference_window(&self) -> Element<'_, Message> { - let classes = self.inferred_triples.triples_for_predicate(vocab::rdf::TYPE); + let classes = self + .inferred_triples + .triples_for_predicate(vocab::rdf::TYPE); column![ self.view_inferred_class_table(classes), scrollable(self.view_triple_table(&self.inferred_triples)), - ].into() + ] + .into() } fn view_read_only_window(&self) -> Element<'_, Message> { - let read_only_triples = self.document + let read_only_triples = self + .document .dataset() .iter_both() .filter_map(|(_, quad, state)| if state.read_only { Some(quad) } else { None }) .map(|quad| TripleRef::from(quad.as_ref())); - column![ - scrollable(self.view_triple_table(read_only_triples)), - ].into() + column![scrollable(self.view_triple_table(read_only_triples)),].into() } fn view_row<'a>( @@ -910,14 +1064,16 @@ impl Publisher { .on_shift_click(Message::NavigateToPredicate(key)) }; - let predicate_label = self.resource_descriptions + let predicate_label = self + .resource_descriptions .get(&quad.predicate) .and_then(|description| description.label.clone()) .map(|label| container(text(label))); let term = TermHelper::new(&quad.object); - let value_label = term.value_as_named_node() + let value_label = term + .value_as_named_node() .and_then(|node| self.resource_descriptions.get(&node.into_owned())) .and_then(|description| description.label.clone()) .map(|label| container(text(label))); @@ -938,28 +1094,29 @@ impl Publisher { /* If the object is not a valid URL (either as a literal string or as a named node), then * use a normal text input instead of an iri_input. */ - let object_input: Element = if term.is_named_node() || Url::parse(term.value()).is_ok() { - let base = iri_input(&self.curie_helper, "Object", Some(base), value.as_str()) - .on_shift_click(Message::NavigateToObject(key)); - if state.read_only { - base.into() + let object_input: Element = + if term.is_named_node() || Url::parse(term.value()).is_ok() { + let base = iri_input(&self.curie_helper, "Object", Some(base), value.as_str()) + .on_shift_click(Message::NavigateToObject(key)); + if state.read_only { + base.into() + } else { + base.align_x(value_alignment) + .on_input(move |value| Message::ObjectUpdated(key, value)) + .on_control_click(Message::OpenQueryWindow( + SearchResultClickAction::Object(key), + )) + .into() + } } else { - base.align_x(value_alignment) - .on_input(move |value| Message::ObjectUpdated(key, value)) - .on_control_click(Message::OpenQueryWindow(SearchResultClickAction::Object( - key, - ))) - .into() - } - } else { - let base = text_input("Object", value.clone()); - if state.read_only { - base.into() - } else { - base.on_input(move |value| Message::ObjectUpdated(key, value)) - .into() - } - }; + let base = text_input("Object", value.clone()); + if state.read_only { + base.into() + } else { + base.on_input(move |value| Message::ObjectUpdated(key, value)) + .into() + } + }; let selected_datatype = term .datatype() @@ -1032,16 +1189,21 @@ impl Publisher { } pub(crate) fn view(&self, window: window::Id) -> Element<'_, Message> { - if let Some(search_state) = &self.search_state && - search_state.window_id == window { + if let Some(search_state) = &self.search_state + && search_state.window_id == window + { return self.view_search_window(search_state); } - if let Some(inference_window_id) = self.inference_window_id && window == inference_window_id { + if let Some(inference_window_id) = self.inference_window_id + && window == inference_window_id + { return self.view_inference_window(); } - if let Some(read_only_window_id) = self.read_only_window_id && window == read_only_window_id { + if let Some(read_only_window_id) = self.read_only_window_id + && window == read_only_window_id + { return self.view_read_only_window(); } @@ -1060,8 +1222,8 @@ impl Publisher { !self.url_input_valid, )); - let mut rows: Vec> = vec![]; - rows = self.document + let rows: Vec> = self + .document .dataset() .iter_both() .filter(|(_, _, state)| !state.read_only) @@ -1069,24 +1231,31 @@ impl Publisher { .collect(); let body: Element = if self.show_new_document_buttons { - let buttons = Class::CREATABLE.iter() - .map(|class| { - let url = if self.resource_not_found { - Url::parse(&self.url_input).ok() - } else { None }; + let buttons = Class::CREATABLE.iter().map(|class| { + let url = if self.resource_not_found { + Url::parse(&self.url_input).ok() + } else { + None + }; - button(text(class.to_string())) - .on_press(Message::NewDocument(class.clone(), url)) - .into() - }); + button(text(class.to_string())) + .on_press(Message::NewDocument(class.clone(), url)) + .into() + }); - column![ - grid(buttons).height(Sizing::EvenlyDistribute(Length::Shrink)) - ].into() + column![grid(buttons).height(Sizing::EvenlyDistribute(Length::Shrink))].into() } else { column(rows).into() }; + let download_button_base = button("\u{2b73}"); + let download_button = + if self.document.origin_type() == Some(&ldp::ResourceType::NonRdfSource) { + download_button_base.on_press(Message::DownloadResource) + } else { + download_button_base + }; + let save_button_base = button(text("\u{1f4be}")); let save_button = if self.modified { save_button_base.on_press(Message::SaveGraph(false)) @@ -1103,6 +1272,7 @@ impl Publisher { back_button, forward_button, address_input, + download_button, save_button, ], scrollable(body), @@ -1157,4 +1327,4 @@ where ) ] .into() -} \ No newline at end of file +} diff --git a/publish/src/args.rs b/publish/src/args.rs index 3935865..4efa858 100644 --- a/publish/src/args.rs +++ b/publish/src/args.rs @@ -22,7 +22,7 @@ pub(crate) struct SearchArgs { pub(crate) discriminant: Option, #[arg(short, long, value_name = "LIMIT")] - pub(crate) limit: Option, + pub(crate) limit: Option, #[arg(value_name = "QUERY")] pub(crate) query: String, diff --git a/publish/src/error.rs b/publish/src/error.rs index 86be1d8..a72ea0d 100644 --- a/publish/src/error.rs +++ b/publish/src/error.rs @@ -42,10 +42,16 @@ pub(crate) enum Error { #[error(transparent)] TonicTransport(#[from] tonic::transport::Error), - + #[error(transparent)] TonicStatus(#[from] tonic::Status), - + #[error(transparent)] Tantivy(#[from] gl_search::tantivy::TantivyError), -} \ No newline at end of file + + #[error(transparent)] + Reqwest(#[from] ldp::reqwest::Error), + + #[error(transparent)] + Ldp(#[from] ldp::Error), +} diff --git a/publish/src/main.rs b/publish/src/main.rs index 89241e2..30949a1 100644 --- a/publish/src/main.rs +++ b/publish/src/main.rs @@ -3,32 +3,32 @@ mod args; mod error; mod navigator; mod rdf; +mod tasks; mod theme; mod widget; -mod tasks; -use std::collections::HashMap; use crate::app::Publisher; use crate::args::{AppArgs, Command}; use clap::Parser; +use gl_graph::indexer::Indexer; +use gl_graph::ontology::{OntologyBuilder, ResourceSelector}; +use gl_graph::{CurieHelper, language, vocab}; +use gl_inference::proto::OntologyQueryRequest; +use gl_inference::proto::ontology_client::OntologyClient; use gl_search::{Schema, SearchIndex}; use iced::futures::StreamExt; +use ldp::Traverse; use ldp::middleware::BasicAuthMiddleware; use ldp::reqwest::Client; use ldp::reqwest_middleware::ClientBuilder; -use ldp::traverse::Traverse; use oxigraph::model::{Graph, TripleRef}; -use tracing::{debug_span, error, field, Instrument}; +use std::collections::HashMap; +use tracing::{Instrument, debug_span, error, field}; use tracing_subscriber::fmt::format::FmtSpan; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::{EnvFilter, fmt}; use url::Url; -use gl_graph::indexer::Indexer; -use gl_graph::{language, CurieHelper, vocab}; -use gl_graph::ontology::{OntologyBuilder, ResourceSelector}; -use gl_inference::proto::ontology_client::OntologyClient; -use gl_inference::proto::OntologyQueryRequest; fn main() -> color_eyre::Result<()> { let appender = tracing_appender::rolling::never("/tmp", "publisher-log"); @@ -47,16 +47,24 @@ fn main() -> color_eyre::Result<()> { Some(Command::Query(args)) => { let raw_query = if let Some(query) = &args.query_path { Some(String::from_utf8(std::fs::read(query)?)?) - } else { None }; + } else { + None + }; let graph = if let Some(dataset_path) = &args.dataset_path { Some(String::from_utf8(std::fs::read(dataset_path)?)?) - } else { None }; + } else { + None + }; let request = OntologyQueryRequest { sparql_query: raw_query, turtle: graph, - prefixes: HashMap::from_iter(gl_graph::PREFIXES.iter().map( | (name, iri) | (name.clone(), iri.clone()))), + prefixes: HashMap::from_iter( + gl_graph::PREFIXES + .iter() + .map(|(name, iri)| (name.clone(), iri.clone())), + ), base: args.base.clone(), inferences_only: args.inferences_only, }; @@ -83,7 +91,9 @@ fn main() -> color_eyre::Result<()> { .expect("Failed to build search index"); let limit = args.limit.unwrap_or(5); - for document in index.query(args.discriminant, &args.query, Schema::all_fields(), limit)? { + for document in + index.query(args.discriminant, &args.query, Schema::all_fields(), limit)? + { println!("{}", gl_search::to_json(document)); } } @@ -108,14 +118,22 @@ fn main() -> color_eyre::Result<()> { let curie_helper = CurieHelper::new(gl_graph::PREFIXES.clone()); let indexer = Indexer::new(language::ENGLISH_OR_UNTAGGED.clone(), &curie_helper); - let mut client = OntologyBuilder::from_string("http://[::1]:3000", language::ENGLISH_OR_UNTAGGED.clone())? - .connect() + let mut client = OntologyBuilder::from_string( + "http://[::1]:3000", + language::ENGLISH_OR_UNTAGGED.clone(), + )? + .connect() + .await?; + let resource_descriptions = client + .resource_descriptions( + ResourceSelector::Classes, + [ + vocab::rdf::PROPERTY.into_owned(), + vocab::rdfs::CLASS.into_owned(), + vocab::skos::CONCEPT.into_owned(), + ], + ) .await?; - let resource_descriptions = client.resource_descriptions(ResourceSelector::Classes, [ - vocab::rdf::PROPERTY.into_owned(), - vocab::rdfs::CLASS.into_owned(), - vocab::skos::CONCEPT.into_owned(), - ]).await?; let documents = indexer.ontology(resource_descriptions); debug_span!("Index Ontology", documents = field::Empty).in_scope(|| { for document in documents { @@ -140,24 +158,31 @@ fn main() -> color_eyre::Result<()> { while let Some(result) = traversal.next().await { match result { Ok(rdf_source) => { - let triples = rdf_source.dataset() - .iter() - .map(TripleRef::from); + let triples = rdf_source.dataset().iter().map(TripleRef::from); graph.extend(triples); rdf_source_count += 1; - }, + } Err(err) => error!(?err), } } - let span = debug_span!("Index Repository", rdf_sources = rdf_source_count, triples = field::Empty, documents = field::Empty); + let span = debug_span!( + "Index Repository", + rdf_sources = rdf_source_count, + triples = field::Empty, + documents = field::Empty + ); let triple_count = async { let curie_helper = CurieHelper::new(gl_graph::PREFIXES.clone()); - let indexer = Indexer::new(language::ENGLISH_OR_UNTAGGED.clone(), &curie_helper); + let indexer = + Indexer::new(language::ENGLISH_OR_UNTAGGED.clone(), &curie_helper); - let mut client = OntologyBuilder::from_string("http://[::1]:3000", language::ENGLISH_OR_UNTAGGED.clone())? - .connect() - .await?; + let mut client = OntologyBuilder::from_string( + "http://[::1]:3000", + language::ENGLISH_OR_UNTAGGED.clone(), + )? + .connect() + .await?; let graph_with_inferences = client.run_inference(&graph).await?; for document in indexer.graph(&graph_with_inferences) { @@ -165,7 +190,9 @@ fn main() -> color_eyre::Result<()> { } Ok::(graph_with_inferences.len()) - }.instrument(span.clone()).await?; + } + .instrument(span.clone()) + .await?; span.record("triples", triple_count); Ok::<_, crate::error::Error>(writer) diff --git a/publish/src/navigator.rs b/publish/src/navigator.rs index b6af0a7..bfc94cd 100644 --- a/publish/src/navigator.rs +++ b/publish/src/navigator.rs @@ -33,4 +33,4 @@ impl Navigator { } self.current() } -} \ No newline at end of file +} diff --git a/publish/src/tasks.rs b/publish/src/tasks.rs index 14340f9..9f572a4 100644 --- a/publish/src/tasks.rs +++ b/publish/src/tasks.rs @@ -1,57 +1,73 @@ -use iced::Task; -use ldp::reqwest_middleware::ClientWithMiddleware; -use ldp::ResourceRequestBuilder; -use oxigraph::io::RdfFormat; -use oxigraph::model::{Dataset, Graph, NamedNode, NamedNodeRef, NamedOrBlankNodeRef, TermRef}; -use url::Url; +use crate::app::Message; use gl_graph::class::Class; use gl_graph::ontology::{ConnectedOntology, OntologyBuilder, ResourceSelector}; use gl_graph::vocab; -use crate::app::Message; +use iced::Task; +use ldp::ResourceRequestBuilder; +use ldp::reqwest_middleware::ClientWithMiddleware; +use oxigraph::io::RdfFormat; +use oxigraph::model::{Dataset, Graph, NamedNode, NamedNodeRef, NamedOrBlankNodeRef, TermRef}; +use url::Url; pub(crate) fn connect_to_ontology_service(builder: OntologyBuilder) -> Task { - Task::perform(builder.connect(), |result| { - match result { - Ok(ontology) => Message::ConnectedToOntologyService(ontology), - Err(err) => Message::ShowError(err.to_string()) - } + Task::perform(builder.connect(), |result| match result { + Ok(ontology) => Message::ConnectedToOntologyService(ontology), + Err(err) => Message::ShowError(err.to_string()), }) } -pub(crate) fn lookup_resource_descriptions(mut ontology: ConnectedOntology, subjects: impl IntoIterator + Send + 'static) -> Task { - Task::perform(async move { ontology.resource_descriptions(ResourceSelector::Subjects, subjects).await }, |result| { - match result { +pub(crate) fn lookup_resource_descriptions( + mut ontology: ConnectedOntology, + subjects: impl IntoIterator + Send + 'static, +) -> Task { + Task::perform( + async move { + ontology + .resource_descriptions(ResourceSelector::Subjects, subjects) + .await + }, + |result| match result { Ok(descriptions) => Message::CacheResourceDescriptions(descriptions), Err(err) => Message::ShowError(err.to_string()), - } - }) + }, + ) } pub(crate) fn list_datatypes(mut ontology: ConnectedOntology) -> Task { - Task::perform(async move { ontology.resource_descriptions(ResourceSelector::Classes, vec![vocab::rdfs::DATATYPE.into_owned()]).await }, |result| { - match result { + Task::perform( + async move { + ontology + .resource_descriptions( + ResourceSelector::Classes, + vec![vocab::rdfs::DATATYPE.into_owned()], + ) + .await + }, + |result| match result { Ok(datatypes) => Message::CacheDatatypes(datatypes), Err(err) => Message::ShowError(err.to_string()), - } - }) + }, + ) } pub(crate) fn list_read_only_entities(mut ontology: ConnectedOntology) -> Task { - Task::perform(async move { ontology.list_read_only().await }, |result| { - match result { + Task::perform( + async move { ontology.list_read_only().await }, + |result| match result { Ok(entities) => Message::CacheReadOnlyEntities(entities), Err(err) => Message::ShowError(err.to_string()), - } - }) + }, + ) } pub(crate) fn run_inference(mut ontology: ConnectedOntology, graph: Graph) -> Task { - Task::perform(async move { ontology.run_inference(&graph).await }, |result| { - match result { + Task::perform( + async move { ontology.run_inference(&graph).await }, + |result| match result { Ok(inferences) => Message::SetInferredTriples(inferences), Err(err) => Message::ShowError(err.to_string()), - } - }) + }, + ) } pub(crate) fn navigate_to_next_url(client: ClientWithMiddleware, class: Class) -> Task { @@ -66,20 +82,32 @@ pub(crate) fn navigate_to_next_url(client: ClientWithMiddleware, class: Class) - Ok(resource) => match resource.into_rdf_source::().await { Ok(document) => { let node = NamedNodeRef::new_unchecked(lookup_url.as_str()); - let result = document.dataset() - .quads_for_pattern(Some(NamedOrBlankNodeRef::NamedNode(node)), Some(vocab::glo::NEXT_URL), None, None) + let result = document + .dataset() + .quads_for_pattern( + Some(NamedOrBlankNodeRef::NamedNode(node)), + Some(vocab::glo::NEXT_URL), + None, + None, + ) .next(); if let Some(next_url_quad) = result { if let TermRef::NamedNode(next_url) = next_url_quad.object { let url = Url::parse(next_url.as_str()).unwrap(); Message::NewDocument(class, Some(url)) - } else { Message::None } - } else { Message::None } + } else { + Message::None + } + } else { + Message::None + } } - _ => Message::None - } - _ => Message::None + _ => Message::None, + }, + _ => Message::None, } }) - } else { Task::none() } -} \ No newline at end of file + } else { + Task::none() + } +} diff --git a/publish/src/widget/iri_input.rs b/publish/src/widget/iri_input.rs index 03fbd7a..e504ac7 100644 --- a/publish/src/widget/iri_input.rs +++ b/publish/src/widget/iri_input.rs @@ -214,7 +214,8 @@ where state.shift = modifiers.shift(); } Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - if cursor.is_over(layout.bounds()) => { + if cursor.is_over(layout.bounds()) => + { if state.control && let Some(on_control_click) = &self.on_control_click { @@ -244,8 +245,9 @@ where ); let clipboard = shell.clipboard_mut(); - if let Some(Content::Text(content)) = &clipboard.write && - let Some(expanded) = self.curie_helper.expand(self.base, content) { + if let Some(Content::Text(content)) = &clipboard.write + && let Some(expanded) = self.curie_helper.expand(self.base, content) + { clipboard.write = Some(Content::Text(expanded)); } } diff --git a/search/src/index.rs b/search/src/index.rs index 46759d0..359e0d9 100644 --- a/search/src/index.rs +++ b/search/src/index.rs @@ -99,4 +99,4 @@ impl SearchIndex { pub fn to_json(document: HashMap) -> String { document.to_json(Schema::schema()) -} \ No newline at end of file +} diff --git a/search/src/lib.rs b/search/src/lib.rs index de9972a..2fa6100 100644 --- a/search/src/lib.rs +++ b/search/src/lib.rs @@ -9,4 +9,4 @@ pub use tantivy::schema::document::OwnedValue; pub use error::{Result, SearchError}; pub use index::{SearchIndex, SearchIndexBuilder, to_json}; -pub use schema::Schema; \ No newline at end of file +pub use schema::Schema; diff --git a/search/src/schema.rs b/search/src/schema.rs index 232ea1a..b864234 100644 --- a/search/src/schema.rs +++ b/search/src/schema.rs @@ -76,4 +76,4 @@ impl Schema { pub fn all_fields() -> Vec { Self::schema().fields().map(|(field, _)| field).collect() } -} \ No newline at end of file +} diff --git a/search/src/subtitles.rs b/search/src/subtitles.rs index 27836d0..0f230ca 100644 --- a/search/src/subtitles.rs +++ b/search/src/subtitles.rs @@ -20,4 +20,4 @@ pub fn bytes_to_documents(data: &[u8]) -> crate::Result> { }) .collect::>(); Ok(documents) -}*/ \ No newline at end of file +}*/