59 Commits

Author SHA1 Message Date
jhayotte
5a8293c573 feat: enhance the function of typing field with their package 2018-08-08 16:38:58 +02:00
Manfred Touron
6abbdf4cfb Merge pull request #96 from moul/helpers/gfanton/add-store-helper
Add Concat/Join/Store helpers
2018-04-07 19:42:21 +02:00
Guilhem Fanton
adb58bd940 Add concat & Join helper methods 2018-04-06 17:18:38 +02:00
Julien Hayotte
283713bd93 Merge pull request #95 from Tommy-42/master
Add arithmetics helpers - fix #94
2018-03-21 17:29:47 +01:00
Tommy PAGEARD
19fb3c8da9 Add arithmetics helpers - fix #94 2018-03-21 15:03:26 +01:00
Manfred Touron
77a820ea6f Merge pull request #93 from sumup/feature/stringMethodOptionsExtension
Add stringMethodOptionsExtension
2018-02-27 11:02:53 +01:00
Jan Weitz
6765ff04eb Add stringMethodOptionsExtension
This helper function can be used on service methods
to read options, which extend method options by a string.

e.g.

It is possible to read "Some string" for a custom

`my_method_option.bar` message option extension.

See:

https://developers.google.com/protocol-buffers/docs/proto#customoptions

on how to define a custom extension and referencing is by a fieldID.

```
service MyService {

  rpc MyMethod(RequestType) returns(ResponseType) {
    option (my_method_option.bar) = "Some string";
  }
}
```

If `my_method_option.bar` was defined for `fieldID: 50000`
one can reference it using protoc-gen-template like:

