use std::io::IsTerminal;
use std::path::PathBuf;
use crate::cli::MilestoneCommand;
use crate::config::Config;
use crate::error::{Result, TixError};
use crate::planning::Planner;
use crate::storage::Store;
fn root_from_store(store: &Store) -> PathBuf {
Config::project_root(&store.issues_dir).unwrap_or_else(|| PathBuf::from("."))
}
pub fn run(config: &Config, store: &Store, cmd: &MilestoneCommand) -> Result<()> {
let planner = Planner::from_config(config.clone(), root_from_store(store));
match cmd {
MilestoneCommand::Create {
name,
target_date,
description,
} => {
let path = planner.create_milestone_file(
name,
target_date.as_deref(),
description.as_deref(),
)?;
println!("{}", path.display());
}
MilestoneCommand::List => {
for milestone in planner.list_milestones()? {
let issues = store
.list()?
.into_iter()
.filter(|issue| {
issue.milestones.as_ref().is_some_and(|milestones| {
milestones.iter().any(|m| m == &milestone.name)
})
})
.count();
println!(
"{}\t{}\t{}\t{}",
milestone.name, milestone.target_date, milestone.description, issues
);
}
}
MilestoneCommand::Edit {
name,
new_name,
target_date,
description,
} => {
let mut active_name = name.clone();
if let Some(new_name) = new_name {
let old = planner.milestone(name)?;
let impact = planner.rename_milestone_cascade(store, &old, new_name)?;
println!(
"renamed milestone `{name}` to `{new_name}`; updated {} issue(s)",
impact.changed_issue_count
);
active_name = new_name.clone();
}
if target_date.is_some() || description.is_some() {
planner.update_milestone(
&active_name,
target_date.as_deref(),
description.as_deref(),
)?;
}
}
MilestoneCommand::Delete { name, yes } => {
let impact = planner.milestone_delete_impact(store, name)?;
if !impact.referencing_issues.is_empty() {
eprintln!(
"milestone `{name}` is referenced by {} issue(s): {}",
impact.referencing_issues.len(),
impact.referencing_issues.join(", ")
);
}
let mut confirmed = *yes || !std::io::stdin().is_terminal();
if !confirmed {
eprint!("Delete milestone '{name}'? [y/N] ");
let mut line = String::new();
std::io::stdin()
.read_line(&mut line)
.map_err(TixError::Io)?;
confirmed = line.trim().eq_ignore_ascii_case("y");
}
if !confirmed {
eprintln!("aborted");
return Ok(());
}
let filename = planner.delete_milestone(name)?;
println!("deleted milestone `{name}` ({filename})");
if !impact.referencing_issues.is_empty() {
println!("note: issue milestone references were left unchanged");
}
}
}
Ok(())
}