aboutsummaryrefslogtreecommitdiff
path: root/.run
diff options
context:
space:
mode:
Diffstat (limited to '.run')
-rw-r--r--.run/.gitignore2
-rw-r--r--.run/Cargo.lock109
-rw-r--r--.run/Cargo.toml2
-rw-r--r--.run/src/bin/ci.rs20
-rwxr-xr-x[-rw-r--r--].run/src/bin/clippy-fix.sh0
-rw-r--r--.run/src/bin/cov-test.rs106
-rw-r--r--.run/src/bin/deploy-api-docs.rs35
-rw-r--r--.run/src/bin/doc.ps16
-rwxr-xr-x[-rw-r--r--].run/src/bin/doc.sh6
-rwxr-xr-x[-rw-r--r--].run/src/bin/http-page-preview.sh0
-rw-r--r--.run/src/bin/package-all.rs121
-rw-r--r--.run/src/bin/test-all-markdown-code.rs55
-rw-r--r--.run/src/bin/windows-folder-hide.ps1126
-rw-r--r--.run/src/dependency_order.rs17
-rw-r--r--.run/src/lib.rs76
15 files changed, 513 insertions, 168 deletions
diff --git a/.run/.gitignore b/.run/.gitignore
index 0dc39b0..40f407e 100644
--- a/.run/.gitignore
+++ b/.run/.gitignore
@@ -3,5 +3,3 @@
# If you want to add some Rust crates? Remove this line
# /Cargo.*
-
-last_check
diff --git a/.run/Cargo.lock b/.run/Cargo.lock
index bb510bd..c255ad0 100644
--- a/.run/Cargo.lock
+++ b/.run/Cargo.lock
@@ -3,6 +3,18 @@
version = 4
[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "bitflags"
+version = "2.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
+
+[[package]]
name = "bumpalo"
version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -36,6 +48,15 @@ dependencies = [
]
[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -48,6 +69,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "filetime"
+version = "0.2.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
+dependencies = [
+ "cfg-if",
+ "libc",
+]
+
+[[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 = "futures-core"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -139,12 +190,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
+name = "linux-raw-sys"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+
+[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[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 = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -181,6 +248,19 @@ dependencies = [
]
[[package]]
+name = "rustix"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -239,6 +319,12 @@ dependencies = [
]
[[package]]
+name = "simd-adler32"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
+
+[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -256,6 +342,17 @@ dependencies = [
]
[[package]]
+name = "tar"
+version = "0.4.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
+dependencies = [
+ "filetime",
+ "libc",
+ "xattr",
+]
+
+[[package]]
name = "tokio"
version = "1.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -322,11 +419,13 @@ name = "tools"
version = "0.1.0"
dependencies = [
"colored",
+ "flate2",
"indicatif",
"just_fmt",
"just_template",
"serde",
"serde_json",
+ "tar",
"tokio",
"toml",
]
@@ -429,6 +528,16 @@ dependencies = [
]
[[package]]
+name = "xattr"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
+dependencies = [
+ "libc",
+ "rustix",
+]
+
+[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/.run/Cargo.toml b/.run/Cargo.toml
index 283aa47..caeb506 100644
--- a/.run/Cargo.toml
+++ b/.run/Cargo.toml
@@ -19,5 +19,7 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
indicatif = "0.18.4"
+flate2 = "1"
+tar = "0.4"
[workspace]
diff --git a/.run/src/bin/ci.rs b/.run/src/bin/ci.rs
index 2527ece..f3ae9e8 100644
--- a/.run/src/bin/ci.rs
+++ b/.run/src/bin/ci.rs
@@ -123,14 +123,26 @@ fn ci(test_docs: bool, test_codes: bool, run_all: bool) -> Result<(), i32> {
}
if run_all || test_docs {
- println_cargo_style!("Phase: Test all examples");
- test_examples()?;
+ let mut exit_code = 0;
println_cargo_style!("Phase: Verify all *.md document code blocks are compilable");
- test_docs_code_blocks()?;
+ if let Err(code) = test_docs_code_blocks() {
+ exit_code = exit_code.max(code);
+ }
+
+ println_cargo_style!("Phase: Test all examples");
+ if let Err(code) = test_examples() {
+ exit_code = exit_code.max(code);
+ }
println_cargo_style!("Phase: Check all documentation is up to date");
- docs_refresh()?;
+ if let Err(code) = docs_refresh() {
+ exit_code = exit_code.max(code);
+ }
+
+ if exit_code != 0 {
+ return Err(exit_code);
+ }
}
run_cmd!("git add --renormalize .")?;
diff --git a/.run/src/bin/clippy-fix.sh b/.run/src/bin/clippy-fix.sh
index 9771ad4..9771ad4 100644..100755
--- a/.run/src/bin/clippy-fix.sh
+++ b/.run/src/bin/clippy-fix.sh
diff --git a/.run/src/bin/cov-test.rs b/.run/src/bin/cov-test.rs
new file mode 100644
index 0000000..a53f74c
--- /dev/null
+++ b/.run/src/bin/cov-test.rs
@@ -0,0 +1,106 @@
+use std::fs;
+use tools::{println_cargo_style, run_cmd};
+
+const OUTPUT_DIR: &str = "docs/cov-test";
+
+fn main() {
+ let repo_root = find_git_repo().expect("Failed to find git repository root");
+ let output_path = repo_root.join(OUTPUT_DIR);
+
+ // Read features from [package.metadata.docs.rs]
+ let features = tools::read_features().unwrap_or_else(|e| {
+ eprintln!("Error: {}", e);
+ std::process::exit(1);
+ });
+
+ let features_arg = features.join(",");
+
+ // Ensure output directory exists
+ std::fs::create_dir_all(&output_path).expect("Failed to create output directory");
+
+ let cmd = format!(
+ "cargo llvm-cov --html --output-dir \"{}\" --workspace --features \"{}\" --color always",
+ output_path.to_string_lossy(),
+ features_arg,
+ );
+
+ println_cargo_style!("Features: {}", features_arg);
+ println_cargo_style!("Coverage: {}", output_path.display());
+
+ println_cargo_style!("Running: cargo llvm-cov --html");
+ run_cmd!(&cmd).unwrap_or_else(|code| {
+ eprintln!("Error: cargo llvm-cov failed with exit code {}", code);
+ std::process::exit(code);
+ });
+
+ // Move files from <output_path>/html/ to <output_path>
+ let html_dir = output_path.join("html");
+ if html_dir.exists() && html_dir.is_dir() {
+ println_cargo_style!("Moving files from {}/html/ to {}/", OUTPUT_DIR, OUTPUT_DIR);
+
+ // Move each entry in html_dir up one level
+ for entry in fs::read_dir(&html_dir).expect("Failed to read html directory") {
+ let entry = entry.expect("Failed to read entry");
+ let entry_path = entry.path();
+ let file_name = entry
+ .file_name()
+ .to_str()
+ .expect("Invalid filename")
+ .to_owned();
+
+ let dest_path = output_path.join(&file_name);
+ // Remove existing file/directory at destination if any
+ if dest_path.exists() {
+ if dest_path.is_dir() {
+ fs::remove_dir_all(&dest_path).unwrap_or_else(|e| {
+ eprintln!(
+ "Warning: could not remove directory {}: {}",
+ dest_path.display(),
+ e
+ );
+ });
+ } else {
+ fs::remove_file(&dest_path).unwrap_or_else(|e| {
+ eprintln!(
+ "Warning: could not remove file {}: {}",
+ dest_path.display(),
+ e
+ );
+ });
+ }
+ }
+ fs::rename(&entry_path, &dest_path).unwrap_or_else(|e| {
+ eprintln!("Warning: could not move {}: {}", entry_path.display(), e);
+ });
+ }
+
+ // Remove the now-empty html directory
+ fs::remove_dir(&html_dir).unwrap_or_else(|e| {
+ eprintln!("Warning: could not remove html directory: {}", e);
+ });
+
+ println_cargo_style!("Files moved successfully.");
+ }
+
+ println_cargo_style!(
+ "Done: coverage report generated at {}/index.html",
+ OUTPUT_DIR
+ );
+}
+
+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/.run/src/bin/deploy-api-docs.rs b/.run/src/bin/deploy-api-docs.rs
index ea52886..7aa093c 100644
--- a/.run/src/bin/deploy-api-docs.rs
+++ b/.run/src/bin/deploy-api-docs.rs
@@ -2,43 +2,16 @@ use std::path::Path;
use tools::{println_cargo_style, run_cmd};
-const DOCS_RS_MANIFEST: &str = "mingling/Cargo.toml";
const OUTPUT_DIR: &str = "docs/api-docs";
fn main() {
let repo_root = find_git_repo().expect("Failed to find git repository root");
- let manifest_path = repo_root.join(DOCS_RS_MANIFEST);
- if !manifest_path.exists() {
- eprintln!("Error: Manifest not found at {}", manifest_path.display());
+ // Read features from [package.metadata.docs.rs]
+ let features = tools::read_features().unwrap_or_else(|e| {
+ eprintln!("Error: {}", e);
std::process::exit(1);
- }
-
- // Read and parse [package.metadata.docs.rs]
- let manifest_content =
- std::fs::read_to_string(&manifest_path).expect("Failed to read Cargo.toml");
- let cargo_toml: toml::Value = manifest_content
- .parse()
- .expect("Failed to parse Cargo.toml");
-
- let doc_features = cargo_toml
- .get("package")
- .and_then(|p| p.get("metadata"))
- .and_then(|m| m.get("docs"))
- .and_then(|d| d.get("rs"))
- .and_then(|rs| rs.get("features"))
- .and_then(|f| f.as_array())
- .unwrap_or_else(|| {
- eprintln!("Error: [package.metadata.docs.rs] or its 'features' key not found in mingling/Cargo.toml");
- std::process::exit(1);
- });
-
- let features: Vec<&str> = doc_features.iter().filter_map(|v| v.as_str()).collect();
-
- if features.is_empty() {
- eprintln!("Error: No features defined in [package.metadata.docs.rs]");
- std::process::exit(1);
- }
+ });
let features_arg = features.join(",");
diff --git a/.run/src/bin/doc.ps1 b/.run/src/bin/doc.ps1
index fd7afaa..0e55141 100644
--- a/.run/src/bin/doc.ps1
+++ b/.run/src/bin/doc.ps1
@@ -1 +1,5 @@
-cargo doc --workspace --no-deps --features core,macros,builds,structural_renderer,repl,comp,parser,picker,clap,extra_macros --open
+cargo doc `
+ --manifest-path mingling/Cargo.toml `
+ --no-deps `
+ --features docs_rs,core,macros,builds,structural_renderer,repl,comp,parser,picker,clap,extra_macros `
+ --open
diff --git a/.run/src/bin/doc.sh b/.run/src/bin/doc.sh
index 2d59896..014ea44 100644..100755
--- a/.run/src/bin/doc.sh
+++ b/.run/src/bin/doc.sh
@@ -1,3 +1,7 @@
#!/bin/bash
-cargo doc --workspace --no-deps --features core,macros,builds,structural_renderer,repl,comp,parser,picker,clap,extra_macros --open
+cargo doc \
+ --manifest-path mingling/Cargo.toml \
+ --no-deps \
+ --features docs_rs,core,macros,builds,structural_renderer,repl,comp,parser,picker,clap,extra_macros \
+ --open
diff --git a/.run/src/bin/http-page-preview.sh b/.run/src/bin/http-page-preview.sh
index bed4b1c..bed4b1c 100644..100755
--- a/.run/src/bin/http-page-preview.sh
+++ b/.run/src/bin/http-page-preview.sh
diff --git a/.run/src/bin/package-all.rs b/.run/src/bin/package-all.rs
index 59f0f89..5d7cbbb 100644
--- a/.run/src/bin/package-all.rs
+++ b/.run/src/bin/package-all.rs
@@ -1,6 +1,8 @@
use std::path::{Path, PathBuf};
+use flate2::read::GzDecoder;
use serde::Deserialize;
+use tar::Archive;
use tools::{
dependency_order::find_workspace_root, eprintln_cargo_style, println_cargo_style,
run_cmd_capture_with_dir, wprintln_cargo_style,
@@ -41,8 +43,8 @@ fn main() {
"cargo metadata --format-version 1 --no-deps",
&workspace_root,
)
- .unwrap_or_else(|(_code, msg)| {
- eprintln_cargo_style!("cargo metadata failed (exit {code}):\n{msg}");
+ .unwrap_or_else(|(code, _msg)| {
+ eprintln_cargo_style!(format!("cargo metadata failed (exit {code}):\n{{msg}}"));
std::process::exit(1);
});
@@ -171,7 +173,7 @@ fn main() {
}
Err((code, msg)) => {
// Print output but don't fail yet
- eprintln_cargo_style!("cargo package exited with code {code}:");
+ eprintln_cargo_style!(format!("cargo package exited with code {code}:"));
println!("{msg}");
}
}
@@ -202,7 +204,7 @@ fn main() {
for entry in std::fs::read_dir(&temp_package_dir).expect("failed to read target/package") {
let entry = entry.expect("failed to read entry");
let path = entry.path();
- if path.extension().map_or(true, |e| e != "crate") {
+ if path.extension().is_none_or(|e| e != "crate") {
continue;
}
@@ -217,11 +219,7 @@ fn main() {
.and_then(|dash| {
// Check if what follows looks like a semver
let ver_part = &fname[dash + 1..];
- if ver_part
- .chars()
- .next()
- .map_or(false, |c| c.is_ascii_digit())
- {
+ if ver_part.chars().next().is_some_and(|c| c.is_ascii_digit()) {
Some(&fname[..dash])
} else {
None
@@ -234,20 +232,18 @@ fn main() {
std::fs::create_dir_all(&target_dir)
.unwrap_or_else(|e| panic!("failed to create {}: {e}", target_dir.display()));
- // Extract using tar + gzip
- let extract_cmd = format!(
- "tar -xzf \"{}\" -C \"{}\"",
- path.display(),
- target_dir.display()
- );
- let extract_ok = std::process::Command::new("sh")
- .arg("-c")
- .arg(&extract_cmd)
- .status()
- .expect("failed to run tar");
-
- if !extract_ok.success() {
- eprintln_cargo_style!("Failed to extract {}", fname);
+ // Extract using flate2 + tar (cross-platform)
+ let file = match std::fs::File::open(&path) {
+ Ok(f) => f,
+ Err(e) => {
+ eprintln_cargo_style!("Failed to open {}: {e}", path.display());
+ continue;
+ }
+ };
+ let decoder = GzDecoder::new(file);
+ let mut archive = Archive::new(decoder);
+ if let Err(e) = archive.unpack(&target_dir) {
+ eprintln_cargo_style!("Failed to extract {}: {e}", fname);
continue;
}
@@ -317,31 +313,30 @@ fn patch_workspace_deps(
in_workspace_deps = false;
}
- if in_workspace_deps {
- if let Some(dep_name) = trimmed.split('=').next().map(|s| s.trim()) {
- if let Some(version) = version_map.get(dep_name) {
- let indent = line
- .chars()
- .take_while(|c| c.is_whitespace())
- .collect::<String>();
-
- if trimmed.contains("path =") {
- let path_value = extract_path_value(trimmed);
- let patched_line: String = if let Some(pv) = path_value {
- trimmed.replace(
- &format!("path = \"{}\"", pv),
- &format!("path = \"{}\", version = \"{}\"", pv, version),
- )
- } else {
- trimmed.to_string()
- };
- result.push_str(&format!("{indent}{patched_line}\n"));
- } else {
- result.push_str(&format!("{indent}{dep_name} = \"{version}\"\n"));
- }
- continue;
- }
+ if in_workspace_deps
+ && let Some(dep_name) = trimmed.split('=').next().map(|s| s.trim())
+ && let Some(version) = version_map.get(dep_name)
+ {
+ let indent = line
+ .chars()
+ .take_while(|c| c.is_whitespace())
+ .collect::<String>();
+
+ if trimmed.contains("path =") {
+ let path_value = extract_path_value(trimmed);
+ let patched_line: String = if let Some(pv) = path_value {
+ trimmed.replace(
+ &format!("path = \"{}\"", pv),
+ &format!("path = \"{}\", version = \"{}\"", pv, version),
+ )
+ } else {
+ trimmed.to_string()
+ };
+ result.push_str(&format!("{indent}{patched_line}\n"));
+ } else {
+ result.push_str(&format!("{indent}{dep_name} = \"{version}\"\n"));
}
+ continue;
}
result.push_str(line);
@@ -391,25 +386,21 @@ fn patch_member_path_deps(
if (in_deps || in_build_deps)
&& trimmed.contains("path = \"")
&& !trimmed.contains("workspace = true")
+ && let Some(dep_name) = trimmed.split('=').next().map(|s| s.trim())
+ && let Some(version) = version_map.get(dep_name.trim_end_matches(".workspace"))
+ && !trimmed.contains("version = \"")
{
- if let Some(dep_name) = trimmed.split('=').next().map(|s| s.trim()) {
- let dep_name = dep_name.trim_end_matches(".workspace");
- if let Some(version) = version_map.get(dep_name) {
- if !trimmed.contains("version = \"") {
- let indent = line
- .chars()
- .take_while(|c| c.is_whitespace())
- .collect::<String>();
- let path_val = extract_path_value(trimmed).unwrap_or_default();
- let patched = trimmed.replace(
- &format!("path = \"{path_val}\""),
- &format!("path = \"{path_val}\", version = \"{version}\""),
- );
- result.push_str(&format!("{indent}{patched}\n"));
- continue;
- }
- }
- }
+ let indent = line
+ .chars()
+ .take_while(|c| c.is_whitespace())
+ .collect::<String>();
+ let path_val = extract_path_value(trimmed).unwrap_or_default();
+ let patched = trimmed.replace(
+ &format!("path = \"{path_val}\""),
+ &format!("path = \"{path_val}\", version = \"{version}\""),
+ );
+ result.push_str(&format!("{indent}{patched}\n"));
+ continue;
}
result.push_str(line);
diff --git a/.run/src/bin/test-all-markdown-code.rs b/.run/src/bin/test-all-markdown-code.rs
index 280fca7..1c0c9e2 100644
--- a/.run/src/bin/test-all-markdown-code.rs
+++ b/.run/src/bin/test-all-markdown-code.rs
@@ -5,7 +5,7 @@ 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,
+ build_block, compute_block_hash, generate_build_rs, generate_cargo_toml, generate_main_rs,
is_block_testable, parse_code_blocks, write_summary_report,
};
use tools::{eprintln_cargo_style, println_cargo_style};
@@ -13,16 +13,7 @@ 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>,
+ verified: HashMap<String, String>,
}
#[tokio::main]
@@ -64,31 +55,28 @@ async fn main() {
};
// Collect all markdown files from config
+ // Keys are used as labels; values are either single file paths or directory globs
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");
+ for (key, value) in &config.verified {
+ let candidate = PathBuf::from(value);
+ if candidate.is_dir() {
+ // Directory — walk it for all .md files, using the key as label
+ collect_md_files(&candidate, &mut files, key);
+ } else if candidate.exists() && candidate.is_file() {
+ // Single file
+ files.push((key.to_string(), candidate));
+ } else if candidate.extension().is_none() {
+ // No extension — treat as a glob like "docs/pages/**", walk the base dir instead
+ let base = PathBuf::from(value.trim_end_matches("/**").trim_end_matches('*'));
+ if base.is_dir() {
+ collect_md_files(&base, &mut files, key);
+ }
}
}
- // 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");
- }
- }
+ // Sort for deterministic ordering
+ files.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
// If a single file was specified, filter the list to only that file
if let Some(ref target) = single_file {
@@ -201,10 +189,7 @@ async fn main() {
bar.inc(1);
} else {
bar.inc(1);
- bar.println(format!(
- " {} {block_label}",
- "failed".bold().bright_red()
- ));
+ 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));
diff --git a/.run/src/bin/windows-folder-hide.ps1 b/.run/src/bin/windows-folder-hide.ps1
index 0ab2632..ff53202 100644
--- a/.run/src/bin/windows-folder-hide.ps1
+++ b/.run/src/bin/windows-folder-hide.ps1
@@ -1,43 +1,115 @@
-# Check `last_check`
+$skipDirs = @('.git', '.temp', 'target', 'node_modules', '.pnpm')
+$selfPath = (Get-Item -LiteralPath $MyInvocation.MyCommand.Path).Directory.FullName
-$lastCheckFile = Join-Path $PSScriptRoot "last_check"
-$currentTime = Get-Date
-$timeThreshold = 10
+function Test-InSkipDir {
+ param(
+ [object]$Item
+ )
+ $path = if ($Item -is [string]) {
+ $Item
+ } elseif ($Item.PSPath) {
+ $Item.PSPath -replace '^.*::', ''
+ } else {
+ $Item.FullName
+ }
+
+ $parts = $path.Split([System.IO.Path]::DirectorySeparatorChar)
+ for ($i = 0; $i -lt $parts.Length - 1; $i++) {
+ if ($parts[$i] -in $skipDirs) {
+ return $true
+ }
+ }
+ return $false
+}
-if (Test-Path $lastCheckFile) {
- $lastCheckTime = Get-Content $lastCheckFile | Get-Date
- $timeDiff = ($currentTime - $lastCheckTime).TotalMinutes
+function Invoke-UnhideRecursive {
+ param([string]$Path)
+ Get-ChildItem -LiteralPath $Path -Force | ForEach-Object {
+ if ($_.PSIsContainer) {
+ if ($_.Name -in $skipDirs) {
+ if ($_.Attributes -band [System.IO.FileAttributes]::Hidden) {
+ Write-Host " -> unhiding skip directory (self only): `"$($_.FullName)`""
+ $_.Attributes = $_.Attributes -bxor [System.IO.FileAttributes]::Hidden
+ }
+ return
+ }
+ Invoke-UnhideRecursive $_.FullName
+ } else {
+ if ($_.Attributes -band [System.IO.FileAttributes]::Hidden) {
+ Write-Host " -> unhiding: `"$($_.FullName)`""
+ $_.Attributes = $_.Attributes -bxor [System.IO.FileAttributes]::Hidden
+ }
+ }
+ }
+}
- if ($timeDiff -lt $timeThreshold) {
- exit
+function Test-GitPathSkippable {
+ param([string]$GitPath)
+ $parts = $GitPath.Split(@('/', '\'))
+ for ($i = 0; $i -lt $parts.Length - 1; $i++) {
+ if ($parts[$i] -in $skipDirs) {
+ return $true
+ }
}
+ return $false
}
-$currentTime.ToString() | Out-File -FilePath $lastCheckFile -Force
+Write-Host "Step 1: Unhiding all files and directories (skipping $($skipDirs -join ', '))..."
-# Hide Files
+Invoke-UnhideRecursive -Path (Get-Location).Path
-Set-Location -Path (Join-Path $PSScriptRoot "..\..")
+Write-Host "Step 2: Hiding git-ignored items..."
-Get-ChildItem -Path . -Force -Recurse -ErrorAction SilentlyContinue | Where-Object {
- $_.FullName -notmatch '\\.temp\\' -and $_.FullName -notmatch '\\.git\\'
+git ls-files --others --ignored --exclude-standard | Where-Object {
+ -not (Test-GitPathSkippable $_)
} | ForEach-Object {
- attrib -h $_.FullName 2>&1 | Out-Null
+ $itemPath = $_
+ Write-Host "... checking: `"$itemPath`""
+ $item = Get-Item $_ -Force -ErrorAction SilentlyContinue
+ if (-not $item) { return }
+
+ if ($item.FullName -eq $selfPath) { return }
+
+ if (Test-InSkipDir $item) {
+ Write-Host " -> skipping (inside skip directory)"
+ return
+ }
+
+ if ($item.PSIsContainer) {
+ if (-not ($item.Attributes -band [System.IO.FileAttributes]::Hidden)) {
+ Write-Host " -> hiding directory (non-recursive)"
+ $item.Attributes = $item.Attributes -bor [System.IO.FileAttributes]::Hidden
+ }
+ } else {
+ if (-not ($item.Attributes -band [System.IO.FileAttributes]::Hidden)) {
+ Write-Host " -> hiding"
+ $item.Attributes = $item.Attributes -bor [System.IO.FileAttributes]::Hidden
+ }
+ }
}
-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
+Write-Host "Step 3: Hiding dot-prefixed items..."
+Get-ChildItem -Path . -Force -Directory | Where-Object { $_.Name -match '^\.' } | ForEach-Object {
+ Write-Host "... checking: `"$($_.FullName)`""
+ if (Test-InSkipDir $_) {
+ Write-Host " -> skipping (inside skip directory)"
+ return
+ }
+ if (-not ($_.Attributes -band [System.IO.FileAttributes]::Hidden)) {
+ Write-Host " -> hiding directory"
+ $_.Attributes = $_.Attributes -bor [System.IO.FileAttributes]::Hidden
+ }
}
-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
- }
- }
+Get-ChildItem -Path . -Force -File | Where-Object { $_.Name -match '^\.' } | ForEach-Object {
+ if ($_.FullName -eq $selfPath) { return }
+ Write-Host "... checking: `"$($_.FullName)`""
+ if (Test-InSkipDir $_) {
+ Write-Host " -> skipping (inside skip directory)"
+ return
+ }
+ if (-not ($_.Attributes -band [System.IO.FileAttributes]::Hidden)) {
+ Write-Host " -> hiding file"
+ $_.Attributes = $_.Attributes -bor [System.IO.FileAttributes]::Hidden
}
}
diff --git a/.run/src/dependency_order.rs b/.run/src/dependency_order.rs
index 99317ae..145bdbd 100644
--- a/.run/src/dependency_order.rs
+++ b/.run/src/dependency_order.rs
@@ -1,5 +1,5 @@
use std::collections::{HashMap, HashSet};
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
/// Parse Cargo.toml content and return dependency names that start with `prefix`.
fn parse_mingling_deps(content: &str, prefix: &str) -> Vec<String> {
@@ -119,10 +119,23 @@ fn topological_sort(
result
}
+/// Strip the `\\?\` prefix that `std::fs::canonicalize` may add on Windows.
+fn strip_verbatim_prefix(p: &Path) -> PathBuf {
+ let s = p.to_string_lossy();
+ let s_ref: &str = &s;
+ if let Some(rest) = s_ref.strip_prefix("\\\\?\\") {
+ PathBuf::from(rest)
+ } else {
+ p.to_path_buf()
+ }
+}
+
/// Find the workspace root by looking for a Cargo.toml with `[workspace]` members.
/// Starts from `start` and walks up the directory tree.
pub fn find_workspace_root(start: &std::path::Path) -> Option<PathBuf> {
- let mut current = Some(std::fs::canonicalize(start).unwrap_or_else(|_| start.to_path_buf()));
+ let mut current = Some(strip_verbatim_prefix(
+ &std::fs::canonicalize(start).unwrap_or_else(|_| start.to_path_buf()),
+ ));
while let Some(dir) = current {
let members = get_workspace_members(&dir);
if !members.is_empty() {
diff --git a/.run/src/lib.rs b/.run/src/lib.rs
index d52c7fd..cff606a 100644
--- a/.run/src/lib.rs
+++ b/.run/src/lib.rs
@@ -334,6 +334,82 @@ pub fn run_cmd_with_progress(phase: &str, label: &str, cmd: String) -> Result<()
}
}
+/// Read `[package.metadata.docs.rs].features` from `mingling/Cargo.toml`.
+///
+/// Finds the git repository root, reads `mingling/Cargo.toml`, parses it as TOML,
+/// and extracts the feature list under `[package.metadata.docs.rs].features`.
+///
+/// # Errors
+///
+/// Returns `std::io::Error` if:
+/// - The git repository root cannot be found.
+/// - The manifest file cannot be read.
+/// - The TOML cannot be parsed.
+/// - The `[package.metadata.docs.rs].features` key is missing or empty.
+pub fn read_features() -> Result<Vec<String>, std::io::Error> {
+ // Find git repo root
+ let mut current_dir = std::env::current_dir()?;
+ let repo_root = loop {
+ let git_dir = current_dir.join(".git");
+ if git_dir.exists() && git_dir.is_dir() {
+ break Some(current_dir);
+ }
+ if !current_dir.pop() {
+ break None;
+ }
+ };
+ let repo_root = repo_root.ok_or_else(|| {
+ std::io::Error::new(
+ std::io::ErrorKind::NotFound,
+ "Failed to find git repository root",
+ )
+ })?;
+
+ let manifest_path = repo_root.join("mingling/Cargo.toml");
+ if !manifest_path.exists() {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::NotFound,
+ format!("Manifest not found at {}", manifest_path.display()),
+ ));
+ }
+
+ let manifest_content = std::fs::read_to_string(&manifest_path)?;
+ let cargo_toml: toml::Value = manifest_content.parse().map_err(|e| {
+ std::io::Error::new(
+ std::io::ErrorKind::InvalidData,
+ format!("Failed to parse Cargo.toml: {}", e),
+ )
+ })?;
+
+ let doc_features = cargo_toml
+ .get("package")
+ .and_then(|p| p.get("metadata"))
+ .and_then(|m| m.get("docs"))
+ .and_then(|d| d.get("rs"))
+ .and_then(|rs| rs.get("features"))
+ .and_then(|f| f.as_array())
+ .ok_or_else(|| {
+ std::io::Error::new(
+ std::io::ErrorKind::NotFound,
+ "[package.metadata.docs.rs] or its 'features' key not found in mingling/Cargo.toml",
+ )
+ })?;
+
+ let features: Vec<String> = doc_features
+ .iter()
+ .filter_map(|v| v.as_str().map(String::from))
+ .collect();
+
+ if features.is_empty() {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::InvalidData,
+ "No features defined in [package.metadata.docs.rs]",
+ ));
+ }
+
+ Ok(features)
+}
+
#[must_use]
pub fn cargo_tomls() -> Vec<std::path::PathBuf> {
let mut cargo_tomls = Vec::new();