```
{{- stringMethodOptionsExtension 50000 .method }}
```
2018-02-23 16:55:45 +01:00
Alexandre Beslic
bb3105a862 Merge pull request #91 from tkerambloch/dev/tkerambloch/getHttpPathInAdditionalBindings
Helper: add helper like httpPath for get path in additional bindings options
2018-02-13 17:34:22 +01:00
Thomas KERAMBLOCH
0c29a9922f add helper like httpPath for get path in additional bindings options 2018-02-13 16:00:04 +01:00
Alexandre Beslic
aff21c1e7c Merge pull request #90 from tkerambloch/dev/tkerambloch/helperGetIndexArray
fix type int32 in index helper
2018-02-12 17:03:31 +01:00
Thomas KERAMBLOCH
15ac366c8d fix type int32 in index helper 2018-02-12 16:48:36 +01:00
Alexandre Beslic
5a9555bcf5 Merge pull request #89 from tkerambloch/dev/tkerambloch/helperGetIndexArray
Add Helper 'index': get elem of array with the index
2018-02-10 18:43:45 +01:00
Thomas KERAMBLOCH
3f1d5001d9 Add Helper 'index': get elem of array with the index 2018-02-10 18:00:39 +01:00
Manfred Touron
be5f14041f Merge pull request #83 from pmoroney/new-helpers
add new helpers
2018-01-12 23:25:50 +01:00
Pat Moroney
100279cd16 add comment about tree traversal 2018-01-12 11:36:29 -07:00
Pat Moroney
4f138c4f92 remove duplicated line 2018-01-12 11:23:17 -07:00
Pat Moroney
78cb5588b1 make nilString constant 2018-01-12 11:12:15 -07:00
Pat Moroney
0b600dd93f rename fieldId to fieldID 2018-01-12 10:56:25 -07:00
Pat Moroney
decb64ccd8 add new helpers 2018-01-12 10:38:10 -07:00
Manfred Touron
7e17e4319f Merge pull request #82 from moul/dev/moul/lint
Setup gometalinter + fix lint
2017-12-28 15:38:19 +01:00
Manfred Touron
c64e1d8ed6 Setup gometalinter + fix lint 2017-12-28 15:34:09 +01:00
Manfred Touron
f84ba571b5 Merge pull request #81 from moul/dev/moul/bump-deps
Bump deps + add helpers examples
2017-12-19 17:42:54 +01:00
Manfred Touron
5da3d00df2 Add a new 'helpers' example 2017-12-19 15:55:33 +01:00
Manfred Touron
9f831eb4de Bump sprig@v2.14.1 2017-12-19 14:00:29 +01:00
Manfred Touron
230480afd1 Switch from glide to govendor 2017-12-19 13:55:52 +01:00
Manfred Touron
ccffd8bfe2 Merge pull request #78 from shiwano/split-array
Fix error on use pipeline after splitArray result
2017-12-19 08:57:29 +01:00
Alexandre Beslic
bd68210bc3 Merge pull request #77 from abronan/func_map_additions
Additional sprig rule to handle Go convention
2017-12-01 10:53:35 +01:00
Shogo Iwano
c833301bd5 Fix error on use pipeline after splitArray result 2017-12-01 01:43:18 +09:00
Alexandre Beslic
3dbba43e5f func map additions to handle golang convention while executing templates with id fields
Signed-off-by: Alexandre Beslic <abeslic@abronan.com>
2017-11-29 15:46:48 +01:00
Manfred Touron
055b1a5a86 Add fork-me ribbon 2017-10-26 23:04:12 +02:00
Manfred Touron
6db1457a28 Update README.md 2017-10-26 22:59:32 +02:00
Manfred Touron
6ceb257a3b Add web-editor.jpg 2017-10-26 22:58:10 +02:00
Manfred Touron
fc09c682e3 Merge pull request #76 from moul/dev/moul/web-editor
Initial version of the web-editor
2017-10-26 22:54:41 +02:00
Manfred Touron
22c3e39e89 Update vendors 2017-10-26 19:13:44 +02:00
Manfred Touron
64c92d0c8d Initial version of the web-editor 2017-10-26 19:13:44 +02:00
Manfred Touron
ba84ae0c01 Merge pull request #75 from moul/dev/moul/fix-docker-build
Fix Docker build
2017-10-10 11:48:00 +02:00
Manfred Touron
8caafb8ec4 Refactor Dockerfile 2017-10-10 10:59:53 +02:00
Manfred Touron
fe71150b76 Merge pull request #74 from pmoroney/master
Add httpBody helper function
2017-09-19 21:29:51 +00:00
Pat Moroney
7172d5b49d Add httpBody helper function 2017-09-19 14:53:57 -06:00
jhayotte
a0e68f8a2b enhance isFieldMessageTimeStamp 2017-09-18 18:56:37 +02:00
Julien Hayotte
1dc5500690 Merge pull request #73 from moul/dev/jhayotte/time
Add helper isFieldMessageTimeStamp
2017-09-18 16:22:28 +02:00
jhayotte
dab343fc15 Add helper isFieldMessageTimeStamp 2017-09-18 15:04:39 +02:00
Julien Hayotte
a921a29c7e Merge pull request #72 from moul/dev/jhayotte/deps
fixes: upgrade huandu/xtrings to handle capital word with func ToCamelCase
2017-09-08 09:52:43 +02:00
jhayotte
6db729b136 fix: upgrade huandu/xtrings to handle capital word with func ToCamelCase 2017-09-08 09:10:17 +02:00
Anastasia DERUELLE
ee845f3ed6 fix (helper): fix goTypeWithPackage enum case 2017-08-30 15:33:35 +02:00
Manfred Touron
19fd9d7959 Merge pull request #69 from moul/dev/proullon/helpers
feat (helpers): add strings helper
2017-06-26 17:34:13 +02:00
Pierre Roullon
07992907fd feat (helpers): add strings helper 2017-06-26 11:34:02 +02:00
Pierre Roullon
c52b282d96 fix (helper): fix goType enum case 2017-06-23 07:49:29 +00:00
Pierre Roullon
d454efa152 feat (helper): add haskellType helper 2017-06-23 07:49:29 +00:00
Pierre Roullon
80a62f29d8 fix (helper): splitArray helper does not return emtpy string 2017-06-23 07:49:29 +00:00
Valerio Gheri
8a25e9ba06 Merge pull request #67 from moul/vgheri/int32-fix
Fixes #5
2017-06-09 11:59:07 +02:00
Valerio Gheri
37fe30907a Fixes #5 2017-06-09 11:53:33 +02:00
Manfred Touron
fd268a723e Merge pull request #65 from moul/dev/jhayotte/timestamp
handle google.protobuf.timestamp
2017-06-08 17:02:19 +02:00
jhayotte
d0c5bb5a97 handle google.protobuf.timestamp 2017-06-08 16:40:52 +02:00
Manfred Touron
098b247649 Merge pull request #64 from moul/dev/moul/fix-urls-vars-from-message
Make urlHasVarsFromMessage consistent with getMessageType
2017-05-19 20:13:42 +02:00
Manfred Touron
09adcbdcd9 Make urlHasVarsFromMessage consistent with getMessageType 2017-05-19 20:10:20 +02:00
Manfred Touron
31a84ee58f Isolate helpers in their own package (#63) 2017-05-19 10:02:40 +02:00
Manfred Touron
62b7b2227e Add Docker usage (fix #8) 2017-05-19 09:56:47 +02:00
Manfred Touron
0f43ead321 Add $GOPATH/bin in /usr/local/bin:/usr/local/sbin::/Users/moul/mbin:/Users/moul/mbin2:/Users/moul/node_modules/.bin:/usr/local/share/npm/bin:/bin:/sbin:/usr/bin:/usr/sbin:/Users/moul/go/bin env var 2017-05-19 09:49:03 +02:00
1918 changed files with 5404 additions and 798515 deletions

View File

@@ -4,9 +4,12 @@ install:
- go get github.com/Masterminds/glide - go get github.com/Masterminds/glide
- wget https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/.travis/install-protoc.sh && chmod +x install-protoc.sh && ./install-protoc.sh 3.2.0 - wget https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master/.travis/install-protoc.sh && chmod +x install-protoc.sh && ./install-protoc.sh 3.2.0
- go get -u github.com/golang/protobuf/protoc-gen-go - go get -u github.com/golang/protobuf/protoc-gen-go
- go get -u github.com/alecthomas/gometalinter
- gometalinter --install
script: script:
- make install - make install
- make test - make test
- make lint
cache: cache:
directories: directories:
- $HOME/local - $HOME/local

View File

@@ -1,5 +1,13 @@
FROM znly/protoc FROM znly/protoc
RUN apk --update add make git go rsync
COPY . /go/src/github.com/moul/protoc-gen-gotemplate ENV GOPATH=/go \
WORKDIR /go/src/github.com/moul/protoc-gen-gotemplate PATH=/go/bin:${PATH}
RUN go install .
# Install deps and common tools
RUN apk --update add make git go rsync libc-dev \
&& go get -u golang.org/x/tools/cmd/goimports
# Install protoc-gen-gotemplate
COPY . /go/src/github.com/moul/protoc-gen-gotemplate
WORKDIR /go/src/github.com/moul/protoc-gen-gotemplate
RUN go install . ./cmd/web-editor

View File

@@ -5,9 +5,11 @@ build:
.PHONY: install .PHONY: install
install: install:
go install . go install .
go install ./cmd/web-editor
.PHONY: test .PHONY: test
test: install test: install
cd examples/time && make
cd examples/enum && make cd examples/enum && make
cd examples/import && make cd examples/import && make
cd examples/dummy && make cd examples/dummy && make
@@ -17,6 +19,8 @@ test: install
cd examples/sitemap && make cd examples/sitemap && make
cd examples/go-generate && make cd examples/go-generate && make
cd examples/single-package-mode && make cd examples/single-package-mode && make
cd examples/helpers && make
cd examples/arithmetics && make
# cd examples/go-kit && make # cd examples/go-kit && make
.PHONY: docker.build .PHONY: docker.build
@@ -26,3 +30,7 @@ docker.build:
.PHONY: docker.push .PHONY: docker.push
docker.push: docker.build docker.push: docker.build
docker push moul/protoc-gen-gotemplate docker push moul/protoc-gen-gotemplate
.PHONY: lint
lint:
gometalinter --disable-all --enable=errcheck --enable=vet --enable=vetshadow --enable=golint --enable=gas --enable=ineffassign --enable=goconst --enable=goimports --enable=gofmt --exclude="Binds to all network interfaces" --exclude="should have comment" --enable=staticcheck --enable=gosimple --enable=misspell --deadline=120s . ./cmd/... ./helpers/...

View File

@@ -22,6 +22,12 @@ The plugin parses **protobuf** files, generates an **ast**, and walks a local **
3. the `ast` is given to [Golang's `text/template` engine](https://golang.org/pkg/text/template/) for each *user* template files 3. the `ast` is given to [Golang's `text/template` engine](https://golang.org/pkg/text/template/) for each *user* template files
4. the *funcmap* enriching the template engine is based on [Masterminds/sprig](https://github.com/Masterminds/sprig), and contains type-manipulation, iteration and language-specific helpers 4. the *funcmap* enriching the template engine is based on [Masterminds/sprig](https://github.com/Masterminds/sprig), and contains type-manipulation, iteration and language-specific helpers
## Web editor
![Web editor screenshot](https://github.com/moul/protoc-gen-gotemplate/raw/master/assets/web-editor.jpg)
[Demo server](http://protoc-gen-gotemplate.m.42.am/)
## Usage ## Usage
`protoc-gen-gotemplate` requires a **template_dir** directory *(by default `./templates`)*. `protoc-gen-gotemplate` requires a **template_dir** directory *(by default `./templates`)*.
@@ -67,34 +73,59 @@ See [examples](./examples).
This project uses [Masterminds/sprig](https://github.com/Masterminds/sprig) library and additional functions to extend the builtin [text/template](https://golang.org/pkg/text/template) helpers. This project uses [Masterminds/sprig](https://github.com/Masterminds/sprig) library and additional functions to extend the builtin [text/template](https://golang.org/pkg/text/template) helpers.
Non-exhaustive list of new helpers:s Non-exhaustive list of new helpers:
* **all the functions from [sprig](https://github.com/Masterminds/sprig)** * **all the functions from [sprig](https://github.com/Masterminds/sprig)**
* `string`
* `json` * `json`
* `prettyjson` * `prettyjson`
* `splitArray`
* `first` * `first`
* `last` * `last`
* `splitArray`
* `upperFirst` * `upperFirst`
* `lowerFirst` * `lowerFirst`
* `camelCase` * `camelCase`
* `lowerCamelCase` * `lowerCamelCase`
* `kebabCase` * `kebabCase`
* `contains`
* `trimstr`
* `index`
* `snakeCase` * `snakeCase`
* `getProtoFile` * `getProtoFile`
* `getMessageType` * `getMessageType`
* `getEnumValue` * `getEnumValue`
* `isFieldMessage` * `isFieldMessage`
* `isFieldMessageTimeStamp`
* `isFieldRepeated` * `isFieldRepeated`
* `haskellType`
* `goType` * `goType`
* `goZeroValue`
* `goTypeWithPackage` * `goTypeWithPackage`
* `jsType` * `jsType`
* `jsSuffixReserved` * `jsSuffixReserved`
* `namespacedFlowType` * `namespacedFlowType`
* `httpVerb` * `httpVerb`
* `httpPath` * `httpPath`
* `httpPathsAdditionalBindings`
* `httpBody`
* `shortType` * `shortType`
* `urlHasVarsFromMessage` * `urlHasVarsFromMessage`
* `lowerGoNormalize`
* `goNormalize`
* `leadingComment`
* `trailingComment`
* `leadingDetachedComments`
* `stringFieldExtension`
* `stringMethodOptionsExtension`
* `boolFieldExtension`
* `isFieldMap`
* `fieldMapKeyType`
* `fieldMapValueType`
* `replaceDict`
* `add`
* `subtract`
* `multiply`
* `divide`
See the project helpers for the complete list. See the project helpers for the complete list.
@@ -104,6 +135,17 @@ See the project helpers for the complete list.
* Install **protobuf**: `go get -u github.com/golang/protobuf/{proto,protoc-gen-go}` * Install **protobuf**: `go get -u github.com/golang/protobuf/{proto,protoc-gen-go}`
* Install **protoc-gen-gotemplate**: `go get -u github.com/moul/protoc-gen-gotemplate` * Install **protoc-gen-gotemplate**: `go get -u github.com/moul/protoc-gen-gotemplate`
## Docker
* automated docker hub build: [https://hub.docker.com/r/moul/protoc-gen-gotemplate/](https://hub.docker.com/r/moul/protoc-gen-gotemplate/)
* Based on [http://github.com/znly/protoc](http://github.com/znly/protoc)
Usage:
```console
$> docker run --rm -v "$(pwd):$(pwd)" -w "$(pwd)" moul/protoc-gen-gotemplate -I. --gotemplate_out=./output/ ./*.proto
```
## Projects using `protoc-gen-gotemplate` ## Projects using `protoc-gen-gotemplate`
* [kafka-gateway](https://github.com/moul/kafka-gateway/): Kafka gateway/proxy (gRPC + http) using Go-Kit * [kafka-gateway](https://github.com/moul/kafka-gateway/): Kafka gateway/proxy (gRPC + http) using Go-Kit

BIN
assets/web-editor.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

118
cmd/web-editor/main.go Normal file
View File

@@ -0,0 +1,118 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
)
func generate(w http.ResponseWriter, r *http.Request) {
// read input
decoder := json.NewDecoder(r.Body)
type Input struct {
Protobuf string `json:"protobuf"`
Template string `json:"template"`
}
var input Input
if err := decoder.Decode(&input); err != nil {
returnError(w, err)
return
}
// create workspace
dir, err := ioutil.TempDir("", "pggt")
if err != nil {
returnError(w, err)
}
// clean up
defer func() {
if err = os.RemoveAll(dir); err != nil {
log.Printf("error: failed to remove temporary directory: %v", err)
}
}()
if err = ioutil.WriteFile(filepath.Join(dir, "example.proto"), []byte(input.Protobuf), 0644); err != nil {
returnError(w, err)
return
}
if err = ioutil.WriteFile(filepath.Join(dir, "example.output.tmpl"), []byte(input.Template), 0644); err != nil {
returnError(w, err)
return
}
// generate
cmd := exec.Command("protoc", "-I"+dir, "--gotemplate_out=template_dir="+dir+",debug=true:"+dir, filepath.Join(dir, "example.proto")) // #nosec
out, err := cmd.CombinedOutput()
if err != nil {
returnError(w, errors.New(string(out)))
return
}
// read output
content, err := ioutil.ReadFile(filepath.Join(dir, "example.output"))
if err != nil {
returnError(w, err)
return
}
returnContent(w, content)
}
func returnContent(w http.ResponseWriter, output interface{}) {
payload := map[string]interface{}{
"output": fmt.Sprintf("%s", output),
}
response, err := json.Marshal(payload)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
if _, err := w.Write(response); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func returnError(w http.ResponseWriter, err error) {
payload := map[string]interface{}{
"error": fmt.Sprintf("%v", err),
}
response, err := json.Marshal(payload)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
if _, err := w.Write(response); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func main() {
r := mux.NewRouter()
r.Handle("/", http.FileServer(http.Dir("static")))
r.HandleFunc("/generate", generate)
addr := fmt.Sprintf(":%s", os.Getenv("PORT"))
if addr == ":" {
addr = ":8080"
}
fmt.Printf("Listening on %s...\n", addr)
h := handlers.LoggingHandler(os.Stderr, r)
h = handlers.CompressHandler(h)
h = handlers.RecoveryHandler()(h)
if err := http.ListenAndServe(addr, h); err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,138 @@
<html>
<head>
<title>protoc-gen-gotemplate web editor</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular-animate.js"></script>
<script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.13.3.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.3/ui-bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/ace.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/mode-protobuf.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/mode-golang.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/theme-cobalt.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/worker-javascript.js"></script>
<script src="//angular-ui.github.io/ui-ace/dist/ui-ace.min.js"></script>
<script type="text/javascript">
var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(e){var t="";var n,r,i,s,o,u,a;var f=0;e=Base64._utf8_encode(e);while(f<e.length){n=e.charCodeAt(f++);r=e.charCodeAt(f++);i=e.charCodeAt(f++);s=n>>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+this._keyStr.charAt(s)+this._keyStr.charAt(o)+this._keyStr.charAt(u)+this._keyStr.charAt(a)}return t},decode:function(e){var t="";var n,r,i;var s,o,u,a;var f=0;e=e.replace(/[^A-Za-z0-9+/=]/g,"");while(f<e.length){s=this._keyStr.indexOf(e.charAt(f++));o=this._keyStr.indexOf(e.charAt(f++));u=this._keyStr.indexOf(e.charAt(f++));a=this._keyStr.indexOf(e.charAt(f++));n=s<<2|o>>4;r=(o&15)<<4|u>>2;i=(u&3)<<6|a;t=t+String.fromCharCode(n);if(u!=64){t=t+String.fromCharCode(r)}if(a!=64){t=t+String.fromCharCode(i)}}t=Base64._utf8_decode(t);return t},_utf8_encode:function(e){e=e.replace(/rn/g,"n");var t="";for(var n=0;n<e.length;n++){var r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r)}else if(r>127&&r<2048){t+=String.fromCharCode(r>>6|192);t+=String.fromCharCode(r&63|128)}else{t+=String.fromCharCode(r>>12|224);t+=String.fromCharCode(r>>6&63|128);t+=String.fromCharCode(r&63|128)}}return t},_utf8_decode:function(e){var t="";var n=0;var r=c1=c2=0;while(n<e.length){r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r);n++}else if(r>191&&r<224){c2=e.charCodeAt(n+1);t+=String.fromCharCode((r&31)<<6|c2&63);n+=2}else{c2=e.charCodeAt(n+1);c3=e.charCodeAt(n+2);t+=String.fromCharCode((r&15)<<12|(c2&63)<<6|c3&63);n+=3}}return t}}
angular.module("pggt", ['pggt.controllers','ngAnimate','ui.bootstrap', 'ui.ace']);
angular.module("pggt.controllers", [])
.controller('PggtCtrl', ['$scope', '$http', '$interval', function($scope, $http, $interval) {
$scope.requestType = 'post';
$scope.url = '/generate';
$scope.response = null;
$scope.errors = null;
$scope.inputHasChanged = false;
$scope.checkModel = {
optimize: false,
disableNetwork: false,
toArm: false,
};
$scope.$watchCollection('checkModel', function() {
$scope.sendRequest();
});
var cron = $interval(function() {
if ($scope.inputHasChanged) {
$scope.sendRequest();
}
}, 2000);
// b64encoded version of https://github.com/grpc/grpc-go/blob/master/examples/route_guide/routeguide/route_guide.proto
$scope.protobuf = Base64.decode("Ly8gQ29weXJpZ2h0IDIwMTUgZ1JQQyBhdXRob3JzLg0KLy8NCi8vIExpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOw0KLy8geW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLg0KLy8gWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0DQovLw0KLy8gICAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMA0KLy8NCi8vIFVubGVzcyByZXF1aXJlZCBieSBhcHBsaWNhYmxlIGxhdyBvciBhZ3JlZWQgdG8gaW4gd3JpdGluZywgc29mdHdhcmUNCi8vIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuICJBUyBJUyIgQkFTSVMsDQovLyBXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4NCi8vIFNlZSB0aGUgTGljZW5zZSBmb3IgdGhlIHNwZWNpZmljIGxhbmd1YWdlIGdvdmVybmluZyBwZXJtaXNzaW9ucyBhbmQNCi8vIGxpbWl0YXRpb25zIHVuZGVyIHRoZSBMaWNlbnNlLg0KDQpzeW50YXggPSAicHJvdG8zIjsNCg0Kb3B0aW9uIGphdmFfbXVsdGlwbGVfZmlsZXMgPSB0cnVlOw0Kb3B0aW9uIGphdmFfcGFja2FnZSA9ICJpby5ncnBjLmV4YW1wbGVzLnJvdXRlZ3VpZGUiOw0Kb3B0aW9uIGphdmFfb3V0ZXJfY2xhc3NuYW1lID0gIlJvdXRlR3VpZGVQcm90byI7DQoNCnBhY2thZ2Ugcm91dGVndWlkZTsNCg0KLy8gSW50ZXJmYWNlIGV4cG9ydGVkIGJ5IHRoZSBzZXJ2ZXIuDQpzZXJ2aWNlIFJvdXRlR3VpZGUgew0KICAvLyBBIHNpbXBsZSBSUEMuDQogIC8vDQogIC8vIE9idGFpbnMgdGhlIGZlYXR1cmUgYXQgYSBnaXZlbiBwb3NpdGlvbi4NCiAgLy8NCiAgLy8gQSBmZWF0dXJlIHdpdGggYW4gZW1wdHkgbmFtZSBpcyByZXR1cm5lZCBpZiB0aGVyZSdzIG5vIGZlYXR1cmUgYXQgdGhlIGdpdmVuDQogIC8vIHBvc2l0aW9uLg0KICBycGMgR2V0RmVhdHVyZShQb2ludCkgcmV0dXJucyAoRmVhdHVyZSkge30NCg0KICAvLyBBIHNlcnZlci10by1jbGllbnQgc3RyZWFtaW5nIFJQQy4NCiAgLy8NCiAgLy8gT2J0YWlucyB0aGUgRmVhdHVyZXMgYXZhaWxhYmxlIHdpdGhpbiB0aGUgZ2l2ZW4gUmVjdGFuZ2xlLiAgUmVzdWx0cyBhcmUNCiAgLy8gc3RyZWFtZWQgcmF0aGVyIHRoYW4gcmV0dXJuZWQgYXQgb25jZSAoZS5nLiBpbiBhIHJlc3BvbnNlIG1lc3NhZ2Ugd2l0aCBhDQogIC8vIHJlcGVhdGVkIGZpZWxkKSwgYXMgdGhlIHJlY3RhbmdsZSBtYXkgY292ZXIgYSBsYXJnZSBhcmVhIGFuZCBjb250YWluIGENCiAgLy8gaHVnZSBudW1iZXIgb2YgZmVhdHVyZXMuDQogIHJwYyBMaXN0RmVhdHVyZXMoUmVjdGFuZ2xlKSByZXR1cm5zIChzdHJlYW0gRmVhdHVyZSkge30NCg0KICAvLyBBIGNsaWVudC10by1zZXJ2ZXIgc3RyZWFtaW5nIFJQQy4NCiAgLy8NCiAgLy8gQWNjZXB0cyBhIHN0cmVhbSBvZiBQb2ludHMgb24gYSByb3V0ZSBiZWluZyB0cmF2ZXJzZWQsIHJldHVybmluZyBhDQogIC8vIFJvdXRlU3VtbWFyeSB3aGVuIHRyYXZlcnNhbCBpcyBjb21wbGV0ZWQuDQogIHJwYyBSZWNvcmRSb3V0ZShzdHJlYW0gUG9pbnQpIHJldHVybnMgKFJvdXRlU3VtbWFyeSkge30NCg0KICAvLyBBIEJpZGlyZWN0aW9uYWwgc3RyZWFtaW5nIFJQQy4NCiAgLy8NCiAgLy8gQWNjZXB0cyBhIHN0cmVhbSBvZiBSb3V0ZU5vdGVzIHNlbnQgd2hpbGUgYSByb3V0ZSBpcyBiZWluZyB0cmF2ZXJzZWQsDQogIC8vIHdoaWxlIHJlY2VpdmluZyBvdGhlciBSb3V0ZU5vdGVzIChlLmcuIGZyb20gb3RoZXIgdXNlcnMpLg0KICBycGMgUm91dGVDaGF0KHN0cmVhbSBSb3V0ZU5vdGUpIHJldHVybnMgKHN0cmVhbSBSb3V0ZU5vdGUpIHt9DQp9DQoNCi8vIFBvaW50cyBhcmUgcmVwcmVzZW50ZWQgYXMgbGF0aXR1ZGUtbG9uZ2l0dWRlIHBhaXJzIGluIHRoZSBFNyByZXByZXNlbnRhdGlvbg0KLy8gKGRlZ3JlZXMgbXVsdGlwbGllZCBieSAxMCoqNyBhbmQgcm91bmRlZCB0byB0aGUgbmVhcmVzdCBpbnRlZ2VyKS4NCi8vIExhdGl0dWRlcyBzaG91bGQgYmUgaW4gdGhlIHJhbmdlICsvLSA5MCBkZWdyZWVzIGFuZCBsb25naXR1ZGUgc2hvdWxkIGJlIGluDQovLyB0aGUgcmFuZ2UgKy8tIDE4MCBkZWdyZWVzIChpbmNsdXNpdmUpLg0KbWVzc2FnZSBQb2ludCB7DQogIGludDMyIGxhdGl0dWRlID0gMTsNCiAgaW50MzIgbG9uZ2l0dWRlID0gMjsNCn0NCg0KLy8gQSBsYXRpdHVkZS1sb25naXR1ZGUgcmVjdGFuZ2xlLCByZXByZXNlbnRlZCBhcyB0d28gZGlhZ29uYWxseSBvcHBvc2l0ZQ0KLy8gcG9pbnRzICJsbyIgYW5kICJoaSIuDQptZXNzYWdlIFJlY3RhbmdsZSB7DQogIC8vIE9uZSBjb3JuZXIgb2YgdGhlIHJlY3RhbmdsZS4NCiAgUG9pbnQgbG8gPSAxOw0KDQogIC8vIFRoZSBvdGhlciBjb3JuZXIgb2YgdGhlIHJlY3RhbmdsZS4NCiAgUG9pbnQgaGkgPSAyOw0KfQ0KDQovLyBBIGZlYXR1cmUgbmFtZXMgc29tZXRoaW5nIGF0IGEgZ2l2ZW4gcG9pbnQuDQovLw0KLy8gSWYgYSBmZWF0dXJlIGNvdWxkIG5vdCBiZSBuYW1lZCwgdGhlIG5hbWUgaXMgZW1wdHkuDQptZXNzYWdlIEZlYXR1cmUgew0KICAvLyBUaGUgbmFtZSBvZiB0aGUgZmVhdHVyZS4NCiAgc3RyaW5nIG5hbWUgPSAxOw0KDQogIC8vIFRoZSBwb2ludCB3aGVyZSB0aGUgZmVhdHVyZSBpcyBkZXRlY3RlZC4NCiAgUG9pbnQgbG9jYXRpb24gPSAyOw0KfQ0KDQovLyBBIFJvdXRlTm90ZSBpcyBhIG1lc3NhZ2Ugc2VudCB3aGlsZSBhdCBhIGdpdmVuIHBvaW50Lg0KbWVzc2FnZSBSb3V0ZU5vdGUgew0KICAvLyBUaGUgbG9jYXRpb24gZnJvbSB3aGljaCB0aGUgbWVzc2FnZSBpcyBzZW50Lg0KICBQb2ludCBsb2NhdGlvbiA9IDE7DQoNCiAgLy8gVGhlIG1lc3NhZ2UgdG8gYmUgc2VudC4NCiAgc3RyaW5nIG1lc3NhZ2UgPSAyOw0KfQ0KDQovLyBBIFJvdXRlU3VtbWFyeSBpcyByZWNlaXZlZCBpbiByZXNwb25zZSB0byBhIFJlY29yZFJvdXRlIHJwYy4NCi8vDQovLyBJdCBjb250YWlucyB0aGUgbnVtYmVyIG9mIGluZGl2aWR1YWwgcG9pbnRzIHJlY2VpdmVkLCB0aGUgbnVtYmVyIG9mDQovLyBkZXRlY3RlZCBmZWF0dXJlcywgYW5kIHRoZSB0b3RhbCBkaXN0YW5jZSBjb3ZlcmVkIGFzIHRoZSBjdW11bGF0aXZlIHN1bSBvZg0KLy8gdGhlIGRpc3RhbmNlIGJldHdlZW4gZWFjaCBwb2ludC4NCm1lc3NhZ2UgUm91dGVTdW1tYXJ5IHsNCiAgLy8gVGhlIG51bWJlciBvZiBwb2ludHMgcmVjZWl2ZWQuDQogIGludDMyIHBvaW50X2NvdW50ID0gMTsNCg0KICAvLyBUaGUgbnVtYmVyIG9mIGtub3duIGZlYXR1cmVzIHBhc3NlZCB3aGlsZSB0cmF2ZXJzaW5nIHRoZSByb3V0ZS4NCiAgaW50MzIgZmVhdHVyZV9jb3VudCA9IDI7DQoNCiAgLy8gVGhlIGRpc3RhbmNlIGNvdmVyZWQgaW4gbWV0cmVzLg0KICBpbnQzMiBkaXN0YW5jZSA9IDM7DQoNCiAgLy8gVGhlIGR1cmF0aW9uIG9mIHRoZSB0cmF2ZXJzYWwgaW4gc2Vjb25kcy4NCiAgaW50MzIgZWxhcHNlZF90aW1lID0gNDsNCn0=");
$scope.template = Base64.decode("Ly8gQ29kZSBnZW5lcmF0ZWQgYnkgcHJvdG9jLWdlbi1nb3RlbXBsYXRlDQpwYWNrYWdlIHt7LlNlcnZpY2UuTmFtZX19cGINCg0KaW1wb3J0ICgNCiAgImdvbGFuZy5vcmcveC9uZXQvY29udGV4dCINCikNCg0KdHlwZSBTZXJ2aWNlIGludGVyZmFjZSB7DQp7ey0gcmFuZ2UgLlNlcnZpY2UuTWV0aG9kfX0NCnt7LSBpZiBhbmQgKG5vdCAuU2VydmVyU3RyZWFtaW5nKSAobm90IC5DbGllbnRTdHJlYW1pbmcpfX0NCiAge3suTmFtZX19KGNvbnRleHQuQ29udGV4dCwgKnt7Lk5hbWV9fVJlcXVlc3QpICgqe3suTmFtZX19UmVzcG9uc2UsIGVycm9yKQ0Ke3stIGVsc2UgaWYgYW5kIC5TZXJ2ZXJTdHJlYW1pbmcgKG5vdCAuQ2xpZW50U3RyZWFtaW5nKX19DQoge3suTmFtZX19KGNvbnRleHQuQ29udGV4dCwgKnt7Lk5hbWV9fVJlcXVlc3QsIGNoYW4gc3RydWN0e30pIChjaGFuICp7ey5OYW1lfX1SZXNwb25zZSwgZXJyb3IpDQp7ey0gZWxzZSBpZiBhbmQgKG5vdCAuU2VydmVyU3RyZWFtaW5nKSAuQ2xpZW50U3RyZWFtaW5nfX0NCiAge3suTmFtZX19KGNvbnRleHQuQ29udGV4dCwgY2hhbiAqe3suTmFtZX19UmVxdWVzdCkgKCp7ey5OYW1lfX1SZXNwb25zZSwgZXJyb3IpDQp7ey0gZWxzZSBpZiBhbmQgKC5TZXJ2ZXJTdHJlYW1pbmcpICguQ2xpZW50U3RyZWFtaW5nKX19DQogIHt7Lk5hbWV9fShjb250ZXh0LkNvbnRleHQsIGNoYW4gKnt7Lk5hbWV9fVJlcXVlc3QpIChjaGFuICp7ey5OYW1lfX1SZXNwb25zZSwgZXJyb3IpDQp7ey0gZW5kfX0ge3svKiBzdHJlYW1pbmcgaWZzKi99fQ0Ke3stIGVuZH19IHt7LypyYW5nZSBNZXRob2QqL319DQp9DQoNCi8vIE1ldGhvZHMNCi8vIC0tLS0tLS0NCnt7LSByYW5nZSAuU2VydmljZS5NZXRob2R9fQ0KLy8gKiB7ey5OYW1lfX0NCnt7LSBlbmR9fQ0KLy8NCi8vIE1lc3NhZ2UgdHlwZXMNCi8vIC0tLS0tLS0tLS0tLS0NCnt7LSByYW5nZSAuRmlsZS5NZXNzYWdlVHlwZX19DQovLyAqIHt7Lk5hbWV9fQ0Ke3stIGVuZH19");
$scope.inputLoaded = function(_editor) {
$scope.inputEditor = _editor;
};
$scope.outputLoaded = function(_editor) {
$scope.outputEditor = _editor;
};
$scope.inputChanged = function(e) {
$scope.inputHasChanged = true;
};
$scope.sendRequest = function(){
$scope.inputHasChanged = false;
var data = {
protobuf: $scope.protobuf,
template: $scope.template,
};
$http.post($scope.url, data)
.success(function(data,status,headers,config) {
$scope.errors = {};
$scope.response = {};
$scope.response.data = data;
$scope.response.status = status;
$scope.response.headers = headers;
$scope.response.config = config;
$scope.outputEditor.setValue(data['output'], 1);
})
.error(function(data,status,headers,config) {
$scope.errors = {};
$scope.response = {};
$scope.errors.data = data;
$scope.errors.status = status;
$scope.errors.headers = headers;
$scope.errors.config = config;
$scope.outputEditor.setValue(data['error'], 1);
});
};
}]);
</script>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.3/styles/github.min.css">
<style>.ace_editor { height: 80%; }</style>
</head>
<body ng-app="pggt">
<div class="container-fluid" ng-controller="PggtCtrl">
<div class="row">
<div class="col-md-8">
<form name="dpform" ng-submit="sendRequest()" class="well">
<fieldset>
<legend>`protoc-gen-gotemplate`: input</legend>
<div class="row">
<div class="col-md-6">
<label for="protobuf">./example.proto</label>
<div ng-model="protobuf" name="protobuf" id="protobuf" language="protobuf"
ui-ace="{mode:'protobuf',theme:'cobalt',onChange:inputChanged,onLoad:inputLoaded,useWrapMode:true}">
</div>
</div>
<div class="col-md-6">
<label for="template">./example.txt.tmpl</label>
<div ng-model="template" name="template" id="template" language="text"
ui-ace="{mode:'text',theme:'cobalt',onChange:inputChanged,onLoad:inputLoaded,useWrapMode:true}">
</div>
</div>
</div>
</fieldset>
</form>
</div>
<div class="col-md-4">
<div class="well">
<fieldset>
<legend>Output</legend>
<label>./example.txt</label>
<div ui-ace="{mode:'text',theme:'cobalt',onLoad:outputLoaded,useWrapMode:true}" readonly></div>
</fieldset>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div>Command: <code>protoc --gotemplate_out=template_dir=.:. example.proto</code></div>
<div>Powered by <a href="https://github.com/moul/protoc-gen-gotemplate">protoc-gen-gotemplate</a></div>
</div>
</div>
</div>
<a href="https://github.com/moul/protoc-gen-gotemplate"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/365986a132ccd6a44c23a9169022c0b5c890c387/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"></a>
</body>
</html>

View File

@@ -11,6 +11,8 @@ import (
"github.com/golang/protobuf/protoc-gen-go/descriptor" "github.com/golang/protobuf/protoc-gen-go/descriptor"
"github.com/golang/protobuf/protoc-gen-go/plugin" "github.com/golang/protobuf/protoc-gen-go/plugin"
pgghelpers "github.com/moul/protoc-gen-gotemplate/helpers"
) )
type GenericTemplateBasedEncoder struct { type GenericTemplateBasedEncoder struct {
@@ -50,6 +52,7 @@ func NewGenericServiceTemplateBasedEncoder(templateDir string, service *descript
if debug { if debug {
log.Printf("new encoder: file=%q service=%q template-dir=%q", file.GetName(), service.GetName(), templateDir) log.Printf("new encoder: file=%q service=%q template-dir=%q", file.GetName(), service.GetName(), templateDir)
} }
pgghelpers.InitPathMap(file)
return return
} }
@@ -66,6 +69,7 @@ func NewGenericTemplateBasedEncoder(templateDir string, file *descriptor.FileDes
if debug { if debug {
log.Printf("new encoder: file=%q template-dir=%q", file.GetName(), templateDir) log.Printf("new encoder: file=%q template-dir=%q", file.GetName(), templateDir)
} }
pgghelpers.InitPathMap(file)
return return
} }
@@ -98,11 +102,20 @@ func (e *GenericTemplateBasedEncoder) templates() ([]string, error) {
func (e *GenericTemplateBasedEncoder) genAst(templateFilename string) (*Ast, error) { func (e *GenericTemplateBasedEncoder) genAst(templateFilename string) (*Ast, error) {
// prepare the ast passed to the template engine // prepare the ast passed to the template engine
hostname, _ := os.Hostname() hostname, err := os.Hostname()
pwd, _ := os.Getwd() if err != nil {
return nil, err
}
pwd, err := os.Getwd()
if err != nil {
return nil, err
}
goPwd := "" goPwd := ""
if os.Getenv("GOPATH") != "" { if os.Getenv("GOPATH") != "" {
goPwd, _ = filepath.Rel(os.Getenv("GOPATH")+"/src", pwd) goPwd, err = filepath.Rel(os.Getenv("GOPATH")+"/src", pwd)
if err != nil {
return nil, err
}
if strings.Contains(goPwd, "../") { if strings.Contains(goPwd, "../") {
goPwd = "" goPwd = ""
} }
@@ -122,7 +135,7 @@ func (e *GenericTemplateBasedEncoder) genAst(templateFilename string) (*Ast, err
Enum: e.enum, Enum: e.enum,
} }
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
tmpl, err := template.New("").Funcs(ProtoHelpersFuncMap).Parse(templateFilename) tmpl, err := template.New("").Funcs(pgghelpers.ProtoHelpersFuncMap).Parse(templateFilename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -137,7 +150,7 @@ func (e *GenericTemplateBasedEncoder) buildContent(templateFilename string) (str
// initialize template engine // initialize template engine
fullPath := filepath.Join(e.templateDir, templateFilename) fullPath := filepath.Join(e.templateDir, templateFilename)
templateName := filepath.Base(fullPath) templateName := filepath.Base(fullPath)
tmpl, err := template.New(templateName).Funcs(ProtoHelpersFuncMap).ParseFiles(fullPath) tmpl, err := template.New(templateName).Funcs(pgghelpers.ProtoHelpersFuncMap).ParseFiles(fullPath)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
@@ -168,7 +181,8 @@ func (e *GenericTemplateBasedEncoder) Files() []*plugin_go.CodeGeneratorResponse
resultChan := make(chan *plugin_go.CodeGeneratorResponse_File, length) resultChan := make(chan *plugin_go.CodeGeneratorResponse_File, length)
for _, templateFilename := range templates { for _, templateFilename := range templates {
go func(tmpl string) { go func(tmpl string) {
content, translatedFilename, err := e.buildContent(tmpl) var translatedFilename, content string
content, translatedFilename, err = e.buildContent(tmpl)
if err != nil { if err != nil {
errChan <- err errChan <- err
return return

View File

@@ -0,0 +1,13 @@
.PHONY: build
build:
mkdir -p output
protoc -I. --gotemplate_out=template_dir=templates,debug=true,all=true:output proto/*.proto
.PHONY: re
re: clean build
.PHONY: clean
clean:
rm -rf output

View File

@@ -0,0 +1,5 @@
add(1,2) = 3
subtract(1,2) = -1
multiply(1,2) = 2
divide(2,1) = 2

View File

@@ -0,0 +1,2 @@
syntax = "proto3";
package Arithmetics;

View File

@@ -0,0 +1,6 @@
{{with $a := 1}}{{with $b := 2}}
add(1,2) = {{add $a $b}}
subtract(1,2) = {{subtract $a $b}}
multiply(1,2) = {{multiply $a $b}}
divide(2,1) = {{divide $b $a}}
{{end}}{{end}}

View File

@@ -1,6 +1,6 @@
{ {
"build-date": "2017-05-02T11:50:25.063291628+02:00", "build-date": "2017-05-19T20:09:45.954357761+02:00",
"build-hostname": "manfred-spacegray.local", "build-hostname": "manfred-spacegray.aircard",
"build-user": "moul", "build-user": "moul",
"pwd": "/Users/moul/Git/moul/protoc-gen-gotemplate/examples/dummy", "pwd": "/Users/moul/Git/moul/protoc-gen-gotemplate/examples/dummy",
"debug": false, "debug": false,
@@ -839,5 +839,6 @@
"options": {} "options": {}
} }
] ]
} },
"enum": null
} }

13
examples/helpers/Makefile Normal file
View File

@@ -0,0 +1,13 @@
.PHONY: build
build:
mkdir -p output
protoc -I. --gotemplate_out=template_dir=.,debug=true:. *.proto
.PHONY: re
re: clean build
.PHONY: clean
clean:
rm -rf output

View File

@@ -0,0 +1,58 @@
# Common variables
{{.File.Name}}: helpers.proto
{{.File.Name | upper}}: HELPERS.PROTO
{{.File.Package | base | replace "." "-"}} dummy
{{$packageDir := .File.Name | dir}}{{$packageDir}} .
{{$packageName := .File.Name | base | replace ".proto" ""}}{{$packageName}} helpers
{{$packageImport := .File.Package | replace "." "_"}}{{$packageImport}} dummy
{{$namespacedPackage := .File.Package}}{{$namespacedPackage}} dummy
{{$currentFile := .File.Name | getProtoFile}}{{$currentFile}} <nil>
{{- /*{{- $currentPackageName := $currentFile.GoPkg.Name}}{{$currentPackageName}}*/}}
# TODO: more variables
# Sprig: strings
{{trim " hello "}}: hello
{{trimAll "$" "$5.00"}}: 5.00
{{trimSuffix "-" "hello-"}}: hello
{{upper "hello"}}: HELLO
{{lower "HELLO"}}: hello
{{title "hello world"}}: Hello World
{{untitle "Hello World"}}: hello world
{{repeat 3 "hello"}}: hellohellohello
{{substr 0 5 "hello world"}}: hello
{{nospace "hello w o r l d"}}: helloworld
{{trunc 5 "hello world"}}: hello
{{abbrev 5 "hello world"}}: he...
{{abbrevboth 5 10 "1234 5678 9123"}}: ...5678...
{{initials "First Try"}}: FT
{{randNumeric 3}}: 528
{{- /*{{wrap 80 $someText}}*/}}:
{{wrapWith 5 "\t" "Hello World"}}: Hello World
{{contains "cat" "catch"}}: true
{{hasPrefix "cat" "catch"}}: true
{{cat "hello" "beautiful" "world"}}: hello beautiful world
{{- /*{{indent 4 $lots_of_text}}*/}}:
{{- /*{{indent 4 $lots_of_text}}*/}}:
{{"I Am Henry VIII" | replace " " "-"}}: I-Am-Henry-VIII
{{len .Service.Method | plural "one anchovy" "many anchovies"}}: many anchovies
{{snakecase "FirstName"}}: first_name
{{camelcase "http_server"}}: HttpServer
{{shuffle "hello"}}: holle
{{regexMatch "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}" "test@acme.com"}}: true
{{- /*{{regexFindAll "[2,4,6,8]" "123456789"}}*/}}:
{{regexFind "[a-zA-Z][1-9]" "abcd1234"}}: d1
{{regexReplaceAll "a(x*)b" "-ab-axxb-" "${1}W"}}: -W-xxW-
{{regexReplaceAllLiteral "a(x*)b" "-ab-axxb-" "${1}"}}: -${1}-${1}-
{{regexSplit "z+" "pizza" -1}}: [pi a]
# Get one specific method on array method using index
{{ index .Service.Method 1 }}: name:"Iii" input_type:".dummy.Dummy2" output_type:".dummy.Dummy1" options:<>
# Sprig: advanced
{{if contains "cat" "catch"}}yes{{else}}no{{end}}: yes
{{1 | plural "one anchovy" "many anchovies"}}: one anchovy
{{2 | plural "one anchovy" "many anchovies"}}: many anchovies
{{3 | plural "one anchovy" "many anchovies"}}: many anchovies
# TODO: more sprig examples
# TODO: all built-in examples

View File

@@ -0,0 +1,59 @@
# Common variables
{{`{{.File.Name}}`}}: {{.File.Name}}
{{`{{.File.Name | upper}}`}}: {{.File.Name | upper}}
{{`{{.File.Package | base | replace "." "-"}}`}} {{.File.Package | base | replace "." "-"}}
{{- /*{{`{{$file := .File}}{{$file}}`}} {{$file := .File}}{{$file}}*/}}
{{`{{$packageDir := .File.Name | dir}}{{$packageDir}}`}} {{$packageDir := .File.Name | dir}}{{$packageDir}}
{{`{{$packageName := .File.Name | base | replace ".proto" ""}}{{$packageName}}`}} {{$packageName := .File.Name | base | replace ".proto" ""}}{{$packageName}}
{{`{{$packageImport := .File.Package | replace "." "_"}}{{$packageImport}}`}} {{$packageImport := .File.Package | replace "." "_"}}{{$packageImport}}
{{`{{$namespacedPackage := .File.Package}}{{$namespacedPackage}}`}} {{$namespacedPackage := .File.Package}}{{$namespacedPackage}}
{{`{{$currentFile := .File.Name | getProtoFile}}{{$currentFile}}`}} {{$currentFile := .File.Name | getProtoFile}}{{$currentFile}}
{{`{{- /*{{- $currentPackageName := $currentFile.GoPkg.Name}}{{$currentPackageName}}*/}}`}} {{- /*{{- $currentPackageName := $currentFile.GoPkg.Name}}{{$currentPackageName}}*/}}
# TODO: more variables
# Sprig: strings
{{`{{trim " hello "}}`}}: {{trim " hello "}}
{{`{{trimAll "$" "$5.00"}}`}}: {{trimAll "$" "$5.00"}}
{{`{{trimSuffix "-" "hello-"}}`}}: {{trimSuffix "-" "hello-"}}
{{`{{upper "hello"}}`}}: {{upper "hello"}}
{{`{{lower "HELLO"}}`}}: {{lower "HELLO"}}
{{`{{title "hello world"}}`}}: {{title "hello world"}}
{{`{{untitle "Hello World"}}`}}: {{untitle "Hello World"}}
{{`{{repeat 3 "hello"}}`}}: {{repeat 3 "hello"}}
{{`{{substr 0 5 "hello world"}}`}}: {{substr 0 5 "hello world"}}
{{`{{nospace "hello w o r l d"}}`}}: {{nospace "hello w o r l d"}}
{{`{{trunc 5 "hello world"}}`}}: {{trunc 5 "hello world"}}
{{`{{abbrev 5 "hello world"}}`}}: {{abbrev 5 "hello world"}}
{{`{{abbrevboth 5 10 "1234 5678 9123"}}`}}: {{abbrevboth 5 10 "1234 5678 9123"}}
{{`{{initials "First Try"}}`}}: {{initials "First Try"}}
{{`{{randNumeric 3}}`}}: {{randNumeric 3}}
{{`{{- /*{{wrap 80 $someText}}*/}}`}}: {{- /*{{wrap 80 $someText}}*/}}
{{`{{wrapWith 5 "\t" "Hello World"}}`}}: {{wrapWith 5 "\t" "Hello World"}}
{{`{{contains "cat" "catch"}}`}}: {{contains "cat" "catch"}}
{{`{{hasPrefix "cat" "catch"}}`}}: {{hasPrefix "cat" "catch"}}
{{`{{cat "hello" "beautiful" "world"}}`}}: {{cat "hello" "beautiful" "world"}}
{{`{{- /*{{indent 4 $lots_of_text}}*/}}`}}: {{- /*{{indent 4 $lots_of_text}}*/}}
{{`{{- /*{{indent 4 $lots_of_text}}*/}}`}}: {{- /*{{indent 4 $lots_of_text}}*/}}
{{`{{"I Am Henry VIII" | replace " " "-"}}`}}: {{"I Am Henry VIII" | replace " " "-"}}
{{`{{len .Service.Method | plural "one anchovy" "many anchovies"}}`}}: {{len .Service.Method | plural "one anchovy" "many anchovies"}}
{{`{{snakecase "FirstName"}}`}}: {{snakecase "FirstName"}}
{{`{{camelcase "http_server"}}`}}: {{camelcase "http_server"}}
{{`{{shuffle "hello"}}`}}: {{shuffle "hello"}}
{{`{{regexMatch "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}" "test@acme.com"}}`}}: {{regexMatch "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}" "test@acme.com"}}
{{`{{- /*{{regexFindAll "[2,4,6,8]" "123456789"}}*/}}`}}: {{- /*{{regexFindAll "[2,4,6,8]" "123456789"}}*/}}
{{`{{regexFind "[a-zA-Z][1-9]" "abcd1234"}}`}}: {{regexFind "[a-zA-Z][1-9]" "abcd1234"}}
{{`{{regexReplaceAll "a(x*)b" "-ab-axxb-" "${1}W"}}`}}: {{regexReplaceAll "a(x*)b" "-ab-axxb-" "${1}W"}}
{{`{{regexReplaceAllLiteral "a(x*)b" "-ab-axxb-" "${1}"}}`}}: {{regexReplaceAllLiteral "a(x*)b" "-ab-axxb-" "${1}"}}
{{`{{regexSplit "z+" "pizza" -1}}`}}: {{regexSplit "z+" "pizza" -1}}
# Get one specific method on array method using index
{{`{{ index .Service.Method 1 }}`}}: {{ index .Service.Method 1 }}
# Sprig: advanced
{{`{{if contains "cat" "catch"}}yes{{else}}no{{end}}`}}: {{if contains "cat" "catch"}}yes{{else}}no{{end}}
{{`{{1 | plural "one anchovy" "many anchovies"}}`}}: {{1 | plural "one anchovy" "many anchovies"}}
{{`{{2 | plural "one anchovy" "many anchovies"}}`}}: {{2 | plural "one anchovy" "many anchovies"}}
{{`{{3 | plural "one anchovy" "many anchovies"}}`}}: {{3 | plural "one anchovy" "many anchovies"}}
# TODO: more sprig examples
# TODO: all built-in examples

View File

@@ -0,0 +1,25 @@
syntax = "proto3";
package dummy;
option go_package = "github.com/moul/protoc-gen-gotemplate/examples/helpers";
message Dummy1 {
float aaa = 1;
string bbb = 2;
int32 ccc = 3;
int64 ddd = 4;
repeated string eee = 5;
}
message Dummy2 {
float fff = 1;
Dummy1 ggg = 2;
}
message Dummy3 {}
service DummyService {
rpc Hhh(Dummy1) returns (Dummy2) {}
rpc Iii(Dummy2) returns (Dummy1) {}
}

View File

@@ -1,6 +1,5 @@
// Code generated by protoc-gen-go. // Code generated by protoc-gen-go. DO NOT EDIT.
// source: proto/article.proto // source: proto/article.proto
// DO NOT EDIT!
/* /*
Package article is a generated protocol buffer package. Package article is a generated protocol buffer package.
@@ -49,6 +48,8 @@ func (m *GetArticleRequest) GetGetarticle() *common.GetArticle {
type GetArticleResponse struct { type GetArticleResponse struct {
Article *Article `protobuf:"bytes,1,opt,name=article" json:"article,omitempty"` Article *Article `protobuf:"bytes,1,opt,name=article" json:"article,omitempty"`
// The generated output should write []*GetArticleResponse_Storage.Storage for this field.
Storages []*GetArticleResponse_Storage `protobuf:"bytes,2,rep,name=storages" json:"storages,omitempty"`
} }
func (m *GetArticleResponse) Reset() { *m = GetArticleResponse{} } func (m *GetArticleResponse) Reset() { *m = GetArticleResponse{} }
@@ -63,6 +64,29 @@ func (m *GetArticleResponse) GetArticle() *Article {
return nil return nil
} }
func (m *GetArticleResponse) GetStorages() []*GetArticleResponse_Storage {
if m != nil {
return m.Storages
}
return nil
}
type GetArticleResponse_Storage struct {
Code string `protobuf:"bytes,1,opt,name=code" json:"code,omitempty"`
}
func (m *GetArticleResponse_Storage) Reset() { *m = GetArticleResponse_Storage{} }
func (m *GetArticleResponse_Storage) String() string { return proto.CompactTextString(m) }
func (*GetArticleResponse_Storage) ProtoMessage() {}
func (*GetArticleResponse_Storage) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1, 0} }
func (m *GetArticleResponse_Storage) GetCode() string {
if m != nil {
return m.Code
}
return ""
}
type Article struct { type Article struct {
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"`
@@ -88,26 +112,30 @@ func (m *Article) GetName() string {
} }
func init() { func init() {
proto.RegisterType((*GetArticleRequest)(nil), "article.GetArticleRequest") proto.RegisterType((*GetArticleRequest)(nil), "company.GetArticleRequest")
proto.RegisterType((*GetArticleResponse)(nil), "article.GetArticleResponse") proto.RegisterType((*GetArticleResponse)(nil), "company.GetArticleResponse")
proto.RegisterType((*Article)(nil), "article.Article") proto.RegisterType((*GetArticleResponse_Storage)(nil), "company.GetArticleResponse.Storage")
proto.RegisterType((*Article)(nil), "company.Article")
} }
func init() { proto.RegisterFile("proto/article.proto", fileDescriptor0) } func init() { proto.RegisterFile("proto/article.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{ var fileDescriptor0 = []byte{
// 208 bytes of a gzipped FileDescriptorProto // 256 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2e, 0x28, 0xca, 0x2f, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x91, 0x41, 0x4b, 0xc4, 0x30,
0xc9, 0xd7, 0x4f, 0x2c, 0x2a, 0xc9, 0x4c, 0xce, 0x49, 0xd5, 0x03, 0xf3, 0x84, 0xd8, 0xa1, 0x5c, 0x10, 0x85, 0x6d, 0x15, 0xab, 0xb3, 0x20, 0x3a, 0x82, 0x94, 0x8a, 0xb0, 0xd4, 0xcb, 0x22, 0x18,
0x29, 0x21, 0x88, 0x6c, 0x72, 0x7e, 0x6e, 0x6e, 0x7e, 0x1e, 0x44, 0x52, 0xc9, 0x9d, 0x4b, 0xd0, 0xa1, 0x1e, 0x3d, 0x88, 0x5e, 0x7a, 0xaf, 0x78, 0xf1, 0x56, 0xd3, 0x61, 0x29, 0x6c, 0x3a, 0xb5,
0x3d, 0xb5, 0xc4, 0x11, 0xa2, 0x22, 0x28, 0xb5, 0xb0, 0x34, 0xb5, 0xb8, 0x44, 0xc8, 0x88, 0x8b, 0x89, 0x82, 0xff, 0xc6, 0x9f, 0x2a, 0x9b, 0x4c, 0xd7, 0x8a, 0xb2, 0xa7, 0x4e, 0xf3, 0xbe, 0xf7,
0x2b, 0x3d, 0xb5, 0x04, 0xaa, 0x4d, 0x82, 0x51, 0x81, 0x51, 0x83, 0xdb, 0x48, 0x48, 0x0f, 0xaa, 0xf2, 0xc8, 0xc0, 0x69, 0x3f, 0xb0, 0xe3, 0x9b, 0x7a, 0x70, 0xad, 0x5e, 0x91, 0xf2, 0x7f, 0x98,
0x0f, 0x49, 0x39, 0x92, 0x2a, 0x25, 0x07, 0x2e, 0x21, 0x64, 0x83, 0x8a, 0x0b, 0xf2, 0xf3, 0x8a, 0x68, 0x36, 0x7d, 0xdd, 0x7d, 0x66, 0x18, 0x54, 0xcd, 0xc6, 0x70, 0x17, 0xc4, 0xbc, 0x84, 0x93,
0x53, 0x85, 0xb4, 0xb8, 0xd8, 0x51, 0x8d, 0x11, 0xd0, 0x83, 0x39, 0x0e, 0xa6, 0x14, 0xa6, 0x40, 0x92, 0xdc, 0x43, 0x30, 0x54, 0xf4, 0xf6, 0x4e, 0xd6, 0x61, 0x01, 0xb0, 0x24, 0x27, 0x29, 0x69,
0x49, 0x97, 0x8b, 0x1d, 0x2a, 0x26, 0xc4, 0xc7, 0xc5, 0x94, 0x99, 0x02, 0xd6, 0xc1, 0x19, 0xc4, 0x34, 0x8f, 0x16, 0xb3, 0x02, 0x95, 0xf8, 0x26, 0xf8, 0x84, 0xca, 0xbf, 0x22, 0xc0, 0x69, 0x92,
0x94, 0x99, 0x22, 0x24, 0xc4, 0xc5, 0x92, 0x97, 0x98, 0x9b, 0x2a, 0xc1, 0x04, 0x16, 0x01, 0xb3, 0xed, 0xb9, 0xb3, 0x84, 0x57, 0x90, 0xfc, 0xce, 0x39, 0x56, 0x52, 0x47, 0x8d, 0xe8, 0x08, 0xe0,
0x8d, 0x42, 0xb9, 0xb8, 0xa0, 0x3a, 0x8b, 0xcb, 0x92, 0x85, 0xdc, 0xb9, 0xb8, 0x10, 0xd6, 0x0b, 0x3d, 0x1c, 0x58, 0xc7, 0x43, 0xbd, 0x24, 0x9b, 0xc6, 0xf3, 0xdd, 0xc5, 0xac, 0xb8, 0xdc, 0xc0,
0x49, 0xc1, 0x6d, 0xc1, 0xf0, 0x9c, 0x94, 0x34, 0x56, 0x39, 0x88, 0x7b, 0x95, 0x18, 0x9c, 0x24, 0x7f, 0xa3, 0xd5, 0x53, 0x60, 0xab, 0x8d, 0x29, 0xbb, 0x80, 0x44, 0x0e, 0x11, 0x61, 0x4f, 0x73,
0xa2, 0xc4, 0x72, 0xf3, 0x53, 0x52, 0x73, 0x8a, 0x61, 0xa1, 0x68, 0x0d, 0xa5, 0x93, 0xd8, 0xc0, 0x13, 0x2e, 0x3d, 0xac, 0xfc, 0x9c, 0x5f, 0x43, 0x22, 0x19, 0x78, 0x04, 0x71, 0xdb, 0x88, 0x18,
0x21, 0x66, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x16, 0x18, 0x87, 0xc4, 0x65, 0x01, 0x00, 0x00, 0xb7, 0xcd, 0x1a, 0xef, 0x6a, 0x43, 0x69, 0x1c, 0xf0, 0xf5, 0x5c, 0x3c, 0x03, 0x48, 0x33, 0xfb,
0xa1, 0xb1, 0x04, 0xf8, 0xe9, 0x80, 0xd9, 0xbf, 0xc5, 0xfc, 0xeb, 0x65, 0xe7, 0x5b, 0x4a, 0xe7,
0x3b, 0x8f, 0xe9, 0xcb, 0x99, 0xe1, 0x86, 0x56, 0x76, 0x5c, 0xd3, 0x9d, 0x7c, 0x5f, 0xf7, 0xfd,
0x4a, 0x6e, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x93, 0xb5, 0x4a, 0x95, 0xc6, 0x01, 0x00, 0x00,
} }

View File

@@ -1,6 +1,5 @@
// Code generated by protoc-gen-go. // Code generated by protoc-gen-go. DO NOT EDIT.
// source: proto/common.proto // source: proto/common.proto
// DO NOT EDIT!
/* /*
Package common is a generated protocol buffer package. Package common is a generated protocol buffer package.

View File

@@ -1,5 +1,5 @@
// Code generated by protoc-gen-gotemplate // Code generated by protoc-gen-gotemplate
package article package company
import ( import (
"github.com/moul/protoc-gen-gotemplate/examples/import/output/models/article" "github.com/moul/protoc-gen-gotemplate/examples/import/output/models/article"
@@ -7,5 +7,34 @@ import (
) )
type Repository interface { type Repository interface {
GetArticle(getarticle *common.GetArticle ) (*article.Article, error) GetArticle(getarticle *common.GetArticle ) (*company.Article, []*company.Storage, error)
} }
// ------------------------- Public SDK -----------------------------
// GetArticle : proto: missing extension proto: missing extension
func (sdk *Sdk) GetArticle(ctx context.Context,
getarticle *GetArticle.GetArticle, token, requestID string)(article *Article.Article, storages []*GetArticleResponse_Storage.Storage, err error) {
out := &pb.GetArticleResponse{}
_ = out
return out.Article, out.Storages, nil
}

View File

@@ -1,13 +1,25 @@
syntax = "proto3"; syntax = "proto3";
package article; package company;
option go_package = "models/article;article"; option go_package = "models/article;article";
import "proto/common.proto"; import "proto/common.proto";
message GetArticleRequest { common.GetArticle getarticle = 1;} message GetArticleRequest {
message GetArticleResponse { Article article = 1;} common.GetArticle getarticle = 1;
}
message GetArticleResponse {
Article article = 1;
message Storage {
string code = 1;
}
// The generated output should write []*GetArticleResponse_Storage.Storage for this field.
repeated Storage storages = 2;
}
message Article{ message Article{
string id = 1; string id = 1;

View File

@@ -1,4 +1,5 @@
// Code generated by protoc-gen-gotemplate // Code generated by protoc-gen-gotemplate
{{- $file := .File}}
package {{.File.Package}} package {{.File.Package}}
import ( import (
@@ -9,3 +10,32 @@ import (
type Repository interface { type Repository interface {
{{range $m := .Service.Method}}{{with $t := $m.InputType | getMessageType $.File}} {{$m.Name}}({{range $f := $t.Field}}{{$f.Name|lowerCamelCase}} {{$f| goTypeWithPackage }} {{end}}) ({{with $out := $m.OutputType | getMessageType $.File}}{{range $f := $out.Field}}{{$f | goTypeWithPackage}}, {{end}}{{end}} error){{end}}{{end}} {{range $m := .Service.Method}}{{with $t := $m.InputType | getMessageType $.File}} {{$m.Name}}({{range $f := $t.Field}}{{$f.Name|lowerCamelCase}} {{$f| goTypeWithPackage }} {{end}}) ({{with $out := $m.OutputType | getMessageType $.File}}{{range $f := $out.Field}}{{$f | goTypeWithPackage}}, {{end}}{{end}} error){{end}}{{end}}
} }
// ------------------------- Public SDK -----------------------------
{{$pkg := "pb"}}
{{range $m := .Service.Method}}
{{with $t := $m.InputType | getMessageType $.File}}
{{if and (not $m.ServerStreaming) (not $m.ClientStreaming)}}
{{/* ----------------------------- nominal case ---------------------------- */}}
// {{$m.Name}} : {{$m | httpVerb}} {{$m | httpPath}}
func (sdk *Sdk) {{$m.Name}}(ctx context.Context, {{if $t.OneofDecl}} req *{{$pkg}}.{{$m.Name}}Request,{{else}}{{range $f := $t.Field}}
{{$f.Name|lowerCamelCase}} {{$f| goTypeWithPackageV2 $.File}},{{end}}{{end}} token, requestID string)({{with $out := $m.OutputType | getMessageType $.File}}{{range $f := $out.Field}}{{$f.Name|lowerCamelCase}} {{$f | goTypeWithPackageV2 $.File}}, {{end}}{{end}}err error) {
out := &{{$pkg}}.{{$m.Name}}Response{}
_ = out
{{with $out := $m.OutputType | getMessageType $.File}}
return {{range $f := $out.Field}}out.{{$f.Name|camelCase}}, {{end}}nil
{{end}} {{/* with */}}
}
{{end}} {{/* streaming ifs */}}
{{end}}{{end}} {{/* range with */}}

View File

@@ -4,18 +4,18 @@
<loc>/posts</loc> <loc>/posts</loc>
<priority>0.5</priority> <priority>0.5</priority>
<changefreq>monthly</changefreq> <changefreq>monthly</changefreq>
<lastmod>2017-03-31</lastmod> <lastmod>2017-05-19</lastmod>
</url> </url>
<url> <url>
<loc>/authors</loc> <loc>/authors</loc>
<priority>0.5</priority> <priority>0.5</priority>
<changefreq>monthly</changefreq> <changefreq>monthly</changefreq>
<lastmod>2017-03-31</lastmod> <lastmod>2017-05-19</lastmod>
</url> </url>
<url> <url>
<loc>/comments</loc> <loc>/comments</loc>
<priority>0.5</priority> <priority>0.5</priority>
<changefreq>monthly</changefreq> <changefreq>monthly</changefreq>
<lastmod>2017-03-31</lastmod> <lastmod>2017-05-19</lastmod>
</url> </url>
</urlset> </urlset>

13
examples/time/Makefile Normal file
View File

@@ -0,0 +1,13 @@
.PHONY: build
build:
mkdir -p output
protoc -I. --gotemplate_out=template_dir=templates,debug=true:output proto/*.proto
.PHONY: re
re: clean build
.PHONY: clean
clean:
rm -rf output

View File

@@ -0,0 +1,10 @@
// Code generated by protoc-gen-gotemplate
package foo
import (
"github.com/golang/protobuf/ptypes/timestamp"
)
type Repository interface {
GetFoo(timestamp *timestamp.Timestamp ) (*timestamp.Timestamp, error)
}

View File

@@ -0,0 +1,12 @@
syntax = "proto3";
package foo;
import "google/protobuf/timestamp.proto";
message GetFooRequest { google.protobuf.Timestamp timestamp = 1;}
message GetFooResponse { string result = 1;}
service foosvc {
rpc GetFoo (GetFooRequest) returns (GetFooResponse){}
}

View File

@@ -0,0 +1,10 @@
// Code generated by protoc-gen-gotemplate
package {{.File.Package}}
import (
"github.com/golang/protobuf/ptypes/timestamp"
)
type Repository interface {
{{range $m := .Service.Method}}{{with $t := $m.InputType | getMessageType $.File}} {{$m.Name}}({{range $f := $t.Field}}{{$f.Name|lowerCamelCase}} {{$f| goTypeWithPackage }} {{end}}) ({{with $out := $m.OutputType | getMessageType $.File}}{{range $f := $out.Field}}{{$f | goTypeWithPackage}}, {{end}}{{end}} error){{end}}{{end}}
}

89
glide.lock generated
View File

@@ -1,89 +0,0 @@
hash: 1944ae13e983e8da7b26c697fc40d79d34326b8c7f56c8939fb16f1ff8caca5b
updated: 2017-05-18T19:20:01.855895064+02:00
imports:
- name: github.com/aokoli/goutils
version: e57d01ace047c1a43e6a49ecf3ecc50ed2be81d1
- name: github.com/dgrijalva/jwt-go
version: c9eaceb2896dbb515dae7ec352b377a226a52721
- name: github.com/go-kit/kit
version: 9f5c614cd1e70102f80b644edbc760805ebf16d5
subpackages:
- auth/jwt
- endpoint
- log
- transport/grpc
- transport/http
- name: github.com/go-logfmt/logfmt
version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5
- name: github.com/go-stack/stack
version: 7a2f19628aabfe68f0766b59e74d6315f8347d22
- name: github.com/golang/glog
version: 23def4e6c14b4da8ac2ed8007337bc5eb5007998
- name: github.com/golang/protobuf
version: 8ee79997227bf9b34611aee7946ae64735e6fd93
subpackages:
- proto
- protoc-gen-go/descriptor
- protoc-gen-go/generator
- protoc-gen-go/plugin
- name: github.com/gorilla/handlers
version: e1b2144f2167de0e1042d1d35e5cba5119d4fb5d
- name: github.com/grpc-ecosystem/grpc-gateway
version: 589b126116b5fc961939b3e156c29e4d9d58222f
subpackages:
- protoc-gen-grpc-gateway/descriptor
- protoc-gen-grpc-gateway/httprule
- utilities
- name: github.com/huandu/xstrings
version: 3959339b333561bf62a38b424fd41517c2c90f40
- name: github.com/imdario/mergo
version: 3e95a51e0639b4cf372f2ccf74c86749d747fbdc
- name: github.com/kr/fs
version: 2788f0dbd16903de03cb8186e5c7d97b69ad387b
- name: github.com/kr/logfmt
version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0
- name: github.com/Masterminds/semver
version: 59c29afe1a994eacb71c833025ca7acf874bb1da
- name: github.com/Masterminds/sprig
version: 2f4371ac162f912989f01cc2b6af4ba6660e6a30
- name: github.com/satori/go.uuid
version: 879c5887cd475cd7864858769793b2ceb0d44feb
- name: golang.org/x/crypto
version: 0fe963104e9d1877082f8fb38f816fcd97eb1d10
subpackages:
- pbkdf2
- scrypt
- name: golang.org/x/net
version: da2b4fa28524a3baf148c1b94df4440267063c88
subpackages:
- context
- context/ctxhttp
- http2
- http2/hpack
- idna
- internal/timeseries
- lex/httplex
- trace
- name: golang.org/x/text
version: a49bea13b776691cb1b49873e5d8df96ec74831a
subpackages:
- secure/bidirule
- transform
- unicode/bidi
- unicode/norm
- name: google.golang.org/genproto
version: bb3573be0c484136831138976d444b8754777aff
subpackages:
- googleapis/api/annotations
- name: google.golang.org/grpc
version: 777daa17ff9b5daef1cfdf915088a2ada3332bf0
subpackages:
- codes
- credentials
- grpclog
- internal
- metadata
- naming
- peer
- transport
testImports: []

View File

@@ -1,16 +0,0 @@
package: github.com/moul/protoc-gen-gotemplate
import:
- package: github.com/golang/protobuf
subpackages:
- proto
- protoc-gen-go/descriptor
- protoc-gen-go/generator
- protoc-gen-go/plugin
- package: github.com/kr/fs
- package: github.com/Masterminds/sprig
- package: github.com/huandu/xstrings
- package: google.golang.org/genproto
subpackages:
- googleapis/api/annotations
- package: github.com/grpc-ecosystem/grpc-gateway
version: 1.2.2

View File

@@ -1,343 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"regexp"
"strings"
"text/template"
"github.com/Masterminds/sprig"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/protoc-gen-go/descriptor"
ggdescriptor "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor"
"github.com/huandu/xstrings"
options "google.golang.org/genproto/googleapis/api/annotations"
)
var jsReservedRe *regexp.Regexp = regexp.MustCompile(`(^|[^A-Za-z])(do|if|in|for|let|new|try|var|case|else|enum|eval|false|null|this|true|void|with|break|catch|class|const|super|throw|while|yield|delete|export|import|public|return|static|switch|typeof|default|extends|finally|package|private|continue|debugger|function|arguments|interface|protected|implements|instanceof)($|[^A-Za-z])`)
var ProtoHelpersFuncMap = template.FuncMap{
"string": func(i interface {
String() string
}) string {
return i.String()
},
"json": func(v interface{}) string {
a, _ := json.Marshal(v)
return string(a)
},
"prettyjson": func(v interface{}) string {
a, _ := json.MarshalIndent(v, "", " ")
return string(a)
},
"splitArray": func(sep string, s string) []string {
return strings.Split(s, sep)
},
"first": func(a []string) string {
return a[0]
},
"last": func(a []string) string {
return a[len(a)-1]
},
"upperFirst": func(s string) string {
return strings.ToUpper(s[:1]) + s[1:]
},
"lowerFirst": func(s string) string {
return strings.ToLower(s[:1]) + s[1:]
},
"camelCase": func(s string) string {
if len(s) > 1 {
return xstrings.ToCamelCase(s)
}
return strings.ToUpper(s[:1])
},
"lowerCamelCase": func(s string) string {
if len(s) > 1 {
s = xstrings.ToCamelCase(s)
}
return strings.ToLower(s[:1]) + s[1:]
},
"kebabCase": func(s string) string {
return strings.Replace(xstrings.ToSnakeCase(s), "_", "-", -1)
},
"snakeCase": xstrings.ToSnakeCase,
"getProtoFile": getProtoFile,
"getMessageType": getMessageType,
"getEnumValue": getEnumValue,
"isFieldMessage": isFieldMessage,
"isFieldRepeated": isFieldRepeated,
"goType": goType,
"goTypeWithPackage": goTypeWithPackage,
"jsType": jsType,
"jsSuffixReserved": jsSuffixReservedKeyword,
"namespacedFlowType": namespacedFlowType,
"httpVerb": httpVerb,
"httpPath": httpPath,
"shortType": shortType,
"urlHasVarsFromMessage": urlHasVarsFromMessage,
}
func init() {
for k, v := range sprig.TxtFuncMap() {
ProtoHelpersFuncMap[k] = v
}
}
func getProtoFile(name string) *ggdescriptor.File {
if registry == nil {
return nil
}
file, err := registry.LookupFile(name)
if err != nil {
panic(err)
}
return file
}
func getMessageType(f *descriptor.FileDescriptorProto, name string) *ggdescriptor.Message {
if registry != nil {
msg, err := registry.LookupMsg(".", name)
if err != nil {
panic(err)
}
return msg
}
// name is in the form .packageName.MessageTypeName.InnerMessageTypeName...
// e.g. .article.ProductTag
splits := strings.Split(name, ".")
target := splits[len(splits)-1]
for _, m := range f.MessageType {
if target == *m.Name {
return &ggdescriptor.Message{
DescriptorProto: m,
}
}
}
return nil
}
func getEnumValue(f []*descriptor.EnumDescriptorProto, name string) []*descriptor.EnumValueDescriptorProto {
for _, item := range f {
if strings.EqualFold(*item.Name, name) {
return item.GetValue()
}
}
return nil
}
func isFieldMessage(f *descriptor.FieldDescriptorProto) bool {
if f.Type != nil && *f.Type == descriptor.FieldDescriptorProto_TYPE_MESSAGE {
return true
}
return false
}
func isFieldRepeated(f *descriptor.FieldDescriptorProto) bool {
if f.Type != nil && f.Label != nil && *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return true
}
return false
}
func goTypeWithPackage(f *descriptor.FieldDescriptorProto) string {
pkg := ""
if *f.Type == descriptor.FieldDescriptorProto_TYPE_MESSAGE {
pkg = getPackageTypeName(*f.TypeName)
}
return goType(pkg, f)
}
func goType(pkg string, f *descriptor.FieldDescriptorProto) string {
switch *f.Type {
case descriptor.FieldDescriptorProto_TYPE_DOUBLE:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]float64"
}
return "float64"
case descriptor.FieldDescriptorProto_TYPE_FLOAT:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]float32"
}
return "float32"
case descriptor.FieldDescriptorProto_TYPE_INT64:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]int64"
}
return "int64"
case descriptor.FieldDescriptorProto_TYPE_UINT64:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]uint64"
}
return "uint64"
case descriptor.FieldDescriptorProto_TYPE_INT32:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]uint32"
}
return "uint32"
case descriptor.FieldDescriptorProto_TYPE_BOOL:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]bool"
}
return "bool"
case descriptor.FieldDescriptorProto_TYPE_STRING:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]string"
}
return "string"
case descriptor.FieldDescriptorProto_TYPE_MESSAGE:
if pkg != "" {
pkg = pkg + "."
}
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return fmt.Sprintf("[]*%s%s", pkg, shortType(*f.TypeName))
}
return fmt.Sprintf("*%s%s", pkg, shortType(*f.TypeName))
case descriptor.FieldDescriptorProto_TYPE_BYTES:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]byte"
}
return "byte"
case descriptor.FieldDescriptorProto_TYPE_UINT32:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]uint32"
}
return "uint32"
case descriptor.FieldDescriptorProto_TYPE_ENUM:
return fmt.Sprintf("*%s.%s", pkg, shortType(*f.TypeName))
default:
return "interface{}"
}
}
func jsType(f *descriptor.FieldDescriptorProto) string {
template := "%s"
if isFieldRepeated(f) == true {
template = "Array<%s>"
}
switch *f.Type {
case descriptor.FieldDescriptorProto_TYPE_MESSAGE,
descriptor.FieldDescriptorProto_TYPE_ENUM:
return fmt.Sprintf(template, namespacedFlowType(*f.TypeName))
case descriptor.FieldDescriptorProto_TYPE_DOUBLE,
descriptor.FieldDescriptorProto_TYPE_FLOAT,
descriptor.FieldDescriptorProto_TYPE_INT64,
descriptor.FieldDescriptorProto_TYPE_UINT64,
descriptor.FieldDescriptorProto_TYPE_INT32,
descriptor.FieldDescriptorProto_TYPE_FIXED64,
descriptor.FieldDescriptorProto_TYPE_FIXED32,
descriptor.FieldDescriptorProto_TYPE_UINT32,
descriptor.FieldDescriptorProto_TYPE_SFIXED32,
descriptor.FieldDescriptorProto_TYPE_SFIXED64,
descriptor.FieldDescriptorProto_TYPE_SINT32,
descriptor.FieldDescriptorProto_TYPE_SINT64:
return fmt.Sprintf(template, "number")
case descriptor.FieldDescriptorProto_TYPE_BOOL:
return fmt.Sprintf(template, "boolean")
case descriptor.FieldDescriptorProto_TYPE_BYTES:
return fmt.Sprintf(template, "Uint8Array")
case descriptor.FieldDescriptorProto_TYPE_STRING:
return fmt.Sprintf(template, "string")
default:
return fmt.Sprintf(template, "any")
}
}
func jsSuffixReservedKeyword(s string) string {
return jsReservedRe.ReplaceAllString(s, "${1}${2}_${3}")
}
func getPackageTypeName(s string) string {
if strings.Contains(s, ".") {
return strings.Split(s, ".")[1]
}
return ""
}
func shortType(s string) string {
t := strings.Split(s, ".")
return t[len(t)-1]
}
func namespacedFlowType(s string) string {
trimmed := strings.TrimLeft(s, ".")
splitted := strings.Split(trimmed, ".")
return strings.Join(splitted, "$")
}
func httpPath(m *descriptor.MethodDescriptorProto) string {
ext, err := proto.GetExtension(m.Options, options.E_Http)
if err != nil {
return err.Error()
}
opts, ok := ext.(*options.HttpRule)
if !ok {
return fmt.Sprintf("extension is %T; want an HttpRule", ext)
}
switch t := opts.Pattern.(type) {
default:
return ""
case *options.HttpRule_Get:
return t.Get
case *options.HttpRule_Post:
return t.Post
case *options.HttpRule_Put:
return t.Put
case *options.HttpRule_Delete:
return t.Delete
case *options.HttpRule_Patch:
return t.Patch
case *options.HttpRule_Custom:
return t.Custom.Path
}
}
func httpVerb(m *descriptor.MethodDescriptorProto) string {
ext, err := proto.GetExtension(m.Options, options.E_Http)
if err != nil {
return err.Error()
}
opts, ok := ext.(*options.HttpRule)
if !ok {
return fmt.Sprintf("extension is %T; want an HttpRule", ext)
}
switch t := opts.Pattern.(type) {
default:
return ""
case *options.HttpRule_Get:
return "GET"
case *options.HttpRule_Post:
return "POST"
case *options.HttpRule_Put:
return "PUT"
case *options.HttpRule_Delete:
return "DELETE"
case *options.HttpRule_Patch:
return "PATCH"
case *options.HttpRule_Custom:
return t.Custom.Kind
}
}
func urlHasVarsFromMessage(path string, d *descriptor.DescriptorProto) bool {
for _, field := range d.Field {
if !isFieldMessage(field) {
if strings.Contains(path, fmt.Sprintf("{%s}", *field.Name)) {
return true
}
}
}
return false
}

993
helpers/helpers.go Normal file
View File

@@ -0,0 +1,993 @@
package pgghelpers
import (
"encoding/json"
"fmt"
"reflect"
"regexp"
"strings"
"text/template"
"github.com/Masterminds/sprig"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/protoc-gen-go/descriptor"
ggdescriptor "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor"
"github.com/huandu/xstrings"
options "google.golang.org/genproto/googleapis/api/annotations"
)
var jsReservedRe = regexp.MustCompile(`(^|[^A-Za-z])(do|if|in|for|let|new|try|var|case|else|enum|eval|false|null|this|true|void|with|break|catch|class|const|super|throw|while|yield|delete|export|import|public|return|static|switch|typeof|default|extends|finally|package|private|continue|debugger|function|arguments|interface|protected|implements|instanceof)($|[^A-Za-z])`)
var (
registry *ggdescriptor.Registry // some helpers need access to registry
)
// Utility to store some vars across multiple scope
var store = make(map[string]interface{})
func SetRegistry(reg *ggdescriptor.Registry) {
registry = reg
}
var ProtoHelpersFuncMap = template.FuncMap{
"string": func(i interface {
String() string
}) string {
return i.String()
},
"json": func(v interface{}) string {
a, err := json.Marshal(v)
if err != nil {
return err.Error()
}
return string(a)
},
"prettyjson": func(v interface{}) string {
a, err := json.MarshalIndent(v, "", " ")
if err != nil {
return err.Error()
}
return string(a)
},
"splitArray": func(sep string, s string) []interface{} {
var r []interface{}
t := strings.Split(s, sep)
for i := range t {
if t[i] != "" {
r = append(r, t[i])
}
}
return r
},
"first": func(a []string) string {
return a[0]
},
"last": func(a []string) string {
return a[len(a)-1]
},
"concat": func(a string, b ...string) string {
return strings.Join(append([]string{a}, b...), "")
},
"join": func(sep string, a ...string) string {
return strings.Join(a, sep)
},
"upperFirst": func(s string) string {
return strings.ToUpper(s[:1]) + s[1:]
},
"lowerFirst": func(s string) string {
return strings.ToLower(s[:1]) + s[1:]
},
"camelCase": func(s string) string {
if len(s) > 1 {
return xstrings.ToCamelCase(s)
}
return strings.ToUpper(s[:1])
},
"lowerCamelCase": func(s string) string {
if len(s) > 1 {
s = xstrings.ToCamelCase(s)
}
return strings.ToLower(s[:1]) + s[1:]
},
"kebabCase": func(s string) string {
return strings.Replace(xstrings.ToSnakeCase(s), "_", "-", -1)
},
"contains": func(sub, s string) bool {
return strings.Contains(s, sub)
},
"trimstr": func(cutset, s string) string {
return strings.Trim(s, cutset)
},
"index": func(array interface{}, i int32) interface{} {
slice := reflect.ValueOf(array)
if slice.Kind() != reflect.Slice {
panic("Error in index(): given a non-slice type")
}
if i < 0 || int(i) >= slice.Len() {
panic("Error in index(): index out of bounds")
}
return slice.Index(int(i)).Interface()
},
"add": func(a int, b int) int {
return a + b
},
"subtract": func(a int, b int) int {
return a - b
},
"multiply": func(a int, b int) int {
return a * b
},
"divide": func(a int, b int) int {
if b == 0 {
panic("psssst ... little help here ... you cannot divide by 0")
}
return a / b
},
"snakeCase": xstrings.ToSnakeCase,
"getProtoFile": getProtoFile,
"getMessageType": getMessageType,
"getEnumValue": getEnumValue,
"isFieldMessage": isFieldMessage,
"isFieldMessageTimeStamp": isFieldMessageTimeStamp,
"isFieldRepeated": isFieldRepeated,
"haskellType": haskellType,
"goType": goType,
"goZeroValue": goZeroValue,
"goTypeWithPackage": goTypeWithPackage,
"goTypeWithPackageV2": goTypeWithPackageV2,
"jsType": jsType,
"jsSuffixReserved": jsSuffixReservedKeyword,
"namespacedFlowType": namespacedFlowType,
"httpVerb": httpVerb,
"httpPath": httpPath,
"httpPathsAdditionalBindings": httpPathsAdditionalBindings,
"httpBody": httpBody,
"shortType": shortType,
"urlHasVarsFromMessage": urlHasVarsFromMessage,
"lowerGoNormalize": lowerGoNormalize,
"goNormalize": goNormalize,
"leadingComment": leadingComment,
"trailingComment": trailingComment,
"leadingDetachedComments": leadingDetachedComments,
"stringFieldExtension": stringFieldExtension,
"stringMethodOptionsExtension": stringMethodOptionsExtension,
"boolFieldExtension": boolFieldExtension,
"isFieldMap": isFieldMap,
"fieldMapKeyType": fieldMapKeyType,
"fieldMapValueType": fieldMapValueType,
"replaceDict": replaceDict,
"setStore": setStore,
"getStore": getStore,
}
var pathMap map[interface{}]*descriptor.SourceCodeInfo_Location
func setStore(key string, i interface{}) string {
store[key] = i
return ""
}
func getStore(s string) interface{} {
if v, ok := store[s]; ok {
return v
}
panic(fmt.Sprintf("No key named '%s' found", s))
}
func InitPathMap(file *descriptor.FileDescriptorProto) {
pathMap = make(map[interface{}]*descriptor.SourceCodeInfo_Location)
addToPathMap(file.GetSourceCodeInfo(), file, []int32{})
}
func InitPathMaps(files []*descriptor.FileDescriptorProto) {
pathMap = make(map[interface{}]*descriptor.SourceCodeInfo_Location)
for _, file := range files {
addToPathMap(file.GetSourceCodeInfo(), file, []int32{})
}
}
// addToPathMap traverses through the AST adding SourceCodeInfo_Location entries to the pathMap.
// Since the AST is a tree, the recursion finishes once it has gone through all the nodes.
func addToPathMap(info *descriptor.SourceCodeInfo, i interface{}, path []int32) {
loc := findLoc(info, path)
if loc != nil {
pathMap[i] = loc
}
switch d := i.(type) {
case *descriptor.FileDescriptorProto:
for index, descriptor := range d.MessageType {
addToPathMap(info, descriptor, newPath(path, 4, index))
}
for index, descriptor := range d.EnumType {
addToPathMap(info, descriptor, newPath(path, 5, index))
}
for index, descriptor := range d.Service {
addToPathMap(info, descriptor, newPath(path, 6, index))
}
case *descriptor.DescriptorProto:
for index, descriptor := range d.Field {
addToPathMap(info, descriptor, newPath(path, 2, index))
}
for index, descriptor := range d.NestedType {
addToPathMap(info, descriptor, newPath(path, 3, index))
}
for index, descriptor := range d.EnumType {
addToPathMap(info, descriptor, newPath(path, 4, index))
}
case *descriptor.EnumDescriptorProto:
for index, descriptor := range d.Value {
addToPathMap(info, descriptor, newPath(path, 2, index))
}
case *descriptor.ServiceDescriptorProto:
for index, descriptor := range d.Method {
addToPathMap(info, descriptor, newPath(path, 2, index))
}
}
}
func newPath(base []int32, field int32, index int) []int32 {
p := append([]int32{}, base...)
p = append(p, field, int32(index))
return p
}
func findLoc(info *descriptor.SourceCodeInfo, path []int32) *descriptor.SourceCodeInfo_Location {
for _, loc := range info.GetLocation() {
if samePath(loc.Path, path) {
return loc
}
}
return nil
}
func samePath(a, b []int32) bool {
if len(a) != len(b) {
return false
}
for i, p := range a {
if p != b[i] {
return false
}
}
return true
}
func findSourceInfoLocation(i interface{}) *descriptor.SourceCodeInfo_Location {
if pathMap == nil {
return nil
}
return pathMap[i]
}
func leadingComment(i interface{}) string {
loc := pathMap[i]
return loc.GetLeadingComments()
}
func trailingComment(i interface{}) string {
loc := pathMap[i]
return loc.GetTrailingComments()
}
func leadingDetachedComments(i interface{}) []string {
loc := pathMap[i]
return loc.GetLeadingDetachedComments()
}
// stringMethodOptionsExtension extracts method options of a string type.
// To define your own extensions see:
// https://developers.google.com/protocol-buffers/docs/proto#customoptions
// Typically the fieldID of private extensions should be in the range:
// 50000-99999
func stringMethodOptionsExtension(fieldID int32, f *descriptor.MethodDescriptorProto) string {
if f == nil {
return ""
}
if f.Options == nil {
return ""
}
var extendedType *descriptor.MethodOptions
var extensionType *string
eds := proto.RegisteredExtensions(f.Options)
if eds[fieldID] == nil {
ed := &proto.ExtensionDesc{
ExtendedType: extendedType,
ExtensionType: extensionType,
Field: fieldID,
Tag: fmt.Sprintf("bytes,%d", fieldID),
}
proto.RegisterExtension(ed)
eds = proto.RegisteredExtensions(f.Options)
}
ext, err := proto.GetExtension(f.Options, eds[fieldID])
if err != nil {
return ""
}
str, ok := ext.(*string)
if !ok {
return ""
}
return *str
}
func stringFieldExtension(fieldID int32, f *descriptor.FieldDescriptorProto) string {
if f == nil {
return ""
}
if f.Options == nil {
return ""
}
var extendedType *descriptor.FieldOptions
var extensionType *string
eds := proto.RegisteredExtensions(f.Options)
if eds[fieldID] == nil {
ed := &proto.ExtensionDesc{
ExtendedType: extendedType,
ExtensionType: extensionType,
Field: fieldID,
Tag: fmt.Sprintf("bytes,%d", fieldID),
}
proto.RegisterExtension(ed)
eds = proto.RegisteredExtensions(f.Options)
}
ext, err := proto.GetExtension(f.Options, eds[fieldID])
if err != nil {
return ""
}
str, ok := ext.(*string)
if !ok {
return ""
}
return *str
}
func boolFieldExtension(fieldID int32, f *descriptor.FieldDescriptorProto) bool {
if f == nil {
return false
}
if f.Options == nil {
return false
}
var extendedType *descriptor.FieldOptions
var extensionType *bool
eds := proto.RegisteredExtensions(f.Options)
if eds[fieldID] == nil {
ed := &proto.ExtensionDesc{
ExtendedType: extendedType,
ExtensionType: extensionType,
Field: fieldID,
Tag: fmt.Sprintf("varint,%d", fieldID),
}
proto.RegisterExtension(ed)
eds = proto.RegisteredExtensions(f.Options)
}
ext, err := proto.GetExtension(f.Options, eds[fieldID])
if err != nil {
return false
}
str, ok := ext.(*bool)
if !ok {
return false
}
return *str
}
func init() {
for k, v := range sprig.TxtFuncMap() {
ProtoHelpersFuncMap[k] = v
}
}
func getProtoFile(name string) *ggdescriptor.File {
if registry == nil {
return nil
}
file, err := registry.LookupFile(name)
if err != nil {
panic(err)
}
return file
}
func getMessageType(f *descriptor.FileDescriptorProto, name string) *ggdescriptor.Message {
if registry != nil {
msg, err := registry.LookupMsg(".", name)
if err != nil {
panic(err)
}
return msg
}
// name is in the form .packageName.MessageTypeName.InnerMessageTypeName...
// e.g. .article.ProductTag
splits := strings.Split(name, ".")
target := splits[len(splits)-1]
for _, m := range f.MessageType {
if target == *m.Name {
return &ggdescriptor.Message{
DescriptorProto: m,
}
}
}
return nil
}
func getEnumValue(f []*descriptor.EnumDescriptorProto, name string) []*descriptor.EnumValueDescriptorProto {
for _, item := range f {
if strings.EqualFold(*item.Name, name) {
return item.GetValue()
}
}
return nil
}
func isFieldMessageTimeStamp(f *descriptor.FieldDescriptorProto) bool {
if f.Type != nil && *f.Type == descriptor.FieldDescriptorProto_TYPE_MESSAGE {
if strings.Compare(*f.TypeName, ".google.protobuf.Timestamp") == 0 {
return true
}
}
return false
}
func isFieldMessage(f *descriptor.FieldDescriptorProto) bool {
if f.Type != nil && *f.Type == descriptor.FieldDescriptorProto_TYPE_MESSAGE {
return true
}
return false
}
func isFieldRepeated(f *descriptor.FieldDescriptorProto) bool {
if f == nil {
return false
}
if f.Type != nil && f.Label != nil && *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return true
}
return false
}
func isFieldMap(f *descriptor.FieldDescriptorProto, m *descriptor.DescriptorProto) bool {
if f.TypeName == nil {
return false
}
shortName := shortType(*f.TypeName)
var nt *descriptor.DescriptorProto
for _, t := range m.NestedType {
if *t.Name == shortName {
nt = t
break
}
}
if nt == nil {
return false
}
for _, f := range nt.Field {
switch *f.Name {
case "key":
if *f.Number != 1 {
return false
}
case "value":
if *f.Number != 2 {
return false
}
default:
return false
}
}
return true
}
func fieldMapKeyType(f *descriptor.FieldDescriptorProto, m *descriptor.DescriptorProto) *descriptor.FieldDescriptorProto {
if f.TypeName == nil {
return nil
}
shortName := shortType(*f.TypeName)
var nt *descriptor.DescriptorProto
for _, t := range m.NestedType {
if *t.Name == shortName {
nt = t
break
}
}
if nt == nil {
return nil
}
for _, f := range nt.Field {
if *f.Name == "key" {
return f
}
}
return nil
}
func fieldMapValueType(f *descriptor.FieldDescriptorProto, m *descriptor.DescriptorProto) *descriptor.FieldDescriptorProto {
if f.TypeName == nil {
return nil
}
shortName := shortType(*f.TypeName)
var nt *descriptor.DescriptorProto
for _, t := range m.NestedType {
if *t.Name == shortName {
nt = t
break
}
}
if nt == nil {
return nil
}
for _, f := range nt.Field {
if *f.Name == "value" {
return f
}
}
return nil
}
// goTypeWithPackageV2 type the field given with its package name.
// This method is an evolution of goTypeWithPackage. It allows you to type a field with a message embedded.
//
// exemple:
// ```proto
// message GetArticleResponse {
// Article article = 1;
// message Storage {
// string code = 1;
// }
// repeated Storage storages = 2;
// }
// ```
// Then the type of `storages` is `GetArticleResponse_Storage` for the go language.
func goTypeWithPackageV2(p *descriptor.FileDescriptorProto, f *descriptor.FieldDescriptorProto) string {
pkg := ""
if *f.Type == descriptor.FieldDescriptorProto_TYPE_MESSAGE || *f.Type == descriptor.FieldDescriptorProto_TYPE_ENUM {
if isTimestampPackage(*f.TypeName) {
return "timestamp"
}
fieldPackage := strings.Split(*f.TypeName, ".")
filePackage := strings.Split(*p.Package, ".")
// check if we are working with a message embedded.
if len(fieldPackage) > 1 && len(fieldPackage)+1 > len(filePackage)+1 {
pkg = strings.Join(fieldPackage[len(filePackage)+1:len(fieldPackage)], "_")
} else {
pkg = getPackageTypeName(*f.TypeName)
}
}
res := goType(pkg, f)
return res
}
// Deprecated. Instead use goTypeWithPackageV2
func goTypeWithPackage(f *descriptor.FieldDescriptorProto) string {
pkg := ""
if *f.Type == descriptor.FieldDescriptorProto_TYPE_MESSAGE || *f.Type == descriptor.FieldDescriptorProto_TYPE_ENUM {
if isTimestampPackage(*f.TypeName) {
return "timestamp"
}
pkg = getPackageTypeName(*f.TypeName)
}
return goType(pkg, f)
}
func haskellType(pkg string, f *descriptor.FieldDescriptorProto) string {
switch *f.Type {
case descriptor.FieldDescriptorProto_TYPE_DOUBLE:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[Float]"
}
return "Float"
case descriptor.FieldDescriptorProto_TYPE_FLOAT:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[Float]"
}
return "Float"
case descriptor.FieldDescriptorProto_TYPE_INT64:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[Int64]"
}
return "Int64"
case descriptor.FieldDescriptorProto_TYPE_UINT64:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[Word]"
}
return "Word"
case descriptor.FieldDescriptorProto_TYPE_INT32:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[Int]"
}
return "Int"
case descriptor.FieldDescriptorProto_TYPE_UINT32:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[Word]"
}
return "Word"
case descriptor.FieldDescriptorProto_TYPE_BOOL:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[Bool]"
}
return "Bool"
case descriptor.FieldDescriptorProto_TYPE_STRING:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[Text]"
}
return "Text"
case descriptor.FieldDescriptorProto_TYPE_MESSAGE:
if pkg != "" {
pkg = pkg + "."
}
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return fmt.Sprintf("[%s%s]", pkg, shortType(*f.TypeName))
}
return fmt.Sprintf("%s%s", pkg, shortType(*f.TypeName))
case descriptor.FieldDescriptorProto_TYPE_BYTES:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[Word8]"
}
return "Word8"
case descriptor.FieldDescriptorProto_TYPE_ENUM:
return fmt.Sprintf("%s%s", pkg, shortType(*f.TypeName))
default:
return "Generic"
}
}
func goType(pkg string, f *descriptor.FieldDescriptorProto) string {
if pkg != "" {
pkg = pkg + "."
}
switch *f.Type {
case descriptor.FieldDescriptorProto_TYPE_DOUBLE:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]float64"
}
return "float64"
case descriptor.FieldDescriptorProto_TYPE_FLOAT:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]float32"
}
return "float32"
case descriptor.FieldDescriptorProto_TYPE_INT64:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]int64"
}
return "int64"
case descriptor.FieldDescriptorProto_TYPE_UINT64:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]uint64"
}
return "uint64"
case descriptor.FieldDescriptorProto_TYPE_INT32:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]int32"
}
return "int32"
case descriptor.FieldDescriptorProto_TYPE_UINT32:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]uint32"
}
return "uint32"
case descriptor.FieldDescriptorProto_TYPE_BOOL:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]bool"
}
return "bool"
case descriptor.FieldDescriptorProto_TYPE_STRING:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]string"
}
return "string"
case descriptor.FieldDescriptorProto_TYPE_MESSAGE:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return fmt.Sprintf("[]*%s%s", pkg, shortType(*f.TypeName))
}
return fmt.Sprintf("*%s%s", pkg, shortType(*f.TypeName))
case descriptor.FieldDescriptorProto_TYPE_BYTES:
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return "[]byte"
}
return "byte"
case descriptor.FieldDescriptorProto_TYPE_ENUM:
return fmt.Sprintf("*%s%s", pkg, shortType(*f.TypeName))
default:
return "interface{}"
}
}
func goZeroValue(f *descriptor.FieldDescriptorProto) string {
const nilString = "nil"
if *f.Label == descriptor.FieldDescriptorProto_LABEL_REPEATED {
return nilString
}
switch *f.Type {
case descriptor.FieldDescriptorProto_TYPE_DOUBLE:
return "0.0"
case descriptor.FieldDescriptorProto_TYPE_FLOAT:
return "0.0"
case descriptor.FieldDescriptorProto_TYPE_INT64:
return "0"
case descriptor.FieldDescriptorProto_TYPE_UINT64:
return "0"
case descriptor.FieldDescriptorProto_TYPE_INT32:
return "0"
case descriptor.FieldDescriptorProto_TYPE_UINT32:
return "0"
case descriptor.FieldDescriptorProto_TYPE_BOOL:
return "false"
case descriptor.FieldDescriptorProto_TYPE_STRING:
return "\"\""
case descriptor.FieldDescriptorProto_TYPE_MESSAGE:
return nilString
case descriptor.FieldDescriptorProto_TYPE_BYTES:
return "0"
case descriptor.FieldDescriptorProto_TYPE_ENUM:
return nilString
default:
return nilString
}
}
func jsType(f *descriptor.FieldDescriptorProto) string {
template := "%s"
if isFieldRepeated(f) {
template = "Array<%s>"
}
switch *f.Type {
case descriptor.FieldDescriptorProto_TYPE_MESSAGE,
descriptor.FieldDescriptorProto_TYPE_ENUM:
return fmt.Sprintf(template, namespacedFlowType(*f.TypeName))
case descriptor.FieldDescriptorProto_TYPE_DOUBLE,
descriptor.FieldDescriptorProto_TYPE_FLOAT,
descriptor.FieldDescriptorProto_TYPE_INT64,
descriptor.FieldDescriptorProto_TYPE_UINT64,
descriptor.FieldDescriptorProto_TYPE_INT32,
descriptor.FieldDescriptorProto_TYPE_FIXED64,
descriptor.FieldDescriptorProto_TYPE_FIXED32,
descriptor.FieldDescriptorProto_TYPE_UINT32,
descriptor.FieldDescriptorProto_TYPE_SFIXED32,
descriptor.FieldDescriptorProto_TYPE_SFIXED64,
descriptor.FieldDescriptorProto_TYPE_SINT32,
descriptor.FieldDescriptorProto_TYPE_SINT64:
return fmt.Sprintf(template, "number")
case descriptor.FieldDescriptorProto_TYPE_BOOL:
return fmt.Sprintf(template, "boolean")
case descriptor.FieldDescriptorProto_TYPE_BYTES:
return fmt.Sprintf(template, "Uint8Array")
case descriptor.FieldDescriptorProto_TYPE_STRING:
return fmt.Sprintf(template, "string")
default:
return fmt.Sprintf(template, "any")
}
}
func jsSuffixReservedKeyword(s string) string {
return jsReservedRe.ReplaceAllString(s, "${1}${2}_${3}")
}
func isTimestampPackage(s string) bool {
if strings.Compare(s, ".google.protobuf.Timestamp") == 0 {
return true
}
return false
}
func getPackageTypeName(s string) string {
if strings.Contains(s, ".") {
return strings.Split(s, ".")[1]
}
return ""
}
func shortType(s string) string {
t := strings.Split(s, ".")
return t[len(t)-1]
}
func namespacedFlowType(s string) string {
trimmed := strings.TrimLeft(s, ".")
splitted := strings.Split(trimmed, ".")
return strings.Join(splitted, "$")
}
func httpPath(m *descriptor.MethodDescriptorProto) string {
ext, err := proto.GetExtension(m.Options, options.E_Http)
if err != nil {
return err.Error()
}
opts, ok := ext.(*options.HttpRule)
if !ok {
return fmt.Sprintf("extension is %T; want an HttpRule", ext)
}
switch t := opts.Pattern.(type) {
default:
return ""
case *options.HttpRule_Get:
return t.Get
case *options.HttpRule_Post:
return t.Post
case *options.HttpRule_Put:
return t.Put
case *options.HttpRule_Delete:
return t.Delete
case *options.HttpRule_Patch:
return t.Patch
case *options.HttpRule_Custom:
return t.Custom.Path
}
}
func httpPathsAdditionalBindings(m *descriptor.MethodDescriptorProto) []string {
ext, err := proto.GetExtension(m.Options, options.E_Http)
if err != nil {
panic(err.Error())
}
opts, ok := ext.(*options.HttpRule)
if !ok {
panic(fmt.Sprintf("extension is %T; want an HttpRule", ext))
}
var httpPaths []string
var optsAdditionalBindings = opts.GetAdditionalBindings()
for _, optAdditionalBindings := range optsAdditionalBindings {
switch t := optAdditionalBindings.Pattern.(type) {
case *options.HttpRule_Get:
httpPaths = append(httpPaths, t.Get)
case *options.HttpRule_Post:
httpPaths = append(httpPaths, t.Post)
case *options.HttpRule_Put:
httpPaths = append(httpPaths, t.Put)
case *options.HttpRule_Delete:
httpPaths = append(httpPaths, t.Delete)
case *options.HttpRule_Patch:
httpPaths = append(httpPaths, t.Patch)
case *options.HttpRule_Custom:
httpPaths = append(httpPaths, t.Custom.Path)
default:
// nothing
}
}
return httpPaths
}
func httpVerb(m *descriptor.MethodDescriptorProto) string {
ext, err := proto.GetExtension(m.Options, options.E_Http)
if err != nil {
return err.Error()
}
opts, ok := ext.(*options.HttpRule)
if !ok {
return fmt.Sprintf("extension is %T; want an HttpRule", ext)
}
switch t := opts.Pattern.(type) {
default:
return ""
case *options.HttpRule_Get:
return "GET"
case *options.HttpRule_Post:
return "POST"
case *options.HttpRule_Put:
return "PUT"
case *options.HttpRule_Delete:
return "DELETE"
case *options.HttpRule_Patch:
return "PATCH"
case *options.HttpRule_Custom:
return t.Custom.Kind
}
}
func httpBody(m *descriptor.MethodDescriptorProto) string {
ext, err := proto.GetExtension(m.Options, options.E_Http)
if err != nil {
return err.Error()
}
opts, ok := ext.(*options.HttpRule)
if !ok {
return fmt.Sprintf("extension is %T; want an HttpRule", ext)
}
return opts.Body
}
func urlHasVarsFromMessage(path string, d *ggdescriptor.Message) bool {
for _, field := range d.Field {
if !isFieldMessage(field) {
if strings.Contains(path, fmt.Sprintf("{%s}", *field.Name)) {
return true
}
}
}
return false
}
// lowerGoNormalize takes a string and applies formatting
// rules to conform to Golang convention. It applies a camel
// case filter, lowers the first character and formats fields
// with `id` to `ID`.
func lowerGoNormalize(s string) string {
fmtd := xstrings.ToCamelCase(s)
fmtd = xstrings.FirstRuneToLower(fmtd)
return formatID(s, fmtd)
}
// goNormalize takes a string and applies formatting rules
// to conform to Golang convention. It applies a camel case
// filter and formats fields with `id` to `ID`.
func goNormalize(s string) string {
fmtd := xstrings.ToCamelCase(s)
return formatID(s, fmtd)
}
// formatID takes a base string alonsgide a formatted string.
// It acts as a transformation filter for fields containing
// `id` in order to conform to Golang convention.
func formatID(base string, formatted string) string {
if formatted == "" {
return formatted
}
switch {
case base == "id":
// id -> ID
return "ID"
case strings.HasPrefix(base, "id_"):
// id_some -> IDSome
return "ID" + formatted[2:]
case strings.HasSuffix(base, "_id"):
// some_id -> SomeID
return formatted[:len(formatted)-2] + "ID"
case strings.HasSuffix(base, "_ids"):
// some_ids -> SomeIDs
return formatted[:len(formatted)-3] + "IDs"
}
return formatted
}
func replaceDict(src string, dict map[string]interface{}) string {
for old, v := range dict {
new, ok := v.(string)
if !ok {
continue
}
src = strings.Replace(src, old, new, -1)
}
return src
}

31
main.go
View File

@@ -10,12 +10,19 @@ import (
"github.com/golang/protobuf/protoc-gen-go/generator" "github.com/golang/protobuf/protoc-gen-go/generator"
"github.com/golang/protobuf/protoc-gen-go/plugin" "github.com/golang/protobuf/protoc-gen-go/plugin"
ggdescriptor "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor" ggdescriptor "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor"
pgghelpers "github.com/moul/protoc-gen-gotemplate/helpers"
) )
var ( var (
registry *ggdescriptor.Registry // some helpers need access to registry registry *ggdescriptor.Registry // some helpers need access to registry
) )
const (
boolTrue = "true"
boolFalse = "false"
)
func main() { func main() {
g := generator.New() g := generator.New()
@@ -24,7 +31,7 @@ func main() {
g.Error(err, "reading input") g.Error(err, "reading input")
} }
if err := proto.Unmarshal(data, g.Request); err != nil { if err = proto.Unmarshal(data, g.Request); err != nil {
g.Error(err, "parsing input proto") g.Error(err, "parsing input proto")
} }
@@ -52,37 +59,32 @@ func main() {
switch parts[0] { switch parts[0] {
case "template_dir": case "template_dir":
templateDir = parts[1] templateDir = parts[1]
break
case "destination_dir": case "destination_dir":
destinationDir = parts[1] destinationDir = parts[1]
break
case "single-package-mode": case "single-package-mode":
switch strings.ToLower(parts[1]) { switch strings.ToLower(parts[1]) {
case "true", "t": case boolTrue, "t":
singlePackageMode = true singlePackageMode = true
case "false", "f": case boolFalse, "f":
default: default:
log.Printf("Err: invalid value for single-package-mode: %q", parts[1]) log.Printf("Err: invalid value for single-package-mode: %q", parts[1])
} }
break
case "debug": case "debug":
switch strings.ToLower(parts[1]) { switch strings.ToLower(parts[1]) {
case "true", "t": case boolTrue, "t":
debug = true debug = true
case "false", "f": case boolFalse, "f":
default: default:
log.Printf("Err: invalid value for debug: %q", parts[1]) log.Printf("Err: invalid value for debug: %q", parts[1])
} }
break
case "all": case "all":
switch strings.ToLower(parts[1]) { switch strings.ToLower(parts[1]) {
case "true", "t": case boolTrue, "t":
all = true all = true
case "false", "f": case boolFalse, "f":
default: default:
log.Printf("Err: invalid value for debug: %q", parts[1]) log.Printf("Err: invalid value for debug: %q", parts[1])
} }
break
default: default:
log.Printf("Err: unknown parameter: %q", param) log.Printf("Err: unknown parameter: %q", param)
} }
@@ -101,7 +103,8 @@ func main() {
if singlePackageMode { if singlePackageMode {
registry = ggdescriptor.NewRegistry() registry = ggdescriptor.NewRegistry()
if err := registry.Load(g.Request); err != nil { pgghelpers.SetRegistry(registry)
if err = registry.Load(g.Request); err != nil {
g.Error(err, "registry: failed to load the request") g.Error(err, "registry: failed to load the request")
} }
} }
@@ -110,7 +113,7 @@ func main() {
for _, file := range g.Request.GetProtoFile() { for _, file := range g.Request.GetProtoFile() {
if all { if all {
if singlePackageMode { if singlePackageMode {
if _, err := registry.LookupFile(file.GetName()); err != nil { if _, err = registry.LookupFile(file.GetName()); err != nil {
g.Error(err, "registry: failed to lookup file %q", file.GetName()) g.Error(err, "registry: failed to lookup file %q", file.GetName())
} }
} }

View File

@@ -1,25 +0,0 @@
language: go
go:
- 1.5
- 1.6
- 1.7
- tip
# Setting sudo access to false will let Travis CI use containers rather than
# VMs to run the tests. For more details see:
# - http://docs.travis-ci.com/user/workers/container-based-infrastructure/
# - http://docs.travis-ci.com/user/workers/standard-infrastructure/
sudo: false
script:
- GO15VENDOREXPERIMENT=1 make setup
- GO15VENDOREXPERIMENT=1 make test
notifications:
webhooks:
urls:
- https://webhooks.gitter.im/e/06e3328629952dabe3e0
on_success: change # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: never # options: [always|never|change] default: always

View File

@@ -1,157 +0,0 @@
package semver_test
import (
"testing"
"github.com/Masterminds/semver"
)
/* Constraint creation benchmarks */
func benchNewConstraint(c string, b *testing.B) {
for i := 0; i < b.N; i++ {
semver.NewConstraint(c)
}
}
func BenchmarkNewConstraintUnary(b *testing.B) {
benchNewConstraint("=2.0", b)
}
func BenchmarkNewConstraintTilde(b *testing.B) {
benchNewConstraint("~2.0.0", b)
}
func BenchmarkNewConstraintCaret(b *testing.B) {
benchNewConstraint("^2.0.0", b)
}
func BenchmarkNewConstraintWildcard(b *testing.B) {
benchNewConstraint("1.x", b)
}
func BenchmarkNewConstraintRange(b *testing.B) {
benchNewConstraint(">=2.1.x, <3.1.0", b)
}
func BenchmarkNewConstraintUnion(b *testing.B) {
benchNewConstraint("~2.0.0 || =3.1.0", b)
}
/* Check benchmarks */
func benchCheckVersion(c, v string, b *testing.B) {
version, _ := semver.NewVersion(v)
constraint, _ := semver.NewConstraint(c)
for i := 0; i < b.N; i++ {
constraint.Check(version)
}
}
func BenchmarkCheckVersionUnary(b *testing.B) {
benchCheckVersion("=2.0", "2.0.0", b)
}
func BenchmarkCheckVersionTilde(b *testing.B) {
benchCheckVersion("~2.0.0", "2.0.5", b)
}
func BenchmarkCheckVersionCaret(b *testing.B) {
benchCheckVersion("^2.0.0", "2.1.0", b)
}
func BenchmarkCheckVersionWildcard(b *testing.B) {
benchCheckVersion("1.x", "1.4.0", b)
}
func BenchmarkCheckVersionRange(b *testing.B) {
benchCheckVersion(">=2.1.x, <3.1.0", "2.4.5", b)
}
func BenchmarkCheckVersionUnion(b *testing.B) {
benchCheckVersion("~2.0.0 || =3.1.0", "3.1.0", b)
}
func benchValidateVersion(c, v string, b *testing.B) {
version, _ := semver.NewVersion(v)
constraint, _ := semver.NewConstraint(c)
for i := 0; i < b.N; i++ {
constraint.Validate(version)
}
}
/* Validate benchmarks, including fails */
func BenchmarkValidateVersionUnary(b *testing.B) {
benchValidateVersion("=2.0", "2.0.0", b)
}
func BenchmarkValidateVersionUnaryFail(b *testing.B) {
benchValidateVersion("=2.0", "2.0.1", b)
}
func BenchmarkValidateVersionTilde(b *testing.B) {
benchValidateVersion("~2.0.0", "2.0.5", b)
}
func BenchmarkValidateVersionTildeFail(b *testing.B) {
benchValidateVersion("~2.0.0", "1.0.5", b)
}
func BenchmarkValidateVersionCaret(b *testing.B) {
benchValidateVersion("^2.0.0", "2.1.0", b)
}
func BenchmarkValidateVersionCaretFail(b *testing.B) {
benchValidateVersion("^2.0.0", "4.1.0", b)
}
func BenchmarkValidateVersionWildcard(b *testing.B) {
benchValidateVersion("1.x", "1.4.0", b)
}
func BenchmarkValidateVersionWildcardFail(b *testing.B) {
benchValidateVersion("1.x", "2.4.0", b)
}
func BenchmarkValidateVersionRange(b *testing.B) {
benchValidateVersion(">=2.1.x, <3.1.0", "2.4.5", b)
}
func BenchmarkValidateVersionRangeFail(b *testing.B) {
benchValidateVersion(">=2.1.x, <3.1.0", "1.4.5", b)
}
func BenchmarkValidateVersionUnion(b *testing.B) {
benchValidateVersion("~2.0.0 || =3.1.0", "3.1.0", b)
}
func BenchmarkValidateVersionUnionFail(b *testing.B) {
benchValidateVersion("~2.0.0 || =3.1.0", "3.1.1", b)
}
/* Version creation benchmarks */
func benchNewVersion(v string, b *testing.B) {
for i := 0; i < b.N; i++ {
semver.NewVersion(v)
}
}
func BenchmarkNewVersionSimple(b *testing.B) {
benchNewVersion("1.0.0", b)
}
func BenchmarkNewVersionPre(b *testing.B) {
benchNewVersion("1.0.0-alpha", b)
}
func BenchmarkNewVersionMeta(b *testing.B) {
benchNewVersion("1.0.0+metadata", b)
}
func BenchmarkNewVersionMetaDash(b *testing.B) {
benchNewVersion("1.0.0+metadata-dash", b)
}

View File

@@ -1,46 +0,0 @@
package semver
import (
"reflect"
"sort"
"testing"
)
func TestCollection(t *testing.T) {
raw := []string{
"1.2.3",
"1.0",
"1.3",
"2",
"0.4.2",
}
vs := make([]*Version, len(raw))
for i, r := range raw {
v, err := NewVersion(r)
if err != nil {
t.Errorf("Error parsing version: %s", err)
}
vs[i] = v
}
sort.Sort(Collection(vs))
e := []string{
"0.4.2",
"1.0.0",
"1.2.3",
"1.3.0",
"2.0.0",
}
a := make([]string, len(vs))
for i, v := range vs {
a[i] = v.String()
}
if !reflect.DeepEqual(a, e) {
t.Error("Sorting Collection failed")
}
}

View File

@@ -1,452 +0,0 @@
package semver
import (
"reflect"
"testing"
)
func TestParseConstraint(t *testing.T) {
tests := []struct {
in string
f cfunc
v string
err bool
}{
{">= 1.2", constraintGreaterThanEqual, "1.2.0", false},
{"1.0", constraintTildeOrEqual, "1.0.0", false},
{"foo", nil, "", true},
{"<= 1.2", constraintLessThanEqual, "1.2.0", false},
{"=< 1.2", constraintLessThanEqual, "1.2.0", false},
{"=> 1.2", constraintGreaterThanEqual, "1.2.0", false},
{"v1.2", constraintTildeOrEqual, "1.2.0", false},
{"=1.5", constraintTildeOrEqual, "1.5.0", false},
{"> 1.3", constraintGreaterThan, "1.3.0", false},
{"< 1.4.1", constraintLessThan, "1.4.1", false},
}
for _, tc := range tests {
c, err := parseConstraint(tc.in)
if tc.err && err == nil {
t.Errorf("Expected error for %s didn't occur", tc.in)
} else if !tc.err && err != nil {
t.Errorf("Unexpected error for %s", tc.in)
}
// If an error was expected continue the loop and don't try the other
// tests as they will cause errors.
if tc.err {
continue
}
if tc.v != c.con.String() {
t.Errorf("Incorrect version found on %s", tc.in)
}
f1 := reflect.ValueOf(tc.f)
f2 := reflect.ValueOf(c.function)
if f1 != f2 {
t.Errorf("Wrong constraint found for %s", tc.in)
}
}
}
func TestConstraintCheck(t *testing.T) {
tests := []struct {
constraint string
version string
check bool
}{
{"= 2.0", "1.2.3", false},
{"= 2.0", "2.0.0", true},
{"4.1", "4.1.0", true},
{"!=4.1", "4.1.0", false},
{"!=4.1", "5.1.0", true},
{">1.1", "4.1.0", true},
{">1.1", "1.1.0", false},
{"<1.1", "0.1.0", true},
{"<1.1", "1.1.0", false},
{"<1.1", "1.1.1", false},
{">=1.1", "4.1.0", true},
{">=1.1", "1.1.0", true},
{">=1.1", "0.0.9", false},
{"<=1.1", "0.1.0", true},
{"<=1.1", "1.1.0", true},
{"<=1.1", "1.1.1", false},
{">0", "0.0.1-alpha", true},
{">=0", "0.0.1-alpha", true},
{">0", "0", false},
{">=0", "0", true},
}
for _, tc := range tests {
c, err := parseConstraint(tc.constraint)
if err != nil {
t.Errorf("err: %s", err)
continue
}
v, err := NewVersion(tc.version)
if err != nil {
t.Errorf("err: %s", err)
continue
}
a := c.check(v)
if a != tc.check {
t.Errorf("Constraint %q failing with %q", tc.constraint, tc.version)
}
}
}
func TestNewConstraint(t *testing.T) {
tests := []struct {
input string
ors int
count int
err bool
}{
{">= 1.1", 1, 1, false},
{"2.0", 1, 1, false},
{"v2.3.5-20161202202307-sha.e8fc5e5", 1, 1, false},
{">= bar", 0, 0, true},
{">= 1.2.3, < 2.0", 1, 2, false},
{">= 1.2.3, < 2.0 || => 3.0, < 4", 2, 2, false},
// The 3 - 4 should be broken into 2 by the range rewriting
{"3 - 4 || => 3.0, < 4", 2, 2, false},
}
for _, tc := range tests {
v, err := NewConstraint(tc.input)
if tc.err && err == nil {
t.Errorf("expected but did not get error for: %s", tc.input)
continue
} else if !tc.err && err != nil {
t.Errorf("unexpectederror for input %s: %s", tc.input, err)
continue
}
if tc.err {
continue
}
l := len(v.constraints)
if tc.ors != l {
t.Errorf("Expected %s to have %d ORs but got %d",
tc.input, tc.ors, l)
}
l = len(v.constraints[0])
if tc.count != l {
t.Errorf("Expected %s to have %d constraints but got %d",
tc.input, tc.count, l)
}
}
}
func TestConstraintsCheck(t *testing.T) {
tests := []struct {
constraint string
version string
check bool
}{
{"*", "1.2.3", true},
{"~0.0.0", "1.2.3", true},
{"= 2.0", "1.2.3", false},
{"= 2.0", "2.0.0", true},
{"4.1", "4.1.0", true},
{"4.1.x", "4.1.3", true},
{"1.x", "1.4", true},
{"!=4.1", "4.1.0", false},
{"!=4.1-alpha", "4.1.0-alpha", false},
{"!=4.1-alpha", "4.1.0", true},
{"!=4.1", "5.1.0", true},
{"!=4.x", "5.1.0", true},
{"!=4.x", "4.1.0", false},
{"!=4.1.x", "4.2.0", true},
{"!=4.2.x", "4.2.3", false},
{">1.1", "4.1.0", true},
{">1.1", "1.1.0", false},
{"<1.1", "0.1.0", true},
{"<1.1", "1.1.0", false},
{"<1.1", "1.1.1", false},
{"<1.x", "1.1.1", true},
{"<1.x", "2.1.1", false},
{"<1.1.x", "1.2.1", false},
{"<1.1.x", "1.1.500", true},
{"<1.2.x", "1.1.1", true},
{">=1.1", "4.1.0", true},
{">=1.1", "4.1.0-beta", false},
{">=1.1", "1.1.0", true},
{">=1.1", "0.0.9", false},
{"<=1.1", "0.1.0", true},
{"<=1.1", "0.1.0-alpha", false},
{"<=1.1-a", "0.1.0-alpha", true},
{"<=1.1", "1.1.0", true},
{"<=1.x", "1.1.0", true},
{"<=2.x", "3.1.0", false},
{"<=1.1", "1.1.1", false},
{"<=1.1.x", "1.2.500", false},
{">1.1, <2", "1.1.1", true},
{">1.1, <3", "4.3.2", false},
{">=1.1, <2, !=1.2.3", "1.2.3", false},
{">=1.1, <2, !=1.2.3 || > 3", "3.1.2", true},
{">=1.1, <2, !=1.2.3 || >= 3", "3.0.0", true},
{">=1.1, <2, !=1.2.3 || > 3", "3.0.0", false},
{">=1.1, <2, !=1.2.3 || > 3", "1.2.3", false},
{"1.1 - 2", "1.1.1", true},
{"1.1-3", "4.3.2", false},
{"^1.1", "1.1.1", true},
{"^1.1", "4.3.2", false},
{"^1.x", "1.1.1", true},
{"^2.x", "1.1.1", false},
{"^1.x", "2.1.1", false},
{"^1.x", "1.1.1-beta1", false},
{"^1.1.2-alpha", "1.2.1-beta1", true},
{"^1.2.x-alpha", "1.1.1-beta1", false},
{"~*", "2.1.1", true},
{"~1.x", "2.1.1", false},
{"~1.x", "1.3.5", true},
{"~1.x", "1.4", true},
{"~1.1", "1.1.1", true},
{"~1.1", "1.1.1-alpha", false},
{"~1.1-alpha", "1.1.1-beta", true},
{"~1.1.1-beta", "1.1.1-alpha", false},
{"~1.1.1-beta", "1.1.1", true},
{"~1.2.3", "1.2.5", true},
{"~1.2.3", "1.2.2", false},
{"~1.2.3", "1.3.2", false},
{"~1.1", "1.2.3", false},
{"~1.3", "2.4.5", false},
}
for _, tc := range tests {
c, err := NewConstraint(tc.constraint)
if err != nil {
t.Errorf("err: %s", err)
continue
}
v, err := NewVersion(tc.version)
if err != nil {
t.Errorf("err: %s", err)
continue
}
a := c.Check(v)
if a != tc.check {
t.Errorf("Constraint '%s' failing with '%s'", tc.constraint, tc.version)
}
}
}
func TestRewriteRange(t *testing.T) {
tests := []struct {
c string
nc string
}{
{"2 - 3", ">= 2, <= 3"},
{"2 - 3, 2 - 3", ">= 2, <= 3,>= 2, <= 3"},
{"2 - 3, 4.0.0 - 5.1", ">= 2, <= 3,>= 4.0.0, <= 5.1"},
}
for _, tc := range tests {
o := rewriteRange(tc.c)
if o != tc.nc {
t.Errorf("Range %s rewritten incorrectly as '%s'", tc.c, o)
}
}
}
func TestIsX(t *testing.T) {
tests := []struct {
t string
c bool
}{
{"A", false},
{"%", false},
{"X", true},
{"x", true},
{"*", true},
}
for _, tc := range tests {
a := isX(tc.t)
if a != tc.c {
t.Errorf("Function isX error on %s", tc.t)
}
}
}
func TestConstraintsValidate(t *testing.T) {
tests := []struct {
constraint string
version string
check bool
}{
{"*", "1.2.3", true},
{"~0.0.0", "1.2.3", true},
{"= 2.0", "1.2.3", false},
{"= 2.0", "2.0.0", true},
{"4.1", "4.1.0", true},
{"4.1.x", "4.1.3", true},
{"1.x", "1.4", true},
{"!=4.1", "4.1.0", false},
{"!=4.1", "5.1.0", true},
{"!=4.x", "5.1.0", true},
{"!=4.x", "4.1.0", false},
{"!=4.1.x", "4.2.0", true},
{"!=4.2.x", "4.2.3", false},
{">1.1", "4.1.0", true},
{">1.1", "1.1.0", false},
{"<1.1", "0.1.0", true},
{"<1.1", "1.1.0", false},
{"<1.1", "1.1.1", false},
{"<1.x", "1.1.1", true},
{"<1.x", "2.1.1", false},
{"<1.1.x", "1.2.1", false},
{"<1.1.x", "1.1.500", true},
{"<1.2.x", "1.1.1", true},
{">=1.1", "4.1.0", true},
{">=1.1", "1.1.0", true},
{">=1.1", "0.0.9", false},
{"<=1.1", "0.1.0", true},
{"<=1.1", "1.1.0", true},
{"<=1.x", "1.1.0", true},
{"<=2.x", "3.1.0", false},
{"<=1.1", "1.1.1", false},
{"<=1.1.x", "1.2.500", false},
{">1.1, <2", "1.1.1", true},
{">1.1, <3", "4.3.2", false},
{">=1.1, <2, !=1.2.3", "1.2.3", false},
{">=1.1, <2, !=1.2.3 || > 3", "3.1.2", true},
{">=1.1, <2, !=1.2.3 || >= 3", "3.0.0", true},
{">=1.1, <2, !=1.2.3 || > 3", "3.0.0", false},
{">=1.1, <2, !=1.2.3 || > 3", "1.2.3", false},
{"1.1 - 2", "1.1.1", true},
{"1.1-3", "4.3.2", false},
{"^1.1", "1.1.1", true},
{"^1.1", "1.1.1-alpha", false},
{"^1.1.1-alpha", "1.1.1-beta", true},
{"^1.1.1-beta", "1.1.1-alpha", false},
{"^1.1", "4.3.2", false},
{"^1.x", "1.1.1", true},
{"^2.x", "1.1.1", false},
{"^1.x", "2.1.1", false},
{"~*", "2.1.1", true},
{"~1.x", "2.1.1", false},
{"~1.x", "1.3.5", true},
{"~1.x", "1.3.5-beta", false},
{"~1.3.6-alpha", "1.3.5-beta", false},
{"~1.3.5-alpha", "1.3.5-beta", true},
{"~1.3.5-beta", "1.3.5-alpha", false},
{"~1.x", "1.4", true},
{"~1.1", "1.1.1", true},
{"~1.2.3", "1.2.5", true},
{"~1.2.3", "1.2.2", false},
{"~1.2.3", "1.3.2", false},
{"~1.1", "1.2.3", false},
{"~1.3", "2.4.5", false},
}
for _, tc := range tests {
c, err := NewConstraint(tc.constraint)
if err != nil {
t.Errorf("err: %s", err)
continue
}
v, err := NewVersion(tc.version)
if err != nil {
t.Errorf("err: %s", err)
continue
}
a, msgs := c.Validate(v)
if a != tc.check {
t.Errorf("Constraint '%s' failing with '%s'", tc.constraint, tc.version)
} else if !a && len(msgs) == 0 {
t.Errorf("%q failed with %q but no errors returned", tc.constraint, tc.version)
}
// if a == false {
// for _, m := range msgs {
// t.Errorf("%s", m)
// }
// }
}
v, err := NewVersion("1.2.3")
if err != nil {
t.Errorf("err: %s", err)
}
c, err := NewConstraint("!= 1.2.5, ^2, <= 1.1.x")
if err != nil {
t.Errorf("err: %s", err)
}
_, msgs := c.Validate(v)
if len(msgs) != 2 {
t.Error("Invalid number of validations found")
}
e := msgs[0].Error()
if e != "1.2.3 does not have same major version as 2" {
t.Error("Did not get expected message: 1.2.3 does not have same major version as 2")
}
e = msgs[1].Error()
if e != "1.2.3 is greater than 1.1.x" {
t.Error("Did not get expected message: 1.2.3 is greater than 1.1.x")
}
tests2 := []struct {
constraint, version, msg string
}{
{"= 2.0", "1.2.3", "1.2.3 is not equal to 2.0"},
{"!=4.1", "4.1.0", "4.1.0 is equal to 4.1"},
{"!=4.x", "4.1.0", "4.1.0 is equal to 4.x"},
{"!=4.2.x", "4.2.3", "4.2.3 is equal to 4.2.x"},
{">1.1", "1.1.0", "1.1.0 is less than or equal to 1.1"},
{"<1.1", "1.1.0", "1.1.0 is greater than or equal to 1.1"},
{"<1.1", "1.1.1", "1.1.1 is greater than or equal to 1.1"},
{"<1.x", "2.1.1", "2.1.1 is greater than or equal to 1.x"},
{"<1.1.x", "1.2.1", "1.2.1 is greater than or equal to 1.1.x"},
{">=1.1", "0.0.9", "0.0.9 is less than 1.1"},
{"<=2.x", "3.1.0", "3.1.0 is greater than 2.x"},
{"<=1.1", "1.1.1", "1.1.1 is greater than 1.1"},
{"<=1.1.x", "1.2.500", "1.2.500 is greater than 1.1.x"},
{">1.1, <3", "4.3.2", "4.3.2 is greater than or equal to 3"},
{">=1.1, <2, !=1.2.3", "1.2.3", "1.2.3 is equal to 1.2.3"},
{">=1.1, <2, !=1.2.3 || > 3", "3.0.0", "3.0.0 is greater than or equal to 2"},
{">=1.1, <2, !=1.2.3 || > 3", "1.2.3", "1.2.3 is equal to 1.2.3"},
{"1.1 - 3", "4.3.2", "4.3.2 is greater than 3"},
{"^1.1", "4.3.2", "4.3.2 does not have same major version as 1.1"},
{"^2.x", "1.1.1", "1.1.1 does not have same major version as 2.x"},
{"^1.x", "2.1.1", "2.1.1 does not have same major version as 1.x"},
{"~1.x", "2.1.1", "2.1.1 does not have same major and minor version as 1.x"},
{"~1.2.3", "1.2.2", "1.2.2 does not have same major and minor version as 1.2.3"},
{"~1.2.3", "1.3.2", "1.3.2 does not have same major and minor version as 1.2.3"},
{"~1.1", "1.2.3", "1.2.3 does not have same major and minor version as 1.1"},
{"~1.3", "2.4.5", "2.4.5 does not have same major and minor version as 1.3"},
}
for _, tc := range tests2 {
c, err := NewConstraint(tc.constraint)
if err != nil {
t.Errorf("err: %s", err)
continue
}
v, err := NewVersion(tc.version)
if err != nil {
t.Errorf("err: %s", err)
continue
}
_, msgs := c.Validate(v)
e := msgs[0].Error()
if e != tc.msg {
t.Errorf("Did not get expected message %q: %s", tc.msg, e)
}
}
}

View File

@@ -1,450 +0,0 @@
package semver
import (
"testing"
)
func TestNewVersion(t *testing.T) {
tests := []struct {
version string
err bool
}{
{"1.2.3", false},
{"v1.2.3", false},
{"1.0", false},
{"v1.0", false},
{"1", false},
{"v1", false},
{"1.2.beta", true},
{"v1.2.beta", true},
{"foo", true},
{"1.2-5", false},
{"v1.2-5", false},
{"1.2-beta.5", false},
{"v1.2-beta.5", false},
{"\n1.2", true},
{"\nv1.2", true},
{"1.2.0-x.Y.0+metadata", false},
{"v1.2.0-x.Y.0+metadata", false},
{"1.2.0-x.Y.0+metadata-width-hypen", false},
{"v1.2.0-x.Y.0+metadata-width-hypen", false},
{"1.2.3-rc1-with-hypen", false},
{"v1.2.3-rc1-with-hypen", false},
{"1.2.3.4", true},
{"v1.2.3.4", true},
}
for _, tc := range tests {
_, err := NewVersion(tc.version)
if tc.err && err == nil {
t.Fatalf("expected error for version: %s", tc.version)
} else if !tc.err && err != nil {
t.Fatalf("error for version %s: %s", tc.version, err)
}
}
}
func TestOriginal(t *testing.T) {
tests := []string{
"1.2.3",
"v1.2.3",
"1.0",
"v1.0",
"1",
"v1",
"1.2-5",
"v1.2-5",
"1.2-beta.5",
"v1.2-beta.5",
"1.2.0-x.Y.0+metadata",
"v1.2.0-x.Y.0+metadata",
"1.2.0-x.Y.0+metadata-width-hypen",
"v1.2.0-x.Y.0+metadata-width-hypen",
"1.2.3-rc1-with-hypen",
"v1.2.3-rc1-with-hypen",
}
for _, tc := range tests {
v, err := NewVersion(tc)
if err != nil {
t.Errorf("Error parsing version %s", tc)
}
o := v.Original()
if o != tc {
t.Errorf("Error retrieving originl. Expected '%s' but got '%s'", tc, v)
}
}
}
func TestParts(t *testing.T) {
v, err := NewVersion("1.2.3-beta.1+build.123")
if err != nil {
t.Error("Error parsing version 1.2.3-beta.1+build.123")
}
if v.Major() != 1 {
t.Error("Major() returning wrong value")
}
if v.Minor() != 2 {
t.Error("Minor() returning wrong value")
}
if v.Patch() != 3 {
t.Error("Patch() returning wrong value")
}
if v.Prerelease() != "beta.1" {
t.Error("Prerelease() returning wrong value")
}
if v.Metadata() != "build.123" {
t.Error("Metadata() returning wrong value")
}
}
func TestString(t *testing.T) {
tests := []struct {
version string
expected string
}{
{"1.2.3", "1.2.3"},
{"v1.2.3", "1.2.3"},
{"1.0", "1.0.0"},
{"v1.0", "1.0.0"},
{"1", "1.0.0"},
{"v1", "1.0.0"},
{"1.2-5", "1.2.0-5"},
{"v1.2-5", "1.2.0-5"},
{"1.2-beta.5", "1.2.0-beta.5"},
{"v1.2-beta.5", "1.2.0-beta.5"},
{"1.2.0-x.Y.0+metadata", "1.2.0-x.Y.0+metadata"},
{"v1.2.0-x.Y.0+metadata", "1.2.0-x.Y.0+metadata"},
{"1.2.0-x.Y.0+metadata-width-hypen", "1.2.0-x.Y.0+metadata-width-hypen"},
{"v1.2.0-x.Y.0+metadata-width-hypen", "1.2.0-x.Y.0+metadata-width-hypen"},
{"1.2.3-rc1-with-hypen", "1.2.3-rc1-with-hypen"},
{"v1.2.3-rc1-with-hypen", "1.2.3-rc1-with-hypen"},
}
for _, tc := range tests {
v, err := NewVersion(tc.version)
if err != nil {
t.Errorf("Error parsing version %s", tc)
}
s := v.String()
if s != tc.expected {
t.Errorf("Error generating string. Expected '%s' but got '%s'", tc.expected, s)
}
}
}
func TestCompare(t *testing.T) {
tests := []struct {
v1 string
v2 string
expected int
}{
{"1.2.3", "1.5.1", -1},
{"2.2.3", "1.5.1", 1},
{"2.2.3", "2.2.2", 1},
{"3.2-beta", "3.2-beta", 0},
{"1.3", "1.1.4", 1},
{"4.2", "4.2-beta", 1},
{"4.2-beta", "4.2", -1},
{"4.2-alpha", "4.2-beta", -1},
{"4.2-alpha", "4.2-alpha", 0},
{"4.2-beta.2", "4.2-beta.1", 1},
{"4.2-beta2", "4.2-beta1", 1},
{"4.2-beta", "4.2-beta.2", -1},
{"4.2-beta", "4.2-beta.foo", 1},
{"4.2-beta.2", "4.2-beta", 1},
{"4.2-beta.foo", "4.2-beta", -1},
{"1.2+bar", "1.2+baz", 0},
}
for _, tc := range tests {
v1, err := NewVersion(tc.v1)
if err != nil {
t.Errorf("Error parsing version: %s", err)
}
v2, err := NewVersion(tc.v2)
if err != nil {
t.Errorf("Error parsing version: %s", err)
}
a := v1.Compare(v2)
e := tc.expected
if a != e {
t.Errorf(
"Comparison of '%s' and '%s' failed. Expected '%d', got '%d'",
tc.v1, tc.v2, e, a,
)
}
}
}
func TestLessThan(t *testing.T) {
tests := []struct {
v1 string
v2 string
expected bool
}{
{"1.2.3", "1.5.1", true},
{"2.2.3", "1.5.1", false},
{"3.2-beta", "3.2-beta", false},
}
for _, tc := range tests {
v1, err := NewVersion(tc.v1)
if err != nil {
t.Errorf("Error parsing version: %s", err)
}
v2, err := NewVersion(tc.v2)
if err != nil {
t.Errorf("Error parsing version: %s", err)
}
a := v1.LessThan(v2)
e := tc.expected
if a != e {
t.Errorf(
"Comparison of '%s' and '%s' failed. Expected '%t', got '%t'",
tc.v1, tc.v2, e, a,
)
}
}
}
func TestGreaterThan(t *testing.T) {
tests := []struct {
v1 string
v2 string
expected bool
}{
{"1.2.3", "1.5.1", false},
{"2.2.3", "1.5.1", true},
{"3.2-beta", "3.2-beta", false},
}
for _, tc := range tests {
v1, err := NewVersion(tc.v1)
if err != nil {
t.Errorf("Error parsing version: %s", err)
}
v2, err := NewVersion(tc.v2)
if err != nil {
t.Errorf("Error parsing version: %s", err)
}
a := v1.GreaterThan(v2)
e := tc.expected
if a != e {
t.Errorf(
"Comparison of '%s' and '%s' failed. Expected '%t', got '%t'",
tc.v1, tc.v2, e, a,
)
}
}
}
func TestEqual(t *testing.T) {
tests := []struct {
v1 string
v2 string
expected bool
}{
{"1.2.3", "1.5.1", false},
{"2.2.3", "1.5.1", false},
{"3.2-beta", "3.2-beta", true},
{"3.2-beta+foo", "3.2-beta+bar", true},
}
for _, tc := range tests {
v1, err := NewVersion(tc.v1)
if err != nil {
t.Errorf("Error parsing version: %s", err)
}
v2, err := NewVersion(tc.v2)
if err != nil {
t.Errorf("Error parsing version: %s", err)
}
a := v1.Equal(v2)
e := tc.expected
if a != e {
t.Errorf(
"Comparison of '%s' and '%s' failed. Expected '%t', got '%t'",
tc.v1, tc.v2, e, a,
)
}
}
}
func TestInc(t *testing.T) {
tests := []struct {
v1 string
expected string
how string
expectedOriginal string
}{
{"1.2.3", "1.2.4", "patch", "1.2.4"},
{"v1.2.4", "1.2.5", "patch", "v1.2.5"},
{"1.2.3", "1.3.0", "minor", "1.3.0"},
{"v1.2.4", "1.3.0", "minor", "v1.3.0"},
{"1.2.3", "2.0.0", "major", "2.0.0"},
{"v1.2.4", "2.0.0", "major", "v2.0.0"},
{"1.2.3+meta", "1.2.4", "patch", "1.2.4"},
{"1.2.3-beta+meta", "1.2.3", "patch", "1.2.3"},
{"v1.2.4-beta+meta", "1.2.4", "patch", "v1.2.4"},
{"1.2.3-beta+meta", "1.3.0", "minor", "1.3.0"},
{"v1.2.4-beta+meta", "1.3.0", "minor", "v1.3.0"},
{"1.2.3-beta+meta", "2.0.0", "major", "2.0.0"},
{"v1.2.4-beta+meta", "2.0.0", "major", "v2.0.0"},
}
for _, tc := range tests {
v1, err := NewVersion(tc.v1)
if err != nil {
t.Errorf("Error parsing version: %s", err)
}
var v2 Version
switch tc.how {
case "patch":
v2 = v1.IncPatch()
case "minor":
v2 = v1.IncMinor()
case "major":
v2 = v1.IncMajor()
}
a := v2.String()
e := tc.expected
if a != e {
t.Errorf(
"Inc %q failed. Expected %q got %q",
tc.how, e, a,
)
}
a = v2.Original()
e = tc.expectedOriginal
if a != e {
t.Errorf(
"Inc %q failed. Expected original %q got %q",
tc.how, e, a,
)
}
}
}
func TestSetPrerelease(t *testing.T) {
tests := []struct {
v1 string
prerelease string
expectedVersion string
expectedPrerelease string
expectedOriginal string
expectedErr error
}{
{"1.2.3", "**", "1.2.3", "", "1.2.3", ErrInvalidPrerelease},
{"1.2.3", "beta", "1.2.3-beta", "beta", "1.2.3-beta", nil},
{"v1.2.4", "beta", "1.2.4-beta", "beta", "v1.2.4-beta", nil},
}
for _, tc := range tests {
v1, err := NewVersion(tc.v1)
if err != nil {
t.Errorf("Error parsing version: %s", err)
}
v2, err := v1.SetPrerelease(tc.prerelease)
if err != tc.expectedErr {
t.Errorf("Expected to get err=%s, but got err=%s", tc.expectedErr, err)
}
a := v2.Prerelease()
e := tc.expectedPrerelease
if a != e {
t.Errorf("Expected prerelease value=%q, but got %q", e, a)
}
a = v2.String()
e = tc.expectedVersion
if a != e {
t.Errorf("Expected version string=%q, but got %q", e, a)
}
a = v2.Original()
e = tc.expectedOriginal
if a != e {
t.Errorf("Expected version original=%q, but got %q", e, a)
}
}
}
func TestSetMetadata(t *testing.T) {
tests := []struct {
v1 string
metadata string
expectedVersion string
expectedMetadata string
expectedOriginal string
expectedErr error
}{
{"1.2.3", "**", "1.2.3", "", "1.2.3", ErrInvalidMetadata},
{"1.2.3", "meta", "1.2.3+meta", "meta", "1.2.3+meta", nil},
{"v1.2.4", "meta", "1.2.4+meta", "meta", "v1.2.4+meta", nil},
}
for _, tc := range tests {
v1, err := NewVersion(tc.v1)
if err != nil {
t.Errorf("Error parsing version: %s", err)
}
v2, err := v1.SetMetadata(tc.metadata)
if err != tc.expectedErr {
t.Errorf("Expected to get err=%s, but got err=%s", tc.expectedErr, err)
}
a := v2.Metadata()
e := tc.expectedMetadata
if a != e {
t.Errorf("Expected metadata value=%q, but got %q", e, a)
}
a = v2.String()
e = tc.expectedVersion
if e != a {
t.Errorf("Expected version string=%q, but got %q", e, a)
}
a = v2.Original()
e = tc.expectedOriginal
if a != e {
t.Errorf("Expected version original=%q, but got %q", e, a)
}
}
}
func TestOriginalVPrefix(t *testing.T) {
tests := []struct {
version string
vprefix string
}{
{"1.2.3", ""},
{"v1.2.4", "v"},
}
for _, tc := range tests {
v1, _ := NewVersion(tc.version)
a := v1.originalVPrefix()
e := tc.vprefix
if a != e {
t.Errorf("Expected vprefix=%q, but got %q", e, a)
}
}
}

View File

@@ -1,2 +0,0 @@
vendor/
/.glide

View File

@@ -1,24 +0,0 @@
language: go
go:
- 1.6
- 1.7
- 1.8
- tip
# Setting sudo access to false will let Travis CI use containers rather than
# VMs to run the tests. For more details see:
# - http://docs.travis-ci.com/user/workers/container-based-infrastructure/
# - http://docs.travis-ci.com/user/workers/standard-infrastructure/
sudo: false
script:
- make setup test
notifications:
webhooks:
urls:
- https://webhooks.gitter.im/e/06e3328629952dabe3e0
on_success: change # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: never # options: [always|never|change] default: always

View File

@@ -1,16 +1,131 @@
# Release 1.2.0 (2016-02-01) # Changelog
## Release 2.14.1 (2017-12-01)
### Fixed
- #60: Fix typo in function name documentation (thanks @neil-ca-moore)
- #61: Removing line with {{ due to blocking github pages genertion
- #64: Update the list functions to handle int, string, and other slices for compatibility
## Release 2.14.0 (2017-10-06)
This new version of Sprig adds a set of functions for generating and working with SSL certificates.
- `genCA` generates an SSL Certificate Authority
- `genSelfSignedCert` generates an SSL self-signed certificate
- `genSignedCert` generates an SSL certificate and key based on a given CA
## Release 2.13.0 (2017-09-18)
This release adds new functions, including:
- `regexMatch`, `regexFindAll`, `regexFind`, `regexReplaceAll`, `regexReplaceAllLiteral`, and `regexSplit` to work with regular expressions
- `floor`, `ceil`, and `round` math functions
- `toDate` converts a string to a date
- `nindent` is just like `indent` but also prepends a new line
- `ago` returns the time from `time.Now`
### Added
- #40: Added basic regex functionality (thanks @alanquillin)
- #41: Added ceil floor and round functions (thanks @alanquillin)
- #48: Added toDate function (thanks @andreynering)
- #50: Added nindent function (thanks @binoculars)
- #46: Added ago function (thanks @slayer)
### Changed
- #51: Updated godocs to include new string functions (thanks @curtisallen)
- #49: Added ability to merge multiple dicts (thanks @binoculars)
## Release 2.12.0 (2017-05-17)
- `snakecase`, `camelcase`, and `shuffle` are three new string functions
- `fail` allows you to bail out of a template render when conditions are not met
## Release 2.11.0 (2017-05-02)
- Added `toJson` and `toPrettyJson`
- Added `merge`
- Refactored documentation
## Release 2.10.0 (2017-03-15)
- Added `semver` and `semverCompare` for Semantic Versions
- `list` replaces `tuple`
- Fixed issue with `join`
- Added `first`, `last`, `intial`, `rest`, `prepend`, `append`, `toString`, `toStrings`, `sortAlpha`, `reverse`, `coalesce`, `pluck`, `pick`, `compact`, `keys`, `omit`, `uniq`, `has`, `without`
## Release 2.9.0 (2017-02-23)
- Added `splitList` to split a list
- Added crypto functions of `genPrivateKey` and `derivePassword`
## Release 2.8.0 (2016-12-21)
- Added access to several path functions (`base`, `dir`, `clean`, `ext`, and `abs`)
- Added functions for _mutating_ dictionaries (`set`, `unset`, `hasKey`)
## Release 2.7.0 (2016-12-01)
- Added `sha256sum` to generate a hash of an input
- Added functions to convert a numeric or string to `int`, `int64`, `float64`
## Release 2.6.0 (2016-10-03)
- Added a `uuidv4` template function for generating UUIDs inside of a template.
## Release 2.5.0 (2016-08-19)
- New `trimSuffix`, `trimPrefix`, `hasSuffix`, and `hasPrefix` functions
- New aliases have been added for a few functions that didn't follow the naming conventions (`trimAll` and `abbrevBoth`)
- `trimall` and `abbrevboth` (notice the case) are deprecated and will be removed in 3.0.0
## Release 2.4.0 (2016-08-16)
- Adds two functions: `until` and `untilStep`
## Release 2.3.0 (2016-06-21)
- cat: Concatenate strings with whitespace separators.
- replace: Replace parts of a string: `replace " " "-" "Me First"` renders "Me-First"
- plural: Format plurals: `len "foo" | plural "one foo" "many foos"` renders "many foos"
- indent: Indent blocks of text in a way that is sensitive to "\n" characters.
## Release 2.2.0 (2016-04-21)
- Added a `genPrivateKey` function (Thanks @bacongobbler)
## Release 2.1.0 (2016-03-30)
- `default` now prints the default value when it does not receive a value down the pipeline. It is much safer now to do `{{.Foo | default "bar"}}`.
- Added accessors for "hermetic" functions. These return only functions that, when given the same input, produce the same output.
## Release 2.0.0 (2016-03-29)
Because we switched from `int` to `int64` as the return value for all integer math functions, the library's major version number has been incremented.
- `min` complements `max` (formerly `biggest`)
- `empty` indicates that a value is the empty value for its type
- `tuple` creates a tuple inside of a template: `{{$t := tuple "a", "b" "c"}}`
- `dict` creates a dictionary inside of a template `{{$d := dict "key1" "val1" "key2" "val2"}}`
- Date formatters have been added for HTML dates (as used in `date` input fields)
- Integer math functions can convert from a number of types, including `string` (via `strconv.ParseInt`).
## Release 1.2.0 (2016-02-01)
- Added quote and squote - Added quote and squote
- Added b32enc and b32dec - Added b32enc and b32dec
- add now takes varargs - add now takes varargs
- biggest now takes varargs - biggest now takes varargs
# Release 1.1.0 (2015-12-29) ## Release 1.1.0 (2015-12-29)
- Added #4: Added contains function. strings.Contains, but with the arguments - Added #4: Added contains function. strings.Contains, but with the arguments
switched to simplify common pipelines. (thanks krancour) switched to simplify common pipelines. (thanks krancour)
- Added Travis-CI testing support - Added Travis-CI testing support
# Release 1.0.0 (2015-12-23) ## Release 1.0.0 (2015-12-23)
- Initial release - Initial release

View File

@@ -10,12 +10,16 @@ import (
"crypto/rsa" "crypto/rsa"
"crypto/sha256" "crypto/sha256"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix"
"encoding/asn1" "encoding/asn1"
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"encoding/pem" "encoding/pem"
"errors"
"fmt" "fmt"
"math/big" "math/big"
"net"
"time"
uuid "github.com/satori/go.uuid" uuid "github.com/satori/go.uuid"
"golang.org/x/crypto/scrypt" "golang.org/x/crypto/scrypt"
@@ -146,3 +150,231 @@ func pemBlockForKey(priv interface{}) *pem.Block {
return nil return nil
} }
} }
type certificate struct {
Cert string
Key string
}
func generateCertificateAuthority(
cn string,
daysValid int,
) (certificate, error) {
ca := certificate{}
template, err := getBaseCertTemplate(cn, nil, nil, daysValid)
if err != nil {
return ca, err
}
// Override KeyUsage and IsCA
template.KeyUsage = x509.KeyUsageKeyEncipherment |
x509.KeyUsageDigitalSignature |
x509.KeyUsageCertSign
template.IsCA = true
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return ca, fmt.Errorf("error generating rsa key: %s", err)
}
ca.Cert, ca.Key, err = getCertAndKey(template, priv, template, priv)
if err != nil {
return ca, err
}
return ca, nil
}
func generateSelfSignedCertificate(
cn string,
ips []interface{},
alternateDNS []interface{},
daysValid int,
) (certificate, error) {
cert := certificate{}
template, err := getBaseCertTemplate(cn, ips, alternateDNS, daysValid)
if err != nil {
return cert, err
}
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return cert, fmt.Errorf("error generating rsa key: %s", err)
}
cert.Cert, cert.Key, err = getCertAndKey(template, priv, template, priv)
if err != nil {
return cert, err
}
return cert, nil
}
func generateSignedCertificate(
cn string,
ips []interface{},
alternateDNS []interface{},
daysValid int,
ca certificate,
) (certificate, error) {
cert := certificate{}
decodedSignerCert, _ := pem.Decode([]byte(ca.Cert))
if decodedSignerCert == nil {
return cert, errors.New("unable to decode certificate")
}
signerCert, err := x509.ParseCertificate(decodedSignerCert.Bytes)
if err != nil {
return cert, fmt.Errorf(
"error parsing certificate: decodedSignerCert.Bytes: %s",
err,
)
}
decodedSignerKey, _ := pem.Decode([]byte(ca.Key))
if decodedSignerKey == nil {
return cert, errors.New("unable to decode key")
}
signerKey, err := x509.ParsePKCS1PrivateKey(decodedSignerKey.Bytes)
if err != nil {
return cert, fmt.Errorf(
"error parsing prive key: decodedSignerKey.Bytes: %s",
err,
)
}
template, err := getBaseCertTemplate(cn, ips, alternateDNS, daysValid)
if err != nil {
return cert, err
}
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return cert, fmt.Errorf("error generating rsa key: %s", err)
}
cert.Cert, cert.Key, err = getCertAndKey(
template,
priv,
signerCert,
signerKey,
)
if err != nil {
return cert, err
}
return cert, nil
}
func getCertAndKey(
template *x509.Certificate,
signeeKey *rsa.PrivateKey,
parent *x509.Certificate,
signingKey *rsa.PrivateKey,
) (string, string, error) {
derBytes, err := x509.CreateCertificate(
rand.Reader,
template,
parent,
&signeeKey.PublicKey,
signingKey,
)
if err != nil {
return "", "", fmt.Errorf("error creating certificate: %s", err)
}
certBuffer := bytes.Buffer{}
if err := pem.Encode(
&certBuffer,
&pem.Block{Type: "CERTIFICATE", Bytes: derBytes},
); err != nil {
return "", "", fmt.Errorf("error pem-encoding certificate: %s", err)
}
keyBuffer := bytes.Buffer{}
if err := pem.Encode(
&keyBuffer,
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(signeeKey),
},
); err != nil {
return "", "", fmt.Errorf("error pem-encoding key: %s", err)
}
return string(certBuffer.Bytes()), string(keyBuffer.Bytes()), nil
}
func getBaseCertTemplate(
cn string,
ips []interface{},
alternateDNS []interface{},
daysValid int,
) (*x509.Certificate, error) {
ipAddresses, err := getNetIPs(ips)
if err != nil {
return nil, err
}
dnsNames, err := getAlternateDNSStrs(alternateDNS)
if err != nil {
return nil, err
}
return &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: cn,
},
IPAddresses: ipAddresses,
DNSNames: dnsNames,
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * time.Duration(daysValid)),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageServerAuth,
x509.ExtKeyUsageClientAuth,
},
BasicConstraintsValid: true,
}, nil
}
func getNetIPs(ips []interface{}) ([]net.IP, error) {
if ips == nil {
return []net.IP{}, nil
}
var ipStr string
var ok bool
var netIP net.IP
netIPs := make([]net.IP, len(ips))
for i, ip := range ips {
ipStr, ok = ip.(string)
if !ok {
return nil, fmt.Errorf("error parsing ip: %v is not a string", ip)
}
netIP = net.ParseIP(ipStr)
if netIP == nil {
return nil, fmt.Errorf("error parsing ip: %s", ipStr)
}
netIPs[i] = netIP
}
return netIPs, nil
}
func getAlternateDNSStrs(alternateDNS []interface{}) ([]string, error) {
if alternateDNS == nil {
return []string{}, nil
}
var dnsStr string
var ok bool
alternateDNSStrs := make([]string, len(alternateDNS))
for i, dns := range alternateDNS {
dnsStr, ok = dns.(string)
if !ok {
return nil, fmt.Errorf(
"error processing alternate dns name: %v is not a string",
dns,
)
}
alternateDNSStrs[i] = dnsStr
}
return alternateDNSStrs, nil
}

View File

@@ -1,110 +0,0 @@
package sprig
import (
"strings"
"testing"
)
func TestSha256Sum(t *testing.T) {
tpl := `{{"abc" | sha256sum}}`
if err := runt(tpl, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"); err != nil {
t.Error(err)
}
}
func TestDerivePassword(t *testing.T) {
expectations := map[string]string{
`{{derivePassword 1 "long" "password" "user" "example.com"}}`: "ZedaFaxcZaso9*",
`{{derivePassword 2 "long" "password" "user" "example.com"}}`: "Fovi2@JifpTupx",
`{{derivePassword 1 "maximum" "password" "user" "example.com"}}`: "pf4zS1LjCg&LjhsZ7T2~",
`{{derivePassword 1 "medium" "password" "user" "example.com"}}`: "ZedJuz8$",
`{{derivePassword 1 "basic" "password" "user" "example.com"}}`: "pIS54PLs",
`{{derivePassword 1 "short" "password" "user" "example.com"}}`: "Zed5",
`{{derivePassword 1 "pin" "password" "user" "example.com"}}`: "6685",
}
for tpl, result := range expectations {
out, err := runRaw(tpl, nil)
if err != nil {
t.Error(err)
}
if 0 != strings.Compare(out, result) {
t.Error("Generated password does not match for", tpl)
}
}
}
// NOTE(bacongobbler): this test is really _slow_ because of how long it takes to compute
// and generate a new crypto key.
func TestGenPrivateKey(t *testing.T) {
// test that calling by default generates an RSA private key
tpl := `{{genPrivateKey ""}}`
out, err := runRaw(tpl, nil)
if err != nil {
t.Error(err)
}
if !strings.Contains(out, "RSA PRIVATE KEY") {
t.Error("Expected RSA PRIVATE KEY")
}
// test all acceptable arguments
tpl = `{{genPrivateKey "rsa"}}`
out, err = runRaw(tpl, nil)
if err != nil {
t.Error(err)
}
if !strings.Contains(out, "RSA PRIVATE KEY") {
t.Error("Expected RSA PRIVATE KEY")
}
tpl = `{{genPrivateKey "dsa"}}`
out, err = runRaw(tpl, nil)
if err != nil {
t.Error(err)
}
if !strings.Contains(out, "DSA PRIVATE KEY") {
t.Error("Expected DSA PRIVATE KEY")
}
tpl = `{{genPrivateKey "ecdsa"}}`
out, err = runRaw(tpl, nil)
if err != nil {
t.Error(err)
}
if !strings.Contains(out, "EC PRIVATE KEY") {
t.Error("Expected EC PRIVATE KEY")
}
// test bad
tpl = `{{genPrivateKey "bad"}}`
out, err = runRaw(tpl, nil)
if err != nil {
t.Error(err)
}
if out != "Unknown type bad" {
t.Error("Expected type 'bad' to be an unknown crypto algorithm")
}
// ensure that we can base64 encode the string
tpl = `{{genPrivateKey "rsa" | b64enc}}`
out, err = runRaw(tpl, nil)
if err != nil {
t.Error(err)
}
}
func TestUUIDGeneration(t *testing.T) {
tpl := `{{uuidv4}}`
out, err := runRaw(tpl, nil)
if err != nil {
t.Error(err)
}
if len(out) != 36 {
t.Error("Expected UUID of length 36")
}
out2, err := runRaw(tpl, nil)
if err != nil {
t.Error(err)
}
if out == out2 {
t.Error("Expected subsequent UUID generations to be different")
}
}

View File

@@ -51,3 +51,26 @@ func dateModify(fmt string, date time.Time) time.Time {
} }
return date.Add(d) return date.Add(d)
} }
func dateAgo(date interface{}) string {
var t time.Time
switch date := date.(type) {
default:
t = time.Now()
case time.Time:
t = date
case int64:
t = time.Unix(date, 0)
case int:
t = time.Unix(int64(date), 0)
}
// Drop resolution to seconds
duration := time.Since(t) / time.Second * time.Second
return duration.String()
}
func toDate(fmt, str string) time.Time {
t, _ := time.ParseInLocation(fmt, str, time.Local)
return t
}

View File

@@ -1,13 +0,0 @@
package sprig
import (
"testing"
)
func TestHtmlDate(t *testing.T) {
t.Skip()
tpl := `{{ htmlDate 0}}`
if err := runt(tpl, "1970-01-01"); err != nil {
t.Error(err)
}
}

View File

@@ -1,107 +0,0 @@
package sprig
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestDefault(t *testing.T) {
tpl := `{{"" | default "foo"}}`
if err := runt(tpl, "foo"); err != nil {
t.Error(err)
}
tpl = `{{default "foo" 234}}`
if err := runt(tpl, "234"); err != nil {
t.Error(err)
}
tpl = `{{default "foo" 2.34}}`
if err := runt(tpl, "2.34"); err != nil {
t.Error(err)
}
tpl = `{{ .Nothing | default "123" }}`
if err := runt(tpl, "123"); err != nil {
t.Error(err)
}
tpl = `{{ default "123" }}`
if err := runt(tpl, "123"); err != nil {
t.Error(err)
}
}
func TestEmpty(t *testing.T) {
tpl := `{{if empty 1}}1{{else}}0{{end}}`
if err := runt(tpl, "0"); err != nil {
t.Error(err)
}
tpl = `{{if empty 0}}1{{else}}0{{end}}`
if err := runt(tpl, "1"); err != nil {
t.Error(err)
}
tpl = `{{if empty ""}}1{{else}}0{{end}}`
if err := runt(tpl, "1"); err != nil {
t.Error(err)
}
tpl = `{{if empty 0.0}}1{{else}}0{{end}}`
if err := runt(tpl, "1"); err != nil {
t.Error(err)
}
tpl = `{{if empty false}}1{{else}}0{{end}}`
if err := runt(tpl, "1"); err != nil {
t.Error(err)
}
dict := map[string]interface{}{"top": map[string]interface{}{}}
tpl = `{{if empty .top.NoSuchThing}}1{{else}}0{{end}}`
if err := runtv(tpl, "1", dict); err != nil {
t.Error(err)
}
tpl = `{{if empty .bottom.NoSuchThing}}1{{else}}0{{end}}`
if err := runtv(tpl, "1", dict); err != nil {
t.Error(err)
}
}
func TestCoalesce(t *testing.T) {
tests := map[string]string{
`{{ coalesce 1 }}`: "1",
`{{ coalesce "" 0 nil 2 }}`: "2",
`{{ $two := 2 }}{{ coalesce "" 0 nil $two }}`: "2",
`{{ $two := 2 }}{{ coalesce "" $two 0 0 0 }}`: "2",
`{{ $two := 2 }}{{ coalesce "" $two 3 4 5 }}`: "2",
`{{ coalesce }}`: "<no value>",
}
for tpl, expect := range tests {
assert.NoError(t, runt(tpl, expect))
}
dict := map[string]interface{}{"top": map[string]interface{}{}}
tpl := `{{ coalesce .top.NoSuchThing .bottom .bottom.dollar "airplane"}}`
if err := runtv(tpl, "airplane", dict); err != nil {
t.Error(err)
}
}
func TestToJson(t *testing.T) {
dict := map[string]interface{}{"Top": map[string]interface{}{"bool": true, "string": "test", "number": 42}}
tpl := `{{.Top | toJson}}`
expected := `{"bool":true,"number":42,"string":"test"}`
if err := runtv(tpl, expected, dict); err != nil {
t.Error(err)
}
}
func TestToPrettyJson(t *testing.T) {
dict := map[string]interface{}{"Top": map[string]interface{}{"bool": true, "string": "test", "number": 42}}
tpl := `{{.Top | toPrettyJson}}`
expected := `{
"bool": true,
"number": 42,
"string": "test"
}`
if err := runtv(tpl, expected, dict); err != nil {
t.Error(err)
}
}

View File

@@ -75,10 +75,12 @@ func dict(v ...interface{}) map[string]interface{} {
return dict return dict
} }
func merge(dst map[string]interface{}, src map[string]interface{}) interface{} { func merge(dst map[string]interface{}, srcs ...map[string]interface{}) interface{} {
if err := mergo.Merge(&dst, src); err != nil { for _, src := range srcs {
// Swallow errors inside of a template. if err := mergo.Merge(&dst, src); err != nil {
return "" // Swallow errors inside of a template.
return ""
}
} }
return dst return dst
} }

