diff --git a/maud/src/lib.rs b/maud/src/lib.rs index 73ac24f5..137d8cbf 100644 --- a/maud/src/lib.rs +++ b/maud/src/lib.rs @@ -14,7 +14,7 @@ extern crate alloc; use alloc::{borrow::Cow, boxed::Box, string::String}; use core::fmt::{self, Arguments, Write}; -pub use maud_macros::{html, html_debug}; +pub use maud_macros::{html, html_debug, html_file}; mod escape; diff --git a/maud_macros/src/lib.rs b/maud_macros/src/lib.rs index 7323459c..3370561f 100644 --- a/maud_macros/src/lib.rs +++ b/maud_macros/src/lib.rs @@ -11,8 +11,18 @@ mod generate; mod parse; use proc_macro2::{Ident, Span, TokenStream, TokenTree}; -use proc_macro_error::proc_macro_error; +use proc_macro_error::{abort_call_site, proc_macro_error}; use quote::quote; +use std::{ + env, + ffi::OsStr, + fmt::Display, + fs::File, + io::Read, + path::{Path, PathBuf}, + str::FromStr, +}; +use syn::{parse_macro_input, LitStr}; #[proc_macro] #[proc_macro_error] @@ -28,6 +38,40 @@ pub fn html_debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream { expr.into() } +#[proc_macro] +#[proc_macro_error] +pub fn html_file(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let orig_path = parse_macro_input!(input as LitStr); + let root = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string()); + let path = Path::new(&root).join(&orig_path.value()); + + let file_name = path.file_name().unwrap_or_else(|| OsStr::new("unknown")); + + let mut file = abort_on_error(file_name, "error while opening", || { + File::open(>::as_ref(&path)) + }); + let mut file_contents = String::new(); + abort_on_error(file_name, "error while reading", || { + file.read_to_string(&mut file_contents) + }); + + expand(abort_on_error(file_name, "error while parsing", || { + TokenStream::from_str(&file_contents) + })) + .into() +} + +fn abort_on_error(file_name: &OsStr, description: &str, f: F) -> T +where + F: FnOnce() -> Result, + E: Display, +{ + match f() { + Ok(result) => result, + Err(error) => abort_call_site!("{} {:?}: {}", description, file_name, error), + } +} + fn expand(input: TokenStream) -> TokenStream { let output_ident = TokenTree::Ident(Ident::new("__maud_output", Span::mixed_site())); // Heuristic: the size of the resulting markup tends to correlate with the