zng_env_proc_macros/
lib.rs

1#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
3//!
4//! Proc-macros for `zng-env`.
5//!
6//! # Crate
7//!
8#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11
12use std::path::PathBuf;
13
14use proc_macro::TokenStream;
15use semver::Version;
16
17#[macro_use]
18extern crate quote;
19
20#[doc(hidden)]
21#[proc_macro]
22pub fn init_parse(crate_: TokenStream) -> TokenStream {
23    let crate_ = proc_macro2::TokenStream::from(crate_);
24
25    let manifest = match std::env::var("CARGO_MANIFEST_DIR") {
26        Ok(m) => PathBuf::from(m).join("Cargo.toml"),
27        Err(e) => {
28            let msg = format!("missing CARGO_MANIFEST_DIR, {e}");
29            return quote! {
30                compile_error!(#msg)
31            }
32            .into();
33        }
34    };
35    let manifest_str = match std::fs::read_to_string(&manifest) {
36        Ok(s) => s,
37        Err(e) => {
38            let msg = format!("cannot read `{}`, {e}", manifest.display());
39            return quote! {
40                compile_error!(#msg)
41            }
42            .into();
43        }
44    };
45
46    let m: Manifest = match toml::from_str(&manifest_str) {
47        Ok(m) => m,
48        Err(e) => {
49            let msg = format!("cannot parse Cargo.toml manifest, {e}");
50            return quote! {
51                compile_error!(#msg)
52            }
53            .into();
54        }
55    };
56    let p_name = m.package.name;
57    let c_name = p_name.replace('-', "_");
58    let p_authors = m.package.authors.unwrap_or_default();
59    let major = m.package.version.major;
60    let minor = m.package.version.minor;
61    let patch = m.package.version.patch;
62    let pre = m.package.version.pre.to_string();
63    let build = m.package.version.build.to_string();
64    let desc = m.package.description.unwrap_or_default();
65    let home = m.package.homepage.unwrap_or_default();
66    let license = m.package.license.unwrap_or_default();
67    let mut app = "";
68    let mut org = "";
69    let mut qualifier = "";
70    let mut has_about = false;
71
72    if let Some(m) = m
73        .package
74        .metadata
75        .as_ref()
76        .and_then(|m| m.zng.as_ref())
77        .and_then(|z| z.about.as_ref())
78    {
79        has_about = true;
80        app = m.app.as_deref().unwrap_or_default();
81        org = m.org.as_deref().unwrap_or_default();
82        qualifier = m.qualifier.as_deref().unwrap_or_default();
83    }
84    if app.is_empty() {
85        app = &p_name;
86    }
87    if org.is_empty() {
88        org = p_authors.first().map(|s| s.as_str()).unwrap_or_default();
89    }
90
91    /*
92    pub fn macro_new(
93        pkg_name: &'static str,
94        pkg_authors: &[&'static str],
95        cargo_pkg_name: &'static str,
96        cargo_pkg_authors: &[&'static str],
97        crate_name: &'static str,
98        (major, minor, patch, pre, build): (u64, u64, u64, &'static str, &'static str),
99        app: &'static str,
100        org: &'static str,
101        qualifier: &'static str,
102        description: &'static str,
103        homepage: &'static str,
104        license: &'static str,
105    )
106     */
107    quote! {
108        #crate_::init(#crate_::About::macro_new(
109            #p_name,
110            &[#(#p_authors),*],
111            #c_name,
112            (#major, #minor, #patch, #pre, #build),
113            #app,
114            #org,
115            #qualifier,
116            #desc,
117            #home,
118            #license,
119            #has_about,
120        ))
121    }
122    .into()
123}
124
125#[derive(serde::Deserialize)]
126struct Manifest {
127    package: Package,
128}
129#[derive(serde::Deserialize)]
130struct Package {
131    name: String,
132    version: Version,
133    description: Option<String>,
134    homepage: Option<String>,
135    license: Option<String>,
136    authors: Option<Box<[String]>>,
137    metadata: Option<Metadata>,
138}
139#[derive(serde::Deserialize)]
140struct Metadata {
141    zng: Option<Zng>,
142}
143#[derive(serde::Deserialize)]
144struct Zng {
145    about: Option<MetadataAbout>,
146}
147#[derive(serde::Deserialize)]
148struct MetadataAbout {
149    app: Option<String>,
150    org: Option<String>,
151    qualifier: Option<String>,
152}
153
154#[doc(hidden)]
155#[proc_macro]
156// #[cfg(target_arch = "wasm32")] // cannot do this, target_arch is the build system arch
157pub fn wasm_process_start(crate_closure: TokenStream) -> TokenStream {
158    use quote::TokenStreamExt as _;
159
160    let crate_closure = proc_macro2::TokenStream::from(crate_closure);
161    let mut crate_ = proc_macro2::TokenStream::new();
162    let mut crate_ok = false;
163    let mut closure = proc_macro2::TokenStream::new();
164
165    for tt in crate_closure {
166        if crate_ok {
167            closure.append(tt);
168        } else if matches!(&tt, proc_macro2::TokenTree::Punct(p) if p.as_char() == ',') {
169            crate_ok = true;
170        } else {
171            crate_.append(tt);
172        }
173    }
174
175    use sha2::Digest;
176    let mut start_ident = sha2::Sha256::new();
177    start_ident.update(closure.to_string().as_bytes());
178    let start_ident = format!("__zng_env_start_{:x}", start_ident.finalize());
179    let start_ident = proc_macro2::Ident::new(&start_ident, proc_macro2::Span::call_site());
180
181    quote! {
182        #[doc(hidden)]
183        #[#crate_::wasm_bindgen]
184        pub fn #start_ident() {
185            #crate_::WASM_INIT.with_borrow_mut(|v| {
186                v.push(_on_process_start);
187            })
188        }
189        fn _on_process_start(args: &#crate_::ProcessStartArgs) {
190            fn on_process_start(args: &#crate_::ProcessStartArgs, handler: impl FnOnce(&#crate_::ProcessStartArgs)) {
191                handler(args)
192            }
193            on_process_start(args, #closure)
194        }
195    }
196    .into()
197}