Compare commits

...

31 commits

Author SHA1 Message Date
fc5498ef29
Actually pass the flake build 2026-06-21 18:40:15 -03:00
1efe2d4859
CI: Get Windows build's version from Cargo.toml
Some checks failed
/ verify (push) Has been cancelled
2026-06-20 20:39:40 -03:00
b3c210dc47
CI: Build Windows binary in a separate job
Some checks failed
/ verify (push) Has been cancelled
2026-06-20 20:14:03 -03:00
3494ba71e6 Setup Windows builds
Some checks failed
/ verify (push) Has been cancelled
2026-06-20 01:24:35 -03:00
3d7dbe5622 Update dependencies 2026-06-20 01:11:03 -03:00
f8708f2500 Add several new lints from latest Clippy versions
Some checks failed
/ verify (push) Has been cancelled
2026-06-08 00:52:48 -03:00
1aef9d6ee6 Draft customization docs 2026-06-07 05:19:25 -03:00
0987269307 Add flake.nix 2026-06-07 04:19:45 -03:00
a38d93ba2a schema: Move node schema to $defs 2026-06-05 21:31:43 -03:00
26ba5c90e6 Update README for v0.5 node structure 2026-06-04 22:26:08 -03:00
4d7ae2e729 Tweak paragraph and header spacing
Some checks failed
/ verify (push) Has been cancelled
2026-06-04 22:06:18 -03:00
297030696e Merge planned features overview in README with the Intro's 2026-06-04 22:06:18 -03:00
2a01b9ac4c CI: Add new assessments to verify workflow, improve Git tag check 2026-06-04 22:06:18 -03:00
83d7b7e7aa CI: Allow manually triggering publish workflow
Some checks failed
/ verify (push) Has been cancelled
2026-06-04 13:14:14 -03:00
47f8530487 Add spelling and TOML schema checks
Some checks failed
/ verify (push) Has been cancelled
2026-06-04 13:00:12 -03:00
e0a5eaaf2f Rename Start to Introduction, tweak About page links
Some checks failed
/ verify (push) Has been cancelled
2026-06-04 11:10:28 -03:00
c2de0283fd Update roadmap
Some checks failed
/ verify (push) Has been cancelled
2026-06-04 10:31:11 -03:00
8649169bae Add CSS to watchexec extensions 2026-06-04 10:31:11 -03:00
70bdba50c8 Spell check docs 2026-06-04 10:31:11 -03:00
88fdd3084e Flag absolute anchors 2026-06-04 10:31:11 -03:00
1d801dce06 Docs review 2026-06-03 15:01:22 -03:00
ab9b587018 Rename 'hidden' node option to 'listed' 2026-06-03 15:01:22 -03:00
0d48291d5f Draft a JSON schema for graphs 2026-06-03 15:01:22 -03:00
8100b1ba10 Tone down code styles 2026-06-03 15:01:22 -03:00
d0ca4e6cb3 Add a context parser for PreFormat blocks 2026-06-02 23:45:46 -03:00
29c2beb3ed Update docs for latest version, add more pointers across starting pages
Some checks failed
/ verify (push) Has been cancelled
2026-06-01 16:07:26 -03:00
aab419a18a Bump dependencies, add justfile comments, apply fixes 2026-06-01 14:36:22 -03:00
c18b656125 Add supply chain security checks
Some checks failed
/ verify (push) Has been cancelled
2026-04-23 19:06:18 -03:00
5bfd23925b Add mutation testing to justfile 2026-04-23 17:55:21 -03:00
d881f0bd58 Bump minimum Rust version to 1.94.0 2026-04-23 17:54:22 -03:00
cad3213dad CI: Update publish workflow
Some checks failed
/ verify (push) Has been cancelled
/ publish (push) Has been cancelled
2026-03-22 19:14:59 -03:00
37 changed files with 1611 additions and 802 deletions

View file

@ -3,3 +3,4 @@ allow-expect-in-tests = true
single-char-binding-names-threshold = 2 single-char-binding-names-threshold = 2
upper-case-acronyms-aggressive = true upper-case-acronyms-aggressive = true
allow-indexing-slicing-in-tests = true allow-indexing-slicing-in-tests = true
check-incompatible-msrv-in-tests = true

View file

