use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum Runnable {
Skill {
name: String,
arguments: String,
},
Run {
task: Option<String>,
},
Converge,
Commit {
paths: Vec<String>,
},
Approve {
task: String,
},
Reject {
task: String,
},
Accept {
task: String,
},
Skip {
task: String,
},
Nudge {
task: String,
},
Rule {
task: String,
},
Review {
ids: Vec<String>,
verdict: String,
},
IssueComment {
issue: u64,
assign: bool,
},
QuickChange {
request: String,
checks: Vec<String>,
run: bool,
},
Feature,
PullRequest,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct Action {
pub group: String,
pub label: String,
pub why: String,
pub command: String,
pub run: Option<Runnable>,
pub blocked: Option<String>,
pub input: Option<String>,
pub confirm: Option<String>,
}
impl Action {
#[must_use]
pub const fn runnable(&self) -> bool {
self.run.is_some() && self.blocked.is_none()
}
}
#[must_use]
pub fn primary(actions: &[Action]) -> Option<&Action> {
actions.iter().find(|a| a.group == "next" && a.runnable())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, schemars::JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum StepState {
Done,
Now,
Todo,
Skipped,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, schemars::JsonSchema)]
pub struct JourneyStep {
pub name: String,
pub state: StepState,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct JourneyFacts {
pub spec: bool,
pub plan: bool,
pub tasks: bool,
pub uncommitted: bool,
pub total: usize,
pub finished: usize,
}
#[must_use]
pub fn journey(f: JourneyFacts) -> Vec<JourneyStep> {
let skipped = [!f.spec && (f.plan || f.tasks), !f.plan && f.tasks];
let done = [
("spec", f.spec),
("plan", f.plan),
("tasks", f.tasks),
("commit", f.tasks && !f.uncommitted),
("run", f.total > 0 && f.finished == f.total),
("review", false),
];
let passed = |i: usize| skipped.get(i).copied().unwrap_or(false);
let now = done
.iter()
.enumerate()
.position(|(i, (_, d))| !d && !passed(i))
.unwrap_or(done.len());
done.iter()
.enumerate()
.map(|(i, (name, d))| JourneyStep {
name: (*name).to_owned(),
state: if *d && i < now {
StepState::Done
} else if passed(i) && i < now {
StepState::Skipped
} else if i == now {
StepState::Now
} else {
StepState::Todo
},
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn states(f: JourneyFacts) -> Vec<(String, StepState)> {
journey(f).into_iter().map(|s| (s.name, s.state)).collect()
}
#[test]
fn the_journey_marks_what_is_done_and_what_is_now() {
use StepState::{Done, Now, Todo};
let names = ["spec", "plan", "tasks", "commit", "run", "review"].map(String::from);
let at = |v: [StepState; 6]| names.iter().cloned().zip(v).collect::<Vec<_>>();
assert_eq!(
states(JourneyFacts::default()),
at([Now, Todo, Todo, Todo, Todo, Todo])
);
let planned = JourneyFacts {
spec: true,
plan: true,
..JourneyFacts::default()
};
assert_eq!(states(planned), at([Done, Done, Now, Todo, Todo, Todo]));
let written = JourneyFacts {
tasks: true,
uncommitted: true,
total: 3,
..planned
};
assert_eq!(states(written), at([Done, Done, Done, Now, Todo, Todo]));
let running = JourneyFacts {
uncommitted: false,
finished: 1,
..written
};
assert_eq!(states(running), at([Done, Done, Done, Done, Now, Todo]));
let finished = JourneyFacts {
finished: 3,
..running
};
assert_eq!(states(finished), at([Done, Done, Done, Done, Done, Now]));
}
#[test]
fn a_task_list_without_a_spec_skips_to_the_run() {
use StepState::{Now, Skipped, Todo};
let names = ["spec", "plan", "tasks", "commit", "run", "review"].map(String::from);
let at = |v: [StepState; 6]| names.iter().cloned().zip(v).collect::<Vec<_>>();
let only_tasks = JourneyFacts {
tasks: true,
total: 4,
finished: 1,
..JourneyFacts::default()
};
assert_eq!(
states(only_tasks),
at([
Skipped,
Skipped,
StepState::Done,
StepState::Done,
Now,
Todo
])
);
let planned = JourneyFacts {
plan: true,
..JourneyFacts::default()
};
assert_eq!(
states(planned),
at([Skipped, StepState::Done, Now, Todo, Todo, Todo])
);
}
}