#!/usr/bin/env bash
# ---
# This file is automatically generated from imposer.md - DO NOT EDIT
# ---

# MIT License
#
# Copyright (c) 2017 PJ Eby
#
# 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.

set -euo pipefail  # Strict mode
mdsh-parse() {
	local cmd=$1 lno=0 block_start lang mdsh_block ln indent fence close_fence indent_remove
	local mdsh_fence=$'^( {0,3})(~~~+|```+) *([^`]*)$'
	while mdsh-find-block; do
		indent=${BASH_REMATCH[1]} fence=${BASH_REMATCH[2]} lang=${BASH_REMATCH[3]} mdsh_block=
		block_start=$lno close_fence="^( {0,3})$fence+ *\$" indent_remove="^${indent// / ?}"
		while ((lno++)); IFS= read -r ln && ! [[ $ln =~ $close_fence ]]; do
			! [[ $ln =~ $indent_remove ]] || ln=${ln#${BASH_REMATCH[0]}}; mdsh_block+=$ln$'\n'
		done
		lang="${lang%"${lang##*[![:space:]]}"}"; "$cmd" fenced "$lang" "$mdsh_block"
	done
}
mdsh-find-block(){
	while ((lno++)); IFS= read -r ln; do if [[ $ln =~ $mdsh_fence ]]; then return; fi; done; false
}
mdsh-source() {
	local MDSH_FOOTER='' MDSH_SOURCE
	if [[ ${1:--} != '-' ]]; then
		MDSH_SOURCE="$1"
		mdsh-parse __COMPILE__ <"$1"
	else mdsh-parse __COMPILE__
	fi
	${MDSH_FOOTER:+ printf %s "$MDSH_FOOTER"}; MDSH_FOOTER=
}
mdsh-compile() (  # <-- force subshell to prevent escape of compile-time state
	mdsh-safe-subshell mdsh-source "$@"
)
__COMPILE__() {
	[[ $1 == fenced && $fence == $'```' && ! $indent ]] || return 0  # only unindented ``` code
	local mdsh_tag=$2 mdsh_lang tag_words
	mdsh-splitwords "$2" tag_words  # check for command blocks first
	case ${tag_words[1]-} in
	'') mdsh_lang=${tag_words[0]-} ;;  # fast exit for common case
	'@'*)
		mdsh_lang=${tag_words[1]#@} ;; # language alias: fall through to function lookup
	'!'*)
		mdsh_lang=${tag_words[0]}; set -- "$3" "$2" "$block_start"; eval "${2#*!}"; return
		;;
	'+'*)
		printf 'mdsh_lang=%q; %s %q\n' "${tag_words[0]}" "${2#"${tag_words[0]}"*+}" "$3"
		return
		;;
	'|'*)
		printf 'mdsh_lang=%q; ' "${tag_words[0]}"
		echo "${2#"${tag_words[0]}"*|} <<'\`\`\`'"; printf $'%s```\n' "$3"
		return
		;;
	*)  mdsh_lang=${2//[^_[:alnum:]]/_}  # convert entire line to safe variable name
	esac
	mdsh-emit-block
}
mdsh-block() {
	local mdsh_lang=${1-${mdsh_lang-}} mdsh_block=${2-${mdsh_block-}}
	local block_start=${3-${block_start-}} mdsh_tag=${4-${mdsh_lang-}} tag_words
	mdsh-splitwords "$mdsh_tag" tag_words; mdsh-emit-block
}
mdsh-emit-block() {
	if fn-exists "mdsh-lang-$mdsh_lang"; then
		mdsh-rewrite "mdsh-lang-$mdsh_lang" "{" "} <<'\`\`\`'"; printf $'%s```\n' "$mdsh_block"
	elif fn-exists "mdsh-compile-$mdsh_lang"; then
		"mdsh-compile-$mdsh_lang" "$mdsh_block" "$mdsh_tag" "$block_start"
	else
		mdsh-misc "$mdsh_tag" "$mdsh_block"
	fi
	if fn-exists "mdsh-after-$mdsh_lang"; then
		mdsh-rewrite "mdsh-after-$mdsh_lang"
	fi
}
# split words in $1 into the array named by $2 (REPLY by default), without wildcard expansion
# shellcheck disable=SC2206  # set -f is in effect
mdsh-splitwords() {
	local f=$-; set -f;  if [[ ${2-} ]]; then eval "$2"'=($1)'; else REPLY=($1); fi
	[[ $1 == *f* ]] || set +f
}
# fn-exists: succeed if argument is a function
fn-exists() { declare -F -- "$1"; } >/dev/null
# Output body of func $1, optionally replacing the opening/closing { and } with $2 and $3
mdsh-rewrite() {
	local b='}' r; r="$(declare -f -- "$1")"; r=${r#*{ }; r=${r%\}*}; echo "${2-{}$r${3-$b}"
}
mdsh-misc()          { mdsh-data "$@"; }    # Treat unknown languages as data
mdsh-compile-()      { :; }                 # Ignore language-less blocks

mdsh-compile-mdsh()  { eval "$1"; }         # Execute `mdsh` blocks in-line
mdsh-compile-mdsh_main() { ! @is-main || eval "$1"; }

mdsh-compile-shell() { printf '%s' "$1"; }  # Copy `shell` blocks to the output
mdsh-compile-shell_main() { ! @is-main || printf '%s' "$1"; }
mdsh-data() {
	printf 'mdsh_raw_%s+=(%q)\n' "${1//[^_[:alnum:]]/_}" "$2"
}
mdsh-compile-shell_mdsh() {
	indent='' fence=$'```' __COMPILE__ fenced mdsh "$1"
}
mdsh-compile-shell_mdsh_main() {
	indent='' fence=$'```' __COMPILE__ fenced "mdsh main" "$1"
}
# Main program: check for arguments and run markdown script
mdsh-main() {
	(($#)) || mdsh-error "Usage: %s [--out FILE] [ --compile | --eval ] markdownfile [args...]" "${0##*/}"
	case "$1" in
	--) mdsh-interpret "${@:2}" ;;
	--*|-?) fn-exists "mdsh.$1" || mdsh-error "%s: unrecognized option: %s" "${0##*/}" "$1"
		"mdsh.$1" "${@:2}"
		;;
	-??*) mdsh-main "${1::2}" "-${1:2}" "${@:2}" ;;  # split '-abc' into '-a -bc' and recurse
	*)  mdsh-interpret "$@" ;;
	esac
}
# Run markdown file as main program, with $0 == $BASH_SOURCE == "" and
# MDSH_ZERO pointing to the original $0.

