tour_parser/
metadata.rs

1//! The [`Metadata`] struct.
2use std::{borrow::Cow, fs::read_to_string, rc::Rc};
3use syn::*;
4
5use crate::{
6    common::{error, path},
7    config::Config,
8    syntax::LayoutTempl,
9};
10
11mod attribute;
12
13use attribute::AttrVisitor;
14
15// ===== Metadata =====
16
17/// Extra information declared outside template file.
18#[derive(Debug)]
19pub struct Metadata {
20    path: Rc<str>,
21    source: Option<Rc<str>>,
22    reload: Reload,
23    block: Option<Ident>,
24    kind: TemplKind,
25}
26
27#[derive(Debug)]
28pub enum TemplKind {
29    Main,
30    MainWrapper,
31    Layout,
32    Import,
33}
34
35impl std::fmt::Display for TemplKind {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        match self {
38            Self::Main => write!(f, "Main"),
39            Self::MainWrapper => write!(f, "MainWrapper"),
40            Self::Layout => write!(f, "Layout"),
41            Self::Import => write!(f, "Import"),
42        }
43    }
44}
45
46impl Metadata {
47    /// Create metadata by parsing [`Attribute`].
48    pub fn from_attrs(attrs: &[Attribute], conf: &Config) -> Result<Metadata> {
49        AttrVisitor::parse(attrs, conf)
50    }
51
52    /// Create [`Metadata`] with given path inherited from parent meta.
53    ///
54    /// This will set [`TemplKind`] to [`TemplKind::Import`].
55    pub fn clone_as_import(&self, path: impl AsRef<std::path::Path>) -> Metadata {
56        Self {
57            path: path::resolve_at(path, self.dir_ref()),
58            source: None,
59            reload: self.reload.clone(),
60            block: None,
61            kind: TemplKind::Import,
62        }
63    }
64
65    /// Generate layout [`Metadata`] inherited from parent meta.
66    ///
67    /// This will set [`TemplKind`] to [`TemplKind::Layout`].
68    pub fn clone_as_layout(&self, layout: &LayoutTempl) -> Metadata {
69        Self {
70            path: path::resolve_at(layout.path.value(), self.dir_ref()),
71            source: None,                // there is no inline layout
72            reload: self.reload.clone(), // layout specific reload seems redundant
73            block: None,                 // allows select block for a layout ?
74            kind: TemplKind::Layout,
75        }
76    }
77
78    /// Returns inlined source or read source from filesystem.
79    pub fn resolve_source(&self) -> Result<Cow<'_, str>> {
80        match self.source.as_deref() {
81            Some(src) => Ok(src.into()),
82            None => Ok(error!(
83                !read_to_string(&*self.path),
84                "cannot read `{}`: {}", self.path
85            )
86            .into()),
87        }
88    }
89
90    pub(crate) fn dir_ref(&self) -> &std::path::Path {
91        std::path::Path::new(&*self.path)
92            .parent()
93            .unwrap_or(std::path::Path::new("/"))
94    }
95
96    /// Returns `true` if template is a file, not inlined.
97    ///
98    /// Internally, its just check if the file exists.
99    pub(crate) fn is_file(&self) -> bool {
100        std::path::Path::new(&*self.path).is_file()
101    }
102
103    /// Returns selected block name, if any.
104    pub fn block(&self) -> Option<&Ident> {
105        self.block.as_ref()
106    }
107
108    /// Returns template directory, if source is inlined, returns current dir.
109    pub fn path(&self) -> &str {
110        &self.path
111    }
112
113    /// Returns [`Reload`] behavior.
114    pub fn reload(&self) -> &Reload {
115        &self.reload
116    }
117
118    /// Returns the [`TemplKind`].
119    pub fn kind(&self) -> &TemplKind {
120        &self.kind
121    }
122
123    /// Returns template source if its inlined.
124    pub fn inline(&self) -> Option<&str> {
125        self.source.as_deref()
126    }
127}
128
129// ===== Reload =====
130
131/// Runtime template reload behavior.
132#[derive(Clone)]
133pub enum Reload {
134    Debug,
135    Always,
136    Never,
137    Expr(Rc<Expr>),
138}
139
140impl Default for Reload {
141    fn default() -> Self {
142        if cfg!(feature = "dev-reload") {
143            Reload::Debug
144        } else {
145            Reload::Never
146        }
147    }
148}
149
150impl Reload {
151    /// Returns `Ok(true)` if runtime reload is enabled, otherwise returns `Err(expr)` containing
152    /// user defined expression to decide runtime reload.
153    pub fn as_bool(&self) -> std::result::Result<bool, &Expr> {
154        match self {
155            Reload::Debug => Ok(cfg!(debug_assertions)),
156            Reload::Always => Ok(true),
157            Reload::Never => Ok(false),
158            Reload::Expr(expr) => Err(expr),
159        }
160    }
161}
162
163impl std::fmt::Debug for Reload {
164    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165        match self {
166            Self::Debug => write!(f, "Reload::Debug"),
167            Self::Always => write!(f, "Reload::Always"),
168            Self::Never => write!(f, "Reload::Never"),
169            Self::Expr(_) => write!(f, "Reload::<Expr>"),
170        }
171    }
172}
173