@ -2,44 +2,80 @@ on:
push: push:
tags: tags:
- 'v*' - 'v*'
workflow_dispatch:
inputs:
upload:
description: 'Upload built assets to the git.jutty.dev package registry'
type: boolean
required: false
default: true
linux:
description: 'Build Linux binaries'
type: boolean
required: false
default: true
windows:
description: 'Build Windows binary'
type: boolean
required: false
default: true
jobs: jobs:
publish: publish:
runs-on: docker runs-on: docker
if: ${{ inputs.linux }}
timeout-minutes: 20 timeout-minutes: 20
container: container:
image: rust:slim image: rust:slim
steps: steps:
- name: Install action dependencies - name: Install action dependencies
run: | run: apt-get install --no-install-recommends --update -y nodejs curl git
apt-get install --no-install-recommends --update -y nodejs curl git
- name: Checkout code - name: Checkout code
uses: actions/checkout@v6 uses: actions/checkout@v6
with: with:
fetch-tags: true fetch-tags: true
- name: Setup Rust toolchain - name: Set up Rust toolchain
run: | run: |
rustup component add llvm-tools-preview rustup component add llvm-tools
rustup component add --toolchain nightly rustfmt clippy rustup component add --toolchain nightly rustfmt clippy
rustup target add x86_64-unknown-linux-gnu
rustup target add x86_64-unknown-linux-musl rustup target add x86_64-unknown-linux-musl
- name: Setup additional tooling - name: Set up additional tooling
run: .forgejo/workflows/setup-tools.sh run: .forgejo/workflows/setup-tools.sh
- name: Set up CI user
run: |
useradd -m ci && chown -R ci:ci .
git config --global --add safe.directory "$PWD"
- name: Run all assessments - name: Run all assessments
run: just verify run: just ci verify
- name: Build x64 glibc release binary - name: Build x64 Linux glibc release binary
run: just release-build x86_64-unknown-linux-gnu run: just release-build-gnu
- name: Build x64 musl release binary - name: Build x64 Linux musl release binary
run: just release-build x86_64-unknown-linux-musl run: just release-build-musl
- name: Calculate SHA-256 hashes - name: Calculate SHA-256 hashes
run: just shasum run: just shasum
- name: Publish x64 glibc binary to git.jutty.dev registry - name: Publish x64 Linux musl binary to git.jutty.dev registry
if: ${{ inputs.upload }}
run: |
version=$(./target/x86_64-unknown-linux-musl/release/en --version)
api_root=https://git.jutty.dev/api
url=$api_root/packages/jutty/generic/en/$version/en-x64-linux-musl
curl -fsSL \
--user jutty:${{ secrets.GJD_REGISTRY_TOKEN }} \
--upload-file target/x86_64-unknown-linux-musl/release/en $url
- name: Publish x64 Linux glibc binary to git.jutty.dev registry
if: ${{ inputs.upload }}
run: | run: |
version=$(./target/x86_64-unknown-linux-gnu/release/en --version) version=$(./target/x86_64-unknown-linux-gnu/release/en --version)
api_root=https://git.jutty.dev/api api_root=https://git.jutty.dev/api
@ -49,12 +85,60 @@ jobs:
--user jutty:${{ secrets.GJD_REGISTRY_TOKEN }} \ --user jutty:${{ secrets.GJD_REGISTRY_TOKEN }} \
--upload-file target/x86_64-unknown-linux-gnu/release/en $url --upload-file target/x86_64-unknown-linux-gnu/release/en $url
- name: Publish x64 musl binary to git.jutty.dev registry publish-windows:
runs-on: docker
if: ${{ inputs.windows }}
timeout-minutes: 20
container:
image: messense/cargo-xwin
env:
XWIN_CACHE_DIR: /tmp
steps:
- name: Install action dependencies
run: apt-get install --no-install-recommends --update -y nodejs curl git
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-tags: true
- name: Set up Rust toolchain
run: | run: |
version=$(./target/x86_64-unknown-linux-musl/release/en --version) version=$(cat Cargo.toml | grep '^rust-version' | cut -d \" -f 2)
rustup set auto-self-update disable
rustup toolchain install "$version-x86_64-unknown-linux-gnu"
rustup default "$version-x86_64-unknown-linux-gnu"
rustup target add x86_64-pc-windows-msvc
- name: Set up additional tooling
run: .forgejo/workflows/setup-tools.sh
- name: Cache MSVC CRT and SDK
run: cargo-xwin cache xwin
- name: Set up CI user
run: |
useradd -m ci && chown -R ci:ci .
git config --global --add safe.directory "$PWD"
- name: Run Windows tests
run: just ci test-windows
- name: Build x64 Windows MSVC release binary
run: |
rm -v /usr/bin/clang
just release-build-msvc
- name: Calculate SHA-256 hashes
run: just shasum
- name: Publish x64 Windows MSVC binary to git.jutty.dev registry
if: ${{ inputs.upload }}
run: |
version=$(grep '^version' Cargo.toml | cut -d '"' -f 2)
api_root=https://git.jutty.dev/api api_root=https://git.jutty.dev/api
url=$api_root/packages/jutty/generic/en/$version/en-x64-linux-musl url=$api_root/packages/jutty/generic/en/$version/en-x64-windows-msvc
curl -fsSL \ curl -fsSL \
--user jutty:${{ secrets.GJD_REGISTRY_TOKEN }} \ --user jutty:${{ secrets.GJD_REGISTRY_TOKEN }} \
--upload-file target/x86_64-unknown-linux-musl/release/en $url --upload-file target/x86_64-pc-windows-msvc/release/en.exe $url

View file

@ -2,19 +2,38 @@
set -eu set -eu
JUST_VERSION="1.47.1" TRIPLE="x86_64-unknown-linux-musl"
JUST_SHA256SUM="3cb931ae25860f261ee373f32ede3b772ac91f14f588e4071576d3ffcf1a16fd" TRIPLE_GNU="x86_64-unknown-linux-gnu"
JUST_VERSION="1.51.0"
JUST_SHA256SUM="c8f085ca3e885723c341d06243fc291b5abfdc8bbe3b2c076b117de490387b59"
CARGO_LLVM_COV_VERSION="0.6.21" CARGO_LLVM_COV_VERSION="0.6.21"
CARGO_LLVM_COV_SHA256SUM="57f491aedf7cdb261538ceb49cbb1ee9d27df7ca205a5e1a009caaf5cb911afb" CARGO_LLVM_COV_SHA256SUM="57f491aedf7cdb261538ceb49cbb1ee9d27df7ca205a5e1a009caaf5cb911afb"
CARGO_AUDIT_VERSION="0.22.1" CARGO_AUDIT_VERSION="0.22.1"
CARGO_AUDIT_TAG="cargo-audit%2Fv$CARGO_AUDIT_VERSION" CARGO_AUDIT_TAG="cargo-audit%2Fv$CARGO_AUDIT_VERSION"
CARGO_AUDIT_SHA256SUM="1890badd5f15831a9af4b074399fcd21e6f7c0fe42c84e9254cdffc9f813765c" CARGO_AUDIT_SHA256SUM="1890badd5f15831a9af4b074399fcd21e6f7c0fe42c84e9254cdffc9f813765c"
TRIPLE="x86_64-unknown-linux-gnu" TAPLO_VERSION="0.10.0"
TRIPLE_MUSL="x86_64-unknown-linux-musl" TAPLO_SHA256SUM="8fe196b894ccf9072f98d4e1013a180306e17d244830b03986ee5e8eabeb6156"
TYPOS_VERSION="1.47.2"
TYPOS_SHA256SUM="7aef58932fc123b4cf4b40d86468e89a3297d80169051d7cfd13a235e05fc426"
CARGO_DENY_VERSION="0.19.8"
CARGO_DENY_SHA256SUM="70e769ae3872e34d45132b17040859175e11401dc12dddb0303e0b8c7d088f3f"
CARGO_MSRV_VERSION="0.19.3"
CARGO_MSRV_SHA256SUM="6f9a0011ca8ad959d304185b9090390fd1ad87e2d764007fbb0abe043afa9ff5"
fetch() { fetch() {
repo="$1"; tag="$2"; filename="$3"; digest="$4"; binary="$5" repo="$1"
tag="$2"
filename="$3"
digest="$4"
binary="$5"
renamed_binary="${6:-$binary}"
[ -d /tmp/tools ] || mkdir -p /tmp/tools [ -d /tmp/tools ] || mkdir -p /tmp/tools
@ -24,19 +43,45 @@ fetch() {
printf '%s %s\n' "$digest" "/tmp/$filename" > /tmp/digest printf '%s %s\n' "$digest" "/tmp/$filename" > /tmp/digest
sha256sum --check /tmp/digest sha256sum --check /tmp/digest
tar xf "/tmp/$filename" -C /tmp/tools
find /tmp/tools -type f -executable -name "$binary" \ if printf '%s' "$filename" | grep -qE '\.tar\.|\.tgz$'; then
-exec mv -v '{}' /usr/local/bin ';' tar xf "/tmp/$filename" -C /tmp/tools
elif printf '%s' "$filename" | grep -q '\.gz$'; then
gunzip -c "/tmp/$filename" > "/tmp/tools/$binary"
else
echo "Fatal: can't determine how to unpack $filename"
exit 1
fi
find /tmp/tools -type f -name "$binary" \
-exec mv -v '{}' "/usr/local/bin/$renamed_binary" ';'
chmod +x "/usr/local/bin/$renamed_binary"
} }
fetch casey/just "$JUST_VERSION" \ fetch casey/just "$JUST_VERSION" \
"just-$JUST_VERSION-$TRIPLE_MUSL.tar.gz" \ "just-$JUST_VERSION-$TRIPLE.tar.gz" \
"$JUST_SHA256SUM" just "$JUST_SHA256SUM" just
fetch taiki-e/cargo-llvm-cov "v$CARGO_LLVM_COV_VERSION" \ fetch taiki-e/cargo-llvm-cov "v$CARGO_LLVM_COV_VERSION" \
"cargo-llvm-cov-$TRIPLE.tar.gz" \ "cargo-llvm-cov-$TRIPLE_GNU.tar.gz" \
"$CARGO_LLVM_COV_SHA256SUM" cargo-llvm-cov "$CARGO_LLVM_COV_SHA256SUM" cargo-llvm-cov
fetch rustsec/rustsec "$CARGO_AUDIT_TAG" \ fetch rustsec/rustsec "$CARGO_AUDIT_TAG" \
"cargo-audit-$TRIPLE-v$CARGO_AUDIT_VERSION.tgz" \ "cargo-audit-$TRIPLE_GNU-v$CARGO_AUDIT_VERSION.tgz" \
"$CARGO_AUDIT_SHA256SUM" cargo-audit "$CARGO_AUDIT_SHA256SUM" cargo-audit
fetch tamasfe/taplo "$TAPLO_VERSION" \
"taplo-linux-x86_64.gz" \
"$TAPLO_SHA256SUM" taplo-linux-x86_64 taplo
fetch crate-ci/typos "v$TYPOS_VERSION" \
"typos-v$TYPOS_VERSION-$TRIPLE.tar.gz" \
"$TYPOS_SHA256SUM" typos
fetch EmbarkStudios/cargo-deny "$CARGO_DENY_VERSION" \
"cargo-deny-$CARGO_DENY_VERSION-$TRIPLE.tar.gz" \
"$CARGO_DENY_SHA256SUM" cargo-deny
fetch foresterre/cargo-msrv "v$CARGO_MSRV_VERSION" \
"cargo-msrv-$TRIPLE_GNU-v$CARGO_MSRV_VERSION.tgz" \
"$CARGO_MSRV_SHA256SUM" cargo-msrv

View file

@ -4,11 +4,13 @@ on:
- '*' - '*'
paths: paths:
- src/** - src/**
- tests/** - static/graph*
- static/graph.toml - static/welcome.toml
- .forgejo/** - .forgejo/**
- .justfile
- Cargo.toml - Cargo.toml
- Cargo.lock - Cargo.lock
jobs: jobs:
verify: verify:
runs-on: docker runs-on: docker
@ -24,32 +26,37 @@ jobs:
with: with:
fetch-tags: true fetch-tags: true
- name: Setup Rust toolchain - name: Set up Rust toolchain
run: | run: |
rustup component add llvm-tools-preview rustup component add llvm-tools
rustup component add --toolchain nightly rustfmt clippy rustup component add --toolchain nightly rustfmt clippy
rustup target add x86_64-unknown-linux-gnu rustup target add x86_64-unknown-linux-gnu
rustup target add x86_64-unknown-linux-musl rustup target add x86_64-unknown-linux-musl
- name: Setup additional tooling - name: Set up additional tooling
run: .forgejo/workflows/setup-tools.sh run: .forgejo/workflows/setup-tools.sh
- name: Setup CI user - name: Set up CI user
run: | run: |
useradd -m ci && chown -R ci:ci . useradd -m ci && chown -R ci:ci .
git config --global --add safe.directory "$PWD" git config --global --add safe.directory "$PWD"
- name: Build - name: Text matches
run: just ci build run: just ci todos-assess version-assess spell
- name: Schema lint
run: just ci schema-lint
- name: Deny
run: just ci deny
- name: Format - name: Format
run: just ci format-assess run: just ci format-assess
- name: Lint - name: Lint
run: just ci lint-assess run: just ci lint-assess
- name: MSRV
run: just ci msrv-assess
- name: Cargo check - name: Cargo check
run: just ci check run: just ci check
- name: Test - name: Test
run: just ci test run: just ci test
- name: Assess test coverage - name: Coverage
run: just ci cover-assess run: just ci cover-assess

1
.gitignore vendored
View file

@ -1 +1,2 @@
/target /target
/result

165
.justfile
View file

@ -51,7 +51,7 @@ alias wc := run-watch-containerized
[private] [private]
assess-quick: assess-quick:
{{ just_cmd }} lint check test-cover-quick {{ just_cmd }} lint check test-cover-quick spell schema-lint
[private] [private]
assess-run-quick: assess-run-quick:
@ -72,6 +72,17 @@ assess-run-watch-quick:
alias rq := assess-run-watch-quick alias rq := assess-run-watch-quick
# Determine the Minimum Supported Rust Version
[group: 'develop']
msrv-find:
@{{ make_msrv("find", musl_target) }}
@{{ make_msrv("find", glibc_target) }}
@{{ make_msrv("find", msvc_target) }}
make_msrv(command, target) := f"printf '{{ target }}: '; " + \
f"cargo msrv {{ command }} --target {{ target }} --output-format minimal " \
+ f"-- {{ maybe_xwin(target) }} build --ignore-rust-version"
[private] [private]
test-cover-quick: test-cover-quick:
{{ cover_cmd }} --no-report -- --test 'serial_tests::' --test-threads 1 {{ cover_cmd }} --no-report -- --test 'serial_tests::' --test-threads 1
@ -198,11 +209,29 @@ cover-open:
alias oo := cover-open alias oo := cover-open
# Perform mutation testing
[group: 'develop']
mutate:
-just mutate-single -- --test-threads=1 serial_tests::
-just mutate-single -- --skip serial_tests::
alias m := mutate
[private]
mutate-single *cargo_test_args:
cargo mutants --iterate \
-E '<impl Debug<' \
-E '<impl From<' \
-E '<impl std::fmt::Display for ' \
-E 'print_help -> bool' \
--output target/mutants \
-- {{ cargo_test_args }}
# Tag HEAD with version from Cargo.toml # Tag HEAD with version from Cargo.toml
[script, group: 'assess'] [script, group: 'assess']
tag commit="HEAD": update tag commit="HEAD": update
if [ "{{ last_tag }}" = "{{ manifest_version }}" ]; then if [ "{{ last_local_tag }}" = "{{ manifest_version }}" ]; then
echo "Last tag {{ last_tag }} and manifest ({{ manifest_version }}) already match" echo "Last tag {{ last_local_tag }} and manifest ({{ manifest_version }}) already match"
exit exit
elif [ "{{ manifest_version }}" != "{{ lockfile_version }}" ]; then elif [ "{{ manifest_version }}" != "{{ lockfile_version }}" ]; then
echo "Manifest and lockfile versions don't match: update failed?" echo "Manifest and lockfile versions don't match: update failed?"
@ -213,15 +242,15 @@ tag commit="HEAD": update
{{ just_cmd }} version-assess {{ just_cmd }} version-assess
# Verify and push # Verify and push
[group: 'develop'] [group: 'develop', env("JUST", "1")]
push: verify push: verify && (version-assess "true")
git push git push
git push --tags git push --tags
alias p := push alias p := push
# Push without verifying # Push without verifying
[group: 'develop'] [group: 'develop', env("JUST", "1")]
push-unsafe: push-unsafe:
git push --no-verify git push --no-verify
git push --tags --no-verify git push --tags --no-verify
@ -283,13 +312,28 @@ alias c := check
# Run tests # Run tests
[group: 'assess'] [group: 'assess']
test pattern="": test pattern="":
cargo test {{ pattern}} --timings -- --test-threads=1 'serial_tests::' cargo test {{ pattern }} --timings -- --test-threads=1 'serial_tests::'
cargo test {{ pattern}} --timings --bin en cargo test {{ pattern }} --timings --bin en
cargo test {{ pattern}} --timings --doc cargo test {{ pattern }} --timings --doc
cargo test {{ pattern}} --timings --lib -- --skip 'serial_tests::' cargo test {{ pattern }} --timings --lib -- --skip 'serial_tests::'
{{ if which("wine") == "" {
"echo Skipping Windows tests: wine not found"
} else {
just_cmd + " test-windows"
} }}
alias t := test alias t := test
# Run Windows tests using wine
[group: 'assess']
test-windows pattern="":
-{{ xwin_test(pattern) }} --timings -- --test-threads=1 'serial_tests::'
-{{ xwin_test(pattern) }} --timings --bin en
-{{ xwin_test(pattern) }} --timings --doc
-{{ xwin_test(pattern) }} --timings --lib -- --skip 'serial_tests::'
xwin_test(pattern) := f"cargo-xwin test {{ pattern }} --target {{ msvc_target }}"
# Run tests with unfiltered output # Run tests with unfiltered output
[group: 'assess'] [group: 'assess']
test-output pattern="": test-output pattern="":
@ -329,41 +373,65 @@ verify:
exit 1 exit 1
fi fi
{{ just_cmd }} \ {{ just_cmd }} \
todos-assess version-assess \ todos-assess version-assess spell schema-lint deny \
security-assess \ format-assess lint-assess msrv-assess check \
format-assess lint-assess check \
test "" cover-assess test "" cover-assess
alias v := verify alias v := verify
# Check tag-manifest consistency # Check tag-manifest consistency
[script, group: 'assess'] [script, group: 'assess']
version-assess: update [arg("remote", long="remote", short="r", value="true")]
if version-assess remote="false": update
[ "{{ last_tag }}" != "{{ lockfile_version }}" ] \ last_remote_tag=$(
|| [ "{{ last_tag }}" != "{{ lockfile_version }}" ] git ls-remote --tags --sort=-creatordate origin |
then head -n 1 | tr -d v | cut -d / -f 3
printf 'Last tag: %s\nManifest: %s\nLockfile: %s\n' \ )
"{{ last_tag }}" \ printf 'Local: %s\nRemote: %s\nManifest: %s\nLockfile: %s\n' \
"{{ manifest_version }}" \ "{{ last_local_tag }}" \
"{{ lockfile_version }}" "$last_remote_tag" \
exit 1 "{{ manifest_version }}" \
"{{ lockfile_version }}"
test "{{ last_local_tag }}" = "{{ lockfile_version }}"
test "{{ last_local_tag }}" = "{{ manifest_version }}"
if {{ if remote == "true" { "true" } else { "false" } }}; then
test "{{ last_local_tag }}" = "$last_remote_tag"
fi fi
alias va := version-assess alias va := version-assess
# Audit security advisories # Assess if the current Minimum Supported Rust Version is accurate
security-assess: [group: 'assess']
cargo audit --deny warnings msrv-assess:
alias sa := security-assess {{ make_msrv("verify", musl_target) }}
{{ make_msrv("verify", glibc_target) }}
if rustup toolchain list | grep -q {{ msvc_target }}; then \
{{ make_msrv("verify", msvc_target) }}; \
fi
# Find TODOs # Find TODOs
[group: 'assess'] [group: 'assess']
todos-assess: todos-assess:
! rg -M 200 --max-columns-preview TODO src ! grep -rn TODO src
alias ta := todos-assess alias ta := todos-assess
# Check for security advisories, unexpected licenses, allowed registries
[group: 'assess']
deny:
cargo-deny check
# Check for spelling mistakes
[group: 'assess']
spell:
typos
# Lint the default and builtin graphs against the schema
[group: 'assess']
schema-lint:
test -z "$(grep -LF '#:schema ./graph-schema.json' static/*.toml)"
taplo lint static/*.toml
# BUILD # BUILD
# Cleanup build artifacts # Cleanup build artifacts
@ -398,8 +466,12 @@ alias bm := build-musl
# Release build # Release build
[group: 'build'] [group: 'build']
release-build target=default_target: release-build target=default_target:
cargo build --timings --target {{ target }} --locked --release {{ maybe_xwin(target) }} build \
du -h target/{{ target }}/release/en --timings --target {{ target }} --locked --release
du -h target/{{ target }}/release/{{ with_suffix("en", target) }}
maybe_xwin(target) := if target == msvc_target { "cargo-xwin" } else { "cargo" }
with_suffix(file, target) := if target == msvc_target { f"{{ file }}.exe" } else { file }
alias rb := release-build alias rb := release-build
@ -417,15 +489,29 @@ release-build-musl:
alias rbm := release-build-musl alias rbm := release-build-musl
# MSVC release build
[group: 'build']
release-build-msvc:
{{ just_cmd }} release-build {{ msvc_target }}
alias rbw := release-build-msvc
# Calculate SHA 256 hashes for release binaries # Calculate SHA 256 hashes for release binaries
[group: 'build'] [group: 'build']
[script]
shasum: shasum:
find target -type d -name 'release' \ binaries=$(find target -type d -name 'release' \
-exec find '{}' -maxdepth 1 -type f -name en -executable ';' \ -exec find '{}' -maxdepth 1 -type f \
| xargs sha256sum -regex '.*/en\(\.exe\)?$' -executable ';' \
)
if [ -n "$binaries" ]; then
printf '%s\n' "$binaries" | xargs sha256sum
else
echo "Can't generate SHA-256 hashes: No release binaries found"
exit 1
fi
## META ## META
[default, private] [default, private]
default: default:
@just --list --unsorted --justfile {{justfile()}} @just --list --unsorted --justfile {{justfile()}}
@ -435,25 +521,25 @@ choose:
alias ch := choose alias ch := choose
[script, private] [script, private, env("CARGO_HOME", "$HOME/.cargo")]
ci recipe: ci recipe:
su ci -c "just {{ recipe }}" su ci -c "just {{ recipe }}"
## VARIABLES ## VARIABLES
export CARGO_TERM_COLOR := 'always' export CARGO_TERM_COLOR := 'always'
musl_target := "x86_64-unknown-linux-musl" musl_target := "x86_64-unknown-linux-musl"
glibc_target := "x86_64-unknown-linux-gnu" glibc_target := "x86_64-unknown-linux-gnu"
msvc_target := "x86_64-pc-windows-msvc"
default_target := musl_target default_target := musl_target
debug_vars := 'DEBUG=${DEBUG:-} DEBUG_FILTER=${DEBUG_FILTER:-} RUST_BACKTRACE=${RUST_BACKTRACE:-} RUSTFLAGS=${RUSTFLAGS:-}' debug_vars := 'DEBUG=${DEBUG:-} DEBUG_FILTER=${DEBUG_FILTER:-} RUST_BACKTRACE=${RUST_BACKTRACE:-} RUSTFLAGS=${RUSTFLAGS:-}'
just_cmd := 'just --timestamp --explain --command-color green' just_cmd := 'just --timestamp --command-color green'
just_cmd_no_ts := 'just --explain --command-color green' just_cmd_no_ts := 'just --explain --command-color green'
watch_cmd := "watchexec -qc -r -e rs,toml,html --color always -- " watch_cmd := "watchexec -qc -r -e rs,toml,html,css --color always -- "
cover_cmd := 'cargo llvm-cov --color always --ignore-filename-regex "main\.rs|log\.rs"' cover_cmd := 'cargo llvm-cov --color always --ignore-filename-regex "main\.rs|log\.rs"'
last_tag := `git tag --sort=-creatordate | head -1 | tr -d v` last_local_tag := `git tag --sort=-creatordate | head -n 1 | tr -d v`
manifest_version := `grep "^version" Cargo.toml | cut -d \" -f 2` manifest_version := `grep "^version" Cargo.toml | cut -d \" -f 2`
lockfile_version := ``` lockfile_version := ```
grep -A 1 'name = "en"' Cargo.lock \ grep -A 1 'name = "en"' Cargo.lock \
@ -461,6 +547,5 @@ lockfile_version := ```
``` ```
## OPTIONS ## OPTIONS
set unstable set unstable
set lazy set lazy

412
Cargo.lock generated
View file

@ -2,12 +2,6 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.1.4" version = "1.1.4"
@ -34,15 +28,15 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.5.0" version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]] [[package]]
name = "axum" name = "axum"
version = "0.8.8" version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
dependencies = [ dependencies = [
"axum-core", "axum-core",
"bytes", "bytes",
@ -90,17 +84,11 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.11.0" version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
@ -123,21 +111,21 @@ dependencies = [
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.20.2" version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.11.1" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.57" version = "1.2.65"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"shlex", "shlex",
@ -151,9 +139,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.44" version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
dependencies = [ dependencies = [
"iana-time-zone", "iana-time-zone",
"num-traits", "num-traits",
@ -197,15 +185,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "crc32fast"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "crossbeam-deque" name = "crossbeam-deque"
version = "0.8.6" version = "0.8.6"
@ -268,7 +247,6 @@ dependencies = [
"tokio", "tokio",
"toml", "toml",
"tower", "tower",
"ureq",
] ]
[[package]] [[package]]
@ -283,16 +261,6 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "flate2"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.2.2" version = "1.2.2"
@ -382,15 +350,15 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.16.1" version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]] [[package]]
name = "http" name = "http"
version = "1.4.0" version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425"
dependencies = [ dependencies = [
"bytes", "bytes",
"itoa", "itoa",
@ -442,9 +410,9 @@ dependencies = [
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.8.1" version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"bytes", "bytes",
@ -456,7 +424,6 @@ dependencies = [
"httpdate", "httpdate",
"itoa", "itoa",
"pin-project-lite", "pin-project-lite",
"pin-utils",
"smallvec", "smallvec",
"tokio", "tokio",
] ]
@ -502,9 +469,9 @@ dependencies = [
[[package]] [[package]]
name = "ignore" name = "ignore"
version = "0.4.25" version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" checksum = "b915661dd01db3f05050265b2477bcc6527b3792388e2749b41623cc592be67d"
dependencies = [ dependencies = [
"crossbeam-deque", "crossbeam-deque",
"globset", "globset",
@ -518,9 +485,9 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.13.0" version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown",
@ -534,11 +501,12 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.91" version = "0.3.102"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31"
dependencies = [ dependencies = [
"once_cell", "cfg-if",
"futures-util",
"wasm-bindgen", "wasm-bindgen",
] ]
@ -550,9 +518,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.183" version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]] [[package]]
name = "libm" name = "libm"
@ -562,9 +530,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.29" version = "0.4.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
[[package]] [[package]]
name = "matchit" name = "matchit"
@ -574,9 +542,9 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.8.0" version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]] [[package]]
name = "mime" name = "mime"
@ -584,25 +552,15 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]] [[package]]
name = "mio" name = "mio"
version = "1.1.1" version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
dependencies = [ dependencies = [
"libc", "libc",
"wasi", "wasi",
"windows-sys 0.61.2", "windows-sys",
] ]
[[package]] [[package]]
@ -722,12 +680,6 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.21" version = "0.2.21"
@ -757,9 +709,9 @@ dependencies = [
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
dependencies = [ dependencies = [
"libc", "libc",
"rand_chacha", "rand_chacha",
@ -787,9 +739,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.12.3" version = "1.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -810,58 +762,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.8.10" version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rustls"
version = "0.23.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
dependencies = [
"log",
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pki-types"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
@ -916,9 +819,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.149" version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@ -940,9 +843,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "1.0.4" version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
dependencies = [ dependencies = [
"serde_core", "serde_core",
] ]
@ -972,21 +875,15 @@ dependencies = [
[[package]] [[package]]
name = "shlex" name = "shlex"
version = "1.3.0" version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "simd-adler32"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
[[package]] [[package]]
name = "siphasher" name = "siphasher"
version = "1.0.2" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
[[package]] [[package]]
name = "slab" name = "slab"
@ -1006,31 +903,25 @@ dependencies = [
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.15.1" version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.6.3" version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.61.2", "windows-sys",
] ]
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.117" version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1067,23 +958,23 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.50.0" version = "1.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
dependencies = [ dependencies = [
"libc", "libc",
"mio", "mio",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"windows-sys 0.61.2", "windows-sys",
] ]
[[package]] [[package]]
name = "tokio-macros" name = "tokio-macros"
version = "2.6.1" version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1116,18 +1007,18 @@ dependencies = [
[[package]] [[package]]
name = "toml_parser" name = "toml_parser"
version = "1.0.10+spec-1.1.0" version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
dependencies = [ dependencies = [
"winnow 1.0.0", "winnow 1.0.3",
] ]
[[package]] [[package]]
name = "toml_writer" name = "toml_writer"
version = "1.0.7+spec-1.1.0" version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d" checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
[[package]] [[package]]
name = "tower" name = "tower"
@ -1179,9 +1070,9 @@ dependencies = [
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.19.0" version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]] [[package]]
name = "ucd-trie" name = "ucd-trie"
@ -1197,50 +1088,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.12.0" version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0"
dependencies = [
"base64",
"flate2",
"log",
"percent-encoding",
"rustls",
"rustls-pki-types",
"ureq-proto",
"utf8-zero",
"webpki-roots",
]
[[package]]
name = "ureq-proto"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c"
dependencies = [
"base64",
"http",
"httparse",
"log",
]
[[package]]
name = "utf8-zero"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e"
[[package]] [[package]]
name = "version_check" name = "version_check"
@ -1266,9 +1116,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.114" version = "0.2.125"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@ -1279,9 +1129,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.114" version = "0.2.125"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@ -1289,9 +1139,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.114" version = "0.2.125"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
@ -1302,29 +1152,20 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.114" version = "0.2.125"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "webpki-roots"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed"
dependencies = [
"rustls-pki-types",
]
[[package]] [[package]]
name = "winapi-util" name = "winapi-util"
version = "0.1.11" version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [ dependencies = [
"windows-sys 0.61.2", "windows-sys",
] ]
[[package]] [[package]]
@ -1386,15 +1227,6 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.61.2" version = "0.61.2"
@ -1404,70 +1236,6 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.7.15" version = "0.7.15"
@ -1476,36 +1244,30 @@ checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "1.0.0" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.47" version = "0.8.52"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
dependencies = [ dependencies = [
"zerocopy-derive", "zerocopy-derive",
] ]
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.8.47" version = "0.8.52"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",
] ]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]] [[package]]
name = "zmij" name = "zmij"
version = "1.0.21" version = "1.0.21"

View file

@ -23,7 +23,6 @@ serde = { version = "1.0.228", features = ["derive"] }
toml = "0.9.8" toml = "0.9.8"
[dev-dependencies] [dev-dependencies]
ureq = "3"
tower = { version = "0.5.2", features = ["util"] } tower = { version = "0.5.2", features = ["util"] }
[lints.rust] [lints.rust]
@ -55,6 +54,7 @@ cast_sign_loss = "warn"
checked_conversions = "warn" checked_conversions = "warn"
clear_with_drain = "warn" clear_with_drain = "warn"
cloned_instead_of_copied = "warn" cloned_instead_of_copied = "warn"
cmp_owned = "warn"
coerce_container_to_any = "warn" coerce_container_to_any = "warn"
collapsible_else_if = "allow" collapsible_else_if = "allow"
collapsible_if = "allow" collapsible_if = "allow"
@ -71,6 +71,7 @@ doc_link_code = "warn"
doc_link_with_quotes = "warn" doc_link_with_quotes = "warn"
doc_markdown = "warn" doc_markdown = "warn"
doc_paragraphs_missing_punctuation = "warn" doc_paragraphs_missing_punctuation = "warn"
double_must_use = "warn"
duration_suboptimal_units = "warn" duration_suboptimal_units = "warn"
empty_drop = "warn" empty_drop = "warn"
empty_enum_variants_with_brackets = "warn" empty_enum_variants_with_brackets = "warn"
@ -81,6 +82,7 @@ error_impl_error = "warn"
exit = "warn" exit = "warn"
expect_used = "warn" expect_used = "warn"
expl_impl_clone_on_copy = "warn" expl_impl_clone_on_copy = "warn"
explicit_counter_loop = "warn"
explicit_deref_methods = "warn" explicit_deref_methods = "warn"
explicit_into_iter_loop = "warn" explicit_into_iter_loop = "warn"
explicit_iter_loop = "warn" explicit_iter_loop = "warn"
@ -94,7 +96,6 @@ float_cmp_const = "warn"
fn_to_numeric_cast_any = "warn" fn_to_numeric_cast_any = "warn"
format_collect = "warn" format_collect = "warn"
format_push_string = "warn" format_push_string = "warn"
from_iter_instead_of_collect = "warn"
get_unwrap = "warn" get_unwrap = "warn"
if_not_else = "warn" if_not_else = "warn"
if_then_some_else_none = "warn" if_then_some_else_none = "warn"
@ -103,11 +104,13 @@ ignored_unit_patterns = "warn"
implicit_clone = "warn" implicit_clone = "warn"
implicit_hasher = "warn" implicit_hasher = "warn"
imprecise_flops = "warn" imprecise_flops = "warn"
incompatible_msrv = "warn"
inconsistent_struct_constructor = "warn" inconsistent_struct_constructor = "warn"
index_refutable_slice = "warn" index_refutable_slice = "warn"
indexing_slicing = "warn" indexing_slicing = "warn"
inefficient_to_string = "warn" inefficient_to_string = "warn"
infinite_loop = "warn" infinite_loop = "warn"
int_plus_one = "warn"
integer_division = "warn" integer_division = "warn"
integer_division_remainder_used = "warn" integer_division_remainder_used = "warn"
into_iter_without_iter = "warn" into_iter_without_iter = "warn"
@ -115,6 +118,7 @@ invalid_upcast_comparisons = "warn"
ip_constant = "warn" ip_constant = "warn"
iter_filter_is_ok = "warn" iter_filter_is_ok = "warn"
iter_filter_is_some = "warn" iter_filter_is_some = "warn"
iter_kv_map = "warn"
iter_not_returning_iterator = "warn" iter_not_returning_iterator = "warn"
iter_on_empty_collections = "warn" iter_on_empty_collections = "warn"
iter_on_single_items = "warn" iter_on_single_items = "warn"
@ -130,14 +134,19 @@ literal_string_with_formatting_args = "warn"
lossy_float_literal = "warn" lossy_float_literal = "warn"
macro_use_imports = "warn" macro_use_imports = "warn"
manual_assert = "warn" manual_assert = "warn"
manual_checked_ops = "warn"
manual_ilog2 = "warn" manual_ilog2 = "warn"
manual_instant_elapsed = "warn" manual_instant_elapsed = "warn"
manual_is_multiple_of = "warn"
manual_is_power_of_two = "warn" manual_is_power_of_two = "warn"
manual_is_variant_and = "warn" manual_is_variant_and = "warn"
manual_let_else = "warn" manual_let_else = "warn"
manual_midpoint = "warn" manual_midpoint = "warn"
manual_non_exhaustive = "allow" manual_non_exhaustive = "allow"
manual_option_zip = "warn"
manual_pop_if = "warn"
manual_string_new = "warn" manual_string_new = "warn"
manual_take = "warn"
many_single_char_names = "warn" many_single_char_names = "warn"
map_err_ignore = "warn" map_err_ignore = "warn"
map_with_unused_argument_over_ranges = "warn" map_with_unused_argument_over_ranges = "warn"
@ -180,15 +189,19 @@ option_option = "warn"
panic_in_result_fn = "warn" panic_in_result_fn = "warn"
path_buf_push_overwrite = "warn" path_buf_push_overwrite = "warn"
pathbuf_init_then_push = "warn" pathbuf_init_then_push = "warn"
possible_missing_else = "warn"
pub_underscore_fields = "warn" pub_underscore_fields = "warn"
pub_without_shorthand = "warn" pub_without_shorthand = "warn"
question_mark = "warn"
range_minus_one = "warn" range_minus_one = "warn"
range_plus_one = "warn" range_plus_one = "warn"
rc_buffer = "warn" rc_buffer = "warn"
rc_mutex = "warn" rc_mutex = "warn"
read_zero_byte_vec = "warn" read_zero_byte_vec = "warn"
redundant_clone = "warn" redundant_clone = "warn"
redundant_closure = "warn"
redundant_closure_for_method_calls = "warn" redundant_closure_for_method_calls = "warn"
redundant_iter_cloned = "warn"
redundant_pub_crate = "warn" redundant_pub_crate = "warn"
redundant_test_prefix = "warn" redundant_test_prefix = "warn"
redundant_type_annotations = "warn" redundant_type_annotations = "warn"
@ -196,6 +209,7 @@ ref_binding_to_reference = "warn"
ref_option = "warn" ref_option = "warn"
ref_option_ref = "warn" ref_option_ref = "warn"
renamed_function_params = "warn" renamed_function_params = "warn"
replace_box = "warn"
rest_pat_in_fully_bound_structs = "warn" rest_pat_in_fully_bound_structs = "warn"
return_and_then = "warn" return_and_then = "warn"
return_self_not_must_use = "warn" return_self_not_must_use = "warn"
@ -236,12 +250,17 @@ unchecked_time_subtraction = "warn"
unicode_not_nfc = "warn" unicode_not_nfc = "warn"
uninlined_format_args = "warn" uninlined_format_args = "warn"
unnecessary_box_returns = "warn" unnecessary_box_returns = "warn"
unnecessary_cast = "warn"
unnecessary_debug_formatting = "warn" unnecessary_debug_formatting = "warn"
unnecessary_fold = "warn"
unnecessary_join = "warn" unnecessary_join = "warn"
unnecessary_literal_bound = "warn" unnecessary_literal_bound = "warn"
unnecessary_option_map_or_else = "warn"
unnecessary_result_map_or_else = "warn"
unnecessary_self_imports = "warn" unnecessary_self_imports = "warn"
unnecessary_semicolon = "warn" unnecessary_semicolon = "warn"
unnecessary_struct_initialization = "warn" unnecessary_struct_initialization = "warn"
unnecessary_trailing_comma = "warn"
unnecessary_wraps = "warn" unnecessary_wraps = "warn"
unneeded_field_pattern = "warn" unneeded_field_pattern = "warn"
unnested_or_patterns = "warn" unnested_or_patterns = "warn"
@ -258,6 +277,8 @@ unwrap_in_result = "warn"
unwrap_used = "warn" unwrap_used = "warn"
used_underscore_binding = "warn" used_underscore_binding = "warn"
used_underscore_items = "warn" used_underscore_items = "warn"
useless_concat = "warn"
useless_conversion = "warn"
useless_let_if_seq = "warn" useless_let_if_seq = "warn"
verbose_file_reads = "warn" verbose_file_reads = "warn"
volatile_composites = "warn" volatile_composites = "warn"
@ -269,3 +290,12 @@ zero_sized_map_values = "warn"
negative_feature_names = "warn" negative_feature_names = "warn"
redundant_feature_names = "warn" redundant_feature_names = "warn"
wildcard_dependencies = "warn" wildcard_dependencies = "warn"
[package.metadata.typos.default]
extend-ignore-re = [
"oficial do Brasil",
"Creative Commons ND",
"Creative Commons BY-ND",
"CC_BY_ND_",
"(?ms)^mod tests \\{.*^\\}",
]

View file

@ -10,17 +10,10 @@ You can learn more and see what en looks like by visiting the [homepage](https:/
## Install and run ## Install and run
See the [Documentation](https://en.jutty.dev/node/Documentation) page for instructions and how to install and start using en. See the [Get Started](https://en.jutty.dev/node/GetStarted) page for instructions and how to install and start using en.
## Roadmap ## Roadmap
On a high-level, here are some things that en intends to achieve at some point: For a high-level view of what's in the future for en, see the [What's ahead section](https://en.jutty.dev/node/Introduction#What's) of the docs Introduction.
1. Separate 'en' the server from 'en' the source-to-source translator. This would allow using en's markup syntax as a standalone compile-to-HTML language, effectively creating an "en cli" that should also have options to make it more practical to manipulate a graph, such as adding new nodes or querying your graph For a more detailed outline of what's planned, along with what's already been completed, see the [roadmap](https://en.jutty.dev/node/Roadmap).
3. Multifile graphs, with _optional_ TOML frontmatter
2. Make en more fitting to the 'note taking' use case
3. Multigraph, with the server being capable to let the user choose which graph to render and with inter-graph connections
4. Different rendering modes such as to a static website, to PDF or EPUB (with paper-friendly metadata)
5. Stateful and stateless online graph editing with 'export to TOML'
For a more detailed outline of what's planned for the future of en, along with what's already been completed, see the [roadmap](https://en.jutty.dev/node/Roadmap).

243
deny.toml Normal file
View file

@ -0,0 +1,243 @@
# This template contains all of the possible sections and their default values
# Note that all fields that take a lint level have these possible values:
# * deny - An error will be produced and the check will fail
# * warn - A warning will be produced, but the check will not fail
# * allow - No warning or error will be produced, though in some cases a note
# will be
# The values provided in this template are the default values that will be used
# when any section or field is not specified in your own configuration
# Root options
# The graph table configures how the dependency graph is constructed and thus
# which crates the checks are performed against
[graph]
# If 1 or more target triples (and optionally, target_features) are specified,
# only the specified targets will be checked when running `cargo deny check`.
# This means, if a particular package is only ever used as a target specific
# dependency, such as, for example, the `nix` crate only being used via the
# `target_family = "unix"` configuration, that only having windows targets in
# this list would mean the nix crate, as well as any of its exclusive
# dependencies not shared by any other crates, would be ignored, as the target
# list here is effectively saying which targets you are building for.
targets = [
# The triple can be any string, but only the target triples built in to
# rustc (as of 1.40) can be checked against actual config expressions
#"x86_64-unknown-linux-musl",
# You can also specify which target_features you promise are enabled for a
# particular target. target_features are currently not validated against
# the actual valid features supported by the target architecture.
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
]
# When creating the dependency graph used as the source of truth when checks are
# executed, this field can be used to prune crates from the graph, removing them
# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate
# is pruned from the graph, all of its dependencies will also be pruned unless
# they are connected to another crate in the graph that hasn't been pruned,
# so it should be used with care. The identifiers are [Package ID Specifications]
# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html)
#exclude = []
# If true, metadata will be collected with `--all-features`. Note that this can't
# be toggled off if true, if you want to conditionally enable `--all-features` it
# is recommended to pass `--all-features` on the cmd line instead
all-features = false
# If true, metadata will be collected with `--no-default-features`. The same
# caveat with `all-features` applies
no-default-features = false
# If set, these feature will be enabled when collecting metadata. If `--features`
# is specified on the cmd line they will take precedence over this option.
#features = []
# The output table provides options for how/if diagnostics are outputted
[output]
# When outputting inclusion graphs in diagnostics that include features, this
# option can be used to specify the depth at which feature edges will be added.
# This option is included since the graphs can be quite large and the addition
# of features from the crate(s) to all of the graph roots can be far too verbose.
# This option can be overridden via `--feature-depth` on the cmd line
feature-depth = 1
# This section is considered when running `cargo deny check advisories`
# More documentation for the advisories section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
[advisories]
# The path where the advisory databases are cloned/fetched into
#db-path = "$CARGO_HOME/advisory-dbs"
# The url(s) of the advisory databases to use
#db-urls = ["https://github.com/rustsec/advisory-db"]
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
ignore = [
#"RUSTSEC-0000-0000",
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
]
# If this is true, then cargo deny will use the git executable to fetch advisory database.
# If this is false, then it uses a built-in git library.
# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
# See Git Authentication for more information about setting up git authentication.
#git-fetch-with-cli = true
# This section is considered when running `cargo deny check licenses`
# More documentation for the licenses section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
[licenses]
# List of explicitly allowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
allow = [
"MIT",
"BSD-2-Clause",
"BSD-3-Clause",
"Apache-2.0",
"Apache-2.0 WITH LLVM-exception",
"AGPL-3.0-only",
"Unicode-3.0",
]
# The confidence threshold for detecting a license from license text.
# The higher the value, the more closely the license text must be to the
# canonical license text of a valid SPDX license file.
# [possible values: any between 0.0 and 1.0].
confidence-threshold = 0.8
# Allow 1 or more licenses on a per-crate basis, so that particular licenses
# aren't accepted for every possible crate as with the normal allow list
exceptions = [
# Each entry is the crate and version constraint, and its specific allow
# list
#{ allow = ["Zlib"], crate = "adler32" },
]
# Some crates don't have (easily) machine readable licensing information,
# adding a clarification entry for it allows you to manually specify the
# licensing information
#[[licenses.clarify]]
# The package spec the clarification applies to
#crate = "ring"
# The SPDX expression for the license requirements of the crate
#expression = "MIT AND ISC AND OpenSSL"
# One or more files in the crate's source used as the "source of truth" for
# the license expression. If the contents match, the clarification will be used
# when running the license check, otherwise the clarification will be ignored
# and the crate will be checked normally, which may produce warnings or errors
# depending on the rest of your configuration
#license-files = [
# Each entry is a crate relative path, and the (opaque) hash of its contents
#{ path = "LICENSE", hash = 0xbd0eed23 }
#]
[licenses.private]
# If true, ignores workspace crates that aren't published, or are only
# published to private registries.
# To see how to mark a crate as unpublished (to the official registry),
# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
ignore = false
# One or more private registries that you might publish crates to, if a crate
# is only published to private registries, and ignore is true, the crate will
# not have its license(s) checked
registries = [
#"https://sekretz.com/registry
]
# This section is considered when running `cargo deny check bans`.
# More documentation about the 'bans' section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
[bans]
# Lint level for when multiple versions of the same crate are detected
multiple-versions = "warn"
# Lint level for when a crate version requirement is `*`
wildcards = "allow"
# The graph highlighting used when creating dotgraphs for crates
# with multiple versions
# * lowest-version - The path to the lowest versioned duplicate is highlighted
# * simplest-path - The path to the version with the fewest edges is highlighted
# * all - Both lowest-version and simplest-path are used
highlight = "all"
# The default lint level for `default` features for crates that are members of
# the workspace that is being checked. This can be overridden by allowing/denying
# `default` on a crate-by-crate basis if desired.
workspace-default-features = "allow"
# The default lint level for `default` features for external crates that are not
# members of the workspace. This can be overridden by allowing/denying `default`
# on a crate-by-crate basis if desired.
external-default-features = "allow"
# List of crates that are allowed. Use with care!
allow = [
#"ansi_term@0.11.0",
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" },
]
# If true, workspace members are automatically allowed even when using deny-by-default
# This is useful for organizations that want to deny all external dependencies by default
# but allow their own workspace crates without having to explicitly list them
allow-workspace = false
# List of crates to deny
deny = [
#"ansi_term@0.11.0",
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" },
# Wrapper crates can optionally be specified to allow the crate when it
# is a direct dependency of the otherwise banned crate
#{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] },
]
# List of features to allow/deny
# Each entry the name of a crate and a version range. If version is
# not specified, all versions will be matched.
#[[bans.features]]
#crate = "reqwest"
# Features to not allow
#deny = ["json"]
# Features to allow
#allow = [
# "rustls",
# "__rustls",
# "__tls",
# "hyper-rustls",
# "rustls",
# "rustls-pemfile",
# "rustls-tls-webpki-roots",
# "tokio-rustls",
# "webpki-roots",
#]
# If true, the allowed features must exactly match the enabled feature set. If
# this is set there is no point setting `deny`
#exact = true
# Certain crates/versions that will be skipped when doing duplicate detection.
skip = [
#"ansi_term@0.11.0",
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" },
]
# Similarly to `skip` allows you to skip certain crates during duplicate
# detection. Unlike skip, it also includes the entire tree of transitive
# dependencies starting at the specified crate, up to a certain depth, which is
# by default infinite.
skip-tree = [
#"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies
#{ crate = "ansi_term@0.11.0", depth = 20 },
]
# This section is considered when running `cargo deny check sources`.
# More documentation about the 'sources' section can be found here:
# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
[sources]
# Lint level for what to happen when a crate from a crate registry that is not
# in the allow list is encountered
unknown-registry = "warn"
# Lint level for what to happen when a crate from a git repository that is not
# in the allow list is encountered
unknown-git = "warn"
# List of URLs for allowed crate registries. Defaults to the crates.io index
# if not specified. If it is specified but empty, no registries are allowed.
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
# List of URLs for allowed Git repositories
allow-git = []
[sources.allow-org]
# github.com organizations to allow git sources for
github = []
# gitlab.com organizations to allow git sources for
gitlab = []
# bitbucket.org organizations to allow git sources for
bitbucket = []

26
flake.lock generated Normal file
View file

@ -0,0 +1,26 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1780453794,
"narHash": "sha256-bXMRa9VTsHSPXL4Cw8R6JJLQeY3Y/IP4+YJCYVmQ7FY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6b316287bae2ee04c9b93c8c858d930fd07d7338",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-26.05",
"type": "indirect"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

99
flake.nix Normal file
View file

@ -0,0 +1,99 @@
# Note this is just a draft, not properly tested or documented yet
{
description = "A non-linear writing instrument.";
inputs.nixpkgs.url = "nixpkgs/nixos-26.05";
outputs = { nixpkgs, self }: let
name = "en";
version = "0.4.0-alpha";
supportedSystems = [
"x86_64-linux"
];
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; });
in {
packages = forAllSystems (system: let pkgs = nixpkgsFor.${system};
in {
default = pkgs.rustPlatform.buildRustPackage {
inherit name version;
src = ./.;
nativeBuildInputs = with pkgs; [
just
];
dontUseJustInstall = true;
dontUseJustBuild = true;
cargoHash =
"sha256-U6jf48KmqcdCBYKyZhYPpiv5qj98SjlNqw8Ii2mJn0I=";
};
});
devShells = forAllSystems (system:
let
pkgs = nixpkgsFor.${system};
base = with pkgs; [
rustup
just
watchexec
cargo-deny
cargo-llvm-cov
cargo-mutants
cargo-msrv
typos
taplo
];
in {
default = pkgs.mkShell { buildInputs = base; };
cross = pkgs.mkShell {
buildInputs = with pkgs; base ++ [
cargo-xwin
wine
];
};
}
);
nixosModules.en = { config, lib, system, ... }:
{
options.within.services.en = {
enable = lib.mkEnableOption "enable en server";
port = lib.mkOption {
description = "Port to serve your graph on";
defaultText = "A random port chosen by the OS";
type = lib.types.port;
};
host = lib.mkOption {
description = "Address or hostname to serve your graph on";
default = "0.0.0.0";
type = lib.types.str;
};
graph = lib.mkOption {
description = "File path pointing to your graph file";
default = "./static/graph.toml";
type = lib.types.path;
};
};
config =
lib.mkIf config.within.services.en.enable {
systemd.services.en = {
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Restart = "always";
ExecStart = let
user_opts = config.within.services.en;
in
"${self.packages.${system}.default}/bin/en"
+ " --port ${user_opts.port}"
+ " --host ${user_opts.host}"
+ " --graph ${user_opts.graph}";
};
};
};
};
};
}

View file

@ -396,7 +396,7 @@ impl Graph {
); );
} else { } else {
if let Some(destination) = anchor.destination() if let Some(destination) = anchor.destination()
&& !anchor.external() && !anchor.absolute()
{ {
let trimmed_destination = destination let trimmed_destination = destination
.trim_start_matches("/node/") .trim_start_matches("/node/")
@ -665,7 +665,7 @@ mod tests {
"title": "JSON", "title": "JSON",
"links": [], "links": [],
"id": "JSON", "id": "JSON",
"hidden": false, "listed": true,
"connections": {} "connections": {}
} }
}, },

View file

@ -18,8 +18,8 @@ pub struct Node {
pub links: Vec<String>, pub links: Vec<String>,
#[serde(default)] #[serde(default)]
pub redirect: String, pub redirect: String,
#[serde(default)] #[serde(default = "mktrue")]
pub hidden: bool, pub listed: bool,
#[serde(default)] #[serde(default)]
pub connections: HashMap<String, Edge>, pub connections: HashMap<String, Edge>,
@ -28,6 +28,9 @@ pub struct Node {
pub stats: Stats, pub stats: Stats,
} }
// See: https://github.com/serde-rs/serde/issues/368
const fn mktrue() -> bool { true }
#[derive(Serialize, Deserialize, Clone, Default, Eq, PartialEq, Debug)] #[derive(Serialize, Deserialize, Clone, Default, Eq, PartialEq, Debug)]
pub struct Stats { pub struct Stats {
pub outgoing: u32, pub outgoing: u32,
@ -46,7 +49,7 @@ impl Node {
connections: HashMap::default(), connections: HashMap::default(),
links: vec![], links: vec![],
redirect: String::default(), redirect: String::default(),
hidden: false, listed: true,
summary: String::default(), summary: String::default(),
stats: Stats::default(), stats: Stats::default(),
} }
@ -78,8 +81,8 @@ impl std::fmt::Display for Node {
meta_elements.push(format!("links:{links}")); meta_elements.push(format!("links:{links}"));
} }
if self.hidden { if !self.listed {
meta_elements.push(String::from("hidden")); meta_elements.push(String::from("unlisted"));
} }
let meta = meta_elements.join(" "); let meta = meta_elements.join(" ");
@ -143,7 +146,7 @@ mod tests {
) )
); );
node.hidden = true; node.listed = false;
assert_eq!( assert_eq!(
format!("{node}"), format!("{node}"),
@ -153,7 +156,7 @@ mod tests {
text:15l summary:{} \ text:15l summary:{} \
redirect:{redirect} \ redirect:{redirect} \
links:{} \ links:{} \
hidden\ unlisted\
]", ]",
summary.len(), summary.len(),
node.links.len(), node.links.len(),

View file

@ -216,7 +216,7 @@ impl Asset {
/// ///
/// Defaults are found in the `fixed::DEFAULTS` map. /// Defaults are found in the `fixed::DEFAULTS` map.
/// ///
/// Returns a `FallbackError` if neither is found or an I/O error ocurred. /// Returns a `FallbackError` if neither is found or an I/O error occurred.
fn fallback(path: &str, graph: &Graph) -> Result<Asset, AssetError> { fn fallback(path: &str, graph: &Graph) -> Result<Asset, AssetError> {
let target = format!("static/public/assets/{path}"); let target = format!("static/public/assets/{path}");
let defaults: HashMap<&str, &str> = TEXTS.iter().copied().collect(); let defaults: HashMap<&str, &str> = TEXTS.iter().copied().collect();
@ -292,7 +292,7 @@ pub async fn file(
} else { } else {
String::from( String::from(
"The requested file exists, but the server lacks \ "The requested file exists, but the server lacks \
permission to access it or another I/O error ocurred.", permission to access it or another I/O error occurred.",
) )
}; };
if log::env_level() >= DEBUG { if log::env_level() >= DEBUG {

View file

@ -93,7 +93,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn docs_redirect() { async fn docs_redirect() {
let response = wrap_node("docs").await; let response = wrap_node("RedirectTest").await;
assert_eq!(response.status(), StatusCode::PERMANENT_REDIRECT); assert_eq!(response.status(), StatusCode::PERMANENT_REDIRECT);
} }

View file

@ -1,12 +1,13 @@
use crate::syntax::content::parser::{ use crate::syntax::content::parser::{
State, Token, State, Token,
token::{Header, Paragraph, PreFormat, Verse}, token::{Header, Paragraph, Verse},
}; };
pub mod anchor; pub mod anchor;
pub mod block; pub mod block;
pub mod inline; pub mod inline;
pub mod list; pub mod list;
pub mod preformat;
pub mod quote; pub mod quote;
pub mod table; pub mod table;
@ -38,30 +39,32 @@ pub enum Inline {
} }
/// # Panics /// # Panics
/// Panics if there is an open header or list at end of input. /// Panics if there is an open token at end of input that can't be easily
/// closed by simply adding a matching closing token. This normally is handled
/// by context parsers and probably indicates an error in one of them.
pub fn close(state: &State, tokens: &mut Vec<Token>) { pub fn close(state: &State, tokens: &mut Vec<Token>) {
match state.context.block { match state.context.block {
Block::PreFormat => {
tokens.push(Token::PreFormat(PreFormat::new(false)));
},
Block::Paragraph => { Block::Paragraph => {
tokens.push(Token::Paragraph(Paragraph::new(false))); tokens.push(Token::Paragraph(Paragraph::new(false)));
}, },
Block::List => {
panic!("End of input with open list")
},
Block::Header(level) => { Block::Header(level) => {
tokens.push(Token::Header(Header::from_u8(level, false, None))); tokens.push(Token::Header(Header::from_u8(level, false, None)));
}, },
Block::Quote => {
panic!("End of input with open quote")
},
Block::Table => {
panic!("End of input with open table")
},
Block::Verse => { Block::Verse => {
tokens.push(Token::Verse(Verse::new(false))); tokens.push(Token::Verse(Verse::new(false)));
}, },
Block::PreFormat => {
panic!("End of input with open preformat: {tokens:#?}")
},
Block::List => {
panic!("End of input with open list: {tokens:#?}")
},
Block::Quote => {
panic!("End of input with open quote: {tokens:#?}")
},
Block::Table => {
panic!("End of input with open table: {tokens:#?}")
},
Block::None => (), Block::None => (),
} }
} }

View file

@ -34,6 +34,9 @@ pub fn parse(
log!(VERBOSE, "End: Next lexeme is a pipe"); log!(VERBOSE, "End: Next lexeme is a pipe");
buffer.text.push_str(&lexeme.text()); buffer.text.push_str(&lexeme.text());
candidate.set_text(&buffer.text.clone()); candidate.set_text(&buffer.text.clone());
if buffer.text.starts_with('/') {
candidate.set_absolute(true);
}
} else { } else {
log!( log!(
VERBOSE, VERBOSE,
@ -84,6 +87,13 @@ pub fn parse(
"State: Found a pipe, but no boundary: destination follows" "State: Found a pipe, but no boundary: destination follows"
); );
candidate.set_balanced(true); candidate.set_balanced(true);
if lexeme.match_next_first_char('/') {
log!(
VERBOSE,
"State: Destination starts with a dash, marking as absolute"
);
candidate.set_absolute(true);
}
return true; return true;
} else if lexeme.match_char(':') { } else if lexeme.match_char(':') {
log!(VERBOSE, "State: Found a colon, marking anchor as external"); log!(VERBOSE, "State: Found a colon, marking anchor as external");
@ -205,8 +215,10 @@ mod tests {
fn needless_three_pipe_anchor() { fn needless_three_pipe_anchor() {
assert_eq!( assert_eq!(
read("|Node|Destination|"), read("|Node|Destination|"),
concat!(r#"<p><a class="detached" title="" "#, concat!(
r#"href="/node/Destination">Node</a></p>"#) r#"<p><a class="detached" title="" "#,
r#"href="/node/Destination">Node</a></p>"#
)
); );
} }
@ -225,9 +237,11 @@ mod tests {
fn anchor_to_node_s() { fn anchor_to_node_s() {
assert_eq!( assert_eq!(
read("The |letter s|s|'s node: |s|!"), read("The |letter s|s|'s node: |s|!"),
concat!(r#"<p>The <a class="detached" title="" "#, concat!(
r#"<p>The <a class="detached" title="" "#,
r#"href="/node/s">letter s</a>'s node: "#, r#"href="/node/s">letter s</a>'s node: "#,
r#"<a class="detached" title="" href="/node/s">s</a>!</p>"#) r#"<a class="detached" title="" href="/node/s">s</a>!</p>"#
)
); );
} }
@ -246,9 +260,11 @@ mod tests {
fn leading_plural_anchor() { fn leading_plural_anchor() {
assert_eq!( assert_eq!(
read("Interfaces are |element|s of |system|s."), read("Interfaces are |element|s of |system|s."),
concat!(r#"<p>Interfaces are <a class="detached" title="" "#, concat!(
r#"<p>Interfaces are <a class="detached" title="" "#,
r#"href="/node/element">elements</a> of <a class="detached" "#, r#"href="/node/element">elements</a> of <a class="detached" "#,
r#"title="" href="/node/system">systems</a>.</p>"#) r#"title="" href="/node/system">systems</a>.</p>"#
)
); );
} }
@ -268,9 +284,11 @@ mod tests {
fn explicit_end_of_destination() { fn explicit_end_of_destination() {
assert_eq!( assert_eq!(
read("interactions are |basic elements|BasicElements| of systems"), read("interactions are |basic elements|BasicElements| of systems"),
concat!(r#"<p>interactions are <a class="detached" title="" "#, concat!(
r#"<p>interactions are <a class="detached" title="" "#,
r#"href="/node/BasicElements">basic elements</a> of "#, r#"href="/node/BasicElements">basic elements</a> of "#,
r#"systems</p>"#) r#"systems</p>"#
)
); );
} }
@ -327,6 +345,21 @@ mod tests {
); );
} }
#[test]
fn absolute_anchor() {
let parse_result =
parser::rich_read("see the |raw endpoints|/data|.", &Graph::load());
println!("Parsed tokens: {:#?}", parse_result.tokens);
assert_eq!(
parse_result.text.unwrap(),
concat!(
r#"<p>see the <a class="absolute" title="" "#,
r#"href="/data">"#,
r#"raw endpoints</a>.</p>"#,
),
);
}
#[test] #[test]
fn http_external_anchor() { fn http_external_anchor() {
assert_eq!( assert_eq!(

View file

@ -6,15 +6,17 @@ use crate::{
syntax::content::{ syntax::content::{
Parseable as _, Parseable as _,
parser::{ parser::{
Block, Lexeme, State, Token, Block, Lexeme, State, Token, context,
token::{ token::{
Header, LineBreak, List, Literal, Paragraph, PreFormat, Quote, Header, LineBreak, List, Paragraph, PreFormat, Quote, Table,
Table, Verse, Verse,
}, },
}, },
}, },
}; };
/// A return of `true` will trigger a `continue` on the outer parser, causing
/// no more subsequent parsing of the current lexeme.
pub fn parse( pub fn parse(
lexeme: &Lexeme, lexeme: &Lexeme,
state: &mut State, state: &mut State,
@ -27,8 +29,7 @@ pub fn parse(
if PreFormat::probe(lexeme) { if PreFormat::probe(lexeme) {
log!(VERBOSE, "Block Context: None -> PreFormat on {lexeme}"); log!(VERBOSE, "Block Context: None -> PreFormat on {lexeme}");
state.context.block = Block::PreFormat; state.context.block = Block::PreFormat;
tokens.push(Token::PreFormat(PreFormat::new(true))); return true
return true;
} else if Header::probe(lexeme) { } else if Header::probe(lexeme) {
let mut header = Header::lex(lexeme); let mut header = Header::lex(lexeme);
header.dom_id = Some(Header::make_id( header.dom_id = Some(Header::make_id(
@ -44,7 +45,7 @@ pub fn parse(
log!(VERBOSE, "Block Context: None -> List on {lexeme}"); log!(VERBOSE, "Block Context: None -> List on {lexeme}");
state.context.block = Block::List; state.context.block = Block::List;
state.buffers.list.candidate.ordered = lexeme.match_char('+'); state.buffers.list.candidate.ordered = lexeme.match_char('+');
return super::list::parse( return context::list::parse(
lexeme, state, tokens, iterator, graph, lexeme, state, tokens, iterator, graph,
); );
} else if Quote::probe(lexeme) { } else if Quote::probe(lexeme) {
@ -71,14 +72,7 @@ pub fn parse(
} }
}, },
Block::PreFormat => { Block::PreFormat => {
if PreFormat::probe(lexeme) { return context::preformat::parse(lexeme, state, tokens, iterator);
tokens.push(Token::PreFormat(PreFormat::new(false)));
log!(VERBOSE, "Block Context: PreFormat -> None on {lexeme}");
state.context.block = Block::None;
} else {
tokens.push(Token::Literal(Literal::lex(lexeme)));
}
return true;
}, },
Block::Paragraph => { Block::Paragraph => {
if Paragraph::probe_end(lexeme) { if Paragraph::probe_end(lexeme) {
@ -95,13 +89,17 @@ pub fn parse(
} }
}, },
Block::List => { Block::List => {
return super::list::parse(lexeme, state, tokens, iterator, graph); return context::list::parse(lexeme, state, tokens, iterator, graph);
}, },
Block::Quote => { Block::Quote => {
return super::quote::parse(lexeme, state, tokens, iterator, graph); return context::quote::parse(
lexeme, state, tokens, iterator, graph,
);
}, },
Block::Table => { Block::Table => {
return super::table::parse(lexeme, state, tokens, iterator, graph); return context::table::parse(
lexeme, state, tokens, iterator, graph,
);
}, },
Block::Verse => { Block::Verse => {
if Verse::probe_end(lexeme) { if Verse::probe_end(lexeme) {
@ -127,7 +125,7 @@ mod tests {
graph::Graph, graph::Graph,
syntax::content::parser::{ syntax::content::parser::{
self, Block, State, Token, context, self, Block, State, Token, context,
token::{Header, PreFormat, header::Level}, token::{Header, header::Level},
}, },
}; };
@ -161,16 +159,6 @@ mod tests {
assert_eq!(vec, vec![Token::Header(Header::from_u8(1, false, None))]); assert_eq!(vec, vec![Token::Header(Header::from_u8(1, false, None))]);
} }
#[test]
fn end_with_open_preformat() {
let mut state = State::default();
state.context.block = Block::PreFormat;
let mut vec: Vec<Token> = vec![];
context::close(&state, &mut vec);
assert_eq!(vec, vec![Token::PreFormat(PreFormat::new(false))]);
}
#[test] #[test]
fn truncated_header_level() { fn truncated_header_level() {
let u: usize = 999; let u: usize = 999;

View file

@ -0,0 +1,61 @@
use std::{iter::Peekable, slice::Iter};
use crate::{
prelude::*,
syntax::content::{
Parseable as _,
parser::{Lexeme, State, Token, context::Block, token::PreFormat},
},
};
/// Handles open `PreFormat` contexts until a block is fully parsed.
///
/// A return of `true` will trigger a continue in the outer parser,
/// skipping any further parsing of the current lexeme.
///
/// # Panics
/// This parser can handle only the List context, and will panic if passed an
/// unrelated context since it has no knowledge on how to handle them.
pub fn parse(
lexeme: &Lexeme,
state: &mut State,
tokens: &mut Vec<Token>,
iterator: &mut Peekable<Iter<'_, Lexeme>>,
) -> bool {
let buffer = &mut state.buffers.preformat;
let candidate = &mut buffer.candidate;
#[expect(clippy::wildcard_enum_match_arm)]
match state.context.block {
Block::PreFormat => {
if lexeme.match_first_char('<') {
candidate.text.push_str("&lt;");
candidate.text.push_str(
lexeme.text().strip_prefix('<').unwrap_or(&lexeme.text()),
);
} else if lexeme.match_last_char('>') {
candidate.text.push_str(
lexeme.text().strip_suffix('>').unwrap_or(&lexeme.text()),
);
candidate.text.push_str("&gt;");
} else if lexeme.match_char('\\') {
candidate.text.push_str(lexeme.next().as_str());
iterator.next();
return true;
} else if PreFormat::probe(lexeme) {
// found end of block, push it and reset state
log!(VERBOSE, "Accepting preformat candidate {candidate}");
tokens.push(Token::PreFormat(candidate.clone()));
state.context.block = Block::None;
*candidate = PreFormat::default();
} else {
// anything else is pushed into the candidate preformat's text
candidate.text.push_str(&lexeme.text());
}
},
_ => {
panic!("PreFormat context parser called for {:?}", state.context)
},
}
true
}

View file

@ -32,6 +32,8 @@ impl Lexeme {
pub fn mutate_text(&mut self, new: &str) { self.text = new.to_string(); } pub fn mutate_text(&mut self, new: &str) { self.text = new.to_string(); }
/// Returns an Option containing the character if the raw lexeme text
/// is composed of a single character, None if it has multiple characters.
pub fn as_char(&self) -> Option<char> { pub fn as_char(&self) -> Option<char> {
if self.text.chars().count() == 1 { if self.text.chars().count() == 1 {
self.text.chars().nth(0) self.text.chars().nth(0)
@ -56,6 +58,7 @@ impl Lexeme {
} }
} }
/// Returns true if the raw lexeme text is a single matching character.
pub fn match_char(&self, c: char) -> bool { pub fn match_char(&self, c: char) -> bool {
self.as_char().is_some_and(|as_char| as_char == c) self.as_char().is_some_and(|as_char| as_char == c)
} }
@ -86,6 +89,8 @@ impl Lexeme {
&& self.match_third_char(c3) && self.match_third_char(c3)
} }
/// Returns true if the lexeme raw text is composed of a single character
/// and this character is in the provided slice.
pub fn match_char_in(&self, slice: &[char]) -> bool { pub fn match_char_in(&self, slice: &[char]) -> bool {
self.as_char().is_some_and(|c| slice.contains(&c)) self.as_char().is_some_and(|c| slice.contains(&c))
} }

View file

@ -38,7 +38,9 @@ pub(super) fn lex(
let mut iterator = lexemes.iter().peekable(); let mut iterator = lexemes.iter().peekable();
while let Some(lexeme) = iterator.next() { while let Some(lexeme) = iterator.next() {
if lexeme.match_char('\\') { if lexeme.match_char('\\')
&& !matches!(state.context.block, context::Block::PreFormat)
{
if let Some(next) = iterator.next() { if let Some(next) = iterator.next() {
tokens.push(Token::Literal(Literal::lex(next))); tokens.push(Token::Literal(Literal::lex(next)));
} }

View file

@ -3,7 +3,7 @@ use std::collections::HashMap;
use crate::syntax::content::parser::{ use crate::syntax::content::parser::{
Token, Token,
context::Context, context::Context,
token::{Anchor, Item, List, Quote, Table}, token::{Anchor, Item, List, PreFormat, Quote, Table},
}; };
#[derive(Clone, Default, Debug)] #[derive(Clone, Default, Debug)]
@ -29,6 +29,7 @@ pub struct Buffers {
pub list: ListBuffer, pub list: ListBuffer,
pub quote: QuoteBuffer, pub quote: QuoteBuffer,
pub table: TableBuffer, pub table: TableBuffer,
pub preformat: PreFormatBuffer,
} }
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
@ -59,6 +60,11 @@ pub struct TableBuffer {
pub in_header: bool, pub in_header: bool,
} }
#[derive(Default, Clone, Debug)]
pub struct PreFormatBuffer {
pub candidate: PreFormat,
}
impl std::fmt::Display for AnchorBuffer { impl std::fmt::Display for AnchorBuffer {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let display_text = if self.text.is_empty() { let display_text = if self.text.is_empty() {

View file

@ -11,6 +11,7 @@ pub struct Anchor {
node: Option<Node>, node: Option<Node>,
leading: bool, leading: bool,
balanced: bool, balanced: bool,
absolute: bool,
external: bool, external: bool,
} }
@ -34,10 +35,17 @@ impl Anchor {
self.balanced = balanced; self.balanced = balanced;
} }
pub const fn absolute(&self) -> bool { self.absolute }
pub const fn set_absolute(&mut self, absolute: bool) {
self.absolute = absolute;
}
pub const fn external(&self) -> bool { self.external } pub const fn external(&self) -> bool { self.external }
pub const fn set_external(&mut self, external: bool) { pub const fn set_external(&mut self, external: bool) {
self.external = external; self.external = external;
self.absolute = true;
} }
pub const fn set_leading(&mut self, leading: bool) { pub const fn set_leading(&mut self, leading: bool) {
@ -58,21 +66,28 @@ impl Anchor {
fn route(&mut self) { fn route(&mut self) {
self.destination = if let Some(destination) = self.destination.clone() { self.destination = if let Some(destination) = self.destination.clone() {
if destination.contains(':') || destination.contains('/') { if destination.contains(':') || destination.starts_with('/') {
Some(destination) Some(destination)
} else if destination.is_empty() && self.text.is_empty() { } else if destination.is_empty() && self.text.is_empty() {
None None
} else if destination.is_empty() { } else if destination.is_empty() {
self.node_id = Some(self.text.clone()); self.node_id = Some(Self::strip_fragment(&self.text));
Some(format!("/node/{}", self.text)) Some(format!("/node/{}", self.text))
} else { } else {
self.node_id = self.destination.clone(); self.node_id = self
.destination
.clone()
.map(|d| Anchor::strip_fragment(&d));
Some(format!("/node/{destination}")) Some(format!("/node/{destination}"))
} }
} else { } else {
None None
} }
} }
fn strip_fragment(target: &str) -> String {
target.split('#').next().unwrap_or(target).to_string()
}
} }
impl Parseable for Anchor { impl Parseable for Anchor {
@ -100,19 +115,31 @@ impl Parseable for Anchor {
String::default() String::default()
}; };
let classes = if self.node.is_some() { let classes = if self.external {
String::from(r#"class="attached""#)
} else if !self.external {
String::from(r#"class="detached""#)
} else if self.external {
String::from(r#"class="external""#) String::from(r#"class="external""#)
} else if self.absolute {
String::from(r#"class="absolute""#)
} else if self.node.is_some() {
String::from(r#"class="attached""#)
} else { } else {
String::default() String::from(r#"class="detached""#)
};
let text = if destination.contains('#')
&& !self.absolute
&& destination == &format!("/node/{}", self.text)
{
self.text
.split('#')
.next()
.unwrap_or(&self.text)
.to_string()
} else {
self.text.clone()
}; };
format!( format!(
r#"<a {classes} title="{summary}" href="{}">{}</a>"#, r#"<a {classes} title="{summary}" href="{destination}">{text}</a>"#
destination, self.text,
) )
} }

View file

@ -1,46 +1,42 @@
use crate::syntax::content::{Lexeme, Parseable}; use crate::syntax::content::{Lexeme, Parseable};
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Default, Clone, Eq, PartialEq)]
pub struct PreFormat { pub struct PreFormat {
open: Option<bool>, pub text: String,
} }
impl PreFormat { impl PreFormat {
pub const fn new(open: bool) -> PreFormat { PreFormat { open: Some(open) } } pub fn new(text: &str) -> PreFormat {
PreFormat {
text: String::from(text),
}
}
} }
impl std::fmt::Display for PreFormat { impl std::fmt::Display for PreFormat {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let display_open_state = if let Some(open_state) = self.open { let character_count = self.text.chars().count();
if open_state { "open" } else { "closed" } let is_whitespace = self.text.trim_ascii().is_empty();
let summary = if is_whitespace {
"empty"
} else { } else {
"unknown" &format!("{character_count} chars")
}; };
write!(f, "PreFormat [{display_open_state}]") write!(f, "PreFormat [{summary}]")
} }
} }
impl Parseable for PreFormat { impl Parseable for PreFormat {
fn probe(lexeme: &Lexeme) -> bool { fn probe(lexeme: &Lexeme) -> bool {
lexeme.match_first_char('`') && (lexeme.next() == "\n" || lexeme.last()) lexeme.match_char('`') && (lexeme.next() == "\n" || lexeme.last())
} }
fn lex(_lexeme: &Lexeme) -> PreFormat { PreFormat { open: None } } fn lex(_lexeme: &Lexeme) -> PreFormat {
panic!("Attempt to lex a preformat directly from a lexeme")
fn render(&self) -> String {
if let Some(o) = self.open {
if o {
"<pre>".to_owned()
} else {
"</pre>".to_owned()
}
} else {
panic!(
"Attempt to render a preformat tag while open state is unknown"
)
}
} }
fn render(&self) -> String { format!("<pre>{}</pre>", self.text) }
fn flatten(&self) -> String { String::default() } fn flatten(&self) -> String { String::default() }
} }
@ -50,49 +46,39 @@ mod tests {
use crate::syntax::content::parser::Token; use crate::syntax::content::parser::Token;
#[test] #[test]
#[should_panic(
expected = "Attempt to lex a preformat directly from a lexeme"
)]
fn lex() { fn lex() {
let from_empty_lexeme = PreFormat::lex(&Lexeme::default()); let lexeme = Lexeme::new("a", "b", "c");
assert!(from_empty_lexeme.open.is_none()); PreFormat::lex(&lexeme);
let from_non_empty_lexeme = PreFormat::lex(&Lexeme::default());
assert!(from_non_empty_lexeme.open.is_none());
}
#[test]
#[should_panic(expected = "Attempt to render a preformat tag while \
open state is unknown")]
fn render() {
let from_empty_lexeme = PreFormat::lex(&Lexeme::default());
from_empty_lexeme.render();
let from_non_empty_lexeme = PreFormat::lex(&Lexeme::default());
from_non_empty_lexeme.render();
} }
#[test] #[test]
fn token_display() { fn token_display() {
let mut preformat = PreFormat::new(true); let mut preformat = PreFormat::new("");
assert_eq!( assert_eq!(
format!("{}", Token::PreFormat(preformat.clone())), format!("{}", Token::PreFormat(preformat.clone())),
"Tk:PreFormat [open]" "Tk:PreFormat [empty]"
); );
preformat.open = Some(false); preformat.text = "\n ".to_string();
assert_eq!( assert_eq!(
format!("{}", Token::PreFormat(preformat.clone())), format!("{}", Token::PreFormat(preformat.clone())),
"Tk:PreFormat [closed]" "Tk:PreFormat [empty]"
); );
preformat.open = None; preformat.text = "text".to_string();
assert_eq!( assert_eq!(
format!("{}", Token::PreFormat(preformat)), format!("{}", Token::PreFormat(preformat)),
"Tk:PreFormat [unknown]" "Tk:PreFormat [4 chars]"
); );
} }
#[test] #[test]
fn flatten() { fn flatten() {
let preformat = PreFormat::new(false); let preformat = PreFormat::new("");
assert_eq!(preformat.flatten(), ""); assert_eq!(preformat.flatten(), "");
let token = Token::PreFormat(preformat); let token = Token::PreFormat(preformat);

View file

@ -40,7 +40,7 @@ impl Parseable for Quote {
let content = if let Some(citation) = &self.citation { let content = if let Some(citation) = &self.citation {
format!( format!(
r#"{}<br/><cite class="quote-citation">{citation}</cite>"#, r#"{}<br/><cite class="quote-citation">{citation}</cite>"#,
&self.text self.text
) )
} else { } else {
String::from(&self.text) String::from(&self.text)

View file

@ -122,8 +122,8 @@ mod tests {
table.add_header("C"); table.add_header("C");
let table_token = Token::Table(table.clone()); let table_token = Token::Table(table.clone());
assert_eq!(format!("{table}"), format!("Table [+3 headers]")); assert_eq!(format!("{table}"), "Table [+3 headers]");
assert_eq!(format!("{table_token}"), format!("Tk:Table [+3 headers]")); assert_eq!(format!("{table_token}"), "Tk:Table [+3 headers]");
table.add_row( table.add_row(
["1", "2", "3"] ["1", "2", "3"]

80
static/graph-schema.json Normal file
View file

@ -0,0 +1,80 @@
{
"$id": "https://git.jutty.dev/jutty/en/raw/branch/main/static/graph-schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Graph",
"description": "An en graph file",
"type": "object",
"$defs": {
"node": {
"type": "object",
"title": "Node",
"description": "A node object.",
"example": "[nodes.Earth]\ntext = \"Earth is a planet of the solar system.\"",
"default": null,
"properties": {
"text": {
"type": "string",
"description": "The text content of this node."
},
"title": {
"type": "string",
"description": "The node display title, useful to make it distinct from the ID.",
"default": "The node's ID."
},
"listed": {
"type": "boolean",
"description": "Whether this node is shown in listing pages.",
"default": true
},
"redirect": {
"type": "string",
"description": "A node ID to where any requests for this node will be redirected.",
"default": null
}
},
"additionalProperties": false
}
},
"properties": {
"root_node": {
"type": "string",
"title": "Root node ID",
"description": "The ID of node highlighted in the homepage as the graph root.",
"example": "MyNode",
"default": null
},
"nodes": {
"title": "Nodes",
"description": "The graph's nodes.",
"default": null,
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/node"
},
"propertyNames": {
"pattern": "^[^-][^!@#$%^&*;:/~| \\]\\[()\\\\]*$"
}
},
"meta": {
"type": "object",
"properties": {
"config": {
"type": "object",
"properties": {
"content_language": { "type": "string" },
"footer_credits": { "type": "boolean" },
"error_poem": { "type": "boolean" },
"node_selector": { "type": "boolean" },
"navbar_search": { "type": "boolean" },
"about": { "type": "boolean" },
"footer_text": { "type": "string" },
"acknowledgments": { "type": "string" }
},
"additionalProperties": false
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}

File diff suppressed because it is too large Load diff

View file

@ -34,6 +34,7 @@ p:not(.quote-citation), section li {
section p { section p {
line-height: 2; line-height: 2;
margin: 4px 0 16px;
} }
pre { pre {
@ -46,30 +47,25 @@ pre {
transition: 1000ms; transition: 1000ms;
} }
pre:hover {
box-shadow: 1px 1px 3px 2.5px #00777777;
}
code { code {
padding: 3px 6px; padding: 3px 6px;
margin-right: 3px; margin-right: 3px;
white-space: nowrap; white-space: nowrap;
font-size: calc(var(--base-font-size) * 0.8);
box-shadow: 1px 1px 1px 1px #00000077;
transition: 1000ms; transition: 1000ms;
} box-shadow: 1px 1px 0.5px 0.5px #00000077;
code:hover {
box-shadow: 1px 1px 1px 1px #00777799;
} }
pre, code { pre, code {
background: light-dark(#e0e0e0, #333); background: light-dark(#e0e0e0, #333);
font-family: mono, monospace; font-family: mono, monospace;
border: solid 2px light-dark(#d0d0d0, #434343); border: solid 1px light-dark(#d0d0d0, #434343);
border-radius: 6px; border-radius: 6px;
} }
h1 , h2 , h3 , h4 , h5 , h6 {
code { font-size: calc(var(--base-font-size) * 0.6); }
}
a { a {
color: light-dark(#0f6366, #1dd7d7); color: light-dark(#0f6366, #1dd7d7);
text-decoration: underline dotted #159b9b; text-decoration: underline dotted #159b9b;
@ -100,8 +96,20 @@ a.external {
} }
a.external:hover { a.external:hover {
color: light-dark(#0393b2, #74e5ff); color: light-dark(#037cb2, #74e5ff);
text-decoration-color: light-dark(#1ed4f1, #aeffff); transition: 1500ms;
}
a.absolute {
color: light-dark(#196719, #2fe471);
text-decoration-color: light-dark(#196719, #2fe471);
text-decoration-style: solid;
text-decoration-thickness: 1.5px;
transition: 1500ms;
}
a.absolute:hover {
filter: brightness(1.25);
transition: 1500ms; transition: 1500ms;
} }
@ -150,7 +158,7 @@ span.id-label {
border: solid 1px light-dark(#d0d0d0, #666); border: solid 1px light-dark(#d0d0d0, #666);
} }
span.hidden-label { span.unlisted-label {
background: light-dark(#888, #000); background: light-dark(#888, #000);
color: light-dark(#eee, #969696); color: light-dark(#eee, #969696);
border: solid 1px light-dark(#d0d0d0, #555); border: solid 1px light-dark(#d0d0d0, #555);
@ -171,7 +179,7 @@ main h2 {
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
font-weight: 1; font-weight: 1;
margin: 10px 0; margin: 10px 0 5px;
overflow-wrap: break-word; overflow-wrap: break-word;
} }

View file

@ -1,3 +1,5 @@
#:schema ./graph-schema.json
[nodes.GettingStarted] [nodes.GettingStarted]
title = "Getting Started" title = "Getting Started"
text = """ text = """

View file

@ -8,17 +8,17 @@
{% if graph.meta.config.about_text %} {% if graph.meta.config.about_text %}
{{ graph.meta.config.about_text | safe }} {{ graph.meta.config.about_text | safe }}
{% else %} {% else %}
<p>en is a program to create a connected collection of texts.</p> <p>en is a writing instrument to create a connected collection of texts.</p>
<p>You define your graph using a plain-text configuration file, en reads this file and generates a website like the one you are browsing right now.</p> <p>You define your graph using plain-text files, en reads these files and generates a website like the one you are browsing right now.</p>
<p>If you'd like to learn more:</p> <p>If you'd like to learn more:</p>
<ul> <ul>
<li><a href="https://en.jutty.dev">Website</a></li> <li><a href="https://en.jutty.dev/node/Introduction">More about en</a></li>
<li><a href="https://en.jutty.dev/node/en">More about en</a></li> <li><a href="https://en.jutty.dev/node/GetStarted">Get Started</a></li>
<li><a href="https://en.jutty.dev/node/Documentation">Documentation</a></li> <li><a href="https://en.jutty.dev">Documentation</a></li>
<li><a href="https://codeberg.org/jutty/en">Source code repository</a></li> <li><a href="https://codeberg.org/jutty/en">Code repository</a></li>
</ul> </ul>
{% endif %} {% endif %}

View file

@ -52,8 +52,8 @@
<select class="node-selector" name="node"> <select class="node-selector" name="node">
<option value="">Nodes</option> <option value="">Nodes</option>
{% for _, node in graph.nodes %} {% for _, node in graph.nodes %}
{% if node.hidden or node.redirect %}{% continue %}{% endif %} {% if not node.listed or node.redirect %}{% continue %}{% endif %}
{% if not node.hidden and not node.redirect %} {% if node.listed and not node.redirect %}
<option value="{{node.id}}">{{node.title}}</option> <option value="{{node.id}}">{{node.title}}</option>
{% endif %} {% endif %}
{% endfor %} {% endfor %}

View file

@ -9,15 +9,16 @@
<table> <table>
<tr> <tr>
<td> <td>
<a href="#detached-edges">Detached edges</a> <a href="#detached-connections">Detached connections</a>
</td> </td>
<td> <td>
<a href="#detached-edges:~:text=Anchors">{{ detached_count }}</a> <a href="#detached-connections:~:text=Anchors">{{ detached_count }}</a>
</td> </td>
</tr> </tr>
</table> </table>
<h3 id="detached-edges">Detached edges</h3> <h3 id="detached-connections">Detached connections</h3>
<p>These are the destinations to which connections exist, but the target node doesn't.</p>
<details> <details>
<summary>Expand to see all detached edges.</summary> <summary>Expand to see all detached edges.</summary>

View file

@ -37,7 +37,7 @@
{% if loop.index > graph.meta.config.index_node_count %} {% if loop.index > graph.meta.config.index_node_count %}
{% break %} {% break %}
{% endif %} {% endif %}
{% if node.id != graph.root_node and not node.hidden and not node.redirect %} {% if node.id != graph.root_node and node.listed and not node.redirect %}
<li><a href="/node/{{node.id}}">{{node.title}}</a></li> <li><a href="/node/{{node.id}}">{{node.title}}</a></li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}

View file

@ -8,7 +8,7 @@
<h1 class="node-title">{{ node.title }}</h1> <h1 class="node-title">{{ node.title }}</h1>
<div class="labels"> <div class="labels">
{% if node.title != node.id %}<span class="label id-label">ID: {{ node.id }}</span>{% endif %} {% if node.title != node.id %}<span class="label id-label">ID: {{ node.id }}</span>{% endif %}
{% if node.hidden %}<span class="label hidden-label">Hidden</span>{% endif %} {% if not node.listed %}<span class="label unlisted-label">Unlisted</span>{% endif %}
{% if node.id == graph.root_node %}<span class="label root-label">Root</span>{% endif %} {% if node.id == graph.root_node %}<span class="label root-label">Root</span>{% endif %}
</div> </div>
</div> </div>

View file

@ -6,7 +6,7 @@
{% if graph.nodes or root_node %} {% if graph.nodes or root_node %}
<h1>Tree</h1> <h1>Tree</h1>
<ul> <ul>
{% if root_node and not root_node.hidden %} {% if root_node and root_node.listed %}
<li> <li>
<a href="/node/{{root_node.id}}">{{root_node.title}}</a> <a href="/node/{{root_node.id}}">{{root_node.title}}</a>
<span class="label root-label">Root</span> <span class="label root-label">Root</span>
@ -34,7 +34,7 @@
{% endif %} {% endif %}
{% if graph.nodes %} {% if graph.nodes %}
{% for id, node in graph.nodes %} {% for id, node in graph.nodes %}
{% if node.hidden or (root_node and node.id == root_node.id ) %}{% continue %}{% endif %} {% if not node.listed or (root_node and node.id == root_node.id ) %}{% continue %}{% endif %}
<li> <li>
<a href="/node/{{node.id}}">{{node.title}}</a> <a href="/node/{{node.id}}">{{node.title}}</a>
{% if node.connections or graph.meta.config.tree_node_summary %} {% if node.connections or graph.meta.config.tree_node_summary %}