From 1d7899b6dc5763f110827b198be215f7230af4bb Mon Sep 17 00:00:00 2001 From: pjdufour Date: Thu, 20 Jun 2019 09:57:47 -0400 Subject: [PATCH 1/4] cross compile to c-shared and javascript --- .circleci/config.yml | 59 +++--------- .eslintignore | 10 ++ .eslintrc | 35 +++++++ .gitignore | 11 ++- Makefile | 174 ++++++++++++++++++++++++++++++++--- README.md | 42 ++++++++- cmd/gotmpl.global.js/main.go | 39 ++++++++ cmd/gotmpl.mod.js/main.go | 38 ++++++++ cmd/gotmpl/main.go | 100 ++++++++++++++++++++ examples/c/main.c | 34 +++++++ examples/cpp/main.cpp | 65 +++++++++++++ examples/js/index.global.js | 21 +++++ examples/js/index.mod.js | 20 ++++ examples/python/test.py | 50 ++++++++++ js/gotmpl.test.js | 39 ++++++++ main.go | 126 ------------------------- package.json | 39 ++++++++ pkg/gotmpl/InitFunctions.go | 57 ++++++++++++ pkg/gotmpl/LoadContext.go | 33 +++++++ pkg/gotmpljs/Exports.go | 66 +++++++++++++ pkg/gotmpljs/gotmpljs.go | 10 ++ plugins/gotmpl/main.go | 68 ++++++++++++++ testEnvironment.js | 21 +++++ 23 files changed, 964 insertions(+), 193 deletions(-) create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 cmd/gotmpl.global.js/main.go create mode 100644 cmd/gotmpl.mod.js/main.go create mode 100644 cmd/gotmpl/main.go create mode 100644 examples/c/main.c create mode 100644 examples/cpp/main.cpp create mode 100644 examples/js/index.global.js create mode 100644 examples/js/index.mod.js create mode 100644 examples/python/test.py create mode 100644 js/gotmpl.test.js delete mode 100644 main.go create mode 100644 package.json create mode 100644 pkg/gotmpl/InitFunctions.go create mode 100644 pkg/gotmpl/LoadContext.go create mode 100644 pkg/gotmpljs/Exports.go create mode 100644 pkg/gotmpljs/gotmpljs.go create mode 100644 plugins/gotmpl/main.go create mode 100644 testEnvironment.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 7e15367..cf72bc2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,68 +9,35 @@ jobs: executor: base steps: - checkout - - run: go get -d ./... - - run: go get github.com/inconshreveable/mousetrap # for windows CLI builds + - run: make deps_go - run: sudo chown -R circleci /go/src - save_cache: key: v1-go-src-{{ .Branch }}-{{ .Revision }} paths: - /go/src - test: - executor: base - steps: - - run: sudo chown -R circleci /go/src - - restore_cache: - keys: - - v1-go-src-{{ .Branch }}-{{ .Revision }} - - run: - name: Get shadow - command: go get golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow - - run: - name: Install shadow - command: go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow - - run: - name: Download and install errcheck - command: go get -u github.com/kisielk/errcheck - - run: - name: Download and install misspell - command: go get -u github.com/client9/misspell/cmd/misspell - - run: - name: Download and install ineffassign - command: go get -u github.com/gordonklaus/ineffassign - - run: - name: Download and install staticheck - command: go get -u honnef.co/go/tools/cmd/staticcheck - - run: make test - validate: + build_cli: executor: base steps: - run: sudo chown -R circleci /go/src - restore_cache: keys: - v1-go-src-{{ .Branch }}-{{ .Revision }} - - run: - name: Install Dig - command: sudo apt update && sudo apt install dnsutils - - run: - name: "Update ~/.ssh/known_hosts" - command: | - mkdir ~/.ssh/ - touch ~/.ssh/known_hosts - for ip in $(dig @8.8.8.8 github.com +short); do ssh-keyscan github.com,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts - - run: go get github.com/spatialcurrent/go-header/... - - run: go install github.com/spatialcurrent/go-header/cmd/goheader - - run: goheader fix --fix-year 2019 --exit-code-on-changes 1 --verbose - build_cli: + - run: go get github.com/inconshreveable/mousetrap # for windows CLI builds + - run: make build_cli + - store_artifacts: + path: bin + destination: / + build_javascript: executor: base steps: - run: sudo chown -R circleci /go/src - restore_cache: keys: - v1-go-src-{{ .Branch }}-{{ .Revision }} - - run: make build + - run: make deps_gopherjs + - run: make build_javascript - store_artifacts: - path: bin + path: dist destination: / workflows: main: @@ -79,9 +46,9 @@ workflows: - test: requires: - pre_deps_golang - - validate: + - build_cli: requires: - pre_deps_golang - - build_cli: + - build_javascript: requires: - pre_deps_golang diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..c9aac35 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,10 @@ +__mocks__/ +dist/ +bin/ +node_modules/ +webpack/ +.DS_Store +.npm +node_modules +npm-debug.* +package-lock.json diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..d98e117 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,35 @@ +{ + "parser": "babel-eslint", + "extends": "eslint-config-airbnb", + "rules": { + "arrow-parens": ["error", "as-needed"], + "class-methods-use-this": "off", + "comma-dangle": ["error", { "objects": "always-multiline" }], + "import/no-named-as-default": "off", + "import/no-unresolved": "off", + "no-use-before-define": "off", + "object-curly-newline": "off", + "prefer-destructuring": "off", + "react/destructuring-assignment": "off", + "react/forbid-prop-types": "off", + "react/jsx-filename-extension": "off", + "react/jsx-no-bind": "off", + "react/no-access-state-in-setstate": "off", + "react/prefer-stateless-function": "off", + "react/require-default-props": "off", + "react/sort-comp": ["error", { // Team Preference + "order": [ + "static-methods", + "/constructor/", + "/state/", + "instance-variables", + "lifecycle", + "getters", + "everything-else", + "render" + ] + }], + "sort-keys": "error", + "space-before-function-paren": ["error", "always"] + } +} diff --git a/.gitignore b/.gitignore index 3605725..5950002 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,11 @@ -secret/ -bin/ +bin +dist +vendor +Gopkg.lock *.so *.h +.DS_Store +.npm +node_modules +npm-debug.* +package-lock.json diff --git a/Makefile b/Makefile index c2e411e..6870928 100644 --- a/Makefile +++ b/Makefile @@ -5,32 +5,176 @@ # # ================================================================= +ifdef GOPATH +GCFLAGS=-trimpath=$(shell printenv GOPATH)/src +else +GCFLAGS=-trimpath=$(shell go env GOPATH)/src +endif + +LDFLAGS=-X main.gitBranch=$(shell git branch | grep \* | cut -d ' ' -f2) -X main.gitCommit=$(shell git rev-list -1 HEAD) + +ifndef DEST +DEST=bin +endif + +.PHONY: help +help: ## Print the help documentation + @grep -E '^[a-zA-Z_-\]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +# +# Dependencies +# + +deps_go: ## Install Go dependencies + go get -d -t ./... + +.PHONY: deps_go_test +deps_go_test: ## Download Go dependencies for tests + go get golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow # download shadow + go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow # install shadow + go get -u github.com/kisielk/errcheck # download and install errcheck + go get -u github.com/client9/misspell/cmd/misspell # download and install misspell + go get -u github.com/gordonklaus/ineffassign # download and install ineffassign + go get -u honnef.co/go/tools/cmd/staticcheck # download and instal staticcheck + go get -u golang.org/x/tools/cmd/goimports # download and install goimports + +deps_arm: ## Install dependencies to cross-compile to ARM + # ARMv7 + apt-get install -y libc6-armel-cross libc6-dev-armel-cross binutils-arm-linux-gnueabi libncurses5-dev gcc-arm-linux-gnueabi g++-arm-linux-gnueabi + # ARMv8 + apt-get install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu + +deps_gopherjs: ## Install GopherJS with jsbuiltin + go get -u github.com/gopherjs/gopherjs + go get -u -d -tags=js github.com/gopherjs/jsbuiltin + +deps_javascript: ## Install dependencies for JavaScript tests + npm install . + +# +# Go building, formatting, testing, and installing +# + +.PHONY: fmt +fmt: ## Format Go source code + go fmt $$(go list ./... ) + +.PHONY: imports +imports: ## Update imports in Go source code + goimports -w $$(find . -iname '*.go') + +.PHONY: vet +vet: ## Vet Go source code + go vet $$(go list ./... ) + +.PHONY: test_go +test_go: ## Run Go tests + bash scripts/test.sh + +build: build_cli build_javascript ## Build CLI and JavaScript + +install: ## Install gotmpl CLI on current platform + go install -gcflags="$(GCFLAGS)" -ldflags="$(LDFLAGS)" github.com/spatialcurrent/gotmpl/cmd/gotmpl + +# +# Command line Programs +# + bin/gotmpl_darwin_amd64: - GOOS=darwin GOARCH=amd64 go build -o bin/gotmpl_darwin_amd64 $$(go list ./...) + GOOS=darwin GOARCH=amd64 go build -o bin/gotmpl_darwin_amd64 -gcflags="$(GCFLAGS)" -ldflags="$(LDFLAGS)" github.com/spatialcurrent/gotmpl/cmd/gotmpl bin/gotmpl_linux_amd64: - GOOS=linux GOARCH=amd64 go build -o bin/gotmpl_linux_amd64 $$(go list ./...) + GOOS=linux GOARCH=amd64 go build -o bin/gotmpl_linux_amd64 -gcflags="$(GCFLAGS)" -ldflags="$(LDFLAGS)" github.com/spatialcurrent/gotmpl/cmd/gotmpl bin/gotmpl_windows_amd64.exe: - GOOS=windows GOARCH=amd64 go build -o bin/gotmpl_windows_amd64.exe $$(go list ./...) + GOOS=windows GOARCH=amd64 go build -o bin/gotmpl_windows_amd64.exe -gcflags="$(GCFLAGS)" -ldflags="$(LDFLAGS)" github.com/spatialcurrent/gotmpl/cmd/gotmpl bin/gotmpl_linux_arm64: - GOOS=linux GOARCH=arm64 go build -o bin/gotmpl_linux_arm64 $$(go list ./...) + GOOS=linux GOARCH=arm64 go build -o bin/gotmpl_linux_arm64 -gcflags="$(GCFLAGS)" -ldflags="$(LDFLAGS)" github.com/spatialcurrent/gotmpl/cmd/gotmpl -build: \ -bin/gotmpl_darwin_amd64 \ -bin/gotmpl_linux_amd64 \ -bin/gotmpl_windows_amd64.exe \ -bin/gotmpl_linux_arm64 +build_cli: bin/gotmpl_darwin_amd64 bin/gotmpl_linux_amd64 bin/gotmpl_windows_amd64.exe bin/gotmpl_linux_arm64 ## Build command line programs -fmt: - go fmt $$(go list ./... ) +# +# Shared Objects +# -install: - go install $$(go list ./...) +bin/gotmpl.so: ## Compile Shared Object for current platform + # https://golang.org/cmd/link/ + # CGO Enabled : https://github.com/golang/go/issues/24068 + CGO_ENABLED=1 go build -o $(DEST)/gotmpl.so -buildmode=c-shared -ldflags "$(LDFLAGS)" -gcflags="$(GCFLAGS)" github.com/spatialcurrent/gotmpl/plugins/gotmpl -test: - bash scripts/test.sh +bin/gotmpl_linux_amd64.so: ## Compile Shared Object for Linux / amd64 + # https://golang.org/cmd/link/ + # CGO Enabled : https://github.com/golang/go/issues/24068 + GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -o $(DEST)/gotmpl_linux_amd64.so -buildmode=c-shared -ldflags "$(LDFLAGS)" -gcflags="$(GCFLAGS)" github.com/spatialcurrent/gotmpl/plugins/gotmpl + +bin/gotmpl_linux_armv7.so: ## Compile Shared Object for Linux / ARMv7 + # LDFLAGS - https://golang.org/cmd/link/ + # CGO Enabled - https://github.com/golang/go/issues/24068 + # GOARM/GOARCH Compatability Table - https://github.com/golang/go/wiki/GoArm + # ARM Cross Compiler Required - https://www.acmesystems.it/arm9_toolchain + GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=1 CC=arm-linux-gnueabi-gcc go build -ldflags "-linkmode external -extldflags -static" -o $(DEST)/gotmpl_linux_armv7.so -buildmode=c-shared -ldflags "$(LDFLAGS)" -gcflags="$(GCFLAGS)" github.com/spatialcurrent/gotmpl/plugins/gotmpl + +bin/gotmpl_linux_armv8.so: ## Compile Shared Object for Linux / ARMv8 + # LDFLAGS - https://golang.org/cmd/link/ + # CGO Enabled - https://github.com/golang/go/issues/24068 + # GOARM/GOARCH Compatability Table - https://github.com/golang/go/wiki/GoArm + # ARM Cross Compiler Required - https://www.acmesystems.it/arm9_toolchain + # Dependencies - https://www.96boards.org/blog/cross-compile-files-x86-linux-to-96boards/ + GOOS=linux GOARCH=arm64 CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc go build -ldflags "-linkmode external -extldflags -static" -o $(DEST)/gotmpl_linux_armv8.so -buildmode=c-shared -ldflags "$(LDFLAGS)" -gcflags="$(GCFLAGS)" github.com/spatialcurrent/gotmpl/plugins/gotmpl + +build_so: bin/gotmpl_linux_amd64.so bin/gotmpl_linux_armv7.so bin/gotmpl_linux_armv8.so ## Build Shared Objects (.so) + +# +# JavaScript +# + +dist/gotmpl.mod.js: ## Build JavaScript module + gopherjs build -o dist/gotmpl.mod.js github.com/spatialcurrent/gotmpl/cmd/gotmpl.mod.js + +dist/gotmpl.mod.min.js: ## Build minified JavaScript module + gopherjs build -m -o dist/gotmpl.mod.min.js github.com/spatialcurrent/gotmpl/cmd/gotmpl.mod.js + +dist/gotmpl.global.js: ## Build JavaScript library that attaches to global or window. + gopherjs build -o dist/gotmpl.global.js github.com/spatialcurrent/gotmpl/cmd/gotmpl.global.js + +dist/gotmpl.global.min.js: ## Build minified JavaScript library that attaches to global or window. + gopherjs build -m -o dist/gotmpl.global.min.js github.com/spatialcurrent/gotmpl/cmd/gotmpl.global.js + +build_javascript: dist/gotmpl.mod.js dist/gotmpl.mod.min.js dist/gotmpl.global.js dist/gotmpl.global.min.js ## Build artifacts for JavaScript + +test_javascript: ## Run JavaScript tests + npm run test + +lint: ## Lint JavaScript source code + npm run lint + +# +# Examples +# + +bin/gotmpl_example_c: bin/gotmpl.so ## Build C example + mkdir -p bin && cd bin && gcc -o gotmpl_example_c -I. ./../examples/c/main.c -L. -l:gotmpl.so + +bin/gotmpl_example_cpp: bin/gotmpl.so ## Build C++ example + mkdir -p bin && cd bin && g++ -o gotmpl_example_cpp -I . ./../examples/cpp/main.cpp -L. -l:gotmpl.so + +run_example_c: bin/gotmpl.so bin/gotmpl_example_c ## Run C example + cd bin && LD_LIBRARY_PATH=. ./gotmpl_example_c + +run_example_cpp: bin/gotmpl.so bin/gotmpl_example_cpp ## Run C++ example + cd bin && LD_LIBRARY_PATH=. ./gotmpl_example_cpp + +run_example_python: bin/gotmpl.so ## Run Python example + LD_LIBRARY_PATH=bin python examples/python/test.py + +run_example_javascript: dist/gotmpl.mod.js ## Run JavaScript module example + node examples/js/index.mod.js + +# +# Clean +# clean: rm -fr bin + rm -fr dist diff --git a/README.md b/README.md index 4616cb5..5e04e8e 100644 --- a/README.md +++ b/README.md @@ -4,19 +4,53 @@ # Description -**gotmpl** is a super simple command line program for rendering templates. **gotmpl** uses environment variables as its context and [go-adaptive-functions](https://github.com/spatialcurrent/go-adaptive-functions) for its functions. **gotmpl** uses [go-simple-serializer](https://github.com/spatialcurrent/go-simple-serializer) for parsing text into data, such as in the "Render Time Table" example below. +**gotmpl** is a super simple command line program for rendering template. **gotmpl** parses its context from environment variables and positional arguments. **gotmpl** uses [go-adaptive-functions](https://github.com/spatialcurrent/go-adaptive-functions) for its functions and [go-simple-serializer](https://github.com/spatialcurrent/go-simple-serializer) for serializing data, such as in the "Render Time Table" example below. # Installation No installation is required. Just grab a [release](https://github.com/spatialcurrent/gotmpl/releases). You might want to rename your binary to just `gotmpl` for convenience. -If you do have go already installed, you can just run using `go run main.go` or install with `make install` +If you do have go already installed, you can just run using `go run cmd/gotmpl/main.go` or install with `make install` # Usage -See the few examples below. +**CLI** -**Note**: Since Go templates add the piped value to the end of the positional argument array, **gotmpl** reorders the piped value to the beginning of the argument array, so **go-adaptive-functions** can be used. This leads to a more seamless pattern, particularly for functions such as `split`, `join`, etc. +The command line tool, `gotmpl`, can be used to render templates. We currently support the following platforms. + +| GOOS | GOARCH | +| ---- | ------ | +| darwin | amd64 | +| linux | amd64 | +| windows | amd64 | +| linux | arm64 | + +Pull requests to support other platforms are welcome! See the [examples](#examples) section below for usage. + +**Note**: Since Go's native template engine behavior is to add the piped value to the end of the positional argument array, **gotmpl** reorders the piped value to the beginning of the argument array, so **go-adaptive-functions** can be used. This leads to a more seamless pattern, particularly for functions such as `split`, `join`, etc. + +**Go** + +You can install the gotmpl package with. + + +```shell +go get -u -d github.com/spatialcurrent/gotmpl/... +``` + +You can then import the main public API with `import "github.com/spatialcurrent/gotmpl/pkg/gotmpl"`. The package only contains 2 functions. If you want to load the [go-adaptive-functions](https://github.com/spatialcurrent/go-adaptive-functions) into your own template, you can use `InitFunctions` to return a map of functions. + +See [gotmpl](https://godoc.org/github.com/spatialcurrent/gotmpl/pkg/gotmpl) in GoDoc for API documentation and examples. + +**Node** + +gotmpl is built as a module. In modern JavaScript, the module can be imported using [destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment). + +```javascript +const { render } = require('./dist/gotmpl.global.min.js'); +``` + +In legacy JavaScript, you can use the `gotmpl.main.js` file that simply adds `gotmpl` to the global scope. # Examples diff --git a/cmd/gotmpl.global.js/main.go b/cmd/gotmpl.global.js/main.go new file mode 100644 index 0000000..1419da5 --- /dev/null +++ b/cmd/gotmpl.global.js/main.go @@ -0,0 +1,39 @@ +// ================================================================= +// +// Copyright (C) 2019 Spatial Current, Inc. - All Rights Reserved +// Released as open source under the MIT License. See LICENSE file. +// +// ================================================================= + +// gotmpl.global.js is the package for gotmpl that adds the render function to the global scope under the "gotmpl" variable. +// +// In Node, depending on where require is called and the build system used, the functions may need to be required at the top of each module file. +// In a web browser, gss can be made available to the entire web page. +// The functions are defined in the Exports variable in the gotmpljs package. +// +// Usage +// // Below is a simple set of examples of how to use this package in a JavaScript application. +// +// // load functions into global scope +// // require('./dist/gotmpl.global.min.js); +// +// // Render a template to a string. +// // Returns an object, which can be destructured to the rendered string and error as a string. +// // If there is no error, then err will be null. +// var { str, err } = gotmpl.render(tmpl, context); +// +// References +// - https://godoc.org/pkg/github.com/spatialcurrent/gotmpl/pkg/gotmpljs/ +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment +// - https://nodejs.org/api/globals.html#globals_global_objects +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects +package main + +import ( + "github.com/gopherjs/gopherjs/js" + "github.com/spatialcurrent/gotmpl/pkg/gotmpljs" +) + +func main() { + js.Global.Set("gotmpl", gotmpljs.Exports) +} diff --git a/cmd/gotmpl.mod.js/main.go b/cmd/gotmpl.mod.js/main.go new file mode 100644 index 0000000..b06706f --- /dev/null +++ b/cmd/gotmpl.mod.js/main.go @@ -0,0 +1,38 @@ +// ================================================================= +// +// Copyright (C) 2019 Spatial Current, Inc. - All Rights Reserved +// Released as open source under the MIT License. See LICENSE file. +// +// ================================================================= + +// gotmpl.mod.js is the package for gotmpl that is built as a JavaScript module. +// In modern JavaScript, the module can be imported using destructuring assignment. +// The functions are defined in the Exports variable in the gotmpljs package. +// +// Usage +// // Below is a simple set of examples of how to use this package in a JavaScript application. +// +// // load functions into current scope +// const { render } = require('./dist/gotmpl.global.min.js); +// +// // Render a template to a string. +// // Returns an object, which can be destructured to the rendered string and error as a string. +// // If there is no error, then err will be null. +// var { str, err } = render(tmpl, context); +// +// References +// - https://godoc.org/pkg/github.com/spatialcurrent/gotmpl/pkg/gotmpljs/ +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment +package main + +import ( + "github.com/gopherjs/gopherjs/js" + "github.com/spatialcurrent/gotmpl/pkg/gotmpljs" +) + +func main() { + jsModuleExports := js.Module.Get("exports") + for name, value := range gotmpljs.Exports { + jsModuleExports.Set(name, value) + } +} diff --git a/cmd/gotmpl/main.go b/cmd/gotmpl/main.go new file mode 100644 index 0000000..fbacf3c --- /dev/null +++ b/cmd/gotmpl/main.go @@ -0,0 +1,100 @@ +// ================================================================= +// +// Copyright (C) 2019 Spatial Current, Inc. - All Rights Reserved +// Released as open source under the MIT License. See LICENSE file. +// +// ================================================================= + +// gotmpl is a super simple command line program for rendering templates. +// +// Usage +// +// Use `gotmpl help` to see full help documentation. +// +// gotmpl [k=v]... < template_file +// +// Examples +// +// # convert .gitignore to JSON +// cat .gitignore | gss -i csv --input-header path -o json +// +// # extract version from CircleCI config +// cat .circleci/config.yml | gss -i yaml -o json -c '#' | jq -r .version +// +// # convert list of files to JSON Lines +// find . -name '*.go' | gss -i csv --input-header path -o jsonl +package main + +import ( + //"fmt" + "io/ioutil" + "os" + "strings" + "text/template" +) + +import ( + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +import ( + "github.com/spatialcurrent/gotmpl/pkg/gotmpl" +) + +func initFlags(flag *pflag.FlagSet) { + flag.StringP("delimiter", "d", "", "split stdin by delimiter with each element being treated as a separate template.") +} + +func main() { + cmd := &cobra.Command{ + Use: "gotmpl [k=v]... < template_file", + DisableFlagsInUseLine: true, + Short: "gotmpl", + Long: `gotmpl is a super simple command line program for rendering templates that uses environment variables and command line arguments as its context variables. The template is read from stdin.`, + RunE: func(cmd *cobra.Command, args []string) error { + + fi, err := os.Stdin.Stat() + if err != nil { + return err + } + + if fi.Mode()&os.ModeNamedPipe == 0 && !fi.Mode().IsRegular() { + return cmd.Usage() + } + + stdinBytes, err := ioutil.ReadAll(os.Stdin) + if err != nil { + return err + } + + templates := make([]string, 0) + if delimiter, err := cmd.Flags().GetString("delimiter"); err == nil && len(delimiter) > 0 { + templates = strings.Split(string(stdinBytes), delimiter) + } else { + templates = append(templates, string(stdinBytes)) + } + + ctx := gotmpl.LoadContext(args) + funcs := gotmpl.InitFunctions() + for _, text := range templates { + tmpl, err := template.New("main").Funcs(funcs).Parse(text) + if err != nil { + return errors.Wrap(err, "error creating template") + } + err = tmpl.Execute(os.Stdout, ctx) + if err != nil { + return errors.Wrap(err, "error rendering template") + } + } + + return nil + }, + } + initFlags(cmd.Flags()) + + if err := cmd.Execute(); err != nil { + panic(err) + } +} diff --git a/examples/c/main.c b/examples/c/main.c new file mode 100644 index 0000000..a81f100 --- /dev/null +++ b/examples/c/main.c @@ -0,0 +1,34 @@ +// ================================================================= +// +// Copyright (C) 2018 Spatial Current, Inc. - All Rights Reserved +// Released as open source under the MIT License. See LICENSE file. +// +// ================================================================= + +#include +#include +#include + +#include "gotmpl.h" + +int +main(int argc, char **argv) { + char *err; + + char *input_string = "Sum: {{ .values | sum }}\nMax: {{ .values | max }}\nValues: {{ .values | join \", \" }}"; + char *output_string; + + printf("# Template\n%s\n", input_string); + + err = Render(input_string, "{\"values\":[1,2,3,4]}", "json", &output_string); + + if (err != NULL) { + fprintf(stderr, "error: %s\n", err); + free(err); + return 1; + } + + printf("# Rendered:\n%s\n", output_string); + + return 0; +} diff --git a/examples/cpp/main.cpp b/examples/cpp/main.cpp new file mode 100644 index 0000000..cdf0a6f --- /dev/null +++ b/examples/cpp/main.cpp @@ -0,0 +1,65 @@ +// ================================================================= +// +// Copyright (C) 2018 Spatial Current, Inc. - All Rights Reserved +// Released as open source under the MIT License. See LICENSE file. +// +// ================================================================= + +#include +#include +#include +#include "gotmpl.h" + +// render is a example of a C++ function that can render templates using some std::string variables. +// In production, you would want to write the function definition to match the use case. +char* render(std::string tmpl, std::string ctx, std::string format, char** output_string_c) { + + char *tmpl_c = new char[tmpl.length() + 1]; + std::strcpy(tmpl_c, tmpl.c_str()); + + char *ctx_c = new char[ctx.length() + 1]; + std::strcpy(ctx_c, ctx.c_str()); + + char *format_c = new char[format.length() + 1]; + std::strcpy(format_c, format.c_str()); + + char *err = Render(tmpl_c, ctx_c, format_c, output_string_c); + + free(tmpl_c); + free(ctx_c); + free(format_c); + + return err; + +} + +int main(int argc, char **argv) { + + // Since Go requires non-const values, we must define our parameters as variables + // https://stackoverflow.com/questions/4044255/passing-a-string-literal-to-a-function-that-takes-a-stdstring + std::string tmpl("Sum: {{ .values | sum }}\nMax: {{ .values | max }}\nValues: {{ .values | join \", \" }}"); + std::string ctx("{\"values\":[1,2,3,4]}"); + std::string format("json"); + char *output_char_ptr; + + // Write version to stderr + //std::cout << "Version" << Version() < { + + it('render single context variable', () => { + var { str, err } = render("{{ .foo }}", {foo: "bar"}); + expect(err).toBeNull(); + expect(str).toEqual("bar"); + }); + + it('split variable and join', () => { + var { str, err } = render("{{ .foo | split \",\" | join \":\" }}", {foo: "a,b,c"}); + expect(err).toBeNull(); + expect(str).toEqual("a:b:c"); + }); + + it('sum array', () => { + var { str, err } = render("{{ .values | sum }}", {values: [1,2,3,4]}); + expect(err).toBeNull(); + expect(str).toEqual("10"); + }); + + it('max variables', () => { + var { str, err } = render("{{ .values | max }} {{ .unit }}", {values: [1,2,3,4], unit: "seconds"}); + expect(err).toBeNull(); + expect(str).toEqual("4 seconds"); + }); + + it('sum, max, and values', () => { + var { str, err } = render("Sum: {{ .values | sum }}\nMax: {{ .values | max }}\nValues: {{ .values | join \", \" }}", {values: [1,2,3,4], unit: "seconds"}); + expect(err).toBeNull(); + expect(str).toEqual("Sum: 10\nMax: 4\nValues: 1, 2, 3, 4"); + }); + +}); diff --git a/main.go b/main.go deleted file mode 100644 index bee6c50..0000000 --- a/main.go +++ /dev/null @@ -1,126 +0,0 @@ -// ================================================================= -// -// Copyright (C) 2019 Spatial Current, Inc. - All Rights Reserved -// Released as open source under the MIT License. See LICENSE file. -// -// ================================================================= - -package main - -import ( - "fmt" - "io/ioutil" - "os" - "strings" - "text/template" -) - -import ( - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -import ( - "github.com/spatialcurrent/go-adaptive-functions/af" - "github.com/spatialcurrent/go-simple-serializer/gss" -) - -func initFunctions() map[string]interface{} { - funcs := map[string]interface{}{ - "parse": func(args ...interface{}) (interface{}, error) { - if len(args) != 2 { - return nil, errors.New("invalid arguments for parse " + fmt.Sprint(args)) - } - if text, ok := args[1].(string); ok { - if f, ok := args[0].(string); ok { - t, err := gss.GetType([]byte(text), f) - if err != nil { - return "", errors.Wrap(err, "error creating new object for format "+f) - } - return gss.DeserializeString( - text, - f, - gss.NoHeader, - gss.NoComment, - true, - 0, - gss.NoLimit, - t, - false, - false) - } - } - return nil, errors.New("invalid arguments for parse " + fmt.Sprint(args)) - }, - } - - for _, f := range af.Functions { - f := f - for _, alias := range f.Aliases { - alias := alias - funcs[alias] = func(args ...interface{}) (interface{}, error) { - if len(args) <= 1 { - return f.ValidateRun(args) - } - return f.ValidateRun(append([]interface{}{args[len(args)-1]}, args[0:len(args)-1]...)) - } - } - } - - return funcs -} - -func main() { - cmd := &cobra.Command{ - Use: "gotmpl [k=v]... < template_file", - DisableFlagsInUseLine: true, - Short: "gotmpl", - Long: `gotmpl is a super simple command line program for rendering templates that uses environment variables and command line arguments as its context variables. The template is read from stdin.`, - RunE: func(cmd *cobra.Command, args []string) error { - - fi, err := os.Stdin.Stat() - if err != nil { - return err - } - - if fi.Mode()&os.ModeNamedPipe == 0 && !fi.Mode().IsRegular() { - return cmd.Usage() - } - - text, err := ioutil.ReadAll(os.Stdin) - if err != nil { - return err - } - - ctx := map[string]string{} - - // load context from environment variables - for _, str := range os.Environ() { - parts := strings.SplitN(str, "=", 2) - ctx[parts[0]] = parts[1] - } - - // load context from command line arguments - for _, str := range args { - parts := strings.SplitN(str, "=", 2) - ctx[parts[0]] = parts[1] - } - - tmpl, err := template.New("main").Funcs(initFunctions()).Parse(string(text)) - if err != nil { - return err - } - - err = tmpl.Execute(os.Stdout, ctx) - if err != nil { - return err - } - - return nil - }, - } - - if err := cmd.Execute(); err != nil { - panic(err) - } -} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1d7d4b5 --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "gotmpl", + "version": "0.0.1", + "description": "Simple library and command line program for rendering templates.", + "main": "bin/gotmpl.js", + "scripts": { + "lint": "eslint js", + "clean": "rm -fr dist/gotmpl.mod.min.js", + "build": "make dist/gotmpl.mod.min.js", + "test": "./node_modules/.bin/jest", + "test:clean": "npm run clean && npm run build && npm run test" + }, + "sideEffects": false, + "repository": { + "type": "git", + "url": "git+https://github.com/spatialcurrent/gotmpl.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/spatialcurrent/gotmpl/issues" + }, + "homepage": "https://github.com/spatialcurrent/gotmpl#readme", + "devDependencies": { + "jest": "^23.6.0", + "jest-cli": "^23.6.0" + }, + "dependencies": {}, + "jest": { + "roots": [ + "js" + ], + "moduleFileExtensions": [ + "js", + "json" + ], + "testEnvironment": "./testEnvironment" + } +} diff --git a/pkg/gotmpl/InitFunctions.go b/pkg/gotmpl/InitFunctions.go new file mode 100644 index 0000000..133e01f --- /dev/null +++ b/pkg/gotmpl/InitFunctions.go @@ -0,0 +1,57 @@ +// ================================================================= +// +// Copyright (C) 2019 Spatial Current, Inc. - All Rights Reserved +// Released as open source under the MIT License. See LICENSE file. +// +// ================================================================= + +package gotmpl + +import ( + "fmt" +) + +import ( + "github.com/pkg/errors" +) + +import ( + "github.com/spatialcurrent/go-adaptive-functions/pkg/af" + "github.com/spatialcurrent/go-simple-serializer/pkg/serializer" +) + +// InitFunctions returns a map of functions for use with Go's templating engine. +func InitFunctions() map[string]interface{} { + funcs := map[string]interface{}{ + "parse": func(args ...interface{}) (interface{}, error) { + if len(args) != 2 { + return nil, errors.New("invalid arguments for parse " + fmt.Sprint(args)) + } + if text, ok := args[1].(string); ok { + if format, ok := args[0].(string); ok { + b, err := serializer.New(format).Limit(-1).Deserialize([]byte(text)) + if err != nil { + return "", errors.Wrap(err, "error deserializing object with format "+format) + } + return b, nil + } + } + return nil, errors.New("invalid arguments for parse " + fmt.Sprint(args)) + }, + } + + for _, f := range af.Functions { + f := f + for _, alias := range f.Aliases { + alias := alias + funcs[alias] = func(args ...interface{}) (interface{}, error) { + if len(args) <= 1 { + return f.ValidateRun(args...) + } + return f.ValidateRun(append([]interface{}{args[len(args)-1]}, args[0:len(args)-1]...)...) + } + } + } + + return funcs +} diff --git a/pkg/gotmpl/LoadContext.go b/pkg/gotmpl/LoadContext.go new file mode 100644 index 0000000..1fa804c --- /dev/null +++ b/pkg/gotmpl/LoadContext.go @@ -0,0 +1,33 @@ +// ================================================================= +// +// Copyright (C) 2019 Spatial Current, Inc. - All Rights Reserved +// Released as open source under the MIT License. See LICENSE file. +// +// ================================================================= + +package gotmpl + +import ( + "os" + "strings" +) + +// LoadContext returns a context for use with Go's templating engine. +// LoadContext parses the context from environment variables and positional arguments. +func LoadContext(args []string) map[string]string { + ctx := map[string]string{} + + // load context from environment variables + for _, str := range os.Environ() { + parts := strings.SplitN(str, "=", 2) + ctx[parts[0]] = parts[1] + } + + // load context from command line arguments + for _, str := range args { + parts := strings.SplitN(str, "=", 2) + ctx[parts[0]] = parts[1] + } + + return ctx +} diff --git a/pkg/gotmpljs/Exports.go b/pkg/gotmpljs/Exports.go new file mode 100644 index 0000000..ccc604d --- /dev/null +++ b/pkg/gotmpljs/Exports.go @@ -0,0 +1,66 @@ +// ================================================================= +// +// Copyright (C) 2019 Spatial Current, Inc. - All Rights Reserved +// Released as open source under the MIT License. See LICENSE file. +// +// ================================================================= + +package gotmpljs + +import ( + "bytes" + "text/template" +) + +import ( + "github.com/gopherjs/gopherjs/js" + "github.com/gopherjs/jsbuiltin" + "github.com/pkg/errors" +) + +import ( + "github.com/spatialcurrent/gotmpl/pkg/gotmpl" +) + +func isArray(object *js.Object) bool { + return js.Global.Get("Array").Call("isArray", object).Bool() +} + +func toArray(object *js.Object) []interface{} { + arr := make([]interface{}, 0, object.Length()) + for i := 0; i < object.Length(); i++ { + arr = append(arr, parseObject(object.Index(i))) + } + return arr +} + +func parseObject(in *js.Object) interface{} { + if isArray(in) { + return toArray(in) + } + switch jsbuiltin.TypeOf(in) { + case jsbuiltin.TypeObject: + out := map[string]interface{}{} + for _, key := range js.Keys(in) { + out[key] = parseObject(in.Get(key)) + } + return out + } + return in.Interface() +} + +var Exports = map[string]interface{}{ + "render": func(tmpl string, ctx *js.Object) map[string]interface{} { + funcs := gotmpl.InitFunctions() + t, err := template.New("main").Funcs(funcs).Parse(tmpl) + if err != nil { + return map[string]interface{}{"str": nil, "err": errors.Wrap(err, "error creating template").Error()} + } + buf := new(bytes.Buffer) + err = t.Execute(buf, parseObject(ctx)) + if err != nil { + return map[string]interface{}{"str": nil, "err": errors.Wrap(err, "error rendering template").Error()} + } + return map[string]interface{}{"str": buf.String(), "err": nil} + }, +} diff --git a/pkg/gotmpljs/gotmpljs.go b/pkg/gotmpljs/gotmpljs.go new file mode 100644 index 0000000..0a610be --- /dev/null +++ b/pkg/gotmpljs/gotmpljs.go @@ -0,0 +1,10 @@ +// ================================================================= +// +// Copyright (C) 2019 Spatial Current, Inc. - All Rights Reserved +// Released as open source under the MIT License. See LICENSE file. +// +// ================================================================= + +// Package gotmpljs includes the exports for the JavaScript build of gotmpl. +// +package gotmpljs diff --git a/plugins/gotmpl/main.go b/plugins/gotmpl/main.go new file mode 100644 index 0000000..149d6f8 --- /dev/null +++ b/plugins/gotmpl/main.go @@ -0,0 +1,68 @@ +// ================================================================= +// +// Copyright (C) 2019 Spatial Current, Inc. - All Rights Reserved +// Released as open source under the MIT License. See LICENSE file. +// +// ================================================================= + +// gss.so creates a shared library of Go that can be called by C, C++, or Python +// +// +// - https://godoc.org/cmd/cgo +// - https://blog.golang.org/c-go-cgo +// +package main + +import ( + "C" + "bytes" + "fmt" + "strings" + "text/template" +) + +import ( + "github.com/pkg/errors" +) + +import ( + "github.com/spatialcurrent/go-simple-serializer/pkg/serializer" + "github.com/spatialcurrent/gotmpl/pkg/gotmpl" +) + +var gitBranch string +var gitCommit string + +func main() {} + +//export Version +func Version() *C.char { + var b strings.Builder + if len(gitBranch) > 0 { + b.WriteString(fmt.Sprintf("Branch: %q\n", gitBranch)) + } + if len(gitCommit) > 0 { + b.WriteString(fmt.Sprintf("Commit: %q\n", gitCommit)) + } + return C.CString(b.String()) +} + +//export Render +func Render(tmpl *C.char, ctx *C.char, format *C.char, outputString **C.char) *C.char { + c, err := serializer.New(C.GoString(format)).Limit(-1).Deserialize([]byte(C.GoString(ctx))) + if err != nil { + return C.CString(errors.Wrapf(err, "error deserializing context with format %q", C.GoString(format)).Error()) + } + funcs := gotmpl.InitFunctions() + t, err := template.New("main").Funcs(funcs).Parse(C.GoString(tmpl)) + if err != nil { + return C.CString(errors.Wrapf(err, "error creating template %q", C.GoString(tmpl)).Error()) + } + buf := new(bytes.Buffer) + err = t.Execute(buf, c) + if err != nil { + return C.CString(errors.Wrap(err, "error rendering template").Error()) + } + *outputString = C.CString(buf.String()) + return nil +} diff --git a/testEnvironment.js b/testEnvironment.js new file mode 100644 index 0000000..529d759 --- /dev/null +++ b/testEnvironment.js @@ -0,0 +1,21 @@ +const NodeEnvironment = require('jest-environment-node'); + +class TestEnvironment extends NodeEnvironment { + constructor(config) { + super(config); + } + + async setup() { + this.global.gotmpl = require('./dist/gotmpl.mod.min.js'); + } + + async teardown() { + await super.teardown(); + } + + runScript(script) { + return super.runScript(script); + } +} + +module.exports = TestEnvironment; From e3e2142ac51ec663a31488ca3964d788a117851e Mon Sep 17 00:00:00 2001 From: pjdufour Date: Thu, 20 Jun 2019 09:59:59 -0400 Subject: [PATCH 2/4] update ci --- .circleci/config.yml | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cf72bc2..e457153 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,6 +15,27 @@ jobs: key: v1-go-src-{{ .Branch }}-{{ .Revision }} paths: - /go/src + test_go: + executor: base + steps: + - run: sudo chown -R circleci /go/src + - restore_cache: + keys: + - v1-go-src-{{ .Branch }}-{{ .Revision }} + - run: make deps_go_test + - run: make test_go + - run: make imports + - run: git diff --exit-code + test_javascript: + executor: base + steps: + - run: sudo chown -R circleci /go/src + - restore_cache: + keys: + - v1-go-src-{{ .Branch }}-{{ .Revision }} + - run: make deps_gopherjs + - run: make deps_javascript + - run: npm run test:clean build_cli: executor: base steps: @@ -39,11 +60,26 @@ jobs: - store_artifacts: path: dist destination: / + build_so: + executor: base + steps: + - run: sudo chown -R circleci /go/src + - restore_cache: + keys: + - v1-go-src-{{ .Branch }}-{{ .Revision }} + - run: sudo make deps_arm + - run: make build_so + - store_artifacts: + path: bin + destination: / workflows: main: jobs: - pre_deps_golang - - test: + - test_go: + requires: + - pre_deps_golang + - test_javascript: requires: - pre_deps_golang - build_cli: From 9935bc2bd33522e4d5e67f96d5078cdfd445d464 Mon Sep 17 00:00:00 2001 From: pjdufour Date: Thu, 20 Jun 2019 10:06:53 -0400 Subject: [PATCH 3/4] node ci --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e457153..74be61c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2.1 executors: base: docker: - - image: circleci/golang:1.12 + - image: circleci/golang:1.12-node working_directory: /go/src/github.com/spatialcurrent/gotmpl jobs: pre_deps_golang: From 8e96cef340e7c0d4ea34666d136eeebf906e99ff Mon Sep 17 00:00:00 2001 From: pjdufour Date: Thu, 20 Jun 2019 13:04:22 -0400 Subject: [PATCH 4/4] better examples and docs --- .eslintrc | 37 +++++-------------------------------- README.md | 26 +++++++++++++++++++------- examples/c/main.c | 2 +- examples/cpp/main.cpp | 2 +- examples/js/index.global.js | 7 +++++++ examples/js/index.mod.js | 7 +++++++ examples/python/test.py | 2 +- js/gotmpl.test.js | 9 ++++++++- package.json | 3 ++- plugins/gotmpl/main.go | 2 +- scripts/test.sh | 3 ++- testEnvironment.js | 7 +++++++ 12 files changed, 61 insertions(+), 46 deletions(-) diff --git a/.eslintrc b/.eslintrc index d98e117..c07b523 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,35 +1,8 @@ { - "parser": "babel-eslint", - "extends": "eslint-config-airbnb", - "rules": { - "arrow-parens": ["error", "as-needed"], - "class-methods-use-this": "off", - "comma-dangle": ["error", { "objects": "always-multiline" }], - "import/no-named-as-default": "off", - "import/no-unresolved": "off", - "no-use-before-define": "off", - "object-curly-newline": "off", - "prefer-destructuring": "off", - "react/destructuring-assignment": "off", - "react/forbid-prop-types": "off", - "react/jsx-filename-extension": "off", - "react/jsx-no-bind": "off", - "react/no-access-state-in-setstate": "off", - "react/prefer-stateless-function": "off", - "react/require-default-props": "off", - "react/sort-comp": ["error", { // Team Preference - "order": [ - "static-methods", - "/constructor/", - "/state/", - "instance-variables", - "lifecycle", - "getters", - "everything-else", - "render" - ] - }], - "sort-keys": "error", - "space-before-function-paren": ["error", "always"] + "parserOptions": { + "ecmaVersion": 2017 + }, + "env": { + "es6": true } } diff --git a/README.md b/README.md index 5e04e8e..de62f4a 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,24 @@ # Description -**gotmpl** is a super simple command line program for rendering template. **gotmpl** parses its context from environment variables and positional arguments. **gotmpl** uses [go-adaptive-functions](https://github.com/spatialcurrent/go-adaptive-functions) for its functions and [go-simple-serializer](https://github.com/spatialcurrent/go-simple-serializer) for serializing data, such as in the "Render Time Table" example below. +**gotmpl** is a lightweight wrapper around Go's native templating engine ([text/template](https://godoc.org/text/template)) that includes many more [template functions](https://godoc.org/text/template#hdr-Functions). gotmpl is delivered as a simple command line program and supports multiple languages through cross compilers. + +gotmpl uses [go-adaptive-functions](https://github.com/spatialcurrent/go-adaptive-functions) for its functions and [go-simple-serializer](https://github.com/spatialcurrent/go-simple-serializer) for serializing data, such as in the "Render Time Table" example below. + +Using cross compilers, this library can also be called by other languages, including `C`, `C++`, `Python`, and `JavaScript`. This library is cross compiled into a Shared Object file (`*.so`), which can be called by `C`, `C++`, and `Python` on Linux machines. This library is also compiled to pure `JavaScript` using [GopherJS](https://github.com/gopherjs/gopherjs), which can be called by [Node.js](https://nodejs.org) and loaded in the browser. See the examples folder for patterns that you can use. # Installation -No installation is required. Just grab a [release](https://github.com/spatialcurrent/gotmpl/releases). You might want to rename your binary to just `gotmpl` for convenience. +**CLI** -If you do have go already installed, you can just run using `go run cmd/gotmpl/main.go` or install with `make install` +No setup is required to run a gotmpl executable. Just grab a [release](https://github.com/spatialcurrent/gotmpl/releases). You might want to rename your binary to just `gotmpl` for convenience. + +If you do have go already installed, you can run using `go run github.com/spatialcurrent/gotmpl/cmd/gotmpl` or install with `make install` # Usage +See the [text/template](https://godoc.org/text/template) documentation for information on how to use go templates. [hugo](https://gohugo.io/) uses the same template engine, but with some changes. Since Go's native template engine behavior is to add the piped value to the end of the positional argument array, gotmpl reorders the piped value to the beginning of the argument array, so functions from go-adaptive-functions can be used. This leads to a more seamless pattern, particularly for functions such as `split`, `join`, etc. + **CLI** The command line tool, `gotmpl`, can be used to render templates. We currently support the following platforms. @@ -25,9 +33,7 @@ The command line tool, `gotmpl`, can be used to render templates. We currently | windows | amd64 | | linux | arm64 | -Pull requests to support other platforms are welcome! See the [examples](#examples) section below for usage. - -**Note**: Since Go's native template engine behavior is to add the piped value to the end of the positional argument array, **gotmpl** reorders the piped value to the beginning of the argument array, so **go-adaptive-functions** can be used. This leads to a more seamless pattern, particularly for functions such as `split`, `join`, etc. +Pull requests to support other platforms are welcome! **gotmpl** parses its context from environment variables and positional arguments. The template is read from stdin. See the [examples](#examples) section below for usage. **Go** @@ -74,7 +80,13 @@ echo '{{ with $items := .data | parse "csv" }}