diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 0000000..f4a85ee --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,62 @@ +version: '{build}' +image: 'Visual Studio 2017' +platform: x64 + +clone_folder: 'c:\go\src\github.com\drone-plugins\drone-matrix' +max_jobs: 1 + +environment: + DOCKER_USERNAME: + secure: '4YzzahbEiMZQJpOCOd1LAw==' + DOCKER_PASSWORD: + secure: 'VqO/G3Zfslu6zSLdwHKO+Q==' + +install: + - ps: | + docker version + go version + +build_script: + - ps: | + if ( $env:APPVEYOR_REPO_TAG -eq 'false' ) { + go build -ldflags "-X main.build=$env:APPVEYOR_BUILD_VERSION" -a -o drone-matrix.exe + } else { + $version = $env:APPVEYOR_REPO_TAG_NAME.substring(1) + go build -ldflags "-X main.version=$version -X main.build=$env:APPVEYOR_BUILD_VERSION" -a -o drone-matrix.exe + } + + docker pull microsoft/nanoserver:10.0.14393.1593 + docker build -f Dockerfile.windows -t plugins/matrix:windows . + +test_script: + - ps: | + docker run --rm plugins/matrix:windows --version + +deploy_script: + - ps: | + $ErrorActionPreference = 'Stop'; + + if ( $env:APPVEYOR_PULL_REQUEST_NUMBER ) { + Write-Host Nothing to deploy. + } else { + docker login --username $env:DOCKER_USERNAME --password $env:DOCKER_PASSWORD + + if ( $env:APPVEYOR_REPO_TAG -eq 'true' ) { + $major,$minor,$patch = $env:APPVEYOR_REPO_TAG_NAME.substring(1).split('.') + + docker push plugins/matrix:windows + + docker tag plugins/matrix:windows plugins/matrix:$major.$minor.$patch-windows + docker push plugins/matrix:$major.$minor.$patch-windows + + docker tag plugins/matrix:windows plugins/matrix:$major.$minor-windows + docker push plugins/matrix:$major.$minor-windows + + docker tag plugins/matrix:windows plugins/matrix:$major-windows + docker push plugins/matrix:$major-windows + } else { + if ( $env:APPVEYOR_REPO_BRANCH -eq 'master' ) { + docker push plugins/matrix:windows + } + } + } diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c13ca3f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +* +!release/ diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..e6f0f11 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,133 @@ +workspace: + base: /go + path: src/github.com/drone-plugins/drone-matrix + +pipeline: + test: + image: golang:1.9 + pull: true + commands: + - go vet + - | + for PKG in $(go list ./... | grep -v /vendor/); do + go test -cover -coverprofile $PKG/coverage.out $PKG + done + + build_linux_amd64: + image: golang:1.9 + pull: true + group: build + environment: + - GOOS=linux + - GOARCH=amd64 + - CGO_ENABLED=0 + commands: + - | + if test "${DRONE_TAG}" = ""; then + go build -v -ldflags "-X main.build=${DRONE_BUILD_NUMBER}" -a -o release/linux/amd64/drone-matrix + else + go build -v -ldflags "-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}" -a -o release/linux/amd64/drone-matrix + fi + + build_linux_i386: + image: golang:1.9 + pull: true + group: build + environment: + - GOOS=linux + - GOARCH=386 + - CGO_ENABLED=0 + commands: + - | + if test "${DRONE_TAG}" = ""; then + go build -v -ldflags "-X main.build=${DRONE_BUILD_NUMBER}" -a -o release/linux/i386/drone-matrix + else + go build -v -ldflags "-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}" -a -o release/linux/i386/drone-matrix + fi + + build_linux_arm64: + image: golang:1.9 + pull: true + group: build + environment: + - GOOS=linux + - GOARCH=arm64 + - CGO_ENABLED=0 + commands: + - | + if test "${DRONE_TAG}" = ""; then + go build -v -ldflags "-X main.build=${DRONE_BUILD_NUMBER}" -a -o release/linux/arm64/drone-matrix + else + go build -v -ldflags "-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}" -a -o release/linux/arm64/drone-matrix + fi + + build_linux_arm: + image: golang:1.9 + pull: true + group: build + environment: + - GOOS=linux + - GOARCH=arm + - CGO_ENABLED=0 + - GOARM=7 + commands: + - | + if test "${DRONE_TAG}" = ""; then + go build -v -ldflags "-X main.build=${DRONE_BUILD_NUMBER}" -a -o release/linux/arm/drone-matrix + else + go build -v -ldflags "-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}" -a -o release/linux/arm/drone-matrix + fi + + publish_linux_amd64: + image: plugins/docker:17.05 + pull: true + secrets: [ docker_username, docker_password ] + group: docker + repo: plugins/matrix + auto_tag: true + dockerfile: Dockerfile + when: + event: [ push, tag ] + + publish_linux_i386: + image: plugins/docker:17.05 + pull: true + secrets: [ docker_username, docker_password ] + group: docker + repo: plugins/matrix + auto_tag: true + auto_tag_suffix: i386 + dockerfile: Dockerfile.i386 + when: + event: [ push, tag ] + + publish_linux_arm64: + image: plugins/docker:17.05 + pull: true + secrets: [ docker_username, docker_password ] + group: docker + repo: plugins/matrix + auto_tag: true + auto_tag_suffix: arm64 + dockerfile: Dockerfile.arm64 + when: + event: [ push, tag ] + + publish_linux_arm: + image: plugins/docker:17.05 + pull: true + secrets: [ docker_username, docker_password ] + group: docker + repo: plugins/matrix + auto_tag: true + auto_tag_suffix: arm + dockerfile: Dockerfile.arm + when: + event: [ push, tag ] + + microbadger: + image: plugins/webhook:1 + pull: true + secrets: [ webhook_url ] + when: + status: [ success ] diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 0000000..e69de29 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore index 5b8f9f0..57472fa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,28 @@ -*.swp -*~ -/drone-plugin-matrix -/vendor +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +release/ +coverage.out +drone-matrix diff --git a/Dockerfile b/Dockerfile index 927d18e..1429bc1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,9 @@ -# vim: set ft=dockerfile: -FROM alpine:3.6 -# Author with no obligation to maintain -MAINTAINER Paul Tötterman +FROM plugins/base:multiarch -RUN apk --no-cache add ca-certificates -ADD drone-plugin-matrix / -ENTRYPOINT /drone-plugin-matrix +LABEL maintainer="Drone.IO Community " \ + org.label-schema.name="Drone Matrix" \ + org.label-schema.vendor="Drone.IO Community" \ + org.label-schema.schema-version="1.0" + +ADD release/linux/amd64/drone-matrix /bin/ +ENTRYPOINT ["/bin/drone-matrix"] diff --git a/Dockerfile.arm b/Dockerfile.arm new file mode 100644 index 0000000..4f10bd9 --- /dev/null +++ b/Dockerfile.arm @@ -0,0 +1,9 @@ +FROM plugins/base:multiarch + +LABEL maintainer="Drone.IO Community " \ + org.label-schema.name="Drone Matrix" \ + org.label-schema.vendor="Drone.IO Community" \ + org.label-schema.schema-version="1.0" + +ADD release/linux/arm/drone-matrix /bin/ +ENTRYPOINT ["/bin/drone-matrix"] diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 new file mode 100644 index 0000000..dba8bf6 --- /dev/null +++ b/Dockerfile.arm64 @@ -0,0 +1,9 @@ +FROM plugins/base:multiarch + +LABEL maintainer="Drone.IO Community " \ + org.label-schema.name="Drone Matrix" \ + org.label-schema.vendor="Drone.IO Community" \ + org.label-schema.schema-version="1.0" + +ADD release/linux/arm64/drone-matrix /bin/ +ENTRYPOINT ["/bin/drone-matrix"] diff --git a/Dockerfile.i386 b/Dockerfile.i386 new file mode 100644 index 0000000..5c515de --- /dev/null +++ b/Dockerfile.i386 @@ -0,0 +1,9 @@ +FROM plugins/base:multiarch + +LABEL maintainer="Drone.IO Community " \ + org.label-schema.name="Drone Matrix" \ + org.label-schema.vendor="Drone.IO Community" \ + org.label-schema.schema-version="1.0" + +ADD release/linux/i386/drone-matrix /bin/ +ENTRYPOINT ["/bin/drone-matrix"] diff --git a/Dockerfile.windows b/Dockerfile.windows new file mode 100644 index 0000000..9de0dbe --- /dev/null +++ b/Dockerfile.windows @@ -0,0 +1,10 @@ +# escape=` +FROM microsoft/nanoserver:10.0.14393.1593 + +LABEL maintainer="Drone.IO Community " ` + org.label-schema.name="Drone Matrix" ` + org.label-schema.vendor="Drone.IO Community" ` + org.label-schema.schema-version="1.0" + +ADD drone-matrix.exe /drone-matrix.exe +ENTRYPOINT [ "\\drone-matrix.exe" ] diff --git a/Gopkg.lock b/Gopkg.lock index 77d6019..e6dcba5 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,15 +1,27 @@ # 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 = "a2232af10b53ef1ae5a767f5178db3a6c1dab655" + version = "v2.0.1" + [[projects]] branch = "master" name = "github.com/matrix-org/gomatrix" packages = ["."] revision = "a7fc80c8060c2544fe5d4dae465b584f8e9b4e27" +[[projects]] + name = "github.com/urfave/cli" + packages = ["."] + revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1" + version = "v1.20.0" + [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "d631b7f46070377e77e160dda36075f4421695f6149e974427eafc8458012b3c" + inputs-digest = "02adaa1a61f60449825943c7e92d6d66d4b88b225834f75dc6c181a96836f25b" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 36e6b1d..3edad83 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -21,6 +21,14 @@ # version = "2.4.0" +[[constraint]] + name = "github.com/aymerick/raymond" + version = "2.0.1" + [[constraint]] branch = "master" name = "github.com/matrix-org/gomatrix" + +[[constraint]] + name = "github.com/urfave/cli" + version = "1.20.0" diff --git a/Makefile b/Makefile deleted file mode 100644 index d82484f..0000000 --- a/Makefile +++ /dev/null @@ -1,13 +0,0 @@ -.PHONY: build -build: drone-plugin-matrix - -drone-plugin-matrix: main.go - CGO_ENABLED=0 go build -ldflags '-s -w' - -.PHONY: docker -docker: drone-plugin-matrix - docker build -t drone-plugin-matrix . - -.PHONY: clean -clean: - rm -f drone-plugin-matrix diff --git a/README.md b/README.md index c1098e4..161fb7c 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,37 @@ -# drone-plugin-matrix +# drone-matrix -[Drone](https://drone.io/) notifications to [Matrix](https://matrix.org/) +[![Build Status](http://beta.drone.io/api/badges/drone-plugins/drone-matrix/status.svg)](http://beta.drone.io/drone-plugins/drone-matrix) +[![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-plugins/drone-matrix?status.svg)](http://godoc.org/github.com/drone-plugins/drone-matrix) +[![Go Report](https://goreportcard.com/badge/github.com/drone-plugins/drone-matrix)](https://goreportcard.com/report/github.com/drone-plugins/drone-matrix) +[![](https://images.microbadger.com/badges/image/plugins/matrix.svg)](https://microbadger.com/images/plugins/matrix "Get your own image badge on microbadger.com") -Usage: +Drone plugin for sending build notifications to [Matrix](https://matrix.org/). For the usage information and a listing of the available options please take a look at [the docs](http://plugins.drone.io/drone-plugins/drone-matrix/). -```yaml - matrix: - image: ptman/drone-plugin-matrix - homeserver: https://matrix.org # defaults to https://matrix.org - roomid: '!0123456789abcdef:matrix.org' # room has to already be joined - secrets: - - matrix_username # either username ('ourbot') - - matrix_password # and password ('*ourbot-password*') - # - matrix_userid # or userid ('@ourbot:matrix.org') - # - matrix_accesstoken # and access token ('long string of characters') +## Build + +Build the binary with the following commands: + +``` +go build ``` -## License +## Docker -Apache-2.0 +Build the Docker image with the following commands: + +``` +GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -a -tags netgo -o release/linux/amd64/drone-matrix +docker build --rm -t plugins/matrix . +``` + +### Usage + +``` +docker run --rm \ + -e PLUGIN_ROOMID=0123456789abcdef:matrix.org \ + -e PLUGIN_USERNAME=yourbot \ + -e PLUGIN_PASSWORD=p455w0rd \ + plugins/matrix +``` diff --git a/main.go b/main.go index 4fbce1c..6281170 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,3 @@ -// Copyright (c) 2017 Paul Tötterman . All rights reserved. - package main import ( @@ -7,79 +5,183 @@ import ( "log" "os" - "github.com/matrix-org/gomatrix" + "github.com/urfave/cli" +) + +var ( + version = "0.0.0" + build = "0" ) func main() { - // Secrets - password := os.Getenv("MATRIX_PASSWORD") - accessToken := os.Getenv("MATRIX_ACCESSTOKEN") - // Not sure if these are secrets or nice to have close to them - userName := os.Getenv("MATRIX_USERNAME") - userID := os.Getenv("MATRIX_USERID") - - // Override secrets if present - if pw := os.Getenv("PLUGIN_PASSWORD"); pw != "" { - password = pw - } - if at := os.Getenv("PLUGIN_ACCESSTOKEN"); at != "" { - accessToken = at - } - if un := os.Getenv("PLUGIN_USERNAME"); un != "" { - userName = un - } - if ui := os.Getenv("PLUGIN_USERID"); ui != "" { - userID = ui + app := cli.NewApp() + app.Name = "codecov plugin" + app.Usage = "codecov plugin" + app.Version = fmt.Sprintf("%s+%s", version, build) + app.Action = run + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "username", + Usage: "username for authentication", + EnvVar: "PLUGIN_USERNAME,MATRIX_USERNAME", + }, + cli.StringFlag{ + Name: "password", + Usage: "password for authentication", + EnvVar: "PLUGIN_PASSWORD,MATRIX_PASSWORD", + }, + cli.StringFlag{ + Name: "userid", + Usage: "userid for authentication", + EnvVar: "PLUGIN_USERID,PLUGIN_USER_ID,MATRIX_USERID,MATRIX_USER_ID", + }, + cli.StringFlag{ + Name: "accesstoken", + Usage: "accesstoken for authentication", + EnvVar: "PLUGIN_ACCESSTOKEN,PLUGIN_ACCESS_TOKEN,MATRIX_ACCESSTOKEN,MATRIX_ACCESS_TOKEN", + }, + cli.StringFlag{ + Name: "homeserver", + Value: "https://matrix.org", + Usage: "matrix home server", + EnvVar: "PLUGIN_HOMESERVER,MATRIX_HOMESERVER", + }, + cli.StringFlag{ + Name: "roomid", + Usage: "roomid to send messages", + EnvVar: "PLUGIN_ROOMID,MATRIX_ROOMID", + }, + cli.StringFlag{ + Name: "template", + Usage: "template for the message", + EnvVar: "PLUGIN_TEMPLATE,MATRIX_TEMPLATE", + }, + cli.StringFlag{ + Name: "repo.owner", + Usage: "repository owner", + EnvVar: "DRONE_REPO_OWNER", + }, + cli.StringFlag{ + Name: "repo.name", + Usage: "repository name", + EnvVar: "DRONE_REPO_NAME", + }, + cli.StringFlag{ + Name: "commit.sha", + Usage: "git commit sha", + EnvVar: "DRONE_COMMIT_SHA", + Value: "00000000", + }, + cli.StringFlag{ + Name: "commit.ref", + Value: "refs/heads/master", + Usage: "git commit ref", + EnvVar: "DRONE_COMMIT_REF", + }, + cli.StringFlag{ + Name: "commit.branch", + Value: "master", + Usage: "git commit branch", + EnvVar: "DRONE_COMMIT_BRANCH", + }, + cli.StringFlag{ + Name: "commit.author", + Usage: "git author name", + EnvVar: "DRONE_COMMIT_AUTHOR", + }, + cli.StringFlag{ + Name: "commit.message", + Usage: "commit message", + EnvVar: "DRONE_COMMIT_MESSAGE", + }, + cli.StringFlag{ + Name: "build.event", + Value: "push", + Usage: "build event", + EnvVar: "DRONE_BUILD_EVENT", + }, + cli.IntFlag{ + Name: "build.number", + Usage: "build number", + EnvVar: "DRONE_BUILD_NUMBER", + }, + cli.StringFlag{ + Name: "build.status", + Usage: "build status", + Value: "success", + EnvVar: "DRONE_BUILD_STATUS", + }, + cli.StringFlag{ + Name: "build.link", + Usage: "build link", + EnvVar: "DRONE_BUILD_LINK", + }, + cli.Int64Flag{ + Name: "build.started", + Usage: "build started", + EnvVar: "DRONE_BUILD_STARTED", + }, + cli.Int64Flag{ + Name: "build.created", + Usage: "build created", + EnvVar: "DRONE_BUILD_CREATED", + }, + cli.StringFlag{ + Name: "build.tag", + Usage: "build tag", + EnvVar: "DRONE_TAG", + }, + cli.StringFlag{ + Name: "build.deployTo", + Usage: "environment deployed to", + EnvVar: "DRONE_DEPLOY_TO", + }, + cli.Int64Flag{ + Name: "job.started", + Usage: "job started", + EnvVar: "DRONE_JOB_STARTED", + }, } - homeServer := os.Getenv("PLUGIN_HOMESERVER") - if homeServer == "" { - homeServer = "https://matrix.org" - } - - // TODO: resolve room aliases - roomID := os.Getenv("PLUGIN_ROOMID") - message := os.Getenv("PLUGIN_MESSAGE") - - repoOwner := os.Getenv("DRONE_REPO_OWNER") - repoName := os.Getenv("DRONE_REPO_NAME") - - buildStatus := os.Getenv("DRONE_BUILD_STATUS") - buildLink := os.Getenv("DRONE_BUILD_LINK") - buildBranch := os.Getenv("DRONE_BRANCH") - buildAuthor := os.Getenv("DRONE_COMMIT_AUTHOR") - buildCommit := os.Getenv("DRONE_COMMIT") - - m, err := gomatrix.NewClient(homeServer, userID, accessToken) - if err != nil { - log.Fatal(err) - } - - if userID == "" || accessToken == "" { - r, err := m.Login(&gomatrix.ReqLogin{ - Type: "m.login.password", - User: userName, - Password: password, - InitialDeviceDisplayName: "Drone", - }) - if err != nil { - log.Fatal(err) - } - m.SetCredentials(r.UserID, r.AccessToken) - } - - if message == "" { - message = fmt.Sprintf("Build %s <%s> %s/%s#%s (%s) by %s", - buildStatus, - buildLink, - repoOwner, - repoName, - buildCommit[:8], - buildBranch, - buildAuthor) - } - - if _, err := m.SendNotice(roomID, message); err != nil { + if err := app.Run(os.Args); err != nil { log.Fatal(err) } } + +func run(c *cli.Context) error { + plugin := Plugin{ + Repo: Repo{ + Owner: c.String("repo.owner"), + Name: c.String("repo.name"), + }, + Build: Build{ + Tag: c.String("build.tag"), + Number: c.Int("build.number"), + Event: c.String("build.event"), + Status: c.String("build.status"), + Commit: c.String("commit.sha"), + Ref: c.String("commit.ref"), + Branch: c.String("commit.branch"), + Author: c.String("commit.author"), + Message: c.String("commit.message"), + DeployTo: c.String("build.deployTo"), + Link: c.String("build.link"), + Started: c.Int64("build.started"), + Created: c.Int64("build.created"), + }, + Job: Job{ + Started: c.Int64("job.started"), + }, + Config: Config{ + Username: c.String("username"), + Password: c.String("password"), + UserID: c.String("userid"), + AccessToken: c.String("accesstoken"), + Homeserver: c.String("homeserver"), + RoomID: c.String("roomid"), + Template: c.String("template"), + }, + } + + return plugin.Exec() +} diff --git a/plugin.go b/plugin.go new file mode 100644 index 0000000..d1e1ccf --- /dev/null +++ b/plugin.go @@ -0,0 +1,124 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/matrix-org/gomatrix" +) + +type ( + Repo struct { + Owner string + Name string + } + + Build struct { + Tag string + Event string + Number int + Commit string + Ref string + Branch string + Author string + Message string + DeployTo string + Status string + Link string + Started int64 + Created int64 + } + + Job struct { + Started int64 + } + + Config struct { + Username string + Password string + UserID string + AccessToken string + Homeserver string + RoomID string + Template string + } + + Plugin struct { + Repo Repo + Build Build + Job Job + Config Config + } +) + +func (p Plugin) Exec() error { + m, err := gomatrix.NewClient(p.Config.Homeserver, prepend("@", p.Config.UserID), p.Config.AccessToken) + + if err != nil { + return err + } + + if p.Config.UserID == "" || p.Config.AccessToken == "" { + r, err := m.Login(&gomatrix.ReqLogin{ + Type: "m.login.password", + User: p.Config.Username, + Password: p.Config.Password, + InitialDeviceDisplayName: "Drone", + }) + + if err != nil { + return err + } + + m.SetCredentials(r.UserID, r.AccessToken) + } + + joined, err := m.JoinRoom(p.Config.RoomID, "", nil) + + if err != nil { + return err + } + + message := message(p.Repo, p.Build) + + if p.Config.Template != "" { + if message, err = RenderTrim(p.Config.Template, p); err != nil { + return err + } + + if err != nil { + return err + } + } + + if _, err := m.SendNotice(joined.RoomID, message); err != nil { + return err + } + + return nil +} + +func message(repo Repo, build Build) string { + return fmt.Sprintf( + "Build %s <%s|%s/%s#%s> (%s) by %s", + build.Status, + build.Link, + repo.Owner, + repo.Name, + build.Commit[:8], + build.Branch, + build.Author, + ) +} + +func prepend(prefix, s string) string { + if s == "" { + return s + } + + if strings.HasPrefix(s, prefix) { + return s + } + + return prefix + s +} diff --git a/template.go b/template.go new file mode 100644 index 0000000..1f9c0ec --- /dev/null +++ b/template.go @@ -0,0 +1,137 @@ +package main + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" + "time" + "unicode" + "unicode/utf8" + + "github.com/aymerick/raymond" +) + +func init() { + raymond.RegisterHelpers(funcs) +} + +// Render parses and executes a template, returning the results in string format. +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, err + } + defer res.Body.Close() + out, err := ioutil.ReadAll(res.Body) + if err != nil { + return s, err + } + template = string(out) + + case "file": + out, err := ioutil.ReadFile(u.Path) + if err != nil { + return s, err + } + 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 +} + +var funcs = map[string]interface{}{ + "uppercasefirst": uppercaseFirst, + "uppercase": strings.ToUpper, + "lowercase": strings.ToLower, + "duration": toDuration, + "datetime": toDatetime, + "success": isSuccess, + "failure": isFailure, + "truncate": truncate, + "urlencode": urlencode, + "since": since, +} + +func truncate(s string, len int) string { + if utf8.RuneCountInString(s) <= len { + return s + } + runes := []rune(s) + return string(runes[:len]) + +} + +func uppercaseFirst(s string) string { + a := []rune(s) + a[0] = unicode.ToUpper(a[0]) + s = string(a) + return s +} + +func toDuration(started, finished float64) string { + return fmt.Sprintln(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 urlencode(options *raymond.Options) string { + return url.QueryEscape(options.Fn()) +} + +func since(start int64) string { + // NOTE: not using `time.Since()` because the fractional second component + // will give us something like "40m12.917523438s" vs "40m12s". We lose + // some precision, but the format is much more readable. + now := time.Unix(time.Now().Unix(), 0) + return fmt.Sprintln(now.Sub(time.Unix(start, 0))) +}