bash-tpl icon indicating copy to clipboard operation
bash-tpl copied to clipboard

Allow variable with `.tpl` file path as argument to `.INCLUDE` directive

Open benjamindblock opened this issue 2 years ago • 8 comments

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:

  1. 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>
  1. 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>
  1. 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.

benjamindblock avatar Apr 11 '23 13:04 benjamindblock

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 avatar Apr 12 '23 05:04 TekWizely

@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>

benjamindblock avatar Apr 13 '23 18:04 benjamindblock

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"

benjamindblock avatar Apr 21 '23 21:04 benjamindblock

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:

  1. Scripts intended to be executed (not sourced), need a #! header
  2. Scripts intended to be executed (not sourced), need a +x modifier

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

TekWizely avatar May 04 '23 17:05 TekWizely

hey @benjamindblock just a quick ping to see if you had a chance to read my post above?

TekWizely avatar May 11 '23 15:05 TekWizely

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!

benjamindblock avatar May 11 '23 19:05 benjamindblock

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

TekWizely avatar Aug 14 '23 02:08 TekWizely

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 avatar Sep 01 '23 19:09 benjamindblock

@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!

TekWizely avatar May 28 '24 00:05 TekWizely