* Make medley.sh and its associated scripts POSIX compliant - i.e., debashify them * Added config file for medley script, medley now reads from config file and prepends arguemnts from file to the copmmand line arguments * WIP. Updates to medley.sh scripts. * WIP. More on medley.sh and friends update. * WIP. Medley redo * WIP. Debugging new medley scripts * Renamed medley.sh/medley.command to be medley_main.sh. Added code to compile single medley.sh/medley.command script by inlining all of the source'd medley_*.sh files. * Add temp fix for cygwin Issue #1685 * Minor fixup to medley_utils.sh; take debug code out out of run_medley * Add README to medley directory to explain how to compile medley.sh (medley.command). * Ooops. This time really adding the README file to the medley directory explaining how to compile medley.sh (medley.command) * Update loadup- scripts to use updated medley scripts rather than run-medley * Fix default setting of $config_file in medley_configfile.sh * Redo medley compile to pick up last commikt * Fixing how maiko exe is found and sysout argument error processing - both issues discovered testing on MAcOS * In medley_configfile, replace echo with printf %s because echo - does not work in zsh * Supress config file on loadups calls to Medley * Add oldschool support (use original run-medley) to loadup scripts; improve FAILURE detection so loadup-all won't proceed once one of the components fails * Add in medley_args.sh add -prog as synonym to --maikoprog to aid in loadup scripts; in medley_run.sh script try to get a good exit code for call to maiko, especially useful for loadup scripts * Run loadup scripts thru shellcheck and update as necessary to make Posix compliant * Get rid of -nt comparisons in loadup-setup.sh because they are not posix-complaint. They were not really needed anyway. * Removing (for now) use of lde exit codes to decide FAILURE case in loadup-setup.sh since exit codes from lde apperar to be inverted on MacOS. * Update medley man page. Add - functionality to more args is medley_args.sh * Compile medley.sh with changes from last commit * Ooops. Left medley_args.sh changes out of last commit. Rectifying here. * Added support for LDEKEYBOARDTYPE to medley_run to match run-medley * Add to medley.sh: auto numbered id's and titles with id's inserted * Cleanup some shellcheck issues in medley_main.sh * fix maiko args -nh-xxx. were -nethub-xxxx. In medley_run.sh * Overhaul handling of pass-on args to manage the quoting issues prevelant in the previous implementation * Cleanup minor shellcheck issues in medley_*.sh scripts * Add underscore as character allowed in ids - makes things clearer when id used with + * Add a self-numbering id to medley calls in loadup scripts * Put workaround in medley_run.sh for Issue #1702 - issues with sysout arg processing in Maiko * Oops. messed up LDESRCSYSOUT in last commit. should be LDESOURCESYSOUT * compile medley.sh
461 lines
12 KiB
Bash
Executable File
461 lines
12 KiB
Bash
Executable File
#!/usr/bin/env sh
|
|
# MIT License
|
|
#
|
|
# Copyright (c) 2022-2022 Carlo Corradini
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in all
|
|
# copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
# SOFTWARE.
|
|
|
|
# Inspired by https://gist.github.com/joehillen/30f08738c1c3c0ca3e4c754ad33ad2ff
|
|
|
|
# Fail on error
|
|
set -o errexit
|
|
# Disable wildcard character expansion
|
|
set -o noglob
|
|
|
|
# PID shell
|
|
SELF_PID=$$
|
|
|
|
# ================
|
|
# LOGGER
|
|
# ================
|
|
# Fatal log level. Cause exit failure
|
|
LOG_LEVEL_FATAL=100
|
|
# Warning log level
|
|
LOG_LEVEL_WARN=200
|
|
# Informational log level
|
|
LOG_LEVEL_INFO=300
|
|
# Debug log level
|
|
LOG_LEVEL_DEBUG=400
|
|
# Silent log level
|
|
LOG_LEVEL_SILENT=500
|
|
# Log level
|
|
LOG_LEVEL=$LOG_LEVEL_INFO
|
|
# Log color flag
|
|
LOG_COLOR_ENABLE=true
|
|
|
|
# Convert log level to equivalent name
|
|
# @param $1 Log level
|
|
to_log_level_name() {
|
|
_log_level=${1:-LOG_LEVEL}
|
|
_log_level_name=
|
|
|
|
case $_log_level in
|
|
"$LOG_LEVEL_FATAL") _log_level_name=fatal ;;
|
|
"$LOG_LEVEL_WARN") _log_level_name=warn ;;
|
|
"$LOG_LEVEL_INFO") _log_level_name=info ;;
|
|
"$LOG_LEVEL_DEBUG") _log_level_name=debug ;;
|
|
"$LOG_LEVEL_SILENT") _log_level_name=silent ;;
|
|
*) FATAL "Unknown log level '$_log_level'" ;;
|
|
esac
|
|
|
|
printf "%s\n" "$_log_level_name"
|
|
}
|
|
|
|
# Print log message
|
|
# @param $1 Log level
|
|
# @param $2 Message
|
|
_log_print_message() {
|
|
_log_level=${1:-LOG_LEVEL_FATAL}
|
|
shift
|
|
_log_level_name=
|
|
_log_message=${*:-}
|
|
_log_prefix=
|
|
_log_suffix="\033[0m"
|
|
|
|
# Check log level
|
|
if [ "$LOG_LEVEL" -eq "$LOG_LEVEL_SILENT" ] || [ "$_log_level" -gt "$LOG_LEVEL" ]; then
|
|
return 0
|
|
fi
|
|
|
|
case $_log_level in
|
|
"$LOG_LEVEL_FATAL")
|
|
_log_level_name=FATAL
|
|
_log_prefix="\033[41;37m"
|
|
;;
|
|
"$LOG_LEVEL_WARN")
|
|
_log_level_name=WARN
|
|
_log_prefix="\033[1;33m"
|
|
;;
|
|
"$LOG_LEVEL_INFO")
|
|
_log_level_name=INFO
|
|
_log_prefix="\033[37m"
|
|
;;
|
|
"$LOG_LEVEL_DEBUG")
|
|
_log_level_name=DEBUG
|
|
_log_prefix="\033[1;34m"
|
|
;;
|
|
esac
|
|
|
|
# Check color flag
|
|
if [ "$LOG_COLOR_ENABLE" = false ]; then
|
|
_log_prefix=
|
|
_log_suffix=
|
|
fi
|
|
|
|
# Log
|
|
printf '%b[%-5s] %b%b\n' "$_log_prefix" "$_log_level_name" "$_log_message" "$_log_suffix"
|
|
}
|
|
|
|
# Fatal log message
|
|
# @param $1 Message
|
|
FATAL() {
|
|
_log_print_message "$LOG_LEVEL_FATAL" "$1" >&2
|
|
exit 1
|
|
}
|
|
# Warning log message
|
|
# @param $1 Message
|
|
WARN() { _log_print_message "$LOG_LEVEL_WARN" "$1" >&2; }
|
|
# Informational log message
|
|
# @param $1 Message
|
|
INFO() { _log_print_message "$LOG_LEVEL_INFO" "$1" >&2; }
|
|
# Debug log message
|
|
# @param $1 Message
|
|
DEBUG() { _log_print_message "$LOG_LEVEL_DEBUG" "$1" >&2; }
|
|
|
|
# ================
|
|
# FUNCTIONS
|
|
# ================
|
|
# Show help message
|
|
show_help() {
|
|
cat << EOF
|
|
Usage: $(basename "$0") --in-file <FILE> [--disable-color] [--help] [--log-level <LEVEL>] [--out-file <FILE>] [--overwrite]
|
|
|
|
reCluster bundle script.
|
|
|
|
Options:
|
|
--disable-color Disable color
|
|
|
|
--help Show this help message and exit
|
|
|
|
--in-file <FILE> Input file
|
|
Values:
|
|
Any valid file
|
|
|
|
--log-level <LEVEL> Logger level
|
|
Default: $(to_log_level_name "$LOG_LEVEL")
|
|
Values:
|
|
fatal Fatal level
|
|
warn Warning level
|
|
info Informational level
|
|
debug Debug level
|
|
silent Silent level
|
|
|
|
--out-file <FILE> Output file
|
|
Default: [IN_FILE_NAME].inlined[IN_FILE_EXTENSION]
|
|
Values:
|
|
Any valid file
|
|
|
|
--overwrite Overwrite input file
|
|
EOF
|
|
}
|
|
|
|
# Assert command is installed
|
|
# @param $1 Command name
|
|
assert_cmd() {
|
|
command -v "$1" > /dev/null 2>&1 || FATAL "Command '$1' not found"
|
|
DEBUG "Command '$1' found at '$(command -v "$1")'"
|
|
}
|
|
|
|
# ================
|
|
# CACHE
|
|
# ================
|
|
# Cache
|
|
CACHE=
|
|
|
|
# Add file path to cache
|
|
# @param $1 File path
|
|
cache_add() {
|
|
if [ -z "$CACHE" ]; then
|
|
CACHE=$(printf '%s\n' "$1")
|
|
else
|
|
CACHE=$(printf '%s\n%s\n' "$CACHE" "$1")
|
|
fi
|
|
}
|
|
|
|
# Check cache has file path
|
|
# @param $1 File path
|
|
cache_has() {
|
|
while read -r _entry; do
|
|
if [ "$_entry" = "$1" ]; then
|
|
return 0
|
|
fi
|
|
done << EOF
|
|
$CACHE
|
|
EOF
|
|
|
|
return 1
|
|
}
|
|
|
|
# Inline sources ('source' or '.') of given script file
|
|
# @param $1 Script file path
|
|
inline_sources() {
|
|
_file=$1
|
|
_file_dir=$(dirname "$_file")
|
|
_regex='^([[:space:]]*)(source|\.)[[:space:]]+(.+)'
|
|
_regex_inline_skip='^[[:space:]]*#[[:space:]]*inline[[:space:]]+skip.*'
|
|
_regex_shellcheck='^[[:space:]]*#[[:space:]]*shellcheck[[:space:]]+source=(.+)'
|
|
_inline_skip=false
|
|
_source_file_shellcheck=
|
|
|
|
[ -f "$_file" ] || FATAL "File '$_file' does not exists"
|
|
INFO "Reading file '$_file'"
|
|
|
|
# Add to cache
|
|
cache_add "$_file"
|
|
|
|
# Read
|
|
while IFS='' read -r _line; do
|
|
DEBUG "Analyzing line '$_line'"
|
|
|
|
if printf "%s\n" "$_line" | grep -q -E "$_regex_inline_skip"; then
|
|
# # inline skip
|
|
_inline_skip=true
|
|
DEBUG "Inline skip '$_line'"
|
|
|
|
# Print line
|
|
printf '%s\n' "$_line"
|
|
elif printf "%s\n" "$_line" | grep -q -E "$_regex_shellcheck"; then
|
|
# # shellcheck source=...
|
|
DEBUG "ShellCheck source '$_line'"
|
|
|
|
# Source
|
|
_source_file_shellcheck=$(printf "%s\n" "$_line" | sed -n -r "s/$_regex_shellcheck/\1/p")
|
|
# Print line
|
|
printf '%s\n' "$_line"
|
|
elif printf "%s\n" "$_line" | grep -q -E "$_regex"; then
|
|
# source ...
|
|
# . ...
|
|
DEBUG "Source '$_line'"
|
|
|
|
# Source
|
|
_source_file=$(printf "%s\n" "$_line" | sed -n -r "s/$_regex/\3/p" | sed -e 's/^"//' -e 's/"$//')
|
|
|
|
# Check skip
|
|
[ "$_inline_skip" = false ] || {
|
|
# Skip
|
|
WARN "Skipping source '$_line'"
|
|
# Reset inline skip
|
|
_inline_skip=false
|
|
# Reset shellcheck
|
|
_source_file_shellcheck=
|
|
# Print line
|
|
printf '%s\n' "$_line"
|
|
continue
|
|
}
|
|
|
|
# Resolve source path
|
|
_path=
|
|
if printf "%s\n" "$_source_file" | grep -q -E -v '.*\/.*'; then
|
|
# Search $PATH
|
|
DEBUG "Searching '\$PATH'"
|
|
|
|
_path=$(command -v "$_source_file" || :)
|
|
fi
|
|
if [ -z "$_path" ]; then
|
|
# Resolve links, relative paths, ~, quotes, and escapes
|
|
DEBUG "Path is undefined, continue searching"
|
|
|
|
_path=$_source_file
|
|
if printf "%s\n" "$_path" | grep -q -E -v '^\/|^\$'; then
|
|
# Path does not start with '/' or '$' symbol, preprend directory
|
|
_path="$_file_dir/$_path"
|
|
fi
|
|
|
|
# Canonicalize
|
|
_path=$(eval readlink -f "$_path" || :)
|
|
DEBUG "Path candidate '$_path'"
|
|
|
|
if [ ! -f "$_path" ] && [ -n "$_source_file_shellcheck" ]; then
|
|
# File does not exists, try shellcheck
|
|
DEBUG "Path '$_path' is invalid, searching ShellCheck"
|
|
|
|
_path=$_source_file_shellcheck
|
|
if printf "%s\n" "$_path" | grep -q -E -v '^\/'; then
|
|
# Path does not start with '/' symbol, preprend directory
|
|
_path="$_file_dir/$_path"
|
|
fi
|
|
|
|
# Canonicalize
|
|
_path=$(readlink -f "$_path" || :)
|
|
DEBUG "Path candidate '$_path'"
|
|
# Reset shellcheck
|
|
_source_file_shellcheck=
|
|
fi
|
|
fi
|
|
|
|
# Check path
|
|
[ -f "$_path" ] || FATAL "Unable to resolve source file path '$_source_file'"
|
|
DEBUG "Source '$_source_file' resolved to '$_path'"
|
|
|
|
# Comment source
|
|
printf '# %s\n' "$_line"
|
|
|
|
# Check if already sourced
|
|
! cache_has "$_path" || {
|
|
WARN "Recursion detected, source '$_source_file' of '$_file'"
|
|
kill $SELF_PID
|
|
wait $SELF_PID
|
|
}
|
|
|
|
# Inline source and remove shebang
|
|
inline_sources "$_path" | sed '/^#!.*/d'
|
|
else
|
|
# Reset inline skip
|
|
_inline_skip=false
|
|
# Reset shellcheck
|
|
_source_file_shellcheck=
|
|
# Print line
|
|
printf '%s\n' "$_line"
|
|
fi
|
|
done < "$_file"
|
|
}
|
|
|
|
################################################################################################################################
|
|
|
|
# Parse command line arguments
|
|
# @param $@ Arguments
|
|
parse_args() {
|
|
# Assert argument has a value
|
|
# @param $1 Argument name
|
|
# @param $2 Argument value
|
|
parse_args_assert_value() {
|
|
[ -n "$2" ] || FATAL "Argument '$1' requires a non-empty value"
|
|
}
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case $1 in
|
|
--disable-color)
|
|
# Disable color
|
|
LOG_COLOR_ENABLE=false
|
|
shift
|
|
;;
|
|
--help)
|
|
# Display help message and exit
|
|
show_help
|
|
exit 0
|
|
;;
|
|
--in-file)
|
|
# Input file
|
|
parse_args_assert_value "$@"
|
|
|
|
IN_FILE=$2
|
|
shift
|
|
shift
|
|
;;
|
|
--log-level)
|
|
# Log level
|
|
parse_args_assert_value "$@"
|
|
|
|
case $2 in
|
|
fatal) LOG_LEVEL=$LOG_LEVEL_FATAL ;;
|
|
warn) LOG_LEVEL=$LOG_LEVEL_WARN ;;
|
|
info) LOG_LEVEL=$LOG_LEVEL_INFO ;;
|
|
debug) LOG_LEVEL=$LOG_LEVEL_DEBUG ;;
|
|
silent) LOG_LEVEL=$LOG_LEVEL_SILENT ;;
|
|
*) FATAL "Value '$2' of argument '$1' is invalid" ;;
|
|
esac
|
|
shift
|
|
shift
|
|
;;
|
|
--out-file)
|
|
# Output file
|
|
parse_args_assert_value "$@"
|
|
|
|
OUT_FILE=$2
|
|
shift
|
|
shift
|
|
;;
|
|
--overwrite)
|
|
# Overwrite
|
|
OVERWRITE=true
|
|
shift
|
|
;;
|
|
-*)
|
|
# Unknown argument
|
|
WARN "Unknown argument '$1' is ignored"
|
|
shift
|
|
;;
|
|
*)
|
|
# No argument
|
|
WARN "Skipping argument '$1'"
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Determine output file
|
|
if [ "$OVERWRITE" = true ]; then
|
|
# Input file
|
|
OUT_FILE=$IN_FILE
|
|
elif [ -n "$IN_FILE" ] && [ -z "$OUT_FILE" ]; then
|
|
# Input file 'inlined'
|
|
_in_file_basename=$(basename -- "$IN_FILE")
|
|
_in_file_name="${_in_file_basename%.*}"
|
|
_in_file_extension=
|
|
case $_in_file_basename in
|
|
*.*) _in_file_extension=".${_in_file_basename##*.}" ;;
|
|
esac
|
|
|
|
OUT_FILE="$_in_file_name.inlined$_in_file_extension"
|
|
fi
|
|
}
|
|
|
|
# Verify system
|
|
verify_system() {
|
|
assert_cmd grep
|
|
assert_cmd sed
|
|
|
|
[ -n "$IN_FILE" ] || FATAL "Input file required"
|
|
[ -f "$IN_FILE" ] || FATAL "Input file '$IN_FILE' does not exists"
|
|
if [ "$OVERWRITE" = false ] && [ -f "$OUT_FILE" ]; then FATAL "Output file '$OUT_FILE' already exists"; fi
|
|
}
|
|
|
|
# Inline input file
|
|
inline() {
|
|
INFO "Inlining file '$IN_FILE'"
|
|
_inlined=$(inline_sources "$(readlink -f "$IN_FILE")") || FATAL "Error inlining file '$IN_FILE'"
|
|
|
|
INFO "Saving file '$OUT_FILE'"
|
|
printf '%s\n' "$_inlined" > "$OUT_FILE"
|
|
}
|
|
|
|
# ================
|
|
# CONFIGURATION
|
|
# ================
|
|
# Input file
|
|
IN_FILE=
|
|
# Log level
|
|
LOG_LEVEL=$LOG_LEVEL_INFO
|
|
# Log color flag
|
|
LOG_COLOR_ENABLE=true
|
|
# Output file
|
|
OUT_FILE=
|
|
# Overwrite flag
|
|
OVERWRITE=false
|
|
|
|
# ================
|
|
# MAIN
|
|
# ================
|
|
{
|
|
parse_args "$@"
|
|
verify_system
|
|
inline
|
|
}
|
|
|