Files
vns/vns
2018-07-15 02:04:54 -05:00

371 lines
11 KiB
Bash

#!/usr/bin/env bash
# Vim Note System v1.1, a simple script for managing encrypted plaintext notes
# Copyright (C) 2018 Jon Lewis
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
set -euo pipefail # Terminate the script if anything goes wrong
# http://redsymbol.net/articles/unofficial-bash-strict-mode/
readonly VNS_PRODUCT="Vim Note System, v1.1" # String to print for reports
readonly VNS_STORE="$HOME/.config/vns" # Where vns expects its note store
readonly GPG_TTY="$(tty)" # Sets up gpg-agent for use
vns_raise (){
# raise (message) (exit code)
# Prints error (message) to stderr, then exits with (exit code)
local -r VNS_RED_COLOR='\033[0;31m'
local -r VNS_RESET_COLOR='\033[0m'
printf "%s, %s\\n\\n" "$VNS_PRODUCT"
printf "$VNS_RED_COLOR!$VNS_RESET_COLOR - %s\\n" "$1" 1>&2
exit "$2"
}
vns_checkDeps (){
# checkDeps
# Prints a list of unbound variables
readonly VNS_DEPS=("vim" "gpg2" "tree" "git")
for DEP in "${VNS_DEPS[@]}"; do
if test ! -e "$(which "$DEP" 2>/dev/null)"; then
printf "%s " "$DEP"
fi
done
}
vns_sanityCheck () {
# sanityCheck
# Terminates script if environment is not suitable for vns
# --- Dependencies ---
# Get list of missing dependencies
local MISSING; MISSING="$(vns_checkDeps)"
# If that list is not empty, inform the user and exit on code 20
if [ -n "$MISSING" ]; then
vns_raise "Missing Dependencies: $MISSING" 20
fi
# --- Store ---
# Verify that the note store has been initialized
if [ ! -d "$VNS_STORE" ]; then
vns_raise "The vns store needs to be initialized (hint: -i)" 21
fi
}
vns_init () {
# initStore
# Prepares $VNS_STORE for use
if [ -d "$VNS_STORE/.git" ]; then
vns_raise "Store already initialized" 2;
fi
mkdir -p "$VNS_STORE"
cd "$VNS_STORE" || exit
git init >/dev/null
}
vns_printHelp (){
# printHelp
# Prints help information to stdout
printf "%s" "usage: vns [-cedlp] <notebook/section/name>"
printf "\\n%s" " vns -I"
printf "\\n%s" " vns -h"
printf "\\n%s" " vns git ..."
printf "\\n"
printf "\\n%s" " -c : Create note"
printf "\\n%s" " -d : Delete note"
printf "\\n%s" " -e : Open note for editing"
printf "\\n%s" " -h : Display this message"
printf "\\n%s" " -i : import file as note"
printf "\\n%s" " -I : Initialize note store"
printf "\\n%s" " -l : List all notes in <notebook>"
printf "\\n%s" " -m : Merge two or more notes"
printf "\\n%s" " -p : Print note to console"
printf "\\n\\n"
}
vns_list () {
# list (notebook)
# Prints a tree containing all notes in the notebook
# If no notebook is specified, the entire store is used
numNotes () { find . -name "*.gpg" \( ! -regex '.*/\..*' \) | wc -l; }
# Check for default behavior
if [ -z "${*:1:1}" ]; then
readonly NOTEBOOK=""
if [ "$(numNotes)" != "0" ];then
printf "%s\\n" "Store"
fi
else
readonly NOTEBOOK="$1"
printf "%s\\n" "$1"
fi
tree -C --noreport --prune "$VNS_STORE/$NOTEBOOK"\
| tail -n +2\
| sed s/\.gpg//g
}
vns_rm () {
# rm (note)
# removes (note) from the store
local -r VNS_BLUE_COLOR='\033[0;34m'
local -r VNS_RESET_COLOR='\033[0m'
# Verify $1 is bound
if [ -z "${*:1:1}" ]; then vns_raise "Insufficient arguments" 30; fi
# Verify the note exists
if [ ! -r "$VNS_STORE/$1.gpg" ]; then
vns_raise "Note does not exist" 31;
fi
# Delete the Note
git rm "$VNS_STORE/$1.gpg" > /dev/null
git commit -m "Deleted $1" > /dev/null
# Report success
printf "%s, %s\\n\\n" "$VNS_PRODUCT"
printf "$VNS_BLUE_COLOR!$VNS_RESET_COLOR - %s\\n" "Deleted $VNS_STORE/$1"
}
vns_create () {
# create (note)
# Create a new note and open it for editing
local -r VNS_RED_COLOR='\033[0;31m'
local -r VNS_RESET_COLOR='\033[0m'
# Verify $1 is bound
if [ -z "${*:1:1}" ]; then vns_raise "Insufficient arguments" 40; fi
# Refuse to overwrite a note
if [ -r "$VNS_STORE/$1.gpg" ]; then vns_raise "Note already exists" 41; fi
# If the note belongs to a new notebook, create the notebook
if [ ! -d "$(dirname "$1")" ]; then mkdir -p "$VNS_STORE/$(dirname "$1")"; fi
# Create-Edit the note
vim "$VNS_STORE/$1.gpg"
# Warn the user if the note failed to encrypt
if ! grep "PGP" <<< "$(file "$VNS_STORE/$1.gpg")" > /dev/null; then
printf "$VNS_RED_COLOR!$VNS_RESET_COLOR - %s\\n %s\\n"\
"The created note was not encrypted."\
"Check your vim-gnupg setup."
fi
# Update Git
git add "$VNS_STORE/$1.gpg" > /dev/null
git commit -m "Added $1" > /dev/null
}
vns_edit () {
# edit (note)
# Open (note) for editing in Vim
# Verify $1 is bound
if [ -z "${*:1:1}" ]; then vns_raise "Insufficient arguments" 50; fi
# Refuse to edit non-existent notes
if [ ! -r "$VNS_STORE/$1.gpg" ]; then vns_raise "Note not found: $1" 51; fi
# Edit the specified note
vim "$VNS_STORE/$1.gpg"
# Update Git
git add "$VNS_STORE/$1.gpg" > /dev/null
git commit -m "Edited $1" > /dev/null
}
vns_import () {
# import (src) (dest)
# If src is the path to a file, and dest is a valid note name,
# import src dest copies an encrypted version of src to the note store
if [ -z "${*:1:1}" ]; then
vns_raise "No source file specified" 90
elif [ -z "${*:2:2}" ]; then
vns_raise "No destination name specified" 91
elif [ ! -f "$1" ]; then
vns_raise "Cannot read $1" 92
elif [ -f "$VNS_STORE/$2" ]; then
printf "%s" "Note already exists. Overwrite? (Y/N) "
local response; read -r response
case "$response" in
Yy)
rm "$VNS_STORE/$2";;
*)
vns_raise "User declined overwrite" 0;;
esac
fi
printf "%s:" "Specify GPG Public Key: "
local gpg_key; read -r gpg_key
gpg2 -r "$gpg_key" -o "$VNS_STORE/$2.gpg" -e "$1"
git add "$VNS_STORE/$2.gpg"
git commit -m "Imported file $1 as $2"
}
vns_print () {
# print (note)
# print the given note to stdout
# Verify $1 is bound
if [ -z "${*:1:1}" ]; then vns_raise "Insufficient arguments" 11; fi
# Decrypt the specified note
gpg2 -d "$VNS_STORE/$1.gpg"
}
vns_mv () {
# mv (src) (dest)
# If (src) is a note in the vns store, mv src dest moves src to dest
if [ -z "${*:2}" ]; then vns_raise "Insufficient arguments" 70; fi
if [ ! -r "$VNS_STORE/$1.gpg" ]; then vns_raise "Cannot read $1" 71; fi
if [ -r "$VNS_STORE/$2.gpg" ]; then vns_raise "Note $2 already exists" 72; fi
git mv "$VNS_STORE/$1.gpg" "$VNS_STORE/$2.gpg"
}
vns_merge () {
# merge (output) (notes[])
# If notes[] is an array of note names and output is a new note name,
# merge (output) (notes[]) concatenates notes[] into output.
if [ -z "${*:1}" ]; then vns_raise "No output specified" 81; fi
if [ -z "${*:2}" ]; then vns_raise "No inputs specified" 84; fi
if [ -r "$VNS_STORE/$1.gpg" ]; then vns_raise "Output file already exists." 82; fi
# Make output file
if [ ! -r "$VNS_STORE/$2.gpg" ]; then vns_raise "Could not read $2" 83; fi
cp "$VNS_STORE/$2.gpg" "$VNS_STORE/$1.gpg"
local -r GPG_RECIP="$(gpg --list-only -v -d "$VNS_STORE/$1.gpg" 2>&1 | cut -f 5 -d " ")"
for note in "${@:3}"; do
if [ ! -r "$VNS_STORE/$note.gpg" ]; then
rm "$VNS_STORE/$1.gpg"
vns_raise "Could not read $note" 83;
fi
gpg2 --batch --yes -r "$GPG_RECIP" -o "$VNS_STORE/$1.gpg" -e <<< "$(gpg2 -d "$VNS_STORE/$1.gpg") $(gpg2 -d "$VNS_STORE/$note.gpg")"
done
for note in "${@:2}"; do
git rm "$VNS_STORE/$note.gpg"
done
git add "$VNS_STORE/$1.gpg"
git commit -m "merged files ${*:2} into $1"
}
vns_duplicate () {
if [ -z "${*:1:1}" ]; then
vns_raise "No source note specified" 100
elif [ -z "${*:2:2}" ]; then
vns_raise "No destination name specified" 101
elif [ ! -f "$VNS_STORE/$1" ]; then
vns_raise "Cannot read $1" 102
elif [ -f "$VNS_STORE/$2" ]; then
printf "%s" "Note already exists. Overwrite? (Y/N) "
local response; read -r response
case "$response" in
Yy)
rm "$VNS_STORE/$2";;
*)
vns_raise "User declined overwrite" 0;;
esac
fi
cp "$VNS_STORE/$1.gpg" "$VNS_STORE/$2.gpg"
git add "$VNS_STORE/$2"
}
vns () {
# Bypass sanity check if told to initialize store
if [ "$*" != "-i" ]; then
vns_sanityCheck;
# Change Directory to Store for Git passthrough
cd "$VNS_STORE" || exit;
fi
# Default action is to list all notes
if [ -z "${*:1:1}" ]; then vns_list ""; exit 0; fi
# List of valid arguments
declare -A -r ACTIONS=( ["-c"]="vns_create" ["-d"]="vns_rm"\
["-e"]="vns_edit" ["-l"]="vns_list"\
["-h"]="vns_printHelp" ["-I"]="vns_init"\
["-i"]="vns_import" ["-m"]="vns_merge"\
["-p"]="vns_print" ["-r"]="vns_mv"\
["-u"]="vns_duplicate" ["git"]="git" )
# If given an invalid argument, inform the user and exit on code 10
# Otherwise, perform the corresponding action
if ! test "${ACTIONS["$1"]+isset}"; then
vns_raise "Invalid argument: $1" 10
else
"${ACTIONS["$1"]}" "${@:2}"
fi
exit 0;
}
vns "$@"