use super::{presentation, Decide, FactorSide, ListOptions};
use crate::out::Out;
use anyhow::Result;
use clap::{CommandFactory, Parser, Subcommand};
use std::path::PathBuf;
use triblespace::prelude::Id;
#[derive(Parser)]
#[command(
version = crate::GIT_VERSION,
name = "decide",
about = "A fork-visible TribleSpace deliberation ledger"
)]
pub struct Cli {
#[arg(long, env = "PILE")]
pile: PathBuf,
#[arg(long, env = "TRIBLESPACE_KEY")]
key: Option<PathBuf>,
#[command(subcommand)]
command: Option<Command>,
}
#[derive(Subcommand)]
enum Command {
Propose {
#[arg(help = "Decision title. Use @path for file input or @- for stdin.")]
title: String,
#[arg(
long,
help = "Optional context. Use @path for file input or @- for stdin."
)]
context: Option<String>,
#[arg(long, value_parser = parse_id_arg)]
about: Option<Id>,
},
Pro {
decision: String,
#[arg(help = "Factor text. Use @path for file input or @- for stdin.")]
text: String,
},
Con {
decision: String,
#[arg(help = "Factor text. Use @path for file input or @- for stdin.")]
text: String,
},
Resolve {
decision: String,
#[arg(help = "Outcome text. Use @path for file input or @- for stdin.")]
outcome: String,
#[arg(long, value_parser = parse_result_arg)]
result: Option<Id>,
#[arg(long)]
force: bool,
},
Reconcile {
decision: String,
#[arg(help = "Reconciled outcome. Use @path for file input or @- for stdin.")]
outcome: String,
#[arg(long, value_parser = parse_result_arg)]
result: Option<Id>,
#[arg(long)]
force: bool,
},
List {
#[arg(long)]
all: bool,
#[arg(long)]
forced: bool,
},
Show { decision: String },
ResolveId { prefix: String },
}
fn parse_id_arg(raw: &str) -> std::result::Result<Id, String> {
Id::from_hex(raw.trim()).ok_or_else(|| format!("invalid id '{raw}'"))
}
fn parse_result_arg(raw: &str) -> std::result::Result<Id, String> {
super::result_id(raw).map_err(|error| error.to_string())
}
pub fn run() -> Result<()> {
let cli = Cli::parse();
if cli.command.is_none() {
Cli::command().print_help()?;
println!();
return Ok(());
}
crate::cli::with_output("decide", |out| execute(cli, out))
}
pub fn execute(cli: Cli, out: &mut Out<'_>) -> Result<()> {
let Some(command) = cli.command else {
return out.text(Cli::command().render_help().to_string());
};
let decide = Decide::new(cli.pile, cli.key);
match command {
Command::Propose {
title,
context,
about,
} => {
let title = crate::text_arg(&title, "decision title")?;
let context = context
.as_deref()
.map(|value| crate::text_arg(value, "decision context"))
.transpose()?;
presentation::proposed(&decide.propose(&title, context.as_deref(), about)?, out)
}
Command::Pro { decision, text } => presentation::factor(
&decide.factor(
&decision,
&crate::text_arg(&text, "pro factor")?,
FactorSide::Pro,
)?,
out,
),
Command::Con { decision, text } => presentation::factor(
&decide.factor(
&decision,
&crate::text_arg(&text, "con factor")?,
FactorSide::Con,
)?,
out,
),
Command::Resolve {
decision,
outcome,
result,
force,
} => presentation::resolved(
&decide.resolve(
&decision,
&crate::text_arg(&outcome, "resolution outcome")?,
result,
force,
)?,
false,
out,
),
Command::Reconcile {
decision,
outcome,
result,
force,
} => presentation::resolved(
&decide.reconcile(
&decision,
&crate::text_arg(&outcome, "reconciled outcome")?,
result,
force,
)?,
true,
out,
),
Command::List { all, forced } => {
presentation::list(&decide.list(ListOptions { all, forced })?, out)
}
Command::Show { decision } => presentation::show(&decide.show(&decision)?, out),
Command::ResolveId { prefix } => out.line(format!("{:x}", decide.resolve_id(&prefix)?)),
}
}