Skip to content

Instantly share code, notes, and snippets.

@dstar4138
Last active December 14, 2021 14:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dstar4138/eecd6f9b47b1b8f9b849b72b1b68c701 to your computer and use it in GitHub Desktop.
Save dstar4138/eecd6f9b47b1b8f9b849b72b1b68c701 to your computer and use it in GitHub Desktop.
My note/journal manager, similar to memo but only in bash.
#!/usr/bin/env bash
# My simple note/journal manager. Needs fzf/vim by default.
set -aeuf -o pipefail
VERSION="0.5"
USAGE="Usage: note COMMAND
Note is a simple bash script for note creation/management. It can be extended
with plug-ins, or used as is. It creates notes in NOTE_BASE (~/.notes), and
keeps an append-only daily journal that you can add quick-notes to.
COMMANDS:
new [name]
Create a new note in the notes directory. If no name is
provided, then it will ask you for one. If NOTE_LOG_NEW
is 'true' (default), then this new note creation is logged
in the journal.
edit [query]
Opens a file selector, then drops into the editor with the
given selection. If a query is provided, that is given
to the selector to fuzzy search.
quick [note]
Saves a quick note to the daily-journal, or opens for editing
if not note is provided.
select [query]
Opens a file selector, and then prints the full path to the
selected file. You may pipe this to another editor, etc.
"
# Base-Path.
NOTE_BASE="${NOTE_BASE:-$HOME/.notes}"
# Path for Plug-ins, and Templates.
NOTE_PLUG_PATH="${NOTE_PLUG_PATH:-$NOTE_BASE/.plugins}"
NOTE_TEMPLATE_PATH="${NOTE_TEMPLATE_PATH:-$NOTE_BASE/.templates}"
# Should we log new note creation in the journal to time-stamp it?
NOTE_LOG_NEW=${NOTE_LOG_NEW:-true}
# Mock this if you want to pretend its a different day?
NOTE_DATE="$(date +%Y-%m-%d)"
NOTE_TIMESTAMP="$(date)"
# Change this if you want to use a different file type?
NOTE_EXT="${NOTE_EXT:-md}"
# Change this if you don't like your normal editor for some reason.
# Default is 'vim +' which opens vim at the LAST line of the file.
NOTE_EDITOR=${NOTE_EDITOR:-vim --not-a-term +}
# Change this if you have a different program for selecting things.
NOTE_SELECTOR=${NOTE_SELECTOR:-fzf --no-multi --exit-0}
NOTE_SELECTOR_ARGS=${NOTE_SELECTOR_ARGS:-"--select-1 -q"}
# We have a daily journal that is append-only. Used for quick notes.
DAILY_JOURNAL="${DAILY_JOURNAL:-$NOTE_BASE/journal.md}"
# This file contains the current date IFF we have already evaluated the
# do_daily/check_daily function (i.e. we've added a quick-note).
DAILY_TS="$NOTE_BASE/.today"
# Templates for Journal and Notes, feel free to edit them yourself!
DAILY_TEMPLATE_FILE="${DAILY_TEMPLATE_FILE:-$NOTE_TEMPLATE_PATH/daily}"
NOTE_TEMPLATE_FILE="${NOTE_TEMPLATE_FILE:-$NOTE_TEMPLATE_PATH/notes}"
LOG_TEMPLATE_FILE="${LOG_TEMPLATE_FILE:-$NOTE_TEMPLATE_PATH/logs}"
# We have some super simple Markdown templates that we can use here..
DAILY_TEMPLATE='\#\# ${NOTE_DATE}\\n'
NOTE_TEMPLATE='\# ${NAME}\\n_Created: ${NOTE_TIMESTAMP}_\\n\\n'
LOG_TEMPLATE='\* New note created: "${NAME}"\\n \* Path: ${NOTE_PATH}\\n'
# Example plugin created.
EXAMPLE_PLUG='#!/usr/bin/env bash
is_note_plug() { [[ "$1" = "list" ]] || [[ "$1" = "ls" ]]; }
run_note_plug() { ls -1 "$NOTE_BASE" ; }
echo_plug_usage() {
echo "
ls | list
Print all note files in a single column.
"
}
'
##############################################################################
# Templates for Journal/Notes/Logs.
##############################################################################
# You may override any of the templates by updating the template files:
# NOTE_TEMPLATE_FILE - Used for "new" notes,
# DAILY_TEMPLATE_FILE - Used for appending the daily journal each day, and
# LOG_TEMPLATE_FILE - Used for appending to the journal to timestamp each
# "new" note as long as NOTE_LOG_NEW is 'true'.
#
eval_t() { echo -e $(eval echo "$1"); } # Evaluate a template for variables.
get_t() { [[ -f "$1" ]] || build; cat "$1"; } # Get template file content.
put_t() { [[ -f "$1" ]] || echo "$2" >"$1"; } # Save template file content.
t_note() { get_t "$NOTE_TEMPLATE_FILE"; } # Normal note template.
t_daily() { get_t "$DAILY_TEMPLATE_FILE"; } # Daily note template.
t_log() { get_t "$LOG_TEMPLATE_FILE"; } # Log note template.
build_t() {
mkdir -p "$NOTE_TEMPLATE_PATH"
put_t "$DAILY_TEMPLATE_FILE" "$DAILY_TEMPLATE"
put_t "$NOTE_TEMPLATE_FILE" "$NOTE_TEMPLATE"
put_t "$LOG_TEMPLATE_FILE" "$LOG_TEMPLATE"
}
##############################################################################
# Plugin Integration.
##############################################################################
# Plug-ins are just BASH scripts with at least three functions implemented:
# is_note_plug - Returns 0 if the plugin can handle the command at "$1".
# run_note_plug - Performs the operation on "$@", "$1" is the command.
# echo_plug_usage - Emits a string to be concatenated with note's USAGE.
#
plug_usage() { (source "$1" && exists echo_plug_usage && echo_plug_usage); }
plug_check() { (source "$1" && exists is_note_plug && is_note_plug "$2"); }
plug_run() { (source "$1" && shift && run_note_plug "$@"); }
try_plug_usage() {
local PLUGS=$(find "$NOTE_PLUG_PATH" -type f -name "*.sh")
for plug in $PLUGS; do
[[ -f "$plug" ]] && OUT="${OUT:-}$(plug_usage "$plug")"
done
[[ -z "${OUT:-}" ]] || echo -e "PLUGIN COMMANDS:\n$OUT"
}
try_plugs() {
local PLUGS=$(find "$NOTE_PLUG_PATH" -type f -name "*.sh")
for plug in $PLUGS; do
([[ -f "$plug" ]] && plug_check "$plug" "$@") || continue
found=true; (plug_run "$plug" "$@" && exit $?)
done
${found:-false} || die "Unknown command: $1"
}
build_plug() {
[[ -d "$NOTE_PLUG_PATH" ]] && return 0
mkdir -p "$NOTE_PLUG_PATH"
echo "$EXAMPLE_PLUG" >"$NOTE_PLUG_PATH/example.sh"
}
##############################################################################
# State handling.
##############################################################################
# We create NOTE_BASE and everything else inside.
# * Plugins are stored in .plugins/
# - We populate it with a dummy plugin for an example.
# * Templates are stored in .templates/
# - We populate it with three templates we use; notes, daily, logs.
# * Current timestamp is stored at .today
# - We use this for calculating if we need to create a new daily journal.
#
build() { mkdir -p "$NOTE_BASE" && build_t && build_plug && touch "$DAILY_TS"; }
die() { echo "$@" >&2; exit 1; }
clean() { echo "$@" | tr -s ' \\|/:\t~+=]})({[&!`,.><?@#$%^' - | head -c 40; }
exists(){ declare -f -F "$1" >/dev/null; return $?; }
do_daily() {
# DAILY_TS file must have current-date, else return 1.
[[ "$(cat "$DAILY_TS")" = "$NOTE_DATE" ]] && return 0
echo "$NOTE_DATE" >"$DAILY_TS"; return 1
}
check_daily() { do_daily || eval_t "$(t_daily)" >>"$DAILY_JOURNAL"; }
##############################################################################
# Operations on Notes.
##############################################################################
usage() { echo "$USAGE" && try_plug_usage; }
new() {
local NAME="$@"
[[ $# -eq 0 ]] && echo -n "Note Title: " && read NAME
local NOTE_PATH="$NOTE_BASE/$NOTE_DATE-$(clean "$NAME").$NOTE_EXT"
[[ -f "$NOTE_PATH" ]] && die "Note with title exists already..."
eval_t "$(t_note)" | (cd "$NOTE_BASE" && $NOTE_EDITOR "+:f $NOTE_PATH" -)
if [[ -f "$NOTE_PATH" ]] && $NOTE_LOG_NEW; then
check_daily
eval_t "$(t_log)" >>"$DAILY_JOURNAL"
fi
}
quick() {
check_daily
[[ $# -ne 0 ]] && echo "$@" >>"$DAILY_JOURNAL" && exit 0
$NOTE_EDITOR "$DAILY_JOURNAL"
}
selector() {
local ARGS="${NOTE_SELECTOR_ARGS} $@"
cd "$NOTE_BASE"
[[ $# -eq 0 ]] && ARGS=""
$NOTE_SELECTOR --print0 $ARGS | xargs -0 -I{} echo "$NOTE_BASE/{}"
}
edit() { local f=$(selector "$@"); cd "$NOTE_BASE" && $NOTE_EDITOR "$f"; }
##############################################################################
# Main section
##############################################################################
build
[[ $# -eq 0 ]] && usage && exit 1
cmd="$1"; shift
case "$cmd" in
new) new "$@" ;;
edit) edit "$@" ;;
quick) quick "$@" ;;
select) selector "$@" ;;
-v|version) echo "$VERSION";;
-h|info|help) usage ;;
*) try_plugs "$cmd" "$@"
esac
@dstar4138
Copy link
Author

Newest version is here along with a hand-full of plugins:
https://git.sr.ht/~dstar4138/note/tree

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment