script passed through shellcheck linter
[bash-notes.git] / notes.sh
CommitLineData
a4aaf855 1#! /bin/bash
2
6c152f7e 3# bash-notes © 2023 by danix is licensed under CC BY-NC 4.0.
4# To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/4.0/
5
cb8fcb2f 6# to debug the script run it like:
7# DEBUG=true notes.sh ...
8# and check /tmp/debug_bash-notes.log
9if [[ $DEBUG == true ]]; then
10 exec 5> /tmp/debug_bash-notes.log
11 BASH_XTRACEFD="5"
12 PS4='$LINENO: '
13 set -x
14fi
a4aaf855 15
16PID=$$
cb8fcb2f 17VERSION="0.2"
53f2ed57 18
d80ac20a 19set_defaults() {
20# Binaries to use
cb8fcb2f 21JQ=${JQ:-/usr/bin/jq}
a4aaf855 22EDITOR=${EDITOR:-/usr/bin/vim}
53f2ed57 23TERMINAL=${TERMINAL:-/usr/bin/alacritty}
b648c006 24# add options for your terminal. Remember to add the last option to execute
25# your editor program, otherwise the script will fail.
26# see example in the addnote function
e3670e83 27TERM_OPTS="--class notes --title notes -e "
53f2ed57 28
cb8fcb2f 29# set this to true to have output in plain text
30# or use the -p option on the command line before every other option
31PLAIN=false
d80ac20a 32# base directory for program files
53f2ed57 33BASEDIR=${BASEDIR:-~/.local/share/bash-notes}
d80ac20a 34# notes database in json format
a4aaf855 35DB=${BASEDIR}/db.json
d80ac20a 36# directory containing the actual notes
a4aaf855 37NOTESDIR=${BASEDIR}/notes
53f2ed57 38
d80ac20a 39} # end set_defaults, do not change this line.
40
41set_defaults
42
43# Do not edit below this point
44RCFILE=${RCFILE:-~/.config/bash-notes.rc}
53f2ed57 45TMPDB=/tmp/db.json
b9f21021 46BASENAME=$( basename "$0" )
d80ac20a 47NOW=$(date +%s)
a4aaf855 48
b9f21021 49if [ ! -x "$JQ" ]; then
a4aaf855 50 echo "jq not found in your PATH"
51 echo "install jq to continue"
52 exit 1
53fi
54
53f2ed57 55# IMPORT USER DEFINED OPTIONS IF ANY
56if [[ -f $RCFILE ]]; then
b9f21021 57 # shellcheck disable=SC1090
58 source "$RCFILE"
53f2ed57 59fi
60
a4aaf855 61# We prevent the program from running more than one instance:
b9f21021 62PIDFILE=/var/tmp/$(basename "$0" .sh).pid
a4aaf855 63
64# Make sure the PID file is removed when we kill the process
65trap 'rm -f $PIDFILE; exit 1' TERM INT
66
67if [[ -r $PIDFILE ]]; then
68 # PIDFILE exists, so I guess there's already an instance running
69 # let's kill it and run again
b9f21021 70 # shellcheck disable=SC2046,SC2086
a4aaf855 71 kill -s 15 $(cat $PIDFILE) > /dev/null 2>&1
72 # should already be deleted by trap, but just to be sure
b9f21021 73 rm "$PIDFILE"
a4aaf855 74fi
75
76# create PIDFILE
b9f21021 77echo $PID > "$PIDFILE"
a4aaf855 78
61c91990 79# check if input is a number, returns false or the number itself
80function check_noteID() {
81 IN=$1
82 case $IN in
83 ''|*[!0-9]*)
84 return 1
85 ;;
86 *)
b9f21021 87 echo "$IN"
61c91990 88 ;;
89 esac
90}
91
a4aaf855 92function helptext() {
d80ac20a 93 echo "Usage:"
c018122c 94 echo " $0 [PARAMS] ..."
d80ac20a 95 echo ""
96 cat << __NOWCONF__
97${BASENAME} configuration is:
98
99base directory: ${BASEDIR}/
100notes archive: ${NOTESDIR}/
101notes database: ${DB}
102rc file: $RCFILE
cb8fcb2f 103debug file: /tmp/debug_bash-note.log
c018122c 104
d80ac20a 105text editor: ${EDITOR}
106terminal: ${TERMINAL}
107jq executable: ${JQ}
108__NOWCONF__
109
110 echo ""
e3670e83 111 echo "${BASENAME} parameters are:"
d80ac20a 112 echo " -h | --help : This help text"
c018122c 113 echo " -p | --plain : Output is in plain text"
b648c006 114 echo " (without this option the output is formatted)"
115 echo " (this option must precede all others)"
d80ac20a 116 echo " -l | --list : List existing notes"
026502da 117 echo " -a | --add [\"<title>\"] : Add new note"
6c152f7e 118 echo " -e | --edit [<note>] : Edit note"
026502da 119 echo " -d | --delete [<note> | all] : Delete single note or all notes at once"
d80ac20a 120 echo " -v | --version : Print version"
121 echo " --userconf : Export User config file"
e3670e83 122 echo ""
a4aaf855 123}
124
125function addnote() {
026502da 126 # remove eventually existing temp DB file
127 if [[ -f $TMPDB ]]; then
128 rm $TMPDB
129 fi
130
53f2ed57 131 NOTETITLE="$1"
132 echo "adding new note - \"$NOTETITLE\""
b9f21021 133 # shellcheck disable=SC2086
e3670e83 134 LASTID=$($JQ '.notes[-1].id // 0 | tonumber' $DB)
135 # [ "" == $LASTID ] && LASTID=0
b9f21021 136 NOTEID=$(( LASTID + 1 ))
137 # shellcheck disable=SC2086
a4aaf855 138 touch ${NOTESDIR}/${NOW}
b9f21021 139 # shellcheck disable=SC2016
a4aaf855 140 $JQ --arg i "$NOTEID" --arg t "$NOTETITLE" --arg f "$NOW" '.notes += [{"id": $i, "title": $t, "file": $f}]' "$DB" > $TMPDB
b9f21021 141 # shellcheck disable=SC2086
a4aaf855 142 mv $TMPDB $DB
b648c006 143 # example for alacritty:
144 # alacritty --class notes --title notes -e /usr/bin/vim ...
b9f21021 145 # shellcheck disable=SC2086,SC2091
e3670e83 146 $(${TERMINAL} ${TERM_OPTS} ${EDITOR} ${NOTESDIR}/${NOW})
a4aaf855 147}
148
149function listnotes() {
e3670e83 150 # [ $PLAIN == true ] && echo "output is plain text" || echo "output is colored"
b9f21021 151 if [[ $(ls -A "$NOTESDIR") ]]; then
b648c006 152 if [ $PLAIN == false ]; then
153 echo "listing all notes"
154 echo ""
155 fi
156 [ $PLAIN == false ] && echo "[ID] [TITLE] [CREATED]"
b9f21021 157 for i in "${NOTESDIR}"/*; do
158 # shellcheck disable=SC2155
b648c006 159 local fname=$(basename $i)
160 DATE=$(date -d @${fname} +"%d/%m/%Y %R %z%Z")
b9f21021 161 # shellcheck disable=SC2016,SC2086
e3670e83 162 TITLE=$($JQ -r --arg z $(basename $i) '.notes[] | select(.file == $z) | .title' $DB)
b9f21021 163 # shellcheck disable=SC2016,SC2086
e3670e83 164 ID=$($JQ -r --arg z $(basename $i) '.notes[] | select(.file == $z) | .id' $DB)
b648c006 165 [ $PLAIN == false ] && echo "[${ID}] ${TITLE} ${DATE}" || echo "${ID} - ${TITLE} - ${DATE}"
e3670e83 166 done
167 else
168 echo "no notes yet. You can add your first one with: ${BASENAME} -a \"your note title\""
169 fi
a4aaf855 170}
171
172function editnote() {
61c91990 173 NOTE=$1
b9f21021 174 # shellcheck disable=SC2155
175 local OK=$(check_noteID "$NOTE")
176 if [ ! "$OK" ]; then
61c91990 177 echo "invalid note \"$NOTE\""
b9f21021 178 echo "Use the note ID that you can fetch after listing your notes"
61c91990 179 exit 1
180 fi
181
b9f21021 182 # shellcheck disable=SC2016,SC2086
61c91990 183 TITLE=$($JQ --arg i $OK '.notes[] | select(.id == $i) | .title' $DB)
b9f21021 184 # shellcheck disable=SC2016,SC2086
61c91990 185 FILE=$($JQ -r --arg i $OK '.notes[] | select(.id == $i) | .file' $DB)
44abbfe7 186 if [ "$TITLE" ]; then
187 echo "editing note $TITLE"
b9f21021 188 # shellcheck disable=SC2086,SC2091
b648c006 189 $(${TERMINAL} ${TERM_OPTS} ${EDITOR} ${NOTESDIR}/${FILE})
44abbfe7 190 else
191 echo "note not found"
192 exit 1
193 fi
a4aaf855 194}
195
a4aaf855 196function rmnote() {
026502da 197 # remove eventually existing temp DB file
198 if [[ -f $TMPDB ]]; then
199 rm $TMPDB
b648c006 200 fi
201
026502da 202 NOTE=$1
b9f21021 203 if [ "all" == "$NOTE" ]; then
026502da 204 echo "You're going to delete all notes."
205 read -r -p "Do you wish to continue? (y/N) " ANSWER
206 case $ANSWER in
207 y|Y )
b9f21021 208 # shellcheck disable=SC2086
026502da 209 $JQ 'del(.notes[])' $DB > $TMPDB
b9f21021 210 # shellcheck disable=SC2086
026502da 211 mv $TMPDB $DB
b9f21021 212 # shellcheck disable=SC2086
026502da 213 rm $NOTESDIR/*
214 echo "Deleted all notes"
215 ;;
216 * )
217 echo "Aborting, no notes were deleted."
218 exit 1
219 ;;
220 esac
b648c006 221 else
b9f21021 222 # shellcheck disable=SC2155
223 local OK=$(check_noteID "$NOTE")
224 if [ ! "$OK" ]; then
026502da 225 echo "invalid note \"$NOTE\""
b9f21021 226 echo "Use the note ID that you can fetch after listing your notes"
026502da 227 exit 1
228 fi
229
b9f21021 230 # shellcheck disable=SC2016,SC2086
026502da 231 TITLE=$($JQ --arg i $OK '.notes[] | select(.id == $i) | .title' $DB)
b9f21021 232 # shellcheck disable=SC2016,SC2086
026502da 233 FILE=$($JQ -r --arg i $OK '.notes[] | select(.id == $i) | .file' $DB)
234 if [ "$TITLE" ]; then
b9f21021 235 # shellcheck disable=SC2016,SC2086
026502da 236 $JQ -r --arg i $OK 'del(.notes[] | select(.id == $i))' $DB > $TMPDB
b9f21021 237 # shellcheck disable=SC2086
026502da 238 mv $TMPDB $DB
239 rm $NOTESDIR/$FILE
240 echo "Deleted note $TITLE"
241 else
242 echo "note not found"
243 exit 1
244 fi
b648c006 245 fi
a4aaf855 246}
247
d80ac20a 248function export_config() {
249 if [ -r ${RCFILE} ]; then
250 echo "Backing up current '${RCFILE}'...."
251 mv -f ${RCFILE} ${RCFILE}.$(date +%Y%m%d_%H%M)
252 fi
253 echo "Writing '${RCFILE}'...."
254 sed -n '/^set_defaults() {/,/^} # end set_defaults, do not change this line./p' $0 \
255 | grep -v set_defaults \
256 | sed -e 's/^\([^=]*\)=\${\1:-\([^}]*\)}/\1=\2/' \
257 > ${RCFILE}
258 if [ -r ${RCFILE} ]; then
259 echo "Taking no further action."
260 exit 0
261 else
262 echo "Could not write '${RCFILE}'...!"
263 exit 1
264 fi
265}
266
53f2ed57 267# we should expand on this function to add a sample note and explain a little bit
268# how the program works.
a4aaf855 269function firstrun() {
53f2ed57 270 [ -f $RCFILE ] && RC=$RCFILE || RC="none"
271
272 clear
273 echo "${BASENAME} configuration:
274
275base directory: ${BASEDIR}/
276notes archive: ${NOTESDIR}/
277notes database: ${DB}
278rc file: $RC
279text editor: ${EDITOR}
280terminal: ${TERMINAL}
281jq executable: ${JQ}
282"
283
284 read -r -p "Do you wish to continue? (y/N) " ANSWER
285 case $ANSWER in
286 y|Y )
287 mkdir -p $NOTESDIR
d80ac20a 288 cat << __EOL__ > ${DB}
a4aaf855 289{
d80ac20a 290 "params": {
291 "version": "${VERSION}",
292 "dbversion": "${NOW}"
293 },
a4aaf855 294 "notes": []
295}
296__EOL__
d80ac20a 297 echo; echo "All done, you can now write your first note."
53f2ed57 298 ;;
299 * )
300 echo "No changes made. Exiting"
301 exit
302 ;;
303 esac
a4aaf855 304}
305
306# check for notes dir existance and create it in case it doesn't exists
307if [[ ! -d $NOTESDIR ]]; then
308 # we don't have a directory. FIRST RUN?
309 firstrun
310fi
311
53f2ed57 312# NOTE: This requires GNU getopt. On Mac OS X and FreeBSD, you have to install this
313# separately; see below.
b9f21021 314# shellcheck disable=SC2006
6c152f7e 315GOPT=`getopt -o hvpla:e:d: --long help,version,list,plain,userconf,add:,edit:,delete:,editor:,storage: \
53f2ed57 316 -n 'bash-notes' -- "$@"`
317
b9f21021 318# shellcheck disable=SC2181
cb8fcb2f 319if [ $? != 0 ] ; then helptext >&2 ; exit 1 ; fi
53f2ed57 320
321# Note the quotes around `$GOPT': they are essential!
322eval set -- "$GOPT"
323
324while true; do
325 case "$1" in
326 -h | --help )
327 helptext
328 exit
329 ;;
330 -v | --version )
331 echo $BASENAME v${VERSION}
332 exit
333 ;;
c018122c 334 -p | --plain )
335 PLAIN=true
336 shift
337 ;;
53f2ed57 338 -l | --list )
339 listnotes
340 exit
341 ;;
342 -a | --add )
343 TITLE="$2"
344 shift 2
345 addnote "$TITLE"
346 ;;
6c152f7e 347 -e | --edit )
53f2ed57 348 NOTE="$2"
349 shift 2
350 editnote "$NOTE"
351 ;;
b648c006 352 -d | --delete )
53f2ed57 353 NOTE="$2"
354 shift 2
355 rmnote "$NOTE"
356 ;;
d80ac20a 357 --userconf )
358 export_config
b9f21021 359 # shellcheck disable=SC2317
d80ac20a 360 echo "config exported to \"$RCFILE\""
b9f21021 361 # shellcheck disable=SC2317
d80ac20a 362 exit
53f2ed57 363 ;;
364 -- )
026502da 365 shift
366 break
53f2ed57 367 ;;
368 * )
369 break
370 ;;
371 esac
a4aaf855 372done
373