Use short IDs in next and rm human output

Amolith created

next extracted task IDs as raw ULID strings for the scoring engine
then displayed those same strings. rm did the same with unblocked_ids
in its warning message. Both now go through TaskId::Display or
display_id() at render time.

Add display_id() to TaskId for contexts where a raw &str needs
shortening without constructing a TaskId.

Change summary

src/cmd/next.rs   |  6 ++++--
src/cmd/rm.rs     | 15 ++++++++-------
tests/cli_next.rs |  3 ++-
tests/cli_rm.rs   |  2 +-
4 files changed, 15 insertions(+), 11 deletions(-)

Detailed changes

src/cmd/next.rs 🔗

@@ -96,9 +96,10 @@ pub fn run(root: &Path, mode_str: &str, verbose: bool, limit: usize, json: bool)
         table.set_header(vec!["#", "ID", "SCORE", "TITLE"]);
 
         for (i, s) in scored.iter().enumerate() {
+            let short = db::TaskId::display_id(&s.id);
             table.add_row(vec![
                 Cell::new(i + 1),
-                cell_bold(&s.id, use_color),
+                cell_bold(&short, use_color),
                 Cell::new(format!("{:.2}", s.score)),
                 Cell::new(&s.title),
             ]);
@@ -108,7 +109,8 @@ pub fn run(root: &Path, mode_str: &str, verbose: bool, limit: usize, json: bool)
         if verbose {
             println!();
             for (i, s) in scored.iter().enumerate() {
-                println!("{}. {} — score: {:.2}", i + 1, s.id, s.score);
+                let short = db::TaskId::display_id(&s.id);
+                println!("{}. {} — score: {:.2}", i + 1, short, s.score);
             }
         }
     }

src/cmd/rm.rs 🔗

@@ -38,11 +38,11 @@ pub fn run(root: &Path, ids: &[String], recursive: bool, force: bool, json: bool
         .map(|id| id.as_str().to_string())
         .collect();
 
-    let unblocked_ids: Vec<String> = all
+    let unblocked_ids: Vec<db::TaskId> = all
         .iter()
         .filter(|t| !deleted_set.contains(t.id.as_str()))
         .filter(|t| t.blockers.iter().any(|b| deleted_set.contains(b.as_str())))
-        .map(|t| t.id.as_str().to_string())
+        .map(|t| t.id.clone())
         .collect();
 
     let ts = db::now_utc();
@@ -73,10 +73,8 @@ pub fn run(root: &Path, ids: &[String], recursive: bool, force: bool, json: bool
     })?;
 
     if !force && !unblocked_ids.is_empty() {
-        eprintln!(
-            "warning: removed blockers from {}",
-            unblocked_ids.join(", ")
-        );
+        let short: Vec<String> = unblocked_ids.iter().map(ToString::to_string).collect();
+        eprintln!("warning: removed blockers from {}", short.join(", "));
     }
 
     if json {
@@ -86,7 +84,10 @@ pub fn run(root: &Path, ids: &[String], recursive: bool, force: bool, json: bool
                 .iter()
                 .map(|id| id.as_str().to_string())
                 .collect(),
-            unblocked_ids,
+            unblocked_ids: unblocked_ids
+                .iter()
+                .map(|id| id.as_str().to_string())
+                .collect(),
         };
         println!("{}", serde_json::to_string(&out)?);
     } else {

tests/cli_next.rs 🔗

@@ -45,12 +45,13 @@ fn next_single_task() {
     let tmp = init_tmp();
     let id = create_task(&tmp, "Only task", "high", "low");
 
+    let short = &id[id.len() - 7..];
     td(&tmp)
         .arg("next")
         .current_dir(&tmp)
         .assert()
         .success()
-        .stdout(predicate::str::contains(&id))
+        .stdout(predicate::str::contains(short))
         .stdout(predicate::str::contains("Only task"))
         .stdout(predicate::str::contains("SCORE"));
 }

tests/cli_rm.rs 🔗

@@ -137,7 +137,7 @@ fn rm_detaches_dependents_and_warns() {
         .assert()
         .success()
         .stderr(predicate::str::contains("warning"))
-        .stderr(predicate::str::contains(&dependent));
+        .stderr(predicate::str::contains(&dependent[dependent.len() - 7..]));
 
     let dependent_task = get_task_json(&tmp, &dependent);
     let blockers = dependent_task["blockers"].as_array().unwrap();