Skip to main content

sofie_check/
screens.rs

1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3use std::{fs, io};
4
5use sofie_engine::{ActionPost, Binding, Components, Template, TemplateError};
6use sofie_model::Module;
7
8/// Read and parsed once per run, then borrowed by every rule and generator.
9pub struct Screens {
10    by_file: HashMap<PathBuf, io::Result<Screen>>,
11    components: Components,
12    /// A `web/` component that cannot be read or added, with why.
13    refused_components: Vec<(PathBuf, String)>,
14}
15
16pub struct Screen {
17    pub source: String,
18    pub template: Result<Template, TemplateError>,
19    pub bindings: Result<Vec<Binding>, TemplateError>,
20    pub actions: Result<Vec<ActionPost>, TemplateError>,
21}
22
23impl Screens {
24    #[must_use]
25    pub fn read(modules: &[Module]) -> Self {
26        let (components, refused_components) = read_components(modules);
27        let files = modules
28            .iter()
29            .flat_map(|module| &module.areas)
30            .flat_map(|area| {
31                let overrides = area.overrides.iter().map(|replacement| &replacement.file);
32                area.screens.iter().chain(overrides)
33            });
34        let by_file = files
35            .map(|file| (file.clone(), Screen::read(file, &components)))
36            .collect();
37        Self {
38            by_file,
39            components,
40            refused_components,
41        }
42    }
43
44    /// # Panics
45    ///
46    /// When `file` is no screen or override of the app the screens were read from.
47    #[must_use]
48    pub fn screen(&self, file: &Path) -> &io::Result<Screen> {
49        self.by_file.get(file).unwrap_or_else(|| {
50            panic!(
51                "{} is not a screen of the app the screens were read from",
52                file.display()
53            )
54        })
55    }
56
57    #[must_use]
58    pub fn components(&self) -> &Components {
59        &self.components
60    }
61
62    #[must_use]
63    pub fn refused_components(&self) -> &[(PathBuf, String)] {
64        &self.refused_components
65    }
66}
67
68fn read_components(modules: &[Module]) -> (Components, Vec<(PathBuf, String)>) {
69    let mut components = Components::default();
70    let mut refused = Vec::new();
71    for file in modules.iter().flat_map(|module| &module.components) {
72        let name = file
73            .file_stem()
74            .map(|stem| stem.to_string_lossy().into_owned())
75            .unwrap_or_default();
76        let added = fs::read_to_string(file)
77            .map_err(|error| format!("the component cannot be read: {error}"))
78            .and_then(|source| components.add(&name, source).map_err(|error| error.message));
79        if let Err(message) = added {
80            refused.push((file.clone(), message));
81        }
82    }
83    (components, refused)
84}
85
86impl Screen {
87    fn read(file: &Path, components: &Components) -> io::Result<Self> {
88        let source = fs::read_to_string(file)?;
89        let name = file.display().to_string();
90        Ok(Self {
91            template: Template::parse_with(&name, &source, components),
92            bindings: sofie_engine::bindings_with(&name, &source, components),
93            actions: sofie_engine::action_posts_with(&name, &source, components),
94            source,
95        })
96    }
97}