├── .cargo └── config.toml ├── .dockerignore ├── .env.dist ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bugreport.yml │ └── featurerequest.yml ├── dependabot.yml └── workflows │ ├── docker.yml │ ├── lint_and_build.yml │ └── release.yml ├── .gitignore ├── CITATION.cff ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md ├── benchmark ├── codemeta.json ├── data ├── about.html ├── custom.html ├── default.toml ├── example.ttl ├── favicon.ico ├── footer.html ├── header.html ├── index.html ├── resource.html ├── rickview.css └── roboto.css ├── fonts └── roboto300.woff2 ├── rustfmt.toml └── src ├── about.rs ├── classes.rs ├── config.rs ├── main.rs ├── rdf.rs └── resource.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.aarch64-unknown-linux-gnu] 2 | linker = "aarch64-linux-gnu-gcc" 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !src 3 | !Cargo.toml 4 | !Cargo.lock 5 | !data/default.toml 6 | !data/favicon.ico 7 | !data/example.ttl 8 | !data/*.html 9 | !data/*.css 10 | !fonts 11 | -------------------------------------------------------------------------------- /.env.dist: -------------------------------------------------------------------------------- 1 | RICKVIEW_KB_FILE=data/hito.ttl 2 | RICKVIEW_NAMESPACE=http://hitontology.eu/ontology/ 3 | RICKVIEW_TITLE_PROPERTIES=dc:title,dcterms:title,rdfs:label 4 | RICKVIEW_TYPE_PROPERTIES=rdf:type 5 | RICKVIEW_DESCRIPTION_PROPERTIES=rdfs:comment 6 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @KonradHoeffner 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bugreport.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report. 3 | labels: ["bug"] 4 | assignees: 5 | - KonradHoeffner 6 | body: 7 | - type: textarea 8 | id: what-happened 9 | attributes: 10 | label: What happened? 11 | validations: 12 | required: true 13 | - type: input 14 | id: version 15 | attributes: 16 | label: RickView version (lower left corner in the web page) 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/featurerequest.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest a new feature. 3 | labels: ["feature"] 4 | assignees: 5 | - KonradHoeffner 6 | body: 7 | - type: textarea 8 | id: new-feature 9 | attributes: 10 | label: What new feature do you want? 11 | validations: 12 | required: true 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/.github/workflows" 6 | commit-message: 7 | prefix: 'ci' 8 | include: 'scope' 9 | assignees: ["KonradHoeffner"] 10 | schedule: 11 | interval: "daily" 12 | - package-ecosystem: "cargo" 13 | directory: "/" 14 | commit-message: 15 | prefix: 'chore' 16 | include: 'scope' 17 | assignees: ["KonradHoeffner"] 18 | schedule: 19 | interval: "daily" 20 | allow: 21 | - dependency-type: "direct" 22 | ignore: 23 | - dependency-name: "*" 24 | update-types: ["version-update:semver-minor", "version-update:semver-patch"] 25 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker image 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: ['master'] 7 | tags: '[0-9]+\.[0-9]+\.[0-9]+*' 8 | paths-ignore: 9 | - '.gitignore' 10 | - '.env.dist' 11 | - '.github/dependabot.yml' 12 | - '.github/workflows/release.yml' 13 | - 'LICENSE' 14 | - 'README.md' 15 | #pull_request: 16 | #pull_request_target: 17 | # branches: ['master'] 18 | # paths-ignore: 19 | # - '.gitignore' 20 | # - '.env.dist' 21 | # - '.github/dependabot.yml' 22 | # - '.github/workflows/release.yml' 23 | # - 'LICENSE' 24 | # - 'README.md' 25 | 26 | env: 27 | GHCR_REPO: ghcr.io/konradhoeffner/rickview 28 | 29 | jobs: 30 | build: 31 | strategy: 32 | matrix: 33 | platform: 34 | - linux/amd64 35 | - linux/arm64 36 | # config: 37 | # - {arch: 'arm64'} 38 | # - {arch: 'amd64'} 39 | # runs-on: ${{ matrix.config.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} 40 | runs-on: ${{ matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} 41 | permissions: 42 | contents: read 43 | packages: write 44 | 45 | steps: 46 | # checkout action does not seem to be needed 47 | #- name: Checkout repository 48 | # uses: actions/checkout@v4 49 | - name: Prepare 50 | run: | 51 | platform=${{ matrix.platform }} 52 | echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV 53 | 54 | - name: Login to GHCR 55 | uses: docker/login-action@v3 56 | with: 57 | registry: ghcr.io 58 | username: ${{ github.repository_owner }} 59 | password: ${{ secrets.GITHUB_TOKEN }} 60 | 61 | - name: Extract metadata (tags, labels) for Docker 62 | id: meta 63 | uses: docker/metadata-action@v5 64 | with: 65 | images: ${{ env.GHCR_REPO }} 66 | 67 | - name: Set up Docker Buildx 68 | uses: docker/setup-buildx-action@v3 69 | 70 | - name: Build and push by digest 71 | id: build 72 | uses: docker/build-push-action@v6 73 | with: 74 | #tags: | 75 | # ${{ steps.meta.outputs.tags }} 76 | # ghcr.io/konradhoeffner/rickview:latest-${{matrix.config.arch}} 77 | platforms: ${{ matrix.platform }} 78 | labels: ${{ steps.meta.outputs.labels }} 79 | #outputs: type=image,"name=${{ env.DOCKERHUB_REPO }},${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=true 80 | outputs: type=image,"name=${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=true 81 | #cache-from: type=registry,ref=ghcr.io/konradhoeffner/rickview:master-${{matrix.config.arch}} 82 | #cache-to: type=inline 83 | 84 | - name: Export digest 85 | run: | 86 | mkdir -p ${{ runner.temp }}/digests 87 | digest="${{ steps.build.outputs.digest }}" 88 | touch "${{ runner.temp }}/digests/${digest#sha256:}" 89 | 90 | - name: Upload digest 91 | uses: actions/upload-artifact@v4 92 | with: 93 | name: digests-${{ env.PLATFORM_PAIR }} 94 | path: ${{ runner.temp }}/digests/* 95 | if-no-files-found: error 96 | retention-days: 1 97 | 98 | merge: 99 | runs-on: ubuntu-24.04-arm 100 | needs: build 101 | steps: 102 | - name: Download digests 103 | uses: actions/download-artifact@v4 104 | with: 105 | path: ${{ runner.temp }}/digests 106 | pattern: digests-* 107 | merge-multiple: true 108 | 109 | - name: Login to GHCR 110 | uses: docker/login-action@v3 111 | with: 112 | registry: ghcr.io 113 | username: ${{ github.repository_owner }} 114 | password: ${{ secrets.GITHUB_TOKEN }} 115 | 116 | - name: Extract metadata (tags, labels) for Docker 117 | id: meta 118 | uses: docker/metadata-action@v5 119 | with: 120 | images: ${{ env.GHCR_REPO }} 121 | #type=ref,event=pr 122 | tags: | 123 | type=ref,event=branch 124 | type=semver,pattern={{version}} 125 | type=semver,pattern={{major}}.{{minor}} 126 | type=semver,pattern={{major}} 127 | 128 | - name: Create manifest list and push 129 | working-directory: ${{ runner.temp }}/digests 130 | # docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ 131 | # $(printf '${{ env.DOCKERHUB_REPO }}@sha256:%s ' *) 132 | run: | 133 | docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ 134 | $(printf '${{ env.GHCR_REPO }}@sha256:%s ' *) 135 | 136 | - name: Inspect image 137 | # docker buildx imagetools inspect ${{ env.DOCKERHUB_REPO }}:${{ steps.meta.outputs.version }} 138 | run: | 139 | docker buildx imagetools inspect ${{ env.GHCR_REPO }}:${{ steps.meta.outputs.version }} 140 | 141 | # - name: Create and push manifest images 142 | # uses: Noelware/docker-manifest-action@master 143 | # with: 144 | # inputs: ghcr.io/konradhoeffner/rickview:latest,ghcr.io/konradhoeffner/rickview:master 145 | # images: ghcr.io/konradhoeffner/rickview:latest-amd64,ghcr.io/konradhoeffner/rickview:latest-arm64 146 | # push: true 147 | -------------------------------------------------------------------------------- /.github/workflows/lint_and_build.yml: -------------------------------------------------------------------------------- 1 | name: Lint and Check 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '.gitignore' 7 | - '.dockerignore' 8 | - '.env.dist' 9 | - '.github/dependabot.yml' 10 | - '.github/workflows/release.yml' 11 | - '.github/workflows/docker.yml' 12 | - 'Dockerfile' 13 | - 'LICENSE' 14 | - 'README.md' 15 | pull_request: 16 | paths-ignore: 17 | - '.gitignore' 18 | - '.dockerignore' 19 | - '.env.dist' 20 | - '.github/dependabot.yml' 21 | - '.github/workflows/release.yml' 22 | - '.github/workflows/docker.yml' 23 | - 'Dockerfile' 24 | - 'LICENSE' 25 | - 'README.md' 26 | 27 | jobs: 28 | check: 29 | #strategy: 30 | #matrix: 31 | #include: 32 | #- os: ubuntu-latest 33 | #- os: macos-latest 34 | #- os: windows-latest 35 | #runs-on: ${{ matrix.os }} 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v4 39 | - name: Install nightly Rust 40 | uses: dtolnay/rust-toolchain@master 41 | with: 42 | components: rustfmt,clippy 43 | toolchain: nightly # we want to know if it breaks with the newest nightly 44 | # toolchain: nightly-2024-02-17 45 | - name: fmt 46 | run: cargo fmt --check 47 | - name: clippy 48 | run: cargo clippy --no-deps --all-features 49 | - name: check 50 | run: cargo check 51 | - name: Install stable Rust 52 | uses: dtolnay/rust-toolchain@stable 53 | - name: check on stable Rust 54 | run: cargo check 55 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # adapted copy from https://github.com/termapps/enquirer/blob/master/.github/workflows/release.yml 2 | env: 3 | NAME: rickview 4 | CARGO_TERM_COLOR: always 5 | CARGO_INCREMENTAL: 0 6 | RUSTFLAGS: -Dwarnings 7 | 8 | name: Release 9 | on: 10 | push: 11 | tags: '[0-9]+\.[0-9]+\.[0-9]+*' 12 | jobs: 13 | create-release: 14 | name: Create release 15 | runs-on: ubuntu-latest 16 | outputs: 17 | upload_url: ${{ steps.create-release.outputs.upload_url }} 18 | steps: 19 | - name: Build Changelog 20 | id: build-changelog 21 | uses: mikepenz/release-changelog-builder-action@v5 22 | with: 23 | mode: "COMMIT" 24 | configurationJson: | 25 | { 26 | "pr_template": "- #{{TITLE}}", 27 | "template": "#{{UNCATEGORIZED}}" 28 | } 29 | - name: Create Release 30 | id: create-release 31 | uses: softprops/action-gh-release@v2 32 | with: 33 | body: ${{steps.build-changelog.outputs.changelog}} 34 | read-version: 35 | name: Read version 36 | runs-on: ubuntu-latest 37 | outputs: 38 | version: ${{ steps.version.outputs.VERSION }} 39 | steps: 40 | - name: Read version 41 | id: version 42 | env: 43 | REF: ${{ github.ref }} 44 | shell: bash 45 | run: echo "VERSION=${REF/refs\/tags\//}" >> $GITHUB_OUTPUT 46 | build-upload: 47 | name: Build & Upload 48 | needs: [create-release, read-version] 49 | strategy: 50 | fail-fast: false 51 | matrix: 52 | include: 53 | - os: ubuntu-latest 54 | target: x86_64-unknown-linux-musl 55 | - os: ubuntu-latest 56 | target: x86_64-unknown-linux-gnu 57 | - os: ubuntu-22.04-arm 58 | target: aarch64-unknown-linux-gnu 59 | - os: macos-latest 60 | target: x86_64-apple-darwin 61 | # - os: windows-latest 62 | # target: i686-pc-windows-msvc 63 | - os: windows-latest 64 | target: x86_64-pc-windows-msvc 65 | runs-on: ${{ matrix.os }} 66 | steps: 67 | - name: Checkout 68 | uses: actions/checkout@v4 69 | - name: Install rust 70 | uses: dtolnay/rust-toolchain@stable 71 | with: 72 | targets: ${{ matrix.target }} 73 | - name: Set up cargo cache 74 | uses: actions/cache@v4 75 | continue-on-error: false 76 | with: 77 | path: | 78 | ~/.cargo/bin/ 79 | ~/.cargo/registry/index/ 80 | ~/.cargo/registry/cache/ 81 | ~/.cargo/git/db/ 82 | target/ 83 | key: ${{ runner.os }}-cargo 84 | #- name: Install linker # we don't build for 32 bit so we don't need this 85 | # if: matrix.target == 'i686-unknown-linux-gnu' 86 | # run: sudo apt-get install gcc-multilib 87 | #- name: Install aarch64 gcc # should not be needed anymore with the ARM runner 88 | # if: matrix.target == 'aarch64-unknown-linux-gnu' 89 | # run: sudo apt-get install gcc-aarch64-linux-gnu 90 | - name: Install musl 91 | if: contains(matrix.target, 'linux-musl') 92 | run: sudo apt-get install musl-tools 93 | - name: Build 94 | run: cargo build --target ${{ matrix.target }} --release 95 | - name: Set variables 96 | id: vars 97 | env: 98 | BUILD_NAME: ${{ env.NAME }}-${{ needs.read-version.outputs.version }}-${{ matrix.target }} 99 | shell: bash 100 | run: | 101 | echo "BUILD_NAME=${BUILD_NAME}" >> $GITHUB_OUTPUT 102 | echo "BUILD_NAME=${BUILD_NAME}" >> $GITHUB_ENV 103 | - name: Ready artifacts 104 | env: 105 | BUILD_NAME: ${{ steps.vars.outputs.BUILD_NAME }} 106 | TARGET: ${{ matrix.target }} 107 | shell: bash 108 | run: | 109 | mkdir "${BUILD_NAME}" 110 | # cp target/$TARGET/release/$NAME LICENSE *.md $NAME.1 $BUILD_NAME 111 | cp target/$TARGET/release/$NAME $BUILD_NAME 112 | - name: Compress artifacts 113 | uses: papeloto/action-zip@v1 114 | with: 115 | files: ${{ steps.vars.outputs.BUILD_NAME }}/ 116 | recursive: false 117 | dest: ${{ steps.vars.outputs.BUILD_NAME }}.zip 118 | - name: Upload artifacts 119 | uses: actions/upload-release-asset@v1 120 | env: 121 | GITHUB_TOKEN: ${{ github.token }} 122 | with: 123 | upload_url: ${{ needs.create-release.outputs.upload_url }} 124 | asset_path: ./${{ steps.vars.outputs.BUILD_NAME }}.zip 125 | asset_name: ${{ steps.vars.outputs.BUILD_NAME }}.zip 126 | asset_content_type: application/zip 127 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | data/*.toml 3 | !data/default.toml 4 | data/*.ttl 5 | !data/example.ttl 6 | data/*.hdt 7 | !data/example.hdt 8 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | --- 2 | cff-version: 1.2.0 3 | title: RickView—A performant RDF browser 4 | type: software 5 | authors: 6 | - given-names: Konrad 7 | family-names: Höffner 8 | email: konrad.hoeffner@uni-leipzig.de 9 | affiliation: Institute for Medical Informatics, Statistics and Epidemiology (IMISE), Leipzig, Germany 10 | orcid: https://orcid.org/0000-0001-7358-3217 11 | identifiers: 12 | - type: doi 13 | value: 10.5281/zenodo.8290117 14 | description: Zenodo archive, all versions 15 | repository-code: https://github.com/konradhoeffner/rickview 16 | url: https://crates.io/crates/rickview 17 | keywords: [RDF, Rust, Linked Data, Linked Data Browser, Semantic Web] 18 | license: MIT 19 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "abnf" 7 | version = "0.13.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "087113bd50d9adce24850eed5d0476c7d199d532fce8fab5173650331e09033a" 10 | dependencies = [ 11 | "abnf-core", 12 | "nom", 13 | ] 14 | 15 | [[package]] 16 | name = "abnf-core" 17 | version = "0.5.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "c44e09c43ae1c368fb91a03a566472d0087c26cf7e1b9e8e289c14ede681dd7d" 20 | dependencies = [ 21 | "nom", 22 | ] 23 | 24 | [[package]] 25 | name = "actix-codec" 26 | version = "0.5.2" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" 29 | dependencies = [ 30 | "bitflags", 31 | "bytes", 32 | "futures-core", 33 | "futures-sink", 34 | "memchr", 35 | "pin-project-lite", 36 | "tokio", 37 | "tokio-util", 38 | "tracing", 39 | ] 40 | 41 | [[package]] 42 | name = "actix-http" 43 | version = "3.9.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "d48f96fc3003717aeb9856ca3d02a8c7de502667ad76eeacd830b48d2e91fac4" 46 | dependencies = [ 47 | "actix-codec", 48 | "actix-rt", 49 | "actix-service", 50 | "actix-utils", 51 | "ahash", 52 | "base64", 53 | "bitflags", 54 | "brotli", 55 | "bytes", 56 | "bytestring", 57 | "derive_more", 58 | "encoding_rs", 59 | "flate2", 60 | "futures-core", 61 | "h2", 62 | "http 0.2.12", 63 | "httparse", 64 | "httpdate", 65 | "itoa", 66 | "language-tags", 67 | "local-channel", 68 | "mime", 69 | "percent-encoding", 70 | "pin-project-lite", 71 | "rand", 72 | "sha1", 73 | "smallvec", 74 | "tokio", 75 | "tokio-util", 76 | "tracing", 77 | "zstd", 78 | ] 79 | 80 | [[package]] 81 | name = "actix-macros" 82 | version = "0.2.4" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" 85 | dependencies = [ 86 | "quote 1.0.39", 87 | "syn 2.0.99", 88 | ] 89 | 90 | [[package]] 91 | name = "actix-router" 92 | version = "0.5.3" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" 95 | dependencies = [ 96 | "bytestring", 97 | "cfg-if", 98 | "http 0.2.12", 99 | "regex", 100 | "regex-lite", 101 | "serde", 102 | "tracing", 103 | ] 104 | 105 | [[package]] 106 | name = "actix-rt" 107 | version = "2.10.0" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" 110 | dependencies = [ 111 | "futures-core", 112 | "tokio", 113 | ] 114 | 115 | [[package]] 116 | name = "actix-server" 117 | version = "2.5.0" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "7ca2549781d8dd6d75c40cf6b6051260a2cc2f3c62343d761a969a0640646894" 120 | dependencies = [ 121 | "actix-rt", 122 | "actix-service", 123 | "actix-utils", 124 | "futures-core", 125 | "futures-util", 126 | "mio", 127 | "socket2", 128 | "tokio", 129 | "tracing", 130 | ] 131 | 132 | [[package]] 133 | name = "actix-service" 134 | version = "2.0.2" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" 137 | dependencies = [ 138 | "futures-core", 139 | "paste", 140 | "pin-project-lite", 141 | ] 142 | 143 | [[package]] 144 | name = "actix-utils" 145 | version = "3.0.1" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" 148 | dependencies = [ 149 | "local-waker", 150 | "pin-project-lite", 151 | ] 152 | 153 | [[package]] 154 | name = "actix-web" 155 | version = "4.9.0" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "9180d76e5cc7ccbc4d60a506f2c727730b154010262df5b910eb17dbe4b8cb38" 158 | dependencies = [ 159 | "actix-codec", 160 | "actix-http", 161 | "actix-macros", 162 | "actix-router", 163 | "actix-rt", 164 | "actix-server", 165 | "actix-service", 166 | "actix-utils", 167 | "actix-web-codegen", 168 | "ahash", 169 | "bytes", 170 | "bytestring", 171 | "cfg-if", 172 | "cookie", 173 | "derive_more", 174 | "encoding_rs", 175 | "futures-core", 176 | "futures-util", 177 | "impl-more", 178 | "itoa", 179 | "language-tags", 180 | "log", 181 | "mime", 182 | "once_cell", 183 | "pin-project-lite", 184 | "regex", 185 | "regex-lite", 186 | "serde", 187 | "serde_json", 188 | "serde_urlencoded", 189 | "smallvec", 190 | "socket2", 191 | "time", 192 | "url", 193 | ] 194 | 195 | [[package]] 196 | name = "actix-web-codegen" 197 | version = "4.3.0" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" 200 | dependencies = [ 201 | "actix-router", 202 | "proc-macro2", 203 | "quote 1.0.39", 204 | "syn 2.0.99", 205 | ] 206 | 207 | [[package]] 208 | name = "addr2line" 209 | version = "0.24.2" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 212 | dependencies = [ 213 | "gimli", 214 | ] 215 | 216 | [[package]] 217 | name = "adler2" 218 | version = "2.0.0" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 221 | 222 | [[package]] 223 | name = "ahash" 224 | version = "0.8.11" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 227 | dependencies = [ 228 | "cfg-if", 229 | "getrandom", 230 | "once_cell", 231 | "version_check", 232 | "zerocopy", 233 | ] 234 | 235 | [[package]] 236 | name = "aho-corasick" 237 | version = "1.1.3" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 240 | dependencies = [ 241 | "memchr", 242 | ] 243 | 244 | [[package]] 245 | name = "alloc-no-stdlib" 246 | version = "2.0.4" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" 249 | 250 | [[package]] 251 | name = "alloc-stdlib" 252 | version = "0.2.2" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" 255 | dependencies = [ 256 | "alloc-no-stdlib", 257 | ] 258 | 259 | [[package]] 260 | name = "anstream" 261 | version = "0.6.18" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 264 | dependencies = [ 265 | "anstyle", 266 | "anstyle-parse", 267 | "anstyle-query", 268 | "anstyle-wincon", 269 | "colorchoice", 270 | "is_terminal_polyfill", 271 | "utf8parse", 272 | ] 273 | 274 | [[package]] 275 | name = "anstyle" 276 | version = "1.0.10" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 279 | 280 | [[package]] 281 | name = "anstyle-parse" 282 | version = "0.2.6" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 285 | dependencies = [ 286 | "utf8parse", 287 | ] 288 | 289 | [[package]] 290 | name = "anstyle-query" 291 | version = "1.1.2" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 294 | dependencies = [ 295 | "windows-sys 0.59.0", 296 | ] 297 | 298 | [[package]] 299 | name = "anstyle-wincon" 300 | version = "3.0.7" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 303 | dependencies = [ 304 | "anstyle", 305 | "once_cell", 306 | "windows-sys 0.59.0", 307 | ] 308 | 309 | [[package]] 310 | name = "anyhow" 311 | version = "1.0.97" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" 314 | 315 | [[package]] 316 | name = "autocfg" 317 | version = "1.4.0" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 320 | 321 | [[package]] 322 | name = "backtrace" 323 | version = "0.3.74" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 326 | dependencies = [ 327 | "addr2line", 328 | "cfg-if", 329 | "libc", 330 | "miniz_oxide", 331 | "object", 332 | "rustc-demangle", 333 | "windows-targets", 334 | ] 335 | 336 | [[package]] 337 | name = "base64" 338 | version = "0.22.1" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 341 | 342 | [[package]] 343 | name = "bitflags" 344 | version = "2.9.0" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 347 | 348 | [[package]] 349 | name = "block-buffer" 350 | version = "0.10.4" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 353 | dependencies = [ 354 | "generic-array", 355 | ] 356 | 357 | [[package]] 358 | name = "brotli" 359 | version = "6.0.0" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" 362 | dependencies = [ 363 | "alloc-no-stdlib", 364 | "alloc-stdlib", 365 | "brotli-decompressor", 366 | ] 367 | 368 | [[package]] 369 | name = "brotli-decompressor" 370 | version = "4.0.2" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37" 373 | dependencies = [ 374 | "alloc-no-stdlib", 375 | "alloc-stdlib", 376 | ] 377 | 378 | [[package]] 379 | name = "btree-range-map" 380 | version = "0.7.2" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "1be5c9672446d3800bcbcaabaeba121fe22f1fb25700c4562b22faf76d377c33" 383 | dependencies = [ 384 | "btree-slab", 385 | "cc-traits", 386 | "range-traits", 387 | "serde", 388 | "slab", 389 | ] 390 | 391 | [[package]] 392 | name = "btree-slab" 393 | version = "0.6.1" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "7a2b56d3029f075c4fa892428a098425b86cef5c89ae54073137ece416aef13c" 396 | dependencies = [ 397 | "cc-traits", 398 | "slab", 399 | "smallvec", 400 | ] 401 | 402 | [[package]] 403 | name = "byteorder" 404 | version = "1.5.0" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 407 | 408 | [[package]] 409 | name = "bytes" 410 | version = "1.10.1" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 413 | 414 | [[package]] 415 | name = "bytesize" 416 | version = "2.0.1" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "a3c8f83209414aacf0eeae3cf730b18d6981697fba62f200fcfb92b9f082acba" 419 | 420 | [[package]] 421 | name = "bytestring" 422 | version = "1.4.0" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "e465647ae23b2823b0753f50decb2d5a86d2bb2cac04788fafd1f80e45378e5f" 425 | dependencies = [ 426 | "bytes", 427 | ] 428 | 429 | [[package]] 430 | name = "cc" 431 | version = "1.2.16" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" 434 | dependencies = [ 435 | "jobserver", 436 | "libc", 437 | "shlex", 438 | ] 439 | 440 | [[package]] 441 | name = "cc-traits" 442 | version = "2.0.0" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "060303ef31ef4a522737e1b1ab68c67916f2a787bb2f4f54f383279adba962b5" 445 | dependencies = [ 446 | "slab", 447 | ] 448 | 449 | [[package]] 450 | name = "cfg-if" 451 | version = "1.0.0" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 454 | 455 | [[package]] 456 | name = "ciborium" 457 | version = "0.2.2" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" 460 | dependencies = [ 461 | "ciborium-io", 462 | "ciborium-ll", 463 | "serde", 464 | ] 465 | 466 | [[package]] 467 | name = "ciborium-io" 468 | version = "0.2.2" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" 471 | 472 | [[package]] 473 | name = "ciborium-ll" 474 | version = "0.2.2" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" 477 | dependencies = [ 478 | "ciborium-io", 479 | "half", 480 | ] 481 | 482 | [[package]] 483 | name = "colorchoice" 484 | version = "1.0.3" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 487 | 488 | [[package]] 489 | name = "config" 490 | version = "0.15.9" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "fb07d21d12f9f0bc5e7c3e97ccc78b2341b9b4a4604eac3ed7c1d0d6e2c3b23e" 493 | dependencies = [ 494 | "pathdiff", 495 | "serde", 496 | "toml", 497 | "winnow", 498 | ] 499 | 500 | [[package]] 501 | name = "const-fnv1a-hash" 502 | version = "1.1.0" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "32b13ea120a812beba79e34316b3942a857c86ec1593cb34f27bb28272ce2cca" 505 | 506 | [[package]] 507 | name = "convert_case" 508 | version = "0.4.0" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 511 | 512 | [[package]] 513 | name = "cookie" 514 | version = "0.16.2" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" 517 | dependencies = [ 518 | "percent-encoding", 519 | "time", 520 | "version_check", 521 | ] 522 | 523 | [[package]] 524 | name = "cpufeatures" 525 | version = "0.2.17" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 528 | dependencies = [ 529 | "libc", 530 | ] 531 | 532 | [[package]] 533 | name = "crc" 534 | version = "3.2.1" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" 537 | dependencies = [ 538 | "crc-catalog", 539 | ] 540 | 541 | [[package]] 542 | name = "crc-catalog" 543 | version = "2.4.0" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" 546 | 547 | [[package]] 548 | name = "crc32fast" 549 | version = "1.4.2" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 552 | dependencies = [ 553 | "cfg-if", 554 | ] 555 | 556 | [[package]] 557 | name = "crunchy" 558 | version = "0.2.3" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" 561 | 562 | [[package]] 563 | name = "crypto-common" 564 | version = "0.1.6" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 567 | dependencies = [ 568 | "generic-array", 569 | "typenum", 570 | ] 571 | 572 | [[package]] 573 | name = "deepsize" 574 | version = "0.2.0" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "1cdb987ec36f6bf7bfbea3f928b75590b736fc42af8e54d97592481351b2b96c" 577 | 578 | [[package]] 579 | name = "deranged" 580 | version = "0.3.11" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 583 | dependencies = [ 584 | "powerfmt", 585 | ] 586 | 587 | [[package]] 588 | name = "derive_more" 589 | version = "0.99.19" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" 592 | dependencies = [ 593 | "convert_case", 594 | "proc-macro2", 595 | "quote 1.0.39", 596 | "rustc_version", 597 | "syn 2.0.99", 598 | ] 599 | 600 | [[package]] 601 | name = "digest" 602 | version = "0.10.7" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 605 | dependencies = [ 606 | "block-buffer", 607 | "crypto-common", 608 | ] 609 | 610 | [[package]] 611 | name = "displaydoc" 612 | version = "0.2.5" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 615 | dependencies = [ 616 | "proc-macro2", 617 | "quote 1.0.39", 618 | "syn 2.0.99", 619 | ] 620 | 621 | [[package]] 622 | name = "encoding_rs" 623 | version = "0.8.35" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 626 | dependencies = [ 627 | "cfg-if", 628 | ] 629 | 630 | [[package]] 631 | name = "env_filter" 632 | version = "0.1.3" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" 635 | dependencies = [ 636 | "log", 637 | ] 638 | 639 | [[package]] 640 | name = "env_logger" 641 | version = "0.11.6" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" 644 | dependencies = [ 645 | "anstream", 646 | "anstyle", 647 | "env_filter", 648 | "log", 649 | ] 650 | 651 | [[package]] 652 | name = "equivalent" 653 | version = "1.0.2" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 656 | 657 | [[package]] 658 | name = "eyre" 659 | version = "0.6.12" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" 662 | dependencies = [ 663 | "indenter", 664 | "once_cell", 665 | ] 666 | 667 | [[package]] 668 | name = "flate2" 669 | version = "1.1.0" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" 672 | dependencies = [ 673 | "crc32fast", 674 | "miniz_oxide", 675 | ] 676 | 677 | [[package]] 678 | name = "fnv" 679 | version = "1.0.7" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 682 | 683 | [[package]] 684 | name = "form_urlencoded" 685 | version = "1.2.1" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 688 | dependencies = [ 689 | "percent-encoding", 690 | ] 691 | 692 | [[package]] 693 | name = "futures-core" 694 | version = "0.3.31" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 697 | 698 | [[package]] 699 | name = "futures-sink" 700 | version = "0.3.31" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 703 | 704 | [[package]] 705 | name = "futures-task" 706 | version = "0.3.31" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 709 | 710 | [[package]] 711 | name = "futures-util" 712 | version = "0.3.31" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 715 | dependencies = [ 716 | "futures-core", 717 | "futures-task", 718 | "pin-project-lite", 719 | "pin-utils", 720 | ] 721 | 722 | [[package]] 723 | name = "generic-array" 724 | version = "0.14.7" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 727 | dependencies = [ 728 | "typenum", 729 | "version_check", 730 | ] 731 | 732 | [[package]] 733 | name = "getrandom" 734 | version = "0.2.15" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 737 | dependencies = [ 738 | "cfg-if", 739 | "libc", 740 | "wasi", 741 | ] 742 | 743 | [[package]] 744 | name = "gimli" 745 | version = "0.31.1" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 748 | 749 | [[package]] 750 | name = "h2" 751 | version = "0.3.26" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" 754 | dependencies = [ 755 | "bytes", 756 | "fnv", 757 | "futures-core", 758 | "futures-sink", 759 | "futures-util", 760 | "http 0.2.12", 761 | "indexmap", 762 | "slab", 763 | "tokio", 764 | "tokio-util", 765 | "tracing", 766 | ] 767 | 768 | [[package]] 769 | name = "half" 770 | version = "2.4.1" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" 773 | dependencies = [ 774 | "cfg-if", 775 | "crunchy", 776 | ] 777 | 778 | [[package]] 779 | name = "hashbrown" 780 | version = "0.15.2" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 783 | 784 | [[package]] 785 | name = "hdt" 786 | version = "0.3.0" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "bc04aee5f5d96473f46df1fa6a699051d8282573dddb16d043d5d8a60d7faef4" 789 | dependencies = [ 790 | "bytesize", 791 | "crc", 792 | "eyre", 793 | "iref", 794 | "langtag", 795 | "log", 796 | "mownstr", 797 | "ntriple", 798 | "sophia", 799 | "sucds", 800 | "thiserror 2.0.12", 801 | ] 802 | 803 | [[package]] 804 | name = "hex_fmt" 805 | version = "0.3.0" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" 808 | 809 | [[package]] 810 | name = "http" 811 | version = "0.2.12" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 814 | dependencies = [ 815 | "bytes", 816 | "fnv", 817 | "itoa", 818 | ] 819 | 820 | [[package]] 821 | name = "http" 822 | version = "1.2.0" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" 825 | dependencies = [ 826 | "bytes", 827 | "fnv", 828 | "itoa", 829 | ] 830 | 831 | [[package]] 832 | name = "httparse" 833 | version = "1.10.1" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 836 | 837 | [[package]] 838 | name = "httpdate" 839 | version = "1.0.3" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 842 | 843 | [[package]] 844 | name = "icu_collections" 845 | version = "1.5.0" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 848 | dependencies = [ 849 | "displaydoc", 850 | "yoke", 851 | "zerofrom", 852 | "zerovec", 853 | ] 854 | 855 | [[package]] 856 | name = "icu_locid" 857 | version = "1.5.0" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 860 | dependencies = [ 861 | "displaydoc", 862 | "litemap", 863 | "tinystr", 864 | "writeable", 865 | "zerovec", 866 | ] 867 | 868 | [[package]] 869 | name = "icu_locid_transform" 870 | version = "1.5.0" 871 | source = "registry+https://github.com/rust-lang/crates.io-index" 872 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 873 | dependencies = [ 874 | "displaydoc", 875 | "icu_locid", 876 | "icu_locid_transform_data", 877 | "icu_provider", 878 | "tinystr", 879 | "zerovec", 880 | ] 881 | 882 | [[package]] 883 | name = "icu_locid_transform_data" 884 | version = "1.5.0" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 887 | 888 | [[package]] 889 | name = "icu_normalizer" 890 | version = "1.5.0" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 893 | dependencies = [ 894 | "displaydoc", 895 | "icu_collections", 896 | "icu_normalizer_data", 897 | "icu_properties", 898 | "icu_provider", 899 | "smallvec", 900 | "utf16_iter", 901 | "utf8_iter", 902 | "write16", 903 | "zerovec", 904 | ] 905 | 906 | [[package]] 907 | name = "icu_normalizer_data" 908 | version = "1.5.0" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 911 | 912 | [[package]] 913 | name = "icu_properties" 914 | version = "1.5.1" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 917 | dependencies = [ 918 | "displaydoc", 919 | "icu_collections", 920 | "icu_locid_transform", 921 | "icu_properties_data", 922 | "icu_provider", 923 | "tinystr", 924 | "zerovec", 925 | ] 926 | 927 | [[package]] 928 | name = "icu_properties_data" 929 | version = "1.5.0" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 932 | 933 | [[package]] 934 | name = "icu_provider" 935 | version = "1.5.0" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 938 | dependencies = [ 939 | "displaydoc", 940 | "icu_locid", 941 | "icu_provider_macros", 942 | "stable_deref_trait", 943 | "tinystr", 944 | "writeable", 945 | "yoke", 946 | "zerofrom", 947 | "zerovec", 948 | ] 949 | 950 | [[package]] 951 | name = "icu_provider_macros" 952 | version = "1.5.0" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 955 | dependencies = [ 956 | "proc-macro2", 957 | "quote 1.0.39", 958 | "syn 2.0.99", 959 | ] 960 | 961 | [[package]] 962 | name = "idna" 963 | version = "1.0.3" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 966 | dependencies = [ 967 | "idna_adapter", 968 | "smallvec", 969 | "utf8_iter", 970 | ] 971 | 972 | [[package]] 973 | name = "idna_adapter" 974 | version = "1.2.0" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 977 | dependencies = [ 978 | "icu_normalizer", 979 | "icu_properties", 980 | ] 981 | 982 | [[package]] 983 | name = "impl-more" 984 | version = "0.1.9" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" 987 | 988 | [[package]] 989 | name = "indenter" 990 | version = "0.3.3" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 993 | 994 | [[package]] 995 | name = "indexmap" 996 | version = "2.7.1" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" 999 | dependencies = [ 1000 | "equivalent", 1001 | "hashbrown", 1002 | ] 1003 | 1004 | [[package]] 1005 | name = "indoc" 1006 | version = "2.0.6" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" 1009 | 1010 | [[package]] 1011 | name = "iref" 1012 | version = "3.2.2" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "374372d9ca7331cec26f307b12552554849143e6b2077be3553576aa9aa8258c" 1015 | dependencies = [ 1016 | "iref-core", 1017 | ] 1018 | 1019 | [[package]] 1020 | name = "iref-core" 1021 | version = "3.2.2" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "b10559a0d518effd4f2cee107f40f83acf8583dcd3e6760b9b60293b0d2c2a70" 1024 | dependencies = [ 1025 | "pct-str", 1026 | "smallvec", 1027 | "static-regular-grammar", 1028 | "thiserror 1.0.69", 1029 | ] 1030 | 1031 | [[package]] 1032 | name = "is_terminal_polyfill" 1033 | version = "1.70.1" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 1036 | 1037 | [[package]] 1038 | name = "itoa" 1039 | version = "1.0.15" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 1042 | 1043 | [[package]] 1044 | name = "jobserver" 1045 | version = "0.1.32" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" 1048 | dependencies = [ 1049 | "libc", 1050 | ] 1051 | 1052 | [[package]] 1053 | name = "langtag" 1054 | version = "0.4.0" 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" 1056 | checksum = "9ecb4c689a30e48ebeaa14237f34037e300dd072e6ad21a9ec72e810ff3c6600" 1057 | dependencies = [ 1058 | "static-regular-grammar", 1059 | "thiserror 1.0.69", 1060 | ] 1061 | 1062 | [[package]] 1063 | name = "language-tags" 1064 | version = "0.3.2" 1065 | source = "registry+https://github.com/rust-lang/crates.io-index" 1066 | checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" 1067 | 1068 | [[package]] 1069 | name = "lazy_static" 1070 | version = "1.5.0" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1073 | 1074 | [[package]] 1075 | name = "libc" 1076 | version = "0.2.170" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" 1079 | 1080 | [[package]] 1081 | name = "litemap" 1082 | version = "0.7.5" 1083 | source = "registry+https://github.com/rust-lang/crates.io-index" 1084 | checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" 1085 | 1086 | [[package]] 1087 | name = "local-channel" 1088 | version = "0.1.5" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" 1091 | dependencies = [ 1092 | "futures-core", 1093 | "futures-sink", 1094 | "local-waker", 1095 | ] 1096 | 1097 | [[package]] 1098 | name = "local-waker" 1099 | version = "0.1.4" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" 1102 | 1103 | [[package]] 1104 | name = "lock_api" 1105 | version = "0.4.12" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 1108 | dependencies = [ 1109 | "autocfg", 1110 | "scopeguard", 1111 | ] 1112 | 1113 | [[package]] 1114 | name = "log" 1115 | version = "0.4.26" 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" 1117 | checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" 1118 | 1119 | [[package]] 1120 | name = "memchr" 1121 | version = "2.7.4" 1122 | source = "registry+https://github.com/rust-lang/crates.io-index" 1123 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1124 | 1125 | [[package]] 1126 | name = "mime" 1127 | version = "0.3.17" 1128 | source = "registry+https://github.com/rust-lang/crates.io-index" 1129 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1130 | 1131 | [[package]] 1132 | name = "minimal-lexical" 1133 | version = "0.2.1" 1134 | source = "registry+https://github.com/rust-lang/crates.io-index" 1135 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 1136 | 1137 | [[package]] 1138 | name = "miniz_oxide" 1139 | version = "0.8.5" 1140 | source = "registry+https://github.com/rust-lang/crates.io-index" 1141 | checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" 1142 | dependencies = [ 1143 | "adler2", 1144 | ] 1145 | 1146 | [[package]] 1147 | name = "mio" 1148 | version = "1.0.3" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 1151 | dependencies = [ 1152 | "libc", 1153 | "log", 1154 | "wasi", 1155 | "windows-sys 0.52.0", 1156 | ] 1157 | 1158 | [[package]] 1159 | name = "mownstr" 1160 | version = "0.3.0" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "476c8c04737daac89304071f64cb9b5ee32dea0ab714db2f8519c03105630d4d" 1163 | 1164 | [[package]] 1165 | name = "multimap" 1166 | version = "0.10.0" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" 1169 | dependencies = [ 1170 | "serde", 1171 | ] 1172 | 1173 | [[package]] 1174 | name = "nom" 1175 | version = "7.1.3" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 1178 | dependencies = [ 1179 | "memchr", 1180 | "minimal-lexical", 1181 | ] 1182 | 1183 | [[package]] 1184 | name = "ntriple" 1185 | version = "0.1.1" 1186 | source = "registry+https://github.com/rust-lang/crates.io-index" 1187 | checksum = "020fb7cf74ddf131e4ba84e13221d2493ae4d17cad3982a9158771442d6b0730" 1188 | dependencies = [ 1189 | "peg", 1190 | ] 1191 | 1192 | [[package]] 1193 | name = "num-conv" 1194 | version = "0.1.0" 1195 | source = "registry+https://github.com/rust-lang/crates.io-index" 1196 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1197 | 1198 | [[package]] 1199 | name = "num-traits" 1200 | version = "0.2.19" 1201 | source = "registry+https://github.com/rust-lang/crates.io-index" 1202 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1203 | dependencies = [ 1204 | "autocfg", 1205 | ] 1206 | 1207 | [[package]] 1208 | name = "object" 1209 | version = "0.36.7" 1210 | source = "registry+https://github.com/rust-lang/crates.io-index" 1211 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 1212 | dependencies = [ 1213 | "memchr", 1214 | ] 1215 | 1216 | [[package]] 1217 | name = "once_cell" 1218 | version = "1.20.3" 1219 | source = "registry+https://github.com/rust-lang/crates.io-index" 1220 | checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" 1221 | 1222 | [[package]] 1223 | name = "oxilangtag" 1224 | version = "0.1.5" 1225 | source = "registry+https://github.com/rust-lang/crates.io-index" 1226 | checksum = "23f3f87617a86af77fa3691e6350483e7154c2ead9f1261b75130e21ca0f8acb" 1227 | dependencies = [ 1228 | "serde", 1229 | ] 1230 | 1231 | [[package]] 1232 | name = "oxiri" 1233 | version = "0.2.11" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "54b4ed3a7192fa19f5f48f99871f2755047fabefd7f222f12a1df1773796a102" 1236 | 1237 | [[package]] 1238 | name = "parking_lot" 1239 | version = "0.12.3" 1240 | source = "registry+https://github.com/rust-lang/crates.io-index" 1241 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1242 | dependencies = [ 1243 | "lock_api", 1244 | "parking_lot_core", 1245 | ] 1246 | 1247 | [[package]] 1248 | name = "parking_lot_core" 1249 | version = "0.9.10" 1250 | source = "registry+https://github.com/rust-lang/crates.io-index" 1251 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1252 | dependencies = [ 1253 | "cfg-if", 1254 | "libc", 1255 | "redox_syscall", 1256 | "smallvec", 1257 | "windows-targets", 1258 | ] 1259 | 1260 | [[package]] 1261 | name = "paste" 1262 | version = "1.0.15" 1263 | source = "registry+https://github.com/rust-lang/crates.io-index" 1264 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1265 | 1266 | [[package]] 1267 | name = "pathdiff" 1268 | version = "0.2.3" 1269 | source = "registry+https://github.com/rust-lang/crates.io-index" 1270 | checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" 1271 | 1272 | [[package]] 1273 | name = "pct-str" 1274 | version = "2.0.0" 1275 | source = "registry+https://github.com/rust-lang/crates.io-index" 1276 | checksum = "bf1bdcc492c285a50bed60860dfa00b50baf1f60c73c7d6b435b01a2a11fd6ff" 1277 | dependencies = [ 1278 | "thiserror 1.0.69", 1279 | "utf8-decode", 1280 | ] 1281 | 1282 | [[package]] 1283 | name = "peg" 1284 | version = "0.5.7" 1285 | source = "registry+https://github.com/rust-lang/crates.io-index" 1286 | checksum = "40df12dde1d836ed2a4c3bfc2799797e3abaf807d97520d28d6e3f3bf41a5f85" 1287 | dependencies = [ 1288 | "quote 0.3.15", 1289 | ] 1290 | 1291 | [[package]] 1292 | name = "percent-encoding" 1293 | version = "2.3.1" 1294 | source = "registry+https://github.com/rust-lang/crates.io-index" 1295 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1296 | 1297 | [[package]] 1298 | name = "pin-project-lite" 1299 | version = "0.2.16" 1300 | source = "registry+https://github.com/rust-lang/crates.io-index" 1301 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1302 | 1303 | [[package]] 1304 | name = "pin-utils" 1305 | version = "0.1.0" 1306 | source = "registry+https://github.com/rust-lang/crates.io-index" 1307 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1308 | 1309 | [[package]] 1310 | name = "pkg-config" 1311 | version = "0.3.32" 1312 | source = "registry+https://github.com/rust-lang/crates.io-index" 1313 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 1314 | 1315 | [[package]] 1316 | name = "powerfmt" 1317 | version = "0.2.0" 1318 | source = "registry+https://github.com/rust-lang/crates.io-index" 1319 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1320 | 1321 | [[package]] 1322 | name = "ppv-lite86" 1323 | version = "0.2.20" 1324 | source = "registry+https://github.com/rust-lang/crates.io-index" 1325 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 1326 | dependencies = [ 1327 | "zerocopy", 1328 | ] 1329 | 1330 | [[package]] 1331 | name = "proc-macro-error" 1332 | version = "1.0.4" 1333 | source = "registry+https://github.com/rust-lang/crates.io-index" 1334 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1335 | dependencies = [ 1336 | "proc-macro-error-attr", 1337 | "proc-macro2", 1338 | "quote 1.0.39", 1339 | "syn 1.0.109", 1340 | "version_check", 1341 | ] 1342 | 1343 | [[package]] 1344 | name = "proc-macro-error-attr" 1345 | version = "1.0.4" 1346 | source = "registry+https://github.com/rust-lang/crates.io-index" 1347 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1348 | dependencies = [ 1349 | "proc-macro2", 1350 | "quote 1.0.39", 1351 | "version_check", 1352 | ] 1353 | 1354 | [[package]] 1355 | name = "proc-macro2" 1356 | version = "1.0.94" 1357 | source = "registry+https://github.com/rust-lang/crates.io-index" 1358 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 1359 | dependencies = [ 1360 | "unicode-ident", 1361 | ] 1362 | 1363 | [[package]] 1364 | name = "quick-xml" 1365 | version = "0.36.2" 1366 | source = "registry+https://github.com/rust-lang/crates.io-index" 1367 | checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" 1368 | dependencies = [ 1369 | "memchr", 1370 | ] 1371 | 1372 | [[package]] 1373 | name = "quote" 1374 | version = "0.3.15" 1375 | source = "registry+https://github.com/rust-lang/crates.io-index" 1376 | checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" 1377 | 1378 | [[package]] 1379 | name = "quote" 1380 | version = "1.0.39" 1381 | source = "registry+https://github.com/rust-lang/crates.io-index" 1382 | checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" 1383 | dependencies = [ 1384 | "proc-macro2", 1385 | ] 1386 | 1387 | [[package]] 1388 | name = "rand" 1389 | version = "0.8.5" 1390 | source = "registry+https://github.com/rust-lang/crates.io-index" 1391 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1392 | dependencies = [ 1393 | "libc", 1394 | "rand_chacha", 1395 | "rand_core", 1396 | ] 1397 | 1398 | [[package]] 1399 | name = "rand_chacha" 1400 | version = "0.3.1" 1401 | source = "registry+https://github.com/rust-lang/crates.io-index" 1402 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1403 | dependencies = [ 1404 | "ppv-lite86", 1405 | "rand_core", 1406 | ] 1407 | 1408 | [[package]] 1409 | name = "rand_core" 1410 | version = "0.6.4" 1411 | source = "registry+https://github.com/rust-lang/crates.io-index" 1412 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1413 | dependencies = [ 1414 | "getrandom", 1415 | ] 1416 | 1417 | [[package]] 1418 | name = "range-traits" 1419 | version = "0.3.2" 1420 | source = "registry+https://github.com/rust-lang/crates.io-index" 1421 | checksum = "d20581732dd76fa913c7dff1a2412b714afe3573e94d41c34719de73337cc8ab" 1422 | 1423 | [[package]] 1424 | name = "redox_syscall" 1425 | version = "0.5.10" 1426 | source = "registry+https://github.com/rust-lang/crates.io-index" 1427 | checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" 1428 | dependencies = [ 1429 | "bitflags", 1430 | ] 1431 | 1432 | [[package]] 1433 | name = "regex" 1434 | version = "1.11.1" 1435 | source = "registry+https://github.com/rust-lang/crates.io-index" 1436 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1437 | dependencies = [ 1438 | "aho-corasick", 1439 | "memchr", 1440 | "regex-automata", 1441 | "regex-syntax", 1442 | ] 1443 | 1444 | [[package]] 1445 | name = "regex-automata" 1446 | version = "0.4.9" 1447 | source = "registry+https://github.com/rust-lang/crates.io-index" 1448 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1449 | dependencies = [ 1450 | "aho-corasick", 1451 | "memchr", 1452 | "regex-syntax", 1453 | ] 1454 | 1455 | [[package]] 1456 | name = "regex-lite" 1457 | version = "0.1.6" 1458 | source = "registry+https://github.com/rust-lang/crates.io-index" 1459 | checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" 1460 | 1461 | [[package]] 1462 | name = "regex-syntax" 1463 | version = "0.8.5" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1466 | 1467 | [[package]] 1468 | name = "resiter" 1469 | version = "0.5.0" 1470 | source = "registry+https://github.com/rust-lang/crates.io-index" 1471 | checksum = "cbc95d56eb1865f69288945759cc0879d60ee68168dce676730275804ad2b276" 1472 | 1473 | [[package]] 1474 | name = "rickview" 1475 | version = "0.4.0" 1476 | dependencies = [ 1477 | "actix-web", 1478 | "bytesize", 1479 | "config", 1480 | "const-fnv1a-hash", 1481 | "deepsize", 1482 | "env_logger", 1483 | "hdt", 1484 | "log", 1485 | "multimap", 1486 | "serde", 1487 | "serde_json", 1488 | "sophia", 1489 | "tinytemplate", 1490 | "ureq", 1491 | "zstd", 1492 | ] 1493 | 1494 | [[package]] 1495 | name = "ring" 1496 | version = "0.17.13" 1497 | source = "registry+https://github.com/rust-lang/crates.io-index" 1498 | checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee" 1499 | dependencies = [ 1500 | "cc", 1501 | "cfg-if", 1502 | "getrandom", 1503 | "libc", 1504 | "untrusted", 1505 | "windows-sys 0.52.0", 1506 | ] 1507 | 1508 | [[package]] 1509 | name = "rio_api" 1510 | version = "0.8.5" 1511 | source = "registry+https://github.com/rust-lang/crates.io-index" 1512 | checksum = "61d0c76ddf8b00cbb4d2c5932d067d49245c2f1f651809bde3cf265033ddb1af" 1513 | 1514 | [[package]] 1515 | name = "rio_turtle" 1516 | version = "0.8.5" 1517 | source = "registry+https://github.com/rust-lang/crates.io-index" 1518 | checksum = "d6f351b77353c7c896f0cd5ced2a25a7e95b5360cb68d1d7c16682ee096d7f40" 1519 | dependencies = [ 1520 | "oxilangtag", 1521 | "oxiri", 1522 | "rio_api", 1523 | ] 1524 | 1525 | [[package]] 1526 | name = "rio_xml" 1527 | version = "0.8.5" 1528 | source = "registry+https://github.com/rust-lang/crates.io-index" 1529 | checksum = "abd3384ae785ed3b0159607adc08adef580a28e277fbfa375c42d162e9da93b1" 1530 | dependencies = [ 1531 | "oxilangtag", 1532 | "oxiri", 1533 | "quick-xml", 1534 | "rio_api", 1535 | ] 1536 | 1537 | [[package]] 1538 | name = "rustc-demangle" 1539 | version = "0.1.24" 1540 | source = "registry+https://github.com/rust-lang/crates.io-index" 1541 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1542 | 1543 | [[package]] 1544 | name = "rustc_version" 1545 | version = "0.4.1" 1546 | source = "registry+https://github.com/rust-lang/crates.io-index" 1547 | checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 1548 | dependencies = [ 1549 | "semver", 1550 | ] 1551 | 1552 | [[package]] 1553 | name = "rustls" 1554 | version = "0.23.23" 1555 | source = "registry+https://github.com/rust-lang/crates.io-index" 1556 | checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" 1557 | dependencies = [ 1558 | "log", 1559 | "once_cell", 1560 | "ring", 1561 | "rustls-pki-types", 1562 | "rustls-webpki", 1563 | "subtle", 1564 | "zeroize", 1565 | ] 1566 | 1567 | [[package]] 1568 | name = "rustls-pemfile" 1569 | version = "2.2.0" 1570 | source = "registry+https://github.com/rust-lang/crates.io-index" 1571 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 1572 | dependencies = [ 1573 | "rustls-pki-types", 1574 | ] 1575 | 1576 | [[package]] 1577 | name = "rustls-pki-types" 1578 | version = "1.11.0" 1579 | source = "registry+https://github.com/rust-lang/crates.io-index" 1580 | checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" 1581 | 1582 | [[package]] 1583 | name = "rustls-webpki" 1584 | version = "0.102.8" 1585 | source = "registry+https://github.com/rust-lang/crates.io-index" 1586 | checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" 1587 | dependencies = [ 1588 | "ring", 1589 | "rustls-pki-types", 1590 | "untrusted", 1591 | ] 1592 | 1593 | [[package]] 1594 | name = "ryu" 1595 | version = "1.0.20" 1596 | source = "registry+https://github.com/rust-lang/crates.io-index" 1597 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 1598 | 1599 | [[package]] 1600 | name = "scopeguard" 1601 | version = "1.2.0" 1602 | source = "registry+https://github.com/rust-lang/crates.io-index" 1603 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1604 | 1605 | [[package]] 1606 | name = "semver" 1607 | version = "1.0.26" 1608 | source = "registry+https://github.com/rust-lang/crates.io-index" 1609 | checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" 1610 | 1611 | [[package]] 1612 | name = "serde" 1613 | version = "1.0.218" 1614 | source = "registry+https://github.com/rust-lang/crates.io-index" 1615 | checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" 1616 | dependencies = [ 1617 | "serde_derive", 1618 | ] 1619 | 1620 | [[package]] 1621 | name = "serde_derive" 1622 | version = "1.0.218" 1623 | source = "registry+https://github.com/rust-lang/crates.io-index" 1624 | checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" 1625 | dependencies = [ 1626 | "proc-macro2", 1627 | "quote 1.0.39", 1628 | "syn 2.0.99", 1629 | ] 1630 | 1631 | [[package]] 1632 | name = "serde_json" 1633 | version = "1.0.140" 1634 | source = "registry+https://github.com/rust-lang/crates.io-index" 1635 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 1636 | dependencies = [ 1637 | "itoa", 1638 | "memchr", 1639 | "ryu", 1640 | "serde", 1641 | ] 1642 | 1643 | [[package]] 1644 | name = "serde_spanned" 1645 | version = "0.6.8" 1646 | source = "registry+https://github.com/rust-lang/crates.io-index" 1647 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 1648 | dependencies = [ 1649 | "serde", 1650 | ] 1651 | 1652 | [[package]] 1653 | name = "serde_urlencoded" 1654 | version = "0.7.1" 1655 | source = "registry+https://github.com/rust-lang/crates.io-index" 1656 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1657 | dependencies = [ 1658 | "form_urlencoded", 1659 | "itoa", 1660 | "ryu", 1661 | "serde", 1662 | ] 1663 | 1664 | [[package]] 1665 | name = "sha1" 1666 | version = "0.10.6" 1667 | source = "registry+https://github.com/rust-lang/crates.io-index" 1668 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 1669 | dependencies = [ 1670 | "cfg-if", 1671 | "cpufeatures", 1672 | "digest", 1673 | ] 1674 | 1675 | [[package]] 1676 | name = "sha2" 1677 | version = "0.10.8" 1678 | source = "registry+https://github.com/rust-lang/crates.io-index" 1679 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 1680 | dependencies = [ 1681 | "cfg-if", 1682 | "cpufeatures", 1683 | "digest", 1684 | ] 1685 | 1686 | [[package]] 1687 | name = "shlex" 1688 | version = "1.3.0" 1689 | source = "registry+https://github.com/rust-lang/crates.io-index" 1690 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1691 | 1692 | [[package]] 1693 | name = "signal-hook-registry" 1694 | version = "1.4.2" 1695 | source = "registry+https://github.com/rust-lang/crates.io-index" 1696 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1697 | dependencies = [ 1698 | "libc", 1699 | ] 1700 | 1701 | [[package]] 1702 | name = "slab" 1703 | version = "0.4.9" 1704 | source = "registry+https://github.com/rust-lang/crates.io-index" 1705 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1706 | dependencies = [ 1707 | "autocfg", 1708 | ] 1709 | 1710 | [[package]] 1711 | name = "smallvec" 1712 | version = "1.14.0" 1713 | source = "registry+https://github.com/rust-lang/crates.io-index" 1714 | checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" 1715 | 1716 | [[package]] 1717 | name = "socket2" 1718 | version = "0.5.8" 1719 | source = "registry+https://github.com/rust-lang/crates.io-index" 1720 | checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" 1721 | dependencies = [ 1722 | "libc", 1723 | "windows-sys 0.52.0", 1724 | ] 1725 | 1726 | [[package]] 1727 | name = "sophia" 1728 | version = "0.9.0" 1729 | source = "registry+https://github.com/rust-lang/crates.io-index" 1730 | checksum = "9fe9928a3014b591068c7b8eb143d996a917f06119b3a64f07694830c640783b" 1731 | dependencies = [ 1732 | "sophia_api", 1733 | "sophia_c14n", 1734 | "sophia_inmem", 1735 | "sophia_iri", 1736 | "sophia_isomorphism", 1737 | "sophia_resource", 1738 | "sophia_rio", 1739 | "sophia_term", 1740 | "sophia_turtle", 1741 | "sophia_xml", 1742 | ] 1743 | 1744 | [[package]] 1745 | name = "sophia_api" 1746 | version = "0.9.0" 1747 | source = "registry+https://github.com/rust-lang/crates.io-index" 1748 | checksum = "103a4138290bec38b9b10e0682b613173a102bca9fd2a74b3db25346e22599a3" 1749 | dependencies = [ 1750 | "lazy_static", 1751 | "mownstr", 1752 | "regex", 1753 | "resiter", 1754 | "serde", 1755 | "sophia_iri", 1756 | "thiserror 2.0.12", 1757 | ] 1758 | 1759 | [[package]] 1760 | name = "sophia_c14n" 1761 | version = "0.9.0" 1762 | source = "registry+https://github.com/rust-lang/crates.io-index" 1763 | checksum = "83f6facc93703fcb7fa773b0ff71e0ee7d937157c5c22ae2aa6c95b5cf834949" 1764 | dependencies = [ 1765 | "log", 1766 | "sha2", 1767 | "sophia_api", 1768 | "sophia_iri", 1769 | "thiserror 2.0.12", 1770 | ] 1771 | 1772 | [[package]] 1773 | name = "sophia_inmem" 1774 | version = "0.9.0" 1775 | source = "registry+https://github.com/rust-lang/crates.io-index" 1776 | checksum = "ebacba4fa7baed53f89844a5c9e5962d6232a449d5b450b9de72bb67f0203332" 1777 | dependencies = [ 1778 | "sophia_api", 1779 | "thiserror 2.0.12", 1780 | ] 1781 | 1782 | [[package]] 1783 | name = "sophia_iri" 1784 | version = "0.9.0" 1785 | source = "registry+https://github.com/rust-lang/crates.io-index" 1786 | checksum = "7675ff44ad920ac07fde1b61ff20d3c832d8cb65395416906df90b76631ea95f" 1787 | dependencies = [ 1788 | "lazy_static", 1789 | "oxiri", 1790 | "regex", 1791 | "serde", 1792 | "thiserror 2.0.12", 1793 | ] 1794 | 1795 | [[package]] 1796 | name = "sophia_isomorphism" 1797 | version = "0.9.0" 1798 | source = "registry+https://github.com/rust-lang/crates.io-index" 1799 | checksum = "e85205f57b26b7f35c4a7691eaeefdb7c44e392515e21f745e9ab48acb12ff11" 1800 | dependencies = [ 1801 | "sophia_api", 1802 | "sophia_iri", 1803 | ] 1804 | 1805 | [[package]] 1806 | name = "sophia_resource" 1807 | version = "0.9.0" 1808 | source = "registry+https://github.com/rust-lang/crates.io-index" 1809 | checksum = "6ddbf91aae6959a6d88bcd6e15579d8256fc5ae0a980486bab18c182e33c524b" 1810 | dependencies = [ 1811 | "sophia_api", 1812 | "sophia_iri", 1813 | "sophia_turtle", 1814 | "sophia_xml", 1815 | "thiserror 2.0.12", 1816 | ] 1817 | 1818 | [[package]] 1819 | name = "sophia_rio" 1820 | version = "0.9.0" 1821 | source = "registry+https://github.com/rust-lang/crates.io-index" 1822 | checksum = "57a2938da8eeb8645ff616e64ac99af8099772c3e22a955ae5669ceac5372c34" 1823 | dependencies = [ 1824 | "rio_api", 1825 | "sophia_api", 1826 | "sophia_iri", 1827 | ] 1828 | 1829 | [[package]] 1830 | name = "sophia_term" 1831 | version = "0.9.0" 1832 | source = "registry+https://github.com/rust-lang/crates.io-index" 1833 | checksum = "51f4c42480d50d14ac7128ad738d28b68368938cb6f507c9505f68875fd0e4db" 1834 | dependencies = [ 1835 | "lazy_static", 1836 | "sophia_api", 1837 | ] 1838 | 1839 | [[package]] 1840 | name = "sophia_turtle" 1841 | version = "0.9.0" 1842 | source = "registry+https://github.com/rust-lang/crates.io-index" 1843 | checksum = "e9ff316c00bed741ba431b8533b2ce08e089ca031c742d9b6cccdf01b7f6ef2d" 1844 | dependencies = [ 1845 | "lazy_static", 1846 | "oxiri", 1847 | "regex", 1848 | "rio_turtle", 1849 | "sophia_api", 1850 | "sophia_iri", 1851 | "sophia_rio", 1852 | ] 1853 | 1854 | [[package]] 1855 | name = "sophia_xml" 1856 | version = "0.9.0" 1857 | source = "registry+https://github.com/rust-lang/crates.io-index" 1858 | checksum = "a2eaaa067bace76e15f0231b24a504e7f6a38e04b7900cef69e0b9deeb11a8bf" 1859 | dependencies = [ 1860 | "oxiri", 1861 | "rio_xml", 1862 | "sophia_api", 1863 | "sophia_iri", 1864 | "sophia_rio", 1865 | ] 1866 | 1867 | [[package]] 1868 | name = "stable_deref_trait" 1869 | version = "1.2.0" 1870 | source = "registry+https://github.com/rust-lang/crates.io-index" 1871 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1872 | 1873 | [[package]] 1874 | name = "static-regular-grammar" 1875 | version = "2.0.2" 1876 | source = "registry+https://github.com/rust-lang/crates.io-index" 1877 | checksum = "4f4a6c40247579acfbb138c3cd7de3dab113ab4ac6227f1b7de7d626ee667957" 1878 | dependencies = [ 1879 | "abnf", 1880 | "btree-range-map", 1881 | "ciborium", 1882 | "hex_fmt", 1883 | "indoc", 1884 | "proc-macro-error", 1885 | "proc-macro2", 1886 | "quote 1.0.39", 1887 | "serde", 1888 | "sha2", 1889 | "syn 2.0.99", 1890 | "thiserror 1.0.69", 1891 | ] 1892 | 1893 | [[package]] 1894 | name = "subtle" 1895 | version = "2.6.1" 1896 | source = "registry+https://github.com/rust-lang/crates.io-index" 1897 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1898 | 1899 | [[package]] 1900 | name = "sucds" 1901 | version = "0.8.1" 1902 | source = "registry+https://github.com/rust-lang/crates.io-index" 1903 | checksum = "d53d46182afe6ed822a94c54a532dc0d59691a8f49226bdc4596529ca864cdd6" 1904 | dependencies = [ 1905 | "anyhow", 1906 | "num-traits", 1907 | ] 1908 | 1909 | [[package]] 1910 | name = "syn" 1911 | version = "1.0.109" 1912 | source = "registry+https://github.com/rust-lang/crates.io-index" 1913 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1914 | dependencies = [ 1915 | "proc-macro2", 1916 | "unicode-ident", 1917 | ] 1918 | 1919 | [[package]] 1920 | name = "syn" 1921 | version = "2.0.99" 1922 | source = "registry+https://github.com/rust-lang/crates.io-index" 1923 | checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" 1924 | dependencies = [ 1925 | "proc-macro2", 1926 | "quote 1.0.39", 1927 | "unicode-ident", 1928 | ] 1929 | 1930 | [[package]] 1931 | name = "synstructure" 1932 | version = "0.13.1" 1933 | source = "registry+https://github.com/rust-lang/crates.io-index" 1934 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 1935 | dependencies = [ 1936 | "proc-macro2", 1937 | "quote 1.0.39", 1938 | "syn 2.0.99", 1939 | ] 1940 | 1941 | [[package]] 1942 | name = "thiserror" 1943 | version = "1.0.69" 1944 | source = "registry+https://github.com/rust-lang/crates.io-index" 1945 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1946 | dependencies = [ 1947 | "thiserror-impl 1.0.69", 1948 | ] 1949 | 1950 | [[package]] 1951 | name = "thiserror" 1952 | version = "2.0.12" 1953 | source = "registry+https://github.com/rust-lang/crates.io-index" 1954 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 1955 | dependencies = [ 1956 | "thiserror-impl 2.0.12", 1957 | ] 1958 | 1959 | [[package]] 1960 | name = "thiserror-impl" 1961 | version = "1.0.69" 1962 | source = "registry+https://github.com/rust-lang/crates.io-index" 1963 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1964 | dependencies = [ 1965 | "proc-macro2", 1966 | "quote 1.0.39", 1967 | "syn 2.0.99", 1968 | ] 1969 | 1970 | [[package]] 1971 | name = "thiserror-impl" 1972 | version = "2.0.12" 1973 | source = "registry+https://github.com/rust-lang/crates.io-index" 1974 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 1975 | dependencies = [ 1976 | "proc-macro2", 1977 | "quote 1.0.39", 1978 | "syn 2.0.99", 1979 | ] 1980 | 1981 | [[package]] 1982 | name = "time" 1983 | version = "0.3.39" 1984 | source = "registry+https://github.com/rust-lang/crates.io-index" 1985 | checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" 1986 | dependencies = [ 1987 | "deranged", 1988 | "itoa", 1989 | "num-conv", 1990 | "powerfmt", 1991 | "serde", 1992 | "time-core", 1993 | "time-macros", 1994 | ] 1995 | 1996 | [[package]] 1997 | name = "time-core" 1998 | version = "0.1.3" 1999 | source = "registry+https://github.com/rust-lang/crates.io-index" 2000 | checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" 2001 | 2002 | [[package]] 2003 | name = "time-macros" 2004 | version = "0.2.20" 2005 | source = "registry+https://github.com/rust-lang/crates.io-index" 2006 | checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" 2007 | dependencies = [ 2008 | "num-conv", 2009 | "time-core", 2010 | ] 2011 | 2012 | [[package]] 2013 | name = "tinystr" 2014 | version = "0.7.6" 2015 | source = "registry+https://github.com/rust-lang/crates.io-index" 2016 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 2017 | dependencies = [ 2018 | "displaydoc", 2019 | "zerovec", 2020 | ] 2021 | 2022 | [[package]] 2023 | name = "tinytemplate" 2024 | version = "1.2.1" 2025 | source = "registry+https://github.com/rust-lang/crates.io-index" 2026 | checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" 2027 | dependencies = [ 2028 | "serde", 2029 | "serde_json", 2030 | ] 2031 | 2032 | [[package]] 2033 | name = "tokio" 2034 | version = "1.43.0" 2035 | source = "registry+https://github.com/rust-lang/crates.io-index" 2036 | checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" 2037 | dependencies = [ 2038 | "backtrace", 2039 | "bytes", 2040 | "libc", 2041 | "mio", 2042 | "parking_lot", 2043 | "pin-project-lite", 2044 | "signal-hook-registry", 2045 | "socket2", 2046 | "windows-sys 0.52.0", 2047 | ] 2048 | 2049 | [[package]] 2050 | name = "tokio-util" 2051 | version = "0.7.13" 2052 | source = "registry+https://github.com/rust-lang/crates.io-index" 2053 | checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" 2054 | dependencies = [ 2055 | "bytes", 2056 | "futures-core", 2057 | "futures-sink", 2058 | "pin-project-lite", 2059 | "tokio", 2060 | ] 2061 | 2062 | [[package]] 2063 | name = "toml" 2064 | version = "0.8.20" 2065 | source = "registry+https://github.com/rust-lang/crates.io-index" 2066 | checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" 2067 | dependencies = [ 2068 | "serde", 2069 | "serde_spanned", 2070 | "toml_datetime", 2071 | "toml_edit", 2072 | ] 2073 | 2074 | [[package]] 2075 | name = "toml_datetime" 2076 | version = "0.6.8" 2077 | source = "registry+https://github.com/rust-lang/crates.io-index" 2078 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 2079 | dependencies = [ 2080 | "serde", 2081 | ] 2082 | 2083 | [[package]] 2084 | name = "toml_edit" 2085 | version = "0.22.24" 2086 | source = "registry+https://github.com/rust-lang/crates.io-index" 2087 | checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" 2088 | dependencies = [ 2089 | "indexmap", 2090 | "serde", 2091 | "serde_spanned", 2092 | "toml_datetime", 2093 | "winnow", 2094 | ] 2095 | 2096 | [[package]] 2097 | name = "tracing" 2098 | version = "0.1.41" 2099 | source = "registry+https://github.com/rust-lang/crates.io-index" 2100 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 2101 | dependencies = [ 2102 | "log", 2103 | "pin-project-lite", 2104 | "tracing-core", 2105 | ] 2106 | 2107 | [[package]] 2108 | name = "tracing-core" 2109 | version = "0.1.33" 2110 | source = "registry+https://github.com/rust-lang/crates.io-index" 2111 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 2112 | dependencies = [ 2113 | "once_cell", 2114 | ] 2115 | 2116 | [[package]] 2117 | name = "typenum" 2118 | version = "1.18.0" 2119 | source = "registry+https://github.com/rust-lang/crates.io-index" 2120 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 2121 | 2122 | [[package]] 2123 | name = "unicode-ident" 2124 | version = "1.0.18" 2125 | source = "registry+https://github.com/rust-lang/crates.io-index" 2126 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 2127 | 2128 | [[package]] 2129 | name = "untrusted" 2130 | version = "0.9.0" 2131 | source = "registry+https://github.com/rust-lang/crates.io-index" 2132 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 2133 | 2134 | [[package]] 2135 | name = "ureq" 2136 | version = "3.0.8" 2137 | source = "registry+https://github.com/rust-lang/crates.io-index" 2138 | checksum = "06f78313c985f2fba11100dd06d60dd402d0cabb458af4d94791b8e09c025323" 2139 | dependencies = [ 2140 | "base64", 2141 | "flate2", 2142 | "log", 2143 | "percent-encoding", 2144 | "rustls", 2145 | "rustls-pemfile", 2146 | "rustls-pki-types", 2147 | "ureq-proto", 2148 | "utf-8", 2149 | "webpki-roots", 2150 | ] 2151 | 2152 | [[package]] 2153 | name = "ureq-proto" 2154 | version = "0.3.3" 2155 | source = "registry+https://github.com/rust-lang/crates.io-index" 2156 | checksum = "64adb55464bad1ab1aa9229133d0d59d2f679180f4d15f0d9debe616f541f25e" 2157 | dependencies = [ 2158 | "base64", 2159 | "http 1.2.0", 2160 | "httparse", 2161 | "log", 2162 | ] 2163 | 2164 | [[package]] 2165 | name = "url" 2166 | version = "2.5.4" 2167 | source = "registry+https://github.com/rust-lang/crates.io-index" 2168 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 2169 | dependencies = [ 2170 | "form_urlencoded", 2171 | "idna", 2172 | "percent-encoding", 2173 | ] 2174 | 2175 | [[package]] 2176 | name = "utf-8" 2177 | version = "0.7.6" 2178 | source = "registry+https://github.com/rust-lang/crates.io-index" 2179 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 2180 | 2181 | [[package]] 2182 | name = "utf16_iter" 2183 | version = "1.0.5" 2184 | source = "registry+https://github.com/rust-lang/crates.io-index" 2185 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 2186 | 2187 | [[package]] 2188 | name = "utf8-decode" 2189 | version = "1.0.1" 2190 | source = "registry+https://github.com/rust-lang/crates.io-index" 2191 | checksum = "ca61eb27fa339aa08826a29f03e87b99b4d8f0fc2255306fd266bb1b6a9de498" 2192 | 2193 | [[package]] 2194 | name = "utf8_iter" 2195 | version = "1.0.4" 2196 | source = "registry+https://github.com/rust-lang/crates.io-index" 2197 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2198 | 2199 | [[package]] 2200 | name = "utf8parse" 2201 | version = "0.2.2" 2202 | source = "registry+https://github.com/rust-lang/crates.io-index" 2203 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 2204 | 2205 | [[package]] 2206 | name = "version_check" 2207 | version = "0.9.5" 2208 | source = "registry+https://github.com/rust-lang/crates.io-index" 2209 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 2210 | 2211 | [[package]] 2212 | name = "wasi" 2213 | version = "0.11.0+wasi-snapshot-preview1" 2214 | source = "registry+https://github.com/rust-lang/crates.io-index" 2215 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2216 | 2217 | [[package]] 2218 | name = "webpki-roots" 2219 | version = "0.26.8" 2220 | source = "registry+https://github.com/rust-lang/crates.io-index" 2221 | checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" 2222 | dependencies = [ 2223 | "rustls-pki-types", 2224 | ] 2225 | 2226 | [[package]] 2227 | name = "windows-sys" 2228 | version = "0.52.0" 2229 | source = "registry+https://github.com/rust-lang/crates.io-index" 2230 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2231 | dependencies = [ 2232 | "windows-targets", 2233 | ] 2234 | 2235 | [[package]] 2236 | name = "windows-sys" 2237 | version = "0.59.0" 2238 | source = "registry+https://github.com/rust-lang/crates.io-index" 2239 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2240 | dependencies = [ 2241 | "windows-targets", 2242 | ] 2243 | 2244 | [[package]] 2245 | name = "windows-targets" 2246 | version = "0.52.6" 2247 | source = "registry+https://github.com/rust-lang/crates.io-index" 2248 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2249 | dependencies = [ 2250 | "windows_aarch64_gnullvm", 2251 | "windows_aarch64_msvc", 2252 | "windows_i686_gnu", 2253 | "windows_i686_gnullvm", 2254 | "windows_i686_msvc", 2255 | "windows_x86_64_gnu", 2256 | "windows_x86_64_gnullvm", 2257 | "windows_x86_64_msvc", 2258 | ] 2259 | 2260 | [[package]] 2261 | name = "windows_aarch64_gnullvm" 2262 | version = "0.52.6" 2263 | source = "registry+https://github.com/rust-lang/crates.io-index" 2264 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2265 | 2266 | [[package]] 2267 | name = "windows_aarch64_msvc" 2268 | version = "0.52.6" 2269 | source = "registry+https://github.com/rust-lang/crates.io-index" 2270 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2271 | 2272 | [[package]] 2273 | name = "windows_i686_gnu" 2274 | version = "0.52.6" 2275 | source = "registry+https://github.com/rust-lang/crates.io-index" 2276 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2277 | 2278 | [[package]] 2279 | name = "windows_i686_gnullvm" 2280 | version = "0.52.6" 2281 | source = "registry+https://github.com/rust-lang/crates.io-index" 2282 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2283 | 2284 | [[package]] 2285 | name = "windows_i686_msvc" 2286 | version = "0.52.6" 2287 | source = "registry+https://github.com/rust-lang/crates.io-index" 2288 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2289 | 2290 | [[package]] 2291 | name = "windows_x86_64_gnu" 2292 | version = "0.52.6" 2293 | source = "registry+https://github.com/rust-lang/crates.io-index" 2294 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2295 | 2296 | [[package]] 2297 | name = "windows_x86_64_gnullvm" 2298 | version = "0.52.6" 2299 | source = "registry+https://github.com/rust-lang/crates.io-index" 2300 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2301 | 2302 | [[package]] 2303 | name = "windows_x86_64_msvc" 2304 | version = "0.52.6" 2305 | source = "registry+https://github.com/rust-lang/crates.io-index" 2306 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2307 | 2308 | [[package]] 2309 | name = "winnow" 2310 | version = "0.7.3" 2311 | source = "registry+https://github.com/rust-lang/crates.io-index" 2312 | checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" 2313 | dependencies = [ 2314 | "memchr", 2315 | ] 2316 | 2317 | [[package]] 2318 | name = "write16" 2319 | version = "1.0.0" 2320 | source = "registry+https://github.com/rust-lang/crates.io-index" 2321 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 2322 | 2323 | [[package]] 2324 | name = "writeable" 2325 | version = "0.5.5" 2326 | source = "registry+https://github.com/rust-lang/crates.io-index" 2327 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 2328 | 2329 | [[package]] 2330 | name = "yoke" 2331 | version = "0.7.5" 2332 | source = "registry+https://github.com/rust-lang/crates.io-index" 2333 | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" 2334 | dependencies = [ 2335 | "serde", 2336 | "stable_deref_trait", 2337 | "yoke-derive", 2338 | "zerofrom", 2339 | ] 2340 | 2341 | [[package]] 2342 | name = "yoke-derive" 2343 | version = "0.7.5" 2344 | source = "registry+https://github.com/rust-lang/crates.io-index" 2345 | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" 2346 | dependencies = [ 2347 | "proc-macro2", 2348 | "quote 1.0.39", 2349 | "syn 2.0.99", 2350 | "synstructure", 2351 | ] 2352 | 2353 | [[package]] 2354 | name = "zerocopy" 2355 | version = "0.7.35" 2356 | source = "registry+https://github.com/rust-lang/crates.io-index" 2357 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 2358 | dependencies = [ 2359 | "byteorder", 2360 | "zerocopy-derive", 2361 | ] 2362 | 2363 | [[package]] 2364 | name = "zerocopy-derive" 2365 | version = "0.7.35" 2366 | source = "registry+https://github.com/rust-lang/crates.io-index" 2367 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 2368 | dependencies = [ 2369 | "proc-macro2", 2370 | "quote 1.0.39", 2371 | "syn 2.0.99", 2372 | ] 2373 | 2374 | [[package]] 2375 | name = "zerofrom" 2376 | version = "0.1.6" 2377 | source = "registry+https://github.com/rust-lang/crates.io-index" 2378 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 2379 | dependencies = [ 2380 | "zerofrom-derive", 2381 | ] 2382 | 2383 | [[package]] 2384 | name = "zerofrom-derive" 2385 | version = "0.1.6" 2386 | source = "registry+https://github.com/rust-lang/crates.io-index" 2387 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 2388 | dependencies = [ 2389 | "proc-macro2", 2390 | "quote 1.0.39", 2391 | "syn 2.0.99", 2392 | "synstructure", 2393 | ] 2394 | 2395 | [[package]] 2396 | name = "zeroize" 2397 | version = "1.8.1" 2398 | source = "registry+https://github.com/rust-lang/crates.io-index" 2399 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2400 | 2401 | [[package]] 2402 | name = "zerovec" 2403 | version = "0.10.4" 2404 | source = "registry+https://github.com/rust-lang/crates.io-index" 2405 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 2406 | dependencies = [ 2407 | "yoke", 2408 | "zerofrom", 2409 | "zerovec-derive", 2410 | ] 2411 | 2412 | [[package]] 2413 | name = "zerovec-derive" 2414 | version = "0.10.3" 2415 | source = "registry+https://github.com/rust-lang/crates.io-index" 2416 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 2417 | dependencies = [ 2418 | "proc-macro2", 2419 | "quote 1.0.39", 2420 | "syn 2.0.99", 2421 | ] 2422 | 2423 | [[package]] 2424 | name = "zstd" 2425 | version = "0.13.3" 2426 | source = "registry+https://github.com/rust-lang/crates.io-index" 2427 | checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" 2428 | dependencies = [ 2429 | "zstd-safe", 2430 | ] 2431 | 2432 | [[package]] 2433 | name = "zstd-safe" 2434 | version = "7.2.3" 2435 | source = "registry+https://github.com/rust-lang/crates.io-index" 2436 | checksum = "f3051792fbdc2e1e143244dc28c60f73d8470e93f3f9cbd0ead44da5ed802722" 2437 | dependencies = [ 2438 | "zstd-sys", 2439 | ] 2440 | 2441 | [[package]] 2442 | name = "zstd-sys" 2443 | version = "2.0.14+zstd.1.5.7" 2444 | source = "registry+https://github.com/rust-lang/crates.io-index" 2445 | checksum = "8fb060d4926e4ac3a3ad15d864e99ceb5f343c6b34f5bd6d81ae6ed417311be5" 2446 | dependencies = [ 2447 | "cc", 2448 | "pkg-config", 2449 | ] 2450 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rickview" 3 | version = "0.4.0" 4 | edition = "2024" 5 | license = "MIT" 6 | keywords = ["rdf", "semantic-web", "linked-data"] 7 | categories = ["web-programming::http-server"] 8 | description = "A fast RDF viewer (Linked Data browser)" 9 | authors = ["Konrad Höffner"] 10 | readme = "README.md" 11 | repository = "https://github.com/konradhoeffner/rickview" 12 | rust-version = "1.85" 13 | 14 | [dependencies] 15 | sophia = "0.9" 16 | actix-web = "4" 17 | multimap = "0.10" 18 | tinytemplate = "1" 19 | serde = { version = "1", features = ["derive"] } 20 | config = { version = "0.15", default-features = false, features = ["toml"] } 21 | log = "0.4" 22 | env_logger = { version = "0.11", default-features = false, features = ["auto-color"] } 23 | hdt = { version = "0.3", optional = true } 24 | deepsize = { version = "0.2", default-features = false, features = ["std"] } 25 | bytesize = "2" 26 | zstd = { version = "0.13", features = ["zstdmt"] } 27 | ureq = "3" 28 | const-fnv1a-hash = "1" 29 | serde_json = "1" 30 | 31 | [features] 32 | default = ["rdfxml", "hdt"] 33 | rdfxml = ["sophia/xml"] 34 | hdt = ["dep:hdt"] 35 | 36 | [profile.release] 37 | # see https://fasterthanli.me/articles/why-is-my-rust-build-so-slow 38 | lto = "thin" 39 | # when profiling, set debug to 1 and strip to false 40 | #debug = 1 41 | strip = true 42 | incremental = true # disable in CI with CARGO_INCREMENTAL=0 43 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef 3 | WORKDIR /app 4 | 5 | FROM chef AS planner 6 | ARG CARGO_INCREMENTAL=0 7 | COPY . . 8 | RUN cargo chef prepare --recipe-path recipe.json 9 | 10 | FROM chef AS builder 11 | ARG CARGO_INCREMENTAL=0 12 | COPY --link --from=planner /app/recipe.json recipe.json 13 | RUN cargo chef cook --release --recipe-path recipe.json 14 | COPY . . 15 | RUN cargo build --release --bin rickview 16 | 17 | FROM chainguard/wolfi-base AS runtime 18 | COPY --link --from=builder /app/target/release/rickview /usr/local/bin/rickview 19 | WORKDIR /app 20 | CMD ["/usr/local/bin/rickview"] 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Konrad Höffner 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RickView 2 | 3 | [![Latest Version](https://img.shields.io/crates/v/rickview.svg)](https://crates.io/crates/rickview) 4 | [![Lint and Build](https://github.com/konradhoeffner/rickview/actions/workflows/lint_and_build.yml/badge.svg)](https://github.com/konradhoeffner/rickview/actions/workflows/lint_and_build.yml) 5 | ![Unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg "Unsafe forbidden") 6 | [![RickView @ Leipzig Semantic Web Day 2023 Video](https://img.shields.io/badge/video-8A2BE2)](https://www.youtube.com/watch?v=n8Kb2P8ilwg&t=8250s) 7 | [![SaxFDM Open Data Award 2024](https://img.shields.io/badge/Open_Data_Award-118811)](https://saxfdm.de/wrapup/) 8 | [![DOI](https://zenodo.org/badge/472769298.svg)](https://zenodo.org/badge/latestdoi/472769298) 9 | 10 | Easy to deploy low-resource stand-alone RDF knowledge graph browser written in Rust. 11 | No SPARQL endpoint needed! 12 | See also the unpublished [paper draft](https://github.com/KonradHoeffner/rickview/releases/download/0.0.2/paper.pdf). 13 | Layout copied from [LodView](https://github.com/LodLive/LodView). 14 | 15 | ## Current deployments 16 | Feel free to browse around! 17 | 18 | * [SNIK — Semantic Network of Hospital Information Management](https://www.snik.eu/ontology/bb) 19 | * [HITO — Health IT Ontology](https://hitontology.eu/ontology/) 20 | * [ANNO — Anthropological Notation Ontology](https://annosaxfdm.de/ontology/) 21 | * [LinkedSpending](https://linkedspending.aksw.org/) 22 | 23 | 24 | ## Requirements 25 | 26 | |CPU Architecture |OS |Compiles|Release|Docker | 27 | |--------------------|-------------|--------|-------|-------------------------| 28 | |AMD64 (x86-64) |Linux GNU |yes |yes |yes | 29 | |AMD64 (x86-64) |Linux MUSL |yes |yes |not anymore due to DNS problems| 30 | |AMD64 (x86-64) |Apple Darwin |yes |yes |use a Linux image | 31 | |AMD64 (x86-64) |Windows |yes |yes |use a Linux image | 32 | |ARM64 (AArch64) |Linux |yes |yes |yes | 33 | |IA-32 (i386, 32 Bit)| |no||| 34 | 35 | ## Docker 36 | 37 | Try it out with the example knowledge base: 38 | 39 | docker run --rm -p 8080:8080 ghcr.io/konradhoeffner/rickview 40 | 41 | ### Docker Compose Example 42 | 43 | services: 44 | rickview: 45 | image: ghcr.io/konradhoeffner/rickview 46 | environment: 47 | - RICKVIEW_KB_FILE=https://raw.githubusercontent.com/hitontology/ontology/dist/all.ttl 48 | - RICKVIEW_NAMESPACE=http://hitontology.eu/ontology/ 49 | - RICKVIEW_BASE=/ontology 50 | - RICKVIEW_TITLE=HITO 51 | - RICKVIEW_SUBTITLE=Health IT Ontology 52 | - RICKVIEW_EXAMPLES=Study SoftwareProduct ApplicationSystemTypeCatalogue 53 | - RICKVIEW_HOMEPAGE=https://hitontology.eu 54 | - RICKVIEW_ENDPOINT=https://hitontology.eu/sparql 55 | - RICKVIEW_GITHUB=https://github.com/hitontology/ontology 56 | - RICKVIEW_DOC=https://hitontology.github.io/ontology/ 57 | ports: 58 | - "127.0.0.1:8080:8080" 59 | restart: unless-stopped 60 | 61 | ## Precompiled Binaries 62 | 63 | Download the binary from the [latest release](https://github.com/konradhoeffner/rickview/releases/latest) and run `rickview`. 64 | If you need binaries for a different operating system and CPU architecture combination, [let me know](https://github.com/konradhoeffner/rickview/issues/new). 65 | 66 | ## Compile it yourself 67 | Alternatively, you can compile it for your own platform with `cargo install rickview`. 68 | Or you can clone [the repository](https://github.com/konradhoeffner/rickview) and then `cargo build`. 69 | This requires you to install [Rust including Cargo](https://www.rust-lang.org/tools/install). 70 | 71 | ## Configure 72 | Default configuration is stored in `data/default.toml`, which you can override with a custom `data/config.toml` or environment variables. 73 | Configuration keys are in lower\_snake\_case, while environment variables are prefixed with RICKVIEW\_ and are in SCREAMING\_SNAKE\_CASE. 74 | For example, `namespace = "http://hitontology.eu/ontology/"` in `config.toml` is equivalent to `RICKVIEW_NAMESPACE=http://hitontology.eu/ontology/` as an environment variable. 75 | You need to provide a knowledge base file path or URL, the default is `data/kb.ttl`. 76 | If you don't, RickView will show a minimal example knowledge base. 77 | You can add custom HTML to the index page by adding a `data/body.html` file. 78 | You can add embedded CSS using the `css` environment variable. 79 | By default, the *Roboto* font is used which RickView hosts locally for robustness, speed and to prevent conflicts with European privacy laws. 80 | If this is not an issue for you and, for example, you want to display Chinese or Japanese characters, you could import a Google Font: 81 | 82 | css = "@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300&display=swap'); body {font-family: 'Noto Sans SC', sans-serif}" 83 | 84 | Compile and run with `cargo run` and then open in your browser. 85 | 86 | ## Supported File Formats 87 | The recognized RDF serialization formats and extensions to load a knowledge base are Turtle (`.ttl`), N-Triples (`.nt`), RDF/XML (`.rdf`), HDT (`.hdt`) as created by [hdt-cpp](https://github.com/rdfhdt/hdt-cpp) and zstd compressed HDT (`.hdt.zst`). 88 | 89 | ## Logging 90 | The default log level is "info" for RickView and "error" for libraries. 91 | Change the log level of RickView with the `log_level` configuration key or the `RICKVIEW_LOG_LEVEL` environment variable. 92 | Override this setting using the `RUST_LOG` env var to configure the log levels of dependencies, see the [env\_logger documentation](https://docs.rs/env_logger/latest/env_logger/), for example: 93 | 94 | RUST_LOG=rickview=debug cargo run 95 | 96 | ## Motivation 97 | Existing RDF browsers like [LodView](https://github.com/LodLive/LodView/) look great but use too much hardware resources as they are based on interpreted or garbage collected languages. 98 | This leads to long wait times and out of memory errors on typical small scale research VMs with dozens of docker containers for longtime archival of finished research projects, whose results should still be available to enable reproducible science. 99 | 100 | ## Goals 101 | Implement a basic RDF browser similar to LodView in Rust with the following properties: 102 | 103 | * speed 104 | * low resource utilization 105 | * good design 106 | * option to generate static HTML 107 | 108 | ## Stats 109 | All values are rounded and were measured on an old RickView version on an Intel i9-12900k (16 cores, 24 threads) with 32 GB of DDR5-5200 RAM and a Samsung SSD 980 Pro 1 TB on Arch Linux, standard kernel 5.18. 110 | The qbench2 test URI is . 111 | Stats for HDT, which uses much less RAM, are not measured yet. 112 | 113 | * Linux x86-64 release binary size (strip, fat link time optimization, all features): 4.1 MB 114 | * Linux x86-64 release binary size (strip, no link time optimization, all features): 5.8 MB 115 | * Docker image size: 9.7 MB 116 | * release compile time (strip, fat LTO, all features, cold): 52 s 117 | * release compile time (strip, no LTO, all features, cold): 19 s 118 | * RAM consumption (FastGraph, docker stats, HITO knowledge base 1.1 MB, idle): 10 MB 119 | * RAM consumption (FastGraph, docker stats, HITO knowledge base 1.1 MB, 30s max load): 15 MB 120 | * RAM consumption (LightGraph, docker stats, qbench2, 16.2 million triples, 3.6 GB N-Triples file): 2.568 GiB 121 | * RAM consumption (LightGraph, qbench2, normalized): 2.524 GiB 122 | * RAM consumption (FastGraph, docker stats, qbench2): 4.9 GB 123 | * graph loading time (FastGraph, qbench2, debug build): 548-552 s 124 | * graph loading time (FastGraph, qbench2): 47 s 125 | * graph loading time (LightGraph, qbench2): 40 s 126 | * HTML page generation time (LightGraph, qbench2): 1.9 s 127 | * HTML page generation time (FastGraph, qbench2): 6-58 ms 128 | * Turtle page generation time (FastGraph, qbench2): 6-35 ms 129 | 130 | ### Throughput Single Resource, HTML 131 | There is no page cache but there could still be internal caching benefits so this should be more elaborate in the future. 132 | 133 | $ wrk -t 24 -c 24 -d 30 http://localhost:8080/SoftwareProduct -H "Accept: text/html" 134 | Running 30s test @ http://localhost:8080/SoftwareProduct 135 | 24 threads and 24 connections 136 | Thread Stats Avg Stdev Max +/- Stdev 137 | Latency 9.79ms 3.25ms 26.92ms 56.40% 138 | Req/Sec 102.36 36.17 212.00 66.74% 139 | 73590 requests in 30.02s, 1.04GB read 140 | Requests/sec: 2451.31 141 | Transfer/sec: 35.43MB 142 | 143 | ### Throughput Single Resource, RDF Turtle 144 | 145 | $ docker run --network=host -v $PWD/ontology/dist/hito.ttl:/app/data/kb.ttl rickview 146 | $ wrk -t 24 -c 24 -d 30 http://localhost:8080/SoftwareProduct 147 | Running 30s test @ http://localhost:8080/SoftwareProduct 148 | 24 threads and 24 connections 149 | Thread Stats Avg Stdev Max +/- Stdev 150 | Latency 13.96ms 4.74ms 37.20ms 55.04% 151 | Req/Sec 71.77 26.17 121.00 66.43% 152 | 51572 requests in 30.02s, 567.72MB read 153 | Requests/sec: 1717.72 154 | Transfer/sec: 18.91MB 155 | 156 | ## Stats of LodView 157 | For comparison, here are the stats for the LodView RDF browser, written in Java and Swing. 158 | 159 | * war file: 21 MB (twice the size of RickView) 160 | * Docker image size: 246 MB (25 times the size of RickView) 161 | * compile and package time: 12 s (compiles 4 times faster than RickView with all optimizations) 162 | * RAM consumption (docker stats, HITO knowledge base 1.1 MB, idle, excluding Virtuoso): 482 MB (48 times more RAM usage) 163 | * RAM consumption (docker stats, HITO knowledge base 1.1 MB, 30s max load, excluding Virtuoso): 790 MB (53x) 164 | * RAM consumption (docker stats, HITO knowledge base 1.1 MB, idle, including Virtuoso): 655 MB (65x) 165 | 166 | ### Throughput Single Resource 167 | As data is loaded after page load via JavaScript, real world performance may be worse. 168 | 169 | $ wrk -t 24 -c 24 -d 30 http://localhost:8104/ontology/SoftwareProduct 170 | Running 30s test @ http://localhost:8104/ontology/SoftwareProduct 171 | 24 threads and 24 connections 172 | Thread Stats Avg Stdev Max +/- Stdev 173 | Latency 1.97ms 2.40ms 44.84ms 88.08% 174 | Req/Sec 713.62 353.46 1.24k 38.76% 175 | 511567 requests in 30.03s, 1.61GB read 176 | Socket errors: connect 0, read 1, write 0, timeout 0 177 | Non-2xx or 3xx responses: 511567 178 | Requests/sec: 17037.90 179 | Transfer/sec: 55.07MB 180 | 181 | LodView was not able to serve 24 threads and 24 connections, so try it with only 1 thread and 1 connection: 182 | 183 | $ wrk -t 1 -c 1 -d 30 http://localhost:8104/ontology/SoftwareProduct 184 | Running 30s test @ http://localhost:8104/ontology/SoftwareProduct 185 | 1 threads and 1 connections 186 | Thread Stats Avg Stdev Max +/- Stdev 187 | Latency 2.90ms 13.30ms 250.66ms 97.34% 188 | Req/Sec 715.41 251.08 1.48k 69.46% 189 | 21227 requests in 30.01s, 68.61MB read 190 | Non-2xx or 3xx responses: 21227 191 | Requests/sec: 707.24 192 | Transfer/sec: 2.29MB 193 | 194 | Even a single thread and a single connection cause the container to report errors, this will be investigated in the future. 195 | 196 | ## FAQ 197 | 198 | ### Why can't I use RickView with a Hash Namespace like http://example.com\#MyResource 199 | This is technically impossible for a server-side application by [the definition of a URI](https://datatracker.ietf.org/doc/html/rfc3986#section-3.5) because the fragment (the part after the hash) is client-only and never sent to the server. 200 | If you have an existing knowledge graph with a hash namespace, I recommend replacing the hash '`#`' with a forward slash '`/`'. 201 | 202 | ### Why is RickView necessary? Performance doesn't matter and RAM costs almost nothing! 203 | According to [Hitzler 2021](https://cacm.acm.org/magazines/2021/2/250085-a-review-of-the-semantic-web-field/fulltext?mobile=false), mainstream adoption of the Semantic Web field has stagnated due to a lack of freely available performant, accessible, robust and adaptable tools. 204 | Instead, limited duration research grants motivate the proliferation of countless research prototypes, which are not optimized for any of those criteria, are not maintained after the project ends and finally compete for resources on crowded servers if they do not break down completely. 205 | 206 | ### Can you implement feature X? 207 | I am very interested in hearing from you using it for your knowledge bases and am happy to assist you setting it up. 208 | Feature and pull requests are welcome, however the goal of RickView is to stay minimalistic and not serve every use case. 209 | Please also consider filling out the [survey](https://forms.gle/wqxdVdn4pDLXgyYZA) so I can see which features are most requested. 210 | 211 | ### Why no .env support? 212 | I think this would be overkill, as there is already a default configuration file, a custom configuration file, environment variables and Docker Compose supports `.env` out of the box as well. 213 | So my assumption is that you use the configuration file for local development and `.env` with Docker Compose. 214 | However if you need `.env` support outside of Docker Compose, just create an issue with a motivation and I may implement it. 215 | 216 | ### How can I use it with large knowledge bases? 217 | 218 | 1. Convert your data to the default HDT format using [hdt-cpp](https://github.com/rdfhdt/hdt-cpp). 219 | 2. Deactivate the title and type indexes by setting `large = true` in `data/config.toml` or setting the environment variable `RICKVIEW_LARGE=true`. 220 | 221 | Without the indexes, RickView's memory usage is only a few MB above the underlying [HDT Sophia adapter](https://github.com/konradhoeffner/hdt) in-memory graph, 222 | [see benchmarks](https://github.com/KonradHoeffner/sophia_benchmark/blob/master/benchmark_results.ipynb). 223 | For example, RickView on uses ~ 2.6 GB RAM and contains LinkedSpending 2015, which is 30 GB as uncompressed N-Triples and 413 MB as zstd compressed HDT. 224 | 225 | ### When to use compression and why not support other compression formats? 226 | 227 | [HDT](https://www.rdfhdt.org/) is a compressed binary format that still supports fast querying. 228 | It can be further compressed but then RickView needs to decompress it before loading, which in a test with a large knowledge base increased loading time from ~15s to ~17s. 229 | Because decompression is done in streaming mode, this restricts the available compressors and may even result in faster loading if you use a slow drive such as an HDD and a fast CPU. 230 | zstd was chosen because it compresses and decompresses quickly with a high ratio, supports streaming, and adds little overhead to the RickView binary. 231 | Brotli compresses extremely slowly on high compression settings while GZip results in much larger file sizes. 232 | If you need support for another streaming compressor, please [create an issue](https://github.com/konradhoeffner/rickview/issues). 233 | 234 | ### Why does it look exactly like LodView? 235 | 1. LodView looks beautiful and works well, the only problems are performance and to a lesser degree simple containerized deployment. 236 | 2. LodView is licensed under MIT, so that is allowed. LodView is Copyright (c) 2014 Diego Valerio Camarda and Alessandro Antonuccio. 237 | 3. I can focus my limited time on programming instead of design decisions. Other designs may follow later. 238 | 4. Users of LodView can switch without training. 239 | 5. Performance comparisons are easier when the interface is very similar. 240 | 241 | ### Can I deploy multiple knowledge graphs or one using OWL imports with a single instance of RickView? 242 | A single instance of RickView always loads a single file or URL and has a single namespace. 243 | All triples from the file that are within the namespace are displayed and mapped to the configured base path. 244 | OWL import statements are treated as normal triples and therefore have no special effects. 245 | URLs outside of the namespace are then resolved normally by the browser, so hopefully this ontology has an RDF browser behind it. 246 | If you want to host multiple ontologies or knowledge graphs with RickView, there are several options: 247 | 248 | #### Same Domain 249 | If they are on the same domain and you have control over the common prefix, you can specify that as the namespace. 250 | For example, the prefixes of [SNIK](https://github.com/snikproject/ontology) include: 251 | 252 | ```ttl 253 | @base . 254 | @prefix meta: . 255 | @prefix bb: . 256 | @prefix ob: . 257 | ``` 258 | 259 | RickView now runs on the server that hosts the website and other things, and all requests to `/ontology` are redirected to RickView. 260 | We [set the base to `/ontology`](https://github.com/snikproject/docker/blob/master/docker-compose.yml) and merge all SNIK files into a snik.ttl file, which is loaded by RickView. 261 | In the GitHub repository, the files are stored individually (meta.ttl, bb.ttl,...). 262 | There is a GitHub Workflow that runs [a merge script](https://github.com/snikproject/ontology/blob/master/.github/workflows/build.yml) 263 | with every commit, which validates and merges the files and pushes the result to a separate branch "dist". 264 | When loading the file with RickView, you only need to make sure that the server from GitHub also provides the file and not HTML. 265 | If you take it directly from the branch, you may need to load the URL with GitHub pages or a service that maps GitHub URLs into ones that download as the raw content. 266 | 267 | In practical use, we don't do it that way with SNIK, but have integrated the ontology as a Git submodule in the GitHub repository. 268 | The reason for this is that during the parallel development of the ontology and the Docker container, you can immediately see the changes without having to wait for the pipeline. 269 | Furthermore, you can then also check out an old state of the Docker repository and have the correct ontology version from that time. 270 | For example, if there were changes to the namespace, the RickView configuration must follow the ontology change, and if you want to look at it from two years ago for some reason, you can do that. 271 | Alternatively instead of the latest commit you can use the latest release via the "latest" reference, which works directly via . 272 | 273 | #### Different Domains 274 | If there are different domains or there is no common path over which you have control or the above methods are too convoluted for your use case, you can just run different RickView instances, which should have a low overhead. 275 | It can also be easier to manage if the different ontologies are in different repositories. 276 | For example, then you don't need a separate CI pipeline that merges the files with every commit or release and then attaches the merged file to the release. 277 | 278 | ## Community Guidelines 279 | 280 | ### Issues and Support 281 | If you have a problem with the software, want to report a bug or have a feature request, please use the [issue tracker](https://github.com/KonradHoeffner/rickview/issues). 282 | If have a different type of request, feel free to send an email to [Konrad](mailto:konrad.hoeffner@uni-leipzig.de). 283 | 284 | ### Citation 285 | 286 | [![DOI](https://zenodo.org/badge/472769298.svg)](https://zenodo.org/badge/latestdoi/472769298) 287 | 288 | There is no publication about RickView yet, so please cite our Zenodo archive for now. 289 | 290 | #### BibTeX entry 291 | 292 | @software{rickview, 293 | author = {Konrad H{\'o}ffner}, 294 | title = {{R}ick{V}iew: {L}ightweight Standalone Knowledge Graph Browsing Powered by {R}ust}, 295 | year = 2023, 296 | publisher = {Zenodo}, 297 | version = {x.y.z}, 298 | doi = {10.5281/zenodo.8290117}, 299 | url = {https://doi.org/10.5281/zenodo.8290117} 300 | } 301 | 302 | #### Citation string 303 | 304 | Konrad Höffner (2023). RickView: Lightweight Standalone Knowledge Graph Browsing Powered by Rust. https://doi.org/10.5281/zenodo.8290117 305 | 306 | ### Contribute 307 | We are happy to receive pull requests. 308 | Please use `cargo +nightly fmt` before committing and make sure that the code compiles on the newest stable and nightly toolchain with the default features. 309 | Browse the default knowledge base after `cargo run` and verify that nothing is broken. 310 | `cargo clippy` should not report any warnings. 311 | You can also contribute by recommending RickView and by [sharing your RickView deployments](https://github.com/KonradHoeffner/hdt/issues/35). 312 | -------------------------------------------------------------------------------- /benchmark: -------------------------------------------------------------------------------- 1 | cargo build --release 2 | /usr/bin/time -v target/release/rickview 3 | -------------------------------------------------------------------------------- /codemeta.json: -------------------------------------------------------------------------------- 1 | { 2 | "@context": "https://w3id.org/codemeta/3.0", 3 | "type": "SoftwareSourceCode", 4 | "applicationCategory": "Semantic Web", 5 | "author": [ 6 | { 7 | "id": "https://orcid.org/0000-0001-7358-3217", 8 | "type": "Person", 9 | "affiliation": { 10 | "type": "Organization", 11 | "name": "Institute for Medical Informatics, Statistics and Epidemiology (IMISE), Leipzig, Germany" 12 | }, 13 | "email": "konrad.hoeffner@uni-leipzig.de", 14 | "familyName": "Höffner", 15 | "givenName": "Konrad" 16 | } 17 | ], 18 | "codeRepository": "git+https://github.com/konradhoeffner/rickview", 19 | "dateCreated": "2022-03-22", 20 | "datePublished": "2022-08-31", 21 | "description": "Easy to deploy low-resource stand-alone RDF knowledge graph browser written in Rust", 22 | "downloadUrl": "https://crates.io/crates/rickview", 23 | "identifier": "10.5281/zenodo.8290117", 24 | "keywords": [ 25 | "RDF", 26 | "Linked Data", 27 | "Linked Data Browser", 28 | "Semantic Web", 29 | "Knowledge Graph" 30 | ], 31 | "license": "https://spdx.org/licenses/MIT", 32 | "name": "RickView", 33 | "operatingSystem": [ 34 | "Linux", 35 | "Windows", 36 | "macOS" 37 | ], 38 | "programmingLanguage": "Rust", 39 | "relatedLink": [ 40 | "https://www.youtube.com/watch?v=n8Kb2P8ilwg&t=8250s", 41 | "https://saxfdm.de/wrapup/" 42 | ], 43 | "codemeta:contIntegration": { 44 | "id": "https://github.com/KonradHoeffner/rickview/actions" 45 | }, 46 | "continuousIntegration": "https://github.com/KonradHoeffner/rickview/actions", 47 | "developmentStatus": "active", 48 | "issueTracker": "https://github.com/KonradHoeffner/rickview/issues" 49 | } -------------------------------------------------------------------------------- /data/about.html: -------------------------------------------------------------------------------- 1 | {{ call header with config}} 2 | 3 |
4 |
5 |
6 |

About RickView {config.cargo_pkg_version}

7 |
8 |
9 |
10 | 11 |
12 |
    13 |
  • title index {about.num_titles} entries with size {about.titles_size}
  • 14 |
  • type index {about.num_types} entries with size {about.types_size}
  • 15 |
  • graph size {about.graph_size}
  • 16 |
17 |
18 |
19 |
20 | {{ call footer with config}} 21 | -------------------------------------------------------------------------------- /data/custom.html: -------------------------------------------------------------------------------- 1 | {{ call header with config}} 2 | 3 |
4 |
5 |
6 | {{- if page.title }}

{page.title}

{{ endif }} 7 |
8 |
9 | 10 |
11 | {{ if page.body }}
{ page.body | unescaped }
{{ endif }} 12 |
13 |
14 |
15 | {{ call footer with config}} 16 | -------------------------------------------------------------------------------- /data/default.toml: -------------------------------------------------------------------------------- 1 | prefix = "ex" 2 | namespace = "http://example.com/resource/" 3 | title_properties = ["http://purl.org/dc/elements/1.1/title", "http://purl.org/dc/terms/title", "http://www.w3.org/2000/01/rdf-schema#label"] 4 | type_properties = ["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"] 5 | description_properties = [ "http://www.w3.org/2004/02/skos/core#definition", "http://www.w3.org/2000/01/rdf-schema#comment"] 6 | examples = ["ExClass", "exProperty", "ExInstance"] 7 | homepage = "https://github.com/konradhoeffner/rickview" 8 | langs = ["en", "de", "", "fr", "ru", "zh", "jp"] 9 | base = "" 10 | port = 8080 11 | title = "RickView Example Knowledge Base" 12 | subtitle = "You have successfully installed RickView but not configured a knowledge base yet" 13 | # error, warn, info, debug, trace 14 | log_level = "info" 15 | show_inverse = true 16 | large = false 17 | [header] 18 | title = "test title" 19 | subtitle = "test subtitle" 20 | css = "test css" 21 | [namespaces] 22 | rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" 23 | rdfs = "http://www.w3.org/2000/01/rdf-schema#" 24 | owl = "http://www.w3.org/2002/07/owl#" 25 | xsd = "http://www.w3.org/2001/XMLSchema#" 26 | vann = "http://purl.org/vocab/vann/" 27 | sh = "http://www.w3.org/ns/shacl#" 28 | skos = "http://www.w3.org/2004/02/skos/core#" 29 | ov = "http://open.vocab.org/terms/" 30 | dc = "http://purl.org/dc/elements/1.1/" 31 | dct = "http://purl.org/dc/terms/" 32 | dbr = "http://dbpedia.org/resource/" 33 | -------------------------------------------------------------------------------- /data/example.ttl: -------------------------------------------------------------------------------- 1 | @prefix rdfs: . 2 | @prefix owl: . 3 | @prefix xsd: . 4 | @prefix : . 5 | 6 | :ExClass 7 | a owl:Class; 8 | owl:equivalentClass [ 9 | a owl:Class; 10 | owl:intersectionOf (:Age 11 | [ 12 | a owl:Restriction; 13 | owl:onProperty :has_value; 14 | owl:someValuesFrom [ 15 | a rdfs:Datatype; 16 | owl:onDatatype xsd:decimal; 17 | owl:withRestrictions ([ xsd:minInclusive 18 ] [ xsd:maxExclusive 34 ]) 18 | ] 19 | ] 20 | ) 21 | ]; 22 | rdfs:comment "an example class"@en; 23 | :exProperty :Multilingualкласс; 24 | rdfs:label "example class"@en. 25 | 26 | :Multilingualкласс 27 | a owl:Class; 28 | rdfs:comment "The RickView default font Roboto does not contain Japanese and Chinese characters."@en; 29 | rdfs:label "пример класса"@ru, "et eksempel på en klasse"@no, "例題教室"@jp, "一个实例类"@zh. 30 | 31 | :ExInstance 32 | :exProperty 5, ( :a :b :c); 33 | :blankTest [ :exProperty :o11, :o12; :p2 :o21, :o22, :o23; :p3 [:nested :o11, :o12]]; 34 | a :ExClass; 35 | rdfs:comment "an example instance."@en; 36 | rdfs:label "example instance"@en. 37 | 38 | :o11 rdfs:label "object 11"@en. 39 | 40 | :exProperty 41 | a owl:DatatypeProperty; 42 | rdfs:domain :ExClass; 43 | rdfs:label "Beispielproperty"@de, "example property"@en. 44 | -------------------------------------------------------------------------------- /data/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonradHoeffner/rickview/1f7445e6a24e9866f77a369a20c0cc29e1d59a18/data/favicon.ico -------------------------------------------------------------------------------- /data/footer.html: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /data/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {title} 10 | {{ if css }}{{ endif }} 11 | 12 | -------------------------------------------------------------------------------- /data/index.html: -------------------------------------------------------------------------------- 1 | {{ call header with config}} 2 | 3 |
4 |
5 |
6 |

{config.title}

7 |

{config.subtitle}

8 |
9 |
10 | {{ if config.examples }} Examples: 11 |
    12 | {{ for c in config.examples }} 13 |
  • {c}
  • 14 | {{ endfor }} 15 |
16 | {{ endif }} 17 |
18 | 19 |
20 | {{ if config.body }}
{ config.body | unescaped }
{{ endif }} 21 | See also: 22 | 27 |
28 |
29 |
30 | {{ call footer with config}} 31 | -------------------------------------------------------------------------------- /data/resource.html: -------------------------------------------------------------------------------- 1 | {{ call header with config}} 2 | 3 |
4 |
5 |
6 |

{resource.title }

7 | {{- if resource.depiction }}{{ endif }} 8 |

{resource.uri} 9 | {{- if resource.main_type }} 10 | 11 | 12 | an entity of type: 13 | { resource.main_type | uri_to_suffix } 14 | 15 | 16 | {{ endif }}

17 |
18 |
19 | 20 | {{- for entry in resource.descriptions }} 21 | 22 | 25 | 31 | 32 | {{- endfor }} 33 |
23 | 24 | 26 | {{- for value in entry.1 }} {{ if not @first }} 27 | {{- endif }} 28 | { value | unescaped } 29 | {{- endfor }} 30 |
34 |
35 | 36 |
37 | 38 | {{- for entry in resource.directs }} 39 | 40 | 43 | 49 | 50 | {{- endfor }} 51 |
41 | 42 | 44 | {{- for value in entry.1 }} {{ if not @first }} 45 | {{- endif }} 46 | { value | unescaped } 47 | {{- endfor }} 48 |
52 |
53 |
54 | 55 | {{- if resource.inverses }} 56 |
57 |

inverse relations

58 | 59 | {{- for entry in resource.inverses }} 60 | 61 | 64 | 70 | 71 | {{- endfor }} 72 |
62 | 63 | 65 | {{- for value in entry.1 }} {{ if not @first }} 66 | {{- endif }} 67 | { value | unescaped } 68 | {{- endfor }}Bone 69 |
73 |
74 | {{- endif }} 75 |
76 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /data/rickview.css: -------------------------------------------------------------------------------- 1 | /* taken from LodView */ 2 | html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, blockquote, pre, abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, dl, dt, dd, ol, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary, time, mark, audio, video { 3 | margin: 0; 4 | padding: 0; 5 | border: 0; 6 | outline: 0; 7 | font-size: 100%; 8 | vertical-align: baseline; 9 | background: transparent; 10 | background-color: transparent; 11 | } 12 | 13 | body { 14 | font-family: "Roboto", sans-serif; 15 | font-synthesis: weight style; 16 | line-height: 20px; 17 | font-size: 14px; 18 | background-color: #212121; 19 | } 20 | 21 | hgroup { 22 | display: block; 23 | min-height: 210px; 24 | color: #fff; 25 | padding: 0 24px; 26 | background-color: #914848; 27 | } 28 | 29 | hgroup h1 { 30 | font-size: 32px; 31 | padding-top: 120px; 32 | line-height: 32px; 33 | font-weight: 300; 34 | margin-right: 100px; 35 | } 36 | 37 | hgroup img.depiction { 38 | margin-top: -120px; 39 | float: right; 40 | max-width: 98px; 41 | max-height: 118px; 42 | border: 1px solid aliceblue; 43 | padding: 1px; 44 | } 45 | 46 | hgroup h2 { 47 | font-size: 16px; 48 | padding: 8px 0 24px 0; 49 | font-weight: 300; 50 | } 51 | 52 | header div#abstract { 53 | padding: 24px; 54 | color: #fff; 55 | background-color: #7d3e3e; 56 | } 57 | 58 | aside { 59 | display: block; 60 | background-color: #eee; 61 | color: #fff; 62 | padding: 0; 63 | font-size: 0px; 64 | min-height: 10px; 65 | } 66 | 67 | h3 { 68 | text-transform: uppercase; 69 | letter-spacing: 0.7; 70 | font-weight: 300; 71 | color: #212121; 72 | font-size: 11px; 73 | } 74 | 75 | div#directs { 76 | padding: 24px; 77 | background: #fff; 78 | color: #212121; 79 | line-height: 20px; 80 | font-size: 14px; 81 | overflow-x: hidden; 82 | } 83 | 84 | div#inverses { 85 | padding: 24px; 86 | background: #d4d4d4; 87 | color: #212121; 88 | line-height: 20px; 89 | font-size: 14px; 90 | } 91 | 92 | div#abstract a { 93 | color: #ffffff; 94 | } 95 | 96 | div#directs a, div#inverses a { 97 | color: #212121; 98 | } 99 | 100 | div#directs a:hover, div#inverses a:hover { 101 | text-decoration: underline; 102 | } 103 | 104 | table { 105 | width: 100%; 106 | border-spacing: 1em 0em; 107 | } 108 | 109 | a { 110 | margin: 0; 111 | padding: 0; 112 | font-size: 100%; 113 | vertical-align: baseline; 114 | background: transparent; 115 | text-decoration: none; 116 | } 117 | 118 | #directs #custom a { 119 | cursor: revert; 120 | color: revert; 121 | text-decoration: revert; 122 | } 123 | 124 | .td1 { 125 | min-width: 15em; 126 | } 127 | 128 | td .c2 { 129 | padding-top: 0.5em; 130 | padding-bottom: 0.5em; 131 | min-width: 70vw; 132 | display: inline-block; 133 | } 134 | 135 | div#directs tr td.td2 { 136 | border-bottom: 1px solid #dbdbdb; 137 | } 138 | 139 | div#inverses tr td.td2 { 140 | border-bottom: 1px solid #b6b6b6; 141 | } 142 | 143 | div#directs tr td span.c2:not(:last-child) { 144 | background: 145 | url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAABCAYAAADXeS5fAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpFOUFBM0VFRjI2MzIxMUU0ODlBQ0JGRDRDQzEzOTQzRSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpFOUFBM0VGMDI2MzIxMUU0ODlBQ0JGRDRDQzEzOTQzRSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjYzQ0FFQ0Q0MjYzMTExRTQ4OUFDQkZENENDMTM5NDNFIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkU5QUEzRUVFMjYzMjExRTQ4OUFDQkZENENDMTM5NDNFIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+0iwesAAAABtJREFUeNpi3L59+38GBgZGICaZ9vDwYAQIMACSFAr/M4NKAgAAAABJRU5ErkJggg==) 146 | no-repeat 147 | bottom left; 148 | } 149 | 150 | hgroup h2 .instance { 151 | float: right; 152 | text-align: right; 153 | display: inline-block; 154 | } 155 | 156 | span.instanceof { 157 | text-transform: uppercase; 158 | font-weight: 300; 159 | color: #ffffff; 160 | font-size: 11px; 161 | } 162 | 163 | hgroup h2 > span a span { 164 | font-weight: 500; 165 | color: #ffffff; 166 | font-size: 13px; 167 | } 168 | 169 | footer { 170 | min-height: 176px; 171 | color: #fff; 172 | padding: 24px; 173 | } 174 | 175 | footer #footer-left, footer #footer-left a { 176 | float: left; 177 | display: block; 178 | font-size: 11px; 179 | letter-spacing: .7; 180 | color: #b4b4b4; 181 | text-decoration: none; 182 | } 183 | 184 | .uppercase { 185 | text-transform: uppercase; 186 | } 187 | 188 | #footer-right { 189 | float: right; 190 | position: relative; 191 | top: -7px; 192 | font-size: 12px; 193 | line-height: 24px; 194 | } 195 | 196 | #footer-right a { 197 | color: #fff; 198 | cursor: pointer; 199 | } 200 | 201 | #footer-right ul li { 202 | min-height: 24px; 203 | text-align: right; 204 | } 205 | 206 | #footer-right ul { 207 | list-style: none; 208 | } 209 | 210 | div.datatype { 211 | color: #9e9e9e; 212 | float: right; 213 | } 214 | 215 | @media (max-width: 950px) { 216 | .td1 { 217 | width: 10em; 218 | min-width: 10em; 219 | } 220 | 221 | td .c2 { 222 | width: 100%; 223 | min-width: 100%; 224 | } 225 | } 226 | 227 | @media (max-width: 480px) { 228 | hgroup { 229 | min-height: inherit; 230 | } 231 | 232 | hgroup h1 { 233 | padding-top: 10px; 234 | } 235 | 236 | hgroup img.depiction { 237 | margin-top: -34px; 238 | max-height: 56px; 239 | max-width: 50px; 240 | } 241 | 242 | td { 243 | display: block; 244 | display: grid; 245 | } 246 | 247 | .td1 .td2 { 248 | margin-left: -1em; 249 | } 250 | 251 | .td2 { 252 | padding-left: 2em; 253 | } 254 | 255 | hgroup h2 .instance { 256 | padding-left: 2em; 257 | } 258 | } 259 | 260 | p { 261 | margin-left: 1em; 262 | margin-top: 0.2em; 263 | } 264 | -------------------------------------------------------------------------------- /data/roboto.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-display: swap; 3 | font-family: 'Roboto'; 4 | src: local('Roboto'), 5 | url('roboto300.woff2') format('woff2'); 6 | } 7 | -------------------------------------------------------------------------------- /fonts/roboto300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonradHoeffner/rickview/1f7445e6a24e9866f77a369a20c0cc29e1d59a18/fonts/roboto300.woff2 -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 155 2 | short_array_element_width_threshold = 155 3 | use_field_init_shorthand = true 4 | use_small_heuristics = "Max" 5 | use_try_shorthand = true 6 | fn_params_layout = "Compressed" 7 | single_line_if_else_max_width = 155 8 | 9 | # *** only available in unstable rust *** 10 | where_single_line = true 11 | fn_single_line = true 12 | group_imports = "One" 13 | condense_wildcard_suffixes = true 14 | imports_granularity = "Module" 15 | # *************************************** 16 | -------------------------------------------------------------------------------- /src/about.rs: -------------------------------------------------------------------------------- 1 | //! About page with stats about the package version and the loaded graph. 2 | use crate::rdf::{GraphEnum, graph, titles, types}; 3 | use bytesize::ByteSize; 4 | use deepsize::DeepSizeOf; 5 | use sophia::api::graph::Graph; 6 | const VERSION: &str = env!("CARGO_PKG_VERSION"); 7 | 8 | use serde::Serialize; 9 | #[derive(Serialize, Debug)] 10 | pub struct About { 11 | pub cargo_pkg_version: &'static str, 12 | pub num_titles: usize, 13 | pub num_types: usize, 14 | pub titles_size: String, 15 | pub types_size: String, 16 | pub graph_size: Option, 17 | } 18 | 19 | impl About { 20 | pub fn new() -> About { 21 | let graph_size = match graph() { 22 | #[cfg(feature = "hdt")] 23 | GraphEnum::HdtGraph(hdt_graph) => Some(ByteSize(hdt_graph.size_in_bytes() as u64).to_string()), 24 | GraphEnum::FastGraph(g) => Some(format!("~{} triples", g.triples().size_hint().0)), 25 | }; 26 | About { 27 | cargo_pkg_version: VERSION, 28 | num_titles: titles().len(), 29 | num_types: types().len(), 30 | types_size: ByteSize(types().deep_size_of() as u64).to_string(), 31 | titles_size: ByteSize(titles().deep_size_of() as u64).to_string(), 32 | graph_size, 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/classes.rs: -------------------------------------------------------------------------------- 1 | use crate::rdf::{Piri, graph, titles}; 2 | use multimap::MultiMap; 3 | use sophia::api::MownStr; 4 | use sophia::api::term::IriRef; 5 | use sophia::api::term::SimpleTerm::Iri; 6 | use sophia::api::term::matcher::Any; 7 | use std::collections::HashSet; 8 | 9 | type IriM<'a> = IriRef>; 10 | 11 | fn node(class: &IriM<'_>, subclasses: &MultiMap<&IriM<'_>, &IriM<'_>>) -> (String, u32) { 12 | let piri = Piri::from(class); 13 | let title = if let Some(title) = titles().get(&piri.to_string()) { title } else { &piri.short() }; 14 | let mut inner = String::new(); 15 | let mut count = 0; 16 | let s = match subclasses.get_vec(class) { 17 | Some(children) => { 18 | for child in children { 19 | let (child_s, child_count) = &node(child, subclasses); 20 | inner += child_s; 21 | count += child_count + 1; 22 | } 23 | format!( 24 | "
{} ({count}){inner}
\n", 25 | piri.root_relative(), 26 | title 27 | ) 28 | } 29 | None => format!("

{}

", piri.root_relative(), title), 30 | }; 31 | (s, count) 32 | } 33 | 34 | pub fn class_tree() -> String { 35 | let g = graph(); 36 | // the graphs we use should never fail 37 | // rdfs:subclassOf is also used with blank nodes for owl restrictions that we ignore 38 | let pairs: Vec<[IriM<'_>; 2]> = g 39 | .triples_matching(Any, Some(sophia::api::ns::rdfs::subClassOf), Any) 40 | .map(|t| t.expect("error fetching class triple terms")) 41 | .filter_map(|t| match t { 42 | [Iri(child), _, Iri(parent)] => Some([child, parent]), 43 | _ => None, 44 | }) 45 | .collect(); 46 | let mut subclasses = MultiMap::<&IriM<'_>, &IriM<'_>>::new(); 47 | let mut children = HashSet::<&IriM<'_>>::new(); 48 | for [child, parent] in &pairs { 49 | subclasses.insert(parent, child); 50 | children.insert(child); 51 | } 52 | let parents: HashSet<_> = subclasses.keys().copied().collect(); 53 | let roots = parents.difference(&children); 54 | 55 | let mut s = String::new(); 56 | s += ""; 57 | for root in roots { 58 | s += &node(root, &subclasses).0; 59 | } 60 | s += ""; 61 | s 62 | } 63 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use config::{ConfigError, Environment, File, FileFormat}; 2 | use log::{debug, error}; 3 | use serde::{Deserialize, Serialize}; 4 | use sophia::iri::Iri; 5 | use std::collections::{HashMap, HashSet}; 6 | use std::io::{BufReader, Read}; 7 | use std::str::FromStr; 8 | use std::sync::OnceLock; 9 | 10 | #[derive(Serialize, Deserialize, Debug)] 11 | pub struct Config { 12 | /// Server-side common path prefix (scope) that normally matches the last part of the namespace but may also be "" e.g. for local testing. 13 | /// For example, port 8080, empty base (default) and namespace would serve at . 14 | /// Base "d" would serve at localhost:8080/d/X. 15 | /// Don't use a trailing slash, it will be removed. 16 | /// See also . 17 | pub base: String, 18 | pub title: Option, 19 | pub subtitle: Option, 20 | pub kb_file: Option, 21 | pub port: u16, 22 | pub github: Option, 23 | pub prefix: Box, 24 | #[serde(with = "iri_serde")] 25 | pub namespace: Iri>, 26 | pub namespaces: HashMap, Box>, 27 | pub examples: Vec, 28 | pub title_properties: Vec, 29 | pub type_properties: Vec, 30 | pub description_properties: HashSet, 31 | pub langs: Vec, 32 | pub homepage: Option, 33 | pub endpoint: Option, 34 | /// Show inverse triples, which use the given URI as object instead of subject. May be slow on very large kbs. 35 | pub show_inverse: bool, 36 | /// When false, knowledge base will only be loaded on first resource (non-index) access. 37 | pub doc: Option, 38 | pub log_level: Option, 39 | pub cargo_pkg_version: String, 40 | /// if data/body.html is present, it is inserted into index.html on rendering 41 | pub body: Option, 42 | // override CSS, for example the font 43 | pub css: Option, 44 | /// disable memory and CPU intensive preprocessing on large knowledge bases 45 | pub large: bool, 46 | } 47 | 48 | mod iri_serde { 49 | use serde::{Deserialize, Deserializer, Serializer}; 50 | use sophia::iri::Iri; 51 | pub fn serialize(namespace: &Iri>, serializer: S) -> Result 52 | where S: Serializer { 53 | serializer.serialize_str(namespace.as_str()) 54 | } 55 | 56 | pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> 57 | where D: Deserializer<'de> { 58 | let s = Box::::deserialize(deserializer)?; 59 | Iri::new(s).map_err(serde::de::Error::custom) 60 | } 61 | } 62 | 63 | // path relative to source file 64 | static DEFAULT: &str = std::include_str!("../data/default.toml"); 65 | pub const VERSION: &str = env!("CARGO_PKG_VERSION"); 66 | 67 | impl Config { 68 | pub fn new() -> Result { 69 | // configuration precedence: env var > config key > default value 70 | // namespaces cannot be configured with env vars 71 | let mut config: Config = config::Config::builder() 72 | .add_source(File::from_str(DEFAULT, FileFormat::Toml)) 73 | .add_source(File::new("data/config.toml", FileFormat::Toml).required(false)) 74 | .add_source( 75 | Environment::with_prefix("rickview") 76 | .try_parsing(true) 77 | .list_separator(" ") 78 | .with_list_parse_key("examples") 79 | .with_list_parse_key("title_properties") 80 | .with_list_parse_key("type_properties"), 81 | ) 82 | .set_override("cargo_pkg_version", VERSION)? 83 | .build()? 84 | .try_deserialize()?; 85 | if !config.base.is_empty() && !config.base.starts_with('/') { 86 | eprintln!("Warning: Non-empty base path '{}' does not start with a leading '/'.", config.base); 87 | } 88 | if config.base.ends_with('/') { 89 | config.base.pop(); 90 | } 91 | // initialize logging here because we want it as early as possible but we need the log level 92 | let mut binding = env_logger::Builder::new(); 93 | let builder = match std::env::var("RUST_LOG") { 94 | Err(_) => binding.filter( 95 | Some("rickview"), 96 | log::LevelFilter::from_str(config.log_level.as_ref().unwrap_or(&"info".to_owned())).unwrap_or(log::LevelFilter::Info), 97 | ), 98 | _ => &mut env_logger::Builder::from_default_env(), 99 | }; 100 | 101 | let _ = builder.format_timestamp(None).format_target(false).try_init(); 102 | 103 | // optional custom HTML included only in the index.html template 104 | // path relative to executable 105 | match std::fs::File::open("data/body.html") { 106 | Ok(body_file) => { 107 | let mut br = BufReader::new(body_file); 108 | let mut s = String::new(); 109 | match br.read_to_string(&mut s) { 110 | Ok(_) => config.body = Some(s), 111 | Err(e) => error!("Cannot read data/body.html: {e:?}"), 112 | } 113 | } 114 | Err(e) if e.kind() == std::io::ErrorKind::NotFound => { 115 | debug!("data/body.html does not exist, skipping."); 116 | } 117 | Err(e) => error!("Cannot open data/body.html: {e:?}"), 118 | } 119 | 120 | Ok(config) 121 | } 122 | } 123 | 124 | static CONFIG: OnceLock = OnceLock::new(); 125 | pub fn config() -> &'static Config { CONFIG.get_or_init(|| Config::new().expect("Error reading configuration.")) } 126 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_docs)] 2 | #![warn(clippy::pedantic)] 3 | #![allow(clippy::wildcard_imports)] 4 | #![allow(clippy::enum_glob_use)] 5 | #![allow(clippy::unused_async)] 6 | #![allow(clippy::similar_names)] 7 | #![deny(rust_2018_idioms)] 8 | //! Lightweight and performant RDF browser. 9 | //! An RDF browser is a web application that *resolves* RDF resources: given the HTTP(s) URL identifying a resource it returns an HTML summary. 10 | //! Besides HTML, the RDF serialization formats RDF/XML, Turtle and N-Triples are also available using content negotiation. 11 | //! Default configuration is stored in `data/default.toml`, which can be overriden in `data/config.toml` or environment variables. 12 | //! Configuration keys are in `lower_snake_case`, while environment variables are prefixed with RICKVIEW\_ and are `in SCREAMING_SNAKE_CASE`. 13 | mod about; 14 | mod classes; 15 | /// The main module uses Actix Web to serve resources as HTML and other formats. 16 | mod config; 17 | mod rdf; 18 | mod resource; 19 | 20 | use crate::config::{Config, config}; 21 | use crate::resource::Resource; 22 | use about::About; 23 | use actix_web::body::MessageBody; 24 | use actix_web::http::header::{self, ETag, EntityTag}; 25 | use actix_web::middleware::Compress; 26 | use actix_web::web::scope; 27 | use actix_web::{App, HttpRequest, HttpResponse, HttpServer, Responder, get, head, web}; 28 | use const_fnv1a_hash::{fnv1a_hash_32, fnv1a_hash_str_32}; 29 | use log::{debug, error, info, trace, warn}; 30 | use serde::{Deserialize, Serialize}; 31 | use sophia::iri::IriRef; 32 | use std::error::Error; 33 | use std::sync::LazyLock; 34 | use std::sync::atomic::{AtomicU32, Ordering}; 35 | use std::time::{Instant, SystemTime, UNIX_EPOCH}; 36 | use tinytemplate::TinyTemplate; 37 | 38 | static HEADER: &str = std::include_str!("../data/header.html"); 39 | static FOOTER: &str = std::include_str!("../data/footer.html"); 40 | static RESOURCE: &str = std::include_str!("../data/resource.html"); 41 | static FAVICON: &[u8; 318] = std::include_bytes!("../data/favicon.ico"); 42 | // extremely low risk of collision, worst case is out of date favicon or CSS 43 | static FAVICON_HASH: u32 = fnv1a_hash_32(FAVICON, None); 44 | static RICKVIEW_CSS: &str = std::include_str!("../data/rickview.css"); 45 | static RICKVIEW_CSS_HASH: u32 = fnv1a_hash_str_32(RICKVIEW_CSS); 46 | static ROBOTO_CSS: &str = std::include_str!("../data/roboto.css"); 47 | static ROBOTO_CSS_HASH: u32 = fnv1a_hash_str_32(ROBOTO_CSS); 48 | static ROBOTO300: &[u8] = std::include_bytes!("../fonts/roboto300.woff2"); 49 | static INDEX: &str = std::include_str!("../data/index.html"); 50 | static ABOUT: &str = std::include_str!("../data/about.html"); 51 | static CUSTOM: &str = std::include_str!("../data/custom.html"); 52 | static RUN_ID: AtomicU32 = AtomicU32::new(0); 53 | 54 | // 8 chars hexadecimal, not worth it to add base64 dependency to save 2 chars 55 | static FAVICON_SHASH: LazyLock = LazyLock::new(|| format!("{FAVICON_HASH:x}")); 56 | static FAVICON_SHASH_QUOTED: LazyLock = LazyLock::new(|| format!("\"{}\"", *FAVICON_SHASH)); 57 | static RICKVIEW_CSS_SHASH: LazyLock = LazyLock::new(|| format!("{RICKVIEW_CSS_HASH:x}")); 58 | static RICKVIEW_CSS_SHASH_QUOTED: LazyLock = LazyLock::new(|| format!("\"{}\"", *RICKVIEW_CSS_SHASH)); 59 | static ROBOTO_CSS_SHASH: LazyLock = LazyLock::new(|| format!("{ROBOTO_CSS_HASH:x}")); 60 | static ROBOTO_CSS_SHASH_QUOTED: LazyLock = LazyLock::new(|| format!("\"{}\"", *ROBOTO_CSS_SHASH)); 61 | 62 | #[derive(Serialize)] 63 | struct Page { 64 | title: String, 65 | //subtitle: String, 66 | body: String, 67 | } 68 | 69 | #[derive(Serialize)] 70 | struct Context { 71 | config: &'static Config, 72 | about: Option, 73 | resource: Option, 74 | page: Option, 75 | } 76 | 77 | fn template() -> TinyTemplate<'static> { 78 | let mut tt = TinyTemplate::new(); 79 | tt.add_template("header", HEADER).expect("Could not parse header template"); 80 | tt.add_template("footer", FOOTER).expect("Could not parse footer template"); 81 | tt.add_template("resource", RESOURCE).expect("Could not parse resource page template"); 82 | tt.add_template("index", INDEX).expect("Could not parse index page template"); 83 | tt.add_template("about", ABOUT).expect("Could not parse about page template"); 84 | tt.add_template("custom", CUSTOM).expect("Could not parse about page template"); 85 | tt.add_formatter("uri_to_suffix", |json, output| { 86 | let s = json.as_str().unwrap_or_else(|| panic!("JSON value is not a string: {json}")); 87 | let mut s = s.rsplit_once('/').unwrap_or_else(|| panic!("no '/' in URI '{s}'")).1; 88 | if s.contains('#') { 89 | s = s.rsplit_once('#').unwrap().1; 90 | } 91 | output.push_str(s); 92 | Ok(()) 93 | }); 94 | tt 95 | } 96 | 97 | fn hash_etag(r: &HttpRequest, body: &'static T, shash: &str, quoted: &str, ct: &str) -> impl Responder + use 98 | where &'static T: MessageBody { 99 | if let Some(e) = r.headers().get(header::IF_NONE_MATCH) { 100 | if let Ok(s) = e.to_str() { 101 | if s == quoted { 102 | return HttpResponse::NotModified().finish(); 103 | } 104 | } 105 | } 106 | let tag = ETag(EntityTag::new_strong(shash.to_owned())); 107 | HttpResponse::Ok().content_type(ct).append_header((header::CACHE_CONTROL, "public, max-age=31536000, immutable")).append_header(tag).body(body) 108 | } 109 | 110 | // For maximum robustness, serve CSS, font and icon from any path. Collision with RDF resource URIs unlikely. 111 | #[get("{_anypath:.*/|}rickview.css")] 112 | async fn rickview_css(r: HttpRequest) -> impl Responder { hash_etag(&r, RICKVIEW_CSS, &RICKVIEW_CSS_SHASH, &RICKVIEW_CSS_SHASH_QUOTED, "text/css") } 113 | 114 | #[get("{_anypath:.*/|}roboto.css")] 115 | async fn roboto_css(r: HttpRequest) -> impl Responder { hash_etag(&r, ROBOTO_CSS, &ROBOTO_CSS_SHASH, &ROBOTO_CSS_SHASH_QUOTED, "text/css") } 116 | 117 | // cached automatically by browser 118 | #[get("{_anypath:.*/|}roboto300.woff2")] 119 | async fn roboto300() -> impl Responder { HttpResponse::Ok().content_type("font/woff2").body(ROBOTO300) } 120 | 121 | #[get("{_anypath:.*/|}favicon.ico")] 122 | async fn favicon(r: HttpRequest) -> impl Responder { hash_etag(&r, &FAVICON[..], &FAVICON_SHASH, &FAVICON_SHASH_QUOTED, "image/x-icon") } 123 | 124 | fn error_response(source: &str, error: impl std::fmt::Debug) -> HttpResponse { 125 | let message = format!("Could not render {source}: {error:?}"); 126 | error!("{}", message); 127 | HttpResponse::InternalServerError().body(message) 128 | } 129 | 130 | fn res_result(resource: &str, content_type: &str, result: Result>) -> HttpResponse { 131 | match result { 132 | Ok(s) => HttpResponse::Ok().content_type(content_type).body(s), 133 | Err(e) => error_response(&format!("resource {resource}"), e), 134 | } 135 | } 136 | 137 | // Pseudo GET parameters with empty value so that asset responders still match and caching works. 138 | fn add_hashes(body: &str) -> String { 139 | body.replacen("rickview.css", &format!("rickview.css?{}", *RICKVIEW_CSS_SHASH), 1) 140 | .replacen("roboto.css", &format!("roboto.css?{}", *ROBOTO_CSS_SHASH), 1) 141 | .replacen("favicon.ico", &format!("favicon.ico?{}", *FAVICON_SHASH), 1) 142 | } 143 | 144 | #[derive(Deserialize)] 145 | struct Params { 146 | output: Option, 147 | } 148 | 149 | #[get("/{suffix:.*}")] 150 | /// Serve an RDF resource either as HTML or one of various serializations depending on the accept header. 151 | async fn rdf_resource(r: HttpRequest, suffix: web::Path, params: web::Query) -> impl Responder { 152 | const NT: &str = "application/n-triples"; 153 | const TTL: &str = "application/turtle"; 154 | #[cfg(feature = "rdfxml")] 155 | const XML: &str = "application/rdf+xml"; 156 | const HTML: &str = "text/html"; 157 | let suffix: &str = &suffix; 158 | let id = RUN_ID.load(Ordering::Relaxed).to_string(); 159 | let quoted = format!("\"{id}\""); 160 | if let Some(e) = r.headers().get(header::IF_NONE_MATCH) { 161 | if let Ok(s) = e.to_str() { 162 | if s == quoted { 163 | return HttpResponse::NotModified().finish(); 164 | } 165 | } 166 | } 167 | let etag = ETag(EntityTag::new_strong(id)); 168 | let output = params.output.as_deref(); 169 | let t = Instant::now(); 170 | let prefixed = config().prefix.to_string() + ":" + suffix; 171 | 172 | let iri = config().namespace.resolve(IriRef::new_unchecked(suffix)); 173 | let mut res = rdf::resource(iri.as_ref()); 174 | // no triples found 175 | if res.directs.is_empty() && res.inverses.is_empty() { 176 | // resource URI equal to namespace takes precedence 177 | if suffix.is_empty() { 178 | return index(); 179 | } 180 | let warning = format!("No triples found for {suffix}. Did you configure the namespace correctly?"); 181 | warn!("{}", warning); 182 | if let Some(a) = r.head().headers().get("Accept") { 183 | if let Ok(accept) = a.to_str() { 184 | if accept.contains(HTML) { 185 | res.descriptions.push(("Warning".to_owned(), vec![warning.clone()])); 186 | // HTML is accepted and there are no errors, create a pseudo element in the empty resource to return 404 with HTML 187 | return match template().render("resource", &Context { config: config(), resource: Some(res), about: None, page: None }) { 188 | Ok(html) => HttpResponse::NotFound().content_type("text/html; charset-utf-8").append_header(etag).body(add_hashes(&html)), 189 | Err(e) => HttpResponse::NotFound().content_type("text/plain").append_header(etag).body(format!("{warning}\n\n{e}")), 190 | }; 191 | } 192 | } 193 | } 194 | // return 404 with plain text 195 | return HttpResponse::NotFound().content_type("text/plain").append_header(etag).body(warning); 196 | } 197 | if let Some(a) = r.head().headers().get("Accept") { 198 | if let Ok(accept) = a.to_str() { 199 | trace!("{} accept header {}", prefixed, accept); 200 | if accept.contains(NT) || output == Some(NT) { 201 | debug!("{} N-Triples {:?}", prefixed, t.elapsed()); 202 | return res_result(&prefixed, NT, rdf::serialize_nt(iri.as_ref())); 203 | } 204 | #[cfg(feature = "rdfxml")] 205 | if accept.contains(XML) || output == Some(XML) { 206 | debug!("{} RDF/XML {:?}", prefixed, t.elapsed()); 207 | return res_result(&prefixed, XML, rdf::serialize_rdfxml(iri.as_ref())); 208 | } 209 | if accept.contains(HTML) && output != Some(TTL) { 210 | let context = Context { config: config(), about: None, page: None, resource: Some(res) }; 211 | return match template().render("resource", &context) { 212 | Ok(html) => { 213 | debug!("{} HTML {:?}", prefixed, t.elapsed()); 214 | HttpResponse::Ok().content_type("text/html; charset-utf-8").append_header(etag).body(add_hashes(&html)) 215 | } 216 | Err(err) => error_response(&format!("resource {prefixed}"), err), 217 | }; 218 | } 219 | if !accept.contains(TTL) { 220 | warn!("{} accept header {} and 'output' param {:?} not recognized, default to RDF Turtle", prefixed, accept, output); 221 | } 222 | } 223 | } else { 224 | warn!("{} accept header missing, using RDF Turtle", prefixed); 225 | } 226 | debug!("{} RDF Turtle {:?}", prefixed, t.elapsed()); 227 | res_result(&prefixed, TTL, rdf::serialize_turtle(iri.as_ref())) 228 | } 229 | 230 | /// does not get shown when there is a resource whose URI equals the namespace, with or without slash 231 | fn index() -> HttpResponse { 232 | let context = Context { config: config(), about: None, page: None, resource: None }; 233 | match template().render("index", &context) { 234 | Ok(body) => HttpResponse::Ok().content_type("text/html").body(add_hashes(&body)), 235 | Err(e) => error_response("index page", e), 236 | } 237 | } 238 | 239 | #[get("/about")] 240 | async fn about_page() -> impl Responder { 241 | let context = Context { config: config(), about: Some(About::new()), page: None, resource: None }; 242 | match template().render("about", &context) { 243 | Ok(body) => HttpResponse::Ok().content_type("text/html").body(add_hashes(&body)), 244 | Err(e) => error_response("about page", e), 245 | } 246 | } 247 | 248 | #[get("/classes")] 249 | async fn class_page() -> impl Responder { 250 | let body = crate::classes::class_tree(); 251 | let context = Context { config: config(), about: None, page: Some(Page { title: "Classes".to_owned(), body }), resource: None }; 252 | match template().render("custom", &context) { 253 | Ok(body) => HttpResponse::Ok().content_type("text/html").body(add_hashes(&body)), 254 | Err(e) => error_response("class page", e), 255 | } 256 | } 257 | 258 | #[head("{_anypath:.*}")] 259 | async fn head() -> HttpResponse { HttpResponse::MethodNotAllowed().body("RickView does not support HEAD requests.") } 260 | 261 | #[get("")] 262 | /// redirect /base to correct index page /base/ 263 | /// For example, a user may erroneously open http://mydomain.org/ontology but mean http://mydomain.org/ontology/, which should be the base resource if it exists as the latter is inside the namespace. 264 | async fn redirect() -> impl Responder { HttpResponse::TemporaryRedirect().append_header(("location", config().base.clone() + "/")).finish() } 265 | 266 | #[actix_web::main] 267 | async fn main() -> std::io::Result<()> { 268 | // we don't care about the upper bits as they rarely change 269 | #[allow(clippy::cast_possible_truncation)] 270 | RUN_ID.store(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() as u32, Ordering::Relaxed); 271 | config(); // enable logging 272 | info!("RickView {} serving {} at http://localhost:{}{}/", config::VERSION, config().namespace.as_str(), config().port, config().base); 273 | HttpServer::new(move || { 274 | App::new() 275 | .wrap(Compress::default()) 276 | .service(rickview_css) 277 | .service(roboto_css) 278 | .service(roboto300) 279 | .service(favicon) 280 | .service(head) 281 | .service(scope(&config().base).service(about_page).service(class_page).service(rdf_resource).service(redirect)) 282 | }) 283 | .bind(("0.0.0.0", config().port))? 284 | .run() 285 | .await 286 | } 287 | -------------------------------------------------------------------------------- /src/rdf.rs: -------------------------------------------------------------------------------- 1 | //! Load the RDF graph and summarize RDF resources. 2 | #![allow(rustdoc::bare_urls)] 3 | use crate::config::config; 4 | use crate::resource::Resource; 5 | #[cfg(feature = "hdt")] 6 | use hdt::HdtGraph; 7 | use log::*; 8 | use multimap::MultiMap; 9 | use sophia::api::graph::Graph; 10 | use sophia::api::prefix::{Prefix, PrefixMap}; 11 | use sophia::api::prelude::{Triple, TripleSource}; 12 | use sophia::api::serializer::{Stringifier, TripleSerializer}; 13 | use sophia::api::term::bnode_id::BnodeId; 14 | use sophia::api::term::matcher::{Any, TermMatcher}; 15 | use sophia::api::term::{FromTerm, SimpleTerm, Term}; 16 | use sophia::inmem::graph::FastGraph; 17 | use sophia::iri::{Iri, IriRef}; 18 | use sophia::turtle::parser::{nt, turtle}; 19 | use sophia::turtle::serializer::nt::NtSerializer; 20 | use sophia::turtle::serializer::turtle::{TurtleConfig, TurtleSerializer}; 21 | #[cfg(feature = "rdfxml")] 22 | use sophia::xml::{self, serializer::RdfXmlSerializer}; 23 | use std::collections::{BTreeMap, BTreeSet, HashMap}; 24 | use std::convert::Infallible; 25 | use std::error::Error; 26 | use std::fmt; 27 | use std::fs::File; 28 | use std::io::BufReader; 29 | use std::path::Path; 30 | use std::sync::OnceLock; 31 | use std::time::Instant; 32 | #[cfg(feature = "hdt")] 33 | use zstd::stream::read::Decoder; 34 | 35 | static EXAMPLE_KB: &str = std::include_str!("../data/example.ttl"); 36 | static CAP: usize = 100; // maximum number of values shown per property 37 | static SKOLEM_START: &str = ".well-known/genid/"; 38 | 39 | type PrefixItem = (Prefix>, Iri>); 40 | 41 | // Prefixed IRI 42 | pub struct Piri { 43 | full: String, 44 | prefixed: Option<(String, String)>, 45 | } 46 | 47 | impl fmt::Display for Piri { 48 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.full) } 49 | } 50 | 51 | impl Piri { 52 | pub fn new(iri: Iri<&str>) -> Self { 53 | Self { prefixed: prefixes().get_prefixed_pair(iri).map(|(p, ms)| (p.to_string(), String::from(ms))), full: iri.as_str().to_owned() } 54 | } 55 | fn embrace(&self) -> String { format!("<{self}>") } 56 | fn prefixed_string(&self, bold: bool, embrace: bool) -> String { 57 | if let Some((p, s)) = &self.prefixed { 58 | if bold { format!("{p}:{s}") } else { format!("{p}:{s}") } 59 | } else if embrace { 60 | self.embrace() 61 | } else { 62 | self.to_string() 63 | } 64 | } 65 | pub fn short(&self) -> String { self.prefixed_string(false, false) } 66 | pub fn suffix(&self) -> String { self.prefixed.as_ref().map_or_else(|| self.full.clone(), |pair| pair.1.clone()) } 67 | pub fn root_relative(&self) -> String { self.full.replace(config().namespace.as_str(), &(config().base.clone() + "/")) } 68 | fn property_anchor(&self) -> String { format!("{}", self.root_relative(), self.prefixed_string(true, false)) } 69 | } 70 | 71 | impl> From<&IriRef> for Piri { 72 | fn from(iref: &IriRef) -> Piri { Piri::new(Iri::new_unchecked(iref.as_str())) } 73 | } 74 | 75 | impl From> for Piri { 76 | fn from(iref: IriRef<&str>) -> Piri { Piri::new(Iri::new_unchecked(&iref)) } 77 | } 78 | 79 | // Graph cannot be made into a trait object as of Rust 1.67 and Sophia 0.7, see https://github.com/pchampin/sophia_rs/issues/122. 80 | // Enum is cumbersome but we don't have a choice. 81 | // There may be a more elegant way in future Rust and Sophia versions. 82 | #[allow(clippy::large_enum_variant)] 83 | pub enum GraphEnum { 84 | // Sophia: "A heavily indexed graph. Fast to query but slow to load, with a relatively high memory footprint.". 85 | // Alternatively, use LightGraph, see . 86 | FastGraph(FastGraph), 87 | #[cfg(feature = "hdt")] 88 | HdtGraph(HdtGraph), 89 | } 90 | 91 | impl GraphEnum { 92 | pub fn triples_matching<'s, S, P, O>(&'s self, sm: S, pm: P, om: O) -> Box; 3], Infallible>> + 's> 93 | where 94 | S: TermMatcher + 's, 95 | P: TermMatcher + 's, 96 | O: TermMatcher + 's, 97 | { 98 | match self { 99 | // both graphs produce infallible results 100 | GraphEnum::FastGraph(g) => Box::new(g.triples_matching(sm, pm, om).flatten().map(|triple| Ok(triple.map(SimpleTerm::from_term)))), 101 | #[cfg(feature = "hdt")] 102 | GraphEnum::HdtGraph(g) => Box::new(g.triples_matching(sm, pm, om).flatten().map(|triple| Ok(triple.map(SimpleTerm::from_term)))), 103 | } 104 | } 105 | } 106 | 107 | pub fn kb_reader(filename: &str) -> Result, Box> { 108 | Ok(BufReader::new(if filename.starts_with("http") { 109 | Box::new(ureq::get(filename).call()?.into_body().into_reader()) as Box 110 | } else { 111 | Box::new(File::open(filename)?) 112 | })) 113 | } 114 | 115 | /// Load RDF graph from the RDF Turtle file specified in the config. 116 | pub fn graph() -> &'static GraphEnum { 117 | GRAPH.get_or_init(|| { 118 | let t = Instant::now(); 119 | let triples = match &config().kb_file { 120 | None => { 121 | warn!("No knowledge base configured. Loading example knowledge base. Set kb_file in data/config.toml or env var RICKVIEW_KB_FILE."); 122 | turtle::parse_str(EXAMPLE_KB).collect_triples() 123 | } 124 | Some(filename) => match kb_reader(filename) { 125 | Err(e) => { 126 | error!("Cannot open '{}': {}. Check kb_file data/config.toml or env var RICKVIEW_KB_FILE. EXITING RICKVIEW.", filename, e); 127 | std::process::exit(1); 128 | } 129 | Ok(br) => { 130 | let br = BufReader::new(br); 131 | match Path::new(&filename).extension().and_then(std::ffi::OsStr::to_str) { 132 | Some("ttl") => turtle::parse_bufread(br).collect_triples(), 133 | Some("nt") => nt::parse_bufread(br).collect_triples(), 134 | // error types not compatible 135 | #[cfg(feature = "rdfxml")] 136 | Some("rdf" | "owl") => { 137 | Ok(xml::parser::parse_bufread(br).collect_triples().unwrap_or_else(|e| panic!("Error parsing {filename} as RDF/XML: {e}"))) 138 | } 139 | #[cfg(feature = "hdt")] 140 | Some("zst") if filename.ends_with("hdt.zst") => { 141 | let decoder = Decoder::with_buffer(br).expect("Error creating zstd decoder."); 142 | let hdt = hdt::Hdt::new(BufReader::new(decoder)).expect("Error loading HDT."); 143 | info!("Decompressed and loaded HDT from {filename} in {:?}", t.elapsed()); 144 | return GraphEnum::HdtGraph(hdt::HdtGraph::new(hdt)); 145 | } 146 | #[cfg(feature = "hdt")] 147 | Some("hdt") => { 148 | let hdt_graph = hdt::HdtGraph::new(hdt::Hdt::new(br).unwrap_or_else(|e| panic!("Error loading HDT from {filename}: {e}"))); 149 | info!("Loaded HDT from {filename} in {:?}", t.elapsed()); 150 | return GraphEnum::HdtGraph(hdt_graph); 151 | } 152 | Some(ext) => { 153 | error!("Unknown extension: \"{ext}\": cannot parse knowledge base. Aborting."); 154 | std::process::exit(1); 155 | } 156 | None => { 157 | warn!("{filename} has no extension: assuming RDF/XML."); 158 | Ok(xml::parser::parse_bufread(br).collect_triples().unwrap_or_else(|e| panic!("Error parsing {filename} as RDF/XML: {e}"))) 159 | } 160 | } 161 | } 162 | }, 163 | }; 164 | let g: FastGraph = triples.unwrap_or_else(|x| { 165 | error!("Unable to parse knowledge base {}: {}", &config().kb_file.as_deref().unwrap_or("example"), x); 166 | std::process::exit(1); 167 | }); 168 | if log_enabled!(Level::Debug) { 169 | info!( 170 | "Loaded ~{} FastGraph triples from {} in {:?}", 171 | g.triples().size_hint().0, 172 | &config().kb_file.as_deref().unwrap_or("example kb"), 173 | t.elapsed() 174 | ); 175 | } 176 | GraphEnum::FastGraph(g) 177 | }) 178 | } 179 | 180 | /// (prefix,iri) pairs from the config 181 | fn prefixes() -> &'static Vec { 182 | PREFIXES.get_or_init(|| { 183 | let mut p: Vec = Vec::new(); 184 | for (prefix, iri) in &config().namespaces { 185 | p.push((Prefix::new_unchecked(prefix.clone()), Iri::new_unchecked(iri.clone()))); 186 | } 187 | p.push((Prefix::new_unchecked(config().prefix.clone()), config().namespace.clone())); 188 | p 189 | }) 190 | } 191 | 192 | /// Maps RDF resource URIs to at most one title each, for example `http://example.com/resource/ExampleResource` -> "example resource". 193 | /// Prioritizes `title_properties` earlier in the list. 194 | /// This is only run once to minimize the number of queries and generates the title for every resource in the graph. 195 | /// For very large graph this can take too much time or memory and can be disabled with setting the "large" config option to true. 196 | pub fn titles() -> &'static HashMap { 197 | TITLES.get_or_init(|| { 198 | let mut titles = HashMap::::new(); 199 | if config().large { 200 | return titles; 201 | } 202 | let g = graph(); 203 | // tag, uri, title 204 | let mut tagged = MultiMap::::new(); 205 | for prop in config().title_properties.iter().rev() { 206 | match IriRef::new(prop.clone().into()) { 207 | Err(_) => { 208 | error!("Skipping invalid title property {prop}"); 209 | } 210 | Ok(iref) => { 211 | let term = SimpleTerm::Iri(iref); 212 | for tt in g.triples_matching(Any, Some(term), Any) { 213 | let t = tt.expect("error fetching title triple"); 214 | // ignore blank node labels as title because they usually don't have any 215 | if t.s().is_blank_node() { 216 | continue; 217 | } 218 | let uri = t.s().as_simple().iri().expect("invalid title subject IRI").as_str().to_owned(); 219 | match t.o() { 220 | SimpleTerm::LiteralLanguage(lit, tag) => tagged.insert(tag.as_str().to_owned(), (uri, lit.to_string())), 221 | SimpleTerm::LiteralDatatype(lit, _) => tagged.insert(String::new(), (uri, lit.to_string())), 222 | _ => warn!("Invalid title value {:?}, skipping", t.o().as_simple()), 223 | } 224 | } 225 | } 226 | } 227 | } 228 | // prioritize language tags listed earlier in config().langs 229 | let mut tags: Vec<&String> = tagged.keys().collect(); 230 | tags.sort_by_cached_key(|tag| config().langs.iter().position(|x| &x == tag).unwrap_or(1000)); 231 | tags.reverse(); 232 | for tag in tags { 233 | if let Some(v) = tagged.get_vec(tag) { 234 | for (uri, title) in v { 235 | titles.insert(uri.clone(), title.clone()); 236 | } 237 | } 238 | } 239 | titles 240 | }) 241 | } 242 | 243 | /// Maps RDF resource suffixes to at most one type URI each, for example "`ExampleResource`" -> `http://example.com/resource/ExampleClass`. 244 | /// Prioritizes `type_properties` earlier in the list. 245 | pub fn types() -> &'static HashMap { 246 | TYPES.get_or_init(|| { 247 | let mut types = HashMap::::new(); 248 | if config().large { 249 | return types; 250 | } 251 | for prop in config().type_properties.iter().rev() { 252 | let iref = IriRef::new(prop.clone().into()); 253 | if iref.is_err() { 254 | error!("invalid type property {prop}"); 255 | continue; 256 | } 257 | let term = SimpleTerm::Iri(iref.unwrap()); 258 | for tt in graph().triples_matching(Any, Some(term), Any) { 259 | let t = tt.expect("error fetching type triple"); 260 | if !t.s().is_iri() { 261 | continue; 262 | } 263 | let suffix = t.s().as_simple().iri().expect("invalid type subject IRI").to_string().replace(config().namespace.as_str(), ""); 264 | match t.o().as_simple() { 265 | SimpleTerm::Iri(iri) => { 266 | types.insert(suffix, iri.to_string()); 267 | } 268 | _ => { 269 | warn!("Skipping invalid type {:?} for suffix {suffix} with property <{prop}>.", t.o().as_simple()); 270 | } 271 | } 272 | } 273 | } 274 | types 275 | }) 276 | } 277 | 278 | /// Contains the knowledge base. 279 | static GRAPH: OnceLock = OnceLock::new(); 280 | static PREFIXES: OnceLock> = OnceLock::new(); 281 | /// Map of RDF resource suffixes to at most one title each. 282 | static TITLES: OnceLock> = OnceLock::new(); 283 | /// Map of RDF resource suffixes to at most one type URI each. Result of [types]. 284 | static TYPES: OnceLock> = OnceLock::new(); 285 | 286 | /// Whether the given resource is in subject or object position. 287 | enum PropertyType { 288 | Direct, 289 | Inverse, 290 | } 291 | 292 | #[derive(Debug)] 293 | struct Property { 294 | prop_html: String, 295 | target_htmls: Vec, 296 | } 297 | 298 | impl From for (String, Vec) { 299 | fn from(p: Property) -> Self { (p.prop_html, p.target_htmls) } 300 | } 301 | 302 | /// Map skolemized IRIs back to blank nodes. Keep deskolemized IRIs as they are. 303 | fn deskolemize<'a>(iri: &'a Iri<&str>) -> SimpleTerm<'a> { 304 | if let Some(id) = iri.as_str().split(SKOLEM_START).nth(1) { SimpleTerm::from_term(BnodeId::new_unchecked(id.to_owned())) } else { iri.as_simple() } 305 | } 306 | 307 | fn blank_html(props: BTreeMap, depth: usize) -> String { 308 | if depth > 9 { 309 | return "...".to_owned(); 310 | } 311 | // temporary manchester syntax emulation 312 | if let Some(on_property) = props.get("http://www.w3.org/2002/07/owl#onProperty") { 313 | if let Some(some) = props.get("http://www.w3.org/2002/07/owl#someValuesFrom") { 314 | return format!("{} some {}", on_property.target_htmls.join(", "), some.target_htmls.join(", ")); 315 | } 316 | } 317 | let indent = "\n".to_owned() + &"\t".repeat(9 + depth); 318 | let indent2 = indent.clone() + "\t"; 319 | #[allow(clippy::format_collect)] 320 | let rows = props 321 | .into_values() 322 | .map(|p| { 323 | format!( 324 | "{indent2}{}{}", 325 | p.prop_html, 326 | p.target_htmls.into_iter().map(|h| format!("{h}")).collect::() 327 | ) 328 | }) 329 | .collect::(); 330 | format!("{indent}{rows}{indent}
") 331 | } 332 | 333 | /// For a given resource r, get either all direct properties (p,o) where (r,p,o) is in the graph or indirect ones (s,p) where (s,p,r) is in the graph. 334 | fn properties(conn_type: &PropertyType, source: &SimpleTerm<'_>, depth: usize) -> BTreeMap { 335 | let g = graph(); 336 | let triples = match conn_type { 337 | PropertyType::Direct => g.triples_matching(Some(source), Any, Any), 338 | PropertyType::Inverse => g.triples_matching(Any, Any, Some(source)), 339 | }; 340 | let mut map: BTreeMap> = BTreeMap::new(); 341 | for res in triples { 342 | let triple = res.expect("error with connection triple"); 343 | let target_term = match conn_type { 344 | PropertyType::Direct => triple.o(), 345 | PropertyType::Inverse => triple.s(), 346 | }; 347 | let target_html = match target_term.as_simple() { 348 | SimpleTerm::LiteralLanguage(lit, tag) => format!("{lit} @{}", tag.as_str()), 349 | 350 | SimpleTerm::LiteralDatatype(lit, dt) => format!(r#"{lit}
{}
"#, Piri::from(dt.as_ref()).short()), 351 | 352 | SimpleTerm::Iri(iri) => { 353 | let piri = Piri::from(iri.as_ref()); 354 | let title = if let Some(title) = titles().get(&piri.to_string()) { format!("
↪ {title}") } else { String::new() }; 355 | let target = if piri.to_string().starts_with(config().namespace.as_str()) { "" } else { " target='_blank' " }; 356 | format!("{}{title}", piri.root_relative(), piri.prefixed_string(false, true)) 357 | } 358 | // https://www.w3.org/TR/rdf11-concepts/ Section 3.5 Replacing Blank Nodes with IRIs 359 | SimpleTerm::BlankNode(blank) => { 360 | let id = blank.as_str(); 361 | let sub_html = if matches!(conn_type, PropertyType::Direct) { 362 | blank_html(properties(&PropertyType::Direct, target_term, depth + 1), depth) 363 | } else { 364 | String::new() 365 | }; 366 | let r = IriRef::new_unchecked(SKOLEM_START.to_owned() + id); 367 | let iri = config().namespace.resolve(r); 368 | //format!("_:{id}

