Files
2026-07-07 15:23:39 +08:00

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"
}