use super::{OpRef, Operation, Request, Setup, SetupRef, State, StateRef};
use crate::{run, utils};
use camino::{Utf8Path, Utf8PathBuf};
use cranelift_entity::{PrimaryMap, SecondaryMap};
#[derive(PartialEq)]
enum Destination {
State(StateRef),
Op(OpRef),
}
pub struct Driver {
pub name: String,
pub setups: PrimaryMap<SetupRef, Setup>,
pub states: PrimaryMap<StateRef, State>,
pub ops: PrimaryMap<OpRef, Operation>,
}
impl Driver {
fn find_path_segment(
&self,
start: StateRef,
end: Destination,
) -> Option<Vec<OpRef>> {
let mut visited = SecondaryMap::<StateRef, bool>::new();
visited[start] = true;
let mut breadcrumbs = SecondaryMap::<StateRef, Option<OpRef>>::new();
let mut state_queue: Vec<StateRef> = vec![start];
while !state_queue.is_empty() {
let cur_state = state_queue.remove(0);
if end == Destination::State(cur_state) {
break;
}
for (op_ref, op) in self.ops.iter() {
if op.input == cur_state && !visited[op.output] {
state_queue.push(op.output);
visited[op.output] = true;
breadcrumbs[op.output] = Some(op_ref);
}
if end == Destination::Op(op_ref) {
break;
}
}
}
let mut op_path: Vec<OpRef> = vec![];
let mut cur_state = match end {
Destination::State(state) => state,
Destination::Op(op) => {
op_path.push(op);
self.ops[op].input
}
};
while cur_state != start {
match breadcrumbs[cur_state] {
Some(op) => {
op_path.push(op);
cur_state = self.ops[op].input;
}
None => return None,
}
}
op_path.reverse();
Some(op_path)
}
pub fn find_path(
&self,
start: StateRef,
end: StateRef,
through: &[OpRef],
) -> Option<Vec<OpRef>> {
let mut cur_state = start;
let mut op_path: Vec<OpRef> = vec![];
for op in through {
let segment =
self.find_path_segment(cur_state, Destination::Op(*op))?;
op_path.extend(segment);
cur_state = self.ops[*op].output;
}
let segment =
self.find_path_segment(cur_state, Destination::State(end))?;
op_path.extend(segment);
Some(op_path)
}
fn gen_name(&self, stem: &str, state: StateRef) -> Utf8PathBuf {
let state = &self.states[state];
if state.is_pseudo() {
Utf8PathBuf::from(format!("_pseudo_{}", state.name))
} else {
Utf8PathBuf::from(stem).with_extension(&state.extensions[0])
}
}
pub fn plan(&self, req: Request) -> Option<Plan> {
let path =
self.find_path(req.start_state, req.end_state, &req.through)?;
let mut steps: Vec<(OpRef, Utf8PathBuf)> = vec![];
let (stdin, start_file) = match req.start_file {
Some(path) => (false, utils::relative_path(&path, &req.workdir)),
None => (true, "stdin".into()),
};
let stem = start_file.file_stem().unwrap();
steps.extend(path.into_iter().map(|op| {
let filename = self.gen_name(stem, self.ops[op].output);
(op, filename)
}));
let stdout = if let Some(end_file) = req.end_file {
let last_step = steps.last_mut().expect("no steps");
last_step.1 = utils::relative_path(&end_file, &req.workdir);
false
} else {
!self.states[req.end_state].is_pseudo()
};
Some(Plan {
start: start_file,
steps,
workdir: req.workdir,
stdin,
stdout,
})
}
pub fn guess_state(&self, path: &Utf8Path) -> Option<StateRef> {
let ext = path.extension()?;
self.states
.iter()
.find(|(_, state_data)| state_data.ext_matches(ext))
.map(|(state, _)| state)
}
pub fn get_state(&self, name: &str) -> Option<StateRef> {
self.states
.iter()
.find(|(_, state_data)| state_data.name == name)
.map(|(state, _)| state)
}
pub fn get_op(&self, name: &str) -> Option<OpRef> {
self.ops
.iter()
.find(|(_, op_data)| op_data.name == name)
.map(|(op, _)| op)
}
pub fn default_workdir(&self) -> Utf8PathBuf {
format!(".{}", &self.name).into()
}
}
pub struct DriverBuilder {
name: String,
setups: PrimaryMap<SetupRef, Setup>,
states: PrimaryMap<StateRef, State>,
ops: PrimaryMap<OpRef, Operation>,
}
impl DriverBuilder {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
setups: Default::default(),
states: Default::default(),
ops: Default::default(),
}
}
pub fn state(&mut self, name: &str, extensions: &[&str]) -> StateRef {
self.states.push(State {
name: name.to_string(),
extensions: extensions.iter().map(|s| s.to_string()).collect(),
})
}
fn add_op<T: run::EmitBuild + 'static>(
&mut self,
name: &str,
setups: &[SetupRef],
input: StateRef,
output: StateRef,
emit: T,
) -> OpRef {
self.ops.push(Operation {
name: name.into(),
setups: setups.into(),
input,
output,
emit: Box::new(emit),
})
}
pub fn add_setup<T: run::EmitSetup + 'static>(
&mut self,
name: &str,
emit: T,
) -> SetupRef {
self.setups.push(Setup {
name: name.into(),
emit: Box::new(emit),
})
}
pub fn setup(&mut self, name: &str, func: run::EmitSetupFn) -> SetupRef {
self.add_setup(name, func)
}
pub fn op(
&mut self,
name: &str,
setups: &[SetupRef],
input: StateRef,
output: StateRef,
build: run::EmitBuildFn,
) -> OpRef {
self.add_op(name, setups, input, output, build)
}
pub fn rule(
&mut self,
setups: &[SetupRef],
input: StateRef,
output: StateRef,
rule_name: &str,
) -> OpRef {
self.add_op(
rule_name,
setups,
input,
output,
run::EmitRuleBuild {
rule_name: rule_name.to_string(),
},
)
}
pub fn build(self) -> Driver {
Driver {
name: self.name,
setups: self.setups,
states: self.states,
ops: self.ops,
}
}
}
#[derive(Debug)]
pub struct Plan {
pub start: Utf8PathBuf,
pub steps: Vec<(OpRef, Utf8PathBuf)>,
pub workdir: Utf8PathBuf,
pub stdin: bool,
pub stdout: bool,
}
impl Plan {
pub fn end(&self) -> &Utf8Path {
match self.steps.last() {
Some((_, path)) => path,
None => &self.start,
}
}
}