1#![warn(missing_docs)]
22#![doc = include_str!("../README.md")]
23
24extern crate proc_macro;
25
26use litrs::{Literal, StringLit};
27use proc_macro2::TokenTree;
28use quote::quote;
29use std::path::PathBuf;
30
31fn quote_bytes(bytes: &[u8]) -> proc_macro2::TokenStream {
32 let bytes_lit = proc_macro2::Literal::byte_string(bytes);
33
34 quote! {
35 {{
36 #[repr(align(16))]
37 #[doc(hidden)]
38 struct __GvdbAligned<T: ?Sized>(T);
39 #[doc(hidden)]
40 static __GVDB_DATA: &'static __GvdbAligned<[u8]> = &__GvdbAligned(*#bytes_lit);
41
42 &__GVDB_DATA.0
43 }}
44 }
45}
46
47fn include_gresource_from_xml_with_filename(filename: &str) -> proc_macro2::TokenStream {
48 let path = PathBuf::from(filename);
49 let xml = gvdb::gresource::XmlManifest::from_file(&path).unwrap();
50 let builder = gvdb::gresource::BundleBuilder::from_xml(xml).unwrap();
51 let data = builder.build().unwrap();
52
53 quote_bytes(&data)
54}
55
56fn include_gresource_from_xml_inner(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
57 let mut iter = input.into_iter();
58
59 let first = iter
60 .next()
61 .expect("Expected exactly one string literal argument (gresource file location)");
62 let second = iter.next();
63 if let Some(second) = second {
64 panic!(
65 "Unexpected token '{second}', expected exactly one string literal argument (gresource file location)"
66 )
67 }
68
69 match Literal::try_from(first) {
70 Err(e) => proc_macro2::TokenStream::from(e.to_compile_error()),
71 Ok(Literal::String(str)) => include_gresource_from_xml_with_filename(str.value()),
72 Ok(other) => panic!(
73 "Unexpected token '{other:?}', expected exactly one string literal argument (gresource file location)"
74 ),
75 }
76}
77
78#[proc_macro]
85pub fn include_gresource_from_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
86 let input = proc_macro2::TokenStream::from(input);
87 let output = include_gresource_from_xml_inner(input);
88 proc_macro::TokenStream::from(output)
89}
90
91fn include_gresource_from_dir_str(prefix: &str, directory: &str) -> proc_macro2::TokenStream {
92 let path = PathBuf::from(directory);
93 let builder =
94 gvdb::gresource::BundleBuilder::from_directory(prefix, &path, true, true).unwrap();
95 let data = builder.build().unwrap();
96
97 quote_bytes(&data)
98}
99
100fn include_gresource_from_dir_inner(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
101 let err_msg = "expected exactly two string literal arguments (prefix, gresource directory)";
102 let (prefix, directory) = match &*input.into_iter().collect::<Vec<_>>() {
103 [
104 TokenTree::Literal(str1),
105 TokenTree::Punct(comma),
106 TokenTree::Literal(str2),
107 ] => {
108 if comma.as_char() != ',' {
109 panic!("{}", err_msg);
110 }
111
112 (
113 StringLit::try_from(str1).expect(err_msg),
114 StringLit::try_from(str2).expect(err_msg),
115 )
116 }
117 _ => panic!("{}", err_msg),
118 };
119
120 include_gresource_from_dir_str(prefix.value(), directory.value())
121}
122
123#[proc_macro]
153pub fn include_gresource_from_dir(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
154 let input = proc_macro2::TokenStream::from(input);
155 let output = include_gresource_from_dir_inner(input);
156 proc_macro::TokenStream::from(output)
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use quote::quote;
163
164 #[test]
165 fn include_gresource_from_xml() {
166 let tokens =
167 include_gresource_from_xml_inner(quote! {"test-data/gresource/test3.gresource.xml"});
168 assert!(tokens.to_string().contains(r#"b"GVariant"#));
169 }
170
171 #[test]
172 #[should_panic]
173 fn include_gresource_from_xml_panic() {
174 include_gresource_from_xml_inner(quote! {4});
175 }
176
177 #[test]
178 #[should_panic]
179 fn include_gresource_from_xml_panic2() {
180 include_gresource_from_xml_inner(quote! { "test", 4 });
181 }
182
183 #[test]
184 #[should_panic]
185 fn include_gresource_from_xml_panic3() {
186 include_gresource_from_xml_inner(quote! { test });
187 }
188
189 #[test]
190 fn include_gresource_from_dir() {
191 let tokens =
192 include_gresource_from_dir_inner(quote! {"/gvdb/rs/test", "test-data/gresource"});
193 assert!(tokens.to_string().contains(r#"b"GVariant"#));
194 }
195
196 #[test]
197 #[should_panic]
198 fn include_gresource_from_dir_panic1() {
199 include_gresource_from_dir_inner(quote! {"/gvdb/rs/test",});
200 }
201
202 #[test]
203 #[should_panic]
204 fn include_gresource_from_dir_panic2() {
205 include_gresource_from_dir_inner(quote! {"/gvdb/rs/test"});
206 }
207
208 #[test]
209 #[should_panic]
210 fn include_gresource_from_dir_panic3() {
211 include_gresource_from_dir_inner(quote! {"/gvdb/rs/test","bla","bla"});
212 }
213
214 #[test]
215 #[should_panic]
216 fn include_gresource_from_dir_panic4() {
217 include_gresource_from_dir_inner(quote! {"/gvdb/rs/test","INVALID_DIRECTORY"});
218 }
219
220 #[test]
221 #[should_panic]
222 fn include_gresource_from_dir_panic5() {
223 include_gresource_from_dir_inner(quote! {"/gvdb/rs/test"."test-data/gresource"});
224 }
225}