View File

@@ -1,174 +0,0 @@
package sprig
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestDict(t *testing.T) {
tpl := `{{$d := dict 1 2 "three" "four" 5}}{{range $k, $v := $d}}{{$k}}{{$v}}{{end}}`
out, err := runRaw(tpl, nil)
if err != nil {
t.Error(err)
}
if len(out) != 12 {
t.Errorf("Expected length 12, got %d", len(out))
}
// dict does not guarantee ordering because it is backed by a map.
if !strings.Contains(out, "12") {
t.Error("Expected grouping 12")
}
if !strings.Contains(out, "threefour") {
t.Error("Expected grouping threefour")
}
if !strings.Contains(out, "5") {
t.Error("Expected 5")
}
tpl = `{{$t := dict "I" "shot" "the" "albatross"}}{{$t.the}} {{$t.I}}`
if err := runt(tpl, "albatross shot"); err != nil {
t.Error(err)
}
}
func TestUnset(t *testing.T) {
tpl := `{{- $d := dict "one" 1 "two" 222222 -}}
{{- $_ := unset $d "two" -}}
{{- range $k, $v := $d}}{{$k}}{{$v}}{{- end -}}
`
expect := "one1"
if err := runt(tpl, expect); err != nil {
t.Error(err)
}
}
func TestHasKey(t *testing.T) {
tpl := `{{- $d := dict "one" 1 "two" 222222 -}}
{{- if hasKey $d "one" -}}1{{- end -}}
`
expect := "1"
if err := runt(tpl, expect); err != nil {
t.Error(err)
}
}
func TestPluck(t *testing.T) {
tpl := `
{{- $d := dict "one" 1 "two" 222222 -}}
{{- $d2 := dict "one" 1 "two" 33333 -}}
{{- $d3 := dict "one" 1 -}}
{{- $d4 := dict "one" 1 "two" 4444 -}}
{{- pluck "two" $d $d2 $d3 $d4 -}}
`
expect := "[222222 33333 4444]"
if err := runt(tpl, expect); err != nil {
t.Error(err)
}
}
func TestKeys(t *testing.T) {
tests := map[string]string{
`{{ dict "foo" 1 "bar" 2 | keys | sortAlpha }}`: "[bar foo]",
`{{ dict | keys }}`: "[]",
}
for tpl, expect := range tests {
if err := runt(tpl, expect); err != nil {
t.Error(err)
}
}
}
func TestPick(t *testing.T) {
tests := map[string]string{
`{{- $d := dict "one" 1 "two" 222222 }}{{ pick $d "two" | len -}}`: "1",
`{{- $d := dict "one" 1 "two" 222222 }}{{ pick $d "two" -}}`: "map[two:222222]",
`{{- $d := dict "one" 1 "two" 222222 }}{{ pick $d "one" "two" | len -}}`: "2",
`{{- $d := dict "one" 1 "two" 222222 }}{{ pick $d "one" "two" "three" | len -}}`: "2",
`{{- $d := dict }}{{ pick $d "two" | len -}}`: "0",
}
for tpl, expect := range tests {
if err := runt(tpl, expect); err != nil {
t.Error(err)
}
}
}
func TestOmit(t *testing.T) {
tests := map[string]string{
`{{- $d := dict "one" 1 "two" 222222 }}{{ omit $d "one" | len -}}`: "1",
`{{- $d := dict "one" 1 "two" 222222 }}{{ omit $d "one" -}}`: "map[two:222222]",
`{{- $d := dict "one" 1 "two" 222222 }}{{ omit $d "one" "two" | len -}}`: "0",
`{{- $d := dict "one" 1 "two" 222222 }}{{ omit $d "two" "three" | len -}}`: "1",
`{{- $d := dict }}{{ omit $d "two" | len -}}`: "0",
}
for tpl, expect := range tests {
if err := runt(tpl, expect); err != nil {
t.Error(err)
}
}
}
func TestSet(t *testing.T) {
tpl := `{{- $d := dict "one" 1 "two" 222222 -}}
{{- $_ := set $d "two" 2 -}}
{{- $_ := set $d "three" 3 -}}
{{- if hasKey $d "one" -}}{{$d.one}}{{- end -}}
{{- if hasKey $d "two" -}}{{$d.two}}{{- end -}}
{{- if hasKey $d "three" -}}{{$d.three}}{{- end -}}
`
expect := "123"
if err := runt(tpl, expect); err != nil {
t.Error(err)
}
}
func TestCompact(t *testing.T) {
tests := map[string]string{
`{{ list 1 0 "" "hello" | compact }}`: `[1 hello]`,
`{{ list "" "" | compact }}`: `[]`,
`{{ list | compact }}`: `[]`,
}
for tpl, expect := range tests {
assert.NoError(t, runt(tpl, expect))
}
}
func TestMerge(t *testing.T) {
dict := map[string]interface{}{
"src": map[string]interface{}{
"a": 1,
"b": 2,
"d": map[string]interface{}{
"e": "four",
},
"g": []int{6, 7},
},
"dst": map[string]interface{}{
"a": "one",
"c": 3,
"d": map[string]interface{}{
"f": 5,
},
"g": []int{8, 9},
},
}
tpl := `{{merge .dst .src}}`
_, err := runRaw(tpl, dict)
if err != nil {
t.Error(err)
}
expected := map[string]interface{}{
"a": "one", // key overridden
"b": 2, // merged from src
"c": 3, // merged from dst
"d": map[string]interface{}{ // deep merge
"e": "four",
"f": 5,
},
"g": []int{8, 9}, // overridden - arrays are not merged
}
assert.Equal(t, expected, dict["dst"])
}

