#!/usr/bin/env bash # "Import" our needed functions from our library LIBRARY_PATH="/usr/local/lib/Custom-Scripts" # shellcheck source=/usr/local/lib/ if ! source "${LIBRARY_PATH}/Logging-RGB.bash"; then echo "Unable to source Logging-RGB.bash at ${LIBRARY_PATH}" exit 1 fi # shellcheck source=/usr/local/lib/ if ! source "${LIBRARY_PATH}/Trim.bash"; then log "error" "Unable to source Trim.bash at ${LIBRARY_PATH}" exit 1 fi # Tmp file used because most distros have this dir exist in RAM, use PID of our process to write to TEMP_FILE="/tmp/$$" # Set the Contents directory to the env variable, script set, or to their .contents file in the user home CONTENTS_DIRECTORY="${CONTENTS_DIRECTORY:=\ $(mkdir -p "${HOME}/.contents" && echo "${HOME}/.contents")}" EXIT_ON_ERROR=0 REPEAT=1 WRITE_TEMPLATE_MANUALLY_SET=1 WRITE_TEMPLATE_TO="" declare -A template_handlers template_handlers=( ["FILE"]="file_handler" ["WRITE-TO"]="write_to_handler" ["TEMPLATE"]="template_handler" ) write_content() { 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}" } _write_to_buffer() { log "debug" "Received content to write to buffer: ${1}" printf "%s\n" "${1}" >> "${TEMP_FILE}" log "debug" "Finished writing content to buffer" } template_handler() { 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 } file_handler() { local file file="${1}" local leading_spaces leading_spaces="${2}" # Check that the given file is not an absolute path if [[ ! "${file:0:1}" == "/" ]] && [[ ! -f "${file}" ]]; then file="${CONTENTS_DIRECTORY}/${file}" fi if [[ ! -f "${file}" ]]; then log "error" "The given file \"${file}\" does not exist" return 1 fi while IFS="\n" read -r line; do write_content "${line}" "${leading_spaces}" done < "${file}" } write_to_handler() { 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 } parse_template_extracted() { 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 var_arg="$(trim "$(echo "${tmpl_extracted}" | cut -d "=" -f2)")" 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 } output_finished_template() { 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 } parse_template_line() { 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 } read_template() { 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}" } templater() { 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 } arg_required() { echo_rgb "${1}" 255 183 0 } arg_optional(){ echo_rgb "${1}" 117 255 255 } arg_description(){ echo_rgb "${@}" 220 190 255 } usage() { # 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") $(arg_required "--template-file") | $(arg_required "-t") $(arg_description "A template file to parse and apply rules for Example: --template-file example.tmpl") $(arg_optional "--contents-dir") | $(arg_optional "-c") $(arg_description "The files that contain contents read by the templates. 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\` Example: --contents-dir ~/Desktop/contents") $(arg_optional "--output-to") | $(arg_optional "-o") $(arg_description "The file to output the applied template to, this can be set within a template, but can be overriden with this option Example: --output-to myfile.out") $(arg_optional "--no-exit") | $(arg_optional "-n") $(arg_description "Do not exit on any errors, continue executing Example: --no-exit")" } main() { # 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}" } main "$@"