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) => {
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 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}