Skip to main content

sofie_engine/
components.rs

1use std::collections::BTreeMap;
2
3use crate::catalog::Kind;
4use crate::error::TemplateError;
5
6/// A module's `web/<Name>.html`, written in a screen as `<Name prop={…} />` and
7/// inlined where it is written, so rendering pays nothing for it.
8#[derive(Clone, Debug, Default)]
9pub struct Components {
10    by_name: BTreeMap<String, String>,
11}
12
13impl Components {
14    /// Fails for a name the catalog already has, one given twice, or one that is not `PascalCase`.
15    pub fn add(&mut self, name: &str, source: String) -> Result<(), TemplateError> {
16        let refused = |message: String| TemplateError::unlocated(name, message);
17        if !is_component_name(name) {
18            return Err(refused(format!(
19                "`{name}` is no component name: name `web/<Name>.html` in PascalCase, such as \
20                 `OrderCard`"
21            )));
22        }
23        if Kind::named(name).is_some() {
24            return Err(refused(format!(
25                "`{name}` is a component the catalog ships; name the template otherwise"
26            )));
27        }
28        if self.by_name.insert(name.to_owned(), source).is_some() {
29            return Err(refused(format!(
30                "component `{name}` is added twice; one `web/{name}.html` per app"
31            )));
32        }
33        Ok(())
34    }
35
36    pub(crate) fn source(&self, name: &str) -> Option<&str> {
37        self.by_name.get(name).map(String::as_str)
38    }
39}
40
41fn is_component_name(name: &str) -> bool {
42    name.starts_with(|first: char| first.is_ascii_uppercase())
43        && name.bytes().all(|byte| byte.is_ascii_alphanumeric())
44}