trillium_include_dir_impl/
lib.rs1use crate::dir::Dir;
6use proc_macro::{TokenStream, TokenTree};
7use quote::{quote, ToTokens};
8use std::{
9 borrow::Cow,
10 env,
11 path::{Path, PathBuf, StripPrefixError},
12 time::{SystemTime, UNIX_EPOCH},
13};
14
15mod dir;
16mod file;
17
18#[derive(Debug)]
19enum Error {
20 Io(std::io::Error),
21 Str(&'static str),
22 StripPrefix(StripPrefixError),
23}
24
25impl std::error::Error for Error {}
26
27impl std::fmt::Display for Error {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 match self {
30 Error::Io(i) => f.write_fmt(format_args!("{}", i)),
31 Error::Str(s) => f.write_str(s),
32 Error::StripPrefix(s) => f.write_fmt(format_args!("{}", s)),
33 }
34 }
35}
36
37impl From<StripPrefixError> for Error {
38 fn from(s: StripPrefixError) -> Self {
39 Self::StripPrefix(s)
40 }
41}
42
43impl From<std::io::Error> for Error {
44 fn from(e: std::io::Error) -> Self {
45 Self::Io(e)
46 }
47}
48
49impl From<&'static str> for Error {
50 fn from(s: &'static str) -> Self {
51 Self::Str(s)
52 }
53}
54
55#[proc_macro]
56pub fn include_dir(input: TokenStream) -> TokenStream {
57 let path = tokens_to_path(input).unwrap();
58
59 Dir::from_disk(&path, &path)
60 .expect("Couldn't load the directory")
61 .to_token_stream()
62 .into()
63}
64
65fn unwrap_string_literal(lit: &proc_macro::Literal) -> Result<String, &'static str> {
66 let mut repr = lit.to_string();
67 if !repr.starts_with('"') || !repr.ends_with('"') {
68 return Err("This macro only accepts a single, non-empty string argument");
69 }
70
71 repr.remove(0);
72 repr.pop();
73
74 Ok(repr)
75}
76
77fn match_path(tokens: Vec<TokenTree>) -> Result<String, &'static str> {
78 match tokens.as_slice() {
79 [TokenTree::Literal(lit)] => unwrap_string_literal(lit),
80
81 [TokenTree::Group(group)] => match_path(group.stream().into_iter().collect()),
82
83 _ => return Err("This macro only accepts a single, non-empty string argument".into()),
84 }
85}
86
87fn tokens_to_path(input: TokenStream) -> Result<PathBuf, Cow<'static, str>> {
88 let path = match_path(input.into_iter().collect())?;
89 let crate_root =
90 env::var("CARGO_MANIFEST_DIR").map_err(|_| "cannot find cargo manifest dir")?;
91 let path = PathBuf::from(crate_root).join(path);
92
93 if !path.exists() {
94 return Err(format!("\"{}\" doesn't exist", path.display()).into());
95 }
96
97 Ok(path
98 .canonicalize()
99 .map_err(|_| "Can't normalize the path")?)
100}
101
102#[proc_macro]
103pub fn try_include_dir(input: TokenStream) -> TokenStream {
104 let path = match tokens_to_path(input) {
105 Ok(path) => path,
106 Err(e) => return quote! { Result::<Dir, &'static str>::Err(#e) }.into(),
107 };
108
109 TokenStream::from(match load_dir(&path) {
110 Ok(dir) => quote! { Result::<Dir, &'static str>::Ok(#dir) },
111 Err(err) => quote! { Result::<Dir, &'static str>::Err(#err) },
112 })
113}
114
115fn load_dir(path: impl AsRef<Path>) -> Result<Dir, String> {
116 let path = path.as_ref();
117
118 Dir::from_disk(&path, &path)
119 .map_err(|e| format!("Couldn't load the directory: {}", e.to_string()))
120}
121
122pub(crate) fn timestamp_to_tokenstream(
123 time: std::io::Result<SystemTime>,
124) -> proc_macro2::TokenStream {
125 time.ok()
126 .and_then(|m| m.duration_since(UNIX_EPOCH).ok())
127 .map(|dur| dur.as_secs_f64())
128 .map(|secs| quote! { Some(#secs) }.to_token_stream())
129 .unwrap_or_else(|| quote! { None }.to_token_stream())
130}