11// HTML -> imperative Rust translator: each tests/cases/<topic>/<case>.html
22// is translated into a `#[test]` that exercises the TestCtx high-level API
33// (create_node / create_text / set_style / append + layout_imperative +
4- // getters). Output goes to tests/generated/<topic>/<case>.rs plus a nested
5- // mod tree (tests/generated/mod.rs → <topic>/mod.rs → <case>.rs), which
6- // tests/mod.rs pulls in via `mod generated;`.
4+ // getters).
5+ //
6+ // Output strategy:
7+ // - Always: OUT_DIR/html_tests.rs (single file, all #[test] inlined),
8+ // pulled in by tests/mod.rs via `include!`. This is what compiles.
9+ // - Local (non-CI, non-publish-verify) builds ALSO mirror per-case .rs
10+ // files under tests/generated/<topic>/<case>.rs (+ mod tree) for human
11+ // review. CI (CI=true) and `cargo package`/`publish` verify
12+ // (CARGO_MANIFEST_DIR under target/package) skip the mirror — CI doesn't
13+ // need it, and publish verify forbids build.rs from writing outside
14+ // OUT_DIR.
715//
816// The translator walks the parsed DOM and emits imperative calls in
917// document order. Layout assertions (data-expect-*) are collected during
@@ -19,13 +27,13 @@ use float_pigment_mlp::{
1927fn main ( ) {
2028 let manifest = std:: env:: var ( "CARGO_MANIFEST_DIR" ) . unwrap ( ) ;
2129 let cases_dir = PathBuf :: from ( & manifest) . join ( "tests/cases" ) ;
22- let gen_dir = PathBuf :: from ( & manifest) . join ( "tests/generated" ) ;
23- // 清空旧生成(删整个 generated 再重建,避免残留)
24- let _ = fs:: remove_dir_all ( & gen_dir) ;
25- fs:: create_dir_all ( & gen_dir) . unwrap ( ) ;
30+ let out = std:: env:: var ( "OUT_DIR" ) . unwrap ( ) ;
31+ let output_file = PathBuf :: from ( & out) . join ( "html_tests.rs" ) ;
2632
27- // topic -> Vec<(case_name, body, ignore)>
28- let mut by_topic: std:: collections:: BTreeMap < String , Vec < ( String , String , bool ) > > =
33+ let mut tests = String :: from ( "// AUTO-GENERATED by build.rs. Do not edit.\n \n " ) ;
34+ // topic -> Vec<(case_name, name_ident, body, ignore)>, used to mirror
35+ // per-case .rs files under tests/generated in local builds.
36+ let mut by_topic: std:: collections:: BTreeMap < String , Vec < ( String , String , String , bool ) > > =
2937 std:: collections:: BTreeMap :: new ( ) ;
3038
3139 if cases_dir. exists ( ) {
@@ -39,28 +47,54 @@ fn main() {
3947 let mut parts = rel_str. split ( '/' ) ;
4048 let topic = parts. next ( ) . unwrap_or ( "misc" ) . to_string ( ) ;
4149 let name = parts. collect :: < Vec < _ > > ( ) . join ( "_" ) ;
50+ let topic_ident = topic. replace ( '-' , "_" ) ;
51+ let name_ident = name. replace ( '-' , "_" ) ;
52+ let fn_name = format ! ( "html_{}_{}" , topic_ident, name_ident) ;
4253 let html = fs:: read_to_string ( & abs) . unwrap_or_default ( ) ;
4354 let ignore = html. contains ( "data-ignore=\" true\" " ) ;
55+ let ignore_attr = if ignore { "#[ignore]\n " } else { "" } ;
4456 let body = translate_html ( & html) ;
57+ // OUT_DIR/html_tests.rs — compiled via include! in tests/mod.rs.
58+ tests. push_str ( & format ! (
59+ "{ignore_attr}#[test]\n fn {fn_name}() {{\n use crate::TestCtx;\n {body}}}\n \n "
60+ ) ) ;
4561 by_topic
4662 . entry ( topic)
4763 . or_default ( )
48- . push ( ( name, body, ignore) ) ;
64+ . push ( ( name, name_ident , body, ignore) ) ;
4965 }
5066 }
5167
52- // 生成每 topic 目录 + mod.rs + 各 case .rs。
53- // topic/name 原样用作目录名/文件名(文件系统允许 `-`),但需 sanitize
54- // 为合法 Rust 标识符(`-` 非法)用于 mod 名与 fn 名。当前 cases 无 `-`,
55- // 此 sanitize 为防御性 —— 与旧 build.rs `replace(['/', '\\', '-', ' '], "_")` 一致。
68+ fs:: write ( & output_file, tests) . unwrap ( ) ;
69+
70+ // Local builds also mirror per-case .rs under tests/generated/ for review.
71+ let is_ci = std:: env:: var ( "CI" ) . is_ok ( ) ;
72+ let is_publish_verify = manifest. contains ( "target/package" ) ;
73+ if !is_ci && !is_publish_verify {
74+ mirror_to_generated ( & manifest, & by_topic) ;
75+ }
76+
77+ println ! ( "cargo:rerun-if-changed=tests/cases" ) ;
78+ println ! ( "cargo:rerun-if-changed=build.rs" ) ;
79+ }
80+
81+ /// Mirror per-case .rs files (+ nested mod tree) under tests/generated/ for
82+ /// human review. Not compiled (tests/mod.rs uses the OUT_DIR include!), so
83+ /// purely for readability.
84+ fn mirror_to_generated (
85+ manifest : & str ,
86+ by_topic : & std:: collections:: BTreeMap < String , Vec < ( String , String , String , bool ) > > ,
87+ ) {
88+ let gen_dir = PathBuf :: from ( manifest) . join ( "tests/generated" ) ;
89+ let _ = fs:: remove_dir_all ( & gen_dir) ;
90+ fs:: create_dir_all ( & gen_dir) . unwrap ( ) ;
5691 let mut top_mod = String :: from ( "// AUTO-GENERATED by build.rs. Do not edit.\n \n " ) ;
57- for ( topic, cases) in & by_topic {
92+ for ( topic, cases) in by_topic {
5893 let topic_dir = gen_dir. join ( topic) ;
5994 fs:: create_dir_all ( & topic_dir) . unwrap ( ) ;
6095 let topic_ident = topic. replace ( '-' , "_" ) ;
6196 let mut topic_mod = String :: from ( "// AUTO-GENERATED. Do not edit.\n \n " ) ;
62- for ( name, body, ignore) in cases {
63- let name_ident = name. replace ( '-' , "_" ) ;
97+ for ( name, name_ident, body, ignore) in cases {
6498 let fn_name = format ! ( "html_{}_{}" , topic_ident, name_ident) ;
6599 let ignore_attr = if * ignore { "#[ignore]\n " } else { "" } ;
66100 let case_rs = format ! (
@@ -73,8 +107,6 @@ fn main() {
73107 top_mod. push_str ( & format ! ( "mod {topic_ident};\n " ) ) ;
74108 }
75109 fs:: write ( gen_dir. join ( "mod.rs" ) , top_mod) . unwrap ( ) ;
76- println ! ( "cargo:rerun-if-changed=tests/cases" ) ;
77- println ! ( "cargo:rerun-if-changed=build.rs" ) ;
78110}
79111
80112/// Parse the HTML and emit imperative TestCtx calls.
0 commit comments