1#![doc = include_str!("../README.md")]
2
3mod error;
4mod exports;
5mod files;
6mod imports;
7mod module;
8mod result;
9mod source;
10
11use std::{fs::File, io::Read, path::PathBuf};
12
13use files::AbsoluteRustFilePathBuf;
14use quote::ToTokens;
15use source::Sourcecode;
16use syn::token::Brace;
17
18fn find_me(root: &str, pattern: &str) -> Option<PathBuf> {
20 let mut options = Vec::new();
21
22 for path in glob::glob(&std::path::Path::new(root).join("**/*.rs").to_string_lossy())
23 .unwrap()
24 .flatten()
25 {
26 if let Ok(mut f) = File::open(&path) {
27 let mut contents = String::new();
28 f.read_to_string(&mut contents).ok();
29 if contents.contains(pattern) {
30 options.push(path.to_owned());
31 }
32 }
33 }
34
35 match options.as_slice() {
36 [] => None,
37 [v] => Some(v.clone()),
38 _ => panic!(
39 "found more than one contender for macro invocation location. \
40 This won't be an issue once `proc_macro_span` is stabilized, \
41 but until then each instance of the `include_wgsl_oil` \
42 must be present in the source text, and each must have a unique argument. \
43 found locations: {:?}",
44 options
45 .into_iter()
46 .map(|path| format!("`{}`", path.display()))
47 .collect::<Vec<String>>()
48 ),
49 }
50}
51
52#[proc_macro_attribute]
53pub fn include_wgsl_oil(
54 path: proc_macro::TokenStream,
55 module: proc_macro::TokenStream,
56) -> proc_macro::TokenStream {
57 let mut module = syn::parse_macro_input!(module as syn::ItemMod);
59 if let Some(content) = &mut module.content {
60 if !content.1.is_empty() {
61 let item = syn::parse_quote_spanned! {content.0.span=>
62 compile_error!(
63 "`include_wgsl_oil` expects an empty module into which to inject the shader objects, \
64 but found a module body - try removing everything within the curly braces `{ ... }`.");
65 };
66
67 module.content = Some((Brace::default(), vec![item]));
68 }
69 } else {
70 module.content = Some((Brace::default(), vec![]));
71 }
72 module.semi = None;
73
74 let requested_path = syn::parse_macro_input!(path as syn::LitStr);
75 let requested_path = requested_path.value();
76
77 let root = std::env::var("CARGO_MANIFEST_DIR").expect("proc macros should be run using cargo");
78 let invocation_path = match find_me(&root, &format!("\"{}\"", requested_path)) {
79 Some(invocation_path) => AbsoluteRustFilePathBuf::new(invocation_path),
80 None => {
81 panic!(
82 "could not find invocation point - maybe it was in a macro? This won't be an issue once \
83 `proc_macro_span` is stabilized, but until then each instance of the `include_wgsl_oil` \
84 must be present in the source text, and each must have a unique argument."
85 )
86 }
87 };
88
89 let sourcecode = Sourcecode::new(invocation_path, requested_path);
90
91 let mut result = sourcecode.complete();
92
93 result.validate();
94
95 module
97 .content
98 .as_mut()
99 .expect("set to some at start")
100 .1
101 .append(&mut result.items());
102
103 module.to_token_stream().into()
104}