diff options
Diffstat (limited to '.run/src')
| -rw-r--r-- | .run/src/bin/ci.rs | 49 | ||||
| -rwxr-xr-x[-rw-r--r--] | .run/src/bin/clippy.sh | 0 | ||||
| -rw-r--r-- | .run/src/bin/cov-test.rs | 106 | ||||
| -rw-r--r-- | .run/src/bin/deploy-api-docs.rs | 58 | ||||
| -rw-r--r-- | .run/src/bin/doc-nightly.ps1 | 6 | ||||
| -rw-r--r-- | .run/src/bin/doc-nightly.sh | 8 | ||||
| -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/test-all-markdown-code.rs | 55 | ||||
| -rw-r--r-- | .run/src/lib.rs | 76 |
11 files changed, 285 insertions, 85 deletions
diff --git a/.run/src/bin/ci.rs b/.run/src/bin/ci.rs index 2527ece..4b6f973 100644 --- a/.run/src/bin/ci.rs +++ b/.run/src/bin/ci.rs @@ -1,6 +1,7 @@ use std::io::Write as _; use std::process::exit; +use arg_picker::{Picker, macros::arg}; use tools::{ cargo_tomls, crate_name_from, eprintln_cargo_style, println_cargo_style, run_cmd, run_parallel, }; @@ -35,19 +36,20 @@ fn main() { let _ = colored::control::set_virtual_terminal(true); println!("{}", include_str!("../../../docs/res/ci_banner.txt")); - let args: Vec<String> = std::env::args().collect(); + let (auto_yes, dirty, test_docs, refresh_docs, test_codes, help) = Picker::from_args() + .pick_or_default(&arg![yes: bool, 'y']) + .pick_or_default(&arg![dirty: bool]) + .pick_or_default(&arg![test_docs: bool]) + .pick_or_default(&arg![refresh_docs: bool]) + .pick_or_default(&arg![test_codes: bool]) + .pick_or_default(&arg![help: bool, 'h']) + .unwrap(); - if args.iter().any(|a| a == "-h" || a == "--help") { + if 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; @@ -123,14 +125,31 @@ 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); + } + + println_cargo_style!("Phase: Try Build API docs"); + if let Err(code) = deploy_api_docs() { + exit_code = exit_code.max(code); + } + + if exit_code != 0 { + return Err(exit_code); + } } run_cmd!("git add --renormalize .")?; @@ -211,6 +230,12 @@ fn test_all() -> Result<(), i32> { run_parallel("Testing", tasks) } +fn deploy_api_docs() -> Result<(), i32> { + run_cmd!( + "cargo run --manifest-path .run/Cargo.toml --color always --bin deploy-api-docs -- --docsrs" + ) +} + fn docs_refresh() -> Result<(), i32> { println_cargo_style!("Refresh: document at `./docs/`"); diff --git a/.run/src/bin/clippy.sh b/.run/src/bin/clippy.sh index b393545..b393545 100644..100755 --- a/.run/src/bin/clippy.sh +++ b/.run/src/bin/clippy.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..961eb04 100644 --- a/.run/src/bin/deploy-api-docs.rs +++ b/.run/src/bin/deploy-api-docs.rs @@ -1,44 +1,22 @@ use std::path::Path; +use arg_picker::{Picker, macros::arg}; 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 using_docsrs = Picker::from_args() + .pick_or_default(&arg![docsrs: bool]) + .unwrap(); - let manifest_path = repo_root.join(DOCS_RS_MANIFEST); - if !manifest_path.exists() { - eprintln!("Error: Manifest not found at {}", manifest_path.display()); - std::process::exit(1); - } + let repo_root = find_git_repo().expect("Failed to find git repository root"); - // 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]"); + // 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(","); @@ -47,11 +25,19 @@ fn main() { std::fs::create_dir_all(&output_path).expect("Failed to create output directory"); // Build cargo doc command - let cmd = format!( - "cargo doc --no-deps --features \"{}\" -p mingling --target-dir \"{}\" --color always", - features_arg, - output_path.join("target").to_string_lossy() - ); + let cmd = if using_docsrs { + format!( + "cargo +nightly rustdoc --features \"{}\" -p mingling --target-dir \"{}\" --color always -- --cfg docsrs", + features_arg, + output_path.join("target").to_string_lossy() + ) + } else { + format!( + "cargo doc --no-deps --features \"{}\" -p mingling --target-dir \"{}\" --color always", + features_arg, + output_path.join("target").to_string_lossy() + ) + }; println_cargo_style!("Features: {}", features_arg); println_cargo_style!("Output: {}", output_path.display()); diff --git a/.run/src/bin/doc-nightly.ps1 b/.run/src/bin/doc-nightly.ps1 new file mode 100644 index 0000000..30d6aaf --- /dev/null +++ b/.run/src/bin/doc-nightly.ps1 @@ -0,0 +1,6 @@ +cargo +nightly rustdoc ` + --manifest-path mingling/Cargo.toml ` + --features docs_rs,core,macros,builds,structural_renderer,repl,comp,parser,picker,clap,extra_macros ` + --open ` + -- ` + --cfg docsrs diff --git a/.run/src/bin/doc-nightly.sh b/.run/src/bin/doc-nightly.sh new file mode 100644 index 0000000..944f4b3 --- /dev/null +++ b/.run/src/bin/doc-nightly.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +cargo rustdoc \ + --manifest-path mingling/Cargo.toml \ + --features docs_rs,core,macros,builds,structural_renderer,repl,comp,parser,picker,clap,extra_macros \ + --open \ + -- \ + --cfg docsrs 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/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/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(); |
