Skip to content

Commit 489d1ec

Browse files
Merge pull request #13 from speakeasy-api/add-multiple-render-funcs
2 parents 7f9b227 + b00acb0 commit 489d1ec

File tree

15 files changed

+275
-71
lines changed

15 files changed

+275
-71
lines changed

.golangci.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ linters:
1515
- golint
1616
- maligned
1717
- gci
18+
- depguard
1819
# deprecated/archived
1920
- interfacer
2021
- scopelint

README.md

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,38 @@ This is done by calling the following functions from within templates and script
145145
* `templateString(templateFile string, data any) (string, error)` - Start a template file and return the rendered template as a string.
146146
* `templateFile` (string) - The path to the template file to start the engine from.
147147
* `data` (any) - Context data to provide to templates and scripts. Available as `{{.Local}}` in templates and `context.Local` in scripts.
148+
* `templateStringInput(templateName string, templateString string, data any) (string, error)` - Template the input string and return the rendered template as a string.
149+
* `templateName` (string) - The name of the template to render.
150+
* `templateString` (string) - An input template string to template.
151+
* `data` (any) - Context data to provide to templates and scripts. Available as `{{.Local}}` in templates and `context.Local` in scripts.
152+
* `recurse(recursions int) string` - Recurse the current template file, recursions is the number of times to recurse the template file.
153+
* `recursions` (int) - The number of times to recurse the template file.
148154

149155
This allows for example:
150156

151157
```gotemplate
152158
{{ templateFile "tmpl.stmpl" "out.txt" .Local }}{{/* Template another file */}}
153159
{{ templateString "tmpl.stmpl" .Local }}{{/* Template another file and include the rendered output in this templates rendered output */}}
160+
{{ templateStringInput "Hello {{ .Local.name }}" .Local }}{{/* Template a string and include the rendered output in this templates rendered output */}}
154161
```
155162

163+
#### Recursive templating
164+
165+
It is possible with the `recurse` function in a template to render the same template multiple times. This can be useful when data to render parts of the template are only available after you have rendered it at least once.
166+
167+
For example:
168+
169+
```go
170+
{{- recurse 1 -}}
171+
{{"{{.RecursiveComputed.Names}}"}}{{/* Render the names of the customers after we have iterated over them later */}}
172+
{{range .Local.Customers}}
173+
{{- addName .RecursiveComputed.Names (print .FirstName " " .LastName) -}}
174+
{{.FirstName}} {{.LastName}}
175+
{{end}}
176+
```
177+
178+
Note: The `recurse` function must be called as the first thing in the template on its own line.
179+
156180
### Registering templating functions
157181