function mdsh-interpret() {
	printf -v cmd $'eval "$(%q --compile %q)"' "$0" "$1"
	MDSH_ZERO="$1" exec bash -c "$cmd" "" "${@:2}"
}
mdsh.--compile() {
	(($#)) || mdsh-error "Usage: %s --compile FILENAME..." "${0##*/}"
	! fn-exists mdsh:file-header || mdsh:file-header
	for REPLY; do mdsh-compile "$REPLY"; done
	! fn-exists mdsh:file-footer || mdsh:file-footer
}

mdsh.-c() { mdsh.--compile "$@"; }
mdsh.--eval() {
	{ (($# == 1)) && [[ $1 != - ]]; } ||
		mdsh-error "Usage: %s --eval FILENAME" "${0##*/}"
	mdsh.--compile "$1"
	echo $'__status=$? eval \'return $__status || exit $__status\' 2>/dev/null'
}

mdsh.-E() { mdsh.--eval "$@"; }
mdsh.--out() {
	REPLY=("$(mdsh-safe-subshell mdsh-main "${@:2}")")
	mdsh-ok && exec echo "$REPLY" >"$1"   # handle self-compiling properly
}

mdsh.-o() { mdsh.--out "$@"; }
# mdsh-error: printf args to stderr and exit w/EX_USAGE (code 64)
# shellcheck disable=SC2059  # argument is a printf format string
mdsh-error() { exit 64 "$1" "${2-}" "${@:3}"; }
mdsh.--help() {
	printf 'Usage: %s [--out FILE] [ --compile | --eval ] markdownfile [args...]\n' "${0##*/}"
	echo $'
Run and/or compile code blocks from markdownfile(s) to bash.
Use a filename of `-` to run or compile from stdin.

Options:
  -h, --help                Show this help message and exit
  -c, --compile MDFILE...   Compile MDFILE(s) to bash and output on stdout.
  -E, --eval MDFILE         Compile one file w/a shelldown-support footer line\n'
}

mdsh.-h() { mdsh.--help "$@"; }
MDSH_LOADED_MODULES=
MDSH_MODULE=

@require() {
	flatname "$1"
	if ! [[ $MDSH_LOADED_MODULES == *"<$REPLY>"* ]]; then
		MDSH_LOADED_MODULES+="<$REPLY>"; local MDSH_MODULE=$1
		if (($#<2)); then
			REPLY="@provide-$1"
			fn-exists "$REPLY" || exit 70 \
				"No @provide defined for module $1 at line $(caller)"
			"$REPLY"
		else "${@:2}"
		fi
	fi
}
@provide() {
	if (($#<2)); then exit 64 \
		"No command given for @provide at line $(caller)"
	elif flatname "$1"; ! [[ $MDSH_LOADED_MODULES == *"<$REPLY>"* ]]; then
		printf -v REPLY "%q " "${@:2}"; eval "@provide-$1(){ $REPLY; }"
	else exit 70 \
		"Module $1 already loaded; attempted redefinition at line $(caller)"
	fi
}
@is-main() { ! [[ $MDSH_MODULE ]]; }
@module() {
	@is-main || return 0
	set -- "${1:-${MDSH_SOURCE-}}"
	echo "#!/usr/bin/env bash"
	echo "# ---"
	echo "# This file is automatically generated from ${1##*/} - DO NOT EDIT"
	echo "# ---"
	echo
}
@main() {
	@is-main || return 0
	MDSH_FOOTER=$'if [[ $0 == "${BASH_SOURCE-}" ]]; then '"$1"$' "$@"; exit; fi\n'
}
@comment() (  # subshell for cd
	! [[ "${MDSH_SOURCE-}" == */* ]] || cd "${MDSH_SOURCE%/*}"
	sed -e 's/^\(.\)/# \1/; s/^$/#/;' "$@"
	echo
)
# shellcheck disable=2059
exit() {
	set -- "${1-$?}" "${@:2}"
	case $# in 0|1) : ;; 2) printf '%s\n' "$2" ;; *) printf "$2\\n" "${@:3}" ;; esac >&2
	builtin exit "$1"
}
mdsh-safe-subshell() { set -E; trap exit ERR; "$@"; }
mdsh-ok(){ return $?;}
mdsh-embed() {
	local f=$1 base=${1##*/}; local boundary="# --- EOF $base ---" contents ctr=
	[[ $f == */* && -f $f ]] || f=$(command -v "$f") || {
		echo "Can't find module $1" >&2; return 69  # EX_UNAVAILABLE
	}
	contents=$'\n'$(<"$f")$'\n'
	while [[ $contents == *$'\n'"$boundary"$'\n'* ]]; do
		((ctr++)); boundary="# --- EOF $base.$ctr ---"
	done
	printf $'{ if [[ $OSTYPE != cygwin && $OSTYPE != msys && -e /dev/fd/0 ]]; then source /dev/fd/0; else source <(cat); fi; } <<\'%s\'%s%s\n' "$boundary" "$contents" "$boundary"
}
mdsh-make() {
	[[ -f "$1" && -f "$2" && ! "$1" -nt "$2" && ! "$1" -ot "$2" ]] || {
		( mdsh-safe-subshell "${@:3}" && mdsh-main --out "$2" --compile "$1" ); mdsh-ok && touch -r "$1" "$2"
	}
}
mdsh-cache() {
	[[ -d "$1" ]] || mkdir -p "$1"
	flatname "${3:-$2}"; REPLY="$1/$REPLY"; mdsh-make "$2" "$REPLY" "${@:4}"
}
flatname() {
	REPLY="${1//\%/%25}"; REPLY="${REPLY//\//%2F}"; REPLY="${REPLY/#./%2E}"
	REPLY="${REPLY//</%3C}"; REPLY="${REPLY//>/%3E}"
	REPLY="${REPLY//\\/%5C}"
}
MDSH_CACHE=
mdsh-use-cache() {
	if ! (($#)); then
		set -- "${XDG_CACHE_HOME:-${HOME:+$HOME/.cache}}"
		set -- "${1:+$1/mdsh}"
	fi
	MDSH_CACHE="$1"
}
mdsh-use-cache
mdsh-run() {
	if [[ ${MDSH_CACHE-} ]]; then
		mdsh-cache "$MDSH_CACHE" "$1" "${2-}"
		mdsh-ok && source "$REPLY" "${@:3}"
	else run-markdown "$1" "${@:3}"
	fi
}
# run-markdown file args...
# Compile `file` and source the result, passing along any positional arguments
run-markdown() {
	REPLY=("$(set -e; mdsh-source "${1--}")"); mdsh-ok || return
	if [[ $BASH_VERSINFO == 3 ]]; then # bash 3 can't source from proc
		# shellcheck disable=SC1091  # shellcheck shouldn't try to read stdin
		source /dev/fd/0 "${@:2}" <<<"$REPLY"
	else source <(echo "$REPLY") "${@:2}"
	fi
}
mdsh_raw_bash_runtime+=($'#!/usr/bin/env bash\n\n# --- BEGIN jqmd runtime ---\n')
mdsh_raw_bash_runtime+=($'jqmd_imports=\njqmd_filters=\njqmd_defines=\n\nHAVE_FILTERS() { [[ ${jqmd_filters-} ]]; }\nCLEAR_FILTERS() { unset jqmd_filters; JQ_OPTS=(jq); }\n\nIMPORTS() { jqmd_imports+="${jqmd_imports:+$\'\\n\'}$1"; }\nDEFINE()  { jqmd_defines+="${jqmd_defines:+$\'\\n\'}$1"; }\nFILTER()  {\n\tcase $# in\n\t1) jqmd_filters+="${jqmd_filters:+$\'\\n\'| }$1"; return ;;\n\t0) return ;;\n\tesac\n\tlocal REPLY ARGS=(printf -v REPLY "$1"); shift\n\tJSON-QUOTE "$@"; ARGS+=("${REPLY[@]}"); "${ARGS[@]}"; FILTER "$REPLY"\n}\n\nAPPLY() {\n\tlocal name filter=\'\' REPLY expr=${1-} lf=$\'\\n\'; shift\n\twhile (($#)); do\n\t\tname=${1#@}; REPLY=${name#*=}\n\t\t[[ $name == *=* ]] || REPLY=${!name}\n\t\tif ((${#REPLY} > 32)); then\n\t\t\tif [[ $1 == @* ]]; then ARGVAL "$REPLY"; else ARGSTR "$REPLY"; fi\n\t\telse\n\t\t\tJSON-QUOTE "$REPLY"; [[ $1 != @* ]] || REPLY="($REPLY|fromjson)"\n\t\tfi\n\t\tfilter+=" | $REPLY as \\$${name%%=*}"; shift\n\tdone\n\tfilter=${filter:3}; [[ ! $expr || $expr == . ]] || filter="( ${filter:+$filter | }$expr )"\n\t${filter:+FILTER "$filter"}\n}\n')
mdsh_raw_bash_runtime+=($'JSON-QUOTE() {\n\tlocal LC_ALL=C\n\t[[ $* != *\\\\* ]] || set -- "${@//\\\\/\\\\\\\\}"\n\t[[ $* != *\'"\'* ]] || set -- "${@//\\"/\\\\\\"}"\n\tset -- "${@/#/\\"}"; set -- "${@/%/\\"}"\n\tif [[ $* != *[$\'\\x01\'-$\'\\x1F\']* ]]; then REPLY=("$@"); else escape-ctrlchars "$@"; fi\n}\n\nJSON-LIST() {\n\t(($#)) || { REPLY=("[]"); return; }\n\tJSON-QUOTE "$@"; REPLY=${REPLY[*]/%/,}; REPLY=("[${REPLY%,}]")\n}\n\nJSON-MAP() {\n\tlocal -n v=$1; local LC_ALL=C n=${v[@]+${#v[@]}}; ((${n:-0})) || { REPLY=("{}"); return; }\n\tlocal i=1 p=\'\\"${@:i:1}\\": \\"${@: \'"$n"\'+ i++:1}\\",\'; p="${v[*]/#*/$p}"\n\tset -- "${!v[@]}" "${v[@]}"\n\t[[ $* != *\\\\* ]] || set -- "${@//\\\\/\\\\\\\\}"\n\t[[ $* != *\\"* ]] || set -- "${@//\\"/\\\\\\"}"\n\teval "REPLY=(\\"{${p%,}}\\")"\n\t[[ $REPLY != *[$\'\\x01\'-$\'\\x1F\']* ]] || escape-ctrlchars "$REPLY"\n}\n\nJSON-KV() {\n\t(($#)) || { REPLY=("{}"); return; }\n\tlocal LC_ALL=C i=0 p=\'\\"${REPLY[i]}\\": \\"${REPLY[\'"$#"\'+i++]}\\",\'; p="${*/#*/$p}"\n\t[[ $* != *\\\\* ]] || set -- "${@//\\\\/\\\\\\\\}"\n\t[[ $* != *\\"* ]] || set -- "${@//\\"/\\\\\\"}"\n\tREPLY=("${@%%=*}" "${@#*=}"); eval "REPLY=(\\"{${p%,}}\\")"\n\t[[ $REPLY != *[$\'\\x01\'-$\'\\x1F\']* ]] || escape-ctrlchars "$REPLY"\n}\n\nescape-ctrlchars() {\n\tlocal LC_ALL=C\n\tset -- "${@//$\'\\n\'/\\\\n}"; set -- "${@//$\'\\r\'/\\\\r}"; set -- "${@//$\'\\t\'/\\\\t}"  # \\n\\r\\t\n\tif [[ $* == *[$\'\\x01\'-$\'\\x1F\']* ]]; then\n\t\tlocal r s=$*; s=${s//[^$\'\\x01\'-$\'\\x1F\']}\n\t\twhile [[ $s ]]; do\n\t\t\tprintf -v r \\\\\\\\u%04x "\'${s:0:1}"; set -- "${@//${s:0:1}/$r}"\n\t\t\ts=${s//${s:0:1}/}\n\t\tdone\n\tfi\n\tREPLY=("$@")\n}\n\n')
mdsh_raw_bash_runtime+=($'JQ_CMD=(jq)\nJQ_OPTS=(jq)\nJQ_OPTS() { JQ_OPTS+=("$@"); }\nARG()     { JQ_OPTS --arg     "$1" "$2"; }\nARGJSON() { JQ_OPTS --argjson "$1" "$2"; }\nARGQUOTE() { ARGSTR "$1"; }  # deprecated\nARGSTR() { REPLY=JQMD_QA_${#JQ_OPTS[@]}; ARG     "$REPLY" "$1"; REPLY=\'$\'$REPLY; }\nARGVAL() { REPLY=JQMD_JA_${#JQ_OPTS[@]}; ARGJSON "$REPLY" "$1"; REPLY=\'$\'$REPLY; }\n')
mdsh_raw_bash_runtime+=($'JQ_CMD() {\n\tlocal f= opt nargs cmd=("${JQ_CMD[@]}"); set -- "${JQ_OPTS[@]:1}" "$@"\n\n\twhile (($#)); do\n\t\tcase "$1" in\n\t\t-f|--fromfile)\n\t\t\topt=$(<"$2") || return 69\n\t\t\tFILTER "$opt"; shift 2; continue\n\t\t\t;;\n\t\t-L|--indent)                            nargs=2 ;;\n\t\t--arg|--argjson|--slurpfile|--argfile)  nargs=3 ;;\n\t\t--)  break   ;; # rest of args are data files\n\t\t-*)  nargs=1 ;;\n\t\t*)   FILTER "$1"; break ;;\t# jq program: data files follow\n\t\tesac\n\t\tcmd+=("${@:1:$nargs}")\t# add $nargs args to cmd\n\t\tshift $nargs\n\tdone\n\n\tHAVE_FILTERS || FILTER .    # jq needs at least one filter expression\n\tfor REPLY in "${jqmd_imports-}" "${jqmd_defines-}" "${jqmd_filters-}"; do\n\t\t[[ $REPLY ]] && f+=${f:+$\'\\n\'}$REPLY\n\tdone\n\n\tREPLY=("${cmd[@]}" "$f" "${@:2}")\n\tCLEAR_FILTERS   # cleanup for any re-runs\n}\n\nRUN_JQ() { JQ_CMD "$@" && "${REPLY[@]}"; }\nCALL_JQ() { JQ_CMD "$@" && REPLY=("$("${REPLY[@]}")"); }\n')
mdsh_raw_bash_runtime+=($'YAML()  { y2j "$1"; JSON "$REPLY"; }\nJSON()  { FILTER "jqmd_data($1)" "${@:2}"; }\n')
mdsh_raw_bash_runtime+=($'DEFINE \'\ndef jqmd::blend($other; combine): . as $this | . *  $other | . as $combined | with_entries(\n  if (.key | in($this)) and (.key | in($other)) then\n    .this = $this[.key] | .other = $other[.key] | combine\n  else . end\n);\n\ndef jqmd::combine: (.this|type) as $this | (.other|type) as $other | .value =\n  if $this == "array" then\n    if $other == "array" then .this + .other else .this + [.other] end\n  elif $this == "object" then\n    if $other == "object" then\n      .other as $o | (.this | jqmd::blend($o; jqmd::combine))\n    else .other end\n  else .other end;  # everything else just overrides\n\ndef jqmd::data($data): {this: ., other:$data} | jqmd::combine | .value ;\ndef jqmd_data($data): jqmd::data($data) ;\n\'\n')
mdsh_raw_bash_runtime+=($'y2j() {\n\tlocal p j; j="$(echo "$1" | yaml2json)" || return $?; REPLY=\n\twhile [[ $j == *\'\\\\(\'* ]]; do\n\t\tp=${j%%\'\\\\(\'*}; j=${j#"$p"\'\\\\(\'}\n\t\tif [[ $p =~ (^|[^\\\\])(\'\\\\\\\\\')*$ ]]; then\n\t\t\tp="${p}"\'\\(\' # odd, unbalance the backslash\n\t\telse\n\t\t\tp="${p}("   # even, remove one actual backslash\n\t\tfi\n\t\tREPLY+=$p\n\tdone\n\tREPLY+=$j\n}\n')
mdsh_raw_bash_runtime+=($'yaml2json:cmd() { command yaml2json /dev/stdin; }\n\nyaml2json:py() {\n\tpython -c \'import sys, yaml, json; json.dump(yaml.safe_load(sys.stdin), sys.stdout)\'\n}\n\nyaml2json:php() { command yaml2json.php; }\n\nyaml2json() {\n\tlocal kind  # auto-select between available yaml2json implementations\n\tfor kind in cmd py php; do\n\t\tREPLY=($(yaml2json:$kind < <(echo "a: {}") 2>/dev/null || true))\n\t\tprintf -v REPLY %s ${REPLY+"${REPLY[@]}"}\n\t\tif [[ "$REPLY" == \'{"a":{}}\' ]]; then\n\t\t\teval "yaml2json() { yaml2json:$kind; }"; yaml2json; return\n\t\tfi\n\tdone\n\texit 69 "To process YAML, must have one of: yaml2json, PyYAML, or yaml2json.php" # EX_UNAVAILABLE\n}\n\n# --- END jqmd runtime ---\n')
# Language Support
mdsh-compile-jq()         { printf 'FILTER %q\n' "$1"$'\n'; }
mdsh-compile-jq_defs()    { printf 'DEFINE %q\n' "$1"$'\n'; }
mdsh-compile-jq_imports() { printf 'IMPORTS %q\n' "$1"$'\n'; }

mdsh-compile-yml()  { y2j "$1"; mdsh-compile-json "$REPLY"; }
mdsh-compile-yaml() { y2j "$1"; mdsh-compile-json "$REPLY"; }
mdsh-compile-json() { mdsh-compile-jq "jqmd_data($1)"; }

mdsh-compile-func() {
	case ${tag_words-} in
		yml|yaml) y2j "$1"; REPLY="jqmd_data($REPLY)"$'\n' ;;
		json*) REPLY="jqmd_data($1)"$'\n' ;;
		jq|javascript|js) REPLY=$1 ;;
		*) mdsh-error "Invalid language for function: '%s'" "${tag_words-}"; return
	esac
	printf 'function %s() {\n\tAPPLY %q \\\n\t\t%s\n}\n' "${tag_words[2]}" "$REPLY" \
		"${2#*${tag_words}*${tag_words[1]}*${tag_words[2]}}"
}

const() {
	case "${mdsh_lang-}" in
	yaml|yml) y2j "$mdsh_block"; printf 'DEFINE %q\n' "def $1: $REPLY ;"$'\n' ;;
	json)     printf 'DEFINE %q\n' "def $1: $mdsh_block ;"$'\n' ;;
	*) mdsh-error "Invalid language for constant: '%s'" "${mdsh_lang-}"
	esac
}
# Load the runtime so it's usable by mdsh
printf -v REPLY '%s\n' "${mdsh_raw_bash_runtime[@]}"; eval "$REPLY"

# Add runtime to the top of compiled (main) scripts
printf -v REPLY 'mdsh:file-header() { ! @is-main || echo -n %q; }' "$REPLY"; eval "$REPLY"

# Ensure (main) scripts process any leftover filters at end
mdsh:file-footer() { ! @is-main || echo $'if [[ $0 == "${BASH_SOURCE[0]-}" ]] && HAVE_FILTERS; then RUN_JQ; fi'; }
mdsh.--no-runtime() ( unset -f mdsh:file-header mdsh:file-footer; mdsh-main "$@"; )
mdsh.--yaml() (
	fn-exists "yaml2json:${1-}" || exit $? "No such yaml2json processor: ${1-}"
	eval 'yaml2json() { yaml2json:'"$1"'; }'
	mdsh-main "${@:2}"
)

mdsh.-R() { mdsh.--no-runtime "$@"; }
mdsh.-y() { mdsh.--yaml "$@"; }
#!/usr/bin/env bash

realpath.location(){ realpath.follow "$1"; realpath.absolute "$REPLY" ".."; }
realpath.resolved(){ realpath.follow "$1"; realpath.absolute "$REPLY"; }
realpath.dirname() { REPLY=.; ! [[ $1 =~ /+[^/]+/*$ ]] || REPLY="${1%${BASH_REMATCH[0]}}"; REPLY=${REPLY:-/}; }
realpath.basename(){ REPLY=/; ! [[ $1 =~ /*([^/]+)/*$ ]] || REPLY="${BASH_REMATCH[1]}"; }

realpath.follow() {
	local target
	while [[ -L "$1" ]] && target=$(readlink -- "$1"); do
		realpath.dirname "$1"
		# Resolve relative to symlink's directory
		[[ $REPLY != . && $target != /* ]] && REPLY=$REPLY/$target || REPLY=$target
		# Break out if we found a symlink loop
		for target; do [[ $REPLY == "$target" ]] && break 2; done
		# Add to the loop-detect list and tail-recurse
		set -- "$REPLY" "$@"
	done
	REPLY="$1"
}

realpath.absolute() {
	REPLY=$PWD; local eg=extglob; ! shopt -q $eg || eg=; ${eg:+shopt -s $eg}
	while (($#)); do case $1 in
		//|//[^/]*) REPLY=//; set -- "${1:2}" "${@:2}" ;;
		/*) REPLY=/; set -- "${1##+(/)}" "${@:2}" ;;
		*/*) set -- "${1%%/*}" "${1##${1%%/*}+(/)}" "${@:2}" ;;
		''|.) shift ;;
		..) realpath.dirname "$REPLY"; shift ;;
		*) REPLY="${REPLY%/}/$1"; shift ;;
	esac; done; ${eg:+shopt -u $eg}
}

realpath.canonical() {
	realpath.follow "$1"; set -- "$REPLY"   # $1 is now resolved
	realpath.basename "$1"; set -- "$1" "$REPLY"   # $2 = basename $1
	realpath.dirname "$1"
	[[ $REPLY != "$1" ]] && realpath.canonical "$REPLY"; # recurse unless root
	realpath.absolute "$REPLY" "$2";   # combine canon parent w/basename
}

realpath.relative() {
	local target=""
	realpath.absolute "$1"; set -- "$REPLY" "${@:2}"; realpath.absolute "${2-$PWD}" X
	while realpath.dirname "$REPLY"; [[ "$1" != "$REPLY" && "$1" == "${1#${REPLY%/}/}" ]]; do
		target=../$target
	done
	[[ $1 == "$REPLY" ]] && REPLY=${target%/} || REPLY="$target${1#${REPLY%/}/}"
	REPLY=${REPLY:-.}
}
# For documentation, see https://github.com/bashup/loco

set -euo pipefail

fn_exists() { declare -F -- "$1"; } >/dev/null
fn_copy()   { REPLY="$(declare -f "$1")"; eval "$2 ${REPLY#$1}"; }
findup()    { walkup "${1:-$PWD}" reply_if_exists "${@:2}"; }

reply_if_exists() {
    local pat dir=$1 IFS= ; shift
    for pat; do
        for REPLY in ${dir%/}/$pat; do [[ -f "$REPLY" ]] && return 0; done
    done
    return 1
}

walkup() {
    realpath.absolute "$1"
    until set -- "$REPLY" "${@:2}"; "$2" "$1" "${@:3}"; do
        [[ "$1" != "/" ]] || return 1; realpath.dirname "$1"
    done
}

_loco_usage() { loco_error "Usage: $LOCO_COMMAND command args..."; }
_loco_error() { echo "$@" >&2; exit 64; }
_loco_cmd() { REPLY="$LOCO_NAME.$1"; }
_loco_exec() { loco_error "Unrecognized command: $1"; }
_loco_exists() { type -t "$1"; } >/dev/null

_loco_do() {
    [[ "${1-}" ]] || loco_usage   # No command given, exit w/usage
    REPLY=""; loco_cmd "$1"; local cmd="$REPLY"
    [[ "$cmd" ]] || loco_usage   # Unrecognized command, exit w/usage

    if loco_exists "$cmd"; then
        # Command, alias, function, or builtin exists
        shift; "$cmd" "$@"
    else
        # Invoke the default command interpreter
        loco_exec "$@"
    fi
}

_loco_findproject() {
    # shellcheck disable=SC2015  # plain var assign can't be false
    findup "$LOCO_PWD" "${LOCO_FILE[@]}" && LOCO_PROJECT=$REPLY ||
        loco_error "Can't find $LOCO_FILE here";
}
_loco_preconfig() { true; }
_loco_postconfig() { true; }
_loco_findroot() { realpath.dirname "$LOCO_PROJECT"; LOCO_ROOT=$REPLY; }
_loco_loadproject() { cd "$LOCO_ROOT";  $LOCO_LOAD "$1"; }
_loco_site_config() { source "$1"; }
_loco_user_config() { source "$1"; }


# Find our configuration, exposing relevant paths and defaults

# shellcheck disable=SC2034  # some vars are only used by extending scripts
_loco_config() {
    LOCO_ARGS=("$@")
    loco_preconfig "$@"
    ${LOCO_COMMAND:+:} realpath.basename "$LOCO_SCRIPT"; LOCO_COMMAND="${LOCO_COMMAND-$REPLY}"
    LOCO_NAME="${LOCO_NAME-${LOCO_COMMAND}}"
    LOCO_PWD="${LOCO_PWD-$PWD}"

    LOCO_SITE_CONFIG="${LOCO_SITE_CONFIG-/etc/$LOCO_NAME/config}"
    [ -f "$LOCO_SITE_CONFIG" ] && loco_site_config "$LOCO_SITE_CONFIG"
    LOCO_RC="${LOCO_RC-.${LOCO_NAME}rc}"
    LOCO_USER_CONFIG="${LOCO_USER_CONFIG-$HOME/$LOCO_RC}"
    [ -f "$LOCO_USER_CONFIG" ] && loco_user_config "$LOCO_USER_CONFIG"

    [[ ${LOCO_FILE-} ]] || LOCO_FILE=(".$LOCO_NAME")
    LOCO_LOAD="${LOCO_LOAD-source}"
    loco_postconfig "$@"
}

_loco_main() {
    loco_config "$@"
    fn_exists "$LOCO_NAME" || eval "$LOCO_NAME() { loco_do \"\$@\"; }"
    ${LOCO_PROJECT:+:} loco_findproject "$@"
    ${LOCO_ROOT:+:}    loco_findroot "$@"
    loco_loadproject "$LOCO_PROJECT"
    loco_do "$@"
}

# Initialize default function implementations
for f in $(compgen -A function _loco_); do
    fn_exists "${f#_}" || fn_copy "$f" "${f#_}"
done

# Clear all LOCO_*  variables before beginning
for lv in ${!LOCO_@}; do unset "$lv"; done

LOCO_SCRIPT=$0
__dotenv=
__dotenv_file=
__dotenv_cmd=.env

.env() {
	REPLY=()
	[[ $__dotenv_file || ${1-} == -* ]] || .env.--file .env || return
	if declare -F -- ".env.${1-}" >/dev/null; then .env."$@"; return ; fi
	.env --help >&2; return 64
}

.env.-f() { .env.--file "$@"; }

.env.get() {
	.env::arg "get requires a key" "$@" &&
	[[ "$__dotenv" =~ ^(.*(^|$'\n'))([ ]*)"$1="(.*)$ ]] &&
	REPLY=${BASH_REMATCH[4]%%$'\n'*} && REPLY=${REPLY%"${REPLY##*[![:space:]]}"}
}

.env.parse() {
	local line key
	while IFS= read -r line; do
		line=${line#"${line%%[![:space:]]*}"}  # trim leading whitespace
		line=${line%"${line##*[![:space:]]}"}  # trim trailing whitespace
		if [[ ! "$line" || "$line" == '#'* ]]; then continue ; fi
		if (($#)); then
			for key; do
				if [[ $key == "${line%%=*}" ]]; then REPLY+=("$line"); break;
				fi
			done
		else
			REPLY+=("$line")
		fi
	done <<<"$__dotenv"
	((${#REPLY[@]}))
}

.env.export() {	! .env.parse "$@" || export "${REPLY[@]}"; }

.env.set() {
	.env::file load || return ; local key saved=$__dotenv
	while (($#)); do
		key=${1#+}; key=${key%%=*}
		if .env.get "$key"; then
			REPLY=()
			if [[ $1 == +* ]]; then shift; continue  # skip if already found
			elif [[ $1 == *=* ]]; then
				__dotenv=${BASH_REMATCH[1]}${BASH_REMATCH[3]}$1$'\n'${BASH_REMATCH[4]#*$'\n'}
			else
				__dotenv=${BASH_REMATCH[1]}${BASH_REMATCH[4]#*$'\n'}
				continue   # delete all occurrences
			fi
		elif [[ $1 == *=* ]]; then
			__dotenv+="${1#+}"$'\n'
		fi
		shift
	done
	[[ $__dotenv == "$saved" ]] || .env::file save
}

.env.puts() { echo "${1-}">>"$__dotenv_file" && __dotenv+="$1"$'\n'; }

.env.generate() {
	.env::arg "key required for generate" "$@" || return
	.env.get "$1" && return || REPLY=$("${@:2}") || return
	.env::one "generate: ouptut of '${*:2}' has more than one line" "$REPLY" || return
	.env.puts "$1=$REPLY"
}

.env.--file() {
	.env::arg "filename required for --file" "$@" || return
	__dotenv_file=$1; .env::file load || return
	(($#<2)) || .env "${@:2}"
}

.env::arg() { [[ "${2-}" ]] || { echo "$__dotenv_cmd: $1" >&2; return 64; }; }

.env::one() { [[ "$2" != *$'\n'* ]] || .env::arg "$1"; }

.env::file() {
	local REPLY=$__dotenv_file
	case "$1" in
	load)
		__dotenv=; ! [[ -f "$REPLY" ]] || __dotenv="$(<"$REPLY")"$'\n' || return ;;
	save)
		if [[ -L "$REPLY" ]] && declare -F -- realpath.resolved >/dev/null; then
			realpath.resolved "$REPLY"
		fi
		{ [[ ! -f "$REPLY" ]] || cp -p "$REPLY" "$REPLY.bak"; } &&
		printf %s "$__dotenv" >"$REPLY.bak" && mv "$REPLY.bak" "$REPLY"
	esac
}
event(){ case $1 in error|quote|encode|decode);; *)
	__ev.encode "${2-}";local f n='' e=bashup_event_$REPLY'[1]';f=${e/event/flag}
	case $1 in emit) shift;${!f-};eval "${!e-}"; return ;;on|once|off|has)
		case "${3-}" in @_) n='$#';; @*[^0-9]*);; @[0-9]*) n=$((${3#@}));; esac; ${n:+
		set -- "$1" "$2" "${@:4}" }
		case $1/$# in
			on*/[12]) set -- error "${2-}: missing callback";; */[12]) REPLY=;;
			*) __ev.quote "${@:3}";((${n/\$#/1}))&&REPLY+=' "${@:2:'"$n"'}"';REPLY+=$'\n'
		esac
	esac
esac ;__ev."$@";}
__ev.error(){ echo "$1">&2;return "${2:-64}";}
__ev.quote(){ REPLY=; ${@+printf -v REPLY ' %q' "$@"}; REPLY=${REPLY# };}
__ev.has(){ [[ ${!e-} && $'\n'"${!e}" == *$'\n'"$REPLY"* && ! ${!f-} ]];}
__ev.get(){ ${!f-};REPLY=${!e-};}
__ev.on(){ __ev.has && return;if [[ ! ${!f-} ]];then eval "$e"+='$REPLY';else eval "${!e-};$REPLY";fi;}
__ev.off(){ __ev.has||return 0; n="${!e}"; n=${REPLY:+"${n#"$REPLY"}"}; eval "$e"=$'"${n//\n"$REPLY"/\n}"';[[ ${!e} ]]||unset "${e%\[1]}";}
__ev.fire(){ ${!f-};set -- "$e" "${@:2}"; while [[ ${!1-} ]];do eval "unset ${1%\[1]};${!1}"; done ;}
__ev.all(){ ${!f-};e=${!e-};eval "${e//$'\n'/||return; }";}
__ev.any(){ ${!f-};e=${!e-};eval "${e//$'\n'/&&return|| } ! :";}
__ev.resolve(){
	${!f-};__ev.fire "$@";__ev.quote "$@"
	printf -v n "eval __ev.error 'event \"%s\" already resolved' 70;return" "$1"; eval "${f}"='$n'
	printf -v n 'set -- %s' "$REPLY"; eval "${e}"='$n';readonly "${f%\[1]}" "${e%\[1]}"
}
__ev.resolved(){ [[ ${!f-} ]];}
__ev.once(){ n=${n:-0} n=${n/\$#/_}; event on "$1" "@$n" __ev_once $# "@$n" "$@";}
__ev_once(){ event off "$3" "$2" __ev_once "${@:1:$1+2}"; "${@:4}";}
__ev_jit(){
	local q r=${__ev_jit-} s=$1;((${#r}<250))||__ev_jit=
	while [[ "$s" ]]; do
		r=${s::1};s=${s:1};printf -v q %q "$r";eval 's=${s//'"$q}";printf -v r 'REPLY=${REPLY//%s/_%02x};' "${q/#[~]/[~]}" "'$r";eval "$r";__ev_jit+="$r"
	done
	eval '__ev.encode(){ local LC_ALL=C;REPLY=${1//_/_5f};'\
	"${__ev_jit-}"' [[ $REPLY != *[^_[:alnum:]]* ]] || __ev_jit "${REPLY//[_[:alnum:]]/}";}'
};__ev_jit ''
__ev.decode(){ REPLY=();while (($#));do printf -v n %b "${1//_/\\x}";REPLY+=("$n");shift;done;}
__ev.list(){ eval 'set -- "${!'"${e%\[1]}"'@}"';__ev.decode "${@#bashup_event_}";}
jq-tty() { if isatty; then tty pager -- command jq  -C "$@"; else command jq "$@"; fi; }

colorize-json()  { tty-tool       JSON_COLOR jq -C . || tty-pygmentize JSON_COLOR json; }
colorize-yaml()  { tty-pygmentize YAML_COLOR yaml; }
colorize-ini()   { tty-pygmentize INI_COLOR  ini; }
colorize-toml()  { tty-pygmentize TOML_COLOR ini; }
colorize-php()   { tty-pygmentize PHP_COLOR  php; }
tty-pygmentize() { tty-tool "$1" pygmentize -f 256 -O style=igor -l "$2"; }

pager() { set -- "${tty_prefix-DEVKIT_}IS_PAGING"; [[ ! ${!1-} ]] && tty-tool PAGER less -FRX; }

pager.screenfull() {
	local lines=${LINES-24}; ! command -v tput >/dev/null || lines=$(tput lines)
	head -n $(( lines - ${1-2} ))
}
pager.watch()      { clear; date; echo; pager.screenfull 4; }

diffcolor() { tty-tool COLORDIFF colordiff || tty-tool COLORDIFF pygmentize -l diff; }

tty-tool() {
	isatty || return; set -- "${tty_prefix-DEVKIT_}$@"; REPLY=${!1-};
	if [[ ! ${!1+_} ]] && command -v "$2" >/dev/null; then
		event quote command "${@:2}"
	else [[ $REPLY ]];
	fi
}

isatty() {
	local t=${tty_prefix-DEVKIT_}ISATTY
	if [[ ${!t+_} ]]; then
		case "${!t-}" in 0|"") false ;; ?*) true ;; esac
	else [[ -t 1 ]]
	fi
}

tty() {
	local pipe=
	while (($#)) && [[ $1 != -- ]]; do
		! $1 || pipe="| $REPLY$pipe"; shift
	done
	event quote "${@:2}"
	eval "$REPLY $pipe"
}

with-pager() {
	! isatty || set -- "${tty_prefix-DEVKIT_}ISATTY=1" "$@"
	tty pager -- "${tty_prefix-DEVKIT_}PAGER=cat" "PAGER=cat" "${tty_prefix-DEVKIT_}IS_PAGING=yes" "$@"
}
tty_prefix=IMPOSER_
loco_preconfig() {
    LOCO_FILE=("imposer-project.md" "composer.json" "wp-cli.yml")
    LOCO_NAME=imposer
    LOCO_USER_CONFIG=$HOME/.config/imposer.md
    LOCO_SITE_CONFIG=/etc/imposer.md
}

loco_site_config() { mark-read "$1"; run-markdown "$1"; }
loco_user_config() { mark-read "$1"; run-markdown "$1"; }
loco_loadproject() {
	cd "$LOCO_ROOT"
	# load environment variables
	.env -f .imposer-env export
	IMPOSER_CACHE=${IMPOSER_CACHE:-$LOCO_ROOT/imposer/.cache}
	if [[ $LOCO_PROJECT == *.md ]]; then
		@require "imposer:project" __load_module imposer-project "$LOCO_PROJECT"
	fi
	event resolve persistent_modules_loaded
}
imposer_dirs=()

wpcon() { wp "$@" --skip-plugins --skip-themes --skip-packages; }

get_imposer_dirs() {
    if [[ ${imposer_dirs+_} ]]; then
        return
    elif (($#)); then
        true
    elif [[ ${IMPOSER_PATH-} ]]; then
        IFS=: eval 'set -- $IMPOSER_PATH'
    else
        set -- imposer \
            "${IMPOSER_THEMES=$(wpcon theme path)}" \
            "${IMPOSER_PLUGINS=$(wpcon plugin path)}" \
            "${IMPOSER_VENDOR=$( [[ -f composer.json ]] && composer config --absolute vendor-dir)}" \
            "${IMPOSER_PACKAGES=$(wpcon package path)/vendor}" \
            "${IMPOSER_GLOBALS=$(composer global config --absolute vendor-dir)}"
    fi
    imposer_dirs=()
    for REPLY; do
        if [[ "$REPLY" && -d "$REPLY" ]]; then
            [[ "$REPLY" == /* ]] || realpath.absolute "$REPLY"
            imposer_dirs+=("${REPLY%/}")
        fi
    done
} 2>/dev/null
imposer.path() {
    get_imposer_dirs;
    if [[ ${imposer_dirs+_} ]]; then IFS=: eval 'echo "${imposer_dirs[*]}"'; fi
}

imposer.default-path() { local imposer_dirs=() IMPOSER_PATH=; imposer path; }
compile-php() {
    if REPLY="$(echo "<?php $2" | php -l 2>&1)"; local s=$?; ((s)); then
        php-error "$REPLY" $s "${3-}"; return
    elif php-uses-namespace "$2"; then
        if [[ $REPLY != '{' ]]; then
            php-error "Namespaces in PHP blocks must be {}-enclosed" 255 "${3-}"; return
        fi
        printf 'compact-php %s %s[1] force\n' "$1" "$1"
    else
        printf 'maybe-compact-php %s %s[1] %s[2]\n' "$1" "$1" "$1"
        set -- "$1[1]" "$2"
    fi
    printf '%s+=%q\n' "$1" "$2"
}

php-error() {
    echo "In PHP block ${3:+at line ${3-} }of ${MDSH_SOURCE--}:"$'\n'"$1" >&2; return "$2"
}

maybe-compact-php() {
    if [[ "${!3-}" && "${!3-}" != "${__FILE__-}" ]]; then
        compact-php "$1" "$2" force
    fi
    eval "$3=\${__FILE__-}"
}

compact-php() {
    if [[ "${!1-}${3-}" && "${!2-}" ]]; then
        printf -v "$1" '%snamespace {\n%s}\n' "${!1-}" "${!2}"; unset "$2"
    fi
}

php-uses-namespace() {
    local r o=nocasematch; ! shopt -q $o || o=; ${o:+shopt -s $o}
    [[ $1 =~ ^((//|#).*$'\n'|'/*'.*'*/'|[[:space:]])*namespace[[:space:]]+[^\;{]*([;{]) ]]; r=$? #))
    ${o:+shopt -u $o}; REPLY=${BASH_REMATCH[3]-}; return $r
}

mdsh-compile-php() { compile-php imposer_php "$1" "$3"; }

# Dump one or more PHP buffers
cat-php() {
    local v1 v2 t php=()
    while (($#)); do
        v1=$1 v2="$1[1]"; compact-php "$v1" "$v2"; t=${!v1-}${!v2-}
        if php-uses-namespace "$t"; then
            compact-php php php[1] force
            php[0]+="$t"
        else
            php[1]+="$t"
        fi
        shift
    done
    [[ ! "${php-}${php[1]-}" ]] || printf '<?php\n%s' "${php-}${php[1]-}"
}
if [[ $0 == "${BASH_SOURCE-}" ]]; then
    realpath.dirname "$0"
    realpath.absolute "$REPLY" yaml2json.php
    if [[ -x "$REPLY" ]]; then
        printf -v REPLY 'yaml2json:php() { %q; }' "$REPLY"; eval "$REPLY"
    fi
fi
FILTER $'jqmd_data({"options":{},"plugins":{"imposer-tweaks":false}})\n'
DEFINE $'def _: "";\n\n'
require() {
    get_imposer_dirs
    while (($#)); do @require "imposer-module:$1" __find_and_load "$1"; shift; done
}

__find_and_load() {
    if have_module "$1"; then
        __load_module "$1" "$REPLY"
    else loco_error "Could not find module $1 in ${imposer_dirs[*]}"
    fi
}
have_module() {
    event encode "$1"; local v="imposer_module_path_$REPLY"
    if [[ ${!v+_} ]]; then REPLY=${!v}; [[ $REPLY ]]; return; fi
    realpath.basename "$1"; local name=$REPLY
    realpath.dirname "$1"; local ns=$REPLY
    local patterns=("$1" "$1/default" "$1/imposer-states/default" "$ns/imposer-states/$name" )
    for REPLY in ${imposer_dirs[@]+"${imposer_dirs[@]}"}; do
        if reply_if_exists "$REPLY" "${patterns[@]/%/.state.md}"; then
            printf -v "$v" %s "$REPLY"; return
        fi
    done
    printf -v "$v" %s ""
    false
}
__load_module() {
    realpath.dirname "$2"
    # shellcheck disable=SC2034  # vars for users + event var
    local __FILE__="$2" __DIR__=$REPLY IMPOSER_MODULE="$1" bashup_event_after_5fmodule=
    mark-read "$2"
    MDSH_CACHE=${IMPOSER_CACHE} mdsh-run "$2" "$1"
    # Force error exit if module fails to compile or run
    mdsh-ok || exit "$?" "Module $1 ($2) aborted with code $?"
    event fire "after_module"
    event emit "module loaded" "$1" "$2"
    event resolve "module loaded $1" "$2"
    # XXX Temporary backward-compatibility
    event emit "module_loaded" "$1" "$2"
    event resolve "module_loaded_$1" "$2"
}
files_used=()
mark-read() { files_used+=("$@"); }

run-modules() {
    # Only require up to the first '--' option
    while (($#)) && [[ "$1" != --* ]]; do require "$1"; shift; done
    event fire "all_modules_loaded"
}

files-read() {
	for REPLY in ${files_used[@]+"${files_used[@]}"}; do
	    realpath.relative "$REPLY"; echo "$REPLY"
	done
}

imposer.sources() {
	run-modules "$@" >/dev/null; CLEAR_FILTERS
	tty pager -- files-read
}
imposer.apply() {
    run-modules "$@"
    if HAVE_FILTERS; then
        CALL_JQ -c -n || return
        declare -r IMPOSER_JSON="$REPLY"
        event fire "before_apply"
        while local s=; run-imposer-php "$@" || s=$?; [[ $s == 75 ]]; do :; done
        ${s:+return $s}
        event fire "after_apply"
    fi
}

run-imposer-php() {
    # skip non-option arguments
    while (($#)) && [[ "$1" != --* ]]; do shift; done
    wp eval 'dirtsimple\imposer\Imposer::run_stream("php://fd/7");' "$@" \
        7<<<"$IMPOSER_JSON" < <(cat-php imposer_php)
}

writefile() {
	realpath.dirname "$1"; mkdir -p "$REPLY" || return
	[[ ! -f "$1" ]] || cp -p "$1" "$1.tmp" || return
	"${@:2}" >"$1.tmp" && mv "$1.tmp" "$1"
}

imposer.json() { run-modules "$@"; ! HAVE_FILTERS || JQ_CMD=jq-tty RUN_JQ -n; }

imposer.php()  {
    run-modules "$@"; dump-php imposer_php
}

dump-php() {
    CLEAR_FILTERS
    tty pager colorize-php -- cat-php "$@"
}
mdsh_raw_php_tweaks_header+=($'# Plugin Name:  Imposer Tweaks\n# Plugin URI:   https://github.com/dirtsimple/imposer#adding-code-tweaks\n# Description:  Automatically-generated from tweaks in imposer state modules\n# Version:      0.0.0\n# Author:       Various\n# License:      Unknown\n\n')
mdsh_raw_php_cli_footer+=($'if ( defined(\'WP_CLI\') && WP_CLI ) (function($file){\n    if (is_file($file)) require_once $file;\n})(__DIR__.\'/imposer-tweaks.cli.php\');\n')
warn-unloaded-tweaks() {
    echo "warning: module '$IMPOSER_MODULE' contains PHP tweaks that will not be loaded; tweaks must be defined in the project or global configuration." >&2
}

activate-tweaks() {
    php_tweaks=("" "${mdsh_raw_php_tweaks_header}")
    FILTER '.plugins."imposer-tweaks" = true'
    event off "php_tweak" activate-tweaks
    event on  "before_apply" write-plugin
}

write-plugin() {
    mkdir -p "${IMPOSER_PLUGINS=$(wpcon plugin path)}"
    local tweaks="$IMPOSER_PLUGINS/imposer-tweaks"
    cat-php captured_cli    >"$tweaks.cli.tmp"
    replace-file-if-changed  "$tweaks.cli.tmp" "$tweaks.cli.php"
    cat-php captured_tweaks >"$tweaks.tmp"
    replace-file-if-changed  "$tweaks.tmp" "$tweaks.php"
}

replace-file-if-changed(){
    if [[ ! -s "$1" ]]; then
        rm "$1"   # zero length file; remove it and the target
        [[ ! -f "$2" ]] || rm "$2"
    elif [[ -f "$2" ]] && diff -q "$1" "$2" >/dev/null; then
        rm "$1"   # no content changed, don't overwrite
    else
        mv "$1" "$2"
    fi
}

# shellcheck disable=SC2034  # captured_tweaks & cli are used in write-plugin function above
capture-tweaks() {
    if [[ "${php_cli-}${php_cli[1]-}" ]]; then
        captured_cli=("${php_cli-}" "${php_cli[1]-}"); unset "php_cli[@]"
        php_tweaks[1]+=$'\n'${mdsh_raw_php_cli_footer}
    fi
    captured_tweaks=("${php_tweaks-}" "${php_tweaks[1]-}"); unset "php_tweaks[@]"
    event off "php_tweak" activate-tweaks
    event on  "php_tweak" event on "after_module" warn-unloaded-tweaks
}

event on "persistent_modules_loaded" capture-tweaks
event on "php_tweak" activate-tweaks

mdsh-compile-php_tweak() { echo 'event emit php_tweak'; compile-php php_tweaks "$1" "$3"; }
mdsh-compile-php_cli()   { echo 'event emit php_tweak'; compile-php php_cli    "$1" "$3"; }

imposer.tweaks()  {
    if (($#)); then
        loco_subcommand "imposer.tweaks-" "imposer.tweaks---help" "$@"
    else
        run-modules; dump-php captured_tweaks
    fi
}

imposer.tweaks---help() {
	loco_subcommand_help "imposer.tweaks-" "imposer tweaks [command [args...]]" \
	'Output the PHP that would be generated by `imposer apply`' ""
}

imposer.tweaks-cli(){
    if (($#)); then echo $'`imposer tweaks cli` does not accept arguments' >&2; exit 64; fi
    run-modules; dump-php captured_cli
}

mdsh-misc(){
	printf 'event emit "block of "%q %q %q %q %q\n' "$1" "$2" \
		"$IMPOSER_MODULE" "$MDSH_SOURCE" "${block_start-}"
}
option-paths() {
	(($#>1)) || exit 64 "No option paths passed to $1 in line $(caller 1)"
	printf -v REPLY '.%s, ' "${@:2}"; REPLY=${REPLY%, }
}

@provide imposer::patch	DEFINE 'def imposer::patch(p; expr):
	reduce path(p) as $p ( . ;
		if getpath($p[0:-1]) | has($p[-1]) then setpath($p; getpath($p) | expr) else . end
	);'

json-options() {
	option-paths json-options "$@"; set -- "$REPLY"; @require imposer::patch
	event on all_modules_loaded FILTER ".options |= imposer::patch($1; tojson)"
	event on "filter options"   FILTER "imposer::patch($1; fromjson)"
}

exclude-options() { option-paths exclude-options "$@"; filter-options "del($REPLY)"; }
filter-options()  { event on "filter options" FILTER "$1"; }

exclude-options cron recently_activated

imposer-filtered-options() {
	wp option list --unserialize --format=json --no-transients --orderby=option_name "$@" |
	(
		CLEAR_FILTERS; FILTER 'map({key:.option_name, value:.option_value}) | from_entries'
		event emit "filter options"; eval 'JQ_CMD=(jq-tty)'; RUN_JQ
	)
}
options-repo:() { (($#==0)) || "options-repo::$@"; }

options-repo::has-directory() {
	# Default to .options-snapshot under IMPOSER_CACHE
	REPLY=$IMPOSER_CACHE/.options-snapshot; [[ ${IMPOSER_OPTIONS_SNAPSHOT-} ]]
}

options-repo::git() ( cd "$IMPOSER_OPTIONS_SNAPSHOT" && git "$@"; )
options-repo::changed() { [[ "$(options-repo: git status --porcelain options.json)" == ?M* ]]; }
options-repo::snapshot(){ options-repo: write json imposer-filtered-options && options-repo: "$@"; }
options-repo::write(){ writefile "$IMPOSER_OPTIONS_SNAPSHOT/options.$1" "${@:2}"; }
options-repo::show() { options-repo: git show :options."$1"; }
options-repo::add()  { options-repo: write "$@" && options-repo: git add options."$1"; }
options-repo::edit() { options-repo: show json | options-repo: add json "$@"; }

options-repo::freshen() {
	# Update git index with last options applied by imposer, if present
	options-repo: setup add json options-repo: approved-json &&
	options-repo: snapshot "$@"
}

options-repo::approved-json() (
	set -- "JQMD_SA_${#JQ_OPTS[@]}"
	FILTER '. as $last | $'"$1"'[0]'
	FILTER 'delpaths( [ $last."delete-options" | paths(type != "object") ])'
	FILTER '. * $last.options | to_entries | sort_by(.key) | from_entries'
	RUN_JQ -n --slurpfile "$1" <(options-repo: show json)
)

# If modules have run, we can't figure out what to apply
event on "all_modules_loaded" eval 'options-repo::approved-json() { options-repo: show json; }'

options-repo::to-json() {
	options-repo: show yml | yaml2json.php | options-repo: add json jq . &&
	options-repo: snapshot "$@"
}

options-repo::to-yaml() {
	options-repo: approved-json | options-repo: add   yml json2yaml.php &&
	imposer-filtered-options    | options-repo: write yml json2yaml.php &&
	options-repo: "$@"
}

options-repo::setup() {
	[[ ${IMPOSER_OPTIONS_SNAPSHOT-} ]] || loco_error "A snapshot directory name is required"
	[[ -d "$IMPOSER_OPTIONS_SNAPSHOT" ]] || mkdir -p "$IMPOSER_OPTIONS_SNAPSHOT"
	[[ -d "$IMPOSER_OPTIONS_SNAPSHOT/.git" ]] || options-repo: git init

	local options=$IMPOSER_OPTIONS_SNAPSHOT/options
	[[ -f "$options.yml" ]] || {
		[[ -f "$options.json" ]] || options-repo: add json imposer-filtered-options
		options-repo: to-yaml
	}
	[[ -f "$options.json" ]] || options-repo: to-json
	options-repo: "$@";
}
imposer.options() {
	options-repo: has-directory || local IMPOSER_OPTIONS_SNAPSHOT=$REPLY
	loco_subcommand "imposer.options-" "imposer.options---help" "$@"
}

imposer.options---help() {
	loco_subcommand_help "imposer.options-" "imposer options [--dir SNAPSHOT-DIR]"
}

imposer.options-jq() { imposer-filtered-options | jq-tty "$@"; }

imposer.options---dir() {
	(($#>1)) || imposer options --help
	local IMPOSER_OPTIONS_SNAPSHOT=$1
	imposer options "${@:2}"
}

# These bits should get promoted upstream
loco_subcommand() { if fn-exists "${1}${3-}"; then "${1}${@:3}"; else "${@:2}"; fi; }

loco_subcommand_help() {
	REPLY=$(compgen -A function "$1") && mdsh-splitwords "$REPLY"
	printf -v REPLY '\n    %s' "${REPLY[@]#$1}"
	printf -v REPLY "Usage: %s${4- COMMMAND [ARGS...]}\\n\\n%s${3:+\\n\\n}Commands:\\n%s" \
		"$2" "${3-}" "$REPLY"
	loco_error "$REPLY"
}
imposer.options-list() { imposer-filtered-options "$@"; }

imposer.options-diff() {
	tty pager diffcolor -- options-repo: setup to-yaml git --no-pager diff options.yml
}

imposer.options-review() {
	(($#==0)) || loco_error "Usage: imposer options [--dir SNAPSHOT-DIR] review"
	while ! options-repo: freshen changed; do
		(($#)) || { echo "Waiting for changes...  (^C to abort)"; set -- started; }
		sleep 10
	done
	options-repo: to-yaml git add --patch options.yml
	options-repo: to-json
}

imposer.options-reset() {
	(($#==0)) || loco_error "Usage: imposer options [--dir SNAPSHOT-DIR] reset"
	options-repo: setup add json imposer-filtered-options
}
imposer.options-yaml() { tty pager colorize-yaml -- unspecified-new-options; }

@provide imposer::rdiff DEFINE '
	def imposer::rdiff($old):
	  if . == $old then empty
	  elif type=="object" and ($old|type)=="object" then
	    . as $new | with_entries(
	      .key as $k |
	      if $k | in($old) then .value | imposer::rdiff($old[$k]) | {key:$k, value:.} else . end
	    ) | if . == {} and $new != {} then empty else . end
	  else .
	  end;
'

# shellcheck disable=SC2154
unspecified-new-options() {
	@require imposer::rdiff
	IMPOSER_ISATTY=0 imposer-filtered-options |
	jq --slurpfile old_options <(options-repo: setup approved-json) "$jqmd_defines
		{ options: . } | imposer::rdiff( { options: \$old_options[0]} )" | json2yaml.php
}
imposer.options-approve() {
	(($#)) || loco_error "Usage: imposer options [--dir SNAPSHOT-DIR] appove option-names..."
	printf -v REPLY '.%s, ' "$@"
	IMPOSER_ISATTY=0 options-repo: edit \
		jq --slurpfile opts <(imposer-filtered-options) "reduce path(${REPLY%, })"' as $p (. ;
			if $opts[0] | getpath($p[0:-1]) | has($p[-1]) then setpath($p; $opts[0] | getpath($p))
			else delpaths([$p]) end
		) | to_entries | sort_by(.key) | from_entries'
}
imposer.options-watch() {
	(($#==0)) || loco_error "Usage: imposer options [--dir SNAPSHOT-DIR] watch"
	watch-continuous 10 imposer options diff "$@"
}

watch-continuous() {
	local interval=$1 oldint oldwinch; oldint=$(trap -p SIGINT); oldwinch="$(trap -p SIGWINCH)"
	shift; trap "continue" SIGWINCH; trap "break" SIGINT
	while :; do watch-once "$@"; sleep "$interval" & wait $! || true; done
	${oldwinch:-trap -- SIGWINCH}; ${oldint:-trap -- SIGINT}
}

watch-once() {
	local cols; cols=$(tput cols) 2>/dev/null || cols=80
	REPLY="Every ${interval}s: $*"
	clear; printf '%s%*s\n\n' "$REPLY" $((cols-${#REPLY})) "$(date "+%Y-%m-%d %H:%M:%S")"
	IMPOSER_PAGER="pager.screenfull 3" "$@" || true
}
imposer.env() { loco_subcommand "imposer.env-" "imposer-env" "$@"; }
imposer-env() {
	{ (($#)) && declare -F -- ".env.$1" >/dev/null; } || { imposer env --help; return; }
	.env -f .imposer-env "$@"; ${REPLY[@]+printf '%s\n' "${REPLY[@]}"}
}
imposer.env---help() { loco_subcommand_help ".env." "imposer env"; }
imposer.env-export() {
	.env -f .imposer-env; .env parse "$@" || return 0; printf 'export %q\n' "${REPLY[@]}"; REPLY=()
}
if [[ $0 == "${BASH_SOURCE-}" ]]; then loco_main "$@"; exit; fi
