#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, clap::ValueEnum)]
pub enum Mode {
Ask,
Plan,
Loop,
#[default]
Auto,
}
impl Mode {
pub const ALL: [Mode; 4] = [Mode::Auto, Mode::Ask, Mode::Plan, Mode::Loop];
pub fn word(self) -> &'static str {
match self {
Mode::Ask => "ask",
Mode::Plan => "plan",
Mode::Loop => "loop",
Mode::Auto => "auto",
}
}
pub fn about(self) -> &'static str {
match self {
Mode::Ask => "a question, answered read-only; nothing is written",
Mode::Plan => "a plan, written read-only and run once you agree to it",
Mode::Loop => "a run tried again with the reason, until it is approved",
Mode::Auto => "a run, gated and reviewed, and never merged",
}
}
pub fn next(self) -> Mode {
let at = Mode::ALL.iter().position(|m| *m == self).unwrap_or(0);
Mode::ALL[(at + 1) % Mode::ALL.len()]
}
pub fn named(typed: &str) -> Option<Mode> {
let word = typed.trim().trim_start_matches('/').to_lowercase();
Mode::ALL.into_iter().find(|mode| mode.word() == word)
}
pub fn consults(self) -> bool {
matches!(self, Mode::Ask | Mode::Plan)
}
pub fn consulting(self, task: &str) -> String {
match self {
Mode::Plan => format!(
"{task}\n\n----- a plan, not the change -----\n\n\
Do not make this change yet. Read what you need in this repository \
and write the plan you would follow: the files involved, the steps \
in order, and how the result should be checked. Do not create, edit \
or delete any file. The plan is the whole of your answer."
),
_ => format!(
"{task}\n\n----- a question, not a change -----\n\n\
Answer from what is in this repository. Do not create, edit or \
delete any file."
),
}
}
pub fn planned(task: &str, plan: &str) -> String {
format!(
"{task}\n\n----- the agreed plan -----\n\n\
This plan was written for this task and agreed before you started. \
Follow it. Where the code shows a step to be wrong, do what the task \
needs instead.\n\n{}",
plan.trim()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_mode_is_reached_by_walking_and_by_name() {
let mut seen = vec![Mode::default()];
let mut mode = Mode::default();
for _ in 1..Mode::ALL.len() {
mode = mode.next();
seen.push(mode);
}
assert_eq!(mode.next(), Mode::default(), "walking did not wrap");
for mode in Mode::ALL {
assert!(seen.contains(&mode), "{mode:?} is never reached");
assert_eq!(Mode::named(mode.word()), Some(mode));
assert_eq!(Mode::named(&format!("/{}", mode.word())), Some(mode));
}
assert_eq!(Mode::named("/ASK"), Some(Mode::Ask));
assert_eq!(Mode::named("/settings"), None);
}
#[test]
fn only_asking_and_planning_consult() {
assert!(Mode::Ask.consults() && Mode::Plan.consults());
assert!(!Mode::Loop.consults() && !Mode::Auto.consults());
}
#[test]
fn a_planned_task_carries_the_task_and_the_plan() {
let said = Mode::planned("add a flag", " 1. edit main.rs\n");
assert!(said.starts_with("add a flag"));
assert!(said.ends_with("1. edit main.rs"));
assert!(
Mode::Plan
.consulting("add a flag")
.contains("Do not create")
);
}
}