├── .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 | ![Screenshot](assets/screenshot.png "Gotp web mode") 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 | 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 | --------------------------------------------------------------------------------