n/nexclamation

431 lines
13 KiB
Bash
Executable File

#!/usr/bin/env bash
set -o errexit
#
# n! (nexclamation or nfactorial) -- command line note taking.
# Homepage: <http://nixhacks.net/nexclamation>
#
# COPYING
#
# MIT License
#
# Copyright (c) 2021-2022 ge <http://nixhacks.net/nexclamation>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
n_version=0.0.11 # Current n! version.
# Defaults.
# All of them can be overrided in $n_config.
# Main config.
n_last_opened=$HOME/.local/share/nexclamation/last_opened
# Path ot save notes.
NPATH="${NPATH:-$HOME/.local/share/nexclamation/notes}"
# Editor.
# User's default editor. Set this value for override.
NEDITOR="${NEDITOR:-$EDITOR}"
# Quick note filename.
NQNOTENAME="${NQNOTENAME:-NOTE}"
# Color scheme.
R="\e[31m" # red
B="\e[34m" # blue
N="\e[0m" # no color
b="\e[1m" # bold font
n_version() {
cat << EOF
n! (nexclamation or 'n factorial') $n_version
Copyright (C) 2021-2022 ge <http://nixhacks.net/nexclamation>
License MIT: <https://opensource.org/licenses/MIT>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
EOF
exit 0
}
n_help() {
cat << EOF
Command line note taking.
Usage: n! [-v|--version] [-h|--help] [<command>] [<args>...]
Commands and options:
q, quick [<name>] take a quick note in current directory.
s, search <query> search in notes via grep.
l, last open last opened file in editor.
mkdir <dir> add new directory. Creates new dir in NPATH.
ls [<dir>] list all notes, or notes from <dir> in NPATH.
lsd list dirs in NPATH (empty dirs too).
rm [-f|--force] <file> remove notes or directories.
i, info [<file>] print info about notes and configuration.
-h, --help print this help message and exit.
-v, --version print version and exit.
Examples:
Take a new note or open existing note:
n! [<note>]
Take new note in 'work' directory:
n! work/my_note
Environment:
n! uses user's default editor. You can specify editor in EDITOR environment
variable in your .bashrc or .bash_profile, or select default editor by 'select-editor'
command.
Also you can set specific editor for nexclamation in ~/.nexclamation file. For example:
NEDITOR=/usr/bin/vim.tiny
Other configuration options:
NPATH path to save notes. Default: $HOME/.local/share/nexclamation/notes
NQNOTENAME default file name for quick notes. Default: NOTE
EOF
exit 0
}
n_err() {
echo -e "$1" >&2
echo -e "See 'n! --help' or 'nexclamation --help' for info." >&2
exit 1
}
# ------------------------------------------------------------------------- #
# App init #
# ------------------------------------------------------------------------- #
n_initialise() {
# Make $NPATH if not exists.
if [ ! -d "$NPATH" ]; then
mkdir -p "$NPATH" 2>/dev/null
fi
# Set editor.
n_set_editor
}
n_set_editor() {
get_selected_editor() {
# shellcheck source=/dev/null
source "$HOME/.selected_editor"
echo "$SELECTED_EDITOR"
}
# Detect default editor.
# Do nothing if editor is set in $n_config
if [ -z "$EDITOR" ]; then
NEDITOR="$(get_selected_editor)"
elif [ -f /usr/bin/select-editor ]; then
select-editor
NEDITOR="$(get_selected_editor)"
else
NEDITOR=/usr/bin/vi
fi
}
# ------------------------------------------------------------------------- #
# Functions #
# ------------------------------------------------------------------------- #
n_take_note() {
# Take a note.
#
# Arguments:
# $@ -- file names
# Examples of $1:
# my_note.txt
# some_dir/my_note.txt
if [[ -n "$*" ]]; then
for file in "$@"; do
if [[ "$file" =~ .+/.+ ]]; then
# if filename contains path
if [ -d "$NPATH/${file%/*}" ]; then
files+=("$NPATH/$file")
else
echo -n "${0##*/}: $NPATH/${file%/*}" >&2
echo ": destination does not exist" >&2
echo "Run 'n! mkdir <dir>' to create new directory." >&2
exit 1
fi
else
files+=("$NPATH/$file")
fi
done
"$NEDITOR" "${files[@]}"
else
cd "$NPATH" || { echo "${0##*/}: Cannot cd into $NPATH"; exit 1; }
"$NEDITOR"
cd - >/dev/null || { echo "${0##*/}: Cannot cd into $OLDPWD"; exit 1; }
fi
}
n_quick_note() {
# Take note in current working directory.
#
# Default $NQNOTENAME can be set in ~/.nexclamation file.
if [[ -n "$*" ]]; then
local temp="$*"
NQNOTENAME="${temp%% *}"
fi
echo "${PWD}/${NQNOTENAME}" > "$n_last_opened"
"$NEDITOR" "$NQNOTENAME"
exit 0 # Prevent new note taking
}
n_last() {
# Open last opened file.
#
# Filename is saved in service file $n_last_opened
if [ -f "$n_last_opened" ]; then
local file
file="$(cat "$n_last_opened")"
else
echo "${0##*/}: No opened files yet" >&2; exit 1
fi
if [ -f "$file" ]; then
"$NEDITOR" "$file"; exit 0
else
echo "${0##*/}: $file: No such file" >&2; exit 1
fi
}
n_search() {
# Search in notes.
#
# $1 is search query.
while (( "$#" )); do
case "$1" in
-h|--help) n_help;;
-v|--version) n_version;;
-*) n_err "${0##*/}: $1: Unknown option";;
*) local q="$1"; shift;;
esac
done
cd "$NPATH" || { echo "${0##*/}: Cannot cd into $NPATH"; exit 1; }
grep --recursive --ignore-case --line-number --color=auto "$q"
cd - >/dev/null || { echo "${0##*/}: Cannot cd into $OLDPWD"; exit 1; }
}
n_mkdir() {
# Create new directory in $NPATH
#
# $1 -- dirname
while (( "$#" )); do
case "$1" in
-h|--help) n_help;;
-v|--version) n_version;;
-*) n_err "${0##*/}: $1: Unknown option";;
*) local dir="$1"; shift;;
esac
done
mkdir -p "${NPATH}/${dir}" 2>/dev/null
echo -e "${b}Created:${N} ${NPATH}/${B}${b}${dir}/${N}"
exit 0
}
n_list() {
# List files from dir or dirs.
while (( "$#" )); do
case "$1" in
-d|--dirs) list_dirs=1; shift;;
-h|--help) n_help;;
-v|--version) n_version;;
-*) n_err "${0##*/}: $1: Unknown option";;
*) local dir="$1"; shift;;
esac
done
if [ ${#dir[@]} -gt 1 ]; then
echo -e "${0##*/}: too many arguments" >&2; exit 1
fi
if [ ! -d "${NPATH}/${dir}" ]; then
echo -e \
"${0##*/}: ${NPATH}/${dir}: No such directory" >&2
exit 1
fi
if [ -n "$list_dirs" ]; then # list only dirs (append / for coloring)
list="$(find "$NPATH" -type d -exec echo {}/ \;)"
elif [ "$dir" ]; then # list files from specific directory
list="$(find "${NPATH}/${dir}" -type f)"
else # list all of files and dirs
list="$(find "$NPATH" -type f)"
fi
# Print output.
for path in $(echo "$list" | sed -E "s%${NPATH}/?%%g;/^$/d"); do
# Add color for dirs (blue) with brainfucking parameter expansion
local temp
temp="${path//\//\\/}" # Escape slashes
# shellcheck disable=SC2059
echo "${path}" | sed "/${temp%\\*}\//s//$(printf "${B}${b}${temp%/*}\/${N}")/"
done | sort
exit 0
}
n_remove() {
# Remove files or directories.
while (( "$#" )); do
case "$1" in
-f|--force) forced=1; shift;;
-h|--help) n_help;;
-v|--version) n_version;;
-*) n_err "${0##*/}: $1: Unknown option";;
*) local files+=("$1"); shift;;
esac
done
if [ ${#files[@]} -eq 0 ]; then
echo -e "${0##*/}: Nothing to remove" >&2; exit 1
fi
if [ ! "$forced" ]; then
echo -e "These files will be removed: ${R}${files[*]}${N}" | fmt -t
while [ -z "$answer" ]; do
echo -en "Remove files permanently? (y/n) "
read -r reply
case "${reply,,}" in
y|yes) answer=1;;
n|no) echo "Abort"; exit 1;;
*) echo 'Please, answer y or n';;
esac
done
fi
for file in "${files[@]}"; do
file="${NPATH}/${file}"
if [ -d "$file" ]; then
rm -rf "$file"
echo -e "${b}Removed:${N} $file"
elif [ -f "$file" ]; then
rm -f "$file"
echo -e "${b}Removed:${N} $file"
else
echo "${0##*/}: $file: No such file or directory" >&2; exit 1
fi
done
exit 0
}
n_info() {
# Show information about notes and configuration.
while (( "$#" )); do
case "$1" in
-h|--help) n_help;;
-v|--version) n_version;;
-*) n_err "${0##*/}: $1: Unknown option";;
*) local file="$1"; shift;;
esac
done
if [ -n "$file" ]; then
file="$NPATH/$file"
if [ ! -f "$file" ]; then
echo "${0##*/}: $file: No such file" >&2; exit 1
fi
{
echo -e "${b}Lines:${N}|$(wc -l "$file" |
tail -n 1 | awk '{print $1}')"
echo -e "${b}Words:${N}|$(wc -w "$file" |
tail -n 1 | awk '{print $1}')"
echo -e "${b}Size:${N}|$(du -hs "$file" | cut -f 1)"
} | column -t -s '|'
exit 0
fi
local all_files
local all_dirs
all_files="$(find "$NPATH" -type f)"
all_dirs="$(find "$NPATH" -type d)"
{
echo -e "${b}Editor:${N}|${NEDITOR}"
echo -e "${b}Quick notes:${N}|${NQNOTENAME}"
echo -e "${b}Notes save path:${N}|${NPATH}"
echo -e "${b}Dirs:${N}|$(<<< "$all_dirs" wc -l)"
echo -e "${b}Files:${N}|$(<<< "$all_files" wc -l)"
# shellcheck disable=SC2001
echo -e "${b}Total lines:${N}|$(<<< "$all_files" sed 's/.*/"&"/' |
xargs wc -l | tail -n 1 | awk '{print $1}')"
# shellcheck disable=SC2001
echo -e "${b}Total words:${N}|$(<<< "$all_files" sed 's/.*/"&"/' |
xargs wc -w | tail -n 1 | awk '{print $1}')"
echo -e "${b}Total size:${N}|$(du -hs "$NPATH" | cut -f 1)"
echo -e "${b}Last opened:${N}|$([ -f "$n_last_opened" ] && \
cat "$n_last_opened" || echo 'no opened files yet')"
} | column -t -s '|'
exit 0
}
# ------------------------------------------------------------------------- #
# n! #
# ------------------------------------------------------------------------- #
n_checkopt() {
if [ "$2" ]; then
return 0
else
n_err "${0##*/}: Missing argument for $1"
fi
}
n_initialise
while (( "$#" )); do
case "$1" in
q|quick) shift; n_quick_note "$@"; shift "$#";;
l|last) n_last;;
s|search) n_checkopt "$1" "$2"; n_search "$2"; exit 0;;
mkdir) n_checkopt "$1" "$2"; n_mkdir "$2"; exit 0;;
ls) shift; n_list "$@"; shift "$#";;
lsd) shift; n_list -d; shift "$#";;
rm) shift; n_remove "$@"; shift "$#";;
i|info) shift; n_info "$@"; shift "$#";;
-v|--version) n_version;;
-h|--help) n_help;;
-*) n_err "${0##*/}: $1: Unknown option";;
*) pos_args+=("$1"); shift;;
esac
done
if [ "${#pos_args[@]}" -gt 0 ]; then
echo "${NPATH}/${pos_args[-1]}" > "$n_last_opened"
fi
n_take_note "${pos_args[@]}"