#!/bin/sh # # anniv - generate anniversary reminders for agenda.txt # # SPDX-FileCopyrightText: 2023 Daniel Kalak # SPDX-License-Identifier: GPL-3.0-or-later # # This program reads lines from stdin and prints lines to stdout, # sorted. It expects the input lines to be of a format like this: # # John Doe (my best friend) (* 1983-06-21) # # These could be the first lines of a group of text files (e.g. with # contact information) that you could assemble with "head -qn1 *.txt". # Then, if the current year is 2023, the output would look like this: # # 2023-06-21 * John Doe (my best friend) (1983, 40) # # If the year specified in the input is 0000 (i.e. if you had # "0000-06-21" in the example), the last parenthesis in the output (i.e. # " (1983, 40)" in the example) is omitted. (This is for cases where the # year is unknown.) # # You can specify an arbitrary "current year" and an arbitrary symbol on # the command line. The symbol needs to be a Basic Regular Expression as # used by GNU sed. (For example, you could use "+" for deaths or "oo" # for marriages, etc.) By default, the current year at invocation and # the symbol "\*" (to symbolize a birthday) are used. # # The program only checks if there is a space followed by an opening # parenthesis (i.e. " (") at some point in an input line, and if that is # the case, it checks if there is the symbol followed by a space # followed by an ISO date (e.g. "* 1983-06-21") at some point after that # parenthesis (but before it is closed, if it ever is). Everything # before the " (" is treated as the "name", and everything else (other # than the date) is discarded. This lets you put arbitrary stuff into # the parenthesis (e.g. multiple dates with different symbols) or after # it. # # This program depends on GNU awk, the GNU coreutils, and GNU sed. It # exits 0 on success and 1 on bad usage. If it encounters an input line # that is not in the format specified above, it redirects it to stderr, # along with a warning; this does not affect the other output lines (on # stdout) and the program still exits 0. usage_quit() { cat <<- EOF >&2 Usage: $0 [YEAR [SYMBOL]] EOF exit 1 } year="${1:-$(date +%Y)}" symbol="${2:-\*}" isodate='[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]' case "$year" in [0-9][0-9][0-9][0-9]) ;; *) usage_quit ;; esac sed ' s/\(.*\) ([^)]*\('"$symbol"'\) \('"$isodate"'\).*/\3 \2 \1/ t s/^/ERROR/' | # At this stage in the pipeline, the example input line given above # would look like "1983-06-21 * John Doe (my best friend)". Faulty input # lines have a prepended "ERROR" tag (without separators) and are left # unchanged otherwise. while read -r line do case "$line" in ERROR*) printf 'Wrong input format: %s\n' "${line#ERROR}" >&2 ;; *) printf '%s\n' "$line" esac done | # At this stage in the pipeline, the faulty lines prepended with "ERROR" # have been redirected to stderr. awk -v "year=$year" ' { birthyear = substr($0, 1, 4) sub(birthyear, year) if (birthyear != "0000") $0 = $0 " (" birthyear ", " year-birthyear ")" print }' | # At this stage in the pipeline, the "birth" year at the beginning of a # line has been replaced with the current year, and the " (1983, 40)" # parenthesis in the example has been appended for all years that are # not "0000". All that is left to do is to sort. LC_ALL=C sort