View File

@@ -48,6 +48,10 @@ String Functions
- randAlpha: Given a length, generate an alphabetic string - randAlpha: Given a length, generate an alphabetic string
- randAscii: Given a length, generate a random ASCII string (symbols included) - randAscii: Given a length, generate a random ASCII string (symbols included)
- randNumeric: Given a length, generate a string of digits. - randNumeric: Given a length, generate a string of digits.
- swapcase: SwapCase swaps the case of a string using a word based algorithm. see https://godoc.org/github.com/Masterminds/goutils#SwapCase
- shuffle: Shuffle randomizes runes in a string and returns the result. It uses default random source in `math/rand`
- snakecase: convert all upper case characters in a string to underscore format.
- camelcase: convert all lower case characters behind underscores to upper case character
- wrap: Force a line wrap at the given width. `wrap 80 "imagine a longer string"` - wrap: Force a line wrap at the given width. `wrap 80 "imagine a longer string"`
- wrapWith: Wrap a line at the given length, but using 'sep' instead of a newline. `wrapWith 50, "<br>", $html` - wrapWith: Wrap a line at the given length, but using 'sep' instead of a newline. `wrapWith 50, "<br>", $html`
- contains: strings.Contains, but with the arguments switched: `contains substr str`. (This simplifies common pipelines) - contains: strings.Contains, but with the arguments switched: `contains substr str`. (This simplifies common pipelines)
@@ -57,6 +61,7 @@ String Functions
- squote: Wrap string(s) in double quotation marks, does not escape content. - squote: Wrap string(s) in double quotation marks, does not escape content.
- cat: Concatenate strings, separating them by spaces. `cat $a $b $c`. - cat: Concatenate strings, separating them by spaces. `cat $a $b $c`.
- indent: Indent a string using space characters. `indent 4 "foo\nbar"` produces " foo\n bar" - indent: Indent a string using space characters. `indent 4 "foo\nbar"` produces " foo\n bar"
- nindent: Indent a string using space characters and prepend a new line. `indent 4 "foo\nbar"` produces "\n foo\n bar"
- replace: Replace an old with a new in a string: `$name | replace " " "-"` - replace: Replace an old with a new in a string: `$name | replace " " "-"`
- plural: Choose singular or plural based on length: `len $fish | plural "one anchovy" "many anchovies"` - plural: Choose singular or plural based on length: `len $fish | plural "one anchovy" "many anchovies"`
- sha256sum: Generate a hex encoded sha256 hash of the input - sha256sum: Generate a hex encoded sha256 hash of the input
@@ -84,7 +89,7 @@ Integer Slice Functions:
Conversions: Conversions:
- atoi: Convert a string to an integer. 0 if the integer could not be parsed. - atoi: Convert a string to an integer. 0 if the integer could not be parsed.
- in64: Convert a string or another numeric type to an int64. - int64: Convert a string or another numeric type to an int64.
- int: Convert a string or another numeric type to an int. - int: Convert a string or another numeric type to an int.
- float64: Convert a string or another numeric type to a float64. - float64: Convert a string or another numeric type to a float64.

