zng_env_proc_macros/
lib.rs1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3#![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
57 let pkg_name = m.package.name;
58 let pkg_authors = m.package.authors.unwrap_or_default();
59 let (major, minor, patch, pre, build) = {
60 let p = m.package.version;
61 (p.major, p.minor, p.patch, p.pre.to_string(), p.build.to_string())
62 };
63 let description = m.package.description.unwrap_or_default();
64 let homepage = m.package.homepage.unwrap_or_default();
65 let license = m.package.license.unwrap_or_default();
66 let mut app = "";
67 let mut org = "";
68 let mut app_id = String::new();
69 let mut has_about = false;
70 let mut meta_keys = vec![];
71 let mut meta_values = vec![];
72 if let Some(zng) = m.package.metadata.as_ref().and_then(|m| m.zng.as_ref())
73 && !zng.about.is_empty()
74 {
75 let s = |key: &str| match zng.about.get(key) {
76 Some(toml::Value::String(s)) => s.as_str(),
77 _ => "",
78 };
79 has_about = true;
80 app = s("app");
81 org = s("org");
82 app_id = clean_id(s("app_id"));
83 for (k, v) in &zng.about {
84 if let toml::Value::String(v) = v
85 && !["app", "org", "app_id"].contains(&k.as_str())
86 {
87 meta_keys.push(k);
88 meta_values.push(v);
89 }
90 }
91 }
92 if app.is_empty() {
93 app = &pkg_name;
94 }
95 if org.is_empty() {
96 org = pkg_authors.first().map(|s| s.as_str()).unwrap_or_default();
97 }
98 if app_id.is_empty() {
99 let qualifier = meta_keys
100 .iter()
101 .position(|k| k.as_str() == "qualifier")
102 .map(|i| meta_values[i].as_str())
103 .unwrap_or_default();
104 app_id = clean_id(&format!("{}.{}.{}", qualifier, org, app));
105 }
106
107 quote! {
123 #crate_::init(#crate_::About::macro_new(
124 #pkg_name,
125 &[#(#pkg_authors),*],
126 (#major, #minor, #patch, #pre, #build),
127 #app_id,
128 #app,
129 #org,
130 #description,
131 #homepage,
132 #license,
133 #has_about,
134 &[#( (#meta_keys, #meta_values) ),*],
135 cfg!(test),
136 ))
137 }
138 .into()
139}
140
141fn clean_id(raw: &str) -> String {
146 let mut r = String::new();
147 let mut sep = "";
148 for i in raw.split('.') {
149 let i = i.trim();
150 if i.is_empty() {
151 continue;
152 }
153 r.push_str(sep);
154 for (i, c) in i.trim().char_indices() {
155 if i == 0 {
156 if !c.is_ascii_alphabetic() {
157 r.push('i');
158 } else {
159 r.push(c.to_ascii_lowercase());
160 }
161 } else if c.is_ascii_alphanumeric() || c == '_' {
162 r.push(c.to_ascii_lowercase());
163 } else {
164 r.push('_');
165 }
166 }
167 sep = ".";
168 }
169 r
170}
171
172#[derive(serde::Deserialize)]
173struct Manifest {
174 package: Package,
175}
176#[derive(serde::Deserialize)]
177struct Package {
178 name: String,
179 version: Version,
180 description: Option<String>,
181 homepage: Option<String>,
182 license: Option<String>,
183 authors: Option<Box<[String]>>,
184 metadata: Option<Metadata>,
185}
186#[derive(serde::Deserialize)]
187struct Metadata {
188 zng: Option<Zng>,
189}
190#[derive(serde::Deserialize)]
191struct Zng {
192 about: toml::Table,
193}
194
195#[doc(hidden)]
196#[proc_macro]
197pub fn wasm_process_start(crate_closure: TokenStream) -> TokenStream {
199 use quote::TokenStreamExt as _;
200
201 let crate_closure = proc_macro2::TokenStream::from(crate_closure);
202 let mut crate_ = proc_macro2::TokenStream::new();
203 let mut crate_ok = false;
204 let mut closure = proc_macro2::TokenStream::new();
205
206 for tt in crate_closure {
207 if crate_ok {
208 closure.append(tt);
209 } else if matches!(&tt, proc_macro2::TokenTree::Punct(p) if p.as_char() == ',') {
210 crate_ok = true;
211 } else {
212 crate_.append(tt);
213 }
214 }
215
216 use sha2::Digest;
217 let mut start_ident = sha2::Sha256::new();
218 start_ident.update(closure.to_string().as_bytes());
219 let start_ident = format!("__zng_env_start_{:x}", start_ident.finalize());
220 let start_ident = proc_macro2::Ident::new(&start_ident, proc_macro2::Span::call_site());
221
222 quote! {
223 #[doc(hidden)]
224 #[#crate_::wasm_bindgen]
225 pub fn #start_ident() {
226 #crate_::WASM_INIT.with_borrow_mut(|v| {
227 v.push(_on_process_start);
228 })
229 }
230 fn _on_process_start(args: &#crate_::ProcessStartArgs) {
231 fn on_process_start(args: &#crate_::ProcessStartArgs, handler: impl FnOnce(&#crate_::ProcessStartArgs)) {
232 handler(args)
233 }
234 on_process_start(args, #closure)
235 }
236 }
237 .into()
238}