├── .gitignore ├── LICENSE ├── README.md ├── circle.yml ├── crypttool ├── examples ├── config.json ├── config.json.tmpl ├── config.yaml ├── config.yaml.tmpl ├── passfile.txt ├── vars.sh └── vars.sh.enc ├── sempl ├── sempl-test.sh └── test ├── fixtures ├── 001_variable_default.tmpl ├── 002_variable_escaping.tmpl ├── 003_variable_expansion.tmpl ├── 004_variable_expansion.tmpl ├── 005_double_quote.tmpl ├── 006_single_quote.tmpl ├── 007_bash_inline.tmpl ├── 008_bash_loop.tmpl ├── 009_varsfile.tmpl ├── 010_symbol_test.tmpl ├── 011_variable_missing.tmpl ├── varsfile.enc ├── varsfile.enc.unenc └── varsfile.txt └── lib └── testdummy /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sempl 2 | 3 | [![Circle CI](https://circleci.com/gh/nextrevision/sempl.svg?style=svg)](https://circleci.com/gh/nextrevision/sempl) 4 | 5 | Stupid simple `bash` templating. 6 | 7 | Uses environment variables, inherited or sourced from a file, to render 8 | templates using common bash utilities. 9 | 10 | Given the following template (`examples/config.yaml.tmpl`): 11 | 12 | user: $USER 13 | password: ${password:-defaultpass} 14 | files: 15 | ### begin 16 | # for f in $(ls); do 17 | - $f 18 | # done 19 | ### end 20 | 21 | We could expect the following output when running `sempl`: 22 | 23 | $ USER=myuser sempl -o examples/config.yaml.tmpl 24 | user: myuser 25 | password: defaultpass 26 | files: 27 | - README.md 28 | - examples 29 | - sempl 30 | 31 | ## Installation 32 | 33 | curl -L -o sempl https://github.com/nextrevision/sempl/raw/master/sempl 34 | chmod +x sempl 35 | 36 | ### Requirements 37 | 38 | * bash 39 | * sed (GNU) 40 | * mktemp 41 | * grep (egrep) 42 | * openssl (if using encryption) 43 | 44 | ## Usage 45 | 46 | ``` 47 | usage: ./sempl [args] template [outfile] 48 | 49 | args: 50 | -s [varsfile] vars file (can be repeated) 51 | -p [password] decryption password 52 | -k [passfile] decryption password file 53 | -v verbose 54 | -o print template to stdout 55 | -f fail if a variable is unset with no default 56 | -c check that the template will render, but do not write the file 57 | -h help 58 | --version print version and exit 59 | --update update script to latest version 60 | ``` 61 | 62 | ## Loops 63 | 64 | It is possible to use inline bash loops for more complex logic. 65 | 66 | In order to designate where the loop should start, you must have in text 67 | `### begin` followed at some point by `### end` signaling the end of a loop. 68 | Any code you wish to execute must be preceded with a `#` and a space. Anything 69 | without a preceding `#` will be rendered as output by the template. 70 | 71 | ## Caveats 72 | 73 | * A backslash must be doubly escaped (i.e. `\\`) 74 | * Redirection in command substitution does not work (i.e. `$(cat blah 2>&1)`) 75 | * Quotes (single and double) must be closed or escaped 76 | 77 | ## Examples 78 | 79 | ### Template Expansion w/ Environment Vars 80 | 81 | source examples/vars.sh 82 | ./sempl -v examples/config.json.tmpl 83 | 84 | ### Template Expansion w/ Vars File 85 | 86 | ./sempl -v -s examples/vars.sh examples/config.json.tmpl 87 | 88 | ### Template Expansion w/ Outfile 89 | 90 | ./sempl -v -s examples/vars.sh examples/config.json.tmpl \ 91 | examples/outfile.json 92 | 93 | ### Template Expansion w/ Decryption Key 94 | 95 | ./sempl -v -p mypassword -s examples/vars.sh.enc \ 96 | examples/config.json.tmpl examples/outfile.json 97 | 98 | ### Template Expansion w/ Decryption File 99 | 100 | ./sempl -v -k examples/passfile.txt -s examples/vars.sh.enc \ 101 | examples/outfile.json examples/config.json.tmpl 102 | 103 | ### Template Expansion w/ Multiple Varsfiles 104 | 105 | Multiple vars files can be specified by repeating the `-s` flag. Vars files can 106 | either be passed either encrypted or decrypted. 107 | 108 | ./sempl -v -p mypassword -s examples/vars.sh.enc \ 109 | -s examples/vars.sh -s examples/vars.sh \ 110 | examples/config.json.tmpl examples/outfile.json 111 | 112 | ### Looping over a list of files 113 | 114 | Given the template `test.txt.tmpl` below: 115 | 116 | This is a text file. Siblings include: 117 | ### begin 118 | # for i in $(ls); do 119 | # if [[ $i == "sibling1.txt" ]]; then 120 | $i (favorite) 121 | # else 122 | $i 123 | # fi 124 | # done 125 | ### end 126 | 127 | Could be rendered as: 128 | 129 | This is a text file. Siblings include: 130 | test.txt.tmpl 131 | sibling1.txt (favorite) 132 | sibling2.txt 133 | 134 | ## Encryption 135 | 136 | `crypttool` is a very simple wrapper around the openssl command that 137 | can encrypt, decrypt, or edit a file. `sempl` can take an encrypted file 138 | and decrypt it at runtime with a password/passfile specified as an argument. 139 | This allows storing of secrets in variable files and decryption at the point 140 | of rendering a template file. 141 | 142 | ### Encrypting a Varsfile 143 | 144 | ./crypttool -p mypassword encrypt examples/vars.sh 145 | 146 | ### Decrypting a Varsfile 147 | 148 | ./crypttool -p mypassword decrypt examples/vars.sh.enc 149 | 150 | ### Editing an Encrypted Varsfile 151 | 152 | ./crypttool -p mypassword edit examples/vars.sh.enc 153 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | test: 2 | override: 3 | - ./test/lib/testdummy -d sempl-test.sh 4 | -------------------------------------------------------------------------------- /crypttool: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | __crypttool_editor=${EDITOR:-vim} 4 | __crypttool_verbose=0 5 | __crypttool_command='' 6 | __crypttool_varsfile='' 7 | __crypttool_password='' 8 | __crypttool_password_file='' 9 | __crypttool_decrypted_file='' 10 | __crypttool_encrypted_file='' 11 | 12 | _usage() { 13 | echo "usage: $0 [args] command varsfile" 14 | echo 15 | echo "commands:" 16 | echo " cat cat the decrypted contents of the varsfile" 17 | echo " create create a new encrypted vars file" 18 | echo " edit edit the varsfile (opens with ${EDITOR})" 19 | echo " decrypt decrypt the varsfile" 20 | echo " encrypt encrypt the varsfile" 21 | echo 22 | echo "args:" 23 | echo " -p [password] (en|de)cryption password" 24 | echo " -k [passfile] (en|de)cryption password file" 25 | echo " -v verbose" 26 | echo " -h help" 27 | echo 28 | } 29 | 30 | _verbose() { 31 | [ $__crypttool_verbose -eq 1 ] && echo ${1} 32 | } 33 | 34 | _error() { 35 | echo "ERROR: ${1}" 36 | exit 1 37 | } 38 | 39 | _clean() { 40 | _verbose "Removing temporary files" 41 | if ! [ -z "${__crypttool_decrypted_file}" ]; then 42 | [ -f "${__crypttool_decrypted_file}" ] && rm $__crypttool_decrypted_file 43 | fi 44 | if ! [ -z "${__crypttool_encrypted_file}" ]; then 45 | [ -f "${__crypttool_encrypted_file}" ] && rm $__crypttool_encrypted_file 46 | fi 47 | } 48 | 49 | _decrypt() { 50 | local varsfile=${1:-$__crypttool_varsfile} 51 | _verbose "Decrypting file ${varsfile}" 52 | __crypttool_decrypted_file=$(mktemp -t crypttool.XXXXXX) 53 | openssl aes-256-cbc -d -salt -in ${varsfile} -out ${__crypttool_decrypted_file} -k ${__crypttool_password} 54 | [ $? -eq 0 ] || _error "Unable to decrypt password vars file ${varsfile}" 55 | } 56 | 57 | _encrypt() { 58 | local varsfile=${1:-$__crypttool_varsfile} 59 | _verbose "Encrypting file ${varsfile}" 60 | __crypttool_encrypted_file=$(mktemp -t crypttool.XXXXXX) 61 | echo "${__crypttool_password}" | openssl aes-256-cbc -salt -in ${varsfile} -out ${__crypttool_encrypted_file} -pass stdin 62 | [ $? -eq 0 ] || _error "Unable to encrypt password vars file ${varsfile}" 63 | } 64 | 65 | _edit() { 66 | _decrypt 67 | $__crypttool_editor $__crypttool_decrypted_file 68 | read -p "Commit and encrypt the changes (y/n)? " 69 | if [[ $REPLY == 'y' ]]; then 70 | _encrypt $__crypttool_decrypted_file 71 | mv $__crypttool_encrypted_file $__crypttool_varsfile 72 | [ -f ${__crypttool_decrypted_file} ] && rm ${__crypttool_decrypted_file} 73 | fi 74 | } 75 | 76 | _cat() { 77 | _decrypt 78 | cat $__crypttool_decrypted_file 79 | rm $__crypttool_decrypted_file 80 | } 81 | 82 | _create() { 83 | local varsfile=${1:-$__crypttool_varsfile} 84 | __crypttool_decrypted_file=$(mktemp -t crypttool.XXXXXX) 85 | $__crypttool_editor ${__crypttool_decrypted_file} 86 | read -p "Commit and encrypt the changes (y/n)? " 87 | if [[ $REPLY == 'y' ]]; then 88 | _encrypt $__crypttool_decrypted_file 89 | mv $__crypttool_encrypted_file ${__crypttool_varsfile//.enc/}.enc 90 | [ -f ${__crypttool_decrypted_file} ] && rm ${__crypttool_decrypted_file} 91 | fi 92 | } 93 | 94 | _main() { 95 | [ -z "$1" ] && { _usage; exit 1; } 96 | 97 | while [ ! -z "$1" ]; do 98 | case "$1" in 99 | -p) shift; __crypttool_password=${1};; 100 | -k) shift; __crypttool_password_file=${1};; 101 | -v) __crypttool_verbose=1;; 102 | -h|--help) _usage; exit;; 103 | *) __crypttool_command=${1}; 104 | shift; __crypttool_varsfile=${1};; 105 | esac 106 | shift 107 | done 108 | 109 | if [ ! -z "${__crypttool_password_file}" ]; then 110 | [ -r ${__crypttool_password_file} ] || _error "Cannot read password file '${__crypttool_password_file}'" 111 | __crypttool_password=$(head -n1 ${__crypttool_password_file}) 112 | fi 113 | 114 | [ -z "${__crypttool_password}" ] && _error "must specify a password" 115 | [ -z "${__crypttool_varsfile}" ] && _error "must specify a varsfile" 116 | if [[ ${__crypttool_command} != "create" ]]; then 117 | [ -r "${__crypttool_varsfile}" ] || _error "cannot read varsfile ${__crypttool_varsfile}" 118 | fi 119 | 120 | case "${__crypttool_command}" in 121 | cat) _cat;; 122 | create) _create;; 123 | edit) _edit;; 124 | decrypt) _decrypt 125 | mv ${__crypttool_decrypted_file} ${__crypttool_varsfile//.enc/} 126 | echo "Decrypted file to ${__crypttool_varsfile//.enc/}";; 127 | encrypt) _encrypt 128 | mv ${__crypttool_encrypted_file} ${__crypttool_varsfile//.unenc/}.enc 129 | echo "Encrypted file to ${__crypttool_varsfile//.unenc/}.enc";; 130 | *) _error "Invalid command: ${__crypttool_command}";; 131 | esac 132 | 133 | _clean 134 | 135 | return 0 136 | } 137 | 138 | # test if script is being called or sourced 139 | if [[ $(basename ${0//-/}) == "crypttool" ]]; then 140 | _main "$@" 141 | fi 142 | -------------------------------------------------------------------------------- /examples/config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "user1": 3 | [ 4 | "singleqoute": 'myvar', 5 | "doublequote": "defaultvalue", 6 | "literal": "$var" 7 | ], 8 | "hostname": "mymachine.local" 9 | }, 10 | # this script checks to see if VAR1 is set to a certain value 11 | "VAR1 is set to myvar", 12 | # this script loops through files in this directory 13 | "config.json", 14 | "config.json.tmpl", 15 | "passfile.txt", 16 | "vars.sh", 17 | "vars.sh.enc" 18 | ] 19 | -------------------------------------------------------------------------------- /examples/config.json.tmpl: -------------------------------------------------------------------------------- 1 | [ 2 | { "${USER}": 3 | [ 4 | "singleqoute": '$VAR1', 5 | "doublequote": "${VAR2:-defaultvalue}", 6 | "literal": "\\$var" 7 | ], 8 | "hostname": "$(hostname -f)" 9 | }, 10 | # this script checks to see if VAR1 is set to a certain value 11 | ### begin 12 | # if [[ $VAR1 == "myvar" ]]; then 13 | "VAR1 is set to myvar", 14 | # fi 15 | ### end 16 | # this script loops through files in this directory 17 | ### begin 18 | # declare -a files=(*) 19 | # for (( i = 0; i < ${#files[*]}; ++ i )); do 20 | # if [ ! -z "${files[$i+1]}" ]; then 21 | "${files[$i]}", 22 | # else 23 | "${files[$i]}" 24 | # fi 25 | # done 26 | ### end 27 | ] 28 | -------------------------------------------------------------------------------- /examples/config.yaml: -------------------------------------------------------------------------------- 1 | user: myuser 2 | password: defaultpass 3 | files: 4 | - README.md 5 | - examples 6 | - sempl 7 | -------------------------------------------------------------------------------- /examples/config.yaml.tmpl: -------------------------------------------------------------------------------- 1 | user: $USER 2 | password: ${password:-defaultpass} 3 | files: 4 | ### begin 5 | # for f in $(ls); do 6 | - $f 7 | # done 8 | ### end 9 | -------------------------------------------------------------------------------- /examples/passfile.txt: -------------------------------------------------------------------------------- 1 | mypassword 2 | -------------------------------------------------------------------------------- /examples/vars.sh: -------------------------------------------------------------------------------- 1 | VAR1=testvar1 2 | VAR2=testvar2 3 | -------------------------------------------------------------------------------- /examples/vars.sh.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nextrevision/sempl/143186a905e443d5507c186900759119fe7f545a/examples/vars.sh.enc -------------------------------------------------------------------------------- /sempl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | __sempl_path=$0 4 | __sempl_version=0.3.0 5 | 6 | __sempl_verbose=0 7 | __sempl_stdout=0 8 | __sempl_fail_missing=0 9 | __sempl_check=0 10 | __sempl_template='' 11 | __sempl_outfile='' 12 | __sempl_varsfiles=( ) 13 | __sempl_password='' 14 | __sempl_password_file='' 15 | 16 | _usage() { 17 | echo "usage: $0 [args] template [outfile]" 18 | echo 19 | echo "args:" 20 | echo "-s [varsfile] vars file (can be repeated)" 21 | echo "-p [password] decryption password" 22 | echo "-k [passfile] decryption password file" 23 | echo "-v verbose" 24 | echo "-o print template to stdout" 25 | echo "-f fail if a variable is unset with no default" 26 | echo "-c check that the template will render, but do not write the file" 27 | echo "-h help" 28 | echo "--version print version and exit" 29 | echo "--update update script to latest version" 30 | echo 31 | } 32 | 33 | _version() { 34 | echo "version ${__sempl_version}" 35 | } 36 | 37 | _verbose() { 38 | [ $__sempl_verbose -eq 1 ] && echo ${1} 39 | } 40 | 41 | _error() { 42 | echo "ERROR: ${1}" 43 | exit 1 44 | } 45 | 46 | _convert_template() { 47 | local __in_block=0 48 | local __tmp_script=/dev/null 49 | local __tmp_tmpl=$(mktemp -t sempl.XXXXXX) 50 | local __escaped_line='' 51 | while IFS='' read __line || [[ -n ${__line} ]]; do 52 | if echo "${__line}" | grep -E -q '^(\s+)?###(\s)?begin'; then 53 | __in_block=1 54 | __tmp_script=$(mktemp -t sempl.XXXXXX) 55 | continue 56 | fi 57 | if echo "${__line}" | grep -E -q '^(\s+)?###(\s)?end'; then 58 | __in_block=0 59 | bash $__tmp_script >> $__tmp_tmpl 60 | rm $__tmp_script 61 | continue 62 | fi 63 | if [ $__in_block -eq 1 ]; then 64 | if echo "${__line}" | grep -E -q '^(\s+)?#'; then 65 | echo "${__line}" | sed 's/#//1' >> $__tmp_script 66 | else 67 | __escaped_line=$(echo "${__line}" | sed 's/"/"\\\"/g') 68 | echo "echo \"${__escaped_line}\"" >> $__tmp_script 69 | fi 70 | else 71 | echo "${__line}" >> $__tmp_tmpl 72 | fi 73 | done < $__sempl_template 74 | if [ $__sempl_fail_missing -eq 1 ]; then 75 | set -o nounset 76 | set -o pipefail 77 | fi 78 | eval "echo \"$(cat ${__tmp_tmpl} \ 79 | | sed 's/\"/\"\\\"/g' \ 80 | | sed 's//\\>/g')\"" \ 82 | | sed 's/\\>/>/g' \ 83 | | sed 's/\\ $__sempl_outfile 85 | local __rc=$? 86 | set +o nounset 87 | set +o pipefail 88 | rm $__tmp_tmpl 89 | if [ $__sempl_check -ne 1 ]; then 90 | if [ $__rc -eq 0 ]; then 91 | [[ $__sempl_outfile == '/dev/stdout' ]] || _verbose "Template written to ${__sempl_outfile}" 92 | else 93 | _error "Could not convert template ${__sempl_template}" 94 | fi 95 | fi 96 | return $__rc 97 | } 98 | 99 | _update() { 100 | local cmd='' 101 | which curl &> /dev/null && cmd="curl --silent -o ${__sempl_path}" 102 | which wget &> /dev/null && cmd="wget -q -O ${__sempl_path}" 103 | [ -z "$cmd" ] && _error "unable to find curl or wget in PATH" 104 | eval "$cmd https://raw.githubusercontent.com/nextrevision/sempl/master/sempl" 105 | local version=$(grep -E -m 1 '^__sempl_version=' $__sempl_path | cut -d'=' -f2) 106 | echo "Updated from ${__sempl_version} to ${version}" 107 | } 108 | 109 | _main() { 110 | [ -z "$1" ] && { _usage; exit 1; } 111 | 112 | while [ ! -z "$1" ]; do 113 | case "$1" in 114 | -s) shift; __sempl_varsfiles[${#__sempl_varsfiles[@]}]=${1};; 115 | -p) shift; __sempl_password=${1};; 116 | -k) shift; __sempl_password_file=${1};; 117 | -v) __sempl_verbose=1;; 118 | -o) __sempl_stdout=1;; 119 | -f) __sempl_fail_missing=1;; 120 | -c) __sempl_check=1;; 121 | -h|--help) _usage; exit;; 122 | --version) _version; exit;; 123 | --update) _update; exit;; 124 | *) __sempl_template=${1}; 125 | shift; __sempl_outfile=${1};; 126 | esac 127 | shift 128 | done 129 | 130 | # ensure a template file was passed 131 | if [ -z "${__sempl_template}" ]; then 132 | _error "No template file supplied" 133 | fi 134 | 135 | # ensure read permissions to template file 136 | [ -r ${__sempl_template} ] || _error "No such template '${__sempl_template}'" 137 | 138 | if [ ! -z "${__sempl_password_file}" ]; then 139 | [ -r ${__sempl_password_file} ] || _error "Cannot read password file '${__sempl_password_file}'" 140 | __sempl_password=$(head -n1 ${__sempl_password_file}) 141 | fi 142 | 143 | # if a vars file was specified ensure read permissions and load 144 | for varsfile in "${__sempl_varsfiles[@]}"; do 145 | if head -1 ${varsfile} | grep -q -e '^Salted_'; then 146 | if [ ! -z "${__sempl_password}" ]; then 147 | openssl aes-256-cbc -d -salt -in ${varsfile} -out ${varsfile}.unenc -k ${__sempl_password} \ 148 | || { rm -f ${varsfile}.unenc; _error "Unable to decrypt password vars file ${varsfile}"; } 149 | source ${varsfile}.unenc \ 150 | && rm -f ${varsfile}.unenc \ 151 | || { rm -f ${varsfile}.unenc; _error "Cannot source vars file '${varsfile}'"; } 152 | else 153 | _error 'No decryption password specified' 154 | fi 155 | else 156 | source ${varsfile} || _error "Cannot source vars file '${varsfile}'" 157 | fi 158 | done 159 | 160 | # default outfile to template file without .tpml extension 161 | if [ -z "${__sempl_outfile}" ]; then 162 | __sempl_outfile=${__sempl_template//.tmpl/} 163 | fi 164 | if [ $__sempl_stdout -eq 1 ]; then 165 | __sempl_outfile=/dev/stdout 166 | fi 167 | if [ $__sempl_check -eq 1 ]; then 168 | __sempl_outfile=/dev/null 169 | fi 170 | 171 | # do work 172 | _convert_template 173 | return $? 174 | } 175 | 176 | # test if script is being called or sourced 177 | if [[ $(basename ${0//-/}) == "sempl" ]]; then 178 | _main "$@" 179 | fi 180 | -------------------------------------------------------------------------------- /sempl-test.sh: -------------------------------------------------------------------------------- 1 | source ./sempl 2 | 3 | describe "test private functions" 4 | it_displays_usage() { 5 | cmd "_usage" 6 | assert_rc 0 7 | assert_content "usage: " 8 | assert_content "-h " 9 | } 10 | it_displays_verbose() { 11 | cmd "__sempl_verbose=1 _verbose 'test out'" 12 | assert_rc 0 13 | assert_content "test out" 14 | } 15 | it_is_not_verbose() { 16 | cmd "__sempl_verbose=0 _verbose 'test out'" 17 | assert_content_not "test out" 18 | } 19 | it_displays_version() { 20 | cmd "__sempl_version=1.2.3 _version" 21 | assert_content "version 1.2.3" 22 | } 23 | it_displays_error() { 24 | cmd "_error 'test error'" 25 | assert_rc 1 26 | assert_content "ERROR: test error" 27 | } 28 | 29 | describe "test cli flags" 30 | it_errors_without_args() { 31 | cmd "_main" 32 | assert_rc 1 33 | assert_content "usage: " 34 | } 35 | it_errors_without_template() { 36 | cmd "_main -v" 37 | assert_rc 1 38 | assert_content "ERROR: " 39 | } 40 | it_errors_with_nonexistent_template() { 41 | cmd "_main nosuchfile" 42 | assert_rc 1 43 | assert_content "ERROR: " 44 | } 45 | it_converts_template() { 46 | cmd "_main -o ./test/fixtures/001_variable_default.tmpl" 47 | assert_rc 0 48 | assert_content "testvar=defaultvalue" 49 | } 50 | it_errors_when_varsfile_is_not_found() { 51 | cmd "_main -o -s nosuchfile ./test/fixtures/009_varsfile.tmpl" 52 | assert_rc 1 53 | assert_content "ERROR: " 54 | } 55 | it_errors_when_varsfile_is_not_found() { 56 | cmd "_main -o -s nosuchfile ./test/fixtures/009_varsfile.tmpl" 57 | assert_rc 1 58 | assert_content "ERROR: " 59 | } 60 | it_sources_varsfile() { 61 | cmd "_main -o -s ./test/fixtures/varsfile.txt ./test/fixtures/009_varsfile.tmpl" 62 | assert_rc 0 63 | assert_content "testvar=sourcedvalue" 64 | } 65 | it_sources_encrypted_varsfile_from_pass() { 66 | cmd "_main -o -p password -s ./test/fixtures/varsfile.enc ./test/fixtures/009_varsfile.tmpl" 67 | assert_rc 0 68 | assert_content "testvar=sourcedvalue" 69 | } 70 | it_sources_encrypted_varsfile_from_passfile() { 71 | echo "password" > passfile 72 | cmd "_main -o -k passfile -s ./test/fixtures/varsfile.enc ./test/fixtures/009_varsfile.tmpl" 73 | assert_rc 0 74 | assert_content "testvar=sourcedvalue" 75 | rm passfile 76 | } 77 | it_sources_varsfile_with_bad_password() { 78 | cmd "_main -o -p badpass -s ./test/fixtures/varsfile.enc ./test/fixtures/009_varsfile.tmpl" 79 | assert_rc 1 80 | assert_content "ERROR: " 81 | } 82 | 83 | describe "test template conversion" 84 | it_converts_template_001_variable_default() { 85 | cmd "__sempl_template=./test/fixtures/001_variable_default.tmpl __sempl_outfile=/dev/stdout _convert_template" 86 | assert_rc 0 87 | assert_content "testvar=defaultvalue" 88 | } 89 | it_converts_template_002_variable_escaping() { 90 | cmd "__sempl_template=./test/fixtures/002_variable_escaping.tmpl __sempl_outfile=/dev/stdout _convert_template" 91 | assert_rc 0 92 | assert_content 'testvar=$testvar' 93 | } 94 | it_converts_template_003_variable_expansion() { 95 | cmd "testvar=testvalue __sempl_template=./test/fixtures/003_variable_expansion.tmpl __sempl_outfile=/dev/stdout _convert_template" 96 | assert_rc 0 97 | assert_content 'testvar=testvalue' 98 | } 99 | it_converts_template_004_variable_expansion() { 100 | cmd "testvar=testvalue __sempl_template=./test/fixtures/004_variable_expansion.tmpl __sempl_outfile=/dev/stdout _convert_template" 101 | assert_rc 0 102 | assert_content 'testvar=testvalue' 103 | } 104 | it_converts_template_005_double_quote() { 105 | cmd "testvar=testvalue __sempl_template=./test/fixtures/005_double_quote.tmpl __sempl_outfile=/dev/stdout _convert_template" 106 | assert_rc 0 107 | assert_content 'testvar="testvalue"' 108 | } 109 | it_converts_template_006_single_quote() { 110 | cmd "testvar=testvalue __sempl_template=./test/fixtures/006_single_quote.tmpl __sempl_outfile=/dev/stdout _convert_template" 111 | assert_rc 0 112 | assert_content "testvar='testvalue'" 113 | } 114 | it_converts_template_007_bash_inline() { 115 | cmd "testvar=testvalue __sempl_template=./test/fixtures/007_bash_inline.tmpl __sempl_outfile=/dev/stdout _convert_template" 116 | assert_rc 0 117 | assert_content 'testvar="inline"' 118 | } 119 | it_converts_template_008_bash_loop() { 120 | cmd "testvar=testvalue __sempl_template=./test/fixtures/008_bash_loop.tmpl __sempl_outfile=/dev/stdout _convert_template" 121 | assert_rc 0 122 | assert_regex '^ 1$' 123 | assert_regex '^ 2$' 124 | assert_regex '^ 3$' 125 | } 126 | it_converts_template_010_symbol_test() { 127 | cmd "__sempl_template=./test/fixtures/010_symbol_test.tmpl __sempl_outfile=/dev/stdout _convert_template" 128 | assert_rc 0 129 | assert_regex '^>test$' 130 | assert_regex '^>test$' 131 | assert_regex '^!test$' 132 | assert_regex '^\\test$' 133 | assert_regex '^#test$' 134 | assert_regex '^\.test$' 135 | assert_regex '^@test$' 136 | assert_regex '^%test$' 137 | assert_regex '^\^test$' 138 | assert_regex '^\&test$' 139 | assert_regex '^\*test$' 140 | assert_regex '^\(test$' 141 | assert_regex '^\)test$' 142 | assert_regex '^\-test$' 143 | assert_regex '^\+test$' 144 | assert_regex '^\=test$' 145 | assert_regex '^\{test$' 146 | assert_regex '^\}test$' 147 | assert_regex '^\|test$' 148 | assert_regex '^\/test$' 149 | assert_regex '^\,test$' 150 | assert_regex '^\;test$' 151 | assert_regex '^\:test$' 152 | assert_regex '^\_test$' 153 | } 154 | it_succeeds_on_missing_var_without_fail_option() { 155 | cmd "__sempl_template=./test/fixtures/011_variable_missing.tmpl __sempl_outfile=/dev/stdout _convert_template" 156 | assert_rc 0 157 | assert_regex '^testvar=$' 158 | } 159 | it_fails_on_missing_var_with_fail_option() { 160 | cmd "__sempl_fail_missing=1 __sempl_template=./test/fixtures/011_variable_missing.tmpl __sempl_outfile=/dev/stdout _convert_template" 161 | assert_rc 1 162 | } 163 | -------------------------------------------------------------------------------- /test/fixtures/001_variable_default.tmpl: -------------------------------------------------------------------------------- 1 | testvar=${testvar:-defaultvalue} 2 | -------------------------------------------------------------------------------- /test/fixtures/002_variable_escaping.tmpl: -------------------------------------------------------------------------------- 1 | testvar=\\$testvar 2 | -------------------------------------------------------------------------------- /test/fixtures/003_variable_expansion.tmpl: -------------------------------------------------------------------------------- 1 | testvar=$testvar 2 | -------------------------------------------------------------------------------- /test/fixtures/004_variable_expansion.tmpl: -------------------------------------------------------------------------------- 1 | testvar=${testvar} 2 | -------------------------------------------------------------------------------- /test/fixtures/005_double_quote.tmpl: -------------------------------------------------------------------------------- 1 | testvar="$testvar" 2 | -------------------------------------------------------------------------------- /test/fixtures/006_single_quote.tmpl: -------------------------------------------------------------------------------- 1 | testvar='$testvar' 2 | -------------------------------------------------------------------------------- /test/fixtures/007_bash_inline.tmpl: -------------------------------------------------------------------------------- 1 | testvar=$(echo "inline") 2 | -------------------------------------------------------------------------------- /test/fixtures/008_bash_loop.tmpl: -------------------------------------------------------------------------------- 1 | ### begin 2 | # for i in $(seq 1 3); do 3 | $i 4 | # done 5 | ### end 6 | -------------------------------------------------------------------------------- /test/fixtures/009_varsfile.tmpl: -------------------------------------------------------------------------------- 1 | testvar=sourcedvalue 2 | -------------------------------------------------------------------------------- /test/fixtures/010_symbol_test.tmpl: -------------------------------------------------------------------------------- 1 | test 3 | !test 4 | \\test 5 | #test 6 | .test 7 | @test 8 | %test 9 | ^test 10 | &test 11 | *test 12 | (test 13 | )test 14 | -test 15 | +test 16 | =test 17 | {test 18 | }test 19 | |test 20 | /test 21 | ,test 22 | ;test 23 | :test 24 | _test 25 | -------------------------------------------------------------------------------- /test/fixtures/011_variable_missing.tmpl: -------------------------------------------------------------------------------- 1 | testvar=${notset} 2 | -------------------------------------------------------------------------------- /test/fixtures/varsfile.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nextrevision/sempl/143186a905e443d5507c186900759119fe7f545a/test/fixtures/varsfile.enc -------------------------------------------------------------------------------- /test/fixtures/varsfile.enc.unenc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nextrevision/sempl/143186a905e443d5507c186900759119fe7f545a/test/fixtures/varsfile.enc.unenc -------------------------------------------------------------------------------- /test/fixtures/varsfile.txt: -------------------------------------------------------------------------------- 1 | testvar=sourcedvalue 2 | -------------------------------------------------------------------------------- /test/lib/testdummy: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | __dummy_version=0.1.0 4 | 5 | # define variables 6 | __dummy_red='\033[0;31m' 7 | __dummy_green='\033[0;32m' 8 | __dummy_clear='\033[0m' 9 | __dummy_cmd_rc=0 10 | __dummy_cmd_out=.__dummy_cmd_out 11 | __dummy_debug=0 12 | __dummy_debug_out=.__dummy_debug_out 13 | __dummy_verbose=0 14 | __dummy_verbose_out=.__dummy_verbose_out 15 | __dummy_nocolor=0 16 | __dummy_tests=0 17 | __dummy_failed_tests=0 18 | __dummy_start_time=$(date +%s) 19 | 20 | # public functions 21 | describe() { 22 | echo $1 23 | } 24 | 25 | cmd() { 26 | ( set +e; eval "${1}" 2>&1 > ${__dummy_cmd_out} ) \ 27 | && __dummy_cmd_rc=0 || __dummy_cmd_rc=$? 28 | } 29 | 30 | assert_rc() { 31 | if [ $__dummy_cmd_rc -ne $1 ]; then 32 | if [ $__dummy_verbose -eq 1 ]; then 33 | __dummy_write_verbose "expecting return code '${1}'" 34 | __dummy_write_verbose "got return code '${__dummy_cmd_rc}'" 35 | fi 36 | return 1 37 | fi 38 | return 0 39 | } 40 | 41 | assert_rc_not() { 42 | if [ $__dummy_cmd_rc -eq $1 ]; then 43 | if [ $__dummy_verbose -eq 1 ]; then 44 | __dummy_write_verbose "expecting return code to not be '${1}'" 45 | __dummy_write_verbose "got return code '${__dummy_cmd_rc}'" 46 | fi 47 | return 1 48 | fi 49 | return 0 50 | } 51 | 52 | assert_content() { 53 | grep -- "${1}" ${__dummy_cmd_out} 54 | local rc=$? 55 | if [ $rc -ne 0 ]; then 56 | __dummy_write_verbose "Content '${1}' not found in output" 57 | fi 58 | return $rc 59 | } 60 | 61 | assert_content_not() { 62 | ! grep -- "${1}" ${__dummy_cmd_out} 63 | local rc=$? 64 | if [ $rc -ne 0 ]; then 65 | __dummy_write_verbose "Content '${1}' not found in output" 66 | fi 67 | return $rc 68 | } 69 | 70 | assert_regex() { 71 | grep -E -- "${1}" ${__dummy_cmd_out} 72 | local rc=$? 73 | if [ $rc -ne 0 ]; then 74 | __dummy_write_verbose "Content '${1}' not found in output" 75 | fi 76 | return $rc 77 | } 78 | 79 | assert_regex_not() { 80 | ! grep -E -- "${1}" ${__dummy_cmd_out} 81 | local rc=$? 82 | if [ $rc -ne 0 ]; then 83 | __dummy_write_verbose "Content '${1}' not found in output" 84 | fi 85 | return $rc 86 | } 87 | 88 | # private functions 89 | __dummy_usage() { 90 | echo "usage: $0 [args] specfile" 91 | echo 92 | echo "-h|--help Help" 93 | echo "-d|--debug Enabled debug logging" 94 | echo "-v|--verbose Enables verbose logging" 95 | echo "--nocolor Disable color output" 96 | echo "--version Prints the version and exits" 97 | echo 98 | } 99 | 100 | __dummy_version() { 101 | echo "version ${__dummy_version}" 102 | } 103 | 104 | __dummy_error() { 105 | echo -e "${__dummy_red}ERROR: $1${__dummy_clear}" 106 | exit 1 107 | } 108 | 109 | __dummy_write_verbose() { 110 | echo "$1" >> $__dummy_verbose_out 111 | } 112 | 113 | __dummy_print_status() { 114 | case $2 in 115 | PASS) s="${__dummy_green}PASS${__dummy_clear}";; 116 | FAIL) s="${__dummy_red}FAIL${__dummy_clear}";; 117 | *) s="????";; 118 | esac 119 | printf " %-52s [${s}]\n" "${1//_/ }" 120 | if [ $__dummy_verbose -eq 1 ]; then 121 | while read output; do 122 | echo " ${output}"; 123 | done < $__dummy_verbose_out 124 | fi 125 | if [[ $2 == "FAIL" ]]; then 126 | if [ $__dummy_debug -eq 1 ]; then 127 | while read output; do 128 | echo " ${output}"; 129 | done < $__dummy_debug_out 130 | fi 131 | fi 132 | } 133 | 134 | __dummy_print_summary() { 135 | printf "%61s\n" | tr " " "-" 136 | printf "Tests: %3d | " $__dummy_tests 137 | printf "Passed: %3d | " $(($__dummy_tests - $__dummy_failed_tests)) 138 | printf "Failed: %3d | " $__dummy_failed_tests 139 | printf "Time: %3ds" $((`date +%s` - $__dummy_start_time)) 140 | printf "\n" 141 | } 142 | 143 | __dummy_run_test() { 144 | local rc=0 145 | __dummy_cmd_rc=0 146 | > $__dummy_cmd_out 147 | > $__dummy_debug_out 148 | > $__dummy_verbose_out 149 | set +e 150 | ( 151 | set -ex 152 | eval ${1} 153 | ) &> $__dummy_debug_out 154 | rc=$? 155 | set +ex 156 | if [ $rc -eq 0 ]; then 157 | __dummy_print_status $1 PASS 158 | else 159 | __dummy_print_status $1 FAIL 160 | let __dummy_failed_tests+=1 161 | fi 162 | let __dummy_tests+=1 163 | [ -f $__dummy_debug_out ] && rm $__dummy_debug_out 164 | [ -f $__dummy_verbose_out ] && rm $__dummy_verbose_out 165 | } 166 | 167 | # allow for sourcing 168 | if [[ $(basename ${0//-/}) == "testdummy" ]]; then 169 | # parse cli args 170 | until [ -z $1 ]; do 171 | case $1 in 172 | -h|--help) __dummy_usage && exit;; 173 | -d|--debug) __dummy_debug=1; __dummy_verbose=1;; 174 | -v|--verbose) __dummy_verbose=1;; 175 | --nocolor) __dummy_nocolor=1;; 176 | --version) __dummy_version && exit;; 177 | *) __dummy_specfile=$1;; 178 | esac 179 | shift 180 | done 181 | 182 | # disable color 183 | if [ $__dummy_nocolor -eq 1 ]; then 184 | __dummy_red='' 185 | __dummy_green='' 186 | __dummy_clear='' 187 | fi 188 | 189 | # ensure we can read the spec 190 | [ -z ${__dummy_specfile} ] && __dummy_usage && __dummy_error "Must pass a spec file" 191 | [ -f ${__dummy_specfile} ] || __dummy_error "No such file ${__dummy_specfile}" 192 | [ -r ${__dummy_specfile} ] || __dummy_error "Cannot read ${__dummy_specfile}" 193 | 194 | # source the spec functions 195 | source ${__dummy_specfile} 2>&1 > /dev/null 196 | 197 | # call the spec functions 198 | while read line; do 199 | if echo $line | grep -q '^it_'; then 200 | t=$(echo $line | cut -d'(' -f1) 201 | if grep -B1 $t ${__dummy_specfile} | grep -q '^describe '; then 202 | eval $(grep -B1 $t ${__dummy_specfile} | grep '^describe ') 203 | elif grep -B1 $t ${__dummy_specfile} | grep -q '^$'; then 204 | echo 205 | fi 206 | __dummy_run_test "${t}" 207 | fi 208 | done < ${__dummy_specfile} 209 | 210 | # print summary 211 | __dummy_print_summary 212 | 213 | # exit if failed tests 214 | [ $__dummy_failed_tests -eq 0 ] || exit 1 215 | fi 216 | --------------------------------------------------------------------------------