View File

@@ -1 +0,0 @@
theme: jekyll-theme-slate

View File

@@ -1,25 +0,0 @@
# Type Conversion Functions
The following type conversion functions are provided by Sprig:
- `atoi`: Convert a string to an integer
- `float64`: Convert to a float64
- `int`: Convert to an `int` at the system's width.
- `int64`: Convert to an `int64`
- `toString`: Convert to a string
- `toStrings`: Convert a list, slice, or array to a list of strings.
Only `atoi` requires that the input be a specific type. The others will attempt
to convert from any type to the destination type. For example, `int64` can convert
floats to ints, and it can also convert strings to ints.
## toStrings
Given a list-like collection, produce a slice of strings.
```
list 1 2 3 | toStrings
```
The above converts `1` to `"1"`, `2` to `"2"`, and so on, and then returns
them as a list.

View File

@@ -1,37 +0,0 @@
# Cryptographic and Security Functions
Sprig provides a couple of advanced cryptographic functions.
## sha256sum
The `sha256sum` function receives a string, and computes it's SHA256 digest.
```
sha256sum "Hello world!"
```
The above will compute the SHA 256 sum in an "ASCII armored" format that is
safe to print.
## derivePassword
The `derivePassword` function can be used to derive a specific password based on
some shared "master password" constraints. The algorithm for this is
[well specified](http://masterpasswordapp.com/algorithm.html).
```
derivePassword 1 "long" "password" "user" "example.com"
```
Note that it is considered insecure to store the parts directly in the template.
## generatePrivateKey
The `generatePrivateKey` function generates a new private key encoded into a PEM
block.
It takes one of the values for its first param:
- `ecdsa`: Generate an elyptical curve DSA key (P256)
- `dsa`: Generate a DSA key (L2048N256)
- `rsa`: Generate an RSA 4096 key

View File

@@ -1,62 +0,0 @@
# Date Functions
## now
The current date/time. Use this in conjunction with other date functions.
## date
The `date` function formats a date.
Format the date to YEAR-MONTH-DAY:
```
now | date "2006-01-02"
```
Date formatting in Go is a [little bit different](https://pauladamsmith.com/blog/2011/05/go_time.html).
In short, take this as the base date:
```
Mon Jan 2 15:04:05 MST 2006
```
Write it in the format you want. Above, `2006-01-02` is the same date, but
in the format we want.
## dateInZone
Same as `date`, but with a timezone.
```
date "2006-01-02" (now) "UTC"
```
## dateModify
The `dateModify` takes a modification and a date and returns the timestamp.
Subtract an hour and thirty minutes from the current time:
```
now | date_modify "-1.5h"
```
## htmlDate
The `htmlDate` function formates a date for inserting into an HTML date picker
input field.
```
now | htmlDate
```
## htmlDateInZone
Same as htmlDate, but with a timezone.
```
htmlDate (now) "UTC"
```

View File

@@ -1,59 +0,0 @@
# Default Functions
Sprig provides tools for setting default values for templates.
## default
To set a simple default value, use `default`:
```
default "foo" .Bar
```
In the above, if `.Bar` evaluates to a non-empty value, it will be used. But if
it is empty, `foo` will be returned instead.
The definition of "empty" depends on type:
- Numeric: 0
- String: ""
- Lists: `[]`
- Dicts: `{}`
- Boolean: `false`
- And always `nil` (aka null)
For structs, there is no definition of empty, so a struct will never return the
default.
## empty
The `empty` function returns `true` if the given value is considered empty, and
`false` otherwise. The empty values are listed in the `default` section.
```
empty .Foo
```
Note that in Go template conditionals, emptiness is calculated for you. Thus,
you rarely need `if empty .Foo`. Instead, just use `if .Foo`.
## coalesce
The `coalesce` function takes a list of values and returns the first non-empty
one.
```
coalesce 0 1 2
```
The above returns `1`.
This function is useful for scanning through multiple variables or values:
```
coalesce .name .parent.name "Matt"
```
The above will first check to see if `.name` is empty. If it is not, it will return
that value. If it _is_ empty, `coalesce` will evaluate `.parent.name` for emptiness.
Finally, if both `.name` and `.parent.name` are empty, it will return `Matt`.

View File

@@ -1,124 +0,0 @@
# Dictionaries and Dict Functions
Sprig provides a key/value storage type called a `dict` (short for "dictionary",
as in Python). A `dict` is an _unorder_ type.
The key to a dictionary **must be a string**. However, the value can be any
type, even another `dict` or `list`.
Unlike `list`s, `dict`s are not immutable. The `set` and `unset` functions will
modify the contents of a dictionary.
## dict
Creating dictionaries is done by calling the `dict` function and passing it a
list of pairs.
The following creates a dictionary with three items:
```
$myDict := dict "name1" "value1" "name2" "value2" "name3" "value 3"
```
## set
Use `set` to add a new key/value pair to a dictionary.
```
$_ := set $myDict "name4" "value4"
```
Note that `set` _returns the dictionary_ (a requirement of Go template functions),
so you may need to trap the value as done above with the `$_` assignment.
## unset
Given a map and a key, delete the key from the map.
```
$_ := unset $myDict "name4"
```
As with `set`, this returns the dictionary.
Note that if the key is not found, this operation will simply return. No error
will be generated.
## hasKey
The `hasKey` function returns `true` if the given dict contains the given key.
```
hasKey $myDict "name1"
```
If the key is not found, this returns `false`.
## pluck
The `pluck` function makes it possible to give one key and multiple maps, and
get a list of all of the matches:
```
pluck "name1" $myDict $myOtherDict
```
The above will return a `list` containing every found value (`[value1 otherValue1]`).
If the give key is _not found_ in a map, that map will not have an item in the
list (and the length of the returned list will be less than the number of dicts
in the call to `pluck`.
If the key is _found_ but the value is an empty value, that value will be
inserted.
A common idiom in Sprig templates is to uses `pluck... | first` to get the first
matching key out of a collection of dictionaries.
## merge
Merge two dictionaries into one, giving precedence to the dest dictionary:
```
$newdict := merge $dest $source
```
This is a deep merge operation.
## keys
The `keys` function will return a `list` of all of the keys in a `dict`. Since
a dictionary is _unordered_, the keys will not be in a predictable order. They
can be sorted with `sortAlpha`.
```
keys $myDict | sortAlpha
```
## pick
The `pick` function selects just the given keys out of a dictionary, creating a
new `dict`.
```
$new := pick $myDict "name1" "name3"
```
The above returns `{name1: value1, name2: value2}`
## omit
The `omit` function is similar to `pick`, except it returns a new `dict` with all
the keys that _do not_ match the given keys.
```
$new := omit $myDict "name1" "name3"
```
The above returns `{name2: value2}`
## A Note on Dict Internals
A `dict` is implemented in Go as a `map[string]interface{}`. Go developers can
pass `map[string]interface{}` values into the context to make them available
to templates as `dict`s.

View File

@@ -1,6 +0,0 @@
# Encoding Functions
Sprig has the following encoding and decoding functions:
- `b64enc`/`b64dec`: Encode or decode with Base64
- `b32enc`/`b32dec`: Encode or decode with Base32

View File

@@ -1,11 +0,0 @@
# Flow Control Functions
## fail
Unconditionally returns an empty `string` and an `error` with the specified
text. This is useful in scenarios where other conditionals have determined that
template rendering should fail.
```
fail "Please accept the end user license agreement"
```

View File

@@ -1,23 +0,0 @@
# Sprig Function Documentation
The Sprig library provides over 70 template functions for Go's template language.
- [String Functions](strings.md): `trim`, `wrap`, `randAlpha`, `plural`, etc.
- [String List Functions](string_slice.md): `splitList`, `sortAlpha`, etc.
- [Math Functions](math.md): `add`, `max`, `mul`, etc.
- [Integer Slice Functions](integer_slice.md): `until`, `untilStep`
- [Date Functions](date.md): `now`, `date`, etc.
- [Defaults Functions](defaults.md): `default`, `empty`, `coalesce`
- [Encoding Functions](encoding.md): `b64enc`, `b64dec`, etc.
- [Lists and List Functions](lists.md): `list`, `first`, `uniq`, etc.
- [Dictionaries and Dict Functions](dicts.md): `dict`, `hasKey`, `pluck`, etc.
- [Type Conversion Functions](conversion.md): `atoi`, `int64`, `toString`, etc.
- [File Path Functions](paths.md): `base`, `dir`, `ext`, `clean`, `isAbs`
- [Flow Control Functions](flow_control.md): `fail`
- Advanced Functions
- [UUID Functions](uuid.md): `uuidv4`
- [OS Functions](os.md): `env`, `expandenv`
- [Version Comparison Functions](semver.md): `semver`, `semverCompare`
- [Reflection](reflection.md): `typeOf`, `kindIs`, `typeIsLike`, etc.
- [Cryptographic and Security Functions](crypto.md): `derivePassword`, `sha256sum`, `genPrivateKey`

View File

@@ -1,25 +0,0 @@
# Integer Slice Functions
## until
The `until` function builds a range of integers.
```
until 5
```
The above generates the list `[0, 1, 2, 3, 4]`.
This is useful for looping with `range $i, $e := until 5`.
## untilStep
Like `until`, `untilStep` generates a list of counting integers. But it allows
you to define a start, stop, and step:
```
untilStep 3 6 2
```
The above will produce `[3 5]` by starting with 3, and adding 2 until it is equal
or greater than 6. This is similar to Python's `range` function.

View File

@@ -1,111 +0,0 @@
# Lists and List Functions
Sprig provides a simple `list` type that can contain arbitrary sequential lists
of data. This is similar to arrays or slices, but lists are designed to be used
as immutable data types.
Create a list of integers:
```
$myList := list 1 2 3 4 5
```
The above creates a list of `[1 2 3 4 5]`.
## first
To get the head item on a list, use `first`.
`first $myList` returns `1`
## rest
To get the tail of the list (everything but the first item), use `rest`.
`rest $myList` returns `[2 3 4 5]`
## last
To get the last item on a list, use `last`:
`last $myList` returns `5`. This is roughly analogous to reversing a list and
then calling `first`.
## initial
This compliments `last` by returning all _but_ the last element.
`initial $myList` returns `[1 2 3 4]`.
## append
Append a new item to an existing list, creating a new list.
```
$new = append $myList 6
```
The above would set `$new` to `[1 2 3 4 5 6]`. `$myList` would remain unaltered.
## prepend
Push an alement onto the front of a list, creating a new list.
```
prepend $myList 0
```
The above would produce `[0 1 2 3 4 5]`. `$myList` would remain unaltered.
## reverse
Produce a new list with the reversed elements of the given list.
```
reverse $myList
```
The above would generate the list `[5 4 3 2 1]`.
## uniq
Generate a list with all of the duplicates removed.
```
list 1 1 1 2 | uniq
```
The above would produce `[1 2]`
## without
The `without` function filters items out of a list.
```
without $myList 3
```
The above would produce `[1 2 4 5]`
Without can take more than one filter:
```
without $myList 1 3 5
```
That would produce `[2 4]`
## has
Test to see if a list has a particular element.
```
has $myList 4
```
The above would return `true`, while `has $myList "hello"` would return false.
## A Note on List Internals
A list is implemented in Go as a `[]interface{}`. For Go developers embedding
Sprig, you may pass `[]interface{}` items into your template context and be
able to use all of the `list` functions on those items.

View File

@@ -1,46 +0,0 @@
# Math Functions
All math functions operate on `int64` values unless specified otherwise.
(In the future, these will be extended to handle floats as well)
## add
Sum numbers with `add`
## add1
To increment by 1, use `add1`
## sub
To subtract, use `sub`
## div
Perform integer division with `div`
## mod
Modulo with `mod`
## mul
Multiply with `mul`
## max
Return the largest of a series of integers:
This will return `3`:
```
max 1 2 3
```
## min
Return the smallest of a series of integers.
`min 1 2 3` will return `1`.

View File

@@ -1,24 +0,0 @@
# OS Functions
_WARNING:_ These functions can lead to information leakage if not used
appropriately.
_WARNING:_ Some notable implementations of Sprig (such as
[Kubernetes Helm](http://helm.sh) _do not provide these functions for security
reasons_.
## env
The `env` function reads an environment variable:
```
env "HOME"
```
## expandenv
To substitute environment variables in a string, use `expandenv`:
```
expandenv "Your path is set to $PATH"
```

View File

@@ -1,43 +0,0 @@
# File Path Functions
While Sprig does not grant access to the filesystem, it does provide functions
for working with strings that follow file path conventions.
# base
Return the last element of a path.
```
base "foo/bar/baz"
```
The above prints "baz"
# dir
Return the directory, stripping the last part of the path. So `dir "foo/bar/baz"`
returns `foo/bar`
# clean
Clean up a path.
```
clean "foo/bar/../baz"
```
The above resolves the `..` and returns `foo/baz`
# ext
Return the file extension.
```
ext "foo.bar"
```
The above returns `.bar`.
# isAbs
To check whether a file path is absolute, use `isAbs`.

View File

@@ -1,38 +0,0 @@
# Reflection Functions
Sprig provides rudimentary reflection tools. These help advanced template
developers understand the underlying Go type information for a particular value.
Go has several primitive _kinds_, like `string`, `slice`, `int64`, and `bool`.
Go has an open _type_ system that allows developers to create their own types.
Sprig provides a set of functions for each.
## Kind Functions
There are two Kind functions: `kindOf` returns the kind of an object.
```
kindOf "hello"
```
The above would return `string`. For simple tests (like in `if` blocks), the
`isKind` function will let you verify that a value is a particular kind:
```
kindIs "int" 123
```
The above will return `true`
## Type Functions
Types are slightly harder to work with, so there are three different functions:
- `typeOf` returns the underlying type of a value: `typeOf $foo`
- `typeIs` is like `kindIs`, but for types: `typeIs "*io.Buffer" $myVal`
- `typeIsLike` works as `kindIs`, except that it also dereferences pointers.
**Note:** None of these can test whether or not something implements a given
interface, since doing so would require compiling the interface in ahead of time.

View File

@@ -1,124 +0,0 @@
# Semantic Version Functions
Some version schemes are easily parseable and comparable. Sprig provides functions
for working with [SemVer 2](http://semver.org) versions.
## semver
The `semver` function parses a string into a Semantic Version:
```
$version := semver "1.2.3-alpha.1+123"
```
_If the parser fails, it will cause template execution to halt with an error._
At this point, `$version` is a pointer to a `Version` object with the following
properties:
- `$version.Major`: The major number (`1` above)
- `$version.Minor`: The minor number (`2` above)
- `$version.Patch`: The patch number (`3` above)
- `$version.Prerelease`: The prerelease (`alpha.1` above)
- `$version.Metadata`: The build metadata (`123` above)
- `$version.Original`: The original version as a string
Additionally, you can compare a `Version` to another `version` using the `Compare`
function:
```
semver "1.4.3" | (semver "1.2.3").Compare
```
The above will return `-1`.
The return values are:
- `-1` if the given semver is greater than the semver whose `Compare` method was called
- `1` if the version who's `Compare` function was called is greater.
- `0` if they are the same version
(Note that in SemVer, the `Metadata` field is not compared during version
comparison operations.)
## semverCompare
A more robust comparison function is provided as `semverCompare`. This version
supports version ranges:
- `semverCompare "1.2.3" "1.2.3"` checks for an exact match
- `semverCompare "^1.2.0" "1.2.3"` checks that the major and minor versions match, and that the patch
number of the second version is _greater than or equal to_ the first parameter.
The SemVer functions use the [Masterminds semver library](https://github.com/Masterminds/semver),
from the creators of Sprig.
## Basic Comparisons
There are two elements to the comparisons. First, a comparison string is a list
of comma separated and comparisons. These are then separated by || separated or
comparisons. For example, `">= 1.2, < 3.0.0 || >= 4.2.3"` is looking for a
comparison that's greater than or equal to 1.2 and less than 3.0.0 or is
greater than or equal to 4.2.3.
The basic comparisons are:
* `=`: equal (aliased to no operator)
* `!=`: not equal
* `>`: greater than
* `<`: less than
* `>=`: greater than or equal to
* `<=`: less than or equal to
_Note, according to the Semantic Version specification pre-releases may not be
API compliant with their release counterpart. It says,_
> _A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version._
_SemVer comparisons without a pre-release value will skip pre-release versions.
For example, `>1.2.3` will skip pre-releases when looking at a list of values
while `>1.2.3-alpha.1` will evaluate pre-releases._
## Hyphen Range Comparisons
There are multiple methods to handle ranges and the first is hyphens ranges.
These look like:
* `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5`
* `2.3.4 - 4.5` which is equivalent to `>= 2.3.4, <= 4.5`
## Wildcards In Comparisons
The `x`, `X`, and `*` characters can be used as a wildcard character. This works
for all comparison operators. When used on the `=` operator it falls
back to the pack level comparison (see tilde below). For example,
* `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0`
* `>= 1.2.x` is equivalent to `>= 1.2.0`
* `<= 2.x` is equivalent to `<= 3`
* `*` is equivalent to `>= 0.0.0`
## Tilde Range Comparisons (Patch)
The tilde (`~`) comparison operator is for patch level ranges when a minor
version is specified and major level changes when the minor number is missing.
For example,
* `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0`
* `~1` is equivalent to `>= 1, < 2`
* `~2.3` is equivalent to `>= 2.3, < 2.4`
* `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0`
* `~1.x` is equivalent to `>= 1, < 2`
## Caret Range Comparisons (Major)
The caret (`^`) comparison operator is for major level changes. This is useful
when comparisons of API versions as a major change is API breaking. For example,
* `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0`
* `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0`
* `^2.3` is equivalent to `>= 2.3, < 3`
* `^2.x` is equivalent to `>= 2.0.0, < 3`

View File

@@ -1,55 +0,0 @@
# String Slice Functions
These function operate on or generate slices of strings. In Go, a slice is a
growable array. In Sprig, it's a special case of a `list`.
## join
Join a list of strings into a single string, with the given separator.
```
list "hello" "world" | join "_"
```
The above will produce `hello_world`
`join` will try to convert non-strings to a string value:
```
list 1 2 3 | join "+"
```
The above will produce `1+2+3`
## splitList and split
Split a string into a list of strings:
```
splitList "$" "foo$bar$baz"
```
The above will return `[foo bar baz]`
The older `split` function splits a string into a `dict`. It is designed to make
it easy to use template dot notation for accessing members:
```
$a := split "$" "foo$bar$baz"
```
The above produces a map with index keys. `{_0: foo, _1: bar, _2: baz}`
```
$a._0
```
The above produces `foo`
## sortAlpha
The `sortAlpha` function sorts a list of strings into alphabetical (lexicographical)
order.
It does _not_ sort in place, but returns a sorted copy of the list, in keeping
with the immutability of lists.

View File

@@ -1,322 +0,0 @@
# String Functions
Sprig has a number of string manipulation functions.
## trim
The `trim` function removes space from either side of a string:
```
trim " hello "
```
The above produces `hello`
## trimAll
Remove given characters from the front or back of a string:
```
trimAll "$" "$5.00"
```
The above returns `5.00` (as a string).
## trimSuffix
Trim just the suffix from a string:
```
trimSuffix "-" "hello-"
```
The above returns `hello`
## upper
Convert the entire string to uppercase:
```
upper "hello"
```
The above returns `HELLO`
## lower
Convert the entire string to lowercase:
```
lower "HELLO"
```
The above returns `hello`
## title
Convert to title case:
```
title "hello world"
```
The above returns `Hello World`
## untitle
Remove title casing. `untitle "Hello World"` produces `hello world`.
## repeat
Repeat a string multiple times:
```
repeat 3 "hello"
```
The above returns `hellohellohello`
## substr
Get a substring from a string. It takes three parameters:
- start (int)
- length (int)
- string (string)
```
substr 0 5 "hello world"
```
The above returns `hello`
## nospace
Remove all whitespace from a string.
```
nospace "hello w o r l d"
```
The above returns `helloworld`
## trunc
Truncate a string (and add no suffix)
```
trunc 5 "hello world"
```
The above produces `hello`.
## abbrev
Truncate a string with ellipses (`...`)
Parameters:
- max length
- the string
```
abbrev 5 "hello world"
```
The above returns `he...`, since it counts the width of the ellipses against the
maximum length.
## abbrevboth
Abbreviate both sides:
```
abbrevboth 5 10 "1234 5678 9123"
```
the above produces `...5678...`
It takes:
- left offset
- max length
- the string
## initials
Given multiple words, take the first letter of each word and combine.
```
initials "First Try"
```
The above returns `FT`
## randAlphaNum, randAlpha, randNumeric, and randAscii
These four functions generate random strings, but with different base character
sets:
- `randAlphaNum` uses `0-9a-zA-Z`
- `randAlpha` uses `a-zA-Z`
- `randNumeric` uses `0-9`
- `randAscii` uses all printable ASCII characters
Each of them takes one parameter: the integer length of the string.
```
randNumeric 3
```
The above will produce a random string with three digits.
## wrap
Wrap text at a given column count:
```
wrap 80 $someText
```
The above will wrap the string in `$someText` at 80 columns.
## wrapWith
`wrapWith` works as `wrap`, but lets you specify the string to wrap with.
(`wrap` uses `\n`)
```
wrapWith 5 "\t" "Hello World"
```
The above produces `hello world` (where the whitespace is an ASCII tab
character)
## contains
Test to see if one string is contained inside of another:
```
contains "cat" "catch"
```
The above returns `true` because `catch` contains `cat`.
## hasPrefix and hasSuffix
The `hasPrefix` and `hasSuffix` functions test whether a string has a given
prefix or suffix:
```
hasPrefix "cat" "catch"
```
The above returns `true` because `catch` has the prefix `cat`.
## quote and squote
These functions wrap a string in double quotes (`quote`) or single quotes
(`squote`).
## cat
The `cat` function concatenates multiple strings together into one, separating
them with spaces:
```
cat "hello" "beautiful" "world"
```
The above produces `hello beautiful world`
## indent
The `indent` function indents every line in a given string to the specified
indent width. This is useful when aligning multi-line strings:
```
indent 4 $lots_of_text
```
The above will indent every line of text by 4 space characters.
## replace
Perform simple string replacement.
It takes three arguments:
- string to replace
- string to replace with
- source string
```
"I Am Henry VIII" | replace " " "-"
```
The above will produce `I-Am-Henry-VIII`
## plural
Pluralize a string.
```
len $fish | plural "one anchovy" "many anchovies"
```
In the above, if the length of the string is 1, the first argument will be
printed (`one anchovy`). Otherwise, the second argument will be printed
(`many anchovies`).
The arguments are:
- singular string
- plural string
- length integer
NOTE: Sprig does not currently support languages with more complex pluralization
rules. And `0` is considered a plural because the English language treats it
as such (`zero anchovies`). The Sprig developers are working on a solution for
better internationalization.
## snakecase
Convert string from camelCase to snake_case.
Introduced in 2.12.0.
```
snakecase "FirstName"
```
This above will produce `first_name`.
## camelcase
Convert string from snake_case to CamelCase
Introduced in 2.12.0.
```
camelcase "http_server"
```
This above will produce `HttpServer`.
## shuffle
Shuffle a string.
Introduced in 2.12.0.
```
shuffle "hello"
```
The above will randomize the letters in `hello`, perhaps producing `oelhl`.
## See Also...
The [Conversion Functions](conversion.html) contain functions for converting
strings. The [String Slice Functions](string_slice.html) contains functions
for working with an array of strings.

View File

@@ -1,9 +0,0 @@
# UUID Functions
Sprig can generate UUID v4 universally unique IDs.
```
uuidv4
```
The above returns a new UUID of the v4 (randomly generated) type.

View File

@@ -1,25 +0,0 @@
package sprig
import (
"fmt"
"os"
"text/template"
)
func Example() {
// Set up variables and template.
vars := map[string]interface{}{"Name": " John Jacob Jingleheimer Schmidt "}
tpl := `Hello {{.Name | trim | lower}}`
// Get the Sprig function map.
fmap := TxtFuncMap()
t := template.Must(template.New("test").Funcs(fmap).Parse(tpl))
err := t.Execute(os.Stdout, vars)
if err != nil {
fmt.Printf("Error during template execution: %s", err)
return
}
// Output:
// Hello john jacob jingleheimer schmidt
}

View File

@@ -1,16 +0,0 @@
package sprig
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFail(t *testing.T) {
const msg = "This is an error!"
tpl := fmt.Sprintf(`{{fail "%s"}}`, msg)
_, err := runRaw(tpl, nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), msg)
}

View File

@@ -98,6 +98,8 @@ var genericMap = map[string]interface{}{
"htmlDateInZone": htmlDateInZone, "htmlDateInZone": htmlDateInZone,
"dateInZone": dateInZone, "dateInZone": dateInZone,
"dateModify": dateModify, "dateModify": dateModify,
"ago": dateAgo,
"toDate": toDate,
// Strings // Strings
"abbrev": abbrev, "abbrev": abbrev,
@@ -137,6 +139,7 @@ var genericMap = map[string]interface{}{
"squote": squote, "squote": squote,
"cat": cat, "cat": cat,
"indent": indent, "indent": indent,
"nindent": nindent,
"replace": replace, "replace": replace,
"plural": plural, "plural": plural,
"sha256sum": sha256sum, "sha256sum": sha256sum,
@@ -183,6 +186,9 @@ var genericMap = map[string]interface{}{
"biggest": max, "biggest": max,
"max": max, "max": max,
"min": min, "min": min,
"ceil": ceil,
"floor": floor,
"round": round,
// string slices. Note that we reverse the order b/c that's better // string slices. Note that we reverse the order b/c that's better
// for template processing. // for template processing.
@@ -243,11 +249,14 @@ var genericMap = map[string]interface{}{
"reverse": reverse, "reverse": reverse,
"uniq": uniq, "uniq": uniq,
"without": without, "without": without,
"has": func(needle interface{}, haystack []interface{}) bool { return inList(haystack, needle) }, "has": has,
// Crypto: // Crypto:
"genPrivateKey": generatePrivateKey, "genPrivateKey": generatePrivateKey,
"derivePassword": derivePassword, "derivePassword": derivePassword,
"genCA": generateCertificateAuthority,
"genSelfSignedCert": generateSelfSignedCertificate,
"genSignedCert": generateSignedCertificate,
// UUIDs: // UUIDs:
"uuidv4": uuidv4, "uuidv4": uuidv4,
@@ -258,4 +267,12 @@ var genericMap = map[string]interface{}{
// Flow Control: // Flow Control:
"fail": func(msg string) (string, error) { return "", errors.New(msg) }, "fail": func(msg string) (string, error) { return "", errors.New(msg) },
// Regex
"regexMatch": regexMatch,
"regexFindAll": regexFindAll,
"regexFind": regexFind,
"regexReplaceAll": regexReplaceAll,
"regexReplaceAllLiteral": regexReplaceAllLiteral,
"regexSplit": regexSplit,
} }

View File

@@ -1,108 +0,0 @@
package sprig
import (
"bytes"
"fmt"
"math/rand"
"os"
"testing"
"text/template"
"github.com/aokoli/goutils"
"github.com/stretchr/testify/assert"
)
func TestEnv(t *testing.T) {
os.Setenv("FOO", "bar")
tpl := `{{env "FOO"}}`
if err := runt(tpl, "bar"); err != nil {
t.Error(err)
}
}
func TestExpandEnv(t *testing.T) {
os.Setenv("FOO", "bar")
tpl := `{{expandenv "Hello $FOO"}}`
if err := runt(tpl, "Hello bar"); err != nil {
t.Error(err)
}
}
func TestBase(t *testing.T) {
assert.NoError(t, runt(`{{ base "foo/bar" }}`, "bar"))
}
func TestDir(t *testing.T) {
assert.NoError(t, runt(`{{ dir "foo/bar/baz" }}`, "foo/bar"))
}
func TestIsAbs(t *testing.T) {
assert.NoError(t, runt(`{{ isAbs "/foo" }}`, "true"))
assert.NoError(t, runt(`{{ isAbs "foo" }}`, "false"))
}
func TestClean(t *testing.T) {
assert.NoError(t, runt(`{{ clean "/foo/../foo/../bar" }}`, "/bar"))
}
func TestExt(t *testing.T) {
assert.NoError(t, runt(`{{ ext "/foo/bar/baz.txt" }}`, ".txt"))
}
func TestSnakeCase(t *testing.T) {
assert.NoError(t, runt(`{{ snakecase "FirstName" }}`, "first_name"))
assert.NoError(t, runt(`{{ snakecase "HTTPServer" }}`, "http_server"))
assert.NoError(t, runt(`{{ snakecase "NoHTTPS" }}`, "no_https"))
assert.NoError(t, runt(`{{ snakecase "GO_PATH" }}`, "go_path"))
assert.NoError(t, runt(`{{ snakecase "GO PATH" }}`, "go_path"))
assert.NoError(t, runt(`{{ snakecase "GO-PATH" }}`, "go_path"))
}
func TestCamelCase(t *testing.T) {
assert.NoError(t, runt(`{{ camelcase "http_server" }}`, "HttpServer"))
assert.NoError(t, runt(`{{ camelcase "_camel_case" }}`, "_CamelCase"))
assert.NoError(t, runt(`{{ camelcase "no_https" }}`, "NoHttps"))
assert.NoError(t, runt(`{{ camelcase "_complex__case_" }}`, "_Complex_Case_"))
assert.NoError(t, runt(`{{ camelcase "all" }}`, "All"))
}
func TestShuffle(t *testing.T) {
goutils.RANDOM = rand.New(rand.NewSource(1))
// Because we're using a random number generator, we need these to go in
// a predictable sequence:
assert.NoError(t, runt(`{{ shuffle "Hello World" }}`, "rldo HWlloe"))
}
// runt runs a template and checks that the output exactly matches the expected string.
func runt(tpl, expect string) error {
return runtv(tpl, expect, map[string]string{})
}
// runtv takes a template, and expected return, and values for substitution.
//
// It runs the template and verifies that the output is an exact match.
func runtv(tpl, expect string, vars interface{}) error {
fmap := TxtFuncMap()
t := template.Must(template.New("test").Funcs(fmap).Parse(tpl))
var b bytes.Buffer
err := t.Execute(&b, vars)
if err != nil {
return err
}
if expect != b.String() {
return fmt.Errorf("Expected '%s', got '%s'", expect, b.String())
}
return nil
}
// runRaw runs a template with the given variables and returns the result.
func runRaw(tpl string, vars interface{}) (string, error) {
fmap := TxtFuncMap()
t := template.Must(template.New("test").Funcs(fmap).Parse(tpl))
var b bytes.Buffer
err := t.Execute(&b, vars)
if err != nil {
return "", err
}
return b.String(), nil
}

View File

@@ -1,50 +1,135 @@
package sprig package sprig
import ( import (
"fmt"
"reflect" "reflect"
"sort" "sort"
) )
// Reflection is used in these functions so that slices and arrays of strings,
// ints, and other types not implementing []interface{} can be worked with.
// For example, this is useful if you need to work on the output of regexs.
func list(v ...interface{}) []interface{} { func list(v ...interface{}) []interface{} {
return v return v
} }
func push(list []interface{}, v interface{}) []interface{} { func push(list interface{}, v interface{}) []interface{} {
return append(list, v) tp := reflect.TypeOf(list).Kind()
} switch tp {
case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(list)
func prepend(list []interface{}, v interface{}) []interface{} { l := l2.Len()
return append([]interface{}{v}, list...) nl := make([]interface{}, l)
} for i := 0; i < l; i++ {
nl[i] = l2.Index(i).Interface()
}
func last(list []interface{}) interface{} { return append(nl, v)
l := len(list)
if l == 0 { default:
return nil panic(fmt.Sprintf("Cannot push on type %s", tp))
} }
return list[l-1]
} }
func first(list []interface{}) interface{} { func prepend(list interface{}, v interface{}) []interface{} {
if len(list) == 0 { //return append([]interface{}{v}, list...)
return nil
tp := reflect.TypeOf(list).Kind()
switch tp {
case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(list)
l := l2.Len()
nl := make([]interface{}, l)
for i := 0; i < l; i++ {
nl[i] = l2.Index(i).Interface()
}
return append([]interface{}{v}, nl...)
default:
panic(fmt.Sprintf("Cannot prepend on type %s", tp))
} }
return list[0]
} }
func rest(list []interface{}) []interface{} { func last(list interface{}) interface{} {
if len(list) == 0 { tp := reflect.TypeOf(list).Kind()
return list switch tp {
case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(list)
l := l2.Len()
if l == 0 {
return nil
}
return l2.Index(l - 1).Interface()
default:
panic(fmt.Sprintf("Cannot find last on type %s", tp))
} }
return list[1:]
} }
func initial(list []interface{}) []interface{} { func first(list interface{}) interface{} {
l := len(list) tp := reflect.TypeOf(list).Kind()
if l == 0 { switch tp {
return list case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(list)
l := l2.Len()
if l == 0 {
return nil
}
return l2.Index(0).Interface()
default:
panic(fmt.Sprintf("Cannot find first on type %s", tp))
}
}
func rest(list interface{}) []interface{} {
tp := reflect.TypeOf(list).Kind()
switch tp {
case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(list)
l := l2.Len()
if l == 0 {
return nil
}
nl := make([]interface{}, l-1)
for i := 1; i < l; i++ {
nl[i-1] = l2.Index(i).Interface()
}
return nl
default:
panic(fmt.Sprintf("Cannot find rest on type %s", tp))
}
}
func initial(list interface{}) []interface{} {
tp := reflect.TypeOf(list).Kind()
switch tp {
case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(list)
l := l2.Len()
if l == 0 {
return nil
}
nl := make([]interface{}, l-1)
for i := 0; i < l-1; i++ {
nl[i] = l2.Index(i).Interface()
}
return nl
default:
panic(fmt.Sprintf("Cannot find initial on type %s", tp))
} }
return list[:l-1]
} }
func sortAlpha(list interface{}) []string { func sortAlpha(list interface{}) []string {
@@ -59,34 +144,67 @@ func sortAlpha(list interface{}) []string {
return []string{strval(list)} return []string{strval(list)}
} }
func reverse(v []interface{}) []interface{} { func reverse(v interface{}) []interface{} {
// We do not sort in place because the incomming array should not be altered. tp := reflect.TypeOf(v).Kind()
l := len(v) switch tp {
c := make([]interface{}, l) case reflect.Slice, reflect.Array:
for i := 0; i < l; i++ { l2 := reflect.ValueOf(v)
c[l-i-1] = v[i]
l := l2.Len()
// We do not sort in place because the incoming array should not be altered.
nl := make([]interface{}, l)
for i := 0; i < l; i++ {
nl[l-i-1] = l2.Index(i).Interface()
}
return nl
default:
panic(fmt.Sprintf("Cannot find reverse on type %s", tp))
} }
return c
} }
func compact(list []interface{}) []interface{} { func compact(list interface{}) []interface{} {
res := []interface{}{} tp := reflect.TypeOf(list).Kind()
for _, item := range list { switch tp {
if !empty(item) { case reflect.Slice, reflect.Array:
res = append(res, item) l2 := reflect.ValueOf(list)
l := l2.Len()
nl := []interface{}{}
var item interface{}
for i := 0; i < l; i++ {
item = l2.Index(i).Interface()
if !empty(item) {
nl = append(nl, item)
}
} }
return nl
default:
panic(fmt.Sprintf("Cannot compact on type %s", tp))
} }
return res
} }
func uniq(list []interface{}) []interface{} { func uniq(list interface{}) []interface{} {
dest := []interface{}{} tp := reflect.TypeOf(list).Kind()
for _, item := range list { switch tp {
if !inList(dest, item) { case reflect.Slice, reflect.Array:
dest = append(dest, item) l2 := reflect.ValueOf(list)
l := l2.Len()
dest := []interface{}{}
var item interface{}
for i := 0; i < l; i++ {
item = l2.Index(i).Interface()
if !inList(dest, item) {
dest = append(dest, item)
}
} }
return dest
default:
panic(fmt.Sprintf("Cannot find uniq on type %s", tp))
} }
return dest
} }
func inList(haystack []interface{}, needle interface{}) bool { func inList(haystack []interface{}, needle interface{}) bool {
@@ -98,12 +216,44 @@ func inList(haystack []interface{}, needle interface{}) bool {
return false return false
} }
func without(list []interface{}, omit ...interface{}) []interface{} { func without(list interface{}, omit ...interface{}) []interface{} {
res := []interface{}{} tp := reflect.TypeOf(list).Kind()
for _, i := range list { switch tp {
if !inList(omit, i) { case reflect.Slice, reflect.Array:
res = append(res, i) l2 := reflect.ValueOf(list)
l := l2.Len()
res := []interface{}{}
var item interface{}
for i := 0; i < l; i++ {
item = l2.Index(i).Interface()
if !inList(omit, item) {
res = append(res, item)
}
} }
return res
default:
panic(fmt.Sprintf("Cannot find without on type %s", tp))
}
}
func has(needle interface{}, haystack interface{}) bool {
tp := reflect.TypeOf(haystack).Kind()
switch tp {
case reflect.Slice, reflect.Array:
l2 := reflect.ValueOf(haystack)
var item interface{}
l := l2.Len()
for i := 0; i < l; i++ {
item = l2.Index(i).Interface()
if reflect.DeepEqual(needle, item) {
return true
}
}
return false
default:
panic(fmt.Sprintf("Cannot find has on type %s", tp))
} }
return res
} }

View File

@@ -1,135 +0,0 @@
package sprig
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestTuple(t *testing.T) {
tpl := `{{$t := tuple 1 "a" "foo"}}{{index $t 2}}{{index $t 0 }}{{index $t 1}}`
if err := runt(tpl, "foo1a"); err != nil {
t.Error(err)
}
}
func TestList(t *testing.T) {
tpl := `{{$t := list 1 "a" "foo"}}{{index $t 2}}{{index $t 0 }}{{index $t 1}}`
if err := runt(tpl, "foo1a"); err != nil {
t.Error(err)
}
}
func TestPush(t *testing.T) {
// Named `append` in the function map
tests := map[string]string{
`{{ $t := tuple 1 2 3 }}{{ append $t 4 | len }}`: "4",
`{{ $t := tuple 1 2 3 4 }}{{ append $t 5 | join "-" }}`: "1-2-3-4-5",
}
for tpl, expect := range tests {
assert.NoError(t, runt(tpl, expect))
}
}
func TestPrepend(t *testing.T) {
tests := map[string]string{
`{{ $t := tuple 1 2 3 }}{{ prepend $t 0 | len }}`: "4",
`{{ $t := tuple 1 2 3 4 }}{{ prepend $t 0 | join "-" }}`: "0-1-2-3-4",
}
for tpl, expect := range tests {
assert.NoError(t, runt(tpl, expect))
}
}
func TestFirst(t *testing.T) {
tests := map[string]string{
`{{ list 1 2 3 | first }}`: "1",
`{{ list | first }}`: "<no value>",
}
for tpl, expect := range tests {
assert.NoError(t, runt(tpl, expect))
}
}
func TestLast(t *testing.T) {
tests := map[string]string{
`{{ list 1 2 3 | last }}`: "3",
`{{ list | last }}`: "<no value>",
}
for tpl, expect := range tests {
assert.NoError(t, runt(tpl, expect))
}
}
func TestInitial(t *testing.T) {
tests := map[string]string{
`{{ list 1 2 3 | initial | len }}`: "2",
`{{ list 1 2 3 | initial | last }}`: "2",
`{{ list 1 2 3 | initial | first }}`: "1",
`{{ list | initial }}`: "[]",
}
for tpl, expect := range tests {
assert.NoError(t, runt(tpl, expect))
}
}
func TestRest(t *testing.T) {
tests := map[string]string{
`{{ list 1 2 3 | rest | len }}`: "2",
`{{ list 1 2 3 | rest | last }}`: "3",
`{{ list 1 2 3 | rest | first }}`: "2",
`{{ list | rest }}`: "[]",
}
for tpl, expect := range tests {
assert.NoError(t, runt(tpl, expect))
}
}
func TestReverse(t *testing.T) {
tests := map[string]string{
`{{ list 1 2 3 | reverse | first }}`: "3",
`{{ list 1 2 3 | reverse | rest | first }}`: "2",
`{{ list 1 2 3 | reverse | last }}`: "1",
`{{ list 1 2 3 4 | reverse }}`: "[4 3 2 1]",
`{{ list 1 | reverse }}`: "[1]",
`{{ list | reverse }}`: "[]",
}
for tpl, expect := range tests {
assert.NoError(t, runt(tpl, expect))
}
}
func TestUniq(t *testing.T) {
tests := map[string]string{
`{{ list 1 2 3 4 | uniq }}`: `[1 2 3 4]`,
`{{ list "a" "b" "c" "d" | uniq }}`: `[a b c d]`,
`{{ list 1 1 1 1 2 2 2 2 | uniq }}`: `[1 2]`,
`{{ list "foo" 1 1 1 1 "foo" "foo" | uniq }}`: `[foo 1]`,
`{{ list | uniq }}`: `[]`,
}
for tpl, expect := range tests {
assert.NoError(t, runt(tpl, expect))
}
}
func TestWithout(t *testing.T) {
tests := map[string]string{
`{{ without (list 1 2 3 4) 1 }}`: `[2 3 4]`,
`{{ without (list "a" "b" "c" "d") "a" }}`: `[b c d]`,
`{{ without (list 1 1 1 1 2) 1 }}`: `[2]`,
`{{ without (list) 1 }}`: `[]`,
`{{ without (list 1 2 3) }}`: `[1 2 3]`,
`{{ without list }}`: `[]`,
}
for tpl, expect := range tests {
assert.NoError(t, runt(tpl, expect))
}
}
func TestHas(t *testing.T) {
tests := map[string]string{
`{{ list 1 2 3 | has 1 }}`: `true`,
`{{ list 1 2 3 | has 4 }}`: `false`,
}
for tpl, expect := range tests {
assert.NoError(t, runt(tpl, expect))
}
}

View File

@@ -127,3 +127,33 @@ func untilStep(start, stop, step int) []int {
} }
return v return v
} }
func floor(a interface{}) float64 {
aa := toFloat64(a)
return math.Floor(aa)
}
func ceil(a interface{}) float64 {
aa := toFloat64(a)
return math.Ceil(aa)
}
func round(a interface{}, p int, r_opt ...float64) float64 {
roundOn := .5
if len(r_opt) > 0 {
roundOn = r_opt[0]
}
val := toFloat64(a)
places := toFloat64(p)
var round float64
pow := math.Pow(10, places)
digit := pow * val
_, div := math.Modf(digit)
if div >= roundOn {
round = math.Ceil(digit)
} else {
round = math.Floor(digit)
}
return round / pow
}

View File

@@ -1,180 +0,0 @@
package sprig
import (
"testing"
)
func TestUntil(t *testing.T) {
tests := map[string]string{
`{{range $i, $e := until 5}}{{$i}}{{$e}}{{end}}`: "0011223344",
`{{range $i, $e := until -5}}{{$i}}{{$e}} {{end}}`: "00 1-1 2-2 3-3 4-4 ",
}
for tpl, expect := range tests {
if err := runt(tpl, expect); err != nil {
t.Error(err)
}
}
}
func TestUntilStep(t *testing.T) {
tests := map[string]string{
`{{range $i, $e := untilStep 0 5 1}}{{$i}}{{$e}}{{end}}`: "0011223344",
`{{range $i, $e := untilStep 3 6 1}}{{$i}}{{$e}}{{end}}`: "031425",
`{{range $i, $e := untilStep 0 -10 -2}}{{$i}}{{$e}} {{end}}`: "00 1-2 2-4 3-6 4-8 ",
`{{range $i, $e := untilStep 3 0 1}}{{$i}}{{$e}}{{end}}`: "",
`{{range $i, $e := untilStep 3 99 0}}{{$i}}{{$e}}{{end}}`: "",
`{{range $i, $e := untilStep 3 99 -1}}{{$i}}{{$e}}{{end}}`: "",
`{{range $i, $e := untilStep 3 0 0}}{{$i}}{{$e}}{{end}}`: "",
}
for tpl, expect := range tests {
if err := runt(tpl, expect); err != nil {
t.Error(err)
}
}
}
func TestBiggest(t *testing.T) {
tpl := `{{ biggest 1 2 3 345 5 6 7}}`
if err := runt(tpl, `345`); err != nil {
t.Error(err)
}
tpl = `{{ max 345}}`
if err := runt(tpl, `345`); err != nil {
t.Error(err)
}
}
func TestMin(t *testing.T) {
tpl := `{{ min 1 2 3 345 5 6 7}}`
if err := runt(tpl, `1`); err != nil {
t.Error(err)
}
tpl = `{{ min 345}}`
if err := runt(tpl, `345`); err != nil {
t.Error(err)
}
}
func TestToFloat64(t *testing.T) {
target := float64(102)
if target != toFloat64(int8(102)) {
t.Errorf("Expected 102")
}
if target != toFloat64(int(102)) {
t.Errorf("Expected 102")
}
if target != toFloat64(int32(102)) {
t.Errorf("Expected 102")
}
if target != toFloat64(int16(102)) {
t.Errorf("Expected 102")
}
if target != toFloat64(int64(102)) {
t.Errorf("Expected 102")
}
if target != toFloat64("102") {
t.Errorf("Expected 102")
}
if 0 != toFloat64("frankie") {
t.Errorf("Expected 0")
}
if target != toFloat64(uint16(102)) {
t.Errorf("Expected 102")
}
if target != toFloat64(uint64(102)) {
t.Errorf("Expected 102")
}
if 102.1234 != toFloat64(float64(102.1234)) {
t.Errorf("Expected 102.1234")
}
if 1 != toFloat64(true) {
t.Errorf("Expected 102")
}
}
func TestToInt64(t *testing.T) {
target := int64(102)
if target != toInt64(int8(102)) {
t.Errorf("Expected 102")
}
if target != toInt64(int(102)) {
t.Errorf("Expected 102")
}
if target != toInt64(int32(102)) {
t.Errorf("Expected 102")
}
if target != toInt64(int16(102)) {
t.Errorf("Expected 102")
}
if target != toInt64(int64(102)) {
t.Errorf("Expected 102")
}
if target != toInt64("102") {
t.Errorf("Expected 102")
}
if 0 != toInt64("frankie") {
t.Errorf("Expected 0")
}
if target != toInt64(uint16(102)) {
t.Errorf("Expected 102")
}
if target != toInt64(uint64(102)) {
t.Errorf("Expected 102")
}
if target != toInt64(float64(102.1234)) {
t.Errorf("Expected 102")
}
if 1 != toInt64(true) {
t.Errorf("Expected 102")
}
}
func TestToInt(t *testing.T) {
target := int(102)
if target != toInt(int8(102)) {
t.Errorf("Expected 102")
}
if target != toInt(int(102)) {
t.Errorf("Expected 102")
}
if target != toInt(int32(102)) {
t.Errorf("Expected 102")
}
if target != toInt(int16(102)) {
t.Errorf("Expected 102")
}
if target != toInt(int64(102)) {
t.Errorf("Expected 102")
}
if target != toInt("102") {
t.Errorf("Expected 102")
}
if 0 != toInt("frankie") {
t.Errorf("Expected 0")
}
if target != toInt(uint16(102)) {
t.Errorf("Expected 102")
}
if target != toInt(uint64(102)) {
t.Errorf("Expected 102")
}
if target != toInt(float64(102.1234)) {
t.Errorf("Expected 102")
}
if 1 != toInt(true) {
t.Errorf("Expected 102")
}
}
func TestAdd(t *testing.T) {
tpl := `{{ 3 | add 1 2}}`
if err := runt(tpl, `6`); err != nil {
t.Error(err)
}
}
func TestMul(t *testing.T) {
tpl := `{{ 1 | mul "2" 3 "4"}}`
if err := runt(tpl, `24`); err != nil {
t.Error(err)
}
}

View File

@@ -1,73 +0,0 @@
package sprig
import (
"testing"
)
type fixtureTO struct {
Name, Value string
}
func TestTypeOf(t *testing.T) {
f := &fixtureTO{"hello", "world"}
tpl := `{{typeOf .}}`
if err := runtv(tpl, "*sprig.fixtureTO", f); err != nil {
t.Error(err)
}
}
func TestKindOf(t *testing.T) {
tpl := `{{kindOf .}}`
f := fixtureTO{"hello", "world"}
if err := runtv(tpl, "struct", f); err != nil {
t.Error(err)
}
f2 := []string{"hello"}
if err := runtv(tpl, "slice", f2); err != nil {
t.Error(err)
}
var f3 *fixtureTO = nil
if err := runtv(tpl, "ptr", f3); err != nil {
t.Error(err)
}
}
func TestTypeIs(t *testing.T) {
f := &fixtureTO{"hello", "world"}
tpl := `{{if typeIs "*sprig.fixtureTO" .}}t{{else}}f{{end}}`
if err := runtv(tpl, "t", f); err != nil {
t.Error(err)
}
f2 := "hello"
if err := runtv(tpl, "f", f2); err != nil {
t.Error(err)
}
}
func TestTypeIsLike(t *testing.T) {
f := "foo"
tpl := `{{if typeIsLike "string" .}}t{{else}}f{{end}}`
if err := runtv(tpl, "t", f); err != nil {
t.Error(err)
}
// Now make a pointer. Should still match.
f2 := &f
if err := runtv(tpl, "t", f2); err != nil {
t.Error(err)
}
}
func TestKindIs(t *testing.T) {
f := &fixtureTO{"hello", "world"}
tpl := `{{if kindIs "ptr" .}}t{{else}}f{{end}}`
if err := runtv(tpl, "t", f); err != nil {
t.Error(err)
}
f2 := "hello"
if err := runtv(tpl, "f", f2); err != nil {
t.Error(err)
}
}

35
vendor/github.com/Masterminds/sprig/regex.go generated vendored Normal file
View File

@@ -0,0 +1,35 @@
package sprig
import (
"regexp"
)
func regexMatch(regex string, s string) bool {
match, _ := regexp.MatchString(regex, s)
return match
}
func regexFindAll(regex string, s string, n int) []string {
r := regexp.MustCompile(regex)
return r.FindAllString(s, n)
}
func regexFind(regex string, s string) string {
r := regexp.MustCompile(regex)
return r.FindString(s)
}
func regexReplaceAll(regex string, s string, repl string) string {
r := regexp.MustCompile(regex)
return r.ReplaceAllString(s, repl)
}
func regexReplaceAllLiteral(regex string, s string, repl string) string {
r := regexp.MustCompile(regex)
return r.ReplaceAllLiteralString(s, repl)
}
func regexSplit(regex string, s string, n int) []string {
r := regexp.MustCompile(regex)
return r.Split(s, n)
}

View File

@@ -1,31 +0,0 @@
package sprig
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSemverCompare(t *testing.T) {
tests := map[string]string{
`{{ semverCompare "1.2.3" "1.2.3" }}`: `true`,
`{{ semverCompare "^1.2.0" "1.2.3" }}`: `true`,
`{{ semverCompare "^1.2.0" "2.2.3" }}`: `false`,
}
for tpl, expect := range tests {
assert.NoError(t, runt(tpl, expect))
}
}
func TestSemver(t *testing.T) {
tests := map[string]string{
`{{ $s := semver "1.2.3-beta.1+c0ff33" }}{{ $s.Prerelease }}`: "beta.1",
`{{ $s := semver "1.2.3-beta.1+c0ff33" }}{{ $s.Major}}`: "1",
`{{ semver "1.2.3" | (semver "1.2.3").Compare }}`: `0`,
`{{ semver "1.2.3" | (semver "1.3.3").Compare }}`: `1`,
`{{ semver "1.4.3" | (semver "1.2.3").Compare }}`: `-1`,
}
for tpl, expect := range tests {
assert.NoError(t, runt(tpl, expect))
}
}

View File

@@ -106,6 +106,10 @@ func indent(spaces int, v string) string {
return pad + strings.Replace(v, "\n", "\n"+pad, -1) return pad + strings.Replace(v, "\n", "\n"+pad, -1)
} }
func nindent(spaces int, v string) string {
return "\n" + indent(spaces, v)
}
func replace(old, new, src string) string { func replace(old, new, src string) string {
return strings.Replace(src, old, new, -1) return strings.Replace(src, old, new, -1)
} }

View File

@@ -1,220 +0,0 @@
package sprig
import (
"encoding/base32"
"encoding/base64"
"fmt"
"math/rand"
"testing"
"github.com/aokoli/goutils"
"github.com/stretchr/testify/assert"
)
func TestSubstr(t *testing.T) {
tpl := `{{"fooo" | substr 0 3 }}`
if err := runt(tpl, "foo"); err != nil {
t.Error(err)
}
}
func TestTrunc(t *testing.T) {
tpl := `{{ "foooooo" | trunc 3 }}`
if err := runt(tpl, "foo"); err != nil {
t.Error(err)
}
}
func TestQuote(t *testing.T) {
tpl := `{{quote "a" "b" "c"}}`
if err := runt(tpl, `"a" "b" "c"`); err != nil {
t.Error(err)
}
tpl = `{{quote "\"a\"" "b" "c"}}`
if err := runt(tpl, `"\"a\"" "b" "c"`); err != nil {
t.Error(err)
}
tpl = `{{quote 1 2 3 }}`
if err := runt(tpl, `"1" "2" "3"`); err != nil {
t.Error(err)
}
}
func TestSquote(t *testing.T) {
tpl := `{{squote "a" "b" "c"}}`
if err := runt(tpl, `'a' 'b' 'c'`); err != nil {
t.Error(err)
}
tpl = `{{squote 1 2 3 }}`
if err := runt(tpl, `'1' '2' '3'`); err != nil {
t.Error(err)
}
}
func TestContains(t *testing.T) {
// Mainly, we're just verifying the paramater order swap.
tests := []string{
`{{if contains "cat" "fair catch"}}1{{end}}`,
`{{if hasPrefix "cat" "catch"}}1{{end}}`,
`{{if hasSuffix "cat" "ducat"}}1{{end}}`,
}
for _, tt := range tests {
if err := runt(tt, "1"); err != nil {
t.Error(err)
}
}
}
func TestTrim(t *testing.T) {
tests := []string{
`{{trim " 5.00 "}}`,
`{{trimAll "$" "$5.00$"}}`,
`{{trimPrefix "$" "$5.00"}}`,
`{{trimSuffix "$" "5.00$"}}`,
}
for _, tt := range tests {
if err := runt(tt, "5.00"); err != nil {
t.Error(err)
}
}
}
func TestSplit(t *testing.T) {
tpl := `{{$v := "foo$bar$baz" | split "$"}}{{$v._0}}`
if err := runt(tpl, "foo"); err != nil {
t.Error(err)
}
}
func TestToString(t *testing.T) {
tpl := `{{ toString 1 | kindOf }}`
assert.NoError(t, runt(tpl, "string"))
}
func TestToStrings(t *testing.T) {
tpl := `{{ $s := list 1 2 3 | toStrings }}{{ index $s 1 | kindOf }}`
assert.NoError(t, runt(tpl, "string"))
}
func TestJoin(t *testing.T) {
assert.NoError(t, runt(`{{ tuple "a" "b" "c" | join "-" }}`, "a-b-c"))
assert.NoError(t, runt(`{{ tuple 1 2 3 | join "-" }}`, "1-2-3"))
assert.NoError(t, runtv(`{{ join "-" .V }}`, "a-b-c", map[string]interface{}{"V": []string{"a", "b", "c"}}))
assert.NoError(t, runtv(`{{ join "-" .V }}`, "abc", map[string]interface{}{"V": "abc"}))
assert.NoError(t, runtv(`{{ join "-" .V }}`, "1-2-3", map[string]interface{}{"V": []int{1, 2, 3}}))
}
func TestSortAlpha(t *testing.T) {
// Named `append` in the function map
tests := map[string]string{
`{{ list "c" "a" "b" | sortAlpha | join "" }}`: "abc",
`{{ list 2 1 4 3 | sortAlpha | join "" }}`: "1234",
}
for tpl, expect := range tests {
assert.NoError(t, runt(tpl, expect))
}
}
func TestBase64EncodeDecode(t *testing.T) {
magicWord := "coffee"
expect := base64.StdEncoding.EncodeToString([]byte(magicWord))
if expect == magicWord {
t.Fatal("Encoder doesn't work.")
}
tpl := `{{b64enc "coffee"}}`
if err := runt(tpl, expect); err != nil {
t.Error(err)
}
tpl = fmt.Sprintf("{{b64dec %q}}", expect)
if err := runt(tpl, magicWord); err != nil {
t.Error(err)
}
}
func TestBase32EncodeDecode(t *testing.T) {
magicWord := "coffee"
expect := base32.StdEncoding.EncodeToString([]byte(magicWord))
if expect == magicWord {
t.Fatal("Encoder doesn't work.")
}
tpl := `{{b32enc "coffee"}}`
if err := runt(tpl, expect); err != nil {
t.Error(err)
}
tpl = fmt.Sprintf("{{b32dec %q}}", expect)
if err := runt(tpl, magicWord); err != nil {
t.Error(err)
}
}
func TestGoutils(t *testing.T) {
tests := map[string]string{
`{{abbrev 5 "hello world"}}`: "he...",
`{{abbrevboth 5 10 "1234 5678 9123"}}`: "...5678...",
`{{nospace "h e l l o "}}`: "hello",
`{{untitle "First Try"}}`: "first try", //https://youtu.be/44-RsrF_V_w
`{{initials "First Try"}}`: "FT",
`{{wrap 5 "Hello World"}}`: "Hello\nWorld",
`{{wrapWith 5 "\t" "Hello World"}}`: "Hello\tWorld",
}
for k, v := range tests {
t.Log(k)
if err := runt(k, v); err != nil {
t.Errorf("Error on tpl %s: %s", err)
}
}
}
func TestRandom(t *testing.T) {
// One of the things I love about Go:
goutils.RANDOM = rand.New(rand.NewSource(1))
// Because we're using a random number generator, we need these to go in
// a predictable sequence:
if err := runt(`{{randAlphaNum 5}}`, "9bzRv"); err != nil {
t.Errorf("Error on tpl %s: %s", err)
}
if err := runt(`{{randAlpha 5}}`, "VjwGe"); err != nil {
t.Errorf("Error on tpl %s: %s", err)
}
if err := runt(`{{randAscii 5}}`, "1KA5p"); err != nil {
t.Errorf("Error on tpl %s: %s", err)
}
if err := runt(`{{randNumeric 5}}`, "26018"); err != nil {
t.Errorf("Error on tpl %s: %s", err)
}
}
func TestCat(t *testing.T) {
tpl := `{{$b := "b"}}{{"c" | cat "a" $b}}`
if err := runt(tpl, "a b c"); err != nil {
t.Error(err)
}
}
func TestIndent(t *testing.T) {
tpl := `{{indent 4 "a\nb\nc"}}`
if err := runt(tpl, " a\n b\n c"); err != nil {
t.Error(err)
}
}
func TestReplace(t *testing.T) {
tpl := `{{"I Am Henry VIII" | replace " " "-"}}`
if err := runt(tpl, "I-Am-Henry-VIII"); err != nil {
t.Error(err)
}
}
func TestPlural(t *testing.T) {
tpl := `{{$num := len "two"}}{{$num}} {{$num | plural "1 char" "chars"}}`
if err := runt(tpl, "3 chars"); err != nil {
t.Error(err)
}
tpl = `{{len "t" | plural "cheese" "%d chars"}}`
if err := runt(tpl, "cheese"); err != nil {
t.Error(err)
}
}

View File

@@ -1,18 +0,0 @@
language: go
go:
- 1.6
- 1.7
- 1.8
- tip
script:
- go test -v
notifications:
webhooks:
urls:
- https://webhooks.gitter.im/e/06e3328629952dabe3e0
on_success: change # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: never # options: [always|never|change] default: always

View File

@@ -1,78 +0,0 @@
package goutils
import (
"fmt"
"math/rand"
"testing"
)
// ****************************** TESTS ********************************************
func TestRandomSeed(t *testing.T) {
// count, start, end, letters, numbers := 5, 0, 0, true, true
random := rand.New(rand.NewSource(10))
out := "3ip9v"
// Test 1: Simulating RandomAlphaNumeric(count int)
if x, _ := RandomSeed(5, 0, 0, true, true, nil, random); x != out {
t.Errorf("RandomSeed(%v, %v, %v, %v, %v, %v, %v) = %v, want %v", 5, 0, 0, true, true, nil, random, x, out)
}
// Test 2: Simulating RandomAlphabetic(count int)
out = "MBrbj"
if x, _ := RandomSeed(5, 0, 0, true, false, nil, random); x != out {
t.Errorf("RandomSeed(%v, %v, %v, %v, %v, %v, %v) = %v, want %v", 5, 0, 0, true, false, nil, random, x, out)
}
// Test 3: Simulating RandomNumeric(count int)
out = "88935"
if x, _ := RandomSeed(5, 0, 0, false, true, nil, random); x != out {
t.Errorf("RandomSeed(%v, %v, %v, %v, %v, %v, %v) = %v, want %v", 5, 0, 0, false, true, nil, random, x, out)
}
// Test 4: Simulating RandomAscii(count int)
out = "H_I;E"
if x, _ := RandomSeed(5, 32, 127, false, false, nil, random); x != out {
t.Errorf("RandomSeed(%v, %v, %v, %v, %v, %v, %v) = %v, want %v", 5, 32, 127, false, false, nil, random, x, out)
}
// Test 5: Simulating RandomSeed(...) with custom chars
chars := []rune{'1', '2', '3', 'a', 'b', 'c'}
out = "2b2ca"
if x, _ := RandomSeed(5, 0, 0, false, false, chars, random); x != out {
t.Errorf("RandomSeed(%v, %v, %v, %v, %v, %v, %v) = %v, want %v", 5, 0, 0, false, false, chars, random, x, out)
}
}
// ****************************** EXAMPLES ********************************************
func ExampleRandomSeed() {
var seed int64 = 10 // If you change this seed #, the random sequence below will change
random := rand.New(rand.NewSource(seed))
chars := []rune{'1', '2', '3', 'a', 'b', 'c'}
rand1, _ := RandomSeed(5, 0, 0, true, true, nil, random) // RandomAlphaNumeric (Alphabets and numbers possible)
rand2, _ := RandomSeed(5, 0, 0, true, false, nil, random) // RandomAlphabetic (Only alphabets)
rand3, _ := RandomSeed(5, 0, 0, false, true, nil, random) // RandomNumeric (Only numbers)
rand4, _ := RandomSeed(5, 32, 127, false, false, nil, random) // RandomAscii (Alphabets, numbers, and other ASCII chars)
rand5, _ := RandomSeed(5, 0, 0, true, true, chars, random) // RandomSeed with custom characters
fmt.Println(rand1)
fmt.Println(rand2)
fmt.Println(rand3)
fmt.Println(rand4)
fmt.Println(rand5)
// Output:
// 3ip9v
// MBrbj
// 88935
// H_I;E
// 2b2ca
}

View File

@@ -1,309 +0,0 @@
package goutils
import (
"fmt"
"testing"
)
// ****************************** TESTS ********************************************
func TestAbbreviate(t *testing.T) {
// Test 1
in := "abcdefg"
out := "abc..."
maxWidth := 6
if x, _ := Abbreviate(in, maxWidth); x != out {
t.Errorf("Abbreviate(%v, %v) = %v, want %v", in, maxWidth, x, out)
}
// Test 2
out = "abcdefg"
maxWidth = 7
if x, _ := Abbreviate(in, maxWidth); x != out {
t.Errorf("Abbreviate(%v, %v) = %v, want %v", in, maxWidth, x, out)
}
// Test 3
out = "a..."
maxWidth = 4
if x, _ := Abbreviate(in, maxWidth); x != out {
t.Errorf("Abbreviate(%v, %v) = %v, want %v", in, maxWidth, x, out)
}
}
func TestAbbreviateFull(t *testing.T) {
// Test 1
in := "abcdefghijklmno"
out := "abcdefg..."
offset := -1
maxWidth := 10
if x, _ := AbbreviateFull(in, offset, maxWidth); x != out {
t.Errorf("AbbreviateFull(%v, %v, %v) = %v, want %v", in, offset, maxWidth, x, out)
}
// Test 2
out = "...fghi..."
offset = 5
maxWidth = 10
if x, _ := AbbreviateFull(in, offset, maxWidth); x != out {
t.Errorf("AbbreviateFull(%v, %v, %v) = %v, want %v", in, offset, maxWidth, x, out)
}
// Test 3
out = "...ijklmno"
offset = 12
maxWidth = 10
if x, _ := AbbreviateFull(in, offset, maxWidth); x != out {
t.Errorf("AbbreviateFull(%v, %v, %v) = %v, want %v", in, offset, maxWidth, x, out)
}
}
func TestIndexOf(t *testing.T) {
// Test 1
str := "abcafgka"
sub := "a"
start := 0
out := 0
if x := IndexOf(str, sub, start); x != out {
t.Errorf("IndexOf(%v, %v, %v) = %v, want %v", str, sub, start, x, out)
}
// Test 2
start = 1
out = 3
if x := IndexOf(str, sub, start); x != out {
t.Errorf("IndexOf(%v, %v, %v) = %v, want %v", str, sub, start, x, out)
}
// Test 3
start = 4
out = 7
if x := IndexOf(str, sub, start); x != out {
t.Errorf("IndexOf(%v, %v, %v) = %v, want %v", str, sub, start, x, out)
}
// Test 4
sub = "z"
out = -1
if x := IndexOf(str, sub, start); x != out {
t.Errorf("IndexOf(%v, %v, %v) = %v, want %v", str, sub, start, x, out)
}
}
func TestIsBlank(t *testing.T) {
// Test 1
str := ""
out := true
if x := IsBlank(str); x != out {
t.Errorf("IndexOf(%v) = %v, want %v", str, x, out)
}
// Test 2
str = " "
out = true
if x := IsBlank(str); x != out {
t.Errorf("IndexOf(%v) = %v, want %v", str, x, out)
}
// Test 3
str = " abc "
out = false
if x := IsBlank(str); x != out {
t.Errorf("IndexOf(%v) = %v, want %v", str, x, out)
}
}
func TestDeleteWhiteSpace(t *testing.T) {
// Test 1
str := " a b c "
out := "abc"
if x := DeleteWhiteSpace(str); x != out {
t.Errorf("IndexOf(%v) = %v, want %v", str, x, out)
}
// Test 2
str = " "
out = ""
if x := DeleteWhiteSpace(str); x != out {
t.Errorf("IndexOf(%v) = %v, want %v", str, x, out)
}
}
func TestIndexOfDifference(t *testing.T) {
str1 := "abc"
str2 := "a_c"
out := 1
if x := IndexOfDifference(str1, str2); x != out {
t.Errorf("IndexOfDifference(%v, %v) = %v, want %v", str1, str2, x, out)
}
}
// ****************************** EXAMPLES ********************************************
func ExampleAbbreviate() {
str := "abcdefg"
out1, _ := Abbreviate(str, 6)
out2, _ := Abbreviate(str, 7)
out3, _ := Abbreviate(str, 8)
out4, _ := Abbreviate(str, 4)
_, err1 := Abbreviate(str, 3)
fmt.Println(out1)
fmt.Println(out2)
fmt.Println(out3)
fmt.Println(out4)
fmt.Println(err1)
// Output:
// abc...
// abcdefg
// abcdefg
// a...
// stringutils illegal argument: Minimum abbreviation width is 4
}
func ExampleAbbreviateFull() {
str := "abcdefghijklmno"
str2 := "abcdefghij"
out1, _ := AbbreviateFull(str, -1, 10)
out2, _ := AbbreviateFull(str, 0, 10)
out3, _ := AbbreviateFull(str, 1, 10)
out4, _ := AbbreviateFull(str, 4, 10)
out5, _ := AbbreviateFull(str, 5, 10)
out6, _ := AbbreviateFull(str, 6, 10)
out7, _ := AbbreviateFull(str, 8, 10)
out8, _ := AbbreviateFull(str, 10, 10)
out9, _ := AbbreviateFull(str, 12, 10)
_, err1 := AbbreviateFull(str2, 0, 3)
_, err2 := AbbreviateFull(str2, 5, 6)
fmt.Println(out1)
fmt.Println(out2)
fmt.Println(out3)
fmt.Println(out4)
fmt.Println(out5)
fmt.Println(out6)
fmt.Println(out7)
fmt.Println(out8)
fmt.Println(out9)
fmt.Println(err1)
fmt.Println(err2)
// Output:
// abcdefg...
// abcdefg...
// abcdefg...
// abcdefg...
// ...fghi...
// ...ghij...
// ...ijklmno
// ...ijklmno
// ...ijklmno
// stringutils illegal argument: Minimum abbreviation width is 4
// stringutils illegal argument: Minimum abbreviation width with offset is 7
}
func ExampleIsBlank() {
out1 := IsBlank("")
out2 := IsBlank(" ")
out3 := IsBlank("bob")
out4 := IsBlank(" bob ")
fmt.Println(out1)
fmt.Println(out2)
fmt.Println(out3)
fmt.Println(out4)
// Output:
// true
// true
// false
// false
}
func ExampleDeleteWhiteSpace() {
out1 := DeleteWhiteSpace(" ")
out2 := DeleteWhiteSpace("bob")
out3 := DeleteWhiteSpace("bob ")
out4 := DeleteWhiteSpace(" b o b ")
fmt.Println(out1)
fmt.Println(out2)
fmt.Println(out3)
fmt.Println(out4)
// Output:
//
// bob
// bob
// bob
}
func ExampleIndexOf() {
str := "abcdefgehije"
out1 := IndexOf(str, "e", 0)
out2 := IndexOf(str, "e", 5)
out3 := IndexOf(str, "e", 8)
out4 := IndexOf(str, "eh", 0)
out5 := IndexOf(str, "eh", 22)
out6 := IndexOf(str, "z", 0)
out7 := IndexOf(str, "", 0)
fmt.Println(out1)
fmt.Println(out2)
fmt.Println(out3)
fmt.Println(out4)
fmt.Println(out5)
fmt.Println(out6)
fmt.Println(out7)
// Output:
// 4
// 7
// 11
// 7
// -1
// -1
// -1
}
func ExampleIndexOfDifference() {
out1 := IndexOfDifference("abc", "abc")
out2 := IndexOfDifference("ab", "abxyz")
out3 := IndexOfDifference("", "abc")
out4 := IndexOfDifference("abcde", "abxyz")
fmt.Println(out1)
fmt.Println(out2)
fmt.Println(out3)
fmt.Println(out4)
// Output:
// -1
// 2
// 0
// 2
}

View File

@@ -1,225 +0,0 @@
package goutils
import (
"fmt"
"testing"
)
// ****************************** TESTS ********************************************
func TestWrapNormalWord(t *testing.T) {
in := "Bob Manuel Bob Manuel"
out := "Bob Manuel\nBob Manuel"
wrapLength := 10
if x := Wrap(in, wrapLength); x != out {
t.Errorf("Wrap(%v) = %v, want %v", in, x, out)
}
}
func TestWrapCustomLongWordFalse(t *testing.T) {
in := "BobManuelBob Bob"
out := "BobManuelBob<br\\>Bob"
wrapLength := 10
newLineStr := "<br\\>"
wrapLongWords := false
if x := WrapCustom(in, wrapLength, newLineStr, wrapLongWords); x != out {
t.Errorf("Wrap(%v) = %v, want %v", in, x, out)
}
}
func TestWrapCustomLongWordTrue(t *testing.T) {
in := "BobManuelBob Bob"
out := "BobManuelB<br\\>ob Bob"
wrapLength := 10
newLineStr := "<br\\>"
wrapLongWords := true
if x := WrapCustom(in, wrapLength, newLineStr, wrapLongWords); x != out {
t.Errorf("WrapCustom(%v) = %v, want %v", in, x, out)
}
}
func TestCapitalize(t *testing.T) {
// Test 1: Checks if function works with 1 parameter, and default whitespace delimiter
in := "test is going.well.thank.you.for inquiring"
out := "Test Is Going.well.thank.you.for Inquiring"
if x := Capitalize(in); x != out {
t.Errorf("Capitalize(%v) = %v, want %v", in, x, out)
}
// Test 2: Checks if function works with both parameters, with param 2 containing whitespace and '.'
out = "Test Is Going.Well.Thank.You.For Inquiring"
delimiters := []rune{' ', '.'}
if x := Capitalize(in, delimiters...); x != out {
t.Errorf("Capitalize(%v) = %v, want %v", in, x, out)
}
}
func TestCapitalizeFully(t *testing.T) {
// Test 1
in := "tEsT iS goiNG.wELL.tHaNk.yOU.for inqUIrING"
out := "Test Is Going.well.thank.you.for Inquiring"
if x := CapitalizeFully(in); x != out {
t.Errorf("CapitalizeFully(%v) = %v, want %v", in, x, out)
}
// Test 2
out = "Test Is Going.Well.Thank.You.For Inquiring"
delimiters := []rune{' ', '.'}
if x := CapitalizeFully(in, delimiters...); x != out {
t.Errorf("CapitalizeFully(%v) = %v, want %v", in, x, out)
}
}
func TestUncapitalize(t *testing.T) {
// Test 1: Checks if function works with 1 parameter, and default whitespace delimiter
in := "This Is A.Test"
out := "this is a.Test"
if x := Uncapitalize(in); x != out {
t.Errorf("Uncapitalize(%v) = %v, want %v", in, x, out)
}
// Test 2: Checks if function works with both parameters, with param 2 containing whitespace and '.'
out = "this is a.test"
delimiters := []rune{' ', '.'}
if x := Uncapitalize(in, delimiters...); x != out {
t.Errorf("Uncapitalize(%v) = %v, want %v", in, x, out)
}
}
func TestSwapCase(t *testing.T) {
in := "This Is A.Test"
out := "tHIS iS a.tEST"
if x := SwapCase(in); x != out {
t.Errorf("SwapCase(%v) = %v, want %v", in, x, out)
}
}
func TestInitials(t *testing.T) {
// Test 1
in := "John Doe.Ray"
out := "JD"
if x := Initials(in); x != out {
t.Errorf("Initials(%v) = %v, want %v", in, x, out)
}
// Test 2
out = "JDR"
delimiters := []rune{' ', '.'}
if x := Initials(in, delimiters...); x != out {
t.Errorf("Initials(%v) = %v, want %v", in, x, out)
}
}
// ****************************** EXAMPLES ********************************************
func ExampleWrap() {
in := "Bob Manuel Bob Manuel"
wrapLength := 10
fmt.Println(Wrap(in, wrapLength))
// Output:
// Bob Manuel
// Bob Manuel
}
func ExampleWrapCustom_1() {
in := "BobManuelBob Bob"
wrapLength := 10
newLineStr := "<br\\>"
wrapLongWords := false
fmt.Println(WrapCustom(in, wrapLength, newLineStr, wrapLongWords))
// Output:
// BobManuelBob<br\>Bob
}
func ExampleWrapCustom_2() {
in := "BobManuelBob Bob"
wrapLength := 10
newLineStr := "<br\\>"
wrapLongWords := true
fmt.Println(WrapCustom(in, wrapLength, newLineStr, wrapLongWords))
// Output:
// BobManuelB<br\>ob Bob
}
func ExampleCapitalize() {
in := "test is going.well.thank.you.for inquiring" // Compare input to CapitalizeFully example
delimiters := []rune{' ', '.'}
fmt.Println(Capitalize(in))
fmt.Println(Capitalize(in, delimiters...))
// Output:
// Test Is Going.well.thank.you.for Inquiring
// Test Is Going.Well.Thank.You.For Inquiring
}
func ExampleCapitalizeFully() {
in := "tEsT iS goiNG.wELL.tHaNk.yOU.for inqUIrING" // Notice scattered capitalization
delimiters := []rune{' ', '.'}
fmt.Println(CapitalizeFully(in))
fmt.Println(CapitalizeFully(in, delimiters...))
// Output:
// Test Is Going.well.thank.you.for Inquiring
// Test Is Going.Well.Thank.You.For Inquiring
}
func ExampleUncapitalize() {
in := "This Is A.Test"
delimiters := []rune{' ', '.'}
fmt.Println(Uncapitalize(in))
fmt.Println(Uncapitalize(in, delimiters...))
// Output:
// this is a.Test
// this is a.test
}
func ExampleSwapCase() {
in := "This Is A.Test"
fmt.Println(SwapCase(in))
// Output:
// tHIS iS a.tEST
}
func ExampleInitials() {
in := "John Doe.Ray"
delimiters := []rune{' ', '.'}
fmt.Println(Initials(in))
fmt.Println(Initials(in, delimiters...))
// Output:
// JD
// JDR
}

View File

@@ -1,4 +0,0 @@
.DS_Store
bin

View File

@@ -1,8 +0,0 @@
language: go
go:
- 1.3
- 1.4
- 1.5
- 1.6
- tip

View File

@@ -1,13 +0,0 @@
`jwt` command-line tool
=======================
This is a simple tool to sign, verify and show JSON Web Tokens from
the command line.
The following will create and sign a token, then verify it and output the original claims:
echo {\"foo\":\"bar\"} | bin/jwt -key test/sample_key -alg RS256 -sign - | bin/jwt -key test/sample_key.pub -verify -
To simply display a token, use:
echo $JWT | jwt -show -

View File

@@ -1,245 +0,0 @@
// A useful example app. You can use this to debug your tokens on the command line.
// This is also a great place to look at how you might use this library.
//
// Example usage:
// The following will create and sign a token, then verify it and output the original claims.
// echo {\"foo\":\"bar\"} | bin/jwt -key test/sample_key -alg RS256 -sign - | bin/jwt -key test/sample_key.pub -verify -
package main
import (
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"regexp"
"strings"
jwt "github.com/dgrijalva/jwt-go"
)
var (
// Options
flagAlg = flag.String("alg", "", "signing algorithm identifier")
flagKey = flag.String("key", "", "path to key file or '-' to read from stdin")
flagCompact = flag.Bool("compact", false, "output compact JSON")
flagDebug = flag.Bool("debug", false, "print out all kinds of debug data")
// Modes - exactly one of these is required
flagSign = flag.String("sign", "", "path to claims object to sign or '-' to read from stdin")
flagVerify = flag.String("verify", "", "path to JWT token to verify or '-' to read from stdin")
flagShow = flag.String("show", "", "path to JWT file or '-' to read from stdin")
)
func main() {
// Usage message if you ask for -help or if you mess up inputs.
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
fmt.Fprintf(os.Stderr, " One of the following flags is required: sign, verify\n")
flag.PrintDefaults()
}
// Parse command line options
flag.Parse()
// Do the thing. If something goes wrong, print error to stderr
// and exit with a non-zero status code
if err := start(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
// Figure out which thing to do and then do that
func start() error {
if *flagSign != "" {
return signToken()
} else if *flagVerify != "" {
return verifyToken()
} else if *flagShow != "" {
return showToken()
} else {
flag.Usage()
return fmt.Errorf("None of the required flags are present. What do you want me to do?")
}
}
// Helper func: Read input from specified file or stdin
func loadData(p string) ([]byte, error) {
if p == "" {
return nil, fmt.Errorf("No path specified")
}
var rdr io.Reader
if p == "-" {
rdr = os.Stdin
} else {
if f, err := os.Open(p); err == nil {
rdr = f
defer f.Close()
} else {
return nil, err
}
}
return ioutil.ReadAll(rdr)
}
// Print a json object in accordance with the prophecy (or the command line options)
func printJSON(j interface{}) error {
var out []byte
var err error
if *flagCompact == false {
out, err = json.MarshalIndent(j, "", " ")
} else {
out, err = json.Marshal(j)
}
if err == nil {
fmt.Println(string(out))
}
return err
}
// Verify a token and output the claims. This is a great example
// of how to verify and view a token.
func verifyToken() error {
// get the token
tokData, err := loadData(*flagVerify)
if err != nil {
return fmt.Errorf("Couldn't read token: %v", err)
}
// trim possible whitespace from token
tokData = regexp.MustCompile(`\s*$`).ReplaceAll(tokData, []byte{})
if *flagDebug {
fmt.Fprintf(os.Stderr, "Token len: %v bytes\n", len(tokData))
}
// Parse the token. Load the key from command line option
token, err := jwt.Parse(string(tokData), func(t *jwt.Token) (interface{}, error) {
data, err := loadData(*flagKey)
if err != nil {
return nil, err
}
if isEs() {
return jwt.ParseECPublicKeyFromPEM(data)
}
return data, nil
})
// Print some debug data
if *flagDebug && token != nil {
fmt.Fprintf(os.Stderr, "Header:\n%v\n", token.Header)
fmt.Fprintf(os.Stderr, "Claims:\n%v\n", token.Claims)
}
// Print an error if we can't parse for some reason
if err != nil {
return fmt.Errorf("Couldn't parse token: %v", err)
}
// Is token invalid?
if !token.Valid {
return fmt.Errorf("Token is invalid")
}
// Print the token details
if err := printJSON(token.Claims); err != nil {
return fmt.Errorf("Failed to output claims: %v", err)
}
return nil
}
// Create, sign, and output a token. This is a great, simple example of
// how to use this library to create and sign a token.
func signToken() error {
// get the token data from command line arguments
tokData, err := loadData(*flagSign)
if err != nil {
return fmt.Errorf("Couldn't read token: %v", err)
} else if *flagDebug {
fmt.Fprintf(os.Stderr, "Token: %v bytes", len(tokData))
}
// parse the JSON of the claims
var claims jwt.MapClaims
if err := json.Unmarshal(tokData, &claims); err != nil {
return fmt.Errorf("Couldn't parse claims JSON: %v", err)
}
// get the key
var key interface{}
key, err = loadData(*flagKey)
if err != nil {
return fmt.Errorf("Couldn't read key: %v", err)
}
// get the signing alg
alg := jwt.GetSigningMethod(*flagAlg)
if alg == nil {
return fmt.Errorf("Couldn't find signing method: %v", *flagAlg)
}
// create a new token
token := jwt.NewWithClaims(alg, claims)
if isEs() {
if k, ok := key.([]byte); !ok {
return fmt.Errorf("Couldn't convert key data to key")
} else {
key, err = jwt.ParseECPrivateKeyFromPEM(k)
if err != nil {
return err
}
}
}
if out, err := token.SignedString(key); err == nil {
fmt.Println(out)
} else {
return fmt.Errorf("Error signing token: %v", err)
}
return nil
}
// showToken pretty-prints the token on the command line.
func showToken() error {
// get the token
tokData, err := loadData(*flagShow)
if err != nil {
return fmt.Errorf("Couldn't read token: %v", err)
}
// trim possible whitespace from token
tokData = regexp.MustCompile(`\s*$`).ReplaceAll(tokData, []byte{})
if *flagDebug {
fmt.Fprintf(os.Stderr, "Token len: %v bytes\n", len(tokData))
}
token, err := jwt.Parse(string(tokData), nil)
if token == nil {
return fmt.Errorf("malformed token: %v", err)
}
// Print the token details
fmt.Println("Header:")
if err := printJSON(token.Header); err != nil {
return fmt.Errorf("Failed to output header: %v", err)
}
fmt.Println("Claims:")
if err := printJSON(token.Claims); err != nil {
return fmt.Errorf("Failed to output claims: %v", err)
}
return nil
}
func isEs() bool {
return strings.HasPrefix(*flagAlg, "ES")
}

View File

@@ -1,100 +0,0 @@
package jwt_test
import (
"crypto/ecdsa"
"io/ioutil"
"strings"
"testing"
"github.com/dgrijalva/jwt-go"
)
var ecdsaTestData = []struct {
name string
keys map[string]string
tokenString string
alg string
claims map[string]interface{}
valid bool
}{
{
"Basic ES256",
map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"},
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJmb28iOiJiYXIifQ.feG39E-bn8HXAKhzDZq7yEAPWYDhZlwTn3sePJnU9VrGMmwdXAIEyoOnrjreYlVM_Z4N13eK9-TmMTWyfKJtHQ",
"ES256",
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic ES384",
map[string]string{"private": "test/ec384-private.pem", "public": "test/ec384-public.pem"},
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJmb28iOiJiYXIifQ.ngAfKMbJUh0WWubSIYe5GMsA-aHNKwFbJk_wq3lq23aPp8H2anb1rRILIzVR0gUf4a8WzDtrzmiikuPWyCS6CN4-PwdgTk-5nehC7JXqlaBZU05p3toM3nWCwm_LXcld",
"ES384",
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic ES512",
map[string]string{"private": "test/ec512-private.pem", "public": "test/ec512-public.pem"},
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJmb28iOiJiYXIifQ.AAU0TvGQOcdg2OvrwY73NHKgfk26UDekh9Prz-L_iWuTBIBqOFCWwwLsRiHB1JOddfKAls5do1W0jR_F30JpVd-6AJeTjGKA4C1A1H6gIKwRY0o_tFDIydZCl_lMBMeG5VNFAjO86-WCSKwc3hqaGkq1MugPRq_qrF9AVbuEB4JPLyL5",
"ES512",
map[string]interface{}{"foo": "bar"},
true,
},
{
"basic ES256 invalid: foo => bar",
map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"},
"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MEQCIHoSJnmGlPaVQDqacx_2XlXEhhqtWceVopjomc2PJLtdAiAUTeGPoNYxZw0z8mgOnnIcjoxRuNDVZvybRZF3wR1l8W",
"ES256",
map[string]interface{}{"foo": "bar"},
false,
},
}
func TestECDSAVerify(t *testing.T) {
for _, data := range ecdsaTestData {
var err error
key, _ := ioutil.ReadFile(data.keys["public"])
var ecdsaKey *ecdsa.PublicKey
if ecdsaKey, err = jwt.ParseECPublicKeyFromPEM(key); err != nil {
t.Errorf("Unable to parse ECDSA public key: %v", err)
}
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
err = method.Verify(strings.Join(parts[0:2], "."), parts[2], ecdsaKey)
if data.valid && err != nil {
t.Errorf("[%v] Error while verifying key: %v", data.name, err)
}
if !data.valid && err == nil {
t.Errorf("[%v] Invalid key passed validation", data.name)
}
}
}
func TestECDSASign(t *testing.T) {
for _, data := range ecdsaTestData {
var err error
key, _ := ioutil.ReadFile(data.keys["private"])
var ecdsaKey *ecdsa.PrivateKey
if ecdsaKey, err = jwt.ParseECPrivateKeyFromPEM(key); err != nil {
t.Errorf("Unable to parse ECDSA private key: %v", err)
}
if data.valid {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
sig, err := method.Sign(strings.Join(parts[0:2], "."), ecdsaKey)
if err != nil {
t.Errorf("[%v] Error signing token: %v", data.name, err)
}
if sig == parts[2] {
t.Errorf("[%v] Identical signatures\nbefore:\n%v\nafter:\n%v", data.name, parts[2], sig)
}
}
}
}

View File

@@ -1,114 +0,0 @@
package jwt_test
import (
"fmt"
"github.com/dgrijalva/jwt-go"
"time"
)
// Example (atypical) using the StandardClaims type by itself to parse a token.
// The StandardClaims type is designed to be embedded into your custom types
// to provide standard validation features. You can use it alone, but there's
// no way to retrieve other fields after parsing.
// See the CustomClaimsType example for intended usage.
func ExampleNewWithClaims_standardClaims() {
mySigningKey := []byte("AllYourBase")
// Create the Claims
claims := &jwt.StandardClaims{
ExpiresAt: 15000,
Issuer: "test",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, err := token.SignedString(mySigningKey)
fmt.Printf("%v %v", ss, err)
//Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.QsODzZu3lUZMVdhbO76u3Jv02iYCvEHcYVUI1kOWEU0 <nil>
}
// Example creating a token using a custom claims type. The StandardClaim is embedded
// in the custom type to allow for easy encoding, parsing and validation of standard claims.
func ExampleNewWithClaims_customClaimsType() {
mySigningKey := []byte("AllYourBase")
type MyCustomClaims struct {
Foo string `json:"foo"`
jwt.StandardClaims
}
// Create the Claims
claims := MyCustomClaims{
"bar",
jwt.StandardClaims{
ExpiresAt: 15000,
Issuer: "test",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, err := token.SignedString(mySigningKey)
fmt.Printf("%v %v", ss, err)
//Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c <nil>
}
// Example creating a token using a custom claims type. The StandardClaim is embedded
// in the custom type to allow for easy encoding, parsing and validation of standard claims.
func ExampleParseWithClaims_customClaimsType() {
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
type MyCustomClaims struct {
Foo string `json:"foo"`
jwt.StandardClaims
}
// sample token is expired. override time so it parses as valid
at(time.Unix(0, 0), func() {
token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("AllYourBase"), nil
})
if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid {
fmt.Printf("%v %v", claims.Foo, claims.StandardClaims.ExpiresAt)
} else {
fmt.Println(err)
}
})
// Output: bar 15000
}
// Override time value for tests. Restore default value after.
func at(t time.Time, f func()) {
jwt.TimeFunc = func() time.Time {
return t
}
f()
jwt.TimeFunc = time.Now
}
// An example of parsing the error types using bitfield checks
func ExampleParse_errorChecking() {
// Token from another example. This token is expired
var tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte("AllYourBase"), nil
})
if token.Valid {
fmt.Println("You look nice today")
} else if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
fmt.Println("That's not even a token")
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
// Token is either expired or not active yet
fmt.Println("Timing is everything")
} else {
fmt.Println("Couldn't handle this token:", err)
}
} else {
fmt.Println("Couldn't handle this token:", err)
}
// Output: Timing is everything
}

View File

@@ -1,64 +0,0 @@
package jwt_test
import (
"fmt"
"github.com/dgrijalva/jwt-go"
"io/ioutil"
"time"
)
// For HMAC signing method, the key can be any []byte. It is recommended to generate
// a key using crypto/rand or something equivalent. You need the same key for signing
// and validating.
var hmacSampleSecret []byte
func init() {
// Load sample key data
if keyData, e := ioutil.ReadFile("test/hmacTestKey"); e == nil {
hmacSampleSecret = keyData
} else {
panic(e)
}
}
// Example creating, signing, and encoding a JWT token using the HMAC signing method
func ExampleNew_hmac() {
// Create a new token object, specifying signing method and the claims
// you would like it to contain.
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"foo": "bar",
"nbf": time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
})
// Sign and get the complete encoded token as a string using the secret
tokenString, err := token.SignedString(hmacSampleSecret)
fmt.Println(tokenString, err)
// Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJuYmYiOjE0NDQ0Nzg0MDB9.u1riaD1rW97opCoAuRCTy4w58Br-Zk-bh7vLiRIsrpU <nil>
}
// Example parsing and validating a token using the HMAC signing method
func ExampleParse_hmac() {
// sample token string taken from the New example
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJuYmYiOjE0NDQ0Nzg0MDB9.u1riaD1rW97opCoAuRCTy4w58Br-Zk-bh7vLiRIsrpU"
// Parse takes the token string and a function for looking up the key. The latter is especially
// useful if you use multiple keys for your application. The standard is to use 'kid' in the
// head of the token to identify which key to use, but the parsed token (head and claims) is provided
// to the callback, providing flexibility.
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return hmacSampleSecret, nil
})
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
fmt.Println(claims["foo"], claims["nbf"])
} else {
fmt.Println(err)
}
// Output: bar 1.4444784e+09
}

View File

@@ -1,91 +0,0 @@
package jwt_test
import (
"github.com/dgrijalva/jwt-go"
"io/ioutil"
"strings"
"testing"
)
var hmacTestData = []struct {
name string
tokenString string
alg string
claims map[string]interface{}
valid bool
}{
{
"web sample",
"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
"HS256",
map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
true,
},
{
"HS384",
"eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJleHAiOjEuMzAwODE5MzhlKzA5LCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIn0.KWZEuOD5lbBxZ34g7F-SlVLAQ_r5KApWNWlZIIMyQVz5Zs58a7XdNzj5_0EcNoOy",
"HS384",
map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
true,
},
{
"HS512",
"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEuMzAwODE5MzhlKzA5LCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIn0.CN7YijRX6Aw1n2jyI2Id1w90ja-DEMYiWixhYCyHnrZ1VfJRaFQz1bEbjjA5Fn4CLYaUG432dEYmSbS4Saokmw",
"HS512",
map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
true,
},
{
"web sample: invalid",
"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXo",
"HS256",
map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true},
false,
},
}
// Sample data from http://tools.ietf.org/html/draft-jones-json-web-signature-04#appendix-A.1
var hmacTestKey, _ = ioutil.ReadFile("test/hmacTestKey")
func TestHMACVerify(t *testing.T) {
for _, data := range hmacTestData {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
err := method.Verify(strings.Join(parts[0:2], "."), parts[2], hmacTestKey)
if data.valid && err != nil {
t.Errorf("[%v] Error while verifying key: %v", data.name, err)
}
if !data.valid && err == nil {
t.Errorf("[%v] Invalid key passed validation", data.name)
}
}
}
func TestHMACSign(t *testing.T) {
for _, data := range hmacTestData {
if data.valid {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
sig, err := method.Sign(strings.Join(parts[0:2], "."), hmacTestKey)
if err != nil {
t.Errorf("[%v] Error signing token: %v", data.name, err)
}
if sig != parts[2] {
t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", data.name, sig, parts[2])
}
}
}
}
func BenchmarkHS256Signing(b *testing.B) {
benchmarkSigning(b, jwt.SigningMethodHS256, hmacTestKey)
}
func BenchmarkHS384Signing(b *testing.B) {
benchmarkSigning(b, jwt.SigningMethodHS384, hmacTestKey)
}
func BenchmarkHS512Signing(b *testing.B) {
benchmarkSigning(b, jwt.SigningMethodHS512, hmacTestKey)
}

