Compare commits

..

3 commits

Author SHA1 Message Date
sdomi
c6311cf4c1 tests: add testcases for v0.97.1 template extensions 2025-05-18 16:44:37 +02:00
sdomi
e9018284f5 docs: upgrade template docs for 0.97.1 2025-05-18 14:14:18 +02:00
sdomi
e809c0be6b template: implement relative paths. bump to 0.97.1 2025-05-18 14:14:00 +02:00
5 changed files with 123 additions and 18 deletions

View file

@ -10,7 +10,7 @@ declare -A str
str[title]="Hello, world!"
str[test]="meow"
render str "${cfg[namespace]}/templates/main.htm"
render str "templates/main.htm"
```
`render` is the core of the templating engine; it takes an assoc array, iterates over it, applies
@ -18,7 +18,10 @@ additional magic and outputs the response directly to stdout. It is likely the f
to run in your script.
The script above has referenced an HTML file; For this example, we put it under
`app/templates/main.htm`, but you're free to use any directory structure for this.
`app/templates/main.htm`, but you're free to use any directory structure for this. An observant
reader might have noticed the relative path; All paths are treated as relative to the namespace's
directory. This behavior can be modified by setting `template_relative_paths`, which is described
in greater detail by the [main template documentation](./template.md).
```
<!DOCTYPE html>
@ -100,7 +103,7 @@ done
# once we have a full list of elements, assign it to the array passed to render
str[_list]=list
render str "${cfg[namespace]}/templates/main.htm"
render str "templates/main.htm"
```
And the template...
@ -182,7 +185,7 @@ declare -A str
str[title]="time pretty-print"
str[+time]="$EPOCHSECONDS"
render str "${cfg[namespace]}/templates/main.htm"
render str "templates/main.htm"
```
![netscape displays the current date and time](https://f.sakamoto.pl/IwIvf3Axw.png)

View file

@ -23,8 +23,34 @@ For practical examples, see the [template examples](template-examples.md) page.
`render <assoc_array> <file> [recurse]`
The first param points to an associative array containing the replacement data. Second one points
to a file containing the template itself. Third is optional, and controls whether `render` will
recurse or not. This is mostly used internally, you likely won't ever need to set it.
to a file containing the template itself (see section below for special usage). Third is optional,
and controls whether `render` will recurse or not (this is mostly used internally, you likely
won't ever need to set it).
### File paths
Starting with HTTP.sh 0.97.1 (2025-05-18), paths in calls to `render` and include tags are
relative to the namespace directory (usually `app/`). This behavior can be changed by defining
an array called `template_relative_paths`:
```
template_relative_paths=(
"${cfg[namespace]}/templates/neue_theme/"
"${cfg[namespace]}/templates/default/"
)
```
The templating engine will check all the files in order and pick the first one that exists. This
can be used to implement basic inheritance.
Before 0.97.1, paths were relative to the main HTTP.sh directory. Take care when upgrading, either
fix the paths, or set `template_relative_paths="./"` to emulate the previous behavior.
### Inline templates
For some purposes, it may be beneficial to store the template within the script file itself.
This can be done either through passing `/dev/stdin` as a file name, or through inlining
a file substitution, such as `<(echo ...)`.
## Simple replace
@ -58,9 +84,8 @@ output (for filling out hidden form values, etc.)
Template includes are special, in that you don't have to define them in the array.
They get processed first to "glue together" one singular template.
Currently, the path starts at the root of HTTPsh's directory. We don't support expanding variables
inside the include tag, so for now you'll need to hardcode `{{#app/templates/...}}`. This will
likely get changed in a future release, starting the path in your namespace.
The path starts at the root of your namespace (usually `app/`). This behavior can be changed,
see section "File paths" above.
**Warning**: No recursion is supported within included templates; This means that you can't have
an "include chain". Furthermore, some interactions between included templates and loops/ifs are

View file

@ -6,11 +6,18 @@
function render() {
local _tpl_newline=$'\01'
local _tpl_ctrl=$'\02'
local tplfile
_template_find_absolute_path "$2"
if [[ ! "$tplfile" ]]; then
exit 1 # fail hard
fi
if [[ "$3" != true ]]; then
local template="$(tr -d "${_tpl_newline}${_tpl_ctrl}" < "$2" | sed 's/\&/<2F>UwU<77>/g')"
local template="$(tr -d "${_tpl_newline}${_tpl_ctrl}" < "$tplfile" | sed 's/\&/<2F>UwU<77>/g')"
else
local template="$(tr -d "${_tpl_ctrl}" < "$2" | sed -E 's/\\/\\\\/g')"
local template="$(tr -d "${_tpl_ctrl}" < "$tplfile" | sed -E 's/\\/\\\\/g')"
fi
local buf=
local garbage="$template"$'\n'
@ -24,14 +31,19 @@ function render() {
# below check prevents the loop loading itself as a template.
# this is possibly not enough to prevent all recursions, but
# i see it as a last-ditch measure. so it'll do here.
if [[ "$file" == "$2" ]]; then
if [[ "$file" == "$tplfile" ]]; then
subtemplate+="s${_tpl_ctrl}\{\{\#$key\}\}${_tpl_ctrl}I cowardly refuse to endlessly recurse\!${_tpl_ctrl}g;"
elif [[ -f "$key" ]]; then
local input="$(tr -d "${_tpl_ctrl}${_tpl_newline}" < "$key" | sed 's/\&/<2F>UwU<77>/g')"
# elif [[ -f "$key" ]]; then
else
local i
local IFS=''
_template_find_absolute_path "$key"
local input="$(tr -d "${_tpl_ctrl}${_tpl_newline}" < "$tplfile" | sed 's/\&/<2F>UwU<77>/g')"
garbage+="$input"$'\n'
input="$(tr $'\n' "${_tpl_newline}" <<< "$input")" # for another hack
subtemplate+="s${_tpl_ctrl}\{\{\#$key\}\}${_tpl_ctrl}${input}${_tpl_ctrl};"
_template_find_special_uri "$(cat "$key")"
_template_find_special_uri "$(cat "$tplfile")"
fi
done <<< "$(grep -Poh '{{#\K(.*?)(?=}})' <<< "$template")"
@ -118,6 +130,27 @@ function render() {
[[ "$3" != true ]] && _template_uri_list=()
}
# internal function that looks for the current template. uses path relative to
# the namespace, unless overriden with ${template_relative_paths[@]}
#
# - /dev/stdin is a special value, which gets passed literally.
# - /dev/fd/* allows file substitutions `<(echo ...)` to be used.
#
# _template_find_absolute_path(name) -> $tplfile
_template_find_absolute_path() {
if [[ ! "${template_relative_paths}" || "$1" == /dev/stdin || "$1" == "/dev/fd/"* ]]; then
tplfile="$1"
else
for (( i=0; i<${#template_relative_paths[@]}; i++ )); do
if [[ -f "${template_relative_paths[i]}/$1" ]]; then
tplfile="${template_relative_paths[i]}/$1"
break
fi
done
fi
}
_template_uri_list=()
# internal function that finds all occurences of the special `{{-uri-N}}` tag.
# here to also make it run on subtemplates
@ -168,8 +201,9 @@ function nested_add() {
ref+=("$nested_id")
}
# nested_get(ref, i)
# nested_get(ref, i, [res])
function nested_get() {
local -n ref=$1
declare -g -n res=_${ref["$2"]}
local name=${3:-res}
declare -g -n $name=_${ref["$2"]}
}

View file

@ -1,2 +1,2 @@
#!/usr/bin/env bash
HTTPSH_VERSION=0.97
HTTPSH_VERSION=0.97.1

View file

@ -51,6 +51,45 @@ tpl_date_invalid() {
match="value: 1970-01-01 01:00:00"
}
tpl_path_custom() {
prepare() {
declare -ga template_relative_paths=("/tmp/")
tempfile="$(mktemp)" || return 1
}
tst() {
declare -A meow
render meow "$(basename "$tempfile")"
}
}
tpl_path_inheritance() {
prepare() {
tempdir="$(mktemp -d)" || return 1
declare -ga template_relative_paths=(
"$tempdir"
"/tmp/"
)
}
}
tpl_path_include() {
prepare() {
another_tempfile="$(mktemp)"
echo "meow?" > "$another_tempfile"
echo "{{#$(basename "$another_tempfile")}}" > "$tempfile"
}
match="meow?"
cleanup() {
rm -R "$tempdir"
rm "$tempfile" "$another_tempfile"
}
}
subtest_list=(
tpl_basic
tpl_basic_specialchars
@ -59,4 +98,8 @@ subtest_list=(
tpl_date
tpl_date_empty
tpl_date_invalid
tpl_path_custom
tpl_path_inheritance
tpl_path_include
)