#!/bin/sh -eu # Process shell source (.) statements into absolute references or inline the script # Version: 2.0.0 # Copyright: 2017, Koalephant Co., Ltd # Author: Stephen Reay , Koalephant Packaging Team # Process shell source (.) statements into absolute references or inline the script proc k_script_build { var KOALEPHANT_TOOL_VERSION = '"1.0.0'" var KOALEPHANT_TOOL_DESCRIPTION = '"Process shell source (.) statements into absolute references or inline the script'" var KOALEPHANT_TOOL_OPTIONS = $[cat << """ Options: -c, --cd change to the input file directory before building -f, --file file the source to read. If not specified, defaults to stdin -o, --output file the file to write to. If not specified, defaults to stdout -d, --define var=val define simple replacements to be performed while processing e.g. LIB_PATH=/foo -i, --inline, --static path dir or file path to explicitly inline. Can be specified multiple times -l, --link, --dynamic path=abs dir or file path to explicitly link to. Can be specified multiple times -r, --report process input and report all discovered inclusions -x, --executable mark the resulting file (if not stdout) executable -h, --help show this help -v, --version show the version -V, --verbose show verbose output -D, --debug show debug output -q, --quiet suppress all output except errors """ ] var FIND_SOURCE_LINES = '"s/^\s*\. (.*)/K_SCRIPT_BUILD_REPORT\\1/p'" const FIND_SOURCE_LINES = '' var FIX_NEWLINES = ''$a\\'' const FIX_NEWLINES = '' var infile = '/dev/stdin' var outfile = '/dev/stdout' var executable = 'false' var report = 'false' var changeDir = 'false' var changeDirPath = ''"" var sourcePaths = ''"" var definitions = ''"" var newline = $[printf '\n ] set newline = $(newline% ) source ./shell-script-library.lib.sh k_log_level $(KOALEPHANT_LOG_LEVEL_NOTICE) > /dev/null proc add_source_path { var type = $1 var source = $2 var value = $(3:-) var sep = ''"" if test -n $(sourcePaths) { set sep = '"\n'" } setglobal sourcePaths = $[printf "%s$(sep)%s#%s#%s" $(sourcePaths) $(type) $(source) $(value)] } proc add_dynamic_mapping { var find = $1 var target = $2 k_log_info "Adding dynamic mapping for '$(find)' => '$(target)'" add_source_path dynamic $(find) $(target) } proc add_static_path { var path = $1 k_log_info "Adding static path for '$(path)'" add_source_path static $(path) } proc add_definition { var definition = $1 var value = $2 var sep = ''"" if test -n $(definitions) { set sep = '"\n'" } k_log_info "Adding definition for '$(definition)' with value '$(value)'" setglobal definitions = $[printf "%s$(sep)%s %s" $(definitions) $definition $(value)] } proc get_definition_pattern { echo $(definitions) | while env IFS=" " read -r key value { if test -z $(key) { continue } k_log_debug "Using definition: '$(key)' = '$(value)'" printf "s#([^A-Za-z0-9_])%s([^A-Za-z0-9_])#\\\\1%s\\\\2#g; " $(key) $(value) } } proc get_include_pattern { echo $(sourcePaths) | while env IFS="#" read -r type source value { match $(type) { with dynamic printf "s#%s#%s#g; " "^(\s*\.\s+)([\'\"]?)($[escape_sed_pattern $(source)])(.*\2\s*)\$" "\1\2$[escape_sed_replacement $(value)]\4" with static printf "s#%s#%s#g; " "^(\s*)\.\s+([\'\"]?)($[glob_to_sed $(source)])\2\s*$" "K_SCRIPT_BUILD_INCLUDE\1\3" } } } proc glob_to_sed { var pattern = ''"" var sep = '"|'" k_log_debug "Converting glob pattern to sed: '$ifsjoin(Argv)'" for file in [@Argv] { k_log_debug "Checking file exists: '$(file)'" if test -e $(file) { set pattern = ""$(pattern)$[escape_sed_pattern $(file)]$(sep)"" } } printf "%s" $(pattern%${sep}) } proc escape_sed_pattern { echo $(1) | sed -e 's/[]\/$*.^\|[()?+{}]/\\&/g' } proc escape_sed_replacement { var delimiter = $(2:-/) if test $(#delimiter) -ne 1 { k_log_alert "Error, pattern delimiter must be exactly one character: $(delimiter)" return 2 } echo $(1) | sed -e "s/[\\$(delimiter)\&]/\\\\&/g" } proc process_stream { var pattern = $1 var parentPrefix = $(2:-) if test $Argc -ge 2 { shift 2 } else { shift } var sedOpts = "$ifsjoin(Argv)" var defPattern = $[get_definition_pattern] k_log_debug "Processing stream using pattern: '$(pattern)', definition pattern: '$(defPattern)'" sed -E $(sedOpts) -e $(FIX_NEWLINES) -e $(defPattern) -e "s/^/$(parentPrefix)/g" -e $(pattern) | while env IFS= read -r line { var prefix = $(parentPrefix) var includeFile = ''"" match $(line) { with (K_SCRIPT_BUILD_INCLUDE* k_log_debug "Processing 'include' line: '$(line)" setglobal line = $[k_string_remove_start "K_SCRIPT_BUILD_INCLUDE" $(line)] setglobal trimmed = $[k_string_trim $(line)] set prefix = ""$(prefix)$[k_string_remove_end $(trimmed) $(line)]"" set includeFile = $(trimmed) with (K_SCRIPT_BUILD_REPORT* k_log_debug "Processing 'report' line: '$(line)" setglobal line = $[k_string_remove_start "K_SCRIPT_BUILD_REPORT" $(line)] printf "%s\n" $(line) } if test -n $(includeFile) && test -f $(includeFile) { k_log_debug "Including file: '$(includeFile)'" printf "$(prefix)#K_SCRIPT_BUILD: include(%s)\n" $(includeFile) process_stream $(pattern) $(prefix) $(sedOpts) < $(includeFile) } else { k_log_debug "Regular line: '$(line)'" printf "%s\n" $(line) } } } proc handle_changedir { if test $(changeDir) = true { if test -z $(changeDirPath) { if test -f $(infile) { setglobal changeDirPath = $[k_fs_dirname $(infile)] } else { k_log_err "No directory given to -c/--cd and no -f/--file specified" return 2 } } if test -f $(infile) { setglobal infile = $[k_fs_resolve $(infile)] } if test -n $(outfile) { setglobal outfile = $[k_fs_resolve $(outfile)] } cd $(changeDirPath) } } proc handle_executable { if test $(executable) = true { if test -f $(outfile) { chmod +x $(outfile) } else { k_log_err "Cannot set executable bit when output is stdout" return 2 } } } var tmpVal = '' while test $Argc -gt 0 { match $1 { with -h|--help k_usage exit with -c|--cd set changeDir = 'true' set changeDirPath = $[k_option_optional_arg @Argv] if test -n $(changeDirPath) { shift } shift with -f|--file set infile = $[k_option_requires_arg @Argv] shift 2 with -d|--define set tmpVal = $[k_option_requires_arg @Argv] add_definition $[echo $(tmpVal) | cut -d = -f 1] $[echo $(tmpVal) | cut -d = -f 2] shift 2 with -o|--output set outfile = $[k_option_requires_arg @Argv] shift 2 with -i|--inline|--static set tmpVal = $[k_option_optional_arg @Argv] if test -n $(tmpVal) { add_static_path $(tmpVal) shift 2 } else { add_static_path "./*" shift } with -l|--link|--dynamic set tmpVal = $[k_option_requires_arg @Argv] add_dynamic_mapping $[echo $(tmpVal) | cut -d = -f 1] $[echo $(tmpVal) | cut -d = -f 2] shift 2 with -r|--report set report = 'true' shift with -x|--exec|--executable set executable = 'true' shift with -v|--version k_version exit with -V|--verbose k_log_level $(KOALEPHANT_LOG_LEVEL_INFO) > /dev/null shift with -D|--debug k_log_level $(KOALEPHANT_LOG_LEVEL_DEBUG) > /dev/null shift with -q|--quiet k_log_level $(KOALEPHANT_LOG_LEVEL_ERR) > /dev/null shift with -- shift break with -* echo "Unknown option: $(1)" > !2 k_usage exit 1 with * break } } handle_changedir if test $(report) = true { process_stream $(FIND_SOURCE_LINES) "" -n < $(infile) > $(outfile) return } process_stream $[get_include_pattern] < $(infile) > $(outfile) handle_executable } k_script_build @Argv