#! /bin/bash # # te # A template-creation script with easy locality and extensibility # Copyright (C) 2020 Vintage Salt # # 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 "$@"