From 61fce6e289213adef9345c36cdf677660a25125b Mon Sep 17 00:00:00 2001 From: Thomas Boerger Date: Fri, 30 Mar 2018 01:04:58 +0200 Subject: [PATCH] Migrated initial version --- .drone.yml | 18 ++++++ .gitignore | 3 + Gopkg.lock | 38 +++++++++++++ Gopkg.toml | 15 +++++ README.md | 23 ++++++++ template/helpers.go | 119 +++++++++++++++++++++++++++++++++++++++ template/helpers_test.go | 92 ++++++++++++++++++++++++++++++ template/template.go | 72 +++++++++++++++++++++++ 8 files changed, 380 insertions(+) create mode 100644 .drone.yml create mode 100644 Gopkg.lock create mode 100644 Gopkg.toml create mode 100644 README.md create mode 100644 template/helpers.go create mode 100644 template/helpers_test.go create mode 100644 template/template.go diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..9362210 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,18 @@ +workspace: + base: /go + path: src/github.com/drone/drone-template-lib + +pipeline: + deps: + image: golang:1.10 + pull: true + commands: + - go get -u github.com/golang/dep/cmd/dep + - dep ensure + + test: + image: golang:1.10 + pull: true + commands: + - go vet ./... + - go test -cover ./... diff --git a/.gitignore b/.gitignore index daf913b..0711390 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ _testmain.go *.exe *.test *.prof + +vendor/ +coverage.out diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..bf10f16 --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,38 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/aymerick/raymond" + packages = [ + ".", + "ast", + "lexer", + "parser" + ] + revision = "2eebd0f5dd9564c0c6e439df11d8a3c7b9b9ab11" + version = "v2.0.2" + +[[projects]] + branch = "master" + name = "github.com/bouk/monkey" + packages = ["."] + revision = "5df1f207ff77e025801505ae4d903133a0b4353f" + +[[projects]] + name = "github.com/pkg/errors" + packages = ["."] + revision = "645ef00459ed84a119197bfb8d8205042c6df63d" + version = "v0.8.0" + +[[projects]] + branch = "master" + name = "github.com/tkuchiki/faketime" + packages = ["."] + revision = "a4500a4f4643cbc8c5855ed1dbbf161d6cfc77a5" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "1ad80a88b5f0b1ddd8f2a548af68aea9c1c6e25c6fc0870a5fa5a5b03b25d414" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..d65cc24 --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,15 @@ +[[constraint]] + name = "github.com/aymerick/raymond" + version = "2.0.2" + +[[constraint]] + name = "github.com/pkg/errors" + version = "0.8.0" + +[prune] + go-tests = true + unused-packages = true + +[[constraint]] + branch = "master" + name = "github.com/tkuchiki/faketime" diff --git a/README.md b/README.md new file mode 100644 index 0000000..5ce25b4 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# drone-template-lib + +[![Build Status](http://beta.drone.io/api/badges/drone/drone-template-lib/status.svg)](http://beta.drone.io/drone/drone-template-lib) +[![Join the discussion at https://discourse.drone.io](https://img.shields.io/badge/discourse-forum-orange.svg)](https://discourse.drone.io) +[![Drone questions at https://stackoverflow.com](https://img.shields.io/badge/drone-stackoverflow-orange.svg)](https://stackoverflow.com/questions/tagged/drone.io) +[![Go Doc](https://godoc.org/github.com/drone/drone-template-lib?status.svg)](http://godoc.org/github.com/drone/drone-template-lib) +[![Go Report](https://goreportcard.com/badge/github.com/drone/drone-template-lib)](https://goreportcard.com/report/github.com/drone/drone-template-lib) + +A Go client library for creating templateable [plugins](http://plugins.drone.io). + +## Usage + +### Download the packe + +```bash +go get -d github.com/drone/drone-template-lib +``` + +### Import the package + +```Go +import "github.com/drone/drone-template-lib/template" +``` diff --git a/template/helpers.go b/template/helpers.go new file mode 100644 index 0000000..2c810fb --- /dev/null +++ b/template/helpers.go @@ -0,0 +1,119 @@ +// Copyright 2018 Drone.IO Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package template + +import ( + "fmt" + "net/url" + "strings" + "time" + "unicode" + "unicode/utf8" + + "github.com/aymerick/raymond" +) + +var ( + funcs = map[string]interface{}{ + "duration": toDuration, + "datetime": toDatetime, + "success": isSuccess, + "failure": isFailure, + "truncate": truncate, + "urlencode": urlencode, + "since": since, + "uppercasefirst": uppercaseFirst, + "uppercase": strings.ToUpper, + "lowercase": strings.ToLower, + "trim": strings.TrimSpace, + "title": strings.Title, + } +) + +func init() { + raymond.RegisterHelpers(funcs) +} + +func toDuration(started, finished float64) string { + return fmt.Sprint(time.Duration(finished-started) * time.Second) +} + +func toDatetime(timestamp float64, layout, zone string) string { + if len(zone) == 0 { + return time.Unix(int64(timestamp), 0).Format(layout) + } + + loc, err := time.LoadLocation(zone) + + if err != nil { + return time.Unix(int64(timestamp), 0).Local().Format(layout) + } + + return time.Unix(int64(timestamp), 0).In(loc).Format(layout) +} + +func isSuccess(conditional bool, options *raymond.Options) string { + if !conditional { + return options.Inverse() + } + + switch options.ParamStr(0) { + case "success": + return options.Fn() + default: + return options.Inverse() + } +} + +func isFailure(conditional bool, options *raymond.Options) string { + if !conditional { + return options.Inverse() + } + + switch options.ParamStr(0) { + case "failure", "error", "killed": + return options.Fn() + default: + return options.Inverse() + } +} + +func truncate(s string, len int) string { + if utf8.RuneCountInString(s) <= len { + return s + } + + runes := []rune(s) + + return string(runes[:len]) +} + +func urlencode(options *raymond.Options) string { + return url.QueryEscape(options.Fn()) +} + +func since(start int64) string { + now := time.Unix(time.Now().Unix(), 0) + return fmt.Sprint(now.Sub(time.Unix(start, 0))) +} + +func uppercaseFirst(s string) string { + a := []rune(s) + + a[0] = unicode.ToUpper(a[0]) + s = string(a) + + return s +} diff --git a/template/helpers_test.go b/template/helpers_test.go new file mode 100644 index 0000000..7dd4cd0 --- /dev/null +++ b/template/helpers_test.go @@ -0,0 +1,92 @@ +// Copyright 2018 Drone.IO Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package template + +import ( + "testing" + "time" + + "github.com/tkuchiki/faketime" +) + +func TestToDuration(t *testing.T) { + from := float64(time.Date(2017, time.November, 15, 23, 0, 0, 0, time.UTC).Unix()) + + vals := map[int64]string{ + time.Date(2018, time.November, 15, 23, 0, 0, 0, time.UTC).Unix(): "8760h0m0s", + time.Date(2017, time.November, 16, 23, 0, 0, 0, time.UTC).Unix(): "24h0m0s", + time.Date(2017, time.November, 15, 23, 30, 0, 0, time.UTC).Unix(): "30m0s", + time.Date(2017, time.November, 15, 23, 10, 15, 0, time.UTC).Unix(): "10m15s", + time.Date(2017, time.October, 15, 23, 0, 0, 0, time.UTC).Unix(): "-744h0m0s", + } + + for input, want := range vals { + if got := toDuration(from, float64(input)); got != want { + t.Errorf("Want transform %f-%f to %s, got %s", from, float64(input), want, got) + } + } +} + +func TestTruncate(t *testing.T) { + vals := map[string]string{ + "foobarz": "fooba", + "foöäüüu": "foöäü", + "üpsßßßk": "üpsßß", + "1234567": "12345", + "!'§$%&/": "!'§$%", + } + + for input, want := range vals { + if got := truncate(input, 5); got != want { + t.Errorf("Want transform %s to %s, got %s", input, want, got) + } + } +} + +func TestSince(t *testing.T) { + f := faketime.NewFaketime(2017, time.November, 15, 23, 0, 0, 0, time.UTC) + defer f.Undo() + f.Do() + + vals := map[int64]string{ + time.Date(2016, time.November, 15, 23, 0, 0, 0, time.UTC).Unix(): "8760h0m0s", + time.Date(2017, time.November, 14, 23, 0, 0, 0, time.UTC).Unix(): "24h0m0s", + time.Date(2017, time.November, 15, 22, 30, 0, 0, time.UTC).Unix(): "30m0s", + time.Date(2017, time.November, 15, 22, 10, 15, 0, time.UTC).Unix(): "49m45s", + time.Date(2017, time.December, 15, 23, 0, 0, 0, time.UTC).Unix(): "-720h0m0s", + } + + for input, want := range vals { + if got := since(input); got != want { + t.Errorf("Want transform %d to %s, got %s", input, want, got) + } + } +} + +func TestUppercaseFirst(t *testing.T) { + vals := map[string]string{ + "hello": "Hello", + "ßqwert": "ßqwert", + "üps": "Üps", + "12345": "12345", + "Foobar": "Foobar", + } + + for input, want := range vals { + if got := uppercaseFirst(input); got != want { + t.Errorf("Want transform %s to %s, got %s", input, want, got) + } + } +} diff --git a/template/template.go b/template/template.go new file mode 100644 index 0000000..936f7a2 --- /dev/null +++ b/template/template.go @@ -0,0 +1,72 @@ +// Copyright 2018 Drone.IO Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package template + +import ( + "io/ioutil" + "net/http" + "net/url" + "strings" + + "github.com/aymerick/raymond" + "github.com/pkg/errors" +) + +// Render parses and executes a template, returning the results in string +// format. Trailing or leading spaces or new-lines are not getting truncated. It +// is able to read templates from remote paths, local files or directly from the +// string. +func Render(template string, payload interface{}) (s string, err error) { + u, err := url.Parse(template) + + if err == nil { + switch u.Scheme { + case "http", "https": + res, err := http.Get(template) + + if err != nil { + return s, errors.Wrap(err, "failed to fetch") + } + + defer res.Body.Close() + + out, err := ioutil.ReadAll(res.Body) + + if err != nil { + return s, errors.Wrap(err, "failed to read") + } + + template = string(out) + case "file": + out, err := ioutil.ReadFile(u.Path) + + if err != nil { + return s, errors.Wrap(err, "failed to read") + } + + template = string(out) + } + } + + return raymond.Render(template, payload) +} + +// RenderTrim parses and executes a template, returning the results in string +// format. The result is trimmed to remove left and right padding and newlines +// that may be added unintentially in the template markup. +func RenderTrim(template string, playload interface{}) (string, error) { + out, err := Render(template, playload) + return strings.Trim(out, " \n"), err +}