From d4319e1ef65bd7228485b688c031341e5f96f361 Mon Sep 17 00:00:00 2001 From: lew Date: Wed, 1 Apr 2026 19:45:20 +0100 Subject: [PATCH] chore: initial scaffolding of nag based on bash-boilerplate --- LICENSE | 21 ++ nag | 440 ++++++++++++++++++++++++++++++++++++++++++ test/nag.bats | 27 +++ test/test_helper.bash | 19 ++ 4 files changed, 507 insertions(+) create mode 100644 LICENSE create mode 100755 nag create mode 100644 test/nag.bats create mode 100644 test/test_helper.bash diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e9c6128 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Lewis Wynne + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/nag b/nag new file mode 100755 index 0000000..a287f97 --- /dev/null +++ b/nag @@ -0,0 +1,440 @@ +#!/usr/bin/env bash + +# nag is a bash script for setting one-off or repeating alarms. + +# MIT License +# +# Copyright (c) 2026 Lewis Wynne +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# Enforce Bash "strict mode". +set -o nounset +set -o errexit +set -o errtrace +set -o pipefail +trap 'echo "Aborting due to errexit on line $LINENO. Exit code: $?" >&2' ERR +IFS=$'\n\t' + +_ME="$(basename "${0}")" +_VERSION="2026.14" + +NAG_PATH="${NAG_PATH:-${HOME}/.local/share/nag}" +NAG_CMD="${NAG_CMD:-notify-send}" +_LOCKFILE="${NAG_PATH}.lock" + +DEFAULT_SUBCOMMAND="${DEFAULT_SUBCOMMAND:-list}" + +# Usage: +# _debug ... +# +# Description: +# Execute a command and print to standard error. The command is expected to +# print a message and should typically be either `echo`, `printf`, or `cat`. +# +# Example: +# _debug printf "Debug info. Variable: %s\\n" "$0" +__DEBUG_COUNTER=0 +_debug() { + if ((${_USE_DEBUG:-0})) + then + __DEBUG_COUNTER=$((__DEBUG_COUNTER+1)) + { + # Prefix debug message with "bug (U+1F41B)" + printf "🐛 %s " "${__DEBUG_COUNTER}" + "${@}" + printf "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\\n" + } 1>&2 + fi +} + +# Usage: +# _exit_1 +# +# Description: +# Exit with status 1 after executing the specified command with output +# redirected to standard error. The command is expected to print a message +# and should typically be either `echo`, `printf`, or `cat`. +_exit_1() { + { + printf "%s " "$(tput setaf 1)!$(tput sgr0)" + "${@}" + } 1>&2 + exit 1 +} + +# Usage: +# _warn +# +# Description: +# Print the specified command with output redirected to standard error. +# The command is expected to print a message and should typically be either +# `echo`, `printf`, or `cat`. +_warn() { + { + printf "%s " "$(tput setaf 1)!$(tput sgr0)" + "${@}" + } 1>&2 +} + +# Usage: +# _function_exists +# +# Exit / Error Status: +# 0 (success, true) If function with is defined in the current +# environment. +# 1 (error, false) If not. +_function_exists() { + [ "$(type -t "${1}")" == 'function' ] +} + +# Usage: +# _command_exists +# +# Exit / Error Status: +# 0 (success, true) If a command with is defined in the current +# environment. +# 1 (error, false) If not. +_command_exists() { + hash "${1}" 2>/dev/null +} + +# Usage: +# _contains ... +# +# Exit / Error Status: +# 0 (success, true) If the item is included in the list. +# 1 (error, false) If not. +# +# Examples: +# _contains "${_query}" "${_list[@]}" +_contains() { + local _query="${1:-}" + shift + + if [[ -z "${_query}" ]] || + [[ -z "${*:-}" ]] + then + return 1 + fi + + for __element in "${@}" + do + [[ "${__element}" == "${_query}" ]] && return 0 + done + + return 1 +} + +# Usage: +# _join ... +# +# Description: +# Print a string containing all arguments separated by +# . +# +# Example: +# _join "${_delimeter}" "${_list[@]}" +_join() { + local _delimiter="${1}" + shift + printf "%s" "${1}" + shift + printf "%s" "${@/#/${_delimiter}}" | tr -d '[:space:]' +} + +# Usage: +# _blank +# +# Exit / Error Status: +# 0 (success, true) If is not present or null. +# 1 (error, false) If is present and not null. +_blank() { + [[ -z "${1:-}" ]] +} + +# Usage: +# _present +# +# Exit / Error Status: +# 0 (success, true) If is present and not null. +# 1 (error, false) If is not present or null. +_present() { + [[ -n "${1:-}" ]] +} + +# Usage: +# _interactive_input +# +# Exit / Error Status: +# 0 (success, true) If the current input is interactive (eg, a shell). +# 1 (error, false) If the current input is stdin / piped input. +_interactive_input() { + [[ -t 0 ]] +} + +# Usage: +# _piped_input +# +# Exit / Error Status: +# 0 (success, true) If the current input is stdin / piped input. +# 1 (error, false) If the current input is interactive (eg, a shell). +_piped_input() { + ! _interactive_input +} + +# _create_alarm() +# +# Usage: +# _create_alarm ... +# +# Description: +# Create a new alarm. Stub for future implementation. +_create_alarm() { + _exit_1 printf "Not yet implemented.\\n" +} + +# Usage: +# describe +# describe --get +# +# Options: +# --get Print the description for if one has been set. +describe() { + _debug printf "describe() \${*}: %s\\n" "$@" + [[ -z "${1:-}" ]] && _exit_1 printf "describe(): required.\\n" + + if [[ "${1}" == "--get" ]] + then + [[ -z "${2:-}" ]] && + _exit_1 printf "describe(): required.\\n" + + local _name="${2:-}" + local _describe_var="___describe_${_name}" + + if [[ -n "${!_describe_var:-}" ]] + then + printf "%s\\n" "${!_describe_var}" + else + printf "No additional information for \`%s\`\\n" "${_name}" + fi + else + if [[ -n "${2:-}" ]] + then + read -r -d '' "___describe_${1}" <] + +Description: + Display help information for ${_ME} or a specified subcommand. +HEREDOC +help() { + if [[ -n "${1:-}" ]] + then + describe --get "${1}" + else + cat < one-shot alarm + ${_ME} every