mod text;
mod verdict;
mod decided;
pub use decided::Decided;
pub use text::{usage, version};
pub use verdict::{Stream, Verdict};
use crate::render::Form;
pub const JSON: &str = "--json";
pub fn run(args: Vec<String>) -> Decided {
let (form, args) = form(args);
let words: Vec<&str> = args.iter().map(String::as_str).collect();
match words.as_slice() {
["entries"] => Decided::Entries,
["ask", envelope] => ask(envelope, form),
["help" | "--help" | "-h"] => Decided::Say(Verdict::ok(usage())),
["help", verb] => Decided::Say(match crate::verbs::help::page(verb) {
Ok(page) => Verdict::ok(page),
Err(refusal) => Verdict::refused(refusal),
}),
["--version" | "-V"] => Decided::Say(Verdict::ok(version())),
["start", address, goal] => started(address, goal, None, form),
["start", address, goal, dir] => started(address, goal, Some(dir), form),
["follow", workspace, agent] => Decided::Follow {
workspace: (*workspace).to_owned(),
agent: (*agent).to_owned(),
form,
},
["model", workspace, role, provider, model] => Decided::Model {
workspace: (*workspace).to_owned(),
role: (*role).to_owned(),
provider: (*provider).to_owned(),
model: (*model).to_owned(),
form,
},
["ops"] => Decided::Fanned(crate::verbs::ops(crate::verbs::DEPTH), form),
["ops", depth] => trail(depth, form),
["enroll", workspace, name, grade, tail @ ..] => enroll(workspace, name, grade, tail),
[] => Decided::Window,
[word, arguments @ ..] => typed(word, arguments, form),
}
}
fn started(address: &str, goal: &str, dir: Option<&str>, form: Form) -> Decided {
Decided::Start {
address: address.to_owned(),
goal: goal.to_owned(),
dir: dir.map(str::to_owned),
form,
}
}
fn trail(depth: &str, form: Form) -> Decided {
let Ok(max) = depth.parse::<u64>() else {
return Decided::Say(Verdict::refused(misplaced(
format!(
"`lernie ops` takes a depth in rows and got {depth:?} — usage: {}",
crate::verbs::doors::OPS.usage()
),
&[depth],
)));
};
Decided::Fanned(crate::verbs::ops(max), form)
}
fn form(args: Vec<String>) -> (Form, Vec<String>) {
match args.split_first() {
Some((first, rest)) if first == JSON => (Form::Json, rest.to_vec()),
_ => (Form::Rendered, args),
}
}
fn misplaced(refusal: String, arguments: &[&str]) -> String {
if arguments.contains(&JSON) {
return format!("{refusal} — `{JSON}` goes BEFORE the word: `lernie {JSON} …`");
}
refusal
}
fn ask(text: &str, form: Form) -> Decided {
match crate::envelope::parse(text) {
Ok(envelope) => Decided::Ask(envelope, form),
Err(refusal) => Decided::Say(Verdict::refused(refusal)),
}
}
fn enroll(workspace: &str, name: &str, grade: &str, tail: &[&str]) -> Decided {
let into = match tail {
[] => None,
[word, dir] if *word == INTO => Some((*dir).to_owned()),
_ => {
return Decided::Say(Verdict::refused(format!(
"`lernie enroll` takes one optional word after the three, and it is \
`{INTO} <dir>` — not {:?}",
tail.join(" ")
)));
}
};
let words = crate::ui::Grade::both();
let Some(held) = words.iter().find(|known| known.word() == grade) else {
return Decided::Say(Verdict::refused(format!(
"unknown grade {grade:?} — `lernie enroll` takes {}",
words
.iter()
.map(|known| format!("{:?}", known.word()))
.collect::<Vec<String>>()
.join(" or ")
)));
};
Decided::Enroll {
workspace: workspace.to_owned(),
name: name.to_owned(),
grade: held.word(),
into,
}
}
pub const INTO: &str = "--into";
fn typed(word: &str, arguments: &[&str], form: Form) -> Decided {
if let Some(verb) = crate::verbs::find(word) {
return match verb.envelope(arguments.iter().map(|a| (*a).to_owned()).collect()) {
Ok(envelope) if verb.addresses_a_workspace() => Decided::Ask(envelope, form),
Ok(envelope) => Decided::Fanned(envelope, form),
Err(refusal) => Decided::Say(Verdict::refused(misplaced(refusal, arguments))),
};
}
if let Some(door) = crate::verbs::doors::find(word) {
return Decided::Say(Verdict::refused(misplaced(
door.refused(arguments.len()),
arguments,
)));
}
Decided::Say(Verdict::refused(format!(
"unrecognised argument: {}",
std::iter::once(word)
.chain(arguments.iter().copied())
.collect::<Vec<&str>>()
.join(" ")
)))
}
#[cfg(test)]
mod tests;