aboutsummaryrefslogtreecommitdiff
path: root/dev_tools
diff options
context:
space:
mode:
Diffstat (limited to 'dev_tools')
-rw-r--r--dev_tools/Cargo.lock435
-rw-r--r--dev_tools/Cargo.toml21
-rw-r--r--dev_tools/scripts/build-all.ps18
-rwxr-xr-xdev_tools/scripts/build-all.sh6
-rw-r--r--dev_tools/scripts/clippy-fix.ps18
-rwxr-xr-xdev_tools/scripts/clippy-fix.sh6
-rw-r--r--dev_tools/scripts/clippy.ps18
-rwxr-xr-xdev_tools/scripts/clippy.sh6
-rwxr-xr-xdev_tools/scripts/doc.ps11
-rwxr-xr-xdev_tools/scripts/doc.sh3
-rw-r--r--dev_tools/scripts/http-page-preview.ps13
-rwxr-xr-xdev_tools/scripts/http-page-preview.sh2
-rwxr-xr-xdev_tools/scripts/install-mling.ps17
-rwxr-xr-xdev_tools/scripts/install-mling.sh6
-rw-r--r--dev_tools/scripts/test-all.ps18
-rw-r--r--dev_tools/scripts/test-all.sh6
-rw-r--r--dev_tools/scripts/windows-folder-hide.ps143
-rw-r--r--dev_tools/src/bin/ci.rs225
-rw-r--r--dev_tools/src/bin/docs-code-box-fix.rs166
-rw-r--r--dev_tools/src/bin/docsify-sidebar-gen.rs265
-rw-r--r--dev_tools/src/bin/refresh-docs.rs178
-rw-r--r--dev_tools/src/bin/refresh-feature-mod.rs97
-rw-r--r--dev_tools/src/bin/sync-examples.rs131
-rw-r--r--dev_tools/src/bin/test-all-markdown-code.rs276
-rw-r--r--dev_tools/src/bin/test-examples.rs158
-rw-r--r--dev_tools/src/bin/update-version.rs98
-rw-r--r--dev_tools/src/lib.rs331
-rw-r--r--dev_tools/src/verify.rs511
-rw-r--r--dev_tools/version-files.toml19
29 files changed, 0 insertions, 3032 deletions
diff --git a/dev_tools/Cargo.lock b/dev_tools/Cargo.lock
deleted file mode 100644
index bb510bd..0000000
--- a/dev_tools/Cargo.lock
+++ /dev/null
@@ -1,435 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 4
-
-[[package]]
-name = "bumpalo"
-version = "3.20.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
-
-[[package]]
-name = "cfg-if"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
-
-[[package]]
-name = "colored"
-version = "3.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
-dependencies = [
- "windows-sys",
-]
-
-[[package]]
-name = "console"
-version = "0.16.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87"
-dependencies = [
- "encode_unicode",
- "libc",
- "unicode-width",
- "windows-sys",
-]
-
-[[package]]
-name = "encode_unicode"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
-
-[[package]]
-name = "equivalent"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
-
-[[package]]
-name = "futures-core"
-version = "0.3.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
-
-[[package]]
-name = "futures-task"
-version = "0.3.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
-
-[[package]]
-name = "futures-util"
-version = "0.3.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
-dependencies = [
- "futures-core",
- "futures-task",
- "pin-project-lite",
- "slab",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.17.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
-
-[[package]]
-name = "indexmap"
-version = "2.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
-dependencies = [
- "equivalent",
- "hashbrown",
-]
-
-[[package]]
-name = "indicatif"
-version = "0.18.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25470f23803092da7d239834776d653104d551bc4d7eacaf31e6837854b8e9eb"
-dependencies = [
- "console",
- "portable-atomic",
- "unicode-width",
- "unit-prefix",
- "web-time",
-]
-
-[[package]]
-name = "itoa"
-version = "1.0.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
-
-[[package]]
-name = "js-sys"
-version = "0.3.102"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31"
-dependencies = [
- "cfg-if",
- "futures-util",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "just_fmt"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5454cda0d57db59778608d7a47bff5b16c6705598265869fb052b657f66cf05e"
-
-[[package]]
-name = "just_template"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db3edb658c34b10b69c4b3b58f7ba989cd09c82c0621dee1eef51843c2327225"
-dependencies = [
- "just_fmt",
-]
-
-[[package]]
-name = "libc"
-version = "0.2.186"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
-
-[[package]]
-name = "memchr"
-version = "2.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
-
-[[package]]
-name = "once_cell"
-version = "1.21.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
-
-[[package]]
-name = "pin-project-lite"
-version = "0.2.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
-
-[[package]]
-name = "portable-atomic"
-version = "1.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.106"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.45"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "rustversion"
-version = "1.0.22"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
-
-[[package]]
-name = "serde"
-version = "1.0.228"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
-dependencies = [
- "serde_core",
- "serde_derive",
-]
-
-[[package]]
-name = "serde_core"
-version = "1.0.228"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
-dependencies = [
- "serde_derive",
-]
-
-[[package]]
-name = "serde_derive"
-version = "1.0.228"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "serde_json"
-version = "1.0.150"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
-dependencies = [
- "itoa",
- "memchr",
- "serde",
- "serde_core",
- "zmij",
-]
-
-[[package]]
-name = "serde_spanned"
-version = "0.6.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "slab"
-version = "0.4.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
-
-[[package]]
-name = "syn"
-version = "2.0.117"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "tokio"
-version = "1.52.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
-dependencies = [
- "pin-project-lite",
- "tokio-macros",
-]
-
-[[package]]
-name = "tokio-macros"
-version = "2.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "toml"
-version = "0.8.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
-dependencies = [
- "serde",
- "serde_spanned",
- "toml_datetime",
- "toml_edit",
-]
-
-[[package]]
-name = "toml_datetime"
-version = "0.6.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "toml_edit"
-version = "0.22.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
-dependencies = [
- "indexmap",
- "serde",
- "serde_spanned",
- "toml_datetime",
- "toml_write",
- "winnow",
-]
-
-[[package]]
-name = "toml_write"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
-
-[[package]]
-name = "tools"
-version = "0.1.0"
-dependencies = [
- "colored",
- "indicatif",
- "just_fmt",
- "just_template",
- "serde",
- "serde_json",
- "tokio",
- "toml",
-]
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
-
-[[package]]
-name = "unicode-width"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
-
-[[package]]
-name = "unit-prefix"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3"
-
-[[package]]
-name = "wasm-bindgen"
-version = "0.2.125"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a"
-dependencies = [
- "cfg-if",
- "once_cell",
- "rustversion",
- "wasm-bindgen-macro",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-macro"
-version = "0.2.125"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d"
-dependencies = [
- "quote",
- "wasm-bindgen-macro-support",
-]
-
-[[package]]
-name = "wasm-bindgen-macro-support"
-version = "0.2.125"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd"
-dependencies = [
- "bumpalo",
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-shared"
-version = "0.2.125"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "web-time"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
-dependencies = [
- "js-sys",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "windows-link"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
-
-[[package]]
-name = "windows-sys"
-version = "0.61.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
-dependencies = [
- "windows-link",
-]
-
-[[package]]
-name = "winnow"
-version = "0.7.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "zmij"
-version = "1.0.21"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
diff --git a/dev_tools/Cargo.toml b/dev_tools/Cargo.toml
deleted file mode 100644
index 9855580..0000000
--- a/dev_tools/Cargo.toml
+++ /dev/null
@@ -1,21 +0,0 @@
-[package]
-name = "tools"
-version = "0.1.0"
-edition = "2024"
-authors = ["Weicao-CatilGrass"]
-description = "Development tools for mingling"
-license = "MIT OR Apache-2.0"
-repository = "https://github.com/catilgrass/mingling"
-readme = "../README.md"
-keywords = ["cli", "development", "tools"]
-categories = ["command-line-interface", "development-tools"]
-
-[dependencies]
-just_template = "0.1.3"
-just_fmt = "0.1.2"
-colored = "3.1.1"
-toml = "0.8"
-serde = { version = "1", features = ["derive"] }
-serde_json = "1"
-tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
-indicatif = "0.18.4"
diff --git a/dev_tools/scripts/build-all.ps1 b/dev_tools/scripts/build-all.ps1
deleted file mode 100644
index 4f35ed8..0000000
--- a/dev_tools/scripts/build-all.ps1
+++ /dev/null
@@ -1,8 +0,0 @@
-$starting_dir = Get-Location
-Get-ChildItem -Recurse -Filter "Cargo.toml" | ForEach-Object {
- $project_dir = $_.DirectoryName
- Push-Location $project_dir
- cargo build
- Pop-Location
-}
-Set-Location $starting_dir
diff --git a/dev_tools/scripts/build-all.sh b/dev_tools/scripts/build-all.sh
deleted file mode 100755
index 2036b41..0000000
--- a/dev_tools/scripts/build-all.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/bash
-
-find . -name "Cargo.toml" -type f | while read -r cargo_file; do
- project_dir=$(dirname "$cargo_file")
- (cd "$project_dir" && cargo build)
-done
diff --git a/dev_tools/scripts/clippy-fix.ps1 b/dev_tools/scripts/clippy-fix.ps1
deleted file mode 100644
index 1d24f92..0000000
--- a/dev_tools/scripts/clippy-fix.ps1
+++ /dev/null
@@ -1,8 +0,0 @@
-$starting_dir = Get-Location
-Get-ChildItem -Recurse -Filter "Cargo.toml" | ForEach-Object {
- $project_dir = $_.DirectoryName
- Push-Location $project_dir
- cargo clippy --fix --allow-dirty --allow-no-vcs --quiet
- Pop-Location
-}
-Set-Location $starting_dir
diff --git a/dev_tools/scripts/clippy-fix.sh b/dev_tools/scripts/clippy-fix.sh
deleted file mode 100755
index 9771ad4..0000000
--- a/dev_tools/scripts/clippy-fix.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/bash
-
-find . -name "Cargo.toml" -type f | while read -r cargo_file; do
- project_dir=$(dirname "$cargo_file")
- (cd "$project_dir" && cargo clippy --fix --allow-dirty --allow-no-vcs --quiet)
-done
diff --git a/dev_tools/scripts/clippy.ps1 b/dev_tools/scripts/clippy.ps1
deleted file mode 100644
index 1858873..0000000
--- a/dev_tools/scripts/clippy.ps1
+++ /dev/null
@@ -1,8 +0,0 @@
-$starting_dir = Get-Location
-Get-ChildItem -Recurse -Filter "Cargo.toml" | ForEach-Object {
- $project_dir = $_.DirectoryName
- Push-Location $project_dir
- cargo clippy --quiet
- Pop-Location
-}
-Set-Location $starting_dir
diff --git a/dev_tools/scripts/clippy.sh b/dev_tools/scripts/clippy.sh
deleted file mode 100755
index b393545..0000000
--- a/dev_tools/scripts/clippy.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/bash
-
-find . -name "Cargo.toml" -type f | while read -r cargo_file; do
- project_dir=$(dirname "$cargo_file")
- (cd "$project_dir" && cargo clippy --quiet)
-done
diff --git a/dev_tools/scripts/doc.ps1 b/dev_tools/scripts/doc.ps1
deleted file mode 100755
index 987f0de..0000000
--- a/dev_tools/scripts/doc.ps1
+++ /dev/null
@@ -1 +0,0 @@
-cargo doc --workspace --no-deps --features builds,structural_renderer,repl,comp,parser,clap,extra_macros --open
diff --git a/dev_tools/scripts/doc.sh b/dev_tools/scripts/doc.sh
deleted file mode 100755
index 5e8a311..0000000
--- a/dev_tools/scripts/doc.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash
-
-cargo doc --workspace --no-deps --features builds,structural_renderer,repl,comp,parser,clap,extra_macros --open
diff --git a/dev_tools/scripts/http-page-preview.ps1 b/dev_tools/scripts/http-page-preview.ps1
deleted file mode 100644
index 8cc3579..0000000
--- a/dev_tools/scripts/http-page-preview.ps1
+++ /dev/null
@@ -1,3 +0,0 @@
-$starting_dir = Get-Location
-python -m http.server 3000
-Set-Location $starting_dir
diff --git a/dev_tools/scripts/http-page-preview.sh b/dev_tools/scripts/http-page-preview.sh
deleted file mode 100755
index bed4b1c..0000000
--- a/dev_tools/scripts/http-page-preview.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/bash
-python3 -m http.server 3000
diff --git a/dev_tools/scripts/install-mling.ps1 b/dev_tools/scripts/install-mling.ps1
deleted file mode 100755
index bebe9ff..0000000
--- a/dev_tools/scripts/install-mling.ps1
+++ /dev/null
@@ -1,7 +0,0 @@
-cargo install --path mling
-
-New-Item -ItemType Directory -Force -Path .temp/comp | Out-Null
-# Copy all files containing _comp from the debug directory
-Get-ChildItem .temp/target/release/*_comp* | ForEach-Object {
- Copy-Item $_.FullName .temp/comp/
-}
diff --git a/dev_tools/scripts/install-mling.sh b/dev_tools/scripts/install-mling.sh
deleted file mode 100755
index 5f2ee7a..0000000
--- a/dev_tools/scripts/install-mling.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/bash
-
-cargo install --path mling
-
-mkdir -p .temp/comp
-cp .temp/target/release/*_comp.* .temp/comp/ 2>/dev/null || echo "No matching files found"
diff --git a/dev_tools/scripts/test-all.ps1 b/dev_tools/scripts/test-all.ps1
deleted file mode 100644
index 231698a..0000000
--- a/dev_tools/scripts/test-all.ps1
+++ /dev/null
@@ -1,8 +0,0 @@
-$starting_dir = Get-Location
-Get-ChildItem -Recurse -Filter "Cargo.toml" | ForEach-Object {
- $project_dir = $_.DirectoryName
- Push-Location $project_dir
- cargo test
- Pop-Location
-}
-Set-Location $starting_dir
diff --git a/dev_tools/scripts/test-all.sh b/dev_tools/scripts/test-all.sh
deleted file mode 100644
index b387463..0000000
--- a/dev_tools/scripts/test-all.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/bash
-
-find . -name "Cargo.toml" -type f | while read -r cargo_file; do
- project_dir=$(dirname "$cargo_file")
- (cd "$project_dir" && cargo test)
-done
diff --git a/dev_tools/scripts/windows-folder-hide.ps1 b/dev_tools/scripts/windows-folder-hide.ps1
deleted file mode 100644
index 0ab2632..0000000
--- a/dev_tools/scripts/windows-folder-hide.ps1
+++ /dev/null
@@ -1,43 +0,0 @@
-# Check `last_check`
-
-$lastCheckFile = Join-Path $PSScriptRoot "last_check"
-$currentTime = Get-Date
-$timeThreshold = 10
-
-if (Test-Path $lastCheckFile) {
- $lastCheckTime = Get-Content $lastCheckFile | Get-Date
- $timeDiff = ($currentTime - $lastCheckTime).TotalMinutes
-
- if ($timeDiff -lt $timeThreshold) {
- exit
- }
-}
-
-$currentTime.ToString() | Out-File -FilePath $lastCheckFile -Force
-
-# Hide Files
-
-Set-Location -Path (Join-Path $PSScriptRoot "..\..")
-
-Get-ChildItem -Path . -Force -Recurse -ErrorAction SilentlyContinue | Where-Object {
- $_.FullName -notmatch '\\.temp\\' -and $_.FullName -notmatch '\\.git\\'
-} | ForEach-Object {
- attrib -h $_.FullName 2>&1 | Out-Null
-}
-
-Get-ChildItem -Path . -Force -Recurse -ErrorAction SilentlyContinue | Where-Object {
- $_.Name -match '^\..*' -and $_.FullName -notmatch '\\\.\.$' -and $_.FullName -notmatch '\\\.$'
-} | ForEach-Object {
- attrib +h $_.FullName 2>&1 | Out-Null
-}
-
-if (Get-Command git -ErrorAction SilentlyContinue) {
- git status --ignored --short | ForEach-Object {
- if ($_ -match '^!!\s+(.+)$') {
- $ignoredPath = $matches[1]
- if ($ignoredPath -notmatch '\.lnk$' -and (Test-Path $ignoredPath)) {
- attrib +h $ignoredPath 2>&1 | Out-Null
- }
- }
- }
-}
diff --git a/dev_tools/src/bin/ci.rs b/dev_tools/src/bin/ci.rs
deleted file mode 100644
index f8aed79..0000000
--- a/dev_tools/src/bin/ci.rs
+++ /dev/null
@@ -1,225 +0,0 @@
-use std::io::Write as _;
-use std::process::exit;
-
-use tools::{
- cargo_tomls, crate_name_from, eprintln_cargo_style, println_cargo_style, run_cmd, run_parallel,
-};
-
-fn get_ignore_dirs() -> Vec<String> {
- vec![
- ".temp".to_string(),
- "mling/res".to_string(),
- "mling\\res".to_string(),
- ]
-}
-
-fn print_help() {
- println!(
- r"
-Usage: ci [options]
-Options:
- -h, --help Print this help message
- -y Auto-confirm temporary commits
- --dirty Run CI on dirty workspace (skip temp commit & clean check)
- --refresh-docs Refresh documentation files
- --test-docs Run documentation tests (build, clippy, test)
- --test-codes Test examples and documentation code blocks
-
-If no specific options are given, all checks are run.
- "
- );
-}
-
-fn main() {
- #[cfg(windows)]
- let _ = colored::control::set_virtual_terminal(true);
- println!("{}", include_str!("../../../docs/res/ci_banner.txt"));
-
- let args: Vec<String> = std::env::args().collect();
-
- if args.iter().any(|a| a == "-h" || a == "--help") {
- print_help();
- return;
- }
-
- let auto_yes = args.iter().any(|a| a == "-y");
- let dirty = args.iter().any(|a| a == "--dirty");
-
- let test_docs = args.iter().any(|a| a == "--test-docs");
- let refresh_docs = args.iter().any(|a| a == "--refresh-docs");
- let test_codes = args.iter().any(|a| a == "--test-codes");
- let any_specified = test_docs || refresh_docs || test_codes;
- let run_all = !any_specified;
-
- let needs_commit_temp = !dirty && !{ run_cmd!("git diff-index --quiet HEAD --").is_ok() };
-
- if needs_commit_temp {
- if auto_yes {
- run_cmd!("git add .").unwrap();
- run_cmd!("git commit -m \"[DO NOT PUSH] CI TEMP [DO NOT PUSH]\"").unwrap();
- } else {
- print!("Working tree is not clean, temporarily commit? [y/N]:");
- std::io::stdout().flush().unwrap();
- let mut input = String::new();
- std::io::stdin().read_line(&mut input).unwrap();
- let input = input.trim();
- if input == "y" || input == "Y" || input == "yes" || input == "Yes" {
- run_cmd!("git add .").unwrap();
- run_cmd!("git commit -m \"[DO NOT PUSH] CI TEMP [DO NOT PUSH]\"").unwrap();
- } else {
- eprintln_cargo_style!("Aborting.");
- exit(2)
- }
- }
- }
-
- if let Err(exit_code) = ci(test_docs, test_codes, run_all) {
- restore_workspace(needs_commit_temp).unwrap();
- exit(exit_code)
- }
-
- if !dirty {
- let is_worktree_clean = run_cmd!("git diff-index --quiet HEAD --").is_ok();
- if !is_worktree_clean {
- eprintln_cargo_style!("The repository was contaminated during CI, failing!");
-
- // Print git status
- println!();
- let _ = run_cmd!("git status");
-
- if needs_commit_temp {
- restore_workspace(true).unwrap();
- }
- exit(1)
- }
- }
-
- println_cargo_style!("Done: All check passed!");
-
- if needs_commit_temp {
- restore_workspace(true).unwrap();
- }
-}
-
-fn restore_workspace(undo_commit: bool) -> Result<(), i32> {
- run_cmd!("git reset --hard --quiet")?;
- if undo_commit {
- run_cmd!("git reset --soft HEAD~1 --quiet")?;
- run_cmd!("git reset --quiet")?;
- }
- Ok(())
-}
-
-fn ci(test_docs: bool, test_codes: bool, run_all: bool) -> Result<(), i32> {
- if run_all || test_codes {
- println_cargo_style!("Phase: Scan and build all crates");
- build_all()?;
-
- println_cargo_style!("Phase: Run clippy for all crates");
- clippy_all()?;
-
- println_cargo_style!("Phase: Test all crates");
- test_all()?;
- }
-
- if run_all || test_docs {
- println_cargo_style!("Phase: Test all examples");
- test_examples()?;
-
- println_cargo_style!("Phase: Verify all *.md document code blocks are compilable");
- test_docs_code_blocks()?;
-
- println_cargo_style!("Phase: Check all documentation is up to date");
- docs_refresh()?;
- }
-
- run_cmd!("git add --renormalize .")?;
-
- Ok(())
-}
-
-fn test_examples() -> Result<(), i32> {
- run_cmd!("cargo run --manifest-path dev_tools/Cargo.toml --color always --bin test-examples")
-}
-
-fn test_docs_code_blocks() -> Result<(), i32> {
- run_cmd!(
- "cargo run --manifest-path dev_tools/Cargo.toml --color always --bin test-all-markdown-code"
- )
-}
-
-fn build_all() -> Result<(), i32> {
- let ignore_dirs = get_ignore_dirs();
- let cargo_tomls = cargo_tomls();
- let mut tasks = Vec::new();
- for cargo_toml in cargo_tomls {
- let path = cargo_toml.parent().unwrap_or(std::path::Path::new(""));
- let path_str = path.to_string_lossy();
- if ignore_dirs.iter().any(|d| path_str.contains(d.as_str())) {
- continue;
- }
- let label = format!("Build: {}", cargo_toml.to_string_lossy());
- let crate_name = crate_name_from(&cargo_toml);
- let cmd = format!(
- "cargo build --manifest-path {} --color always",
- cargo_toml.to_string_lossy()
- );
- tasks.push((label, crate_name, cmd));
- }
- run_parallel("Building", tasks)
-}
-
-fn clippy_all() -> Result<(), i32> {
- let ignore_dirs = get_ignore_dirs();
- let cargo_tomls = cargo_tomls();
- let mut tasks = Vec::new();
- for cargo_toml in cargo_tomls {
- let path = cargo_toml.parent().unwrap_or(std::path::Path::new(""));
- let path_str = path.to_string_lossy();
- if ignore_dirs.iter().any(|d| path_str.contains(d.as_str())) {
- continue;
- }
- let label = format!("Clippy: {}", cargo_toml.to_string_lossy());
- let crate_name = crate_name_from(&cargo_toml);
- let cmd = format!(
- "cargo clippy --manifest-path {} --color always -- -D warnings",
- cargo_toml.to_string_lossy()
- );
- tasks.push((label, crate_name, cmd));
- }
- run_parallel("Clippy", tasks)
-}
-
-fn test_all() -> Result<(), i32> {
- let ignore_dirs = get_ignore_dirs();
- let cargo_tomls = cargo_tomls();
- let mut tasks = Vec::new();
- for cargo_toml in cargo_tomls {
- let path = cargo_toml.parent().unwrap_or(std::path::Path::new(""));
- let path_str = path.to_string_lossy();
- if ignore_dirs.iter().any(|d| path_str.contains(d.as_str())) {
- continue;
- }
- let label = format!("Testing: {}", cargo_toml.to_string_lossy());
- let crate_name = crate_name_from(&cargo_toml);
- let cmd = format!(
- "cargo test --manifest-path {} --color always",
- cargo_toml.to_string_lossy()
- );
- tasks.push((label, crate_name, cmd));
- }
- run_parallel("Testing", tasks)
-}
-
-fn docs_refresh() -> Result<(), i32> {
- println_cargo_style!("Refresh: document at `./docs/`");
-
- run_cmd!("cargo run --manifest-path dev_tools/Cargo.toml --bin docs-code-box-fix")?;
- run_cmd!("cargo run --manifest-path dev_tools/Cargo.toml --bin docsify-sidebar-gen")?;
- run_cmd!("cargo run --manifest-path dev_tools/Cargo.toml --bin refresh-docs")?;
- run_cmd!("cargo run --manifest-path dev_tools/Cargo.toml --bin refresh-feature-mod")?;
- run_cmd!("cargo run --manifest-path dev_tools/Cargo.toml --bin sync-examples")?;
- run_cmd!("cargo fmt")?;
-
- Ok(())
-}
diff --git a/dev_tools/src/bin/docs-code-box-fix.rs b/dev_tools/src/bin/docs-code-box-fix.rs
deleted file mode 100644
index 21d2cce..0000000
--- a/dev_tools/src/bin/docs-code-box-fix.rs
+++ /dev/null
@@ -1,166 +0,0 @@
-use std::fs;
-use std::path::Path;
-
-use tools::println_cargo_style;
-
-/// Docsify code blocks require that blank lines before and after code blocks are not completely empty,
-/// but must contain at least one space, otherwise code block rendering will have issues.
-///
-/// This tool scans all `.md` files in the docs directory,
-/// and replaces completely empty lines before and after code blocks with blank lines containing a single space.
-const DOCS_DIR: &str = "./docs";
-
-fn main() {
- println_cargo_style!("Fixing: code box empty lines in docs/**/*.md ...");
- let repo_root = find_git_repo().expect("Cannot find git repo root");
- let docs_dir = repo_root.join(DOCS_DIR);
-
- let mut fixed_count = 0;
- let mut file_count = 0;
-
- collect_md_files(&docs_dir, &mut |path| {
- if let Some(name) = path.file_name() {
- let name = name.to_string_lossy();
- if name.to_lowercase() == "_sidebar.md" {
- return;
- }
- }
-
- let content = fs::read_to_string(path).unwrap_or_default();
- if content.is_empty() {
- return;
- }
-
- let new_content = fix_code_box_empty_lines(&content);
- if new_content != content {
- fs::write(path, &new_content).unwrap();
- println_cargo_style!("Fixed: {}", path.display());
- fixed_count += 1;
- }
- file_count += 1;
- });
-
- println_cargo_style!(
- "Done: Scanned {} files, fixed {} files.",
- file_count,
- fixed_count
- );
-}
-
-fn fix_code_box_empty_lines(content: &str) -> String {
- let mut result = String::new();
- let lines: Vec<&str> = content.lines().collect();
- let len = lines.len();
-
- let mut i = 0;
- while i < len {
- let line = lines[i];
-
- // detect beginning of code block: beginning with ```
- if line.trim_start().starts_with("```") {
- // record the beginning line of the code block
- result.push_str(line);
- result.push('\n');
- i += 1;
-
- // find the end of the code block
- let mut found_end = false;
- let code_start = i; // record starting position of code content
- let mut code_end = len; // index of code block end line
-
- while i < len {
- let cline = lines[i];
- if cline.trim_start().starts_with("```") && cline.trim() != "" {
- // this is the closing marker
- code_end = i;
- found_end = true;
- break;
- }
- i += 1;
- }
-
- // check the blank line before the code block
- // if result ends with \n\n, add a space to turn it into \n \n
- ensure_space_before_code_block(&mut result);
-
- // output code content
- for code_line in lines.iter().take(code_end).skip(code_start) {
- if code_line.is_empty() {
- result.push(' ');
- } else {
- result.push_str(code_line);
- }
- result.push('\n');
- }
-
- if found_end {
- result.push_str(lines[code_end]);
- result.push('\n');
- i += 1;
-
- // check the blank line after the code block
- // if the next line is blank, change it to one with a space
- if i < len && lines[i].trim().is_empty() && lines[i].is_empty() {
- // skip the original blank line, write " \n"
- result.push(' ');
- result.push('\n');
- i += 1;
- }
- }
- } else {
- result.push_str(line);
- result.push('\n');
- i += 1;
- }
- }
-
- // remove trailing newlines
- while result.ends_with('\n') {
- result.pop();
- }
- result.push('\n');
-
- result
-}
-
-/// ensure there is a blank line with a space before the code block
-fn ensure_space_before_code_block(result: &mut String) {
- // if result ends with \n\n,
- // turn it into \n \n
- let len = result.len();
- if len >= 2 && result[len - 2..] == *"\n\n" {
- // insert a space before the last \n
- result.insert(len - 1, ' ');
- }
-}
-
-/// recursively collect all .md files in the docs directory
-fn collect_md_files(dir: &Path, callback: &mut dyn FnMut(&Path)) {
- if let Ok(entries) = fs::read_dir(dir) {
- for entry in entries.flatten() {
- let path = entry.path();
- if path.is_dir() {
- collect_md_files(&path, callback);
- } else if path.extension().is_some_and(|ext| ext == "md") {
- callback(&path);
- }
- }
- }
-}
-
-fn find_git_repo() -> Option<std::path::PathBuf> {
- let mut current_dir = std::env::current_dir().ok()?;
-
- loop {
- let git_dir = current_dir.join(".git");
- if git_dir.exists() && git_dir.is_dir() {
- return Some(current_dir);
- }
-
- if !current_dir.pop() {
- break;
- }
- }
-
- None
-}
diff --git a/dev_tools/src/bin/docsify-sidebar-gen.rs b/dev_tools/src/bin/docsify-sidebar-gen.rs
deleted file mode 100644
index b926112..0000000
--- a/dev_tools/src/bin/docsify-sidebar-gen.rs
+++ /dev/null
@@ -1,265 +0,0 @@
-use std::collections::BTreeMap;
-use std::fmt::Write;
-use std::path::{Path, PathBuf};
-
-use tools::println_cargo_style;
-
-const SIDEBAR_HEAD: &str = "- [Welcome!](README)\n";
-
-fn main() {
- println_cargo_style!("Refresh: _sidebar.md");
- gen_all_sidebars();
-}
-
-/// Find all README.md under docs/, treat each as a site, and generate _sidebar.md for it.
-fn gen_all_sidebars() {
- let repo_root = find_git_repo().unwrap();
- let docs_root = repo_root.join("docs");
-
- let readme_paths = find_all_readmes(&docs_root);
-
- for readme_path in &readme_paths {
- let site_root = readme_path.parent().unwrap();
-
- let content_dir = find_content_dir(site_root);
-
- if let Some(content_dir) = content_dir {
- let lines = build_sidebar_content(site_root, &content_dir, SIDEBAR_HEAD);
-
- let sidebar_path = site_root.join("_sidebar.md");
- std::fs::write(&sidebar_path, lines).unwrap();
- println_cargo_style!("Generated: {}", sidebar_path.display());
- }
- }
-}
-
-/// Recursively find all README.md files under a directory.
-fn find_all_readmes(dir: &Path) -> Vec<PathBuf> {
- let mut results = Vec::new();
- if let Ok(read_dir) = std::fs::read_dir(dir) {
- let mut entries: Vec<_> = read_dir.flatten().collect();
- entries.sort_by_key(|e| e.path());
- for entry in entries {
- let path = entry.path();
- if path.is_dir() {
- results.extend(find_all_readmes(&path));
- } else if path.file_name().map_or(false, |n| n == "README.md") {
- results.push(path);
- }
- }
- }
- results
-}
-
-/// Find the content directory for a site:
-/// 1. Prefer `pages/` if it exists (backward compatible)
-/// 2. Fall back to the first subdirectory that contains .md files
-fn find_content_dir(site_root: &Path) -> Option<PathBuf> {
- // Try pages/ first
- let pages_dir = site_root.join("pages");
- if pages_dir.exists() && pages_dir.is_dir() {
- return Some(pages_dir);
- }
-
- // Fall back to any subdirectory containing .md files
- if let Ok(read_dir) = std::fs::read_dir(site_root) {
- let mut entries: Vec<_> = read_dir.flatten().collect();
- entries.sort_by_key(|e| e.path());
- for entry in entries {
- let path = entry.path();
- if path.is_dir() {
- if has_markdown_files(&path) {
- return Some(path);
- }
- }
- }
- }
-
- None
-}
-
-/// Check if a directory (recursively) contains any .md files.
-fn has_markdown_files(dir: &Path) -> bool {
- if let Ok(read_dir) = std::fs::read_dir(dir) {
- for entry in read_dir.flatten() {
- let path = entry.path();
- if path.is_dir() {
- if has_markdown_files(&path) {
- return true;
- }
- } else if path.extension().is_some_and(|ext| ext == "md") {
- return true;
- }
- }
- }
- false
-}
-
-/// Build sidebar content: scan .md files in `pages_dir` and return a formatted sidebar string
-fn build_sidebar_content(base_dir: &Path, pages_dir: &Path, sidebar_head: &str) -> String {
- let mut lines = String::from(sidebar_head);
-
- // Collect and sort entries at root level first
- let mut root_files: Vec<SidebarEntry> = Vec::new();
- // Subdirectory name -> its files
- let mut sub_dirs: BTreeMap<String, Vec<SidebarEntry>> = BTreeMap::new();
-
- if let Ok(read_dir) = std::fs::read_dir(pages_dir) {
- for entry in read_dir.flatten() {
- let path = entry.path();
- if path.is_dir() {
- let dir_name = entry.file_name().to_string_lossy().to_string();
- let entries = collect_markdown_files(&path, base_dir);
- if !entries.is_empty() {
- // Check for .name file to override directory display name
- let display_name = get_directory_display_name(&path, &dir_name);
- sub_dirs.insert(display_name, entries);
- }
- } else if path.extension().is_some_and(|ext| ext == "md") {
- let title = extract_title(&path);
- let relative = path
- .strip_prefix(base_dir)
- .unwrap()
- .to_string_lossy()
- .replace('\\', "/");
- let link = relative
- .strip_suffix(".md")
- .unwrap_or(&relative)
- .to_string();
- root_files.push(SidebarEntry { title, link });
- }
- }
- }
-
- // Sort root files — natural order (1, 2, ..., 10, 11)
- root_files.sort_by(|a, b| natural_cmp(&a.link, &b.link));
-
- // Append root-level files
- for f in &root_files {
- let _ = writeln!(lines, "* [{}]({})", f.title, f.link);
- }
-
- // Append subdirectory groups
- for (dir_name, entries) in &sub_dirs {
- let mut sorted_entries = entries.clone();
- sorted_entries.sort_by(|a, b| natural_cmp(&a.link, &b.link));
-
- // Directory header with 2-space indent
- let _ = writeln!(lines, "* {dir_name}");
- for f in &sorted_entries {
- let _ = writeln!(lines, " * [{}]({})", f.title, f.link);
- }
- }
-
- lines
-}
-
-#[derive(Clone)]
-struct SidebarEntry {
- title: String,
- link: String,
-}
-
-/// Collect all `.md` files directly under `dir`
-fn collect_markdown_files(dir: &Path, base_dir: &Path) -> Vec<SidebarEntry> {
- let mut entries = Vec::new();
-
- if let Ok(read_dir) = std::fs::read_dir(dir) {
- for entry in read_dir.flatten() {
- let path = entry.path();
- if path.extension().is_some_and(|ext| ext == "md") {
- let title = extract_title(&path);
- let relative = path
- .strip_prefix(base_dir)
- .unwrap()
- .to_string_lossy()
- .replace('\\', "/");
- let link = relative
- .strip_suffix(".md")
- .unwrap_or(&relative)
- .to_string();
- entries.push(SidebarEntry { title, link });
- }
- }
- }
-
- entries
-}
-
-/// Extract title from the first line `<h1 align="center">TITLE</h1>`.
-/// Fallback to filename stem.
-fn extract_title(path: &Path) -> String {
- let content = std::fs::read_to_string(path).unwrap_or_default();
- if let Some(first_line) = content.lines().next() {
- let trimmed = first_line.trim();
- // Find `>TITLE<` between `<h1 align="center">` and `</h1>`
- if let Some(start) = trimmed.find('>') {
- let after_start = &trimmed[start + 1..];
- if let Some(end) = after_start.find('<') {
- return after_start[..end].to_string();
- }
- }
- }
- // Fallback: use file stem
- path.file_stem().map_or_else(
- || "Untitled".to_string(),
- |s| s.to_string_lossy().to_string(),
- )
-}
-
-/// Read `.name` file inside a directory to get its display name for the sidebar.
-/// Falls back to the directory name itself if no `.name` file exists.
-fn get_directory_display_name(dir_path: &std::path::Path, fallback: &str) -> String {
- let name_file = dir_path.join(".name");
- if name_file.exists() && name_file.is_file() {
- std::fs::read_to_string(&name_file)
- .ok()
- .map(|s| s.trim().to_string())
- .filter(|s| !s.is_empty())
- .unwrap_or_else(|| fallback.to_string())
- } else {
- fallback.to_string()
- }
-}
-
-fn find_git_repo() -> Option<std::path::PathBuf> {
- let mut current_dir = std::env::current_dir().ok()?;
-
- loop {
- let git_dir = current_dir.join(".git");
- if git_dir.exists() && git_dir.is_dir() {
- return Some(current_dir);
- }
-
- if !current_dir.pop() {
- break;
- }
- }
-
- None
-}
-
-/// Natural (numeric-aware) comparison for sidebar links.
-///
-/// Files prefixed with a number (e.g. `1-getting-started`) are sorted by that number;
-/// files without a numeric prefix fall back to lexicographic order (after numbers).
-fn natural_cmp(a: &str, b: &str) -> std::cmp::Ordering {
- let num_a = extract_leading_number(a);
- let num_b = extract_leading_number(b);
- num_a.cmp(&num_b).then_with(|| a.cmp(b))
-}
-
-/// Extract the leading numeric prefix from a sidebar link path.
-///
-/// Looks at the filename stem (after the last `/`) for a number before the first `-`.
-/// Returns `usize::MAX` for entries without a numeric prefix.
-fn extract_leading_number(link: &str) -> usize {
- if let Some(file_stem) = link.rsplit('/').next() {
- if let Some(num_end) = file_stem.find('-') {
- if let Ok(num) = file_stem[..num_end].parse::<usize>() {
- return num;
- }
- }
- }
- usize::MAX
-}
diff --git a/dev_tools/src/bin/refresh-docs.rs b/dev_tools/src/bin/refresh-docs.rs
deleted file mode 100644
index 82ef906..0000000
--- a/dev_tools/src/bin/refresh-docs.rs
+++ /dev/null
@@ -1,178 +0,0 @@
-use std::path::Path;
-
-use just_fmt::snake_case;
-use just_template::{Template, tmpl};
-use tools::println_cargo_style;
-
-const EXAMPLE_ROOT: &str = "./examples/";
-const OUTPUT_PATH: &str = "./mingling/src/example_docs.rs";
-
-const TEMPLATE_CONTENT: &str = include_str!("../../../mingling/src/example_docs.rs.tmpl");
-
-fn main() {
- gen_example_doc_module();
-}
-
-fn gen_example_doc_module() {
- let mut template = Template::from(TEMPLATE_CONTENT);
- let repo_root = find_git_repo().unwrap();
- let example_root = repo_root.join(EXAMPLE_ROOT);
- let mut examples = Vec::new();
- if let Ok(entries) = std::fs::read_dir(&example_root) {
- for entry in entries.flatten() {
- if let Ok(file_type) = entry.file_type()
- && file_type.is_dir()
- {
- let example_name = entry.file_name().to_string_lossy().to_string();
- // Ignore directories that don't start with "example-"
- if !example_name.starts_with("example-") {
- continue;
- }
- let example_content = ExampleContent::read(&example_name);
- examples.push(example_content);
- }
- }
- }
-
- examples.sort();
-
- for example in examples {
- tmpl!(template += {
- examples {
- (
- example_header = example.header,
- example_import = example.cargo_toml,
- example_code = example.code,
- example_name = snake_case!(&example.name)
- )
- }
- });
- println_cargo_style!("Refresh: {}", example.name);
- }
-
- let template_str = template.to_string();
- let template_str = template_str
- .lines()
- .map(str::trim_end)
- .collect::<Vec<_>>()
- .join("\n")
- + "\n";
- std::fs::write(repo_root.join(OUTPUT_PATH), template_str).unwrap();
-}
-
-struct ExampleContent {
- name: String,
- header: String,
- code: String,
- cargo_toml: String,
-}
-
-impl PartialOrd for ExampleContent {
- fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
- Some(self.cmp(other))
- }
-}
-
-impl Ord for ExampleContent {
- fn cmp(&self, other: &Self) -> std::cmp::Ordering {
- self.name.cmp(&other.name)
- }
-}
-
-impl PartialEq for ExampleContent {
- fn eq(&self, other: &Self) -> bool {
- self.name == other.name
- }
-}
-
-impl Eq for ExampleContent {}
-
-impl ExampleContent {
- pub fn read(name: &str) -> Self {
- let repo = find_git_repo().unwrap();
- let cargo_toml = Self::read_cargo_toml(&repo, name);
- let (header, code) = Self::read_header_and_code(&repo, name);
-
- let cargo_toml = cargo_toml
- .lines()
- .map(|line| format!("/// {line}"))
- .collect::<Vec<_>>()
- .join("\n");
-
- let header = header
- .lines()
- .map(|line| format!("/// {line}"))
- .collect::<Vec<_>>()
- .join("\n");
-
- let code = code
- .lines()
- .map(|line| format!("/// {line}"))
- .collect::<Vec<_>>()
- .join("\n");
-
- ExampleContent {
- name: name.to_string(),
- header,
- code,
- cargo_toml,
- }
- }
-
- fn read_header_and_code(repo: &Path, name: &str) -> (String, String) {
- let file_path = repo
- .join(EXAMPLE_ROOT)
- .join(name)
- .join("src")
- .join("main.rs");
- let content = std::fs::read_to_string(&file_path).unwrap_or_default();
- let mut lines = content.lines();
- let mut header = String::new();
- let mut code = String::new();
-
- // Collect header lines (starting with //!)
- for line in lines.by_ref() {
- if line.trim_start().starts_with("//!") {
- let trimmed = line.trim_start_matches("//!");
- header.push_str(trimmed);
- header.push('\n');
- } else {
- // First non-header line found, start collecting code
- code.push_str(line);
- code.push('\n');
- break;
- }
- }
-
- // Collect remaining code lines
- for line in lines {
- code.push_str(line);
- code.push('\n');
- }
-
- (header.trim().to_string(), code.trim().to_string())
- }
-
- fn read_cargo_toml(repo: &Path, name: &str) -> String {
- let file_path = repo.join(EXAMPLE_ROOT).join(name).join("Cargo.toml");
-
- std::fs::read_to_string(&file_path).unwrap_or_default()
- }
-}
-
-fn find_git_repo() -> Option<std::path::PathBuf> {
- let mut current_dir = std::env::current_dir().ok()?;
-
- loop {
- let git_dir = current_dir.join(".git");
- if git_dir.exists() && git_dir.is_dir() {
- return Some(current_dir);
- }
-
- if !current_dir.pop() {
- break;
- }
- }
-
- None
-}
diff --git a/dev_tools/src/bin/refresh-feature-mod.rs b/dev_tools/src/bin/refresh-feature-mod.rs
deleted file mode 100644
index 2255dbc..0000000
--- a/dev_tools/src/bin/refresh-feature-mod.rs
+++ /dev/null
@@ -1,97 +0,0 @@
-use std::collections::BTreeSet;
-use std::path::Path;
-
-use just_fmt::snake_case;
-use just_template::{tmpl, Template};
-use tools::println_cargo_style;
-
-const CARGO_TOML_PATH: &str = "./mingling/Cargo.toml";
-const OUTPUT_PATH: &str = "./mingling/src/features.rs";
-
-const TEMPLATE_CONTENT: &str = include_str!("../../../mingling/src/features.rs.tmpl");
-
-fn main() {
- gen_feature_module();
-}
-
-fn gen_feature_module() {
- let repo_root = find_git_repo().unwrap();
-
- let cargo_toml_path = repo_root.join(CARGO_TOML_PATH);
- let output_path = repo_root.join(OUTPUT_PATH);
-
- let features = parse_features(&cargo_toml_path);
-
- let mut template = Template::from(TEMPLATE_CONTENT);
-
- for feat_name in &features {
- let feat_const_name = snake_case!(feat_name).to_uppercase();
-
- tmpl!(template += {
- features {
- (
- feat_name = feat_name,
- feat_const_name = feat_const_name
- )
- }
- });
- println_cargo_style!("Refresh: feature `{}`", feat_name);
- }
-
- let template_str = template.to_string();
- let template_str = template_str
- .lines()
- .map(str::trim_end)
- .collect::<Vec<_>>()
- .join("\n")
- + "\n";
- std::fs::write(&output_path, template_str).unwrap();
-
- println_cargo_style!("Written: features module to {}", OUTPUT_PATH);
-}
-
-/// Parse all feature names from the `[features]` section of a Cargo.toml.
-fn parse_features(cargo_toml_path: &Path) -> Vec<String> {
- let content = std::fs::read_to_string(cargo_toml_path)
- .unwrap_or_else(|e| panic!("Failed to read {}: {}", cargo_toml_path.display(), e));
-
- let cargo_toml: toml::Value = content
- .parse()
- .unwrap_or_else(|e| panic!("Failed to parse {}: {}", cargo_toml_path.display(), e));
-
- let features_table = cargo_toml
- .get("features")
- .and_then(|v| v.as_table())
- .unwrap_or_else(|| {
- panic!(
- "No [features] section found in {}",
- cargo_toml_path.display()
- )
- });
-
- let mut feature_names: BTreeSet<String> = BTreeSet::new();
- for key in features_table.keys() {
- feature_names.insert(key.clone());
- }
-
- let mut result: Vec<String> = feature_names.into_iter().collect();
- result.sort();
- result
-}
-
-fn find_git_repo() -> Option<std::path::PathBuf> {
- let mut current_dir = std::env::current_dir().ok()?;
-
- loop {
- let git_dir = current_dir.join(".git");
- if git_dir.exists() && git_dir.is_dir() {
- return Some(current_dir);
- }
-
- if !current_dir.pop() {
- break;
- }
- }
-
- None
-}
diff --git a/dev_tools/src/bin/sync-examples.rs b/dev_tools/src/bin/sync-examples.rs
deleted file mode 100644
index 0923b33..0000000
--- a/dev_tools/src/bin/sync-examples.rs
+++ /dev/null
@@ -1,131 +0,0 @@
-use std::fs;
-use std::path::Path;
-
-use serde::{Deserialize, Serialize};
-use tools::println_cargo_style;
-
-#[derive(Serialize)]
-struct ExampleMeta {
- id: String,
- name: String,
- icon: String,
- category: String,
- desc: String,
- tags: Vec<String>,
- files: Vec<String>,
-}
-
-#[derive(Deserialize)]
-struct PageToml {
- example: PageTomlExample,
-}
-
-#[derive(Deserialize)]
-struct PageTomlExample {
- id: String,
- #[serde(default)]
- name: String,
- #[serde(default = "default_icon")]
- icon: String,
- #[serde(default)]
- category: String,
- #[serde(default)]
- desc: String,
- #[serde(default)]
- tags: Vec<String>,
- #[serde(default = "default_files")]
- files: Vec<String>,
-}
-
-fn default_icon() -> String {
- "📦".to_string()
-}
-
-fn default_files() -> Vec<String> {
- vec!["Cargo.toml".to_string(), "src/main.rs".to_string()]
-}
-
-fn main() {
- #[cfg(windows)]
- let _ = colored::control::set_virtual_terminal(true);
-
- let examples_dir = Path::new("examples");
- let output_dir = Path::new("docs/example-pages");
- fs::create_dir_all(output_dir).expect("failed to create docs/example-pages");
-
- let mut examples: Vec<ExampleMeta> = Vec::new();
-
- let entries = fs::read_dir(examples_dir).expect("failed to read examples/");
- for entry in entries.flatten() {
- let path = entry.path();
- if !path.is_dir() {
- continue;
- }
-
- let dir_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
-
- let id = dir_name.to_string();
- let page_toml_path = path.join("page.toml");
-
- let meta = if page_toml_path.exists() {
- match fs::read_to_string(&page_toml_path)
- .map_err(|e| e.to_string())
- .and_then(|content| toml::from_str::<PageToml>(&content).map_err(|e| e.to_string()))
- {
- Ok(page) => {
- let ex = page.example;
- ExampleMeta {
- id: if ex.id.is_empty() { id.clone() } else { ex.id },
- name: if ex.name.is_empty() {
- id.clone()
- } else {
- ex.name
- },
- icon: ex.icon,
- category: ex.category,
- desc: ex.desc,
- tags: ex.tags,
- files: if ex.files.is_empty() {
- default_files()
- } else {
- ex.files
- },
- }
- }
- Err(e) => {
- eprintln!(
- "Warning: failed to parse {}: {}",
- page_toml_path.display(),
- e
- );
- continue;
- }
- }
- } else {
- continue;
- };
-
- examples.push(meta);
- }
-
- // Sort: basic first, then alphabetical
- examples.sort_by(|a, b| {
- if a.id == "example-basic" {
- return std::cmp::Ordering::Less;
- }
- if b.id == "example-basic" {
- return std::cmp::Ordering::Greater;
- }
- a.id.cmp(&b.id)
- });
-
- let json = serde_json::to_string_pretty(&examples).expect("failed to serialize");
- let output_path = output_dir.join("examples.json");
- fs::write(&output_path, &json).expect("failed to write examples.json");
-
- println_cargo_style!(
- "Sync: {} examples -> {}",
- examples.len(),
- output_path.display()
- );
-}
diff --git a/dev_tools/src/bin/test-all-markdown-code.rs b/dev_tools/src/bin/test-all-markdown-code.rs
deleted file mode 100644
index 280fca7..0000000
--- a/dev_tools/src/bin/test-all-markdown-code.rs
+++ /dev/null
@@ -1,276 +0,0 @@
-use std::collections::HashMap;
-use std::env;
-use std::path::{Path, PathBuf};
-
-use colored::Colorize;
-use indicatif::ProgressBar;
-use tools::verify::{
- build_block, compute_block_hash, generate_cargo_toml, generate_main_rs, generate_build_rs,
- is_block_testable, parse_code_blocks, write_summary_report,
-};
-use tools::{eprintln_cargo_style, println_cargo_style};
-
-/// Config from verified-docs.toml
-#[derive(serde::Deserialize)]
-struct Config {
- verified: VerifiedPaths,
-}
-
-#[derive(serde::Deserialize)]
-struct VerifiedPaths {
- readme: String,
- #[serde(default)]
- documents_en_us: Option<String>,
- #[serde(default)]
- documents_zh_cn: Option<String>,
-}
-
-#[tokio::main]
-async fn main() {
- #[cfg(windows)]
- let _ = colored::control::set_virtual_terminal(true);
-
- let config_path = PathBuf::from("verified-docs.toml");
- if !config_path.exists() {
- eprintln_cargo_style!("verified-docs.toml not found in current directory");
- std::process::exit(1);
- }
-
- let config: Config = {
- let content = std::fs::read_to_string(&config_path).unwrap_or_else(|_e| {
- eprintln_cargo_style!("Failed to read verified-docs.toml");
- std::process::exit(1);
- });
- toml::from_str(&content).unwrap_or_else(|_e| {
- eprintln_cargo_style!("Failed to parse verified-docs.toml");
- std::process::exit(1);
- })
- };
-
- // Parse optional path argument from env args
- let single_file: Option<PathBuf> = {
- let args: Vec<String> = env::args().collect();
- if args.len() > 1 {
- let p = PathBuf::from(&args[1]);
- if p.exists() {
- Some(p)
- } else {
- eprintln_cargo_style!("error: specified file '{}' does not exist", args[1]);
- std::process::exit(1);
- }
- } else {
- None
- }
- };
-
- // Collect all markdown files from config
- let mut files: Vec<(String, PathBuf)> = Vec::new();
-
- // README
- let readme_path = PathBuf::from(&config.verified.readme);
- if readme_path.exists() {
- files.push(("README".to_string(), readme_path));
- }
-
- // English docs
- if let Some(pattern) = &config.verified.documents_en_us {
- let base = pattern.trim_end_matches("/**").trim_end_matches('*');
- let dir = PathBuf::from(base);
- if dir.exists() && dir.is_dir() {
- collect_md_files(&dir, &mut files, "en");
- }
- }
-
- // Chinese docs
- if let Some(pattern) = &config.verified.documents_zh_cn {
- let base = pattern.trim_end_matches("/**").trim_end_matches('*');
- let dir = PathBuf::from(base);
- if dir.exists() && dir.is_dir() {
- collect_md_files(&dir, &mut files, "zh_CN");
- }
- }
-
- // If a single file was specified, filter the list to only that file
- if let Some(ref target) = single_file {
- let target_canon = std::fs::canonicalize(target).unwrap_or_else(|_| target.clone());
- files.retain(|(_, path)| {
- std::fs::canonicalize(path)
- .map(|p| p == target_canon)
- .unwrap_or(false)
- });
- if files.is_empty() {
- eprintln_cargo_style!(
- "error: specified file '{}' is not among the configured documentation files",
- target.display()
- );
- std::process::exit(1);
- }
- }
-
- if files.is_empty() {
- eprintln_cargo_style!("No markdown files found to verify");
- std::process::exit(1);
- }
-
- // Parse all code blocks into a flat list with global indices
- let mut flat_blocks: Vec<(usize, tools::verify::CodeBlock)> = Vec::new();
-
- for (label, path) in &files {
- let content = std::fs::read_to_string(path).unwrap_or_else(|e| {
- eprintln_cargo_style!("Failed to read {}: {}", path.display(), e);
- String::new()
- });
- let source_file = format!("{label}/{}", path.file_name().unwrap().to_string_lossy());
- let blocks = parse_code_blocks(&content, &source_file);
- let testable: Vec<_> = blocks.into_iter().filter(is_block_testable).collect();
- for block in testable {
- let idx = flat_blocks.len() + 1; // 1-based global index
- flat_blocks.push((idx, block));
- }
- }
-
- let total_testable = flat_blocks.len();
-
- if total_testable == 0 {
- println_cargo_style!("No testable code blocks found");
- return;
- }
-
- // Create a shared progress bar
- let bar = ProgressBar::new(total_testable as u64);
- bar.set_style(
- indicatif::ProgressStyle::default_bar()
- .template(&format!(
- "{} [{{bar:28}}] {{pos}}/{{len}}: {{msg}}",
- " Testing".bold().bright_cyan()
- ))
- .unwrap()
- .progress_chars("=> "),
- );
- bar.set_message("blocks");
-
- // Group blocks by dependency hash
- let mut groups: HashMap<String, Vec<(usize, tools::verify::CodeBlock)>> = HashMap::new();
- for (idx, block) in flat_blocks {
- let hash = compute_block_hash(&block);
- groups.entry(hash).or_default().push((idx, block));
- }
-
- let temp_base = PathBuf::from(".temp/doc-test");
-
- // Sort groups by hash for deterministic output order
- let mut group_vec: Vec<(String, Vec<(usize, tools::verify::CodeBlock)>)> =
- groups.into_iter().collect();
- group_vec.sort_by(|a, b| a.0.cmp(&b.0));
-
- // Spawn a blocking task per group — groups run in parallel, blocks within a group are serial
- let mut handles = Vec::new();
- for (hash, blocks) in group_vec {
- let temp_base = temp_base.clone();
- let bar = bar.clone(); // clone shares the same underlying progress
- let handle = tokio::task::spawn_blocking(move || {
- let crate_dir = temp_base.join(&hash);
- let src_dir = crate_dir.join("src");
- let manifest_path = crate_dir.join("Cargo.toml");
-
- // Generate a single Cargo.toml for the whole group (all blocks share same deps)
- let first_block = &blocks[0].1;
- let cargo_toml = generate_cargo_toml(first_block, "test-doc", &manifest_path);
-
- let mut group_results: Vec<(String, usize, bool, String)> = Vec::new();
- for (block_idx, block) in &blocks {
- let block_label =
- format!("Block {block_idx} ({}:{})", block.source_file, block.line);
-
- bar.set_message(block_label.clone());
-
- let main_rs = if block.is_build_time {
- // For build-time blocks, write a stub main.rs and generate build.rs
- generate_build_rs(block)
- } else {
- generate_main_rs(block)
- };
- let (ok, err) = build_block(
- &src_dir,
- &manifest_path,
- &cargo_toml,
- &main_rs,
- block.is_build_time,
- );
- if ok {
- bar.inc(1);
- } else {
- bar.inc(1);
- bar.println(format!(
- " {} {block_label}",
- "failed".bold().bright_red()
- ));
- bar.println(format!(" {block_label} FAILED:\n{err}"));
- }
- group_results.push((block.source_file.clone(), block.line, ok, err));
- }
- group_results
- });
- handles.push(handle);
- }
-
- // Collect results from all groups
- let mut results: Vec<(String, usize, bool, String)> = Vec::new();
- let mut passed = 0usize;
- let mut failed = 0usize;
-
- for handle in handles {
- match handle.await {
- Ok(group_results) => {
- for (file, line, ok, err) in group_results {
- if ok {
- passed += 1;
- } else {
- failed += 1;
- }
- results.push((file, line, ok, err));
- }
- }
- Err(e) => {
- eprintln_cargo_style!("Task panicked: {}", e);
- std::process::exit(1);
- }
- }
- }
-
- bar.finish_and_clear();
-
- let result_msg = format!("Result: {passed}/{total_testable} blocks passed");
- println_cargo_style!(result_msg);
-
- write_summary_report(
- Path::new(".temp/DOCS-TEST-RESULT.md"),
- "Documentation Code Block Test Report",
- &results,
- total_testable,
- passed,
- failed,
- );
-
- if failed > 0 {
- let fail_msg = format!("{failed} block(s) failed to build");
- eprintln_cargo_style!(fail_msg);
- std::process::exit(1);
- }
-
- println_cargo_style!("Done: All verified code blocks build successfully!");
-}
-
-/// Recursively collect all `.md` files under a directory
-fn collect_md_files(dir: &Path, files: &mut Vec<(String, PathBuf)>, lang: &str) {
- if let Ok(entries) = std::fs::read_dir(dir) {
- for entry in entries.flatten() {
- let path = entry.path();
- if path.is_dir() {
- collect_md_files(&path, files, lang);
- } else if path.extension().is_some_and(|ext| ext == "md") {
- files.push((lang.to_string(), path));
- }
- }
- }
-}
diff --git a/dev_tools/src/bin/test-examples.rs b/dev_tools/src/bin/test-examples.rs
deleted file mode 100644
index 539459e..0000000
--- a/dev_tools/src/bin/test-examples.rs
+++ /dev/null
@@ -1,158 +0,0 @@
-use std::collections::HashMap;
-
-use colored::Colorize;
-use indicatif::ProgressBar;
-use serde::Deserialize;
-use tools::{eprintln_cargo_style, println_cargo_style};
-
-#[derive(Deserialize)]
-struct TestConfig {
- test: HashMap<String, Vec<TestCase>>,
-}
-
-#[derive(Deserialize)]
-struct TestCase {
- command: String,
- expect: Expect,
-}
-
-#[derive(Deserialize)]
-struct Expect {
- #[serde(rename = "exit-code")]
- exit_code: i32,
- result: String,
-}
-
-fn main() {
- #[cfg(windows)]
- let _ = colored::control::set_virtual_terminal(true);
-
- let config = load_config();
-
- // Count total test cases upfront
- let total: usize = config.test.values().map(|cases| cases.len()).sum();
- let bar = ProgressBar::new(total as u64);
- bar.set_style(
- indicatif::ProgressStyle::default_bar()
- .template(&format!(
- "{} [{{bar:28}}] {{pos}}/{{len}}: {{msg}}",
- " Testing".bold().bright_cyan()
- ))
- .unwrap()
- .progress_chars("=> "),
- );
- bar.set_message("examples");
-
- let passed = run_all_tests(&config, &bar);
-
- bar.finish_and_clear();
-
- println_cargo_style!("Result: {}/{} tests passed", passed, total);
-
- if passed != total {
- eprintln_cargo_style!("{} test(s) failed", total - passed);
- std::process::exit(1);
- }
-}
-
-/// Parse test config from TOML file
-fn load_config() -> TestConfig {
- let content = std::fs::read_to_string("examples/test-examples.toml").unwrap_or_else(|e| {
- eprintln_cargo_style!("Failed to read TOML config file: {}", e);
- std::process::exit(1);
- });
-
- toml::from_str(&content).unwrap_or_else(|e| {
- eprintln_cargo_style!("Failed to parse TOML config: {}", e);
- std::process::exit(1);
- })
-}
-
-/// Run all example test groups, return number passed
-fn run_all_tests(config: &TestConfig, bar: &ProgressBar) -> usize {
- let mut passed = 0;
-
- for (example_name, test_cases) in &config.test {
- bar.set_message(example_name.clone());
-
- if !build_example(example_name) {
- bar.inc(test_cases.len() as u64);
- continue;
- }
-
- for test_case in test_cases {
- if run_single_test(example_name, test_case, bar) {
- passed += 1;
- }
- bar.inc(1);
- }
- }
-
- passed
-}
-
-/// Build the example binary, return true on success
-fn build_example(example_name: &str) -> bool {
- let manifest = format!("examples/{example_name}/Cargo.toml");
- tools::run_cmd_capture(format!(
- "cargo build --manifest-path {manifest} --color always",
- ))
- .is_ok()
-}
-
-/// Run a single test case, return true on pass
-fn run_single_test(example_name: &str, test_case: &TestCase, bar: &ProgressBar) -> bool {
- let binary_path = format!(".temp/target/debug/{}", get_binary_name(example_name));
- let args: Vec<&str> = test_case.command.split_whitespace().collect();
-
- let output = match std::process::Command::new(&binary_path)
- .args(&args)
- .output()
- {
- Ok(o) => o,
- Err(e) => {
- bar.println(format!("'{}' - failed to run: {}", test_case.command, e));
- return false;
- }
- };
-
- let actual_exit_code = output.status.code().unwrap_or(-1);
- let actual_stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
- let actual_stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
-
- let exit_ok = actual_exit_code == test_case.expect.exit_code;
- let result_ok = actual_stdout == test_case.expect.result
- || actual_stdout.contains(&test_case.expect.result);
-
- if exit_ok && result_ok {
- true
- } else {
- bar.println(format!("failed: '{}'", test_case.command));
- if !exit_ok {
- bar.println(format!(
- " Expected exit code: {}, actual: {}",
- test_case.expect.exit_code, actual_exit_code
- ));
- }
- if !result_ok {
- bar.println(format!(" Expected output: {:?}", test_case.expect.result));
- bar.println(format!(" Actual stdout: {:?}", actual_stdout));
- if !actual_stderr.is_empty() {
- bar.println(format!(" Actual stderr: {:?}", actual_stderr));
- }
- }
- false
- }
-}
-
-/// Resolve binary filename for the given example
-///
-/// The binary name matches the package name. On Windows, the `.exe` suffix is required.
-fn get_binary_name(example_name: &str) -> String {
- let base = example_name;
- if cfg!(target_os = "windows") {
- format!("{base}.exe")
- } else {
- base.to_string()
- }
-}
diff --git a/dev_tools/src/bin/update-version.rs b/dev_tools/src/bin/update-version.rs
deleted file mode 100644
index 475e54b..0000000
--- a/dev_tools/src/bin/update-version.rs
+++ /dev/null
@@ -1,98 +0,0 @@
-use std::io::Write as _;
-use std::path::Path;
-
-use serde::Deserialize;
-use tools::println_cargo_style;
-
-#[derive(Deserialize)]
-struct VersionFile {
- file: String,
- pattern: String,
-}
-
-#[derive(Deserialize)]
-struct Config {
- #[serde(rename = "file")]
- files: Vec<VersionFile>,
-}
-
-fn main() {
- let args: Vec<String> = std::env::args().collect();
-
- // Get new version
- let new_ver = if args.len() > 1 {
- args[1].clone()
- } else {
- print!("Update version to: ");
- std::io::stdout().flush().unwrap();
- let mut input = String::new();
- std::io::stdin().read_line(&mut input).unwrap();
- input.trim().to_string()
- };
-
- if new_ver.is_empty() {
- eprintln!("Error: Version cannot be empty.");
- std::process::exit(1);
- }
-
- // Read current version from root Cargo.toml's workspace.package.version
- let root_cargo_path = "Cargo.toml";
- let root_cargo_content =
- std::fs::read_to_string(root_cargo_path).expect("Failed to read Cargo.toml");
- let cargo_value: toml::Value = root_cargo_content.parse().expect("Failed to parse Cargo.toml");
-
- let current_ver = cargo_value["workspace"]["package"]["version"]
- .as_str()
- .expect("workspace.package.version not found in Cargo.toml")
- .to_string();
-
- if new_ver == current_ver {
- println!("Version is already {}. Nothing to do.", current_ver);
- return;
- }
-
- println_cargo_style!("Version: {} -> {}", current_ver, new_ver);
-
- // Read version-files.toml
- let config_path = Path::new("dev_tools").join("version-files.toml");
- let config_str =
- std::fs::read_to_string(&config_path).expect("Failed to read dev_tools/version-files.toml");
- let config: Config = toml::from_str(&config_str)
- .expect("Failed to parse dev_tools/version-files.toml");
-
- let mut updated_count = 0;
- let mut skipped_count = 0;
-
- for vf in &config.files {
- let file_path = &vf.file;
- let old_pattern = vf.pattern.replace("{VER}", &current_ver);
- let new_pattern = vf.pattern.replace("{VER}", &new_ver);
-
- let content = match std::fs::read_to_string(file_path) {
- Ok(c) => c,
- Err(e) => {
- eprintln!("Warning: Could not read {}: {}", file_path, e);
- skipped_count += 1;
- continue;
- }
- };
-
- let new_content = content.replace(&old_pattern, &new_pattern);
-
- if new_content == content {
- eprintln!(
- "Warning: Pattern '{}' not found in {}",
- old_pattern, file_path
- );
- skipped_count += 1;
- continue;
- }
-
- std::fs::write(file_path, &new_content)
- .unwrap_or_else(|e| panic!("Failed to write {}: {}", file_path, e));
- println_cargo_style!("Updated: {}", file_path);
- updated_count += 1;
- }
-
- println_cargo_style!("Done: {} file(s) updated, {} file(s) skipped", updated_count, skipped_count);
-}
diff --git a/dev_tools/src/lib.rs b/dev_tools/src/lib.rs
deleted file mode 100644
index d38a156..0000000
--- a/dev_tools/src/lib.rs
+++ /dev/null
@@ -1,331 +0,0 @@
-pub mod verify;
-
-use colored::Colorize;
-
-#[macro_export]
-macro_rules! run_cmd {
- ($fmt:literal, $($arg:tt)*) => {
- $crate::run_cmd(format!($fmt, $($arg)*))
- };
- ($cmd:expr) => {
- $crate::run_cmd($cmd)
- };
-}
-
-/// Run a shell command and capture its combined stdout+stderr output.
-/// Returns `Ok(output)` on success, `Err((exit_code, stderr))` on failure.
-#[macro_export]
-macro_rules! run_cmd_and_capture_stderr {
- ($fmt:literal, $($arg:tt)*) => {
- $crate::run_cmd_capture(format!($fmt, $($arg)*))
- };
- ($cmd:expr) => {
- $crate::run_cmd_capture($cmd)
- };
-}
-
-#[macro_export]
-macro_rules! println_cargo_style {
- ($fmt:literal, $($arg:tt)*) => {
- $crate::println_cargo_style(format!($fmt, $($arg)*))
- };
- ($cmd:expr) => {
- $crate::println_cargo_style($cmd)
- };
-}
-
-#[macro_export]
-macro_rules! eprintln_cargo_style {
- ($fmt:literal, $($arg:tt)*) => {
- $crate::eprintln_cargo_style(format!($fmt, $($arg)*))
- };
- ($cmd:expr) => {
- $crate::eprintln_cargo_style($cmd)
- };
-}
-
-#[macro_export]
-macro_rules! wprintln_cargo_style {
- ($fmt:literal, $($arg:tt)*) => {
- $crate::wprintln_cargo_style(format!($fmt, $($arg)*))
- };
- ($cmd:expr) => {
- $crate::wprintln_cargo_style($cmd)
- };
-}
-
-/// Print a message in cargo style format, with bold green prefix.
-///
-/// # Panics
-///
-/// Panics if the prefix (text before the first `:`) exceeds 12 characters.
-pub fn println_cargo_style(str: impl Into<String>) {
- let s = str.into();
- let (prefix, content) = if let Some(pos) = s.find(':') {
- (
- s[..pos].trim().to_string(),
- s[pos + 1..].trim_start().to_string(),
- )
- } else {
- (String::new(), s.trim().to_string())
- };
-
- assert!(
- prefix.len() <= 12,
- "prefix length exceeds 12: '{}' has length {}",
- prefix,
- prefix.len()
- );
-
- let padding = " ".repeat(12 - prefix.len());
-
- println!(
- "{}{} {}",
- padding,
- prefix.bold().bright_green(),
- content.trim()
- );
-}
-
-pub fn eprintln_cargo_style(str: impl Into<String>) {
- println!("{}: {}", "error".bold().bright_red(), str.into());
-}
-
-/// Print a message in cargo style format, with bold yellow prefix (warning style).
-///
-/// # Panics
-///
-/// Panics if the prefix (text before the first `:`) exceeds 12 characters.
-pub fn wprintln_cargo_style(str: impl Into<String>) {
- let s = str.into();
- let (prefix, content) = if let Some(pos) = s.find(':') {
- (
- s[..pos].trim().to_string(),
- s[pos + 1..].trim_start().to_string(),
- )
- } else {
- (String::new(), s.trim().to_string())
- };
-
- assert!(
- prefix.len() <= 12,
- "prefix length exceeds 12: '{}' has length {}",
- prefix,
- prefix.len()
- );
-
- let padding = " ".repeat(12 - prefix.len());
-
- println!(
- "{}{} {}",
- padding,
- prefix.bold().bright_yellow(),
- content.trim()
- );
-}
-
-/// Run a shell command and return its exit status.
-///
-/// # Panics
-///
-/// Panics if the shell command cannot be spawned (e.g. the shell binary is not found).
-///
-/// # Errors
-///
-/// Returns `Err` with the exit code if the command finishes with a non-zero exit code.
-pub fn run_cmd(cmd: impl Into<String>) -> Result<(), i32> {
- let shell = if cfg!(target_os = "windows") {
- "powershell"
- } else {
- "sh"
- };
- let status = std::process::Command::new(shell)
- .arg("-c")
- .arg(cmd.into())
- .current_dir(std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from(".")))
- .status()
- .expect("failed to execute command");
-
- let exit_code = status.code().unwrap_or(1);
- if exit_code == 0 {
- Ok(())
- } else {
- Err(exit_code)
- }
-}
-
-/// Run a shell command and capture its combined stdout+stderr output.
-///
-/// On success returns `Ok(combined_output)`. On failure returns `Err((exit_code, stderr))`.
-/// Stderr falls back to stdout if stderr is empty.
-pub fn run_cmd_capture(cmd: impl Into<String>) -> Result<String, (i32, String)> {
- let shell = if cfg!(target_os = "windows") {
- "powershell"
- } else {
- "sh"
- };
- let output = std::process::Command::new(shell)
- .arg("-c")
- .arg(cmd.into())
- .current_dir(std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from(".")))
- .output()
- .expect("failed to execute command");
-
- let exit_code = output.status.code().unwrap_or(1);
- let stderr = String::from_utf8_lossy(&output.stderr).to_string();
- let stdout = String::from_utf8_lossy(&output.stdout).to_string();
- let combined = if stderr.is_empty() { stdout } else { stderr };
-
- if exit_code == 0 {
- Ok(combined)
- } else {
- Err((exit_code, combined))
- }
-}
-
-/// Extract a crate-style name from a `Cargo.toml` path.
-///
-/// Examples:
-/// - `mingling_core/Cargo.toml` → `mingling_core`
-/// - `.` → `(root)`
-pub fn crate_name_from(path: &std::path::Path) -> String {
- path.parent()
- .and_then(|p| p.file_name())
- .and_then(|n| n.to_str())
- .unwrap_or("(root)")
- .to_string()
-}
-
-/// Run a list of `(label_for_errors, crate_name_for_bar, shell_command)` tuples
-/// in parallel with a progress bar.
-///
-/// - Success: silent, the bar tracks progress:
-/// ` Building [============================] 32/32: mingling_core`
-/// - Failure: `pb.println()` prints the error immediately above the bar.
-pub fn run_parallel(phase: &str, tasks: Vec<(String, String, String)>) -> Result<(), i32> {
- let n = tasks.len();
- if n == 0 {
- return Ok(());
- }
-
- // Cargo-style prefix: right-aligned to 12 chars, bold bright cyan
- let padding = " ".repeat(12 - phase.len());
- let styled_prefix = format!("{}{}", padding, phase.bold().bright_cyan());
-
- let pb = indicatif::ProgressBar::new(n as u64);
- pb.set_style(
- indicatif::ProgressStyle::default_bar()
- .template(&format!(
- "{} [{{bar:28}}] {{pos}}/{{len}}: {{msg}}",
- styled_prefix
- ))
- .unwrap()
- .progress_chars("=> "),
- );
- pb.set_position(0);
-
- // Pre-extract labels for error messages
- let labels: Vec<String> = tasks.iter().map(|(l, _, _)| l.clone()).collect();
-
- let (tx, rx) = std::sync::mpsc::channel::<(usize, String, Result<String, (i32, String)>)>();
-
- for (i, (_label, crate_name, cmd)) in tasks.into_iter().enumerate() {
- let tx = tx.clone();
- std::thread::spawn(move || {
- let result = run_cmd_capture(&cmd);
- let _ = tx.send((i, crate_name, result));
- });
- }
- drop(tx);
-
- let mut first_exit_code = 0;
-
- while let Ok((i, crate_name, result)) = rx.recv() {
- pb.inc(1);
- pb.set_message(crate_name);
-
- if let Err((code, output)) = result {
- if first_exit_code == 0 {
- first_exit_code = code;
- }
- pb.println(format!(
- "{}: {} failed (exit code {})",
- "error".bright_red().bold(),
- labels[i],
- code,
- ));
- if !output.is_empty() {
- for line in output.lines() {
- pb.println(format!(" {line}"));
- }
- }
- }
- }
-
- pb.finish_and_clear();
-
- if first_exit_code != 0 {
- Err(first_exit_code)
- } else {
- Ok(())
- }
-}
-
-/// Run a single shell command with a progress bar, capturing its output.
-///
-/// - Success: bar clears silently.
-/// - Failure: error is printed above the bar, then the bar clears.
-pub fn run_cmd_with_progress(phase: &str, label: &str, cmd: String) -> Result<(), i32> {
- let padding = " ".repeat(12 - phase.len());
- let styled_prefix = format!("{}{}", padding, phase.bold().bright_cyan());
-
- let pb = indicatif::ProgressBar::new(1);
- pb.set_style(
- indicatif::ProgressStyle::default_bar()
- .template(&format!(
- "{} [{{bar:28}}] {{pos}}/{{len}}: {{msg}}",
- styled_prefix
- ))
- .unwrap()
- .progress_chars("=> "),
- );
- pb.set_message(label.to_owned());
-
- let result = run_cmd_capture(&cmd);
- pb.inc(1);
- pb.finish_and_clear();
-
- match result {
- Ok(_) => Ok(()),
- Err((code, output)) => {
- eprintln_cargo_style(format!("{} failed (exit code {})", label, code));
- if !output.is_empty() {
- println!("{}", output.trim_end());
- }
- Err(code)
- }
- }
-}
-
-#[must_use]
-pub fn cargo_tomls() -> Vec<std::path::PathBuf> {
- let mut cargo_tomls = Vec::new();
- let mut dirs = vec![std::path::PathBuf::from(".")];
- while let Some(dir) = dirs.pop() {
- if let Ok(entries) = std::fs::read_dir(&dir) {
- for entry in entries.flatten() {
- let path = entry.path();
- if path.is_dir() {
- // Skip the dev_tools directory
- if path.file_name().and_then(|n| n.to_str()) == Some("dev_tools") {
- continue;
- }
- dirs.push(path);
- } else if path.file_name().and_then(|n| n.to_str()) == Some("Cargo.toml") {
- cargo_tomls.push(path);
- }
- }
- }
- }
- cargo_tomls
-}
diff --git a/dev_tools/src/verify.rs b/dev_tools/src/verify.rs
deleted file mode 100644
index 2ddff0c..0000000
--- a/dev_tools/src/verify.rs
+++ /dev/null
@@ -1,511 +0,0 @@
-use std::path::Path;
-
-use crate::println_cargo_style;
-
-/// Represents a parsed code block from a markdown file
-#[derive(Debug, Clone)]
-pub struct CodeBlock {
- /// Source file path (for reporting)
- pub source_file: String,
- /// The line number in source file where this block starts
- pub line: usize,
- /// The raw Rust source code
- pub code: String,
- /// Feature flags extracted from `// Features: [...]` comment
- pub features: Vec<String>,
- /// Whether the block had an explicit `// Features:` header
- pub has_features_header: bool,
- /// Whether the block has `// NOT VERIFIED` to opt out of testing
- pub not_verified: bool,
- /// External dependencies extracted from `// Dependencies:` comments
- pub external_deps: Vec<(String, String)>,
- /// Whether this block has a `fn main` entry point
- pub has_main: bool,
- /// Whether this block has `gen_program!()` call
- pub has_gen_program: bool,
- /// Whether this block has `// BUILD TIME` annotation (write to build.rs, not main.rs)
- pub is_build_time: bool,
-}
-
-/// Parse all ```rust code blocks from markdown content
-pub fn parse_code_blocks(content: &str, source_file: &str) -> Vec<CodeBlock> {
- let mut blocks = Vec::new();
- let lines: Vec<&str> = content.lines().collect();
- let mut i = 0;
-
- while i < lines.len() {
- if lines[i].trim() == "```rust" {
- if let Some(block) = parse_single_block(&lines, i, source_file) {
- blocks.push(block);
- }
- i += 1;
- while i < lines.len() && lines[i].trim() != "```" {
- i += 1;
- }
- }
- i += 1;
- }
-
- blocks
-}
-
-/// Parse a single code block starting at the ```rust line
-fn parse_single_block(lines: &[&str], start: usize, source_file: &str) -> Option<CodeBlock> {
- let line_num = start + 1; // 1-based line number
-
- let mut code_lines: Vec<String> = Vec::new();
- let mut features: Vec<String> = Vec::new();
- let mut has_features_header = false;
- let mut not_verified = false;
- let mut external_deps: Vec<(String, String)> = Vec::new();
- let mut has_main = false;
- let mut has_gen_program = false;
- let mut is_build_time = false;
-
- let mut idx = start + 1;
- let mut in_header = true;
-
- while idx < lines.len() {
- let raw_line = lines[idx];
- let trimmed = raw_line.trim();
-
- if trimmed == "```" {
- break;
- }
-
- // @@@ lines: strip the prefix and treat as regular Rust code
- // These lines are hidden in the rendered docs (filtered by a docsify plugin)
- // but must still compile.
- if trimmed.starts_with("@@@") {
- in_header = false;
- // Strip @@@ and optionally one following space
- let code = trimmed[3..].trim_start();
- if code.contains("fn main") {
- has_main = true;
- }
- if code.contains("gen_program!") {
- has_gen_program = true;
- }
- code_lines.push(code.to_string());
- idx += 1;
- continue;
- }
-
- // Parse header comments
- // Check for NOT VERIFIED marker
- if in_header && trimmed == "// NOT VERIFIED" {
- not_verified = true;
- idx += 1;
- continue;
- }
-
- if in_header && trimmed == "// BUILD TIME" {
- is_build_time = true;
- idx += 1;
- continue;
- }
-
- if in_header && trimmed.starts_with("// ") {
- if trimmed.starts_with("// Features:") {
- has_features_header = true;
- let feat_str = trimmed.trim_start_matches("// Features:").trim();
- if feat_str.starts_with('[') && feat_str.ends_with(']') {
- let inner = &feat_str[1..feat_str.len() - 1];
- if !inner.is_empty() {
- features = inner
- .split(',')
- .map(|s| s.trim().trim_matches('"').to_string())
- .filter(|s| !s.is_empty())
- .collect();
- }
- }
- idx += 1;
- continue;
- }
- if trimmed == "// Dependencies:" {
- idx += 1;
- // Collect subsequent `// crate = "version"` lines
- while idx < lines.len() {
- let next = lines[idx].trim();
- if next == "```" {
- break;
- }
- if next.starts_with("// ") {
- let dep_line = next.trim_start_matches("// ").trim();
- if let Some((name, ver)) = dep_line.split_once(" = ") {
- external_deps.push((
- name.trim().to_string(),
- ver.trim().trim_matches('"').to_string(),
- ));
- }
- idx += 1;
- } else {
- break;
- }
- }
- continue;
- }
- }
-
- in_header = false;
-
- if raw_line.contains("fn main") {
- has_main = true;
- }
- if raw_line.contains("gen_program!") {
- has_gen_program = true;
- }
-
- code_lines.push(raw_line.to_string());
- idx += 1;
- }
-
- if code_lines.is_empty() {
- return None;
- }
-
- Some(CodeBlock {
- source_file: source_file.to_string(),
- line: line_num,
- code: code_lines.join("\n"),
- features,
- has_features_header,
- not_verified,
- external_deps,
- has_main,
- has_gen_program,
- is_build_time,
- })
-}
-
-/// Generate a Cargo.toml for a block
-///
-/// `manifest_path` is the full path to the Cargo.toml file being written; it is used to
-/// compute the relative path to the `mingling` crate.
-pub fn generate_cargo_toml(block: &CodeBlock, package_name: &str, manifest_path: &Path) -> String {
- let features_str = if !block.features.is_empty() {
- let feats: Vec<String> = block.features.iter().map(|f| format!("\"{f}\"")).collect();
- format!("features = [{}]", feats.join(", "))
- } else {
- String::new()
- };
-
- let mut extra_deps = String::new();
- for (name, version) in &block.external_deps {
- if !version.starts_with('{') {
- if name == "serde" || name == "clap" {
- extra_deps.push_str(&format!(
- "{name} = {{ version = \"{version}\", features = [\"derive\"] }}\n"
- ));
- } else {
- extra_deps.push_str(&format!("{name} = \"{version}\"\n"));
- }
- } else {
- extra_deps.push_str(&format!("{name} = {version}\n"));
- }
- }
-
- let mingling_path = find_mingling_relative_path(manifest_path);
-
- let deps_section = if features_str.is_empty() {
- format!("[dependencies]\nmingling = {{ path = \"{mingling_path}\" }}\n{extra_deps}",)
- } else {
- format!(
- "[dependencies]\nmingling = {{ path = \"{mingling_path}\", {features_str} }}\n{extra_deps}",
- )
- };
-
- // Build-time blocks: add `builds` by default, merge with explicit features
- let build_deps_section = if block.is_build_time {
- let mut all_feats = vec!["builds".to_string()];
- for f in &block.features {
- if f != "builds" {
- all_feats.push(f.clone());
- }
- }
- let feats_str: Vec<String> = all_feats.iter().map(|f| format!("\"{f}\"")).collect();
- let build_feats = format!("features = [{}]", feats_str.join(", "));
- format!(
- "\n[build-dependencies]\nmingling = {{ path = \"{mingling_path}\", {build_feats} }}\n"
- )
- } else {
- String::new()
- };
-
- format!(
- r#"[package]
- name = "{package_name}"
- version = "0.0.0"
- edition = "2024"
-
-{deps_section}{build_deps_section}
-[workspace]
-"#
- )
-}
-
-/// Compute the relative path from a Cargo.toml's parent directory to the `mingling` crate.
-///
-/// The process current directory is expected to be the project root (where `mingling/` lives).
-/// Returns a forward-slash path safe for embedding in TOML strings.
-fn find_mingling_relative_path(manifest_path: &Path) -> String {
- let manifest_dir = manifest_path
- .parent()
- .expect("manifest_path has no parent directory");
- let cwd = std::env::current_dir().expect("failed to get current directory");
-
- // Strip cwd prefix to get the relative components of the manifest directory
- let relative_to_root = manifest_dir.strip_prefix(&cwd).unwrap_or(manifest_dir);
- let depth = relative_to_root.components().count();
-
- let mut result = String::new();
- for _ in 0..depth {
- result.push_str("../");
- }
- result.push_str("mingling");
- result
-}
-
-/// Generate main.rs for a block
-///
-/// Automatically prepends `use mingling::prelude::*;` if the block doesn't already have it.
-pub fn generate_main_rs(block: &CodeBlock) -> String {
- let mut output = String::from("#![allow(dead_code)]\n#![allow(unused)]\n");
-
- if !block.code.contains("use mingling::prelude::*;") {
- output.push_str("#[allow(unused_imports)]\nuse mingling::prelude::*;\n\n");
- }
-
- output.push_str(&block.code);
- output.push('\n');
-
- if !block.has_main {
- output.push_str("\nfn main() {}\n");
- }
-
- if !block.has_gen_program {
- output.push_str("\nmingling::macros::gen_program!();\n");
- }
-
- output
-}
-
-/// Generate build.rs for a build-time block
-///
-/// Default: `use mingling::builds::*;`, code wrapped in `fn main() { }`.
-pub fn generate_build_rs(block: &CodeBlock) -> String {
- let mut output = String::from("#![allow(dead_code)]\n#![allow(unused)]\n");
-
- if !block.code.contains("use mingling::build::*;") {
- output.push_str("#[allow(unused_imports)]\nuse mingling::build::*;\n\n");
- }
-
- if block.has_main {
- output.push_str(&block.code);
- } else {
- output.push_str("fn main() {\n");
- for line in block.code.lines() {
- output.push_str(" ");
- output.push_str(line);
- output.push('\n');
- }
- output.push_str("}\n");
- }
-
- output
-}
-
-/// Build a single code block as a Cargo project.
-///
-/// When `is_build_time` is true, `src_content` is written to `build.rs` instead of `src/main.rs`,
-/// and a minimal `src/main.rs` stub (`fn main() {}`) is created.
-pub fn build_block(
- src_dir: &Path,
- manifest_path: &Path,
- cargo_toml: &str,
- src_content: &str,
- is_build_time: bool,
-) -> (bool, String) {
- if let Err(e) = std::fs::create_dir_all(src_dir) {
- return (false, format!("mkdir: {e}"));
- }
-
- // Write Cargo.toml
- if let Err(e) = std::fs::write(manifest_path, cargo_toml) {
- return (false, format!("write Cargo.toml: {e}"));
- }
-
- if is_build_time {
- // Write build.rs and a stub main.rs
- let crate_dir = manifest_path.parent().unwrap();
- if let Err(e) = std::fs::write(crate_dir.join("build.rs"), src_content) {
- return (false, format!("write build.rs: {e}"));
- }
- if let Err(e) = std::fs::write(src_dir.join("main.rs"), "fn main() {}\n") {
- return (false, format!("write main.rs: {e}"));
- }
- } else {
- // Normal: write src/main.rs
- if let Err(e) = std::fs::write(src_dir.join("main.rs"), src_content) {
- return (false, format!("write main.rs: {e}"));
- }
- }
-
- // Check code — inherit stderr so cargo output is real-time and colored
- let shell = if cfg!(target_os = "windows") {
- "powershell"
- } else {
- "sh"
- };
- let cmd = format!(
- "cargo check --color=always --manifest-path {}",
- manifest_path.to_string_lossy()
- );
-
- let mut child = match std::process::Command::new(shell)
- .arg("-c")
- .arg(&cmd)
- .stdout(std::process::Stdio::inherit())
- .stderr(std::process::Stdio::piped())
- .current_dir(std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from(".")))
- .spawn()
- {
- Ok(c) => c,
- Err(e) => return (false, format!("spawn: {e}")),
- };
-
- // Read stderr (buffered, not forwarded — groups print their own output contiguously)
- use std::io::BufRead;
- let stderr_handle = child.stderr.take().unwrap();
- let reader = std::io::BufReader::new(stderr_handle);
- let mut captured = String::new();
- for line in reader.lines() {
- match line {
- Ok(l) => {
- captured.push_str(&l);
- captured.push('\n');
- }
- Err(_) => break,
- }
- }
-
- let status = child.wait().unwrap_or_else(|_| std::process::exit(1));
- let exit_code = status.code().unwrap_or(1);
-
- if exit_code == 0 {
- (true, String::new())
- } else {
- let mut last_lines: Vec<&str> = captured.lines().rev().take(20).collect();
- last_lines.reverse();
- let detail = last_lines.join("\n");
- (false, format!("exit code {exit_code}\n{detail}"))
- }
-}
-
-/// Compute a stable hash for a code block based on its dependency configuration.
-///
-/// Blocks with the same features and external dependencies produce the same hash,
-/// allowing them to share a compiled crate and avoid redundant recompilation.
-///
-/// Hash input (all sorted for stability):
-/// - Sorted mingling feature strings
-/// - Sorted external dependency names
-/// - Sorted external dependency versions
-/// - Sorted external deps as `name=version` pairs
-pub fn compute_block_hash(block: &CodeBlock) -> String {
- let mut features: Vec<&str> = block.features.iter().map(|s| s.as_str()).collect();
- features.sort();
- let features_str = features.join(",");
-
- let mut dep_names: Vec<&str> = block
- .external_deps
- .iter()
- .map(|(n, _)| n.as_str())
- .collect();
- dep_names.sort();
- let dep_names_str = dep_names.join(",");
-
- let mut dep_versions: Vec<&str> = block
- .external_deps
- .iter()
- .map(|(_, v)| v.as_str())
- .collect();
- dep_versions.sort();
- let dep_versions_str = dep_versions.join(",");
-
- let mut deps: Vec<String> = block
- .external_deps
- .iter()
- .map(|(n, v)| format!("{n}={v}"))
- .collect();
- deps.sort();
- let deps_str = deps.join(",");
-
- let canonical = format!("{features_str}\n{dep_names_str}\n{dep_versions_str}\n{deps_str}");
-
- // FNV-1a 64-bit hash — stable across runs (no random seed)
- let mut hash: u64 = 0xcbf29ce484222325;
- for &byte in canonical.as_bytes() {
- hash ^= byte as u64;
- hash = hash.wrapping_mul(0x100000001b3);
- }
-
- format!("{:016x}", hash)
-}
-
-/// Determine if a block should be treated as a test candidate.
-/// A block is NOT testable only if it has `// NOT VERIFIED` marker.
-pub fn is_block_testable(block: &CodeBlock) -> bool {
- !block.not_verified
-}
-
-/// Write a summary report
-pub fn write_summary_report(
- path: &Path,
- title: &str,
- results: &[(String, usize, bool, String)],
- total: usize,
- passed: usize,
- failed: usize,
-) {
- let mut content = String::new();
- content.push_str(&format!("# {title}\n\n"));
- content.push_str(&format!(
- "Tested **{total}** code blocks: **{passed}** passed, **{failed}** failed.\n\n"
- ));
- content.push_str("## Results\n\n");
- content.push_str("| Block | File | Line | Status |\n");
- content.push_str("|-------|------|------|--------|\n");
-
- for (i, (file, line, ok, _)) in results.iter().enumerate() {
- let status = if *ok { "PASS" } else { "FAIL" };
- let short_file = file.rsplit('/').next().unwrap_or(file);
- content.push_str(&format!(
- "| {} | {} | {} | {status} |\n",
- i + 1,
- short_file,
- line
- ));
- }
-
- let has_failures = results.iter().any(|(_, _, ok, _)| !ok);
- if has_failures {
- content.push_str("\n## Failed Blocks\n\n");
- for (i, (file, line, ok, err)) in results.iter().enumerate() {
- if !ok {
- content.push_str(&format!(
- "### Block {} (`{}`, line {})\n\n```\n{err}\n```\n\n",
- i + 1,
- file,
- line
- ));
- }
- }
- }
-
- std::fs::write(path, &content).unwrap_or_else(|e| {
- eprintln!("Warning: failed to write {path:?}: {e}");
- });
-
- println_cargo_style!("Report: written to {}", path.display());
-}
diff --git a/dev_tools/version-files.toml b/dev_tools/version-files.toml
deleted file mode 100644
index ca104d2..0000000
--- a/dev_tools/version-files.toml
+++ /dev/null
@@ -1,19 +0,0 @@
-[[file]]
-file = "./Cargo.toml"
-pattern = "version = \"{VER}\""
-
-[[file]]
-file = "./README.md"
-pattern = "version = \"{VER}\""
-
-[[file]]
-file = "./docs/_zh_CN/pages/1-getting-started.md"
-pattern = "version = \"{VER}\""
-
-[[file]]
-file = "./docs/pages/1-getting-started.md"
-pattern = "version = \"{VER}\""
-
-[[file]]
-file = "./docs/res/guide.txt"
-pattern = "mingling = \"{VER}\""