You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
-**Pure:** The rendering process is pure, ensuring that the rendered result is fully predictable and consistent across multiple runs.
8
-
- This is the core feature and also the purpose of this template engine. This feature makes `bpt` suitable for generating a large number of small configuration files without unexpected changes due to an environment variable change or an external file change when re-generating config files.
9
-
- A fingerprinting function is provided to digest all the inputs, including files, variables, and the engine itself.
9
+
-**Pure:** The rendering process resembles a [pure function][pure_function], ensuring that the rendered results are fully predictable and consistent across multiple runs given the same inputs. It also produces no side-effects other than the generated script or the render result.
10
+
- This feature makes `bpt` suitable for generating a large number of small configuration files without unexpected changes due to an environment variable change or an external file change when re-generating config files.
11
+
- A fingerprinting function is provided to digest all the inputs, including template files, variables, and the engine itself.
10
12
- Functionalities are also provided to collect all the variables and includes used in a template.
11
-
-**Minimal dependency:**`bpt` requires only minimal external programs in addition to bash itself. All the external programs required are within coreutils, which is most likely bundled with the Linux installation.
13
+
-**Minimal dependency:**`bpt` requires only a minimal set of external programs in addition to bash. All the external programs required are in coreutils, which is most likely bundled with your Linux installation.
-**Single bash script:** The `bpt.sh` script contains everything you need.
15
17
- It can be called directly or used as a library by sourcing it in another bash script.
16
18
- It works out of the box.
17
-
-**A little bit of extra functionalities:** While a simple template engine that only replaces `{{var}}`s can also be easily made pure, the functionalities is somewhat limited. BPT provides a little bit extra that makes life easier.
19
+
-**A little bit of extra functionalities:** While a simple template engine that only replaces `{{var}}`s can also be easily made pure, their functionalities are somewhat limited. BPT provides a little bit extra that makes life easier.
18
20
- Delimiters can be replaced.
19
-
- Branching, looping, and basic boolean operations are supported.
21
+
- Branching, list iteration, and basic boolean operations are supported.
20
22
- Recursive inclusion of templates is also supported.
21
23
- Refer to [Writing Templates](#writing-templates) for more details.
22
24
23
25
**Deficiencies:**
24
26
25
-
-**Slow:** Although optimization has been made, a LALR(1) parser implemented in bash is still quite slow. It is not recommended to use `bpt` for large and complex templates such as an HTML templating engine.
27
+
-**Slow:** Although optimizations have been made, a LALR(1) parser implemented in bash is still quite slow. It is not recommended to use `bpt` for large and complex templates.
26
28
- The startup overhead is around 16ms.
27
29
- Rendering a template consisting of 100 variables takes around 75ms.
28
30
- Rendering a template consisting of 1000 variables takes around 900ms, with most of the time spent on reduction.
29
-
- Refer to [#Benchmarking](#benchmarking) for more details.
31
+
- Refer to [#Benchmarks](#benchmarks) for more details.
30
32
31
33
# Installation & Usages
32
34
@@ -38,7 +40,7 @@ All you need is `bpt.sh`. Place it anywhere you'd like, then `chmod +x bpt.sh`.
38
40
var1=a var2=b ./bpt.sh ge template.tpl # Render the template
39
41
var1=a var2=b ./bpt.sh f template.tpl # Fingerprint the template
40
42
./bpt.sh g template.tpl > out.sh # Generate reusable script
41
-
./bpt.sh g -l '<<' -r '>>'ge template.tpl # Customize delimiters
43
+
./bpt.sh g -l '<<' -r '>>' template.tpl# Customize delimiters
42
44
./bpt.sh cv template.tpl # Collect variables
43
45
./bpt.sh ci template.tpl # Collect includes
44
46
./bpt.sh -h # Print help
@@ -54,15 +56,16 @@ mapfile -t vars <<<"$(bpt.main cv template.tpl)"
54
56
mapfile -t includes <<<"$(bpt.main ci template.tpl)"
55
57
script="$(bpt.main g template.tpl)"
56
58
render_result="$(bpt.main ge template.tpl)"
59
+
fingerprint="$(bpt.main f template.tpl)"
57
60
```
58
61
59
62
# Writing Templates
60
63
61
-
BPT employs a straightforward grammar whereby any content beyond the top-level delimiters is treated as strings, whereas anything enclosed by delimiters is considered either a string or a keyword. Notably, any string located within these delimiters must be enclosed in single or double quotes.
64
+
BPT employs a straightforward grammar whereby any content beyond the top-level delimiters is treated as strings, whereas anything enclosed by delimiters is considered either a string, an identifier, or a keyword. Notably, any string located within these delimiters must be enclosed in single or double quotes.
62
65
63
66
## Examples
64
67
65
-
### Basic Variable Replacement
68
+
### Variable Replacement
66
69
67
70
```
68
71
{{var}}
@@ -72,9 +75,17 @@ BPT employs a straightforward grammar whereby any content beyond the top-level d
72
75
{{var and {{var2}}}} # Use {{var2}} if var is not empty
**Note:** It's obvious that using non-deterministic variables such as `{{RANDOM}}` or `{{SRANDOM}}` results in impure fingerprinting/rendering processes. `$RANDOM` can produce varying results in two subsequent evaluations. It is thus recommended to use `rand=$RANDOM ./bpt ge ...` and use `{{rand}}` in templates to let the `fingerprint` command correctly capture the non-determinism of `$RANDOM`.
85
+
75
86
### Include Another Template
76
87
77
-
All include paths are relative to where the script is called from (`$PWD`).
88
+
All include paths are relative to where the script is called from (`$PWD`). Includes are processed recursively.
78
89
79
90
```
80
91
{{include: "another.tpl"}}
@@ -103,13 +114,20 @@ All include paths are relative to where the script is called from (`$PWD`).
103
114
{{if "abc" == "def": "yes"}}
104
115
{{if "abc" != "def": "no"}}
105
116
{{if ! "abc" == "def": "yes"}}
117
+
{{if "abc" =~ "a.*": "yes"}}
106
118
107
119
# Combined comparison
108
120
{{if "a" or "": "yes"}}
109
121
{{if ("1" -lt "2" or "") and (("a" < "b") or "abc"): "yes"}}
122
+
123
+
# Shorthands
124
+
{{""}} # 'false'
125
+
{{"a"}} # 'true'
126
+
{{"a": "yes"}} # 'yes'
127
+
{{{{var}}: "yes": "no"}} # 'yes' if {{var}} is not empty, otherwise 'no'
110
128
```
111
129
112
-
### Looping
130
+
### Iterate a List
113
131
114
132
```
115
133
{{for var in "a" "b" "c": {{var}}","}}
@@ -118,13 +136,23 @@ All include paths are relative to where the script is called from (`$PWD`).
118
136
119
137
### Builtin Functions
120
138
121
-
Currently we only support `seq`, `len` and `quote`. The `seq` builtin is a wrapper of the `seq` executable in coreutils. The other two are implemented in pure bash.
139
+
Currently we only support `seq`, `len`, `quote`, `cat` and `split`. The `seq` builtin is a wrapper of the `seq` executable in coreutils. The others are implemented in pure bash.
140
+
141
+
```
142
+
{{len: "abc"}} # 3
143
+
{{seq: "3"}} # 1 2 3
144
+
{{seq: "1" "3" "10"}} # 1 4 7 10
145
+
```
122
146
123
147
```
124
-
{{len: "abc"}} # 3
125
-
{{seq: "3"}} # 1 2 3
126
-
{{seq: "1" "3" "10"}}' # 1 4 7 10
127
-
{{quote: {{seq: "3"}}}} # 123
148
+
{{quote: "2 3" "3" }} # 2 33
149
+
{{for i in {{quote: "2 3" "3" }}: {{i}}","}} # 2 33,
150
+
151
+
{{cat: "2 3" "3" }} # 2 33
152
+
{{for i in {{cat: "2 3" "3" }}: {{i}}","}} # 2,33,
153
+
154
+
{{split: "2 3" "3" }} # 2 3 3
155
+
{{for i in {{split: "2 3" "3" }}: {{i}}","}} # 2,3,3,
128
156
```
129
157
130
158
### Mixture of the above
@@ -148,7 +176,8 @@ obj1=Pen obj2=Apple obj3=Pineapple ./bpt.sh ge <<-EOF
148
176
}}
149
177
150
178
EOF
151
-
179
+
```
180
+
```
152
181
I have a Pen, I have a Apple,
153
182
Uh! Apple-Pen,
154
183
I have a Pen, I have Pineapple,
@@ -166,8 +195,11 @@ For reference, the complete BNF of bpt is as follows:
166
195
```
167
196
DOC -> DOC STMT
168
197
| .
169
-
STMT -> IF | FORIN | INCLUDE | BUILTIN | VAR | STR .
170
-
IF -> ld if BOOLS cl DOC ELIF ELSE rd .
198
+
STMT -> IF | FORIN | INCLUDE | BUILTIN | QUOTE | VAR | STR .
199
+
IF -> ld if BOOLS cl DOC ELIF ELSE rd
200
+
| ld BOOLS cl DOC cl DOC rd
201
+
| ld BOOLS cl DOC rd
202
+
| ld BOOLS rd .
171
203
ELIF -> ELIF elif BOOLS cl DOC
172
204
| .
173
205
ELSE -> else cl DOC
@@ -188,20 +220,22 @@ BOOL -> ARGS BOP ARGS
188
220
FORIN -> ld for ID in ARGS cl DOC rd .
189
221
INCLUDE -> ld include cl STR rd .
190
222
BUILTIN -> ld ID cl ARGS rd .
223
+
QUOTE -> ld quote cl ARGS rd .
191
224
ARGS -> ARGS STMT
192
225
| STMT .
193
226
VAR -> ld ID rd
194
227
| ld ID or VAR rd
195
228
| ld ID or STR rd
196
229
| ld ID and VAR rd
197
230
| ld ID and STR rd .
198
-
BOP -> ne | eq | gt | lt | ge | le | strgt | strlt | streq | strne .
231
+
BOP -> ne | eq | gt | lt | ge | le
232
+
| strne | streq | strgt | strlt | strcm .
199
233
UOP -> ex .
200
234
ID -> id .
201
235
STR -> str .
202
236
```
203
237
204
-
And token mappings:
238
+
Token mappings:
205
239
206
240
```
207
241
ld -> left delimiter, default {{
@@ -213,15 +247,13 @@ id -> [[:alpha:]_][[:alnum:]_]*
213
247
str -> Everything outside the toplevel ld...rd or inside "..." or '...' in ld...rd
214
248
```
215
249
216
-
# Benchmarking
250
+
# Benchmarks
217
251
218
252
Comparisons were made among some pure-bash template engines that work out of the box in two different settings:
219
253
220
254
- Rendering a simple plain text "aaa": This measures the startup overhead of the engine.
221
255
- Rendering 1000 variables `${var}`/`{{var}}`/`<%var%>` whose content is set to `a`: This measures the variable render performance.
222
256
223
-
We have set the plain bash processing/variable replacement as the baseline. Each test is run 12 times, with the first 2 times as warmups. You can find more details in the benchmark.sh file. If you want to run the benchmarks yourself, you'll need to install [hyperfine][hyperfine].
224
-
225
257
The plain bash processing/variable replacement was set as the baseline. Each test was run 12 times, with the first 2 times as warmups. More details can be found in the [benchmark.sh](benchmark/benchmark.sh) file. If you want to run the benchmarks yourself, [hyperfine][hyperfine] needs to be installed.
226
258
227
259
The following results were obtained with Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz:
@@ -248,7 +280,7 @@ The following results were obtained with Intel(R) Core(TM) i9-9900K CPU @ 3.60GH
Engines that do complete parsing ([mo][mo], [bash-tpl][bash-tpl] and [bpt][bpt]) are slower by orders of magitude than engines that only do pattern matching and replacement (plain bash, [shtpl][shtpl] and [renderest][renderest]). A simple profiling of bpt shows that most of its time are spent on parsing (compared to lexing and evaluating the output).
283
+
Engines that do complete parsing ([mo][mo], [bash-tpl][bash-tpl] and [bpt][bpt]) are slower by orders of magitude than engines that only do pattern matching and replacement (plain bash, [shtpl][shtpl] and [renderest][renderest]). A simple profiling of bpt shows that most of its time are spent on the parsing part (time spent on lexing and evaluating the output is relatively negligible).
252
284
253
285
# Contributing
254
286
@@ -267,6 +299,7 @@ Adding functionalities shall not conflict with or break existing features.
0 commit comments