aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src/func/gen_program.rs
blob: d50041eae87e4849e7642e657e13097de647f66a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
use proc_macro::TokenStream;
use quote::quote;

/// Entry point for `gen_program!()`.
///
/// Generates the `Next` type alias, `Routable` impl for `ChainProcess`,
/// and delegates to `program_comp_gen!()`, `program_fallback_gen!()`,
/// and `program_final_gen!()`.
pub(crate) fn gen_program_impl(_input: TokenStream) -> TokenStream {
    #[cfg(feature = "comp")]
    let comp_gen = quote! {
        ::mingling::macros::program_comp_gen!();
    };

    #[cfg(not(feature = "comp"))]
    let comp_gen = quote! {};

    // When pathf is enabled, load the type_using.rs generated by the build script
    // and emit its use statements so types from submodules are in scope.
    #[cfg(feature = "pathf")]
    let pathf_uses: Vec<proc_macro2::TokenStream> = {
        let uses = load_pathf_uses();
        if uses.is_empty() {
            // The file might not exist yet — emit a clear hint
            let hint: proc_macro2::TokenStream = syn::parse_quote! {
                compile_error!(
                    "pathf: `{}` not found or empty.\n\
                     Make sure `build.rs` calls `mingling::build::analyze_and_build_type_mapping().unwrap();`\n\
                     with features [\"build\", \"pathf\"] enabled."
                );
            };
            vec![hint]
        } else {
            uses
        }
    };
    #[cfg(not(feature = "pathf"))]
    let pathf_uses: Vec<proc_macro2::TokenStream> = Vec::new();

    #[cfg(feature = "pathf")]
    let super_use = quote! {};

    #[cfg(not(feature = "pathf"))]
    let super_use = quote! {
      use super::*;
    };

    TokenStream::from(quote! {
        pub use __this_program_impl::*;

        #[doc(hidden)]
        pub mod __this_program_impl {
            #super_use
            #(#pathf_uses)*

            /// Alias for the current program type `ThisProgram`
            pub type Next = ::mingling::ChainProcess<ThisProgram>;

            ::mingling::macros::pack!(Entry = Vec<String>);

            impl ::mingling::Routable<ThisProgram> for ::mingling::ChainProcess<ThisProgram>
            {
                fn to_chain(self) -> ::mingling::ChainProcess<ThisProgram> {
                    match self {
                        ::mingling::ChainProcess::Ok((any, _)) => {
                            ::mingling::ChainProcess::Ok((any, mingling::NextProcess::Chain))
                        }
                        other => other,
                    }
                }

                fn to_render(self) -> ::mingling::ChainProcess<ThisProgram> {
                    match self {
                        ::mingling::ChainProcess::Ok((any, _)) => {
                            ::mingling::ChainProcess::Ok((any, mingling::NextProcess::Renderer))
                        }
                        other => other,
                    }
                }
            }

            #comp_gen
            ::mingling::macros::program_fallback_gen!();
            ::mingling::macros::program_final_gen!();
        }
    })
}

/// Loads `type_using.rs` generated by the pathf build script and returns each
/// `use ...;` line as a token stream, ready to be emitted in the generated output.
#[cfg(feature = "pathf")]
fn load_pathf_uses() -> Vec<proc_macro2::TokenStream> {
    let out_dir = match std::env::var("OUT_DIR") {
        Ok(d) => d,
        Err(_) => return Vec::new(),
    };
    let crate_name = match std::env::var("CARGO_PKG_NAME") {
        Ok(n) => n,
        Err(_) => return Vec::new(),
    };
    let path = std::path::Path::new(&out_dir)
        .join(&crate_name)
        .join("type_using.rs");
    let content = match std::fs::read_to_string(&path) {
        Ok(c) => c,
        Err(_) => return Vec::new(),
    };
    content
        .lines()
        .map(|line| line.trim().to_string())
        .filter(|line| !line.is_empty())
        .filter_map(|line| line.parse::<proc_macro2::TokenStream>().ok())
        .collect()
}