From 2f582525f66ddc725a8325d94fcfc4d5d81f7da2 Mon Sep 17 00:00:00 2001 From: Vera Lewis Date: Wed, 27 Mar 2024 19:26:46 -0500 Subject: [PATCH] Significant changes towards VNS 2.0 --- Makefile | 4 + _vns | 72 +++++++++++ vns | 361 +++++++++++++++++++++++++++++-------------------------- 3 files changed, 266 insertions(+), 171 deletions(-) create mode 100644 Makefile create mode 100644 _vns diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2167fe5 --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +PREFIX = /home/xilmwa/.local/ + +install: vns + install vns $(PREFIX)/bin diff --git a/_vns b/_vns new file mode 100644 index 0000000..9a5db37 --- /dev/null +++ b/_vns @@ -0,0 +1,72 @@ +#compdef vns +#autoload + +# Modified from _pass zsh completion, with the following copyright information +# Copyright (C) 2012 - 2014: +# Johan Venant +# Brian Mattern +# Jason A. Donenfeld . +# All Rights Reserved. +# This file is licensed under the GPLv2+. Please see COPYING for more information. + +_vns () { + local cmd + if (( CURRENT > 2)); then + cmd=${words[2]} + # Set the context for the subcommand. + curcontext="${curcontext%:*:*}:vns-$cmd" + # Narrow the range of words we are looking at to exclude `vns' + (( CURRENT-- )) + shift words + # Run the completion for the subcommand + case "${cmd}" in + -e|-p|-r|-d|-c) + _vns_complete_notes + ;; + #-c) + # _vns_complete_notebooks + # ;; + -l) + _vns_complete_notes + ;; + *) + if [ "${cmd[0]}" != '-' ] && (( CURRENT > 2)); then + _vns_complete_notebooks + fi + ;; + esac + else + local -a subcommands + subcommands=( + "-c:Create note" + "-d:Delete note" + "-e:Edit note" + "-h:Display help message" + "-i:Import a note" + "-I:Initialize a new VNS store" + "-l:List notes" + "-m:Merge notes" + "-p:Print note to console" + "-r:Rename/move a note" + ) + _describe -t commands 'vns' subcommands + _vns_complete_notebooks + fi +} + +_vns_complete_entries_helper () { + local IFS=$'\n' + local prefix + zstyle -s ":completion:${curcontext}:" prefix prefix || prefix="${VNS_STORE_DIR:-$HOME/.vns}" + _values -C 'notes' ${$(find -L ${prefix} -mindepth 1 -name .git -prune -o $@ -print | sed -e "s#${prefix}/##" -e "s/.gpg//" -e "s#:#\\:#g" | sort):-""} +} + +_vns_complete_notebooks () { + _vns_complete_entries_helper -type d +} + +_vns_complete_notes () { + _vns_complete_entries_helper -type f +} + +_vns diff --git a/vns b/vns index a0c083b..c2111d4 100755 --- a/vns +++ b/vns @@ -1,35 +1,19 @@ -#!/usr/bin/env bash -# Vim Note System v1.5, a simple script for managing encrypted plaintext notes -# Copyright (C) 2018 Jon Lewis +#!/bin/bash +# Vim Note System, a simple script for managing encrypted plaintext notes -# 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.5" # 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 +readonly VNS_PRODUCT="Vim Note System, v2.0" # String to print for reports +readonly VNS_STORE="${VNS_STORE:-$HOME/.vns}" # Where vns expects its note store +GPG_TTY="$(tty)" # Sets up gpg-agent for use +export GPG_TTY 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' - + + local -r VNS_RED_COLOR='\033[0;31m' + local -r VNS_RESET_COLOR='\033[0m' + printf "%s\\n\\n" "$VNS_PRODUCT" printf "$VNS_RED_COLOR!$VNS_RESET_COLOR - %s\\n" "$1" 1>&2 exit "$2" @@ -37,58 +21,58 @@ vns_raise (){ } vns_report () { - printf "%s\n" "$*" + + local -r VNS_BLUE_COLOR='\033[0;34m' + local -r VNS_RESET_COLOR='\033[0m' + + printf "${VNS_BLUE_COLOR}*${VNS_RESET_COLOR} - %s\n" "$*" } vns_checkDeps (){ - + # checkDeps - # Prints a list of unbound variables - - readonly VNS_DEPS=("vim" "gpg2" "tree" "git") - - for DEP in "${VNS_DEPS[@]}"; do + # Prints a list of missing dependencies + + for DEP in 'vim' 'tree' 'git'; do if test ! -e "$(command -v "$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 - + # Get list of missing dependencies + MISSING="$(vns_checkDeps)" + + # Inform the user of missing dependencies and exit on code 20 if any were found + 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 - + # 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/.vns_git" ]; then vns_raise "Store already initialized" 2; fi - mkdir -p "$VNS_STORE" - cd "$VNS_STORE" || exit - vns_git init >/dev/null + git init "$VNS_STORE" } @@ -96,7 +80,7 @@ vns_printHelp (){ # printHelp # Prints help information to stdout - + printf "%s" "usage: vns [-cedlp] " printf "\\n%s" " vns -I" printf "\\n%s" " vns -h" @@ -121,63 +105,61 @@ 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; } + + #numNotes () { find "${VNS_STORE}" -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 - + if [ "$#" -lt 1 ]; then + local -r "NOTEBOOK"="" + printf "%s\\n" "vns" + else + readonly NOTEBOOK="$1" + printf "%s\\n" "$1" + fi + + tree --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' + printf "%s\\n\\n" "$VNS_PRODUCT" while [ "$#" -gt 0 ]; do # Verify $1 is bound - if [ -z "${*:1:1}" ]; then vns_raise "Insufficient arguments" 30; fi - + if [ "$#" -lt 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 + if [ ! -r "$VNS_STORE/$1.gpg" ]; then + vns_raise "Note does not exist" 31; + fi + # Delete the Note - vns_git rm "$VNS_STORE/$1.gpg" > /dev/null - vns_git commit -m "Deleted $1" > /dev/null - + vns_git rm "$VNS_STORE/$1.gpg" > /dev/null + vns_git commit -m "Deleted $1" > /dev/null + # Report success - printf "$VNS_BLUE_COLOR!$VNS_RESET_COLOR - %s\\n" "Deleted $VNS_STORE/$1" - - shift - done - + vns_report "Deleted $1" + + # Move to the next item + shift + done + } 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' - - if [ -z "${*:1:1}" ]; then + local -r VNS_RED_COLOR='\033[0;31m' + local -r VNS_RESET_COLOR='\033[0m' + + if [ "$#" -lt 1 ]; then printf "usage: %s %s\n" "$(basename "$0")" "-c [notebook/section/](name)" return fi @@ -194,14 +176,14 @@ vns_create () { # Warn the user if the note failed to encrypt if ! file "$VNS_STORE/$1.gpg" | grep "PGP" -qs; then printf "$VNS_RED_COLOR!$VNS_RESET_COLOR - %s\\n %s\\n"\ - "The created note was not encrypted."\ - "Check your vim-gnupg setup." + "The created note was not encrypted."\ + "Check your vim-gnupg setup." fi - + # Update vns_git - vns_git add "$VNS_STORE/$1.gpg" > /dev/null - vns_git commit -m "Added $1" > /dev/null - + vns_git add "$VNS_STORE/$1.gpg" > /dev/null + vns_git commit -m "Added $1" > /dev/null + } vns_edit () { @@ -210,17 +192,17 @@ vns_edit () { # Open (note) for editing in Vim # Verify $1 is bound - if [ -z "${*:1:1}" ]; then vns_raise "Insufficient arguments" 50; fi - + if [ "$#" -lt 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 - + if [ ! -r "$VNS_STORE/$1.gpg" ]; then vns_raise "Note not found: $1" 51; fi + # Edit the specified note - vim "$VNS_STORE/$1.gpg" - + vim "$VNS_STORE/$1.gpg" + # Update vns_git - vns_git add "$VNS_STORE/$1.gpg" > /dev/null - vns_git commit -m "Edited $1" > /dev/null + vns_git add "$VNS_STORE/$1.gpg" > /dev/null + vns_git commit -m "Edited $1" > /dev/null } vns_import () { @@ -228,18 +210,18 @@ 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 - pwd + if [ "$#" -lt 2 ]; then printf "usage: %s %s\n" "$(basename "$0")" "-i (source) (destination)" return fi - + if [ ! -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 + local response=''; read -r response case "$response" in Y|y) rm "$VNS_STORE/$2";; @@ -247,12 +229,12 @@ vns_import () { 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" - + local gpg_key=''; read -r gpg_key + + gpg2 --batch -r "$gpg_key" -o "$VNS_STORE/$2.gpg" -e "$1" + vns_git add "$VNS_STORE/$2.gpg" vns_git commit -m "Imported file $1 as $2" @@ -262,12 +244,12 @@ 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 - + if [ "$#" -lt 1 ]; then vns_raise "Insufficient arguments" 11; fi + # Decrypt the specified note - gpg2 -d "$VNS_STORE/$1.gpg" + gpg2 --batch -d "$VNS_STORE/$1.gpg" } @@ -276,8 +258,13 @@ 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 + # Ensure two arguments were provided + if [ "$#" -lt 2 ]; then vns_raise "Insufficient arguments" 70; fi + + # Ensure source can be read + if [ ! -r "$VNS_STORE/$1.gpg" ]; then vns_raise "Cannot read $1" 71; fi + + # Refuse to overwrite existing notes if [ -r "$VNS_STORE/$2.gpg" ]; then vns_raise "Note $2 already exists" 72; fi vns_git mv "$VNS_STORE/$1.gpg" "$VNS_STORE/$2.gpg" @@ -289,44 +276,49 @@ 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 [ "$#" -lt 2 ]; then printf "usage: %s %s\n" "$(basename "$0")" "-m (output) (notes...)" return fi - + if [ -r "$VNS_STORE/$1.gpg" ]; then vns_raise "Output file already exists." 82; fi if [ ! -r "$VNS_STORE/$2.gpg" ]; then vns_raise "Could not read $2" 83; fi - local -r GPG_RECIP="$(gpg --list-only -v -d "$VNS_STORE/$2.gpg" 2>&1 | grep -oP "(?<=public key is ).*")" - + + local GPG_OPTS=() + for recip in $(gpg --list-only -v -d "${VNS_STORE}/$2.gpg" 2>&1 | awk '/public key is/{print $5}'); do + GPG_OPTS=( "${GPG_OPTS[@]}" -r "$recip" ) + done + local -r OUTPUT="${VNS_STORE}/${1}.gpg" + shift + vns_gpgcat () { for note in "$@"; do if [ ! -r "$VNS_STORE/$note.gpg" ]; then rm "$VNS_STORE/$1.gpg" vns_raise "Could not read $note" 83; fi - + printf "File: %s\n" "$note" gpg2 --batch -d "$VNS_STORE/$note.gpg" printf "\n" done } - - vns_gpgcat "${@:2}" | gpg2 --batch --yes -r "$GPG_RECIP" -o "$VNS_STORE/$1.gpg" -e - + + vns_gpgcat "$@" | gpg2 --batch --yes "${GPG_OPTS[@]}" --encrypt -o "$VNS_STORE/$OUTPUT.gpg" + vns_git add "$VNS_STORE/$1.gpg" - + printf "%s" "Remove old notes? (y/N) " - local response; read -r response + local response=''; read -r response case "$response" in Y|y) - for note in "${@:2}"; do + for note in "$@"; do vns_git rm "$VNS_STORE/$note.gpg" done;; - esac - - vns_git commit -m "merged files ${*:2} into $1" - vns_report "Successfully merged files ${*:2} into $1" + esac + vns_git add "$VNS_STORE/$OUTPUT.gpg" + vns_git commit -m "merged files $OUTPUT $* into $OUTPUT" + vns_report "Successfully merged files $OUTPUT $* into $OUTPUT" } vns_duplicate () { @@ -335,18 +327,13 @@ vns_duplicate () { vns_report "usage: $(basename "$0") -u (source) (destination)" return fi - #if [ -z "${*:1:1}" ]; then - # vns_raise "No source note specified" 100 -# - # elif [ -z "${*:2:2}" ]; then - # vns_raise "No destination name specified" 101 if [ ! -f "$VNS_STORE/$1.gpg" ]; then vns_raise "Cannot read $1" 102 elif [ -f "$VNS_STORE/$2.gpg" ]; then printf "%s" "Note already exists. Overwrite? (Y/N) " - local response; read -r response + response=''; read -r response case "$response" in Y|y) rm "$VNS_STORE/$2.gpg";; @@ -354,44 +341,76 @@ vns_duplicate () { vns_raise "User declined overwrite" 0;; esac fi - + cp "$VNS_STORE/$1.gpg" "$VNS_STORE/$2.gpg" - vns_git add "$VNS_STORE/$2.gpg" } vns_git () { - cd "$VNS_STORE" || _raise "FIXME" - git "$@" + # Simple passthrough for executing git commands in the VNS store + git -C "$VNS_STORE" "$@" } vns () { - + # Bypass sanity check if told to initialize store - if [ "$*" != "-i" ]; then - vns_sanityCheck; - fi + if [ "$*" != "-I" ]; then + vns_sanityCheck; + 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"]="vns_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 + case "$1" in + "-c") + shift + vns_create "$@" + ;; + "-d") + shift + vns_rm "$@" + ;; + "-e") + shift + vns_edit "$@" + ;; + "-l") + shift + vns_list "$@" + ;; + "-h") + shift + vns_printHelp "$@" + ;; + "-I") + shift + vns_init "$@" + ;; + "-i") + shift + vns_import "$@" + ;; + "-m") + shift + vns_merge "$@" + ;; + "-p") + shift + vns_print "$@" + ;; + "-r") + shift + vns_mv "$@" + ;; + "-u") + shift + vns_duplicate "$@" + ;; + "git") + shift + vns_git "$@" + ;; + *) + vns_list "$@" + esac - "${ACTIONS["$1"]}" "${@:2}" - fi - exit 0; }