use crate::config::CliConfig;
use anyhow::{Result, anyhow};
use clap::{ArgGroup, Args};
use org_core::{ClearField, OrgMode, PropertyPair, UpdateEntry};
#[derive(Args)]
#[command(group(
ArgGroup::new("target")
.args(["id", "file"])
.required(true)
.multiple(true),
))]
#[command(group(
ArgGroup::new("mutation")
.args([
"todo_state",
"priority",
"tags",
"scheduled",
"deadline",
"closed",
"clear",
"title",
"body",
"properties",
"remove_properties",
])
.required(true)
.multiple(true),
))]
pub struct UpdateTodoCommand {
#[arg(long)]
id: Option<String>,
#[arg(short = 'F', long, requires = "heading")]
file: Option<String>,
#[arg(long, requires = "file")]
heading: Option<String>,
#[arg(short = 's', long)]
todo_state: Option<String>,
#[arg(short = 'p', long)]
priority: Option<String>,
#[arg(short = 't', long, value_delimiter = ',')]
tags: Option<Vec<String>>,
#[arg(long)]
scheduled: Option<String>,
#[arg(long)]
deadline: Option<String>,
#[arg(long)]
closed: Option<String>,
#[arg(long, value_delimiter = ',')]
clear: Vec<String>,
#[arg(long)]
title: Option<String>,
#[arg(long)]
body: Option<String>,
#[arg(long = "property", value_name = "KEY=VALUE")]
properties: Vec<String>,
#[arg(long = "remove-property", value_name = "KEY")]
remove_properties: Vec<String>,
#[arg(short = 'f', long)]
format: Option<OutputFormat>,
}
#[derive(clap::ValueEnum, Clone)]
enum OutputFormat {
Plain,
Json,
}
impl UpdateTodoCommand {
pub fn execute(&self, org_mode: OrgMode, cli: CliConfig) -> Result<()> {
let clear = self
.clear
.iter()
.map(|s| {
s.parse::<ClearField>()
.map_err(|e| anyhow!("invalid --clear value: {e}"))
})
.collect::<Result<Vec<_>>>()?;
let properties: Option<Vec<PropertyPair>> = if self.properties.is_empty() {
None
} else {
Some(
self.properties
.iter()
.map(|s| {
let (k, v) = s
.split_once('=')
.ok_or_else(|| anyhow!("--property must be KEY=VALUE, got '{s}'"))?;
Ok(PropertyPair {
key: k.to_string(),
value: v.to_string(),
})
})
.collect::<Result<Vec<_>>>()?,
)
};
let remove_properties: Option<Vec<String>> = if self.remove_properties.is_empty() {
None
} else {
Some(self.remove_properties.clone())
};
let entry = UpdateEntry {
id: self.id.clone(),
file: self.file.clone(),
heading_path: self.heading.clone(),
todo_state: self.todo_state.clone(),
priority: self.priority.clone(),
tags: self.tags.clone(),
scheduled: self.scheduled.clone(),
deadline: self.deadline.clone(),
closed: self.closed.clone(),
clear,
title: self.title.clone(),
body: self.body.clone(),
properties,
remove_properties,
};
let result = org_mode.update_todo(entry)?;
let format = self.format.as_ref().unwrap_or({
match cli.default_format.as_str() {
"json" => &OutputFormat::Json,
_ => &OutputFormat::Plain,
}
});
match format {
OutputFormat::Plain => {
println!("Updated {}", result.file_path);
println!(" {}", result.heading_line);
for change in &result.changes {
println!(" {change}");
}
}
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&result)?);
}
}
Ok(())
}
}