View File

@@ -1,216 +0,0 @@
package jwt_test
// Example HTTP auth using asymmetric crypto/RSA keys
// This is based on a (now outdated) example at https://gist.github.com/cryptix/45c33ecf0ae54828e63b
import (
"bytes"
"crypto/rsa"
"fmt"
"github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/request"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"net/url"
"strings"
"time"
)
// location of the files used for signing and verification
const (
privKeyPath = "test/sample_key" // openssl genrsa -out app.rsa keysize
pubKeyPath = "test/sample_key.pub" // openssl rsa -in app.rsa -pubout > app.rsa.pub
)
var (
verifyKey *rsa.PublicKey
signKey *rsa.PrivateKey
serverPort int
// storing sample username/password pairs
// don't do this on a real server
users = map[string]string{
"test": "known",
}
)
// read the key files before starting http handlers
func init() {
signBytes, err := ioutil.ReadFile(privKeyPath)
fatal(err)
signKey, err = jwt.ParseRSAPrivateKeyFromPEM(signBytes)
fatal(err)
verifyBytes, err := ioutil.ReadFile(pubKeyPath)
fatal(err)
verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyBytes)
fatal(err)
http.HandleFunc("/authenticate", authHandler)
http.HandleFunc("/restricted", restrictedHandler)
// Setup listener
listener, err := net.ListenTCP("tcp", &net.TCPAddr{})
serverPort = listener.Addr().(*net.TCPAddr).Port
log.Println("Listening...")
go func() {
fatal(http.Serve(listener, nil))
}()
}
var start func()
func fatal(err error) {
if err != nil {
log.Fatal(err)
}
}
// Define some custom types were going to use within our tokens
type CustomerInfo struct {
Name string
Kind string
}
type CustomClaimsExample struct {
*jwt.StandardClaims
TokenType string
CustomerInfo
}
func Example_getTokenViaHTTP() {
// See func authHandler for an example auth handler that produces a token
res, err := http.PostForm(fmt.Sprintf("http://localhost:%v/authenticate", serverPort), url.Values{
"user": {"test"},
"pass": {"known"},
})
if err != nil {
fatal(err)
}
if res.StatusCode != 200 {
fmt.Println("Unexpected status code", res.StatusCode)
}
// Read the token out of the response body
buf := new(bytes.Buffer)
io.Copy(buf, res.Body)
res.Body.Close()
tokenString := strings.TrimSpace(buf.String())
// Parse the token
token, err := jwt.ParseWithClaims(tokenString, &CustomClaimsExample{}, func(token *jwt.Token) (interface{}, error) {
// since we only use the one private key to sign the tokens,
// we also only use its public counter part to verify
return verifyKey, nil
})
fatal(err)
claims := token.Claims.(*CustomClaimsExample)
fmt.Println(claims.CustomerInfo.Name)
//Output: test
}
func Example_useTokenViaHTTP() {
// Make a sample token
// In a real world situation, this token will have been acquired from
// some other API call (see Example_getTokenViaHTTP)
token, err := createToken("foo")
fatal(err)
// Make request. See func restrictedHandler for example request processor
req, err := http.NewRequest("GET", fmt.Sprintf("http://localhost:%v/restricted", serverPort), nil)
fatal(err)
req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", token))
res, err := http.DefaultClient.Do(req)
fatal(err)
// Read the response body
buf := new(bytes.Buffer)
io.Copy(buf, res.Body)
res.Body.Close()
fmt.Println(buf.String())
// Output: Welcome, foo
}
func createToken(user string) (string, error) {
// create a signer for rsa 256
t := jwt.New(jwt.GetSigningMethod("RS256"))
// set our claims
t.Claims = &CustomClaimsExample{
&jwt.StandardClaims{
// set the expire time
// see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20#section-4.1.4
ExpiresAt: time.Now().Add(time.Minute * 1).Unix(),
},
"level1",
CustomerInfo{user, "human"},
}
// Creat token string
return t.SignedString(signKey)
}
// reads the form values, checks them and creates the token
func authHandler(w http.ResponseWriter, r *http.Request) {
// make sure its post
if r.Method != "POST" {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintln(w, "No POST", r.Method)
return
}
user := r.FormValue("user")
pass := r.FormValue("pass")
log.Printf("Authenticate: user[%s] pass[%s]\n", user, pass)
// check values
if user != "test" || pass != "known" {
w.WriteHeader(http.StatusForbidden)
fmt.Fprintln(w, "Wrong info")
return
}
tokenString, err := createToken(user)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintln(w, "Sorry, error while Signing Token!")
log.Printf("Token Signing error: %v\n", err)
return
}
w.Header().Set("Content-Type", "application/jwt")
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, tokenString)
}
// only accessible with a valid token
func restrictedHandler(w http.ResponseWriter, r *http.Request) {
// Get token from request
token, err := request.ParseFromRequestWithClaims(r, request.OAuth2Extractor, &CustomClaimsExample{}, func(token *jwt.Token) (interface{}, error) {
// since we only use the one private key to sign the tokens,
// we also only use its public counter part to verify
return verifyKey, nil
})
// If the token is missing or invalid, return error
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprintln(w, "Invalid token:", err)
return
}
// Token is valid
fmt.Fprintln(w, "Welcome,", token.Claims.(*CustomClaimsExample).Name)
return
}

