2021-11-13 20:56:19 -06:00
#!/usr/bin/env bash
2023-09-15 17:01:12 -05:00
echo_rgb( ) {
# Echo a colored string to the terminal based on rgb values
#
# Positional Arguments:
#
# message <type: string> <position: 1> <required: true>
# - The message to be printed to stdout
# red <type: int> <position: 2> <required: true>
# - The red value from 0 to 255
# green <type: int> <position: 3> <required: true>
# - The green value from 0 to 255
# blue <type: int> <position: 4> <required: true>
# - The blue value from 0 to 255
#
# Usage:
# echo_rgb "Yep" 10 8 30
#
# POSIX Compliant:
# N/A
#
local red
local green
local blue
local input
input = " ${ 1 } "
red = " ${ 2 } "
green = " ${ 3 } "
blue = " ${ 4 } "
2023-09-15 17:09:57 -05:00
printf "\e[0;38;2;%s;%s;%sm%s\e[m\n" " ${ red } " " ${ green } " " ${ blue } " " ${ input } "
2023-09-15 17:01:12 -05:00
}
log( ) {
# Print a message and send it to stdout or stderr depending upon log level, also configurable with debug etc.
#
# Arguments:
# level <type: string> <position: 1> <required: true>
# - The log level, defined within a case check in this function
# message <type: string> <position: 2> <required: true>
# - The info message
# line_number <type: int> <position: 3> <required: false>
# - The line number of the calling function (${LINNO})
#
# Usage:
# log "info" "Could not find that directory"
#
# POSIX Compliant:
# Yes
#
# Set debug status depending if a global debug variable has been set to either 1 or 0
local debug
if [ ${ DEBUG } ] ; then
debug = ${ DEBUG }
else
debug = 0
fi
local FORMAT
FORMAT = " [ $( echo_rgb " $( date +%Y-%m-%dT%H:%M:%S) " 180 140 255) ] "
# Convert the level to uppercase
local level
level = $( echo " ${ 1 } " | tr '[:lower:]' '[:upper:]' )
local message
message = " ${ 2 } "
case " ${ level } " in
INFO)
# Output all info log levels to stdout
printf " ${ FORMAT } [ $( echo_rgb "INFO" 0 140 255) ] %s\n " " ${ message } " >& 1
return 0
; ;
WARN | WARNING)
# Output all warning log levels to stdout
printf " ${ FORMAT } [ $( echo_rgb "WARNING" 255 255 0) ] %s\n " " ${ message } " >& 1
return 0
; ;
DEBUG)
# Output all debug log levels to stdout
if [ " ${ DEBUG } " ] ; then
printf " ${ FORMAT } [ $( echo_rgb "DEBUG" 0 160 110) ] %s\n " " ${ message } " >& 1
fi
return 0
; ;
ERROR)
# Output all error log levels to stderr
printf " ${ FORMAT } [ $( echo_rgb "ERROR" 255 0 0) ] %s\n " " ${ message } " >& 2
return 0
; ;
# Further log levels can be added by extending this switch statement with more comparisons
*) # Default case, no matches
# Returns non-zero code as an improper log option was passed, this helps with using `set -e`
printf " ${ FORMAT } [ERROR] %s\n " " Invalid log level passed, received level \" ${ level } \" with message \" ${ message } \" " >& 2
return 1
; ;
esac
}
trim( ) {
local input = " ${ 1 } "
while [ [ " ${ input } " = = " " * ] ] ; do
input = " ${ input ## } "
done
while [ [ " ${ input } " = = *" " ] ] ; do
input = " ${ input %% } "
done
echo " ${ input } "
}
# Get a temp working file so we don't pollute directories
TEMP_FILE = " $( mktemp) "
2021-11-13 20:56:19 -06:00
# Set the Contents directory to the env variable, script set, or to their .contents file in the user home
CONTENTS_DIRECTORY = " ${ CONTENTS_DIRECTORY : = \
2023-09-15 17:01:12 -05:00
$( mkdir -p " ${ HOME } /.contents " && echo " ${ HOME } /.contents " ) } "
2021-11-13 20:56:19 -06:00
EXIT_ON_ERROR = 0
REPEAT = 1
WRITE_TEMPLATE_MANUALLY_SET = 1
WRITE_TEMPLATE_TO = ""
declare -A template_handlers
template_handlers = (
2023-09-15 17:01:12 -05:00
[ "FILE" ] = "file_handler"
[ "WRITE-TO" ] = "write_to_handler"
[ "TEMPLATE" ] = "template_handler"
[ "VAR" ] = "var_handler"
[ "EVAL" ] = "eval_handler"
2021-11-13 20:56:19 -06:00
)
write_content( ) {
2023-09-15 17:01:12 -05:00
local content
local leading_spaces
content = " ${ 1 } "
leading_spaces = " ${ 2 } "
local string_builder
string_builder = ""
for ( ( i = 1; i <= leading_spaces; i++) ) ; do
string_builder = " ${ string_builder } "
done
string_builder = " ${ string_builder } ${ content } "
_write_to_buffer " ${ string_builder } "
2021-11-13 20:56:19 -06:00
}
_write_to_buffer( ) {
2023-09-15 17:01:12 -05:00
log "debug" " Received content to write to buffer:
${ 1 } "
printf "%s\n" " ${ 1 } " >>" ${ TEMP_FILE } "
log "debug" "Finished writing content to buffer"
}
var_handler( ) {
local variable = " ${ 1 } "
local leading_spaces = " ${ 2 } "
while IFS = "\n" read -r line; do
write_content " ${ line } " " ${ leading_spaces } "
done <<< " ${ variable } "
}
eval_handler( ) {
local eval_statement = " ${ 1 } "
local leading_spaces = " ${ 2 } "
while IFS = "\n" read -r line; do
write_content " ${ line } " " ${ leading_spaces } "
done <<< " $( eval " ${ eval_statement } " ) "
2021-11-13 20:56:19 -06:00
}
template_handler( ) {
2023-09-15 17:01:12 -05:00
local template_file
template_file = " ${ 1 } "
local leading_spaces
leading_spaces = " ${ 2 } "
# Check that the given file is not an absolute path
if [ [ ! " ${ template_file : 0 : 1 } " = = "/" ] ] ; then
template_file = " ${ CONTENTS_DIRECTORY } / ${ template_file } "
fi
if [ [ ! -f " ${ template_file } " ] ] ; then
log "error" " The given template file \" ${ template_file } \" does not exist "
return 1
fi
file_handler " ${ template_file } " " ${ leading_spaces } "
REPEAT = 0
2021-11-13 20:56:19 -06:00
}
file_handler( ) {
2023-09-15 17:01:12 -05:00
local file
file = " ${ 1 } "
2021-11-13 20:56:19 -06:00
2023-09-15 17:01:12 -05:00
local leading_spaces
leading_spaces = " ${ 2 } "
2021-11-13 20:56:19 -06:00
2023-09-15 17:01:12 -05:00
# Check that the given file is not an absolute path
if [ [ ! " ${ file : 0 : 1 } " = = "/" ] ] && [ [ ! -f " ${ file } " ] ] ; then
file = " ${ CONTENTS_DIRECTORY } / ${ file } "
fi
2021-11-13 20:56:19 -06:00
2023-09-15 17:01:12 -05:00
if [ [ ! -f " ${ file } " ] ] ; then
log "error" " The given file \" ${ file } \" does not exist "
return 1
fi
2021-11-13 20:56:19 -06:00
2023-09-15 17:01:12 -05:00
while IFS = "\n" read -r line; do
write_content " ${ line } " " ${ leading_spaces } "
done <" ${ file } "
2021-11-13 20:56:19 -06:00
}
write_to_handler( ) {
2023-09-15 17:01:12 -05:00
local write_path
write_path = ${ 1 }
log "debug" " Received write path \" ${ write_path } \" "
if [ [ ! " ${ write_path : 0 : 1 } " = = "/" ] ] ; then
log "error" " Given write path was invalid, received \" ${ write_path } \", the path must be an absolute path "
return 1
fi
if [ [ -d " ${ write_path } " ] ] ; then
log "error" " Given write path was invalid, received \" ${ write_path } \", the path must be a path to an output file (doesn't have to exist), not a directory "
return 1
fi
# This means that it was NOT manually set, 0 is true in shell
if ( ( " ${ WRITE_TEMPLATE_MANUALLY_SET } " = = 1) ) ; then
WRITE_TEMPLATE_TO = ${ write_path }
fi
2021-11-13 20:56:19 -06:00
}
parse_template_extracted( ) {
2023-09-15 17:01:12 -05:00
local tmpl_extracted
tmpl_extracted = " ${ 1 } "
local full_line
full_line = " ${ 2 } "
local num_leading_spaces
num_leading_spaces = $( awk -F"[ ]" '{for(i=1;i<=NF && ($i=="");i++);print ""i-1""}' <<< " ${ full_line } " )
# Basic checks to ensure template is correct, first not empty, second that an '=' was passed to give type specifier
# third is to ensure only one equals sign is passed
if [ [ -z " ${ tmpl_extracted } " ] ] ; then
log "error" "Template passed was empty... ignoring"
return 1
elif [ [ ! " ${ tmpl_extracted } " = *"=" * ] ] ; then
log "error" "Template line passed was of an invalid format, missing = specifier, assumed type is missing as well"
return 1
elif [ " $( echo " ${ tmpl_extracted } " | grep -o "=" | wc -l) " -gt 1 ] ; then
log "error" "Template line passed had too many \"=\", there should only be 1"
return 1
fi
local var_type
var_type = " $( trim " $( echo " ${ tmpl_extracted } " | cut -d "=" -f1) " ) "
# Shellcheck incorrectly reads the below line
# shellcheck disable=SC2116
var_type = " $( echo " ${ var_type ^^ } " ) "
local var_arg
2023-09-15 17:07:53 -05:00
var_arg = " $( trim " $( echo " ${ tmpl_extracted } " | cut -d "=" -f 2-) " ) "
2023-09-15 17:01:12 -05:00
local handler_to_use
handler_to_use = " ${ template_handlers [ ${ var_type } ] } "
if [ [ -z " ${ handler_to_use } " ] ] ; then
log "error" " Invalid handler found, no such handler \" ${ var_type } \" exists as a handler "
return 1
else
log "debug" " Using handler \" ${ handler_to_use } \" "
if ! eval " ${ handler_to_use } " " ${ var_arg } " " ${ num_leading_spaces } " ; then
log "error" " Arguments passed for \" ${ var_type } \" were invalid, check the argument "
return 1
fi
log "debug" " Finished using handler \" ${ handler_to_use } \" "
fi
2021-11-13 20:56:19 -06:00
}
output_finished_template( ) {
2023-09-15 17:01:12 -05:00
log "debug" "Outputting finished template"
mkdir -p " $( dirname " ${ WRITE_TEMPLATE_TO } " ) "
# We want globbing here
# shellcheck disable=SC2086
if [ [ -z " ${ WRITE_TEMPLATE_TO } " ] ] ; then
log "error" "Was never given a path to write the template to, check your template file or explicitly specify an output location"
exit 1
else
mv " ${ TEMP_FILE } " ${ WRITE_TEMPLATE_TO }
fi
2021-11-13 20:56:19 -06:00
}
parse_template_line( ) {
2023-09-15 17:01:12 -05:00
local line
line = " ${ 1 } "
local tmpl_extracted
if [ [ " ${ line } " = *\{ #%*%#\}* ]]; then
tmpl_extracted = " ${ line #* \{ #% } "
tmpl_extracted = " ${ tmpl_extracted % \% # \} * } "
tmpl_extracted = " $( trim " ${ tmpl_extracted } " ) "
log "debug" " Extracted a template: ${ tmpl_extracted } "
# Check for non zero status codes and parse the extracted template
if ! parse_template_extracted " ${ tmpl_extracted } " " ${ line } " ; then
return 1
fi
log "debug" " Finished extracting template: ${ tmpl_extracted } "
else
write_content " ${ line } " "0"
fi
2021-11-13 20:56:19 -06:00
}
read_template( ) {
2023-09-15 17:01:12 -05:00
local read_file
local line
local line_num
line_num = 1
read_file = " ${ 1 } "
log "info" " Parsing template file \" ${ read_file } \" "
while IFS = "\n" read -r line; do
# Check for non zero status code
if ! parse_template_line " ${ line } " ; then
log "error" " Invalid template passed from \" ${ read_file } \", check line ${ line_num } "
if ( ( " ${ EXIT_ON_ERROR } " = = 0) ) ; then
exit 1
fi
fi
line_num = $(( line_num + 1 ))
done <" ${ read_file } "
2021-11-13 20:56:19 -06:00
}
templater( ) {
2023-09-15 17:01:12 -05:00
local read_file
read_file = " ${ 1 } "
[ [ ! -f " ${ read_file } " ] ] &&
log "error" " $( important " ${ read_file } " ) does not exist! " &&
return 1
read_template " ${ read_file } "
if ( ( " ${ REPEAT } " = = 0) ) ; then
REPEAT = 1
log "debug" " Repeat set to 0, repeating on the finished file located at ${ TEMP_FILE } "
mv " ${ TEMP_FILE } " " ${ TEMP_FILE } -repeat "
main "-t" " ${ TEMP_FILE } -repeat "
else
log "info" " Finished parsing template file \" ${ read_file } \" "
log "info" "Writing finished template..."
output_finished_template
log "info" " Finished writing template for \" ${ read_file } \" to \" ${ WRITE_TEMPLATE_TO } \" "
fi
2021-11-13 20:56:19 -06:00
}
2021-11-13 21:16:57 -06:00
arg_required( ) {
2023-09-15 17:01:12 -05:00
echo_rgb " ${ 1 } " 255 183 0
2021-11-13 21:16:57 -06:00
}
2023-09-15 17:01:12 -05:00
arg_optional( ) {
echo_rgb " ${ 1 } " 117 255 255
2021-11-13 21:16:57 -06:00
}
2023-09-15 17:01:12 -05:00
arg_description( ) {
echo_rgb " ${ @ } " 220 190 255
2021-11-13 21:16:57 -06:00
}
2021-11-13 20:56:19 -06:00
usage( ) {
2023-09-15 17:01:12 -05:00
# Print out usage instructions for the local script
#
# Arguments:
# None
#
# Usage:
# usage
#
# POSIX Compliant:
# Yes
#
printf "Usage: %s\n" \
" $( basename ${ 0 } ) [OPTIONS]
$( arg_required "REQUIRED" )
$( arg_optional "OPTIONAL" )
2021-11-13 21:16:57 -06:00
2023-09-15 17:01:12 -05:00
$( arg_required "--template-file" ) <templateFile: string> | $( arg_required "-t" ) <templateFile: string>
$( arg_description " A template file to parse and apply rules for
2021-11-13 21:16:57 -06:00
2023-09-15 17:01:12 -05:00
Example:
--template-file example.tmpl" )
2021-11-13 21:16:57 -06:00
2023-09-15 17:01:12 -05:00
$( arg_optional "--contents-dir" ) <contentsDirectory: string> | $( arg_optional "-c" ) <contentsDirectory: string>
$( arg_description " The files that contain contents read by the templates.
2021-11-13 21:16:57 -06:00
2023-09-15 17:01:12 -05:00
By default this is set to ~/.contents/ if unset, to set via variable you can either use this argument or pass
export CONTENTS_DIRECTORY = \` your contents directory\`
2021-11-13 21:16:57 -06:00
2023-09-15 17:01:12 -05:00
Example:
--contents-dir ~/Desktop/contents" )
2021-11-13 21:16:57 -06:00
2023-09-15 17:01:12 -05:00
$( arg_optional "--output-to" ) <outputFile: string> | $( arg_optional "-o" ) <outputFile: string>
$( arg_description " The file to output the applied template to, this can be set within a template, but can be overriden with this option
2021-11-13 21:16:57 -06:00
2023-09-15 17:01:12 -05:00
Example:
--output-to myfile.out" )
2021-11-13 21:16:57 -06:00
2023-09-15 17:01:12 -05:00
$( arg_optional "--no-exit" ) | $( arg_optional "-n" )
$( arg_description " Do not exit on any errors, continue executing
2021-11-13 21:16:57 -06:00
2023-09-15 17:01:12 -05:00
Example:
--no-exit")"
2021-11-13 21:16:57 -06:00
2021-11-13 20:56:19 -06:00
}
main( ) {
2023-09-15 17:01:12 -05:00
# Parse input arguments
#
# Arguments:
# Consult the `usage` function
#
# Usage:
# parse_args "$@"
# - All arguments should be ingested by parse_args first for variable setting
#
# POSIX Compliant:
# Yes
#
local template_file
while :; do
case ${ 1 } in
-h | -\? | --help)
usage # Display a usage synopsis.
exit
; ;
--) # End of all options.
break
; ;
-t | --template-file)
shift
template_file = " ${ 1 } "
[ [ ! -f " ${ template_file } " ] ] &&
log "error" " The given template file \" ${ template_file } \" does not exist " &&
exit 1
log "info" " Set template file to \" ${ template_file } \" "
; ;
-c | --contents-dir)
shift
CONTENTS_DIRECTORY = " ${ 1 } "
[ [ ! -d " ${ CONTENTS_DIRECTORY } " ] ] &&
log "error" " \" ${ CONTENTS_DIRECTORY } \" is an invalid path, contents directory must be an absolute path " &&
exit 1
log "info" " Set contents directory to \" ${ CONTENTS_DIRECTORY } \" "
; ;
-o | --output-to)
shift
WRITE_TEMPLATE_MANUALLY_SET = 0
WRITE_TEMPLATE_TO = " ${ 1 } "
; ;
-n | --no-exit)
EXIT_ON_ERROR = 1
log "info" "No longer exiting on errors."
; ;
-?*)
printf 'Unknown option: %s\n' " $1 " >& 2
usage
exit 1
; ;
*) # Default case: No more options, so break out of the loop.
break ; ;
esac
shift
done
[ [ -z " ${ template_file } " ] ] &&
log "error" "No template file provided, exiting" &&
exit 1
templater " ${ template_file } "
2021-11-13 20:56:19 -06:00
}
main " $@ "