From cb7022047985bdb67a37d67647be0525969ce747 Mon Sep 17 00:00:00 2001 From: Mark Keller <8452750+keller00@users.noreply.github.com> Date: Fri, 23 Jan 2026 22:28:45 -0800 Subject: [PATCH 1/2] style: adding rustfmt hook and running hooky --- .hooky.kdl | 8 +++++ src/cli.rs | 78 ++++++++++++++++++++++++++++------------------ src/lib.rs | 28 ++++++++++++----- tests/cli_tests.rs | 66 ++++++++++++++++++++++++++++++--------- tests/lib_test.rs | 72 ++++++++++++++++++++++++++++++++---------- 5 files changed, 184 insertions(+), 68 deletions(-) diff --git a/.hooky.kdl b/.hooky.kdl index 1b5352d..aa7bedf 100644 --- a/.hooky.kdl +++ b/.hooky.kdl @@ -6,6 +6,14 @@ hook hooky { files * } +hook cargo_fmt { + command cargo fmt { + check_args --check + pass_files none + } + files *.rs +} + lint_rules { - check_broken_symlinks - check_case_conflict diff --git a/src/cli.rs b/src/cli.rs index ea70375..bce232e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,9 +1,9 @@ use crate::constants::{BIN, BIN_VERSION}; use crate::models::NewTodo; -use colored::Colorize; -use chrono::{DateTime, Utc, Local}; +use chrono::{DateTime, Local, Utc}; use clap::{Parser, Subcommand}; +use colored::Colorize; #[derive(Parser)] #[command( @@ -106,7 +106,11 @@ pub fn run_cli() { Commands::Add { title, complete } => { add_todo(title, complete); } - Commands::List { all, completed, open: _ } => { + Commands::List { + all, + completed, + open: _, + } => { // Priority: --all > --completed > default (--open) if all { // Show all TODOs @@ -142,31 +146,25 @@ fn format_datetime(ts: DateTime, precise: bool) -> String { if !precise { let ht = chrono_humanize::HumanTime::from(ts); return format!("{}", ht); - } format!("{}", local_tz.format("%d/%m/%Y %H:%M")) } fn format_datetime_or_else(ts: Option>, else_item: String, precise: bool) -> String { match ts { - Some(ts) => { - format_datetime(ts, precise) - } - None => { - else_item - } + Some(ts) => format_datetime(ts, precise), + None => else_item, } } fn show_todo(id: &String) { let found_todo = crate::get_todo(id); let created_str: String = format_datetime(found_todo.created, false); - let completed_str: String = format_datetime_or_else(found_todo.completed, "not yet".to_string(), false); - println!("{}\n{}\nIt was created: {}\nIt was completed: {}", - found_todo.title, - found_todo.notes, - created_str, - completed_str, + let completed_str: String = + format_datetime_or_else(found_todo.completed, "not yet".to_string(), false); + println!( + "{}\n{}\nIt was created: {}\nIt was completed: {}", + found_todo.title, found_todo.notes, created_str, completed_str, ); } @@ -186,12 +184,22 @@ pub fn edit_todo(id: String) { fn complete_todo(id: &String) { crate::complete_todo(id, None); - println!("{} completed, if this was a mistake reopen with `{} reopen {}`", id.yellow(), BIN, id) + println!( + "{} completed, if this was a mistake reopen with `{} reopen {}`", + id.yellow(), + BIN, + id + ) } fn reopen_todo(id: &String) { crate::reopen_todo(id); - println!("{} reopened, if this was a mistake complete with `{} complete {}`", id.yellow(), BIN, id) + println!( + "{} reopened, if this was a mistake complete with `{} complete {}`", + id.yellow(), + BIN, + id + ) } pub fn delete_todo(id: &String) { @@ -208,8 +216,11 @@ pub fn add_todo(title: Option, complete_after_creation: bool) { }; let p_buff = crate::get_todoeditmsg_file(); let fp = p_buff.as_path(); - let (title, notes) = - crate::create_temp_todo_file_open_and_then_read_remove_process(fp, title_str, String::new()); + let (title, notes) = crate::create_temp_todo_file_open_and_then_read_remove_process( + fp, + title_str, + String::new(), + ); let new_todo = NewTodo { title: title.as_str(), notes: notes.as_str(), @@ -221,14 +232,15 @@ pub fn add_todo(title: Option, complete_after_creation: bool) { &crate::encode_id(created_todo.id.try_into().unwrap()), Some(created_todo.created), ); - } println!( "{} created{}", crate::encode_id(created_todo.id.try_into().unwrap()).yellow(), if complete_after_creation { " and was subsequently completed" - } else {""} + } else { + "" + } ); } @@ -257,7 +269,10 @@ pub fn list_todos(show_completed: Option) { results.sort_by(|a, b| b.id.cmp(&a.id)); if results.is_empty() { - println!("There's nothing to do currently :) Add a new one with `{} add`", BIN); + println!( + "There's nothing to do currently :) Add a new one with `{} add`", + BIN + ); } else { let mut table = comfy_table::Table::new(); table.load_preset(comfy_table::presets::NOTHING); @@ -268,17 +283,20 @@ pub fn list_todos(show_completed: Option) { // With custom_styling comfy_table flag we can keep using colorize colors, but // slow down comfy table by 30-50%. I think this is acceptable for now, but // could later switch to using comfy_table's built-in coloring. - crate::encode_id(post.id.try_into().expect("Failed to cast post id in list")).yellow().to_string(), - ), - comfy_table::Cell::new( - format_datetime(post.created, false), + crate::encode_id(post.id.try_into().expect("Failed to cast post id in list")) + .yellow() + .to_string(), ), + comfy_table::Cell::new(format_datetime(post.created, false)), comfy_table::Cell::new(post.title), ]); } - table.column_mut(2).unwrap().set_constraint( - comfy_table::ColumnConstraint::UpperBoundary(comfy_table::Width::Percentage(60)) - ); + table + .column_mut(2) + .unwrap() + .set_constraint(comfy_table::ColumnConstraint::UpperBoundary( + comfy_table::Width::Percentage(60), + )); println!("{table}") } } diff --git a/src/lib.rs b/src/lib.rs index 8e8f8e8..223c1f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,14 +32,19 @@ fn create_sqids_encoder_with_custom_alphabet() -> Sqids { } pub fn encode_id(i: u64) -> String { - create_sqids_encoder_with_custom_alphabet().encode(&vec![i]).expect("Problem encoding id") + create_sqids_encoder_with_custom_alphabet() + .encode(&vec![i]) + .expect("Problem encoding id") } pub fn decode_id(s: &str) -> i32 { // TODO can I make this nicer? - (*create_sqids_encoder_with_custom_alphabet().decode(s).first().expect("Couldn't decode id")) - .try_into() - .unwrap() + (*create_sqids_encoder_with_custom_alphabet() + .decode(s) + .first() + .expect("Couldn't decode id")) + .try_into() + .unwrap() } // Path-related functions @@ -158,7 +163,8 @@ pub fn get_todo(get_id: &String) -> Todos { use self::schema::todos::dsl::*; let connection = &mut establish_connection(); let decoded_id = decode_id(get_id); - todos.select(Todos::as_select()) + todos + .select(Todos::as_select()) .filter(id.eq(decoded_id)) .first(connection) .unwrap_or_else(|_| panic!("Single TODO couldn't be found with id {}", get_id)) @@ -167,7 +173,8 @@ pub fn get_todo(get_id: &String) -> Todos { pub fn get_todos() -> Vec { use self::schema::todos::dsl::*; let connection = &mut establish_connection(); - todos.select(Todos::as_select()) + todos + .select(Todos::as_select()) .load(connection) .expect("Was unable to get all TODOs") } @@ -200,7 +207,7 @@ pub fn set_todo_notes(update_id: &String, new_notes: &String) { diesel::update(todos.find(decoded_id)) .set(notes.eq(new_notes)) .execute(connection) - .unwrap_or_else( |_| panic!("notes of TODO: {} couldn't be updated", update_id)); + .unwrap_or_else(|_| panic!("notes of TODO: {} couldn't be updated", update_id)); } pub fn reopen_todo(show_id: &String) { @@ -212,7 +219,12 @@ pub fn reopen_todo(show_id: &String) { .set(completed.eq(None::>)) .execute(connection) .expect("TODO couldn't be reopened"); - println!("{} reopened, if this was a mistake complete with `{} complete {}`", show_id.yellow(), BIN, show_id) + println!( + "{} reopened, if this was a mistake complete with `{} complete {}`", + show_id.yellow(), + BIN, + show_id + ) } pub fn delete_todo(delete_id: &String) { diff --git a/tests/cli_tests.rs b/tests/cli_tests.rs index 2fc4dbe..3e3ac23 100644 --- a/tests/cli_tests.rs +++ b/tests/cli_tests.rs @@ -1,4 +1,5 @@ use assert_cmd::Command; +use chrono::Utc; use diesel::prelude::*; use predicates::prelude::*; use rstest::rstest; @@ -7,7 +8,6 @@ use tempdir::TempDir; use workingon::models::NewTodo; use workingon::schema::todos::dsl::*; use workingon::{encode_id, establish_connection}; -use chrono::Utc; // Helper function to get the latest TODO from the database fn get_latest_todo() -> Option<(String, workingon::models::Todos)> { @@ -83,7 +83,9 @@ fn test_list_empty() { .args(["list"]) .assert() .success() - .stdout(predicate::str::contains("There's nothing to do currently :)")); + .stdout(predicate::str::contains( + "There's nothing to do currently :)", + )); } #[test] @@ -95,7 +97,9 @@ fn test_list_alias_ls() { .args(["ls"]) .assert() .success() - .stdout(predicate::str::contains("There's nothing to do currently :)")); + .stdout(predicate::str::contains( + "There's nothing to do currently :)", + )); } #[test] @@ -163,11 +167,18 @@ fn test_add_and_show_todo() { let tmp_dir = TempDir::new("workingon_test").expect("cannot make temp directory for test"); // Set environment variable for the test - std::env::set_var("WORKINGON_DATA_DIR", tmp_dir.path().to_string_lossy().to_string()); + std::env::set_var( + "WORKINGON_DATA_DIR", + tmp_dir.path().to_string_lossy().to_string(), + ); std::env::set_var("EDITOR", "-"); // Add a TODO using the library - workingon::add_todo(&NewTodo{title: "First TODO", notes:"", created:Utc::now()}); + workingon::add_todo(&NewTodo { + title: "First TODO", + notes: "", + created: Utc::now(), + }); // Get the TODO ID directly from the database let (todo_id, _todo) = get_latest_todo().expect("No todo found"); @@ -211,11 +222,18 @@ fn test_complete_and_reopen_todo() { let tmp_dir = TempDir::new("workingon_test").expect("cannot make temp directory for test"); // Set environment variable for the test - std::env::set_var("WORKINGON_DATA_DIR", tmp_dir.path().to_string_lossy().to_string()); + std::env::set_var( + "WORKINGON_DATA_DIR", + tmp_dir.path().to_string_lossy().to_string(), + ); std::env::set_var("EDITOR", "-"); // Add a TODO using the library - workingon::add_todo(&NewTodo{title: "Complete and Reopen Test TODO", notes:"", created:Utc::now()}); + workingon::add_todo(&NewTodo { + title: "Complete and Reopen Test TODO", + notes: "", + created: Utc::now(), + }); // Get the TODO ID directly from the database let (todo_id, todo) = get_latest_todo().expect("No todo found"); @@ -231,7 +249,9 @@ fn test_complete_and_reopen_todo() { .args(["complete", &todo_id]) .assert() .success() - .stdout(predicate::str::contains("completed, if this was a mistake reopen with `")); + .stdout(predicate::str::contains( + "completed, if this was a mistake reopen with `", + )); // Verify it's completed by checking the database let connection = &mut workingon::establish_connection(); @@ -252,7 +272,9 @@ fn test_complete_and_reopen_todo() { .args(["reopen", &todo_id]) .assert() .success() - .stdout(predicate::str::contains("reopened, if this was a mistake complete with `")); + .stdout(predicate::str::contains( + "reopened, if this was a mistake complete with `", + )); // Verify it's reopened by checking the database let reopened_results = todos @@ -271,14 +293,25 @@ fn test_list_flags_precedence() { let tmp_dir = TempDir::new("workingon_test").expect("cannot make temp directory for test"); // Set environment variable for the test - std::env::set_var("WORKINGON_DATA_DIR", tmp_dir.path().to_string_lossy().to_string()); + std::env::set_var( + "WORKINGON_DATA_DIR", + tmp_dir.path().to_string_lossy().to_string(), + ); std::env::set_var("EDITOR", "-"); // Add an open TODO - workingon::add_todo(&NewTodo{title: "Open TODO", notes:"", created:Utc::now()}); + workingon::add_todo(&NewTodo { + title: "Open TODO", + notes: "", + created: Utc::now(), + }); // Add a completed TODO - workingon::add_todo(&NewTodo{title: "Completed TODO", notes:"", created:Utc::now()}); + workingon::add_todo(&NewTodo { + title: "Completed TODO", + notes: "", + created: Utc::now(), + }); let (completed_todo_id, _) = get_latest_todo().expect("No todo found"); workingon::complete_todo(&completed_todo_id, None); @@ -354,7 +387,10 @@ fn test_list_flags_precedence() { fn test_add_and_complete() { let tmp_dir = TempDir::new("workingon_test").expect("cannot make temp directory for test"); - std::env::set_var("WORKINGON_DATA_DIR", tmp_dir.path().to_string_lossy().to_string()); + std::env::set_var( + "WORKINGON_DATA_DIR", + tmp_dir.path().to_string_lossy().to_string(), + ); std::env::set_var("EDITOR", "-"); // Test 1: Default (no flags) should show only open TODOs @@ -365,7 +401,9 @@ fn test_add_and_complete() { .args(["add", "--complete", "Completed TODO"]) .assert() .success() - .stdout(predicate::str::contains("created and was subsequently completed")); + .stdout(predicate::str::contains( + "created and was subsequently completed", + )); Command::cargo_bin("workingon") .unwrap() diff --git a/tests/lib_test.rs b/tests/lib_test.rs index 9a3d27a..81c1029 100644 --- a/tests/lib_test.rs +++ b/tests/lib_test.rs @@ -1,14 +1,17 @@ +use chrono::Utc; use diesel::prelude::*; use serial_test::serial; use std::env; use tempdir::TempDir; use workingon::{models::NewTodo, *}; -use chrono::Utc; // Helper function to set up a test environment fn setup_test_env() -> TempDir { let tmp_dir = TempDir::new("workingon_test").expect("cannot make temp directory for test"); - env::set_var("WORKINGON_DATA_DIR", tmp_dir.path().to_string_lossy().to_string()); + env::set_var( + "WORKINGON_DATA_DIR", + tmp_dir.path().to_string_lossy().to_string(), + ); env::set_var("EDITOR", "-"); tmp_dir } @@ -177,7 +180,11 @@ fn test_add_todo_with_title() { let _tmp_dir = setup_test_env(); env::set_var("EDITOR", "-"); // Ensure editor is set to dash - add_todo(&NewTodo{title: "Test TODO", notes:"", created:Utc::now()}); + add_todo(&NewTodo { + title: "Test TODO", + notes: "", + created: Utc::now(), + }); // Verify the TODO was added by checking the database let connection = &mut establish_connection(); @@ -200,7 +207,11 @@ fn test_add_todo_without_title() { let _tmp_dir = setup_test_env(); env::set_var("EDITOR", "-"); // Ensure editor is set to dash - add_todo(&NewTodo{title: "", notes:"", created:Utc::now()}); + add_todo(&NewTodo { + title: "<title>", + notes: "", + created: Utc::now(), + }); // Verify the TODO was added with default title let connection = &mut establish_connection(); @@ -224,7 +235,11 @@ fn test_show_todo() { env::set_var("EDITOR", "-"); // Ensure editor is set to dash // Add a TODO first - add_todo(&NewTodo{title: "Show Test TODO", notes:"", created:Utc::now()}); + add_todo(&NewTodo { + title: "Show Test TODO", + notes: "", + created: Utc::now(), + }); // Get the TODO ID let connection = &mut establish_connection(); @@ -250,7 +265,11 @@ fn test_list_todos_flags() { env::set_var("EDITOR", "-"); // Ensure editor is set to dash // Add an open TODO - add_todo(&NewTodo{title: "Open TODO", notes:"", created:Utc::now()}); + add_todo(&NewTodo { + title: "Open TODO", + notes: "", + created: Utc::now(), + }); let connection = &mut establish_connection(); use workingon::schema::todos::dsl::*; let open_results = todos @@ -261,7 +280,11 @@ fn test_list_todos_flags() { let open_todo_id = open_results[0].id; // Add a completed TODO - add_todo(&NewTodo{title: "Completed TODO", notes:"", created:Utc::now()}); + add_todo(&NewTodo { + title: "Completed TODO", + notes: "", + created: Utc::now(), + }); let completed_results = todos .select(workingon::models::Todos::as_select()) .filter(title.eq("Completed TODO")) @@ -310,7 +333,11 @@ fn test_delete_todo() { let _tmp_dir = setup_test_env(); // Add a TODO first - add_todo(&NewTodo{title: "Delete Test TODO", notes:"", created:Utc::now()}); + add_todo(&NewTodo { + title: "Delete Test TODO", + notes: "", + created: Utc::now(), + }); // Get the TODO ID let connection = &mut establish_connection(); @@ -344,7 +371,11 @@ fn test_complete_todo() { env::set_var("EDITOR", "-"); // Ensure editor is set to dash // Add a TODO first - add_todo(&NewTodo{title: "Complete Test TODO", notes:"", created:Utc::now()}); + add_todo(&NewTodo { + title: "Complete Test TODO", + notes: "", + created: Utc::now(), + }); // Get the TODO ID let connection = &mut establish_connection(); @@ -383,7 +414,11 @@ fn test_reopen_todo() { env::set_var("EDITOR", "-"); // Ensure editor is set to dash // Add a TODO first - add_todo(&NewTodo{title: "Reopen Test TODO", notes:"", created:Utc::now()}); + add_todo(&NewTodo { + title: "Reopen Test TODO", + notes: "", + created: Utc::now(), + }); // Get the TODO ID let connection = &mut establish_connection(); @@ -432,7 +467,11 @@ fn test_reopen_todo_already_uncompleted() { env::set_var("EDITOR", "-"); // Ensure editor is set to dash // Add a TODO first - add_todo(&NewTodo{title: "Reopen Uncompleted Test TODO", notes:"", created:Utc::now()}); + add_todo(&NewTodo { + title: "Reopen Uncompleted Test TODO", + notes: "", + created: Utc::now(), + }); // Get the TODO ID let connection = &mut establish_connection(); @@ -486,12 +525,13 @@ fn test_add_and_complete() { let _tmp_dir = setup_test_env(); env::set_var("EDITOR", "-"); // Ensure editor is set to dash - let created_todo = add_todo(&NewTodo{title: "Closed TODO", notes:"", created:Utc::now()}); + let created_todo = add_todo(&NewTodo { + title: "Closed TODO", + notes: "", + created: Utc::now(), + }); let id_string = encode_id(created_todo.id.try_into().unwrap()); - complete_todo( - &id_string, - Some(created_todo.created), - ); + complete_todo(&id_string, Some(created_todo.created)); let updated_todo = get_todo(&id_string); assert!(updated_todo.created == updated_todo.completed.unwrap()) } From eef4b07da8e149d17e1d39f5f354de0dc579fd14 Mon Sep 17 00:00:00 2001 From: Mark Keller <8452750+keller00@users.noreply.github.com> Date: Sun, 25 Jan 2026 13:49:13 -0800 Subject: [PATCH 2/2] ci: adding new Hooky action --- .github/workflows/hooky.yml | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/hooky.yml diff --git a/.github/workflows/hooky.yml b/.github/workflows/hooky.yml new file mode 100644 index 0000000..cff246b --- /dev/null +++ b/.github/workflows/hooky.yml @@ -0,0 +1,38 @@ +name: Verify linting + +on: + push: + branches: [main] + pull_request: + types: [opened, synchronize, reopened] + +jobs: + hooky: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - uses: moonrepo/setup-rust@v1 + - name: Get latest Hooky release tag + id: get_tag + run: | + TAG=$(curl -sSf https://api.github.com/repos/brandonchinn178/hooky/releases/latest \ + | jq -r .tag_name) + echo "Latest Hooky tag is $TAG" + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + - name: Download Hooky binary + id: download + run: | + TAG="${{ steps.get_tag.outputs.tag }}" + ASSET="hooky-${TAG#v}-linux-x86_64" + URL="https://github.com/brandonchinn178/hooky/releases/download/${TAG}/${ASSET}" + echo "Downloading $URL" + sudo curl -L -o /usr/bin/hooky "${URL}" + sudo chmod +x /usr/bin/hooky + - name: Verify dependency versions + run: | + hooky --version + rustfmt --version + - name: Execute hooks + run: hooky run --all --format=verbose