diff --git a/.gitignore b/.gitignore index 907d346..5068be8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ base.code-workspace /module.lib load/ucishellgiturl +btpl.run \ No newline at end of file diff --git a/modules/scripting/btpl.inst b/modules/scripting/btpl.inst deleted file mode 100755 index c529450..0000000 --- a/modules/scripting/btpl.inst +++ /dev/null @@ -1 +0,0 @@ -lastversion --assets --output /shell/base/modules/scripting/btpl.lib download bash-tpl \ No newline at end of file diff --git a/modules/scripting/btpl.lib b/modules/scripting/btpl.lib old mode 100644 new mode 100755 index 4e9bf11..dda82a1 --- a/modules/scripting/btpl.lib +++ b/modules/scripting/btpl.lib @@ -1,1298 +1,38 @@ -#!/usr/bin/env bash -####################################################################### -# SPDX-License-Identifier: MIT -# Copyright (c) 2021 TekWizely & co-authors -# -# Use of this source code is governed by the MIT license. -# See the accompanying LICENSE file, if present, or visit: -# https://opensource.org/licenses/MIT -####################################################################### -VERSION="v0.7.1" -####################################################################### -# Bash-TPL: A Smart, Lightweight shell script templating engine -# -# Lets you mark up textual files with shell commands and variable -# replacements, while minimally impacting your original file layout. -# -# Templates are compiled into shell scripts that you can invoke -# (along with variables, arguments, etc.) to generate complete and -# well-formatted output text files. -# -# Smart -# -# Encourages you to use extra indentation to write clean, well- -# formatted templates, and smartly removes the indentations from the -# generated template scripts. -# -# This results in both templates that are easily readable and -# maintainable, and generated text files that look as good as if they -# were written by hand. -# -# NOTE: Consistent Formatting -# -# The key to success with Bash-TPL indentation fix-up logic is -# Consistent Formatting; using consistent indentation throughout your -# template will yield best results. -# -# Learn More: -# https://github.com/TekWizely/bash-tpl -####################################################################### +#!/bin/bash -function usage() { - cat << USAGE -Bash-TPL is a smart, lightweight shell script templating engine +BTPLDIR=$(dirname $(module_find btpl)) +export BTPLDIR -usage: bash-tpl [flags] [--] file - bash-tpl [flags] - - cat file | bash-tpl [flags] - -options: - -h, --help - show help screen - --version - show version - -o, --output-file - write to specified file (default: stdout) - -- treat remaining options as positional arguments - - read from stdin - -customize delimiters: - --tag-delims 'xx xx' - set tag delimiters (default: '<% %>') - --tag-stmt-delim 'x' - set tag statement delimiter (default: '%') - --stmt-delim 'x+' - set statement delimiter (default: '%') - --stmt-block-delims 'x+ x+' - set statement block delimiters - defaults to statement delimiter if not explicitly set - --txt-delim - --text-delim 'x+[ ]?' (single trailing space allowed) - set text delimiter (default: '% ') - --dir-delim - --directive-delim 'x+' - set directive delimiter (default: '.') - --cmt-delim - --comment-delim 'x+' - set template comment delimiter - defaults to directive delimiter + '#' if not explicitly set - --reset-delims - reset all delimiters to defaults - delim options provided after this option are honored - -supported environment variables: - BASH_TPL_TAG_DELIMS - BASH_TPL_TAG_STMT_DELIM - BASH_TPL_STMT_DELIM - BASH_TPL_STMT_BLOCK_DELIMS - BASH_TPL_TEXT_DELIM - BASH_TPL_DIR_DELIM - BASH_TPL_CMT_DELIM - -example: - $ echo 'Hello <% \$NAME %>' > test.tpl - $ NAME="Chuck Norris" source <( bash-tpl test.tpl ) - - Hello Chuck Norris - -learn more: https://github.com/TekWizely/bash-tpl - -USAGE +btpl_fetch () { +# /opt/python/apps/bin/lastversion --assets --output /shell/base/modules/scripting/btpl.lib download bash-tpl +wget -O "$BTPLDIR"/btpl.run https://raw.githubusercontent.com/TekWizely/bash-tpl/main/bash-tpl +chmod +x "$BTPLDIR"/btpl.run +# sed -i 's/\bmain\b/btpl/g' /shell/base/modules/scripting/btpl.lib } -####################################################################### -# Delim Functions -####################################################################### - -TAG_DELIM_REGEX='^([^[:blank:]])([^[:blank:]]) ([^[:blank:]])([^[:blank:]])$' -TAG_STMT_DELIM_REGEX='^([^[:blank:]])$' -STMT_DELIM_REGEX='^([^[:blank:]]+)$' -STMT_BLOCK_DELIM_REGEX='^([^[:blank:]]+) ([^[:blank:]]+)$' -STMT_BLOCK_TEXT_REGEX='^([^[:blank:]]+\ ?)$' # Optional trailing ' ' - -## -# reset_delims -# -function reset_delims() { - TAG_START_DELIM1='<' - TAG_START_DELIM2='%' - TAG_STOP_DELIM1='%' - TAG_STOP_DELIM2='>' - - TAG_STMT_DELIM='%' - - STMT_DELIM='%' - - TEXT_DELIM_UNDEFINED=1 - TEXT_DELIM='' - - STMT_BLOCK_DELIM_UNDEFINED=1 - STMT_BLOCK_START_DELIM='' - STMT_BLOCK_STOP_DELIM='' - - DIRECTIVE_DELIM='.' - - COMMENT_DELIM_UNDEFINED=1 - COMMENT_DELIM='' +btpl () { +local dir +if [[ $1 == "-d" ]]; then shift; dir=$1; shift; pushd $dir 1>/dev/null || return; fi + [[ ! $BTPLDIR ]] && echo unable to establish Bash Template directory && return 2 + [[ ! -f $BTPLDIR/btpl.run ]] && btpl_fetch + for btpl in *.btpl; do + [[ -f "$btpl" ]] || break + echo processing $btpl ... + # echo sudo $BTPLDIR/btpl.run --output-file $(basename $btpl .btpl)$([[ $1 ]] && echo .$1) $btpl + $BTPLDIR/btpl.run $btpl + source <($BTPLDIR/btpl.run $btpl) | sudo tee $(basename $btpl .btpl)$([[ $1 ]] && echo .$1) + done +if [[ $dir ]]; then popd 1>/dev/null || return; fi } -## -# parse_tag_delims -# $1 = delims -# $2 = src (for error msg) -# -function parse_tag_delims() { - if [[ "${1}" =~ $TAG_DELIM_REGEX ]]; then - TAG_START_DELIM1="${BASH_REMATCH[1]}" - TAG_START_DELIM2="${BASH_REMATCH[2]}" - TAG_STOP_DELIM1="${BASH_REMATCH[3]}" - TAG_STOP_DELIM2="${BASH_REMATCH[4]}" - else - echo "Error: Invalid or missing tag delimiter values for ${2-tag delims}: '${1}'" >&2 - exit 1 - fi -} -## -# parse_tag_stmt_delims -# $1 = delim -# $2 = src (for error msg) -# -function parse_tag_stmt_delim() { - if [[ "${1}" =~ $TAG_STMT_DELIM_REGEX ]]; then - TAG_STMT_DELIM="${BASH_REMATCH[1]}" - else - echo "Error: Invalid or missing tag stmt delimiter value for ${2-tag stmt delim}: '${1}'" >&2 - exit 1 - fi -} - -## -# parse_stmt_delim -# $1 = delim -# $2 = src (for error msg) -# -function parse_stmt_delim() { - if [[ "${1}" =~ $STMT_DELIM_REGEX ]]; then - STMT_DELIM="${BASH_REMATCH[1]}" - else - echo "Error: Invalid or missing stmt delimiter value for ${2:-stmt delim}: '${1}'" >&2 - exit 1 - fi -} - -## -# parse_stmt_block_delims -# $1 = delims -# $2 = src (for error msg) -# -function parse_stmt_block_delims() { - if [[ "${1}" =~ $STMT_BLOCK_DELIM_REGEX ]]; then - STMT_BLOCK_START_DELIM="${BASH_REMATCH[1]}" - STMT_BLOCK_STOP_DELIM="${BASH_REMATCH[2]}" - STMT_BLOCK_DELIM_UNDEFINED='' - else - echo "Error: Invalid or missing stmt-block delimiter values for ${2:-stmt-block delims}: '${1}'" >&2 - exit 1 - fi -} - -## -# parse_text_delim - Uses STMT delim regex -# $1 = delim -# $2 = src (for error msg) -# -function parse_text_delim() { - if [[ "${1}" =~ $STMT_BLOCK_TEXT_REGEX ]]; then - TEXT_DELIM="${1}" - TEXT_DELIM_UNDEFINED='' - else - echo "Error: Invalid or missing text delimiter value for ${2:-txt delim}: '${1}'" >&2 - exit 1 - fi -} - -## -# parse_directive_delim - Uses STMT delim regex -# $1 = delim -# $2 = src (for error msg) -# -function parse_directive_delim() { - if [[ "${1}" =~ $STMT_DELIM_REGEX ]]; then - DIRECTIVE_DELIM="${1}" - else - echo "Error: Invalid or missing directive delimiter value for ${2:-dir delim}: '${1}'" >&2 - exit 1 - fi -} - -## -# parse_comment_delim - Uses STMT delim regex -# $1 = delim -# $2 = src (for error msg) -# -function parse_comment_delim() { - if [[ "${1}" =~ $STMT_DELIM_REGEX ]]; then - COMMENT_DELIM="${1}" - COMMENT_DELIM_UNDEFINED='' - else - echo "Error: Invalid or missing comment delimiter value for ${2:-cmt delim}: '${1}'" >&2 - exit 1 - fi -} - -## -# reset_template_regexes -# -function reset_template_regexes() { - # Fixup STMT_BLOCK delims - Default to STMT_DELIM if not set - # - if [[ -n "${STMT_BLOCK_DELIM_UNDEFINED}" ]]; then - STMT_BLOCK_START_DELIM="${STMT_DELIM}" - STMT_BLOCK_STOP_DELIM="${STMT_DELIM}" - fi - - # Fixup TEXT delim - Default to STMT_DELIM followed by ' ' if not set - # - if [[ -n "${TEXT_DELIM_UNDEFINED}" ]]; then - TEXT_DELIM="${STMT_DELIM} " # Note trailing space (' ') - fi - - # Fixup COMMENT delim - Default to STMT_DELIM followed by '#' if not set - # - if [[ -n "${COMMENT_DELIM_UNDEFINED}" ]]; then - COMMENT_DELIM="${STMT_DELIM}#" - fi - - # - # Create regexes - # - - local d ds d1 d2 d3 d4 - - d="${DIRECTIVE_DELIM}" - escape_regex d - DIRECTIVE_REGEX="^([[:blank:]]*)${d}([a-zA-Z_-]+)(.*)\$" - - d="${COMMENT_DELIM}" - escape_regex d - COMMENT_REGEX="^([[:blank:]]*)${d}" - - d="${STMT_DELIM}" - escape_regex d - STATEMENT_REGEX="^([[:blank:]]*)${d}[[:blank:]]+(.+)\$" - - d="${STMT_BLOCK_START_DELIM}" - escape_regex d - STATEMENT_BLOCK_START_REGEX="^([[:blank:]]*)${d}[[:blank:]]*\$" - - d="${STMT_BLOCK_STOP_DELIM}" - escape_regex d - STATEMENT_BLOCK_STOP_REGEX="^([[:blank:]]*)${d}[[:blank:]]*\$" - - d="${TEXT_DELIM}" - escape_regex d - STATEMENT_BLOCK_TEXT_REGEX="^([[:blank:]]*)${d}([[:blank:]]*[^[:blank:]](.*))\$" - - TEXT_REGEX='^([[:blank:]]*)([^[:blank:]](.*))?$' - - d1="${TAG_START_DELIM1}" - escape_regex d1 - - d2="${TAG_START_DELIM2}" - escape_regex d2 - - d3="${TAG_STOP_DELIM1}" - escape_regex d3 - - d4="${TAG_STOP_DELIM2}" - escape_regex d4 - - ds="${TAG_STMT_DELIM}" - escape_regex ds - - TAG_TEXT_REGEX="^([^${d1}]+|${d1}$|${d1}[^${d1}${d2}]+)(.*)" - - TAG_STD_REGEX="^${d1}${d2}((([^${d3}])|(${d3}[^${d4}]))*)${d3}${d4}(.*)" - - TAG_QUOTE_REGEX="^${d1}${d2}\"((([^\"])|(\"[^${d3}])|(\"${d3}[^${d4}]))*)\"${d3}${d4}(.*)" - - TAG_STATEMENT_REGEX="^${d1}${d2}${ds}((([^${d3}])|(${d3}[^${d4}]))*)${d3}${d4}(.*)" - - # printf "# ---> delim regexes:" - # printf "# STATEMENT_BLOCK_START_REGEX: '%s'\n" "${STATEMENT_BLOCK_START_REGEX}" - # printf "# STATEMENT_BLOCK_STOP_REGEX: '%s'\n" "${STATEMENT_BLOCK_STOP_REGEX}" - # printf "# COMMENT_REGEX: '%s'\n" "${COMMENT_REGEX}" - # printf "# DIRECTIVE_REGEX: '%s'\n" "${DIRECTIVE_REGEX}" - # printf "# STATEMENT_REGEX: '%s'\n" "${STATEMENT_REGEX}" - # printf "# TAG_TEXT_REGEX: '%s'\n" "${TAG_TEXT_REGEX}" - # printf "# TAG_STD_REGEX: '%s'\n" "${TAG_STD_REGEX}" - # printf "# TAG_QUOTE_REGEX: '%s'\n" "${TAG_QUOTE_REGEX}" - # printf "# TAG_STATEMENT_REGEX: '%s'\n" "${TAG_STATEMENT_REGEX}" -} - -####################################################################### -# Misc Functions -####################################################################### - -## -# trim -# usage: trim varname -# NOTE: Expects value to NOT contain '\n' -# -function trim() { - read -r "$1" <<< "${!1}"$'\n' -} - -## -# escape_regex -# usage: escape_regex varname -# -function escape_regex() { - local result - # shellcheck disable=SC2001 # Too complex for ${variable//search/replace} - # shellcheck disable=SC2016 # Not using expansion, prefer single quotes - # shellcheck disable=SC2034 # ref is used - result=$(sed 's/[][\.|$(){}?+*^]/\\&/g' <<< "${!1}") - printf -v "${1}" "%s" "${result}" -} - -## -# normalize_directive -# usage: normalize_directive varname -# -function normalize_directive() { - local result - # shellcheck disable=SC2034 # ref is used - result=$(tr 'a-z_' 'A-Z-' <<< "${!1}") - printf -v "${1}" "%s" "${result}" -} - -####################################################################### -# STATES -####################################################################### - -STATES=() # empty => DEFAULT -STATE="DEFAULT" # [ DEFAULT, MAYBE_TXT_BLOCK, TXT_BLOCK, START_STMT_BLOCK, STMT_BLOCK ] - -## -# push_state -# -function push_state() { - STATES+=("${STATE}") - STATE="${1}" -} - -## -# pop_state -# -function pop_state() { - if [[ ${#STATES[@]} -gt 0 ]]; then - STATE="${STATES[${#STATES[@]} - 1]}" - unset "STATES[${#STATES[@]}-1]" - else - STATE="DEFAULT" - fi -} - -####################################################################### -# TEXT_INDENTS -####################################################################### - -TEXT_INDENTS=() # empty => "" -TEXT_INDENT="" - -## -# push_text_indent -# -function push_text_indent() { - TEXT_INDENTS+=("${TEXT_INDENT}") - TEXT_INDENT="${1}" -} - -## -# pop_text_indent -# -function pop_text_indent() { - if [[ ${#TEXT_INDENTS[@]} -gt 0 ]]; then - TEXT_INDENT="${TEXT_INDENTS[${#TEXT_INDENTS[@]} - 1]}" - unset "TEXT_INDENTS[${#TEXT_INDENTS[@]} - 1]" - else - TEXT_INDENT="" - fi -} - -####################################################################### -# STATEMENT_INDENTS -####################################################################### - -STATEMENT_INDENTS=() # empty => "" -STATEMENT_INDENT="" - -## -# push_statement_indent -# -function push_statement_indent() { - STATEMENT_INDENTS+=("${STATEMENT_INDENT}") - STATEMENT_INDENT="${1}" -} - -## -# pop_statement_indent -# -function pop_statement_indent() { - if [[ ${#STATEMENT_INDENTS[@]} -gt 0 ]]; then - STATEMENT_INDENT="${STATEMENT_INDENTS[${#STATEMENT_INDENTS[@]} - 1]}" - unset "STATEMENT_INDENTS[${#STATEMENT_INDENTS[@]} - 1]" - else - STATEMENT_INDENT="" - fi -} - -####################################################################### -# BLOCK_INDENTS -####################################################################### - -BLOCK_INDENTS=() # empty => "" -BLOCK_INDENT="" - -## -# push_block_indent -# -function push_block_indent() { - BLOCK_INDENTS+=("${BLOCK_INDENT}") - BLOCK_INDENT="${1}" -} - -## -# pop_block_indent -# -function pop_block_indent() { - if [[ ${#BLOCK_INDENTS[@]} -gt 0 ]]; then - BLOCK_INDENT="${BLOCK_INDENTS[${#BLOCK_INDENTS[@]} - 1]}" - unset "BLOCK_INDENTS[${#BLOCK_INDENTS[@]} - 1]" - else - BLOCK_INDENT="" - fi -} - -####################################################################### -# Print Functions -####################################################################### - -## -# print_statement -# $1 = leading indentation -# $2 = statement -# -function print_statement() { - local indent="${1}" - if [[ "${indent}" == "${BLOCK_INDENT}"* ]]; then - indent="${indent/#$BLOCK_INDENT/}" - fi - printf "%s\n" "${BASE_STMT_INDENT}${STATEMENT_INDENT}${indent}${2}" -} - -## -# print_text - generates a printf statement for template text -# $1 = leading text indentation -# $2 = text -# $3 = leading stmt indentation [OPTIONAL] -# -function print_text() { - local indent="${1}" - if [[ "${indent}" == "${BLOCK_INDENT}"* ]]; then - indent="${indent/#$BLOCK_INDENT/}" - fi - process_tags "${3-${BLOCK_INDENT}}" "${BASE_TEXT_INDENT}${TEXT_INDENT}${indent}${2}" -} - -####################################################################### -# Process Functions -####################################################################### - -## -# process_tags -# $1 = statement indentation -# $2 = full line of text to process -# -function process_tags() { - local stmt_indent line args arg quoted - stmt_indent="${1}" - line="${2}" - args="" - while [ -n "${line}" ]; do - # echo "# LINE @ START: $(declare -p line)" >&2 - if [[ "${line}" =~ $TAG_TEXT_REGEX ]]; then - # echo "# TEXT TAG MATCH: $(declare -p BASH_REMATCH)" >&2 - printf -v quoted "%q" "${BASH_REMATCH[1]}" - args="${args}${quoted}" - line="${BASH_REMATCH[2]}" - elif [[ "${line}" =~ $TAG_QUOTE_REGEX ]]; then - # echo "# QUOTE TAG MATCH: $(declare -p BASH_REMATCH)" >&2 - args="${args}\"${BASH_REMATCH[1]}\"" - line="${BASH_REMATCH[6]}" - elif [[ "${line}" =~ $TAG_STATEMENT_REGEX ]]; then - # echo "# STMT TAG MATCH: $(declare -p BASH_REMATCH)" >&2 - arg="${BASH_REMATCH[1]}" - trim arg - args="${args}\"\$(${arg})\"" - line="${BASH_REMATCH[5]}" - # Check standard regex last as it's a super-set of quote and stmt regex - # - elif [[ "${line}" =~ $TAG_STD_REGEX ]]; then - # echo "# STD TAG MATCH: $(declare -p BASH_REMATCH)" >&2 - arg="${BASH_REMATCH[1]}" - trim arg - args="${args}\"${arg}\"" - line="${BASH_REMATCH[5]}" - # Assume next character is TEXT - extract and process remainder - # - elif [[ "${line}" =~ (.)(.*) ]]; then - # echo "# DEFAULT: Assuming first char is TEXT: $(declare -p line)" - printf -v quoted "%q" "${BASH_REMATCH[1]}" - args="${args}${quoted}" - line="${BASH_REMATCH[2]}" - fi - # echo "# LINE @ END: $(declare -p line)" >&2 +sDNS () { + output=$(basename $1 .dns) + cp $1 $output + cat $output + for host in $(sed -e 's/ /\n/g' $1 | sed -n 's/[Dd][Nn][Ss]://p'); do + echo $host: $(dip $host) + sed -i 's/[Dd][Nn][Ss]:'$host'/'$(dip $host)'/g' $output + cat $output done - local stmt - if [ -n "${args}" ]; then - printf -v stmt "printf \"%%s\\\\n\" %s" "${args}" - else - printf -v stmt "printf \"\\\\n\"" - fi - print_statement "${stmt_indent}" "${stmt}" -} - -DELIM_DIR_TAG_REGEX='[Tt][Aa][Gg]\s*=\s*"([^"]*)"' -DELIM_DIR_TAG_STMT_REGEX='[Tt][Aa][Gg][_-]?[Ss][Tt][Mm][Tt]\s*=\s*"([^"]*)"' -DELIM_DIR_STMT_REGEX='[Ss][Tt][Mm][Tt]\s*=\s*"([^"]*)"' -DELIM_DIR_STMT_BLOCK_REGEX='[Ss][Tt][Mm][Tt][_-]?[Bb][Ll][Oo][Cc][Kk]\s*=\s*"([^"]*)"' -DELIM_DIR_TXT_REGEX='[Tt][Xx][Tt]\s*=\s*"([^"]*)"' -DELIM_DIR_TEXT_REGEX='[Tt][Ee][Xx][Tt]\s*=\s*"([^"]*)"' -DELIM_DIR_DIR_REGEX='[Dd][Ii][Rr]\s*=\s*"([^"]*)"' -DELIM_DIR_DIRECTIVE_REGEX='[Dd][Ii][Rr][Ee][Cc][Tt][Ii][Vv][Ee]\s*=\s*"([^"]*)"' -DELIM_DIR_CMT_REGEX='[Cc][Mm][Tt]\s*=\s*"([^"]*)"' -DELIM_DIR_COMMENT_REGEX='[Cc][Oo][Mm][Mm][Ee][Nn][Tt]\s*=\s*"([^"]*)"' - -## -# process_directive -# $1 = leading_indent -# $2 = directive -# $3 = directive arg(s) -# -function process_directive() { - local directive - directive="${2}" - normalize_directive directive - case "${directive}" in - INCLUDE) - local indent="${1}" - if [[ "${indent}" == "${BLOCK_INDENT}"* ]]; then - indent="${indent/#$BLOCK_INDENT/}" - fi - local args args_arr - args="${3}" - trim args - declare -a args_arr="(${args})" - # shellcheck disable=SC2128 # We choose BASH_SOURCE vs BASH_SOURCE[0] for compatability - "${BASH_SOURCE}" \ - --text-indent "${BASE_TEXT_INDENT}${TEXT_INDENT}${indent}" \ - --stmt-indent "${BASE_STMT_INDENT}${STATEMENT_INDENT}${indent}" \ - --block-indent "${BASE_BLOCK_INDENT}${BLOCK_INDENT}" \ - --tag-delims "${TAG_START_DELIM1}${TAG_START_DELIM2} ${TAG_STOP_DELIM1}${TAG_STOP_DELIM2}" \ - --tag-stmt-delim "${TAG_STMT_DELIM}" \ - --stmt-delim "${STMT_DELIM}" \ - --stmt-block-delims "${STMT_BLOCK_START_DELIM} ${STMT_BLOCK_STOP_DELIM}" \ - --txt-delim "${TEXT_DELIM}" \ - --dir-delim "${DIRECTIVE_DELIM}" \ - --cmt-delim "${COMMENT_DELIM}" \ - "${args_arr[@]}" - ;; - DELIMS) - # TAG - # - if [[ "${3}" =~ $DELIM_DIR_TAG_REGEX ]]; then - parse_tag_delims "${BASH_REMATCH[1]}" 'DELIMS TAG directive' - fi - # TAG-STMT - # - if [[ "${3}" =~ $DELIM_DIR_TAG_STMT_REGEX ]]; then - parse_tag_stmt_delim "${BASH_REMATCH[1]}" 'DELIMS TAG-STMT directive' - fi - # STMT - # - if [[ "${3}" =~ $DELIM_DIR_STMT_REGEX ]]; then - parse_stmt_delim "${BASH_REMATCH[1]}" 'DELIMS STMT directive' - fi - # STMT-BLOCK - # - if [[ "${3}" =~ $DELIM_DIR_STMT_BLOCK_REGEX ]]; then - parse_stmt_block_delims "${BASH_REMATCH[1]}" '"DELIMS STMT-BLOCK directive' - fi - # TEXT - # - if [[ "${3}" =~ $DELIM_DIR_TXT_REGEX || "${3}" =~ $DELIM_DIR_TEXT_REGEX ]]; then - parse_text_delim "${BASH_REMATCH[1]}" 'DELIMS TEXT directive' - fi - # DIRECTIVE - # - if [[ "${3}" =~ $DELIM_DIR_DIR_REGEX || "${3}" =~ $DELIM_DIR_DIRECTIVE_REGEX ]]; then - parse_directive_delim "${BASH_REMATCH[1]}" 'DELIMS DIR directive' - fi - # COMMENT - # - if [[ "${3}" =~ $DELIM_DIR_CMT_REGEX || "${3}" =~ $DELIM_DIR_COMMENT_REGEX ]]; then - parse_comment_delim "${BASH_REMATCH[1]}" 'DELIMS CMT directive' - fi - # Apply changes - # - reset_template_regexes - ;; - RESET-DELIMS) - reset_delims - reset_template_regexes - ;; - *) # unsupported directive - echo "Error: Unknown directive: '${directive}' - Skipping" >&2 - ;; - esac -} - -function debug_array() { - printf "[" - local need_comma="" - while [[ ${#@} -gt 0 ]]; do - if [ -n "${need_comma:-}" ]; then - printf ", " - fi - if [ -n "${1:-}" ]; then - printf "'%q'" "${1}" - else - printf "''" - fi - need_comma=1 - shift - done - printf "]" -} -## -# debug_state logs the state of the global variables. -# To use this set the BASH_TPL_DEBUG variable to a non-empty value -# when invoking the script. -# TODO Track source input file+line numbers -# $1 = template line | EOF -# -function debug_state() { - printf "#<< ---------------\n" - printf "#LINE TEXT : '%s'\n" "${1-}" - printf "#STATE : %s\n" "${STATE:-}" - printf "#STATES : %s\n" "$(debug_array "${STATES[@]}")" - printf "#TEXT_INDENT : %q\n" "${TEXT_INDENT:-}" - printf "#TEXT_INDENTS : %s\n" "$(debug_array "${TEXT_INDENTS[@]}")" - printf "#STATEMENT_INDENT : %q\n" "${STATEMENT_INDENT:-}" - printf "#STATEMENT_INDENTS: %s\n" "$(debug_array "${STATEMENT_INDENTS[@]}")" - printf "#BLOCK_INDENT : %q\n" "${BLOCK_INDENT:-}" - printf "#BLOCK_INDENTS : %s\n" "$(debug_array "${BLOCK_INDENTS[@]}")" - printf "#TEXT_BLOCK_LINES_INDENT : %q\n" "${TEXT_BLOCK_LINES_INDENT}" - printf "#TEXT_BLOCK_LINES_INDENT_SET : %q\n" "${TEXT_BLOCK_LINES_INDENT_SET}" - printf "#STATEMENT_BLOCK_LINES_INDENT : %q\n" "${STATEMENT_BLOCK_LINES_INDENT}" - printf "#STATEMENT_BLOCK_LINES_INDENT_STATE : %q\n" "${STATEMENT_BLOCK_LINES_INDENT_STATE}" - printf "#>> ---------------\n" -} - -function process_line() { - [ -n "${BASH_TPL_DEBUG:-}" ] && debug_state ${@+"$@"} - state_"${STATE}" ${@+"$@"} -} - -function process_stdin() { - local line - while IFS="" read -r line || [ -n "${line}" ]; do - process_line "${line}" - done - # EOF - Notify states - # Call with no args - # - while [[ "${STATE}" != "DEFAULT" ]]; do - process_line - done - process_line # DEFAULT -} - -####################################################################### -# State Handler Functions -####################################################################### - -## -# state_DEFAULT -# Not inside any blocks -# Assumes *_INDENT and STATE arrays are empty -# -function state_DEFAULT() { - [[ ${#@} -gt 0 ]] || return # Exit early on EOF - # Line is a statement - # - if [[ "${1}" =~ $STATEMENT_REGEX ]]; then - print_statement "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" - push_statement_indent "${BASH_REMATCH[1]}" - push_state "MAYBE_TXT_BLOCK" - # Line is a statement block start - # - elif [[ "${1}" =~ $STATEMENT_BLOCK_START_REGEX ]]; then - push_statement_indent "${BASH_REMATCH[1]}" - push_state "START_STMT_BLOCK" - # Line is a directive - # - elif [[ "${1}" =~ $DIRECTIVE_REGEX ]]; then - process_directive "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}" - # Line is a comment - # - elif [[ "${1}" =~ $COMMENT_REGEX ]]; then - : # Comments do not generate output - # Line is text - # NOTE : Check LAST because regex always matches - # - elif [[ "${1}" =~ $TEXT_REGEX ]]; then - print_text "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" - fi -} - -TEXT_BLOCK_LINES=() -TEXT_BLOCK_LINES_INDENT="" -TEXT_BLOCK_LINES_INDENT_SET="" - -## -# state_MAYBE_TXT_BLOCK -# Previous line was a statement -# We might be starting a text block -# NOTE: Assumes -# push_state "MAYBE_TXT_BLOCK" -# push_statement_indent -# -function state_MAYBE_TXT_BLOCK() { - # If there's a line to process (i.e. not EOF) - # - if [[ ${#@} -gt 0 ]]; then - # Current line is empty - # Considered text block content, - # but doesn't contribute to indentation tracking - # - if [[ "${1}" == "" ]]; then - TEXT_BLOCK_LINES+=("${1}") # Save line - return - # Current line is a block-end statement, - # i.e. it's a statement at the same indentation as the start statement - # - elif [[ "${1}" =~ $STATEMENT_REGEX && "${BASH_REMATCH[1]}" == "${STATEMENT_INDENT}" ]]; then - # We've saved a FULL text block ! - # Use computed indentation - # - push_text_indent "${TEXT_INDENT}${STATEMENT_INDENT/#$BLOCK_INDENT/}" # Additive - push_statement_indent "${TEXT_BLOCK_LINES_INDENT}" - push_block_indent "${TEXT_BLOCK_LINES_INDENT}" - local state_marker=${#STATES[@]} # Save for cleanup - push_state "TXT_BLOCK" - # Text blocks can be nested, so save lines and cleanup *before* processing - # - local lines=("${TEXT_BLOCK_LINES[@]}") - TEXT_BLOCK_LINES=() - TEXT_BLOCK_LINES_INDENT="" - TEXT_BLOCK_LINES_INDENT_SET="" - # Process saved lines now in new state - # - local line - for line in "${lines[@]}"; do - process_line "${line}" - done - # Clean up our TXT_BLOCK state and any other danglers - # - while [[ ${#STATES[@]} -gt $state_marker ]]; do - process_line # EOF - done - # Clean up our MAYBE_TXT_BLOCK state - # - pop_statement_indent - pop_state - # Process close block in parent context - # - process_line "${1}" - return - # Capture line indentation for tracking - # TEXT_REGEX is perfect for this, so just re-use it - # NOTE: Regex always matches - # - elif [[ "${1}" =~ $TEXT_REGEX ]]; then - TEXT_BLOCK_LINES+=("${1}") # Save line - # If current line is indented - # - if [[ "${BASH_REMATCH[1]}" != "${STATEMENT_INDENT}" && "${BASH_REMATCH[1]}" == "${STATEMENT_INDENT}"* ]]; then - # If first time through - # - if [[ "${TEXT_BLOCK_LINES_INDENT_SET}" == "" ]]; then - # Track current indentation - # - TEXT_BLOCK_LINES_INDENT="${BASH_REMATCH[1]}" - TEXT_BLOCK_LINES_INDENT_SET="1" - return - # If current line is indented SAME OR LESS than tracked - # - elif [[ "${TEXT_BLOCK_LINES_INDENT}" == "${BASH_REMATCH[1]}"* ]]; then - # Update tracked indentation (may set to same value) - # - TEXT_BLOCK_LINES_INDENT="${BASH_REMATCH[1]}" - return - # If current line is indented MORE than tracked - # - elif [[ "${BASH_REMATCH[1]}" == "${TEXT_BLOCK_LINES_INDENT}"* ]]; then - # No change - # - return - # Neither line is a subset of the other - # - else - : # Here for completeness - fi - # Current line is NOT indented - # - else - : # Here for completeness - fi - fi - # EOF - # - else - : # Fall through - fi - # If we haven't returned by now, then we're not in a text block - # Discard saved state and process saved lines - # - pop_statement_indent - pop_state - # Text blocks can be nested, so save lines and cleanup *before* processing - # - local lines=("${TEXT_BLOCK_LINES[@]}") - # Clean up - # - TEXT_BLOCK_LINES=() - TEXT_BLOCK_LINES_INDENT="" - TEXT_BLOCK_LINES_INDENT_SET="" - # Process saved lines now in parent context - # TODO "push" these back onto primary line-processing stream? - # - local line - for line in "${lines[@]}"; do - process_line "${line}" - done -} - -## -# state_TXT_BLOCK -# NOTE: Assumes -# Called within MAYBE_TXT_BLOCK with a complete block to process -# Will NOT be called with TXT Block Close -# Every line has a minimum indentation of BLOCK_INDENT -# push_state TXT_BLOCK -# push_text_indent -# push_statement_indent -# push_block_indent -# -function state_TXT_BLOCK() { - # EOF - # - if [[ ${#@} -eq 0 ]]; then - # End of text block - # Discard saved state - # - pop_text_indent - pop_statement_indent - pop_block_indent - pop_state - # Current line is empty - # - elif [[ "${1}" == "" ]]; then - process_tags "${BLOCK_INDENT}" "" - # Current line is a statement - # - elif [[ "${1}" =~ $STATEMENT_REGEX ]]; then - print_statement "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" - push_statement_indent "${BASH_REMATCH[1]}" - push_state "MAYBE_TXT_BLOCK" - # Current line is a statement Start Block - # - elif [[ "${1}" =~ $STATEMENT_BLOCK_START_REGEX ]]; then - push_statement_indent "${BASH_REMATCH[1]}" - push_state "START_STMT_BLOCK" - # Current line is a directive - # - elif [[ "${1}" =~ $DIRECTIVE_REGEX ]]; then - process_directive "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}" - # Line is a comment - # - elif [[ "${1}" =~ $COMMENT_REGEX ]]; then - : # Comments do not generate output - # Line is text - # NOTE: Regex always matches - # - elif [[ "${1}" =~ $TEXT_REGEX ]]; then - print_text "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" - fi -} - -STATEMENT_BLOCK_LINES=() -STATEMENT_BLOCK_LINES_INDENT="" -STATEMENT_BLOCK_LINES_INDENT_STATE="" - -## -# state_START_STMT_BLOCK -# NOTE: Assumes -# push_state "START_STMT_BLOCK" -# push_statement_indent -# -function state_START_STMT_BLOCK() { - # If there's a line to process (i.e. not EOF) - # - if [[ ${#@} -gt 0 ]]; then - # Current line is empty - # Considered statement block content, - # but doesn't contribute to indentation tracking - # - if [[ "${1}" == "" ]]; then - STATEMENT_BLOCK_LINES+=("${1}") # Save line - return - # Current line is a statement block end - # - elif [[ "${1}" =~ $STATEMENT_BLOCK_STOP_REGEX ]]; then - # If indentation does not match block-open, then error - # TODO Track line numbers for better reporting - # - if [[ "${BASH_REMATCH[1]}" != "${STATEMENT_INDENT}" ]]; then - echo "Error: stmt-block close indentation does not match open" >&2 - exit 1 - fi - # We've saved a FULL statement block ! - # Is it fully indented? - # - if [[ "${STATEMENT_BLOCK_LINES_INDENT_STATE}" == "1" ]]; then - # Use computed indentation - # - push_text_indent "${TEXT_INDENT}${STATEMENT_INDENT/#$BLOCK_INDENT/}" # Additive - push_block_indent "${STATEMENT_BLOCK_LINES_INDENT}" - else - # If not consistently indented, default to no indent - # TODO Print warning? - # - push_text_indent "" - pop_statement_indent - push_statement_indent "" - push_block_indent "" - fi - # Process the saved lines - # NOTE: Statement block end (+ cleanup) will be processed by STMT_BLOCK handler - # - pop_state - push_state "STMT_BLOCK" - # Process saved lines now in new state - # Statement blocks do not nest, so we use global and cleanup *after* - # - local line - for line in "${STATEMENT_BLOCK_LINES[@]}"; do - process_line "${line}" - done - # Clean up our STMT_BLOCK state - # - process_line # EOF - # Clean up - # - STATEMENT_BLOCK_LINES=() - STATEMENT_BLOCK_LINES_INDENT="" - STATEMENT_BLOCK_LINES_INDENT_STATE="" - # Capture line indentation for tracking - # TEXT_REGEX is perfect for this, so just re-use it - # NOTE: Regex always matches - # - elif [[ "${1}" =~ $TEXT_REGEX ]]; then - STATEMENT_BLOCK_LINES+=("${1}") # Save line - # If current line is indented (or even) - # - if [[ "${BASH_REMATCH[1]}" == "${STATEMENT_INDENT}"* ]]; then - # If first time through - # - if [[ "${STATEMENT_BLOCK_LINES_INDENT_STATE}" == "" ]]; then - # Track current indentation - # - STATEMENT_BLOCK_LINES_INDENT="${BASH_REMATCH[1]}" - STATEMENT_BLOCK_LINES_INDENT_STATE="1" - # If still working with fully indented block - # - elif [[ "${STATEMENT_BLOCK_LINES_INDENT_STATE}" == "1" ]]; then - # If current line is indented SAME OR LESS than tracked - # - if [[ "${STATEMENT_BLOCK_LINES_INDENT}" == "${BASH_REMATCH[1]}"* ]]; then - # Update tracked indentation (may set to same value) - # - STATEMENT_BLOCK_LINES_INDENT="${BASH_REMATCH[1]}" - # If current line is indented MORE than tracked - # - elif [[ "${BASH_REMATCH[1]}" == "${STATEMENT_BLOCK_LINES_INDENT}"* ]]; then - # No change - # - : - # Neither line is a subset of the other - # - else - STATEMENT_BLOCK_LINES_INDENT_STATE="2" - fi - fi - # Current line is NOT indented (or even) - # - else - STATEMENT_BLOCK_LINES_INDENT_STATE="3" - fi - fi - # EOF - # - else - # EOF before close block reached is an error - # TODO Track line numbers for better reporting - # - echo "Error: Missing stmt-block close ('${STMT_BLOCK_STOP_DELIM}')" >&2 - exit 1 - fi -} - -## -# state_STMT_BLOCK -# NOTE: Assumes -# Called within START_STMT_BLOCK with a complete block to process -# Will NOT be called with STMT Block Close -# push_state STMT_BLOCK -# push_text_indent -# push_statement_indent -# push_block_indent -# -function state_STMT_BLOCK() { - # EOF - # - if [[ ${#@} -eq 0 ]]; then - # End of statement block - # Discard saved state - # - pop_text_indent - pop_statement_indent - pop_block_indent - pop_state - # Current line is empty - # - elif [[ "${1}" == "" ]]; then - # TODO Do we need a flag to print BASE_STMT_INDENT when included? - # - printf "\n" - # Line is text - # - elif [[ "${1}" =~ $STATEMENT_BLOCK_TEXT_REGEX ]]; then - print_text "${BLOCK_INDENT}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[1]}" - # Line is assumed to be a statement - # TEXT_REGEX is perfect for this, so just re-use it - # NOTE: Regex always matches - # - elif [[ "${1}" =~ $TEXT_REGEX ]]; then - print_statement "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" - fi -} - -####################################################################### -# Main -####################################################################### - -function version() { - printf "%s\n" "${VERSION}" -} - -## -# parse_env_delims - Set Delims from Environment Vars -# -function parse_env_delims() { - if [ -n "${BASH_TPL_TAG_DELIMS}" ]; then - parse_tag_delims "${BASH_TPL_TAG_DELIMS}" "BASH_TPL_TAG_DELIMS" - fi - - if [ -n "${BASH_TPL_TAG_STMT_DELIM}" ]; then - parse_tag_stmt_delim "${BASH_TPL_TAG_STMT_DELIM}" "BASH_TPL_TAG_STMT_DELIM" - fi - - if [ -n "${BASH_TPL_STMT_DELIM}" ]; then - parse_stmt_delim "${BASH_TPL_STMT_DELIM}" "BASH_TPL_STMT_DELIM" - fi - - if [ -n "${BASH_TPL_STMT_BLOCK_DELIMS}" ]; then - parse_stmt_block_delims "${BASH_TPL_STMT_BLOCK_DELIMS}" "BASH_TPL_STMT_BLOCK_DELIMS" - fi - - if [ -n "${BASH_TPL_TEXT_DELIM}" ]; then - parse_text_delim "${BASH_TPL_TEXT_DELIM}" "BASH_TPL_TEXT_DELIM" - fi - - if [ -n "${BASH_TPL_DIR_DELIM}" ]; then - parse_directive_delim "${BASH_TPL_DIR_DELIM}" "BASH_TPL_DIR_DELIM" - fi - - if [ -n "${BASH_TPL_CMT_DELIM}" ]; then - parse_comment_delim "${BASH_TPL_CMT_DELIM}" "BASH_TPL_CMT_DELIM" - fi -} - -## -# parse_args -# $@ args to parse -# -# Stores positional args in global array __ARGS[@] -# -function parse_args() { - __ARGS=() # Global - while (($#)); do - case "$1" in - -h | --help) - usage - exit 0 - ;; - --version) - version - exit 0 - ;; - -o | --output-file) - if [ -n "${2}" ]; then - OUTPUT_FILE="${2}" - else - echo "Error: Invalid or missing value for --output-file: '${2}'" >&2 - exit 1 - fi - shift 2 - ;; - --reset-delims) - # Reset delims immediately - Any delim flags after this will be honored - # - reset_delims - shift - ;; - --tag-delims) - parse_tag_delims "$2" "$1" - shift 2 - ;; - --tag-stmt-delim) - parse_tag_stmt_delim "$2" "$1" - shift 2 - ;; - --stmt-delim) - parse_stmt_delim "$2" "$1" - shift 2 - ;; - --stmt-block-delims) - parse_stmt_block_delims "$2" "$1" - shift 2 - ;; - --txt-delim | --text-delim) - parse_text_delim "$2" "$1" - shift 2 - ;; - --dir-delim | --directive-delim) - parse_directive_delim "$2" "$1" - shift 2 - ;; - --cmt-delim | --comment-delim) - parse_comment_delim "$2" "$1" - shift 2 - ;; - --text-indent) - BASE_TEXT_INDENT="${2}" - shift 2 - ;; - --stmt-indent) - BASE_STMT_INDENT="${2}" - shift 2 - ;; - --block-indent) - BASE_BLOCK_INDENT="${2}" - shift 2 - ;; - -) - __ARGS+=("$1") - shift - ;; - --) - shift - while (($#)); do - __ARGS+=("$1") - shift - done - ;; - --* | -*) # unsupported flags - echo "Error: unknown flag: '$1'; use -h for help" >&2 - exit 1 - ;; - *) # preserve positional arguments - __ARGS+=("$1") - shift - ;; - esac - done -} - -function main() { - - OUTPUT_FILE="" - - BASE_TEXT_INDENT="" - BASE_STMT_INDENT="" - BASE_BLOCK_INDENT="" - - reset_delims - parse_env_delims - - parse_args "$@" - set -- "${__ARGS[@]}" - unset __ARGS - - # No file argument - # - if [[ -z "${1}" ]]; then - # Nothing waiting on stdin - # - if [[ -t 0 ]]; then - usage - exit 1 - fi - else - # File argument is explicitly stdin - # - if [[ "${1}" == '-' ]]; then - shift - else - # File argument points to non-existing/readable file - # - if [[ ! -r "${1}" ]]; then - echo "File not found: '${1}'" >&2 - exit 1 - fi - # File argument is good, re-route it to stdin - # - exec < "${1}" - shift - fi - fi - - reset_template_regexes - - if [[ -n "${OUTPUT_FILE}" ]]; then - exec > "${OUTPUT_FILE}" - fi - - process_stdin - - return 0 # ALL OK -} - -# Only process main logic if not being sourced (ie tested) -# -(return 0 2> /dev/null) || main "$@" +} \ No newline at end of file