Compare commits
45 commits
v0.3.0-alp
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f8708f2500 | |||
| 1aef9d6ee6 | |||
| 0987269307 | |||
| a38d93ba2a | |||
| 26ba5c90e6 | |||
| 4d7ae2e729 | |||
| 297030696e | |||
| 2a01b9ac4c | |||
| 83d7b7e7aa | |||
| 47f8530487 | |||
| e0a5eaaf2f | |||
| c2de0283fd | |||
| 8649169bae | |||
| 70bdba50c8 | |||
| 88fdd3084e | |||
| 1d801dce06 | |||
| ab9b587018 | |||
| 0d48291d5f | |||
| 8100b1ba10 | |||
| d0ca4e6cb3 | |||
| 29c2beb3ed | |||
| aab419a18a | |||
| c18b656125 | |||
| 5bfd23925b | |||
| d881f0bd58 | |||
| cad3213dad | |||
| dfddbba4ef | |||
| 6180bb371f | |||
| 024b0a06ee | |||
| 3f69fe87f6 | |||
| 16088de227 | |||
| ebcb3340c0 | |||
| 58c1957e9f | |||
| dc4c331cb8 | |||
| 9024e56e72 | |||
| a60d6913c7 | |||
| ce61c0df27 | |||
| 92287bc9c8 | |||
| d756a16132 | |||
| 6af32696be | |||
| c305627822 | |||
| 9ebd91a589 | |||
| b806d84451 | |||
| 0514fcd05f | |||
| 1d5a7bad80 |
71 changed files with 3248 additions and 1089 deletions
|
|
@ -2,3 +2,4 @@ allow-unwrap-in-tests = true
|
|||
allow-expect-in-tests = true
|
||||
single-char-binding-names-threshold = 2
|
||||
upper-case-acronyms-aggressive = true
|
||||
allow-indexing-slicing-in-tests = true
|
||||
|
|
|
|||
|
|
@ -2,6 +2,14 @@ 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
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: docker
|
||||
|
|
@ -10,8 +18,7 @@ jobs:
|
|||
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
|
||||
|
|
@ -22,13 +29,19 @@ jobs:
|
|||
run: |
|
||||
rustup component add llvm-tools-preview
|
||||
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
|
||||
run: .forgejo/workflows/setup-tools.sh
|
||||
|
||||
- name: Setup 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
|
||||
|
|
@ -40,6 +53,7 @@ jobs:
|
|||
run: just shasum
|
||||
|
||||
- name: Publish x64 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
|
||||
|
|
@ -50,6 +64,7 @@ jobs:
|
|||
--upload-file target/x86_64-unknown-linux-gnu/release/en $url
|
||||
|
||||
- name: Publish x64 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
|
||||
|
|
|
|||
|
|
@ -2,19 +2,30 @@
|
|||
|
||||
set -eu
|
||||
|
||||
JUST_VERSION="1.45.0"
|
||||
JUST_SHA256SUM="dc3f958aaf8c6506dd90426e9b03f86dd15e74a6467ee0e54929f750af3d9e49"
|
||||
JUST_VERSION="1.47.1"
|
||||
JUST_SHA256SUM="3cb931ae25860f261ee373f32ede3b772ac91f14f588e4071576d3ffcf1a16fd"
|
||||
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"
|
||||
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"
|
||||
|
||||
TRIPLE="x86_64-unknown-linux-gnu"
|
||||
TRIPLE_MUSL="x86_64-unknown-linux-musl"
|
||||
|
||||
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,9 +35,19 @@ 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" \
|
||||
|
|
@ -40,3 +61,15 @@ fetch taiki-e/cargo-llvm-cov "v$CARGO_LLVM_COV_VERSION" \
|
|||
fetch rustsec/rustsec "$CARGO_AUDIT_TAG" \
|
||||
"cargo-audit-$TRIPLE-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_MUSL.tar.gz" \
|
||||
"$TYPOS_SHA256SUM" typos
|
||||
|
||||
fetch EmbarkStudios/cargo-deny "$CARGO_DENY_VERSION" \
|
||||
cargo-deny-$CARGO_DENY_VERSION-$TRIPLE_MUSL.tar.gz \
|
||||
"$CARGO_DENY_SHA256SUM" cargo-deny
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ on:
|
|||
- .forgejo/**
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
runs-on: docker
|
||||
|
|
@ -34,17 +35,25 @@ jobs:
|
|||
- name: Setup additional tooling
|
||||
run: .forgejo/workflows/setup-tools.sh
|
||||
|
||||
- name: Build
|
||||
run: just build
|
||||
- name: Setup CI user
|
||||
run: |
|
||||
useradd -m ci && chown -R ci:ci .
|
||||
git config --global --add safe.directory "$PWD"
|
||||
|
||||
- name: Text matching checks
|
||||
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 format-assess
|
||||
run: just ci format-assess
|
||||
- name: Lint
|
||||
run: just lint-assess
|
||||
run: just ci lint-assess
|
||||
- name: Cargo check
|
||||
run: just check
|
||||
run: just ci check
|
||||
- name: Test
|
||||
run: just test
|
||||
run: just ci test
|
||||
- name: Assess test coverage
|
||||
run: just cover-assess
|
||||
run: just ci cover-assess
|
||||
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
|||
/target
|
||||
/result
|
||||
|
|
|
|||
235
.justfile
235
.justfile
|
|
@ -50,30 +50,30 @@ run-watch-containerized:
|
|||
alias wc := run-watch-containerized
|
||||
|
||||
[private]
|
||||
quick-assess:
|
||||
{{ just_cmd }} lint check quick-test-cover
|
||||
assess-quick:
|
||||
{{ just_cmd }} lint check test-cover-quick spell schema-lint
|
||||
|
||||
[private]
|
||||
quick-assess-run:
|
||||
-{{ just_cmd }} quick-assess
|
||||
assess-run-quick:
|
||||
-{{ just_cmd }} assess-quick
|
||||
{{ just_cmd }} run
|
||||
|
||||
# Run quick assessments on changes
|
||||
[group: 'develop']
|
||||
quick-assess-watch:
|
||||
{{ watch_cmd }} {{ just_cmd }} quick-assess
|
||||
assess-watch-quick:
|
||||
{{ watch_cmd }} {{ just_cmd }} assess-quick
|
||||
|
||||
alias qa := quick-assess-watch
|
||||
alias aq := assess-watch-quick
|
||||
|
||||
# Run quick assessments, build and serve on changes
|
||||
[group: 'develop']
|
||||
quick-assess-run-watch:
|
||||
{{ watch_cmd }} {{ just_cmd }} quick-assess-run
|
||||
assess-run-watch-quick:
|
||||
{{ watch_cmd }} {{ just_cmd }} assess-run-quick
|
||||
|
||||
alias qr := quick-assess-run-watch
|
||||
alias rq := assess-run-watch-quick
|
||||
|
||||
[private]
|
||||
quick-test-cover:
|
||||
test-cover-quick:
|
||||
{{ cover_cmd }} --no-report -- --test 'serial_tests::' --test-threads 1
|
||||
{{ cover_cmd }} --no-report -- --skip 'serial_tests::'
|
||||
{{ cover_cmd }} report --html
|
||||
|
|
@ -81,10 +81,11 @@ quick-test-cover:
|
|||
|
||||
# Quickly update coverage reports (inaccurate)
|
||||
[group: 'assess']
|
||||
quick-test-cover-watch:
|
||||
{{ watch_cmd }} {{ just_cmd }} quick-test-cover
|
||||
test-cover-watch-quick:
|
||||
@{{ watch_cmd }} {{ just_cmd_no_ts }} test-cover-quick 2>&1 \
|
||||
| grep -v "process didn't exit successfully:" || true
|
||||
|
||||
alias qo := quick-test-cover-watch
|
||||
alias oq := test-cover-watch-quick
|
||||
|
||||
# Format all files
|
||||
[group: 'develop']
|
||||
|
|
@ -93,10 +94,30 @@ format:
|
|||
|
||||
alias f := format
|
||||
|
||||
# Test (short output)
|
||||
[group: 'develop']
|
||||
test-short pattern="":
|
||||
cargo test {{ pattern }} -q --message-format short \
|
||||
-- 'serial_tests::' --test-threads=1 \
|
||||
--format=terse -- quiet
|
||||
cargo test {{ pattern }} -q --message-format short --bin en
|
||||
cargo test {{ pattern }} -q --message-format short --doc
|
||||
cargo test {{ pattern }} --message-format short --lib \
|
||||
-- --format terse --skip 'serial_tests::'
|
||||
|
||||
alias ts := test-short
|
||||
|
||||
# Test on changes (shorter output)
|
||||
[group: 'develop']
|
||||
test-short-watch:
|
||||
{{ watch_cmd }} {{ just_cmd }} test-short
|
||||
|
||||
alias tsw := test-short-watch
|
||||
|
||||
# Lint
|
||||
[group: 'develop']
|
||||
lint:
|
||||
cargo +nightly clippy
|
||||
cargo +nightly clippy --timings --all-targets
|
||||
|
||||
alias l := lint
|
||||
|
||||
|
|
@ -107,6 +128,20 @@ lint-watch:
|
|||
|
||||
alias lw := lint-watch
|
||||
|
||||
# Lint (short output)
|
||||
[group: 'develop']
|
||||
lint-short:
|
||||
cargo +nightly clippy --all-targets -q --message-format short
|
||||
|
||||
alias ls := lint-short
|
||||
|
||||
# Lint on changes (shorter output)
|
||||
[group: 'develop']
|
||||
lint-short-watch:
|
||||
{{ watch_cmd }} {{ just_cmd }} lint-short
|
||||
|
||||
alias lsw := lint-short-watch
|
||||
|
||||
# Run cargo check on changes
|
||||
[group: 'develop']
|
||||
check-watch:
|
||||
|
|
@ -136,8 +171,8 @@ alias x := fix
|
|||
|
||||
# Run tests on changes
|
||||
[group: 'develop']
|
||||
test-watch:
|
||||
{{ watch_cmd }} {{ just_cmd }} test
|
||||
test-watch pattern="":
|
||||
{{ watch_cmd }} {{ just_cmd }} test {{ pattern}}
|
||||
|
||||
alias tw := test-watch
|
||||
|
||||
|
|
@ -163,43 +198,48 @@ 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: update
|
||||
if [ "{{ last_tag }}" = "{{ manifest_version }}" ]; then
|
||||
echo "Last tag {{ last_tag }} and manifest ({{ manifest_version }}) already match"
|
||||
if [ "{{ last_tag }}" != "{{ tagged_latest }}" ]; then
|
||||
echo "Last tag {{ last_tag }} and 'latest' tag ({{ tagged_latest }}) diverge"
|
||||
git tag --force latest "v{{ manifest_version }}"
|
||||
{{ just_cmd }} version-assess
|
||||
fi
|
||||
tag commit="HEAD": update
|
||||
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?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git tag "v{{ manifest_version }}" HEAD
|
||||
git tag --force latest "v{{ manifest_version }}"
|
||||
git tag "v{{ manifest_version }}" {{ commit }}
|
||||
{{ 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 tag 'latest'
|
||||
[group: 'develop']
|
||||
push-tag-latest-unsafe:
|
||||
git push origin tag latest --force --no-verify
|
||||
|
||||
alias ptlu := push-tag-latest-unsafe
|
||||
|
||||
# Push without verifying
|
||||
[group: 'develop']
|
||||
[group: 'develop', env("JUST", "1")]
|
||||
push-unsafe:
|
||||
git push --no-verify
|
||||
git push --tags --no-verify
|
||||
|
|
@ -211,14 +251,14 @@ alias pu := push-unsafe
|
|||
# Generate crate documentation
|
||||
[group: 'document']
|
||||
doc:
|
||||
cargo doc --document-private-items --no-deps
|
||||
cargo doc --timings --document-private-items --no-deps
|
||||
|
||||
alias d := doc
|
||||
|
||||
# Generate crate and dependencies documentation
|
||||
[group: 'document']
|
||||
doc-all:
|
||||
cargo doc --document-private-items
|
||||
cargo doc --timings --document-private-items
|
||||
|
||||
alias da := doc-all
|
||||
|
||||
|
|
@ -236,14 +276,15 @@ alias do := doc-open
|
|||
format-assess:
|
||||
cargo +nightly fmt -- --check
|
||||
|
||||
alias fc := format-assess
|
||||
alias fa := format-assess
|
||||
|
||||
# Assess production lints
|
||||
[group: 'assess']
|
||||
lint-assess:
|
||||
cargo +nightly clippy -- \
|
||||
-D clippy::dbg_macro -D clippy::use_debug \
|
||||
cargo +nightly clippy --timings -- \
|
||||
-D clippy::print_stdout -D clippy::print_stderr \
|
||||
-D clippy::dbg_macro -D clippy::use_debug
|
||||
cargo +nightly clippy --timings --all-targets -- \
|
||||
-D clippy::todo -D clippy::unimplemented -D clippy::unreachable
|
||||
|
||||
alias la := lint-assess
|
||||
|
|
@ -252,20 +293,28 @@ alias lp := lint-assess
|
|||
# Run cargo check
|
||||
[group: 'assess']
|
||||
check:
|
||||
{{ debug_vars }} cargo check --workspace
|
||||
{{ debug_vars }} cargo check --workspace --all-targets \
|
||||
--future-incompat-report
|
||||
|
||||
alias c := check
|
||||
|
||||
# Run tests
|
||||
[group: 'assess']
|
||||
test:
|
||||
cargo test -- --test 'serial_tests::' --test-threads 1
|
||||
cargo test --bin en
|
||||
cargo test --doc
|
||||
cargo test --lib -- --skip 'serial_tests::'
|
||||
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::'
|
||||
|
||||
alias t := test
|
||||
|
||||
# Run tests with unfiltered output
|
||||
[group: 'assess']
|
||||
test-output pattern="":
|
||||
DEBUG=${DEBUG:-debug} cargo test {{ pattern }} -- --no-capture 2>&1
|
||||
|
||||
alias to := test-output
|
||||
|
||||
# Clean test coverage data
|
||||
[group: 'assess']
|
||||
test-cover-clean:
|
||||
|
|
@ -297,41 +346,57 @@ verify:
|
|||
git status
|
||||
exit 1
|
||||
fi
|
||||
{{ just_cmd }} todos-assess version-assess \
|
||||
security-assess format-assess lint-assess check test cover-assess
|
||||
{{ just_cmd }} \
|
||||
todos-assess version-assess spell schema-lint deny \
|
||||
format-assess lint-assess check \
|
||||
test "" cover-assess
|
||||
|
||||
alias v := verify
|
||||
|
||||
# Check tag-manifest consistency
|
||||
[script, group: 'assess']
|
||||
version-assess: update
|
||||
if
|
||||
[ "{{ last_tag }}" != "{{ tagged_latest }}" ] \
|
||||
|| [ "{{ last_tag }}" != "{{ lockfile_version }}" ] \
|
||||
|| [ "{{ last_tag }}" != "{{ lockfile_version }}" ]
|
||||
then
|
||||
printf 'Last tag: %s\nManifest: %s\nLockfile: %s\nTagged latest: %s\n' \
|
||||
"{{ last_tag }}" \
|
||||
"{{ manifest_version }}" \
|
||||
"{{ lockfile_version }}" \
|
||||
"{{ tagged_latest }}"
|
||||
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
|
||||
|
||||
# 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
|
||||
|
|
@ -344,7 +409,7 @@ alias cl := clean
|
|||
# Build
|
||||
[group: 'build']
|
||||
build target=default_target:
|
||||
cargo build --target {{ target }} --locked
|
||||
cargo build --timings --target {{ target }} --locked
|
||||
|
||||
|
||||
alias b := build
|
||||
|
|
@ -352,35 +417,36 @@ alias b := build
|
|||
# glibc build
|
||||
[group: 'build']
|
||||
build-gnu:
|
||||
cargo build --target {{ glibc_target }} --locked
|
||||
cargo build --timings --target {{ glibc_target }} --locked
|
||||
|
||||
alias bg := build-gnu
|
||||
|
||||
# musl build
|
||||
[group: 'build']
|
||||
build-musl:
|
||||
cargo build --target {{ musl_target }} --locked
|
||||
cargo build --timings --target {{ musl_target }} --locked
|
||||
|
||||
alias bm := build-musl
|
||||
|
||||
# Release build
|
||||
[group: 'build']
|
||||
release-build target=default_target:
|
||||
cargo build --target {{ target }} --locked --release
|
||||
cargo build --timings --target {{ target }} --locked --release
|
||||
du -h target/{{ target }}/release/en
|
||||
|
||||
alias rb := release-build
|
||||
|
||||
# glibc release build
|
||||
[group: 'build']
|
||||
release-build-gnu:
|
||||
cargo build --target {{ glibc_target }} --locked --release
|
||||
{{ just_cmd }} release-build {{ glibc_target }}
|
||||
|
||||
alias rbg := release-build-gnu
|
||||
|
||||
# musl release build
|
||||
[group: 'build']
|
||||
release-build-musl:
|
||||
cargo build --target {{ musl_target }} --locked --release
|
||||
{{ just_cmd }} release-build {{ musl_target }}
|
||||
|
||||
alias rbm := release-build-musl
|
||||
|
||||
|
|
@ -402,6 +468,12 @@ choose:
|
|||
|
||||
alias ch := choose
|
||||
|
||||
[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"
|
||||
|
|
@ -409,20 +481,19 @@ glibc_target := "x86_64-unknown-linux-gnu"
|
|||
default_target := musl_target
|
||||
|
||||
debug_vars := 'DEBUG=${DEBUG:-} DEBUG_FILTER=${DEBUG_FILTER:-} RUST_BACKTRACE=${RUST_BACKTRACE:-} RUSTFLAGS=${RUSTFLAGS:-}'
|
||||
watch_cmd := "watchexec -qc -r -e rs,toml,html --color always -- "
|
||||
cover_cmd := 'cargo llvm-cov --color always --ignore-filename-regex "main\.rs|log\.rs"'
|
||||
just_cmd := 'just --timestamp --explain --command-color green'
|
||||
just_cmd_no_ts := 'just --explain --command-color green'
|
||||
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 \
|
||||
| grep -v '^latest$' | head -1 | tr -d v
|
||||
```
|
||||
tagged_latest := `git tag --points-at $(git rev-parse latest) \
|
||||
| grep -v '^latest$' | 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 \
|
||||
| grep version | cut -d '"' -f 2
|
||||
```
|
||||
|
||||
## OPTIONS
|
||||
|
||||
set unstable
|
||||
set lazy
|
||||
|
|
|
|||
407
Cargo.lock
generated
407
Cargo.lock
generated
|
|
@ -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,9 +111,9 @@ 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"
|
||||
|
|
@ -135,9 +123,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.56"
|
||||
version = "1.2.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
|
||||
checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
|
||||
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"
|
||||
|
|
@ -259,7 +238,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "en"
|
||||
version = "0.3.0-alpha"
|
||||
version = "0.4.0-alpha"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"serde",
|
||||
|
|
@ -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.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
|
||||
checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0"
|
||||
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",
|
||||
|
|
@ -528,16 +495,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.17"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.91"
|
||||
version = "0.3.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
|
||||
checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
|
@ -550,9 +519,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 +531,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 +543,9 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
|||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
|
|
@ -584,25 +553,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]]
|
||||
|
|
@ -616,9 +575,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "parse-zoneinfo"
|
||||
|
|
@ -722,12 +681,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 +710,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",
|
||||
|
|
@ -814,55 +767,6 @@ version = "0.8.10"
|
|||
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.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
|
|
@ -916,9 +820,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 +844,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 +876,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"
|
||||
|
|
@ -1012,20 +910,14 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
|||
|
||||
[[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"
|
||||
|
|
@ -1067,23 +959,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",
|
||||
|
|
@ -1102,7 +994,7 @@ dependencies = [
|
|||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
"winnow 0.7.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1116,18 +1008,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.9+spec-1.1.0"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
|
||||
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
"winnow 1.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.0.6+spec-1.1.0"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
|
||||
checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
|
|
@ -1179,9 +1071,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 +1089,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.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"flate2",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"ureq-proto",
|
||||
"utf-8",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ureq-proto"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
|
|
@ -1266,9 +1117,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.114"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
|
||||
checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
|
|
@ -1279,9 +1130,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.114"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
|
||||
checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
|
|
@ -1289,9 +1140,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.114"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
|
||||
checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
|
|
@ -1302,29 +1153,20 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.114"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
|
||||
checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
|
||||
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 +1228,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 +1237,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"
|
||||
|
|
@ -1475,31 +1244,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.42"
|
||||
name = "winnow"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3"
|
||||
checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.42"
|
||||
version = "0.8.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f"
|
||||
checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639"
|
||||
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"
|
||||
|
|
|
|||
38
Cargo.toml
38
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "en"
|
||||
version = "0.3.0-alpha"
|
||||
version = "0.4.0-alpha"
|
||||
description = "A non-linear writing instrument."
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ homepage = "https://en.jutty.dev"
|
|||
documentation = "https://en.jutty.dev/node/Documentation"
|
||||
|
||||
edition = "2024"
|
||||
rust-version= "1.91.1"
|
||||
rust-version= "1.94.0"
|
||||
|
||||
[features]
|
||||
serial-tests = []
|
||||
|
|
@ -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"
|
||||
|
|
@ -108,6 +110,7 @@ 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"
|
||||
|
|
@ -211,7 +225,6 @@ shadow_reuse = "warn"
|
|||
shadow_same = "warn"
|
||||
shadow_unrelated = "warn"
|
||||
should_panic_without_expect = "warn"
|
||||
similar_names = "warn"
|
||||
single_char_pattern = "warn"
|
||||
single_match_else = "warn"
|
||||
single_option_map = "warn"
|
||||
|
|
@ -237,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"
|
||||
|
|
@ -259,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,5 +289,13 @@ zero_sized_map_values = "warn"
|
|||
# cargo
|
||||
negative_feature_names = "warn"
|
||||
redundant_feature_names = "warn"
|
||||
multiple_crate_versions = "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 \\{.*^\\}",
|
||||
]
|
||||
|
|
|
|||
17
README.md
17
README.md
|
|
@ -1,14 +1,19 @@
|
|||
# en
|
||||
|
||||
en is a tool to write non-linear, connected pieces of text and have their references mapped out as a graph of connected information.
|
||||
en is a tool to write non-linear, connected pieces of text and have their references mapped out as a metadata-rich graph of connected information.
|
||||
|
||||
It works by ingesting a TOML file containing your node specification and serving it as a website that allows nodes to be browsed, searched and listed in relation to each other or as a shallow tree of nodes.
|
||||
|
||||
## Roadmap
|
||||
|
||||
For an outline of planned and completed features, see the [roadmap](https://en.jutty.dev/node/Roadmap).
|
||||
It works by ingesting a graph definition that leverages TOML for metadata and a special markup language for prose. en can then serve this textual representation as a website (with other targets planned) that allows nodes to be explored.
|
||||
|
||||
## Learn more
|
||||
|
||||
You can learn more and see what en looks like by visiting the [homepage](https://en.jutty.dev), which is rendered using en itself.
|
||||
|
||||
## Install and run
|
||||
|
||||
See the [Get Started](https://en.jutty.dev/node/GetStarted) page for instructions and how to install and start using en.
|
||||
|
||||
## Roadmap
|
||||
|
||||
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.
|
||||
|
||||
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).
|
||||
|
|
|
|||
|
|
@ -1,14 +1,27 @@
|
|||
FROM alpine:latest
|
||||
MAINTAINER Juno Takano juno@jutty.dev
|
||||
ENV DEBUG=debug
|
||||
ENV TAG=${TAG:-latest}
|
||||
|
||||
# Setup tooling
|
||||
RUN apk add curl clang git file
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
|
||||
# Install
|
||||
RUN git clone -b "$TAG" --single-branch https://codeberg.org/jutty/en /build
|
||||
RUN <<EOF
|
||||
REPO_URL=https://codeberg.org/jutty/en
|
||||
LAST_TAG=$(
|
||||
git ls-remote --tags "$REPO_URL" \
|
||||
| grep refs/tags \
|
||||
| sed 's/.*refs\/tags\///' \
|
||||
| sort -V \
|
||||
| tail -n 1
|
||||
)
|
||||
git config --global advice.detachedHead false
|
||||
echo "Selected tag: $LAST_TAG"
|
||||
git clone --verbose -b "$LAST_TAG" \
|
||||
--depth 1 --single-branch --no-tags \
|
||||
"$REPO_URL" /build
|
||||
EOF
|
||||
RUN <<EOF
|
||||
cd /build && /root/.cargo/bin/cargo build --locked --release
|
||||
mv /build/target/release/en /usr/local/bin/en
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
FROM debian:stable-slim
|
||||
MAINTAINER Juno Takano juno@jutty.dev
|
||||
ENV DEBUG=debug
|
||||
ENV TAG=${TAG:-latest}
|
||||
|
||||
# Setup tooling
|
||||
RUN apt-get -y --update install --no-install-recommends \
|
||||
|
|
@ -9,7 +8,21 @@ RUN apt-get -y --update install --no-install-recommends \
|
|||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
|
||||
# Install
|
||||
RUN git clone -b "$TAG" --single-branch https://codeberg.org/jutty/en /build
|
||||
RUN <<EOF
|
||||
REPO_URL=https://codeberg.org/jutty/en
|
||||
LAST_TAG=$(
|
||||
git ls-remote --tags "$REPO_URL" \
|
||||
| grep refs/tags \
|
||||
| sed 's/.*refs\/tags\///' \
|
||||
| sort -V \
|
||||
| tail -n 1
|
||||
)
|
||||
git config --global advice.detachedHead false
|
||||
echo "Selected tag: $LAST_TAG"
|
||||
git clone --verbose -b "$LAST_TAG" \
|
||||
--depth 1 --single-branch --no-tags \
|
||||
"$REPO_URL" /build
|
||||
EOF
|
||||
RUN <<EOF
|
||||
cd /build && /root/.cargo/bin/cargo build --locked --release
|
||||
mv /build/target/release/en /usr/local/bin/en
|
||||
|
|
|
|||
243
deny.toml
Normal file
243
deny.toml
Normal 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
26
flake.lock
generated
Normal 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
|
||||
}
|
||||
80
flake.nix
Normal file
80
flake.nix
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
{
|
||||
description = "A non-linear writing instrument.";
|
||||
|
||||
inputs.nixpkgs.url = "nixpkgs/nixos-26.05";
|
||||
|
||||
outputs = { nixpkgs, self }: let
|
||||
name = "en";
|
||||
version = "0.4.0";
|
||||
|
||||
supportedSystems = [
|
||||
"x86_64-linux"
|
||||
"aarch64-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 = ./.;
|
||||
|
||||
cargoHash =
|
||||
"sha256-"
|
||||
+ "em229cShq/IShRnxlp5mgcIu7pIOf0LflV8Pw0lLUEY=";
|
||||
};
|
||||
});
|
||||
|
||||
apps = forAllSystems (system: {
|
||||
default = {
|
||||
type = "app";
|
||||
program =
|
||||
"${self.packages.${system}.default}/bin/en";
|
||||
};
|
||||
});
|
||||
|
||||
devShells = forAllSystems (system:
|
||||
let pkgs = nixpkgsFor.${system}; in {
|
||||
default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
rustup
|
||||
just
|
||||
watchexec
|
||||
cargo-deny
|
||||
cargo-llvm-cov
|
||||
cargo-mutants
|
||||
go-tools
|
||||
typos
|
||||
taplo
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
nixosModules.bot = { config, lib, system, ... }: {
|
||||
options.within.services.en.enable =
|
||||
lib.mkEnableOption "enable en server";
|
||||
|
||||
config = lib.mkIf config.within.services.en.enable {
|
||||
systemd.services.en = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Restart = "always";
|
||||
ExecStart =
|
||||
"${self.packages."${system}".default}/bin/en";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
2
src/dev.rs
Normal file
2
src/dev.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub mod log;
|
||||
pub mod test;
|
||||
|
|
@ -114,7 +114,7 @@ pub fn timed(past: &Instant, message: &str) -> Instant {
|
|||
#[macro_export]
|
||||
macro_rules! tlog {
|
||||
($instant:expr, $fmt:expr $(, $($arg:tt)+ )?) => {{
|
||||
$crate::log::timed($instant, &format!($fmt $(, $($arg)+ )?))
|
||||
$crate::dev::log::timed($instant, &format!($fmt $(, $($arg)+ )?))
|
||||
}};
|
||||
}
|
||||
|
||||
|
|
@ -129,26 +129,26 @@ pub fn elog(function: &str, message: &str) {
|
|||
macro_rules! log {
|
||||
($level:path, $fmt:expr $(, $($arg:tt)+ )?) => {{
|
||||
|
||||
let data = $crate::log::Data::new(
|
||||
let data = $crate::dev::log::Data::new(
|
||||
Some($level),
|
||||
std::any::type_name_of_val(&|| {}),
|
||||
std::backtrace::Backtrace::capture(),
|
||||
);
|
||||
|
||||
if data.should_log {
|
||||
$crate::log::elog(&data.path, &format!($fmt $(, $($arg)+ )?));
|
||||
$crate::dev::log::elog(&data.path, &format!($fmt $(, $($arg)+ )?));
|
||||
}
|
||||
}};
|
||||
($fmt:expr $(, $($arg:tt)+ )?) => {{
|
||||
|
||||
let data = $crate::log::Data::new(
|
||||
let data = $crate::dev::log::Data::new(
|
||||
None,
|
||||
std::any::type_name_of_val(&|| {}),
|
||||
std::backtrace::Backtrace::capture(),
|
||||
);
|
||||
|
||||
if data.should_log {
|
||||
$crate::log::elog(&data.path, &format!($fmt $(, $($arg)+ )?));
|
||||
$crate::dev::log::elog(&data.path, &format!($fmt $(, $($arg)+ )?));
|
||||
};
|
||||
|
||||
}};
|
||||
|
|
@ -272,7 +272,7 @@ mod tests {
|
|||
fn test(&self);
|
||||
}
|
||||
|
||||
struct Logger {}
|
||||
struct Logger;
|
||||
|
||||
impl Loggable for Logger {
|
||||
fn test(&self) {
|
||||
211
src/dev/test.rs
Normal file
211
src/dev/test.rs
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
use std::{env, fs, io, path::PathBuf};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Directories {
|
||||
pub original: PathBuf,
|
||||
pub templates: PathBuf,
|
||||
pub assets: PathBuf,
|
||||
pub test: PathBuf,
|
||||
}
|
||||
|
||||
impl Directories {
|
||||
/// Sets up self-cleaning original, temporary and 'templates' directories.
|
||||
///
|
||||
/// # Errors
|
||||
/// May return Error when:
|
||||
/// - Current directory does not exist or lacking permissions
|
||||
/// - Several I/O possibilities from directory creation failures
|
||||
/// - Several I/O possibilities from working directory changing failures
|
||||
pub fn setup(dir_name: &str) -> Result<Directories, Error> {
|
||||
let original = env::current_dir()?;
|
||||
let test = original.join(format!("target/mocks/{dir_name}"));
|
||||
let templates = test.join("templates");
|
||||
let assets = test.join("static").join("public").join("assets");
|
||||
|
||||
drop(fs::remove_dir_all(&test));
|
||||
|
||||
if let Err(error) = fs::create_dir_all(&test) {
|
||||
return Err(Error::with_io(
|
||||
"Failed test's directory creation",
|
||||
error,
|
||||
))
|
||||
}
|
||||
|
||||
if let Err(error) = fs::create_dir_all(&templates) {
|
||||
return Err(Error::with_io(
|
||||
"Failed 'templates' directory creation",
|
||||
error,
|
||||
))
|
||||
}
|
||||
|
||||
if let Err(error) = fs::create_dir_all(&assets) {
|
||||
return Err(Error::with_io(
|
||||
"Failed 'assets' directory creation",
|
||||
error,
|
||||
))
|
||||
}
|
||||
|
||||
if let Err(error) = env::set_current_dir(&test) {
|
||||
return Err(Error::with_io("Failed current directory change", error))
|
||||
}
|
||||
|
||||
Ok(Directories {
|
||||
original,
|
||||
templates,
|
||||
assets,
|
||||
test,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Directories {
|
||||
fn drop(&mut self) {
|
||||
if let Err(error) = std::env::set_current_dir(&self.original) {
|
||||
log!(ERROR, "Couldn't reset to original directory: {error}");
|
||||
}
|
||||
if let Err(error) = std::fs::remove_dir_all(&self.test) {
|
||||
log!(WARN, "Couldn't cleanup test directory: {error}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
pub message: String,
|
||||
pub inner_io: Option<io::Error>,
|
||||
pub inner_tera: Option<tera::Error>,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
fn with_io(message: &str, inner_error: io::Error) -> Error {
|
||||
Error {
|
||||
message: String::from(message),
|
||||
inner_io: Some(inner_error),
|
||||
inner_tera: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let mut message = self.message.clone();
|
||||
|
||||
if let Some(inner_io) = &self.inner_io {
|
||||
message = format!("{message}\n{inner_io}");
|
||||
}
|
||||
|
||||
if let Some(inner_tera) = &self.inner_tera {
|
||||
message = format!("{message}\n{inner_tera}");
|
||||
}
|
||||
|
||||
write!(f, "{message}")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Error {
|
||||
fn from(string: String) -> Error {
|
||||
Error {
|
||||
message: string,
|
||||
inner_io: None,
|
||||
inner_tera: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Error {
|
||||
fn from(str: &str) -> Error { Error::from(String::from(str)) }
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(inner: io::Error) -> Error {
|
||||
let mut error = Error::from(inner.to_string());
|
||||
error.inner_io = Some(inner);
|
||||
error
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tera::Error> for Error {
|
||||
fn from(inner: tera::Error) -> Error {
|
||||
let mut error = Error::from(inner.to_string());
|
||||
error.inner_tera = Some(inner);
|
||||
error
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn bad_test_directory_name() {
|
||||
let dirs = Directories::setup("\0");
|
||||
assert!(dirs.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_contains_str_from_from() {
|
||||
let payload = "rHneusPkYNGW0Ia0";
|
||||
let error = Error::from(payload);
|
||||
assert!(format!("{error}").contains(payload));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_contains_str_from_io_error() {
|
||||
let payload = "SsVi0d3Ywc8kVhwp";
|
||||
let io_payload = "LoPbZP7cJEHzAjGW";
|
||||
let io_error = std::io::Error::other(io_payload);
|
||||
let error = Error::with_io(payload, io_error);
|
||||
assert!(format!("{error}").contains(payload));
|
||||
assert!(format!("{error}").contains(io_payload));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_contains_str_from_tera_error() {
|
||||
let payload = "pA6B0LhiiDMNCl1J";
|
||||
let tera_payload = "5ob8H594dCAQ8pfk";
|
||||
let error = Error {
|
||||
message: payload.to_string(),
|
||||
inner_tera: Some(tera::Error::msg(tera_payload)),
|
||||
inner_io: None,
|
||||
};
|
||||
assert!(format!("{error}").contains(payload));
|
||||
assert!(format!("{error}").contains(tera_payload));
|
||||
}
|
||||
#[test]
|
||||
fn from_io_error() {
|
||||
let payload = "YgmTKBm3VtHt5h3x9";
|
||||
let io_error = std::io::Error::other(payload);
|
||||
let error = Error::from(io_error);
|
||||
|
||||
assert!(error.message.contains(payload));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_tera_error() {
|
||||
let payload = "XEB3dcvYuz0M1lYt";
|
||||
let tera_error = tera::Error::msg(payload);
|
||||
let error = Error::from(tera_error);
|
||||
|
||||
assert!(error.message.contains(payload));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod serial_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn failed_working_directory_reset() {
|
||||
let dirs = Directories::setup("\0");
|
||||
|
||||
let error = dirs.unwrap_err();
|
||||
println!("{error}");
|
||||
assert!(error.message.contains("Failed test's directory creation"));
|
||||
assert!(
|
||||
format!("{error}")
|
||||
.contains("file name contained an unexpected NUL byte")
|
||||
);
|
||||
}
|
||||
}
|
||||
178
src/graph.rs
178
src/graph.rs
|
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::HashMap, path::PathBuf};
|
||||
use std::{collections::HashMap, io, path::PathBuf};
|
||||
|
||||
pub use edge::Edge;
|
||||
pub use meta::{Config, Meta};
|
||||
|
|
@ -43,6 +43,18 @@ pub struct Stats {
|
|||
}
|
||||
|
||||
impl Graph {
|
||||
fn welcome() -> Graph {
|
||||
let toml = include_str!("../static/welcome.toml");
|
||||
let mut welcome_graph = match Graph::from_serial(toml, &Format::TOML) {
|
||||
Ok(graph) => graph,
|
||||
Err(error) => {
|
||||
panic!("Welcome graph parsing must be infallible: {error:?}")
|
||||
},
|
||||
};
|
||||
welcome_graph.modulate();
|
||||
welcome_graph
|
||||
}
|
||||
|
||||
pub fn with_message(message: &str) -> Graph {
|
||||
let graph = Graph::default();
|
||||
let mut messages = graph.meta.messages;
|
||||
|
|
@ -69,12 +81,23 @@ impl Graph {
|
|||
/// Loads a Graph TOML file from CLI arguments or their defaults and
|
||||
/// returns a modulated Graph.
|
||||
///
|
||||
/// Loads a default graph with basic usage instructions if no file is found.
|
||||
///
|
||||
/// Returns a graph with an error message if any errors are propagated.
|
||||
pub fn load() -> Graph {
|
||||
let result = Graph::load_file(None);
|
||||
match result {
|
||||
Ok(graph) => graph,
|
||||
Err(error) => Graph::malformed(Some(&error)),
|
||||
Err(error) => {
|
||||
if error.not_found {
|
||||
return Graph::welcome()
|
||||
}
|
||||
if let Some(message) = error.message {
|
||||
Graph::malformed(Some(&message))
|
||||
} else {
|
||||
Graph::malformed(None)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -84,7 +107,7 @@ impl Graph {
|
|||
///
|
||||
/// # Errors
|
||||
/// Propagates errors from `Graph::read_file`.
|
||||
pub fn load_file(path: Option<&str>) -> Result<Graph, String> {
|
||||
pub fn load_file(path: Option<&str>) -> Result<Graph, LoadError> {
|
||||
let mut graph = Graph::from_file(path)?;
|
||||
graph.modulate();
|
||||
Ok(graph)
|
||||
|
|
@ -95,21 +118,24 @@ impl Graph {
|
|||
/// # Errors
|
||||
/// Returns Err if it can't read the contents of `in_path`.
|
||||
/// Propagates errors from `Graph::from_serial`.
|
||||
pub fn from_file(in_path: Option<&str>) -> Result<Graph, String> {
|
||||
pub fn from_file(in_path: Option<&str>) -> Result<Graph, LoadError> {
|
||||
let cli_path = Arguments::default().parse().graph_path;
|
||||
let path = in_path.map_or(cli_path, PathBuf::from);
|
||||
|
||||
let toml_source = match std::fs::read_to_string(&path) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
Err(error) => {
|
||||
log!(
|
||||
ERROR,
|
||||
"Error reading path {}: {e}",
|
||||
"Error reading path {}: {error}",
|
||||
path.as_path().display(),
|
||||
);
|
||||
return Err(format!(
|
||||
"Failed reading file at {}",
|
||||
path.as_path().display(),
|
||||
return Err(LoadError::from_io_with_message(
|
||||
&format!(
|
||||
"Failed reading file at {}",
|
||||
path.as_path().display(),
|
||||
),
|
||||
error,
|
||||
));
|
||||
},
|
||||
};
|
||||
|
|
@ -370,7 +396,7 @@ impl Graph {
|
|||
);
|
||||
} else {
|
||||
if let Some(destination) = anchor.destination()
|
||||
&& !anchor.external()
|
||||
&& !anchor.absolute()
|
||||
{
|
||||
let trimmed_destination = destination
|
||||
.trim_start_matches("/node/")
|
||||
|
|
@ -489,12 +515,60 @@ impl Graph {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Format {
|
||||
TOML,
|
||||
JSON,
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LoadError {
|
||||
pub message: Option<String>,
|
||||
pub not_found: bool,
|
||||
pub io_error: Option<io::Error>,
|
||||
pub serial_error: Option<SerialError>,
|
||||
}
|
||||
|
||||
impl LoadError {
|
||||
fn from_io_with_message(message: &str, io_error: io::Error) -> LoadError {
|
||||
LoadError {
|
||||
message: Some(String::from(message)),
|
||||
not_found: io_error.kind() == io::ErrorKind::NotFound,
|
||||
io_error: Some(io_error),
|
||||
serial_error: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SerialError> for LoadError {
|
||||
fn from(error: SerialError) -> LoadError {
|
||||
LoadError {
|
||||
message: Some(error.message.clone()),
|
||||
not_found: false,
|
||||
serial_error: Some(error),
|
||||
io_error: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for LoadError {
|
||||
fn from(error: io::Error) -> LoadError {
|
||||
LoadError {
|
||||
message: Some(error.to_string()),
|
||||
not_found: error.kind() == io::ErrorKind::NotFound,
|
||||
io_error: Some(error),
|
||||
serial_error: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct SerialError {
|
||||
pub cause: SerialErrorCause,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub enum SerialErrorCause {
|
||||
UnsupportedFormat,
|
||||
|
|
@ -511,12 +585,6 @@ impl std::fmt::Display for SerialErrorCause {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct SerialError {
|
||||
pub cause: SerialErrorCause,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl From<SerialError> for String {
|
||||
fn from(error: SerialError) -> String {
|
||||
format!("{}: {}", error.cause, error.message)
|
||||
|
|
@ -597,7 +665,7 @@ mod tests {
|
|||
"title": "JSON",
|
||||
"links": [],
|
||||
"id": "JSON",
|
||||
"hidden": false,
|
||||
"listed": true,
|
||||
"connections": {}
|
||||
}
|
||||
},
|
||||
|
|
@ -721,8 +789,8 @@ mod tests {
|
|||
.unwrap();
|
||||
graph.modulate();
|
||||
|
||||
let node = graph.nodes.get("Node").unwrap();
|
||||
let connection = node.connections.get("Nowhere").unwrap();
|
||||
let node = &graph.nodes["Node"];
|
||||
let connection = &node.connections["Nowhere"];
|
||||
assert!(connection.detached);
|
||||
}
|
||||
|
||||
|
|
@ -743,8 +811,8 @@ mod tests {
|
|||
.unwrap();
|
||||
graph.modulate();
|
||||
|
||||
let node = graph.nodes.get("NodeOne").unwrap();
|
||||
let connection = node.connections.get("NodeTwo").unwrap();
|
||||
let node = &graph.nodes["NodeOne"];
|
||||
let connection = &node.connections["NodeTwo"];
|
||||
println!("{connection:#?}");
|
||||
assert!(!connection.detached);
|
||||
}
|
||||
|
|
@ -763,10 +831,10 @@ mod tests {
|
|||
.unwrap();
|
||||
graph.modulate();
|
||||
|
||||
let n01 = graph.nodes.get("n01").unwrap();
|
||||
let n02 = graph.nodes.get("n02").unwrap();
|
||||
let n01_to_n02 = n01.connections.get("n02").unwrap();
|
||||
let n02_to_n03 = n02.connections.get("n03").unwrap();
|
||||
let n01 = &graph.nodes["n01"];
|
||||
let n02 = &graph.nodes["n02"];
|
||||
let n01_to_n02 = &n01.connections["n02"];
|
||||
let n02_to_n03 = &n02.connections["n03"];
|
||||
|
||||
assert_eq!(n01_to_n02.from, "n01");
|
||||
assert_eq!(n01_to_n02.to, "n02");
|
||||
|
|
@ -793,16 +861,16 @@ mod tests {
|
|||
.unwrap();
|
||||
graph.modulate();
|
||||
|
||||
let n01 = graph.nodes.get("n01").unwrap();
|
||||
let n02 = graph.nodes.get("n02").unwrap();
|
||||
let n04 = graph.nodes.get("n04").unwrap();
|
||||
let n01 = &graph.nodes["n01"];
|
||||
let n02 = &graph.nodes["n02"];
|
||||
let n04 = &graph.nodes["n04"];
|
||||
|
||||
let n01_to_n02 = n01.connections.get("n02").unwrap();
|
||||
let n01_to_n03 = n01.connections.get("n03").unwrap();
|
||||
let n01_to_n04 = n01.connections.get("n04").unwrap();
|
||||
let n01_to_n02 = &n01.connections["n02"];
|
||||
let n01_to_n03 = &n01.connections["n03"];
|
||||
let n01_to_n04 = &n01.connections["n04"];
|
||||
|
||||
let n04_to_n01 = n04.connections.get("n01").unwrap();
|
||||
let n04_to_n03 = n04.connections.get("n03").unwrap();
|
||||
let n04_to_n01 = &n04.connections["n01"];
|
||||
let n04_to_n03 = &n04.connections["n03"];
|
||||
|
||||
assert_eq!(n01_to_n02.from, "n01");
|
||||
assert_eq!(n01_to_n02.to, "n02");
|
||||
|
|
@ -861,8 +929,8 @@ mod tests {
|
|||
.unwrap();
|
||||
graph.modulate();
|
||||
|
||||
assert!(graph.nodes.get("n01").unwrap().summary.contains(text));
|
||||
assert!(graph.nodes.get("n01").unwrap().text.contains(text));
|
||||
assert!(&graph.nodes["n01"].summary.contains(text));
|
||||
assert!(&graph.nodes["n01"].text.contains(text));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -881,8 +949,8 @@ mod tests {
|
|||
.unwrap();
|
||||
graph.modulate();
|
||||
|
||||
assert_eq!(graph.nodes.get("n01").unwrap().summary, summary);
|
||||
assert!(graph.nodes.get("n01").unwrap().text.contains(text));
|
||||
assert_eq!(&graph.nodes["n01"].summary, summary);
|
||||
assert!(&graph.nodes["n01"].text.contains(text));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -900,7 +968,7 @@ mod tests {
|
|||
.unwrap();
|
||||
graph.modulate();
|
||||
|
||||
assert_eq!(graph.nodes.get("n01").unwrap().summary, first_sentence);
|
||||
assert_eq!(&graph.nodes["n01"].summary, first_sentence);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -919,7 +987,7 @@ mod tests {
|
|||
.unwrap();
|
||||
graph.modulate();
|
||||
|
||||
assert_eq!(graph.nodes.get("n01").unwrap().summary, first_paragraph);
|
||||
assert_eq!(&graph.nodes["n01"].summary, first_paragraph);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -948,7 +1016,7 @@ mod tests {
|
|||
.unwrap();
|
||||
graph.modulate();
|
||||
|
||||
assert_eq!(graph.nodes.get("n01").unwrap().summary, summary);
|
||||
assert_eq!(graph.nodes["n01"].summary, summary);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -967,9 +1035,9 @@ mod tests {
|
|||
.unwrap();
|
||||
graph.modulate();
|
||||
|
||||
let n1_to_n2 = graph.nodes.get("n1").unwrap().connections.get("n2");
|
||||
let n1_to_n2 = &graph.nodes["n1"].connections.get("n2");
|
||||
|
||||
let n2_to_n0 = graph.nodes.get("n2").unwrap().connections.get("n0");
|
||||
let n2_to_n0 = &graph.nodes["n2"].connections.get("n0");
|
||||
|
||||
println!("{n1_to_n2:#?}");
|
||||
println!("{n2_to_n0:#?}");
|
||||
|
|
@ -991,8 +1059,8 @@ mod tests {
|
|||
graph.modulate();
|
||||
|
||||
assert_eq!(graph.stats.detached_total, 2);
|
||||
assert_eq!(*graph.stats.detached.get("anchor").unwrap(), 1);
|
||||
assert_eq!(*graph.stats.detached.get("this one").unwrap(), 1);
|
||||
assert_eq!(graph.stats.detached["anchor"], 1);
|
||||
assert_eq!(graph.stats.detached["this one"], 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1008,7 +1076,7 @@ mod tests {
|
|||
graph.modulate();
|
||||
|
||||
assert_eq!(graph.stats.detached_total, 2);
|
||||
assert_eq!(*graph.stats.detached.get("anchor").unwrap(), 2);
|
||||
assert_eq!(graph.stats.detached["anchor"], 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1295,7 +1363,7 @@ mod tests {
|
|||
assert!(!format!("{none}").contains("exact"));
|
||||
|
||||
let some = QueryResult {
|
||||
node: Some(node.clone()),
|
||||
node: Some(node),
|
||||
exact: false,
|
||||
redirect: false,
|
||||
};
|
||||
|
|
@ -1308,24 +1376,18 @@ mod tests {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[expect(clippy::panic_in_result_fn)]
|
||||
mod serial_tests {
|
||||
use super::*;
|
||||
use crate::dev::test::{Directories, Error};
|
||||
|
||||
#[test]
|
||||
fn bad_graph_path() {
|
||||
let original_working_directory = std::env::current_dir().unwrap();
|
||||
|
||||
assert!(
|
||||
std::env::set_current_dir(std::path::Path::new(
|
||||
"tests/mocks/no_graph"
|
||||
))
|
||||
.is_ok()
|
||||
);
|
||||
fn no_graph_fallback() -> Result<(), Error> {
|
||||
let _dirs = Directories::setup("bad_graph_path")?;
|
||||
|
||||
let graph = Graph::load();
|
||||
let message = graph.meta.messages.first().unwrap();
|
||||
assert!(message.contains("Failed reading file at"));
|
||||
assert_eq!(graph.nodes["GettingStarted"].title, "Getting Started");
|
||||
|
||||
assert!(std::env::set_current_dir(original_working_directory).is_ok());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -600,7 +600,6 @@ mod tests {
|
|||
assert!(
|
||||
validation_error
|
||||
.message
|
||||
.clone()
|
||||
.unwrap()
|
||||
.contains("Splits to three elements: false")
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -2,14 +2,13 @@ use std::{sync, time};
|
|||
|
||||
pub mod prelude {
|
||||
pub use crate::{
|
||||
log,
|
||||
log::{Level::*, now},
|
||||
tlog, write_log,
|
||||
dev::log::{Level::*, now},
|
||||
log, tlog, write_log,
|
||||
};
|
||||
}
|
||||
|
||||
pub mod dev;
|
||||
pub mod graph;
|
||||
pub mod log;
|
||||
pub mod router;
|
||||
pub mod syntax;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::{backtrace, io, panic};
|
||||
|
||||
use en::{ONSET, graph::Graph, log, prelude::*, syntax};
|
||||
use en::{ONSET, dev::log, graph::Graph, prelude::*, syntax};
|
||||
|
||||
#[tokio::main]
|
||||
#[expect(clippy::print_stderr, clippy::print_stdout, clippy::use_debug)]
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use axum::{
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
dev::log,
|
||||
graph::{Format, Graph, SerialErrorCause},
|
||||
prelude::*,
|
||||
router::{
|
||||
|
|
@ -18,17 +19,6 @@ use crate::{
|
|||
|
||||
/// Assembles an HTTP response given Asset.
|
||||
fn assemble(asset: Asset, graph: &Graph) -> Response<Body> {
|
||||
let kind = match asset.mime.kind() {
|
||||
Ok(kind) => kind,
|
||||
Err(error) => {
|
||||
return error::make(
|
||||
Some(500),
|
||||
Some(&format!("Could not determine a mimetype kind: {error}")),
|
||||
graph,
|
||||
)
|
||||
},
|
||||
};
|
||||
|
||||
let set_content_type = |response: &mut Response<_>, content_type: &str| {
|
||||
if let Ok(header_value) =
|
||||
HeaderValue::from_str(&String::from(content_type))
|
||||
|
|
@ -37,6 +27,8 @@ fn assemble(asset: Asset, graph: &Graph) -> Response<Body> {
|
|||
.headers_mut()
|
||||
.append(header::CONTENT_TYPE, header_value);
|
||||
} else {
|
||||
// This should be unreachable considering the possible mimetypes
|
||||
// and their string representations are internal to en
|
||||
log!(
|
||||
WARN,
|
||||
"Failed to create content type header value from {content_type}"
|
||||
|
|
@ -44,7 +36,7 @@ fn assemble(asset: Asset, graph: &Graph) -> Response<Body> {
|
|||
}
|
||||
};
|
||||
|
||||
match kind {
|
||||
match asset.mime.kind() {
|
||||
mime::Kind::Text => {
|
||||
if let Some(text) = asset.text {
|
||||
let mut response = Response::new(Body::from(text));
|
||||
|
|
@ -54,6 +46,8 @@ fn assemble(asset: Asset, graph: &Graph) -> Response<Body> {
|
|||
);
|
||||
response
|
||||
} else {
|
||||
// This should be unreachable, considering the constructors
|
||||
// will convert to text even if a blob is passed
|
||||
let mut response = error::make(
|
||||
Some(500),
|
||||
Some(
|
||||
|
|
@ -75,6 +69,8 @@ fn assemble(asset: Asset, graph: &Graph) -> Response<Body> {
|
|||
);
|
||||
response
|
||||
} else {
|
||||
// This should be unreachable, considering the constructors
|
||||
// will convert to blob even if a text is passed
|
||||
let mut response = error::make(
|
||||
Some(500),
|
||||
Some(
|
||||
|
|
@ -90,9 +86,9 @@ fn assemble(asset: Asset, graph: &Graph) -> Response<Body> {
|
|||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::upper_case_acronyms)]
|
||||
#[derive(Debug)]
|
||||
enum AssetErrorKind {
|
||||
#[expect(clippy::upper_case_acronyms)]
|
||||
pub enum AssetErrorKind {
|
||||
NotFound,
|
||||
IO,
|
||||
UTF8,
|
||||
|
|
@ -100,11 +96,11 @@ enum AssetErrorKind {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AssetError {
|
||||
path: String,
|
||||
kind: AssetErrorKind,
|
||||
io_error: Option<std::io::Error>,
|
||||
utf8_error: Option<FromUtf8Error>,
|
||||
pub struct AssetError {
|
||||
pub path: String,
|
||||
pub kind: AssetErrorKind,
|
||||
pub io_error: Option<std::io::Error>,
|
||||
pub utf8_error: Option<FromUtf8Error>,
|
||||
}
|
||||
|
||||
impl AssetError {
|
||||
|
|
@ -140,8 +136,7 @@ impl std::fmt::Display for AssetError {
|
|||
let mut message = match self.kind {
|
||||
AssetErrorKind::IO => {
|
||||
format!(
|
||||
"A default fallback for {} was found, \
|
||||
but it could not be loaded",
|
||||
"File {} was found, but it could not be loaded",
|
||||
self.path
|
||||
)
|
||||
},
|
||||
|
|
@ -174,6 +169,7 @@ impl std::fmt::Display for AssetError {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Asset {
|
||||
blob: Option<Vec<u8>>,
|
||||
text: Option<String>,
|
||||
|
|
@ -182,7 +178,7 @@ struct Asset {
|
|||
|
||||
impl Asset {
|
||||
fn new(blob: &[u8], mime: mime::Mime) -> Result<Asset, AssetError> {
|
||||
match mime.kind()? {
|
||||
match mime.kind() {
|
||||
mime::Kind::Text => Ok(Asset {
|
||||
text: Some(String::from_utf8(blob.to_vec())?),
|
||||
blob: None,
|
||||
|
|
@ -198,19 +194,17 @@ impl Asset {
|
|||
}
|
||||
}
|
||||
|
||||
fn from_str(str: &str, mime: mime::Mime) -> Result<Asset, AssetError> {
|
||||
match mime.kind()? {
|
||||
mime::Kind::Text => Ok(Asset {
|
||||
fn from_str(str: &str, mime: mime::Mime) -> Asset {
|
||||
match mime.kind() {
|
||||
mime::Kind::Text => Asset {
|
||||
text: Some(String::from(str)),
|
||||
blob: None,
|
||||
mime,
|
||||
}),
|
||||
mime::Kind::Font | mime::Kind::Image | mime::Kind::Blob => {
|
||||
Ok(Asset {
|
||||
text: None,
|
||||
blob: Some(String::from(str).into_bytes()),
|
||||
mime,
|
||||
})
|
||||
},
|
||||
mime::Kind::Font | mime::Kind::Image | mime::Kind::Blob => Asset {
|
||||
text: None,
|
||||
blob: Some(String::from(str).into_bytes()),
|
||||
mime,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -222,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();
|
||||
|
|
@ -230,16 +224,17 @@ fn fallback(path: &str, graph: &Graph) -> Result<Asset, AssetError> {
|
|||
let mime = mime::Mime::guess(path);
|
||||
|
||||
match std::fs::read(&target) {
|
||||
// A matching file exists on disk
|
||||
// A matching file exists on disk and is accessible
|
||||
Ok(content) => Ok(Asset {
|
||||
blob: Some(content),
|
||||
text: None,
|
||||
mime,
|
||||
}),
|
||||
Err(io_error) => {
|
||||
// A matching file does not exist on disk
|
||||
if io_error.kind() == ErrorKind::NotFound {
|
||||
if let Some(content) = defaults.get(path) {
|
||||
Asset::from_str(content, mime)
|
||||
Ok(Asset::from_str(content, mime))
|
||||
} else {
|
||||
let not_found_error = Err(AssetError::new(
|
||||
path,
|
||||
|
|
@ -258,6 +253,7 @@ fn fallback(path: &str, graph: &Graph) -> Result<Asset, AssetError> {
|
|||
None => not_found_error,
|
||||
}
|
||||
}
|
||||
// A matching file exists on disk and is not accessible
|
||||
} else {
|
||||
Err(AssetError::new(
|
||||
path,
|
||||
|
|
@ -296,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 {
|
||||
|
|
@ -390,8 +386,6 @@ static TEXTS: &[(&str, &str)] = &[
|
|||
"assets/favicon.svg",
|
||||
include_str!("../../../static/public/assets/favicon.svg"),
|
||||
),
|
||||
("assets/licenses/SIL_OFL_1_1.txt", OFL.text),
|
||||
("assets/licenses/CC_BY_ND_4_0_INTERNATIONAL.txt", CCND.text),
|
||||
];
|
||||
|
||||
pub static FONTS: &[(&str, &Font)] = &[
|
||||
|
|
@ -611,7 +605,7 @@ static OFL: License = License {
|
|||
kind: &LicenseKind::SIL_OFL_1_1,
|
||||
url: "assets/licenses/SIL_OFL_1_1.txt",
|
||||
text: include_str!(
|
||||
"../../../static/public/assets/fonts/_canon/SIL_OFL_1_1.LICENSE"
|
||||
"../../../static/public/assets/fonts/_canon/SIL_OFL_1_1.body.LICENSE"
|
||||
),
|
||||
};
|
||||
|
||||
|
|
@ -621,13 +615,16 @@ static CCND: License = License {
|
|||
url: "/assets/licenses/CC_BY_ND_4_0_INTERNATIONAL.txt",
|
||||
text: include_str!(
|
||||
"../../../static/public/assets/fonts/_canon/\
|
||||
CC_BY_ND_4_0_INTERNATIONAL.LICENSE"
|
||||
CC_BY_ND_4_0_INTERNATIONAL.body.LICENSE"
|
||||
),
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use axum::http::status::StatusCode;
|
||||
|
||||
use super::*;
|
||||
use crate::router::handlers::mime::Mime;
|
||||
|
||||
async fn wrap_serial(format: &str) -> Response<Body> {
|
||||
let state = GlobalState {
|
||||
|
|
@ -672,6 +669,211 @@ mod tests {
|
|||
graph: Graph::default(),
|
||||
};
|
||||
let response = file(Path("/k/j/m".to_string()), State(state)).await;
|
||||
assert!(response.status() == axum::http::status::StatusCode::NOT_FOUND);
|
||||
assert!(response.status() == StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_from_utf8error() {
|
||||
let bytes = vec![0, 159];
|
||||
let utf8error = String::from_utf8(bytes.clone()).unwrap_err();
|
||||
let error = AssetError::from(utf8error);
|
||||
assert!(error.utf8_error.is_some());
|
||||
assert_eq!(error.utf8_error.unwrap().into_bytes(), bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_from_string() {
|
||||
let payload = "r5MDnkEojW9HZDAG";
|
||||
let asset_error = AssetError::from(payload.to_string());
|
||||
println!("{asset_error}");
|
||||
assert!(asset_error.path.contains(payload));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_text_asset() {
|
||||
let asset = Asset::new(&[1, 0, 1], mime::Mime::Txt).unwrap();
|
||||
|
||||
assert!(asset.blob.is_none());
|
||||
assert!(asset.text.is_some());
|
||||
assert_eq!(asset.text.unwrap(), "\u{1}\0\u{1}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_blob_asset() {
|
||||
let asset = Asset::new(&[1, 0, 1], mime::Mime::Png).unwrap();
|
||||
|
||||
assert!(asset.blob.is_some());
|
||||
assert!(asset.text.is_none());
|
||||
assert_eq!(asset.blob.unwrap(), &[1, 0, 1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn asset_from_str() {
|
||||
let payload = "\u{1}\0\u{6}";
|
||||
let asset = Asset::from_str(payload, mime::Mime::Ico);
|
||||
assert_eq!(asset.blob.unwrap(), &[1, 0, 6]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_asset_utf8_error() {
|
||||
let bad_bytes = [0xff, 0xc0, 0xf5, 0xc1, 0x80];
|
||||
|
||||
let error = Asset::new(&bad_bytes, mime::Mime::Txt).unwrap_err();
|
||||
|
||||
assert!(matches!(&error.kind, AssetErrorKind::UTF8));
|
||||
assert!(format!("{error}").contains("UTF8 decoding error"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_found_asset_error() {
|
||||
let error = fallback("not_found.png", &Graph::default()).unwrap_err();
|
||||
|
||||
assert!(matches!(&error.kind, AssetErrorKind::NotFound));
|
||||
assert!(
|
||||
format!("{error}")
|
||||
.contains("The file was not found in the searched path")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assemble_from_blob() {
|
||||
let asset = Asset::new(&[1, 0, 1], Mime::Pdf).unwrap();
|
||||
let response = assemble(asset, &Graph::default());
|
||||
let content_type =
|
||||
response.headers().get(header::CONTENT_TYPE).unwrap();
|
||||
assert_eq!(content_type, "application/pdf");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(unix)]
|
||||
#[expect(clippy::panic_in_result_fn, clippy::unwrap_in_result)]
|
||||
mod serial_tests {
|
||||
use std::{fs, os::unix::fs::PermissionsExt as _, path::PathBuf};
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
dev::test::{Directories, Error},
|
||||
router::handlers::mime::Mime,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn io_asset_error() -> Result<(), Error> {
|
||||
let dirs = Directories::setup("io_asset_error")?;
|
||||
|
||||
let assets = dirs.assets.clone();
|
||||
let file = assets.join("unreadable.png");
|
||||
|
||||
fs::write(&file, [1, 0, 1])?;
|
||||
let mut permissions = fs::metadata(&file)?.permissions();
|
||||
permissions.set_mode(0o200);
|
||||
fs::set_permissions(&file, permissions)?;
|
||||
|
||||
let new_permissions = fs::metadata(&file)?.permissions();
|
||||
assert_eq!(new_permissions.mode() & 0o777, 0o200);
|
||||
|
||||
let error = fallback("unreadable.png", &Graph::default()).unwrap_err();
|
||||
|
||||
assert!(matches!(&error.kind, AssetErrorKind::IO));
|
||||
assert!(
|
||||
format!("{error}")
|
||||
.contains("was found, but it could not be loaded")
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn target_file_exists() -> Result<(), Error> {
|
||||
let dirs = Directories::setup("target_file_exists")?;
|
||||
|
||||
let assets = dirs.assets.clone();
|
||||
let file = assets.join("asset.woff2");
|
||||
|
||||
fs::write(&file, [1, 0, 1])?;
|
||||
let asset = fallback("asset.woff2", &Graph::default()).unwrap();
|
||||
assert!(asset.text.is_none());
|
||||
assert!(asset.blob.is_some());
|
||||
assert!(matches!(asset.mime, Mime::Woff2));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_font_found_if_serving_enabled() -> Result<(), Error> {
|
||||
let dirs = Directories::setup("font_found_if_serving_enabled")?;
|
||||
|
||||
let assets = dirs.assets.clone();
|
||||
let relative_font_path =
|
||||
PathBuf::from(FONTS[0].0.replace("assets/", ""));
|
||||
let font_path = assets.join(&relative_font_path);
|
||||
let font_dir = font_path.parent().expect("failed getting font dir");
|
||||
|
||||
println!("{font_dir:?}");
|
||||
fs::create_dir_all(font_dir)?;
|
||||
fs::write(&font_path, [1, 0, 1])?;
|
||||
let graph = Graph::from_serial(
|
||||
"[meta.config]\nserve_fonts = true",
|
||||
&Format::TOML,
|
||||
)
|
||||
.expect("failed instantiating graph");
|
||||
println!("{font_path:?}");
|
||||
let asset = fallback(relative_font_path.to_str().unwrap(), &graph)
|
||||
.expect("fallback failed");
|
||||
|
||||
assert!(asset.text.is_none());
|
||||
assert!(asset.blob.is_some());
|
||||
assert!(matches!(asset.mime, Mime::Woff2));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_font_found_if_serving_enabled() -> Result<(), Error> {
|
||||
let dirs = Directories::setup("font_found_if_serving_enabled")?;
|
||||
|
||||
let assets = dirs.assets.clone();
|
||||
let relative_font_path = "fonts/custom.ttf";
|
||||
let font_path = assets.join(relative_font_path);
|
||||
let font_dir = font_path.parent().unwrap();
|
||||
|
||||
fs::create_dir_all(font_dir)?;
|
||||
fs::write(&font_path, [1, 0, 1])?;
|
||||
let graph = Graph::from_serial(
|
||||
"[meta.config]\nserve_fonts = true",
|
||||
&Format::TOML,
|
||||
)
|
||||
.expect("failed instantiating graph");
|
||||
let asset =
|
||||
fallback(relative_font_path, &graph).expect("fallback failed");
|
||||
|
||||
assert!(asset.text.is_none());
|
||||
assert!(asset.blob.is_some());
|
||||
assert!(matches!(asset.mime, Mime::Ttf));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn font_not_found_if_serving_disabled() -> Result<(), Error> {
|
||||
let dirs = Directories::setup("target_file_exists")?;
|
||||
|
||||
let assets = dirs.assets.clone();
|
||||
let relative_font_path =
|
||||
PathBuf::from(FONTS[0].0.replace("assets/", ""));
|
||||
let font_path = assets.join(&relative_font_path);
|
||||
let font_dir = font_path.parent().unwrap();
|
||||
|
||||
fs::create_dir_all(font_dir)?;
|
||||
fs::write(&font_path, [1, 0, 1])?;
|
||||
let graph = Graph::from_serial(
|
||||
"[meta.config]\nserve_fonts = false",
|
||||
&Format::TOML,
|
||||
)
|
||||
.unwrap();
|
||||
let error = fallback(font_path.to_str().unwrap(), &graph).unwrap_err();
|
||||
assert!(matches!(error.kind, AssetErrorKind::NotFound));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -102,39 +102,18 @@ impl Mime {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> Result<Kind, String> {
|
||||
let string = String::from(self.clone());
|
||||
let mut parts = string.split('/');
|
||||
let first_opt = parts.next();
|
||||
let second_opt = parts.next();
|
||||
if let Some(first) = first_opt
|
||||
&& let Some(second) = second_opt
|
||||
{
|
||||
if first == "application" {
|
||||
if second == "toml" || second == "xml" || second == "json" {
|
||||
Ok(Kind::Text)
|
||||
} else if second == "pdf"
|
||||
|| second == "epub"
|
||||
|| second == "epub+zip"
|
||||
|| second == "octet-stream"
|
||||
{
|
||||
Ok(Kind::Blob)
|
||||
} else {
|
||||
Err(format!(
|
||||
"Unexpected application kind for mimetype {string}"
|
||||
))
|
||||
}
|
||||
} else if first == "text" {
|
||||
Ok(Kind::Text)
|
||||
} else if first == "font" {
|
||||
Ok(Kind::Font)
|
||||
} else if first == "image" {
|
||||
Ok(Kind::Image)
|
||||
} else {
|
||||
Err(format!("Could not determine a kind for mimetype {string}"))
|
||||
}
|
||||
} else {
|
||||
Err(format!("Mimetype {string} couldn't be split on a slash"))
|
||||
/// Returns one of four kind of mimetypes among text, font, image and blob.
|
||||
///
|
||||
/// This is mainly used when serving assets through the `fixed` module in
|
||||
/// order to determine what `Asset` field to use when assemblimg a response
|
||||
/// body.
|
||||
pub const fn kind(&self) -> Kind {
|
||||
use Mime::*;
|
||||
match self {
|
||||
Txt | Csv | Css | Toml | Xml | Json | Js | Svg => Kind::Text,
|
||||
Ttf | Otf | Woff | Woff2 => Kind::Font,
|
||||
Ico | Jpeg | Png | Apng | Gif | Webp | Avif => Kind::Image,
|
||||
Pdf | Epub | Unknown => Kind::Blob,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use axum::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
dev::log,
|
||||
prelude::*,
|
||||
router::{GlobalState, handlers::raw::make_response},
|
||||
};
|
||||
|
|
@ -138,6 +139,12 @@ fn load_templates() -> Result<tera::Tera, tera::Error> {
|
|||
|
||||
match fs::read_dir(&root) {
|
||||
Ok(dir) => {
|
||||
log!(
|
||||
DEBUG,
|
||||
"Reading templates from root directory '{}', canonically {:?}",
|
||||
root.display(),
|
||||
root.canonicalize()
|
||||
);
|
||||
for file_opt in dir {
|
||||
let file = file_opt?;
|
||||
let path = file.path();
|
||||
|
|
@ -160,6 +167,11 @@ fn load_templates() -> Result<tera::Tera, tera::Error> {
|
|||
}
|
||||
},
|
||||
Err(error) => {
|
||||
log!(
|
||||
VERBOSE,
|
||||
"A 'templates' directory was not found or is not accessible: \
|
||||
only built-in templates will be available"
|
||||
);
|
||||
if error.kind() != ErrorKind::NotFound {
|
||||
return Err(tera::Error::msg(error.to_string()))
|
||||
}
|
||||
|
|
@ -432,4 +444,157 @@ mod tests {
|
|||
assert_eq!(&contents, map_contents);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rendering_error_html_contains_inner_error() {
|
||||
let outer_payload = "Gl0c7CyArjlG1Zgvj3D5BFmZT6zRz5Ky";
|
||||
let inner_payload = "t53pvXCf0JqUzwiM5BZbYxAQadYSJ9XW";
|
||||
let inner_error = tera::Error::msg(inner_payload);
|
||||
let error = RenderingError::new(outer_payload, 501, &inner_error);
|
||||
assert!(error.template.html.contains(inner_payload));
|
||||
assert!(error.template.html.contains(outer_payload));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rendering_error_display() {
|
||||
let payload = "4LKNOSqfW0Ys3LALDAond8IIp5RgN7vK";
|
||||
let error = RenderingError::new(payload, 501, &tera::Error::msg(""));
|
||||
let display_string = format!("{error}");
|
||||
assert!(display_string.contains(payload));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_template_read_is_an_error() {
|
||||
let result = read_template("", PathBuf::from(""));
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn template_read_without_permissions_is_an_error() {
|
||||
let result = read_template("", PathBuf::from("/etc/shadow"));
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn template_read_without_a_default_is_an_error() {
|
||||
let result = read_template(
|
||||
"xkQwFZpqf5iz",
|
||||
PathBuf::from("templates/Boy5CZQUk2oX"),
|
||||
);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn template_read_with_a_default_is_ok() {
|
||||
let result =
|
||||
read_template("base.html", PathBuf::from("templates/St1iFgeOrhCK"));
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn template_read_with_a_file_is_ok() {
|
||||
let result =
|
||||
read_template("GpzjjAPhCTIr", PathBuf::from("templates/base.html"));
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[expect(clippy::panic_in_result_fn)]
|
||||
mod serial_tests {
|
||||
use std::{ffi::OsStr, os::unix::ffi::OsStrExt as _};
|
||||
|
||||
use super::*;
|
||||
use crate::dev::test::{Directories, Error};
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(not(unix), ignore)]
|
||||
fn invalid_utf8_template_filename() -> Result<(), Error> {
|
||||
let dirs = Directories::setup("encoding")?;
|
||||
|
||||
let invalid_name = OsStr::from_bytes(&[0xff, 0xfe, 0x80]);
|
||||
let file_path = dirs.templates.join(invalid_name);
|
||||
fs::write(file_path, b"")?;
|
||||
|
||||
let template_load_result = load_templates();
|
||||
let err = template_load_result.err().unwrap();
|
||||
|
||||
let error_message = err.to_string();
|
||||
assert!(error_message.contains("not valid unicode"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_template() -> Result<(), Error> {
|
||||
let dirs = Directories::setup("custom_template")?;
|
||||
|
||||
let file_name = "custom.html";
|
||||
let file_path = dirs.templates.join(file_name);
|
||||
fs::write(file_path, b"")?;
|
||||
|
||||
let engine = load_templates()?;
|
||||
assert!(engine.get_template_names().any(|t| t == "custom.html"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_template_inheritance_error() -> Result<(), Error> {
|
||||
let dirs = Directories::setup("custom_template")?;
|
||||
|
||||
let file_name = "custom.html";
|
||||
let file_path = dirs.templates.join(file_name);
|
||||
fs::write(file_path, br#"{% extends "nonexistent.html" %}"#)?;
|
||||
|
||||
let template_load_result = load_templates();
|
||||
assert!(template_load_result.is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inner_template_no_op() -> Result<(), Error> {
|
||||
let dirs = Directories::setup("inner_template")?;
|
||||
|
||||
let inner_dir = dirs.templates.join("inner");
|
||||
fs::create_dir(&inner_dir)?;
|
||||
let inner_template = inner_dir.join("inner.html");
|
||||
fs::write(inner_template, br#"{% extends "nonexistent.html" %}"#)?;
|
||||
|
||||
let engine = load_templates()?;
|
||||
let default_count = dirs.original.join("templates").read_dir()?.count();
|
||||
let template_count = engine.get_template_names().count();
|
||||
assert!(template_count == default_count);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn templates_dir_not_found_ok() -> Result<(), Error> {
|
||||
let dirs = Directories::setup("not_found_error")?;
|
||||
|
||||
std::fs::remove_dir_all(&dirs.templates)?;
|
||||
let template_load_result = load_templates();
|
||||
template_load_result?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Unexpected here means any error other than 'not found'
|
||||
fn templates_dir_unexpected_error() -> Result<(), Error> {
|
||||
let dirs = Directories::setup("unexpected_error")?;
|
||||
|
||||
log!(DEBUG, "Working directory is {:?}", std::env::current_dir());
|
||||
|
||||
std::fs::remove_dir_all(&dirs.templates)?;
|
||||
let templates = dirs.test.join("templates");
|
||||
fs::write(&templates, b"")?;
|
||||
|
||||
let template_load_result = load_templates();
|
||||
assert!(template_load_result.is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 => (),
|
||||
}
|
||||
}
|
||||
|
|
@ -77,4 +80,27 @@ mod tests {
|
|||
state.context.block = Block::List;
|
||||
super::close(&state, &mut vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "End of input with open quote")]
|
||||
fn open_quote_eoi() {
|
||||
let mut state = State::default();
|
||||
state.context.block = Block::Quote;
|
||||
super::close(&state, &mut vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "End of input with open table")]
|
||||
fn open_table_eoi() {
|
||||
let mut state = State::default();
|
||||
state.context.block = Block::Table;
|
||||
super::close(&state, &mut vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn open_verse_eoi() {
|
||||
let mut state = State::default();
|
||||
state.context.block = Block::Verse;
|
||||
super::close(&state, &mut vec![]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
61
src/syntax/content/parser/context/preformat.rs
Normal file
61
src/syntax/content/parser/context/preformat.rs
Normal 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("<");
|
||||
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(">");
|
||||
} 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
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
|
@ -224,7 +229,7 @@ impl Lexeme {
|
|||
|
||||
impl fmt::Display for Lexeme {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use crate::log::wrap;
|
||||
use crate::dev::log::wrap;
|
||||
|
||||
let properties = if self.last && self.first {
|
||||
"[S] "
|
||||
|
|
@ -286,7 +291,7 @@ mod tests {
|
|||
fn first_segment() {
|
||||
let payload = "nhNc fGev QnGW E4hj ExyZ";
|
||||
let lexeme = Lexeme::new(payload, "", "");
|
||||
assert_eq!(lexeme.clone().first_segment(), Some(String::from("nhNc")));
|
||||
assert_eq!(lexeme.first_segment(), Some(String::from("nhNc")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -381,8 +386,8 @@ mod tests {
|
|||
let lexemes = Lexeme::collect(&input);
|
||||
|
||||
let first = lexemes.first().unwrap();
|
||||
let second = lexemes.get(1).unwrap();
|
||||
let third = lexemes.get(2).unwrap();
|
||||
let second = &lexemes[1];
|
||||
let third = &lexemes[2];
|
||||
let last = lexemes.last().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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>"#
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -121,7 +148,7 @@ impl Parseable for Anchor {
|
|||
|
||||
impl std::fmt::Display for Anchor {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
use crate::log::wrap;
|
||||
use crate::dev::log::wrap;
|
||||
|
||||
let wrapped_text = wrap(&self.text);
|
||||
let display_text = if wrapped_text.is_empty() {
|
||||
|
|
@ -216,7 +243,7 @@ mod tests {
|
|||
anchor.external = true;
|
||||
|
||||
assert_eq!(
|
||||
format!("{}", Token::Anchor(Box::new(anchor.clone()))),
|
||||
format!("{}", Token::Anchor(Box::new(anchor))),
|
||||
"Tk:Anchor 'wPVo1 0OmYm' -> \"M1UEp 1gbfr\" \
|
||||
+Leading +Balanced +External",
|
||||
);
|
||||
|
|
@ -261,4 +288,15 @@ mod tests {
|
|||
let anchor = Anchor::default();
|
||||
assert_eq!(format!("{anchor}"), "Anchor <empty> -> <unknown>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten() {
|
||||
let payload = "tpBTViYnldoTqDsB";
|
||||
let mut anchor = Anchor::default();
|
||||
anchor.text = String::from(payload);
|
||||
assert_eq!(anchor.flatten(), payload);
|
||||
|
||||
let token = Token::Anchor(Box::new(anchor));
|
||||
assert_eq!(token.flatten(), payload);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,15 +60,15 @@ mod tests {
|
|||
assert_eq!(format!("{}", Token::Bold(bold.clone())), "Tk:Bold [open]");
|
||||
|
||||
bold.open = false;
|
||||
assert_eq!(
|
||||
format!("{}", Token::Bold(bold.clone())),
|
||||
"Tk:Bold [closed]"
|
||||
);
|
||||
assert_eq!(format!("{}", Token::Bold(bold)), "Tk:Bold [closed]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten() {
|
||||
let bold = Bold::new(false);
|
||||
assert_eq!(bold.flatten(), "");
|
||||
|
||||
let token = Token::Bold(bold);
|
||||
assert_eq!(token.flatten(), "");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ mod tests {
|
|||
|
||||
checkbox.checked = false;
|
||||
assert_eq!(
|
||||
format!("{}", Token::CheckBox(checkbox.clone())),
|
||||
format!("{}", Token::CheckBox(checkbox)),
|
||||
"Tk:CheckBox [empty]"
|
||||
);
|
||||
}
|
||||
|
|
@ -73,5 +73,8 @@ mod tests {
|
|||
fn flatten() {
|
||||
let checkbox = CheckBox::new(false);
|
||||
assert_eq!(checkbox.flatten(), "");
|
||||
|
||||
let token = Token::CheckBox(checkbox);
|
||||
assert_eq!(token.flatten(), "");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,9 +60,15 @@ mod tests {
|
|||
assert_eq!(format!("{}", Token::Code(code.clone())), "Tk:Code [open]");
|
||||
|
||||
code.open = false;
|
||||
assert_eq!(
|
||||
format!("{}", Token::Code(code.clone())),
|
||||
"Tk:Code [closed]"
|
||||
);
|
||||
assert_eq!(format!("{}", Token::Code(code)), "Tk:Code [closed]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten() {
|
||||
let code = Code::new(true);
|
||||
assert_eq!(code.flatten(), "");
|
||||
|
||||
let token = Token::Code(code);
|
||||
assert_eq!(token.flatten(), "");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -322,4 +322,13 @@ mod tests {
|
|||
format!("Header [unknown L1 DOM ID {payload}]")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten() {
|
||||
let header = Header::new(Level::Two, true, Some("MNxqaFfIbCzw"));
|
||||
assert_eq!(header.flatten(), "");
|
||||
|
||||
let token = Token::Header(header);
|
||||
assert_eq!(token.flatten(), "");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,11 +53,21 @@ mod tests {
|
|||
#[should_panic(
|
||||
expected = "Items should only be rendered by a list's render method"
|
||||
)]
|
||||
fn render() {
|
||||
fn token_render() {
|
||||
let item = Item::new("aCNuZwwzrt", None);
|
||||
item.render();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "Items should only be rendered by a list's render method"
|
||||
)]
|
||||
fn render() {
|
||||
let item = Item::new("vuv3ipykTzuf", None);
|
||||
let token = Token::Item(item);
|
||||
token.render();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probe() {
|
||||
let lexeme = Lexeme::new("bOa", "2R6", "4Mp");
|
||||
|
|
@ -80,7 +90,7 @@ mod tests {
|
|||
);
|
||||
item.depth = None;
|
||||
assert_eq!(
|
||||
format!("{}", Token::Item(item.clone())),
|
||||
format!("{}", Token::Item(item)),
|
||||
"Tk:Item [<unknown>] dRMy4"
|
||||
);
|
||||
}
|
||||
|
|
@ -89,5 +99,6 @@ mod tests {
|
|||
fn flatten() {
|
||||
let item = Item::new("", None);
|
||||
assert_eq!(item.flatten(), "");
|
||||
assert_eq!(Token::Item(item).flatten(), "");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,12 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn token_display() {
|
||||
let linebreak = LineBreak::default();
|
||||
assert_eq!(format!("{}", Token::LineBreak(linebreak)), "Tk:LineBreak");
|
||||
assert_eq!(format!("{}", Token::LineBreak(LineBreak)), "Tk:LineBreak");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten() {
|
||||
assert_eq!(LineBreak.flatten(), "\n");
|
||||
assert_eq!(Token::LineBreak(LineBreak).flatten(), "\n");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -232,4 +232,13 @@ mod tests {
|
|||
</ol>\n\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten() {
|
||||
let list = List::new(true);
|
||||
assert_eq!(list.flatten(), "[List: 0 items]");
|
||||
|
||||
let token = Token::List(List::new(true));
|
||||
assert_eq!(token.flatten(), "[List: 0 items]");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ impl Parseable for Literal {
|
|||
|
||||
impl std::fmt::Display for Literal {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "Literal {}", crate::log::wrap(&self.text))
|
||||
write!(f, "Literal {}", crate::dev::log::wrap(&self.text))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -43,9 +43,14 @@ mod tests {
|
|||
);
|
||||
|
||||
literal.text = String::from("TjY02");
|
||||
assert_eq!(
|
||||
format!("{}", Token::Literal(literal.clone())),
|
||||
"Tk:Literal TjY02"
|
||||
);
|
||||
assert_eq!(format!("{}", Token::Literal(literal)), "Tk:Literal TjY02");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten() {
|
||||
let payload = "vJtsvWD7ErYB";
|
||||
let literal = Literal::lex(&Lexeme::new(payload, "", ""));
|
||||
assert_eq!(literal.flatten(), payload);
|
||||
assert_eq!(Token::Literal(literal).flatten(), payload);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ mod tests {
|
|||
|
||||
oblique.open = false;
|
||||
assert_eq!(
|
||||
format!("{}", Token::Oblique(oblique.clone())),
|
||||
format!("{}", Token::Oblique(oblique)),
|
||||
"Tk:Oblique [closed]"
|
||||
);
|
||||
}
|
||||
|
|
@ -73,5 +73,8 @@ mod tests {
|
|||
fn flatten() {
|
||||
let oblique = Oblique::new(false);
|
||||
assert_eq!(oblique.flatten(), "");
|
||||
|
||||
let token = Token::Oblique(oblique);
|
||||
assert_eq!(token.flatten(), "");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,8 +89,19 @@ mod tests {
|
|||
|
||||
paragraph.open = None;
|
||||
assert_eq!(
|
||||
format!("{}", Token::Paragraph(paragraph.clone())),
|
||||
format!("{}", Token::Paragraph(paragraph)),
|
||||
"Tk:Paragraph [unknown]"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten() {
|
||||
let open = Paragraph::new(true);
|
||||
let closed = Paragraph::new(false);
|
||||
|
||||
assert_eq!(open.flatten(), "");
|
||||
assert_eq!(closed.flatten(), "");
|
||||
assert_eq!(Token::Paragraph(open).flatten(), "");
|
||||
assert_eq!(Token::Paragraph(closed).flatten(), "");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,42 @@ 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.clone())),
|
||||
"Tk:PreFormat [unknown]"
|
||||
format!("{}", Token::PreFormat(preformat)),
|
||||
"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);
|
||||
assert_eq!(token.flatten(), "");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ impl Parseable for Quote {
|
|||
lexeme.match_char('>') && lexeme.match_next_char(' ')
|
||||
}
|
||||
|
||||
fn lex(_lexeme: &Lexeme) -> Quote { Quote::default() }
|
||||
fn lex(_lexeme: &Lexeme) -> Quote {
|
||||
panic!("Attempt to lex a quote directly from a lexeme")
|
||||
}
|
||||
|
||||
fn render(&self) -> String {
|
||||
let opening = if let Some(url) = &self.url {
|
||||
|
|
@ -38,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)
|
||||
|
|
@ -47,7 +49,13 @@ impl Parseable for Quote {
|
|||
format!("\n{opening}\n{content}\n</blockquote>\n")
|
||||
}
|
||||
|
||||
fn flatten(&self) -> String { String::default() }
|
||||
fn flatten(&self) -> String {
|
||||
if let Some(citation) = &self.citation {
|
||||
format!(r#""{}" -- {}"#, self.text, citation)
|
||||
} else {
|
||||
format!(r#""{}""#, self.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Quote {
|
||||
|
|
@ -63,3 +71,96 @@ impl std::fmt::Display for Quote {
|
|||
write!(f, "Quote [{}]", meta.trim())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::syntax::content::parser::Token;
|
||||
|
||||
#[test]
|
||||
fn display() {
|
||||
let mut quote_slim = Quote::default();
|
||||
quote_slim.text = "iXh0141J7B8P46Gv".to_string();
|
||||
|
||||
println!("{quote_slim}");
|
||||
assert!(format!("{quote_slim}").contains("Quote"));
|
||||
assert!(!format!("{quote_slim}").contains("+url"));
|
||||
assert!(!format!("{quote_slim}").contains("+citation"));
|
||||
assert_eq!(format!("{}", Token::Quote(quote_slim)), "Tk:Quote []");
|
||||
|
||||
let mut quote_cited = Quote::default();
|
||||
quote_cited.text = "iXh0141J7B8P46Gv".to_string();
|
||||
quote_cited.citation = Some("k8Fy7htmvi2NG7yh".to_string());
|
||||
|
||||
println!("{quote_cited}");
|
||||
assert!(format!("{quote_cited}").contains("Quote"));
|
||||
assert!(!format!("{quote_cited}").contains("+url"));
|
||||
assert!(format!("{quote_cited}").contains("+citation"));
|
||||
assert_eq!(
|
||||
format!("{}", Token::Quote(quote_cited)),
|
||||
"Tk:Quote [+citation]",
|
||||
);
|
||||
|
||||
let mut quote_with_url = Quote::default();
|
||||
quote_with_url.text = "iXh0141J7B8P46Gv".to_string();
|
||||
quote_with_url.url = Some("CttVJU2IHDsjSjao".to_string());
|
||||
|
||||
println!("{quote_with_url}");
|
||||
assert!(format!("{quote_with_url}").contains("Quote"));
|
||||
assert!(format!("{quote_with_url}").contains("+url"));
|
||||
assert!(!format!("{quote_with_url}").contains("+citation"));
|
||||
assert_eq!(
|
||||
format!("{}", Token::Quote(quote_with_url)),
|
||||
"Tk:Quote [+url]",
|
||||
);
|
||||
|
||||
let mut quote_full = Quote::default();
|
||||
quote_full.text = "iXh0141J7B8P46Gv".to_string();
|
||||
quote_full.citation = Some("k8Fy7htmvi2NG7yh".to_string());
|
||||
quote_full.url = Some("CttVJU2IHDsjSjao".to_string());
|
||||
|
||||
println!("{quote_full}");
|
||||
assert!(format!("{quote_full}").contains("Quote"));
|
||||
assert!(format!("{quote_full}").contains("+url"));
|
||||
assert!(format!("{quote_full}").contains("+citation"));
|
||||
assert_eq!(
|
||||
format!("{}", Token::Quote(quote_full)),
|
||||
"Tk:Quote [+url +citation]",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten() {
|
||||
assert_eq!(Quote::default().flatten(), r#""""#);
|
||||
|
||||
let mut without_citation = Quote::default();
|
||||
let text = "AphyFDQHVbkOeaNw";
|
||||
without_citation.text = text.to_string();
|
||||
assert_eq!(without_citation.flatten(), format!(r#""{text}""#));
|
||||
|
||||
let without_citation_token = Token::Quote(without_citation);
|
||||
assert_eq!(without_citation_token.flatten(), format!(r#""{text}""#));
|
||||
|
||||
let mut with_citation = Quote::default();
|
||||
let citation = "B35rcofYM0J7";
|
||||
with_citation.text = text.to_string();
|
||||
with_citation.citation = Some(citation.to_string());
|
||||
assert_eq!(
|
||||
with_citation.flatten(),
|
||||
format!(r#""{text}" -- {citation}"#)
|
||||
);
|
||||
|
||||
let with_citation_token = Token::Quote(with_citation);
|
||||
assert_eq!(
|
||||
with_citation_token.flatten(),
|
||||
format!(r#""{text}" -- {citation}"#)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Attempt to lex a quote directly from a lexeme")]
|
||||
fn lex() {
|
||||
let lexeme = Lexeme::new("z2UI", "FiCd", "rtq4");
|
||||
Quote::lex(&lexeme);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,15 +62,15 @@ mod tests {
|
|||
);
|
||||
|
||||
strike.open = false;
|
||||
assert_eq!(
|
||||
format!("{}", Token::Strike(strike.clone())),
|
||||
"Tk:Strike [closed]"
|
||||
);
|
||||
assert_eq!(format!("{}", Token::Strike(strike)), "Tk:Strike [closed]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten() {
|
||||
let strike = Strike::new(false);
|
||||
assert_eq!(strike.flatten(), "");
|
||||
|
||||
let token = Token::Strike(strike);
|
||||
assert_eq!(token.flatten(), "");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ impl Table {
|
|||
}
|
||||
}
|
||||
|
||||
/// Counts the number of cells in the last row.
|
||||
pub fn last_row_count(&self) -> usize {
|
||||
if let Some(last) = self.contents.last() {
|
||||
last.len()
|
||||
|
|
@ -37,7 +38,9 @@ impl Table {
|
|||
impl Parseable for Table {
|
||||
fn probe(lexeme: &Lexeme) -> bool { lexeme.match_char_sequence('%', '\n') }
|
||||
|
||||
fn lex(_lexeme: &Lexeme) -> Table { Table::default() }
|
||||
fn lex(_lexeme: &Lexeme) -> Table {
|
||||
panic!("Attempt to lex a table directly from a lexeme")
|
||||
}
|
||||
|
||||
fn render(&self) -> String {
|
||||
let mut xml = String::from("\n<table>\n");
|
||||
|
|
@ -67,11 +70,100 @@ impl Parseable for Table {
|
|||
xml
|
||||
}
|
||||
|
||||
fn flatten(&self) -> String { String::default() }
|
||||
fn flatten(&self) -> String { String::from("[Table]") }
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Table {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "Table")
|
||||
let headers_width = self.headers.len();
|
||||
let contents_height = self.contents.len();
|
||||
let contents_width = self.last_row_count();
|
||||
|
||||
let mut extra = String::default();
|
||||
if headers_width > 0 && contents_height > 0 {
|
||||
extra = format!(
|
||||
" [{contents_width}x{contents_height} +{headers_width} headers]"
|
||||
);
|
||||
} else if headers_width > 0 {
|
||||
extra = format!(" [+{headers_width} headers]");
|
||||
} else if contents_height > 0 {
|
||||
extra = format!(" [{contents_width}x{contents_height}]");
|
||||
}
|
||||
|
||||
write!(f, "Table{extra}")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::syntax::content::parser::Token;
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Attempt to lex a table directly from a lexeme")]
|
||||
fn lex() {
|
||||
let lexeme = Lexeme::new("tp0h", "rrFt", "Qouf");
|
||||
Table::lex(&lexeme);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten() {
|
||||
assert_eq!(Table::default().flatten(), "[Table]");
|
||||
assert_eq!(Token::Table(Table::default()).flatten(), "[Table]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display() {
|
||||
use std::string::ToString;
|
||||
|
||||
let mut table = Table::default();
|
||||
table.add_header("A");
|
||||
table.add_header("B");
|
||||
table.add_header("C");
|
||||
|
||||
let table_token = Token::Table(table.clone());
|
||||
assert_eq!(format!("{table}"), "Table [+3 headers]");
|
||||
assert_eq!(format!("{table_token}"), "Tk:Table [+3 headers]");
|
||||
|
||||
table.add_row(
|
||||
["1", "2", "3"]
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>(),
|
||||
);
|
||||
table.add_row(
|
||||
["4", "5", "6"]
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>(),
|
||||
);
|
||||
table.add_row(
|
||||
["7", "8", "9"]
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>(),
|
||||
);
|
||||
|
||||
let table_token2 = Token::Table(table.clone());
|
||||
assert_eq!(format!("{table}"), "Table [3x3 +3 headers]");
|
||||
assert_eq!(format!("{table_token2}"), "Tk:Table [3x3 +3 headers]");
|
||||
|
||||
let mut table2 = Table::default();
|
||||
table2.add_row(
|
||||
["1", "2", "3"]
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>(),
|
||||
);
|
||||
table2.add_row(
|
||||
["2", "4", "6"]
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>(),
|
||||
);
|
||||
|
||||
let table2_token = Token::Table(table2.clone());
|
||||
assert_eq!(format!("{table2}"), "Table [3x2]");
|
||||
assert_eq!(format!("{table2_token}"), "Tk:Table [3x2]");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ mod tests {
|
|||
|
||||
underline.open = false;
|
||||
assert_eq!(
|
||||
format!("{}", Token::Underline(underline.clone())),
|
||||
format!("{}", Token::Underline(underline)),
|
||||
"Tk:Underline [closed]"
|
||||
);
|
||||
}
|
||||
|
|
@ -75,5 +75,8 @@ mod tests {
|
|||
fn flatten() {
|
||||
let underline = Underline::new(false);
|
||||
assert_eq!(underline.flatten(), "");
|
||||
|
||||
let token = Token::Underline(underline);
|
||||
assert_eq!(token.flatten(), "");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,16 +3,10 @@ use crate::syntax::content::{Parseable, parser::Lexeme};
|
|||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Verse {
|
||||
open: Option<bool>,
|
||||
citation: Option<String>,
|
||||
}
|
||||
|
||||
impl Verse {
|
||||
pub const fn new(open: bool) -> Verse {
|
||||
Verse {
|
||||
open: Some(open),
|
||||
citation: None,
|
||||
}
|
||||
}
|
||||
pub const fn new(open: bool) -> Verse { Verse { open: Some(open) } }
|
||||
|
||||
pub fn probe_end(lexeme: &Lexeme) -> bool {
|
||||
lexeme.match_char_triple('\n', '&', '\n')
|
||||
|
|
@ -24,12 +18,7 @@ impl Parseable for Verse {
|
|||
lexeme.match_char_triple('\n', '&', '\n')
|
||||
}
|
||||
|
||||
fn lex(_lexeme: &Lexeme) -> Verse {
|
||||
Verse {
|
||||
open: None,
|
||||
citation: None,
|
||||
}
|
||||
}
|
||||
fn lex(_lexeme: &Lexeme) -> Verse { Verse { open: None } }
|
||||
|
||||
fn render(&self) -> String {
|
||||
if let Some(open) = self.open {
|
||||
|
|
@ -59,12 +48,54 @@ impl std::fmt::Display for Verse {
|
|||
None => "unknown",
|
||||
};
|
||||
|
||||
let citation = if self.citation.is_some() {
|
||||
" cited"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
write!(f, "Verse [{display_open_state}{citation}]")
|
||||
write!(f, "Verse [{display_open_state}]")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::syntax::content::parser::Token;
|
||||
|
||||
#[test]
|
||||
fn lexed_verse_is_empty() {
|
||||
let verse = Verse::lex(&Lexeme::default());
|
||||
assert!(verse.open.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flatten() {
|
||||
let verse = Verse::new(true);
|
||||
assert!(verse.flatten().is_empty());
|
||||
|
||||
let token = Token::Verse(verse);
|
||||
assert_eq!(token.flatten(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "Attempt to render a verse tag while open state is unknown"
|
||||
)]
|
||||
fn render_attempt_with_unknown_open_state() {
|
||||
let verse = Verse::lex(&Lexeme::default());
|
||||
verse.render();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display() {
|
||||
let open = Verse::new(true);
|
||||
let open_token = Token::Verse(open.clone());
|
||||
assert_eq!(format!("{open}"), "Verse [open]");
|
||||
assert_eq!(format!("{open_token}"), "Tk:Verse [open]");
|
||||
|
||||
let closed = Verse::new(false);
|
||||
let closed_token = Token::Verse(closed.clone());
|
||||
assert_eq!(format!("{closed}"), "Verse [closed]");
|
||||
assert_eq!(format!("{closed_token}"), "Tk:Verse [closed]");
|
||||
|
||||
let unknown = Verse::lex(&Lexeme::default());
|
||||
let unknown_token = Token::Verse(unknown.clone());
|
||||
assert_eq!(format!("{unknown}"), "Verse [unknown]");
|
||||
assert_eq!(format!("{unknown_token}"), "Tk:Verse [unknown]");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
80
static/graph-schema.json
Normal file
80
static/graph-schema.json
Normal 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
|
|
@ -1,19 +1,21 @@
|
|||
9cc97638cf0185884ac800144b6246c7772f94ff2cc70686afa9574aaea4fa2b ./_licenses/CC_BY_ND_4_0_INTERNATIONAL.LICENSE
|
||||
8a0b028b517cc55196eb9d8b52be9af6681a012fea18b1f66fb54e8d98f5689b ./_licenses/CC_BY_ND_4_0_INTERNATIONAL.compact.LICENSE
|
||||
1d361a8f8e8ce6e68457dcd93fb56e162e6baa3bbb7e7573a290d44399f6b57e ./_licenses/SIL_OFL_1_1.LICENSE
|
||||
b9b403f45aa19c80648bfc7164caf8bf0b38727d7ea31d8bbc8ec850a8646237 ./_licenses/SIL_OFL_1_1.compact.LICENSE
|
||||
9cc97638cf0185884ac800144b6246c7772f94ff2cc70686afa9574aaea4fa2b ./_canon/CC_BY_ND_4_0_INTERNATIONAL.LICENSE
|
||||
00b6b2ffad0a8a99a0497b6474d16296adb91a9d9d83dd745d3148176aa16b7e ./_canon/CC_BY_ND_4_0_INTERNATIONAL.body.LICENSE
|
||||
5f1cc99e4d0fe6ed3495b8f7fb57bdd07623b99a9f0b2bde6076ef3719f162f9 ./_canon/CC_BY_ND_4_0_INTERNATIONAL.compact.LICENSE
|
||||
1d361a8f8e8ce6e68457dcd93fb56e162e6baa3bbb7e7573a290d44399f6b57e ./_canon/SIL_OFL_1_1.LICENSE
|
||||
6863ff4dc820f24756b6acfb38a2c6bc19d5b8d71b17030330a6f16eb4cee348 ./_canon/SIL_OFL_1_1.body.LICENSE
|
||||
2533bdb563fb91d70cb719edca0a0cdc8f5cc347d567561b4a87e7509e02073d ./_canon/SIL_OFL_1_1.compact.LICENSE
|
||||
3f70f82d58c8f58b91cf8d718621ac1537234eb6f37ee9a83469a321ef46e4db ./cormorant/LICENSE
|
||||
b9b403f45aa19c80648bfc7164caf8bf0b38727d7ea31d8bbc8ec850a8646237 ./cormorant/compact.LICENSE
|
||||
2533bdb563fb91d70cb719edca0a0cdc8f5cc347d567561b4a87e7509e02073d ./cormorant/compact.LICENSE
|
||||
c110f34253aced57d6d4f02edce58b171dca3340591a1119e02ea084a4b78f93 ./cormorant/header.LICENSE
|
||||
7d5da66dc426b59bbba125049b7016f2011f93724ddf7b7f97ce1911f958b963 ./maven/LICENSE
|
||||
b9b403f45aa19c80648bfc7164caf8bf0b38727d7ea31d8bbc8ec850a8646237 ./maven/compact.LICENSE
|
||||
2533bdb563fb91d70cb719edca0a0cdc8f5cc347d567561b4a87e7509e02073d ./maven/compact.LICENSE
|
||||
c63dd928ccd5c2c80491b84d1874227536dfced1f1f82cbd900ddc86c59b738b ./maven/header.LICENSE
|
||||
1d2892790cc9522de02d3f01ba48d12742862b1a69d04d69752938591efabecd ./mononoki/LICENSE
|
||||
b9b403f45aa19c80648bfc7164caf8bf0b38727d7ea31d8bbc8ec850a8646237 ./mononoki/compact.LICENSE
|
||||
2533bdb563fb91d70cb719edca0a0cdc8f5cc347d567561b4a87e7509e02073d ./mononoki/compact.LICENSE
|
||||
20601bbaafe0fe8d6a39527785fc501f242ebae18d1aaa4d5d15a8ac4ff4fff3 ./mononoki/header.LICENSE
|
||||
3e57bc17e8ece63050a2ac5aa99a80f87183289b18303d46311199875b4808b7 ./rawengulk/LICENSE
|
||||
b9b403f45aa19c80648bfc7164caf8bf0b38727d7ea31d8bbc8ec850a8646237 ./rawengulk/compact.LICENSE
|
||||
2533bdb563fb91d70cb719edca0a0cdc8f5cc347d567561b4a87e7509e02073d ./rawengulk/compact.LICENSE
|
||||
4d8b5ec962dfeb2813bdeb0762e3b714e68e19f9fbcdb3481d12f018b125308b ./rawengulk/header.LICENSE
|
||||
18837f5b671bd48dc6b595a475d8c45ab3e6ae33adb65d9bbeac59c58f77bc24 ./reforma/LICENSE
|
||||
71a379fe7cce14275bab647c25fe024c085d9618c836c476338ef88d33e38b22 ./reforma/compact.LICENSE
|
||||
c870d796e9bad6ab1b29bb73ea3ef316355d24d8efad78272636aaf95033ae9a ./reforma/header.LICENSE
|
||||
2edceb2f872692052b281629777bc5af5386601dd4c437f297f84846f9fa75cf ./reforma/compact.LICENSE
|
||||
a3d29ef378426d0b1d9f17364a3d7532ad623a51c8c19a3badc74c23e5e93ef6 ./reforma/header.LICENSE
|
||||
|
|
|
|||
|
|
@ -0,0 +1,392 @@
|
|||
Attribution-NoDerivatives 4.0 International
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||
does not provide legal services or legal advice. Distribution of
|
||||
Creative Commons public licenses does not create a lawyer-client or
|
||||
other relationship. Creative Commons makes its licenses and related
|
||||
information available on an "as-is" basis. Creative Commons gives no
|
||||
warranties regarding its licenses, any material licensed under their
|
||||
terms and conditions, or any related information. Creative Commons
|
||||
disclaims all liability for damages resulting from their use to the
|
||||
fullest extent possible.
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and
|
||||
conditions that creators and other rights holders may use to share
|
||||
original works of authorship and other material subject to copyright
|
||||
and certain other rights specified in the public license below. The
|
||||
following considerations are for informational purposes only, are not
|
||||
exhaustive, and do not form part of our licenses.
|
||||
|
||||
Considerations for licensors: Our public licenses are
|
||||
intended for use by those authorized to give the public
|
||||
permission to use material in ways otherwise restricted by
|
||||
copyright and certain other rights. Our licenses are
|
||||
irrevocable. Licensors should read and understand the terms
|
||||
and conditions of the license they choose before applying it.
|
||||
Licensors should also secure all rights necessary before
|
||||
applying our licenses so that the public can reuse the
|
||||
material as expected. Licensors should clearly mark any
|
||||
material not subject to the license. This includes other CC-
|
||||
licensed material, or material used under an exception or
|
||||
limitation to copyright. More considerations for licensors:
|
||||
wiki.creativecommons.org/Considerations_for_licensors
|
||||
|
||||
Considerations for the public: By using one of our public
|
||||
licenses, a licensor grants the public permission to use the
|
||||
licensed material under specified terms and conditions. If
|
||||
the licensor's permission is not necessary for any reason--for
|
||||
example, because of any applicable exception or limitation to
|
||||
copyright--then that use is not regulated by the license. Our
|
||||
licenses grant only permissions under copyright and certain
|
||||
other rights that a licensor has authority to grant. Use of
|
||||
the licensed material may still be restricted for other
|
||||
reasons, including because others have copyright or other
|
||||
rights in the material. A licensor may make special requests,
|
||||
such as asking that all changes be marked or described.
|
||||
Although not required by our licenses, you are encouraged to
|
||||
respect those requests where reasonable. More considerations
|
||||
for the public:
|
||||
wiki.creativecommons.org/Considerations_for_licensees
|
||||
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons Attribution-NoDerivatives 4.0 International Public
|
||||
License
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree
|
||||
to be bound by the terms and conditions of this Creative Commons
|
||||
Attribution-NoDerivatives 4.0 International Public License ("Public
|
||||
License"). To the extent this Public License may be interpreted as a
|
||||
contract, You are granted the Licensed Rights in consideration of Your
|
||||
acceptance of these terms and conditions, and the Licensor grants You
|
||||
such rights in consideration of benefits the Licensor receives from
|
||||
making the Licensed Material available under these terms and
|
||||
conditions.
|
||||
|
||||
|
||||
Section 1 -- Definitions.
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar
|
||||
Rights that is derived from or based upon the Licensed Material
|
||||
and in which the Licensed Material is translated, altered,
|
||||
arranged, transformed, or otherwise modified in a manner requiring
|
||||
permission under the Copyright and Similar Rights held by the
|
||||
Licensor. For purposes of this Public License, where the Licensed
|
||||
Material is a musical work, performance, or sound recording,
|
||||
Adapted Material is always produced where the Licensed Material is
|
||||
synched in timed relation with a moving image.
|
||||
|
||||
b. Copyright and Similar Rights means copyright and/or similar rights
|
||||
closely related to copyright including, without limitation,
|
||||
performance, broadcast, sound recording, and Sui Generis Database
|
||||
Rights, without regard to how the rights are labeled or
|
||||
categorized. For purposes of this Public License, the rights
|
||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||
Rights.
|
||||
|
||||
c. Effective Technological Measures means those measures that, in the
|
||||
absence of proper authority, may not be circumvented under laws
|
||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||
Treaty adopted on December 20, 1996, and/or similar international
|
||||
agreements.
|
||||
|
||||
d. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||
any other exception or limitation to Copyright and Similar Rights
|
||||
that applies to Your use of the Licensed Material.
|
||||
|
||||
e. Licensed Material means the artistic or literary work, database,
|
||||
or other material to which the Licensor applied this Public
|
||||
License.
|
||||
|
||||
f. Licensed Rights means the rights granted to You subject to the
|
||||
terms and conditions of this Public License, which are limited to
|
||||
all Copyright and Similar Rights that apply to Your use of the
|
||||
Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
g. Licensor means the individual(s) or entity(ies) granting rights
|
||||
under this Public License.
|
||||
|
||||
h. Share means to provide material to the public by any means or
|
||||
process that requires permission under the Licensed Rights, such
|
||||
as reproduction, public display, public performance, distribution,
|
||||
dissemination, communication, or importation, and to make material
|
||||
available to the public including in ways that members of the
|
||||
public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
i. Sui Generis Database Rights means rights other than copyright
|
||||
resulting from Directive 96/9/EC of the European Parliament and of
|
||||
the Council of 11 March 1996 on the legal protection of databases,
|
||||
as amended and/or succeeded, as well as other essentially
|
||||
equivalent rights anywhere in the world.
|
||||
|
||||
j. You means the individual or entity exercising the Licensed Rights
|
||||
under this Public License. Your has a corresponding meaning.
|
||||
|
||||
|
||||
Section 2 -- Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License,
|
||||
the Licensor hereby grants You a worldwide, royalty-free,
|
||||
non-sublicensable, non-exclusive, irrevocable license to
|
||||
exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
a. reproduce and Share the Licensed Material, in whole or
|
||||
in part; and
|
||||
|
||||
b. produce and reproduce, but not Share, Adapted Material.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||
Exceptions and Limitations apply to Your use, this Public
|
||||
License does not apply, and You do not need to comply with
|
||||
its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section
|
||||
6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The
|
||||
Licensor authorizes You to exercise the Licensed Rights in
|
||||
all media and formats whether now known or hereafter created,
|
||||
and to make technical modifications necessary to do so. The
|
||||
Licensor waives and/or agrees not to assert any right or
|
||||
authority to forbid You from making technical modifications
|
||||
necessary to exercise the Licensed Rights, including
|
||||
technical modifications necessary to circumvent Effective
|
||||
Technological Measures. For purposes of this Public License,
|
||||
simply making modifications authorized by this Section 2(a)
|
||||
(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
a. Offer from the Licensor -- Licensed Material. Every
|
||||
recipient of the Licensed Material automatically
|
||||
receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this
|
||||
Public License.
|
||||
|
||||
b. No downstream restrictions. You may not offer or impose
|
||||
any additional or different terms or conditions on, or
|
||||
apply any Effective Technological Measures to, the
|
||||
Licensed Material if doing so restricts exercise of the
|
||||
Licensed Rights by any recipient of the Licensed
|
||||
Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or
|
||||
may be construed as permission to assert or imply that You
|
||||
are, or that Your use of the Licensed Material is, connected
|
||||
with, or sponsored, endorsed, or granted official status by,
|
||||
the Licensor or others designated to receive attribution as
|
||||
provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not
|
||||
licensed under this Public License, nor are publicity,
|
||||
privacy, and/or other similar personality rights; however, to
|
||||
the extent possible, the Licensor waives and/or agrees not to
|
||||
assert any such rights held by the Licensor to the limited
|
||||
extent necessary to allow You to exercise the Licensed
|
||||
Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this
|
||||
Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to
|
||||
collect royalties from You for the exercise of the Licensed
|
||||
Rights, whether directly or through a collecting society
|
||||
under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly
|
||||
reserves any right to collect such royalties.
|
||||
|
||||
|
||||
Section 3 -- License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the
|
||||
following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material, You must:
|
||||
|
||||
a. retain the following if it is supplied by the Licensor
|
||||
with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed
|
||||
Material and any others designated to receive
|
||||
attribution, in any reasonable manner requested by
|
||||
the Licensor (including by pseudonym if
|
||||
designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of
|
||||
warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the
|
||||
extent reasonably practicable;
|
||||
|
||||
b. indicate if You modified the Licensed Material and
|
||||
retain an indication of any previous modifications; and
|
||||
|
||||
c. indicate the Licensed Material is licensed under this
|
||||
Public License, and include the text of, or the URI or
|
||||
hyperlink to, this Public License.
|
||||
|
||||
For the avoidance of doubt, You do not have permission under
|
||||
this Public License to Share Adapted Material.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||
reasonable manner based on the medium, means, and context in
|
||||
which You Share the Licensed Material. For example, it may be
|
||||
reasonable to satisfy the conditions by providing a URI or
|
||||
hyperlink to a resource that includes the required
|
||||
information.
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the
|
||||
information required by Section 3(a)(1)(A) to the extent
|
||||
reasonably practicable.
|
||||
|
||||
|
||||
Section 4 -- Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that
|
||||
apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||
to extract, reuse, reproduce, and Share all or a substantial
|
||||
portion of the contents of the database, provided You do not Share
|
||||
Adapted Material;
|
||||
|
||||
b. if You include all or a substantial portion of the database
|
||||
contents in a database in which You have Sui Generis Database
|
||||
Rights, then the database in which You have Sui Generis Database
|
||||
Rights (but not its individual contents) is Adapted Material; and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share
|
||||
all or a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not
|
||||
replace Your obligations under this Public License where the Licensed
|
||||
Rights include other Copyright and Similar Rights.
|
||||
|
||||
|
||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||
|
||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided
|
||||
above shall be interpreted in a manner that, to the extent
|
||||
possible, most closely approximates an absolute disclaimer and
|
||||
waiver of all liability.
|
||||
|
||||
|
||||
Section 6 -- Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and
|
||||
Similar Rights licensed here. However, if You fail to comply with
|
||||
this Public License, then Your rights under this Public License
|
||||
terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under
|
||||
Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided
|
||||
it is cured within 30 days of Your discovery of the
|
||||
violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||
right the Licensor may have to seek remedies for Your violations
|
||||
of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the
|
||||
Licensed Material under separate terms or conditions or stop
|
||||
distributing the Licensed Material at any time; however, doing so
|
||||
will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||
License.
|
||||
|
||||
|
||||
Section 7 -- Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different
|
||||
terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the
|
||||
Licensed Material not stated herein are separate from and
|
||||
independent of the terms and conditions of this Public License.
|
||||
|
||||
|
||||
Section 8 -- Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and
|
||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||
conditions on any use of the Licensed Material that could lawfully
|
||||
be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is
|
||||
deemed unenforceable, it shall be automatically reformed to the
|
||||
minimum extent necessary to make it enforceable. If the provision
|
||||
cannot be reformed, it shall be severed from this Public License
|
||||
without affecting the enforceability of the remaining terms and
|
||||
conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no
|
||||
failure to comply consented to unless expressly agreed to by the
|
||||
Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted
|
||||
as a limitation upon, or waiver of, any privileges and immunities
|
||||
that apply to the Licensor or You, including from the legal
|
||||
processes of any jurisdiction or authority.
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons is not a party to its public
|
||||
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
||||
its public licenses to material it publishes and in those instances
|
||||
will be considered the “Licensor.” The text of the Creative Commons
|
||||
public licenses is dedicated to the public domain under the CC0 Public
|
||||
Domain Dedication. Except for the limited purpose of indicating that
|
||||
material is shared under a Creative Commons public license or as
|
||||
otherwise permitted by the Creative Commons policies published at
|
||||
creativecommons.org/policies, Creative Commons does not authorize the
|
||||
use of the trademark "Creative Commons" or any other trademark or logo
|
||||
of Creative Commons without its prior written consent including,
|
||||
without limitation, in connection with any unauthorized modifications
|
||||
to any of its public licenses or any other arrangements,
|
||||
understandings, or agreements concerning use of licensed material. For
|
||||
the avoidance of doubt, this paragraph does not form part of the
|
||||
public licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
||||
85
static/public/assets/fonts/_canon/SIL_OFL_1_1.body.LICENSE
Normal file
85
static/public/assets/fonts/_canon/SIL_OFL_1_1.body.LICENSE
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
|
@ -4,4 +4,3 @@ http://pampatype.com.
|
|||
This Font Software is licensed under the Creative Commons BY-ND 4.0. This full license is available at: https://creativecommons.org/licenses/by-nd/4.0/
|
||||
|
||||
## creative commons
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
set -eu
|
||||
|
||||
log() {
|
||||
printf ' [split] %s\n' "$@" >&2
|
||||
}
|
||||
|
||||
get_marker() {
|
||||
# extended regex syntax
|
||||
cc_marker='^\#? ?Attribution-NoDerivatives 4.0 International$'
|
||||
|
|
@ -14,7 +18,7 @@ get_marker() {
|
|||
elif grep -Eq "$cc_marker" "$m_file"; then
|
||||
printf '%s' "$cc_marker"
|
||||
else
|
||||
printf '%s %s\n' "$m_file" "matches no marker" >&2
|
||||
log "$m_file matches no marker"
|
||||
printf ''
|
||||
fi
|
||||
}
|
||||
|
|
@ -22,9 +26,25 @@ get_marker() {
|
|||
behead() {
|
||||
b_file="$1"
|
||||
b_marker="$2"
|
||||
b_out_path="$3"
|
||||
b_head_out_path="$3"
|
||||
b_body_out_path="$4"
|
||||
|
||||
log "Beheading $b_file on marker $b_marker"
|
||||
b_body=$(sed -En "/$b_marker/, \$p" "$b_file")
|
||||
b_head=$(sed -E "/$b_marker/, \$d" "$b_file")
|
||||
|
||||
if [ -n "$b_head_out_path" ]; then
|
||||
log "Keeping head of $b_file on $b_head_out_path"
|
||||
printf '%s\n' "$b_head" \
|
||||
| sed -E 's/^-+$//' \
|
||||
> "$b_head_out_path"
|
||||
fi
|
||||
|
||||
if [ -n "$b_body_out_path" ]; then
|
||||
log "Keeping body of $b_file on $b_body_out_path"
|
||||
printf '%s\n' "$b_body" > "$b_body_out_path"
|
||||
fi
|
||||
|
||||
sed -E "/$b_marker/, \$d" "$b_file" | sed -E 's/^-+$//' > "$b_out_path"
|
||||
}
|
||||
|
||||
compact() {
|
||||
|
|
@ -32,39 +52,49 @@ compact() {
|
|||
c_marker="$2"
|
||||
c_out_path="$3"
|
||||
|
||||
# Eliminating [:space:] is enough for OFL to match, but Reforma's
|
||||
# markdown license file is full of quirks so we must reduce aggresively
|
||||
sed -En "/$c_marker/, \$p" "$c_file" \
|
||||
| sed -E 's/(wiki\.creativecommons\.org|https?).*\s//g' \
|
||||
| tr -cd '[:alnum:]' \
|
||||
| tr '[:upper:]' '[:lower:]' > "$c_out_path"
|
||||
| tr -d '[:space:]' \
|
||||
> "$c_out_path"
|
||||
}
|
||||
|
||||
log "Iterating over font directories"
|
||||
for dir in *; do
|
||||
[ -d "$dir" ] || continue
|
||||
[ "$dir" != _licenses ] || continue
|
||||
[ "$dir" != _canon ] || continue
|
||||
license="$dir/LICENSE"; [ -f "$license" ]
|
||||
|
||||
log "On license $license"
|
||||
|
||||
marker=$(get_marker "$license")
|
||||
log "Got '$marker' marker for license $license"
|
||||
|
||||
if [ -n "$marker" ]; then
|
||||
behead "$license" "$marker" "$dir/header.LICENSE"
|
||||
behead "$license" "$marker" "$dir/header.LICENSE" ""
|
||||
compact "$license" "$marker" "$dir/compact.LICENSE"
|
||||
fi
|
||||
done
|
||||
|
||||
for license in _licenses/*.LICENSE; do
|
||||
printf '%s' "$license" | grep -qEv '(header|compact)\.LICENSE' || continue
|
||||
log "Iterating over canonical licenses"
|
||||
for license in _canon/*.LICENSE; do
|
||||
printf '%s' "$license" \
|
||||
| grep -qEv '(header|compact|body)\.LICENSE' \
|
||||
|| continue
|
||||
|
||||
marker=$(get_marker "$license")
|
||||
log "Got '$marker' marker for license $license"
|
||||
|
||||
body_name=$(printf '%s' "$license" | sed "s*\.LICENSE*.body.LICENSE*")
|
||||
compact_name=$(printf '%s' "$license" | sed "s*\.LICENSE*.compact.LICENSE*")
|
||||
log "Using names $body_name (body) and $compact_name (compact)"
|
||||
|
||||
behead "$license" "$marker" "" "$body_name"
|
||||
compact "$license" "$marker" "$compact_name"
|
||||
done
|
||||
|
||||
for file in ./*/*LICENSE*; do
|
||||
size=$(du "$file" | awk '{print $1}')
|
||||
if [ "$size" -le 0 ]; then
|
||||
echo "$file is empty"
|
||||
log "$file is empty"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
|
@ -73,15 +103,25 @@ sha256sum ./*/*LICENSE* > LICENSES.sha256sum
|
|||
grep compact LICENSES.sha256sum | sort
|
||||
|
||||
unique_licenses=$(
|
||||
find _licenses/ -name '*.LICENSE' -not -name '*.compact.LICENSE' | wc -l
|
||||
find _canon/ \
|
||||
-name '*.LICENSE' \
|
||||
-not -name '*.compact.LICENSE' \
|
||||
-not -name '*.body.LICENSE' \
|
||||
| wc -l
|
||||
)
|
||||
unique_hashes=$(
|
||||
cat LICENSES.sha256sum | grep compact | awk '{print $1}' | sort | uniq | wc -l
|
||||
cat LICENSES.sha256sum \
|
||||
| grep compact \
|
||||
| awk '{print $1}' \
|
||||
| sort \
|
||||
| uniq \
|
||||
| wc -l
|
||||
)
|
||||
|
||||
if [ "$unique_hashes" -ne "$unique_licenses" ]; then
|
||||
echo "unique hashes: $unique_hashes"
|
||||
echo "unique licenses: $unique_licenses"
|
||||
log "Number of distinct hashes and licenses don't match."
|
||||
log "unique hashes: $unique_hashes"
|
||||
log "unique licenses: $unique_licenses"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
19
static/welcome.toml
Normal file
19
static/welcome.toml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#:schema ./graph-schema.json
|
||||
|
||||
[nodes.GettingStarted]
|
||||
title = "Getting Started"
|
||||
text = """
|
||||
## Welcome to en!
|
||||
#
|
||||
If you are seeing this, it's working!
|
||||
|
||||
Now that you know how to run it, tell en how to find your graph file by adding a `--graph` option:
|
||||
|
||||
`
|
||||
en --graph my_graph.toml
|
||||
`
|
||||
|
||||
Alternatively, you can also add a `static` directory next to the en binary with a `graph.toml` file in it.
|
||||
|
||||
To learn how to write your first graph and everything else about en, check out the |documentation|https://en.jutty.dev|.
|
||||
"""
|
||||
|
|
@ -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 %}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
set -eu
|
||||
suffix=$(printf '%s' "$1" | sed 's/.*\.//')
|
||||
tag="en:$suffix"
|
||||
|
||||
if podman container exists "$tag"; then
|
||||
podman stop --time 3 "$tag"
|
||||
fi
|
||||
|
||||
if [ "$suffix" = 'debian-dev' ]; then
|
||||
cp ../../target/release/en en
|
||||
elif [ "$suffix" = 'alpine-dev' ]; then
|
||||
cp ../../target/x86_64-unknown-linux-musl/release/en en
|
||||
fi
|
||||
|
||||
podman build \
|
||||
--tag "$tag" \
|
||||
-f "Containerfile.$suffix"
|
||||
|
||||
rm en
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
set -eu
|
||||
suffix=$(printf '%s' "$1" | sed 's/.*\.//')
|
||||
name="en-$suffix"
|
||||
tag="en:$suffix"
|
||||
|
||||
podman run \
|
||||
--replace \
|
||||
--name "$name" \
|
||||
--publish 3008:80 \
|
||||
"$tag"
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
[meta]
|
||||
config = {
|
||||
'
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"nodes": {
|
||||
"JSON": {
|
||||
"text": "",
|
||||
"title": "JSON",
|
||||
"links": [],
|
||||
"id": "JSON",
|
||||
"hidden": false,
|
||||
"connections": []
|
||||
}
|
||||
},
|
||||
"root_node": "JSON"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue