Allow variable with `.tpl` file path as argument to `.INCLUDE` directive
First, thanks for the great work on bash-tpl -- I've been using it to great effect when making some new static sites.
I ran across an issue recently -- it appears that .INCLUDE cannot be used with a variable to render another .tpl file dynamically (eg., .INCLUDE $1).
Bash version:
GNU bash, version 3.2.57(1)-release (arm64-apple-darwin22)
Repro steps:
- Create a top-level "layout" file.
printf '<div class="container">\n .INCLUDE $1\n</div>\n' > layout.tpl
Which produces:
<div class="container">
.INCLUDE $1
</div>
- Create a "partial" view file with some content
echo '<span>Subcontent should be displayed</span>' > partial.tpl
Which produces:
<span>Subcontent should be displayed</span>
- Call
bash-tpl
bash-tpl layout.tpl partial.tpl
This produces the following:
printf "%s\n" \<div\ class=\"container\"\>
printf "%s\n" \ \ \</div\>
I expected this output:
printf "%s\n" \<div\ class=\"container\"\>
printf "%s\n" \ \ Subcontent\ should\ be\ displayed
printf "%s\n" \</div\>
I would expect that the argument $1 could be used in the .INCLUDE directive to dynamically pass in the .tpl file that should be rendered. I also could be missing something straightforward here, in which case any insight would be appreciated.
If this is not currently a feature, having this ability in bash-tpl would be great.
Greetings @benjamindblock and thank you for using my project and for taking the time to open this issue !
I've done some tests and I am pleasantly surprised !! it looks like the technique I use to parse directive arguments does honor variable references.
The issue you're seeing in your example is that the $[0-9] variables no-longer reference the original command line args but are instead referencing the arguments passed to the process_directive function that handles the directive.
If you wanna see something cool, try this:
layout.tpl
<div class="container">
.INCLUDE ${INCLUDE_TPL}
</div>
partial.tpl
<span>Subcontent should be displayed</span>
process template
$ INCLUDE_TPL=partial.tpl bash-tpl layout.tpl
output
printf "%s\n" \<div\ class=\"container\"\>
printf "%s\n" \ \ \<span\>Subcontent\ should\ be\ displayed\</span\>
printf "%s\n" \</div\>
To officially support abusing this technique within bash_tpl, I think we need to expose the command line arguments in a variable that can be referenced from the directive.
maybe something like $ARGS[x]
Honestly though that feels a bit messy in the case of multiple arguments being available for use.
Maybe adding a command-line flag to create named variables could more useful. Something like:
$ bash-tpl layout.tpl --var INCLUDE_TPL=partial.tpl
Or maybe a .ENV directive that could read a .env file ..
I'll grind on this a bit more, but in the meantime lemme know your thoughts and thanks again for participating in my project!
-TW
@TekWizely Aha -- thanks for the details on the process_directive function -- I tried out the .INCLUDE call with the explicit named variable and everything worked as expected. Brilliant.
I really like the idea of a .ENV directive that could load variables from an .env type of file. I'm actually doing a similar hack with eval(...) right now to accomplish just that type of behavior. Brief explanation following...
In my static site generator, each page has a .meta file that can include a tpl= key if multiple pages share a template (like blog posts).
# blog20230415.meta
title="Website | April 15th"
content="Long text..."
tpl="tpls/blog_template.tpl.html
I then use eval(...) to load those variables and render the primary site layout and the subcontent for a particular page with those vars present.
content() {
bash <(bin/bash-tpl tpls/layout.tpl.html)
}
renderPage() {
echo "$(eval $(cat "$1") content)"
}
# Example: renderPage "renderPage "blog20230415.meta" > "blog20230415.html"
Example view files:
<!-- tpls/layout.tpl.html -->
<header><% ${title} %></header>
<body>
.INCLUDE ${tpl}
</body>
<!-- tpls/blog_template.tpl.html -->
<div><% ${content} %></div>
If an .ENV directive was included, we could simplify this nicely.
renderPage() {
echo "$(bash <(bin/bash-tpl tpls/layout.tpl.html blog20230415.meta))"
}
<!-- tpls/layout.tpl.html -->
%
.ENV ${1}
%
<header><% ${title} %></header>
<body>
.INCLUDE ${tpl}
</body>
I've come up with a workaround for rendering subcontent that avoids .INCLUDE and instead just calls source on the subcontent and renders it with the standard <% %> tags. It's been working pretty nicely, though I've still been using .INCLUDE though for partials that require no args (as it has a nicer interface for this sort of thing).
Here's a small example:
renderContent() {
META_FILE="$1"
bash <(bin/bash-tpl tpls/site/layout.tpl.html) "${META_FILE}"
}
blogs/1/index.meta
title="Blog Post #1"
tpl="blog.tpl.html"
content="Some content"
layout.html.tpl
%
META_FILE="$1"
source "${META_FILE}"
# Fail if "$tpl" is missing.
SUBCONTENT="$(source <(bin/bash-tpl ${tpl:?}))"
%
<head>
<title><% $title %></title>
</head>
<div id="subcontent">
<% ${SUBCONTENT} %>
</div>
blog.tpl.html
<div id="blog">
<% "${content}" %>
</div>
This would all get kicked off in a script running: renderContent "blogs/1/index.meta"
Hey @benjamindblock it occurs to me that you may be invoking bash-tpl more often than needed.
Bash-tpl is actually a Template Generator ie:
The output of bash-tpl is a shell-script that you can re-use without bash-tpl, with bash-tpl only being needed when the original template changes (ie. the resulting shell script changes).
This fact is a bit obscured because the resulting shell script cannot be executed directly without a little setup + processing:
- Scripts intended to be executed (not sourced), need a
#!header - Scripts intended to be executed (not sourced), need a
+xmodifier
Here's an example in action:
compile-once.tpl
% #!/usr/bin/env sh
Hello, world
usage example
$ bash-tpl compile-once.tpl -o use-many-times.sh
$ chmod +x use-many-times.sh
$ ./use-many-times.sh
Hello, world
Here's an example for runtime (sourced) includes:
compile-once-outer.tpl
% #!/usr/bin/env sh
% source ${INCLUDE?}
compile-once-sourced.tpl
Hello, world
usage example
$ bash-tpl compile-once-outer.tpl -o use-many-times-outer.sh
$ chmod +x use-many-times-outer.sh
$ bash-tpl compile-once-sourced.tpl -o use-many-times-sourced.sh
$ INCLUDE="./use-many-times-sourced.sh" ./use-many-times-outer.sh
Hello, world
Makefile
With these ideas in mind, I whipped up a small makefile that might be useful for compiling a directory containing templates into a directory containing re-usable scripts:
Makefile
.PHONY: all clean
.DEFAULT_GOAL := all
BASH_TPL := bash-tpl
TPL_DIR := ./tpl
SH_DIR := ./sh
TPL_FILES := $(wildcard $(TPL_DIR)/*.tpl $(TPL_DIR)/**/*.tpl)
SH_FILES := $(patsubst $(TPL_DIR)/%.tpl,$(SH_DIR)/%.sh,$(TPL_FILES))
all: $(SH_FILES) ## Compile templates into shell scipts
clean: ## Remove any shell scripts that have matching templates
rm -f $(SH_FILES)
$(SH_DIR)/%.sh: $(TPL_DIR)/%.tpl
@mkdir -p $(dir $@)
$(BASH_TPL) $< -o $@
@chmod +x $@
This makefile is very much a quick hack, but may serve as a useful starter for your project.
I'm interested in seeing what you can do with these ideas in your project.
Please give it some thought and lemme know what you think!
-TW
hey @benjamindblock just a quick ping to see if you had a chance to read my post above?
Hey @TekWizely -- yes, thanks for all the details! I was, like you suspected, calling bash-tpl many more times than necessary.
With your updates I was able to get the build times for my static site from 11s down to 3s (the site has ~40 HTML pages to build). There may still be a few more optimizations I can make to speed that up a little bit more.
I'll be AFK for a few days but will post some more details about my improvements after that!
Hey @benjamindblock just checking back to see how things are going? BTW: I think I may have stumbled onto your site?
- https://eveningjazz.net/about.html
Hey @TekWizely, apologies for the late response -- busy summer. Did some work on the site this week and made the repo public (for now), in case you're interested in taking a look at the patterns I setup with Bash-TPL (thanks to your input): https://github.com/benjamindblock/eveningjazz.net
Ultimately I was able to get the full site build down to ~3s for 33 .html pages. Not too bad, I think!
@benjamindblock Thanks for the final update post - I'm going to close this now as I think we got a working solution to the initial concern. Thanks!