Skip to main content

pyro_macro/
lib.rs

1//! Derive macros and code generation utilities for Pyroduct.
2//!
3//! This crate provides proc-macro functionality for Pyroduct capabilities:
4//! - [`ffi`] — FFI binding generation for capability interfaces
5//! - [`format`] — Token formatting and diffing utilities
6//! - [`module`] — Module scaffolding and generation
7//! - [`struct_doc`] — Struct documentation generation
8//! - [`utils`] — Internal helper utilities
9//!
10//! Test infrastructure includes a `fmt` submodule with token pretty-printing
11//! and assertion helpers for diff-based code comparison.
12
13pub mod ffi;
14pub mod format;
15pub mod module;
16pub mod struct_doc;
17pub(crate) mod utils;
18
19#[cfg(test)]
20pub mod fmt {
21    use proc_macro2::TokenStream;
22    use similar::{ChangeTag, TextDiff};
23    use std::fmt::Write; // Import Write for efficient string building
24
25    pub fn format_tokens(tokens: &TokenStream) -> String {
26        match syn::parse_file(&tokens.to_string()) {
27            Ok(file) => prettyplease::unparse(&file),
28            Err(err) => {
29                println!("Parsing Error {err:?}");
30                tokens.to_string()
31            }
32        }
33    }
34
35    pub fn assert_code_eq_token(tokens: &TokenStream, expected: &TokenStream) {
36        let actual_file: syn::File = match syn::parse2(tokens.clone()) {
37            Ok(actual_file) => actual_file,
38            Err(err) => {
39                println!("=== Bad Code: ===");
40                println!("{tokens}");
41                println!("=================");
42                println!("Error: {err}");
43                panic!("Actual string is not valid Rust code (syn::File)")
44            }
45        };
46        let expected_file: syn::File = match syn::parse2(expected.clone()) {
47            Ok(actual_file) => actual_file,
48            Err(err) => {
49                println!("=== Bad Code: ===");
50                println!("{expected}");
51                println!("=================");
52                println!("Error: {err}");
53                panic!("Expected string is not valid Rust code (syn::File)")
54            }
55        };
56
57        let actual_str = prettyplease::unparse(&actual_file);
58        let expected_str = prettyplease::unparse(&expected_file);
59
60        if actual_str != expected_str {
61            // "expected" is the original, "actual" is the new version
62            let diff = TextDiff::from_lines(&expected_str, &actual_str);
63            let mut diff_output = String::new();
64
65            for change in diff.iter_all_changes() {
66                let sign = match change.tag() {
67                    ChangeTag::Delete => "-", // Present in expected, missing in actual
68                    ChangeTag::Insert => "+", // Present in actual, missing in expected
69                    ChangeTag::Equal => " ",
70                };
71
72                // Write the formatted line to the buffer
73                let _ = write!(&mut diff_output, "{}{}", sign, change);
74            }
75
76            panic!(
77                "Code mismatch!\n\nDIFF ((- expected) (+ actual)):\n\n{}\n",
78                diff_output
79            );
80        }
81    }
82}