aboutsummaryrefslogtreecommitdiff
path: root/dev_tools/src/verify.rs
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-07-10 16:54:57 +0800
committer魏曹先生 <1992414357@qq.com>2026-07-10 16:54:57 +0800
commit5d258482fb84d58c966a7ccc1b467708278d28ad (patch)
tree1a0c9d2e245d43ff7bc11d9088cf88a5264d293f /dev_tools/src/verify.rs
parenta8c8d50b6c59b103da7fd4bbb5e7ca3ff7b2f49a (diff)
refactor(devtools): rename dev_tools to .run and replace run-tools
scripts Replace the old run-tools.ps1/run-tools.sh scripts with a new unified run.ps1/run.sh launcher that supports multiple languages (PowerShell, Rust, Python, Go, C#, Nim, Perl, Ruby, Zig, and arbitrary binaries) from the `.run/src/bin` directory. Move all development tools from `dev_tools/` to `.run/`, update Cargo workspace config and relevant documentation references.
Diffstat (limited to 'dev_tools/src/verify.rs')
-rw-r--r--dev_tools/src/verify.rs511
1 files changed, 0 insertions, 511 deletions
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());
-}