#![warn(missing_docs)]
#![doc = include_str!("../README.md")]
extern crate proc_macro;
use litrs::{Literal, StringLit};
use proc_macro2::TokenTree;
use quote::quote;
use std::path::PathBuf;
fn quote_bytes(bytes: &[u8]) -> proc_macro2::TokenStream {
let bytes_lit = proc_macro2::Literal::byte_string(bytes);
quote! {
{{
#[repr(align(16))]
#[doc(hidden)]
struct __GVDB_Aligned<T: ?Sized>(T);
#[doc(hidden)]
static __GVDB_DATA: &'static __GVDB_Aligned<[u8]> = &__GVDB_Aligned(*#bytes_lit);
&__GVDB_DATA.0
}}
}
}
fn include_gresource_from_xml_with_filename(filename: &str) -> proc_macro2::TokenStream {
let path = PathBuf::from(filename);
let xml = gvdb::gresource::GResourceXMLDocument::from_file(&path).unwrap();
let builder = gvdb::gresource::GResourceBuilder::from_xml(xml).unwrap();
let data = builder.build().unwrap();
quote_bytes(&data)
}
fn include_gresource_from_xml_inner(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
let mut iter = input.into_iter();
let first = iter
.next()
.expect("Expected exactly one string literal argument (gresource file location)");
let second = iter.next();
if let Some(second) = second {
panic!("Unexpected token '{}', expected exactly one string literal argument (gresource file location)", second)
}
match Literal::try_from(first) {
Err(e) => proc_macro2::TokenStream::from(e.to_compile_error()),
Ok(Literal::String(str)) => {
include_gresource_from_xml_with_filename(str.value())
}
Ok(other) => panic!("Unexpected token '{:?}', expected exactly one string literal argument (gresource file location)", other)
}
}
#[proc_macro]
pub fn include_gresource_from_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = proc_macro2::TokenStream::from(input);
let output = include_gresource_from_xml_inner(input);
proc_macro::TokenStream::from(output)
}
fn include_gresource_from_dir_str(prefix: &str, directory: &str) -> proc_macro2::TokenStream {
let path = PathBuf::from(directory);
let builder =
gvdb::gresource::GResourceBuilder::from_directory(prefix, &path, true, true).unwrap();
let data = builder.build().unwrap();
quote_bytes(&data)
}
fn include_gresource_from_dir_inner(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
let err_msg = "expected exactly two string literal arguments (prefix, gresource directory)";
let (prefix, directory) = match &*input.into_iter().collect::<Vec<_>>() {
[TokenTree::Literal(str1), TokenTree::Punct(comma), TokenTree::Literal(str2)] => {
if comma.as_char() != ',' {
panic!("{}", err_msg);
}
(
StringLit::try_from(str1).expect(err_msg),
StringLit::try_from(str2).expect(err_msg),
)
}
_ => panic!("{}", err_msg),
};
include_gresource_from_dir_str(prefix.value(), directory.value())
}
#[proc_macro]
pub fn include_gresource_from_dir(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = proc_macro2::TokenStream::from(input);
let output = include_gresource_from_dir_inner(input);
proc_macro::TokenStream::from(output)
}
#[cfg(test)]
mod tests {
use super::*;
use quote::quote;
#[test]
fn include_gresource_from_xml() {
let tokens =
include_gresource_from_xml_inner(quote! {"test-data/gresource/test3.gresource.xml"});
assert!(tokens.to_string().contains(r#"b"GVariant"#));
}
#[test]
#[should_panic]
fn include_gresource_from_xml_panic() {
include_gresource_from_xml_inner(quote! {4});
}
#[test]
#[should_panic]
fn include_gresource_from_xml_panic2() {
include_gresource_from_xml_inner(quote! { "test", 4 });
}
#[test]
#[should_panic]
fn include_gresource_from_xml_panic3() {
include_gresource_from_xml_inner(quote! { test });
}
#[test]
fn include_gresource_from_dir() {
let tokens =
include_gresource_from_dir_inner(quote! {"/gvdb/rs/test", "test-data/gresource"});
assert!(tokens.to_string().contains(r#"b"GVariant"#));
}
#[test]
#[should_panic]
fn include_gresource_from_dir_panic1() {
include_gresource_from_dir_inner(quote! {"/gvdb/rs/test",});
}
#[test]
#[should_panic]
fn include_gresource_from_dir_panic2() {
include_gresource_from_dir_inner(quote! {"/gvdb/rs/test"});
}
#[test]
#[should_panic]
fn include_gresource_from_dir_panic3() {
include_gresource_from_dir_inner(quote! {"/gvdb/rs/test","bla","bla"});
}
#[test]
#[should_panic]
fn include_gresource_from_dir_panic4() {
include_gresource_from_dir_inner(quote! {"/gvdb/rs/test","INVALID_DIRECTORY"});
}
#[test]
#[should_panic]
fn include_gresource_from_dir_panic5() {
include_gresource_from_dir_inner(quote! {"/gvdb/rs/test"."test-data/gresource"});
}
}