From a2c375563592aab432ceaef436ea2bc921880599 Mon Sep 17 00:00:00 2001 From: lew Date: Thu, 2 Apr 2026 19:35:42 +0100 Subject: [PATCH] fix: snooze and unsnooze always resolve targets to alarm IDs --- nag | 146 ++++++++++++++++++++++++++++++++--------------- test/snooze.bats | 75 +++++++++++++++++------- 2 files changed, 156 insertions(+), 65 deletions(-) diff --git a/nag b/nag index e3b52e2..49b34f0 100755 --- a/nag +++ b/nag @@ -2033,20 +2033,34 @@ _snooze_by_tag() { fi _ensure_nag_dir + + local _until_ts="" _until_date="" if [[ -n "${_duration_str}" ]] then - local _until_ts _until_ts="$(_parse_time "${_duration_str}")" - printf "%s\t%s\\n" "${_tag}" "${_until_ts}" >> "${_SNOOZED_FILE}" - _release_lock - local _until_date _until_date="$(date -d "@${_until_ts}" "+%b %-d")" - printf "Snoozed [%s] until %s.\\n" "${_tag}" "${_until_date}" - else - printf "%s\\n" "${_tag}" >> "${_SNOOZED_FILE}" - _release_lock - printf "Snoozed [%s].\\n" "${_tag}" fi + + for _line in "${_ALARMS[@]}" + do + [[ -n "${_line}" ]] || continue + local _tags _id _message + _tags="$(_get_alarm_field "${_line}" 2)" + _alarm_has_tag "${_tags}" "${_tag}" || continue + _id="${_line%%$'\t'*}" + _message="$(_get_alarm_field "${_line}" 5)" + _remove_snoozed_entry "${_id}" 2>/dev/null || true + if [[ -n "${_until_ts}" ]] + then + printf "%s\t%s\\n" "${_id}" "${_until_ts}" >> "${_SNOOZED_FILE}" + printf "Snoozed [%s] %s until %s.\\n" "${_id}" "${_message}" "${_until_date}" + else + printf "%s\\n" "${_id}" >> "${_SNOOZED_FILE}" + printf "Snoozed [%s] %s.\\n" "${_id}" "${_message}" + fi + done + + _release_lock } snooze() { @@ -2056,22 +2070,43 @@ snooze() { if [[ "${_target}" == "all" ]] then local _duration_str="${2:-}" - _ensure_nag_dir _acquire_lock + _read_alarms + + if (( ${#_ALARMS[@]} == 0 )) + then + _release_lock + _exit_1 printf "No alarms to snooze.\\n" + fi + + _ensure_nag_dir + + local _until_ts="" _until_date="" if [[ -n "${_duration_str}" ]] then - local _until_ts _until_ts="$(_parse_time "${_duration_str}")" - printf "*\t%s\\n" "${_until_ts}" > "${_SNOOZED_FILE}" - _release_lock - local _until_date _until_date="$(date -d "@${_until_ts}" "+%b %-d")" - printf "Snoozed all until %s.\\n" "${_until_date}" - else - printf "*\\n" > "${_SNOOZED_FILE}" - _release_lock - printf "Snoozed all.\\n" fi + + local _line + for _line in "${_ALARMS[@]}" + do + [[ -n "${_line}" ]] || continue + local _id _message + _id="${_line%%$'\t'*}" + _message="$(_get_alarm_field "${_line}" 5)" + _remove_snoozed_entry "${_id}" 2>/dev/null || true + if [[ -n "${_until_ts}" ]] + then + printf "%s\t%s\\n" "${_id}" "${_until_ts}" >> "${_SNOOZED_FILE}" + printf "Snoozed [%s] %s until %s.\\n" "${_id}" "${_message}" "${_until_date}" + else + printf "%s\\n" "${_id}" >> "${_SNOOZED_FILE}" + printf "Snoozed [%s] %s.\\n" "${_id}" "${_message}" + fi + done + + _release_lock return fi @@ -2107,6 +2142,7 @@ snooze() { local _duration_str="${2:-}" _ensure_nag_dir + _remove_snoozed_entry "${_target}" 2>/dev/null || true if [[ -n "${_duration_str}" ]] then local _until_ts @@ -2127,46 +2163,66 @@ _unsnooze_by_tag() { local _tag="${1}" _acquire_lock - - if [[ ! -f "${_SNOOZED_FILE}" ]] - then - _release_lock - _exit_1 printf "Tag [%s] is not snoozed.\\n" "${_tag}" - fi - - local _found_in_file=0 _entry - while IFS= read -r _entry || [[ -n "${_entry}" ]] - do - [[ -n "${_entry}" ]] || continue - local _key="${_entry%%$'\t'*}" - [[ "${_key}" == "${_tag}" ]] && _found_in_file=1 && break - done < "${_SNOOZED_FILE}" - - if (( ! _found_in_file )) - then - _release_lock - _exit_1 printf "Tag [%s] is not snoozed.\\n" "${_tag}" - fi - _read_alarms - local _match_count=0 _line + + # Find alarms with this tag whose IDs are in the snoozed file. + local -a _snoozed_ids=() + local _line for _line in "${_ALARMS[@]:-}" do [[ -n "${_line}" ]] || continue local _tags _tags="$(_get_alarm_field "${_line}" 2)" - _alarm_has_tag "${_tags}" "${_tag}" && _match_count=$((_match_count + 1)) + _alarm_has_tag "${_tags}" "${_tag}" || continue + local _id="${_line%%$'\t'*}" + # Check if this ID is in the snoozed file. + if [[ -f "${_SNOOZED_FILE}" ]] + then + local _entry + while IFS= read -r _entry || [[ -n "${_entry}" ]] + do + [[ -n "${_entry}" ]] || continue + local _key="${_entry%%$'\t'*}" + if [[ "${_key}" == "${_id}" ]] + then + _snoozed_ids+=("${_id}") + break + fi + done < "${_SNOOZED_FILE}" + fi done - if ! _confirm_action "Unsnooze" "${_match_count}" "${_tag}" + if (( ${#_snoozed_ids[@]} == 0 )) + then + _release_lock + _exit_1 printf "[%s] alarms are not snoozed.\\n" "${_tag}" + fi + + if ! _confirm_action "Unsnooze" "${#_snoozed_ids[@]}" "${_tag}" then _release_lock return 0 fi - _remove_snoozed_entry "${_tag}" + local _id + for _id in "${_snoozed_ids[@]}" + do + _remove_snoozed_entry "${_id}" + local _message="" + for _line in "${_ALARMS[@]:-}" + do + [[ -n "${_line}" ]] || continue + local _aid="${_line%%$'\t'*}" + if [[ "${_aid}" == "${_id}" ]] + then + _message="$(_get_alarm_field "${_line}" 5)" + break + fi + done + printf "Unsnoozed [%s] %s.\\n" "${_id}" "${_message}" + done + _release_lock - printf "Unsnoozed [%s].\\n" "${_tag}" } # unsnooze ################################################################### diff --git a/test/snooze.bats b/test/snooze.bats index 7cc28fb..d648eb8 100644 --- a/test/snooze.bats +++ b/test/snooze.bats @@ -27,31 +27,36 @@ load test_helper [ "${status}" -eq 1 ] } -@test "snooze all creates global entry" { +@test "snooze all creates entries for all alarm IDs" { + run_nag at "tomorrow 3pm" "first" + run_nag at "tomorrow 4pm" "second" run_nag snooze all [ "${status}" -eq 0 ] - [[ "${output}" =~ "Snoozed all." ]] + [[ "${output}" =~ "Snoozed [1]" ]] + [[ "${output}" =~ "Snoozed [2]" ]] [ -f "${NAG_DIR}/snoozed" ] - grep -q "^\*$" "${NAG_DIR}/snoozed" + grep -q "^1$" "${NAG_DIR}/snoozed" + grep -q "^2$" "${NAG_DIR}/snoozed" } -@test "snooze all with duration sets expiry" { +@test "snooze all with duration sets expiry for each alarm" { + run_nag at "tomorrow 3pm" "first" + run_nag at "tomorrow 4pm" "second" run_nag snooze all "tomorrow" [ "${status}" -eq 0 ] - [[ "${output}" =~ "Snoozed all" ]] [[ "${output}" =~ "until" ]] - local _line - _line="$(cat "${NAG_DIR}/snoozed")" - [[ "${_line}" == \*$'\t'* ]] + grep -q "^1"$'\t' "${NAG_DIR}/snoozed" + grep -q "^2"$'\t' "${NAG_DIR}/snoozed" } -@test "snooze by tag creates entry in snoozed file" { +@test "snooze by tag creates entries for matching alarm IDs" { run_nag at "tomorrow 3pm" "work task" run_nag tag 1 work run_nag snooze work [ "${status}" -eq 0 ] - [[ "${output}" =~ "Snoozed [work]" ]] - grep -q "^work$" "${NAG_DIR}/snoozed" + [[ "${output}" =~ "Snoozed [1]" ]] + grep -q "^1$" "${NAG_DIR}/snoozed" + ! grep -q "^work" "${NAG_DIR}/snoozed" } @test "snooze by tag requires -f" { @@ -80,16 +85,15 @@ load test_helper [[ "${_line}" == 1$'\t'* ]] } -@test "snooze by tag with duration sets expiry" { +@test "snooze by tag with duration sets expiry for matching IDs" { run_nag at "tomorrow 3pm" "work task" run_nag tag 1 work run_nag snooze work "tomorrow" [ "${status}" -eq 0 ] - [[ "${output}" =~ "Snoozed [work]" ]] + [[ "${output}" =~ "Snoozed [1]" ]] [[ "${output}" =~ "until" ]] - local _line - _line="$(cat "${NAG_DIR}/snoozed")" - [[ "${_line}" == work$'\t'* ]] + grep -q "^1"$'\t' "${NAG_DIR}/snoozed" + ! grep -q "^work" "${NAG_DIR}/snoozed" } @test "unsnooze by ID removes entry from snoozed file" { @@ -102,6 +106,7 @@ load test_helper } @test "unsnooze all removes snoozed file" { + run_nag at "tomorrow 3pm" "test alarm" run_nag snooze all run_nag unsnooze all [ "${status}" -eq 0 ] @@ -120,14 +125,14 @@ load test_helper [ "${status}" -eq 1 ] } -@test "unsnooze by tag removes tag entry" { +@test "unsnooze by tag removes snoozed entries for matching alarms" { run_nag at "tomorrow 3pm" "work task" run_nag tag 1 work run_nag snooze work run_nag unsnooze work [ "${status}" -eq 0 ] - [[ "${output}" =~ "Unsnoozed [work]" ]] - ! grep -q "^work" "${NAG_DIR}/snoozed" 2>/dev/null + [[ "${output}" =~ "Unsnoozed [1]" ]] + ! grep -q "^1" "${NAG_DIR}/snoozed" 2>/dev/null } @test "unsnooze by tag requires -f" { @@ -145,10 +150,40 @@ load test_helper [ "${status}" -eq 1 ] } -@test "unsnooze tag that is not snoozed fails" { +@test "unsnooze tag with no snoozed alarms fails" { run_nag at "tomorrow 3pm" "work task" run_nag tag 1 work run "${_NAG}" -f unsnooze work [ "${status}" -eq 1 ] [[ "${output}" =~ "not snoozed" ]] } + +@test "snooze replaces existing entry instead of duplicating" { + run_nag at "tomorrow 3pm" "take a break" + run_nag snooze 1 + run_nag snooze 1 + [ "${status}" -eq 0 ] + [ "$(grep -c "^1" "${NAG_DIR}/snoozed")" -eq 1 ] +} + +@test "snooze by tag does not duplicate already-snoozed IDs" { + run_nag at "tomorrow 3pm" "work task" + run_nag tag 1 work + run_nag snooze 1 + run_nag snooze work + [ "${status}" -eq 0 ] + [ "$(grep -c "^1" "${NAG_DIR}/snoozed")" -eq 1 ] +} + +@test "unsnooze by tag removes ID-snoozed alarms with that tag" { + run_nag at "tomorrow 3pm" "work task" + run_nag tag 1 work + run_nag snooze 1 + run_nag snooze work + run_nag unsnooze work + [ "${status}" -eq 0 ] + # Alarm should be fully unsnoozed, no entries left + if [ -f "${NAG_DIR}/snoozed" ]; then + [ ! -s "${NAG_DIR}/snoozed" ] || ! grep -q "^1" "${NAG_DIR}/snoozed" + fi +}