diff --git a/.circleci/config.yml b/.circleci/config.yml
index 7e15367..74be61c 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -2,65 +2,40 @@ 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:
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:
+ test_go:
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:
+ - 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:
- 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
+ - run: make deps_gopherjs
+ - run: make deps_javascript
+ - run: npm run test:clean
build_cli:
executor: base
steps:
@@ -68,7 +43,32 @@ jobs:
- restore_cache:
keys:
- v1-go-src-{{ .Branch }}-{{ .Revision }}
- - run: make build
+ - 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 deps_gopherjs
+ - run: make build_javascript
+ - 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: /
@@ -76,12 +76,15 @@ workflows:
main:
jobs:
- pre_deps_golang
- - test:
+ - test_go:
requires:
- pre_deps_golang
- - validate:
+ - test_javascript:
requires:
- pre_deps_golang
- build_cli:
requires:
- pre_deps_golang
+ - 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..c07b523
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,8 @@
+{
+ "parserOptions": {
+ "ecmaVersion": 2017
+ },
+ "env": {
+ "es6": true
+ }
+}
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..de62f4a 100644
--- a/README.md
+++ b/README.md
@@ -4,19 +4,59 @@
# 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 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**
+
+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 just run using `go run main.go` or install with `make install`
+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 few examples below.
+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.
+
+| GOOS | GOARCH |
+| ---- | ------ |
+| darwin | amd64 |
+| linux | amd64 |
+| windows | amd64 |
+| linux | arm64 |
+
+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**
+
+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.
-**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.
+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
@@ -40,7 +80,13 @@ echo '{{ with $items := .data | parse "csv" }}
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..dc7c12f
--- /dev/null
+++ b/examples/c/main.c
@@ -0,0 +1,34 @@
+// =================================================================
+//
+// Copyright (C) 2019 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..c92c4b2
--- /dev/null
+++ b/examples/cpp/main.cpp
@@ -0,0 +1,65 @@
+// =================================================================
+//
+// Copyright (C) 2019 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..c962e7c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,40 @@
+{
+ "name": "gotmpl",
+ "version": "0.0.1",
+ "description": "Simple library and command line program for rendering templates.",
+ "main": "bin/gotmpl.js",
+ "scripts": {
+ "lint": "./node_modules/eslint/bin/eslint.js ./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": {
+ "eslint": "^5.6.1",
+ "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..e26e903
--- /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.
+//
+// =================================================================
+
+// gotmpl.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/scripts/test.sh b/scripts/test.sh
index 096c58c..f6e9bdb 100644
--- a/scripts/test.sh
+++ b/scripts/test.sh
@@ -8,7 +8,7 @@
# =================================================================
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
-set -eu
+#set -eu
cd $DIR/..
pkgs=$(go list ./... | grep -v /vendor/ | tr "\n" " ")
echo "******************"
@@ -32,3 +32,4 @@ staticcheck -checks all ${pkgs}
echo "******************"
echo "Running misspell"
misspell -locale US -error *.md *.go
+echo "******************"
diff --git a/testEnvironment.js b/testEnvironment.js
new file mode 100644
index 0000000..82410a1
--- /dev/null
+++ b/testEnvironment.js
@@ -0,0 +1,28 @@
+// =================================================================
+//
+// Copyright (C) 2019 Spatial Current, Inc. - All Rights Reserved
+// Released as open source under the MIT License. See LICENSE file.
+//
+// =================================================================
+
+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;