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
upper-case-acronyms-aggressive = true
allow-indexing-slicing-in-tests = true
check-incompatible-msrv-in-tests = true

View file

@ -2,44 +2,80 @@ on:
push:
tags:
- '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:
publish:
runs-on: docker
if: ${{ inputs.linux }}
timeout-minutes: 20
container:
image: rust:slim
steps:
- name: Install action dependencies
run: |
apt-get install --no-install-recommends --update -y nodejs curl git
run: apt-get install --no-install-recommends --update -y nodejs curl git
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-tags: true
- name: Setup Rust toolchain
- name: Set up Rust toolchain
run: |
rustup component add llvm-tools-preview
rustup component add llvm-tools
rustup component add --toolchain nightly rustfmt clippy
rustup target add x86_64-unknown-linux-gnu
rustup target add x86_64-unknown-linux-musl
- name: Setup additional tooling
- name: Set up additional tooling
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
run: just verify
run: just ci verify
- name: Build x64 glibc release binary
run: just release-build x86_64-unknown-linux-gnu
- name: Build x64 Linux glibc release binary
run: just release-build-gnu
- name: Build x64 musl release binary
run: just release-build x86_64-unknown-linux-musl
- name: Build x64 Linux musl release binary
run: just release-build-musl
- name: Calculate SHA-256 hashes
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: |
version=$(./target/x86_64-unknown-linux-gnu/release/en --version)
api_root=https://git.jutty.dev/api
@ -49,12 +85,60 @@ jobs:
--user jutty:${{ secrets.GJD_REGISTRY_TOKEN }} \
--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: |
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
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 \
--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
JUST_VERSION="1.47.1"
JUST_SHA256SUM="3cb931ae25860f261ee373f32ede3b772ac91f14f588e4071576d3ffcf1a16fd"
TRIPLE="x86_64-unknown-linux-musl"
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_SHA256SUM="57f491aedf7cdb261538ceb49cbb1ee9d27df7ca205a5e1a009caaf5cb911afb"
CARGO_AUDIT_VERSION="0.22.1"
CARGO_AUDIT_TAG="cargo-audit%2Fv$CARGO_AUDIT_VERSION"
CARGO_AUDIT_SHA256SUM="1890badd5f15831a9af4b074399fcd21e6f7c0fe42c84e9254cdffc9f813765c"
TRIPLE="x86_64-unknown-linux-gnu"
TRIPLE_MUSL="x86_64-unknown-linux-musl"
TAPLO_VERSION="0.10.0"
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() {
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
@ -24,19 +43,45 @@ fetch() {
printf '%s %s\n' "$digest" "/tmp/$filename" > /tmp/digest
sha256sum --check /tmp/digest
tar xf "/tmp/$filename" -C /tmp/tools
find /tmp/tools -type f -executable -name "$binary" \
-exec mv -v '{}' /usr/local/bin ';'
if printf '%s' "$filename" | grep -qE '\.tar\.|\.tgz$'; then
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" \
"just-$JUST_VERSION-$TRIPLE_MUSL.tar.gz" \
"just-$JUST_VERSION-$TRIPLE.tar.gz" \
"$JUST_SHA256SUM" just
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
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
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:
- src/**
- tests/**
- static/graph.toml
- static/graph*
- static/welcome.toml
- .forgejo/**
- .justfile
- Cargo.toml
- Cargo.lock
jobs:
verify:
runs-on: docker
@ -24,32 +26,37 @@ jobs:
with:
fetch-tags: true
- name: Setup Rust toolchain
- name: Set up Rust toolchain
run: |
rustup component add llvm-tools-preview
rustup component add llvm-tools
rustup component add --toolchain nightly rustfmt clippy
rustup target add x86_64-unknown-linux-gnu
rustup target add x86_64-unknown-linux-musl
- name: Setup additional tooling
- name: Set up additional tooling
run: .forgejo/workflows/setup-tools.sh
- name: Setup CI user
- name: Set up CI user
run: |
useradd -m ci && chown -R ci:ci .
git config --global --add safe.directory "$PWD"
- name: Build
run: just ci build
- name: Text matches
run: just ci todos-assess version-assess spell
- name: Schema lint
run: just ci schema-lint
- name: Deny
run: just ci deny
- name: Format
run: just ci format-assess
- name: Lint
run: just ci lint-assess
- name: MSRV
run: just ci msrv-assess
- name: Cargo check
run: just ci check
- name: Test
run: just ci test
- name: Assess test coverage
- name: Coverage
run: just ci cover-assess

1
.gitignore vendored
View file

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

165
.justfile
View file

@ -51,7 +51,7 @@ alias wc := run-watch-containerized
[private]
assess-quick:
{{ just_cmd }} lint check test-cover-quick
{{ just_cmd }} lint check test-cover-quick spell schema-lint
[private]
assess-run-quick:
@ -72,6 +72,17 @@ 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]
test-cover-quick:
{{ cover_cmd }} --no-report -- --test 'serial_tests::' --test-threads 1
@ -198,11 +209,29 @@ 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
[script, group: 'assess']
tag commit="HEAD": update
if [ "{{ last_tag }}" = "{{ manifest_version }}" ]; then
echo "Last tag {{ last_tag }} and manifest ({{ manifest_version }}) already match"
if [ "{{ last_local_tag }}" = "{{ manifest_version }}" ]; then
echo "Last tag {{ last_local_tag }} and manifest ({{ manifest_version }}) already match"
exit
elif [ "{{ manifest_version }}" != "{{ lockfile_version }}" ]; then
echo "Manifest and lockfile versions don't match: update failed?"
@ -213,15 +242,15 @@ tag commit="HEAD": update
{{ just_cmd }} version-assess
# Verify and push
[group: 'develop']
push: verify
[group: 'develop', env("JUST", "1")]
push: verify && (version-assess "true")
git push
git push --tags
alias p := push
# Push without verifying
[group: 'develop']
[group: 'develop', env("JUST", "1")]
push-unsafe:
git push --no-verify
git push --tags --no-verify
@ -283,13 +312,28 @@ alias c := check
# Run tests
[group: 'assess']
test pattern="":
cargo test {{ pattern}} --timings -- --test-threads=1 'serial_tests::'
cargo test {{ pattern}} --timings --bin en
cargo test {{ pattern}} --timings --doc
cargo test {{ pattern}} --timings --lib -- --skip 'serial_tests::'
cargo test {{ pattern }} --timings -- --test-threads=1 'serial_tests::'
cargo test {{ pattern }} --timings --bin en
cargo test {{ pattern }} --timings --doc
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
# 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
[group: 'assess']
test-output pattern="":
@ -329,41 +373,65 @@ verify:
exit 1
fi
{{ just_cmd }} \
todos-assess version-assess \
security-assess \
format-assess lint-assess check \
todos-assess version-assess spell schema-lint deny \
format-assess lint-assess msrv-assess check \
test "" cover-assess
alias v := verify
# Check tag-manifest consistency
[script, group: 'assess']
version-assess: update
if
[ "{{ last_tag }}" != "{{ lockfile_version }}" ] \
|| [ "{{ last_tag }}" != "{{ lockfile_version }}" ]
then
printf 'Last tag: %s\nManifest: %s\nLockfile: %s\n' \
"{{ last_tag }}" \
"{{ manifest_version }}" \
"{{ lockfile_version }}"
exit 1
[arg("remote", long="remote", short="r", value="true")]
version-assess remote="false": update
last_remote_tag=$(
git ls-remote --tags --sort=-creatordate origin |
head -n 1 | tr -d v | cut -d / -f 3
)
printf 'Local: %s\nRemote: %s\nManifest: %s\nLockfile: %s\n' \
"{{ last_local_tag }}" \
"$last_remote_tag" \
"{{ 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
alias va := version-assess
# Audit security advisories
security-assess:
cargo audit --deny warnings
alias sa := security-assess
# Assess if the current Minimum Supported Rust Version is accurate
[group: 'assess']
msrv-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
[group: 'assess']
todos-assess:
! rg -M 200 --max-columns-preview TODO src
! grep -rn TODO src
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
# Cleanup build artifacts
@ -398,8 +466,12 @@ alias bm := build-musl
# Release build
[group: 'build']
release-build target=default_target:
cargo build --timings --target {{ target }} --locked --release
du -h target/{{ target }}/release/en
{{ maybe_xwin(target) }} build \
--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
@ -417,15 +489,29 @@ 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
[group: 'build']
[script]
shasum:
find target -type d -name 'release' \
-exec find '{}' -maxdepth 1 -type f -name en -executable ';' \
| xargs sha256sum
binaries=$(find target -type d -name 'release' \
-exec find '{}' -maxdepth 1 -type f \
-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
[default, private]
default:
@just --list --unsorted --justfile {{justfile()}}
@ -435,25 +521,25 @@ choose:
alias ch := choose
[script, private]
[script, private, env("CARGO_HOME", "$HOME/.cargo")]
ci recipe:
su ci -c "just {{ recipe }}"
## VARIABLES
export CARGO_TERM_COLOR := 'always'
musl_target := "x86_64-unknown-linux-musl"
glibc_target := "x86_64-unknown-linux-gnu"
msvc_target := "x86_64-pc-windows-msvc"
default_target := musl_target
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'
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"'
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`
lockfile_version := ```
grep -A 1 'name = "en"' Cargo.lock \
@ -461,6 +547,5 @@ lockfile_version := ```
```
## OPTIONS
set unstable
set lazy

412
Cargo.lock generated
View file

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

View file

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

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 {
if let Some(destination) = anchor.destination()
&& !anchor.external()
&& !anchor.absolute()
{
let trimmed_destination = destination
.trim_start_matches("/node/")
@ -665,7 +665,7 @@ mod tests {
"title": "JSON",
"links": [],
"id": "JSON",
"hidden": false,
"listed": true,
"connections": {}
}
},

View file

@ -18,8 +18,8 @@ pub struct Node {
pub links: Vec<String>,
#[serde(default)]
pub redirect: String,
#[serde(default)]
pub hidden: bool,
#[serde(default = "mktrue")]
pub listed: bool,
#[serde(default)]
pub connections: HashMap<String, Edge>,
@ -28,6 +28,9 @@ pub struct Node {
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)]
pub struct Stats {
pub outgoing: u32,
@ -46,7 +49,7 @@ impl Node {
connections: HashMap::default(),
links: vec![],
redirect: String::default(),
hidden: false,
listed: true,
summary: String::default(),
stats: Stats::default(),
}
@ -78,8 +81,8 @@ impl std::fmt::Display for Node {
meta_elements.push(format!("links:{links}"));
}
if self.hidden {
meta_elements.push(String::from("hidden"));
if !self.listed {
meta_elements.push(String::from("unlisted"));
}
let meta = meta_elements.join(" ");
@ -143,7 +146,7 @@ mod tests {
)
);
node.hidden = true;
node.listed = false;
assert_eq!(
format!("{node}"),
@ -153,7 +156,7 @@ mod tests {
text:15l summary:{} \
redirect:{redirect} \
links:{} \
hidden\
unlisted\
]",
summary.len(),
node.links.len(),

View file

@ -216,7 +216,7 @@ impl Asset {
///
/// 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> {
let target = format!("static/public/assets/{path}");
let defaults: HashMap<&str, &str> = TEXTS.iter().copied().collect();
@ -292,7 +292,7 @@ pub async fn file(
} else {
String::from(
"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 {

View file

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

View file

@ -1,12 +1,13 @@
use crate::syntax::content::parser::{
State, Token,
token::{Header, Paragraph, PreFormat, Verse},
token::{Header, Paragraph, Verse},
};
pub mod anchor;
pub mod block;
pub mod inline;
pub mod list;
pub mod preformat;
pub mod quote;
pub mod table;
@ -38,30 +39,32 @@ pub enum Inline {
}
/// # 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>) {
match state.context.block {
Block::PreFormat => {
tokens.push(Token::PreFormat(PreFormat::new(false)));
},
Block::Paragraph => {
tokens.push(Token::Paragraph(Paragraph::new(false)));
},
Block::List => {
panic!("End of input with open list")
},
Block::Header(level) => {
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 => {
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 => (),
}
}

View file

@ -34,6 +34,9 @@ pub fn parse(
log!(VERBOSE, "End: Next lexeme is a pipe");
buffer.text.push_str(&lexeme.text());
candidate.set_text(&buffer.text.clone());
if buffer.text.starts_with('/') {
candidate.set_absolute(true);
}
} else {
log!(
VERBOSE,
@ -84,6 +87,13 @@ pub fn parse(
"State: Found a pipe, but no boundary: destination follows"
);
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;
} else if lexeme.match_char(':') {
log!(VERBOSE, "State: Found a colon, marking anchor as external");
@ -205,8 +215,10 @@ mod tests {
fn needless_three_pipe_anchor() {
assert_eq!(
read("|Node|Destination|"),
concat!(r#"<p><a class="detached" title="" "#,
r#"href="/node/Destination">Node</a></p>"#)
concat!(
r#"<p><a class="detached" title="" "#,
r#"href="/node/Destination">Node</a></p>"#
)
);
}
@ -225,9 +237,11 @@ mod tests {
fn anchor_to_node_s() {
assert_eq!(
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#"<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() {
assert_eq!(
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#"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() {
assert_eq!(
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#"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]
fn http_external_anchor() {
assert_eq!(

View file

@ -6,15 +6,17 @@ use crate::{
syntax::content::{
Parseable as _,
parser::{
Block, Lexeme, State, Token,
Block, Lexeme, State, Token, context,
token::{
Header, LineBreak, List, Literal, Paragraph, PreFormat, Quote,
Table, Verse,
Header, LineBreak, List, Paragraph, PreFormat, Quote, Table,
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(
lexeme: &Lexeme,
state: &mut State,
@ -27,8 +29,7 @@ pub fn parse(
if PreFormat::probe(lexeme) {
log!(VERBOSE, "Block Context: None -> PreFormat on {lexeme}");
state.context.block = Block::PreFormat;
tokens.push(Token::PreFormat(PreFormat::new(true)));
return true;
return true
} else if Header::probe(lexeme) {
let mut header = Header::lex(lexeme);
header.dom_id = Some(Header::make_id(
@ -44,7 +45,7 @@ pub fn parse(
log!(VERBOSE, "Block Context: None -> List on {lexeme}");
state.context.block = Block::List;
state.buffers.list.candidate.ordered = lexeme.match_char('+');
return super::list::parse(
return context::list::parse(
lexeme, state, tokens, iterator, graph,
);
} else if Quote::probe(lexeme) {
@ -71,14 +72,7 @@ pub fn parse(
}
},
Block::PreFormat => {
if PreFormat::probe(lexeme) {
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;
return context::preformat::parse(lexeme, state, tokens, iterator);
},
Block::Paragraph => {
if Paragraph::probe_end(lexeme) {
@ -95,13 +89,17 @@ pub fn parse(
}
},
Block::List => {
return super::list::parse(lexeme, state, tokens, iterator, graph);
return context::list::parse(lexeme, state, tokens, iterator, graph);
},
Block::Quote => {
return super::quote::parse(lexeme, state, tokens, iterator, graph);
return context::quote::parse(
lexeme, state, tokens, iterator, graph,
);
},
Block::Table => {
return super::table::parse(lexeme, state, tokens, iterator, graph);
return context::table::parse(
lexeme, state, tokens, iterator, graph,
);
},
Block::Verse => {
if Verse::probe_end(lexeme) {
@ -127,7 +125,7 @@ mod tests {
graph::Graph,
syntax::content::parser::{
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))]);
}
#[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]
fn truncated_header_level() {
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(); }
/// 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> {
if self.text.chars().count() == 1 {
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 {
self.as_char().is_some_and(|as_char| as_char == c)
}
@ -86,6 +89,8 @@ impl Lexeme {
&& 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 {
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();
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() {
tokens.push(Token::Literal(Literal::lex(next)));
}

View file

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

View file

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

View file

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

View file

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

View file

@ -122,8 +122,8 @@ mod tests {
table.add_header("C");
let table_token = Token::Table(table.clone());
assert_eq!(format!("{table}"), format!("Table [+3 headers]"));
assert_eq!(format!("{table_token}"), format!("Tk:Table [+3 headers]"));
assert_eq!(format!("{table}"), "Table [+3 headers]");
assert_eq!(format!("{table_token}"), "Tk:Table [+3 headers]");
table.add_row(
["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 {
line-height: 2;
margin: 4px 0 16px;
}
pre {
@ -46,30 +47,25 @@ pre {
transition: 1000ms;
}
pre:hover {
box-shadow: 1px 1px 3px 2.5px #00777777;
}
code {
padding: 3px 6px;
margin-right: 3px;
white-space: nowrap;
font-size: calc(var(--base-font-size) * 0.8);
box-shadow: 1px 1px 1px 1px #00000077;
transition: 1000ms;
}
code:hover {
box-shadow: 1px 1px 1px 1px #00777799;
box-shadow: 1px 1px 0.5px 0.5px #00000077;
}
pre, code {
background: light-dark(#e0e0e0, #333);
font-family: mono, monospace;
border: solid 2px light-dark(#d0d0d0, #434343);
border: solid 1px light-dark(#d0d0d0, #434343);
border-radius: 6px;
}
h1 , h2 , h3 , h4 , h5 , h6 {
code { font-size: calc(var(--base-font-size) * 0.6); }
}
a {
color: light-dark(#0f6366, #1dd7d7);
text-decoration: underline dotted #159b9b;
@ -100,8 +96,20 @@ a.external {
}
a.external:hover {
color: light-dark(#0393b2, #74e5ff);
text-decoration-color: light-dark(#1ed4f1, #aeffff);
color: light-dark(#037cb2, #74e5ff);
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;
}
@ -150,7 +158,7 @@ span.id-label {
border: solid 1px light-dark(#d0d0d0, #666);
}
span.hidden-label {
span.unlisted-label {
background: light-dark(#888, #000);
color: light-dark(#eee, #969696);
border: solid 1px light-dark(#d0d0d0, #555);
@ -171,7 +179,7 @@ main h2 {
h1, h2, h3, h4, h5, h6 {
font-weight: 1;
margin: 10px 0;
margin: 10px 0 5px;
overflow-wrap: break-word;
}

View file

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

View file

@ -8,17 +8,17 @@
{% if graph.meta.config.about_text %}
{{ graph.meta.config.about_text | safe }}
{% 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>
<ul>
<li><a href="https://en.jutty.dev">Website</a></li>
<li><a href="https://en.jutty.dev/node/en">More about en</a></li>
<li><a href="https://en.jutty.dev/node/Documentation">Documentation</a></li>
<li><a href="https://codeberg.org/jutty/en">Source code repository</a></li>
<li><a href="https://en.jutty.dev/node/Introduction">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">Documentation</a></li>
<li><a href="https://codeberg.org/jutty/en">Code repository</a></li>
</ul>
{% endif %}

View file

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

View file

@ -9,15 +9,16 @@
<table>
<tr>
<td>
<a href="#detached-edges">Detached edges</a>
<a href="#detached-connections">Detached connections</a>
</td>
<td>
<a href="#detached-edges:~:text=Anchors">{{ detached_count }}</a>
<a href="#detached-connections:~:text=Anchors">{{ detached_count }}</a>
</td>
</tr>
</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>
<summary>Expand to see all detached edges.</summary>

View file

@ -37,7 +37,7 @@
{% if loop.index > graph.meta.config.index_node_count %}
{% break %}
{% 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>
{% endif %}
{% endfor %}

View file

@ -8,7 +8,7 @@
<h1 class="node-title">{{ node.title }}</h1>
<div class="labels">
{% 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 %}
</div>
</div>

View file

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