tix-tracker 0.12.0

A terminal issue tracker: markdown + YAML frontmatter issues with a kanban TUI and CLI
Documentation
use std::io::IsTerminal;
use std::path::PathBuf;

use crate::config::Config;
use crate::error::{Result, TixError};
use crate::planning::Planner;
use crate::storage::{IssueSelector, 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, cycle: Option<&str>, yes: bool) -> Result<()> {
    let planner = Planner::from_config(config.clone(), root_from_store(store));
    let today = crate::model::today();
    let mut plan = planner.rollover_plan(store, &today);
    if let Some(cycle) = cycle {
        plan.retain(|target| target.from_cycle == cycle);
    }
    if plan.is_empty() {
        println!("no rollover needed");
        return Ok(());
    }

    let mut confirmed = yes || !std::io::stdin().is_terminal();
    if !confirmed {
        let total: usize = plan.iter().map(|t| t.issues.len()).sum();
        eprint!("Apply rollover for {total} issue(s)? [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(());
    }

    for target in plan {
        let mut moved = 0usize;
        for number in target.issues {
            let mut issue = store.get(IssueSelector::Number(number))?;
            let mut cycles = issue.cycles.take().unwrap_or_default();
            if cycles.iter().any(|c| c == &target.to_cycle) {
                continue;
            }
            cycles.retain(|c| c != &target.from_cycle);
            cycles.push(target.to_cycle.clone());
            issue.cycles = Some(cycles);
            issue.bump_updated();
            store.update(&issue.filename, &issue)?;
            moved += 1;
        }
        println!(
            "moved {moved} issue(s) from \"{}\" to \"{}\"",
            target.from_cycle, target.to_cycle
        );
    }
    Ok(())
}