use crate::cli::ExitCode;
use crate::error::Result;
use crate::storage::Store;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Action {
Archive,
Restore,
}
impl Action {
fn past_tense(self) -> &'static str {
match self {
Action::Archive => "Archived",
Action::Restore => "Restored",
}
}
}
pub fn run(store: &Store, action: Action, selectors: &[String]) -> Result<ExitCode> {
let mut code = ExitCode::Success;
for selector in selectors {
let outcome = match action {
Action::Archive => store.archive(selector),
Action::Restore => store.restore(selector),
};
match outcome {
Ok(issue) => println!("{} {}", action.past_tense(), issue.filename),
Err(e) => {
eprintln!("tix: {e}");
if code == ExitCode::Success {
code = crate::cli::exit_code_for(&e);
}
}
}
}
Ok(code)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
fn make_store() -> (TempDir, Store) {
let tmp = TempDir::new().unwrap();
let dir = tmp.path().join("issues");
fs::create_dir_all(dir.join("alice")).unwrap();
let config = crate::config::Config {
issues_dir: dir,
..crate::config::Config::default()
};
(tmp, Store::open(&config).unwrap())
}
fn write_issue(store: &Store, name: &str, number: u32, title: &str) {
let contents = format!(
"---\nnumber: {number}\ntitle: \"{title}\"\nstatus: \"todo\"\nlabels: []\n\
priority: \"medium\"\ncreated: \"2026-01-01\"\nupdated: \"2026-01-01\"\n---\n\nbody\n"
);
fs::write(store.issues_dir.join(name), contents).unwrap();
}
#[test]
fn one_failure_does_not_roll_back_another_item() {
let (_tmp, store) = make_store();
write_issue(&store, "alice/2026-01-01-a.md", 1, "A");
write_issue(&store, "alice/2026-01-01-b.md", 2, "B");
let code = run(&store, Action::Archive, &["999".into(), "2".into()]).unwrap();
assert_ne!(code, ExitCode::Success, "the bogus item fails the batch");
assert!(
store.issues_dir.join("alice/.2026-01-01-b.md").is_file(),
"the valid item is archived despite the earlier failure"
);
assert!(store.issues_dir.join("alice/2026-01-01-a.md").is_file());
}
#[test]
fn redundant_operation_reports_but_other_items_proceed() {
let (_tmp, store) = make_store();
write_issue(&store, "alice/2026-01-01-a.md", 1, "A");
write_issue(&store, "alice/2026-01-01-b.md", 2, "B");
let code = run(
&store,
Action::Archive,
&["1".into(), "1".into(), "2".into()],
)
.unwrap();
assert_ne!(code, ExitCode::Success);
assert!(store.issues_dir.join("alice/.2026-01-01-a.md").is_file());
assert!(store.issues_dir.join("alice/.2026-01-01-b.md").is_file());
}
#[test]
fn all_success_returns_success() {
let (_tmp, store) = make_store();
write_issue(&store, "alice/2026-01-01-a.md", 1, "A");
let code = run(&store, Action::Archive, &["1".into()]).unwrap();
assert_eq!(code, ExitCode::Success);
let code = run(&store, Action::Restore, &["1".into()]).unwrap();
assert_eq!(code, ExitCode::Success);
assert!(store.issues_dir.join("alice/2026-01-01-a.md").is_file());
}
}