View File

@@ -1,72 +0,0 @@
package jwt_test
import (
"github.com/dgrijalva/jwt-go"
"strings"
"testing"
)
var noneTestData = []struct {
name string
tokenString string
alg string
key interface{}
claims map[string]interface{}
valid bool
}{
{
"Basic",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.",
"none",
jwt.UnsafeAllowNoneSignatureType,
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic - no key",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.",
"none",
nil,
map[string]interface{}{"foo": "bar"},
false,
},
{
"Signed",
"eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.W-jEzRfBigtCWsinvVVuldiuilzVdU5ty0MvpLaSaqK9PlAWWlDQ1VIQ_qSKzwL5IXaZkvZFJXT3yL3n7OUVu7zCNJzdwznbC8Z-b0z2lYvcklJYi2VOFRcGbJtXUqgjk2oGsiqUMUMOLP70TTefkpsgqDxbRh9CDUfpOJgW-dU7cmgaoswe3wjUAUi6B6G2YEaiuXC0XScQYSYVKIzgKXJV8Zw-7AN_DBUI4GkTpsvQ9fVVjZM9csQiEXhYekyrKu1nu_POpQonGd8yqkIyXPECNmmqH5jH4sFiF67XhD7_JpkvLziBpI-uh86evBUadmHhb9Otqw3uV3NTaXLzJw",
"none",
jwt.UnsafeAllowNoneSignatureType,
map[string]interface{}{"foo": "bar"},
false,
},
}
func TestNoneVerify(t *testing.T) {
for _, data := range noneTestData {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
err := method.Verify(strings.Join(parts[0:2], "."), parts[2], data.key)
if data.valid && err != nil {
t.Errorf("[%v] Error while verifying key: %v", data.name, err)
}
if !data.valid && err == nil {
t.Errorf("[%v] Invalid key passed validation", data.name)
}
}
}
func TestNoneSign(t *testing.T) {
for _, data := range noneTestData {
if data.valid {
parts := strings.Split(data.tokenString, ".")
method := jwt.GetSigningMethod(data.alg)
sig, err := method.Sign(strings.Join(parts[0:2], "."), data.key)
if err != nil {
t.Errorf("[%v] Error signing token: %v", data.name, err)
}
if sig != parts[2] {
t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", data.name, sig, parts[2])
}
}
}
}