158182
The engine allows you to register custom templating functions from Go which can be used within the templates.
@@ -184,8 +208,9 @@ sjs```
184208
The `sjs` snippet can be used anywhere within your template (including multiple snippets) and will be replaced with any "rendered" output returned when using the `render` function.
185209

186210
Naive transformation of typescript code is supported through [esbuild](https://esbuild.github.io/api/#transformation). This means that you can directly import typescript code and use type annotations in place of any JavaScript. However, be aware:
187-
* EasyTemplate will not perform type checking itself. Type annotations are transformed into commented out code.
188-
* Scripts/Snippets are not bundled, but executed as a single module on the global scope. This means no `import` statements are possible. [Instead, the global `require` function](#importing-javascript) is available to directly execute JS/TS code.
211+
212+
* EasyTemplate will not perform type checking itself. Type annotations are transformed into commented out code.
213+
* Scripts/Snippets are not bundled, but executed as a single module on the global scope. This means no `import` statements are possible. [Instead, the global `require` function](#importing-javascript) is available to directly execute JS/TS code.
189214

190215
### Context data
191216

@@ -305,6 +330,10 @@ The following functions are available to JavaScript from the templating engine:
305330
* `templateString(templateString, data)` - Render a template and return the rendered output.
306331
* `templateString` (string) - The template string to render.
307332
* `data` (object) - Data available to the template as `Local` context ie `{name: "John"}` is available as `{{ .Local.name }}`.
333+
* `templateStringInput(templateName, templateString, data)` - Render a template and return the rendered output.
334+
* `templateName` (string) - The name of the template to render.
335+
* `templateString` (string) - The template string to render.
336+
* `data` (object) - Data available to the template as `Local` context ie `{name: "John"}` is available as `{{ .Local.name }}`.
308337
* `render(output)` - Render the output to the template file, if called multiples times the output will be appended to the previous output as a new line. The cumulative output will replace the current `sjs` block in the template file.
309338
* `output` (string) - The output to render.
310339
* `require(filePath)` - Import a JavaScript file into the global scope.

engine.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ func New(opts ...Opt) *Engine {
129129

130130
e.jsFuncs = map[string]func(call CallContext) goja.Value{
131131
"require": e.require,
132+
"recurse": e.recurseJS,
132133
"templateFile": e.templateFileJS,
133134
"templateString": e.templateStringJS,
134135
"templateStringInput": e.templateStringInputJS,
@@ -290,6 +291,16 @@ func (e *Engine) init(data any) (*vm.VM, error) {
290291
return templated, nil
291292
}
292293
}(v)
294+
e.templator.TmplFuncs["recurse"] = func(v *vm.VM) func(int) (string, error) {
295+
return func(numTimes int) (string, error) {
296+
templated, err := e.templator.Recurse(v, numTimes)
297+
if err != nil {
298+
return "", err
299+
}
300+
301+
return templated, nil
302+
}
303+
}(v)
293304

294305
if _, err := v.Run("initCreateComputedContextObject", `function createComputedContextObject() { return {}; }`); err != nil {
295306
return nil, utils.HandleJSError("failed to init createComputedContextObject", err)

engine_integration_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func TestEngine_RunScript_Success(t *testing.T) {
5454
err = e.RunScript("scripts/test.js", map[string]interface{}{
5555
"Test": "global",
5656
})
57-
assert.NoError(t, err)
57+
require.NoError(t, err)
5858

5959
assert.Empty(t, expectedFiles, "not all expected files were written")
6060
}

go.mod

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@ module github.com/speakeasy-api/easytemplate
33
go 1.19
44

55
require (
6-
github.com/dop251/goja v0.0.0-20231024180952-594410467bc6
7-
github.com/dop251/goja_nodejs v0.0.0-20221211191749-434192f0843e
8-
github.com/evanw/esbuild v0.17.8
6+
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d
7+
github.com/dop251/goja_nodejs v0.0.0-20231022114343-5c1f9037c9ab
8+
github.com/evanw/esbuild v0.19.5
99
github.com/go-sourcemap/sourcemap v2.1.3+incompatible
1010
github.com/golang/mock v1.6.0
1111
github.com/stretchr/testify v1.8.1
1212
)
1313

1414
require (
1515
github.com/davecgh/go-spew v1.1.1 // indirect
16-
github.com/dlclark/regexp2 v1.7.0 // indirect
17-
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
16+
github.com/dlclark/regexp2 v1.10.0 // indirect
17+
github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 // indirect
1818
github.com/pmezard/go-difflib v1.0.0 // indirect
19-
golang.org/x/sys v0.3.0 // indirect
20-
golang.org/x/text v0.5.0 // indirect
19+
golang.org/x/sys v0.6.0 // indirect
20+
golang.org/x/text v0.13.0 // indirect
2121
gopkg.in/yaml.v3 v3.0.1 // indirect
2222
)

go.sum

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,25 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
66
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
77
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
88
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
9-
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
109
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
10+
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
11+
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
1112
github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
12-
github.com/dop251/goja v0.0.0-20221118162653-d4bf6fde1b86/go.mod h1:yRkwfj0CBpOGre+TwBsqPV0IH0Pk73e4PXJOeNDboGs=
13-
github.com/dop251/goja v0.0.0-20231024180952-594410467bc6 h1:U9bRrSlYCu0P8hMulhIdYpr5HUao66tKPdNgD88Zi5M=
14-
github.com/dop251/goja v0.0.0-20231024180952-594410467bc6/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
13+
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d h1:wi6jN5LVt/ljaBG4ue79Ekzb12QfJ52L9Q98tl8SWhw=
14+
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
1515
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
1616
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
17-
github.com/dop251/goja_nodejs v0.0.0-20221211191749-434192f0843e h1:DJ5cKH4HUYevCd09vMIbwc8U02eBKLFR2q1O1hSAJcY=
18-
github.com/dop251/goja_nodejs v0.0.0-20221211191749-434192f0843e/go.mod h1:0tlktQL7yHfYEtjcRGi/eiOkbDR5XF7gyFFvbC5//E0=
19-
github.com/evanw/esbuild v0.17.8 h1:QzE7cRRq7y3qH7ZKGN0/nUGbdZPyikfNaMaCMNUufnU=
20-
github.com/evanw/esbuild v0.17.8/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk=
17+
github.com/dop251/goja_nodejs v0.0.0-20231022114343-5c1f9037c9ab h1:LrVf0AFnp5WiGKJ0a6cFf4RwNIN327uNUeVGJtmAFEE=
18+
github.com/dop251/goja_nodejs v0.0.0-20231022114343-5c1f9037c9ab/go.mod h1:bhGPmCgCCTSRfiMYWjpS46IDo9EUZXlsuUaPXSWGbv0=
19+
github.com/evanw/esbuild v0.19.5 h1:9ildZqajUJzDAwNf9MyQsLh2RdDRKTq3kcyyzhE39us=
20+
github.com/evanw/esbuild v0.19.5/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
2121
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
2222
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
2323
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
2424
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
25-
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U=
2625
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
26+
github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 h1:pUa4ghanp6q4IJHwE9RwLgmVFfReJN+KbQ8ExNEUUoQ=
27+
github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
2728
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
2829
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
2930
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@@ -56,7 +57,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
5657
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
5758
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
5859
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
59-
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
6060
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
6161
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
6262
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -70,18 +70,17 @@ golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBc
7070
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
7171
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
7272
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
73-
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
74-
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
73+
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
74+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
7575
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
7676
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
77-
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
7877
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
7978
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
8079
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
8180
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
8281
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
83-
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
84-
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
82+
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
83+
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
8584
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
8685
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
8786
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=

0 commit comments

Comments
 (0)