feat: tag subcommand for adding tags and listing by tag
This commit is contained in:
parent
2b36640567
commit
d198b5c2a4
2 changed files with 238 additions and 0 deletions
174
nag
174
nag
|
|
@ -530,6 +530,89 @@ _get_alarm_field() {
|
|||
fi
|
||||
}
|
||||
|
||||
# Usage:
|
||||
# _alarm_has_tag <tags_field> <tag>
|
||||
#
|
||||
# Description:
|
||||
# Check whether a comma-separated tags field contains a specific tag.
|
||||
#
|
||||
# Exit / Error Status:
|
||||
# 0 (success, true) If the tag is present.
|
||||
# 1 (error, false) If the tag is absent or the field is empty.
|
||||
_alarm_has_tag() {
|
||||
local _tags_field="${1}" _tag="${2}"
|
||||
[[ -n "${_tags_field}" ]] || return 1
|
||||
local IFS=","
|
||||
local _t
|
||||
for _t in ${_tags_field}
|
||||
do
|
||||
[[ "${_t}" == "${_tag}" ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Usage:
|
||||
# _tag_list <tag>
|
||||
#
|
||||
# Description:
|
||||
# List all alarms that carry the given tag, sorted by timestamp.
|
||||
_tag_list() {
|
||||
local _filter_tag="${1}"
|
||||
|
||||
if [[ ! -f "${_ALARMS_FILE}" ]] || [[ ! -s "${_ALARMS_FILE}" ]]
|
||||
then
|
||||
printf "No alarms tagged [%s].\\n" "${_filter_tag}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
_read_alarms
|
||||
|
||||
local -a _sorted
|
||||
IFS=$'\n' _sorted=($(printf "%s\n" "${_ALARMS[@]}" | sort -t$'\t' -k3 -n))
|
||||
IFS=$'\n\t'
|
||||
|
||||
local _today _today_epoch
|
||||
_today="$(date +%Y-%m-%d)"
|
||||
_today_epoch="$(date -d "${_today}" +%s)"
|
||||
|
||||
local _matched=0
|
||||
local _line
|
||||
for _line in "${_sorted[@]}"
|
||||
do
|
||||
[[ -n "${_line}" ]] || continue
|
||||
local _id _tags _timestamp _rule _message
|
||||
|
||||
_id="${_line%%$'\t'*}"; local _rest="${_line#*$'\t'}"
|
||||
_tags="${_rest%%$'\t'*}"; _rest="${_rest#*$'\t'}"
|
||||
_timestamp="${_rest%%$'\t'*}"; _rest="${_rest#*$'\t'}"
|
||||
_rule="${_rest%%$'\t'*}"
|
||||
_message="${_rest#*$'\t'}"
|
||||
|
||||
_alarm_has_tag "${_tags}" "${_filter_tag}" || continue
|
||||
_matched=1
|
||||
|
||||
local _human_time _rule_display _tag_display
|
||||
_format_time "${_timestamp}" "${_today_epoch}"
|
||||
_human_time="${REPLY}"
|
||||
|
||||
if [[ -n "${_rule}" ]]
|
||||
then
|
||||
_rule_display=" (${_rule//,/, })"
|
||||
else
|
||||
_rule_display=""
|
||||
fi
|
||||
|
||||
_tag_display=" [${_tags//,/, }]"
|
||||
|
||||
printf "[%s]%s %s%s — %s\\n" "${_id}" "${_tag_display}" "${_human_time}" "${_rule_display}" "${_message}"
|
||||
done
|
||||
|
||||
if (( ! _matched ))
|
||||
then
|
||||
printf "No alarms tagged [%s].\\n" "${_filter_tag}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Usage:
|
||||
# _format_time <timestamp> [today_epoch]
|
||||
#
|
||||
|
|
@ -1504,5 +1587,96 @@ edit() {
|
|||
"${EDITOR:-${VISUAL:-vi}}" "${_ALARMS_FILE}"
|
||||
}
|
||||
|
||||
# tag #########################################################################
|
||||
|
||||
describe "tag" <<HEREDOC
|
||||
Usage:
|
||||
${_ME} tag <id> <tags...> add tags to an alarm
|
||||
${_ME} tag <tag> list alarms with a tag
|
||||
|
||||
Description:
|
||||
Add tags to an alarm by ID, or list all alarms matching a tag.
|
||||
Tags must not be pure integers.
|
||||
HEREDOC
|
||||
tag() {
|
||||
local _first="${1:-}"
|
||||
[[ -n "${_first}" ]] || _exit_1 printf "Usage: %s tag <id> <tags...> or %s tag <tag>\\n" "${_ME}" "${_ME}"
|
||||
|
||||
if [[ ! "${_first}" =~ ^[0-9]+$ ]]
|
||||
then
|
||||
_tag_list "${_first}"
|
||||
return
|
||||
fi
|
||||
|
||||
local _target_id="${_first}"
|
||||
shift
|
||||
[[ -n "${1:-}" ]] || _exit_1 printf "Usage: %s tag <id> <tags...>\\n" "${_ME}"
|
||||
|
||||
local _tag
|
||||
for _tag in "$@"
|
||||
do
|
||||
if [[ "${_tag}" =~ ^[0-9]+$ ]]
|
||||
then
|
||||
_exit_1 printf "Tag cannot be a number: %s\\n" "${_tag}"
|
||||
fi
|
||||
done
|
||||
|
||||
_acquire_lock
|
||||
_read_alarms
|
||||
|
||||
local -a _new_alarms=()
|
||||
local _found=0
|
||||
local _result_tags=""
|
||||
local _line
|
||||
|
||||
for _line in "${_ALARMS[@]:-}"
|
||||
do
|
||||
[[ -n "${_line}" ]] || continue
|
||||
local _id _existing_tags _timestamp _rule _message
|
||||
_id="${_line%%$'\t'*}"; local _rest="${_line#*$'\t'}"
|
||||
if [[ "${_id}" == "${_target_id}" ]]
|
||||
then
|
||||
_found=1
|
||||
_existing_tags="${_rest%%$'\t'*}"; _rest="${_rest#*$'\t'}"
|
||||
_timestamp="${_rest%%$'\t'*}"; _rest="${_rest#*$'\t'}"
|
||||
_rule="${_rest%%$'\t'*}"
|
||||
_message="${_rest#*$'\t'}"
|
||||
|
||||
local -a _tag_arr=()
|
||||
if [[ -n "${_existing_tags}" ]]
|
||||
then
|
||||
IFS=',' read -r -a _tag_arr <<< "${_existing_tags}"
|
||||
fi
|
||||
|
||||
for _tag in "$@"
|
||||
do
|
||||
local _already=0 _t
|
||||
for _t in "${_tag_arr[@]:-}"
|
||||
do
|
||||
[[ "${_t}" == "${_tag}" ]] && _already=1 && break
|
||||
done
|
||||
(( _already )) || _tag_arr+=("${_tag}")
|
||||
done
|
||||
|
||||
_result_tags="$(_join "," "${_tag_arr[@]}")"
|
||||
_new_alarms+=("$(printf "%s\t%s\t%s\t%s\t%s" "${_id}" "${_result_tags}" "${_timestamp}" "${_rule}" "${_message}")")
|
||||
else
|
||||
_new_alarms+=("${_line}")
|
||||
fi
|
||||
done
|
||||
|
||||
if (( ! _found ))
|
||||
then
|
||||
_release_lock
|
||||
_exit_1 printf "No alarm with ID %s.\\n" "${_target_id}"
|
||||
fi
|
||||
|
||||
_ALARMS=("${_new_alarms[@]}")
|
||||
_write_alarms
|
||||
_release_lock
|
||||
|
||||
printf "Tagged [%s] %s.\\n" "${_target_id}" "${_result_tags//,/, }"
|
||||
}
|
||||
|
||||
# _main must be called after everything has been defined.
|
||||
_main
|
||||
|
|
|
|||
64
test/tag.bats
Normal file
64
test/tag.bats
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
@test "tag adds a single tag to an alarm" {
|
||||
run_nag at "tomorrow 3pm" "test alarm"
|
||||
run_nag tag 1 work
|
||||
[ "${status}" -eq 0 ]
|
||||
local _tags
|
||||
_tags="$(cut -f2 "${NAG_DIR}/alarms")"
|
||||
[ "${_tags}" = "work" ]
|
||||
}
|
||||
|
||||
@test "tag adds multiple tags to an alarm" {
|
||||
run_nag at "tomorrow 3pm" "test alarm"
|
||||
run_nag tag 1 work meetings
|
||||
[ "${status}" -eq 0 ]
|
||||
local _tags
|
||||
_tags="$(cut -f2 "${NAG_DIR}/alarms")"
|
||||
[ "${_tags}" = "work,meetings" ]
|
||||
}
|
||||
|
||||
@test "tag merges with existing tags without duplicates" {
|
||||
run_nag at "tomorrow 3pm" "test alarm"
|
||||
run_nag tag 1 work
|
||||
run_nag tag 1 meetings work
|
||||
[ "${status}" -eq 0 ]
|
||||
local _tags
|
||||
_tags="$(cut -f2 "${NAG_DIR}/alarms")"
|
||||
[ "${_tags}" = "work,meetings" ]
|
||||
}
|
||||
|
||||
@test "tag rejects pure integer tag names" {
|
||||
run_nag at "tomorrow 3pm" "test alarm"
|
||||
run_nag tag 1 123
|
||||
[ "${status}" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "tag with nonexistent ID fails" {
|
||||
run_nag tag 99 work
|
||||
[ "${status}" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "tag without arguments fails" {
|
||||
run_nag tag
|
||||
[ "${status}" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "tag with non-numeric arg lists matching alarms" {
|
||||
run_nag at "tomorrow 3pm" "work task"
|
||||
run_nag tag 1 work
|
||||
run_nag at "tomorrow 4pm" "personal task"
|
||||
run_nag tag work
|
||||
[ "${status}" -eq 0 ]
|
||||
[[ "${output}" =~ "work task" ]]
|
||||
[[ ! "${output}" =~ "personal task" ]]
|
||||
}
|
||||
|
||||
@test "tag list shows no matches message" {
|
||||
run_nag at "tomorrow 3pm" "test alarm"
|
||||
run_nag tag nonexistent
|
||||
[ "${status}" -eq 0 ]
|
||||
[[ "${output}" =~ "No alarms tagged" ]]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue