431 lines
13 KiB
Bash
Executable File
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[@]}"
|