feat!: add builtin templates
WIP: document, check for breaking changes, decide on version, etc.
This commit is contained in:
@@ -12,4 +12,11 @@ Initial release.
|
||||
- Edit, list todo notes by any date format understood by `date(1)`
|
||||
- Write custom template programs to generate content for newly created notes
|
||||
|
||||
## v0.2.0
|
||||
|
||||
### Added
|
||||
|
||||
- Builtin templates for the markdown and xit formats (`.md` and `.xit`
|
||||
extensions); the templates carry over open tasks from previous agenda notes
|
||||
|
||||
[changelog.md]: https://changelog.md/
|
||||
|
||||
12
Makefile
12
Makefile
@@ -6,15 +6,19 @@ PREFIX := /usr/local
|
||||
.PHONY: install
|
||||
install: ./agenda.1.gz
|
||||
install -m 755 -D ./agenda $(PREFIX)/bin/agenda
|
||||
mkdir -p $(PREFIX)/share/agenda/templates
|
||||
install -m 755 ./templates/* $(PREFIX)/share/agenda/templates
|
||||
install -m 644 -D ./agenda.1.gz $(PREFIX)/share/man/man1/agenda.1.gz
|
||||
install -m 644 -D ./completion.bash $(PREFIX)/share/bash-completion/completions/agenda
|
||||
|
||||
.PHONY: uninstall
|
||||
uninstall:
|
||||
rm -f $(PREFIX)/bin/agenda
|
||||
rm -f $(PREFIX)/share/man/man1/agenda.1.gz
|
||||
rm -f $(PREFIX)/share/bash-completion/completions/agenda
|
||||
$(RM) $(PREFIX)/bin/agenda
|
||||
$(RM) $(PREFIX)/share/agenda/templates/*
|
||||
$(RM) $(PREFIX)/share/man/man1/agenda.1.gz
|
||||
$(RM) $(PREFIX)/share/bash-completion/completions/agenda
|
||||
rmdir $(PREFIX)/share/agenda/templates $(PREFIX)/share/agenda
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f ./agenda.1.gz
|
||||
$(RM) ./agenda.1.gz
|
||||
|
||||
93
agenda
93
agenda
@@ -17,6 +17,9 @@ AGENDA_EDITOR=${AGENDA_EDITOR:-${VISUAL:-${EDITOR:-vi}}}
|
||||
#
|
||||
AGENDA_DIR=${AGENDA_DIR:-}
|
||||
|
||||
# Location of additional files of an agenda installation.
|
||||
AGENDA_DATA_DIRS=${AGENDA_DATA_DIRS:-}
|
||||
|
||||
# The default `-e` option.
|
||||
AGENDA_EXTENSION=${AGENDA_EXTENSION:-"md"}
|
||||
|
||||
@@ -24,7 +27,10 @@ AGENDA_EXTENSION=${AGENDA_EXTENSION:-"md"}
|
||||
AGENDA_DEFAULT=${AGENDA_DEFAULT:-"agenda"}
|
||||
|
||||
# Command that provides the initial contents of new agenda files.
|
||||
AGENDA_TEMPLATE=${AGENDA_TEMPLATE:-"__agenda_default_template"}
|
||||
#
|
||||
# If this is null AGENDA_PATH is searched for an executable in the template
|
||||
# subdirectory with the same name as AGENDA_EXTENSION.
|
||||
AGENDA_TEMPLATE=${AGENDA_TEMPLATE:-}
|
||||
|
||||
__usage() {
|
||||
fold -s <<-USAGE
|
||||
@@ -56,7 +62,15 @@ __main() {
|
||||
"--version") __version && return ;;
|
||||
esac
|
||||
|
||||
if [ -z "$AGENDA_DATA_DIRS" ]; then
|
||||
# FIXME: As per spec only absolute paths should be accepted!
|
||||
AGENDA_DATA_DIRS="${XDG_CONFIG_HOME:-"$HOME/.config"}/agenda"
|
||||
AGENDA_DATA_DIRS="$AGENDA_DATA_DIRS:/usr/local/share/agenda"
|
||||
AGENDA_DATA_DIRS="$AGENDA_DATA_DIRS:/usr/share/agenda"
|
||||
fi
|
||||
|
||||
if [ -z "$AGENDA_DIR" ]; then
|
||||
# FIXME: As per spec only absolute paths should be accepted!
|
||||
AGENDA_DIR="${XDG_DATA_HOME:-"$HOME/.local/share"}/agenda"
|
||||
fi
|
||||
|
||||
@@ -291,6 +305,7 @@ __list() {
|
||||
fi
|
||||
done < <(
|
||||
# Sort the paths by basename.
|
||||
# TODO: Inline the prefix functions and explain this.
|
||||
find "$dir" -type f \
|
||||
| __agenda_sort_prefix \
|
||||
| sort \
|
||||
@@ -312,27 +327,41 @@ __agenda_create() {
|
||||
tmp_err=$(mktemp)
|
||||
|
||||
if [ "$backlog" = "no" ]; then
|
||||
# Capture exit code of the template command.
|
||||
local code
|
||||
set +e
|
||||
"$AGENDA_TEMPLATE" > "$tmp" 2> "$tmp_err"
|
||||
code=$?
|
||||
set -e
|
||||
|
||||
if [ $code -eq 127 ]; then
|
||||
echo "${0##*/}: cannot create '$file': template '$AGENDA_TEMPLATE' does not exist" >&2
|
||||
return 127
|
||||
# Search for a template program if the AGENDA_TEMPLATE variable is empty.
|
||||
if [ -z "$AGENDA_TEMPLATE" ]; then
|
||||
AGENDA_TEMPLATE=$(
|
||||
__agenda_get_template_path "$AGENDA_EXTENSION" ||
|
||||
__agenda_get_template_path "default" ||
|
||||
true
|
||||
)
|
||||
fi
|
||||
|
||||
if [ -s "$tmp_err" ]; then
|
||||
while read -r REPLY; do
|
||||
printf "agenda: $AGENDA_TEMPLATE: %s\n" "${REPLY#"${0}: line"*": "}" >&2
|
||||
done < "$tmp_err"
|
||||
fi
|
||||
# If a template program is set, attempt to use it to create the new agenda
|
||||
# note file.
|
||||
if [ -n "$AGENDA_TEMPLATE" ]; then
|
||||
local code
|
||||
set +e
|
||||
"$AGENDA_TEMPLATE" > "$tmp" 2> "$tmp_err"
|
||||
code=$?
|
||||
set -e
|
||||
|
||||
if [ $code -gt 0 ]; then
|
||||
echo "${0##*/}: cannot create '$file': template error" >&2
|
||||
return $code
|
||||
if [ $code -eq 127 ]; then
|
||||
echo "${0##*/}: cannot create '$file': template '$AGENDA_TEMPLATE' does not exist" >&2
|
||||
return 127
|
||||
fi
|
||||
|
||||
if [ -s "$tmp_err" ]; then
|
||||
while read -r REPLY; do
|
||||
printf "agenda: $AGENDA_TEMPLATE: %s\n" "${REPLY#"${0}: line"*": "}" >&2
|
||||
done < "$tmp_err"
|
||||
fi
|
||||
|
||||
if [ $code -gt 0 ]; then
|
||||
echo "${0##*/}: cannot create '$file': template error" >&2
|
||||
return $code
|
||||
fi
|
||||
else
|
||||
touch "$tmp"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -340,19 +369,6 @@ __agenda_create() {
|
||||
mv "$tmp" "$file"
|
||||
}
|
||||
|
||||
__agenda_default_template() {
|
||||
case $AGENDA_EXTENSION in
|
||||
"md")
|
||||
printf "# %s %s\n\n- [ ] " "$AGENDA_NAME" "$AGENDA_DATE"
|
||||
;;
|
||||
"txt"|"xit"|"")
|
||||
printf "%s %s\n[ ] " "$AGENDA_NAME" "$AGENDA_DATE"
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
__agenda_last() {
|
||||
local file=$1 name=$2
|
||||
|
||||
@@ -375,6 +391,19 @@ __agenda_last() {
|
||||
echo "${last:-}"
|
||||
}
|
||||
|
||||
__agenda_get_template_path() {
|
||||
local template=$1
|
||||
|
||||
local IFS=":" path
|
||||
for path in $AGENDA_DATA_DIRS; do
|
||||
if [ -x "$path/templates/$template" ]; then
|
||||
AGENDA_TEMPLATE="$path/templates/$template"
|
||||
return
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# https://stackoverflow.com/a/49035906
|
||||
__slugify() {
|
||||
echo "$1" \
|
||||
|
||||
54
agenda.1
54
agenda.1
@@ -1,4 +1,4 @@
|
||||
.TH AGENDA 1 "2024-06-07" "agenda 0.1.0"
|
||||
.TH AGENDA 1 "2024-06-07" "agenda 0.2.0"
|
||||
\#=============================================================================
|
||||
.SH NAME
|
||||
agenda \- Manage daily tasks and notes in any plain text format.
|
||||
@@ -107,7 +107,26 @@ The editor process has access to certain environment variables.
|
||||
\fBAGENDA_DIR\fP
|
||||
.RS 4
|
||||
The home of all agenda note files read and written by agenda. If this is empty
|
||||
`${XDG_DATA_HOME:-"$HOME/.local/share"}/agenda` will be used.
|
||||
\fBXDG_DATA_HOME\fP\fI/agenda\fP will be used.
|
||||
.PP
|
||||
.RE
|
||||
\#
|
||||
\fBAGENDA_DATA_DIRS\fP
|
||||
.RS 4
|
||||
Colon delimited Paths to additional files that come with an agenda installation
|
||||
and custom overrides. Currently only used for template programs (located in the
|
||||
\fItemplate/\fP sub-directory). If a file is searched in these paths the first file
|
||||
that is found is used.
|
||||
|
||||
.nr PI 2n
|
||||
If this variable is not set or empty it is set to include (in order)
|
||||
.IP \[bu]
|
||||
\fBXDG_CONFIG_HOME\fP\fI/agenda\fP
|
||||
.IP \[bu]
|
||||
\fI/usr/local/share/agenda\fP
|
||||
.IP \[bu]
|
||||
\fI/usr/share/agenda\fP
|
||||
|
||||
.PP
|
||||
.RE
|
||||
\#
|
||||
@@ -127,10 +146,14 @@ The default agenda name if the \fI-t\fP option is omitted. This falls back to
|
||||
\#
|
||||
\fBAGENDA_TEMPLATE\fP
|
||||
.RS 4
|
||||
If this is not empty the value is interpreted as a command and executed when
|
||||
creating a new agenda note with the \fI-c\fP flag. Any std output of the
|
||||
command is written to the new file. The template process has access to certain
|
||||
environment variables.
|
||||
If this variable is not empty the value is interpreted as a command and
|
||||
executed when creating a new agenda note with the \fI-c\fP flag. Any std output
|
||||
of the command is written to the new file. The template process has access to
|
||||
certain environment variables.
|
||||
.PP
|
||||
If this variable is unset or empty \fBAGENDA_DATA_DIRS\fP is searched for an
|
||||
executable in the \fItemplates\fP sub-directory that is named either like the
|
||||
corresponding extension or "default".
|
||||
.sp 1
|
||||
The following example will copy the last agenda note to
|
||||
the new agenda note file.
|
||||
@@ -153,6 +176,10 @@ Note however that this example is specific to \fBbash(1)\fP, since only bash
|
||||
can export functions this way. In general you should write your template as a
|
||||
script, save it, and set it as executable.
|
||||
.PP
|
||||
To set/override a template for a certain extension (e.g. markdown), save the
|
||||
program to one of the \fBAGENDA_DATA_DIRS\fP, e.g.
|
||||
\fI\fBXDG_CONFIG_HOME\fP/agenda/templates/md\fP.
|
||||
.PP
|
||||
\#-----------------------------------------------------------------------------
|
||||
.SS "Templates and editor"
|
||||
The template command and editor processes have access to certain environment
|
||||
@@ -188,6 +215,21 @@ The file path of the agenda note.
|
||||
The last agenda file. Can be empty if no previous agenda note exists.
|
||||
.PP
|
||||
.RE
|
||||
.SS "Other"
|
||||
\#
|
||||
\fBXDG_CONFIG_HOME\fP
|
||||
.RS 4
|
||||
The default user configuration path. If this is not set the default of
|
||||
\fBHOME\fP\fI/.config\fP is assumed.
|
||||
.PP
|
||||
.RE
|
||||
\#
|
||||
\fBXDG_DATA_HOME\fP
|
||||
.RS 4
|
||||
The default user data path. If this is not set the default of
|
||||
\fBHOME\fP\fI/.local/share\fP is assumed.
|
||||
.PP
|
||||
.RE
|
||||
\#=============================================================================
|
||||
.SH "SEE ALSO"
|
||||
\fBdate(1)\fP
|
||||
|
||||
6
templates/default
Executable file
6
templates/default
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
type agenda >/dev/null
|
||||
|
||||
# Capitalize the title.
|
||||
echo "$AGENDA_NAME $AGENDA_DATE" | awk '{print toupper(substr($0,0,1))tolower(substr($0,2))}'
|
||||
42
templates/md
Executable file
42
templates/md
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
type agenda >/dev/null
|
||||
|
||||
if [ "${AGENDA_EXTENSION:-}" != "md" ]; then
|
||||
exit
|
||||
fi
|
||||
|
||||
# Capitalize the title.
|
||||
echo "$AGENDA_NAME $AGENDA_DATE" | awk '{print "# "toupper(substr($0,0,1))tolower(substr($0,2))}'
|
||||
echo
|
||||
|
||||
if [ -n "${AGENDA_IS_BACKLOG:-}" ]; then
|
||||
exit
|
||||
fi
|
||||
|
||||
# Carry over open tasks from the last agenda markdown file.
|
||||
if [ -e "$AGENDA_LAST" ] && [ "${AGENDA_LAST#*.}" = "md" ]; then
|
||||
while IFS= read -r REPLY; do
|
||||
case ${state:-} in
|
||||
"open_task")
|
||||
case $REPLY in
|
||||
"- [ ] "*|" "*)
|
||||
# Append indented lines to open tasks.
|
||||
echo "$REPLY"
|
||||
;;
|
||||
*)
|
||||
unset state
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
case $REPLY in
|
||||
"[ ] "*|"[@] "*|"[?] "*)
|
||||
state="open_task"
|
||||
echo "$REPLY"
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
done < "$AGENDA_LAST"
|
||||
fi
|
||||
56
templates/xit
Executable file
56
templates/xit
Executable file
@@ -0,0 +1,56 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
type agenda >/dev/null
|
||||
|
||||
if [ "${AGENDA_EXTENSION:-}" != "xit" ]; then
|
||||
exit
|
||||
fi
|
||||
|
||||
# Capitalize the title.
|
||||
echo "$AGENDA_NAME $AGENDA_DATE" | awk '{print toupper(substr($0,0,1))tolower(substr($0,2))}'
|
||||
|
||||
if [ -n "${AGENDA_IS_BACKLOG:-}" ]; then
|
||||
exit
|
||||
fi
|
||||
|
||||
# Carry over open tasks from the last agenda xit file.
|
||||
if [ -e "$AGENDA_LAST" ] && [ "${AGENDA_LAST#*.}" = "xit" ]; then
|
||||
line=0
|
||||
while IFS= read -r REPLY; do
|
||||
line=$(( line + 1 ))
|
||||
case ${state:-} in
|
||||
"open_task")
|
||||
case $REPLY in
|
||||
"[ ] "*|"[@] "*|"[?] "*|" "*)
|
||||
# Append indented lines to open tasks.
|
||||
echo "$REPLY"
|
||||
;;
|
||||
*)
|
||||
unset state
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
case $REPLY in
|
||||
"[ ] "*|"[@] "*|"[?] "*)
|
||||
# Carry over group heading with open tasks.
|
||||
if [ -n "${header:-}" ]; then
|
||||
echo
|
||||
echo "$header"
|
||||
unset header
|
||||
fi
|
||||
state="open_task"
|
||||
echo "$REPLY"
|
||||
;;
|
||||
[a-z]*|[A-Z]*)
|
||||
# Skip the first heading (assumed to be the generated
|
||||
# title of the agenda note).
|
||||
if [ $line -gt 1 ]; then
|
||||
header=$REPLY
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
done < "$AGENDA_LAST"
|
||||
fi
|
||||
Reference in New Issue
Block a user