├── .gitignore
├── .goreleaser.yml
├── LICENSE
├── Makefile
├── README.md
├── assets
├── index.html
└── screenshot.png
├── cmd
└── main.go
├── go.mod
├── go.sum
└── gotp.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io/api/go
2 |
3 | ### Go ###
4 | # Binaries for programs and plugins
5 | *.exe
6 | *.exe~
7 | *.dll
8 | *.so
9 | *.dylib
10 |
11 | # Test binary, build with `go test -c`
12 | *.test
13 |
14 | # Output of the go coverage tool, specifically when used with LiteIDE
15 | *.out
16 |
17 | ### Go Patch ###
18 | /vendor/
19 | /Godeps/
20 |
21 | .vscode/
22 | tpls/
23 | dist/
24 | gotp
25 | # End of https://www.gitignore.io/api/go
26 |
--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | env:
2 | - RELEASE_BUILDS=dist/gotp_darwin_amd64/gotp dist/gotp_linux_amd64/gotp dist/gotp_windows_amd64/gotp.exe dist/gotp_linux_arm_6/gotp dist/gotp_linux_arm64/gotp
3 |
4 | before:
5 | hooks:
6 | - make deps
7 |
8 | builds:
9 | - binary: gotp
10 | main: ./cmd/main.go
11 | goos:
12 | - darwin
13 | - linux
14 | - windows
15 | goarch:
16 | - amd64
17 | - arm
18 | - arm64
19 | ldflags:
20 | - -s -w -X "main.buildString={{ .Tag }} ({{ .ShortCommit }} {{ .Date }})"
21 |
22 | hooks:
23 | # stuff executables with static assets.
24 | post: make pack-releases
25 |
26 | archives:
27 | - format: tar.gz
28 | files:
29 | - README.md
30 | - LICENSE
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018 Zerodha Technology Pvt. Ltd. (India)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | LAST_COMMIT := $(shell git rev-parse --short HEAD)
2 | LAST_COMMIT_DATE := $(shell git show -s --format=%ci ${LAST_COMMIT})
3 | TAG := $(shell git describe --tags)
4 | BUILDSTR := ${TAG} (${LAST_COMMIT} ${LAST_COMMIT_DATE})
5 | STATIC := assets/index.html
6 | BIN := gotp
7 |
8 | .PHONY: build
9 | build:
10 | go build -o ${BIN} -ldflags="-X 'main.buildString=${BUILDSTR}'" cmd/*.go
11 | - stuffbin -a stuff -in ${BIN} -out ${BIN} ./assets/index.html
12 |
13 | .PHONY: test
14 | test:
15 | go test ./...
16 |
17 | clean:
18 | go clean
19 | - rm -f ${BIN}
20 |
21 | .PHONY: deps
22 | deps:
23 | go get -u github.com/knadh/stuffbin/...
24 |
25 | # pack-releases runns stuffbin packing on a given list of
26 | # binaries. This is used with goreleaser for packing
27 | # release builds for cross-build targets.
28 | .PHONY: pack-releases
29 | pack-releases:
30 | $(foreach var,$(RELEASE_BUILDS),stuffbin -a stuff -in ${var} -out ${var} ${STATIC};)
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Go Template Previewer
4 |
5 | Simple utility to live edit Go template and preview with custom data.
6 |
7 | 
8 |
9 | ## Installation
10 |
11 | Download the latest release from the [releases page](https://github.com/vividvilla/gotp/releases) or clone this repository and run `make deps` && `make build`.
12 |
13 | ## Usage
14 |
15 | ```txt
16 | Go template previewer - Live preview Go templates with custom data.
17 |
18 | Usage:
19 | gotp sample.tmpl
20 | gotp --base-tmpl base.tmpl sample.tmpl
21 | gotp --base-tmpl base.tmpl sample.tmpl --data '{"name": "Gopher"}'
22 | gotp --base-tmpl base/*.tmpl sample.tmpl
23 | gotp --base-tmpl base.tmpl sample.tmpl
24 | gotp --web sample.tmpl
25 | gotp --web --addr :9000 sample.tmpl
26 | gotp --web --base-tmpl base.tmpl sample.tmpl
27 |
28 | -a, --addr string Address to start the server on. (default ":1111")
29 | -b, --base-tmpl stringArray Base template, or glob pattern.
30 | -d, --data string Template data.
31 | --sprig Enable sprig functions
32 | -v, --version Version info
33 | -w, --web Run web UI
34 | ```
35 |
36 | It can run in either `commandline` or `web` mode. Commandline mode renders the template on stdout and
37 | web mode runs a web server which provides UI to update template data and live rendering when template changes.
38 |
39 | ```bash
40 | # Renders sample.tmpl on stdout.
41 | gotp sample.tmpl
42 |
43 | # If your template uses base templates then you can specify it as a commandline flag.
44 | gotp --base-tmpl base.tmpl sample.tmpl
45 |
46 | # If your template uses Sprig template functions you can specify a flag.
47 | gotp --sprig --base-tmpl base.tmpl sample.tmpl
48 |
49 | # If there are multiple base templates then you can repeat the flag.
50 | gotp --base-tmpl base-header.tmpl --base-tmpl base-footer.tmpl sample.tmpl
51 |
52 | # You can also use glob pattern to specify multiple base templates.
53 | gotp --base-tmpl "base-*.tmpl" sample.tmpl
54 |
55 | # Specify the data required by templates as a JSON input. For example if your template
56 | # has variable `{{ .name }}` then you can specify JSON data as `{ "name": "Gopher" }`.
57 | gotp --base-tmpl base.tmpl sample.tmpl --data '{"name": "Gopher"}'
58 |
59 | # You can do all of the above commands with `--web` flag to start a web server which renders the output.
60 | gotp --web --base-tmpl base.tmpl sample.tmpl --data '{"name": "Gopher"}'
61 |
62 | # By default web server runs on `:1111` you can change it by using `--addr`flag.
63 | gotp --web --addr :9000 --base-tmpl base.tmpl sample.tmpl
64 | ```
65 |
66 | ## Caveats
67 |
68 | Currently there is no support for custom Go template functions and pipelines. Although, you can use the `--sprig` flag to enable [Sprig template functions](http://masterminds.github.io/sprig/).
69 |
--------------------------------------------------------------------------------
/assets/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Go template preview
8 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
177 |
180 |
182 |
184 |
186 |
189 |
191 |
195 |
197 |
199 |
201 |
205 |
207 |
208 |
213 |
214 |
216 |
218 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
231 |
232 |
235 |
237 |
238 |
239 |
240 |
241 |
Render
242 |
243 |
244 |
You can mock your template variables by providing custom template data (JSON encoded map).
245 |
For example following template
246 |
Hi, {{ .name }}, your id is {{ .user_id }}
247 |
can be mocked by JSON below
248 |
{ "name": "Gopher", "user_id": "rambo226" }
249 |
250 |
251 |
Auto Discovered fields
252 |
253 |
Copy fields
254 |
255 |
258 |
259 |
260 |
399 |
400 |
401 |
--------------------------------------------------------------------------------
/assets/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vividvilla/gotp/96255516feee44c4c5575179e9c26d501edf982d/assets/screenshot.png
--------------------------------------------------------------------------------
/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io/ioutil"
7 | "log"
8 | "net/http"
9 | "os"
10 | "path/filepath"
11 | "regexp"
12 | "sync"
13 |
14 | "github.com/fsnotify/fsnotify"
15 | "github.com/knadh/stuffbin"
16 | "github.com/r3labs/sse"
17 | flag "github.com/spf13/pflag"
18 | "github.com/vividvilla/gotp"
19 | )
20 |
21 | const helpTxt = `
22 | Usage:
23 | gotp sample.tmpl
24 | gotp --base-tmpl base.tmpl sample.tmpl
25 | gotp --base-tmpl base.tmpl sample.tmpl --data '{"name": "Gopher"}'
26 | gotp --base-tmpl base/*.tmpl sample.tmpl
27 | gotp --base-tmpl base.tmpl sample.tmpl
28 | gotp --web sample.tmpl
29 | gotp --web --addr :9000 sample.tmpl
30 | gotp --web --base-tmpl base.tmpl sample.tmpl
31 | `
32 |
33 | var (
34 | outputModeWeb bool
35 | tmplData map[string]interface{}
36 | tmplDataMux sync.Mutex
37 | addr string
38 | tmplPath string
39 | baseTmplPaths []string
40 | server *sse.Server
41 | gotpCfg gotp.Config
42 | buildString = "unknown"
43 | nodeFieldRe = regexp.MustCompile(`{{(?:\s+)?(\.[\w]+)(?:\s+)?}}`)
44 | )
45 |
46 | // Resp represents JSON response structure.
47 | type Resp struct {
48 | Data json.RawMessage `json:"data,omitempty"`
49 | Error string `json:"error,omitempty"`
50 | }
51 |
52 | func init() {
53 | var (
54 | tmplDataRaw string
55 | version bool
56 | )
57 | flag.StringVarP(&addr, "addr", "a", ":1111", "Address to start the server on.")
58 | flag.StringArrayVarP(&baseTmplPaths, "base-tmpl", "b", []string{}, "Base template, or glob pattern.")
59 | flag.StringVarP(&tmplDataRaw, "data", "d", "", "Template data.")
60 | flag.BoolVarP(&outputModeWeb, "web", "w", false, "Run web UI")
61 | flag.BoolVarP(&version, "version", "v", false, "Version info")
62 | flag.BoolVar(&gotpCfg.UseSprig, "sprig", false, "Enable sprig functions")
63 |
64 | // Usage help.
65 | flag.Usage = func() {
66 | fmt.Printf("Go template previewer - Live preview Go templates with custom data.\n")
67 | fmt.Println(helpTxt)
68 | flag.PrintDefaults()
69 | }
70 |
71 | // Parse flags.
72 | flag.Parse()
73 | if flag.NFlag() == 0 && len(flag.Args()) == 0 {
74 | flag.Usage()
75 | os.Exit(0)
76 | return
77 | }
78 |
79 | // Print version and exit
80 | if version {
81 | fmt.Printf("Gotp - %s\n", buildString)
82 | os.Exit(0)
83 | }
84 |
85 | // Assign target template path.
86 | tmplPath = flag.Args()[0]
87 |
88 | // Assign templateData
89 | if tmplDataRaw != "" {
90 | d, err := decodeTemplateData([]byte(tmplDataRaw))
91 | if err != nil {
92 | fmt.Printf("invalid template data: %v", err)
93 | os.Exit(1)
94 | }
95 | tmplData = d
96 | }
97 | }
98 |
99 | func initFileWatcher(paths []string) (*fsnotify.Watcher, error) {
100 | fw, err := fsnotify.NewWatcher()
101 | if err != nil {
102 | return fw, err
103 | }
104 |
105 | files := []string{}
106 | // Get all the files which needs to be watched.
107 | for _, p := range paths {
108 | m, err := filepath.Glob(p)
109 | if err != nil {
110 | return fw, err
111 | }
112 | files = append(files, m...)
113 | }
114 |
115 | // Watch all template files.
116 | for _, f := range files {
117 | if err := fw.Add(f); err != nil {
118 | return fw, err
119 | }
120 | }
121 | return fw, err
122 | }
123 |
124 | func initSSEServer(fw *fsnotify.Watcher) *sse.Server {
125 | server := sse.New()
126 | server.CreateStream("messages")
127 | go func() {
128 | for {
129 | select {
130 | // Watch for events.
131 | case _ = <-fw.Events:
132 | log.Printf("files changed")
133 | // Send a ping notify frontent about file changes.
134 | server.Publish("messages", &sse.Event{
135 | Data: []byte("-"),
136 | })
137 | // Watch for errors.
138 | case err := <-fw.Errors:
139 | log.Printf("error watching files: %v", err)
140 | }
141 | }
142 | }()
143 | return server
144 | }
145 |
146 | func binPath() (string, error) {
147 | path, err := os.Executable()
148 | if err != nil {
149 | return "", err
150 | }
151 | return path, nil
152 | }
153 |
154 | func initFileSystem() (stuffbin.FileSystem, error) {
155 | path, err := binPath()
156 | if err != nil {
157 | return nil, fmt.Errorf("error getting binary path: %v", err.Error())
158 | }
159 |
160 | // Read stuffed data from self.
161 | fs, err := stuffbin.UnStuff(path)
162 | if err != nil {
163 | if err == stuffbin.ErrNoID {
164 | fs, err = stuffbin.NewLocalFS("/", "./", "../assets/index.html:/assets/index.html")
165 | if err != nil {
166 | return fs, fmt.Errorf("error falling back to local filesystem: %v", err)
167 | }
168 | } else {
169 | return fs, fmt.Errorf("error reading stuffed binary: %v", err)
170 | }
171 | }
172 | return fs, nil
173 | }
174 |
175 | func decodeTemplateData(dataRaw []byte) (map[string]interface{}, error) {
176 | var data map[string]interface{}
177 | err := json.Unmarshal(dataRaw, &data)
178 | if err != nil {
179 | return data, err
180 | }
181 | return data, nil
182 | }
183 |
184 | func writeJSONResp(w http.ResponseWriter, statusCode int, data []byte, e string) {
185 | var resp = Resp{
186 | Data: json.RawMessage(data),
187 | Error: e,
188 | }
189 | b, err := json.Marshal(resp)
190 | if err != nil {
191 | log.Printf("error encoding response: %v, data: %v", err, string(data))
192 | }
193 | w.WriteHeader(statusCode)
194 | w.Header().Set("Content-Type", "application/json")
195 | w.Write(b)
196 | }
197 |
198 | func handleGetTemplateData(w http.ResponseWriter, r *http.Request) {
199 | d, err := json.Marshal(tmplData)
200 | if err != nil {
201 | writeJSONResp(w, http.StatusInternalServerError, nil, fmt.Sprintf("Error reading request body: %v", err))
202 | } else {
203 | writeJSONResp(w, http.StatusOK, d, "")
204 | }
205 | }
206 |
207 | func handleUpdateTemplateData(w http.ResponseWriter, r *http.Request) {
208 | body, err := ioutil.ReadAll(r.Body)
209 | if err != nil {
210 | writeJSONResp(w, http.StatusBadRequest, nil, fmt.Sprintf("Error reading request body: %v", err))
211 | return
212 | }
213 |
214 | data, err := decodeTemplateData(body)
215 | if err != nil {
216 | writeJSONResp(w, http.StatusBadRequest, nil, fmt.Sprintf("Error parsing JSON data: %v", err))
217 | return
218 | }
219 |
220 | // Update template data.
221 | tmplDataMux.Lock()
222 | tmplData = data
223 | tmplDataMux.Unlock()
224 |
225 | // Publish a message to reload.
226 | server.Publish("messages", &sse.Event{
227 | Data: []byte("-"),
228 | })
229 |
230 | writeJSONResp(w, http.StatusOK, nil, "")
231 | }
232 |
233 | func handleGetTemplateFields(w http.ResponseWriter, r *http.Request) {
234 | t, err := gotp.GetTemplate(gotpCfg, tmplPath, baseTmplPaths)
235 | if err != nil {
236 | writeJSONResp(w, http.StatusInternalServerError, nil, fmt.Sprintf("Error getting template fields: %v", err))
237 | return
238 | }
239 | fields := gotp.NodeFields(t)
240 | mFields := []string{}
241 | for _, f := range fields {
242 | if nodeFieldRe.MatchString(f) {
243 | mFields = append(mFields, f)
244 | }
245 | }
246 | b, err := json.Marshal(mFields)
247 | if err != nil {
248 | writeJSONResp(w, http.StatusInternalServerError, nil, fmt.Sprintf("Error encoding template fields: %v", err))
249 | return
250 | }
251 | writeJSONResp(w, http.StatusOK, b, "")
252 | }
253 |
254 | func main() {
255 | if !outputModeWeb {
256 | b, err := gotp.Compile(gotpCfg, tmplPath, baseTmplPaths, tmplData)
257 | if err != nil {
258 | fmt.Printf("error rendering template: %v\n", err)
259 | os.Exit(1)
260 | }
261 | fmt.Println(string(b))
262 | return
263 | }
264 |
265 | // Initialize file system.
266 | fs, err := initFileSystem()
267 | if err != nil {
268 | log.Printf("error initializing file system: %v", err)
269 | os.Exit(1)
270 | }
271 |
272 | // Initialize file watcher.
273 | paths := append(baseTmplPaths, tmplPath)
274 | fw, err := initFileWatcher(paths)
275 | defer fw.Close()
276 | if err != nil {
277 | log.Printf("error watching files: %v", err)
278 | os.Exit(1)
279 | }
280 |
281 | // Initialize SSE server.
282 | server = initSSEServer(fw)
283 |
284 | // Create a new Mux and set the handler
285 | mux := http.NewServeMux()
286 | // Attach SSE handler.
287 | mux.HandleFunc("/events", server.HTTPHandler)
288 | // Server index.html page.
289 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
290 | f, err := fs.Get("/assets/index.html")
291 | if err != nil {
292 | log.Fatalf("error reading foo.txt: %v", err)
293 | }
294 | // Write response.
295 | w.WriteHeader(http.StatusOK)
296 | w.Header().Set("Content-Type", "text/html")
297 | w.Write(f.ReadBytes())
298 | })
299 |
300 | // Handler to render template.
301 | mux.HandleFunc("/out", func(w http.ResponseWriter, r *http.Request) {
302 | if r.Method != http.MethodGet {
303 | // Write response.
304 | w.WriteHeader(http.StatusMethodNotAllowed)
305 | return
306 | }
307 | // Complie the template.
308 | b, err := gotp.Compile(gotpCfg, tmplPath, baseTmplPaths, tmplData)
309 | // If error send error as output.
310 | if err != nil {
311 | b = []byte(fmt.Sprintf("error rendering: %v", err))
312 | }
313 | // Write response.
314 | w.WriteHeader(http.StatusOK)
315 | w.Header().Set("Content-Type", "text/html")
316 | w.Write(b)
317 | })
318 |
319 | mux.HandleFunc("/data", func(w http.ResponseWriter, r *http.Request) {
320 | if r.Method == http.MethodPut {
321 | handleUpdateTemplateData(w, r)
322 | return
323 | } else if r.Method == http.MethodGet {
324 | handleGetTemplateData(w, r)
325 | } else {
326 | // Write response.
327 | w.WriteHeader(http.StatusMethodNotAllowed)
328 | }
329 | })
330 |
331 | mux.HandleFunc("/fields", func(w http.ResponseWriter, r *http.Request) {
332 | if r.Method == http.MethodGet {
333 | handleGetTemplateFields(w, r)
334 | } else {
335 | w.WriteHeader(http.StatusMethodNotAllowed)
336 | }
337 | })
338 |
339 | log.Printf("Starting server on address - %v", addr)
340 | // Start server on given port.
341 | if err := http.ListenAndServe(addr, mux); err != nil {
342 | panic(err)
343 | }
344 | }
345 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/vividvilla/gotp
2 |
3 | go 1.12
4 |
5 | require (
6 | github.com/Masterminds/sprig/v3 v3.2.3
7 | github.com/fsnotify/fsnotify v1.4.7
8 | github.com/google/uuid v1.3.0 // indirect
9 | github.com/huandu/xstrings v1.4.0 // indirect
10 | github.com/imdario/mergo v0.3.14 // indirect
11 | github.com/knadh/stuffbin v1.1.0
12 | github.com/mitchellh/copystructure v1.2.0 // indirect
13 | github.com/r3labs/sse v0.0.0-20191119133840-aaec8ab50b85
14 | github.com/shopspring/decimal v1.3.1 // indirect
15 | github.com/spf13/cast v1.5.0 // indirect
16 | github.com/spf13/pflag v1.0.5
17 | golang.org/x/crypto v0.7.0 // indirect
18 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
19 | )
20 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
2 | github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
3 | github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
4 | github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
5 | github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
6 | github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
7 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
11 | github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
12 | github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
13 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
14 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
15 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
16 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
17 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
18 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
19 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
20 | github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
21 | github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
22 | github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
23 | github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
24 | github.com/imdario/mergo v0.3.14 h1:fOqeC1+nCuuk6PKQdg9YmosXX7Y7mHX6R/0ZldI9iHo=
25 | github.com/imdario/mergo v0.3.14/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
26 | github.com/knadh/stuffbin v1.0.0 h1:NQon6PTpLXies4bRFhS3VpLCf6y+jn6YVXU3i2wPQ+M=
27 | github.com/knadh/stuffbin v1.0.0/go.mod h1:yVCFaWaKPubSNibBsTAJ939q2ABHudJQxRWZWV5yh+4=
28 | github.com/knadh/stuffbin v1.1.0 h1:f5S5BHzZALjuJEgTIOMC9NidEnBJM7Ze6Lu1GHR/lwU=
29 | github.com/knadh/stuffbin v1.1.0/go.mod h1:yVCFaWaKPubSNibBsTAJ939q2ABHudJQxRWZWV5yh+4=
30 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
31 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
32 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
33 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
34 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
35 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
36 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
37 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
38 | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
39 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
40 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
41 | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
42 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
43 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
44 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
45 | github.com/r3labs/sse v0.0.0-20191119133840-aaec8ab50b85 h1:GVg/+GS8UfqOEFdzAIBknVw6hGq/bmUWsFEsDeAKSLA=
46 | github.com/r3labs/sse v0.0.0-20191119133840-aaec8ab50b85/go.mod h1:S8xSOnV3CgpNrWd0GQ/OoQfMtlg2uPRSuTzcSGrzwK8=
47 | github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
48 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
49 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
50 | github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
51 | github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
52 | github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
53 | github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
54 | github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
55 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
56 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
57 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
58 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
59 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
60 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
61 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
62 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
63 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
64 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
65 | golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
66 | golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
67 | golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
68 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
69 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
70 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
71 | golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
72 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
73 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
74 | golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
75 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
76 | golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
77 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
78 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
79 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
80 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
81 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
82 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
83 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
84 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
85 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
86 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
87 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
88 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
89 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
90 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
91 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
92 | golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
93 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
94 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
95 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
96 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
97 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
98 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
99 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
100 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
101 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
102 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
103 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
104 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
105 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
106 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
107 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
108 | gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y=
109 | gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI=
110 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
111 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
112 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
113 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
114 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
115 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
116 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
117 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
118 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
119 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
120 |
--------------------------------------------------------------------------------
/gotp.go:
--------------------------------------------------------------------------------
1 | package gotp
2 |
3 | import (
4 | "bytes"
5 | "html/template"
6 | "path/filepath"
7 | "text/template/parse"
8 |
9 | "github.com/Masterminds/sprig/v3"
10 | )
11 |
12 | type Config struct {
13 | UseSprig bool
14 | }
15 |
16 | // NodeFields returns list of fields evaluated in template.
17 | func NodeFields(t *template.Template) []string {
18 | return listNodeFields(t.Tree.Root, nil)
19 | }
20 |
21 | func listNodeFields(node parse.Node, res []string) []string {
22 | if node.Type() == parse.NodeAction {
23 | res = append(res, node.String())
24 | }
25 |
26 | if ln, ok := node.(*parse.ListNode); ok {
27 | for _, n := range ln.Nodes {
28 | res = listNodeFields(n, res)
29 | }
30 | }
31 |
32 | return res
33 | }
34 |
35 | // GetTemplate returns a go template for given template paths.
36 | func GetTemplate(cfg Config, tmpl string, baseTmplPaths []string) (*template.Template, error) {
37 | var err error
38 | t := template.New("main")
39 |
40 | if cfg.UseSprig {
41 | t = t.Funcs(sprig.FuncMap())
42 | }
43 |
44 | // Load base templates, if template name is glob pattern then it
45 | // loads all matches templates.
46 | for _, bt := range baseTmplPaths {
47 | t, err = t.ParseGlob(bt)
48 | if err != nil {
49 | return t, err
50 | }
51 | }
52 |
53 | // Load target template.
54 | t, err = t.ParseFiles(tmpl)
55 | if err != nil {
56 | return t, err
57 | }
58 |
59 | // From loaded templates get target template.
60 | // Loaded templates are referenced against the filename instead of full path,
61 | // so get the filename of the target template and load from registered templates.
62 | name := filepath.Base(tmpl)
63 | return t.Lookup(name), nil
64 | }
65 |
66 | // Compile compiles given template and base templates with given data.
67 | func Compile(cfg Config, tmpl string, baseTmplPaths []string, data map[string]interface{}) ([]byte, error) {
68 | var err error
69 | t, err := GetTemplate(cfg, tmpl, baseTmplPaths)
70 | if err != nil {
71 | return []byte(""), err
72 | }
73 |
74 | var tmplBody bytes.Buffer
75 | if err := t.Execute(&tmplBody, data); err != nil {
76 | return []byte(""), err
77 | }
78 | return tmplBody.Bytes(), nil
79 | }
80 |
81 | // CompileString compiles template string with given data.
82 | func CompileString(tmpl string, data map[string]interface{}) ([]byte, error) {
83 | var err error
84 | t := template.New("main")
85 | // Parse template string.
86 | t, err = t.Parse(tmpl)
87 | if err != nil {
88 | return []byte(""), err
89 | }
90 | var tmplBody bytes.Buffer
91 | // Execute template with given data.
92 | if err := t.Execute(&tmplBody, data); err != nil {
93 | return []byte(""), err
94 | }
95 | return tmplBody.Bytes(), nil
96 | }
97 |
--------------------------------------------------------------------------------