bin/te

269 lines
6.1 KiB
Bash
Executable File

#! /bin/bash
#
# te
# A template-creation script with easy locality and extensibility
# Copyright (C) 2020 Vintage Salt <rehashedsalt@cock.li>
#
# Distributed under terms of the MIT license.
#
set -e
# Read-only set-once variables
declare -r _name="$(basename -- "$0")"
# Options
declare -i _optedit
declare -i _optforce
declare -i _opthelp
declare -i _optverbose
# Working variables
declare -a _args
declare _return
# Helper functions
log() {
# Print a line to the terminal if _optverbose is greater than $2
# $2 defaults to 0
# loglevel 0: Daily-use messages
# loglevel 1: Detailed but not quite debugging
# loglevel 2: Definitely debugging
[ -z "$1" ] && return 1
if (( _optverbose >= ${2:-0} )); then
printf "%s\\n" "$1"
fi
}
warn() {
# Print a yellow line to the terminal, respecting _optverbose
[ -z "$1" ] && return 1
if (( _optverbose >= ${2:-0} )); then
if [ -t 1 ]; then
printf "\\e[33m%s\\e[0m\\n" "$1"
else
printf "WARN: %s\\n" "$1"
fi
fi
}
error() {
# Print a red line to the terminal, exit if $2 is specified
[ -z "$1" ] && return 1
if [ -t 2 ]; then
printf "\\e[31m%s\\e[0m\\n" "$1" 1>&2
else
printf "ERROR: %s\\n" "$1" 1>&2
fi
[ -z "$2" ] && return
exit "${2:-1}"
}
has() {
# Parse out all arguments and try to find them in path
# If an argument cannot be found, set _return and fail
for prog in "$@"; do
if ! command -v "$prog" > /dev/null 2>&1; then
_return="$prog"
return 1
fi
done
return 0
}
# Core program functions
findtemplate() {
# Find a template given a filename
[ -z "$1" ] && return 1
_return=""
local -a localdirs=(
"templates"
".templates"
)
local -a globaldirs=(
"$HOME/Templates"
)
# Figure out what sort of file we're looking for
# This removes the first period as well, which makes this function more recursion-safe
local ext="$(basename -- "$1")"
local ext="${ext#*.}"
# If we were supplied no extension, fail now
if [ "$ext" == "$1" ]; then
_return="invalid"
return 1
fi
log "Looking for extension: $ext" 2
for dir in ${localdirs[@]}; do
# Recurse through parent directories
local path="$PWD"
while ! [ "$path" == "/" ]; do
local targetdir="$path/$dir"
# If the directory exists, look into it
if [ -d "$targetdir" ]; then
log "Looking in $targetdir" 2
# Parse out any matching files
while IFS= read -r -d '' line; do
log "Looking at $line" 2
bline="$(basename -- "$line")"
if [ "${bline#*.}" == "$ext" ]; then
log "Found match: $line" 2
_return="$line"
return 0
fi
done < <(find "$targetdir" -iname "*.$ext" 2>/dev/null -print0)
fi
# If we didn't find a match, look at the parent and try again
path="$(readlink -f "$path"/..)"
done
done
for dir in ${globaldirs[@]}; do
if [ -d "$dir" ]; then
log "Looking in $dir" 2
# Parse out any matching files
while IFS= read -r -d '' line; do
log "Looking at $line" 2
bline="$(basename -- "$line")"
if [ "${bline#*.}" == "$ext" ]; then
log "Found match: $line" 2
_return="$line"
return 0
fi
done < <(find "$dir" -iname "*.$ext" 2>/dev/null -print0)
fi
done
# If we have a match, we're done
[ -n "$_return" ] && return 0
# Recurse if we have to
if ! [ "$ext" == "${ext#*.}" ]; then
log "Recursing..." 2
findtemplate "${ext#*.}" && return 0
else
_return="fail"
return 2
fi
}
printhelp() {
cat << EOF
Usage: $_name [OPTION]... [FILENAME]...
Template out a file based on its extension
-e Open \$EDITOR on file after creation. Only works with
exactly one argument.
-f Use force, clobber when necessary
-h Print this help text
-v Print more status messages. Stacks
Configuration:
This utility looks at the file extension of the file you're attempting to
create and looks at the following directories for template files, in this
order:
./templates
../templates
../../templates
(etc)
./.templates
../.templates
../../.templates
(etc)
~/Templates
The first file with a matching extension will be the one templated. Permissions
will be copied over from the template file as well.
The utility will respect compound extensions (ex. .tar.gz) and prioritize them
above more general ones (ex. .gz).
Copyright (c) 2020 rehashedsalt@cock.li
Licensed under the MIT license
EOF
}
createtemplates() {
# Create template files based on contents of _args
[ -z "${_args[*]}" ] && return 1
for arg in ${_args[@]}; do
# Don't clobber if we don't have to
if [ -f "$arg" ] && ! (( _optforce > 0 )); then
error "File already exists: $arg" 2
fi
# Here we go
if findtemplate "$arg"; then
log "Found file: $_return" 2
# Copy our file
cp -f "$_return" "$arg"
# Copy perms
local perms="$(stat -c '%a' $_return)"
log "Applying perms: $perms" 2
chmod "$perms" "$arg"
# If we're supposed to open the editor, do it now
if (( _optedit > 0 )); then
if ! has "$EDITOR"; then
error "Could not find editor: $EDITOR" 3
fi
exec "$EDITOR" "$arg"
fi
else
case $_return in
fail)
warn "Could not find template for file: $arg"
;;
invalid)
warn "File has no extension: $arg"
;;
esac
fi
done
}
# Main
main() {
# Parse out arguments
while [ -n "$1" ]; do
# Parse out flags
while getopts ":efhv" opt; do
case $opt in
e)
_optedit=1
;;
f)
_optforce+=1
;;
h)
_opthelp=1
;;
v)
_optverbose+=1
;;
:)
error "Option requires argument: -$OPTARG" 2
;;
*)
error "Invalid option: -$OPTARG" 2
;;
esac
done
# Store arguments
shift $((OPTIND - 1))
if [ -n "$1" ]; then
_args+=("$1")
shift
fi
unset OPTIND
done
# Early hook for help
[ -n "$_opthelp" ] && printhelp && exit 0
# Validate critical options
if (( _optedit > 0 )) && (( ${#_args[@]} > 1 )); then
error "Option -e only works with one argument" 1
fi
if [ "${#_args[@]}" == "0" ]; then
error "Missing required operand, see \"$_name -h\"" 1
fi
# Validate core program dependencies
log "Validating dependencies" 2
if ! has basename chmod cp find readlink; then
error "Failed to find program: $_return" 2
fi
# Do the do
createtemplates
exit 0
}
main "$@"