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
|
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:
|
# Usage:
|
||||||
# _format_time <timestamp> [today_epoch]
|
# _format_time <timestamp> [today_epoch]
|
||||||
#
|
#
|
||||||
|
|
@ -1504,5 +1587,96 @@ edit() {
|
||||||
"${EDITOR:-${VISUAL:-vi}}" "${_ALARMS_FILE}"
|
"${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 must be called after everything has been defined.
|
||||||
_main
|
_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