269 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			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 "$@"
 | 
						|
 |