diff options
Diffstat (limited to '.run')
| -rw-r--r-- | .run/.gitignore | 2 | ||||
| -rw-r--r-- | .run/Cargo.lock | 109 | ||||
| -rw-r--r-- | .run/Cargo.toml | 2 | ||||
| -rw-r--r-- | .run/src/bin/ci.rs | 20 | ||||
| -rwxr-xr-x[-rw-r--r--] | .run/src/bin/clippy-fix.sh | 0 | ||||
| -rw-r--r-- | .run/src/bin/cov-test.rs | 106 | ||||
| -rw-r--r-- | .run/src/bin/deploy-api-docs.rs | 35 | ||||
| -rw-r--r-- | .run/src/bin/doc.ps1 | 6 | ||||
| -rwxr-xr-x[-rw-r--r--] | .run/src/bin/doc.sh | 6 | ||||
| -rwxr-xr-x[-rw-r--r--] | .run/src/bin/http-page-preview.sh | 0 | ||||
| -rw-r--r-- | .run/src/bin/package-all.rs | 121 | ||||
| -rw-r--r-- | .run/src/bin/test-all-markdown-code.rs | 55 | ||||
| -rw-r--r-- | .run/src/bin/windows-folder-hide.ps1 | 126 | ||||
| -rw-r--r-- | .run/src/dependency_order.rs | 17 | ||||
| -rw-r--r-- | .run/src/lib.rs | 76 |
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(); |
