1use super::{OpRef, Operation, Request, Setup, SetupRef, State, StateRef};
2use crate::{run, utils};
3use camino::{Utf8Path, Utf8PathBuf};
4use cranelift_entity::{PrimaryMap, SecondaryMap};
5
6#[derive(PartialEq)]
7enum Destination {
8 State(StateRef),
9 Op(OpRef),
10}
11
12pub struct Driver {
15 pub name: String,
16 pub setups: PrimaryMap<SetupRef, Setup>,
17 pub states: PrimaryMap<StateRef, State>,
18 pub ops: PrimaryMap<OpRef, Operation>,
19}
20
21impl Driver {
22 fn find_path_segment(
25 &self,
26 start: StateRef,
27 end: Destination,
28 ) -> Option<Vec<OpRef>> {
29 let mut visited = SecondaryMap::<StateRef, bool>::new();
31 visited[start] = true;
32
33 let mut breadcrumbs = SecondaryMap::<StateRef, Option<OpRef>>::new();
35
36 let mut state_queue: Vec<StateRef> = vec![start];
38 while !state_queue.is_empty() {
39 let cur_state = state_queue.remove(0);
40
41 if end == Destination::State(cur_state) {
43 break;
44 }
45
46 for (op_ref, op) in self.ops.iter() {
48 if op.input == cur_state && !visited[op.output] {
49 state_queue.push(op.output);
50 visited[op.output] = true;
51 breadcrumbs[op.output] = Some(op_ref);
52 }
53
54 if end == Destination::Op(op_ref) {
56 break;
57 }
58 }
59 }
60
61 let mut op_path: Vec<OpRef> = vec![];
63 let mut cur_state = match end {
64 Destination::State(state) => state,
65 Destination::Op(op) => {
66 op_path.push(op);
67 self.ops[op].input
68 }
69 };
70 while cur_state != start {
71 match breadcrumbs[cur_state] {
72 Some(op) => {
73 op_path.push(op);
74 cur_state = self.ops[op].input;
75 }
76 None => return None,
77 }
78 }
79 op_path.reverse();
80
81 Some(op_path)
82 }
83
84 pub fn find_path(
87 &self,
88 start: StateRef,
89 end: StateRef,
90 through: &[OpRef],
91 ) -> Option<Vec<OpRef>> {
92 let mut cur_state = start;
93 let mut op_path: Vec<OpRef> = vec![];
94
95 for op in through {
97 let segment =
98 self.find_path_segment(cur_state, Destination::Op(*op))?;
99 op_path.extend(segment);
100 cur_state = self.ops[*op].output;
101 }
102
103 let segment =
105 self.find_path_segment(cur_state, Destination::State(end))?;
106 op_path.extend(segment);
107
108 Some(op_path)
109 }
110
111 fn gen_name(&self, stem: &str, state: StateRef) -> Utf8PathBuf {
113 let state = &self.states[state];
114 if state.is_pseudo() {
115 Utf8PathBuf::from(format!("_pseudo_{}", state.name))
116 } else {
117 Utf8PathBuf::from(stem).with_extension(&state.extensions[0])
119 }
120 }
121
122 pub fn plan(&self, req: Request) -> Option<Plan> {
123 let path =
125 self.find_path(req.start_state, req.end_state, &req.through)?;
126
127 let mut steps: Vec<(OpRef, Utf8PathBuf)> = vec![];
128
129 let (stdin, start_file) = match req.start_file {
131 Some(path) => (false, utils::relative_path(&path, &req.workdir)),
132 None => (true, "stdin".into()),
133 };
134 let stem = start_file.file_stem().unwrap();
135
136 steps.extend(path.into_iter().map(|op| {
138 let filename = self.gen_name(stem, self.ops[op].output);
139 (op, filename)
140 }));
141
142 let stdout = if let Some(end_file) = req.end_file {
144 let last_step = steps.last_mut().expect("no steps");
146 last_step.1 = utils::relative_path(&end_file, &req.workdir);
147 false
148 } else {
149 !self.states[req.end_state].is_pseudo()
151 };
152
153 Some(Plan {
154 start: start_file,
155 steps,
156 workdir: req.workdir,
157 stdin,
158 stdout,
159 })
160 }
161
162 pub fn guess_state(&self, path: &Utf8Path) -> Option<StateRef> {
163 let ext = path.extension()?;
164 self.states
165 .iter()
166 .find(|(_, state_data)| state_data.ext_matches(ext))
167 .map(|(state, _)| state)
168 }
169
170 pub fn get_state(&self, name: &str) -> Option<StateRef> {
171 self.states
172 .iter()
173 .find(|(_, state_data)| state_data.name == name)
174 .map(|(state, _)| state)
175 }
176
177 pub fn get_op(&self, name: &str) -> Option<OpRef> {
178 self.ops
179 .iter()
180 .find(|(_, op_data)| op_data.name == name)
181 .map(|(op, _)| op)
182 }
183
184 pub fn default_workdir(&self) -> Utf8PathBuf {
186 format!(".{}", &self.name).into()
187 }
188}
189
190pub struct DriverBuilder {
191 name: String,
192 setups: PrimaryMap<SetupRef, Setup>,
193 states: PrimaryMap<StateRef, State>,
194 ops: PrimaryMap<OpRef, Operation>,
195}
196
197impl DriverBuilder {
198 pub fn new(name: &str) -> Self {
199 Self {
200 name: name.to_string(),
201 setups: Default::default(),
202 states: Default::default(),
203 ops: Default::default(),
204 }
205 }
206
207 pub fn state(&mut self, name: &str, extensions: &[&str]) -> StateRef {
208 self.states.push(State {
209 name: name.to_string(),
210 extensions: extensions.iter().map(|s| s.to_string()).collect(),
211 })
212 }
213
214 fn add_op<T: run::EmitBuild + 'static>(
215 &mut self,
216 name: &str,
217 setups: &[SetupRef],
218 input: StateRef,
219 output: StateRef,
220 emit: T,
221 ) -> OpRef {
222 self.ops.push(Operation {
223 name: name.into(),
224 setups: setups.into(),
225 input,
226 output,
227 emit: Box::new(emit),
228 })
229 }
230
231 pub fn add_setup<T: run::EmitSetup + 'static>(
232 &mut self,
233 name: &str,
234 emit: T,
235 ) -> SetupRef {
236 self.setups.push(Setup {
237 name: name.into(),
238 emit: Box::new(emit),
239 })
240 }
241
242 pub fn setup(&mut self, name: &str, func: run::EmitSetupFn) -> SetupRef {
243 self.add_setup(name, func)
244 }
245
246 pub fn op(
247 &mut self,
248 name: &str,
249 setups: &[SetupRef],
250 input: StateRef,
251 output: StateRef,
252 build: run::EmitBuildFn,
253 ) -> OpRef {
254 self.add_op(name, setups, input, output, build)
255 }
256
257 pub fn rule(
258 &mut self,
259 setups: &[SetupRef],
260 input: StateRef,
261 output: StateRef,
262 rule_name: &str,
263 ) -> OpRef {
264 self.add_op(
265 rule_name,
266 setups,
267 input,
268 output,
269 run::EmitRuleBuild {
270 rule_name: rule_name.to_string(),
271 },
272 )
273 }
274
275 pub fn build(self) -> Driver {
276 Driver {
277 name: self.name,
278 setups: self.setups,
279 states: self.states,
280 ops: self.ops,
281 }
282 }
283}
284
285#[derive(Debug)]
286pub struct Plan {
287 pub start: Utf8PathBuf,
289
290 pub steps: Vec<(OpRef, Utf8PathBuf)>,
292
293 pub workdir: Utf8PathBuf,
295
296 pub stdin: bool,
298
299 pub stdout: bool,
301}
302
303impl Plan {
304 pub fn end(&self) -> &Utf8Path {
305 match self.steps.last() {
306 Some((_, path)) => path,
307 None => &self.start,
308 }
309 }
310}