Last active
December 14, 2021 14:58
-
-
Save dstar4138/eecd6f9b47b1b8f9b849b72b1b68c701 to your computer and use it in GitHub Desktop.
My note/journal manager, similar to memo but only in bash.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Newest version is here along with a hand-full of plugins:
https://git.sr.ht/~dstar4138/note/tree