{sub_html}

", Piri::new(iri.as_ref()).root_relative()) 369 | format!("↪ Blank Node {id}{sub_html}", Piri::new(iri.as_ref()).root_relative()) 370 | } 371 | _ => format!("{target_term:?}"), 372 | }; 373 | if let SimpleTerm::Iri(iri) = triple.p().as_simple() { 374 | if let Some(values) = map.get_mut(iri.as_str()) { 375 | values.insert(target_html); 376 | } else { 377 | let mut values = BTreeSet::new(); 378 | values.insert(target_html); 379 | map.insert(iri.as_str().to_owned(), values); 380 | } 381 | } 382 | } 383 | map.into_iter() 384 | .map(|(prop, values)| { 385 | let len = values.len(); 386 | let mut target_htmls: Vec = values.into_iter().take(CAP).collect(); 387 | if len > CAP { 388 | target_htmls.push("...".to_string()); 389 | } 390 | (prop.clone(), Property { prop_html: Piri::new(Iri::new_unchecked(&prop)).property_anchor(), target_htmls }) 391 | }) 392 | .collect() 393 | } 394 | 395 | #[cfg(feature = "rdfxml")] 396 | /// Export all triples (s,p,o) for a given subject s as RDF/XML. 397 | pub fn serialize_rdfxml(iri: Iri<&str>) -> Result> { 398 | Ok(RdfXmlSerializer::new_stringifier().serialize_triples(graph().triples_matching(Some(deskolemize(&iri)), Any, Any))?.to_string()) 399 | } 400 | 401 | /// Export all triples (s,p,o) for a given subject s as RDF Turtle using the config prefixes. 402 | pub fn serialize_turtle(iri: Iri<&str>) -> Result> { 403 | let config = TurtleConfig::new().with_pretty(true).with_own_prefix_map(prefixes().clone()); 404 | Ok(TurtleSerializer::new_stringifier_with_config(config).serialize_triples(graph().triples_matching(Some(deskolemize(&iri)), Any, Any))?.to_string()) 405 | } 406 | 407 | /// Export all triples (s,p,o) for a given subject s as N-Triples. 408 | pub fn serialize_nt(iri: Iri<&str>) -> Result> { 409 | Ok(NtSerializer::new_stringifier().serialize_triples(graph().triples_matching(Some(deskolemize(&iri)), Any, Any))?.to_string()) 410 | } 411 | 412 | fn depiction_iri(iri: Iri<&str>) -> Option { 413 | let foaf_depiction = IriRef::new_unchecked("http://xmlns.com/foaf/0.1/depiction"); 414 | graph() 415 | .triples_matching(Some(iri), Some(foaf_depiction), Any) 416 | .filter_map(Result::ok) 417 | .map(Triple::to_o) 418 | .filter(Term::is_iri) 419 | .map(|o| o.iri().unwrap().as_str().to_owned()) 420 | .next() 421 | } 422 | 423 | /// Returns the resource with the given IRI from the configured namespace. 424 | pub fn resource(subject: Iri<&str>) -> Resource { 425 | let start = Instant::now(); 426 | let piri = Piri::new(subject.as_ref()); 427 | let suffix = piri.suffix(); 428 | let convert = |m: BTreeMap| -> Vec<_> { m.into_values().map(Property::into).collect() }; 429 | 430 | let source = deskolemize(&subject); 431 | let mut all_directs = properties(&PropertyType::Direct, &source, 0); 432 | let descriptions = convert(config().description_properties.iter().filter_map(|p| all_directs.remove_entry(p)).collect()); 433 | let directs = convert(all_directs); 434 | let title = titles().get(&piri.full).unwrap_or(&suffix).to_string().replace(SKOLEM_START, "Blank Node "); 435 | let main_type = types().get(&suffix).cloned(); 436 | let inverses = if config().show_inverse { convert(properties(&PropertyType::Inverse, &source, 0)) } else { Vec::new() }; 437 | Resource { 438 | uri: piri.full, 439 | base: config().base.clone(), 440 | duration: format!("{:?}", start.elapsed()), 441 | title, 442 | github_issue_url: config().github.as_ref().map(|g| format!("{g}/issues/new?title={suffix}")), 443 | main_type, 444 | descriptions, 445 | directs, 446 | inverses, 447 | depiction: depiction_iri(subject), 448 | } 449 | } 450 | -------------------------------------------------------------------------------- /src/resource.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | 3 | #[derive(Serialize)] 4 | pub struct Property { 5 | pub uri: String, 6 | pub tooltip: String, 7 | } 8 | 9 | /// Summary of an RDF resource. 10 | #[derive(Serialize)] 11 | pub struct Resource { 12 | pub uri: String, 13 | pub base: String, 14 | //pub suffix: String, 15 | pub title: String, 16 | pub main_type: Option, 17 | /// HTML representations of properties and descriptions of this resource. 18 | pub descriptions: Vec<(String, Vec)>, 19 | /// HTML representations of properties and objects of triples where this resource is a subject. 20 | pub directs: Vec<(String, Vec)>, 21 | /// HTML representations of subjects and properties of triples where this resource is an object. 22 | pub inverses: Vec<(String, Vec)>, 23 | pub duration: String, 24 | pub github_issue_url: Option, 25 | pub depiction: Option, 26 | } 27 | --------------------------------------------------------------------------------