ninja_files_data2/file/
builder.rs

1use camino::Utf8Path;
2
3use crate::{
4    Build, BuildBuilder, BuildBuilderError, BuildOutput, File, FileName, InvalidFileName,
5    InvalidRuleId, InvalidVariable, InvalidVariableId, Rule, RuleBuilder, RuleBuilderError, RuleId,
6    Variable, VariableId,
7};
8
9#[derive(Clone, Debug, Default)]
10pub struct FileBuilder {
11    file: File,
12    errors: Vec<FileBuilderError>,
13}
14
15#[derive(Clone, Debug)]
16pub enum FileBuilderError {
17    InvalidVariableId(InvalidVariableId),
18    InvalidVariable(InvalidVariable),
19    InvaidRuleId(InvalidRuleId),
20    InvalidFileName(InvalidFileName),
21    InvalidRule {
22        id: RuleId,
23        errors: Vec<RuleBuilderError>,
24    },
25    InvalidBuildOutput {
26        errors: Vec<BuildBuilderError>,
27    },
28    InvalidBuild {
29        id: FileName,
30        errors: Vec<BuildBuilderError>,
31    },
32    RuleAlreadyExists {
33        id: RuleId,
34        previous: Rule,
35        rule: Rule,
36    },
37    BuildAlreadyExists {
38        id: FileName,
39        previous: BuildOutput,
40        build: Build,
41    },
42    VariableAlreadyExists(VariableId),
43}
44
45impl FileBuilder {
46    pub fn new() -> FileBuilder {
47        let file = File::default();
48        let errors = vec![];
49        FileBuilder { file, errors }
50    }
51
52    pub fn build(&self) -> Result<File, Vec<FileBuilderError>> {
53        if self.errors.is_empty() {
54            Ok(self.file.clone())
55        } else {
56            Err(self.errors.clone())
57        }
58    }
59
60    pub fn variable<Id, Var>(mut self, id: Id, var: Var) -> FileBuilder
61    where
62        Id: AsRef<str>,
63        Var: AsRef<str>,
64    {
65        let id = VariableId::try_create(id);
66        let var = Variable::try_create(var);
67
68        match (id, var) {
69            (Ok(id), Ok(var)) => match self.file.variables.insert(id.clone(), var.clone()) {
70                Some(exist) if exist == var => (),
71                Some(_) => self
72                    .errors
73                    .push(FileBuilderError::VariableAlreadyExists(id)),
74                None => (),
75            },
76            (Ok(_), Err(e)) => self.errors.push(FileBuilderError::InvalidVariable(e)),
77            (Err(e), Ok(_)) => self.errors.push(FileBuilderError::InvalidVariableId(e)),
78            (Err(e1), Err(e2)) => {
79                self.errors.push(FileBuilderError::InvalidVariableId(e1));
80                self.errors.push(FileBuilderError::InvalidVariable(e2));
81            }
82        }
83        self
84    }
85
86    pub fn rule<Id, Rule>(mut self, id: Id, rule: Rule) -> FileBuilder
87    where
88        Id: AsRef<str>,
89        Rule: Into<RuleBuilder>,
90    {
91        let id = RuleId::try_create(id);
92        let rule = rule.into().build();
93
94        if let Some(err) = id.clone().err() {
95            self.errors.push(FileBuilderError::InvaidRuleId(err));
96            return self;
97        }
98        let id = id.unwrap();
99        if let Some(err) = rule.clone().err() {
100            self.errors
101                .push(FileBuilderError::InvalidRule { id, errors: err });
102            return self;
103        }
104        let rule = rule.unwrap();
105
106        match self.file.rules.get(&id) {
107            Some(previous) if previous == &rule => (),
108            Some(previous) => self.errors.push(FileBuilderError::RuleAlreadyExists {
109                id,
110                previous: previous.clone(),
111                rule,
112            }),
113            None => {
114                let _ = self.file.rules.insert(id, rule);
115            }
116        }
117
118        self
119    }
120
121    fn output_impl<Output, Build>(mut self, output: Output, build: Build, implicit: bool) -> Self
122    where
123        Output: AsRef<Utf8Path>,
124        Build: Into<BuildBuilder>,
125    {
126        let output = FileName::try_create(output);
127        let build = build.into().build();
128
129        if let Some(e) = output.clone().err() {
130            self.errors.push(FileBuilderError::InvalidFileName(e));
131            return self;
132        }
133        let output = output.unwrap();
134
135        if let Some(errors) = build.clone().err() {
136            self.errors.push(FileBuilderError::InvalidBuild {
137                id: output.clone(),
138                errors,
139            })
140        }
141        let build = build.unwrap();
142
143        match self.file.builds.get_mut(&build) {
144            // Some(previous) if previous.explicits.contains(&output) || previous.implicits.contains(&output) => self.errors.push(FileBuilderError::BuildAlreadyExists {
145            //     id: output,
146            //     previous: previous.clone(),
147            //     build,
148            // }),
149            Some(previous) => {
150                if implicit {
151                    previous.implicits.insert(output.clone());
152                } else {
153                    previous.explicits.insert(output.clone());
154                }
155            }
156            None => {
157                let mut build_output = BuildOutput::default();
158                if implicit {
159                    build_output.implicits.insert(output.clone());
160                } else {
161                    build_output.explicits.insert(output.clone());
162                }
163                let _ = self.file.builds.insert(build, build_output);
164            }
165        }
166        self
167    }
168
169    pub fn output<Output, Build>(self, output: Output, build: Build) -> Self
170    where
171        Output: AsRef<Utf8Path>,
172        Build: Into<BuildBuilder>,
173    {
174        self.output_impl(output, build, false)
175    }
176
177    pub fn implicit_output<Output, Build>(self, output: Output, build: Build) -> Self
178    where
179        Output: AsRef<Utf8Path>,
180        Build: Into<BuildBuilder>,
181    {
182        self.output_impl(output, build, true)
183    }
184
185    pub fn merge(mut self, other: &Self) -> Self {
186        // combine all the errors
187        self.errors.extend(other.errors.clone());
188
189        let mut file = self;
190        for (id, var) in other.file.variables.iter() {
191            file = file.variable(id.clone(), var.clone());
192        }
193
194        for (id, rule) in other.file.rules.iter() {
195            file = file.rule(id.clone(), rule.clone());
196        }
197
198        for (id, output) in other.file.builds.iter() {
199            file = output
200                .explicits
201                .iter()
202                .fold(file, |file, e| file.output(e.clone(), id.clone()));
203            file = output
204                .implicits
205                .iter()
206                .fold(file, |file, e| file.implicit_output(e.clone(), id.clone()));
207        }
208
209        file
210    }
211}