use super::Context;
use crate::start::BallSpec;
pub(super) fn workspace(ctx: &Context, verb: &str) -> Result<String, String> {
ctx.workspace
.clone()
.ok_or_else(|| format!("/{verb}: no workspace in context — focus one, or use the envelope"))
}
pub(super) fn agent(ctx: &Context, verb: &str) -> Result<String, String> {
ctx.agent
.clone()
.ok_or_else(|| format!("/{verb}: no conversation is selected — there is nothing to act on"))
}
pub(super) fn project(ctx: &Context, verb: &str) -> Result<String, String> {
ctx.project
.clone()
.ok_or_else(|| format!("/{verb}: no project in context — this seat has no repo to act in"))
}
pub(super) fn name(ctx: &Context, verb: &str) -> Result<String, String> {
ctx.name
.clone()
.ok_or_else(|| format!("/{verb}: no workspace name in context — a ball verb is stamped"))
}
pub(super) fn ball_id(typed: Option<String>, ctx: &Context, verb: &str) -> Result<String, String> {
typed
.or_else(|| match ctx.ball.as_ref() {
Some(BallSpec::Existing { id, .. }) => Some(id.clone()),
Some(BallSpec::New { .. }) | None => None,
})
.ok_or_else(|| format!("/{verb}: no ball id — type one, or select a ball first"))
}
pub(super) fn required(tail: &str, verb: &str, what: &str) -> Result<String, String> {
let text = tail.trim();
if text.is_empty() {
return Err(format!("/{verb}: {what} is required"));
}
Ok(text.to_owned())
}
pub(super) fn none(tail: &str, verb: &str) -> Result<(), String> {
let extra = tail.trim();
if extra.is_empty() {
return Ok(());
}
Err(format!("/{verb}: takes no arguments, got {extra:?}"))
}
pub(super) fn optional_word(tail: &str, verb: &str) -> Result<Option<String>, String> {
match tail.split_whitespace().collect::<Vec<_>>().as_slice() {
[] => Ok(None),
[word] => Ok(Some((*word).to_owned())),
_ => Err(format!("/{verb}: expected at most one word, got {tail:?}")),
}
}
pub(super) fn split_flags(tail: &str) -> (String, Vec<(String, String)>) {
let mut positional: Vec<&str> = Vec::new();
let mut flags: Vec<(String, String)> = Vec::new();
for token in tail.split_whitespace() {
match (token.strip_prefix("--"), flags.last_mut()) {
(Some(key), _) => flags.push((key.to_owned(), String::new())),
(None, Some((_, value))) => {
if !value.is_empty() {
value.push(' ');
}
value.push_str(token);
}
(None, None) => positional.push(token),
}
}
(positional.join(" "), flags)
}
pub(super) fn flag(
flags: &[(String, String)],
key: &str,
verb: &str,
) -> Result<Option<String>, String> {
match flags.iter().find(|(k, _)| k == key) {
None => Ok(None),
Some((_, value)) if value.is_empty() => Err(format!("/{verb}: --{key} needs a value")),
Some((_, value)) => Ok(Some(value.clone())),
}
}
pub(super) fn only(flags: &[(String, String)], allowed: &[&str], verb: &str) -> Result<(), String> {
match flags.iter().find(|(k, _)| !allowed.contains(&k.as_str())) {
None => Ok(()),
Some((key, _)) => Err(format!(
"/{verb}: unknown flag --{key}; this verb takes {}",
allowed
.iter()
.map(|a| format!("--{a}"))
.collect::<Vec<_>>()
.join(", ")
)),
}
}
pub(super) fn first_word(tail: &str) -> (String, String) {
match tail.trim_start().split_once(char::is_whitespace) {
Some((head, rest)) => (head.to_owned(), rest.trim_start().to_owned()),
None => (tail.trim().to_owned(), String::new()),
}
}