232 lines
5.6 KiB
Bash
232 lines
5.6 KiB
Bash
|
|
if ! type "include" > /dev/null 2>&1; then
|
|
include() {
|
|
. "$(cd "$(dirname "$1")" && pwd)/${2}";
|
|
};
|
|
fi
|
|
include "${BASH_SOURCE[0]}" ../pragma_once.bash
|
|
if ! pragma_once ${BASH_SOURCE[0]}; then return 0; fi
|
|
include "${BASH_SOURCE[0]}" ./global_rely/export.bash
|
|
include "${BASH_SOURCE[0]}" ./other.bash
|
|
# shellcheck source=./global_rely/export.bash
|
|
# shellcheck source=./other.bash
|
|
|
|
|
|
|
|
# =========================
|
|
# template.bash
|
|
# 纯 Bash 模板渲染
|
|
# 占位符格式: {{KEY}}
|
|
# =========================
|
|
|
|
# 判断某个 key 是否合法
|
|
_template_validate_key() {
|
|
local key="$1"
|
|
[[ "$key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]
|
|
}
|
|
|
|
# 转义 replacement,避免 sed 替换时特殊字符出问题
|
|
_template_escape_replacement() {
|
|
local s="$1"
|
|
s="${s//\\/\\\\}"
|
|
s="${s//&/\\&}"
|
|
s="${s//|/\\|}"
|
|
printf '%s' "$s"
|
|
}
|
|
|
|
# 核心:对一个字符串做渲染
|
|
# 用法:
|
|
# template_render_string "hello {{NAME}}" NAME "wyc" AGE "18"
|
|
template_render_string() {
|
|
if [ $# -lt 1 ]; then
|
|
echo "template_render_string: missing template string" >&2
|
|
return 1
|
|
fi
|
|
|
|
local template_str="$1"
|
|
shift
|
|
|
|
if [ $(( $# % 2 )) -ne 0 ]; then
|
|
echo "template_render_string: key/value arguments must be paired" >&2
|
|
return 1
|
|
fi
|
|
|
|
local rendered="$template_str"
|
|
local key=""
|
|
local value=""
|
|
local escaped_value=""
|
|
|
|
while [ $# -gt 0 ]; do
|
|
key="$1"
|
|
value="$2"
|
|
shift 2
|
|
|
|
if ! _template_validate_key "$key"; then
|
|
echo "template_render_string: invalid key: $key" >&2
|
|
return 1
|
|
fi
|
|
|
|
escaped_value="$(_template_escape_replacement "$value")"
|
|
rendered="$(printf '%s' "$rendered" | sed "s|{{${key}}}|${escaped_value}|g")"
|
|
done
|
|
|
|
printf '%s' "$rendered"
|
|
}
|
|
|
|
# 从模板文件渲染到 stdout
|
|
# 用法:
|
|
# template_render_file /tmp/a.tpl NAME "wyc" AGE "18"
|
|
template_render_file() {
|
|
if [ $# -lt 1 ]; then
|
|
echo "template_render_file: missing template file" >&2
|
|
return 1
|
|
fi
|
|
|
|
local template_file="$1"
|
|
shift
|
|
|
|
if [ ! -f "$template_file" ]; then
|
|
echo "template_render_file: template file not found: $template_file" >&2
|
|
return 1
|
|
fi
|
|
|
|
local content=""
|
|
content="$(cat "$template_file")" || return 1
|
|
template_render_string "$content" "$@"
|
|
}
|
|
|
|
# 从模板文件渲染到目标文件
|
|
# 用法:
|
|
# template_render_to_file /tmp/a.tpl /tmp/out.conf NAME "wyc"
|
|
template_render_to_file() {
|
|
if [ $# -lt 2 ]; then
|
|
echo "template_render_to_file: usage: template_render_to_file <template> <output> [KEY VALUE ...]" >&2
|
|
return 1
|
|
fi
|
|
|
|
local template_file="$1"
|
|
local output_file="$2"
|
|
shift 2
|
|
|
|
local output_dir=""
|
|
output_dir="$(dirname "$output_file")"
|
|
|
|
mkdir -p "$output_dir" || return 1
|
|
|
|
template_render_file "$template_file" "$@" > "$output_file" || return 1
|
|
}
|
|
|
|
# 检查模板里还有哪些未替换占位符
|
|
# 输出唯一 key 列表
|
|
# 用法:
|
|
# template_list_placeholders /tmp/a.tpl
|
|
template_list_placeholders() {
|
|
if [ $# -ne 1 ]; then
|
|
echo "template_list_placeholders: usage: template_list_placeholders <template_file>" >&2
|
|
return 1
|
|
fi
|
|
|
|
local template_file="$1"
|
|
|
|
if [ ! -f "$template_file" ]; then
|
|
echo "template_list_placeholders: template file not found: $template_file" >&2
|
|
return 1
|
|
fi
|
|
|
|
grep -o '{{[A-Za-z_][A-Za-z0-9_]*}}' "$template_file" \
|
|
| sed 's/^{{//; s/}}$//' \
|
|
| sort -u
|
|
}
|
|
|
|
# 模板文件示例
|
|
# server_name={{SERVER_NAME}}
|
|
# server_port={{SERVER_PORT}}
|
|
# build_type={{BUILD_TYPE}}
|
|
# install_prefix={{INSTALL_PREFIX}}
|
|
|
|
# 调用方式
|
|
|
|
# template_render_to_file_strict \
|
|
# /ae/bash/demo/app.conf.tpl \
|
|
# /ae/bash/demo/out/app.conf \
|
|
# SERVER_NAME "demo-server" \
|
|
# SERVER_PORT "8080" \
|
|
# BUILD_TYPE "Release" \
|
|
# INSTALL_PREFIX "/opt/demo"
|
|
|
|
# 严格模式:如果还有未替换占位符则报错
|
|
# 用法:
|
|
# template_render_to_file_strict /tmp/a.tpl /tmp/out.conf NAME "wyc"
|
|
template_render_to_file_strict() {
|
|
if [ $# -lt 2 ]; then
|
|
echo "template_render_to_file_strict: usage: template_render_to_file_strict <template> <output> [KEY VALUE ...]" >&2
|
|
return 1
|
|
fi
|
|
|
|
local template_file="$1"
|
|
local output_file="$2"
|
|
shift 2
|
|
check_file_var template_file
|
|
template_render_to_file "$template_file" "$output_file" "$@" || return 1
|
|
|
|
if grep -q '{{[A-Za-z_][A-Za-z0-9_]*}}' "$output_file"; then
|
|
echo "template_render_to_file_strict: unresolved placeholders found in: $output_file" >&2
|
|
grep -o '{{[A-Za-z_][A-Za-z0-9_]*}}' "$output_file" | sort -u >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
|
|
# declare -A tpl_vars
|
|
# tpl_vars[SERVER_NAME]="demo-server"
|
|
# tpl_vars[SERVER_PORT]="8080"
|
|
# tpl_vars[BUILD_TYPE]="Debug"
|
|
# tpl_vars[INSTALL_PREFIX]="/opt/demo"
|
|
#
|
|
# template_render_file_by_assoc \
|
|
# /ae/bash/demo/app.conf.tpl \
|
|
# tpl_vars \
|
|
# > /ae/bash/demo/out/app.conf
|
|
|
|
template_render_file_by_assoc() {
|
|
if [ $# -ne 2 ]; then
|
|
echo "template_render_file_by_assoc: usage: template_render_file_by_assoc <template_file> <assoc_name>" >&2
|
|
return 1
|
|
fi
|
|
|
|
local template_file="$1"
|
|
local assoc_name="$2"
|
|
|
|
check_file_var template_file
|
|
|
|
if [ ! -f "$template_file" ]; then
|
|
echo "template_render_file_by_assoc: template file not found: $template_file" >&2
|
|
return 1
|
|
fi
|
|
|
|
local content=""
|
|
content="$(cat "$template_file")" || return 1
|
|
|
|
local key=""
|
|
local value=""
|
|
local escaped_value=""
|
|
|
|
local rendered="$content"
|
|
local -n kv_ref="$assoc_name"
|
|
|
|
for key in "${!kv_ref[@]}"; do
|
|
if ! _template_validate_key "$key"; then
|
|
echo "template_render_file_by_assoc: invalid key: $key" >&2
|
|
return 1
|
|
fi
|
|
value="${kv_ref[$key]}"
|
|
escaped_value="$(_template_escape_replacement "$value")"
|
|
rendered="$(printf '%s' "$rendered" | sed "s|{{${key}}}|${escaped_value}|g")"
|
|
done
|
|
|
|
printf '%s' "$rendered"
|
|
}
|
|
|
|
|
|
|