tour_parser/
common.rs

1//! Common utility.
2
3// ===== Namespace =====
4
5/// `ToTokens` for public name
6pub(crate) struct TemplDisplay;
7
8impl quote::ToTokens for TemplDisplay {
9    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
10        quote::quote! {::tour::TemplDisplay}.to_tokens(tokens);
11    }
12}
13
14/// `ToTokens` for public name
15pub(crate) struct TemplWrite;
16
17impl quote::ToTokens for TemplWrite {
18    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
19        quote::quote! {::tour::TemplWrite}.to_tokens(tokens);
20    }
21}
22
23// ===== Constants =====
24
25/// Attribute namespace for derive macro.
26pub const DERIVE_ATTRIBUTE: &str = "template";
27
28/// Reserved block name containing template without the layout.
29pub const INNER_BLOCK: &str = "inner";
30
31pub(crate) fn name() -> syn::Ident {
32    use std::sync::atomic::{AtomicUsize, Ordering};
33    static COUNTER: AtomicUsize = AtomicUsize::new(0);
34    let c = COUNTER.fetch_add(1, Ordering::Relaxed);
35    quote::format_ident!("TourNS{c}")
36}
37
38/// Path resolution.
39///
40/// user given path:
41///
42/// - `./layout`, resolve relative from current source file
43/// - `layout`, resolve from `templates` directory
44/// - `/layout`, resolve from current directory
45///
46/// currently, rust is unable to get rust source file path,
47/// for now, relative path in attribute returns error.
48///
49/// [issue]: <https://github.com/rust-lang/rust/issuze/54725>
50pub(crate) mod path {
51    use std::{path::{Path, PathBuf}, rc::Rc};
52
53    use super::error;
54    use crate::config::Config;
55
56    pub fn cwd() -> PathBuf {
57        std::env::current_dir().expect("current dir")
58    }
59
60    pub fn boxed(buf: PathBuf) -> Rc<str> {
61        buf.to_string_lossy().into()
62    }
63
64    pub fn resolve(mut path: &str, conf: &Config) -> syn::Result<Rc<str>> {
65        let mut cwd = cwd();
66        match () {
67            _ if path.starts_with(".") => error!("cannot get template file using relative path"),
68            _ if path.starts_with("/") => path = path.trim_start_matches('/'),
69            _ => cwd.push(conf.templ_dir()),
70        };
71        Ok(resolve_at(path, cwd))
72    }
73
74    /// resolve path relative to given directory
75    pub fn resolve_at(path: impl AsRef<Path>, cwd: impl Into<PathBuf>) -> Rc<str> {
76        let path = path.as_ref();
77        let mut cwd = match () {
78            _ if path.starts_with("/") => self::cwd(),
79            _ => cwd.into(),
80        };
81        cwd.push(match path.strip_prefix("/") {
82            Ok(path) => path,
83            Err(_) => path,
84        });
85        normalize(cwd.as_path())
86            .to_string_lossy()
87            .into()
88    }
89
90    /// Copied from [cargo][1]
91    ///
92    /// [1]: https://github.com/rust-lang/cargo/blob/fede83ccf973457de319ba6fa0e36ead454d2e20/src/cargo/util/paths.rs#L61
93    pub fn normalize(path: &Path) -> PathBuf {
94        use std::path::Component;
95        let mut components = path.components().peekable();
96        let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
97            components.next();
98            PathBuf::from(c.as_os_str())
99        } else {
100            PathBuf::new()
101        };
102
103        for component in components {
104            match component {
105                Component::Prefix(..) => unreachable!(),
106                Component::RootDir => {
107                    ret.push(component.as_os_str());
108                }
109                Component::CurDir => {}
110                Component::ParentDir => {
111                    ret.pop();
112                }
113                Component::Normal(c) => {
114                    ret.push(c);
115                }
116            }
117        }
118        ret
119    }
120}
121
122// ===== macros =====
123
124/// Everything will return `Result<T, syn::Error>`
125///
126/// `error!(?option, "{}", error)`, unwrap option with error as standard `format!`.
127///
128/// `error!(!result, "{}", error)`, unwrap result with context.
129///
130/// `error!(!result)`, unwrap result.
131///
132/// `error!(attr, "`{path}`: {}")`, standard `format!` with `attr`s span.
133///
134/// `error!("{}",error)`, standard `format!`
135macro_rules! error {
136    (@ $s:expr, $($tt:tt)*) => {
137        return Err(syn::Error::new($s, format!($($tt)*)))
138    };
139    (dbg $($tt:tt)*) => {{
140        let me = $($tt)*;
141        panic!("{:?}", me);
142        me
143    }};
144    (.$s:expr, $($tt:tt)*) => {
145        if $s { crate::common::error!($($tt)*) }
146    };
147    (?$s:expr, $($tt:tt)*) => {
148        match $s { Some(ok) => ok, None => crate::common::error!($($tt)*), }
149    };
150    (!$s:expr, $($tt:tt)*) => {
151        match $s { Ok(ok) => ok, Err(err) => crate::common::error!(@proc_macro2::Span::call_site(), $($tt)*, err), }
152    };
153    (!$s:expr) => {
154        match $s { Ok(ok) => ok, Err(err) => crate::common::error!("{err}"), }
155    };
156    ($msg:literal, $($tt:tt)*) => {
157        crate::common::error!(@ proc_macro2::Span::call_site(), $msg, $($tt)*)
158    };
159    ($s:expr, $($tt:tt)*) => {
160        crate::common::error!(@ $s.span(), $($tt)*)
161    };
162    ($($tt:tt)*) => {
163        crate::common::error!(@ proc_macro2::Span::call_site(), $($tt)*)
164    };
165}
166
167pub(crate) use error;