use super::{Context, args};
use crate::boundary::config::ConfigFile;
use crate::boundary::config::Read;
use crate::boundary::{Action, Gesture, Query};
use crate::config_edit::branch::edit::EditOrigin;
use crate::model_pick::{LEVELS, Tuning};
use crate::world::marks;
pub(super) fn config(tail: &str, ctx: &Context, verb: &str) -> Result<Gesture, String> {
let (target, rest) = args::first_word(tail);
let (file, rest) = match target.as_str() {
"brazen" => (
ConfigFile::Brazen {
workspace: args::workspace(ctx, verb)?,
},
rest,
),
"models" => (ConfigFile::LitanyModels, rest),
"cadence" => (ConfigFile::Cadence, rest),
"workflow" => {
let (name, rest) = word(&rest, verb, "a workflow name")?;
(ConfigFile::LitanyWorkflow { name }, rest)
}
"branch" => lineage(&rest, ctx, verb, None)?,
"orphan" => lineage(&rest, ctx, verb, Some(EditOrigin::Orphan))?,
"fork" => {
let (lineage_name, rest) = word(&rest, verb, "the lineage name")?;
let (source, rest) = word(&rest, verb, "the lineage to fork from")?;
let (path, rest) = word(&rest, verb, "the file's path in the lineage")?;
(
branch(ctx, verb, lineage_name, EditOrigin::Fork { source }, path)?,
rest,
)
}
other => Err(format!(
"/{verb}: unknown destination {other:?}; usage: {USAGE}"
))?,
};
if rest.trim().is_empty() {
return Ok(Gesture::Ask(Query::Config(Read::File { file })));
}
Ok(Gesture::Act(Action::ApplyConfig {
file,
text: args::required(&rest, verb, "the file's text")?,
}))
}
pub(super) fn marks(tail: &str, ctx: &Context, verb: &str) -> Result<Gesture, String> {
let (branch, rest) = args::first_word(tail);
if branch.is_empty() {
return Ok(Gesture::Ask(Query::Config(Read::Marks {
workspace: args::workspace(ctx, verb)?,
})));
}
args::none(&rest, verb)?;
if !marks::lawful(&branch) {
return Err(format!("/{verb}: {}", marks::REFUSAL));
}
Ok(Gesture::Act(Action::SetMarks {
workspace: args::workspace(ctx, verb)?,
branch,
}))
}
pub(super) fn model(tail: &str, ctx: &Context, verb: &str) -> Result<Gesture, String> {
let [role, provider, model] = *tail.split_whitespace().collect::<Vec<_>>() else {
return Err(format!(
"/{verb}: usage: /model <role> <provider> <model-id>"
));
};
Ok(Gesture::Act(Action::PickModel {
workspace: args::workspace(ctx, verb)?,
role: role.to_owned(),
provider: provider.to_owned(),
model: model.to_owned(),
}))
}
pub(super) fn effort(tail: &str, ctx: &Context, verb: &str) -> Result<Gesture, String> {
let [role, level] = *tail.split_whitespace().collect::<Vec<_>>() else {
return Err(usage(verb));
};
let level = match level {
OFF => None,
word => Some(crate::model_pick::Effort::parse(word).ok_or_else(|| usage(verb))?),
};
Ok(Gesture::Act(Action::Tune(Tuning::Effort {
workspace: args::workspace(ctx, verb)?,
role: role.to_owned(),
level,
})))
}
pub(super) fn priority(tail: &str, ctx: &Context, verb: &str) -> Result<Gesture, String> {
let [role, state] = *tail.split_whitespace().collect::<Vec<_>>() else {
return Err(PRIORITY_USAGE.to_owned());
};
let on = match state {
ON => true,
OFF => false,
_ => return Err(PRIORITY_USAGE.to_owned()),
};
Ok(Gesture::Act(Action::Tune(Tuning::Priority {
workspace: args::workspace(ctx, verb)?,
role: role.to_owned(),
on,
})))
}
const OFF: &str = "off";
const ON: &str = "on";
fn usage(verb: &str) -> String {
format!("/{verb}: usage: /effort <role> <{LEVELS}>")
}
pub const PRIORITY_USAGE: &str = "/priority: usage: /priority <role> <on|off>";
pub(super) fn target_words(file: &ConfigFile) -> String {
match file {
ConfigFile::Brazen { .. } => "brazen".to_owned(),
ConfigFile::LitanyModels => "models".to_owned(),
ConfigFile::Cadence => "cadence".to_owned(),
ConfigFile::LitanyWorkflow { name } => format!("workflow {name}"),
ConfigFile::Branch {
lineage,
origin,
path,
..
} => match origin {
EditOrigin::Advance => format!("branch {lineage} {path}"),
EditOrigin::Fork { source } => format!("fork {lineage} {source} {path}"),
EditOrigin::Orphan => format!("orphan {lineage} {path}"),
},
}
}
pub const USAGE: &str = "/config brazen|models|cadence <text…> | /config workflow <name> <text…> | \
/config branch|orphan <lineage> <path> <text…> | /config fork <lineage> <source> <path> <text…>";
fn lineage(
rest: &str,
ctx: &Context,
verb: &str,
origin: Option<EditOrigin>,
) -> Result<(ConfigFile, String), String> {
let (name, rest) = word(rest, verb, "the lineage name")?;
let (path, rest) = word(&rest, verb, "the file's path in the lineage")?;
let origin = origin.unwrap_or(EditOrigin::Advance);
Ok((branch(ctx, verb, name, origin, path)?, rest))
}
fn branch(
ctx: &Context,
verb: &str,
lineage: String,
origin: EditOrigin,
path: String,
) -> Result<ConfigFile, String> {
Ok(ConfigFile::Branch {
workspace: args::workspace(ctx, verb)?,
lineage,
origin,
path,
})
}
fn word(rest: &str, verb: &str, what: &str) -> Result<(String, String), String> {
let (head, tail) = args::first_word(rest);
if head.is_empty() {
return Err(format!("/{verb}: {what} is required"));
}
Ok((head, tail))
}
#[cfg(test)]
mod tests;