polysite/builder/
rule.rs

1use super::compile::CompileRunner;
2use crate::*;
3use glob::glob;
4use std::path::PathBuf;
5use tracing_error::SpanTrace;
6
7/// The [`Rule`] is used to define the rule name, source files, [`Version`], and the [`Compiler`] used for building.
8/// The results of the compilation are saved in the [`Metadata`], using the rule's name as the key.
9pub struct Rule {
10    name: String,
11    globs: Option<Vec<String>>,
12    creates: Option<Vec<String>>,
13    compiler: Box<dyn Compiler>,
14    version: Version,
15}
16
17impl Rule {
18    pub fn new(name: impl AsRef<str>, compiler: impl Compiler + 'static) -> Self {
19        let name = name.as_ref().to_owned();
20        Rule {
21            name,
22            globs: None,
23            creates: None,
24            compiler: Box::new(compiler),
25            version: Version::default(),
26        }
27    }
28
29    /// Get this rule's name
30    pub fn get_name(&self) -> &str {
31        &self.name
32    }
33
34    /// Set a list of glob patterns to compile.
35    pub fn set_globs(mut self, globs: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
36        self.globs = Some(globs.into_iter().map(|s| s.as_ref().to_owned()).collect());
37        self
38    }
39    /// Set a list of source file names to create.
40    pub fn set_create(mut self, create: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
41        self.creates = Some(create.into_iter().map(|s| s.as_ref().to_owned()).collect());
42        self
43    }
44
45    /// Set compilation [`Version`]
46    pub fn set_version(mut self, version: impl Into<Version>) -> Self {
47        self.version = version.into();
48        self
49    }
50
51    /// Do compilation task
52    #[tracing::instrument(skip(self, ctx))]
53    pub(crate) async fn compile(self, ctx: Context) -> Result<Context, Error> {
54        let src_dir = ctx.config().source_dir();
55        let paths: Vec<_> = match (&self.globs, &self.creates) {
56            (Some(globs), None) => {
57                let globs = globs
58                    .iter()
59                    .map(|g| src_dir.join(PathBuf::from(g)).to_string_lossy().to_string());
60                let paths = globs
61                    .map(|g| glob(&g))
62                    .collect::<Result<Vec<_>, _>>()
63                    .map_err(|_| Error::InvalidRule {
64                        trace: SpanTrace::capture(),
65                    })?
66                    .into_iter()
67                    .map(|p| p.collect::<Result<Vec<_>, _>>())
68                    .collect::<Result<Vec<_>, _>>()
69                    .map_err(|_| Error::InvalidRule {
70                        trace: SpanTrace::capture(),
71                    })?
72                    .into_iter()
73                    .flatten()
74                    .filter(|p| p.is_file())
75                    .collect();
76                paths
77            }
78            (None, Some(paths)) => {
79                let paths = paths
80                    .iter()
81                    .map(|p| src_dir.join(PathBuf::from(p)))
82                    .collect();
83                paths
84            }
85            _ => {
86                return Err(Error::InvalidRule {
87                    trace: SpanTrace::capture(),
88                })
89            }
90        };
91        let version_files: Option<Vec<_>> = ctx
92            .metadata()
93            .read_lock()
94            .await
95            .get_version(&self.version)
96            .map(|v| v.into_iter().map(|(source, _)| source).collect());
97        let paths: Vec<_> = paths
98            .into_iter()
99            .filter(|p| {
100                version_files
101                    .as_ref()
102                    .map(|v| {
103                        v.iter()
104                            .filter(|w| &**w == &*p.to_string_lossy())
105                            .next()
106                            .is_none()
107                    })
108                    .unwrap_or(true)
109            })
110            .collect();
111
112        let name = self.get_name().to_owned();
113        let src_dir = ctx.config().source_dir();
114        let target_dir = ctx.config().target_dir();
115        let runner = CompileRunner::new(name, self.version, ctx, self.compiler);
116        for source in paths {
117            let path = source.strip_prefix(&src_dir).unwrap_or(&source);
118            let target = target_dir.join(path);
119            let path = PathBuf::from("/").join(path);
120            runner.spawn_compile(source, target, path).await;
121        }
122        let ctx = runner.join().await?;
123        Ok(ctx)
124    }
125}