Adopt comfy-table for consistent CLI output formatting

Amolith created

Replace hardcoded format!() spacing with comfy-table across all commands.
Fix missing effort column in list output.

Change summary

Cargo.lock        | 131 +++++++++++++++++++++++++++++++++++++++++++++++++
Cargo.toml        |   1 
src/cmd/list.rs   |  27 +++++----
src/cmd/ready.rs  |  23 ++++---
src/cmd/search.rs |  12 ++++
5 files changed, 170 insertions(+), 24 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -193,18 +193,61 @@ version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
 
+[[package]]
+name = "comfy-table"
+version = "7.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "958c5d6ecf1f214b4c2bbbbf6ab9523a864bd136dcf71a7e8904799acfe1ad47"
+dependencies = [
+ "crossterm",
+ "unicode-segmentation",
+ "unicode-width",
+]
+
 [[package]]
 name = "core-foundation-sys"
 version = "0.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
 
+[[package]]
+name = "crossterm"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
+dependencies = [
+ "bitflags",
+ "crossterm_winapi",
+ "document-features",
+ "parking_lot",
+ "rustix",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
+dependencies = [
+ "winapi",
+]
+
 [[package]]
 name = "difflib"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
 
+[[package]]
+name = "document-features"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
+dependencies = [
+ "litrs",
+]
+
 [[package]]
 name = "equivalent"
 version = "1.0.2"
@@ -396,6 +439,21 @@ version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
 
+[[package]]
+name = "litrs"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
+
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
 [[package]]
 name = "log"
 version = "0.4.29"
@@ -435,6 +493,29 @@ version = "1.70.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
 
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-link",
+]
+
 [[package]]
 name = "pkg-config"
 version = "0.3.32"
@@ -505,6 +586,15 @@ version = "5.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
 
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags",
+]
+
 [[package]]
 name = "regex"
 version = "1.12.3"
@@ -567,6 +657,12 @@ version = "1.0.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
 
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
 [[package]]
 name = "semver"
 version = "1.0.27"
@@ -670,6 +766,18 @@ version = "1.0.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
 
+[[package]]
+name = "unicode-segmentation"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
+
+[[package]]
+name = "unicode-width"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
+
 [[package]]
 name = "unicode-xid"
 version = "0.2.6"
@@ -794,6 +902,28 @@ dependencies = [
  "semver",
 ]
 
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
 [[package]]
 name = "windows-core"
 version = "0.62.2"
@@ -958,6 +1088,7 @@ dependencies = [
  "assert_cmd",
  "chrono",
  "clap",
+ "comfy-table",
  "predicates",
  "rusqlite",
  "serde",

Cargo.toml 🔗

@@ -12,6 +12,7 @@ path = "src/main.rs"
 anyhow = "1"
 chrono = { version = "0.4", default-features = false, features = ["clock"] }
 clap = { version = "4", features = ["derive"] }
+comfy-table = "7.2.2"
 rusqlite = { version = "0.34", features = ["bundled"] }
 serde = { version = "1", features = ["derive"] }
 serde_json = "1"

src/cmd/list.rs 🔗

@@ -1,4 +1,6 @@
 use anyhow::Result;
+use comfy_table::presets::NOTHING;
+use comfy_table::Table;
 use std::path::Path;
 
 use crate::db;
@@ -66,20 +68,19 @@ pub fn run(
         println!("{}", serde_json::to_string(&details)?);
     } else {
         let c = crate::color::stdout_theme();
+        let mut table = Table::new();
+        table.load_preset(NOTHING);
         for t in &tasks {
-            println!(
-                "{}{:<12}{} {}{:<12}{} {}{:<4}{} {}",
-                c.bold,
-                t.id,
-                c.reset,
-                c.yellow,
-                format!("[{}]", t.status),
-                c.reset,
-                c.red,
-                db::priority_label(t.priority),
-                c.reset,
-                t.title,
-            );
+            table.add_row(vec![
+                format!("{}{}{}", c.bold, t.id, c.reset),
+                format!("{}[{}]{}", c.yellow, t.status, c.reset),
+                format!("{}{}{}", c.red, db::priority_label(t.priority), c.reset),
+                format!("{}{}{}", c.blue, db::effort_label(t.effort), c.reset),
+                t.title.clone(),
+            ]);
+        }
+        if !tasks.is_empty() {
+            println!("{table}");
         }
     }
 

src/cmd/ready.rs 🔗

@@ -1,4 +1,6 @@
 use anyhow::Result;
+use comfy_table::presets::NOTHING;
+use comfy_table::Table;
 use std::path::Path;
 
 use crate::db;
@@ -37,17 +39,18 @@ pub fn run(root: &Path, json: bool) -> Result<()> {
         println!("{}", serde_json::to_string(&summary)?);
     } else {
         let c = crate::color::stdout_theme();
+        let mut table = Table::new();
+        table.load_preset(NOTHING);
         for t in &tasks {
-            println!(
-                "{}{:<12}{} {}{:<8}{} {}",
-                c.green,
-                t.id,
-                c.reset,
-                c.red,
-                db::priority_label(t.priority),
-                c.reset,
-                t.title
-            );
+            table.add_row(vec![
+                format!("{}{}{}", c.green, t.id, c.reset),
+                format!("{}{}{}", c.red, db::priority_label(t.priority), c.reset),
+                format!("{}{}{}", c.blue, db::effort_label(t.effort), c.reset),
+                t.title.clone(),
+            ]);
+        }
+        if !tasks.is_empty() {
+            println!("{table}");
         }
     }
 

src/cmd/search.rs 🔗

@@ -1,4 +1,6 @@
 use anyhow::Result;
+use comfy_table::presets::NOTHING;
+use comfy_table::Table;
 use std::path::Path;
 
 use crate::db;
@@ -31,8 +33,16 @@ pub fn run(root: &Path, query: &str, json: bool) -> Result<()> {
         println!("{}", serde_json::to_string(&summary)?);
     } else {
         let c = crate::color::stdout_theme();
+        let mut table = Table::new();
+        table.load_preset(NOTHING);
         for t in &tasks {
-            println!("{}{}{} {}", c.bold, t.id, c.reset, t.title);
+            table.add_row(vec![
+                format!("{}{}{}", c.bold, t.id, c.reset),
+                t.title.clone(),
+            ]);
+        }
+        if !tasks.is_empty() {
+            println!("{table}");
         }
     }