#! /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 "$@"