diff --git a/nag b/nag index f144b18..c4c5755 100755 --- a/nag +++ b/nag @@ -502,6 +502,55 @@ _get_alarm_field() { fi } +# Usage: +# _format_time +# +# Description: +# Format a unix timestamp for display. +# Time: dots for minutes (3.30pm), omit .00 (3pm). +# Date: Today / Tomorrow / day-of-week within 6 days / +# Next within 13 days / Mon Dec 25 beyond that. +_format_time() { + local _timestamp="${1}" + + # Format time: drop .00, use dots for minutes. + local _hour _minute _ampm _time_fmt + _hour="$(date -d "@${_timestamp}" "+%-I")" + _minute="$(date -d "@${_timestamp}" "+%M")" + _ampm="$(date -d "@${_timestamp}" "+%p" | tr '[:upper:]' '[:lower:]')" + if [[ "${_minute}" == "00" ]] + then + _time_fmt="${_hour}${_ampm}" + else + _time_fmt="${_hour}.${_minute}${_ampm}" + fi + + # Format date prefix. + local _alarm_date _today _days_away + _alarm_date="$(date -d "@${_timestamp}" +%Y-%m-%d)" + _today="$(date +%Y-%m-%d)" + _days_away=$(( ( $(date -d "${_alarm_date}" +%s) - $(date -d "${_today}" +%s) ) / 86400 )) + + local _date_prefix + if (( _days_away == 0 )) + then + _date_prefix="Today" + elif (( _days_away == 1 )) + then + _date_prefix="Tomorrow" + elif (( _days_away <= 6 )) + then + _date_prefix="$(date -d "@${_timestamp}" "+%A")" + elif (( _days_away <= 13 )) + then + _date_prefix="Next $(date -d "@${_timestamp}" "+%A")" + else + _date_prefix="$(date -d "@${_timestamp}" "+%a %b %-d")" + fi + + printf "%s, %s" "${_date_prefix}" "${_time_fmt}" +} + # Usage: # _parse_time # @@ -652,6 +701,36 @@ list() { printf "Nothing to nag about.\\n" return 0 fi + + _read_alarms + + # Sort alarms by timestamp (field 2). + local -a _sorted + IFS=$'\n' _sorted=($(printf "%s\n" "${_ALARMS[@]}" | sort -t$'\t' -k2 -n)) + IFS=$'\n\t' + + local _line + for _line in "${_sorted[@]}" + do + [[ -n "${_line}" ]] || continue + local _id _timestamp _rule _message _human_time _rule_display + + _id="$(_get_alarm_field "${_line}" 1)" + _timestamp="$(_get_alarm_field "${_line}" 2)" + _rule="$(_get_alarm_field "${_line}" 3)" + _message="$(_get_alarm_field "${_line}" 4)" + + _human_time="$(_format_time "${_timestamp}")" + + if [[ -n "${_rule}" ]] + then + _rule_display="every ${_rule//,/, }, " + else + _rule_display="" + fi + + printf "[%s] %s%s — %s\\n" "${_id}" "${_rule_display}" "${_human_time}" "${_message}" + done } # at ########################################################################## @@ -693,7 +772,7 @@ at() { _prompt_cron local _human_time - _human_time="$(date -d "@${_timestamp}" "+%a %b %-d %-I:%M%p" | sed 's/AM/am/;s/PM/pm/')" + _human_time="$(_format_time "${_timestamp}")" printf "[%s] %s — %s\\n" "${_id}" "${_human_time}" "${_message}" } diff --git a/test/nag.bats b/test/nag.bats index 10ec106..45ee66c 100644 --- a/test/nag.bats +++ b/test/nag.bats @@ -50,8 +50,7 @@ load test_helper @test "at creates a one-shot alarm" { run_nag at "tomorrow 3pm" "take a break" [ "${status}" -eq 0 ] - [[ "${output}" =~ "[1]" ]] - [[ "${output}" =~ "take a break" ]] + [[ "${output}" =~ "[1] Tomorrow, 3pm — take a break" ]] [ -f "${NAG_PATH}" ] [ "$(wc -l < "${NAG_PATH}")" -eq 1 ] # Verify TSV structure: idtimestampmessage @@ -88,3 +87,23 @@ load test_helper [ "${status}" -eq 0 ] [[ "${output}" =~ ^[0-9]+\.[0-9]+(_[a-zA-Z0-9]+)*$ ]] } + +@test "list shows formatted alarms with smart dates" { + run_nag at "tomorrow 3pm" "take a break" + run_nag + [ "${status}" -eq 0 ] + [[ "${output}" =~ "[1] Tomorrow, 3pm — take a break" ]] +} + +@test "list sorts alarms by time" { + run_nag at "tomorrow 3pm" "take a break" + run_nag at "tomorrow 9am" "standup" + run_nag + [ "${status}" -eq 0 ] + # 9am should come before 3pm in output. + local _first_line _second_line + _first_line="$(printf "%s\n" "${output}" | head -1)" + _second_line="$(printf "%s\n" "${output}" | sed -n '2p')" + [[ "${_first_line}" =~ "standup" ]] + [[ "${_second_line}" =~ "take a break" ]] +}