use super::{render, DeclaredState, Habits};
use anyhow::{bail, Context, Result};
use clap::{CommandFactory, Parser, Subcommand};
use std::path::PathBuf;
#[derive(Parser)]
#[command(
version = crate::GIT_VERSION,
name = "habit",
about = "Standing intentions, pulled rather than pushed"
)]
pub(crate) 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 {
Add {
label: String,
#[arg(long, value_name = "CONDITION")]
when: String,
#[arg(long, value_name = "TEXT")]
nudge: String,
#[arg(long, value_name = "PATH")]
script: Option<String>,
#[arg(long, value_name = "ID")]
supersedes: Vec<String>,
#[arg(long = "persona", value_name = "LABEL_OR_ID")]
personas: Vec<String>,
},
List,
Show { habit: String },
Due,
Done { label: String },
Pause { label: String },
Resume { label: String },
Check,
}
fn script_arg(raw: &str) -> Result<Vec<u8>> {
use std::io::Read;
if raw == "@-" {
let mut bytes = Vec::new();
std::io::stdin()
.read_to_end(&mut bytes)
.context("read Habit script from stdin")?;
return Ok(bytes);
}
let path = match raw.strip_prefix("@@") {
Some(rest) => format!("@{rest}"),
None => raw.to_owned(),
};
std::fs::read(&path).with_context(|| format!("read Habit script from {path}"))
}
pub fn run() -> Result<()> {
let cli = Cli::parse();
let Some(command) = cli.command else {
Cli::command().print_help()?;
println!();
return Ok(());
};
let habits = Habits::new(cli.pile, cli.key);
match command {
Command::List | Command::Due => {
let due = matches!(command, Command::Due);
let report = habits.list(true)?;
let listing = render::listed(&report, due);
crate::cli::with_output("habit", |out| out.text(listing.text))?;
if !listing.diagnostics.is_empty() {
crate::cli::with_diagnostic_output("habit", |out| {
for message in &listing.diagnostics {
out.line(message)?;
}
Ok(())
})?;
bail!(
"{} standing intention(s) could not be evaluated (see above)",
listing.diagnostics.len()
);
}
Ok(())
}
command => crate::cli::with_output("habit", |out| match command {
Command::Add {
label,
when,
nudge,
script,
supersedes,
personas,
} => {
let nudge = crate::text_arg(&nudge, "Habit nudge")?;
let script = script.as_deref().map(script_arg).transpose()?;
render::added(
&habits.add(
&label,
&when,
&nudge,
script.as_deref(),
&supersedes,
&personas,
)?,
out,
)
}
Command::Show { habit } => render::shown(&habits.show(&habit)?, out),
Command::Done { label } => render::completed(&habits.done(&label)?, out),
Command::Pause { label } => {
render::state_changed(&habits.set_state(&label, DeclaredState::Paused)?, out)
}
Command::Resume { label } => {
render::state_changed(&habits.set_state(&label, DeclaredState::Active)?, out)
}
Command::Check => out.line(habits.check()?),
Command::List | Command::Due => unreachable!(),
}),
}
}