use super::{Context, args};
use crate::actions::verbs::{Verb, edit};
use crate::boundary::{Action, Gesture};
fn act(action: Action) -> Gesture {
Gesture::Act(action)
}
pub(super) fn id(tail: &str, ctx: &Context, verb: &str) -> Result<String, String> {
args::ball_id(args::optional_word(tail, verb)?, ctx, verb)
}
pub(super) fn read(verb: &str, tail: &str, ctx: &Context) -> Result<Gesture, String> {
match verb {
"create" => create(tail, ctx, verb),
"update" => update(tail, ctx, verb),
_ => identified(tail, ctx, verb),
}
}
fn identified(tail: &str, ctx: &Context, verb: &str) -> Result<Gesture, String> {
let (project, id, name) = (
args::project(ctx, verb)?,
id(tail, ctx, verb)?,
args::name(ctx, verb)?,
);
Ok(act(Action::Ball(match verb {
"close" => Verb::Close { project, id, name },
"assign" => Verb::Assign { project, id, name },
_ => Verb::Release { project, id, name },
})))
}
fn create(tail: &str, ctx: &Context, verb: &str) -> Result<Gesture, String> {
let (title, flags) = args::split_flags(tail);
only(&flags, &["body"], verb)?;
Ok(act(Action::Ball(Verb::Create {
project: args::project(ctx, verb)?,
name: args::name(ctx, verb)?,
fields: edit::Create {
title: args::required(&title, verb, "a title")?,
body: args::flag(&flags, "body", verb)?,
fields: fields(&flags, verb)?,
},
})))
}
fn update(tail: &str, ctx: &Context, verb: &str) -> Result<Gesture, String> {
let (positional, flags) = args::split_flags(tail);
only(&flags, &["title", "body", "note"], verb)?;
let edits = edit::Update {
title: args::flag(&flags, "title", verb)?,
body: args::flag(&flags, "body", verb)?,
note: args::flag(&flags, "note", verb)?,
fields: fields(&flags, verb)?,
};
if edits == edit::Update::default() {
return Err(format!("/{verb}: nothing to change; usage: {UPDATE_USAGE}"));
}
Ok(act(Action::Ball(Verb::Update {
project: args::project(ctx, verb)?,
id: id(&positional, ctx, verb)?,
name: args::name(ctx, verb)?,
fields: edits,
})))
}
pub const UPDATE_USAGE: &str = "/update [id] [--title T] [--body B] [--note N] [--priority N|--no-priority] \
[--tag T|--no-tag T] [--parent ID|--no-parent] [--needs ID[:OP]|--no-needs ID]";
pub const CREATE_USAGE: &str = "/create <title…> [--body <text…>] [--priority N] [--tag T] \
[--parent ID] [--needs ID[:OP]]";
const FIELD_FLAGS: [&str; 8] = [
"priority",
"no-priority",
"tag",
"no-tag",
"parent",
"no-parent",
"needs",
"no-needs",
];
fn only(flags: &[(String, String)], own: &[&str], verb: &str) -> Result<(), String> {
let mut allowed = own.to_vec();
allowed.extend(FIELD_FLAGS);
args::only(flags, &allowed, verb)
}
fn fields(flags: &[(String, String)], verb: &str) -> Result<Vec<edit::Field>, String> {
flags
.iter()
.filter_map(|(key, value)| field(key, value, verb))
.collect()
}
fn field(key: &str, value: &str, verb: &str) -> Option<Result<edit::Field, String>> {
Some(match key {
"priority" => valued(value, key, verb).and_then(|n| priority(&n, key, verb)),
"no-priority" => bare(value, key, verb).map(|()| edit::Field::Priority(None)),
"parent" => valued(value, key, verb).map(|id| edit::Field::Parent(Some(id))),
"no-parent" => bare(value, key, verb).map(|()| edit::Field::Parent(None)),
"tag" | "no-tag" => valued(value, key, verb).map(|tag| edit::Field::Tag {
tag,
on: key == "tag",
}),
"needs" | "no-needs" => valued(value, key, verb).map(|edge| edit::Field::Needs {
edge,
on: key == "needs",
}),
_ => return None,
})
}
fn priority(word: &str, key: &str, verb: &str) -> Result<edit::Field, String> {
word.parse()
.map(|n| edit::Field::Priority(Some(n)))
.map_err(|_| format!("/{verb}: --{key} takes a number, got {word:?}"))
}
fn valued(value: &str, key: &str, verb: &str) -> Result<String, String> {
if value.is_empty() {
Err(format!("/{verb}: --{key} needs a value"))
} else {
Ok(value.to_owned())
}
}
fn bare(value: &str, key: &str, verb: &str) -> Result<(), String> {
if value.is_empty() {
Ok(())
} else {
Err(format!("/{verb}: --{key} takes no value, got {value:?}"))
}
}
pub(super) fn spell_fields(fields: &[edit::Field]) -> String {
fields.iter().map(spell_field).collect()
}
fn spell_field(field: &edit::Field) -> String {
match field {
edit::Field::Priority(Some(n)) => format!(" --priority {n}"),
edit::Field::Priority(None) => " --no-priority".to_owned(),
edit::Field::Parent(Some(id)) => format!(" --parent {id}"),
edit::Field::Parent(None) => " --no-parent".to_owned(),
edit::Field::Tag { tag, on } => format!(" --{}tag {tag}", negation(*on)),
edit::Field::Needs { edge, on } => format!(" --{}needs {edge}", negation(*on)),
}
}
fn negation(on: bool) -> &'static str {
if on { "" } else { "no-" }
}