View File

@@ -1,261 +0,0 @@
package jwt_test
import (
"crypto/rsa"
"encoding/json"
"fmt"
"reflect"
"testing"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/test"
)
var keyFuncError error = fmt.Errorf("error loading key")
var (
jwtTestDefaultKey *rsa.PublicKey
defaultKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return jwtTestDefaultKey, nil }
emptyKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, nil }
errorKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, keyFuncError }
nilKeyFunc jwt.Keyfunc = nil
)
func init() {
jwtTestDefaultKey = test.LoadRSAPublicKeyFromDisk("test/sample_key.pub")
}
var jwtTestData = []struct {
name string
tokenString string
keyfunc jwt.Keyfunc
claims jwt.Claims
valid bool
errors uint32
parser *jwt.Parser
}{
{
"basic",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
defaultKeyFunc,
jwt.MapClaims{"foo": "bar"},
true,
0,
nil,
},
{
"basic expired",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "exp": float64(time.Now().Unix() - 100)},
false,
jwt.ValidationErrorExpired,
nil,
},
{
"basic nbf",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "nbf": float64(time.Now().Unix() + 100)},
false,
jwt.ValidationErrorNotValidYet,
nil,
},
{
"expired and nbf",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "nbf": float64(time.Now().Unix() + 100), "exp": float64(time.Now().Unix() - 100)},
false,
jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired,
nil,
},
{
"basic invalid",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
defaultKeyFunc,
jwt.MapClaims{"foo": "bar"},
false,
jwt.ValidationErrorSignatureInvalid,
nil,
},
{
"basic nokeyfunc",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
nilKeyFunc,
jwt.MapClaims{"foo": "bar"},
false,
jwt.ValidationErrorUnverifiable,
nil,
},
{
"basic nokey",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
emptyKeyFunc,
jwt.MapClaims{"foo": "bar"},
false,
jwt.ValidationErrorSignatureInvalid,
nil,
},
{
"basic errorkey",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
errorKeyFunc,
jwt.MapClaims{"foo": "bar"},
false,
jwt.ValidationErrorUnverifiable,
nil,
},
{
"invalid signing method",
"",
defaultKeyFunc,
jwt.MapClaims{"foo": "bar"},
false,
jwt.ValidationErrorSignatureInvalid,
&jwt.Parser{ValidMethods: []string{"HS256"}},
},
{
"valid signing method",
"",
defaultKeyFunc,
jwt.MapClaims{"foo": "bar"},
true,
0,
&jwt.Parser{ValidMethods: []string{"RS256", "HS256"}},
},
{
"JSON Number",
"",
defaultKeyFunc,
jwt.MapClaims{"foo": json.Number("123.4")},
true,
0,
&jwt.Parser{UseJSONNumber: true},
},
{
"Standard Claims",
"",
defaultKeyFunc,
&jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Second * 10).Unix(),
},
true,
0,
&jwt.Parser{UseJSONNumber: true},
},
{
"JSON Number - basic expired",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "exp": json.Number(fmt.Sprintf("%v", time.Now().Unix()-100))},
false,
jwt.ValidationErrorExpired,
&jwt.Parser{UseJSONNumber: true},
},
{
"JSON Number - basic nbf",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100))},
false,
jwt.ValidationErrorNotValidYet,
&jwt.Parser{UseJSONNumber: true},
},
{
"JSON Number - expired and nbf",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100)), "exp": json.Number(fmt.Sprintf("%v", time.Now().Unix()-100))},
false,
jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired,
&jwt.Parser{UseJSONNumber: true},
},
{
"SkipClaimsValidation during token parsing",
"", // autogen
defaultKeyFunc,
jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100))},
true,
0,
&jwt.Parser{UseJSONNumber: true, SkipClaimsValidation: true},
},
}
func TestParser_Parse(t *testing.T) {
privateKey := test.LoadRSAPrivateKeyFromDisk("test/sample_key")
// Iterate over test data set and run tests
for _, data := range jwtTestData {
// If the token string is blank, use helper function to generate string
if data.tokenString == "" {
data.tokenString = test.MakeSampleToken(data.claims, privateKey)
}
// Parse the token
var token *jwt.Token
var err error
var parser = data.parser
if parser == nil {
parser = new(jwt.Parser)
}
// Figure out correct claims type
switch data.claims.(type) {
case jwt.MapClaims:
token, err = parser.ParseWithClaims(data.tokenString, jwt.MapClaims{}, data.keyfunc)
case *jwt.StandardClaims:
token, err = parser.ParseWithClaims(data.tokenString, &jwt.StandardClaims{}, data.keyfunc)
}
// Verify result matches expectation
if !reflect.DeepEqual(data.claims, token.Claims) {
t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims)
}
if data.valid && err != nil {
t.Errorf("[%v] Error while verifying token: %T:%v", data.name, err, err)
}
if !data.valid && err == nil {
t.Errorf("[%v] Invalid token passed validation", data.name)
}
if (err == nil && !token.Valid) || (err != nil && token.Valid) {
t.Errorf("[%v] Inconsistent behavior between returned error and token.Valid")
}
if data.errors != 0 {
if err == nil {
t.Errorf("[%v] Expecting error. Didn't get one.", data.name)
} else {
ve := err.(*jwt.ValidationError)
// compare the bitfield part of the error
if e := ve.Errors; e != data.errors {
t.Errorf("[%v] Errors don't match expectation. %v != %v", data.name, e, data.errors)
}
if err.Error() == keyFuncError.Error() && ve.Inner != keyFuncError {
t.Errorf("[%v] Inner error does not match expectation. %v != %v", data.name, ve.Inner, keyFuncError)
}
}
}
if data.valid && token.Signature == "" {
t.Errorf("[%v] Signature is left unpopulated after parsing", data.name)
}
}
}
// Helper method for benchmarking various methods
func benchmarkSigning(b *testing.B, method jwt.SigningMethod, key interface{}) {
t := jwt.New(method)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if _, err := t.SignedString(key); err != nil {
b.Fatal(err)
}
}
})
}

View File

@@ -1,7 +0,0 @@
// Utility package for extracting JWT tokens from
// HTTP requests.
//
// The main function is ParseFromRequest and it's WithClaims variant.
// See examples for how to use the various Extractor implementations
// or roll your own.
package request

View File

@@ -1,81 +0,0 @@
package request
import (
"errors"
"net/http"
)
// Errors
var (
ErrNoTokenInRequest = errors.New("no token present in request")
)
// Interface for extracting a token from an HTTP request.
// The ExtractToken method should return a token string or an error.
// If no token is present, you must return ErrNoTokenInRequest.
type Extractor interface {
ExtractToken(*http.Request) (string, error)
}
// Extractor for finding a token in a header. Looks at each specified
// header in order until there's a match
type HeaderExtractor []string
func (e HeaderExtractor) ExtractToken(req *http.Request) (string, error) {
// loop over header names and return the first one that contains data
for _, header := range e {
if ah := req.Header.Get(header); ah != "" {
return ah, nil
}
}
return "", ErrNoTokenInRequest
}
// Extract token from request arguments. This includes a POSTed form or
// GET URL arguments. Argument names are tried in order until there's a match.
// This extractor calls `ParseMultipartForm` on the request
type ArgumentExtractor []string
func (e ArgumentExtractor) ExtractToken(req *http.Request) (string, error) {
// Make sure form is parsed
req.ParseMultipartForm(10e6)
// loop over arg names and return the first one that contains data
for _, arg := range e {
if ah := req.Form.Get(arg); ah != "" {
return ah, nil
}
}
return "", ErrNoTokenInRequest
}
// Tries Extractors in order until one returns a token string or an error occurs
type MultiExtractor []Extractor
func (e MultiExtractor) ExtractToken(req *http.Request) (string, error) {
// loop over header names and return the first one that contains data
for _, extractor := range e {
if tok, err := extractor.ExtractToken(req); tok != "" {
return tok, nil
} else if err != ErrNoTokenInRequest {
return "", err
}
}
return "", ErrNoTokenInRequest
}
// Wrap an Extractor in this to post-process the value before it's handed off.
// See AuthorizationHeaderExtractor for an example
type PostExtractionFilter struct {
Extractor
Filter func(string) (string, error)
}
func (e *PostExtractionFilter) ExtractToken(req *http.Request) (string, error) {
if tok, err := e.Extractor.ExtractToken(req); tok != "" {
return e.Filter(tok)
} else {
return "", err
}
}

View File

@@ -1,32 +0,0 @@
package request
import (
"fmt"
"net/url"
)
const (
exampleTokenA = "A"
)
func ExampleHeaderExtractor() {
req := makeExampleRequest("GET", "/", map[string]string{"Token": exampleTokenA}, nil)
tokenString, err := HeaderExtractor{"Token"}.ExtractToken(req)
if err == nil {
fmt.Println(tokenString)
} else {
fmt.Println(err)
}
//Output: A
}
func ExampleArgumentExtractor() {
req := makeExampleRequest("GET", "/", nil, url.Values{"token": {extractorTestTokenA}})
tokenString, err := ArgumentExtractor{"token"}.ExtractToken(req)
if err == nil {
fmt.Println(tokenString)
} else {
fmt.Println(err)
}
//Output: A
}

View File

@@ -1,91 +0,0 @@
package request
import (
"fmt"
"net/http"
"net/url"
"testing"
)
var extractorTestTokenA = "A"
var extractorTestTokenB = "B"
var extractorTestData = []struct {
name string
extractor Extractor
headers map[string]string
query url.Values
token string
err error
}{
{
name: "simple header",
extractor: HeaderExtractor{"Foo"},
headers: map[string]string{"Foo": extractorTestTokenA},
query: nil,
token: extractorTestTokenA,
err: nil,
},
{
name: "simple argument",
extractor: ArgumentExtractor{"token"},
headers: map[string]string{},
query: url.Values{"token": {extractorTestTokenA}},
token: extractorTestTokenA,
err: nil,
},
{
name: "multiple extractors",
extractor: MultiExtractor{
HeaderExtractor{"Foo"},
ArgumentExtractor{"token"},
},
headers: map[string]string{"Foo": extractorTestTokenA},
query: url.Values{"token": {extractorTestTokenB}},
token: extractorTestTokenA,
err: nil,
},
{
name: "simple miss",
extractor: HeaderExtractor{"This-Header-Is-Not-Set"},
headers: map[string]string{"Foo": extractorTestTokenA},
query: nil,
token: "",
err: ErrNoTokenInRequest,
},
{
name: "filter",
extractor: AuthorizationHeaderExtractor,
headers: map[string]string{"Authorization": "Bearer " + extractorTestTokenA},
query: nil,
token: extractorTestTokenA,
err: nil,
},
}
func TestExtractor(t *testing.T) {
// Bearer token request
for _, data := range extractorTestData {
// Make request from test struct
r := makeExampleRequest("GET", "/", data.headers, data.query)
// Test extractor
token, err := data.extractor.ExtractToken(r)
if token != data.token {
t.Errorf("[%v] Expected token '%v'. Got '%v'", data.name, data.token, token)
continue
}
if err != data.err {
t.Errorf("[%v] Expected error '%v'. Got '%v'", data.name, data.err, err)
continue
}
}
}
func makeExampleRequest(method, path string, headers map[string]string, urlArgs url.Values) *http.Request {
r, _ := http.NewRequest(method, fmt.Sprintf("%v?%v", path, urlArgs.Encode()), nil)
for k, v := range headers {
r.Header.Set(k, v)
}
return r
}

View File

@@ -1,28 +0,0 @@
package request
import (
"strings"
)
// Strips 'Bearer ' prefix from bearer token string
func stripBearerPrefixFromTokenString(tok string) (string, error) {
// Should be a bearer token
if len(tok) > 6 && strings.ToUpper(tok[0:7]) == "BEARER " {
return tok[7:], nil
}
return tok, nil
}
// Extract bearer token from Authorization header
// Uses PostExtractionFilter to strip "Bearer " prefix from header
var AuthorizationHeaderExtractor = &PostExtractionFilter{
HeaderExtractor{"Authorization"},
stripBearerPrefixFromTokenString,
}
// Extractor for OAuth2 access tokens. Looks in 'Authorization'
// header then 'access_token' argument for a token.
var OAuth2Extractor = &MultiExtractor{
AuthorizationHeaderExtractor,
ArgumentExtractor{"access_token"},
}

Some files were not shown because too many files have changed in this diff Show More