mirror of
https://github.com/thegeeklab/drone-matrix.git
synced 2024-10-31 16:50:41 +00:00
Merge pull request #4 from drone-plugins/restructure
Applied default structure and added some sugar
This commit is contained in:
commit
1bbdfad90b
62
.appveyor.yml
Normal file
62
.appveyor.yml
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!release/
|
144
.drone.yml
Normal file
144
.drone.yml
Normal file
@ -0,0 +1,144 @@
|
||||
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
|
||||
auto_tag_suffix: amd64
|
||||
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 ]
|
||||
|
||||
manifests:
|
||||
image: plugins/manifest:1
|
||||
pull: true
|
||||
secrets: [ docker_username, docker_password ]
|
||||
spec: manifest.tmpl
|
||||
auto_tag: true
|
||||
ignore_missing: true
|
||||
when:
|
||||
event: [ push, tag ]
|
||||
|
||||
microbadger:
|
||||
image: plugins/webhook:1
|
||||
pull: true
|
||||
secrets: [ webhook_url ]
|
||||
when:
|
||||
status: [ success ]
|
0
.github/issue_template.md
vendored
Normal file
0
.github/issue_template.md
vendored
Normal file
0
.github/pull_request_template.md
vendored
Normal file
0
.github/pull_request_template.md
vendored
Normal file
32
.gitignore
vendored
32
.gitignore
vendored
@ -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
|
||||
|
15
Dockerfile
15
Dockerfile
@ -1,8 +1,9 @@
|
||||
# vim: set ft=dockerfile:
|
||||
FROM alpine:3.6
|
||||
# Author with no obligation to maintain
|
||||
MAINTAINER Paul Tötterman <paul.totterman@gmail.com>
|
||||
FROM plugins/base:multiarch
|
||||
|
||||
RUN apk --no-cache add ca-certificates
|
||||
ADD drone-plugin-matrix /
|
||||
ENTRYPOINT /drone-plugin-matrix
|
||||
LABEL maintainer="Drone.IO Community <drone-dev@googlegroups.com>" \
|
||||
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"]
|
||||
|
9
Dockerfile.arm
Normal file
9
Dockerfile.arm
Normal file
@ -0,0 +1,9 @@
|
||||
FROM plugins/base:multiarch
|
||||
|
||||
LABEL maintainer="Drone.IO Community <drone-dev@googlegroups.com>" \
|
||||
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"]
|
9
Dockerfile.arm64
Normal file
9
Dockerfile.arm64
Normal file
@ -0,0 +1,9 @@
|
||||
FROM plugins/base:multiarch
|
||||
|
||||
LABEL maintainer="Drone.IO Community <drone-dev@googlegroups.com>" \
|
||||
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"]
|
9
Dockerfile.i386
Normal file
9
Dockerfile.i386
Normal file
@ -0,0 +1,9 @@
|
||||
FROM plugins/base:multiarch
|
||||
|
||||
LABEL maintainer="Drone.IO Community <drone-dev@googlegroups.com>" \
|
||||
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"]
|
10
Dockerfile.windows
Normal file
10
Dockerfile.windows
Normal file
@ -0,0 +1,10 @@
|
||||
# escape=`
|
||||
FROM microsoft/nanoserver:10.0.14393.1593
|
||||
|
||||
LABEL maintainer="Drone.IO Community <drone-dev@googlegroups.com>" `
|
||||
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" ]
|
14
Gopkg.lock
generated
14
Gopkg.lock
generated
@ -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
|
||||
|
@ -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"
|
||||
|
13
Makefile
13
Makefile
@ -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
|
46
README.md
46
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
|
||||
```
|
||||
|
242
main.go
242
main.go
@ -1,5 +1,3 @@
|
||||
// Copyright (c) 2017 Paul Tötterman <ptman@iki.fi>. 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()
|
||||
}
|
||||
|
33
manifest.tmpl
Normal file
33
manifest.tmpl
Normal file
@ -0,0 +1,33 @@
|
||||
image: plugins/matrix:{{#if build.tag}}{{trimPrefix build.tag "v"}}{{else}}latest{{/if}}
|
||||
{{#if build.tags}}
|
||||
tags:
|
||||
{{#each build.tags}}
|
||||
- {{this}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
manifests:
|
||||
-
|
||||
image: plugins/matrix:{{#if build.tag}}{{trimPrefix build.tag "v"}}-{{/if}}amd64
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
-
|
||||
image: plugins/matrix:{{#if build.tag}}{{trimPrefix build.tag "v"}}-{{/if}}i386
|
||||
platform:
|
||||
architecture: 386
|
||||
os: linux
|
||||
-
|
||||
image: plugins/matrix:{{#if build.tag}}{{trimPrefix build.tag "v"}}-{{/if}}arm64
|
||||
platform:
|
||||
architecture: arm64
|
||||
os: linux
|
||||
-
|
||||
image: plugins/matrix:{{#if build.tag}}{{trimPrefix build.tag "v"}}-{{/if}}arm
|
||||
platform:
|
||||
architecture: arm
|
||||
os: linux
|
||||
-
|
||||
image: plugins/matrix:{{#if build.tag}}{{trimPrefix build.tag "v"}}-{{/if}}windows
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: windows
|
124
plugin.go
Normal file
124
plugin.go
Normal file
@ -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
|
||||
}
|
137
template.go
Normal file
137
template.go
Normal file
@ -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)))
|
||||
}
|
3
vendor/github.com/aymerick/raymond/.gitmodules
generated
vendored
Normal file
3
vendor/github.com/aymerick/raymond/.gitmodules
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "mustache"]
|
||||
path = mustache
|
||||
url = git://github.com/mustache/spec.git
|
6
vendor/github.com/aymerick/raymond/.travis.yml
generated
vendored
Normal file
6
vendor/github.com/aymerick/raymond/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.3
|
||||
- tip
|
46
vendor/github.com/aymerick/raymond/BENCHMARKS.md
generated
vendored
Normal file
46
vendor/github.com/aymerick/raymond/BENCHMARKS.md
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
# Benchmarks
|
||||
|
||||
Hardware: MacBookPro11,1 - Intel Core i5 - 2,6 GHz - 8 Go RAM
|
||||
|
||||
With:
|
||||
|
||||
- handlebars.js #8cba84df119c317fcebc49fb285518542ca9c2d0
|
||||
- raymond #7bbaaf50ed03c96b56687d7fa6c6e04e02375a98
|
||||
|
||||
|
||||
## handlebars.js (ops/ms)
|
||||
|
||||
arguments 198 ±4 (5)
|
||||
array-each 568 ±23 (5)
|
||||
array-mustache 522 ±18 (4)
|
||||
complex 71 ±7 (3)
|
||||
data 67 ±2 (3)
|
||||
depth-1 47 ±2 (3)
|
||||
depth-2 14 ±1 (2)
|
||||
object-mustache 1099 ±47 (5)
|
||||
object 907 ±58 (4)
|
||||
partial-recursion 46 ±3 (4)
|
||||
partial 68 ±3 (3)
|
||||
paths 1650 ±50 (3)
|
||||
string 2552 ±157 (3)
|
||||
subexpression 141 ±2 (4)
|
||||
variables 2671 ±83 (4)
|
||||
|
||||
|
||||
## raymond
|
||||
|
||||
BenchmarkArguments 200000 6642 ns/op 151 ops/ms
|
||||
BenchmarkArrayEach 100000 19584 ns/op 51 ops/ms
|
||||
BenchmarkArrayMustache 100000 17305 ns/op 58 ops/ms
|
||||
BenchmarkComplex 30000 50270 ns/op 20 ops/ms
|
||||
BenchmarkData 50000 25551 ns/op 39 ops/ms
|
||||
BenchmarkDepth1 100000 20162 ns/op 50 ops/ms
|
||||
BenchmarkDepth2 30000 47782 ns/op 21 ops/ms
|
||||
BenchmarkObjectMustache 200000 7668 ns/op 130 ops/ms
|
||||
BenchmarkObject 200000 8843 ns/op 113 ops/ms
|
||||
BenchmarkPartialRecursion 50000 23139 ns/op 43 ops/ms
|
||||
BenchmarkPartial 50000 31015 ns/op 32 ops/ms
|
||||
BenchmarkPath 200000 8997 ns/op 111 ops/ms
|
||||
BenchmarkString 1000000 1879 ns/op 532 ops/ms
|
||||
BenchmarkSubExpression 300000 4935 ns/op 203 ops/ms
|
||||
BenchmarkVariables 200000 6478 ns/op 154 ops/ms
|
33
vendor/github.com/aymerick/raymond/CHANGELOG.md
generated
vendored
Normal file
33
vendor/github.com/aymerick/raymond/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
# Raymond Changelog
|
||||
|
||||
### Raymond 2.0.1 _(June 01, 2016)_
|
||||
|
||||
- [BUGFIX] Removes data races [#3](https://github.com/aymerick/raymond/issues/3) - Thanks [@markbates](https://github.com/markbates)
|
||||
|
||||
### Raymond 2.0.0 _(May 01, 2016)_
|
||||
|
||||
- [BUGFIX] Fixes passing of context in helper options [#2](https://github.com/aymerick/raymond/issues/2) - Thanks [@GhostRussia](https://github.com/GhostRussia)
|
||||
- [BREAKING] Renames and unexports constants:
|
||||
|
||||
- `handlebars.DUMP_TPL`
|
||||
- `lexer.ESCAPED_ESCAPED_OPEN_MUSTACHE`
|
||||
- `lexer.ESCAPED_OPEN_MUSTACHE`
|
||||
- `lexer.OPEN_MUSTACHE`
|
||||
- `lexer.CLOSE_MUSTACHE`
|
||||
- `lexer.CLOSE_STRIP_MUSTACHE`
|
||||
- `lexer.CLOSE_UNESCAPED_STRIP_MUSTACHE`
|
||||
- `lexer.DUMP_TOKEN_POS`
|
||||
- `lexer.DUMP_ALL_TOKENS_VAL`
|
||||
|
||||
|
||||
### Raymond 1.1.0 _(June 15, 2015)_
|
||||
|
||||
- Permits templates references with lowercase versions of struct fields.
|
||||
- Adds `ParseFile()` function.
|
||||
- Adds `RegisterPartialFile()`, `RegisterPartialFiles()` and `Clone()` methods on `Template`.
|
||||
- Helpers can now be struct methods.
|
||||
- Ensures safe concurrent access to helpers and partials.
|
||||
|
||||
### Raymond 1.0.0 _(June 09, 2015)_
|
||||
|
||||
- This is the first release. Raymond supports almost all handlebars features. See https://github.com/aymerick/raymond#limitations for a list of differences with the javascript implementation.
|
22
vendor/github.com/aymerick/raymond/LICENSE
generated
vendored
Normal file
22
vendor/github.com/aymerick/raymond/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Aymerick JEHANNE
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
1382
vendor/github.com/aymerick/raymond/README.md
generated
vendored
Normal file
1382
vendor/github.com/aymerick/raymond/README.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
vendor/github.com/aymerick/raymond/VERSION
generated
vendored
Normal file
1
vendor/github.com/aymerick/raymond/VERSION
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
2.0.1
|
785
vendor/github.com/aymerick/raymond/ast/node.go
generated
vendored
Normal file
785
vendor/github.com/aymerick/raymond/ast/node.go
generated
vendored
Normal file
@ -0,0 +1,785 @@
|
||||
// Package ast provides structures to represent a handlebars Abstract Syntax Tree, and a Visitor interface to visit that tree.
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// References:
|
||||
// - https://github.com/wycats/handlebars.js/blob/master/lib/handlebars/compiler/ast.js
|
||||
// - https://github.com/wycats/handlebars.js/blob/master/docs/compiler-api.md
|
||||
// - https://github.com/golang/go/blob/master/src/text/template/parse/node.go
|
||||
|
||||
// Node is an element in the AST.
|
||||
type Node interface {
|
||||
// node type
|
||||
Type() NodeType
|
||||
|
||||
// location of node in original input string
|
||||
Location() Loc
|
||||
|
||||
// string representation, used for debugging
|
||||
String() string
|
||||
|
||||
// accepts visitor
|
||||
Accept(Visitor) interface{}
|
||||
}
|
||||
|
||||
// Visitor is the interface to visit an AST.
|
||||
type Visitor interface {
|
||||
VisitProgram(*Program) interface{}
|
||||
|
||||
// statements
|
||||
VisitMustache(*MustacheStatement) interface{}
|
||||
VisitBlock(*BlockStatement) interface{}
|
||||
VisitPartial(*PartialStatement) interface{}
|
||||
VisitContent(*ContentStatement) interface{}
|
||||
VisitComment(*CommentStatement) interface{}
|
||||
|
||||
// expressions
|
||||
VisitExpression(*Expression) interface{}
|
||||
VisitSubExpression(*SubExpression) interface{}
|
||||
VisitPath(*PathExpression) interface{}
|
||||
|
||||
// literals
|
||||
VisitString(*StringLiteral) interface{}
|
||||
VisitBoolean(*BooleanLiteral) interface{}
|
||||
VisitNumber(*NumberLiteral) interface{}
|
||||
|
||||
// miscellaneous
|
||||
VisitHash(*Hash) interface{}
|
||||
VisitHashPair(*HashPair) interface{}
|
||||
}
|
||||
|
||||
// NodeType represents an AST Node type.
|
||||
type NodeType int
|
||||
|
||||
// Type returns itself, and permits struct includers to satisfy that part of Node interface.
|
||||
func (t NodeType) Type() NodeType {
|
||||
return t
|
||||
}
|
||||
|
||||
const (
|
||||
// NodeProgram is the program node
|
||||
NodeProgram NodeType = iota
|
||||
|
||||
// NodeMustache is the mustache statement node
|
||||
NodeMustache
|
||||
|
||||
// NodeBlock is the block statement node
|
||||
NodeBlock
|
||||
|
||||
// NodePartial is the partial statement node
|
||||
NodePartial
|
||||
|
||||
// NodeContent is the content statement node
|
||||
NodeContent
|
||||
|
||||
// NodeComment is the comment statement node
|
||||
NodeComment
|
||||
|
||||
// NodeExpression is the expression node
|
||||
NodeExpression
|
||||
|
||||
// NodeSubExpression is the subexpression node
|
||||
NodeSubExpression
|
||||
|
||||
// NodePath is the expression path node
|
||||
NodePath
|
||||
|
||||
// NodeBoolean is the literal boolean node
|
||||
NodeBoolean
|
||||
|
||||
// NodeNumber is the literal number node
|
||||
NodeNumber
|
||||
|
||||
// NodeString is the literal string node
|
||||
NodeString
|
||||
|
||||
// NodeHash is the hash node
|
||||
NodeHash
|
||||
|
||||
// NodeHashPair is the hash pair node
|
||||
NodeHashPair
|
||||
)
|
||||
|
||||
// Loc represents the position of a parsed node in source file.
|
||||
type Loc struct {
|
||||
Pos int // Byte position
|
||||
Line int // Line number
|
||||
}
|
||||
|
||||
// Location returns itself, and permits struct includers to satisfy that part of Node interface.
|
||||
func (l Loc) Location() Loc {
|
||||
return l
|
||||
}
|
||||
|
||||
// Strip describes node whitespace management.
|
||||
type Strip struct {
|
||||
Open bool
|
||||
Close bool
|
||||
|
||||
OpenStandalone bool
|
||||
CloseStandalone bool
|
||||
InlineStandalone bool
|
||||
}
|
||||
|
||||
// NewStrip instanciates a Strip for given open and close mustaches.
|
||||
func NewStrip(openStr, closeStr string) *Strip {
|
||||
return &Strip{
|
||||
Open: (len(openStr) > 2) && openStr[2] == '~',
|
||||
Close: (len(closeStr) > 2) && closeStr[len(closeStr)-3] == '~',
|
||||
}
|
||||
}
|
||||
|
||||
// NewStripForStr instanciates a Strip for given tag.
|
||||
func NewStripForStr(str string) *Strip {
|
||||
return &Strip{
|
||||
Open: (len(str) > 2) && str[2] == '~',
|
||||
Close: (len(str) > 2) && str[len(str)-3] == '~',
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (s *Strip) String() string {
|
||||
return fmt.Sprintf("Open: %t, Close: %t, OpenStandalone: %t, CloseStandalone: %t, InlineStandalone: %t", s.Open, s.Close, s.OpenStandalone, s.CloseStandalone, s.InlineStandalone)
|
||||
}
|
||||
|
||||
//
|
||||
// Program
|
||||
//
|
||||
|
||||
// Program represents a program node.
|
||||
type Program struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Body []Node // [ Statement ... ]
|
||||
BlockParams []string
|
||||
Chained bool
|
||||
|
||||
// whitespace management
|
||||
Strip *Strip
|
||||
}
|
||||
|
||||
// NewProgram instanciates a new program node.
|
||||
func NewProgram(pos int, line int) *Program {
|
||||
return &Program{
|
||||
NodeType: NodeProgram,
|
||||
Loc: Loc{pos, line},
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *Program) String() string {
|
||||
return fmt.Sprintf("Program{Pos: %d}", node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *Program) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitProgram(node)
|
||||
}
|
||||
|
||||
// AddStatement adds given statement to program.
|
||||
func (node *Program) AddStatement(statement Node) {
|
||||
node.Body = append(node.Body, statement)
|
||||
}
|
||||
|
||||
//
|
||||
// Mustache Statement
|
||||
//
|
||||
|
||||
// MustacheStatement represents a mustache node.
|
||||
type MustacheStatement struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Unescaped bool
|
||||
Expression *Expression
|
||||
|
||||
// whitespace management
|
||||
Strip *Strip
|
||||
}
|
||||
|
||||
// NewMustacheStatement instanciates a new mustache node.
|
||||
func NewMustacheStatement(pos int, line int, unescaped bool) *MustacheStatement {
|
||||
return &MustacheStatement{
|
||||
NodeType: NodeMustache,
|
||||
Loc: Loc{pos, line},
|
||||
Unescaped: unescaped,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *MustacheStatement) String() string {
|
||||
return fmt.Sprintf("Mustache{Pos: %d}", node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *MustacheStatement) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitMustache(node)
|
||||
}
|
||||
|
||||
//
|
||||
// Block Statement
|
||||
//
|
||||
|
||||
// BlockStatement represents a block node.
|
||||
type BlockStatement struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Expression *Expression
|
||||
|
||||
Program *Program
|
||||
Inverse *Program
|
||||
|
||||
// whitespace management
|
||||
OpenStrip *Strip
|
||||
InverseStrip *Strip
|
||||
CloseStrip *Strip
|
||||
}
|
||||
|
||||
// NewBlockStatement instanciates a new block node.
|
||||
func NewBlockStatement(pos int, line int) *BlockStatement {
|
||||
return &BlockStatement{
|
||||
NodeType: NodeBlock,
|
||||
Loc: Loc{pos, line},
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *BlockStatement) String() string {
|
||||
return fmt.Sprintf("Block{Pos: %d}", node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *BlockStatement) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitBlock(node)
|
||||
}
|
||||
|
||||
//
|
||||
// Partial Statement
|
||||
//
|
||||
|
||||
// PartialStatement represents a partial node.
|
||||
type PartialStatement struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Name Node // PathExpression | SubExpression
|
||||
Params []Node // [ Expression ... ]
|
||||
Hash *Hash
|
||||
|
||||
// whitespace management
|
||||
Strip *Strip
|
||||
Indent string
|
||||
}
|
||||
|
||||
// NewPartialStatement instanciates a new partial node.
|
||||
func NewPartialStatement(pos int, line int) *PartialStatement {
|
||||
return &PartialStatement{
|
||||
NodeType: NodePartial,
|
||||
Loc: Loc{pos, line},
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *PartialStatement) String() string {
|
||||
return fmt.Sprintf("Partial{Name:%s, Pos:%d}", node.Name, node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *PartialStatement) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitPartial(node)
|
||||
}
|
||||
|
||||
//
|
||||
// Content Statement
|
||||
//
|
||||
|
||||
// ContentStatement represents a content node.
|
||||
type ContentStatement struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Value string
|
||||
Original string
|
||||
|
||||
// whitespace management
|
||||
RightStripped bool
|
||||
LeftStripped bool
|
||||
}
|
||||
|
||||
// NewContentStatement instanciates a new content node.
|
||||
func NewContentStatement(pos int, line int, val string) *ContentStatement {
|
||||
return &ContentStatement{
|
||||
NodeType: NodeContent,
|
||||
Loc: Loc{pos, line},
|
||||
|
||||
Value: val,
|
||||
Original: val,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *ContentStatement) String() string {
|
||||
return fmt.Sprintf("Content{Value:'%s', Pos:%d}", node.Value, node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *ContentStatement) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitContent(node)
|
||||
}
|
||||
|
||||
//
|
||||
// Comment Statement
|
||||
//
|
||||
|
||||
// CommentStatement represents a comment node.
|
||||
type CommentStatement struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Value string
|
||||
|
||||
// whitespace management
|
||||
Strip *Strip
|
||||
}
|
||||
|
||||
// NewCommentStatement instanciates a new comment node.
|
||||
func NewCommentStatement(pos int, line int, val string) *CommentStatement {
|
||||
return &CommentStatement{
|
||||
NodeType: NodeComment,
|
||||
Loc: Loc{pos, line},
|
||||
|
||||
Value: val,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *CommentStatement) String() string {
|
||||
return fmt.Sprintf("Comment{Value:'%s', Pos:%d}", node.Value, node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *CommentStatement) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitComment(node)
|
||||
}
|
||||
|
||||
//
|
||||
// Expression
|
||||
//
|
||||
|
||||
// Expression represents an expression node.
|
||||
type Expression struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Path Node // PathExpression | StringLiteral | BooleanLiteral | NumberLiteral
|
||||
Params []Node // [ Expression ... ]
|
||||
Hash *Hash
|
||||
}
|
||||
|
||||
// NewExpression instanciates a new expression node.
|
||||
func NewExpression(pos int, line int) *Expression {
|
||||
return &Expression{
|
||||
NodeType: NodeExpression,
|
||||
Loc: Loc{pos, line},
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *Expression) String() string {
|
||||
return fmt.Sprintf("Expr{Path:%s, Pos:%d}", node.Path, node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *Expression) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitExpression(node)
|
||||
}
|
||||
|
||||
// HelperName returns helper name, or an empty string if this expression can't be a helper.
|
||||
func (node *Expression) HelperName() string {
|
||||
path, ok := node.Path.(*PathExpression)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
if path.Data || (len(path.Parts) != 1) || (path.Depth > 0) || path.Scoped {
|
||||
return ""
|
||||
}
|
||||
|
||||
return path.Parts[0]
|
||||
}
|
||||
|
||||
// FieldPath returns path expression representing a field path, or nil if this is not a field path.
|
||||
func (node *Expression) FieldPath() *PathExpression {
|
||||
path, ok := node.Path.(*PathExpression)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
// LiteralStr returns the string representation of literal value, with a boolean set to false if this is not a literal.
|
||||
func (node *Expression) LiteralStr() (string, bool) {
|
||||
return LiteralStr(node.Path)
|
||||
}
|
||||
|
||||
// Canonical returns the canonical form of expression node as a string.
|
||||
func (node *Expression) Canonical() string {
|
||||
if str, ok := HelperNameStr(node.Path); ok {
|
||||
return str
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// HelperNameStr returns the string representation of a helper name, with a boolean set to false if this is not a valid helper name.
|
||||
//
|
||||
// helperName : path | dataName | STRING | NUMBER | BOOLEAN | UNDEFINED | NULL
|
||||
func HelperNameStr(node Node) (string, bool) {
|
||||
// PathExpression
|
||||
if str, ok := PathExpressionStr(node); ok {
|
||||
return str, ok
|
||||
}
|
||||
|
||||
// Literal
|
||||
if str, ok := LiteralStr(node); ok {
|
||||
return str, ok
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// PathExpressionStr returns the string representation of path expression value, with a boolean set to false if this is not a path expression.
|
||||
func PathExpressionStr(node Node) (string, bool) {
|
||||
if path, ok := node.(*PathExpression); ok {
|
||||
result := path.Original
|
||||
|
||||
// "[foo bar]"" => "foo bar"
|
||||
if (len(result) >= 2) && (result[0] == '[') && (result[len(result)-1] == ']') {
|
||||
result = result[1 : len(result)-1]
|
||||
}
|
||||
|
||||
return result, true
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// LiteralStr returns the string representation of literal value, with a boolean set to false if this is not a literal.
|
||||
func LiteralStr(node Node) (string, bool) {
|
||||
if lit, ok := node.(*StringLiteral); ok {
|
||||
return lit.Value, true
|
||||
}
|
||||
|
||||
if lit, ok := node.(*BooleanLiteral); ok {
|
||||
return lit.Canonical(), true
|
||||
}
|
||||
|
||||
if lit, ok := node.(*NumberLiteral); ok {
|
||||
return lit.Canonical(), true
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
//
|
||||
// SubExpression
|
||||
//
|
||||
|
||||
// SubExpression represents a subexpression node.
|
||||
type SubExpression struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Expression *Expression
|
||||
}
|
||||
|
||||
// NewSubExpression instanciates a new subexpression node.
|
||||
func NewSubExpression(pos int, line int) *SubExpression {
|
||||
return &SubExpression{
|
||||
NodeType: NodeSubExpression,
|
||||
Loc: Loc{pos, line},
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *SubExpression) String() string {
|
||||
return fmt.Sprintf("Sexp{Path:%s, Pos:%d}", node.Expression.Path, node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *SubExpression) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitSubExpression(node)
|
||||
}
|
||||
|
||||
//
|
||||
// Path Expression
|
||||
//
|
||||
|
||||
// PathExpression represents a path expression node.
|
||||
type PathExpression struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Original string
|
||||
Depth int
|
||||
Parts []string
|
||||
Data bool
|
||||
Scoped bool
|
||||
}
|
||||
|
||||
// NewPathExpression instanciates a new path expression node.
|
||||
func NewPathExpression(pos int, line int, data bool) *PathExpression {
|
||||
result := &PathExpression{
|
||||
NodeType: NodePath,
|
||||
Loc: Loc{pos, line},
|
||||
|
||||
Data: data,
|
||||
}
|
||||
|
||||
if data {
|
||||
result.Original = "@"
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *PathExpression) String() string {
|
||||
return fmt.Sprintf("Path{Original:'%s', Pos:%d}", node.Original, node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *PathExpression) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitPath(node)
|
||||
}
|
||||
|
||||
// Part adds path part.
|
||||
func (node *PathExpression) Part(part string) {
|
||||
node.Original += part
|
||||
|
||||
switch part {
|
||||
case "..":
|
||||
node.Depth++
|
||||
node.Scoped = true
|
||||
case ".", "this":
|
||||
node.Scoped = true
|
||||
default:
|
||||
node.Parts = append(node.Parts, part)
|
||||
}
|
||||
}
|
||||
|
||||
// Sep adds path separator.
|
||||
func (node *PathExpression) Sep(separator string) {
|
||||
node.Original += separator
|
||||
}
|
||||
|
||||
// IsDataRoot returns true if path expression is @root.
|
||||
func (node *PathExpression) IsDataRoot() bool {
|
||||
return node.Data && (node.Parts[0] == "root")
|
||||
}
|
||||
|
||||
//
|
||||
// String Literal
|
||||
//
|
||||
|
||||
// StringLiteral represents a string node.
|
||||
type StringLiteral struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Value string
|
||||
}
|
||||
|
||||
// NewStringLiteral instanciates a new string node.
|
||||
func NewStringLiteral(pos int, line int, val string) *StringLiteral {
|
||||
return &StringLiteral{
|
||||
NodeType: NodeString,
|
||||
Loc: Loc{pos, line},
|
||||
|
||||
Value: val,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *StringLiteral) String() string {
|
||||
return fmt.Sprintf("String{Value:'%s', Pos:%d}", node.Value, node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *StringLiteral) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitString(node)
|
||||
}
|
||||
|
||||
//
|
||||
// Boolean Literal
|
||||
//
|
||||
|
||||
// BooleanLiteral represents a boolean node.
|
||||
type BooleanLiteral struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Value bool
|
||||
Original string
|
||||
}
|
||||
|
||||
// NewBooleanLiteral instanciates a new boolean node.
|
||||
func NewBooleanLiteral(pos int, line int, val bool, original string) *BooleanLiteral {
|
||||
return &BooleanLiteral{
|
||||
NodeType: NodeBoolean,
|
||||
Loc: Loc{pos, line},
|
||||
|
||||
Value: val,
|
||||
Original: original,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *BooleanLiteral) String() string {
|
||||
return fmt.Sprintf("Boolean{Value:%s, Pos:%d}", node.Canonical(), node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *BooleanLiteral) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitBoolean(node)
|
||||
}
|
||||
|
||||
// Canonical returns the canonical form of boolean node as a string (ie. "true" | "false").
|
||||
func (node *BooleanLiteral) Canonical() string {
|
||||
if node.Value {
|
||||
return "true"
|
||||
}
|
||||
|
||||
return "false"
|
||||
}
|
||||
|
||||
//
|
||||
// Number Literal
|
||||
//
|
||||
|
||||
// NumberLiteral represents a number node.
|
||||
type NumberLiteral struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Value float64
|
||||
IsInt bool
|
||||
Original string
|
||||
}
|
||||
|
||||
// NewNumberLiteral instanciates a new number node.
|
||||
func NewNumberLiteral(pos int, line int, val float64, isInt bool, original string) *NumberLiteral {
|
||||
return &NumberLiteral{
|
||||
NodeType: NodeNumber,
|
||||
Loc: Loc{pos, line},
|
||||
|
||||
Value: val,
|
||||
IsInt: isInt,
|
||||
Original: original,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *NumberLiteral) String() string {
|
||||
return fmt.Sprintf("Number{Value:%s, Pos:%d}", node.Canonical(), node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *NumberLiteral) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitNumber(node)
|
||||
}
|
||||
|
||||
// Canonical returns the canonical form of number node as a string (eg: "12", "-1.51").
|
||||
func (node *NumberLiteral) Canonical() string {
|
||||
prec := -1
|
||||
if node.IsInt {
|
||||
prec = 0
|
||||
}
|
||||
return strconv.FormatFloat(node.Value, 'f', prec, 64)
|
||||
}
|
||||
|
||||
// Number returns an integer or a float.
|
||||
func (node *NumberLiteral) Number() interface{} {
|
||||
if node.IsInt {
|
||||
return int(node.Value)
|
||||
}
|
||||
|
||||
return node.Value
|
||||
}
|
||||
|
||||
//
|
||||
// Hash
|
||||
//
|
||||
|
||||
// Hash represents a hash node.
|
||||
type Hash struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Pairs []*HashPair
|
||||
}
|
||||
|
||||
// NewHash instanciates a new hash node.
|
||||
func NewHash(pos int, line int) *Hash {
|
||||
return &Hash{
|
||||
NodeType: NodeHash,
|
||||
Loc: Loc{pos, line},
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *Hash) String() string {
|
||||
result := fmt.Sprintf("Hash{[%d", node.Loc.Pos)
|
||||
|
||||
for i, p := range node.Pairs {
|
||||
if i > 0 {
|
||||
result += ", "
|
||||
}
|
||||
result += p.String()
|
||||
}
|
||||
|
||||
return result + fmt.Sprintf("], Pos:%d}", node.Loc.Pos)
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *Hash) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitHash(node)
|
||||
}
|
||||
|
||||
//
|
||||
// HashPair
|
||||
//
|
||||
|
||||
// HashPair represents a hash pair node.
|
||||
type HashPair struct {
|
||||
NodeType
|
||||
Loc
|
||||
|
||||
Key string
|
||||
Val Node // Expression
|
||||
}
|
||||
|
||||
// NewHashPair instanciates a new hash pair node.
|
||||
func NewHashPair(pos int, line int) *HashPair {
|
||||
return &HashPair{
|
||||
NodeType: NodeHashPair,
|
||||
Loc: Loc{pos, line},
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of receiver that can be used for debugging.
|
||||
func (node *HashPair) String() string {
|
||||
return node.Key + "=" + node.Val.String()
|
||||
}
|
||||
|
||||
// Accept is the receiver entry point for visitors.
|
||||
func (node *HashPair) Accept(visitor Visitor) interface{} {
|
||||
return visitor.VisitHashPair(node)
|
||||
}
|
279
vendor/github.com/aymerick/raymond/ast/print.go
generated
vendored
Normal file
279
vendor/github.com/aymerick/raymond/ast/print.go
generated
vendored
Normal file
@ -0,0 +1,279 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// printVisitor implements the Visitor interface to print a AST.
|
||||
type printVisitor struct {
|
||||
buf string
|
||||
depth int
|
||||
|
||||
original bool
|
||||
inBlock bool
|
||||
}
|
||||
|
||||
func newPrintVisitor() *printVisitor {
|
||||
return &printVisitor{}
|
||||
}
|
||||
|
||||
// Print returns a string representation of given AST, that can be used for debugging purpose.
|
||||
func Print(node Node) string {
|
||||
visitor := newPrintVisitor()
|
||||
node.Accept(visitor)
|
||||
return visitor.output()
|
||||
}
|
||||
|
||||
func (v *printVisitor) output() string {
|
||||
return v.buf
|
||||
}
|
||||
|
||||
func (v *printVisitor) indent() {
|
||||
for i := 0; i < v.depth; {
|
||||
v.buf += " "
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func (v *printVisitor) str(val string) {
|
||||
v.buf += val
|
||||
}
|
||||
|
||||
func (v *printVisitor) nl() {
|
||||
v.str("\n")
|
||||
}
|
||||
|
||||
func (v *printVisitor) line(val string) {
|
||||
v.indent()
|
||||
v.str(val)
|
||||
v.nl()
|
||||
}
|
||||
|
||||
//
|
||||
// Visitor interface
|
||||
//
|
||||
|
||||
// Statements
|
||||
|
||||
// VisitProgram implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitProgram(node *Program) interface{} {
|
||||
if len(node.BlockParams) > 0 {
|
||||
v.line("BLOCK PARAMS: [ " + strings.Join(node.BlockParams, " ") + " ]")
|
||||
}
|
||||
|
||||
for _, n := range node.Body {
|
||||
n.Accept(v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitMustache implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitMustache(node *MustacheStatement) interface{} {
|
||||
v.indent()
|
||||
v.str("{{ ")
|
||||
|
||||
node.Expression.Accept(v)
|
||||
|
||||
v.str(" }}")
|
||||
v.nl()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitBlock implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitBlock(node *BlockStatement) interface{} {
|
||||
v.inBlock = true
|
||||
|
||||
v.line("BLOCK:")
|
||||
v.depth++
|
||||
|
||||
node.Expression.Accept(v)
|
||||
|
||||
if node.Program != nil {
|
||||
v.line("PROGRAM:")
|
||||
v.depth++
|
||||
node.Program.Accept(v)
|
||||
v.depth--
|
||||
}
|
||||
|
||||
if node.Inverse != nil {
|
||||
// if node.Program != nil {
|
||||
// v.depth++
|
||||
// }
|
||||
|
||||
v.line("{{^}}")
|
||||
v.depth++
|
||||
node.Inverse.Accept(v)
|
||||
v.depth--
|
||||
|
||||
// if node.Program != nil {
|
||||
// v.depth--
|
||||
// }
|
||||
}
|
||||
|
||||
v.inBlock = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitPartial implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitPartial(node *PartialStatement) interface{} {
|
||||
v.indent()
|
||||
v.str("{{> PARTIAL:")
|
||||
|
||||
v.original = true
|
||||
node.Name.Accept(v)
|
||||
v.original = false
|
||||
|
||||
if len(node.Params) > 0 {
|
||||
v.str(" ")
|
||||
node.Params[0].Accept(v)
|
||||
}
|
||||
|
||||
// hash
|
||||
if node.Hash != nil {
|
||||
v.str(" ")
|
||||
node.Hash.Accept(v)
|
||||
}
|
||||
|
||||
v.str(" }}")
|
||||
v.nl()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitContent implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitContent(node *ContentStatement) interface{} {
|
||||
v.line("CONTENT[ '" + node.Value + "' ]")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitComment implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitComment(node *CommentStatement) interface{} {
|
||||
v.line("{{! '" + node.Value + "' }}")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Expressions
|
||||
|
||||
// VisitExpression implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitExpression(node *Expression) interface{} {
|
||||
if v.inBlock {
|
||||
v.indent()
|
||||
}
|
||||
|
||||
// path
|
||||
node.Path.Accept(v)
|
||||
|
||||
// params
|
||||
v.str(" [")
|
||||
for i, n := range node.Params {
|
||||
if i > 0 {
|
||||
v.str(", ")
|
||||
}
|
||||
n.Accept(v)
|
||||
}
|
||||
v.str("]")
|
||||
|
||||
// hash
|
||||
if node.Hash != nil {
|
||||
v.str(" ")
|
||||
node.Hash.Accept(v)
|
||||
}
|
||||
|
||||
if v.inBlock {
|
||||
v.nl()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitSubExpression implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitSubExpression(node *SubExpression) interface{} {
|
||||
node.Expression.Accept(v)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitPath implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitPath(node *PathExpression) interface{} {
|
||||
if v.original {
|
||||
v.str(node.Original)
|
||||
} else {
|
||||
path := strings.Join(node.Parts, "/")
|
||||
|
||||
result := ""
|
||||
if node.Data {
|
||||
result += "@"
|
||||
}
|
||||
|
||||
v.str(result + "PATH:" + path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Literals
|
||||
|
||||
// VisitString implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitString(node *StringLiteral) interface{} {
|
||||
if v.original {
|
||||
v.str(node.Value)
|
||||
} else {
|
||||
v.str("\"" + node.Value + "\"")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitBoolean implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitBoolean(node *BooleanLiteral) interface{} {
|
||||
if v.original {
|
||||
v.str(node.Original)
|
||||
} else {
|
||||
v.str(fmt.Sprintf("BOOLEAN{%s}", node.Canonical()))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitNumber implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitNumber(node *NumberLiteral) interface{} {
|
||||
if v.original {
|
||||
v.str(node.Original)
|
||||
} else {
|
||||
v.str(fmt.Sprintf("NUMBER{%s}", node.Canonical()))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Miscellaneous
|
||||
|
||||
// VisitHash implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitHash(node *Hash) interface{} {
|
||||
v.str("HASH{")
|
||||
|
||||
for i, p := range node.Pairs {
|
||||
if i > 0 {
|
||||
v.str(", ")
|
||||
}
|
||||
p.Accept(v)
|
||||
}
|
||||
|
||||
v.str("}")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitHashPair implements corresponding Visitor interface method
|
||||
func (v *printVisitor) VisitHashPair(node *HashPair) interface{} {
|
||||
v.str(node.Key + "=")
|
||||
node.Val.Accept(v)
|
||||
|
||||
return nil
|
||||
}
|
167
vendor/github.com/aymerick/raymond/base_test.go
generated
vendored
Normal file
167
vendor/github.com/aymerick/raymond/base_test.go
generated
vendored
Normal file
@ -0,0 +1,167 @@
|
||||
package raymond
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Test struct {
|
||||
name string
|
||||
input string
|
||||
data interface{}
|
||||
privData map[string]interface{}
|
||||
helpers map[string]interface{}
|
||||
partials map[string]string
|
||||
output interface{}
|
||||
}
|
||||
|
||||
func launchTests(t *testing.T, tests []Test) {
|
||||
// NOTE: TestMustache() makes Parallel testing fail
|
||||
// t.Parallel()
|
||||
|
||||
for _, test := range tests {
|
||||
var err error
|
||||
var tpl *Template
|
||||
|
||||
// parse template
|
||||
tpl, err = Parse(test.input)
|
||||
if err != nil {
|
||||
t.Errorf("Test '%s' failed - Failed to parse template\ninput:\n\t'%s'\nerror:\n\t%s", test.name, test.input, err)
|
||||
} else {
|
||||
if len(test.helpers) > 0 {
|
||||
// register helpers
|
||||
tpl.RegisterHelpers(test.helpers)
|
||||
}
|
||||
|
||||
if len(test.partials) > 0 {
|
||||
// register partials
|
||||
tpl.RegisterPartials(test.partials)
|
||||
}
|
||||
|
||||
// setup private data frame
|
||||
var privData *DataFrame
|
||||
if test.privData != nil {
|
||||
privData = NewDataFrame()
|
||||
for k, v := range test.privData {
|
||||
privData.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// render template
|
||||
output, err := tpl.ExecWith(test.data, privData)
|
||||
if err != nil {
|
||||
t.Errorf("Test '%s' failed\ninput:\n\t'%s'\ndata:\n\t%s\nerror:\n\t%s\nAST:\n\t%s", test.name, test.input, Str(test.data), err, tpl.PrintAST())
|
||||
} else {
|
||||
// check output
|
||||
var expectedArr []string
|
||||
expectedArr, ok := test.output.([]string)
|
||||
if ok {
|
||||
match := false
|
||||
for _, expectedStr := range expectedArr {
|
||||
if expectedStr == output {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !match {
|
||||
t.Errorf("Test '%s' failed\ninput:\n\t'%s'\ndata:\n\t%s\npartials:\n\t%s\nexpected\n\t%q\ngot\n\t%q\nAST:\n%s", test.name, test.input, Str(test.data), Str(test.partials), expectedArr, output, tpl.PrintAST())
|
||||
}
|
||||
} else {
|
||||
expectedStr, ok := test.output.(string)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("Erroneous test output description: %q", test.output))
|
||||
}
|
||||
|
||||
if expectedStr != output {
|
||||
t.Errorf("Test '%s' failed\ninput:\n\t'%s'\ndata:\n\t%s\npartials:\n\t%s\nexpected\n\t%q\ngot\n\t%q\nAST:\n%s", test.name, test.input, Str(test.data), Str(test.partials), expectedStr, output, tpl.PrintAST())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func launchErrorTests(t *testing.T, tests []Test) {
|
||||
t.Parallel()
|
||||
|
||||
for _, test := range tests {
|
||||
var err error
|
||||
var tpl *Template
|
||||
|
||||
// parse template
|
||||
tpl, err = Parse(test.input)
|
||||
if err != nil {
|
||||
t.Errorf("Test '%s' failed - Failed to parse template\ninput:\n\t'%s'\nerror:\n\t%s", test.name, test.input, err)
|
||||
} else {
|
||||
if len(test.helpers) > 0 {
|
||||
// register helpers
|
||||
tpl.RegisterHelpers(test.helpers)
|
||||
}
|
||||
|
||||
if len(test.partials) > 0 {
|
||||
// register partials
|
||||
tpl.RegisterPartials(test.partials)
|
||||
}
|
||||
|
||||
// setup private data frame
|
||||
var privData *DataFrame
|
||||
if test.privData != nil {
|
||||
privData := NewDataFrame()
|
||||
for k, v := range test.privData {
|
||||
privData.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// render template
|
||||
output, err := tpl.ExecWith(test.data, privData)
|
||||
if err == nil {
|
||||
t.Errorf("Test '%s' failed - Error expected\ninput:\n\t'%s'\ngot\n\t%q\nAST:\n%q", test.name, test.input, output, tpl.PrintAST())
|
||||
} else {
|
||||
var errMatch error
|
||||
match := false
|
||||
|
||||
// check output
|
||||
var expectedArr []string
|
||||
expectedArr, ok := test.output.([]string)
|
||||
if ok {
|
||||
if len(expectedArr) > 0 {
|
||||
for _, expectedStr := range expectedArr {
|
||||
match, errMatch = regexp.MatchString(regexp.QuoteMeta(expectedStr), fmt.Sprint(err))
|
||||
if errMatch != nil {
|
||||
panic("Failed to match regexp")
|
||||
}
|
||||
|
||||
if match {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// nothing to test
|
||||
match = true
|
||||
}
|
||||
} else {
|
||||
expectedStr, ok := test.output.(string)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("Erroneous test output description: %q", test.output))
|
||||
}
|
||||
|
||||
if expectedStr != "" {
|
||||
match, errMatch = regexp.MatchString(regexp.QuoteMeta(expectedStr), fmt.Sprint(err))
|
||||
if errMatch != nil {
|
||||
panic("Failed to match regexp")
|
||||
}
|
||||
} else {
|
||||
// nothing to test
|
||||
match = true
|
||||
}
|
||||
}
|
||||
|
||||
if !match {
|
||||
t.Errorf("Test '%s' failed - Incorrect error returned\ninput:\n\t'%s'\ndata:\n\t%s\nexpected\n\t%q\ngot\n\t%q", test.name, test.input, Str(test.data), test.output, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
316
vendor/github.com/aymerick/raymond/benchmark_test.go
generated
vendored
Normal file
316
vendor/github.com/aymerick/raymond/benchmark_test.go
generated
vendored
Normal file
@ -0,0 +1,316 @@
|
||||
package raymond
|
||||
|
||||
import "testing"
|
||||
|
||||
//
|
||||
// Those tests come from:
|
||||
// https://github.com/wycats/handlebars.js/blob/master/bench/
|
||||
//
|
||||
// Note that handlebars.js does NOT benchmark template compilation, it only benchmarks evaluation.
|
||||
//
|
||||
|
||||
func BenchmarkArguments(b *testing.B) {
|
||||
source := `{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}`
|
||||
|
||||
ctx := map[string]bool{
|
||||
"bar": true,
|
||||
}
|
||||
|
||||
tpl := MustParse(source)
|
||||
tpl.RegisterHelper("foo", func(a, b, c, d interface{}) string { return "" })
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tpl.MustExec(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkArrayEach(b *testing.B) {
|
||||
source := `{{#each names}}{{name}}{{/each}}`
|
||||
|
||||
ctx := map[string][]map[string]string{
|
||||
"names": {
|
||||
{"name": "Moe"},
|
||||
{"name": "Larry"},
|
||||
{"name": "Curly"},
|
||||
{"name": "Shemp"},
|
||||
},
|
||||
}
|
||||
|
||||
tpl := MustParse(source)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tpl.MustExec(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkArrayMustache(b *testing.B) {
|
||||
source := `{{#names}}{{name}}{{/names}}`
|
||||
|
||||
ctx := map[string][]map[string]string{
|
||||
"names": {
|
||||
{"name": "Moe"},
|
||||
{"name": "Larry"},
|
||||
{"name": "Curly"},
|
||||
{"name": "Shemp"},
|
||||
},
|
||||
}
|
||||
|
||||
tpl := MustParse(source)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tpl.MustExec(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkComplex(b *testing.B) {
|
||||
source := `<h1>{{header}}</h1>
|
||||
{{#if items}}
|
||||
<ul>
|
||||
{{#each items}}
|
||||
{{#if current}}
|
||||
<li><strong>{{name}}</strong></li>
|
||||
{{^}}
|
||||
<li><a href="{{url}}">{{name}}</a></li>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{^}}
|
||||
<p>The list is empty.</p>
|
||||
{{/if}}
|
||||
`
|
||||
|
||||
ctx := map[string]interface{}{
|
||||
"header": func() string { return "Colors" },
|
||||
"hasItems": true,
|
||||
"items": []map[string]interface{}{
|
||||
{"name": "red", "current": true, "url": "#Red"},
|
||||
{"name": "green", "current": false, "url": "#Green"},
|
||||
{"name": "blue", "current": false, "url": "#Blue"},
|
||||
},
|
||||
}
|
||||
|
||||
tpl := MustParse(source)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tpl.MustExec(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkData(b *testing.B) {
|
||||
source := `{{#each names}}{{@index}}{{name}}{{/each}}`
|
||||
|
||||
ctx := map[string][]map[string]string{
|
||||
"names": {
|
||||
{"name": "Moe"},
|
||||
{"name": "Larry"},
|
||||
{"name": "Curly"},
|
||||
{"name": "Shemp"},
|
||||
},
|
||||
}
|
||||
|
||||
tpl := MustParse(source)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tpl.MustExec(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDepth1(b *testing.B) {
|
||||
source := `{{#each names}}{{../foo}}{{/each}}`
|
||||
|
||||
ctx := map[string]interface{}{
|
||||
"names": []map[string]string{
|
||||
{"name": "Moe"},
|
||||
{"name": "Larry"},
|
||||
{"name": "Curly"},
|
||||
{"name": "Shemp"},
|
||||
},
|
||||
"foo": "bar",
|
||||
}
|
||||
|
||||
tpl := MustParse(source)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tpl.MustExec(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDepth2(b *testing.B) {
|
||||
source := `{{#each names}}{{#each name}}{{../bat}}{{../../foo}}{{/each}}{{/each}}`
|
||||
|
||||
ctx := map[string]interface{}{
|
||||
"names": []map[string]interface{}{
|
||||
{"bat": "foo", "name": []string{"Moe"}},
|
||||
{"bat": "foo", "name": []string{"Larry"}},
|
||||
{"bat": "foo", "name": []string{"Curly"}},
|
||||
{"bat": "foo", "name": []string{"Shemp"}},
|
||||
},
|
||||
"foo": "bar",
|
||||
}
|
||||
|
||||
tpl := MustParse(source)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tpl.MustExec(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkObjectMustache(b *testing.B) {
|
||||
source := `{{#person}}{{name}}{{age}}{{/person}}`
|
||||
|
||||
ctx := map[string]interface{}{
|
||||
"person": map[string]interface{}{
|
||||
"name": "Larry",
|
||||
"age": 45,
|
||||
},
|
||||
}
|
||||
|
||||
tpl := MustParse(source)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tpl.MustExec(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkObject(b *testing.B) {
|
||||
source := `{{#with person}}{{name}}{{age}}{{/with}}`
|
||||
|
||||
ctx := map[string]interface{}{
|
||||
"person": map[string]interface{}{
|
||||
"name": "Larry",
|
||||
"age": 45,
|
||||
},
|
||||
}
|
||||
|
||||
tpl := MustParse(source)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tpl.MustExec(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPartialRecursion(b *testing.B) {
|
||||
source := `{{name}}{{#each kids}}{{>recursion}}{{/each}}`
|
||||
|
||||
ctx := map[string]interface{}{
|
||||
"name": 1,
|
||||
"kids": []map[string]interface{}{
|
||||
{
|
||||
"name": "1.1",
|
||||
"kids": []map[string]interface{}{
|
||||
{
|
||||
"name": "1.1.1",
|
||||
"kids": []map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tpl := MustParse(source)
|
||||
|
||||
partial := MustParse(`{{name}}{{#each kids}}{{>recursion}}{{/each}}`)
|
||||
tpl.RegisterPartialTemplate("recursion", partial)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tpl.MustExec(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPartial(b *testing.B) {
|
||||
source := `{{#each peeps}}{{>variables}}{{/each}}`
|
||||
|
||||
ctx := map[string]interface{}{
|
||||
"peeps": []map[string]interface{}{
|
||||
{"name": "Moe", "count": 15},
|
||||
{"name": "Moe", "count": 5},
|
||||
{"name": "Curly", "count": 1},
|
||||
},
|
||||
}
|
||||
|
||||
tpl := MustParse(source)
|
||||
|
||||
partial := MustParse(`Hello {{name}}! You have {{count}} new messages.`)
|
||||
tpl.RegisterPartialTemplate("variables", partial)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tpl.MustExec(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPath(b *testing.B) {
|
||||
source := `{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}`
|
||||
|
||||
ctx := map[string]interface{}{
|
||||
"person": map[string]interface{}{
|
||||
"name": map[string]interface{}{
|
||||
"bar": map[string]string{
|
||||
"baz": "Larry",
|
||||
},
|
||||
},
|
||||
"age": 45,
|
||||
},
|
||||
}
|
||||
|
||||
tpl := MustParse(source)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tpl.MustExec(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkString(b *testing.B) {
|
||||
source := `Hello world`
|
||||
|
||||
tpl := MustParse(source)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tpl.MustExec(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSubExpression(b *testing.B) {
|
||||
source := `{{echo (header)}}`
|
||||
|
||||
ctx := map[string]interface{}{}
|
||||
|
||||
tpl := MustParse(source)
|
||||
tpl.RegisterHelpers(map[string]interface{}{
|
||||
"echo": func(v string) string { return "foo " + v },
|
||||
"header": func() string { return "Colors" },
|
||||
})
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tpl.MustExec(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkVariables(b *testing.B) {
|
||||
source := `Hello {{name}}! You have {{count}} new messages.`
|
||||
|
||||
ctx := map[string]interface{}{
|
||||
"name": "Mick",
|
||||
"count": 30,
|
||||
}
|
||||
|
||||
tpl := MustParse(source)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tpl.MustExec(ctx)
|
||||
}
|
||||
}
|
95
vendor/github.com/aymerick/raymond/data_frame.go
generated
vendored
Normal file
95
vendor/github.com/aymerick/raymond/data_frame.go
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
package raymond
|
||||
|
||||
import "reflect"
|
||||
|
||||
// DataFrame represents a private data frame.
|
||||
//
|
||||
// Cf. private variables documentation at: http://handlebarsjs.com/block_helpers.html
|
||||
type DataFrame struct {
|
||||
parent *DataFrame
|
||||
data map[string]interface{}
|
||||
}
|
||||
|
||||
// NewDataFrame instanciates a new private data frame.
|
||||
func NewDataFrame() *DataFrame {
|
||||
return &DataFrame{
|
||||
data: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Copy instanciates a new private data frame with receiver as parent.
|
||||
func (p *DataFrame) Copy() *DataFrame {
|
||||
result := NewDataFrame()
|
||||
|
||||
for k, v := range p.data {
|
||||
result.data[k] = v
|
||||
}
|
||||
|
||||
result.parent = p
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// newIterDataFrame instanciates a new private data frame with receiver as parent and with iteration data set (@index, @key, @first, @last)
|
||||
func (p *DataFrame) newIterDataFrame(length int, i int, key interface{}) *DataFrame {
|
||||
result := p.Copy()
|
||||
|
||||
result.Set("index", i)
|
||||
result.Set("key", key)
|
||||
result.Set("first", i == 0)
|
||||
result.Set("last", i == length-1)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Set sets a data value.
|
||||
func (p *DataFrame) Set(key string, val interface{}) {
|
||||
p.data[key] = val
|
||||
}
|
||||
|
||||
// Get gets a data value.
|
||||
func (p *DataFrame) Get(key string) interface{} {
|
||||
return p.find([]string{key})
|
||||
}
|
||||
|
||||
// find gets a deep data value
|
||||
//
|
||||
// @todo This is NOT consistent with the way we resolve data in template (cf. `evalDataPathExpression()`) ! FIX THAT !
|
||||
func (p *DataFrame) find(parts []string) interface{} {
|
||||
data := p.data
|
||||
|
||||
for i, part := range parts {
|
||||
val := data[part]
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if i == len(parts)-1 {
|
||||
// found
|
||||
return val
|
||||
}
|
||||
|
||||
valValue := reflect.ValueOf(val)
|
||||
if valValue.Kind() != reflect.Map {
|
||||
// not found
|
||||
return nil
|
||||
}
|
||||
|
||||
// continue
|
||||
data = mapStringInterface(valValue)
|
||||
}
|
||||
|
||||
// not found
|
||||
return nil
|
||||
}
|
||||
|
||||
// mapStringInterface converts any `map` to `map[string]interface{}`
|
||||
func mapStringInterface(value reflect.Value) map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
|
||||
for _, key := range value.MapKeys() {
|
||||
result[strValue(key)] = value.MapIndex(key).Interface()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
65
vendor/github.com/aymerick/raymond/escape.go
generated
vendored
Normal file
65
vendor/github.com/aymerick/raymond/escape.go
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
package raymond
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//
|
||||
// That whole file is borrowed from https://github.com/golang/go/tree/master/src/html/escape.go
|
||||
//
|
||||
// With changes:
|
||||
// ' => '
|
||||
// " => "
|
||||
//
|
||||
// To stay in sync with JS implementation, and make mustache tests pass.
|
||||
//
|
||||
|
||||
type writer interface {
|
||||
WriteString(string) (int, error)
|
||||
}
|
||||
|
||||
const escapedChars = `&'<>"`
|
||||
|
||||
func escape(w writer, s string) error {
|
||||
i := strings.IndexAny(s, escapedChars)
|
||||
for i != -1 {
|
||||
if _, err := w.WriteString(s[:i]); err != nil {
|
||||
return err
|
||||
}
|
||||
var esc string
|
||||
switch s[i] {
|
||||
case '&':
|
||||
esc = "&"
|
||||
case '\'':
|
||||
esc = "'"
|
||||
case '<':
|
||||
esc = "<"
|
||||
case '>':
|
||||
esc = ">"
|
||||
case '"':
|
||||
esc = """
|
||||
default:
|
||||
panic("unrecognized escape character")
|
||||
}
|
||||
s = s[i+1:]
|
||||
if _, err := w.WriteString(esc); err != nil {
|
||||
return err
|
||||
}
|
||||
i = strings.IndexAny(s, escapedChars)
|
||||
}
|
||||
_, err := w.WriteString(s)
|
||||
return err
|
||||
}
|
||||
|
||||
// Escape escapes special HTML characters.
|
||||
//
|
||||
// It can be used by helpers that return a SafeString and that need to escape some content by themselves.
|
||||
func Escape(s string) string {
|
||||
if strings.IndexAny(s, escapedChars) == -1 {
|
||||
return s
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
escape(&buf, s)
|
||||
return buf.String()
|
||||
}
|
20
vendor/github.com/aymerick/raymond/escape_test.go
generated
vendored
Normal file
20
vendor/github.com/aymerick/raymond/escape_test.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
package raymond
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleEscape() {
|
||||
tpl := MustParse("{{link url text}}")
|
||||
|
||||
tpl.RegisterHelper("link", func(url string, text string) SafeString {
|
||||
return SafeString("<a href='" + Escape(url) + "'>" + Escape(text) + "</a>")
|
||||
})
|
||||
|
||||
ctx := map[string]string{
|
||||
"url": "http://www.aymerick.com/",
|
||||
"text": "This is a <em>cool</em> website",
|
||||
}
|
||||
|
||||
result := tpl.MustExec(ctx)
|
||||
fmt.Print(result)
|
||||
// Output: <a href='http://www.aymerick.com/'>This is a <em>cool</em> website</a>
|
||||
}
|
984
vendor/github.com/aymerick/raymond/eval.go
generated
vendored
Normal file
984
vendor/github.com/aymerick/raymond/eval.go
generated
vendored
Normal file
@ -0,0 +1,984 @@
|
||||
package raymond
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/aymerick/raymond/ast"
|
||||
)
|
||||
|
||||
var (
|
||||
// @note borrowed from https://github.com/golang/go/tree/master/src/text/template/exec.go
|
||||
errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
|
||||
|
||||
zero reflect.Value
|
||||
)
|
||||
|
||||
// evalVisitor evaluates a handlebars template with context
|
||||
type evalVisitor struct {
|
||||
tpl *Template
|
||||
|
||||
// contexts stack
|
||||
ctx []reflect.Value
|
||||
|
||||
// current data frame (chained with parent)
|
||||
dataFrame *DataFrame
|
||||
|
||||
// block parameters stack
|
||||
blockParams []map[string]interface{}
|
||||
|
||||
// block statements stack
|
||||
blocks []*ast.BlockStatement
|
||||
|
||||
// expressions stack
|
||||
exprs []*ast.Expression
|
||||
|
||||
// memoize expressions that were function calls
|
||||
exprFunc map[*ast.Expression]bool
|
||||
|
||||
// used for info on panic
|
||||
curNode ast.Node
|
||||
}
|
||||
|
||||
// NewEvalVisitor instanciate a new evaluation visitor with given context and initial private data frame
|
||||
//
|
||||
// If privData is nil, then a default data frame is created
|
||||
func newEvalVisitor(tpl *Template, ctx interface{}, privData *DataFrame) *evalVisitor {
|
||||
frame := privData
|
||||
if frame == nil {
|
||||
frame = NewDataFrame()
|
||||
}
|
||||
|
||||
return &evalVisitor{
|
||||
tpl: tpl,
|
||||
ctx: []reflect.Value{reflect.ValueOf(ctx)},
|
||||
dataFrame: frame,
|
||||
exprFunc: make(map[*ast.Expression]bool),
|
||||
}
|
||||
}
|
||||
|
||||
// at sets current node
|
||||
func (v *evalVisitor) at(node ast.Node) {
|
||||
v.curNode = node
|
||||
}
|
||||
|
||||
//
|
||||
// Contexts stack
|
||||
//
|
||||
|
||||
// pushCtx pushes new context to the stack
|
||||
func (v *evalVisitor) pushCtx(ctx reflect.Value) {
|
||||
v.ctx = append(v.ctx, ctx)
|
||||
}
|
||||
|
||||
// popCtx pops last context from stack
|
||||
func (v *evalVisitor) popCtx() reflect.Value {
|
||||
if len(v.ctx) == 0 {
|
||||
return zero
|
||||
}
|
||||
|
||||
var result reflect.Value
|
||||
result, v.ctx = v.ctx[len(v.ctx)-1], v.ctx[:len(v.ctx)-1]
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// rootCtx returns root context
|
||||
func (v *evalVisitor) rootCtx() reflect.Value {
|
||||
return v.ctx[0]
|
||||
}
|
||||
|
||||
// curCtx returns current context
|
||||
func (v *evalVisitor) curCtx() reflect.Value {
|
||||
return v.ancestorCtx(0)
|
||||
}
|
||||
|
||||
// ancestorCtx returns ancestor context
|
||||
func (v *evalVisitor) ancestorCtx(depth int) reflect.Value {
|
||||
index := len(v.ctx) - 1 - depth
|
||||
if index < 0 {
|
||||
return zero
|
||||
}
|
||||
|
||||
return v.ctx[index]
|
||||
}
|
||||
|
||||
//
|
||||
// Private data frame
|
||||
//
|
||||
|
||||
// setDataFrame sets new data frame
|
||||
func (v *evalVisitor) setDataFrame(frame *DataFrame) {
|
||||
v.dataFrame = frame
|
||||
}
|
||||
|
||||
// popDataFrame sets back parent data frame
|
||||
func (v *evalVisitor) popDataFrame() {
|
||||
v.dataFrame = v.dataFrame.parent
|
||||
}
|
||||
|
||||
//
|
||||
// Block Parameters stack
|
||||
//
|
||||
|
||||
// pushBlockParams pushes new block params to the stack
|
||||
func (v *evalVisitor) pushBlockParams(params map[string]interface{}) {
|
||||
v.blockParams = append(v.blockParams, params)
|
||||
}
|
||||
|
||||
// popBlockParams pops last block params from stack
|
||||
func (v *evalVisitor) popBlockParams() map[string]interface{} {
|
||||
var result map[string]interface{}
|
||||
|
||||
if len(v.blockParams) == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
result, v.blockParams = v.blockParams[len(v.blockParams)-1], v.blockParams[:len(v.blockParams)-1]
|
||||
return result
|
||||
}
|
||||
|
||||
// blockParam iterates on stack to find given block parameter, and returns its value or nil if not founc
|
||||
func (v *evalVisitor) blockParam(name string) interface{} {
|
||||
for i := len(v.blockParams) - 1; i >= 0; i-- {
|
||||
for k, v := range v.blockParams[i] {
|
||||
if name == k {
|
||||
return v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//
|
||||
// Blocks stack
|
||||
//
|
||||
|
||||
// pushBlock pushes new block statement to stack
|
||||
func (v *evalVisitor) pushBlock(block *ast.BlockStatement) {
|
||||
v.blocks = append(v.blocks, block)
|
||||
}
|
||||
|
||||
// popBlock pops last block statement from stack
|
||||
func (v *evalVisitor) popBlock() *ast.BlockStatement {
|
||||
if len(v.blocks) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var result *ast.BlockStatement
|
||||
result, v.blocks = v.blocks[len(v.blocks)-1], v.blocks[:len(v.blocks)-1]
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// curBlock returns current block statement
|
||||
func (v *evalVisitor) curBlock() *ast.BlockStatement {
|
||||
if len(v.blocks) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return v.blocks[len(v.blocks)-1]
|
||||
}
|
||||
|
||||
//
|
||||
// Expressions stack
|
||||
//
|
||||
|
||||
// pushExpr pushes new expression to stack
|
||||
func (v *evalVisitor) pushExpr(expression *ast.Expression) {
|
||||
v.exprs = append(v.exprs, expression)
|
||||
}
|
||||
|
||||
// popExpr pops last expression from stack
|
||||
func (v *evalVisitor) popExpr() *ast.Expression {
|
||||
if len(v.exprs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var result *ast.Expression
|
||||
result, v.exprs = v.exprs[len(v.exprs)-1], v.exprs[:len(v.exprs)-1]
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// curExpr returns current expression
|
||||
func (v *evalVisitor) curExpr() *ast.Expression {
|
||||
if len(v.exprs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return v.exprs[len(v.exprs)-1]
|
||||
}
|
||||
|
||||
//
|
||||
// Error functions
|
||||
//
|
||||
|
||||
// errPanic panics
|
||||
func (v *evalVisitor) errPanic(err error) {
|
||||
panic(fmt.Errorf("Evaluation error: %s\nCurrent node:\n\t%s", err, v.curNode))
|
||||
}
|
||||
|
||||
// errorf panics with a custom message
|
||||
func (v *evalVisitor) errorf(format string, args ...interface{}) {
|
||||
v.errPanic(fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
//
|
||||
// Evaluation
|
||||
//
|
||||
|
||||
// evalProgram eEvaluates program with given context and returns string result
|
||||
func (v *evalVisitor) evalProgram(program *ast.Program, ctx interface{}, data *DataFrame, key interface{}) string {
|
||||
blockParams := make(map[string]interface{})
|
||||
|
||||
// compute block params
|
||||
if len(program.BlockParams) > 0 {
|
||||
blockParams[program.BlockParams[0]] = ctx
|
||||
}
|
||||
|
||||
if (len(program.BlockParams) > 1) && (key != nil) {
|
||||
blockParams[program.BlockParams[1]] = key
|
||||
}
|
||||
|
||||
// push contexts
|
||||
if len(blockParams) > 0 {
|
||||
v.pushBlockParams(blockParams)
|
||||
}
|
||||
|
||||
ctxVal := reflect.ValueOf(ctx)
|
||||
if ctxVal.IsValid() {
|
||||
v.pushCtx(ctxVal)
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
v.setDataFrame(data)
|
||||
}
|
||||
|
||||
// evaluate program
|
||||
result, _ := program.Accept(v).(string)
|
||||
|
||||
// pop contexts
|
||||
if data != nil {
|
||||
v.popDataFrame()
|
||||
}
|
||||
|
||||
if ctxVal.IsValid() {
|
||||
v.popCtx()
|
||||
}
|
||||
|
||||
if len(blockParams) > 0 {
|
||||
v.popBlockParams()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// evalPath evaluates all path parts with given context
|
||||
func (v *evalVisitor) evalPath(ctx reflect.Value, parts []string, exprRoot bool) (reflect.Value, bool) {
|
||||
partResolved := false
|
||||
|
||||
for i := 0; i < len(parts); i++ {
|
||||
part := parts[i]
|
||||
|
||||
// "[foo bar]"" => "foo bar"
|
||||
if (len(part) >= 2) && (part[0] == '[') && (part[len(part)-1] == ']') {
|
||||
part = part[1 : len(part)-1]
|
||||
}
|
||||
|
||||
ctx = v.evalField(ctx, part, exprRoot)
|
||||
if !ctx.IsValid() {
|
||||
break
|
||||
}
|
||||
|
||||
// we resolved at least one part of path
|
||||
partResolved = true
|
||||
}
|
||||
|
||||
return ctx, partResolved
|
||||
}
|
||||
|
||||
// evalField evaluates field with given context
|
||||
func (v *evalVisitor) evalField(ctx reflect.Value, fieldName string, exprRoot bool) reflect.Value {
|
||||
result := zero
|
||||
|
||||
ctx, _ = indirect(ctx)
|
||||
if !ctx.IsValid() {
|
||||
return result
|
||||
}
|
||||
|
||||
// check if this is a method call
|
||||
result, isMeth := v.evalMethod(ctx, fieldName, exprRoot)
|
||||
if !isMeth {
|
||||
switch ctx.Kind() {
|
||||
case reflect.Struct:
|
||||
// example: firstName => FirstName
|
||||
expFieldName := strings.Title(fieldName)
|
||||
|
||||
// check if struct have this field and that it is exported
|
||||
if tField, ok := ctx.Type().FieldByName(expFieldName); ok && (tField.PkgPath == "") {
|
||||
// struct field
|
||||
result = ctx.FieldByIndex(tField.Index)
|
||||
}
|
||||
case reflect.Map:
|
||||
nameVal := reflect.ValueOf(fieldName)
|
||||
if nameVal.Type().AssignableTo(ctx.Type().Key()) {
|
||||
// map key
|
||||
result = ctx.MapIndex(nameVal)
|
||||
}
|
||||
case reflect.Array, reflect.Slice:
|
||||
if i, err := strconv.Atoi(fieldName); (err == nil) && (i < ctx.Len()) {
|
||||
result = ctx.Index(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if result is a function
|
||||
result, _ = indirect(result)
|
||||
if result.Kind() == reflect.Func {
|
||||
result = v.evalFieldFunc(fieldName, result, exprRoot)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// evalFieldFunc tries to evaluate given method name, and a boolean to indicate if this was a method call
|
||||
func (v *evalVisitor) evalMethod(ctx reflect.Value, name string, exprRoot bool) (reflect.Value, bool) {
|
||||
if ctx.Kind() != reflect.Interface && ctx.CanAddr() {
|
||||
ctx = ctx.Addr()
|
||||
}
|
||||
|
||||
method := ctx.MethodByName(name)
|
||||
if !method.IsValid() {
|
||||
// example: subject() => Subject()
|
||||
method = ctx.MethodByName(strings.Title(name))
|
||||
}
|
||||
|
||||
if !method.IsValid() {
|
||||
return zero, false
|
||||
}
|
||||
|
||||
return v.evalFieldFunc(name, method, exprRoot), true
|
||||
}
|
||||
|
||||
// evalFieldFunc evaluates given function
|
||||
func (v *evalVisitor) evalFieldFunc(name string, funcVal reflect.Value, exprRoot bool) reflect.Value {
|
||||
ensureValidHelper(name, funcVal)
|
||||
|
||||
var options *Options
|
||||
if exprRoot {
|
||||
// create function arg with all params/hash
|
||||
expr := v.curExpr()
|
||||
options = v.helperOptions(expr)
|
||||
|
||||
// ok, that expression was a function call
|
||||
v.exprFunc[expr] = true
|
||||
} else {
|
||||
// we are not at root of expression, so we are a parameter... and we don't like
|
||||
// infinite loops caused by trying to parse ourself forever
|
||||
options = newEmptyOptions(v)
|
||||
}
|
||||
|
||||
return v.callFunc(name, funcVal, options)
|
||||
}
|
||||
|
||||
// findBlockParam returns node's block parameter
|
||||
func (v *evalVisitor) findBlockParam(node *ast.PathExpression) (string, interface{}) {
|
||||
if len(node.Parts) > 0 {
|
||||
name := node.Parts[0]
|
||||
if value := v.blockParam(name); value != nil {
|
||||
return name, value
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// evalPathExpression evaluates a path expression
|
||||
func (v *evalVisitor) evalPathExpression(node *ast.PathExpression, exprRoot bool) interface{} {
|
||||
var result interface{}
|
||||
|
||||
if name, value := v.findBlockParam(node); value != nil {
|
||||
// block parameter value
|
||||
|
||||
// We push a new context so we can evaluate the path expression (note: this may be a bad idea).
|
||||
//
|
||||
// Example:
|
||||
// {{#foo as |bar|}}
|
||||
// {{bar.baz}}
|
||||
// {{/foo}}
|
||||
//
|
||||
// With data:
|
||||
// {"foo": {"baz": "bat"}}
|
||||
newCtx := map[string]interface{}{name: value}
|
||||
|
||||
v.pushCtx(reflect.ValueOf(newCtx))
|
||||
result = v.evalCtxPathExpression(node, exprRoot)
|
||||
v.popCtx()
|
||||
} else {
|
||||
ctxTried := false
|
||||
|
||||
if node.IsDataRoot() {
|
||||
// context path
|
||||
result = v.evalCtxPathExpression(node, exprRoot)
|
||||
|
||||
ctxTried = true
|
||||
}
|
||||
|
||||
if (result == nil) && node.Data {
|
||||
// if it is @root, then we tried to evaluate with root context but nothing was found
|
||||
// so let's try with private data
|
||||
|
||||
// private data
|
||||
result = v.evalDataPathExpression(node, exprRoot)
|
||||
}
|
||||
|
||||
if (result == nil) && !ctxTried {
|
||||
// context path
|
||||
result = v.evalCtxPathExpression(node, exprRoot)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// evalDataPathExpression evaluates a private data path expression
|
||||
func (v *evalVisitor) evalDataPathExpression(node *ast.PathExpression, exprRoot bool) interface{} {
|
||||
// find data frame
|
||||
frame := v.dataFrame
|
||||
for i := node.Depth; i > 0; i-- {
|
||||
if frame.parent == nil {
|
||||
return nil
|
||||
}
|
||||
frame = frame.parent
|
||||
}
|
||||
|
||||
// resolve data
|
||||
// @note Can be changed to v.evalCtx() as context can't be an array
|
||||
result, _ := v.evalCtxPath(reflect.ValueOf(frame.data), node.Parts, exprRoot)
|
||||
return result
|
||||
}
|
||||
|
||||
// evalCtxPathExpression evaluates a context path expression
|
||||
func (v *evalVisitor) evalCtxPathExpression(node *ast.PathExpression, exprRoot bool) interface{} {
|
||||
v.at(node)
|
||||
|
||||
if node.IsDataRoot() {
|
||||
// `@root` - remove the first part
|
||||
parts := node.Parts[1:len(node.Parts)]
|
||||
|
||||
result, _ := v.evalCtxPath(v.rootCtx(), parts, exprRoot)
|
||||
return result
|
||||
}
|
||||
|
||||
return v.evalDepthPath(node.Depth, node.Parts, exprRoot)
|
||||
}
|
||||
|
||||
// evalDepthPath iterates on contexts, starting at given depth, until there is one that resolve given path parts
|
||||
func (v *evalVisitor) evalDepthPath(depth int, parts []string, exprRoot bool) interface{} {
|
||||
var result interface{}
|
||||
partResolved := false
|
||||
|
||||
ctx := v.ancestorCtx(depth)
|
||||
|
||||
for (result == nil) && ctx.IsValid() && (depth <= len(v.ctx) && !partResolved) {
|
||||
// try with context
|
||||
result, partResolved = v.evalCtxPath(ctx, parts, exprRoot)
|
||||
|
||||
// As soon as we find the first part of a path, we must not try to resolve with parent context if result is finally `nil`
|
||||
// Reference: "Dotted Names - Context Precedence" mustache test
|
||||
if !partResolved && (result == nil) {
|
||||
// try with previous context
|
||||
depth++
|
||||
ctx = v.ancestorCtx(depth)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// evalCtxPath evaluates path with given context
|
||||
func (v *evalVisitor) evalCtxPath(ctx reflect.Value, parts []string, exprRoot bool) (interface{}, bool) {
|
||||
var result interface{}
|
||||
partResolved := false
|
||||
|
||||
switch ctx.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
// Array context
|
||||
var results []interface{}
|
||||
|
||||
for i := 0; i < ctx.Len(); i++ {
|
||||
value, _ := v.evalPath(ctx.Index(i), parts, exprRoot)
|
||||
if value.IsValid() {
|
||||
results = append(results, value.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
result = results
|
||||
default:
|
||||
// NOT array context
|
||||
var value reflect.Value
|
||||
|
||||
value, partResolved = v.evalPath(ctx, parts, exprRoot)
|
||||
if value.IsValid() {
|
||||
result = value.Interface()
|
||||
}
|
||||
}
|
||||
|
||||
return result, partResolved
|
||||
}
|
||||
|
||||
//
|
||||
// Helpers
|
||||
//
|
||||
|
||||
// isHelperCall returns true if given expression is a helper call
|
||||
func (v *evalVisitor) isHelperCall(node *ast.Expression) bool {
|
||||
if helperName := node.HelperName(); helperName != "" {
|
||||
return v.findHelper(helperName) != zero
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// findHelper finds given helper
|
||||
func (v *evalVisitor) findHelper(name string) reflect.Value {
|
||||
// check template helpers
|
||||
if h := v.tpl.findHelper(name); h != zero {
|
||||
return h
|
||||
}
|
||||
|
||||
// check global helpers
|
||||
return findHelper(name)
|
||||
}
|
||||
|
||||
// callFunc calls function with given options
|
||||
func (v *evalVisitor) callFunc(name string, funcVal reflect.Value, options *Options) reflect.Value {
|
||||
params := options.Params()
|
||||
|
||||
funcType := funcVal.Type()
|
||||
|
||||
// @todo Is there a better way to do that ?
|
||||
strType := reflect.TypeOf("")
|
||||
boolType := reflect.TypeOf(true)
|
||||
|
||||
// check parameters number
|
||||
addOptions := false
|
||||
numIn := funcType.NumIn()
|
||||
|
||||
if numIn == len(params)+1 {
|
||||
lastArgType := funcType.In(numIn - 1)
|
||||
if reflect.TypeOf(options).AssignableTo(lastArgType) {
|
||||
addOptions = true
|
||||
}
|
||||
}
|
||||
|
||||
if !addOptions && (len(params) != numIn) {
|
||||
v.errorf("Helper '%s' called with wrong number of arguments, needed %d but got %d", name, numIn, len(params))
|
||||
}
|
||||
|
||||
// check and collect arguments
|
||||
args := make([]reflect.Value, numIn)
|
||||
for i, param := range params {
|
||||
arg := reflect.ValueOf(param)
|
||||
argType := funcType.In(i)
|
||||
|
||||
if !arg.IsValid() {
|
||||
if canBeNil(argType) {
|
||||
arg = reflect.Zero(argType)
|
||||
} else if argType.Kind() == reflect.String {
|
||||
arg = reflect.ValueOf("")
|
||||
} else {
|
||||
// @todo Maybe we can panic on that
|
||||
return reflect.Zero(strType)
|
||||
}
|
||||
}
|
||||
|
||||
if !arg.Type().AssignableTo(argType) {
|
||||
if strType.AssignableTo(argType) {
|
||||
// convert parameter to string
|
||||
arg = reflect.ValueOf(strValue(arg))
|
||||
} else if boolType.AssignableTo(argType) {
|
||||
// convert parameter to bool
|
||||
val, _ := isTrueValue(arg)
|
||||
arg = reflect.ValueOf(val)
|
||||
} else {
|
||||
v.errorf("Helper %s called with argument %d with type %s but it should be %s", name, i, arg.Type(), argType)
|
||||
}
|
||||
}
|
||||
|
||||
args[i] = arg
|
||||
}
|
||||
|
||||
if addOptions {
|
||||
args[numIn-1] = reflect.ValueOf(options)
|
||||
}
|
||||
|
||||
result := funcVal.Call(args)
|
||||
|
||||
return result[0]
|
||||
}
|
||||
|
||||
// callHelper invoqs helper function for given expression node
|
||||
func (v *evalVisitor) callHelper(name string, helper reflect.Value, node *ast.Expression) interface{} {
|
||||
result := v.callFunc(name, helper, v.helperOptions(node))
|
||||
if !result.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// @todo We maybe want to ensure here that helper returned a string or a SafeString
|
||||
return result.Interface()
|
||||
}
|
||||
|
||||
// helperOptions computes helper options argument from an expression
|
||||
func (v *evalVisitor) helperOptions(node *ast.Expression) *Options {
|
||||
var params []interface{}
|
||||
var hash map[string]interface{}
|
||||
|
||||
for _, paramNode := range node.Params {
|
||||
param := paramNode.Accept(v)
|
||||
params = append(params, param)
|
||||
}
|
||||
|
||||
if node.Hash != nil {
|
||||
hash, _ = node.Hash.Accept(v).(map[string]interface{})
|
||||
}
|
||||
|
||||
return newOptions(v, params, hash)
|
||||
}
|
||||
|
||||
//
|
||||
// Partials
|
||||
//
|
||||
|
||||
// findPartial finds given partial
|
||||
func (v *evalVisitor) findPartial(name string) *partial {
|
||||
// check template partials
|
||||
if p := v.tpl.findPartial(name); p != nil {
|
||||
return p
|
||||
}
|
||||
|
||||
// check global partials
|
||||
return findPartial(name)
|
||||
}
|
||||
|
||||
// partialContext computes partial context
|
||||
func (v *evalVisitor) partialContext(node *ast.PartialStatement) reflect.Value {
|
||||
if nb := len(node.Params); nb > 1 {
|
||||
v.errorf("Unsupported number of partial arguments: %d", nb)
|
||||
}
|
||||
|
||||
if (len(node.Params) > 0) && (node.Hash != nil) {
|
||||
v.errorf("Passing both context and named parameters to a partial is not allowed")
|
||||
}
|
||||
|
||||
if len(node.Params) == 1 {
|
||||
return reflect.ValueOf(node.Params[0].Accept(v))
|
||||
}
|
||||
|
||||
if node.Hash != nil {
|
||||
hash, _ := node.Hash.Accept(v).(map[string]interface{})
|
||||
return reflect.ValueOf(hash)
|
||||
}
|
||||
|
||||
return zero
|
||||
}
|
||||
|
||||
// evalPartial evaluates a partial
|
||||
func (v *evalVisitor) evalPartial(p *partial, node *ast.PartialStatement) string {
|
||||
// get partial template
|
||||
partialTpl, err := p.template()
|
||||
if err != nil {
|
||||
v.errPanic(err)
|
||||
}
|
||||
|
||||
// push partial context
|
||||
ctx := v.partialContext(node)
|
||||
if ctx.IsValid() {
|
||||
v.pushCtx(ctx)
|
||||
}
|
||||
|
||||
// evaluate partial template
|
||||
result, _ := partialTpl.program.Accept(v).(string)
|
||||
|
||||
// ident partial
|
||||
result = indentLines(result, node.Indent)
|
||||
|
||||
if ctx.IsValid() {
|
||||
v.popCtx()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// indentLines indents all lines of given string
|
||||
func indentLines(str string, indent string) string {
|
||||
if indent == "" {
|
||||
return str
|
||||
}
|
||||
|
||||
var indented []string
|
||||
|
||||
lines := strings.Split(str, "\n")
|
||||
for i, line := range lines {
|
||||
if (i == (len(lines) - 1)) && (line == "") {
|
||||
// input string ends with a new line
|
||||
indented = append(indented, line)
|
||||
} else {
|
||||
indented = append(indented, indent+line)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(indented, "\n")
|
||||
}
|
||||
|
||||
//
|
||||
// Functions
|
||||
//
|
||||
|
||||
// wasFuncCall returns true if given expression was a function call
|
||||
func (v *evalVisitor) wasFuncCall(node *ast.Expression) bool {
|
||||
// check if expression was tagged as a function call
|
||||
return v.exprFunc[node]
|
||||
}
|
||||
|
||||
//
|
||||
// Visitor interface
|
||||
//
|
||||
|
||||
// Statements
|
||||
|
||||
// VisitProgram implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitProgram(node *ast.Program) interface{} {
|
||||
v.at(node)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
for _, n := range node.Body {
|
||||
if str := Str(n.Accept(v)); str != "" {
|
||||
if _, err := buf.Write([]byte(str)); err != nil {
|
||||
v.errPanic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// VisitMustache implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitMustache(node *ast.MustacheStatement) interface{} {
|
||||
v.at(node)
|
||||
|
||||
// evaluate expression
|
||||
expr := node.Expression.Accept(v)
|
||||
|
||||
// check if this is a safe string
|
||||
isSafe := isSafeString(expr)
|
||||
|
||||
// get string value
|
||||
str := Str(expr)
|
||||
if !isSafe && !node.Unescaped {
|
||||
// escape html
|
||||
str = Escape(str)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
// VisitBlock implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitBlock(node *ast.BlockStatement) interface{} {
|
||||
v.at(node)
|
||||
|
||||
v.pushBlock(node)
|
||||
|
||||
var result interface{}
|
||||
|
||||
// evaluate expression
|
||||
expr := node.Expression.Accept(v)
|
||||
|
||||
if v.isHelperCall(node.Expression) || v.wasFuncCall(node.Expression) {
|
||||
// it is the responsability of the helper/function to evaluate block
|
||||
result = expr
|
||||
} else {
|
||||
val := reflect.ValueOf(expr)
|
||||
|
||||
truth, _ := isTrueValue(val)
|
||||
if truth {
|
||||
if node.Program != nil {
|
||||
switch val.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
concat := ""
|
||||
|
||||
// Array context
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
// Computes new private data frame
|
||||
frame := v.dataFrame.newIterDataFrame(val.Len(), i, nil)
|
||||
|
||||
// Evaluate program
|
||||
concat += v.evalProgram(node.Program, val.Index(i).Interface(), frame, i)
|
||||
}
|
||||
|
||||
result = concat
|
||||
default:
|
||||
// NOT array
|
||||
result = v.evalProgram(node.Program, expr, nil, nil)
|
||||
}
|
||||
}
|
||||
} else if node.Inverse != nil {
|
||||
result, _ = node.Inverse.Accept(v).(string)
|
||||
}
|
||||
}
|
||||
|
||||
v.popBlock()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// VisitPartial implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitPartial(node *ast.PartialStatement) interface{} {
|
||||
v.at(node)
|
||||
|
||||
// partialName: helperName | sexpr
|
||||
name, ok := ast.HelperNameStr(node.Name)
|
||||
if !ok {
|
||||
if subExpr, ok := node.Name.(*ast.SubExpression); ok {
|
||||
name, _ = subExpr.Accept(v).(string)
|
||||
}
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
v.errorf("Unexpected partial name: %q", node.Name)
|
||||
}
|
||||
|
||||
partial := v.findPartial(name)
|
||||
if partial == nil {
|
||||
v.errorf("Partial not found: %s", name)
|
||||
}
|
||||
|
||||
return v.evalPartial(partial, node)
|
||||
}
|
||||
|
||||
// VisitContent implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitContent(node *ast.ContentStatement) interface{} {
|
||||
v.at(node)
|
||||
|
||||
// write content as is
|
||||
return node.Value
|
||||
}
|
||||
|
||||
// VisitComment implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitComment(node *ast.CommentStatement) interface{} {
|
||||
v.at(node)
|
||||
|
||||
// ignore comments
|
||||
return ""
|
||||
}
|
||||
|
||||
// Expressions
|
||||
|
||||
// VisitExpression implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitExpression(node *ast.Expression) interface{} {
|
||||
v.at(node)
|
||||
|
||||
var result interface{}
|
||||
done := false
|
||||
|
||||
v.pushExpr(node)
|
||||
|
||||
// helper call
|
||||
if helperName := node.HelperName(); helperName != "" {
|
||||
if helper := v.findHelper(helperName); helper != zero {
|
||||
result = v.callHelper(helperName, helper, node)
|
||||
done = true
|
||||
}
|
||||
}
|
||||
|
||||
if !done {
|
||||
// literal
|
||||
if literal, ok := node.LiteralStr(); ok {
|
||||
if val := v.evalField(v.curCtx(), literal, true); val.IsValid() {
|
||||
result = val.Interface()
|
||||
done = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !done {
|
||||
// field path
|
||||
if path := node.FieldPath(); path != nil {
|
||||
// @todo Find a cleaner way ! Don't break the pattern !
|
||||
// this is an exception to visitor pattern, because we need to pass the info
|
||||
// that this path is at root of current expression
|
||||
if val := v.evalPathExpression(path, true); val != nil {
|
||||
result = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
v.popExpr()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// VisitSubExpression implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitSubExpression(node *ast.SubExpression) interface{} {
|
||||
v.at(node)
|
||||
|
||||
return node.Expression.Accept(v)
|
||||
}
|
||||
|
||||
// VisitPath implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitPath(node *ast.PathExpression) interface{} {
|
||||
return v.evalPathExpression(node, false)
|
||||
}
|
||||
|
||||
// Literals
|
||||
|
||||
// VisitString implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitString(node *ast.StringLiteral) interface{} {
|
||||
v.at(node)
|
||||
|
||||
return node.Value
|
||||
}
|
||||
|
||||
// VisitBoolean implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitBoolean(node *ast.BooleanLiteral) interface{} {
|
||||
v.at(node)
|
||||
|
||||
return node.Value
|
||||
}
|
||||
|
||||
// VisitNumber implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitNumber(node *ast.NumberLiteral) interface{} {
|
||||
v.at(node)
|
||||
|
||||
return node.Number()
|
||||
}
|
||||
|
||||
// Miscellaneous
|
||||
|
||||
// VisitHash implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitHash(node *ast.Hash) interface{} {
|
||||
v.at(node)
|
||||
|
||||
result := make(map[string]interface{})
|
||||
|
||||
for _, pair := range node.Pairs {
|
||||
if value := pair.Accept(v); value != nil {
|
||||
result[pair.Key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// VisitHashPair implements corresponding Visitor interface method
|
||||
func (v *evalVisitor) VisitHashPair(node *ast.HashPair) interface{} {
|
||||
v.at(node)
|
||||
|
||||
return node.Val.Accept(v)
|
||||
}
|
215
vendor/github.com/aymerick/raymond/eval_test.go
generated
vendored
Normal file
215
vendor/github.com/aymerick/raymond/eval_test.go
generated
vendored
Normal file
@ -0,0 +1,215 @@
|
||||
package raymond
|
||||
|
||||
import "testing"
|
||||
|
||||
var evalTests = []Test{
|
||||
{
|
||||
"only content",
|
||||
"this is content",
|
||||
nil, nil, nil, nil,
|
||||
"this is content",
|
||||
},
|
||||
{
|
||||
"checks path in parent contexts",
|
||||
"{{#a}}{{one}}{{#b}}{{one}}{{two}}{{one}}{{/b}}{{/a}}",
|
||||
map[string]interface{}{"a": map[string]int{"one": 1}, "b": map[string]int{"two": 2}},
|
||||
nil, nil, nil,
|
||||
"1121",
|
||||
},
|
||||
{
|
||||
"block params",
|
||||
"{{#foo as |bar|}}{{bar}}{{/foo}}{{bar}}",
|
||||
map[string]string{"foo": "baz", "bar": "bat"},
|
||||
nil, nil, nil,
|
||||
"bazbat",
|
||||
},
|
||||
{
|
||||
"block params on array",
|
||||
"{{#foo as |bar i|}}{{i}}.{{bar}} {{/foo}}",
|
||||
map[string][]string{"foo": {"baz", "bar", "bat"}},
|
||||
nil, nil, nil,
|
||||
"0.baz 1.bar 2.bat ",
|
||||
},
|
||||
{
|
||||
"nested block params",
|
||||
"{{#foos as |foo iFoo|}}{{#wats as |wat iWat|}}{{iFoo}}.{{iWat}}.{{foo}}-{{wat}} {{/wats}}{{/foos}}",
|
||||
map[string][]string{"foos": {"baz", "bar"}, "wats": {"the", "phoque"}},
|
||||
nil, nil, nil,
|
||||
"0.0.baz-the 0.1.baz-phoque 1.0.bar-the 1.1.bar-phoque ",
|
||||
},
|
||||
{
|
||||
"block params with path reference",
|
||||
"{{#foo as |bar|}}{{bar.baz}}{{/foo}}",
|
||||
map[string]map[string]string{"foo": {"baz": "bat"}},
|
||||
nil, nil, nil,
|
||||
"bat",
|
||||
},
|
||||
{
|
||||
"falsy block evaluation",
|
||||
"{{#foo}}bar{{/foo}} baz",
|
||||
map[string]interface{}{"foo": false},
|
||||
nil, nil, nil,
|
||||
" baz",
|
||||
},
|
||||
{
|
||||
"block helper returns a SafeString",
|
||||
"{{title}} - {{#bold}}{{body}}{{/bold}}",
|
||||
map[string]string{
|
||||
"title": "My new blog post",
|
||||
"body": "I have so many things to say!",
|
||||
},
|
||||
nil,
|
||||
map[string]interface{}{"bold": func(options *Options) SafeString {
|
||||
return SafeString(`<div class="mybold">` + options.Fn() + "</div>")
|
||||
}},
|
||||
nil,
|
||||
`My new blog post - <div class="mybold">I have so many things to say!</div>`,
|
||||
},
|
||||
{
|
||||
"chained blocks",
|
||||
"{{#if a}}A{{else if b}}B{{else}}C{{/if}}",
|
||||
map[string]interface{}{"b": false},
|
||||
nil, nil, nil,
|
||||
"C",
|
||||
},
|
||||
|
||||
// @todo Test with a "../../path" (depth 2 path) while context is only depth 1
|
||||
}
|
||||
|
||||
func TestEval(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
launchTests(t, evalTests)
|
||||
}
|
||||
|
||||
var evalErrors = []Test{
|
||||
{
|
||||
"functions with wrong number of arguments",
|
||||
`{{foo "bar"}}`,
|
||||
map[string]interface{}{"foo": func(a string, b string) string { return "foo" }},
|
||||
nil, nil, nil,
|
||||
"Helper 'foo' called with wrong number of arguments, needed 2 but got 1",
|
||||
},
|
||||
{
|
||||
"functions with wrong number of returned values (1)",
|
||||
"{{foo}}",
|
||||
map[string]interface{}{"foo": func() {}},
|
||||
nil, nil, nil,
|
||||
"Helper function must return a string or a SafeString",
|
||||
},
|
||||
{
|
||||
"functions with wrong number of returned values (2)",
|
||||
"{{foo}}",
|
||||
map[string]interface{}{"foo": func() (string, bool, string) { return "foo", true, "bar" }},
|
||||
nil, nil, nil,
|
||||
"Helper function must return a string or a SafeString",
|
||||
},
|
||||
}
|
||||
|
||||
func TestEvalErrors(t *testing.T) {
|
||||
launchErrorTests(t, evalErrors)
|
||||
}
|
||||
|
||||
func TestEvalStruct(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
source := `<div class="post">
|
||||
<h1>By {{author.FirstName}} {{Author.lastName}}</h1>
|
||||
<div class="body">{{Body}}</div>
|
||||
|
||||
<h1>Comments</h1>
|
||||
|
||||
{{#each comments}}
|
||||
<h2>By {{Author.FirstName}} {{author.LastName}}</h2>
|
||||
<div class="body">{{body}}</div>
|
||||
{{/each}}
|
||||
</div>`
|
||||
|
||||
expected := `<div class="post">
|
||||
<h1>By Jean Valjean</h1>
|
||||
<div class="body">Life is difficult</div>
|
||||
|
||||
<h1>Comments</h1>
|
||||
|
||||
<h2>By Marcel Beliveau</h2>
|
||||
<div class="body">LOL!</div>
|
||||
</div>`
|
||||
|
||||
type Person struct {
|
||||
FirstName string
|
||||
LastName string
|
||||
}
|
||||
|
||||
type Comment struct {
|
||||
Author Person
|
||||
Body string
|
||||
}
|
||||
|
||||
type Post struct {
|
||||
Author Person
|
||||
Body string
|
||||
Comments []Comment
|
||||
}
|
||||
|
||||
ctx := Post{
|
||||
Person{"Jean", "Valjean"},
|
||||
"Life is difficult",
|
||||
[]Comment{
|
||||
Comment{
|
||||
Person{"Marcel", "Beliveau"},
|
||||
"LOL!",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
output := MustRender(source, ctx)
|
||||
if output != expected {
|
||||
t.Errorf("Failed to evaluate with struct context")
|
||||
}
|
||||
}
|
||||
|
||||
type TestFoo struct {
|
||||
}
|
||||
|
||||
func (t *TestFoo) Subject() string {
|
||||
return "foo"
|
||||
}
|
||||
|
||||
func TestEvalMethod(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
source := `Subject is {{subject}}! YES I SAID {{Subject}}!`
|
||||
expected := `Subject is foo! YES I SAID foo!`
|
||||
|
||||
ctx := &TestFoo{}
|
||||
|
||||
output := MustRender(source, ctx)
|
||||
if output != expected {
|
||||
t.Errorf("Failed to evaluate struct method: %s", output)
|
||||
}
|
||||
}
|
||||
|
||||
type TestBar struct {
|
||||
}
|
||||
|
||||
func (t *TestBar) Subject() interface{} {
|
||||
return testBar
|
||||
}
|
||||
|
||||
func testBar() string {
|
||||
return "bar"
|
||||
}
|
||||
|
||||
func TestEvalMethodReturningFunc(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
source := `Subject is {{subject}}! YES I SAID {{Subject}}!`
|
||||
expected := `Subject is bar! YES I SAID bar!`
|
||||
|
||||
ctx := &TestBar{}
|
||||
|
||||
output := MustRender(source, ctx)
|
||||
if output != expected {
|
||||
t.Errorf("Failed to evaluate struct method: %s", output)
|
||||
}
|
||||
}
|
100
vendor/github.com/aymerick/raymond/handlebars/base_test.go
generated
vendored
Normal file
100
vendor/github.com/aymerick/raymond/handlebars/base_test.go
generated
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
package handlebars
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/aymerick/raymond"
|
||||
)
|
||||
|
||||
// cf. https://github.com/aymerick/go-fuzz-tests/raymond
|
||||
const dumpTpl = false
|
||||
|
||||
var dumpTplNb = 0
|
||||
|
||||
type Test struct {
|
||||
name string
|
||||
input string
|
||||
data interface{}
|
||||
privData map[string]interface{}
|
||||
helpers map[string]interface{}
|
||||
partials map[string]string
|
||||
output interface{}
|
||||
}
|
||||
|
||||
func launchTests(t *testing.T, tests []Test) {
|
||||
t.Parallel()
|
||||
|
||||
for _, test := range tests {
|
||||
var err error
|
||||
var tpl *raymond.Template
|
||||
|
||||
if dumpTpl {
|
||||
filename := strconv.Itoa(dumpTplNb)
|
||||
if err := ioutil.WriteFile(path.Join(".", "dump_tpl", filename), []byte(test.input), 0644); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dumpTplNb++
|
||||
}
|
||||
|
||||
// parse template
|
||||
tpl, err = raymond.Parse(test.input)
|
||||
if err != nil {
|
||||
t.Errorf("Test '%s' failed - Failed to parse template\ninput:\n\t'%s'\nerror:\n\t%s", test.name, test.input, err)
|
||||
} else {
|
||||
if len(test.helpers) > 0 {
|
||||
// register helpers
|
||||
tpl.RegisterHelpers(test.helpers)
|
||||
}
|
||||
|
||||
if len(test.partials) > 0 {
|
||||
// register partials
|
||||
tpl.RegisterPartials(test.partials)
|
||||
}
|
||||
|
||||
// setup private data frame
|
||||
var privData *raymond.DataFrame
|
||||
if test.privData != nil {
|
||||
privData = raymond.NewDataFrame()
|
||||
for k, v := range test.privData {
|
||||
privData.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// render template
|
||||
output, err := tpl.ExecWith(test.data, privData)
|
||||
if err != nil {
|
||||
t.Errorf("Test '%s' failed\ninput:\n\t'%s'\ndata:\n\t%s\nerror:\n\t%s\nAST:\n\t%s", test.name, test.input, raymond.Str(test.data), err, tpl.PrintAST())
|
||||
} else {
|
||||
// check output
|
||||
var expectedArr []string
|
||||
expectedArr, ok := test.output.([]string)
|
||||
if ok {
|
||||
match := false
|
||||
for _, expectedStr := range expectedArr {
|
||||
if expectedStr == output {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !match {
|
||||
t.Errorf("Test '%s' failed\ninput:\n\t'%s'\ndata:\n\t%s\npartials:\n\t%s\nexpected\n\t%q\ngot\n\t%q\nAST:\n%s", test.name, test.input, raymond.Str(test.data), raymond.Str(test.partials), expectedArr, output, tpl.PrintAST())
|
||||
}
|
||||
} else {
|
||||
expectedStr, ok := test.output.(string)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("Erroneous test output description: %q", test.output))
|
||||
}
|
||||
|
||||
if expectedStr != output {
|
||||
t.Errorf("Test '%s' failed\ninput:\n\t'%s'\ndata:\n\t%s\npartials:\n\t%s\nexpected\n\t%q\ngot\n\t%q\nAST:\n%s", test.name, test.input, raymond.Str(test.data), raymond.Str(test.partials), expectedStr, output, tpl.PrintAST())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
650
vendor/github.com/aymerick/raymond/handlebars/basic_test.go
generated
vendored
Normal file
650
vendor/github.com/aymerick/raymond/handlebars/basic_test.go
generated
vendored
Normal file
@ -0,0 +1,650 @@
|
||||
package handlebars
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/aymerick/raymond"
|
||||
)
|
||||
|
||||
//
|
||||
// Those tests come from:
|
||||
// https://github.com/wycats/handlebars.js/blob/master/spec/basic.js
|
||||
//
|
||||
var basicTests = []Test{
|
||||
{
|
||||
"most basic",
|
||||
"{{foo}}",
|
||||
map[string]string{"foo": "foo"},
|
||||
nil, nil, nil,
|
||||
"foo",
|
||||
},
|
||||
{
|
||||
"escaping (1)",
|
||||
"\\{{foo}}",
|
||||
map[string]string{"foo": "food"},
|
||||
nil, nil, nil,
|
||||
"{{foo}}",
|
||||
},
|
||||
{
|
||||
"escaping (2)",
|
||||
"content \\{{foo}}",
|
||||
map[string]string{},
|
||||
nil, nil, nil,
|
||||
"content {{foo}}",
|
||||
},
|
||||
{
|
||||
"escaping (3)",
|
||||
"\\\\{{foo}}",
|
||||
map[string]string{"foo": "food"},
|
||||
nil, nil, nil,
|
||||
"\\food",
|
||||
},
|
||||
{
|
||||
"escaping (4)",
|
||||
"content \\\\{{foo}}",
|
||||
map[string]string{"foo": "food"},
|
||||
nil, nil, nil,
|
||||
"content \\food",
|
||||
},
|
||||
{
|
||||
"escaping (5)",
|
||||
"\\\\ {{foo}}",
|
||||
map[string]string{"foo": "food"},
|
||||
nil, nil, nil,
|
||||
"\\\\ food",
|
||||
},
|
||||
{
|
||||
"compiling with a basic context",
|
||||
"Goodbye\n{{cruel}}\n{{world}}!",
|
||||
map[string]string{"cruel": "cruel", "world": "world"},
|
||||
nil, nil, nil,
|
||||
"Goodbye\ncruel\nworld!",
|
||||
},
|
||||
{
|
||||
"compiling with an undefined context (1)",
|
||||
"Goodbye\n{{cruel}}\n{{world.bar}}!",
|
||||
nil, nil, nil, nil,
|
||||
"Goodbye\n\n!",
|
||||
},
|
||||
{
|
||||
"compiling with an undefined context (2)",
|
||||
"{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}",
|
||||
nil, nil, nil, nil,
|
||||
"Goodbye",
|
||||
},
|
||||
{
|
||||
"comments (1)",
|
||||
"{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!",
|
||||
map[string]string{"cruel": "cruel", "world": "world"},
|
||||
nil, nil, nil,
|
||||
"Goodbye\ncruel\nworld!",
|
||||
},
|
||||
{
|
||||
"comments (2)",
|
||||
" {{~! comment ~}} blah",
|
||||
nil, nil, nil, nil,
|
||||
"blah",
|
||||
},
|
||||
{
|
||||
"comments (3)",
|
||||
" {{~!-- long-comment --~}} blah",
|
||||
nil, nil, nil, nil,
|
||||
"blah",
|
||||
},
|
||||
{
|
||||
"comments (4)",
|
||||
" {{! comment ~}} blah",
|
||||
nil, nil, nil, nil,
|
||||
" blah",
|
||||
},
|
||||
{
|
||||
"comments (5)",
|
||||
" {{!-- long-comment --~}} blah",
|
||||
nil, nil, nil, nil,
|
||||
" blah",
|
||||
},
|
||||
{
|
||||
"comments (6)",
|
||||
" {{~! comment}} blah",
|
||||
nil, nil, nil, nil,
|
||||
" blah",
|
||||
},
|
||||
{
|
||||
"comments (7)",
|
||||
" {{~!-- long-comment --}} blah",
|
||||
nil, nil, nil, nil,
|
||||
" blah",
|
||||
},
|
||||
{
|
||||
"boolean (1)",
|
||||
"{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbye": true, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"GOODBYE cruel world!",
|
||||
},
|
||||
{
|
||||
"boolean (2)",
|
||||
"{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbye": false, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"cruel world!",
|
||||
},
|
||||
{
|
||||
"zeros (1)",
|
||||
"num1: {{num1}}, num2: {{num2}}",
|
||||
map[string]interface{}{"num1": 42, "num2": 0},
|
||||
nil, nil, nil,
|
||||
"num1: 42, num2: 0",
|
||||
},
|
||||
{
|
||||
"zeros (2)",
|
||||
"num: {{.}}",
|
||||
0,
|
||||
nil, nil, nil,
|
||||
"num: 0",
|
||||
},
|
||||
{
|
||||
"zeros (3)",
|
||||
"num: {{num1/num2}}",
|
||||
map[string]map[string]interface{}{"num1": {"num2": 0}},
|
||||
nil, nil, nil,
|
||||
"num: 0",
|
||||
},
|
||||
{
|
||||
"false (1)",
|
||||
"val1: {{val1}}, val2: {{val2}}",
|
||||
map[string]interface{}{"val1": false, "val2": false},
|
||||
nil, nil, nil,
|
||||
"val1: false, val2: false",
|
||||
},
|
||||
{
|
||||
"false (2)",
|
||||
"val: {{.}}",
|
||||
false,
|
||||
nil, nil, nil,
|
||||
"val: false",
|
||||
},
|
||||
{
|
||||
"false (3)",
|
||||
"val: {{val1/val2}}",
|
||||
map[string]map[string]interface{}{"val1": {"val2": false}},
|
||||
nil, nil, nil,
|
||||
"val: false",
|
||||
},
|
||||
{
|
||||
"false (4)",
|
||||
"val1: {{{val1}}}, val2: {{{val2}}}",
|
||||
map[string]interface{}{"val1": false, "val2": false},
|
||||
nil, nil, nil,
|
||||
"val1: false, val2: false",
|
||||
},
|
||||
{
|
||||
"false (5)",
|
||||
"val: {{{val1/val2}}}",
|
||||
map[string]map[string]interface{}{"val1": {"val2": false}},
|
||||
nil, nil, nil,
|
||||
"val: false",
|
||||
},
|
||||
{
|
||||
"newlines (1)",
|
||||
"Alan's\nTest",
|
||||
nil, nil, nil, nil,
|
||||
"Alan's\nTest",
|
||||
},
|
||||
{
|
||||
"newlines (2)",
|
||||
"Alan's\rTest",
|
||||
nil, nil, nil, nil,
|
||||
"Alan's\rTest",
|
||||
},
|
||||
{
|
||||
"escaping text (1)",
|
||||
"Awesome's",
|
||||
map[string]string{},
|
||||
nil, nil, nil,
|
||||
"Awesome's",
|
||||
},
|
||||
{
|
||||
"escaping text (2)",
|
||||
"Awesome\\",
|
||||
map[string]string{},
|
||||
nil, nil, nil,
|
||||
"Awesome\\",
|
||||
},
|
||||
{
|
||||
"escaping text (3)",
|
||||
"Awesome\\\\ foo",
|
||||
map[string]string{},
|
||||
nil, nil, nil,
|
||||
"Awesome\\\\ foo",
|
||||
},
|
||||
{
|
||||
"escaping text (4)",
|
||||
"Awesome {{foo}}",
|
||||
map[string]string{"foo": "\\"},
|
||||
nil, nil, nil,
|
||||
"Awesome \\",
|
||||
},
|
||||
{
|
||||
"escaping text (5)",
|
||||
" ' ' ",
|
||||
map[string]string{},
|
||||
nil, nil, nil,
|
||||
" ' ' ",
|
||||
},
|
||||
{
|
||||
"escaping expressions (6)",
|
||||
"{{{awesome}}}",
|
||||
map[string]string{"awesome": "&'\\<>"},
|
||||
nil, nil, nil,
|
||||
"&'\\<>",
|
||||
},
|
||||
{
|
||||
"escaping expressions (7)",
|
||||
"{{&awesome}}",
|
||||
map[string]string{"awesome": "&'\\<>"},
|
||||
nil, nil, nil,
|
||||
"&'\\<>",
|
||||
},
|
||||
{
|
||||
"escaping expressions (8)",
|
||||
"{{awesome}}",
|
||||
map[string]string{"awesome": "&\"'`\\<>"},
|
||||
nil, nil, nil,
|
||||
"&"'`\\<>",
|
||||
},
|
||||
{
|
||||
"escaping expressions (9)",
|
||||
"{{awesome}}",
|
||||
map[string]string{"awesome": "Escaped, <b> looks like: <b>"},
|
||||
nil, nil, nil,
|
||||
"Escaped, <b> looks like: &lt;b&gt;",
|
||||
},
|
||||
{
|
||||
"functions returning safestrings shouldn't be escaped",
|
||||
"{{awesome}}",
|
||||
map[string]interface{}{"awesome": func() raymond.SafeString { return raymond.SafeString("&'\\<>") }},
|
||||
nil, nil, nil,
|
||||
"&'\\<>",
|
||||
},
|
||||
{
|
||||
"functions (1)",
|
||||
"{{awesome}}",
|
||||
map[string]interface{}{"awesome": func() string { return "Awesome" }},
|
||||
nil, nil, nil,
|
||||
"Awesome",
|
||||
},
|
||||
{
|
||||
"functions (2)",
|
||||
"{{awesome}}",
|
||||
map[string]interface{}{"awesome": func(options *raymond.Options) string {
|
||||
return options.ValueStr("more")
|
||||
}, "more": "More awesome"},
|
||||
nil, nil, nil,
|
||||
"More awesome",
|
||||
},
|
||||
{
|
||||
"functions with context argument",
|
||||
"{{awesome frank}}",
|
||||
map[string]interface{}{"awesome": func(context string) string {
|
||||
return context
|
||||
}, "frank": "Frank"},
|
||||
nil, nil, nil,
|
||||
"Frank",
|
||||
},
|
||||
{
|
||||
"pathed functions with context argument",
|
||||
"{{bar.awesome frank}}",
|
||||
map[string]interface{}{"bar": map[string]interface{}{"awesome": func(context string) string {
|
||||
return context
|
||||
}}, "frank": "Frank"},
|
||||
nil, nil, nil,
|
||||
"Frank",
|
||||
},
|
||||
{
|
||||
"depthed functions with context argument",
|
||||
"{{#with frank}}{{../awesome .}}{{/with}}",
|
||||
map[string]interface{}{"awesome": func(context string) string {
|
||||
return context
|
||||
}, "frank": "Frank"},
|
||||
nil, nil, nil,
|
||||
"Frank",
|
||||
},
|
||||
{
|
||||
"block functions with context argument",
|
||||
"{{#awesome 1}}inner {{.}}{{/awesome}}",
|
||||
map[string]interface{}{"awesome": func(context interface{}, options *raymond.Options) string {
|
||||
return options.FnWith(context)
|
||||
}},
|
||||
nil, nil, nil,
|
||||
"inner 1",
|
||||
},
|
||||
{
|
||||
"depthed block functions with context argument",
|
||||
"{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}",
|
||||
map[string]interface{}{
|
||||
"awesome": func(context interface{}, options *raymond.Options) string {
|
||||
return options.FnWith(context)
|
||||
},
|
||||
"value": true,
|
||||
},
|
||||
nil, nil, nil,
|
||||
"inner 1",
|
||||
},
|
||||
{
|
||||
"block functions without context argument",
|
||||
"{{#awesome}}inner{{/awesome}}",
|
||||
map[string]interface{}{
|
||||
"awesome": func(options *raymond.Options) string {
|
||||
return options.Fn()
|
||||
},
|
||||
},
|
||||
nil, nil, nil,
|
||||
"inner",
|
||||
},
|
||||
// // @note I don't even understand why this test passes with the JS implementation... it should be
|
||||
// // the responsability of the function to evaluate the block
|
||||
// {
|
||||
// "pathed block functions without context argument",
|
||||
// "{{#foo.awesome}}inner{{/foo.awesome}}",
|
||||
// map[string]map[string]interface{}{
|
||||
// "foo": {
|
||||
// "awesome": func(options *raymond.Options) interface{} {
|
||||
// return options.Ctx()
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// nil, nil, nil,
|
||||
// "inner",
|
||||
// },
|
||||
// // @note I don't even understand why this test passes with the JS implementation... it should be
|
||||
// // the responsability of the function to evaluate the block
|
||||
// {
|
||||
// "depthed block functions without context argument",
|
||||
// "{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}",
|
||||
// map[string]interface{}{
|
||||
// "value": true,
|
||||
// "awesome": func(options *raymond.Options) interface{} {
|
||||
// return options.Ctx()
|
||||
// },
|
||||
// },
|
||||
// nil, nil, nil,
|
||||
// "inner",
|
||||
// },
|
||||
{
|
||||
"paths with hyphens (1)",
|
||||
"{{foo-bar}}",
|
||||
map[string]string{"foo-bar": "baz"},
|
||||
nil, nil, nil,
|
||||
"baz",
|
||||
},
|
||||
{
|
||||
"paths with hyphens (2)",
|
||||
"{{foo.foo-bar}}",
|
||||
map[string]map[string]string{"foo": {"foo-bar": "baz"}},
|
||||
nil, nil, nil,
|
||||
"baz",
|
||||
},
|
||||
{
|
||||
"paths with hyphens (3)",
|
||||
"{{foo/foo-bar}}",
|
||||
map[string]map[string]string{"foo": {"foo-bar": "baz"}},
|
||||
nil, nil, nil,
|
||||
"baz",
|
||||
},
|
||||
{
|
||||
"nested paths",
|
||||
"Goodbye {{alan/expression}} world!",
|
||||
map[string]map[string]string{"alan": {"expression": "beautiful"}},
|
||||
nil, nil, nil,
|
||||
"Goodbye beautiful world!",
|
||||
},
|
||||
{
|
||||
"nested paths with empty string value",
|
||||
"Goodbye {{alan/expression}} world!",
|
||||
map[string]map[string]string{"alan": {"expression": ""}},
|
||||
nil, nil, nil,
|
||||
"Goodbye world!",
|
||||
},
|
||||
{
|
||||
"literal paths (1)",
|
||||
"Goodbye {{[@alan]/expression}} world!",
|
||||
map[string]map[string]string{"@alan": {"expression": "beautiful"}},
|
||||
nil, nil, nil,
|
||||
"Goodbye beautiful world!",
|
||||
},
|
||||
{
|
||||
"literal paths (2)",
|
||||
"Goodbye {{[foo bar]/expression}} world!",
|
||||
map[string]map[string]string{"foo bar": {"expression": "beautiful"}},
|
||||
nil, nil, nil,
|
||||
"Goodbye beautiful world!",
|
||||
},
|
||||
{
|
||||
"literal references",
|
||||
"Goodbye {{[foo bar]}} world!",
|
||||
map[string]string{"foo bar": "beautiful"},
|
||||
nil, nil, nil,
|
||||
"Goodbye beautiful world!",
|
||||
},
|
||||
// @note MMm ok, well... no... I don't see the purpose of that test
|
||||
{
|
||||
"that current context path ({{.}}) doesn't hit helpers",
|
||||
"test: {{.}}",
|
||||
nil, nil,
|
||||
map[string]interface{}{"helper": func() string {
|
||||
panic("fail")
|
||||
}},
|
||||
nil,
|
||||
"test: ",
|
||||
},
|
||||
{
|
||||
"complex but empty paths (1)",
|
||||
"{{person/name}}",
|
||||
map[string]map[string]interface{}{"person": {"name": nil}},
|
||||
nil, nil, nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"complex but empty paths (2)",
|
||||
"{{person/name}}",
|
||||
map[string]map[string]string{"person": {}},
|
||||
nil, nil, nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"this keyword in paths (1)",
|
||||
"{{#goodbyes}}{{this}}{{/goodbyes}}",
|
||||
map[string]interface{}{"goodbyes": []string{"goodbye", "Goodbye", "GOODBYE"}},
|
||||
nil, nil, nil,
|
||||
"goodbyeGoodbyeGOODBYE",
|
||||
},
|
||||
{
|
||||
"this keyword in paths (2)",
|
||||
"{{#hellos}}{{this/text}}{{/hellos}}",
|
||||
map[string]interface{}{"hellos": []interface{}{
|
||||
map[string]string{"text": "hello"},
|
||||
map[string]string{"text": "Hello"},
|
||||
map[string]string{"text": "HELLO"},
|
||||
}},
|
||||
nil, nil, nil,
|
||||
"helloHelloHELLO",
|
||||
},
|
||||
{
|
||||
"this keyword nested inside path' (1)",
|
||||
"{{[this]}}",
|
||||
map[string]string{"this": "bar"},
|
||||
nil, nil, nil,
|
||||
"bar",
|
||||
},
|
||||
{
|
||||
"this keyword nested inside path' (2)",
|
||||
"{{text/[this]}}",
|
||||
map[string]map[string]string{"text": {"this": "bar"}},
|
||||
nil, nil, nil,
|
||||
"bar",
|
||||
},
|
||||
{
|
||||
"this keyword in helpers (1)",
|
||||
"{{#goodbyes}}{{foo this}}{{/goodbyes}}",
|
||||
map[string]interface{}{"goodbyes": []string{"goodbye", "Goodbye", "GOODBYE"}},
|
||||
nil,
|
||||
map[string]interface{}{"foo": barSuffixHelper},
|
||||
nil,
|
||||
"bar goodbyebar Goodbyebar GOODBYE",
|
||||
},
|
||||
{
|
||||
"this keyword in helpers (2)",
|
||||
"{{#hellos}}{{foo this/text}}{{/hellos}}",
|
||||
map[string]interface{}{"hellos": []map[string]string{{"text": "hello"}, {"text": "Hello"}, {"text": "HELLO"}}},
|
||||
nil,
|
||||
map[string]interface{}{"foo": barSuffixHelper},
|
||||
nil,
|
||||
"bar hellobar Hellobar HELLO",
|
||||
},
|
||||
{
|
||||
"this keyword nested inside helpers param (1)",
|
||||
"{{foo [this]}}",
|
||||
map[string]interface{}{"this": "bar"},
|
||||
nil,
|
||||
map[string]interface{}{"foo": echoHelper},
|
||||
nil,
|
||||
"bar",
|
||||
},
|
||||
{
|
||||
"this keyword nested inside helpers param (2)",
|
||||
"{{foo text/[this]}}",
|
||||
map[string]map[string]string{"text": {"this": "bar"}},
|
||||
nil,
|
||||
map[string]interface{}{"foo": echoHelper},
|
||||
nil,
|
||||
"bar",
|
||||
},
|
||||
{
|
||||
"pass string literals (1)",
|
||||
`{{"foo"}}`,
|
||||
map[string]string{},
|
||||
nil, nil, nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"pass string literals (2)",
|
||||
`{{"foo"}}`,
|
||||
map[string]string{"foo": "bar"},
|
||||
nil, nil, nil,
|
||||
"bar",
|
||||
},
|
||||
{
|
||||
"pass string literals (3)",
|
||||
`{{#"foo"}}{{.}}{{/"foo"}}`,
|
||||
map[string]interface{}{"foo": []string{"bar", "baz"}},
|
||||
nil, nil, nil,
|
||||
"barbaz",
|
||||
},
|
||||
{
|
||||
"pass number literals (1)",
|
||||
"{{12}}",
|
||||
map[string]string{},
|
||||
nil, nil, nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"pass number literals (2)",
|
||||
"{{12}}",
|
||||
map[string]string{"12": "bar"},
|
||||
nil, nil, nil,
|
||||
"bar",
|
||||
},
|
||||
{
|
||||
"pass number literals (3)",
|
||||
"{{12.34}}",
|
||||
map[string]string{},
|
||||
nil, nil, nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"pass number literals (4)",
|
||||
"{{12.34}}",
|
||||
map[string]string{"12.34": "bar"},
|
||||
nil, nil, nil,
|
||||
"bar",
|
||||
},
|
||||
{
|
||||
"pass number literals (5)",
|
||||
"{{12.34 1}}",
|
||||
map[string]interface{}{"12.34": func(context string) string {
|
||||
return "bar" + context
|
||||
}},
|
||||
nil, nil, nil,
|
||||
"bar1",
|
||||
},
|
||||
{
|
||||
"pass boolean literals (1)",
|
||||
"{{true}}",
|
||||
map[string]string{},
|
||||
nil, nil, nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"pass boolean literals (2)",
|
||||
"{{true}}",
|
||||
map[string]string{"": "foo"},
|
||||
nil, nil, nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"pass boolean literals (3)",
|
||||
"{{false}}",
|
||||
map[string]string{"false": "foo"},
|
||||
nil, nil, nil,
|
||||
"foo",
|
||||
},
|
||||
{
|
||||
"should handle literals in subexpression",
|
||||
"{{foo (false)}}",
|
||||
map[string]interface{}{"false": func() string { return "bar" }},
|
||||
nil,
|
||||
map[string]interface{}{"foo": func(context string) string {
|
||||
return context
|
||||
}},
|
||||
nil,
|
||||
"bar",
|
||||
},
|
||||
}
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
launchTests(t, basicTests)
|
||||
}
|
||||
|
||||
func TestBasicErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var err error
|
||||
|
||||
inputs := []string{
|
||||
// this keyword nested inside path
|
||||
"{{#hellos}}{{text/this/foo}}{{/hellos}}",
|
||||
// this keyword nested inside helpers param
|
||||
"{{#hellos}}{{foo text/this/foo}}{{/hellos}}",
|
||||
}
|
||||
|
||||
expectedError := regexp.QuoteMeta("Invalid path: text/this")
|
||||
|
||||
for _, input := range inputs {
|
||||
_, err = raymond.Parse(input)
|
||||
if err == nil {
|
||||
t.Errorf("Test failed - Error expected")
|
||||
}
|
||||
|
||||
match, errMatch := regexp.MatchString(expectedError, fmt.Sprint(err))
|
||||
if errMatch != nil {
|
||||
panic("Failed to match regexp")
|
||||
}
|
||||
|
||||
if !match {
|
||||
t.Errorf("Test failed - Expected error:\n\t%s\n\nGot:\n\t%s", expectedError, err)
|
||||
}
|
||||
}
|
||||
}
|
208
vendor/github.com/aymerick/raymond/handlebars/blocks_test.go
generated
vendored
Normal file
208
vendor/github.com/aymerick/raymond/handlebars/blocks_test.go
generated
vendored
Normal file
@ -0,0 +1,208 @@
|
||||
package handlebars
|
||||
|
||||
import "testing"
|
||||
|
||||
//
|
||||
// Those tests come from:
|
||||
// https://github.com/wycats/handlebars.js/blob/master/spec/blocks.js
|
||||
//
|
||||
var blocksTests = []Test{
|
||||
{
|
||||
"array (1) - Arrays iterate over the contents when not empty",
|
||||
"{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"goodbye! Goodbye! GOODBYE! cruel world!",
|
||||
},
|
||||
{
|
||||
"array (2) - Arrays ignore the contents when empty",
|
||||
"{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbyes": []map[string]string{}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"cruel world!",
|
||||
},
|
||||
{
|
||||
"array without data",
|
||||
"{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}",
|
||||
map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE",
|
||||
},
|
||||
{
|
||||
"array with @index - The @index variable is used",
|
||||
"{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!",
|
||||
},
|
||||
{
|
||||
"empty block (1) - Arrays iterate over the contents when not empty",
|
||||
"{{#goodbyes}}{{/goodbyes}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"cruel world!",
|
||||
},
|
||||
{
|
||||
"empty block (1) - Arrays ignore the contents when empty",
|
||||
"{{#goodbyes}}{{/goodbyes}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbyes": []map[string]string{}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"cruel world!",
|
||||
},
|
||||
{
|
||||
"block with complex lookup - Templates can access variables in contexts up the stack with relative path syntax",
|
||||
"{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}",
|
||||
map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "name": "Alan"},
|
||||
nil, nil, nil,
|
||||
"goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ",
|
||||
},
|
||||
{
|
||||
"multiple blocks with complex lookup",
|
||||
"{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}",
|
||||
map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "name": "Alan"},
|
||||
nil, nil, nil,
|
||||
"AlanAlanAlanAlanAlanAlan",
|
||||
},
|
||||
|
||||
// @todo "{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}" should throw error
|
||||
|
||||
{
|
||||
"block with deep nested complex lookup",
|
||||
"{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}",
|
||||
map[string]interface{}{"omg": "OMG!", "outer": []map[string]interface{}{{"sibling": "sad", "inner": []map[string]string{{"text": "goodbye"}}}}},
|
||||
nil, nil, nil,
|
||||
"Goodbye cruel sad OMG!",
|
||||
},
|
||||
{
|
||||
"inverted sections with unset value - Inverted section rendered when value isn't set.",
|
||||
"{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}",
|
||||
map[string]interface{}{},
|
||||
nil, nil, nil,
|
||||
"Right On!",
|
||||
},
|
||||
{
|
||||
"inverted sections with false value - Inverted section rendered when value is false.",
|
||||
"{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}",
|
||||
map[string]interface{}{"goodbyes": false},
|
||||
nil, nil, nil,
|
||||
"Right On!",
|
||||
},
|
||||
{
|
||||
"inverted section with empty set - Inverted section rendered when value is empty set.",
|
||||
"{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}",
|
||||
map[string]interface{}{"goodbyes": []interface{}{}},
|
||||
nil, nil, nil,
|
||||
"Right On!",
|
||||
},
|
||||
{
|
||||
"block inverted sections",
|
||||
"{{#people}}{{name}}{{^}}{{none}}{{/people}}",
|
||||
map[string]interface{}{"none": "No people"},
|
||||
nil, nil, nil,
|
||||
"No people",
|
||||
},
|
||||
{
|
||||
"chained inverted sections (1)",
|
||||
"{{#people}}{{name}}{{else if none}}{{none}}{{/people}}",
|
||||
map[string]interface{}{"none": "No people"},
|
||||
nil, nil, nil,
|
||||
"No people",
|
||||
},
|
||||
{
|
||||
"chained inverted sections (2)",
|
||||
"{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}",
|
||||
map[string]interface{}{"none": "No people"},
|
||||
nil, nil, nil,
|
||||
"No people",
|
||||
},
|
||||
{
|
||||
"chained inverted sections (3)",
|
||||
"{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}",
|
||||
map[string]interface{}{"none": "No people"},
|
||||
nil, nil, nil,
|
||||
"No people",
|
||||
},
|
||||
|
||||
// @todo "{{#people}}{{name}}{{else if none}}{{none}}{{/if}}" should throw error
|
||||
|
||||
{
|
||||
"block inverted sections with empty arrays",
|
||||
"{{#people}}{{name}}{{^}}{{none}}{{/people}}",
|
||||
map[string]interface{}{"none": "No people", "people": map[string]interface{}{}},
|
||||
nil, nil, nil,
|
||||
"No people",
|
||||
},
|
||||
{
|
||||
"block standalone else sections (1)",
|
||||
"{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n",
|
||||
map[string]interface{}{"none": "No people"},
|
||||
nil, nil, nil,
|
||||
"No people\n",
|
||||
},
|
||||
{
|
||||
"block standalone else sections (2)",
|
||||
"{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n",
|
||||
map[string]interface{}{"none": "No people"},
|
||||
nil, nil, nil,
|
||||
"No people\n",
|
||||
},
|
||||
{
|
||||
"block standalone else sections (3)",
|
||||
"{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n",
|
||||
map[string]interface{}{"none": "No people"},
|
||||
nil, nil, nil,
|
||||
"No people\n",
|
||||
},
|
||||
{
|
||||
"block standalone chained else sections (1)",
|
||||
"{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n",
|
||||
map[string]interface{}{"none": "No people"},
|
||||
nil, nil, nil,
|
||||
"No people\n",
|
||||
},
|
||||
{
|
||||
"block standalone chained else sections (2)",
|
||||
"{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n",
|
||||
map[string]interface{}{"none": "No people"},
|
||||
nil, nil, nil,
|
||||
"No people\n",
|
||||
},
|
||||
{
|
||||
"should handle nesting",
|
||||
"{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.",
|
||||
map[string]interface{}{"data": []int{1, 3, 5}},
|
||||
nil, nil, nil,
|
||||
"1\n3\n5\nOK.",
|
||||
},
|
||||
// // @todo compat mode
|
||||
// {
|
||||
// "block with deep recursive lookup lookup",
|
||||
// "{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}",
|
||||
// map[string]interface{}{"omg": "OMG!", "outer": []map[string]interface{}{{"inner": []map[string]string{{"text": "goodbye"}}}}},
|
||||
// nil,
|
||||
// nil,
|
||||
// nil,
|
||||
// "Goodbye cruel OMG!",
|
||||
// },
|
||||
// // @todo compat mode
|
||||
// {
|
||||
// "block with deep recursive pathed lookup",
|
||||
// "{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}",
|
||||
// map[string]interface{}{"omg": map[string]string{"yes": "OMG!"}, "outer": []map[string]interface{}{{"inner": []map[string]string{{"yes": "no", "text": "goodbye"}}}}},
|
||||
// nil,
|
||||
// nil,
|
||||
// nil,
|
||||
// "Goodbye cruel OMG!",
|
||||
// },
|
||||
{
|
||||
"block with missed recursive lookup",
|
||||
"{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}",
|
||||
map[string]interface{}{"omg": map[string]string{"no": "OMG!"}, "outer": []map[string]interface{}{{"inner": []map[string]string{{"yes": "no", "text": "goodbye"}}}}},
|
||||
nil, nil, nil,
|
||||
"Goodbye cruel ",
|
||||
},
|
||||
}
|
||||
|
||||
func TestBlocks(t *testing.T) {
|
||||
launchTests(t, blocksTests)
|
||||
}
|
341
vendor/github.com/aymerick/raymond/handlebars/builtins_test.go
generated
vendored
Normal file
341
vendor/github.com/aymerick/raymond/handlebars/builtins_test.go
generated
vendored
Normal file
@ -0,0 +1,341 @@
|
||||
package handlebars
|
||||
|
||||
import "testing"
|
||||
|
||||
//
|
||||
// Those tests come from:
|
||||
// https://github.com/wycats/handlebars.js/blob/master/spec/builtin.js
|
||||
//
|
||||
var builtinsTests = []Test{
|
||||
{
|
||||
"#if - if with boolean argument shows the contents when true",
|
||||
"{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbye": true, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"GOODBYE cruel world!",
|
||||
},
|
||||
{
|
||||
"#if - if with string argument shows the contents",
|
||||
"{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbye": "dummy", "world": "world"},
|
||||
nil, nil, nil,
|
||||
"GOODBYE cruel world!",
|
||||
},
|
||||
{
|
||||
"#if - if with boolean argument does not show the contents when false",
|
||||
"{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbye": false, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"cruel world!",
|
||||
},
|
||||
{
|
||||
"#if - if with undefined does not show the contents",
|
||||
"{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!",
|
||||
map[string]interface{}{"world": "world"},
|
||||
nil, nil, nil,
|
||||
"cruel world!",
|
||||
},
|
||||
{
|
||||
"#if - if with non-empty array shows the contents",
|
||||
"{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbye": []string{"foo"}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"GOODBYE cruel world!",
|
||||
},
|
||||
{
|
||||
"#if - if with empty array does not show the contents",
|
||||
"{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbye": []string{}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"cruel world!",
|
||||
},
|
||||
{
|
||||
"#if - if with zero does not show the contents",
|
||||
"{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbye": 0, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"cruel world!",
|
||||
},
|
||||
{
|
||||
"#if - if with zero and includeZero option shows the contents",
|
||||
"{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbye": 0, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"GOODBYE cruel world!",
|
||||
},
|
||||
{
|
||||
"#if - if with function shows the contents when function returns true",
|
||||
"{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!",
|
||||
map[string]interface{}{
|
||||
"goodbye": func() bool { return true },
|
||||
"world": "world",
|
||||
},
|
||||
nil, nil, nil,
|
||||
"GOODBYE cruel world!",
|
||||
},
|
||||
{
|
||||
"#if - if with function shows the contents when function returns string",
|
||||
"{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!",
|
||||
map[string]interface{}{
|
||||
"goodbye": func() string { return "world" },
|
||||
"world": "world",
|
||||
},
|
||||
nil, nil, nil,
|
||||
"GOODBYE cruel world!",
|
||||
},
|
||||
{
|
||||
"#if - if with function does not show the contents when returns false",
|
||||
"{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!",
|
||||
map[string]interface{}{
|
||||
"goodbye": func() bool { return false },
|
||||
"world": "world",
|
||||
},
|
||||
nil, nil, nil,
|
||||
"cruel world!",
|
||||
},
|
||||
{
|
||||
"#if - if with function does not show the contents when returns undefined",
|
||||
"{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!",
|
||||
map[string]interface{}{
|
||||
"goodbye": func() interface{} { return nil },
|
||||
"world": "world",
|
||||
},
|
||||
nil, nil, nil,
|
||||
"cruel world!",
|
||||
},
|
||||
{
|
||||
"#with",
|
||||
"{{#with person}}{{first}} {{last}}{{/with}}",
|
||||
map[string]interface{}{"person": map[string]string{"first": "Alan", "last": "Johnson"}},
|
||||
nil, nil, nil,
|
||||
"Alan Johnson",
|
||||
},
|
||||
{
|
||||
"#with - with with function argument",
|
||||
"{{#with person}}{{first}} {{last}}{{/with}}",
|
||||
map[string]interface{}{
|
||||
"person": func() map[string]string { return map[string]string{"first": "Alan", "last": "Johnson"} },
|
||||
}, nil, nil, nil,
|
||||
"Alan Johnson",
|
||||
},
|
||||
{
|
||||
"#with - with with else",
|
||||
"{{#with person}}Person is present{{else}}Person is not present{{/with}}",
|
||||
map[string]interface{}{},
|
||||
nil, nil, nil,
|
||||
"Person is not present",
|
||||
},
|
||||
|
||||
{
|
||||
"#each - each with array argument iterates over the contents when not empty",
|
||||
"{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"goodbye! Goodbye! GOODBYE! cruel world!",
|
||||
},
|
||||
{
|
||||
"#each - each with array argument ignores the contents when empty",
|
||||
"{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbyes": []map[string]string{}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"cruel world!",
|
||||
},
|
||||
{
|
||||
"#each - each without data (1)",
|
||||
"{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"goodbye! Goodbye! GOODBYE! cruel world!",
|
||||
},
|
||||
{
|
||||
"#each - each without data (2)",
|
||||
"{{#each .}}{{.}}{{/each}}",
|
||||
map[string]interface{}{"goodbyes": "cruel", "world": "world"},
|
||||
nil, nil, nil,
|
||||
// note: a go hash is not ordered, so result may vary, this behaviour differs from the JS implementation
|
||||
[]string{"cruelworld", "worldcruel"},
|
||||
},
|
||||
{
|
||||
"#each - each without context",
|
||||
"{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!",
|
||||
nil, nil, nil, nil,
|
||||
"cruel !",
|
||||
},
|
||||
|
||||
// NOTE: we test with a map instead of an object
|
||||
{
|
||||
"#each - each with an object and @key (map)",
|
||||
"{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbyes": map[interface{}]map[string]string{"<b>#1</b>": {"text": "goodbye"}, 2: {"text": "GOODBYE"}}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
[]string{"<b>#1</b>. goodbye! 2. GOODBYE! cruel world!", "2. GOODBYE! <b>#1</b>. goodbye! cruel world!"},
|
||||
},
|
||||
// NOTE: An additional test with a struct, but without an html stuff for the key, because it is impossible
|
||||
{
|
||||
"#each - each with an object and @key (struct)",
|
||||
"{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!",
|
||||
map[string]interface{}{
|
||||
"goodbyes": struct {
|
||||
Foo map[string]string
|
||||
Bar map[string]int
|
||||
}{map[string]string{"text": "baz"}, map[string]int{"text": 10}},
|
||||
"world": "world",
|
||||
},
|
||||
nil, nil, nil,
|
||||
[]string{"Foo. baz! Bar. 10! cruel world!", "Bar. 10! Foo. baz! cruel world!"},
|
||||
},
|
||||
{
|
||||
"#each - each with @index",
|
||||
"{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!",
|
||||
},
|
||||
{
|
||||
"#each - each with nested @index",
|
||||
"{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!",
|
||||
},
|
||||
{
|
||||
"#each - each with block params",
|
||||
"{{#each goodbyes as |value index|}}{{index}}. {{value.text}}! {{#each ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/each}} After {{index}} {{/each}}{{index}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!",
|
||||
},
|
||||
// @note: That test differs from JS impl because maps and structs are not ordered in go
|
||||
{
|
||||
"#each - each object with @index",
|
||||
"{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbyes": map[string]map[string]string{"a": {"text": "goodbye"}, "b": {"text": "Goodbye"}}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
[]string{"0. goodbye! 1. Goodbye! cruel world!", "0. Goodbye! 1. goodbye! cruel world!"},
|
||||
},
|
||||
{
|
||||
"#each - each with nested @first",
|
||||
"{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!",
|
||||
},
|
||||
// @note: That test differs from JS impl because maps and structs are not ordered in go
|
||||
{
|
||||
"#each - each object with @first",
|
||||
"{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbyes": map[string]map[string]string{"foo": {"text": "goodbye"}, "bar": {"text": "Goodbye"}}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
[]string{"goodbye! cruel world!", "Goodbye! cruel world!"},
|
||||
},
|
||||
{
|
||||
"#each - each with @last",
|
||||
"{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"GOODBYE! cruel world!",
|
||||
},
|
||||
// @note: That test differs from JS impl because maps and structs are not ordered in go
|
||||
{
|
||||
"#each - each object with @last",
|
||||
"{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbyes": map[string]map[string]string{"foo": {"text": "goodbye"}, "bar": {"text": "Goodbye"}}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
[]string{"goodbye! cruel world!", "Goodbye! cruel world!"},
|
||||
},
|
||||
{
|
||||
"#each - each with nested @last",
|
||||
"{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!",
|
||||
},
|
||||
|
||||
{
|
||||
"#each - each with function argument (1)",
|
||||
"{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbyes": func() []map[string]string {
|
||||
return []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}
|
||||
}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"goodbye! Goodbye! GOODBYE! cruel world!",
|
||||
},
|
||||
{
|
||||
"#each - each with function argument (2)",
|
||||
"{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!",
|
||||
map[string]interface{}{"goodbyes": []map[string]string{}, "world": "world"},
|
||||
nil, nil, nil,
|
||||
"cruel world!",
|
||||
},
|
||||
{
|
||||
"#each - data passed to helpers",
|
||||
"{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}",
|
||||
map[string][]string{"letters": {"a", "b", "c"}},
|
||||
map[string]interface{}{"exclaim": "!"},
|
||||
map[string]interface{}{"detectDataInsideEach": detectDataHelper},
|
||||
nil,
|
||||
"a!b!c!",
|
||||
},
|
||||
|
||||
// @todo "each on implicit context" should throw error
|
||||
|
||||
// SKIP: #log - "should call logger at default level"
|
||||
// SKIP: #log - "should call logger at data level"
|
||||
// SKIP: #log - "should output to info"
|
||||
// SKIP: #log - "should log at data level"
|
||||
// SKIP: #log - "should handle missing logger"
|
||||
|
||||
// @note Test added
|
||||
// @todo Check log output
|
||||
{
|
||||
"#log",
|
||||
"{{log blah}}",
|
||||
map[string]string{"blah": "whee"},
|
||||
nil, nil, nil,
|
||||
"",
|
||||
},
|
||||
|
||||
// @note Test added
|
||||
{
|
||||
"#lookup - should lookup array element",
|
||||
"{{#each goodbyes}}{{lookup ../data @index}}{{/each}}",
|
||||
map[string]interface{}{"goodbyes": []int{0, 1}, "data": []string{"foo", "bar"}},
|
||||
nil, nil, nil,
|
||||
"foobar",
|
||||
},
|
||||
{
|
||||
"#lookup - should lookup map element",
|
||||
"{{#each goodbyes}}{{lookup ../data .}}{{/each}}",
|
||||
map[string]interface{}{"goodbyes": []string{"foo", "bar"}, "data": map[string]string{"foo": "baz", "bar": "bat"}},
|
||||
nil, nil, nil,
|
||||
"bazbat",
|
||||
},
|
||||
{
|
||||
"#lookup - should lookup struct field",
|
||||
"{{#each goodbyes}}{{lookup ../data .}}{{/each}}",
|
||||
map[string]interface{}{"goodbyes": []string{"Foo", "Bar"}, "data": struct {
|
||||
Foo string
|
||||
Bar string
|
||||
}{"baz", "bat"}},
|
||||
nil, nil, nil,
|
||||
"bazbat",
|
||||
},
|
||||
{
|
||||
"#lookup - should lookup arbitrary content",
|
||||
"{{#each goodbyes}}{{lookup ../data .}}{{/each}}",
|
||||
map[string]interface{}{"goodbyes": []int{0, 1}, "data": []string{"foo", "bar"}},
|
||||
nil, nil, nil,
|
||||
"foobar",
|
||||
},
|
||||
{
|
||||
"#lookup - should not fail on undefined value",
|
||||
"{{#each goodbyes}}{{lookup ../bar .}}{{/each}}",
|
||||
map[string]interface{}{"goodbyes": []int{0, 1}, "data": []string{"foo", "bar"}},
|
||||
nil, nil, nil,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
func TestBuiltins(t *testing.T) {
|
||||
launchTests(t, builtinsTests)
|
||||
}
|
300
vendor/github.com/aymerick/raymond/handlebars/data_test.go
generated
vendored
Normal file
300
vendor/github.com/aymerick/raymond/handlebars/data_test.go
generated
vendored
Normal file
@ -0,0 +1,300 @@
|
||||
package handlebars
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/aymerick/raymond"
|
||||
)
|
||||
|
||||
//
|
||||
// Those tests come from:
|
||||
// https://github.com/wycats/handlebars.js/blob/master/spec/data.js
|
||||
//
|
||||
var dataTests = []Test{
|
||||
{
|
||||
"passing in data to a compiled function that expects data - works with helpers",
|
||||
"{{hello}}",
|
||||
map[string]string{"noun": "cat"},
|
||||
map[string]interface{}{"adjective": "happy"},
|
||||
map[string]interface{}{"hello": func(options *raymond.Options) string {
|
||||
return options.DataStr("adjective") + " " + options.ValueStr("noun")
|
||||
}},
|
||||
nil,
|
||||
"happy cat",
|
||||
},
|
||||
{
|
||||
"data can be looked up via @foo",
|
||||
"{{@hello}}",
|
||||
nil,
|
||||
map[string]interface{}{"hello": "hello"},
|
||||
nil, nil,
|
||||
"hello",
|
||||
},
|
||||
{
|
||||
"deep @foo triggers automatic top-level data",
|
||||
`{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}`,
|
||||
map[string]bool{"foo": true},
|
||||
map[string]interface{}{"hello": "hello"},
|
||||
map[string]interface{}{"let": func(options *raymond.Options) string {
|
||||
frame := options.NewDataFrame()
|
||||
|
||||
for k, v := range options.Hash() {
|
||||
frame.Set(k, v)
|
||||
}
|
||||
|
||||
return options.FnData(frame)
|
||||
}},
|
||||
nil,
|
||||
"Hello world",
|
||||
},
|
||||
{
|
||||
"parameter data can be looked up via @foo",
|
||||
`{{hello @world}}`,
|
||||
nil,
|
||||
map[string]interface{}{"world": "world"},
|
||||
map[string]interface{}{"hello": func(context string) string {
|
||||
return "Hello " + context
|
||||
}},
|
||||
nil,
|
||||
"Hello world",
|
||||
},
|
||||
{
|
||||
"hash values can be looked up via @foo",
|
||||
`{{hello noun=@world}}`,
|
||||
nil,
|
||||
map[string]interface{}{"world": "world"},
|
||||
map[string]interface{}{"hello": func(options *raymond.Options) string {
|
||||
return "Hello " + options.HashStr("noun")
|
||||
}},
|
||||
nil,
|
||||
"Hello world",
|
||||
},
|
||||
{
|
||||
"nested parameter data can be looked up via @foo.bar",
|
||||
`{{hello @world.bar}}`,
|
||||
nil,
|
||||
map[string]interface{}{"world": map[string]string{"bar": "world"}},
|
||||
map[string]interface{}{"hello": func(context string) string {
|
||||
return "Hello " + context
|
||||
}},
|
||||
nil,
|
||||
"Hello world",
|
||||
},
|
||||
{
|
||||
"nested parameter data does not fail with @world.bar",
|
||||
`{{hello @world.bar}}`,
|
||||
nil,
|
||||
map[string]interface{}{"foo": map[string]string{"bar": "world"}},
|
||||
map[string]interface{}{"hello": func(context string) string {
|
||||
return "Hello " + context
|
||||
}},
|
||||
nil,
|
||||
// @todo Test differs with JS implementation: we don't output `undefined`
|
||||
"Hello ",
|
||||
},
|
||||
|
||||
// @todo "parameter data throws when using complex scope references",
|
||||
|
||||
{
|
||||
"data can be functions",
|
||||
`{{@hello}}`,
|
||||
nil,
|
||||
map[string]interface{}{"hello": func() string { return "hello" }},
|
||||
nil, nil,
|
||||
"hello",
|
||||
},
|
||||
{
|
||||
"data can be functions with params",
|
||||
`{{@hello "hello"}}`,
|
||||
nil,
|
||||
map[string]interface{}{"hello": func(context string) string { return context }},
|
||||
nil, nil,
|
||||
"hello",
|
||||
},
|
||||
|
||||
{
|
||||
"data is inherited downstream",
|
||||
`{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}`,
|
||||
map[string]map[string]string{"bar": {"baz": "hello world"}},
|
||||
nil,
|
||||
map[string]interface{}{"let": func(options *raymond.Options) string {
|
||||
frame := options.NewDataFrame()
|
||||
|
||||
for k, v := range options.Hash() {
|
||||
frame.Set(k, v)
|
||||
}
|
||||
|
||||
return options.FnData(frame)
|
||||
}},
|
||||
nil,
|
||||
"2hello world1",
|
||||
},
|
||||
{
|
||||
"passing in data to a compiled function that expects data - works with helpers in partials",
|
||||
`{{>myPartial}}`,
|
||||
map[string]string{"noun": "cat"},
|
||||
map[string]interface{}{"adjective": "happy"},
|
||||
map[string]interface{}{"hello": func(options *raymond.Options) string {
|
||||
return options.DataStr("adjective") + " " + options.ValueStr("noun")
|
||||
}},
|
||||
map[string]string{
|
||||
"myPartial": "{{hello}}",
|
||||
},
|
||||
"happy cat",
|
||||
},
|
||||
{
|
||||
"passing in data to a compiled function that expects data - works with helpers and parameters",
|
||||
`{{hello world}}`,
|
||||
map[string]interface{}{"exclaim": true, "world": "world"},
|
||||
map[string]interface{}{"adjective": "happy"},
|
||||
map[string]interface{}{"hello": func(context string, options *raymond.Options) string {
|
||||
str := "error"
|
||||
if b, ok := options.Value("exclaim").(bool); ok {
|
||||
if b {
|
||||
str = "!"
|
||||
} else {
|
||||
str = ""
|
||||
}
|
||||
}
|
||||
|
||||
return options.DataStr("adjective") + " " + context + str
|
||||
}},
|
||||
nil,
|
||||
"happy world!",
|
||||
},
|
||||
{
|
||||
"passing in data to a compiled function that expects data - works with block helpers",
|
||||
`{{#hello}}{{world}}{{/hello}}`,
|
||||
map[string]bool{"exclaim": true},
|
||||
map[string]interface{}{"adjective": "happy"},
|
||||
map[string]interface{}{
|
||||
"hello": func(options *raymond.Options) string {
|
||||
return options.Fn()
|
||||
},
|
||||
"world": func(options *raymond.Options) string {
|
||||
str := "error"
|
||||
if b, ok := options.Value("exclaim").(bool); ok {
|
||||
if b {
|
||||
str = "!"
|
||||
} else {
|
||||
str = ""
|
||||
}
|
||||
}
|
||||
|
||||
return options.DataStr("adjective") + " world" + str
|
||||
},
|
||||
},
|
||||
nil,
|
||||
"happy world!",
|
||||
},
|
||||
{
|
||||
"passing in data to a compiled function that expects data - works with block helpers that use ..",
|
||||
`{{#hello}}{{world ../zomg}}{{/hello}}`,
|
||||
map[string]interface{}{"exclaim": true, "zomg": "world"},
|
||||
map[string]interface{}{"adjective": "happy"},
|
||||
map[string]interface{}{
|
||||
"hello": func(options *raymond.Options) string {
|
||||
return options.FnWith(map[string]string{"exclaim": "?"})
|
||||
},
|
||||
"world": func(context string, options *raymond.Options) string {
|
||||
return options.DataStr("adjective") + " " + context + options.ValueStr("exclaim")
|
||||
},
|
||||
},
|
||||
nil,
|
||||
"happy world?",
|
||||
},
|
||||
{
|
||||
"passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..",
|
||||
`{{#hello}}{{world ../zomg}}{{/hello}}`,
|
||||
map[string]interface{}{"exclaim": true, "zomg": "world"},
|
||||
map[string]interface{}{"adjective": "happy", "accessData": "#win"},
|
||||
map[string]interface{}{
|
||||
"hello": func(options *raymond.Options) string {
|
||||
return options.DataStr("accessData") + " " + options.FnWith(map[string]string{"exclaim": "?"})
|
||||
},
|
||||
"world": func(context string, options *raymond.Options) string {
|
||||
return options.DataStr("adjective") + " " + context + options.ValueStr("exclaim")
|
||||
},
|
||||
},
|
||||
nil,
|
||||
"#win happy world?",
|
||||
},
|
||||
{
|
||||
"you can override inherited data when invoking a helper",
|
||||
`{{#hello}}{{world zomg}}{{/hello}}`,
|
||||
map[string]interface{}{"exclaim": true, "zomg": "planet"},
|
||||
map[string]interface{}{"adjective": "happy"},
|
||||
map[string]interface{}{
|
||||
"hello": func(options *raymond.Options) string {
|
||||
ctx := map[string]string{"exclaim": "?", "zomg": "world"}
|
||||
data := options.NewDataFrame()
|
||||
data.Set("adjective", "sad")
|
||||
|
||||
return options.FnCtxData(ctx, data)
|
||||
},
|
||||
"world": func(context string, options *raymond.Options) string {
|
||||
return options.DataStr("adjective") + " " + context + options.ValueStr("exclaim")
|
||||
},
|
||||
},
|
||||
nil,
|
||||
"sad world?",
|
||||
},
|
||||
{
|
||||
"you can override inherited data when invoking a helper with depth",
|
||||
`{{#hello}}{{world ../zomg}}{{/hello}}`,
|
||||
map[string]interface{}{"exclaim": true, "zomg": "world"},
|
||||
map[string]interface{}{"adjective": "happy"},
|
||||
map[string]interface{}{
|
||||
"hello": func(options *raymond.Options) string {
|
||||
ctx := map[string]string{"exclaim": "?"}
|
||||
data := options.NewDataFrame()
|
||||
data.Set("adjective", "sad")
|
||||
|
||||
return options.FnCtxData(ctx, data)
|
||||
},
|
||||
"world": func(context string, options *raymond.Options) string {
|
||||
return options.DataStr("adjective") + " " + context + options.ValueStr("exclaim")
|
||||
},
|
||||
},
|
||||
nil,
|
||||
"sad world?",
|
||||
},
|
||||
{
|
||||
"@root - the root context can be looked up via @root",
|
||||
`{{@root.foo}}`,
|
||||
map[string]interface{}{"foo": "hello"},
|
||||
nil, nil, nil,
|
||||
"hello",
|
||||
},
|
||||
{
|
||||
"@root - passed root values take priority",
|
||||
`{{@root.foo}}`,
|
||||
nil,
|
||||
map[string]interface{}{"root": map[string]string{"foo": "hello"}},
|
||||
nil, nil,
|
||||
"hello",
|
||||
},
|
||||
{
|
||||
"nesting - the root context can be looked up via @root",
|
||||
`{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}`,
|
||||
map[string]interface{}{"foo": "hello"},
|
||||
map[string]interface{}{"depth": 0},
|
||||
map[string]interface{}{
|
||||
"helper": func(options *raymond.Options) string {
|
||||
data := options.NewDataFrame()
|
||||
|
||||
if depth, ok := options.Data("depth").(int); ok {
|
||||
data.Set("depth", depth+1)
|
||||
}
|
||||
|
||||
return options.FnData(data)
|
||||
},
|
||||
},
|
||||
nil,
|
||||
"2 1 0",
|
||||
},
|
||||
}
|
||||
|
||||
func TestData(t *testing.T) {
|
||||
launchTests(t, dataTests)
|
||||
}
|
2
vendor/github.com/aymerick/raymond/handlebars/doc.go
generated
vendored
Normal file
2
vendor/github.com/aymerick/raymond/handlebars/doc.go
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
// Package handlebars contains all the tests that come from handlebars.js project.
|
||||
package handlebars
|
665
vendor/github.com/aymerick/raymond/handlebars/helpers_test.go
generated
vendored
Normal file
665
vendor/github.com/aymerick/raymond/handlebars/helpers_test.go
generated
vendored
Normal file
@ -0,0 +1,665 @@
|
||||
package handlebars
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/aymerick/raymond"
|
||||
)
|
||||
|
||||
//
|
||||
// Helpers
|
||||
//
|
||||
|
||||
func barSuffixHelper(context string) string {
|
||||
return "bar " + context
|
||||
}
|
||||
|
||||
func echoHelper(str string) string {
|
||||
return str
|
||||
}
|
||||
|
||||
func echoNbHelper(str string, nb int) string {
|
||||
result := ""
|
||||
for i := 0; i < nb; i++ {
|
||||
result += str
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func linkHelper(prefix string, options *raymond.Options) string {
|
||||
return fmt.Sprintf(`<a href="%s/%s">%s</a>`, prefix, options.ValueStr("url"), options.ValueStr("text"))
|
||||
}
|
||||
|
||||
func rawHelper(options *raymond.Options) string {
|
||||
return options.Fn()
|
||||
}
|
||||
|
||||
func rawThreeHelper(a, b, c string, options *raymond.Options) string {
|
||||
return options.Fn() + a + b + c
|
||||
}
|
||||
|
||||
func formHelper(options *raymond.Options) string {
|
||||
return "<form>" + options.Fn() + "</form>"
|
||||
}
|
||||
|
||||
func formCtxHelper(context interface{}, options *raymond.Options) string {
|
||||
return "<form>" + options.FnWith(context) + "</form>"
|
||||
}
|
||||
|
||||
func listHelper(context interface{}, options *raymond.Options) string {
|
||||
val := reflect.ValueOf(context)
|
||||
switch val.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
if val.Len() > 0 {
|
||||
result := "<ul>"
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
result += "<li>"
|
||||
result += options.FnWith(val.Index(i).Interface())
|
||||
result += "</li>"
|
||||
}
|
||||
result += "</ul>"
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return "<p>" + options.Inverse() + "</p>"
|
||||
}
|
||||
|
||||
func blogHelper(val string) string {
|
||||
return "val is " + val
|
||||
}
|
||||
|
||||
func equalHelper(a, b string) string {
|
||||
return raymond.Str(a == b)
|
||||
}
|
||||
|
||||
func dashHelper(a, b string) string {
|
||||
return a + "-" + b
|
||||
}
|
||||
|
||||
func concatHelper(a, b string) string {
|
||||
return a + b
|
||||
}
|
||||
|
||||
func detectDataHelper(options *raymond.Options) string {
|
||||
if val, ok := options.DataFrame().Get("exclaim").(string); ok {
|
||||
return val
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
//
|
||||
// Those tests come from:
|
||||
// https://github.com/wycats/handlebars.js/blob/master/spec/helper.js
|
||||
//
|
||||
var helpersTests = []Test{
|
||||
{
|
||||
"helper with complex lookup",
|
||||
"{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}",
|
||||
map[string]interface{}{"prefix": "/root", "goodbyes": []map[string]string{{"text": "Goodbye", "url": "goodbye"}}},
|
||||
nil,
|
||||
map[string]interface{}{"link": linkHelper},
|
||||
nil,
|
||||
`<a href="/root/goodbye">Goodbye</a>`,
|
||||
},
|
||||
{
|
||||
"helper for raw block gets raw content",
|
||||
"{{{{raw}}}} {{test}} {{{{/raw}}}}",
|
||||
map[string]interface{}{"test": "hello"},
|
||||
nil,
|
||||
map[string]interface{}{"raw": rawHelper},
|
||||
nil,
|
||||
" {{test}} ",
|
||||
},
|
||||
{
|
||||
"helper for raw block gets parameters",
|
||||
"{{{{raw 1 2 3}}}} {{test}} {{{{/raw}}}}",
|
||||
map[string]interface{}{"test": "hello"},
|
||||
nil,
|
||||
map[string]interface{}{"raw": rawThreeHelper},
|
||||
nil,
|
||||
" {{test}} 123",
|
||||
},
|
||||
{
|
||||
"helper block with complex lookup expression",
|
||||
"{{#goodbyes}}{{../name}}{{/goodbyes}}",
|
||||
map[string]interface{}{"name": "Alan"},
|
||||
nil,
|
||||
map[string]interface{}{"goodbyes": func(options *raymond.Options) string {
|
||||
out := ""
|
||||
for _, str := range []string{"Goodbye", "goodbye", "GOODBYE"} {
|
||||
out += str + " " + options.FnWith(str) + "! "
|
||||
}
|
||||
return out
|
||||
}},
|
||||
nil,
|
||||
"Goodbye Alan! goodbye Alan! GOODBYE Alan! ",
|
||||
},
|
||||
{
|
||||
"helper with complex lookup and nested template",
|
||||
"{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}",
|
||||
map[string]interface{}{"prefix": "/root", "goodbyes": []map[string]string{{"text": "Goodbye", "url": "goodbye"}}},
|
||||
nil,
|
||||
map[string]interface{}{"link": linkHelper},
|
||||
nil,
|
||||
`<a href="/root/goodbye">Goodbye</a>`,
|
||||
},
|
||||
{
|
||||
// note: The JS implementation returns undefined, we return empty string
|
||||
"helper returning undefined value (1)",
|
||||
" {{nothere}}",
|
||||
map[string]interface{}{},
|
||||
nil,
|
||||
map[string]interface{}{"nothere": func() string {
|
||||
return ""
|
||||
}},
|
||||
nil,
|
||||
" ",
|
||||
},
|
||||
{
|
||||
// note: The JS implementation returns undefined, we return empty string
|
||||
"helper returning undefined value (2)",
|
||||
" {{#nothere}}{{/nothere}}",
|
||||
map[string]interface{}{},
|
||||
nil,
|
||||
map[string]interface{}{"nothere": func() string {
|
||||
return ""
|
||||
}},
|
||||
nil,
|
||||
" ",
|
||||
},
|
||||
{
|
||||
"block helper",
|
||||
"{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!",
|
||||
map[string]interface{}{"world": "world"},
|
||||
nil,
|
||||
map[string]interface{}{"goodbyes": func(options *raymond.Options) string {
|
||||
return options.FnWith(map[string]string{"text": "GOODBYE"})
|
||||
}},
|
||||
nil,
|
||||
"GOODBYE! cruel world!",
|
||||
},
|
||||
{
|
||||
"block helper staying in the same context",
|
||||
"{{#form}}<p>{{name}}</p>{{/form}}",
|
||||
map[string]interface{}{"name": "Yehuda"},
|
||||
nil,
|
||||
map[string]interface{}{"form": formHelper},
|
||||
nil,
|
||||
"<form><p>Yehuda</p></form>",
|
||||
},
|
||||
{
|
||||
"block helper should have context in this",
|
||||
"<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>",
|
||||
map[string]interface{}{"people": []map[string]interface{}{{"name": "Alan", "id": 1}, {"name": "Yehuda", "id": 2}}},
|
||||
nil,
|
||||
map[string]interface{}{"link": func(options *raymond.Options) string {
|
||||
return fmt.Sprintf("<a href=\"/people/%s\">%s</a>", options.ValueStr("id"), options.Fn())
|
||||
}},
|
||||
nil,
|
||||
`<ul><li><a href="/people/1">Alan</a></li><li><a href="/people/2">Yehuda</a></li></ul>`,
|
||||
},
|
||||
{
|
||||
"block helper for undefined value",
|
||||
"{{#empty}}shouldn't render{{/empty}}",
|
||||
nil, nil, nil, nil,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"block helper passing a new context",
|
||||
"{{#form yehuda}}<p>{{name}}</p>{{/form}}",
|
||||
map[string]map[string]string{"yehuda": {"name": "Yehuda"}},
|
||||
nil,
|
||||
map[string]interface{}{"form": formCtxHelper},
|
||||
nil,
|
||||
"<form><p>Yehuda</p></form>",
|
||||
},
|
||||
{
|
||||
"block helper passing a complex path context",
|
||||
"{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}",
|
||||
map[string]map[string]interface{}{"yehuda": {"name": "Yehuda", "cat": map[string]string{"name": "Harold"}}},
|
||||
nil,
|
||||
map[string]interface{}{"form": formCtxHelper},
|
||||
nil,
|
||||
"<form><p>Harold</p></form>",
|
||||
},
|
||||
{
|
||||
"nested block helpers",
|
||||
"{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}",
|
||||
map[string]map[string]string{"yehuda": {"name": "Yehuda"}},
|
||||
nil,
|
||||
map[string]interface{}{"link": func(options *raymond.Options) string {
|
||||
return fmt.Sprintf("<a href=\"%s\">%s</a>", options.ValueStr("name"), options.Fn())
|
||||
}, "form": formCtxHelper},
|
||||
nil,
|
||||
`<form><p>Yehuda</p><a href="Yehuda">Hello</a></form>`,
|
||||
},
|
||||
{
|
||||
"block helper inverted sections (1) - an inverse wrapper is passed in as a new context",
|
||||
"{{#list people}}{{name}}{{^}}<em>Nobody's here</em>{{/list}}",
|
||||
map[string][]map[string]string{"people": {{"name": "Alan"}, {"name": "Yehuda"}}},
|
||||
nil,
|
||||
map[string]interface{}{"list": listHelper},
|
||||
nil,
|
||||
`<ul><li>Alan</li><li>Yehuda</li></ul>`,
|
||||
},
|
||||
{
|
||||
"block helper inverted sections (2) - an inverse wrapper can be optionally called",
|
||||
"{{#list people}}{{name}}{{^}}<em>Nobody's here</em>{{/list}}",
|
||||
map[string][]map[string]string{"people": {}},
|
||||
nil,
|
||||
map[string]interface{}{"list": listHelper},
|
||||
nil,
|
||||
`<p><em>Nobody's here</em></p>`,
|
||||
},
|
||||
{
|
||||
"block helper inverted sections (3) - the context of an inverse is the parent of the block",
|
||||
"{{#list people}}Hello{{^}}{{message}}{{/list}}",
|
||||
map[string]interface{}{"people": []interface{}{}, "message": "Nobody's here"},
|
||||
nil,
|
||||
map[string]interface{}{"list": listHelper},
|
||||
nil,
|
||||
`<p>Nobody's here</p>`,
|
||||
},
|
||||
|
||||
{
|
||||
"pathed lambdas with parameters (1)",
|
||||
"{{./helper 1}}",
|
||||
map[string]interface{}{
|
||||
"helper": func(param int) string { return "winning" },
|
||||
"hash": map[string]interface{}{
|
||||
"helper": func(param int) string { return "winning" },
|
||||
}},
|
||||
nil,
|
||||
map[string]interface{}{"./helper": func(param int) string { return "fail" }},
|
||||
nil,
|
||||
"winning",
|
||||
},
|
||||
{
|
||||
"pathed lambdas with parameters (2)",
|
||||
"{{hash/helper 1}}",
|
||||
map[string]interface{}{
|
||||
"helper": func(param int) string { return "winning" },
|
||||
"hash": map[string]interface{}{
|
||||
"helper": func(param int) string { return "winning" },
|
||||
}},
|
||||
nil,
|
||||
map[string]interface{}{"./helper": func(param int) string { return "fail" }},
|
||||
nil,
|
||||
"winning",
|
||||
},
|
||||
|
||||
{
|
||||
"helpers hash - providing a helpers hash (1)",
|
||||
"Goodbye {{cruel}} {{world}}!",
|
||||
map[string]interface{}{"cruel": "cruel"},
|
||||
nil,
|
||||
map[string]interface{}{"world": func() string { return "world" }},
|
||||
nil,
|
||||
"Goodbye cruel world!",
|
||||
},
|
||||
{
|
||||
"helpers hash - providing a helpers hash (2)",
|
||||
"Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!",
|
||||
map[string]interface{}{"iter": []map[string]string{{"cruel": "cruel"}}},
|
||||
nil,
|
||||
map[string]interface{}{"world": func() string { return "world" }},
|
||||
nil,
|
||||
"Goodbye cruel world!",
|
||||
},
|
||||
{
|
||||
"helpers hash - in cases of conflict, helpers win (1)",
|
||||
"{{{lookup}}}",
|
||||
map[string]interface{}{"lookup": "Explicit"},
|
||||
nil,
|
||||
map[string]interface{}{"lookup": func() string { return "helpers" }},
|
||||
nil,
|
||||
"helpers",
|
||||
},
|
||||
{
|
||||
"helpers hash - in cases of conflict, helpers win (2)",
|
||||
"{{lookup}}",
|
||||
map[string]interface{}{"lookup": "Explicit"},
|
||||
nil,
|
||||
map[string]interface{}{"lookup": func() string { return "helpers" }},
|
||||
nil,
|
||||
"helpers",
|
||||
},
|
||||
{
|
||||
"helpers hash - the helpers hash is available is nested contexts",
|
||||
"{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}",
|
||||
map[string]interface{}{"outer": map[string]interface{}{"inner": map[string]interface{}{"unused": []string{}}}},
|
||||
nil,
|
||||
map[string]interface{}{"helper": func() string { return "helper" }},
|
||||
nil,
|
||||
"helper",
|
||||
},
|
||||
|
||||
// @todo "helpers hash - the helper hash should augment the global hash"
|
||||
|
||||
// @todo "registration"
|
||||
|
||||
{
|
||||
"decimal number literals work",
|
||||
"Message: {{hello -1.2 1.2}}",
|
||||
nil, nil,
|
||||
map[string]interface{}{"hello": func(times, times2 interface{}) string {
|
||||
ts, t2s := "NaN", "NaN"
|
||||
|
||||
if v, ok := times.(float64); ok {
|
||||
ts = raymond.Str(v)
|
||||
}
|
||||
|
||||
if v, ok := times2.(float64); ok {
|
||||
t2s = raymond.Str(v)
|
||||
}
|
||||
|
||||
return "Hello " + ts + " " + t2s + " times"
|
||||
}},
|
||||
nil,
|
||||
"Message: Hello -1.2 1.2 times",
|
||||
},
|
||||
{
|
||||
"negative number literals work",
|
||||
"Message: {{hello -12}}",
|
||||
nil, nil,
|
||||
map[string]interface{}{"hello": func(times interface{}) string {
|
||||
ts := "NaN"
|
||||
|
||||
if v, ok := times.(int); ok {
|
||||
ts = raymond.Str(v)
|
||||
}
|
||||
|
||||
return "Hello " + ts + " times"
|
||||
}},
|
||||
nil,
|
||||
"Message: Hello -12 times",
|
||||
},
|
||||
|
||||
{
|
||||
"String literal parameters - simple literals work",
|
||||
`Message: {{hello "world" 12 true false}}`,
|
||||
nil, nil,
|
||||
map[string]interface{}{"hello": func(p, t, b, b2 interface{}) string {
|
||||
times, bool1, bool2 := "NaN", "NaB", "NaB"
|
||||
|
||||
param, ok := p.(string)
|
||||
if !ok {
|
||||
param = "NaN"
|
||||
}
|
||||
|
||||
if v, ok := t.(int); ok {
|
||||
times = raymond.Str(v)
|
||||
}
|
||||
|
||||
if v, ok := b.(bool); ok {
|
||||
bool1 = raymond.Str(v)
|
||||
}
|
||||
|
||||
if v, ok := b2.(bool); ok {
|
||||
bool2 = raymond.Str(v)
|
||||
}
|
||||
|
||||
return "Hello " + param + " " + times + " times: " + bool1 + " " + bool2
|
||||
}},
|
||||
nil,
|
||||
"Message: Hello world 12 times: true false",
|
||||
},
|
||||
|
||||
// @todo "using a quote in the middle of a parameter raises an error"
|
||||
|
||||
{
|
||||
"String literal parameters - escaping a String is possible",
|
||||
"Message: {{{hello \"\\\"world\\\"\"}}}",
|
||||
nil, nil,
|
||||
map[string]interface{}{"hello": func(param string) string {
|
||||
return "Hello " + param
|
||||
}},
|
||||
nil,
|
||||
`Message: Hello "world"`,
|
||||
},
|
||||
{
|
||||
"String literal parameters - it works with ' marks",
|
||||
"Message: {{{hello \"Alan's world\"}}}",
|
||||
nil, nil,
|
||||
map[string]interface{}{"hello": func(param string) string {
|
||||
return "Hello " + param
|
||||
}},
|
||||
nil,
|
||||
`Message: Hello Alan's world`,
|
||||
},
|
||||
|
||||
{
|
||||
"multiple parameters - simple multi-params work",
|
||||
"Message: {{goodbye cruel world}}",
|
||||
map[string]string{"cruel": "cruel", "world": "world"},
|
||||
nil,
|
||||
map[string]interface{}{"goodbye": func(cruel, world string) string {
|
||||
return "Goodbye " + cruel + " " + world
|
||||
}},
|
||||
nil,
|
||||
"Message: Goodbye cruel world",
|
||||
},
|
||||
{
|
||||
"multiple parameters - block multi-params work",
|
||||
"Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}",
|
||||
map[string]string{"cruel": "cruel", "world": "world"},
|
||||
nil,
|
||||
map[string]interface{}{"goodbye": func(cruel, world string, options *raymond.Options) string {
|
||||
return options.FnWith(map[string]interface{}{"greeting": "Goodbye", "adj": cruel, "noun": world})
|
||||
}},
|
||||
nil,
|
||||
"Message: Goodbye cruel world",
|
||||
},
|
||||
|
||||
{
|
||||
"hash - helpers can take an optional hash",
|
||||
`{{goodbye cruel="CRUEL" world="WORLD" times=12}}`,
|
||||
nil, nil,
|
||||
map[string]interface{}{"goodbye": func(options *raymond.Options) string {
|
||||
return "GOODBYE " + options.HashStr("cruel") + " " + options.HashStr("world") + " " + options.HashStr("times") + " TIMES"
|
||||
}},
|
||||
nil,
|
||||
"GOODBYE CRUEL WORLD 12 TIMES",
|
||||
},
|
||||
{
|
||||
"hash - helpers can take an optional hash with booleans (1)",
|
||||
`{{goodbye cruel="CRUEL" world="WORLD" print=true}}`,
|
||||
nil, nil,
|
||||
map[string]interface{}{"goodbye": func(options *raymond.Options) string {
|
||||
p, ok := options.HashProp("print").(bool)
|
||||
if ok {
|
||||
if p {
|
||||
return "GOODBYE " + options.HashStr("cruel") + " " + options.HashStr("world")
|
||||
}
|
||||
return "NOT PRINTING"
|
||||
}
|
||||
|
||||
return "THIS SHOULD NOT HAPPEN"
|
||||
}},
|
||||
nil,
|
||||
"GOODBYE CRUEL WORLD",
|
||||
},
|
||||
{
|
||||
"hash - helpers can take an optional hash with booleans (2)",
|
||||
`{{goodbye cruel="CRUEL" world="WORLD" print=false}}`,
|
||||
nil, nil,
|
||||
map[string]interface{}{"goodbye": func(options *raymond.Options) string {
|
||||
p, ok := options.HashProp("print").(bool)
|
||||
if ok {
|
||||
if p {
|
||||
return "GOODBYE " + options.HashStr("cruel") + " " + options.HashStr("world")
|
||||
}
|
||||
return "NOT PRINTING"
|
||||
}
|
||||
|
||||
return "THIS SHOULD NOT HAPPEN"
|
||||
}},
|
||||
nil,
|
||||
"NOT PRINTING",
|
||||
},
|
||||
{
|
||||
"block helpers can take an optional hash",
|
||||
`{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}`,
|
||||
nil, nil,
|
||||
map[string]interface{}{"goodbye": func(options *raymond.Options) string {
|
||||
return "GOODBYE " + options.HashStr("cruel") + " " + options.Fn() + " " + options.HashStr("times") + " TIMES"
|
||||
}},
|
||||
nil,
|
||||
"GOODBYE CRUEL world 12 TIMES",
|
||||
},
|
||||
{
|
||||
"block helpers can take an optional hash with single quoted stings",
|
||||
`{{#goodbye cruel='CRUEL' times=12}}world{{/goodbye}}`,
|
||||
nil, nil,
|
||||
map[string]interface{}{"goodbye": func(options *raymond.Options) string {
|
||||
return "GOODBYE " + options.HashStr("cruel") + " " + options.Fn() + " " + options.HashStr("times") + " TIMES"
|
||||
}},
|
||||
nil,
|
||||
"GOODBYE CRUEL world 12 TIMES",
|
||||
},
|
||||
{
|
||||
"block helpers can take an optional hash with booleans (1)",
|
||||
`{{#goodbye cruel="CRUEL" print=true}}world{{/goodbye}}`,
|
||||
nil, nil,
|
||||
map[string]interface{}{"goodbye": func(options *raymond.Options) string {
|
||||
p, ok := options.HashProp("print").(bool)
|
||||
if ok {
|
||||
if p {
|
||||
return "GOODBYE " + options.HashStr("cruel") + " " + options.Fn()
|
||||
}
|
||||
return "NOT PRINTING"
|
||||
}
|
||||
|
||||
return "THIS SHOULD NOT HAPPEN"
|
||||
}},
|
||||
nil,
|
||||
"GOODBYE CRUEL world",
|
||||
},
|
||||
{
|
||||
"block helpers can take an optional hash with booleans (1)",
|
||||
`{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}`,
|
||||
nil, nil,
|
||||
map[string]interface{}{"goodbye": func(options *raymond.Options) string {
|
||||
p, ok := options.HashProp("print").(bool)
|
||||
if ok {
|
||||
if p {
|
||||
return "GOODBYE " + options.HashStr("cruel") + " " + options.Fn()
|
||||
}
|
||||
return "NOT PRINTING"
|
||||
}
|
||||
|
||||
return "THIS SHOULD NOT HAPPEN"
|
||||
}},
|
||||
nil,
|
||||
"NOT PRINTING",
|
||||
},
|
||||
|
||||
// @todo "helperMissing - if a context is not found, helperMissing is used" throw error
|
||||
|
||||
// @todo "helperMissing - if a context is not found, custom helperMissing is used"
|
||||
|
||||
// @todo "helperMissing - if a value is not found, custom helperMissing is used"
|
||||
|
||||
{
|
||||
"block helpers can take an optional hash with booleans (1)",
|
||||
`{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}`,
|
||||
nil, nil,
|
||||
map[string]interface{}{"goodbye": func(options *raymond.Options) string {
|
||||
p, ok := options.HashProp("print").(bool)
|
||||
if ok {
|
||||
if p {
|
||||
return "GOODBYE " + options.HashStr("cruel") + " " + options.Fn()
|
||||
}
|
||||
return "NOT PRINTING"
|
||||
}
|
||||
|
||||
return "THIS SHOULD NOT HAPPEN"
|
||||
}},
|
||||
nil,
|
||||
"NOT PRINTING",
|
||||
},
|
||||
|
||||
// @todo "knownHelpers/knownHelpersOnly" tests
|
||||
|
||||
// @todo "blockHelperMissing" tests
|
||||
|
||||
// @todo "name field" tests
|
||||
|
||||
{
|
||||
"name conflicts - helpers take precedence over same-named context properties",
|
||||
`{{goodbye}} {{cruel world}}`,
|
||||
map[string]string{"goodbye": "goodbye", "world": "world"},
|
||||
nil,
|
||||
map[string]interface{}{
|
||||
"goodbye": func(options *raymond.Options) string {
|
||||
return strings.ToUpper(options.ValueStr("goodbye"))
|
||||
},
|
||||
"cruel": func(world string) string {
|
||||
return "cruel " + strings.ToUpper(world)
|
||||
},
|
||||
},
|
||||
nil,
|
||||
"GOODBYE cruel WORLD",
|
||||
},
|
||||
{
|
||||
"name conflicts - helpers take precedence over same-named context properties",
|
||||
`{{#goodbye}} {{cruel world}}{{/goodbye}}`,
|
||||
map[string]string{"goodbye": "goodbye", "world": "world"},
|
||||
nil,
|
||||
map[string]interface{}{
|
||||
"goodbye": func(options *raymond.Options) string {
|
||||
return strings.ToUpper(options.ValueStr("goodbye")) + options.Fn()
|
||||
},
|
||||
"cruel": func(world string) string {
|
||||
return "cruel " + strings.ToUpper(world)
|
||||
},
|
||||
},
|
||||
nil,
|
||||
"GOODBYE cruel WORLD",
|
||||
},
|
||||
{
|
||||
"name conflicts - Scoped names take precedence over helpers",
|
||||
`{{this.goodbye}} {{cruel world}} {{cruel this.goodbye}}`,
|
||||
map[string]string{"goodbye": "goodbye", "world": "world"},
|
||||
nil,
|
||||
map[string]interface{}{
|
||||
"goodbye": func(options *raymond.Options) string {
|
||||
return strings.ToUpper(options.ValueStr("goodbye"))
|
||||
},
|
||||
"cruel": func(world string) string {
|
||||
return "cruel " + strings.ToUpper(world)
|
||||
},
|
||||
},
|
||||
nil,
|
||||
"goodbye cruel WORLD cruel GOODBYE",
|
||||
},
|
||||
{
|
||||
"name conflicts - Scoped names take precedence over block helpers",
|
||||
`{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}`,
|
||||
map[string]string{"goodbye": "goodbye", "world": "world"},
|
||||
nil,
|
||||
map[string]interface{}{
|
||||
"goodbye": func(options *raymond.Options) string {
|
||||
return strings.ToUpper(options.ValueStr("goodbye")) + options.Fn()
|
||||
},
|
||||
"cruel": func(world string) string {
|
||||
return "cruel " + strings.ToUpper(world)
|
||||
},
|
||||
},
|
||||
nil,
|
||||
"GOODBYE cruel WORLD goodbye",
|
||||
},
|
||||
|
||||
// @todo "block params" tests
|
||||
}
|
||||
|
||||
func TestHelpers(t *testing.T) {
|
||||
launchTests(t, helpersTests)
|
||||
}
|
182
vendor/github.com/aymerick/raymond/handlebars/partials_test.go
generated
vendored
Normal file
182
vendor/github.com/aymerick/raymond/handlebars/partials_test.go
generated
vendored
Normal file
@ -0,0 +1,182 @@
|
||||
package handlebars
|
||||
|
||||
import "testing"
|
||||
|
||||
//
|
||||
// Those tests come from:
|
||||
// https://github.com/wycats/handlebars.js/blob/master/spec/partials.js
|
||||
//
|
||||
var partialsTests = []Test{
|
||||
{
|
||||
"basic partials",
|
||||
"Dudes: {{#dudes}}{{> dude}}{{/dudes}}",
|
||||
map[string]interface{}{"dudes": []map[string]string{{"name": "Yehuda", "url": "http://yehuda"}, {"name": "Alan", "url": "http://alan"}}},
|
||||
nil, nil,
|
||||
map[string]string{"dude": "{{name}} ({{url}}) "},
|
||||
"Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
|
||||
},
|
||||
{
|
||||
"dynamic partials",
|
||||
"Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}",
|
||||
map[string]interface{}{"dudes": []map[string]string{{"name": "Yehuda", "url": "http://yehuda"}, {"name": "Alan", "url": "http://alan"}}},
|
||||
nil,
|
||||
map[string]interface{}{"partial": func() string {
|
||||
return "dude"
|
||||
}},
|
||||
map[string]string{"dude": "{{name}} ({{url}}) "},
|
||||
"Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
|
||||
},
|
||||
|
||||
// @todo "failing dynamic partials"
|
||||
|
||||
{
|
||||
"partials with context",
|
||||
"Dudes: {{>dude dudes}}",
|
||||
map[string]interface{}{"dudes": []map[string]string{{"name": "Yehuda", "url": "http://yehuda"}, {"name": "Alan", "url": "http://alan"}}},
|
||||
nil, nil,
|
||||
map[string]string{"dude": "{{#this}}{{name}} ({{url}}) {{/this}}"},
|
||||
"Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
|
||||
},
|
||||
{
|
||||
"partials with undefined context",
|
||||
"Dudes: {{>dude dudes}}",
|
||||
map[string]interface{}{},
|
||||
nil, nil,
|
||||
map[string]string{"dude": "{{foo}} Empty"},
|
||||
"Dudes: Empty",
|
||||
},
|
||||
|
||||
// @todo "partials with duplicate parameters"
|
||||
|
||||
{
|
||||
"partials with parameters",
|
||||
"Dudes: {{#dudes}}{{> dude others=..}}{{/dudes}}",
|
||||
map[string]interface{}{"foo": "bar", "dudes": []map[string]string{{"name": "Yehuda", "url": "http://yehuda"}, {"name": "Alan", "url": "http://alan"}}},
|
||||
nil, nil,
|
||||
map[string]string{"dude": "{{others.foo}}{{name}} ({{url}}) "},
|
||||
"Dudes: barYehuda (http://yehuda) barAlan (http://alan) ",
|
||||
},
|
||||
{
|
||||
"partial in a partial",
|
||||
"Dudes: {{#dudes}}{{>dude}}{{/dudes}}",
|
||||
map[string]interface{}{"dudes": []map[string]string{{"name": "Yehuda", "url": "http://yehuda"}, {"name": "Alan", "url": "http://alan"}}},
|
||||
nil, nil,
|
||||
map[string]string{"dude": "{{name}} {{> url}} ", "url": `<a href="{{url}}">{{url}}</a>`},
|
||||
`Dudes: Yehuda <a href="http://yehuda">http://yehuda</a> Alan <a href="http://alan">http://alan</a> `,
|
||||
},
|
||||
|
||||
// @todo "rendering undefined partial throws an exception"
|
||||
|
||||
// @todo "registering undefined partial throws an exception"
|
||||
|
||||
// SKIP: "rendering template partial in vm mode throws an exception"
|
||||
// SKIP: "rendering function partial in vm mode"
|
||||
|
||||
{
|
||||
"GH-14: a partial preceding a selector",
|
||||
"Dudes: {{>dude}} {{anotherDude}}",
|
||||
map[string]string{"name": "Jeepers", "anotherDude": "Creepers"},
|
||||
nil, nil,
|
||||
map[string]string{"dude": "{{name}}"},
|
||||
"Dudes: Jeepers Creepers",
|
||||
},
|
||||
{
|
||||
"Partials with slash paths",
|
||||
"Dudes: {{> shared/dude}}",
|
||||
map[string]string{"name": "Jeepers", "anotherDude": "Creepers"},
|
||||
nil, nil,
|
||||
map[string]string{"shared/dude": "{{name}}"},
|
||||
"Dudes: Jeepers",
|
||||
},
|
||||
{
|
||||
"Partials with slash and point paths",
|
||||
"Dudes: {{> shared/dude.thing}}",
|
||||
map[string]string{"name": "Jeepers", "anotherDude": "Creepers"},
|
||||
nil, nil,
|
||||
map[string]string{"shared/dude.thing": "{{name}}"},
|
||||
"Dudes: Jeepers",
|
||||
},
|
||||
|
||||
// @todo "Global Partials"
|
||||
|
||||
// @todo "Multiple partial registration"
|
||||
|
||||
{
|
||||
"Partials with integer path",
|
||||
"Dudes: {{> 404}}",
|
||||
map[string]string{"name": "Jeepers", "anotherDude": "Creepers"},
|
||||
nil, nil,
|
||||
map[string]string{"404": "{{name}}"}, // @note Difference with JS test: partial name is a string
|
||||
"Dudes: Jeepers",
|
||||
},
|
||||
// @note This is not supported by our implementation. But really... who cares ?
|
||||
// {
|
||||
// "Partials with complex path",
|
||||
// "Dudes: {{> 404/asdf?.bar}}",
|
||||
// map[string]string{"name": "Jeepers", "anotherDude": "Creepers"},
|
||||
// nil, nil,
|
||||
// map[string]string{"404/asdf?.bar": "{{name}}"},
|
||||
// "Dudes: Jeepers",
|
||||
// },
|
||||
{
|
||||
"Partials with escaped",
|
||||
"Dudes: {{> [+404/asdf?.bar]}}",
|
||||
map[string]string{"name": "Jeepers", "anotherDude": "Creepers"},
|
||||
nil, nil,
|
||||
map[string]string{"+404/asdf?.bar": "{{name}}"},
|
||||
"Dudes: Jeepers",
|
||||
},
|
||||
{
|
||||
"Partials with string",
|
||||
"Dudes: {{> '+404/asdf?.bar'}}",
|
||||
map[string]string{"name": "Jeepers", "anotherDude": "Creepers"},
|
||||
nil, nil,
|
||||
map[string]string{"+404/asdf?.bar": "{{name}}"},
|
||||
"Dudes: Jeepers",
|
||||
},
|
||||
{
|
||||
"should handle empty partial",
|
||||
"Dudes: {{#dudes}}{{> dude}}{{/dudes}}",
|
||||
map[string]interface{}{"dudes": []map[string]string{{"name": "Yehuda", "url": "http://yehuda"}, {"name": "Alan", "url": "http://alan"}}},
|
||||
nil, nil,
|
||||
map[string]string{"dude": ""},
|
||||
"Dudes: ",
|
||||
},
|
||||
|
||||
// @todo "throw on missing partial"
|
||||
|
||||
// SKIP: "should pass compiler flags"
|
||||
|
||||
{
|
||||
"standalone partials (1) - indented partials",
|
||||
"Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}",
|
||||
map[string]interface{}{"dudes": []map[string]string{{"name": "Yehuda", "url": "http://yehuda"}, {"name": "Alan", "url": "http://alan"}}},
|
||||
nil, nil,
|
||||
map[string]string{"dude": "{{name}}\n"},
|
||||
"Dudes:\n Yehuda\n Alan\n",
|
||||
},
|
||||
{
|
||||
"standalone partials (2) - nested indented partials",
|
||||
"Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}",
|
||||
map[string]interface{}{"dudes": []map[string]string{{"name": "Yehuda", "url": "http://yehuda"}, {"name": "Alan", "url": "http://alan"}}},
|
||||
nil, nil,
|
||||
map[string]string{"dude": "{{name}}\n {{> url}}", "url": "{{url}}!\n"},
|
||||
"Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n",
|
||||
},
|
||||
|
||||
// // @todo preventIndent option
|
||||
// {
|
||||
// "standalone partials (3) - prevent nested indented partials",
|
||||
// "Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}",
|
||||
// map[string]interface{}{"dudes": []map[string]string{{"name": "Yehuda", "url": "http://yehuda"}, {"name": "Alan", "url": "http://alan"}}},
|
||||
// nil, nil,
|
||||
// map[string]string{"dude": "{{name}}\n {{> url}}", "url": "{{url}}!\n"},
|
||||
// "Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n",
|
||||
// },
|
||||
|
||||
// @todo "compat mode"
|
||||
}
|
||||
|
||||
func TestPartials(t *testing.T) {
|
||||
launchTests(t, partialsTests)
|
||||
}
|
209
vendor/github.com/aymerick/raymond/handlebars/subexpressions_test.go
generated
vendored
Normal file
209
vendor/github.com/aymerick/raymond/handlebars/subexpressions_test.go
generated
vendored
Normal file
@ -0,0 +1,209 @@
|
||||
package handlebars
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/aymerick/raymond"
|
||||
)
|
||||
|
||||
//
|
||||
// Those tests come from:
|
||||
// https://github.com/wycats/handlebars.js/blob/master/spec/subexpression.js
|
||||
//
|
||||
var subexpressionsTests = []Test{
|
||||
{
|
||||
"arg-less helper",
|
||||
"{{foo (bar)}}!",
|
||||
map[string]interface{}{},
|
||||
nil,
|
||||
map[string]interface{}{
|
||||
"foo": func(val string) string {
|
||||
return val + val
|
||||
},
|
||||
"bar": func() string {
|
||||
return "LOL"
|
||||
},
|
||||
},
|
||||
nil,
|
||||
"LOLLOL!",
|
||||
},
|
||||
{
|
||||
"helper w args",
|
||||
"{{blog (equal a b)}}",
|
||||
map[string]interface{}{"bar": "LOL"},
|
||||
nil,
|
||||
map[string]interface{}{
|
||||
"blog": blogHelper,
|
||||
"equal": equalHelper,
|
||||
},
|
||||
nil,
|
||||
"val is true",
|
||||
},
|
||||
{
|
||||
"mixed paths and helpers",
|
||||
"{{blog baz.bat (equal a b) baz.bar}}",
|
||||
map[string]interface{}{"bar": "LOL", "baz": map[string]string{"bat": "foo!", "bar": "bar!"}},
|
||||
nil,
|
||||
map[string]interface{}{
|
||||
"blog": func(p, p2, p3 string) string {
|
||||
return "val is " + p + ", " + p2 + " and " + p3
|
||||
},
|
||||
"equal": equalHelper,
|
||||
},
|
||||
nil,
|
||||
"val is foo!, true and bar!",
|
||||
},
|
||||
{
|
||||
"supports much nesting",
|
||||
"{{blog (equal (equal true true) true)}}",
|
||||
map[string]interface{}{"bar": "LOL"},
|
||||
nil,
|
||||
map[string]interface{}{
|
||||
"blog": blogHelper,
|
||||
"equal": equalHelper,
|
||||
},
|
||||
nil,
|
||||
"val is true",
|
||||
},
|
||||
|
||||
{
|
||||
"GH-800 : Complex subexpressions (1)",
|
||||
"{{dash 'abc' (concat a b)}}",
|
||||
map[string]interface{}{"a": "a", "b": "b", "c": map[string]string{"c": "c"}, "d": "d", "e": map[string]string{"e": "e"}},
|
||||
nil,
|
||||
map[string]interface{}{"dash": dashHelper, "concat": concatHelper},
|
||||
nil,
|
||||
"abc-ab",
|
||||
},
|
||||
{
|
||||
"GH-800 : Complex subexpressions (2)",
|
||||
"{{dash d (concat a b)}}",
|
||||
map[string]interface{}{"a": "a", "b": "b", "c": map[string]string{"c": "c"}, "d": "d", "e": map[string]string{"e": "e"}},
|
||||
nil,
|
||||
map[string]interface{}{"dash": dashHelper, "concat": concatHelper},
|
||||
nil,
|
||||
"d-ab",
|
||||
},
|
||||
{
|
||||
"GH-800 : Complex subexpressions (3)",
|
||||
"{{dash c.c (concat a b)}}",
|
||||
map[string]interface{}{"a": "a", "b": "b", "c": map[string]string{"c": "c"}, "d": "d", "e": map[string]string{"e": "e"}},
|
||||
nil,
|
||||
map[string]interface{}{"dash": dashHelper, "concat": concatHelper},
|
||||
nil,
|
||||
"c-ab",
|
||||
},
|
||||
{
|
||||
"GH-800 : Complex subexpressions (4)",
|
||||
"{{dash (concat a b) c.c}}",
|
||||
map[string]interface{}{"a": "a", "b": "b", "c": map[string]string{"c": "c"}, "d": "d", "e": map[string]string{"e": "e"}},
|
||||
nil,
|
||||
map[string]interface{}{"dash": dashHelper, "concat": concatHelper},
|
||||
nil,
|
||||
"ab-c",
|
||||
},
|
||||
{
|
||||
"GH-800 : Complex subexpressions (5)",
|
||||
"{{dash (concat a e.e) c.c}}",
|
||||
map[string]interface{}{"a": "a", "b": "b", "c": map[string]string{"c": "c"}, "d": "d", "e": map[string]string{"e": "e"}},
|
||||
nil,
|
||||
map[string]interface{}{"dash": dashHelper, "concat": concatHelper},
|
||||
nil,
|
||||
"ae-c",
|
||||
},
|
||||
|
||||
{
|
||||
// note: test not relevant
|
||||
"provides each nested helper invocation its own options hash",
|
||||
"{{equal (equal true true) true}}",
|
||||
map[string]interface{}{},
|
||||
nil,
|
||||
map[string]interface{}{
|
||||
"equal": equalHelper,
|
||||
},
|
||||
nil,
|
||||
"true",
|
||||
},
|
||||
{
|
||||
"with hashes",
|
||||
"{{blog (equal (equal true true) true fun='yes')}}",
|
||||
map[string]interface{}{"bar": "LOL"},
|
||||
nil,
|
||||
map[string]interface{}{
|
||||
"blog": blogHelper,
|
||||
"equal": equalHelper,
|
||||
},
|
||||
nil,
|
||||
"val is true",
|
||||
},
|
||||
{
|
||||
"as hashes",
|
||||
"{{blog fun=(equal (blog fun=1) 'val is 1')}}",
|
||||
map[string]interface{}{},
|
||||
nil,
|
||||
map[string]interface{}{
|
||||
"blog": func(options *raymond.Options) string {
|
||||
return "val is " + options.HashStr("fun")
|
||||
},
|
||||
"equal": equalHelper,
|
||||
},
|
||||
nil,
|
||||
"val is true",
|
||||
},
|
||||
{
|
||||
"multiple subexpressions in a hash",
|
||||
`{{input aria-label=(t "Name") placeholder=(t "Example User")}}`,
|
||||
map[string]interface{}{},
|
||||
nil,
|
||||
map[string]interface{}{
|
||||
"input": func(options *raymond.Options) raymond.SafeString {
|
||||
return raymond.SafeString(`<input aria-label="` + options.HashStr("aria-label") + `" placeholder="` + options.HashStr("placeholder") + `" />`)
|
||||
},
|
||||
"t": func(param string) raymond.SafeString {
|
||||
return raymond.SafeString(param)
|
||||
},
|
||||
},
|
||||
nil,
|
||||
`<input aria-label="Name" placeholder="Example User" />`,
|
||||
},
|
||||
{
|
||||
"multiple subexpressions in a hash with context",
|
||||
`{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}`,
|
||||
map[string]map[string]string{"item": {"field": "Name", "placeholder": "Example User"}},
|
||||
nil,
|
||||
map[string]interface{}{
|
||||
"input": func(options *raymond.Options) raymond.SafeString {
|
||||
return raymond.SafeString(`<input aria-label="` + options.HashStr("aria-label") + `" placeholder="` + options.HashStr("placeholder") + `" />`)
|
||||
},
|
||||
"t": func(param string) raymond.SafeString {
|
||||
return raymond.SafeString(param)
|
||||
},
|
||||
},
|
||||
nil,
|
||||
`<input aria-label="Name" placeholder="Example User" />`,
|
||||
},
|
||||
|
||||
// @todo "in string params mode"
|
||||
|
||||
// @todo "as hashes in string params mode"
|
||||
|
||||
{
|
||||
"subexpression functions on the context",
|
||||
"{{foo (bar)}}!",
|
||||
map[string]interface{}{"bar": func() string { return "LOL" }},
|
||||
nil,
|
||||
map[string]interface{}{
|
||||
"foo": func(val string) string {
|
||||
return val + val
|
||||
},
|
||||
},
|
||||
nil,
|
||||
"LOLLOL!",
|
||||
},
|
||||
|
||||
// @todo "subexpressions can't just be property lookups" should raise error
|
||||
}
|
||||
|
||||
func TestSubexpressions(t *testing.T) {
|
||||
launchTests(t, subexpressionsTests)
|
||||
}
|
259
vendor/github.com/aymerick/raymond/handlebars/whitespace_test.go
generated
vendored
Normal file
259
vendor/github.com/aymerick/raymond/handlebars/whitespace_test.go
generated
vendored
Normal file
@ -0,0 +1,259 @@
|
||||
package handlebars
|
||||
|
||||
import "testing"
|
||||
|
||||
//
|
||||
// Those tests come from:
|
||||
// https://github.com/wycats/handlebars.js/blob/master/spec/whitespace-control.js
|
||||
//
|
||||
var whitespaceControlTests = []Test{
|
||||
{
|
||||
"should strip whitespace around mustache calls (1)",
|
||||
" {{~foo~}} ",
|
||||
map[string]string{"foo": "bar<"},
|
||||
nil, nil, nil,
|
||||
"bar<",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around mustache calls (2)",
|
||||
" {{~foo}} ",
|
||||
map[string]string{"foo": "bar<"},
|
||||
nil, nil, nil,
|
||||
"bar< ",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around mustache calls (3)",
|
||||
" {{foo~}} ",
|
||||
map[string]string{"foo": "bar<"},
|
||||
nil, nil, nil,
|
||||
" bar<",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around mustache calls (4)",
|
||||
" {{~&foo~}} ",
|
||||
map[string]string{"foo": "bar<"},
|
||||
nil, nil, nil,
|
||||
"bar<",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around mustache calls (5)",
|
||||
" {{~{foo}~}} ",
|
||||
map[string]string{"foo": "bar<"},
|
||||
nil, nil, nil,
|
||||
"bar<",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around mustache calls (6)",
|
||||
"1\n{{foo~}} \n\n 23\n{{bar}}4",
|
||||
nil, nil, nil, nil,
|
||||
"1\n23\n4",
|
||||
},
|
||||
|
||||
{
|
||||
"blocks - should strip whitespace around simple block calls (1)",
|
||||
" {{~#if foo~}} bar {{~/if~}} ",
|
||||
map[string]string{"foo": "bar<"},
|
||||
nil, nil, nil,
|
||||
"bar",
|
||||
},
|
||||
{
|
||||
"blocks - should strip whitespace around simple block calls (2)",
|
||||
" {{#if foo~}} bar {{/if~}} ",
|
||||
map[string]string{"foo": "bar<"},
|
||||
nil, nil, nil,
|
||||
" bar ",
|
||||
},
|
||||
{
|
||||
"blocks - should strip whitespace around simple block calls (3)",
|
||||
" {{~#if foo}} bar {{~/if}} ",
|
||||
map[string]string{"foo": "bar<"},
|
||||
nil, nil, nil,
|
||||
" bar ",
|
||||
},
|
||||
{
|
||||
"blocks - should strip whitespace around simple block calls (4)",
|
||||
" {{#if foo}} bar {{/if}} ",
|
||||
map[string]string{"foo": "bar<"},
|
||||
nil, nil, nil,
|
||||
" bar ",
|
||||
},
|
||||
{
|
||||
"blocks - should strip whitespace around simple block calls (5)",
|
||||
" \n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\n ",
|
||||
map[string]string{"foo": "bar<"},
|
||||
nil, nil, nil,
|
||||
"bar",
|
||||
},
|
||||
{
|
||||
"blocks - should strip whitespace around simple block calls (6)",
|
||||
" a\n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\na ",
|
||||
map[string]string{"foo": "bar<"},
|
||||
nil, nil, nil,
|
||||
" abara ",
|
||||
},
|
||||
|
||||
{
|
||||
"should strip whitespace around inverse block calls (1)",
|
||||
" {{~^if foo~}} bar {{~/if~}} ",
|
||||
nil, nil, nil, nil,
|
||||
"bar",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around inverse block calls (2)",
|
||||
" {{^if foo~}} bar {{/if~}} ",
|
||||
nil, nil, nil, nil,
|
||||
" bar ",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around inverse block calls (3)",
|
||||
" {{~^if foo}} bar {{~/if}} ",
|
||||
nil, nil, nil, nil,
|
||||
" bar ",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around inverse block calls (4)",
|
||||
" {{^if foo}} bar {{/if}} ",
|
||||
nil, nil, nil, nil,
|
||||
" bar ",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around inverse block calls (5)",
|
||||
" \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ",
|
||||
nil, nil, nil, nil,
|
||||
"bar",
|
||||
},
|
||||
|
||||
{
|
||||
"should strip whitespace around complex block calls (1)",
|
||||
"{{#if foo~}} bar {{~^~}} baz {{~/if}}",
|
||||
map[string]string{"foo": "bar<"},
|
||||
nil, nil, nil,
|
||||
"bar",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around complex block calls (2)",
|
||||
"{{#if foo~}} bar {{^~}} baz {{/if}}",
|
||||
map[string]string{"foo": "bar<"},
|
||||
nil, nil, nil,
|
||||
"bar ",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around complex block calls (3)",
|
||||
"{{#if foo}} bar {{~^~}} baz {{~/if}}",
|
||||
map[string]string{"foo": "bar<"},
|
||||
nil, nil, nil,
|
||||
" bar",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around complex block calls (4)",
|
||||
"{{#if foo}} bar {{^~}} baz {{/if}}",
|
||||
map[string]string{"foo": "bar<"},
|
||||
nil, nil, nil,
|
||||
" bar ",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around complex block calls (5)",
|
||||
"{{#if foo~}} bar {{~else~}} baz {{~/if}}",
|
||||
map[string]string{"foo": "bar<"},
|
||||
nil, nil, nil,
|
||||
"bar",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around complex block calls (6)",
|
||||
"\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n",
|
||||
map[string]string{"foo": "bar<"},
|
||||
nil, nil, nil,
|
||||
"bar",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around complex block calls (7)",
|
||||
"\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n",
|
||||
map[string]string{"foo": "bar<"},
|
||||
nil, nil, nil,
|
||||
"bar<",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around complex block calls (8)",
|
||||
"{{#if foo~}} bar {{~^~}} baz {{~/if}}",
|
||||
nil, nil, nil, nil,
|
||||
"baz",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around complex block calls (9)",
|
||||
"{{#if foo}} bar {{~^~}} baz {{/if}}",
|
||||
nil, nil, nil, nil,
|
||||
"baz ",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around complex block calls (10)",
|
||||
"{{#if foo~}} bar {{~^}} baz {{~/if}}",
|
||||
nil, nil, nil, nil,
|
||||
" baz",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around complex block calls (11)",
|
||||
"{{#if foo~}} bar {{~^}} baz {{/if}}",
|
||||
nil, nil, nil, nil,
|
||||
" baz ",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around complex block calls (12)",
|
||||
"{{#if foo~}} bar {{~else~}} baz {{~/if}}",
|
||||
nil, nil, nil, nil,
|
||||
"baz",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around complex block calls (13)",
|
||||
"\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n",
|
||||
nil, nil, nil, nil,
|
||||
"baz",
|
||||
},
|
||||
|
||||
{
|
||||
"should strip whitespace around partials (1)",
|
||||
"foo {{~> dude~}} ",
|
||||
nil, nil, nil,
|
||||
map[string]string{"dude": "bar"},
|
||||
"foobar",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around partials (2)",
|
||||
"foo {{> dude~}} ",
|
||||
nil, nil, nil,
|
||||
map[string]string{"dude": "bar"},
|
||||
"foo bar",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around partials (3)",
|
||||
"foo {{> dude}} ",
|
||||
nil, nil, nil,
|
||||
map[string]string{"dude": "bar"},
|
||||
"foo bar ",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around partials (4)",
|
||||
"foo\n {{~> dude}} ",
|
||||
nil, nil, nil,
|
||||
map[string]string{"dude": "bar"},
|
||||
"foobar",
|
||||
},
|
||||
{
|
||||
"should strip whitespace around partials (5)",
|
||||
"foo\n {{> dude}} ",
|
||||
nil, nil, nil,
|
||||
map[string]string{"dude": "bar"},
|
||||
"foo\n bar",
|
||||
},
|
||||
|
||||
{
|
||||
"should only strip whitespace once",
|
||||
" {{~foo~}} {{foo}} {{foo}} ",
|
||||
map[string]string{"foo": "bar"},
|
||||
nil, nil, nil,
|
||||
"barbar bar ",
|
||||
},
|
||||
}
|
||||
|
||||
func TestWhitespaceControl(t *testing.T) {
|
||||
launchTests(t, whitespaceControlTests)
|
||||
}
|
371
vendor/github.com/aymerick/raymond/helper.go
generated
vendored
Normal file
371
vendor/github.com/aymerick/raymond/helper.go
generated
vendored
Normal file
@ -0,0 +1,371 @@
|
||||
package raymond
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Options represents the options argument provided to helpers and context functions.
|
||||
type Options struct {
|
||||
// evaluation visitor
|
||||
eval *evalVisitor
|
||||
|
||||
// params
|
||||
params []interface{}
|
||||
hash map[string]interface{}
|
||||
}
|
||||
|
||||
// helpers stores all globally registered helpers
|
||||
var helpers = make(map[string]reflect.Value)
|
||||
|
||||
// protects global helpers
|
||||
var helpersMutex sync.RWMutex
|
||||
|
||||
func init() {
|
||||
// register builtin helpers
|
||||
RegisterHelper("if", ifHelper)
|
||||
RegisterHelper("unless", unlessHelper)
|
||||
RegisterHelper("with", withHelper)
|
||||
RegisterHelper("each", eachHelper)
|
||||
RegisterHelper("log", logHelper)
|
||||
RegisterHelper("lookup", lookupHelper)
|
||||
}
|
||||
|
||||
// RegisterHelper registers a global helper. That helper will be available to all templates.
|
||||
func RegisterHelper(name string, helper interface{}) {
|
||||
helpersMutex.Lock()
|
||||
defer helpersMutex.Unlock()
|
||||
|
||||
if helpers[name] != zero {
|
||||
panic(fmt.Errorf("Helper already registered: %s", name))
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(helper)
|
||||
ensureValidHelper(name, val)
|
||||
|
||||
helpers[name] = val
|
||||
}
|
||||
|
||||
// RegisterHelpers registers several global helpers. Those helpers will be available to all templates.
|
||||
func RegisterHelpers(helpers map[string]interface{}) {
|
||||
for name, helper := range helpers {
|
||||
RegisterHelper(name, helper)
|
||||
}
|
||||
}
|
||||
|
||||
// ensureValidHelper panics if given helper is not valid
|
||||
func ensureValidHelper(name string, funcValue reflect.Value) {
|
||||
if funcValue.Kind() != reflect.Func {
|
||||
panic(fmt.Errorf("Helper must be a function: %s", name))
|
||||
}
|
||||
|
||||
funcType := funcValue.Type()
|
||||
|
||||
if funcType.NumOut() != 1 {
|
||||
panic(fmt.Errorf("Helper function must return a string or a SafeString: %s", name))
|
||||
}
|
||||
|
||||
// @todo Check if first returned value is a string, SafeString or interface{} ?
|
||||
}
|
||||
|
||||
// findHelper finds a globally registered helper
|
||||
func findHelper(name string) reflect.Value {
|
||||
helpersMutex.RLock()
|
||||
defer helpersMutex.RUnlock()
|
||||
|
||||
return helpers[name]
|
||||
}
|
||||
|
||||
// newOptions instanciates a new Options
|
||||
func newOptions(eval *evalVisitor, params []interface{}, hash map[string]interface{}) *Options {
|
||||
return &Options{
|
||||
eval: eval,
|
||||
params: params,
|
||||
hash: hash,
|
||||
}
|
||||
}
|
||||
|
||||
// newEmptyOptions instanciates a new empty Options
|
||||
func newEmptyOptions(eval *evalVisitor) *Options {
|
||||
return &Options{
|
||||
eval: eval,
|
||||
hash: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Context Values
|
||||
//
|
||||
|
||||
// Value returns field value from current context.
|
||||
func (options *Options) Value(name string) interface{} {
|
||||
value := options.eval.evalField(options.eval.curCtx(), name, false)
|
||||
if !value.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return value.Interface()
|
||||
}
|
||||
|
||||
// ValueStr returns string representation of field value from current context.
|
||||
func (options *Options) ValueStr(name string) string {
|
||||
return Str(options.Value(name))
|
||||
}
|
||||
|
||||
// Ctx returns current evaluation context.
|
||||
func (options *Options) Ctx() interface{} {
|
||||
return options.eval.curCtx().Interface()
|
||||
}
|
||||
|
||||
//
|
||||
// Hash Arguments
|
||||
//
|
||||
|
||||
// HashProp returns hash property.
|
||||
func (options *Options) HashProp(name string) interface{} {
|
||||
return options.hash[name]
|
||||
}
|
||||
|
||||
// HashStr returns string representation of hash property.
|
||||
func (options *Options) HashStr(name string) string {
|
||||
return Str(options.hash[name])
|
||||
}
|
||||
|
||||
// Hash returns entire hash.
|
||||
func (options *Options) Hash() map[string]interface{} {
|
||||
return options.hash
|
||||
}
|
||||
|
||||
//
|
||||
// Parameters
|
||||
//
|
||||
|
||||
// Param returns parameter at given position.
|
||||
func (options *Options) Param(pos int) interface{} {
|
||||
if len(options.params) > pos {
|
||||
return options.params[pos]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParamStr returns string representation of parameter at given position.
|
||||
func (options *Options) ParamStr(pos int) string {
|
||||
return Str(options.Param(pos))
|
||||
}
|
||||
|
||||
// Params returns all parameters.
|
||||
func (options *Options) Params() []interface{} {
|
||||
return options.params
|
||||
}
|
||||
|
||||
//
|
||||
// Private data
|
||||
//
|
||||
|
||||
// Data returns private data value.
|
||||
func (options *Options) Data(name string) interface{} {
|
||||
return options.eval.dataFrame.Get(name)
|
||||
}
|
||||
|
||||
// DataStr returns string representation of private data value.
|
||||
func (options *Options) DataStr(name string) string {
|
||||
return Str(options.eval.dataFrame.Get(name))
|
||||
}
|
||||
|
||||
// DataFrame returns current private data frame.
|
||||
func (options *Options) DataFrame() *DataFrame {
|
||||
return options.eval.dataFrame
|
||||
}
|
||||
|
||||
// NewDataFrame instanciates a new data frame that is a copy of current evaluation data frame.
|
||||
//
|
||||
// Parent of returned data frame is set to current evaluation data frame.
|
||||
func (options *Options) NewDataFrame() *DataFrame {
|
||||
return options.eval.dataFrame.Copy()
|
||||
}
|
||||
|
||||
// newIterDataFrame instanciates a new data frame and set iteration specific vars
|
||||
func (options *Options) newIterDataFrame(length int, i int, key interface{}) *DataFrame {
|
||||
return options.eval.dataFrame.newIterDataFrame(length, i, key)
|
||||
}
|
||||
|
||||
//
|
||||
// Evaluation
|
||||
//
|
||||
|
||||
// evalBlock evaluates block with given context, private data and iteration key
|
||||
func (options *Options) evalBlock(ctx interface{}, data *DataFrame, key interface{}) string {
|
||||
result := ""
|
||||
|
||||
if block := options.eval.curBlock(); (block != nil) && (block.Program != nil) {
|
||||
result = options.eval.evalProgram(block.Program, ctx, data, key)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Fn evaluates block with current evaluation context.
|
||||
func (options *Options) Fn() string {
|
||||
return options.evalBlock(nil, nil, nil)
|
||||
}
|
||||
|
||||
// FnCtxData evaluates block with given context and private data frame.
|
||||
func (options *Options) FnCtxData(ctx interface{}, data *DataFrame) string {
|
||||
return options.evalBlock(ctx, data, nil)
|
||||
}
|
||||
|
||||
// FnWith evaluates block with given context.
|
||||
func (options *Options) FnWith(ctx interface{}) string {
|
||||
return options.evalBlock(ctx, nil, nil)
|
||||
}
|
||||
|
||||
// FnData evaluates block with given private data frame.
|
||||
func (options *Options) FnData(data *DataFrame) string {
|
||||
return options.evalBlock(nil, data, nil)
|
||||
}
|
||||
|
||||
// Inverse evaluates "else block".
|
||||
func (options *Options) Inverse() string {
|
||||
result := ""
|
||||
if block := options.eval.curBlock(); (block != nil) && (block.Inverse != nil) {
|
||||
result, _ = block.Inverse.Accept(options.eval).(string)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Eval evaluates field for given context.
|
||||
func (options *Options) Eval(ctx interface{}, field string) interface{} {
|
||||
if ctx == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if field == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
val := options.eval.evalField(reflect.ValueOf(ctx), field, false)
|
||||
if !val.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return val.Interface()
|
||||
}
|
||||
|
||||
//
|
||||
// Misc
|
||||
//
|
||||
|
||||
// isIncludableZero returns true if 'includeZero' option is set and first param is the number 0
|
||||
func (options *Options) isIncludableZero() bool {
|
||||
b, ok := options.HashProp("includeZero").(bool)
|
||||
if ok && b {
|
||||
nb, ok := options.Param(0).(int)
|
||||
if ok && nb == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
//
|
||||
// Builtin helpers
|
||||
//
|
||||
|
||||
// #if block helper
|
||||
func ifHelper(conditional interface{}, options *Options) interface{} {
|
||||
if options.isIncludableZero() || IsTrue(conditional) {
|
||||
return options.Fn()
|
||||
}
|
||||
|
||||
return options.Inverse()
|
||||
}
|
||||
|
||||
// #unless block helper
|
||||
func unlessHelper(conditional interface{}, options *Options) interface{} {
|
||||
if options.isIncludableZero() || IsTrue(conditional) {
|
||||
return options.Inverse()
|
||||
}
|
||||
|
||||
return options.Fn()
|
||||
}
|
||||
|
||||
// #with block helper
|
||||
func withHelper(context interface{}, options *Options) interface{} {
|
||||
if IsTrue(context) {
|
||||
return options.FnWith(context)
|
||||
}
|
||||
|
||||
return options.Inverse()
|
||||
}
|
||||
|
||||
// #each block helper
|
||||
func eachHelper(context interface{}, options *Options) interface{} {
|
||||
if !IsTrue(context) {
|
||||
return options.Inverse()
|
||||
}
|
||||
|
||||
result := ""
|
||||
|
||||
val := reflect.ValueOf(context)
|
||||
switch val.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
// computes private data
|
||||
data := options.newIterDataFrame(val.Len(), i, nil)
|
||||
|
||||
// evaluates block
|
||||
result += options.evalBlock(val.Index(i).Interface(), data, i)
|
||||
}
|
||||
case reflect.Map:
|
||||
// note: a go hash is not ordered, so result may vary, this behaviour differs from the JS implementation
|
||||
keys := val.MapKeys()
|
||||
for i := 0; i < len(keys); i++ {
|
||||
key := keys[i].Interface()
|
||||
ctx := val.MapIndex(keys[i]).Interface()
|
||||
|
||||
// computes private data
|
||||
data := options.newIterDataFrame(len(keys), i, key)
|
||||
|
||||
// evaluates block
|
||||
result += options.evalBlock(ctx, data, key)
|
||||
}
|
||||
case reflect.Struct:
|
||||
var exportedFields []int
|
||||
|
||||
// collect exported fields only
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
if tField := val.Type().Field(i); tField.PkgPath == "" {
|
||||
exportedFields = append(exportedFields, i)
|
||||
}
|
||||
}
|
||||
|
||||
for i, fieldIndex := range exportedFields {
|
||||
key := val.Type().Field(fieldIndex).Name
|
||||
ctx := val.Field(fieldIndex).Interface()
|
||||
|
||||
// computes private data
|
||||
data := options.newIterDataFrame(len(exportedFields), i, key)
|
||||
|
||||
// evaluates block
|
||||
result += options.evalBlock(ctx, data, key)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// #log helper
|
||||
func logHelper(message string) interface{} {
|
||||
log.Print(message)
|
||||
return ""
|
||||
}
|
||||
|
||||
// #lookup helper
|
||||
func lookupHelper(obj interface{}, field string, options *Options) interface{} {
|
||||
return Str(options.Eval(obj, field))
|
||||
}
|
193
vendor/github.com/aymerick/raymond/helper_test.go
generated
vendored
Normal file
193
vendor/github.com/aymerick/raymond/helper_test.go
generated
vendored
Normal file
@ -0,0 +1,193 @@
|
||||
package raymond
|
||||
|
||||
import "testing"
|
||||
|
||||
const (
|
||||
VERBOSE = false
|
||||
)
|
||||
|
||||
//
|
||||
// Helpers
|
||||
//
|
||||
|
||||
func barHelper(options *Options) string { return "bar" }
|
||||
|
||||
func echoHelper(str string, nb int) string {
|
||||
result := ""
|
||||
for i := 0; i < nb; i++ {
|
||||
result += str
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func boolHelper(b bool) string {
|
||||
if b {
|
||||
return "yes it is"
|
||||
}
|
||||
|
||||
return "absolutely not"
|
||||
}
|
||||
|
||||
func gnakHelper(nb int) string {
|
||||
result := ""
|
||||
for i := 0; i < nb; i++ {
|
||||
result += "GnAK!"
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
//
|
||||
// Tests
|
||||
//
|
||||
|
||||
var helperTests = []Test{
|
||||
{
|
||||
"simple helper",
|
||||
`{{foo}}`,
|
||||
nil, nil,
|
||||
map[string]interface{}{"foo": barHelper},
|
||||
nil,
|
||||
`bar`,
|
||||
},
|
||||
{
|
||||
"helper with literal string param",
|
||||
`{{echo "foo" 1}}`,
|
||||
nil, nil,
|
||||
map[string]interface{}{"echo": echoHelper},
|
||||
nil,
|
||||
`foo`,
|
||||
},
|
||||
{
|
||||
"helper with identifier param",
|
||||
`{{echo foo 1}}`,
|
||||
map[string]interface{}{"foo": "bar"},
|
||||
nil,
|
||||
map[string]interface{}{"echo": echoHelper},
|
||||
nil,
|
||||
`bar`,
|
||||
},
|
||||
{
|
||||
"helper with literal boolean param",
|
||||
`{{bool true}}`,
|
||||
nil, nil,
|
||||
map[string]interface{}{"bool": boolHelper},
|
||||
nil,
|
||||
`yes it is`,
|
||||
},
|
||||
{
|
||||
"helper with literal boolean param",
|
||||
`{{bool false}}`,
|
||||
nil, nil,
|
||||
map[string]interface{}{"bool": boolHelper},
|
||||
nil,
|
||||
`absolutely not`,
|
||||
},
|
||||
{
|
||||
"helper with literal boolean param",
|
||||
`{{gnak 5}}`,
|
||||
nil, nil,
|
||||
map[string]interface{}{"gnak": gnakHelper},
|
||||
nil,
|
||||
`GnAK!GnAK!GnAK!GnAK!GnAK!`,
|
||||
},
|
||||
{
|
||||
"helper with several parameters",
|
||||
`{{echo "GnAK!" 3}}`,
|
||||
nil, nil,
|
||||
map[string]interface{}{"echo": echoHelper},
|
||||
nil,
|
||||
`GnAK!GnAK!GnAK!`,
|
||||
},
|
||||
{
|
||||
"#if helper with true literal",
|
||||
`{{#if true}}YES MAN{{/if}}`,
|
||||
nil, nil, nil, nil,
|
||||
`YES MAN`,
|
||||
},
|
||||
{
|
||||
"#if helper with false literal",
|
||||
`{{#if false}}YES MAN{{/if}}`,
|
||||
nil, nil, nil, nil,
|
||||
``,
|
||||
},
|
||||
{
|
||||
"#if helper with truthy identifier",
|
||||
`{{#if ok}}YES MAN{{/if}}`,
|
||||
map[string]interface{}{"ok": true},
|
||||
nil, nil, nil,
|
||||
`YES MAN`,
|
||||
},
|
||||
{
|
||||
"#if helper with falsy identifier",
|
||||
`{{#if ok}}YES MAN{{/if}}`,
|
||||
map[string]interface{}{"ok": false},
|
||||
nil, nil, nil,
|
||||
``,
|
||||
},
|
||||
{
|
||||
"#unless helper with true literal",
|
||||
`{{#unless true}}YES MAN{{/unless}}`,
|
||||
nil, nil, nil, nil,
|
||||
``,
|
||||
},
|
||||
{
|
||||
"#unless helper with false literal",
|
||||
`{{#unless false}}YES MAN{{/unless}}`,
|
||||
nil, nil, nil, nil,
|
||||
`YES MAN`,
|
||||
},
|
||||
{
|
||||
"#unless helper with truthy identifier",
|
||||
`{{#unless ok}}YES MAN{{/unless}}`,
|
||||
map[string]interface{}{"ok": true},
|
||||
nil, nil, nil,
|
||||
``,
|
||||
},
|
||||
{
|
||||
"#unless helper with falsy identifier",
|
||||
`{{#unless ok}}YES MAN{{/unless}}`,
|
||||
map[string]interface{}{"ok": false},
|
||||
nil, nil, nil,
|
||||
`YES MAN`,
|
||||
},
|
||||
}
|
||||
|
||||
//
|
||||
// Let's go
|
||||
//
|
||||
|
||||
func TestHelper(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
launchTests(t, helperTests)
|
||||
}
|
||||
|
||||
//
|
||||
// Fixes: https://github.com/aymerick/raymond/issues/2
|
||||
//
|
||||
|
||||
type Author struct {
|
||||
FirstName string
|
||||
LastName string
|
||||
}
|
||||
|
||||
func TestHelperCtx(t *testing.T) {
|
||||
RegisterHelper("template", func(name string, options *Options) SafeString {
|
||||
context := options.Ctx()
|
||||
|
||||
template := name + " - {{ firstName }} {{ lastName }}"
|
||||
result, _ := Render(template, context)
|
||||
|
||||
return SafeString(result)
|
||||
})
|
||||
|
||||
template := `By {{ template "namefile" }}`
|
||||
context := Author{"Alan", "Johnson"}
|
||||
|
||||
result, _ := Render(template, context)
|
||||
if result != "By namefile - Alan Johnson" {
|
||||
t.Errorf("Failed to render template in helper: %q", result)
|
||||
}
|
||||
}
|
639
vendor/github.com/aymerick/raymond/lexer/lexer.go
generated
vendored
Normal file
639
vendor/github.com/aymerick/raymond/lexer/lexer.go
generated
vendored
Normal file
@ -0,0 +1,639 @@
|
||||
// Package lexer provides a handlebars tokenizer.
|
||||
package lexer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// References:
|
||||
// - https://github.com/wycats/handlebars.js/blob/master/src/handlebars.l
|
||||
// - https://github.com/golang/go/blob/master/src/text/template/parse/lex.go
|
||||
|
||||
const (
|
||||
// Mustaches detection
|
||||
escapedEscapedOpenMustache = "\\\\{{"
|
||||
escapedOpenMustache = "\\{{"
|
||||
openMustache = "{{"
|
||||
closeMustache = "}}"
|
||||
closeStripMustache = "~}}"
|
||||
closeUnescapedStripMustache = "}~}}"
|
||||
)
|
||||
|
||||
const eof = -1
|
||||
|
||||
// lexFunc represents a function that returns the next lexer function.
|
||||
type lexFunc func(*Lexer) lexFunc
|
||||
|
||||
// Lexer is a lexical analyzer.
|
||||
type Lexer struct {
|
||||
input string // input to scan
|
||||
name string // lexer name, used for testing purpose
|
||||
tokens chan Token // channel of scanned tokens
|
||||
nextFunc lexFunc // the next function to execute
|
||||
|
||||
pos int // current byte position in input string
|
||||
line int // current line position in input string
|
||||
width int // size of last rune scanned from input string
|
||||
start int // start position of the token we are scanning
|
||||
|
||||
// the shameful contextual properties needed because `nextFunc` is not enough
|
||||
closeComment *regexp.Regexp // regexp to scan close of current comment
|
||||
rawBlock bool // are we parsing a raw block content ?
|
||||
}
|
||||
|
||||
var (
|
||||
lookheadChars = `[\s` + regexp.QuoteMeta("=~}/)|") + `]`
|
||||
literalLookheadChars = `[\s` + regexp.QuoteMeta("~})") + `]`
|
||||
|
||||
// characters not allowed in an identifier
|
||||
unallowedIDChars = " \n\t!\"#%&'()*+,./;<=>@[\\]^`{|}~"
|
||||
|
||||
// regular expressions
|
||||
rID = regexp.MustCompile(`^[^` + regexp.QuoteMeta(unallowedIDChars) + `]+`)
|
||||
rDotID = regexp.MustCompile(`^\.` + lookheadChars)
|
||||
rTrue = regexp.MustCompile(`^true` + literalLookheadChars)
|
||||
rFalse = regexp.MustCompile(`^false` + literalLookheadChars)
|
||||
rOpenRaw = regexp.MustCompile(`^\{\{\{\{`)
|
||||
rCloseRaw = regexp.MustCompile(`^\}\}\}\}`)
|
||||
rOpenEndRaw = regexp.MustCompile(`^\{\{\{\{/`)
|
||||
rOpenEndRawLookAhead = regexp.MustCompile(`\{\{\{\{/`)
|
||||
rOpenUnescaped = regexp.MustCompile(`^\{\{~?\{`)
|
||||
rCloseUnescaped = regexp.MustCompile(`^\}~?\}\}`)
|
||||
rOpenBlock = regexp.MustCompile(`^\{\{~?#`)
|
||||
rOpenEndBlock = regexp.MustCompile(`^\{\{~?/`)
|
||||
rOpenPartial = regexp.MustCompile(`^\{\{~?>`)
|
||||
// {{^}} or {{else}}
|
||||
rInverse = regexp.MustCompile(`^(\{\{~?\^\s*~?\}\}|\{\{~?\s*else\s*~?\}\})`)
|
||||
rOpenInverse = regexp.MustCompile(`^\{\{~?\^`)
|
||||
rOpenInverseChain = regexp.MustCompile(`^\{\{~?\s*else`)
|
||||
// {{ or {{&
|
||||
rOpen = regexp.MustCompile(`^\{\{~?&?`)
|
||||
rClose = regexp.MustCompile(`^~?\}\}`)
|
||||
rOpenBlockParams = regexp.MustCompile(`^as\s+\|`)
|
||||
// {{!-- ... --}}
|
||||
rOpenCommentDash = regexp.MustCompile(`^\{\{~?!--\s*`)
|
||||
rCloseCommentDash = regexp.MustCompile(`^\s*--~?\}\}`)
|
||||
// {{! ... }}
|
||||
rOpenComment = regexp.MustCompile(`^\{\{~?!\s*`)
|
||||
rCloseComment = regexp.MustCompile(`^\s*~?\}\}`)
|
||||
)
|
||||
|
||||
// Scan scans given input.
|
||||
//
|
||||
// Tokens can then be fetched sequentially thanks to NextToken() function on returned lexer.
|
||||
func Scan(input string) *Lexer {
|
||||
return scanWithName(input, "")
|
||||
}
|
||||
|
||||
// scanWithName scans given input, with a name used for testing
|
||||
//
|
||||
// Tokens can then be fetched sequentially thanks to NextToken() function on returned lexer.
|
||||
func scanWithName(input string, name string) *Lexer {
|
||||
result := &Lexer{
|
||||
input: input,
|
||||
name: name,
|
||||
tokens: make(chan Token),
|
||||
line: 1,
|
||||
}
|
||||
|
||||
go result.run()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Collect scans and collect all tokens.
|
||||
//
|
||||
// This should be used for debugging purpose only. You should use Scan() and lexer.NextToken() functions instead.
|
||||
func Collect(input string) []Token {
|
||||
var result []Token
|
||||
|
||||
l := Scan(input)
|
||||
for {
|
||||
token := l.NextToken()
|
||||
result = append(result, token)
|
||||
|
||||
if token.Kind == TokenEOF || token.Kind == TokenError {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// NextToken returns the next scanned token.
|
||||
func (l *Lexer) NextToken() Token {
|
||||
result := <-l.tokens
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// run starts lexical analysis
|
||||
func (l *Lexer) run() {
|
||||
for l.nextFunc = lexContent; l.nextFunc != nil; {
|
||||
l.nextFunc = l.nextFunc(l)
|
||||
}
|
||||
}
|
||||
|
||||
// next returns next character from input, or eof of there is nothing left to scan
|
||||
func (l *Lexer) next() rune {
|
||||
if l.pos >= len(l.input) {
|
||||
l.width = 0
|
||||
return eof
|
||||
}
|
||||
|
||||
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
|
||||
l.width = w
|
||||
l.pos += l.width
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (l *Lexer) produce(kind TokenKind, val string) {
|
||||
l.tokens <- Token{kind, val, l.start, l.line}
|
||||
|
||||
// scanning a new token
|
||||
l.start = l.pos
|
||||
|
||||
// update line number
|
||||
l.line += strings.Count(val, "\n")
|
||||
}
|
||||
|
||||
// emit emits a new scanned token
|
||||
func (l *Lexer) emit(kind TokenKind) {
|
||||
l.produce(kind, l.input[l.start:l.pos])
|
||||
}
|
||||
|
||||
// emitContent emits scanned content
|
||||
func (l *Lexer) emitContent() {
|
||||
if l.pos > l.start {
|
||||
l.emit(TokenContent)
|
||||
}
|
||||
}
|
||||
|
||||
// emitString emits a scanned string
|
||||
func (l *Lexer) emitString(delimiter rune) {
|
||||
str := l.input[l.start:l.pos]
|
||||
|
||||
// replace escaped delimiters
|
||||
str = strings.Replace(str, "\\"+string(delimiter), string(delimiter), -1)
|
||||
|
||||
l.produce(TokenString, str)
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next character in the input
|
||||
func (l *Lexer) peek() rune {
|
||||
r := l.next()
|
||||
l.backup()
|
||||
return r
|
||||
}
|
||||
|
||||
// backup steps back one character
|
||||
//
|
||||
// WARNING: Can only be called once per call of next
|
||||
func (l *Lexer) backup() {
|
||||
l.pos -= l.width
|
||||
}
|
||||
|
||||
// ignoreskips all characters that have been scanned up to current position
|
||||
func (l *Lexer) ignore() {
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
// accept scans the next character if it is included in given string
|
||||
func (l *Lexer) accept(valid string) bool {
|
||||
if strings.IndexRune(valid, l.next()) >= 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
l.backup()
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// acceptRun scans all following characters that are part of given string
|
||||
func (l *Lexer) acceptRun(valid string) {
|
||||
for strings.IndexRune(valid, l.next()) >= 0 {
|
||||
}
|
||||
|
||||
l.backup()
|
||||
}
|
||||
|
||||
// errorf emits an error token
|
||||
func (l *Lexer) errorf(format string, args ...interface{}) lexFunc {
|
||||
l.tokens <- Token{TokenError, fmt.Sprintf(format, args...), l.start, l.line}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isString returns true if content at current scanning position starts with given string
|
||||
func (l *Lexer) isString(str string) bool {
|
||||
return strings.HasPrefix(l.input[l.pos:], str)
|
||||
}
|
||||
|
||||
// findRegexp returns the first string from current scanning position that matches given regular expression
|
||||
func (l *Lexer) findRegexp(r *regexp.Regexp) string {
|
||||
return r.FindString(l.input[l.pos:])
|
||||
}
|
||||
|
||||
// indexRegexp returns the index of the first string from current scanning position that matches given regular expression
|
||||
//
|
||||
// It returns -1 if not found
|
||||
func (l *Lexer) indexRegexp(r *regexp.Regexp) int {
|
||||
loc := r.FindStringIndex(l.input[l.pos:])
|
||||
if loc == nil {
|
||||
return -1
|
||||
}
|
||||
return loc[0]
|
||||
}
|
||||
|
||||
// lexContent scans content (ie: not between mustaches)
|
||||
func lexContent(l *Lexer) lexFunc {
|
||||
var next lexFunc
|
||||
|
||||
if l.rawBlock {
|
||||
if i := l.indexRegexp(rOpenEndRawLookAhead); i != -1 {
|
||||
// {{{{/
|
||||
l.rawBlock = false
|
||||
l.pos += i
|
||||
|
||||
next = lexOpenMustache
|
||||
} else {
|
||||
return l.errorf("Unclosed raw block")
|
||||
}
|
||||
} else if l.isString(escapedEscapedOpenMustache) {
|
||||
// \\{{
|
||||
|
||||
// emit content with only one escaped escape
|
||||
l.next()
|
||||
l.emitContent()
|
||||
|
||||
// ignore second escaped escape
|
||||
l.next()
|
||||
l.ignore()
|
||||
|
||||
next = lexContent
|
||||
} else if l.isString(escapedOpenMustache) {
|
||||
// \{{
|
||||
next = lexEscapedOpenMustache
|
||||
} else if str := l.findRegexp(rOpenCommentDash); str != "" {
|
||||
// {{!--
|
||||
l.closeComment = rCloseCommentDash
|
||||
|
||||
next = lexComment
|
||||
} else if str := l.findRegexp(rOpenComment); str != "" {
|
||||
// {{!
|
||||
l.closeComment = rCloseComment
|
||||
|
||||
next = lexComment
|
||||
} else if l.isString(openMustache) {
|
||||
// {{
|
||||
next = lexOpenMustache
|
||||
}
|
||||
|
||||
if next != nil {
|
||||
// emit scanned content
|
||||
l.emitContent()
|
||||
|
||||
// scan next token
|
||||
return next
|
||||
}
|
||||
|
||||
// scan next rune
|
||||
if l.next() == eof {
|
||||
// emit scanned content
|
||||
l.emitContent()
|
||||
|
||||
// this is over
|
||||
l.emit(TokenEOF)
|
||||
return nil
|
||||
}
|
||||
|
||||
// continue content scanning
|
||||
return lexContent
|
||||
}
|
||||
|
||||
// lexEscapedOpenMustache scans \{{
|
||||
func lexEscapedOpenMustache(l *Lexer) lexFunc {
|
||||
// ignore escape character
|
||||
l.next()
|
||||
l.ignore()
|
||||
|
||||
// scan mustaches
|
||||
for l.peek() == '{' {
|
||||
l.next()
|
||||
}
|
||||
|
||||
return lexContent
|
||||
}
|
||||
|
||||
// lexOpenMustache scans {{
|
||||
func lexOpenMustache(l *Lexer) lexFunc {
|
||||
var str string
|
||||
var tok TokenKind
|
||||
|
||||
nextFunc := lexExpression
|
||||
|
||||
if str = l.findRegexp(rOpenEndRaw); str != "" {
|
||||
tok = TokenOpenEndRawBlock
|
||||
} else if str = l.findRegexp(rOpenRaw); str != "" {
|
||||
tok = TokenOpenRawBlock
|
||||
l.rawBlock = true
|
||||
} else if str = l.findRegexp(rOpenUnescaped); str != "" {
|
||||
tok = TokenOpenUnescaped
|
||||
} else if str = l.findRegexp(rOpenBlock); str != "" {
|
||||
tok = TokenOpenBlock
|
||||
} else if str = l.findRegexp(rOpenEndBlock); str != "" {
|
||||
tok = TokenOpenEndBlock
|
||||
} else if str = l.findRegexp(rOpenPartial); str != "" {
|
||||
tok = TokenOpenPartial
|
||||
} else if str = l.findRegexp(rInverse); str != "" {
|
||||
tok = TokenInverse
|
||||
nextFunc = lexContent
|
||||
} else if str = l.findRegexp(rOpenInverse); str != "" {
|
||||
tok = TokenOpenInverse
|
||||
} else if str = l.findRegexp(rOpenInverseChain); str != "" {
|
||||
tok = TokenOpenInverseChain
|
||||
} else if str = l.findRegexp(rOpen); str != "" {
|
||||
tok = TokenOpen
|
||||
} else {
|
||||
// this is rotten
|
||||
panic("Current pos MUST be an opening mustache")
|
||||
}
|
||||
|
||||
l.pos += len(str)
|
||||
l.emit(tok)
|
||||
|
||||
return nextFunc
|
||||
}
|
||||
|
||||
// lexCloseMustache scans }} or ~}}
|
||||
func lexCloseMustache(l *Lexer) lexFunc {
|
||||
var str string
|
||||
var tok TokenKind
|
||||
|
||||
if str = l.findRegexp(rCloseRaw); str != "" {
|
||||
// }}}}
|
||||
tok = TokenCloseRawBlock
|
||||
} else if str = l.findRegexp(rCloseUnescaped); str != "" {
|
||||
// }}}
|
||||
tok = TokenCloseUnescaped
|
||||
} else if str = l.findRegexp(rClose); str != "" {
|
||||
// }}
|
||||
tok = TokenClose
|
||||
} else {
|
||||
// this is rotten
|
||||
panic("Current pos MUST be a closing mustache")
|
||||
}
|
||||
|
||||
l.pos += len(str)
|
||||
l.emit(tok)
|
||||
|
||||
return lexContent
|
||||
}
|
||||
|
||||
// lexExpression scans inside mustaches
|
||||
func lexExpression(l *Lexer) lexFunc {
|
||||
// search close mustache delimiter
|
||||
if l.isString(closeMustache) || l.isString(closeStripMustache) || l.isString(closeUnescapedStripMustache) {
|
||||
return lexCloseMustache
|
||||
}
|
||||
|
||||
// search some patterns before advancing scanning position
|
||||
|
||||
// "as |"
|
||||
if str := l.findRegexp(rOpenBlockParams); str != "" {
|
||||
l.pos += len(str)
|
||||
l.emit(TokenOpenBlockParams)
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// ..
|
||||
if l.isString("..") {
|
||||
l.pos += len("..")
|
||||
l.emit(TokenID)
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// .
|
||||
if str := l.findRegexp(rDotID); str != "" {
|
||||
l.pos += len(".")
|
||||
l.emit(TokenID)
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// true
|
||||
if str := l.findRegexp(rTrue); str != "" {
|
||||
l.pos += len("true")
|
||||
l.emit(TokenBoolean)
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// false
|
||||
if str := l.findRegexp(rFalse); str != "" {
|
||||
l.pos += len("false")
|
||||
l.emit(TokenBoolean)
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// let's scan next character
|
||||
switch r := l.next(); {
|
||||
case r == eof:
|
||||
return l.errorf("Unclosed expression")
|
||||
case isIgnorable(r):
|
||||
return lexIgnorable
|
||||
case r == '(':
|
||||
l.emit(TokenOpenSexpr)
|
||||
case r == ')':
|
||||
l.emit(TokenCloseSexpr)
|
||||
case r == '=':
|
||||
l.emit(TokenEquals)
|
||||
case r == '@':
|
||||
l.emit(TokenData)
|
||||
case r == '"' || r == '\'':
|
||||
l.backup()
|
||||
return lexString
|
||||
case r == '/' || r == '.':
|
||||
l.emit(TokenSep)
|
||||
case r == '|':
|
||||
l.emit(TokenCloseBlockParams)
|
||||
case r == '+' || r == '-' || (r >= '0' && r <= '9'):
|
||||
l.backup()
|
||||
return lexNumber
|
||||
case r == '[':
|
||||
return lexPathLiteral
|
||||
case strings.IndexRune(unallowedIDChars, r) < 0:
|
||||
l.backup()
|
||||
return lexIdentifier
|
||||
default:
|
||||
return l.errorf("Unexpected character in expression: '%c'", r)
|
||||
}
|
||||
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// lexComment scans {{!-- or {{!
|
||||
func lexComment(l *Lexer) lexFunc {
|
||||
if str := l.findRegexp(l.closeComment); str != "" {
|
||||
l.pos += len(str)
|
||||
l.emit(TokenComment)
|
||||
|
||||
return lexContent
|
||||
}
|
||||
|
||||
if r := l.next(); r == eof {
|
||||
return l.errorf("Unclosed comment")
|
||||
}
|
||||
|
||||
return lexComment
|
||||
}
|
||||
|
||||
// lexIgnorable scans all following ignorable characters
|
||||
func lexIgnorable(l *Lexer) lexFunc {
|
||||
for isIgnorable(l.peek()) {
|
||||
l.next()
|
||||
}
|
||||
l.ignore()
|
||||
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// lexString scans a string
|
||||
func lexString(l *Lexer) lexFunc {
|
||||
// get string delimiter
|
||||
delim := l.next()
|
||||
var prev rune
|
||||
|
||||
// ignore delimiter
|
||||
l.ignore()
|
||||
|
||||
for {
|
||||
r := l.next()
|
||||
if r == eof || r == '\n' {
|
||||
return l.errorf("Unterminated string")
|
||||
}
|
||||
|
||||
if (r == delim) && (prev != '\\') {
|
||||
break
|
||||
}
|
||||
|
||||
prev = r
|
||||
}
|
||||
|
||||
// remove end delimiter
|
||||
l.backup()
|
||||
|
||||
// emit string
|
||||
l.emitString(delim)
|
||||
|
||||
// skip end delimiter
|
||||
l.next()
|
||||
l.ignore()
|
||||
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// lexNumber scans a number: decimal, octal, hex, float, or imaginary. This
|
||||
// isn't a perfect number scanner - for instance it accepts "." and "0x0.2"
|
||||
// and "089" - but when it's wrong the input is invalid and the parser (via
|
||||
// strconv) will notice.
|
||||
//
|
||||
// NOTE: borrowed from https://github.com/golang/go/tree/master/src/text/template/parse/lex.go
|
||||
func lexNumber(l *Lexer) lexFunc {
|
||||
if !l.scanNumber() {
|
||||
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
if sign := l.peek(); sign == '+' || sign == '-' {
|
||||
// Complex: 1+2i. No spaces, must end in 'i'.
|
||||
if !l.scanNumber() || l.input[l.pos-1] != 'i' {
|
||||
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
l.emit(TokenNumber)
|
||||
} else {
|
||||
l.emit(TokenNumber)
|
||||
}
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// scanNumber scans a number
|
||||
//
|
||||
// NOTE: borrowed from https://github.com/golang/go/tree/master/src/text/template/parse/lex.go
|
||||
func (l *Lexer) scanNumber() bool {
|
||||
// Optional leading sign.
|
||||
l.accept("+-")
|
||||
|
||||
// Is it hex?
|
||||
digits := "0123456789"
|
||||
|
||||
if l.accept("0") && l.accept("xX") {
|
||||
digits = "0123456789abcdefABCDEF"
|
||||
}
|
||||
|
||||
l.acceptRun(digits)
|
||||
|
||||
if l.accept(".") {
|
||||
l.acceptRun(digits)
|
||||
}
|
||||
|
||||
if l.accept("eE") {
|
||||
l.accept("+-")
|
||||
l.acceptRun("0123456789")
|
||||
}
|
||||
|
||||
// Is it imaginary?
|
||||
l.accept("i")
|
||||
|
||||
// Next thing mustn't be alphanumeric.
|
||||
if isAlphaNumeric(l.peek()) {
|
||||
l.next()
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// lexIdentifier scans an ID
|
||||
func lexIdentifier(l *Lexer) lexFunc {
|
||||
str := l.findRegexp(rID)
|
||||
if len(str) == 0 {
|
||||
// this is rotten
|
||||
panic("Identifier expected")
|
||||
}
|
||||
|
||||
l.pos += len(str)
|
||||
l.emit(TokenID)
|
||||
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// lexPathLiteral scans an [ID]
|
||||
func lexPathLiteral(l *Lexer) lexFunc {
|
||||
for {
|
||||
r := l.next()
|
||||
if r == eof || r == '\n' {
|
||||
return l.errorf("Unterminated path literal")
|
||||
}
|
||||
|
||||
if r == ']' {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
l.emit(TokenID)
|
||||
|
||||
return lexExpression
|
||||
}
|
||||
|
||||
// isIgnorable returns true if given character is ignorable (ie. whitespace of line feed)
|
||||
func isIgnorable(r rune) bool {
|
||||
return r == ' ' || r == '\t' || r == '\n'
|
||||
}
|
||||
|
||||
// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
|
||||
//
|
||||
// NOTE borrowed from https://github.com/golang/go/tree/master/src/text/template/parse/lex.go
|
||||
func isAlphaNumeric(r rune) bool {
|
||||
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
|
||||
}
|
541
vendor/github.com/aymerick/raymond/lexer/lexer_test.go
generated
vendored
Normal file
541
vendor/github.com/aymerick/raymond/lexer/lexer_test.go
generated
vendored
Normal file
@ -0,0 +1,541 @@
|
||||
package lexer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type lexTest struct {
|
||||
name string
|
||||
input string
|
||||
tokens []Token
|
||||
}
|
||||
|
||||
// helpers
|
||||
func tokContent(val string) Token { return Token{TokenContent, val, 0, 1} }
|
||||
func tokID(val string) Token { return Token{TokenID, val, 0, 1} }
|
||||
func tokSep(val string) Token { return Token{TokenSep, val, 0, 1} }
|
||||
func tokString(val string) Token { return Token{TokenString, val, 0, 1} }
|
||||
func tokNumber(val string) Token { return Token{TokenNumber, val, 0, 1} }
|
||||
func tokInverse(val string) Token { return Token{TokenInverse, val, 0, 1} }
|
||||
func tokBool(val string) Token { return Token{TokenBoolean, val, 0, 1} }
|
||||
func tokError(val string) Token { return Token{TokenError, val, 0, 1} }
|
||||
func tokComment(val string) Token { return Token{TokenComment, val, 0, 1} }
|
||||
|
||||
var tokEOF = Token{TokenEOF, "", 0, 1}
|
||||
var tokEquals = Token{TokenEquals, "=", 0, 1}
|
||||
var tokData = Token{TokenData, "@", 0, 1}
|
||||
var tokOpen = Token{TokenOpen, "{{", 0, 1}
|
||||
var tokOpenAmp = Token{TokenOpen, "{{&", 0, 1}
|
||||
var tokOpenPartial = Token{TokenOpenPartial, "{{>", 0, 1}
|
||||
var tokClose = Token{TokenClose, "}}", 0, 1}
|
||||
var tokOpenStrip = Token{TokenOpen, "{{~", 0, 1}
|
||||
var tokCloseStrip = Token{TokenClose, "~}}", 0, 1}
|
||||
var tokOpenUnescaped = Token{TokenOpenUnescaped, "{{{", 0, 1}
|
||||
var tokCloseUnescaped = Token{TokenCloseUnescaped, "}}}", 0, 1}
|
||||
var tokOpenUnescapedStrip = Token{TokenOpenUnescaped, "{{~{", 0, 1}
|
||||
var tokCloseUnescapedStrip = Token{TokenCloseUnescaped, "}~}}", 0, 1}
|
||||
var tokOpenBlock = Token{TokenOpenBlock, "{{#", 0, 1}
|
||||
var tokOpenEndBlock = Token{TokenOpenEndBlock, "{{/", 0, 1}
|
||||
var tokOpenInverse = Token{TokenOpenInverse, "{{^", 0, 1}
|
||||
var tokOpenInverseChain = Token{TokenOpenInverseChain, "{{else", 0, 1}
|
||||
var tokOpenSexpr = Token{TokenOpenSexpr, "(", 0, 1}
|
||||
var tokCloseSexpr = Token{TokenCloseSexpr, ")", 0, 1}
|
||||
var tokOpenBlockParams = Token{TokenOpenBlockParams, "as |", 0, 1}
|
||||
var tokCloseBlockParams = Token{TokenCloseBlockParams, "|", 0, 1}
|
||||
var tokOpenRawBlock = Token{TokenOpenRawBlock, "{{{{", 0, 1}
|
||||
var tokCloseRawBlock = Token{TokenCloseRawBlock, "}}}}", 0, 1}
|
||||
var tokOpenEndRawBlock = Token{TokenOpenEndRawBlock, "{{{{/", 0, 1}
|
||||
|
||||
var lexTests = []lexTest{
|
||||
{"empty", "", []Token{tokEOF}},
|
||||
{"spaces", " \t\n", []Token{tokContent(" \t\n"), tokEOF}},
|
||||
{"content", `now is the time`, []Token{tokContent(`now is the time`), tokEOF}},
|
||||
|
||||
{
|
||||
`does not tokenizes identifier starting with true as boolean`,
|
||||
`{{ foo truebar }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokID("truebar"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`does not tokenizes identifier starting with false as boolean`,
|
||||
`{{ foo falsebar }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokID("falsebar"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes raw block`,
|
||||
`{{{{foo}}}} {{{{/foo}}}}`,
|
||||
[]Token{tokOpenRawBlock, tokID("foo"), tokCloseRawBlock, tokContent(" "), tokOpenEndRawBlock, tokID("foo"), tokCloseRawBlock, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes raw block with mustaches in content`,
|
||||
`{{{{foo}}}}{{bar}}{{{{/foo}}}}`,
|
||||
[]Token{tokOpenRawBlock, tokID("foo"), tokCloseRawBlock, tokContent("{{bar}}"), tokOpenEndRawBlock, tokID("foo"), tokCloseRawBlock, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes @../foo`,
|
||||
`{{@../foo}}`,
|
||||
[]Token{tokOpen, tokData, tokID(".."), tokSep("/"), tokID("foo"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes escaped mustaches`,
|
||||
"\\{{bar}}",
|
||||
[]Token{tokContent("{{bar}}"), tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes strip mustaches`,
|
||||
`{{~ foo ~}}`,
|
||||
[]Token{tokOpenStrip, tokID("foo"), tokCloseStrip, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes unescaped strip mustaches`,
|
||||
`{{~{ foo }~}}`,
|
||||
[]Token{tokOpenUnescapedStrip, tokID("foo"), tokCloseUnescapedStrip, tokEOF},
|
||||
},
|
||||
|
||||
//
|
||||
// Next tests come from:
|
||||
// https://github.com/wycats/handlebars.js/blob/master/spec/tokenizer.js
|
||||
//
|
||||
{
|
||||
`tokenizes a simple mustache as "OPEN ID CLOSE"`,
|
||||
`{{foo}}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`supports unescaping with &`,
|
||||
`{{&bar}}`,
|
||||
[]Token{tokOpenAmp, tokID("bar"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`supports unescaping with {{{`,
|
||||
`{{{bar}}}`,
|
||||
[]Token{tokOpenUnescaped, tokID("bar"), tokCloseUnescaped, tokEOF},
|
||||
},
|
||||
{
|
||||
`supports escaping delimiters`,
|
||||
"{{foo}} \\{{bar}} {{baz}}",
|
||||
[]Token{tokOpen, tokID("foo"), tokClose, tokContent(" "), tokContent("{{bar}} "), tokOpen, tokID("baz"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`supports escaping multiple delimiters`,
|
||||
"{{foo}} \\{{bar}} \\{{baz}}",
|
||||
[]Token{tokOpen, tokID("foo"), tokClose, tokContent(" "), tokContent("{{bar}} "), tokContent("{{baz}}"), tokEOF},
|
||||
},
|
||||
{
|
||||
`supports escaping a triple stash`,
|
||||
"{{foo}} \\{{{bar}}} {{baz}}",
|
||||
[]Token{tokOpen, tokID("foo"), tokClose, tokContent(" "), tokContent("{{{bar}}} "), tokOpen, tokID("baz"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`supports escaping escape character`,
|
||||
"{{foo}} \\\\{{bar}} {{baz}}",
|
||||
[]Token{tokOpen, tokID("foo"), tokClose, tokContent(" \\"), tokOpen, tokID("bar"), tokClose, tokContent(" "), tokOpen, tokID("baz"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`supports escaping multiple escape characters`,
|
||||
"{{foo}} \\\\{{bar}} \\\\{{baz}}",
|
||||
[]Token{tokOpen, tokID("foo"), tokClose, tokContent(" \\"), tokOpen, tokID("bar"), tokClose, tokContent(" \\"), tokOpen, tokID("baz"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`supports escaped mustaches after escaped escape characters`,
|
||||
"{{foo}} \\\\{{bar}} \\{{baz}}",
|
||||
// NOTE: JS implementation returns:
|
||||
// ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT'],
|
||||
// WTF is the last CONTENT ?
|
||||
[]Token{tokOpen, tokID("foo"), tokClose, tokContent(" \\"), tokOpen, tokID("bar"), tokClose, tokContent(" "), tokContent("{{baz}}"), tokEOF},
|
||||
},
|
||||
{
|
||||
`supports escaped escape characters after escaped mustaches`,
|
||||
"{{foo}} \\{{bar}} \\\\{{baz}}",
|
||||
// NOTE: JS implementation returns:
|
||||
// []Token{tokOpen, tokID("foo"), tokClose, tokContent(" "), tokContent("{{bar}} "), tokContent("\\"), tokOpen, tokID("baz"), tokClose, tokEOF},
|
||||
[]Token{tokOpen, tokID("foo"), tokClose, tokContent(" "), tokContent("{{bar}} \\"), tokOpen, tokID("baz"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`supports escaped escape character on a triple stash`,
|
||||
"{{foo}} \\\\{{{bar}}} {{baz}}",
|
||||
[]Token{tokOpen, tokID("foo"), tokClose, tokContent(" \\"), tokOpenUnescaped, tokID("bar"), tokCloseUnescaped, tokContent(" "), tokOpen, tokID("baz"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes a simple path`,
|
||||
`{{foo/bar}}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokSep("/"), tokID("bar"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`allows dot notation (1)`,
|
||||
`{{foo.bar}}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokSep("."), tokID("bar"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`allows dot notation (2)`,
|
||||
`{{foo.bar.baz}}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokSep("."), tokID("bar"), tokSep("."), tokID("baz"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`allows path literals with []`,
|
||||
`{{foo.[bar]}}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokSep("."), tokID("[bar]"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`allows multiple path literals on a line with []`,
|
||||
`{{foo.[bar]}}{{foo.[baz]}}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokSep("."), tokID("[bar]"), tokClose, tokOpen, tokID("foo"), tokSep("."), tokID("[baz]"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes {{.}} as OPEN ID CLOSE`,
|
||||
`{{.}}`,
|
||||
[]Token{tokOpen, tokID("."), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes a path as "OPEN (ID SEP)* ID CLOSE"`,
|
||||
`{{../foo/bar}}`,
|
||||
[]Token{tokOpen, tokID(".."), tokSep("/"), tokID("foo"), tokSep("/"), tokID("bar"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes a path with .. as a parent path`,
|
||||
`{{../foo.bar}}`,
|
||||
[]Token{tokOpen, tokID(".."), tokSep("/"), tokID("foo"), tokSep("."), tokID("bar"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes a path with this/foo as OPEN ID SEP ID CLOSE`,
|
||||
`{{this/foo}}`,
|
||||
[]Token{tokOpen, tokID("this"), tokSep("/"), tokID("foo"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes a simple mustache with spaces as "OPEN ID CLOSE"`,
|
||||
`{{ foo }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes a simple mustache with line breaks as "OPEN ID ID CLOSE"`,
|
||||
"{{ foo \n bar }}",
|
||||
[]Token{tokOpen, tokID("foo"), tokID("bar"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes raw content as "CONTENT"`,
|
||||
`foo {{ bar }} baz`,
|
||||
[]Token{tokContent("foo "), tokOpen, tokID("bar"), tokClose, tokContent(" baz"), tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes a partial as "OPEN_PARTIAL ID CLOSE"`,
|
||||
`{{> foo}}`,
|
||||
[]Token{tokOpenPartial, tokID("foo"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes a partial with context as "OPEN_PARTIAL ID ID CLOSE"`,
|
||||
`{{> foo bar }}`,
|
||||
[]Token{tokOpenPartial, tokID("foo"), tokID("bar"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes a partial without spaces as "OPEN_PARTIAL ID CLOSE"`,
|
||||
`{{>foo}}`,
|
||||
[]Token{tokOpenPartial, tokID("foo"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"`,
|
||||
`{{>foo }}`,
|
||||
[]Token{tokOpenPartial, tokID("foo"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"`,
|
||||
`{{>foo/bar.baz }}`,
|
||||
[]Token{tokOpenPartial, tokID("foo"), tokSep("/"), tokID("bar"), tokSep("."), tokID("baz"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes a comment as "COMMENT"`,
|
||||
`foo {{! this is a comment }} bar {{ baz }}`,
|
||||
[]Token{tokContent("foo "), tokComment("{{! this is a comment }}"), tokContent(" bar "), tokOpen, tokID("baz"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes a block comment as "COMMENT"`,
|
||||
`foo {{!-- this is a {{comment}} --}} bar {{ baz }}`,
|
||||
[]Token{tokContent("foo "), tokComment("{{!-- this is a {{comment}} --}}"), tokContent(" bar "), tokOpen, tokID("baz"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes a block comment with whitespace as "COMMENT"`,
|
||||
"foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}",
|
||||
[]Token{tokContent("foo "), tokComment("{{!-- this is a\n{{comment}}\n--}}"), tokContent(" bar "), tokOpen, tokID("baz"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes open and closing blocks as OPEN_BLOCK, ID, CLOSE ..., OPEN_ENDBLOCK ID CLOSE`,
|
||||
`{{#foo}}content{{/foo}}`,
|
||||
[]Token{tokOpenBlock, tokID("foo"), tokClose, tokContent("content"), tokOpenEndBlock, tokID("foo"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes inverse sections as "INVERSE"`,
|
||||
`{{^}}`,
|
||||
[]Token{tokInverse("{{^}}"), tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes inverse sections as "INVERSE" with alternate format`,
|
||||
`{{else}}`,
|
||||
[]Token{tokInverse("{{else}}"), tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes inverse sections as "INVERSE" with spaces`,
|
||||
`{{ else }}`,
|
||||
[]Token{tokInverse("{{ else }}"), tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes inverse sections with ID as "OPEN_INVERSE ID CLOSE"`,
|
||||
`{{^foo}}`,
|
||||
[]Token{tokOpenInverse, tokID("foo"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes inverse sections with ID and spaces as "OPEN_INVERSE ID CLOSE"`,
|
||||
`{{^ foo }}`,
|
||||
[]Token{tokOpenInverse, tokID("foo"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes mustaches with params as "OPEN ID ID ID CLOSE"`,
|
||||
`{{ foo bar baz }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokID("bar"), tokID("baz"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes mustaches with String params as "OPEN ID ID STRING CLOSE"`,
|
||||
`{{ foo bar "baz" }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokID("bar"), tokString("baz"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes mustaches with String params using single quotes as "OPEN ID ID STRING CLOSE"`,
|
||||
`{{ foo bar 'baz' }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokID("bar"), tokString("baz"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes String params with spaces inside as "STRING"`,
|
||||
`{{ foo bar "baz bat" }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokID("bar"), tokString("baz bat"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes String params with escapes quotes as STRING`,
|
||||
`{{ foo "bar\"baz" }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokString(`bar"baz`), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes String params using single quotes with escapes quotes as STRING`,
|
||||
`{{ foo 'bar\'baz' }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokString(`bar'baz`), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes numbers`,
|
||||
`{{ foo 1 }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokNumber("1"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes floats`,
|
||||
`{{ foo 1.1 }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokNumber("1.1"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes negative numbers`,
|
||||
`{{ foo -1 }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokNumber("-1"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes negative floats`,
|
||||
`{{ foo -1.1 }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokNumber("-1.1"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes boolean true`,
|
||||
`{{ foo true }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokBool("true"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes boolean false`,
|
||||
`{{ foo false }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokBool("false"), tokClose, tokEOF},
|
||||
},
|
||||
// SKIP: 'tokenizes undefined and null'
|
||||
{
|
||||
`tokenizes hash arguments (1)`,
|
||||
`{{ foo bar=baz }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokID("bar"), tokEquals, tokID("baz"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes hash arguments (2)`,
|
||||
`{{ foo bar baz=bat }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokID("bar"), tokID("baz"), tokEquals, tokID("bat"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes hash arguments (3)`,
|
||||
`{{ foo bar baz=1 }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokID("bar"), tokID("baz"), tokEquals, tokNumber("1"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes hash arguments (4)`,
|
||||
`{{ foo bar baz=true }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokID("bar"), tokID("baz"), tokEquals, tokBool("true"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes hash arguments (5)`,
|
||||
`{{ foo bar baz=false }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokID("bar"), tokID("baz"), tokEquals, tokBool("false"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes hash arguments (6)`,
|
||||
"{{ foo bar\n baz=bat }}",
|
||||
[]Token{tokOpen, tokID("foo"), tokID("bar"), tokID("baz"), tokEquals, tokID("bat"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes hash arguments (7)`,
|
||||
`{{ foo bar baz="bat" }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokID("bar"), tokID("baz"), tokEquals, tokString("bat"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes hash arguments (8)`,
|
||||
`{{ foo bar baz="bat" bam=wot }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokID("bar"), tokID("baz"), tokEquals, tokString("bat"), tokID("bam"), tokEquals, tokID("wot"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes hash arguments (9)`,
|
||||
`{{foo omg bar=baz bat="bam"}}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokID("omg"), tokID("bar"), tokEquals, tokID("baz"), tokID("bat"), tokEquals, tokString("bam"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes special @ identifiers (1)`,
|
||||
`{{ @foo }}`,
|
||||
[]Token{tokOpen, tokData, tokID("foo"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes special @ identifiers (2)`,
|
||||
`{{ foo @bar }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokData, tokID("bar"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes special @ identifiers (3)`,
|
||||
`{{ foo bar=@baz }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokID("bar"), tokEquals, tokData, tokID("baz"), tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`does not time out in a mustache with a single } followed by EOF`,
|
||||
`{{foo}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokError("Unexpected character in expression: '}'")},
|
||||
},
|
||||
{
|
||||
`does not time out in a mustache when invalid ID characters are used`,
|
||||
`{{foo & }}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokError("Unexpected character in expression: '&'")},
|
||||
},
|
||||
{
|
||||
`tokenizes subexpressions (1)`,
|
||||
`{{foo (bar)}}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokOpenSexpr, tokID("bar"), tokCloseSexpr, tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes subexpressions (2)`,
|
||||
`{{foo (a-x b-y)}}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokOpenSexpr, tokID("a-x"), tokID("b-y"), tokCloseSexpr, tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes nested subexpressions`,
|
||||
`{{foo (bar (lol rofl)) (baz)}}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokOpenSexpr, tokID("bar"), tokOpenSexpr, tokID("lol"), tokID("rofl"), tokCloseSexpr, tokCloseSexpr, tokOpenSexpr, tokID("baz"), tokCloseSexpr, tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes nested subexpressions: literals`,
|
||||
`{{foo (bar (lol true) false) (baz 1) (blah 'b') (blorg "c")}}`,
|
||||
[]Token{tokOpen, tokID("foo"), tokOpenSexpr, tokID("bar"), tokOpenSexpr, tokID("lol"), tokBool("true"), tokCloseSexpr, tokBool("false"), tokCloseSexpr, tokOpenSexpr, tokID("baz"), tokNumber("1"), tokCloseSexpr, tokOpenSexpr, tokID("blah"), tokString("b"), tokCloseSexpr, tokOpenSexpr, tokID("blorg"), tokString("c"), tokCloseSexpr, tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes block params (1)`,
|
||||
`{{#foo as |bar|}}`,
|
||||
[]Token{tokOpenBlock, tokID("foo"), tokOpenBlockParams, tokID("bar"), tokCloseBlockParams, tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes block params (2)`,
|
||||
`{{#foo as |bar baz|}}`,
|
||||
[]Token{tokOpenBlock, tokID("foo"), tokOpenBlockParams, tokID("bar"), tokID("baz"), tokCloseBlockParams, tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes block params (3)`,
|
||||
`{{#foo as | bar baz |}}`,
|
||||
[]Token{tokOpenBlock, tokID("foo"), tokOpenBlockParams, tokID("bar"), tokID("baz"), tokCloseBlockParams, tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes block params (4)`,
|
||||
`{{#foo as as | bar baz |}}`,
|
||||
[]Token{tokOpenBlock, tokID("foo"), tokID("as"), tokOpenBlockParams, tokID("bar"), tokID("baz"), tokCloseBlockParams, tokClose, tokEOF},
|
||||
},
|
||||
{
|
||||
`tokenizes block params (5)`,
|
||||
`{{else foo as |bar baz|}}`,
|
||||
[]Token{tokOpenInverseChain, tokID("foo"), tokOpenBlockParams, tokID("bar"), tokID("baz"), tokCloseBlockParams, tokClose, tokEOF},
|
||||
},
|
||||
}
|
||||
|
||||
func collect(t *lexTest) []Token {
|
||||
var result []Token
|
||||
|
||||
l := scanWithName(t.input, t.name)
|
||||
for {
|
||||
token := l.NextToken()
|
||||
result = append(result, token)
|
||||
|
||||
if token.Kind == TokenEOF || token.Kind == TokenError {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func equal(i1, i2 []Token, checkPos bool) bool {
|
||||
if len(i1) != len(i2) {
|
||||
return false
|
||||
}
|
||||
|
||||
for k := range i1 {
|
||||
if i1[k].Kind != i2[k].Kind {
|
||||
return false
|
||||
}
|
||||
|
||||
if checkPos && i1[k].Pos != i2[k].Pos {
|
||||
return false
|
||||
}
|
||||
|
||||
if i1[k].Val != i2[k].Val {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func TestLexer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, test := range lexTests {
|
||||
tokens := collect(&test)
|
||||
if !equal(tokens, test.tokens, false) {
|
||||
t.Errorf("Test '%s' failed\ninput:\n\t'%s'\nexpected\n\t%v\ngot\n\t%+v\n", test.name, test.input, test.tokens, tokens)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @todo Test errors:
|
||||
// `{{{{raw foo`
|
||||
|
||||
// package example
|
||||
func Example() {
|
||||
source := "You know {{nothing}} John Snow"
|
||||
|
||||
output := ""
|
||||
|
||||
lex := Scan(source)
|
||||
for {
|
||||
// consume next token
|
||||
token := lex.NextToken()
|
||||
|
||||
output += fmt.Sprintf(" %s", token)
|
||||
|
||||
// stops when all tokens have been consumed, or on error
|
||||
if token.Kind == TokenEOF || token.Kind == TokenError {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Print(output)
|
||||
// Output: Content{"You know "} Open{"{{"} ID{"nothing"} Close{"}}"} Content{" John Snow"} EOF
|
||||
}
|
183
vendor/github.com/aymerick/raymond/lexer/token.go
generated
vendored
Normal file
183
vendor/github.com/aymerick/raymond/lexer/token.go
generated
vendored
Normal file
@ -0,0 +1,183 @@
|
||||
package lexer
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
// TokenError represents an error
|
||||
TokenError TokenKind = iota
|
||||
|
||||
// TokenEOF represents an End Of File
|
||||
TokenEOF
|
||||
|
||||
//
|
||||
// Mustache delimiters
|
||||
//
|
||||
|
||||
// TokenOpen is the OPEN token
|
||||
TokenOpen
|
||||
|
||||
// TokenClose is the CLOSE token
|
||||
TokenClose
|
||||
|
||||
// TokenOpenRawBlock is the OPEN_RAW_BLOCK token
|
||||
TokenOpenRawBlock
|
||||
|
||||
// TokenCloseRawBlock is the CLOSE_RAW_BLOCK token
|
||||
TokenCloseRawBlock
|
||||
|
||||
// TokenOpenEndRawBlock is the END_RAW_BLOCK token
|
||||
TokenOpenEndRawBlock
|
||||
|
||||
// TokenOpenUnescaped is the OPEN_UNESCAPED token
|
||||
TokenOpenUnescaped
|
||||
|
||||
// TokenCloseUnescaped is the CLOSE_UNESCAPED token
|
||||
TokenCloseUnescaped
|
||||
|
||||
// TokenOpenBlock is the OPEN_BLOCK token
|
||||
TokenOpenBlock
|
||||
|
||||
// TokenOpenEndBlock is the OPEN_ENDBLOCK token
|
||||
TokenOpenEndBlock
|
||||
|
||||
// TokenInverse is the INVERSE token
|
||||
TokenInverse
|
||||
|
||||
// TokenOpenInverse is the OPEN_INVERSE token
|
||||
TokenOpenInverse
|
||||
|
||||
// TokenOpenInverseChain is the OPEN_INVERSE_CHAIN token
|
||||
TokenOpenInverseChain
|
||||
|
||||
// TokenOpenPartial is the OPEN_PARTIAL token
|
||||
TokenOpenPartial
|
||||
|
||||
// TokenComment is the COMMENT token
|
||||
TokenComment
|
||||
|
||||
//
|
||||
// Inside mustaches
|
||||
//
|
||||
|
||||
// TokenOpenSexpr is the OPEN_SEXPR token
|
||||
TokenOpenSexpr
|
||||
|
||||
// TokenCloseSexpr is the CLOSE_SEXPR token
|
||||
TokenCloseSexpr
|
||||
|
||||
// TokenEquals is the EQUALS token
|
||||
TokenEquals
|
||||
|
||||
// TokenData is the DATA token
|
||||
TokenData
|
||||
|
||||
// TokenSep is the SEP token
|
||||
TokenSep
|
||||
|
||||
// TokenOpenBlockParams is the OPEN_BLOCK_PARAMS token
|
||||
TokenOpenBlockParams
|
||||
|
||||
// TokenCloseBlockParams is the CLOSE_BLOCK_PARAMS token
|
||||
TokenCloseBlockParams
|
||||
|
||||
//
|
||||
// Tokens with content
|
||||
//
|
||||
|
||||
// TokenContent is the CONTENT token
|
||||
TokenContent
|
||||
|
||||
// TokenID is the ID token
|
||||
TokenID
|
||||
|
||||
// TokenString is the STRING token
|
||||
TokenString
|
||||
|
||||
// TokenNumber is the NUMBER token
|
||||
TokenNumber
|
||||
|
||||
// TokenBoolean is the BOOLEAN token
|
||||
TokenBoolean
|
||||
)
|
||||
|
||||
const (
|
||||
// Option to generate token position in its string representation
|
||||
dumpTokenPos = false
|
||||
|
||||
// Option to generate values for all token kinds for their string representations
|
||||
dumpAllTokensVal = true
|
||||
)
|
||||
|
||||
// TokenKind represents a Token type.
|
||||
type TokenKind int
|
||||
|
||||
// Token represents a scanned token.
|
||||
type Token struct {
|
||||
Kind TokenKind // Token kind
|
||||
Val string // Token value
|
||||
|
||||
Pos int // Byte position in input string
|
||||
Line int // Line number in input string
|
||||
}
|
||||
|
||||
// tokenName permits to display token name given token type
|
||||
var tokenName = map[TokenKind]string{
|
||||
TokenError: "Error",
|
||||
TokenEOF: "EOF",
|
||||
TokenContent: "Content",
|
||||
TokenComment: "Comment",
|
||||
TokenOpen: "Open",
|
||||
TokenClose: "Close",
|
||||
TokenOpenUnescaped: "OpenUnescaped",
|
||||
TokenCloseUnescaped: "CloseUnescaped",
|
||||
TokenOpenBlock: "OpenBlock",
|
||||
TokenOpenEndBlock: "OpenEndBlock",
|
||||
TokenOpenRawBlock: "OpenRawBlock",
|
||||
TokenCloseRawBlock: "CloseRawBlock",
|
||||
TokenOpenEndRawBlock: "OpenEndRawBlock",
|
||||
TokenOpenBlockParams: "OpenBlockParams",
|
||||
TokenCloseBlockParams: "CloseBlockParams",
|
||||
TokenInverse: "Inverse",
|
||||
TokenOpenInverse: "OpenInverse",
|
||||
TokenOpenInverseChain: "OpenInverseChain",
|
||||
TokenOpenPartial: "OpenPartial",
|
||||
TokenOpenSexpr: "OpenSexpr",
|
||||
TokenCloseSexpr: "CloseSexpr",
|
||||
TokenID: "ID",
|
||||
TokenEquals: "Equals",
|
||||
TokenString: "String",
|
||||
TokenNumber: "Number",
|
||||
TokenBoolean: "Boolean",
|
||||
TokenData: "Data",
|
||||
TokenSep: "Sep",
|
||||
}
|
||||
|
||||
// String returns the token kind string representation for debugging.
|
||||
func (k TokenKind) String() string {
|
||||
s := tokenName[k]
|
||||
if s == "" {
|
||||
return fmt.Sprintf("Token-%d", int(k))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// String returns the token string representation for debugging.
|
||||
func (t Token) String() string {
|
||||
result := ""
|
||||
|
||||
if dumpTokenPos {
|
||||
result += fmt.Sprintf("%d:", t.Pos)
|
||||
}
|
||||
|
||||
result += fmt.Sprintf("%s", t.Kind)
|
||||
|
||||
if (dumpAllTokensVal || (t.Kind >= TokenContent)) && len(t.Val) > 0 {
|
||||
if len(t.Val) > 100 {
|
||||
result += fmt.Sprintf("{%.20q...}", t.Val)
|
||||
} else {
|
||||
result += fmt.Sprintf("{%q}", t.Val)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
234
vendor/github.com/aymerick/raymond/mustache_test.go
generated
vendored
Normal file
234
vendor/github.com/aymerick/raymond/mustache_test.go
generated
vendored
Normal file
@ -0,0 +1,234 @@
|
||||
package raymond
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
//
|
||||
// Note, as the JS implementation, the divergences from mustache spec:
|
||||
// - we don't support alternative delimeters
|
||||
// - the mustache lambda spec differs
|
||||
//
|
||||
|
||||
type mustacheTest struct {
|
||||
Name string
|
||||
Desc string
|
||||
Data interface{}
|
||||
Template string
|
||||
Expected string
|
||||
Partials map[string]string
|
||||
}
|
||||
|
||||
type mustacheTestFile struct {
|
||||
Overview string
|
||||
Tests []mustacheTest
|
||||
}
|
||||
|
||||
var (
|
||||
rAltDelim = regexp.MustCompile(regexp.QuoteMeta("{{="))
|
||||
)
|
||||
|
||||
var (
|
||||
musTestLambdaInterMult = 0
|
||||
)
|
||||
|
||||
func TestMustache(t *testing.T) {
|
||||
skipFiles := map[string]bool{
|
||||
// mustache lambdas differ from handlebars lambdas
|
||||
"~lambdas.yml": true,
|
||||
}
|
||||
|
||||
for _, fileName := range mustacheTestFiles() {
|
||||
if skipFiles[fileName] {
|
||||
// fmt.Printf("Skipped file: %s\n", fileName)
|
||||
continue
|
||||
}
|
||||
|
||||
launchTests(t, testsFromMustacheFile(fileName))
|
||||
}
|
||||
}
|
||||
|
||||
func testsFromMustacheFile(fileName string) []Test {
|
||||
result := []Test{}
|
||||
|
||||
fileData, err := ioutil.ReadFile(path.Join("mustache", "specs", fileName))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var testFile mustacheTestFile
|
||||
if err := yaml.Unmarshal(fileData, &testFile); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, mustacheTest := range testFile.Tests {
|
||||
if mustBeSkipped(mustacheTest, fileName) {
|
||||
// fmt.Printf("Skipped test: %s\n", mustacheTest.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
test := Test{
|
||||
name: mustacheTest.Name,
|
||||
input: mustacheTest.Template,
|
||||
data: mustacheTest.Data,
|
||||
partials: mustacheTest.Partials,
|
||||
output: mustacheTest.Expected,
|
||||
}
|
||||
|
||||
result = append(result, test)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// returns true if test must be skipped
|
||||
func mustBeSkipped(test mustacheTest, fileName string) bool {
|
||||
// handlebars does not support alternative delimiters
|
||||
return haveAltDelimiter(test) ||
|
||||
// the JS implementation skips those tests
|
||||
fileName == "partials.yml" && (test.Name == "Failed Lookup" || test.Name == "Standalone Indentation")
|
||||
}
|
||||
|
||||
// returns true if test have alternative delimeter in template or in partials
|
||||
func haveAltDelimiter(test mustacheTest) bool {
|
||||
// check template
|
||||
if rAltDelim.MatchString(test.Template) {
|
||||
return true
|
||||
}
|
||||
|
||||
// check partials
|
||||
for _, partial := range test.Partials {
|
||||
if rAltDelim.MatchString(partial) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func mustacheTestFiles() []string {
|
||||
var result []string
|
||||
|
||||
files, err := ioutil.ReadDir(path.Join("mustache", "specs"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
fileName := file.Name()
|
||||
|
||||
if !file.IsDir() && strings.HasSuffix(fileName, ".yml") {
|
||||
result = append(result, fileName)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
//
|
||||
// Following tests come fron ~lambdas.yml
|
||||
//
|
||||
|
||||
var mustacheLambdasTests = []Test{
|
||||
{
|
||||
"Interpolation",
|
||||
"Hello, {{lambda}}!",
|
||||
map[string]interface{}{"lambda": func() string { return "world" }},
|
||||
nil, nil, nil,
|
||||
"Hello, world!",
|
||||
},
|
||||
|
||||
// // SKIP: lambda return value is not parsed
|
||||
// {
|
||||
// "Interpolation - Expansion",
|
||||
// "Hello, {{lambda}}!",
|
||||
// map[string]interface{}{"lambda": func() string { return "{{planet}}" }},
|
||||
// nil, nil, nil,
|
||||
// "Hello, world!",
|
||||
// },
|
||||
|
||||
// SKIP "Interpolation - Alternate Delimiters"
|
||||
|
||||
{
|
||||
"Interpolation - Multiple Calls",
|
||||
"{{lambda}} == {{{lambda}}} == {{lambda}}",
|
||||
map[string]interface{}{"lambda": func() string {
|
||||
musTestLambdaInterMult++
|
||||
return Str(musTestLambdaInterMult)
|
||||
}},
|
||||
nil, nil, nil,
|
||||
"1 == 2 == 3",
|
||||
},
|
||||
|
||||
{
|
||||
"Escaping",
|
||||
"<{{lambda}}{{{lambda}}}",
|
||||
map[string]interface{}{"lambda": func() string { return ">" }},
|
||||
nil, nil, nil,
|
||||
"<>>",
|
||||
},
|
||||
|
||||
// // SKIP: "Lambdas used for sections should receive the raw section string."
|
||||
// {
|
||||
// "Section",
|
||||
// "<{{#lambda}}{{x}}{{/lambda}}>",
|
||||
// map[string]interface{}{"lambda": func(param string) string {
|
||||
// if param == "{{x}}" {
|
||||
// return "yes"
|
||||
// }
|
||||
|
||||
// return "false"
|
||||
// }, "x": "Error!"},
|
||||
// nil, nil, nil,
|
||||
// "<yes>",
|
||||
// },
|
||||
|
||||
// // SKIP: lambda return value is not parsed
|
||||
// {
|
||||
// "Section - Expansion",
|
||||
// "<{{#lambda}}-{{/lambda}}>",
|
||||
// map[string]interface{}{"lambda": func(param string) string {
|
||||
// return param + "{{planet}}" + param
|
||||
// }, "planet": "Earth"},
|
||||
// nil, nil, nil,
|
||||
// "<-Earth->",
|
||||
// },
|
||||
|
||||
// SKIP: "Section - Alternate Delimiters"
|
||||
|
||||
{
|
||||
"Section - Multiple Calls",
|
||||
"{{#lambda}}FILE{{/lambda}} != {{#lambda}}LINE{{/lambda}}",
|
||||
map[string]interface{}{"lambda": func(options *Options) string {
|
||||
return "__" + options.Fn() + "__"
|
||||
}},
|
||||
nil, nil, nil,
|
||||
"__FILE__ != __LINE__",
|
||||
},
|
||||
|
||||
// // SKIP: "Lambdas used for inverted sections should be considered truthy."
|
||||
// {
|
||||
// "Inverted Section",
|
||||
// "<{{^lambda}}{{static}}{{/lambda}}>",
|
||||
// map[string]interface{}{
|
||||
// "lambda": func() interface{} {
|
||||
// return false
|
||||
// },
|
||||
// "static": "static",
|
||||
// },
|
||||
// nil, nil, nil,
|
||||
// "<>",
|
||||
// },
|
||||
}
|
||||
|
||||
func TestMustacheLambdas(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
launchTests(t, mustacheLambdasTests)
|
||||
}
|
846
vendor/github.com/aymerick/raymond/parser/parser.go
generated
vendored
Normal file
846
vendor/github.com/aymerick/raymond/parser/parser.go
generated
vendored
Normal file
@ -0,0 +1,846 @@
|
||||
// Package parser provides a handlebars syntax analyser. It consumes the tokens provided by the lexer to build an AST.
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"github.com/aymerick/raymond/ast"
|
||||
"github.com/aymerick/raymond/lexer"
|
||||
)
|
||||
|
||||
// References:
|
||||
// - https://github.com/wycats/handlebars.js/blob/master/src/handlebars.yy
|
||||
// - https://github.com/golang/go/blob/master/src/text/template/parse/parse.go
|
||||
|
||||
// parser is a syntax analyzer.
|
||||
type parser struct {
|
||||
// Lexer
|
||||
lex *lexer.Lexer
|
||||
|
||||
// Root node
|
||||
root ast.Node
|
||||
|
||||
// Tokens parsed but not consumed yet
|
||||
tokens []*lexer.Token
|
||||
|
||||
// All tokens have been retreieved from lexer
|
||||
lexOver bool
|
||||
}
|
||||
|
||||
var (
|
||||
rOpenComment = regexp.MustCompile(`^\{\{~?!-?-?`)
|
||||
rCloseComment = regexp.MustCompile(`-?-?~?\}\}$`)
|
||||
rOpenAmp = regexp.MustCompile(`^\{\{~?&`)
|
||||
)
|
||||
|
||||
// new instanciates a new parser
|
||||
func new(input string) *parser {
|
||||
return &parser{
|
||||
lex: lexer.Scan(input),
|
||||
}
|
||||
}
|
||||
|
||||
// Parse analyzes given input and returns the AST root node.
|
||||
func Parse(input string) (result *ast.Program, err error) {
|
||||
// recover error
|
||||
defer errRecover(&err)
|
||||
|
||||
parser := new(input)
|
||||
|
||||
// parse
|
||||
result = parser.parseProgram()
|
||||
|
||||
// check last token
|
||||
token := parser.shift()
|
||||
if token.Kind != lexer.TokenEOF {
|
||||
// Parsing ended before EOF
|
||||
errToken(token, "Syntax error")
|
||||
}
|
||||
|
||||
// fix whitespaces
|
||||
processWhitespaces(result)
|
||||
|
||||
// named returned values
|
||||
return
|
||||
}
|
||||
|
||||
// errRecover recovers parsing panic
|
||||
func errRecover(errp *error) {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
switch err := e.(type) {
|
||||
case runtime.Error:
|
||||
panic(e)
|
||||
case error:
|
||||
*errp = err
|
||||
default:
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// errPanic panics
|
||||
func errPanic(err error, line int) {
|
||||
panic(fmt.Errorf("Parse error on line %d:\n%s", line, err))
|
||||
}
|
||||
|
||||
// errNode panics with given node infos
|
||||
func errNode(node ast.Node, msg string) {
|
||||
errPanic(fmt.Errorf("%s\nNode: %s", msg, node), node.Location().Line)
|
||||
}
|
||||
|
||||
// errNode panics with given Token infos
|
||||
func errToken(tok *lexer.Token, msg string) {
|
||||
errPanic(fmt.Errorf("%s\nToken: %s", msg, tok), tok.Line)
|
||||
}
|
||||
|
||||
// errNode panics because of an unexpected Token kind
|
||||
func errExpected(expect lexer.TokenKind, tok *lexer.Token) {
|
||||
errPanic(fmt.Errorf("Expecting %s, got: '%s'", expect, tok), tok.Line)
|
||||
}
|
||||
|
||||
// program : statement*
|
||||
func (p *parser) parseProgram() *ast.Program {
|
||||
result := ast.NewProgram(p.next().Pos, p.next().Line)
|
||||
|
||||
for p.isStatement() {
|
||||
result.AddStatement(p.parseStatement())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// statement : mustache | block | rawBlock | partial | content | COMMENT
|
||||
func (p *parser) parseStatement() ast.Node {
|
||||
var result ast.Node
|
||||
|
||||
tok := p.next()
|
||||
|
||||
switch tok.Kind {
|
||||
case lexer.TokenOpen, lexer.TokenOpenUnescaped:
|
||||
// mustache
|
||||
result = p.parseMustache()
|
||||
case lexer.TokenOpenBlock:
|
||||
// block
|
||||
result = p.parseBlock()
|
||||
case lexer.TokenOpenInverse:
|
||||
// block
|
||||
result = p.parseInverse()
|
||||
case lexer.TokenOpenRawBlock:
|
||||
// rawBlock
|
||||
result = p.parseRawBlock()
|
||||
case lexer.TokenOpenPartial:
|
||||
// partial
|
||||
result = p.parsePartial()
|
||||
case lexer.TokenContent:
|
||||
// content
|
||||
result = p.parseContent()
|
||||
case lexer.TokenComment:
|
||||
// COMMENT
|
||||
result = p.parseComment()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// isStatement returns true if next token starts a statement
|
||||
func (p *parser) isStatement() bool {
|
||||
if !p.have(1) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch p.next().Kind {
|
||||
case lexer.TokenOpen, lexer.TokenOpenUnescaped, lexer.TokenOpenBlock,
|
||||
lexer.TokenOpenInverse, lexer.TokenOpenRawBlock, lexer.TokenOpenPartial,
|
||||
lexer.TokenContent, lexer.TokenComment:
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// content : CONTENT
|
||||
func (p *parser) parseContent() *ast.ContentStatement {
|
||||
// CONTENT
|
||||
tok := p.shift()
|
||||
if tok.Kind != lexer.TokenContent {
|
||||
// @todo This check can be removed if content is optional in a raw block
|
||||
errExpected(lexer.TokenContent, tok)
|
||||
}
|
||||
|
||||
return ast.NewContentStatement(tok.Pos, tok.Line, tok.Val)
|
||||
}
|
||||
|
||||
// COMMENT
|
||||
func (p *parser) parseComment() *ast.CommentStatement {
|
||||
// COMMENT
|
||||
tok := p.shift()
|
||||
|
||||
value := rOpenComment.ReplaceAllString(tok.Val, "")
|
||||
value = rCloseComment.ReplaceAllString(value, "")
|
||||
|
||||
result := ast.NewCommentStatement(tok.Pos, tok.Line, value)
|
||||
result.Strip = ast.NewStripForStr(tok.Val)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// param* hash?
|
||||
func (p *parser) parseExpressionParamsHash() ([]ast.Node, *ast.Hash) {
|
||||
var params []ast.Node
|
||||
var hash *ast.Hash
|
||||
|
||||
// params*
|
||||
if p.isParam() {
|
||||
params = p.parseParams()
|
||||
}
|
||||
|
||||
// hash?
|
||||
if p.isHashSegment() {
|
||||
hash = p.parseHash()
|
||||
}
|
||||
|
||||
return params, hash
|
||||
}
|
||||
|
||||
// helperName param* hash?
|
||||
func (p *parser) parseExpression(tok *lexer.Token) *ast.Expression {
|
||||
result := ast.NewExpression(tok.Pos, tok.Line)
|
||||
|
||||
// helperName
|
||||
result.Path = p.parseHelperName()
|
||||
|
||||
// param* hash?
|
||||
result.Params, result.Hash = p.parseExpressionParamsHash()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// rawBlock : openRawBlock content endRawBlock
|
||||
// openRawBlock : OPEN_RAW_BLOCK helperName param* hash? CLOSE_RAW_BLOCK
|
||||
// endRawBlock : OPEN_END_RAW_BLOCK helperName CLOSE_RAW_BLOCK
|
||||
func (p *parser) parseRawBlock() *ast.BlockStatement {
|
||||
// OPEN_RAW_BLOCK
|
||||
tok := p.shift()
|
||||
|
||||
result := ast.NewBlockStatement(tok.Pos, tok.Line)
|
||||
|
||||
// helperName param* hash?
|
||||
result.Expression = p.parseExpression(tok)
|
||||
|
||||
openName := result.Expression.Canonical()
|
||||
|
||||
// CLOSE_RAW_BLOCK
|
||||
tok = p.shift()
|
||||
if tok.Kind != lexer.TokenCloseRawBlock {
|
||||
errExpected(lexer.TokenCloseRawBlock, tok)
|
||||
}
|
||||
|
||||
// content
|
||||
// @todo Is content mandatory in a raw block ?
|
||||
content := p.parseContent()
|
||||
|
||||
program := ast.NewProgram(tok.Pos, tok.Line)
|
||||
program.AddStatement(content)
|
||||
|
||||
result.Program = program
|
||||
|
||||
// OPEN_END_RAW_BLOCK
|
||||
tok = p.shift()
|
||||
if tok.Kind != lexer.TokenOpenEndRawBlock {
|
||||
// should never happen as it is caught by lexer
|
||||
errExpected(lexer.TokenOpenEndRawBlock, tok)
|
||||
}
|
||||
|
||||
// helperName
|
||||
endID := p.parseHelperName()
|
||||
|
||||
closeName, ok := ast.HelperNameStr(endID)
|
||||
if !ok {
|
||||
errNode(endID, "Erroneous closing expression")
|
||||
}
|
||||
|
||||
if openName != closeName {
|
||||
errNode(endID, fmt.Sprintf("%s doesn't match %s", openName, closeName))
|
||||
}
|
||||
|
||||
// CLOSE_RAW_BLOCK
|
||||
tok = p.shift()
|
||||
if tok.Kind != lexer.TokenCloseRawBlock {
|
||||
errExpected(lexer.TokenCloseRawBlock, tok)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// block : openBlock program inverseChain? closeBlock
|
||||
func (p *parser) parseBlock() *ast.BlockStatement {
|
||||
// openBlock
|
||||
result, blockParams := p.parseOpenBlock()
|
||||
|
||||
// program
|
||||
program := p.parseProgram()
|
||||
program.BlockParams = blockParams
|
||||
result.Program = program
|
||||
|
||||
// inverseChain?
|
||||
if p.isInverseChain() {
|
||||
result.Inverse = p.parseInverseChain()
|
||||
}
|
||||
|
||||
// closeBlock
|
||||
p.parseCloseBlock(result)
|
||||
|
||||
setBlockInverseStrip(result)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// setBlockInverseStrip is called when parsing `block` (openBlock | openInverse) and `inverseChain`
|
||||
//
|
||||
// TODO: This was totally cargo culted ! CHECK THAT !
|
||||
//
|
||||
// cf. prepareBlock() in:
|
||||
// https://github.com/wycats/handlebars.js/blob/master/lib/handlebars/compiler/helper.js
|
||||
func setBlockInverseStrip(block *ast.BlockStatement) {
|
||||
if block.Inverse == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if block.Inverse.Chained {
|
||||
b, _ := block.Inverse.Body[0].(*ast.BlockStatement)
|
||||
b.CloseStrip = block.CloseStrip
|
||||
}
|
||||
|
||||
block.InverseStrip = block.Inverse.Strip
|
||||
}
|
||||
|
||||
// block : openInverse program inverseAndProgram? closeBlock
|
||||
func (p *parser) parseInverse() *ast.BlockStatement {
|
||||
// openInverse
|
||||
result, blockParams := p.parseOpenBlock()
|
||||
|
||||
// program
|
||||
program := p.parseProgram()
|
||||
|
||||
program.BlockParams = blockParams
|
||||
result.Inverse = program
|
||||
|
||||
// inverseAndProgram?
|
||||
if p.isInverse() {
|
||||
result.Program = p.parseInverseAndProgram()
|
||||
}
|
||||
|
||||
// closeBlock
|
||||
p.parseCloseBlock(result)
|
||||
|
||||
setBlockInverseStrip(result)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// helperName param* hash? blockParams?
|
||||
func (p *parser) parseOpenBlockExpression(tok *lexer.Token) (*ast.BlockStatement, []string) {
|
||||
var blockParams []string
|
||||
|
||||
result := ast.NewBlockStatement(tok.Pos, tok.Line)
|
||||
|
||||
// helperName param* hash?
|
||||
result.Expression = p.parseExpression(tok)
|
||||
|
||||
// blockParams?
|
||||
if p.isBlockParams() {
|
||||
blockParams = p.parseBlockParams()
|
||||
}
|
||||
|
||||
// named returned values
|
||||
return result, blockParams
|
||||
}
|
||||
|
||||
// inverseChain : openInverseChain program inverseChain?
|
||||
// | inverseAndProgram
|
||||
func (p *parser) parseInverseChain() *ast.Program {
|
||||
if p.isInverse() {
|
||||
// inverseAndProgram
|
||||
return p.parseInverseAndProgram()
|
||||
}
|
||||
|
||||
result := ast.NewProgram(p.next().Pos, p.next().Line)
|
||||
|
||||
// openInverseChain
|
||||
block, blockParams := p.parseOpenBlock()
|
||||
|
||||
// program
|
||||
program := p.parseProgram()
|
||||
|
||||
program.BlockParams = blockParams
|
||||
block.Program = program
|
||||
|
||||
// inverseChain?
|
||||
if p.isInverseChain() {
|
||||
block.Inverse = p.parseInverseChain()
|
||||
}
|
||||
|
||||
setBlockInverseStrip(block)
|
||||
|
||||
result.Chained = true
|
||||
result.AddStatement(block)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns true if current token starts an inverse chain
|
||||
func (p *parser) isInverseChain() bool {
|
||||
return p.isOpenInverseChain() || p.isInverse()
|
||||
}
|
||||
|
||||
// inverseAndProgram : INVERSE program
|
||||
func (p *parser) parseInverseAndProgram() *ast.Program {
|
||||
// INVERSE
|
||||
tok := p.shift()
|
||||
|
||||
// program
|
||||
result := p.parseProgram()
|
||||
result.Strip = ast.NewStripForStr(tok.Val)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// openBlock : OPEN_BLOCK helperName param* hash? blockParams? CLOSE
|
||||
// openInverse : OPEN_INVERSE helperName param* hash? blockParams? CLOSE
|
||||
// openInverseChain: OPEN_INVERSE_CHAIN helperName param* hash? blockParams? CLOSE
|
||||
func (p *parser) parseOpenBlock() (*ast.BlockStatement, []string) {
|
||||
// OPEN_BLOCK | OPEN_INVERSE | OPEN_INVERSE_CHAIN
|
||||
tok := p.shift()
|
||||
|
||||
// helperName param* hash? blockParams?
|
||||
result, blockParams := p.parseOpenBlockExpression(tok)
|
||||
|
||||
// CLOSE
|
||||
tokClose := p.shift()
|
||||
if tokClose.Kind != lexer.TokenClose {
|
||||
errExpected(lexer.TokenClose, tokClose)
|
||||
}
|
||||
|
||||
result.OpenStrip = ast.NewStrip(tok.Val, tokClose.Val)
|
||||
|
||||
// named returned values
|
||||
return result, blockParams
|
||||
}
|
||||
|
||||
// closeBlock : OPEN_ENDBLOCK helperName CLOSE
|
||||
func (p *parser) parseCloseBlock(block *ast.BlockStatement) {
|
||||
// OPEN_ENDBLOCK
|
||||
tok := p.shift()
|
||||
if tok.Kind != lexer.TokenOpenEndBlock {
|
||||
errExpected(lexer.TokenOpenEndBlock, tok)
|
||||
}
|
||||
|
||||
// helperName
|
||||
endID := p.parseHelperName()
|
||||
|
||||
closeName, ok := ast.HelperNameStr(endID)
|
||||
if !ok {
|
||||
errNode(endID, "Erroneous closing expression")
|
||||
}
|
||||
|
||||
openName := block.Expression.Canonical()
|
||||
if openName != closeName {
|
||||
errNode(endID, fmt.Sprintf("%s doesn't match %s", openName, closeName))
|
||||
}
|
||||
|
||||
// CLOSE
|
||||
tokClose := p.shift()
|
||||
if tokClose.Kind != lexer.TokenClose {
|
||||
errExpected(lexer.TokenClose, tokClose)
|
||||
}
|
||||
|
||||
block.CloseStrip = ast.NewStrip(tok.Val, tokClose.Val)
|
||||
}
|
||||
|
||||
// mustache : OPEN helperName param* hash? CLOSE
|
||||
// | OPEN_UNESCAPED helperName param* hash? CLOSE_UNESCAPED
|
||||
func (p *parser) parseMustache() *ast.MustacheStatement {
|
||||
// OPEN | OPEN_UNESCAPED
|
||||
tok := p.shift()
|
||||
|
||||
closeToken := lexer.TokenClose
|
||||
if tok.Kind == lexer.TokenOpenUnescaped {
|
||||
closeToken = lexer.TokenCloseUnescaped
|
||||
}
|
||||
|
||||
unescaped := false
|
||||
if (tok.Kind == lexer.TokenOpenUnescaped) || (rOpenAmp.MatchString(tok.Val)) {
|
||||
unescaped = true
|
||||
}
|
||||
|
||||
result := ast.NewMustacheStatement(tok.Pos, tok.Line, unescaped)
|
||||
|
||||
// helperName param* hash?
|
||||
result.Expression = p.parseExpression(tok)
|
||||
|
||||
// CLOSE | CLOSE_UNESCAPED
|
||||
tokClose := p.shift()
|
||||
if tokClose.Kind != closeToken {
|
||||
errExpected(closeToken, tokClose)
|
||||
}
|
||||
|
||||
result.Strip = ast.NewStrip(tok.Val, tokClose.Val)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// partial : OPEN_PARTIAL partialName param* hash? CLOSE
|
||||
func (p *parser) parsePartial() *ast.PartialStatement {
|
||||
// OPEN_PARTIAL
|
||||
tok := p.shift()
|
||||
|
||||
result := ast.NewPartialStatement(tok.Pos, tok.Line)
|
||||
|
||||
// partialName
|
||||
result.Name = p.parsePartialName()
|
||||
|
||||
// param* hash?
|
||||
result.Params, result.Hash = p.parseExpressionParamsHash()
|
||||
|
||||
// CLOSE
|
||||
tokClose := p.shift()
|
||||
if tokClose.Kind != lexer.TokenClose {
|
||||
errExpected(lexer.TokenClose, tokClose)
|
||||
}
|
||||
|
||||
result.Strip = ast.NewStrip(tok.Val, tokClose.Val)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// helperName | sexpr
|
||||
func (p *parser) parseHelperNameOrSexpr() ast.Node {
|
||||
if p.isSexpr() {
|
||||
// sexpr
|
||||
return p.parseSexpr()
|
||||
}
|
||||
|
||||
// helperName
|
||||
return p.parseHelperName()
|
||||
}
|
||||
|
||||
// param : helperName | sexpr
|
||||
func (p *parser) parseParam() ast.Node {
|
||||
return p.parseHelperNameOrSexpr()
|
||||
}
|
||||
|
||||
// Returns true if next tokens represent a `param`
|
||||
func (p *parser) isParam() bool {
|
||||
return (p.isSexpr() || p.isHelperName()) && !p.isHashSegment()
|
||||
}
|
||||
|
||||
// param*
|
||||
func (p *parser) parseParams() []ast.Node {
|
||||
var result []ast.Node
|
||||
|
||||
for p.isParam() {
|
||||
result = append(result, p.parseParam())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// sexpr : OPEN_SEXPR helperName param* hash? CLOSE_SEXPR
|
||||
func (p *parser) parseSexpr() *ast.SubExpression {
|
||||
// OPEN_SEXPR
|
||||
tok := p.shift()
|
||||
|
||||
result := ast.NewSubExpression(tok.Pos, tok.Line)
|
||||
|
||||
// helperName param* hash?
|
||||
result.Expression = p.parseExpression(tok)
|
||||
|
||||
// CLOSE_SEXPR
|
||||
tok = p.shift()
|
||||
if tok.Kind != lexer.TokenCloseSexpr {
|
||||
errExpected(lexer.TokenCloseSexpr, tok)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// hash : hashSegment+
|
||||
func (p *parser) parseHash() *ast.Hash {
|
||||
var pairs []*ast.HashPair
|
||||
|
||||
for p.isHashSegment() {
|
||||
pairs = append(pairs, p.parseHashSegment())
|
||||
}
|
||||
|
||||
firstLoc := pairs[0].Location()
|
||||
|
||||
result := ast.NewHash(firstLoc.Pos, firstLoc.Line)
|
||||
result.Pairs = pairs
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// returns true if next tokens represents a `hashSegment`
|
||||
func (p *parser) isHashSegment() bool {
|
||||
return p.have(2) && (p.next().Kind == lexer.TokenID) && (p.nextAt(1).Kind == lexer.TokenEquals)
|
||||
}
|
||||
|
||||
// hashSegment : ID EQUALS param
|
||||
func (p *parser) parseHashSegment() *ast.HashPair {
|
||||
// ID
|
||||
tok := p.shift()
|
||||
|
||||
// EQUALS
|
||||
p.shift()
|
||||
|
||||
// param
|
||||
param := p.parseParam()
|
||||
|
||||
result := ast.NewHashPair(tok.Pos, tok.Line)
|
||||
result.Key = tok.Val
|
||||
result.Val = param
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// blockParams : OPEN_BLOCK_PARAMS ID+ CLOSE_BLOCK_PARAMS
|
||||
func (p *parser) parseBlockParams() []string {
|
||||
var result []string
|
||||
|
||||
// OPEN_BLOCK_PARAMS
|
||||
tok := p.shift()
|
||||
|
||||
// ID+
|
||||
for p.isID() {
|
||||
result = append(result, p.shift().Val)
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
errExpected(lexer.TokenID, p.next())
|
||||
}
|
||||
|
||||
// CLOSE_BLOCK_PARAMS
|
||||
tok = p.shift()
|
||||
if tok.Kind != lexer.TokenCloseBlockParams {
|
||||
errExpected(lexer.TokenCloseBlockParams, tok)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// helperName : path | dataName | STRING | NUMBER | BOOLEAN | UNDEFINED | NULL
|
||||
func (p *parser) parseHelperName() ast.Node {
|
||||
var result ast.Node
|
||||
|
||||
tok := p.next()
|
||||
|
||||
switch tok.Kind {
|
||||
case lexer.TokenBoolean:
|
||||
// BOOLEAN
|
||||
p.shift()
|
||||
result = ast.NewBooleanLiteral(tok.Pos, tok.Line, (tok.Val == "true"), tok.Val)
|
||||
case lexer.TokenNumber:
|
||||
// NUMBER
|
||||
p.shift()
|
||||
|
||||
val, isInt := parseNumber(tok)
|
||||
result = ast.NewNumberLiteral(tok.Pos, tok.Line, val, isInt, tok.Val)
|
||||
case lexer.TokenString:
|
||||
// STRING
|
||||
p.shift()
|
||||
result = ast.NewStringLiteral(tok.Pos, tok.Line, tok.Val)
|
||||
case lexer.TokenData:
|
||||
// dataName
|
||||
result = p.parseDataName()
|
||||
default:
|
||||
// path
|
||||
result = p.parsePath(false)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// parseNumber parses a number
|
||||
func parseNumber(tok *lexer.Token) (result float64, isInt bool) {
|
||||
var valInt int
|
||||
var err error
|
||||
|
||||
valInt, err = strconv.Atoi(tok.Val)
|
||||
if err == nil {
|
||||
isInt = true
|
||||
|
||||
result = float64(valInt)
|
||||
} else {
|
||||
isInt = false
|
||||
|
||||
result, err = strconv.ParseFloat(tok.Val, 64)
|
||||
if err != nil {
|
||||
errToken(tok, fmt.Sprintf("Failed to parse number: %s", tok.Val))
|
||||
}
|
||||
}
|
||||
|
||||
// named returned values
|
||||
return
|
||||
}
|
||||
|
||||
// Returns true if next tokens represent a `helperName`
|
||||
func (p *parser) isHelperName() bool {
|
||||
switch p.next().Kind {
|
||||
case lexer.TokenBoolean, lexer.TokenNumber, lexer.TokenString, lexer.TokenData, lexer.TokenID:
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// partialName : helperName | sexpr
|
||||
func (p *parser) parsePartialName() ast.Node {
|
||||
return p.parseHelperNameOrSexpr()
|
||||
}
|
||||
|
||||
// dataName : DATA pathSegments
|
||||
func (p *parser) parseDataName() *ast.PathExpression {
|
||||
// DATA
|
||||
p.shift()
|
||||
|
||||
// pathSegments
|
||||
return p.parsePath(true)
|
||||
}
|
||||
|
||||
// path : pathSegments
|
||||
// pathSegments : pathSegments SEP ID
|
||||
// | ID
|
||||
func (p *parser) parsePath(data bool) *ast.PathExpression {
|
||||
var tok *lexer.Token
|
||||
|
||||
// ID
|
||||
tok = p.shift()
|
||||
if tok.Kind != lexer.TokenID {
|
||||
errExpected(lexer.TokenID, tok)
|
||||
}
|
||||
|
||||
result := ast.NewPathExpression(tok.Pos, tok.Line, data)
|
||||
result.Part(tok.Val)
|
||||
|
||||
for p.isPathSep() {
|
||||
// SEP
|
||||
tok = p.shift()
|
||||
result.Sep(tok.Val)
|
||||
|
||||
// ID
|
||||
tok = p.shift()
|
||||
if tok.Kind != lexer.TokenID {
|
||||
errExpected(lexer.TokenID, tok)
|
||||
}
|
||||
|
||||
result.Part(tok.Val)
|
||||
|
||||
if len(result.Parts) > 0 {
|
||||
switch tok.Val {
|
||||
case "..", ".", "this":
|
||||
errToken(tok, "Invalid path: "+result.Original)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Ensures there is token to parse at given index
|
||||
func (p *parser) ensure(index int) {
|
||||
if p.lexOver {
|
||||
// nothing more to grab
|
||||
return
|
||||
}
|
||||
|
||||
nb := index + 1
|
||||
|
||||
for len(p.tokens) < nb {
|
||||
// fetch next token
|
||||
tok := p.lex.NextToken()
|
||||
|
||||
// queue it
|
||||
p.tokens = append(p.tokens, &tok)
|
||||
|
||||
if (tok.Kind == lexer.TokenEOF) || (tok.Kind == lexer.TokenError) {
|
||||
p.lexOver = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// have returns true is there are a list given number of tokens to consume left
|
||||
func (p *parser) have(nb int) bool {
|
||||
p.ensure(nb - 1)
|
||||
|
||||
return len(p.tokens) >= nb
|
||||
}
|
||||
|
||||
// nextAt returns next token at given index, without consuming it
|
||||
func (p *parser) nextAt(index int) *lexer.Token {
|
||||
p.ensure(index)
|
||||
|
||||
return p.tokens[index]
|
||||
}
|
||||
|
||||
// next returns next token without consuming it
|
||||
func (p *parser) next() *lexer.Token {
|
||||
return p.nextAt(0)
|
||||
}
|
||||
|
||||
// shift returns next token and remove it from the tokens buffer
|
||||
//
|
||||
// Panics if next token is `TokenError`
|
||||
func (p *parser) shift() *lexer.Token {
|
||||
var result *lexer.Token
|
||||
|
||||
p.ensure(0)
|
||||
|
||||
result, p.tokens = p.tokens[0], p.tokens[1:]
|
||||
|
||||
// check error token
|
||||
if result.Kind == lexer.TokenError {
|
||||
errToken(result, "Lexer error")
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// isToken returns true if next token is of given type
|
||||
func (p *parser) isToken(kind lexer.TokenKind) bool {
|
||||
return p.have(1) && p.next().Kind == kind
|
||||
}
|
||||
|
||||
// isSexpr returns true if next token starts a sexpr
|
||||
func (p *parser) isSexpr() bool {
|
||||
return p.isToken(lexer.TokenOpenSexpr)
|
||||
}
|
||||
|
||||
// isPathSep returns true if next token is a path separator
|
||||
func (p *parser) isPathSep() bool {
|
||||
return p.isToken(lexer.TokenSep)
|
||||
}
|
||||
|
||||
// isID returns true if next token is an ID
|
||||
func (p *parser) isID() bool {
|
||||
return p.isToken(lexer.TokenID)
|
||||
}
|
||||
|
||||
// isBlockParams returns true if next token starts a block params
|
||||
func (p *parser) isBlockParams() bool {
|
||||
return p.isToken(lexer.TokenOpenBlockParams)
|
||||
}
|
||||
|
||||
// isInverse returns true if next token starts an INVERSE sequence
|
||||
func (p *parser) isInverse() bool {
|
||||
return p.isToken(lexer.TokenInverse)
|
||||
}
|
||||
|
||||
// isOpenInverseChain returns true if next token is OPEN_INVERSE_CHAIN
|
||||
func (p *parser) isOpenInverseChain() bool {
|
||||
return p.isToken(lexer.TokenOpenInverseChain)
|
||||
}
|
200
vendor/github.com/aymerick/raymond/parser/parser_test.go
generated
vendored
Normal file
200
vendor/github.com/aymerick/raymond/parser/parser_test.go
generated
vendored
Normal file
@ -0,0 +1,200 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/aymerick/raymond/ast"
|
||||
"github.com/aymerick/raymond/lexer"
|
||||
)
|
||||
|
||||
type parserTest struct {
|
||||
name string
|
||||
input string
|
||||
output string
|
||||
}
|
||||
|
||||
var parserTests = []parserTest{
|
||||
//
|
||||
// Next tests come from:
|
||||
// https://github.com/wycats/handlebars.js/blob/master/spec/parser.js
|
||||
//
|
||||
{"parses simple mustaches (1)", `{{123}}`, "{{ NUMBER{123} [] }}\n"},
|
||||
{"parses simple mustaches (2)", `{{"foo"}}`, "{{ \"foo\" [] }}\n"},
|
||||
{"parses simple mustaches (3)", `{{false}}`, "{{ BOOLEAN{false} [] }}\n"},
|
||||
{"parses simple mustaches (4)", `{{true}}`, "{{ BOOLEAN{true} [] }}\n"},
|
||||
{"parses simple mustaches (5)", `{{foo}}`, "{{ PATH:foo [] }}\n"},
|
||||
{"parses simple mustaches (6)", `{{foo?}}`, "{{ PATH:foo? [] }}\n"},
|
||||
{"parses simple mustaches (7)", `{{foo_}}`, "{{ PATH:foo_ [] }}\n"},
|
||||
{"parses simple mustaches (8)", `{{foo-}}`, "{{ PATH:foo- [] }}\n"},
|
||||
{"parses simple mustaches (9)", `{{foo:}}`, "{{ PATH:foo: [] }}\n"},
|
||||
|
||||
{"parses simple mustaches with data", `{{@foo}}`, "{{ @PATH:foo [] }}\n"},
|
||||
{"parses simple mustaches with data paths", `{{@../foo}}`, "{{ @PATH:foo [] }}\n"},
|
||||
{"parses mustaches with paths", `{{foo/bar}}`, "{{ PATH:foo/bar [] }}\n"},
|
||||
{"parses mustaches with this/foo", `{{this/foo}}`, "{{ PATH:foo [] }}\n"},
|
||||
{"parses mustaches with - in a path", `{{foo-bar}}`, "{{ PATH:foo-bar [] }}\n"},
|
||||
{"parses mustaches with parameters", `{{foo bar}}`, "{{ PATH:foo [PATH:bar] }}\n"},
|
||||
{"parses mustaches with string parameters", `{{foo bar "baz" }}`, "{{ PATH:foo [PATH:bar, \"baz\"] }}\n"},
|
||||
{"parses mustaches with NUMBER parameters", `{{foo 1}}`, "{{ PATH:foo [NUMBER{1}] }}\n"},
|
||||
{"parses mustaches with BOOLEAN parameters (1)", `{{foo true}}`, "{{ PATH:foo [BOOLEAN{true}] }}\n"},
|
||||
{"parses mustaches with BOOLEAN parameters (2)", `{{foo false}}`, "{{ PATH:foo [BOOLEAN{false}] }}\n"},
|
||||
{"parses mustaches with DATA parameters", `{{foo @bar}}`, "{{ PATH:foo [@PATH:bar] }}\n"},
|
||||
|
||||
{"parses mustaches with hash arguments (01)", `{{foo bar=baz}}`, "{{ PATH:foo [] HASH{bar=PATH:baz} }}\n"},
|
||||
{"parses mustaches with hash arguments (02)", `{{foo bar=1}}`, "{{ PATH:foo [] HASH{bar=NUMBER{1}} }}\n"},
|
||||
{"parses mustaches with hash arguments (03)", `{{foo bar=true}}`, "{{ PATH:foo [] HASH{bar=BOOLEAN{true}} }}\n"},
|
||||
{"parses mustaches with hash arguments (04)", `{{foo bar=false}}`, "{{ PATH:foo [] HASH{bar=BOOLEAN{false}} }}\n"},
|
||||
{"parses mustaches with hash arguments (05)", `{{foo bar=@baz}}`, "{{ PATH:foo [] HASH{bar=@PATH:baz} }}\n"},
|
||||
{"parses mustaches with hash arguments (06)", `{{foo bar=baz bat=bam}}`, "{{ PATH:foo [] HASH{bar=PATH:baz, bat=PATH:bam} }}\n"},
|
||||
{"parses mustaches with hash arguments (07)", `{{foo bar=baz bat="bam"}}`, "{{ PATH:foo [] HASH{bar=PATH:baz, bat=\"bam\"} }}\n"},
|
||||
{"parses mustaches with hash arguments (08)", `{{foo bat='bam'}}`, "{{ PATH:foo [] HASH{bat=\"bam\"} }}\n"},
|
||||
{"parses mustaches with hash arguments (09)", `{{foo omg bar=baz bat="bam"}}`, "{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat=\"bam\"} }}\n"},
|
||||
{"parses mustaches with hash arguments (10)", `{{foo omg bar=baz bat="bam" baz=1}}`, "{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat=\"bam\", baz=NUMBER{1}} }}\n"},
|
||||
{"parses mustaches with hash arguments (11)", `{{foo omg bar=baz bat="bam" baz=true}}`, "{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat=\"bam\", baz=BOOLEAN{true}} }}\n"},
|
||||
{"parses mustaches with hash arguments (12)", `{{foo omg bar=baz bat="bam" baz=false}}`, "{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat=\"bam\", baz=BOOLEAN{false}} }}\n"},
|
||||
|
||||
{"parses contents followed by a mustache", `foo bar {{baz}}`, "CONTENT[ 'foo bar ' ]\n{{ PATH:baz [] }}\n"},
|
||||
|
||||
{"parses a partial (1)", `{{> foo }}`, "{{> PARTIAL:foo }}\n"},
|
||||
{"parses a partial (2)", `{{> "foo" }}`, "{{> PARTIAL:foo }}\n"},
|
||||
{"parses a partial (3)", `{{> 1 }}`, "{{> PARTIAL:1 }}\n"},
|
||||
{"parses a partial with context", `{{> foo bar}}`, "{{> PARTIAL:foo PATH:bar }}\n"},
|
||||
{"parses a partial with hash", `{{> foo bar=bat}}`, "{{> PARTIAL:foo HASH{bar=PATH:bat} }}\n"},
|
||||
{"parses a partial with context and hash", `{{> foo bar bat=baz}}`, "{{> PARTIAL:foo PATH:bar HASH{bat=PATH:baz} }}\n"},
|
||||
{"parses a partial with a complex name", `{{> shared/partial?.bar}}`, "{{> PARTIAL:shared/partial?.bar }}\n"},
|
||||
|
||||
{"parses a comment", `{{! this is a comment }}`, "{{! ' this is a comment ' }}\n"},
|
||||
{"parses a multi-line comment", "{{!\nthis is a multi-line comment\n}}", "{{! '\nthis is a multi-line comment\n' }}\n"},
|
||||
|
||||
{"parses an inverse section", `{{#foo}} bar {{^}} baz {{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n"},
|
||||
{"parses an inverse (else-style) section", `{{#foo}} bar {{else}} baz {{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n"},
|
||||
{"parses multiple inverse sections", `{{#foo}} bar {{else if bar}}{{else}} baz {{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n BLOCK:\n PATH:if [PATH:bar]\n PROGRAM:\n {{^}}\n CONTENT[ ' baz ' ]\n"},
|
||||
{"parses empty blocks", `{{#foo}}{{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n"},
|
||||
{"parses empty blocks with empty inverse section", `{{#foo}}{{^}}{{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n"},
|
||||
{"parses empty blocks with empty inverse (else-style) section", `{{#foo}}{{else}}{{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n"},
|
||||
{"parses non-empty blocks with empty inverse section", `{{#foo}} bar {{^}}{{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n"},
|
||||
{"parses non-empty blocks with empty inverse (else-style) section", `{{#foo}} bar {{else}}{{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n"},
|
||||
{"parses empty blocks with non-empty inverse section", `{{#foo}}{{^}} bar {{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n"},
|
||||
{"parses empty blocks with non-empty inverse (else-style) section", `{{#foo}}{{else}} bar {{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n"},
|
||||
{"parses a standalone inverse section", `{{^foo}}bar{{/foo}}`, "BLOCK:\n PATH:foo []\n {{^}}\n CONTENT[ 'bar' ]\n"},
|
||||
{"parses block with block params", `{{#foo as |bar baz|}}content{{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"},
|
||||
{"parses inverse block with block params", `{{^foo as |bar baz|}}content{{/foo}}`, "BLOCK:\n PATH:foo []\n {{^}}\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"},
|
||||
{"parses chained inverse block with block params", `{{#foo}}{{else foo as |bar baz|}}content{{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"},
|
||||
}
|
||||
|
||||
func TestParser(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, test := range parserTests {
|
||||
output := ""
|
||||
|
||||
node, err := Parse(test.input)
|
||||
if err == nil {
|
||||
output = ast.Print(node)
|
||||
}
|
||||
|
||||
if (err != nil) || (test.output != output) {
|
||||
t.Errorf("Test '%s' failed\ninput:\n\t'%s'\nexpected\n\t%q\ngot\n\t%q\nerror:\n\t%s", test.name, test.input, test.output, output, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var parserErrorTests = []parserTest{
|
||||
{"lexer error", `{{! unclosed comment`, "Lexer error"},
|
||||
{"syntax error", `foo{{^}}`, "Syntax error"},
|
||||
|
||||
{"open raw block must be closed", `{{{{raw foo}} bar {{{{/raw}}}}`, "Expecting CloseRawBlock"},
|
||||
{"end raw block must be closed", `{{{{raw foo}}}} bar {{{{/raw}}`, "Expecting CloseRawBlock"},
|
||||
|
||||
{"raw block names must match (1)", `{{{{1}}}}{{foo}}{{{{/raw}}}}`, "1 doesn't match raw"},
|
||||
{"raw block names must match (2)", `{{{{raw}}}}{{foo}}{{{{/1}}}}`, "raw doesn't match 1"},
|
||||
{"raw block names must match (3)", `{{{{goodbyes}}}}test{{{{/hellos}}}}`, "goodbyes doesn't match hellos"},
|
||||
|
||||
{"open block must be closed", `{{#foo bar}}}{{/foo}}`, "Expecting Close"},
|
||||
{"end block must be closed", `{{#foo bar}}{{/foo}}}`, "Expecting Close"},
|
||||
{"an open block must have a end block", `{{#foo}}test`, "Expecting OpenEndBlock"},
|
||||
|
||||
{"block names must match (1)", `{{#1 bar}}{{/foo}}`, "1 doesn't match foo"},
|
||||
{"block names must match (2)", `{{#foo bar}}{{/1}}`, "foo doesn't match 1"},
|
||||
{"block names must match (3)", `{{#foo}}test{{/bar}}`, "foo doesn't match bar"},
|
||||
|
||||
{"an mustache must terminate with a close mustache", `{{foo}}}`, "Expecting Close"},
|
||||
{"an unescaped mustache must terminate with a close unescaped mustache", `{{{foo}}`, "Expecting CloseUnescaped"},
|
||||
|
||||
{"an partial must terminate with a close mustache", `{{> foo}}}`, "Expecting Close"},
|
||||
{"a subexpression must terminate with a close subexpression", `{{foo (false}}`, "Expecting CloseSexpr"},
|
||||
|
||||
{"raises on missing hash value (1)", `{{foo bar=}}`, "Parse error on line 1"},
|
||||
{"raises on missing hash value (2)", `{{foo bar=baz bim=}}`, "Parse error on line 1"},
|
||||
|
||||
{"block param must have at least one param", `{{#foo as ||}}content{{/foo}}`, "Expecting ID"},
|
||||
{"open block params must be closed", `{{#foo as |}}content{{/foo}}`, "Expecting ID"},
|
||||
|
||||
{"a path must start with an ID", `{{#/}}content{{/foo}}`, "Expecting ID"},
|
||||
{"a path must end with an ID", `{{foo/bar/}}`, "Expecting ID"},
|
||||
|
||||
//
|
||||
// Next tests come from:
|
||||
// https://github.com/wycats/handlebars.js/blob/master/spec/parser.js
|
||||
//
|
||||
{"throws on old inverse section", `{{else foo}}bar{{/foo}}`, ""},
|
||||
|
||||
{"raises if there's a parser error (1)", `foo{{^}}bar`, "Parse error on line 1"},
|
||||
{"raises if there's a parser error (2)", `{{foo}`, "Parse error on line 1"},
|
||||
{"raises if there's a parser error (3)", `{{foo &}}`, "Parse error on line 1"},
|
||||
{"raises if there's a parser error (4)", `{{#goodbyes}}{{/hellos}}`, "Parse error on line 1"},
|
||||
{"raises if there's a parser error (5)", `{{#goodbyes}}{{/hellos}}`, "goodbyes doesn't match hellos"},
|
||||
|
||||
{"should handle invalid paths (1)", `{{foo/../bar}}`, `Invalid path: foo/..`},
|
||||
{"should handle invalid paths (2)", `{{foo/./bar}}`, `Invalid path: foo/.`},
|
||||
{"should handle invalid paths (3)", `{{foo/this/bar}}`, `Invalid path: foo/this`},
|
||||
|
||||
{"knows how to report the correct line number in errors (1)", "hello\nmy\n{{foo}", "Parse error on line 3"},
|
||||
{"knows how to report the correct line number in errors (2)", "hello\n\nmy\n\n{{foo}", "Parse error on line 5"},
|
||||
|
||||
{"knows how to report the correct line number in errors when the first character is a newline", "\n\nhello\n\nmy\n\n{{foo}", "Parse error on line 7"},
|
||||
}
|
||||
|
||||
func TestParserErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, test := range parserErrorTests {
|
||||
node, err := Parse(test.input)
|
||||
if err == nil {
|
||||
output := ast.Print(node)
|
||||
tokens := lexer.Collect(test.input)
|
||||
|
||||
t.Errorf("Test '%s' failed - Error expected\ninput:\n\t'%s'\ngot\n\t%q\ntokens:\n\t%q", test.name, test.input, output, tokens)
|
||||
} else if test.output != "" {
|
||||
matched, errMatch := regexp.MatchString(regexp.QuoteMeta(test.output), fmt.Sprint(err))
|
||||
if errMatch != nil {
|
||||
panic("Failed to match regexp")
|
||||
}
|
||||
|
||||
if !matched {
|
||||
t.Errorf("Test '%s' failed - Incorrect error returned\ninput:\n\t'%s'\nexpected\n\t%q\ngot\n\t%q", test.name, test.input, test.output, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// package example
|
||||
func Example() {
|
||||
source := "You know {{nothing}} John Snow"
|
||||
|
||||
// parse template
|
||||
program, err := Parse(source)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// print AST
|
||||
output := ast.Print(program)
|
||||
|
||||
fmt.Print(output)
|
||||
// CONTENT[ 'You know ' ]
|
||||
// {{ PATH:nothing [] }}
|
||||
// CONTENT[ ' John Snow' ]
|
||||
}
|
360
vendor/github.com/aymerick/raymond/parser/whitespace.go
generated
vendored
Normal file
360
vendor/github.com/aymerick/raymond/parser/whitespace.go
generated
vendored
Normal file
@ -0,0 +1,360 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/aymerick/raymond/ast"
|
||||
)
|
||||
|
||||
// whitespaceVisitor walks through the AST to perform whitespace control
|
||||
//
|
||||
// The logic was shamelessly borrowed from:
|
||||
// https://github.com/wycats/handlebars.js/blob/master/lib/handlebars/compiler/whitespace-control.js
|
||||
type whitespaceVisitor struct {
|
||||
isRootSeen bool
|
||||
}
|
||||
|
||||
var (
|
||||
rTrimLeft = regexp.MustCompile(`^[ \t]*\r?\n?`)
|
||||
rTrimLeftMultiple = regexp.MustCompile(`^\s+`)
|
||||
|
||||
rTrimRight = regexp.MustCompile(`[ \t]+$`)
|
||||
rTrimRightMultiple = regexp.MustCompile(`\s+$`)
|
||||
|
||||
rPrevWhitespace = regexp.MustCompile(`\r?\n\s*?$`)
|
||||
rPrevWhitespaceStart = regexp.MustCompile(`(^|\r?\n)\s*?$`)
|
||||
|
||||
rNextWhitespace = regexp.MustCompile(`^\s*?\r?\n`)
|
||||
rNextWhitespaceEnd = regexp.MustCompile(`^\s*?(\r?\n|$)`)
|
||||
|
||||
rPartialIndent = regexp.MustCompile(`([ \t]+$)`)
|
||||
)
|
||||
|
||||
// newWhitespaceVisitor instanciates a new whitespaceVisitor
|
||||
func newWhitespaceVisitor() *whitespaceVisitor {
|
||||
return &whitespaceVisitor{}
|
||||
}
|
||||
|
||||
// processWhitespaces performs whitespace control on given AST
|
||||
//
|
||||
// WARNING: It must be called only once on AST.
|
||||
func processWhitespaces(node ast.Node) {
|
||||
node.Accept(newWhitespaceVisitor())
|
||||
}
|
||||
|
||||
func omitRightFirst(body []ast.Node, multiple bool) {
|
||||
omitRight(body, -1, multiple)
|
||||
}
|
||||
|
||||
func omitRight(body []ast.Node, i int, multiple bool) {
|
||||
if i+1 >= len(body) {
|
||||
return
|
||||
}
|
||||
|
||||
current := body[i+1]
|
||||
|
||||
node, ok := current.(*ast.ContentStatement)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if !multiple && node.RightStripped {
|
||||
return
|
||||
}
|
||||
|
||||
original := node.Value
|
||||
|
||||
r := rTrimLeft
|
||||
if multiple {
|
||||
r = rTrimLeftMultiple
|
||||
}
|
||||
|
||||
node.Value = r.ReplaceAllString(node.Value, "")
|
||||
|
||||
node.RightStripped = (original != node.Value)
|
||||
}
|
||||
|
||||
func omitLeftLast(body []ast.Node, multiple bool) {
|
||||
omitLeft(body, len(body), multiple)
|
||||
}
|
||||
|
||||
func omitLeft(body []ast.Node, i int, multiple bool) bool {
|
||||
if i-1 < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
current := body[i-1]
|
||||
|
||||
node, ok := current.(*ast.ContentStatement)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if !multiple && node.LeftStripped {
|
||||
return false
|
||||
}
|
||||
|
||||
original := node.Value
|
||||
|
||||
r := rTrimRight
|
||||
if multiple {
|
||||
r = rTrimRightMultiple
|
||||
}
|
||||
|
||||
node.Value = r.ReplaceAllString(node.Value, "")
|
||||
|
||||
node.LeftStripped = (original != node.Value)
|
||||
|
||||
return node.LeftStripped
|
||||
}
|
||||
|
||||
func isPrevWhitespace(body []ast.Node) bool {
|
||||
return isPrevWhitespaceProgram(body, len(body), false)
|
||||
}
|
||||
|
||||
func isPrevWhitespaceProgram(body []ast.Node, i int, isRoot bool) bool {
|
||||
if i < 1 {
|
||||
return isRoot
|
||||
}
|
||||
|
||||
prev := body[i-1]
|
||||
|
||||
if node, ok := prev.(*ast.ContentStatement); ok {
|
||||
if (node.Value == "") && node.RightStripped {
|
||||
// already stripped, so it may be an empty string not catched by regexp
|
||||
return true
|
||||
}
|
||||
|
||||
r := rPrevWhitespaceStart
|
||||
if (i > 1) || !isRoot {
|
||||
r = rPrevWhitespace
|
||||
}
|
||||
|
||||
return r.MatchString(node.Value)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func isNextWhitespace(body []ast.Node) bool {
|
||||
return isNextWhitespaceProgram(body, -1, false)
|
||||
}
|
||||
|
||||
func isNextWhitespaceProgram(body []ast.Node, i int, isRoot bool) bool {
|
||||
if i+1 >= len(body) {
|
||||
return isRoot
|
||||
}
|
||||
|
||||
next := body[i+1]
|
||||
|
||||
if node, ok := next.(*ast.ContentStatement); ok {
|
||||
if (node.Value == "") && node.LeftStripped {
|
||||
// already stripped, so it may be an empty string not catched by regexp
|
||||
return true
|
||||
}
|
||||
|
||||
r := rNextWhitespaceEnd
|
||||
if (i+2 > len(body)) || !isRoot {
|
||||
r = rNextWhitespace
|
||||
}
|
||||
|
||||
return r.MatchString(node.Value)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
//
|
||||
// Visitor interface
|
||||
//
|
||||
|
||||
func (v *whitespaceVisitor) VisitProgram(program *ast.Program) interface{} {
|
||||
isRoot := !v.isRootSeen
|
||||
v.isRootSeen = true
|
||||
|
||||
body := program.Body
|
||||
for i, current := range body {
|
||||
strip, _ := current.Accept(v).(*ast.Strip)
|
||||
if strip == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
_isPrevWhitespace := isPrevWhitespaceProgram(body, i, isRoot)
|
||||
_isNextWhitespace := isNextWhitespaceProgram(body, i, isRoot)
|
||||
|
||||
openStandalone := strip.OpenStandalone && _isPrevWhitespace
|
||||
closeStandalone := strip.CloseStandalone && _isNextWhitespace
|
||||
inlineStandalone := strip.InlineStandalone && _isPrevWhitespace && _isNextWhitespace
|
||||
|
||||
if strip.Close {
|
||||
omitRight(body, i, true)
|
||||
}
|
||||
|
||||
if strip.Open && (i > 0) {
|
||||
omitLeft(body, i, true)
|
||||
}
|
||||
|
||||
if inlineStandalone {
|
||||
omitRight(body, i, false)
|
||||
|
||||
if omitLeft(body, i, false) {
|
||||
// If we are on a standalone node, save the indent info for partials
|
||||
if partial, ok := current.(*ast.PartialStatement); ok {
|
||||
// Pull out the whitespace from the final line
|
||||
if i > 0 {
|
||||
if prevContent, ok := body[i-1].(*ast.ContentStatement); ok {
|
||||
partial.Indent = rPartialIndent.FindString(prevContent.Original)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if b, ok := current.(*ast.BlockStatement); ok {
|
||||
if openStandalone {
|
||||
prog := b.Program
|
||||
if prog == nil {
|
||||
prog = b.Inverse
|
||||
}
|
||||
|
||||
omitRightFirst(prog.Body, false)
|
||||
|
||||
// Strip out the previous content node if it's whitespace only
|
||||
omitLeft(body, i, false)
|
||||
}
|
||||
|
||||
if closeStandalone {
|
||||
prog := b.Inverse
|
||||
if prog == nil {
|
||||
prog = b.Program
|
||||
}
|
||||
|
||||
// Always strip the next node
|
||||
omitRight(body, i, false)
|
||||
|
||||
omitLeftLast(prog.Body, false)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *whitespaceVisitor) VisitBlock(block *ast.BlockStatement) interface{} {
|
||||
if block.Program != nil {
|
||||
block.Program.Accept(v)
|
||||
}
|
||||
|
||||
if block.Inverse != nil {
|
||||
block.Inverse.Accept(v)
|
||||
}
|
||||
|
||||
program := block.Program
|
||||
inverse := block.Inverse
|
||||
|
||||
if program == nil {
|
||||
program = inverse
|
||||
inverse = nil
|
||||
}
|
||||
|
||||
firstInverse := inverse
|
||||
lastInverse := inverse
|
||||
|
||||
if (inverse != nil) && inverse.Chained {
|
||||
b, _ := inverse.Body[0].(*ast.BlockStatement)
|
||||
firstInverse = b.Program
|
||||
|
||||
for lastInverse.Chained {
|
||||
b, _ := lastInverse.Body[len(lastInverse.Body)-1].(*ast.BlockStatement)
|
||||
lastInverse = b.Program
|
||||
}
|
||||
}
|
||||
|
||||
closeProg := firstInverse
|
||||
if closeProg == nil {
|
||||
closeProg = program
|
||||
}
|
||||
|
||||
strip := &ast.Strip{
|
||||
Open: (block.OpenStrip != nil) && block.OpenStrip.Open,
|
||||
Close: (block.CloseStrip != nil) && block.CloseStrip.Close,
|
||||
|
||||
OpenStandalone: isNextWhitespace(program.Body),
|
||||
CloseStandalone: isPrevWhitespace(closeProg.Body),
|
||||
}
|
||||
|
||||
if (block.OpenStrip != nil) && block.OpenStrip.Close {
|
||||
omitRightFirst(program.Body, true)
|
||||
}
|
||||
|
||||
if inverse != nil {
|
||||
if block.InverseStrip != nil {
|
||||
inverseStrip := block.InverseStrip
|
||||
|
||||
if inverseStrip.Open {
|
||||
omitLeftLast(program.Body, true)
|
||||
}
|
||||
|
||||
if inverseStrip.Close {
|
||||
omitRightFirst(firstInverse.Body, true)
|
||||
}
|
||||
}
|
||||
|
||||
if (block.CloseStrip != nil) && block.CloseStrip.Open {
|
||||
omitLeftLast(lastInverse.Body, true)
|
||||
}
|
||||
|
||||
// Find standalone else statements
|
||||
if isPrevWhitespace(program.Body) && isNextWhitespace(firstInverse.Body) {
|
||||
omitLeftLast(program.Body, false)
|
||||
|
||||
omitRightFirst(firstInverse.Body, false)
|
||||
}
|
||||
} else if (block.CloseStrip != nil) && block.CloseStrip.Open {
|
||||
omitLeftLast(program.Body, true)
|
||||
}
|
||||
|
||||
return strip
|
||||
}
|
||||
|
||||
func (v *whitespaceVisitor) VisitMustache(mustache *ast.MustacheStatement) interface{} {
|
||||
return mustache.Strip
|
||||
}
|
||||
|
||||
func _inlineStandalone(strip *ast.Strip) interface{} {
|
||||
return &ast.Strip{
|
||||
Open: strip.Open,
|
||||
Close: strip.Close,
|
||||
InlineStandalone: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *whitespaceVisitor) VisitPartial(node *ast.PartialStatement) interface{} {
|
||||
strip := node.Strip
|
||||
if strip == nil {
|
||||
strip = &ast.Strip{}
|
||||
}
|
||||
|
||||
return _inlineStandalone(strip)
|
||||
}
|
||||
|
||||
func (v *whitespaceVisitor) VisitComment(node *ast.CommentStatement) interface{} {
|
||||
strip := node.Strip
|
||||
if strip == nil {
|
||||
strip = &ast.Strip{}
|
||||
}
|
||||
|
||||
return _inlineStandalone(strip)
|
||||
}
|
||||
|
||||
// NOOP
|
||||
func (v *whitespaceVisitor) VisitContent(node *ast.ContentStatement) interface{} { return nil }
|
||||
func (v *whitespaceVisitor) VisitExpression(node *ast.Expression) interface{} { return nil }
|
||||
func (v *whitespaceVisitor) VisitSubExpression(node *ast.SubExpression) interface{} { return nil }
|
||||
func (v *whitespaceVisitor) VisitPath(node *ast.PathExpression) interface{} { return nil }
|
||||
func (v *whitespaceVisitor) VisitString(node *ast.StringLiteral) interface{} { return nil }
|
||||
func (v *whitespaceVisitor) VisitBoolean(node *ast.BooleanLiteral) interface{} { return nil }
|
||||
func (v *whitespaceVisitor) VisitNumber(node *ast.NumberLiteral) interface{} { return nil }
|
||||
func (v *whitespaceVisitor) VisitHash(node *ast.Hash) interface{} { return nil }
|
||||
func (v *whitespaceVisitor) VisitHashPair(node *ast.HashPair) interface{} { return nil }
|
85
vendor/github.com/aymerick/raymond/partial.go
generated
vendored
Normal file
85
vendor/github.com/aymerick/raymond/partial.go
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
package raymond
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// partial represents a partial template
|
||||
type partial struct {
|
||||
name string
|
||||
source string
|
||||
tpl *Template
|
||||
}
|
||||
|
||||
// partials stores all global partials
|
||||
var partials map[string]*partial
|
||||
|
||||
// protects global partials
|
||||
var partialsMutex sync.RWMutex
|
||||
|
||||
func init() {
|
||||
partials = make(map[string]*partial)
|
||||
}
|
||||
|
||||
// newPartial instanciates a new partial
|
||||
func newPartial(name string, source string, tpl *Template) *partial {
|
||||
return &partial{
|
||||
name: name,
|
||||
source: source,
|
||||
tpl: tpl,
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterPartial registers a global partial. That partial will be available to all templates.
|
||||
func RegisterPartial(name string, source string) {
|
||||
partialsMutex.Lock()
|
||||
defer partialsMutex.Unlock()
|
||||
|
||||
if partials[name] != nil {
|
||||
panic(fmt.Errorf("Partial already registered: %s", name))
|
||||
}
|
||||
|
||||
partials[name] = newPartial(name, source, nil)
|
||||
}
|
||||
|
||||
// RegisterPartials registers several global partials. Those partials will be available to all templates.
|
||||
func RegisterPartials(partials map[string]string) {
|
||||
for name, p := range partials {
|
||||
RegisterPartial(name, p)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterPartialTemplate registers a global partial with given parsed template. That partial will be available to all templates.
|
||||
func RegisterPartialTemplate(name string, tpl *Template) {
|
||||
partialsMutex.Lock()
|
||||
defer partialsMutex.Unlock()
|
||||
|
||||
if partials[name] != nil {
|
||||
panic(fmt.Errorf("Partial already registered: %s", name))
|
||||
}
|
||||
|
||||
partials[name] = newPartial(name, "", tpl)
|
||||
}
|
||||
|
||||
// findPartial finds a registered global partial
|
||||
func findPartial(name string) *partial {
|
||||
partialsMutex.RLock()
|
||||
defer partialsMutex.RUnlock()
|
||||
|
||||
return partials[name]
|
||||
}
|
||||
|
||||
// template returns parsed partial template
|
||||
func (p *partial) template() (*Template, error) {
|
||||
if p.tpl == nil {
|
||||
var err error
|
||||
|
||||
p.tpl, err = Parse(p.source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return p.tpl, nil
|
||||
}
|
28
vendor/github.com/aymerick/raymond/raymond.go
generated
vendored
Normal file
28
vendor/github.com/aymerick/raymond/raymond.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
// Package raymond provides handlebars evaluation
|
||||
package raymond
|
||||
|
||||
// Render parses a template and evaluates it with given context
|
||||
//
|
||||
// Note that this function call is not optimal as your template is parsed everytime you call it. You should use Parse() function instead.
|
||||
func Render(source string, ctx interface{}) (string, error) {
|
||||
// parse template
|
||||
tpl, err := Parse(source)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// renders template
|
||||
str, err := tpl.Exec(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return str, nil
|
||||
}
|
||||
|
||||
// MustRender parses a template and evaluates it with given context. It panics on error.
|
||||
//
|
||||
// Note that this function call is not optimal as your template is parsed everytime you call it. You should use Parse() function instead.
|
||||
func MustRender(source string, ctx interface{}) string {
|
||||
return MustParse(source).MustExec(ctx)
|
||||
}
|
BIN
vendor/github.com/aymerick/raymond/raymond.png
generated
vendored
Normal file
BIN
vendor/github.com/aymerick/raymond/raymond.png
generated
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
115
vendor/github.com/aymerick/raymond/raymond_test.go
generated
vendored
Normal file
115
vendor/github.com/aymerick/raymond/raymond_test.go
generated
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
package raymond
|
||||
|
||||
import "fmt"
|
||||
|
||||
func Example() {
|
||||
source := "<h1>{{title}}</h1><p>{{body.content}}</p>"
|
||||
|
||||
ctx := map[string]interface{}{
|
||||
"title": "foo",
|
||||
"body": map[string]string{"content": "bar"},
|
||||
}
|
||||
|
||||
// parse template
|
||||
tpl := MustParse(source)
|
||||
|
||||
// evaluate template with context
|
||||
output := tpl.MustExec(ctx)
|
||||
|
||||
// alternatively, for one shots:
|
||||
// output := MustRender(source, ctx)
|
||||
|
||||
fmt.Print(output)
|
||||
// Output: <h1>foo</h1><p>bar</p>
|
||||
}
|
||||
|
||||
func Example_struct() {
|
||||
source := `<div class="post">
|
||||
<h1>By {{fullName author}}</h1>
|
||||
<div class="body">{{body}}</div>
|
||||
|
||||
<h1>Comments</h1>
|
||||
|
||||
{{#each comments}}
|
||||
<h2>By {{fullName author}}</h2>
|
||||
<div class="body">{{body}}</div>
|
||||
{{/each}}
|
||||
</div>`
|
||||
|
||||
type Person struct {
|
||||
FirstName string
|
||||
LastName string
|
||||
}
|
||||
|
||||
type Comment struct {
|
||||
Author Person
|
||||
Body string
|
||||
}
|
||||
|
||||
type Post struct {
|
||||
Author Person
|
||||
Body string
|
||||
Comments []Comment
|
||||
}
|
||||
|
||||
ctx := Post{
|
||||
Person{"Jean", "Valjean"},
|
||||
"Life is difficult",
|
||||
[]Comment{
|
||||
Comment{
|
||||
Person{"Marcel", "Beliveau"},
|
||||
"LOL!",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
RegisterHelper("fullName", func(person Person) string {
|
||||
return person.FirstName + " " + person.LastName
|
||||
})
|
||||
|
||||
output := MustRender(source, ctx)
|
||||
|
||||
fmt.Print(output)
|
||||
// Output: <div class="post">
|
||||
// <h1>By Jean Valjean</h1>
|
||||
// <div class="body">Life is difficult</div>
|
||||
//
|
||||
// <h1>Comments</h1>
|
||||
//
|
||||
// <h2>By Marcel Beliveau</h2>
|
||||
// <div class="body">LOL!</div>
|
||||
// </div>
|
||||
}
|
||||
|
||||
func ExampleRender() {
|
||||
tpl := "<h1>{{title}}</h1><p>{{body.content}}</p>"
|
||||
|
||||
ctx := map[string]interface{}{
|
||||
"title": "foo",
|
||||
"body": map[string]string{"content": "bar"},
|
||||
}
|
||||
|
||||
// render template with context
|
||||
output, err := Render(tpl, ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Print(output)
|
||||
// Output: <h1>foo</h1><p>bar</p>
|
||||
}
|
||||
|
||||
func ExampleMustRender() {
|
||||
tpl := "<h1>{{title}}</h1><p>{{body.content}}</p>"
|
||||
|
||||
ctx := map[string]interface{}{
|
||||
"title": "foo",
|
||||
"body": map[string]string{"content": "bar"},
|
||||
}
|
||||
|
||||
// render template with context
|
||||
output := MustRender(tpl, ctx)
|
||||
|
||||
fmt.Print(output)
|
||||
// Output: <h1>foo</h1><p>bar</p>
|
||||
}
|
84
vendor/github.com/aymerick/raymond/string.go
generated
vendored
Normal file
84
vendor/github.com/aymerick/raymond/string.go
generated
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
package raymond
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// SafeString represents a string that must not be escaped.
|
||||
//
|
||||
// A SafeString can be returned by helpers to disable escaping.
|
||||
type SafeString string
|
||||
|
||||
// isSafeString returns true if argument is a SafeString
|
||||
func isSafeString(value interface{}) bool {
|
||||
if _, ok := value.(SafeString); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Str returns string representation of any basic type value.
|
||||
func Str(value interface{}) string {
|
||||
return strValue(reflect.ValueOf(value))
|
||||
}
|
||||
|
||||
// strValue returns string representation of a reflect.Value
|
||||
func strValue(value reflect.Value) string {
|
||||
result := ""
|
||||
|
||||
ival, ok := printableValue(value)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("Can't print value: %q", value))
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(ival)
|
||||
|
||||
switch val.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
result += strValue(val.Index(i))
|
||||
}
|
||||
case reflect.Bool:
|
||||
result = "false"
|
||||
if val.Bool() {
|
||||
result = "true"
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
result = fmt.Sprintf("%d", ival)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
result = strconv.FormatFloat(val.Float(), 'f', -1, 64)
|
||||
case reflect.Invalid:
|
||||
result = ""
|
||||
default:
|
||||
result = fmt.Sprintf("%s", ival)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// printableValue returns the, possibly indirected, interface value inside v that
|
||||
// is best for a call to formatted printer.
|
||||
//
|
||||
// NOTE: borrowed from https://github.com/golang/go/tree/master/src/text/template/exec.go
|
||||
func printableValue(v reflect.Value) (interface{}, bool) {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v, _ = indirect(v) // fmt.Fprint handles nil.
|
||||
}
|
||||
if !v.IsValid() {
|
||||
return "", true
|
||||
}
|
||||
|
||||
if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) {
|
||||
if v.CanAddr() && (reflect.PtrTo(v.Type()).Implements(errorType) || reflect.PtrTo(v.Type()).Implements(fmtStringerType)) {
|
||||
v = v.Addr()
|
||||
} else {
|
||||
switch v.Kind() {
|
||||
case reflect.Chan, reflect.Func:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
}
|
||||
return v.Interface(), true
|
||||
}
|
59
vendor/github.com/aymerick/raymond/string_test.go
generated
vendored
Normal file
59
vendor/github.com/aymerick/raymond/string_test.go
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
package raymond
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type strTest struct {
|
||||
name string
|
||||
input interface{}
|
||||
output string
|
||||
}
|
||||
|
||||
var strTests = []strTest{
|
||||
{"String", "foo", "foo"},
|
||||
{"Boolean true", true, "true"},
|
||||
{"Boolean false", false, "false"},
|
||||
{"Integer", 25, "25"},
|
||||
{"Float", 25.75, "25.75"},
|
||||
{"Nil", nil, ""},
|
||||
{"[]string", []string{"foo", "bar"}, "foobar"},
|
||||
{"[]interface{} (strings)", []interface{}{"foo", "bar"}, "foobar"},
|
||||
{"[]Boolean", []bool{true, false}, "truefalse"},
|
||||
}
|
||||
|
||||
func TestStr(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, test := range strTests {
|
||||
if res := Str(test.input); res != test.output {
|
||||
t.Errorf("Failed to stringify: %s\nexpected:\n\t'%s'got:\n\t%q", test.name, test.output, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleStr() {
|
||||
output := Str(3) + " foos are " + Str(true) + " and " + Str(-1.25) + " bars are " + Str(false) + "\n"
|
||||
output += "But you know '" + Str(nil) + "' John Snow\n"
|
||||
output += "map: " + Str(map[string]string{"foo": "bar"}) + "\n"
|
||||
output += "array: " + Str([]interface{}{true, 10, "foo", 5, "bar"})
|
||||
|
||||
fmt.Println(output)
|
||||
// Output: 3 foos are true and -1.25 bars are false
|
||||
// But you know '' John Snow
|
||||
// map: map[foo:bar]
|
||||
// array: true10foo5bar
|
||||
}
|
||||
|
||||
func ExampleSafeString() {
|
||||
RegisterHelper("em", func() SafeString {
|
||||
return SafeString("<em>FOO BAR</em>")
|
||||
})
|
||||
|
||||
tpl := MustParse("{{em}}")
|
||||
|
||||
result := tpl.MustExec(nil)
|
||||
fmt.Print(result)
|
||||
// Output: <em>FOO BAR</em>
|
||||
}
|
248
vendor/github.com/aymerick/raymond/template.go
generated
vendored
Normal file
248
vendor/github.com/aymerick/raymond/template.go
generated
vendored
Normal file
@ -0,0 +1,248 @@
|
||||
package raymond
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/aymerick/raymond/ast"
|
||||
"github.com/aymerick/raymond/parser"
|
||||
)
|
||||
|
||||
// Template represents a handlebars template.
|
||||
type Template struct {
|
||||
source string
|
||||
program *ast.Program
|
||||
helpers map[string]reflect.Value
|
||||
partials map[string]*partial
|
||||
mutex sync.RWMutex // protects helpers and partials
|
||||
}
|
||||
|
||||
// newTemplate instanciate a new template without parsing it
|
||||
func newTemplate(source string) *Template {
|
||||
return &Template{
|
||||
source: source,
|
||||
helpers: make(map[string]reflect.Value),
|
||||
partials: make(map[string]*partial),
|
||||
}
|
||||
}
|
||||
|
||||
// Parse instanciates a template by parsing given source.
|
||||
func Parse(source string) (*Template, error) {
|
||||
tpl := newTemplate(source)
|
||||
|
||||
// parse template
|
||||
if err := tpl.parse(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tpl, nil
|
||||
}
|
||||
|
||||
// MustParse instanciates a template by parsing given source. It panics on error.
|
||||
func MustParse(source string) *Template {
|
||||
result, err := Parse(source)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ParseFile reads given file and returns parsed template.
|
||||
func ParseFile(filePath string) (*Template, error) {
|
||||
b, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return Parse(string(b))
|
||||
}
|
||||
|
||||
// parse parses the template
|
||||
//
|
||||
// It can be called several times, the parsing will be done only once.
|
||||
func (tpl *Template) parse() error {
|
||||
if tpl.program == nil {
|
||||
var err error
|
||||
|
||||
tpl.program, err = parser.Parse(tpl.source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone returns a copy of that template.
|
||||
func (tpl *Template) Clone() *Template {
|
||||
result := newTemplate(tpl.source)
|
||||
|
||||
result.program = tpl.program
|
||||
|
||||
tpl.mutex.RLock()
|
||||
defer tpl.mutex.RUnlock()
|
||||
|
||||
for name, helper := range tpl.helpers {
|
||||
result.RegisterHelper(name, helper.Interface())
|
||||
}
|
||||
|
||||
for name, partial := range tpl.partials {
|
||||
result.addPartial(name, partial.source, partial.tpl)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (tpl *Template) findHelper(name string) reflect.Value {
|
||||
tpl.mutex.RLock()
|
||||
defer tpl.mutex.RUnlock()
|
||||
|
||||
return tpl.helpers[name]
|
||||
}
|
||||
|
||||
// RegisterHelper registers a helper for that template.
|
||||
func (tpl *Template) RegisterHelper(name string, helper interface{}) {
|
||||
tpl.mutex.Lock()
|
||||
defer tpl.mutex.Unlock()
|
||||
|
||||
if tpl.helpers[name] != zero {
|
||||
panic(fmt.Sprintf("Helper %s already registered", name))
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(helper)
|
||||
ensureValidHelper(name, val)
|
||||
|
||||
tpl.helpers[name] = val
|
||||
}
|
||||
|
||||
// RegisterHelpers registers several helpers for that template.
|
||||
func (tpl *Template) RegisterHelpers(helpers map[string]interface{}) {
|
||||
for name, helper := range helpers {
|
||||
tpl.RegisterHelper(name, helper)
|
||||
}
|
||||
}
|
||||
|
||||
func (tpl *Template) addPartial(name string, source string, template *Template) {
|
||||
tpl.mutex.Lock()
|
||||
defer tpl.mutex.Unlock()
|
||||
|
||||
if tpl.partials[name] != nil {
|
||||
panic(fmt.Sprintf("Partial %s already registered", name))
|
||||
}
|
||||
|
||||
tpl.partials[name] = newPartial(name, source, template)
|
||||
}
|
||||
|
||||
func (tpl *Template) findPartial(name string) *partial {
|
||||
tpl.mutex.RLock()
|
||||
defer tpl.mutex.RUnlock()
|
||||
|
||||
return tpl.partials[name]
|
||||
}
|
||||
|
||||
// RegisterPartial registers a partial for that template.
|
||||
func (tpl *Template) RegisterPartial(name string, source string) {
|
||||
tpl.addPartial(name, source, nil)
|
||||
}
|
||||
|
||||
// RegisterPartials registers several partials for that template.
|
||||
func (tpl *Template) RegisterPartials(partials map[string]string) {
|
||||
for name, partial := range partials {
|
||||
tpl.RegisterPartial(name, partial)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterPartialFile reads given file and registers its content as a partial with given name.
|
||||
func (tpl *Template) RegisterPartialFile(filePath string, name string) error {
|
||||
b, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tpl.RegisterPartial(name, string(b))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterPartialFiles reads several files and registers them as partials, the filename base is used as the partial name.
|
||||
func (tpl *Template) RegisterPartialFiles(filePaths ...string) error {
|
||||
if len(filePaths) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, filePath := range filePaths {
|
||||
name := fileBase(filePath)
|
||||
|
||||
if err := tpl.RegisterPartialFile(filePath, name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterPartialTemplate registers an already parsed partial for that template.
|
||||
func (tpl *Template) RegisterPartialTemplate(name string, template *Template) {
|
||||
tpl.addPartial(name, "", template)
|
||||
}
|
||||
|
||||
// Exec evaluates template with given context.
|
||||
func (tpl *Template) Exec(ctx interface{}) (result string, err error) {
|
||||
return tpl.ExecWith(ctx, nil)
|
||||
}
|
||||
|
||||
// MustExec evaluates template with given context. It panics on error.
|
||||
func (tpl *Template) MustExec(ctx interface{}) string {
|
||||
result, err := tpl.Exec(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ExecWith evaluates template with given context and private data frame.
|
||||
func (tpl *Template) ExecWith(ctx interface{}, privData *DataFrame) (result string, err error) {
|
||||
defer errRecover(&err)
|
||||
|
||||
// parses template if necessary
|
||||
err = tpl.parse()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// setup visitor
|
||||
v := newEvalVisitor(tpl, ctx, privData)
|
||||
|
||||
// visit AST
|
||||
result, _ = tpl.program.Accept(v).(string)
|
||||
|
||||
// named return values
|
||||
return
|
||||
}
|
||||
|
||||
// errRecover recovers evaluation panic
|
||||
func errRecover(errp *error) {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
switch err := e.(type) {
|
||||
case runtime.Error:
|
||||
panic(e)
|
||||
case error:
|
||||
*errp = err
|
||||
default:
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PrintAST returns string representation of parsed template.
|
||||
func (tpl *Template) PrintAST() string {
|
||||
if err := tpl.parse(); err != nil {
|
||||
return fmt.Sprintf("PARSER ERROR: %s", err)
|
||||
}
|
||||
|
||||
return ast.Print(tpl.program)
|
||||
}
|
166
vendor/github.com/aymerick/raymond/template_test.go
generated
vendored
Normal file
166
vendor/github.com/aymerick/raymond/template_test.go
generated
vendored
Normal file
@ -0,0 +1,166 @@
|
||||
package raymond
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var sourceBasic = `<div class="entry">
|
||||
<h1>{{title}}</h1>
|
||||
<div class="body">
|
||||
{{body}}
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
var basicAST = `CONTENT[ '<div class="entry">
|
||||
<h1>' ]
|
||||
{{ PATH:title [] }}
|
||||
CONTENT[ '</h1>
|
||||
<div class="body">
|
||||
' ]
|
||||
{{ PATH:body [] }}
|
||||
CONTENT[ '
|
||||
</div>
|
||||
</div>' ]
|
||||
`
|
||||
|
||||
func TestNewTemplate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tpl := newTemplate(sourceBasic)
|
||||
if tpl.source != sourceBasic {
|
||||
t.Errorf("Failed to instantiate template")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tpl, err := Parse(sourceBasic)
|
||||
if err != nil || (tpl.source != sourceBasic) {
|
||||
t.Errorf("Failed to parse template")
|
||||
}
|
||||
|
||||
if str := tpl.PrintAST(); str != basicAST {
|
||||
t.Errorf("Template parsing incorrect: %s", str)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClone(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
sourcePartial := `I am a {{wat}} partial`
|
||||
sourcePartial2 := `Partial for the {{wat}}`
|
||||
|
||||
tpl := MustParse(sourceBasic)
|
||||
tpl.RegisterPartial("p", sourcePartial)
|
||||
|
||||
if (len(tpl.partials) != 1) || (tpl.partials["p"] == nil) {
|
||||
t.Errorf("What?")
|
||||
}
|
||||
|
||||
cloned := tpl.Clone()
|
||||
|
||||
if (len(cloned.partials) != 1) || (cloned.partials["p"] == nil) {
|
||||
t.Errorf("Template partials must be cloned")
|
||||
}
|
||||
|
||||
cloned.RegisterPartial("p2", sourcePartial2)
|
||||
|
||||
if (len(cloned.partials) != 2) || (cloned.partials["p"] == nil) || (cloned.partials["p2"] == nil) {
|
||||
t.Errorf("Failed to register a partial on cloned template")
|
||||
}
|
||||
|
||||
if (len(tpl.partials) != 1) || (tpl.partials["p"] == nil) {
|
||||
t.Errorf("Modification of a cloned template MUST NOT affect original template")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleTemplate_Exec() {
|
||||
source := "<h1>{{title}}</h1><p>{{body.content}}</p>"
|
||||
|
||||
ctx := map[string]interface{}{
|
||||
"title": "foo",
|
||||
"body": map[string]string{"content": "bar"},
|
||||
}
|
||||
|
||||
// parse template
|
||||
tpl := MustParse(source)
|
||||
|
||||
// evaluate template with context
|
||||
output, err := tpl.Exec(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Print(output)
|
||||
// Output: <h1>foo</h1><p>bar</p>
|
||||
}
|
||||
|
||||
func ExampleTemplate_MustExec() {
|
||||
source := "<h1>{{title}}</h1><p>{{body.content}}</p>"
|
||||
|
||||
ctx := map[string]interface{}{
|
||||
"title": "foo",
|
||||
"body": map[string]string{"content": "bar"},
|
||||
}
|
||||
|
||||
// parse template
|
||||
tpl := MustParse(source)
|
||||
|
||||
// evaluate template with context
|
||||
output := tpl.MustExec(ctx)
|
||||
|
||||
fmt.Print(output)
|
||||
// Output: <h1>foo</h1><p>bar</p>
|
||||
}
|
||||
|
||||
func ExampleTemplate_ExecWith() {
|
||||
source := "<h1>{{title}}</h1><p>{{#body}}{{content}} and {{@baz.bat}}{{/body}}</p>"
|
||||
|
||||
ctx := map[string]interface{}{
|
||||
"title": "foo",
|
||||
"body": map[string]string{"content": "bar"},
|
||||
}
|
||||
|
||||
// parse template
|
||||
tpl := MustParse(source)
|
||||
|
||||
// computes private data frame
|
||||
frame := NewDataFrame()
|
||||
frame.Set("baz", map[string]string{"bat": "unicorns"})
|
||||
|
||||
// evaluate template
|
||||
output, err := tpl.ExecWith(ctx, frame)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Print(output)
|
||||
// Output: <h1>foo</h1><p>bar and unicorns</p>
|
||||
}
|
||||
|
||||
func ExampleTemplate_PrintAST() {
|
||||
source := "<h1>{{title}}</h1><p>{{#body}}{{content}} and {{@baz.bat}}{{/body}}</p>"
|
||||
|
||||
// parse template
|
||||
tpl := MustParse(source)
|
||||
|
||||
// print AST
|
||||
output := tpl.PrintAST()
|
||||
|
||||
fmt.Print(output)
|
||||
// Output: CONTENT[ '<h1>' ]
|
||||
// {{ PATH:title [] }}
|
||||
// CONTENT[ '</h1><p>' ]
|
||||
// BLOCK:
|
||||
// PATH:body []
|
||||
// PROGRAM:
|
||||
// {{ PATH:content []
|
||||
// }}
|
||||
// CONTENT[ ' and ' ]
|
||||
// {{ @PATH:baz/bat []
|
||||
// }}
|
||||
// CONTENT[ '</p>' ]
|
||||
//
|
||||
}
|
85
vendor/github.com/aymerick/raymond/utils.go
generated
vendored
Normal file
85
vendor/github.com/aymerick/raymond/utils.go
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
package raymond
|
||||
|
||||
import (
|
||||
"path"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// indirect returns the item at the end of indirection, and a bool to indicate if it's nil.
|
||||
// We indirect through pointers and empty interfaces (only) because
|
||||
// non-empty interfaces have methods we might need.
|
||||
//
|
||||
// NOTE: borrowed from https://github.com/golang/go/tree/master/src/text/template/exec.go
|
||||
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
|
||||
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
|
||||
if v.IsNil() {
|
||||
return v, true
|
||||
}
|
||||
if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return v, false
|
||||
}
|
||||
|
||||
// IsTrue returns true if obj is a truthy value.
|
||||
func IsTrue(obj interface{}) bool {
|
||||
thruth, ok := isTrueValue(reflect.ValueOf(obj))
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return thruth
|
||||
}
|
||||
|
||||
// isTrueValue reports whether the value is 'true', in the sense of not the zero of its type,
|
||||
// and whether the value has a meaningful truth value
|
||||
//
|
||||
// NOTE: borrowed from https://github.com/golang/go/tree/master/src/text/template/exec.go
|
||||
func isTrueValue(val reflect.Value) (truth, ok bool) {
|
||||
if !val.IsValid() {
|
||||
// Something like var x interface{}, never set. It's a form of nil.
|
||||
return false, true
|
||||
}
|
||||
switch val.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
truth = val.Len() > 0
|
||||
case reflect.Bool:
|
||||
truth = val.Bool()
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
truth = val.Complex() != 0
|
||||
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface:
|
||||
truth = !val.IsNil()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
truth = val.Int() != 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
truth = val.Float() != 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
truth = val.Uint() != 0
|
||||
case reflect.Struct:
|
||||
truth = true // Struct values are always true.
|
||||
default:
|
||||
return
|
||||
}
|
||||
return truth, true
|
||||
}
|
||||
|
||||
// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
|
||||
//
|
||||
// NOTE: borrowed from https://github.com/golang/go/tree/master/src/text/template/exec.go
|
||||
func canBeNil(typ reflect.Type) bool {
|
||||
switch typ.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// fileBase returns base file name
|
||||
//
|
||||
// example: /foo/bar/baz.png => baz
|
||||
func fileBase(filePath string) string {
|
||||
fileName := path.Base(filePath)
|
||||
fileExt := path.Ext(filePath)
|
||||
|
||||
return fileName[:len(fileName)-len(fileExt)]
|
||||
}
|
51
vendor/github.com/aymerick/raymond/utils_test.go
generated
vendored
Normal file
51
vendor/github.com/aymerick/raymond/utils_test.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
package raymond
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleIsTrue() {
|
||||
output := "Empty array: " + Str(IsTrue([0]string{})) + "\n"
|
||||
output += "Non empty array: " + Str(IsTrue([1]string{"foo"})) + "\n"
|
||||
|
||||
output += "Empty slice: " + Str(IsTrue([]string{})) + "\n"
|
||||
output += "Non empty slice: " + Str(IsTrue([]string{"foo"})) + "\n"
|
||||
|
||||
output += "Empty map: " + Str(IsTrue(map[string]string{})) + "\n"
|
||||
output += "Non empty map: " + Str(IsTrue(map[string]string{"foo": "bar"})) + "\n"
|
||||
|
||||
output += "Empty string: " + Str(IsTrue("")) + "\n"
|
||||
output += "Non empty string: " + Str(IsTrue("foo")) + "\n"
|
||||
|
||||
output += "true bool: " + Str(IsTrue(true)) + "\n"
|
||||
output += "false bool: " + Str(IsTrue(false)) + "\n"
|
||||
|
||||
output += "0 integer: " + Str(IsTrue(0)) + "\n"
|
||||
output += "positive integer: " + Str(IsTrue(10)) + "\n"
|
||||
output += "negative integer: " + Str(IsTrue(-10)) + "\n"
|
||||
|
||||
output += "0 float: " + Str(IsTrue(0.0)) + "\n"
|
||||
output += "positive float: " + Str(IsTrue(10.0)) + "\n"
|
||||
output += "negative integer: " + Str(IsTrue(-10.0)) + "\n"
|
||||
|
||||
output += "struct: " + Str(IsTrue(struct{}{})) + "\n"
|
||||
output += "nil: " + Str(IsTrue(nil)) + "\n"
|
||||
|
||||
fmt.Println(output)
|
||||
// Output: Empty array: false
|
||||
// Non empty array: true
|
||||
// Empty slice: false
|
||||
// Non empty slice: true
|
||||
// Empty map: false
|
||||
// Non empty map: true
|
||||
// Empty string: false
|
||||
// Non empty string: true
|
||||
// true bool: true
|
||||
// false bool: false
|
||||
// 0 integer: false
|
||||
// positive integer: true
|
||||
// negative integer: true
|
||||
// 0 float: false
|
||||
// positive float: true
|
||||
// negative integer: true
|
||||
// struct: true
|
||||
// nil: false
|
||||
}
|
24
vendor/github.com/matrix-org/gomatrix/.gitignore
generated
vendored
Normal file
24
vendor/github.com/matrix-org/gomatrix/.gitignore
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# 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
|
9
vendor/github.com/matrix-org/gomatrix/.travis.yml
generated
vendored
Normal file
9
vendor/github.com/matrix-org/gomatrix/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.8
|
||||
install:
|
||||
- go get github.com/golang/lint/golint
|
||||
- go get github.com/fzipp/gocyclo
|
||||
- go get github.com/client9/misspell/...
|
||||
- go get github.com/gordonklaus/ineffassign
|
||||
script: ./hooks/pre-commit
|
201
vendor/github.com/matrix-org/gomatrix/LICENSE
generated
vendored
Normal file
201
vendor/github.com/matrix-org/gomatrix/LICENSE
generated
vendored
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
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.
|
6
vendor/github.com/matrix-org/gomatrix/README.md
generated
vendored
Normal file
6
vendor/github.com/matrix-org/gomatrix/README.md
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
# gomatrix
|
||||
[![GoDoc](https://godoc.org/github.com/matrix-org/gomatrix?status.svg)](https://godoc.org/github.com/matrix-org/gomatrix)
|
||||
|
||||
A Golang Matrix client.
|
||||
|
||||
**THIS IS UNDER ACTIVE DEVELOPMENT: BREAKING CHANGES ARE FREQUENT.**
|
703
vendor/github.com/matrix-org/gomatrix/client.go
generated
vendored
Normal file
703
vendor/github.com/matrix-org/gomatrix/client.go
generated
vendored
Normal file
@ -0,0 +1,703 @@
|
||||
// Package gomatrix implements the Matrix Client-Server API.
|
||||
//
|
||||
// Specification can be found at http://matrix.org/docs/spec/client_server/r0.2.0.html
|
||||
package gomatrix
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Client represents a Matrix client.
|
||||
type Client struct {
|
||||
HomeserverURL *url.URL // The base homeserver URL
|
||||
Prefix string // The API prefix eg '/_matrix/client/r0'
|
||||
UserID string // The user ID of the client. Used for forming HTTP paths which use the client's user ID.
|
||||
AccessToken string // The access_token for the client.
|
||||
Client *http.Client // The underlying HTTP client which will be used to make HTTP requests.
|
||||
Syncer Syncer // The thing which can process /sync responses
|
||||
Store Storer // The thing which can store rooms/tokens/ids
|
||||
|
||||
// The ?user_id= query parameter for application services. This must be set *prior* to calling a method. If this is empty,
|
||||
// no user_id parameter will be sent.
|
||||
// See http://matrix.org/docs/spec/application_service/unstable.html#identity-assertion
|
||||
AppServiceUserID string
|
||||
|
||||
syncingMutex sync.Mutex // protects syncingID
|
||||
syncingID uint32 // Identifies the current Sync. Only one Sync can be active at any given time.
|
||||
}
|
||||
|
||||
// HTTPError An HTTP Error response, which may wrap an underlying native Go Error.
|
||||
type HTTPError struct {
|
||||
WrappedError error
|
||||
Message string
|
||||
Code int
|
||||
}
|
||||
|
||||
func (e HTTPError) Error() string {
|
||||
var wrappedErrMsg string
|
||||
if e.WrappedError != nil {
|
||||
wrappedErrMsg = e.WrappedError.Error()
|
||||
}
|
||||
return fmt.Sprintf("msg=%s code=%d wrapped=%s", e.Message, e.Code, wrappedErrMsg)
|
||||
}
|
||||
|
||||
// BuildURL builds a URL with the Client's homserver/prefix/access_token set already.
|
||||
func (cli *Client) BuildURL(urlPath ...string) string {
|
||||
ps := []string{cli.Prefix}
|
||||
for _, p := range urlPath {
|
||||
ps = append(ps, p)
|
||||
}
|
||||
return cli.BuildBaseURL(ps...)
|
||||
}
|
||||
|
||||
// BuildBaseURL builds a URL with the Client's homeserver/access_token set already. You must
|
||||
// supply the prefix in the path.
|
||||
func (cli *Client) BuildBaseURL(urlPath ...string) string {
|
||||
// copy the URL. Purposefully ignore error as the input is from a valid URL already
|
||||
hsURL, _ := url.Parse(cli.HomeserverURL.String())
|
||||
parts := []string{hsURL.Path}
|
||||
parts = append(parts, urlPath...)
|
||||
hsURL.Path = path.Join(parts...)
|
||||
query := hsURL.Query()
|
||||
if cli.AccessToken != "" {
|
||||
query.Set("access_token", cli.AccessToken)
|
||||
}
|
||||
if cli.AppServiceUserID != "" {
|
||||
query.Set("user_id", cli.AppServiceUserID)
|
||||
}
|
||||
hsURL.RawQuery = query.Encode()
|
||||
return hsURL.String()
|
||||
}
|
||||
|
||||
// BuildURLWithQuery builds a URL with query parameters in addition to the Client's homeserver/prefix/access_token set already.
|
||||
func (cli *Client) BuildURLWithQuery(urlPath []string, urlQuery map[string]string) string {
|
||||
u, _ := url.Parse(cli.BuildURL(urlPath...))
|
||||
q := u.Query()
|
||||
for k, v := range urlQuery {
|
||||
q.Set(k, v)
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// SetCredentials sets the user ID and access token on this client instance.
|
||||
func (cli *Client) SetCredentials(userID, accessToken string) {
|
||||
cli.AccessToken = accessToken
|
||||
cli.UserID = userID
|
||||
}
|
||||
|
||||
// ClearCredentials removes the user ID and access token on this client instance.
|
||||
func (cli *Client) ClearCredentials() {
|
||||
cli.AccessToken = ""
|
||||
cli.UserID = ""
|
||||
}
|
||||
|
||||
// Sync starts syncing with the provided Homeserver. If Sync() is called twice then the first sync will be stopped and the
|
||||
// error will be nil.
|
||||
//
|
||||
// This function will block until a fatal /sync error occurs, so it should almost always be started as a new goroutine.
|
||||
// Fatal sync errors can be caused by:
|
||||
// - The failure to create a filter.
|
||||
// - Client.Syncer.OnFailedSync returning an error in response to a failed sync.
|
||||
// - Client.Syncer.ProcessResponse returning an error.
|
||||
// If you wish to continue retrying in spite of these fatal errors, call Sync() again.
|
||||
func (cli *Client) Sync() error {
|
||||
// Mark the client as syncing.
|
||||
// We will keep syncing until the syncing state changes. Either because
|
||||
// Sync is called or StopSync is called.
|
||||
syncingID := cli.incrementSyncingID()
|
||||
nextBatch := cli.Store.LoadNextBatch(cli.UserID)
|
||||
filterID := cli.Store.LoadFilterID(cli.UserID)
|
||||
if filterID == "" {
|
||||
filterJSON := cli.Syncer.GetFilterJSON(cli.UserID)
|
||||
resFilter, err := cli.CreateFilter(filterJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filterID = resFilter.FilterID
|
||||
cli.Store.SaveFilterID(cli.UserID, filterID)
|
||||
}
|
||||
|
||||
for {
|
||||
resSync, err := cli.SyncRequest(30000, nextBatch, filterID, false, "")
|
||||
if err != nil {
|
||||
duration, err2 := cli.Syncer.OnFailedSync(resSync, err)
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
time.Sleep(duration)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check that the syncing state hasn't changed
|
||||
// Either because we've stopped syncing or another sync has been started.
|
||||
// We discard the response from our sync.
|
||||
if cli.getSyncingID() != syncingID {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save the token now *before* processing it. This means it's possible
|
||||
// to not process some events, but it means that we won't get constantly stuck processing
|
||||
// a malformed/buggy event which keeps making us panic.
|
||||
cli.Store.SaveNextBatch(cli.UserID, resSync.NextBatch)
|
||||
if err = cli.Syncer.ProcessResponse(resSync, nextBatch); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nextBatch = resSync.NextBatch
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) incrementSyncingID() uint32 {
|
||||
cli.syncingMutex.Lock()
|
||||
defer cli.syncingMutex.Unlock()
|
||||
cli.syncingID++
|
||||
return cli.syncingID
|
||||
}
|
||||
|
||||
func (cli *Client) getSyncingID() uint32 {
|
||||
cli.syncingMutex.Lock()
|
||||
defer cli.syncingMutex.Unlock()
|
||||
return cli.syncingID
|
||||
}
|
||||
|
||||
// StopSync stops the ongoing sync started by Sync.
|
||||
func (cli *Client) StopSync() {
|
||||
// Advance the syncing state so that any running Syncs will terminate.
|
||||
cli.incrementSyncingID()
|
||||
}
|
||||
|
||||
// MakeRequest makes a JSON HTTP request to the given URL.
|
||||
// If "resBody" is not nil, the response body will be json.Unmarshalled into it.
|
||||
//
|
||||
// Returns the HTTP body as bytes on 2xx with a nil error. Returns an error if the response is not 2xx along
|
||||
// with the HTTP body bytes if it got that far. This error is an HTTPError which includes the returned
|
||||
// HTTP status code and possibly a RespError as the WrappedError, if the HTTP body could be decoded as a RespError.
|
||||
func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{}, resBody interface{}) ([]byte, error) {
|
||||
var req *http.Request
|
||||
var err error
|
||||
if reqBody != nil {
|
||||
var jsonStr []byte
|
||||
jsonStr, err = json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err = http.NewRequest(method, httpURL, bytes.NewBuffer(jsonStr))
|
||||
} else {
|
||||
req, err = http.NewRequest(method, httpURL, nil)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
res, err := cli.Client.Do(req)
|
||||
if res != nil {
|
||||
defer res.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contents, err := ioutil.ReadAll(res.Body)
|
||||
if res.StatusCode/100 != 2 { // not 2xx
|
||||
var wrap error
|
||||
var respErr RespError
|
||||
if _ = json.Unmarshal(contents, &respErr); respErr.ErrCode != "" {
|
||||
wrap = respErr
|
||||
}
|
||||
|
||||
// If we failed to decode as RespError, don't just drop the HTTP body, include it in the
|
||||
// HTTP error instead (e.g proxy errors which return HTML).
|
||||
msg := "Failed to " + method + " JSON to " + req.URL.Path
|
||||
if wrap == nil {
|
||||
msg = msg + ": " + string(contents)
|
||||
}
|
||||
|
||||
return contents, HTTPError{
|
||||
Code: res.StatusCode,
|
||||
Message: msg,
|
||||
WrappedError: wrap,
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody != nil {
|
||||
if err = json.Unmarshal(contents, &resBody); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return contents, nil
|
||||
}
|
||||
|
||||
// CreateFilter makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-user-userid-filter
|
||||
func (cli *Client) CreateFilter(filter json.RawMessage) (resp *RespCreateFilter, err error) {
|
||||
urlPath := cli.BuildURL("user", cli.UserID, "filter")
|
||||
_, err = cli.MakeRequest("POST", urlPath, &filter, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// SyncRequest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync
|
||||
func (cli *Client) SyncRequest(timeout int, since, filterID string, fullState bool, setPresence string) (resp *RespSync, err error) {
|
||||
query := map[string]string{
|
||||
"timeout": strconv.Itoa(timeout),
|
||||
}
|
||||
if since != "" {
|
||||
query["since"] = since
|
||||
}
|
||||
if filterID != "" {
|
||||
query["filter"] = filterID
|
||||
}
|
||||
if setPresence != "" {
|
||||
query["set_presence"] = setPresence
|
||||
}
|
||||
if fullState {
|
||||
query["full_state"] = "true"
|
||||
}
|
||||
urlPath := cli.BuildURLWithQuery([]string{"sync"}, query)
|
||||
_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
func (cli *Client) register(u string, req *ReqRegister) (resp *RespRegister, uiaResp *RespUserInteractive, err error) {
|
||||
var bodyBytes []byte
|
||||
bodyBytes, err = cli.MakeRequest("POST", u, req, nil)
|
||||
if err != nil {
|
||||
httpErr, ok := err.(HTTPError)
|
||||
if !ok { // network error
|
||||
return
|
||||
}
|
||||
if httpErr.Code == 401 {
|
||||
// body should be RespUserInteractive, if it isn't, fail with the error
|
||||
err = json.Unmarshal(bodyBytes, &uiaResp)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
// body should be RespRegister
|
||||
err = json.Unmarshal(bodyBytes, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// Register makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
|
||||
//
|
||||
// Registers with kind=user. For kind=guest, see RegisterGuest.
|
||||
func (cli *Client) Register(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) {
|
||||
u := cli.BuildURL("register")
|
||||
return cli.register(u, req)
|
||||
}
|
||||
|
||||
// RegisterGuest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
|
||||
// with kind=guest.
|
||||
//
|
||||
// For kind=user, see Register.
|
||||
func (cli *Client) RegisterGuest(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) {
|
||||
query := map[string]string{
|
||||
"kind": "guest",
|
||||
}
|
||||
u := cli.BuildURLWithQuery([]string{"register"}, query)
|
||||
return cli.register(u, req)
|
||||
}
|
||||
|
||||
// RegisterDummy performs m.login.dummy registration according to https://matrix.org/docs/spec/client_server/r0.2.0.html#dummy-auth
|
||||
//
|
||||
// Only a username and password need to be provided on the ReqRegister struct. Most local/developer homeservers will allow registration
|
||||
// this way. If the homeserver does not, an error is returned.
|
||||
//
|
||||
// This does not set credentials on the client instance. See SetCredentials() instead.
|
||||
//
|
||||
// res, err := cli.RegisterDummy(&gomatrix.ReqRegister{
|
||||
// Username: "alice",
|
||||
// Password: "wonderland",
|
||||
// })
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// token := res.AccessToken
|
||||
func (cli *Client) RegisterDummy(req *ReqRegister) (*RespRegister, error) {
|
||||
res, uia, err := cli.Register(req)
|
||||
if err != nil && uia == nil {
|
||||
return nil, err
|
||||
}
|
||||
if uia != nil && uia.HasSingleStageFlow("m.login.dummy") {
|
||||
req.Auth = struct {
|
||||
Type string `json:"type"`
|
||||
Session string `json:"session,omitempty"`
|
||||
}{"m.login.dummy", uia.Session}
|
||||
res, _, err = cli.Register(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if res == nil {
|
||||
return nil, fmt.Errorf("registration failed: does this server support m.login.dummy?")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Login a user to the homeserver according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login
|
||||
// This does not set credentials on this client instance. See SetCredentials() instead.
|
||||
func (cli *Client) Login(req *ReqLogin) (resp *RespLogin, err error) {
|
||||
urlPath := cli.BuildURL("login")
|
||||
_, err = cli.MakeRequest("POST", urlPath, req, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// Logout the current user. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-logout
|
||||
// This does not clear the credentials from the client instance. See ClearCredentials() instead.
|
||||
func (cli *Client) Logout() (resp *RespLogout, err error) {
|
||||
urlPath := cli.BuildURL("logout")
|
||||
_, err = cli.MakeRequest("POST", urlPath, nil, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// Versions returns the list of supported Matrix versions on this homeserver. See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-versions
|
||||
func (cli *Client) Versions() (resp *RespVersions, err error) {
|
||||
urlPath := cli.BuildBaseURL("_matrix", "client", "versions")
|
||||
_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// JoinRoom joins the client to a room ID or alias. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-join-roomidoralias
|
||||
//
|
||||
// If serverName is specified, this will be added as a query param to instruct the homeserver to join via that server. If content is specified, it will
|
||||
// be JSON encoded and used as the request body.
|
||||
func (cli *Client) JoinRoom(roomIDorAlias, serverName string, content interface{}) (resp *RespJoinRoom, err error) {
|
||||
var urlPath string
|
||||
if serverName != "" {
|
||||
urlPath = cli.BuildURLWithQuery([]string{"join", roomIDorAlias}, map[string]string{
|
||||
"server_name": serverName,
|
||||
})
|
||||
} else {
|
||||
urlPath = cli.BuildURL("join", roomIDorAlias)
|
||||
}
|
||||
_, err = cli.MakeRequest("POST", urlPath, content, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// GetDisplayName returns the display name of the user from the specified MXID. See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname
|
||||
func (cli *Client) GetDisplayName(mxid string) (resp *RespUserDisplayName, err error) {
|
||||
urlPath := cli.BuildURL("profile", mxid, "displayname")
|
||||
_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// GetOwnDisplayName returns the user's display name. See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname
|
||||
func (cli *Client) GetOwnDisplayName() (resp *RespUserDisplayName, err error) {
|
||||
urlPath := cli.BuildURL("profile", cli.UserID, "displayname")
|
||||
_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// SetDisplayName sets the user's profile display name. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-profile-userid-displayname
|
||||
func (cli *Client) SetDisplayName(displayName string) (err error) {
|
||||
urlPath := cli.BuildURL("profile", cli.UserID, "displayname")
|
||||
s := struct {
|
||||
DisplayName string `json:"displayname"`
|
||||
}{displayName}
|
||||
_, err = cli.MakeRequest("PUT", urlPath, &s, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// GetAvatarURL gets the user's avatar URL. See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-avatar-url
|
||||
func (cli *Client) GetAvatarURL() (url string, err error) {
|
||||
urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url")
|
||||
s := struct {
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
}{}
|
||||
|
||||
_, err = cli.MakeRequest("GET", urlPath, nil, &s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return s.AvatarURL, nil
|
||||
}
|
||||
|
||||
// SetAvatarURL sets the user's avatar URL. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-profile-userid-avatar-url
|
||||
func (cli *Client) SetAvatarURL(url string) (err error) {
|
||||
urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url")
|
||||
s := struct {
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
}{url}
|
||||
_, err = cli.MakeRequest("PUT", urlPath, &s, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendMessageEvent sends a message event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
|
||||
// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
|
||||
func (cli *Client) SendMessageEvent(roomID string, eventType string, contentJSON interface{}) (resp *RespSendEvent, err error) {
|
||||
txnID := txnID()
|
||||
urlPath := cli.BuildURL("rooms", roomID, "send", eventType, txnID)
|
||||
_, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// SendStateEvent sends a state event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-state-eventtype-statekey
|
||||
// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
|
||||
func (cli *Client) SendStateEvent(roomID, eventType, stateKey string, contentJSON interface{}) (resp *RespSendEvent, err error) {
|
||||
urlPath := cli.BuildURL("rooms", roomID, "state", eventType, stateKey)
|
||||
_, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// SendText sends an m.room.message event into the given room with a msgtype of m.text
|
||||
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-text
|
||||
func (cli *Client) SendText(roomID, text string) (*RespSendEvent, error) {
|
||||
return cli.SendMessageEvent(roomID, "m.room.message",
|
||||
TextMessage{"m.text", text})
|
||||
}
|
||||
|
||||
// SendImage sends an m.room.message event into the given room with a msgtype of m.image
|
||||
// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-image
|
||||
func (cli *Client) SendImage(roomID, body, url string) (*RespSendEvent, error) {
|
||||
return cli.SendMessageEvent(roomID, "m.room.message",
|
||||
ImageMessage{
|
||||
MsgType: "m.image",
|
||||
Body: body,
|
||||
URL: url,
|
||||
})
|
||||
}
|
||||
|
||||
// SendVideo sends an m.room.message event into the given room with a msgtype of m.video
|
||||
// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-video
|
||||
func (cli *Client) SendVideo(roomID, body, url string) (*RespSendEvent, error) {
|
||||
return cli.SendMessageEvent(roomID, "m.room.message",
|
||||
VideoMessage{
|
||||
MsgType: "m.video",
|
||||
Body: body,
|
||||
URL: url,
|
||||
})
|
||||
}
|
||||
|
||||
// SendNotice sends an m.room.message event into the given room with a msgtype of m.notice
|
||||
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-notice
|
||||
func (cli *Client) SendNotice(roomID, text string) (*RespSendEvent, error) {
|
||||
return cli.SendMessageEvent(roomID, "m.room.message",
|
||||
TextMessage{"m.notice", text})
|
||||
}
|
||||
|
||||
// RedactEvent redacts the given event. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid
|
||||
func (cli *Client) RedactEvent(roomID, eventID string, req *ReqRedact) (resp *RespSendEvent, err error) {
|
||||
txnID := txnID()
|
||||
urlPath := cli.BuildURL("rooms", roomID, "redact", eventID, txnID)
|
||||
_, err = cli.MakeRequest("PUT", urlPath, req, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// CreateRoom creates a new Matrix room. See https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
|
||||
// resp, err := cli.CreateRoom(&gomatrix.ReqCreateRoom{
|
||||
// Preset: "public_chat",
|
||||
// })
|
||||
// fmt.Println("Room:", resp.RoomID)
|
||||
func (cli *Client) CreateRoom(req *ReqCreateRoom) (resp *RespCreateRoom, err error) {
|
||||
urlPath := cli.BuildURL("createRoom")
|
||||
_, err = cli.MakeRequest("POST", urlPath, req, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// LeaveRoom leaves the given room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-leave
|
||||
func (cli *Client) LeaveRoom(roomID string) (resp *RespLeaveRoom, err error) {
|
||||
u := cli.BuildURL("rooms", roomID, "leave")
|
||||
_, err = cli.MakeRequest("POST", u, struct{}{}, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// ForgetRoom forgets a room entirely. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-forget
|
||||
func (cli *Client) ForgetRoom(roomID string) (resp *RespForgetRoom, err error) {
|
||||
u := cli.BuildURL("rooms", roomID, "forget")
|
||||
_, err = cli.MakeRequest("POST", u, struct{}{}, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// InviteUser invites a user to a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite
|
||||
func (cli *Client) InviteUser(roomID string, req *ReqInviteUser) (resp *RespInviteUser, err error) {
|
||||
u := cli.BuildURL("rooms", roomID, "invite")
|
||||
_, err = cli.MakeRequest("POST", u, struct{}{}, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// InviteUserByThirdParty invites a third-party identifier to a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#invite-by-third-party-id-endpoint
|
||||
func (cli *Client) InviteUserByThirdParty(roomID string, req *ReqInvite3PID) (resp *RespInviteUser, err error) {
|
||||
u := cli.BuildURL("rooms", roomID, "invite")
|
||||
_, err = cli.MakeRequest("POST", u, req, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// KickUser kicks a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick
|
||||
func (cli *Client) KickUser(roomID string, req *ReqKickUser) (resp *RespKickUser, err error) {
|
||||
u := cli.BuildURL("rooms", roomID, "kick")
|
||||
_, err = cli.MakeRequest("POST", u, req, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// BanUser bans a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban
|
||||
func (cli *Client) BanUser(roomID string, req *ReqBanUser) (resp *RespBanUser, err error) {
|
||||
u := cli.BuildURL("rooms", roomID, "ban")
|
||||
_, err = cli.MakeRequest("POST", u, req, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// UnbanUser unbans a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban
|
||||
func (cli *Client) UnbanUser(roomID string, req *ReqUnbanUser) (resp *RespUnbanUser, err error) {
|
||||
u := cli.BuildURL("rooms", roomID, "unban")
|
||||
_, err = cli.MakeRequest("POST", u, req, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// UserTyping sets the typing status of the user. See https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid
|
||||
func (cli *Client) UserTyping(roomID string, typing bool, timeout int64) (resp *RespTyping, err error) {
|
||||
req := ReqTyping{Typing: typing, Timeout: timeout}
|
||||
u := cli.BuildURL("rooms", roomID, "typing", cli.UserID)
|
||||
_, err = cli.MakeRequest("PUT", u, req, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// StateEvent gets a single state event in a room. It will attempt to JSON unmarshal into the given "outContent" struct with
|
||||
// the HTTP response body, or return an error.
|
||||
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-state-eventtype-statekey
|
||||
func (cli *Client) StateEvent(roomID, eventType, stateKey string, outContent interface{}) (err error) {
|
||||
u := cli.BuildURL("rooms", roomID, "state", eventType, stateKey)
|
||||
_, err = cli.MakeRequest("GET", u, nil, outContent)
|
||||
return
|
||||
}
|
||||
|
||||
// UploadLink uploads an HTTP URL and then returns an MXC URI.
|
||||
func (cli *Client) UploadLink(link string) (*RespMediaUpload, error) {
|
||||
res, err := cli.Client.Get(link)
|
||||
if res != nil {
|
||||
defer res.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cli.UploadToContentRepo(res.Body, res.Header.Get("Content-Type"), res.ContentLength)
|
||||
}
|
||||
|
||||
// UploadToContentRepo uploads the given bytes to the content repository and returns an MXC URI.
|
||||
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-media-r0-upload
|
||||
func (cli *Client) UploadToContentRepo(content io.Reader, contentType string, contentLength int64) (*RespMediaUpload, error) {
|
||||
req, err := http.NewRequest("POST", cli.BuildBaseURL("_matrix/media/r0/upload"), content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
req.ContentLength = contentLength
|
||||
res, err := cli.Client.Do(req)
|
||||
if res != nil {
|
||||
defer res.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
contents, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, HTTPError{
|
||||
Message: "Upload request failed - Failed to read response body: " + err.Error(),
|
||||
Code: res.StatusCode,
|
||||
}
|
||||
}
|
||||
return nil, HTTPError{
|
||||
Message: "Upload request failed: " + string(contents),
|
||||
Code: res.StatusCode,
|
||||
}
|
||||
}
|
||||
var m RespMediaUpload
|
||||
if err := json.NewDecoder(res.Body).Decode(&m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
// JoinedMembers returns a map of joined room members. See TODO-SPEC. https://github.com/matrix-org/synapse/pull/1680
|
||||
//
|
||||
// In general, usage of this API is discouraged in favour of /sync, as calling this API can race with incoming membership changes.
|
||||
// This API is primarily designed for application services which may want to efficiently look up joined members in a room.
|
||||
func (cli *Client) JoinedMembers(roomID string) (resp *RespJoinedMembers, err error) {
|
||||
u := cli.BuildURL("rooms", roomID, "joined_members")
|
||||
_, err = cli.MakeRequest("GET", u, nil, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// JoinedRooms returns a list of rooms which the client is joined to. See TODO-SPEC. https://github.com/matrix-org/synapse/pull/1680
|
||||
//
|
||||
// In general, usage of this API is discouraged in favour of /sync, as calling this API can race with incoming membership changes.
|
||||
// This API is primarily designed for application services which may want to efficiently look up joined rooms.
|
||||
func (cli *Client) JoinedRooms() (resp *RespJoinedRooms, err error) {
|
||||
u := cli.BuildURL("joined_rooms")
|
||||
_, err = cli.MakeRequest("GET", u, nil, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// Messages returns a list of message and state events for a room. It uses
|
||||
// pagination query parameters to paginate history in the room.
|
||||
// See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages
|
||||
func (cli *Client) Messages(roomID, from, to string, dir rune, limit int) (resp *RespMessages, err error) {
|
||||
query := map[string]string{
|
||||
"from": from,
|
||||
"dir": string(dir),
|
||||
}
|
||||
if to != "" {
|
||||
query["to"] = to
|
||||
}
|
||||
if limit != 0 {
|
||||
query["limit"] = strconv.Itoa(limit)
|
||||
}
|
||||
|
||||
urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "messages"}, query)
|
||||
_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// TurnServer returns turn server details and credentials for the client to use when initiating calls.
|
||||
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-voip-turnserver
|
||||
func (cli *Client) TurnServer() (resp *RespTurnServer, err error) {
|
||||
urlPath := cli.BuildURL("voip", "turnServer")
|
||||
_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
func txnID() string {
|
||||
return "go" + strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
}
|
||||
|
||||
// NewClient creates a new Matrix Client ready for syncing
|
||||
func NewClient(homeserverURL, userID, accessToken string) (*Client, error) {
|
||||
hsURL, err := url.Parse(homeserverURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// By default, use an in-memory store which will never save filter ids / next batch tokens to disk.
|
||||
// The client will work with this storer: it just won't remember across restarts.
|
||||
// In practice, a database backend should be used.
|
||||
store := NewInMemoryStore()
|
||||
cli := Client{
|
||||
AccessToken: accessToken,
|
||||
HomeserverURL: hsURL,
|
||||
UserID: userID,
|
||||
Prefix: "/_matrix/client/r0",
|
||||
Syncer: NewDefaultSyncer(userID, store),
|
||||
Store: store,
|
||||
}
|
||||
// By default, use the default HTTP client.
|
||||
cli.Client = http.DefaultClient
|
||||
|
||||
return &cli, nil
|
||||
}
|
119
vendor/github.com/matrix-org/gomatrix/client_examples_test.go
generated
vendored
Normal file
119
vendor/github.com/matrix-org/gomatrix/client_examples_test.go
generated
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
package gomatrix
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Example_sync() {
|
||||
cli, _ := NewClient("https://matrix.org", "@example:matrix.org", "MDAefhiuwehfuiwe")
|
||||
cli.Store.SaveFilterID("@example:matrix.org", "2") // Optional: if you know it already
|
||||
cli.Store.SaveNextBatch("@example:matrix.org", "111_222_333_444") // Optional: if you know it already
|
||||
syncer := cli.Syncer.(*DefaultSyncer)
|
||||
syncer.OnEventType("m.room.message", func(ev *Event) {
|
||||
fmt.Println("Message: ", ev)
|
||||
})
|
||||
|
||||
// Blocking version
|
||||
if err := cli.Sync(); err != nil {
|
||||
fmt.Println("Sync() returned ", err)
|
||||
}
|
||||
|
||||
// Non-blocking version
|
||||
go func() {
|
||||
for {
|
||||
if err := cli.Sync(); err != nil {
|
||||
fmt.Println("Sync() returned ", err)
|
||||
}
|
||||
// Optional: Wait a period of time before trying to sync again.
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func Example_customInterfaces() {
|
||||
// Custom interfaces must be set prior to calling functions on the client.
|
||||
cli, _ := NewClient("https://matrix.org", "@example:matrix.org", "MDAefhiuwehfuiwe")
|
||||
|
||||
// anything which implements the Storer interface
|
||||
customStore := NewInMemoryStore()
|
||||
cli.Store = customStore
|
||||
|
||||
// anything which implements the Syncer interface
|
||||
customSyncer := NewDefaultSyncer("@example:matrix.org", customStore)
|
||||
cli.Syncer = customSyncer
|
||||
|
||||
// any http.Client
|
||||
cli.Client = http.DefaultClient
|
||||
|
||||
// Once you call a function, you can't safely change the interfaces.
|
||||
cli.SendText("!foo:bar", "Down the rabbit hole")
|
||||
}
|
||||
|
||||
func ExampleClient_BuildURLWithQuery() {
|
||||
cli, _ := NewClient("https://matrix.org", "@example:matrix.org", "abcdef123456")
|
||||
out := cli.BuildURLWithQuery([]string{"sync"}, map[string]string{
|
||||
"filter_id": "5",
|
||||
})
|
||||
fmt.Println(out)
|
||||
// Output: https://matrix.org/_matrix/client/r0/sync?access_token=abcdef123456&filter_id=5
|
||||
}
|
||||
|
||||
func ExampleClient_BuildURL() {
|
||||
userID := "@example:matrix.org"
|
||||
cli, _ := NewClient("https://matrix.org", userID, "abcdef123456")
|
||||
out := cli.BuildURL("user", userID, "filter")
|
||||
fmt.Println(out)
|
||||
// Output: https://matrix.org/_matrix/client/r0/user/@example:matrix.org/filter?access_token=abcdef123456
|
||||
}
|
||||
|
||||
func ExampleClient_BuildBaseURL() {
|
||||
userID := "@example:matrix.org"
|
||||
cli, _ := NewClient("https://matrix.org", userID, "abcdef123456")
|
||||
out := cli.BuildBaseURL("_matrix", "client", "r0", "directory", "room", "#matrix:matrix.org")
|
||||
fmt.Println(out)
|
||||
// Output: https://matrix.org/_matrix/client/r0/directory/room/%23matrix:matrix.org?access_token=abcdef123456
|
||||
}
|
||||
|
||||
// Retrieve the content of a m.room.name state event.
|
||||
func ExampleClient_StateEvent() {
|
||||
content := struct {
|
||||
Name string `json:"name"`
|
||||
}{}
|
||||
cli, _ := NewClient("https://matrix.org", "@example:matrix.org", "abcdef123456")
|
||||
if err := cli.StateEvent("!foo:bar", "m.room.name", "", &content); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Join a room by ID.
|
||||
func ExampleClient_JoinRoom_id() {
|
||||
cli, _ := NewClient("http://localhost:8008", "@example:localhost", "abcdef123456")
|
||||
if _, err := cli.JoinRoom("!uOILRrqxnsYgQdUzar:localhost", "", nil); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Join a room by alias.
|
||||
func ExampleClient_JoinRoom_alias() {
|
||||
cli, _ := NewClient("http://localhost:8008", "@example:localhost", "abcdef123456")
|
||||
if resp, err := cli.JoinRoom("#test:localhost", "", nil); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
// Use room ID for something.
|
||||
_ = resp.RoomID
|
||||
}
|
||||
}
|
||||
|
||||
// Login to a local homeserver and set the user ID and access token on success.
|
||||
func ExampleClient_Login() {
|
||||
cli, _ := NewClient("http://localhost:8008", "", "")
|
||||
resp, err := cli.Login(&ReqLogin{
|
||||
Type: "m.login.password",
|
||||
User: "alice",
|
||||
Password: "wonderland",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cli.SetCredentials(resp.UserID, resp.AccessToken)
|
||||
}
|
105
vendor/github.com/matrix-org/gomatrix/client_test.go
generated
vendored
Normal file
105
vendor/github.com/matrix-org/gomatrix/client_test.go
generated
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
package gomatrix
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClient_LeaveRoom(t *testing.T) {
|
||||
cli := mockClient(func(req *http.Request) (*http.Response, error) {
|
||||
if req.Method == "POST" && req.URL.Path == "/_matrix/client/r0/rooms/!foo:bar/leave" {
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(`{}`)),
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unhandled URL: %s", req.URL.Path)
|
||||
})
|
||||
|
||||
if _, err := cli.LeaveRoom("!foo:bar"); err != nil {
|
||||
t.Fatalf("LeaveRoom: error, got %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_GetAvatarUrl(t *testing.T) {
|
||||
cli := mockClient(func(req *http.Request) (*http.Response, error) {
|
||||
if req.Method == "GET" && req.URL.Path == "/_matrix/client/r0/profile/@user:test.gomatrix.org/avatar_url" {
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(`{"avatar_url":"mxc://matrix.org/iJaUjkshgdfsdkjfn"}`)),
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unhandled URL: %s", req.URL.Path)
|
||||
})
|
||||
|
||||
if response, err := cli.GetAvatarURL(); err != nil {
|
||||
t.Fatalf("GetAvatarURL: Got error: %s", err.Error())
|
||||
} else if response == "" {
|
||||
t.Fatal("GetAvatarURL: Got empty response")
|
||||
} else if response != "mxc://matrix.org/iJaUjkshgdfsdkjfn" {
|
||||
t.Fatalf("Unexpected response URL: %s", response)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestClient_SetAvatarUrl(t *testing.T) {
|
||||
cli := mockClient(func(req *http.Request) (*http.Response, error) {
|
||||
if req.Method == "PUT" && req.URL.Path == "/_matrix/client/r0/profile/@user:test.gomatrix.org/avatar_url" {
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(`{}`)),
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unhandled URL: %s", req.URL.Path)
|
||||
})
|
||||
|
||||
if err := cli.SetAvatarURL("https://foo.com/bar.png"); err != nil {
|
||||
t.Fatalf("GetAvatarURL: Got error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_StateEvent(t *testing.T) {
|
||||
cli := mockClient(func(req *http.Request) (*http.Response, error) {
|
||||
if req.Method == "GET" && req.URL.Path == "/_matrix/client/r0/rooms/!foo:bar/state/m.room.name" {
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(`{"name":"Room Name Goes Here"}`)),
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unhandled URL: %s", req.URL.Path)
|
||||
})
|
||||
|
||||
content := struct {
|
||||
Name string `json:"name"`
|
||||
}{}
|
||||
|
||||
if err := cli.StateEvent("!foo:bar", "m.room.name", "", &content); err != nil {
|
||||
t.Fatalf("StateEvent: error, got %s", err.Error())
|
||||
}
|
||||
if content.Name != "Room Name Goes Here" {
|
||||
t.Fatalf("StateEvent: got %s, want %s", content.Name, "Room Name Goes Here")
|
||||
}
|
||||
}
|
||||
|
||||
func mockClient(fn func(*http.Request) (*http.Response, error)) *Client {
|
||||
mrt := MockRoundTripper{
|
||||
RT: fn,
|
||||
}
|
||||
|
||||
cli, _ := NewClient("https://test.gomatrix.org", "@user:test.gomatrix.org", "abcdef")
|
||||
cli.Client = &http.Client{
|
||||
Transport: mrt,
|
||||
}
|
||||
return cli
|
||||
}
|
||||
|
||||
type MockRoundTripper struct {
|
||||
RT func(*http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
func (t MockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return t.RT(req)
|
||||
}
|
101
vendor/github.com/matrix-org/gomatrix/events.go
generated
vendored
Normal file
101
vendor/github.com/matrix-org/gomatrix/events.go
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
package gomatrix
|
||||
|
||||
import (
|
||||
"html"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// Event represents a single Matrix event.
|
||||
type Event struct {
|
||||
StateKey *string `json:"state_key,omitempty"` // The state key for the event. Only present on State Events.
|
||||
Sender string `json:"sender"` // The user ID of the sender of the event
|
||||
Type string `json:"type"` // The event type
|
||||
Timestamp int64 `json:"origin_server_ts"` // The unix timestamp when this message was sent by the origin server
|
||||
ID string `json:"event_id"` // The unique ID of this event
|
||||
RoomID string `json:"room_id"` // The room the event was sent to. May be nil (e.g. for presence)
|
||||
Content map[string]interface{} `json:"content"` // The JSON content of the event.
|
||||
}
|
||||
|
||||
// Body returns the value of the "body" key in the event content if it is
|
||||
// present and is a string.
|
||||
func (event *Event) Body() (body string, ok bool) {
|
||||
value, exists := event.Content["body"]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
body, ok = value.(string)
|
||||
return
|
||||
}
|
||||
|
||||
// MessageType returns the value of the "msgtype" key in the event content if
|
||||
// it is present and is a string.
|
||||
func (event *Event) MessageType() (msgtype string, ok bool) {
|
||||
value, exists := event.Content["msgtype"]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
msgtype, ok = value.(string)
|
||||
return
|
||||
}
|
||||
|
||||
// TextMessage is the contents of a Matrix formated message event.
|
||||
type TextMessage struct {
|
||||
MsgType string `json:"msgtype"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
// ImageInfo contains info about an image - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-image
|
||||
type ImageInfo struct {
|
||||
Height uint `json:"h,omitempty"`
|
||||
Width uint `json:"w,omitempty"`
|
||||
Mimetype string `json:"mimetype,omitempty"`
|
||||
Size uint `json:"size,omitempty"`
|
||||
}
|
||||
|
||||
// VideoInfo contains info about a video - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-video
|
||||
type VideoInfo struct {
|
||||
Mimetype string `json:"mimetype,omitempty"`
|
||||
ThumbnailInfo ImageInfo `json:"thumbnail_info"`
|
||||
ThumbnailURL string `json:"thumbnail_url,omitempty"`
|
||||
Height uint `json:"h,omitempty"`
|
||||
Width uint `json:"w,omitempty"`
|
||||
Duration uint `json:"duration,omitempty"`
|
||||
Size uint `json:"size,omitempty"`
|
||||
}
|
||||
|
||||
// VideoMessage is an m.video - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-video
|
||||
type VideoMessage struct {
|
||||
MsgType string `json:"msgtype"`
|
||||
Body string `json:"body"`
|
||||
URL string `json:"url"`
|
||||
Info VideoInfo `json:"info"`
|
||||
}
|
||||
|
||||
// ImageMessage is an m.image event
|
||||
type ImageMessage struct {
|
||||
MsgType string `json:"msgtype"`
|
||||
Body string `json:"body"`
|
||||
URL string `json:"url"`
|
||||
Info ImageInfo `json:"info"`
|
||||
}
|
||||
|
||||
// An HTMLMessage is the contents of a Matrix HTML formated message event.
|
||||
type HTMLMessage struct {
|
||||
Body string `json:"body"`
|
||||
MsgType string `json:"msgtype"`
|
||||
Format string `json:"format"`
|
||||
FormattedBody string `json:"formatted_body"`
|
||||
}
|
||||
|
||||
var htmlRegex = regexp.MustCompile("<[^<]+?>")
|
||||
|
||||
// GetHTMLMessage returns an HTMLMessage with the body set to a stripped version of the provided HTML, in addition
|
||||
// to the provided HTML.
|
||||
func GetHTMLMessage(msgtype, htmlText string) HTMLMessage {
|
||||
return HTMLMessage{
|
||||
Body: html.UnescapeString(htmlRegex.ReplaceAllLiteralString(htmlText, "")),
|
||||
MsgType: msgtype,
|
||||
Format: "org.matrix.custom.html",
|
||||
FormattedBody: htmlText,
|
||||
}
|
||||
}
|
43
vendor/github.com/matrix-org/gomatrix/filter.go
generated
vendored
Normal file
43
vendor/github.com/matrix-org/gomatrix/filter.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright 2017 Jan Christian Grünhage
|
||||
//
|
||||
// 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 gomatrix
|
||||
|
||||
//Filter is used by clients to specify how the server should filter responses to e.g. sync requests
|
||||
//Specified by: https://matrix.org/docs/spec/client_server/r0.2.0.html#filtering
|
||||
type Filter struct {
|
||||
AccountData FilterPart `json:"account_data,omitempty"`
|
||||
EventFields []string `json:"event_fields,omitempty"`
|
||||
EventFormat string `json:"event_format,omitempty"`
|
||||
Presence FilterPart `json:"presence,omitempty"`
|
||||
Room struct {
|
||||
AccountData FilterPart `json:"account_data,omitempty"`
|
||||
Ephemeral FilterPart `json:"ephemeral,omitempty"`
|
||||
IncludeLeave bool `json:"include_leave,omitempty"`
|
||||
NotRooms []string `json:"not_rooms,omitempty"`
|
||||
Rooms []string `json:"rooms,omitempty"`
|
||||
State FilterPart `json:"state,omitempty"`
|
||||
Timeline FilterPart `json:"timeline,omitempty"`
|
||||
} `json:"room,omitempty"`
|
||||
}
|
||||
|
||||
type FilterPart struct {
|
||||
NotRooms []string `json:"not_rooms,omitempty"`
|
||||
Rooms []string `json:"rooms,omitempty"`
|
||||
Limit *int `json:"limit,omitempty"`
|
||||
NotSenders []string `json:"not_senders,omitempty"`
|
||||
NotTypes []string `json:"not_types,omitempty"`
|
||||
Senders []string `json:"senders,omitempty"`
|
||||
Types []string `json:"types,omitempty"`
|
||||
}
|
5
vendor/github.com/matrix-org/gomatrix/hooks/install.sh
generated
vendored
Executable file
5
vendor/github.com/matrix-org/gomatrix/hooks/install.sh
generated
vendored
Executable file
@ -0,0 +1,5 @@
|
||||
#! /bin/bash
|
||||
|
||||
DOT_GIT="$(dirname $0)/../.git"
|
||||
|
||||
ln -s "../../hooks/pre-commit" "$DOT_GIT/hooks/pre-commit"
|
26
vendor/github.com/matrix-org/gomatrix/hooks/pre-commit
generated
vendored
Executable file
26
vendor/github.com/matrix-org/gomatrix/hooks/pre-commit
generated
vendored
Executable file
@ -0,0 +1,26 @@
|
||||
#! /bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
golint
|
||||
misspell --error .
|
||||
|
||||
# gofmt doesn't exit with an error code if the files don't match the expected
|
||||
# format. So we have to run it and see if it outputs anything.
|
||||
if gofmt -l -s . 2>&1 | read
|
||||
then
|
||||
echo "Error: not all code had been formatted with gofmt."
|
||||
echo "Fixing the following files"
|
||||
gofmt -s -w -l .
|
||||
echo
|
||||
echo "Please add them to the commit"
|
||||
git status --short
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ineffassign .
|
||||
|
||||
go fmt
|
||||
go tool vet --all --shadow .
|
||||
gocyclo -over 12 .
|
||||
go test -timeout 5s -test.v
|
78
vendor/github.com/matrix-org/gomatrix/requests.go
generated
vendored
Normal file
78
vendor/github.com/matrix-org/gomatrix/requests.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
package gomatrix
|
||||
|
||||
// ReqRegister is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
|
||||
type ReqRegister struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
BindEmail bool `json:"bind_email,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
DeviceID string `json:"device_id,omitempty"`
|
||||
InitialDeviceDisplayName string `json:"initial_device_display_name"`
|
||||
Auth interface{} `json:"auth,omitempty"`
|
||||
}
|
||||
|
||||
// ReqLogin is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login
|
||||
type ReqLogin struct {
|
||||
Type string `json:"type"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Medium string `json:"medium,omitempty"`
|
||||
User string `json:"user,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
DeviceID string `json:"device_id,omitempty"`
|
||||
InitialDeviceDisplayName string `json:"initial_device_display_name,omitempty"`
|
||||
}
|
||||
|
||||
// ReqCreateRoom is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
|
||||
type ReqCreateRoom struct {
|
||||
Visibility string `json:"visibility,omitempty"`
|
||||
RoomAliasName string `json:"room_alias_name,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Topic string `json:"topic,omitempty"`
|
||||
Invite []string `json:"invite,omitempty"`
|
||||
Invite3PID []ReqInvite3PID `json:"invite_3pid,omitempty"`
|
||||
CreationContent map[string]interface{} `json:"creation_content,omitempty"`
|
||||
InitialState []Event `json:"initial_state,omitempty"`
|
||||
Preset string `json:"preset,omitempty"`
|
||||
IsDirect bool `json:"is_direct,omitempty"`
|
||||
}
|
||||
|
||||
// ReqRedact is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid
|
||||
type ReqRedact struct {
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
// ReqInvite3PID is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#id57
|
||||
// It is also a JSON object used in https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
|
||||
type ReqInvite3PID struct {
|
||||
IDServer string `json:"id_server"`
|
||||
Medium string `json:"medium"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
// ReqInviteUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite
|
||||
type ReqInviteUser struct {
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
// ReqKickUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick
|
||||
type ReqKickUser struct {
|
||||
Reason string `json:"reason,omitempty"`
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
// ReqBanUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban
|
||||
type ReqBanUser struct {
|
||||
Reason string `json:"reason,omitempty"`
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
// ReqUnbanUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban
|
||||
type ReqUnbanUser struct {
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
// ReqTyping is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid
|
||||
type ReqTyping struct {
|
||||
Typing bool `json:"typing"`
|
||||
Timeout int64 `json:"timeout"`
|
||||
}
|
176
vendor/github.com/matrix-org/gomatrix/responses.go
generated
vendored
Normal file
176
vendor/github.com/matrix-org/gomatrix/responses.go
generated
vendored
Normal file
@ -0,0 +1,176 @@
|
||||
package gomatrix
|
||||
|
||||
// RespError is the standard JSON error response from Homeservers. It also implements the Golang "error" interface.
|
||||
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#api-standards
|
||||
type RespError struct {
|
||||
ErrCode string `json:"errcode"`
|
||||
Err string `json:"error"`
|
||||
}
|
||||
|
||||
// Error returns the errcode and error message.
|
||||
func (e RespError) Error() string {
|
||||
return e.ErrCode + ": " + e.Err
|
||||
}
|
||||
|
||||
// RespCreateFilter is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-user-userid-filter
|
||||
type RespCreateFilter struct {
|
||||
FilterID string `json:"filter_id"`
|
||||
}
|
||||
|
||||
// RespVersions is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-versions
|
||||
type RespVersions struct {
|
||||
Versions []string `json:"versions"`
|
||||
}
|
||||
|
||||
// RespJoinRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-join
|
||||
type RespJoinRoom struct {
|
||||
RoomID string `json:"room_id"`
|
||||
}
|
||||
|
||||
// RespLeaveRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-leave
|
||||
type RespLeaveRoom struct{}
|
||||
|
||||
// RespForgetRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-forget
|
||||
type RespForgetRoom struct{}
|
||||
|
||||
// RespInviteUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite
|
||||
type RespInviteUser struct{}
|
||||
|
||||
// RespKickUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick
|
||||
type RespKickUser struct{}
|
||||
|
||||
// RespBanUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban
|
||||
type RespBanUser struct{}
|
||||
|
||||
// RespUnbanUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban
|
||||
type RespUnbanUser struct{}
|
||||
|
||||
// RespTyping is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid
|
||||
type RespTyping struct{}
|
||||
|
||||
// RespJoinedRooms is the JSON response for TODO-SPEC https://github.com/matrix-org/synapse/pull/1680
|
||||
type RespJoinedRooms struct {
|
||||
JoinedRooms []string `json:"joined_rooms"`
|
||||
}
|
||||
|
||||
// RespJoinedMembers is the JSON response for TODO-SPEC https://github.com/matrix-org/synapse/pull/1680
|
||||
type RespJoinedMembers struct {
|
||||
Joined map[string]struct {
|
||||
DisplayName *string `json:"display_name"`
|
||||
AvatarURL *string `json:"avatar_url"`
|
||||
} `json:"joined"`
|
||||
}
|
||||
|
||||
// RespMessages is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages
|
||||
type RespMessages struct {
|
||||
Start string `json:"start"`
|
||||
Chunk []Event `json:"chunk"`
|
||||
End string `json:"end"`
|
||||
}
|
||||
|
||||
// RespSendEvent is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
|
||||
type RespSendEvent struct {
|
||||
EventID string `json:"event_id"`
|
||||
}
|
||||
|
||||
// RespMediaUpload is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-media-r0-upload
|
||||
type RespMediaUpload struct {
|
||||
ContentURI string `json:"content_uri"`
|
||||
}
|
||||
|
||||
// RespUserInteractive is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#user-interactive-authentication-api
|
||||
type RespUserInteractive struct {
|
||||
Flows []struct {
|
||||
Stages []string `json:"stages"`
|
||||
} `json:"flows"`
|
||||
Params map[string]interface{} `json:"params"`
|
||||
Session string `json:"string"`
|
||||
Completed []string `json:"completed"`
|
||||
ErrCode string `json:"errcode"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// HasSingleStageFlow returns true if there exists at least 1 Flow with a single stage of stageName.
|
||||
func (r RespUserInteractive) HasSingleStageFlow(stageName string) bool {
|
||||
for _, f := range r.Flows {
|
||||
if len(f.Stages) == 1 && f.Stages[0] == stageName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RespUserDisplayName is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname
|
||||
type RespUserDisplayName struct {
|
||||
DisplayName string `json:"displayname"`
|
||||
}
|
||||
|
||||
// RespRegister is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
|
||||
type RespRegister struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
DeviceID string `json:"device_id"`
|
||||
HomeServer string `json:"home_server"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
// RespLogin is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login
|
||||
type RespLogin struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
DeviceID string `json:"device_id"`
|
||||
HomeServer string `json:"home_server"`
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
// RespLogout is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-logout
|
||||
type RespLogout struct{}
|
||||
|
||||
// RespCreateRoom is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
|
||||
type RespCreateRoom struct {
|
||||
RoomID string `json:"room_id"`
|
||||
}
|
||||
|
||||
// RespSync is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync
|
||||
type RespSync struct {
|
||||
NextBatch string `json:"next_batch"`
|
||||
AccountData struct {
|
||||
Events []Event `json:"events"`
|
||||
} `json:"account_data"`
|
||||
Presence struct {
|
||||
Events []Event `json:"events"`
|
||||
} `json:"presence"`
|
||||
Rooms struct {
|
||||
Leave map[string]struct {
|
||||
State struct {
|
||||
Events []Event `json:"events"`
|
||||
} `json:"state"`
|
||||
Timeline struct {
|
||||
Events []Event `json:"events"`
|
||||
Limited bool `json:"limited"`
|
||||
PrevBatch string `json:"prev_batch"`
|
||||
} `json:"timeline"`
|
||||
} `json:"leave"`
|
||||
Join map[string]struct {
|
||||
State struct {
|
||||
Events []Event `json:"events"`
|
||||
} `json:"state"`
|
||||
Timeline struct {
|
||||
Events []Event `json:"events"`
|
||||
Limited bool `json:"limited"`
|
||||
PrevBatch string `json:"prev_batch"`
|
||||
} `json:"timeline"`
|
||||
} `json:"join"`
|
||||
Invite map[string]struct {
|
||||
State struct {
|
||||
Events []Event
|
||||
} `json:"invite_state"`
|
||||
} `json:"invite"`
|
||||
} `json:"rooms"`
|
||||
}
|
||||
|
||||
type RespTurnServer struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
TTL int `json:"ttl"`
|
||||
URIs []string `json:"uris"`
|
||||
}
|
50
vendor/github.com/matrix-org/gomatrix/room.go
generated
vendored
Normal file
50
vendor/github.com/matrix-org/gomatrix/room.go
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
package gomatrix
|
||||
|
||||
// Room represents a single Matrix room.
|
||||
type Room struct {
|
||||
ID string
|
||||
State map[string]map[string]*Event
|
||||
}
|
||||
|
||||
// UpdateState updates the room's current state with the given Event. This will clobber events based
|
||||
// on the type/state_key combination.
|
||||
func (room Room) UpdateState(event *Event) {
|
||||
_, exists := room.State[event.Type]
|
||||
if !exists {
|
||||
room.State[event.Type] = make(map[string]*Event)
|
||||
}
|
||||
room.State[event.Type][*event.StateKey] = event
|
||||
}
|
||||
|
||||
// GetStateEvent returns the state event for the given type/state_key combo, or nil.
|
||||
func (room Room) GetStateEvent(eventType string, stateKey string) *Event {
|
||||
stateEventMap, _ := room.State[eventType]
|
||||
event, _ := stateEventMap[stateKey]
|
||||
return event
|
||||
}
|
||||
|
||||
// GetMembershipState returns the membership state of the given user ID in this room. If there is
|
||||
// no entry for this member, 'leave' is returned for consistency with left users.
|
||||
func (room Room) GetMembershipState(userID string) string {
|
||||
state := "leave"
|
||||
event := room.GetStateEvent("m.room.member", userID)
|
||||
if event != nil {
|
||||
membershipState, found := event.Content["membership"]
|
||||
if found {
|
||||
mState, isString := membershipState.(string)
|
||||
if isString {
|
||||
state = mState
|
||||
}
|
||||
}
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
// NewRoom creates a new Room with the given ID
|
||||
func NewRoom(roomID string) *Room {
|
||||
// Init the State map and return a pointer to the Room
|
||||
return &Room{
|
||||
ID: roomID,
|
||||
State: make(map[string]map[string]*Event),
|
||||
}
|
||||
}
|
65
vendor/github.com/matrix-org/gomatrix/store.go
generated
vendored
Normal file
65
vendor/github.com/matrix-org/gomatrix/store.go
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
package gomatrix
|
||||
|
||||
// Storer is an interface which must be satisfied to store client data.
|
||||
//
|
||||
// You can either write a struct which persists this data to disk, or you can use the
|
||||
// provided "InMemoryStore" which just keeps data around in-memory which is lost on
|
||||
// restarts.
|
||||
type Storer interface {
|
||||
SaveFilterID(userID, filterID string)
|
||||
LoadFilterID(userID string) string
|
||||
SaveNextBatch(userID, nextBatchToken string)
|
||||
LoadNextBatch(userID string) string
|
||||
SaveRoom(room *Room)
|
||||
LoadRoom(roomID string) *Room
|
||||
}
|
||||
|
||||
// InMemoryStore implements the Storer interface.
|
||||
//
|
||||
// Everything is persisted in-memory as maps. It is not safe to load/save filter IDs
|
||||
// or next batch tokens on any goroutine other than the syncing goroutine: the one
|
||||
// which called Client.Sync().
|
||||
type InMemoryStore struct {
|
||||
Filters map[string]string
|
||||
NextBatch map[string]string
|
||||
Rooms map[string]*Room
|
||||
}
|
||||
|
||||
// SaveFilterID to memory.
|
||||
func (s *InMemoryStore) SaveFilterID(userID, filterID string) {
|
||||
s.Filters[userID] = filterID
|
||||
}
|
||||
|
||||
// LoadFilterID from memory.
|
||||
func (s *InMemoryStore) LoadFilterID(userID string) string {
|
||||
return s.Filters[userID]
|
||||
}
|
||||
|
||||
// SaveNextBatch to memory.
|
||||
func (s *InMemoryStore) SaveNextBatch(userID, nextBatchToken string) {
|
||||
s.NextBatch[userID] = nextBatchToken
|
||||
}
|
||||
|
||||
// LoadNextBatch from memory.
|
||||
func (s *InMemoryStore) LoadNextBatch(userID string) string {
|
||||
return s.NextBatch[userID]
|
||||
}
|
||||
|
||||
// SaveRoom to memory.
|
||||
func (s *InMemoryStore) SaveRoom(room *Room) {
|
||||
s.Rooms[room.ID] = room
|
||||
}
|
||||
|
||||
// LoadRoom from memory.
|
||||
func (s *InMemoryStore) LoadRoom(roomID string) *Room {
|
||||
return s.Rooms[roomID]
|
||||
}
|
||||
|
||||
// NewInMemoryStore constructs a new InMemoryStore.
|
||||
func NewInMemoryStore() *InMemoryStore {
|
||||
return &InMemoryStore{
|
||||
Filters: make(map[string]string),
|
||||
NextBatch: make(map[string]string),
|
||||
Rooms: make(map[string]*Room),
|
||||
}
|
||||
}
|
164
vendor/github.com/matrix-org/gomatrix/sync.go
generated
vendored
Normal file
164
vendor/github.com/matrix-org/gomatrix/sync.go
generated
vendored
Normal file
@ -0,0 +1,164 @@
|
||||
package gomatrix
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Syncer represents an interface that must be satisfied in order to do /sync requests on a client.
|
||||
type Syncer interface {
|
||||
// Process the /sync response. The since parameter is the since= value that was used to produce the response.
|
||||
// This is useful for detecting the very first sync (since=""). If an error is return, Syncing will be stopped
|
||||
// permanently.
|
||||
ProcessResponse(resp *RespSync, since string) error
|
||||
// OnFailedSync returns either the time to wait before retrying or an error to stop syncing permanently.
|
||||
OnFailedSync(res *RespSync, err error) (time.Duration, error)
|
||||
// GetFilterJSON for the given user ID. NOT the filter ID.
|
||||
GetFilterJSON(userID string) json.RawMessage
|
||||
}
|
||||
|
||||
// DefaultSyncer is the default syncing implementation. You can either write your own syncer, or selectively
|
||||
// replace parts of this default syncer (e.g. the ProcessResponse method). The default syncer uses the observer
|
||||
// pattern to notify callers about incoming events. See DefaultSyncer.OnEventType for more information.
|
||||
type DefaultSyncer struct {
|
||||
UserID string
|
||||
Store Storer
|
||||
listeners map[string][]OnEventListener // event type to listeners array
|
||||
}
|
||||
|
||||
// OnEventListener can be used with DefaultSyncer.OnEventType to be informed of incoming events.
|
||||
type OnEventListener func(*Event)
|
||||
|
||||
// NewDefaultSyncer returns an instantiated DefaultSyncer
|
||||
func NewDefaultSyncer(userID string, store Storer) *DefaultSyncer {
|
||||
return &DefaultSyncer{
|
||||
UserID: userID,
|
||||
Store: store,
|
||||
listeners: make(map[string][]OnEventListener),
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessResponse processes the /sync response in a way suitable for bots. "Suitable for bots" means a stream of
|
||||
// unrepeating events. Returns a fatal error if a listener panics.
|
||||
func (s *DefaultSyncer) ProcessResponse(res *RespSync, since string) (err error) {
|
||||
if !s.shouldProcessResponse(res, since) {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("ProcessResponse panicked! userID=%s since=%s panic=%s\n%s", s.UserID, since, r, debug.Stack())
|
||||
}
|
||||
}()
|
||||
|
||||
for roomID, roomData := range res.Rooms.Join {
|
||||
room := s.getOrCreateRoom(roomID)
|
||||
for _, event := range roomData.State.Events {
|
||||
event.RoomID = roomID
|
||||
room.UpdateState(&event)
|
||||
s.notifyListeners(&event)
|
||||
}
|
||||
for _, event := range roomData.Timeline.Events {
|
||||
event.RoomID = roomID
|
||||
s.notifyListeners(&event)
|
||||
}
|
||||
}
|
||||
for roomID, roomData := range res.Rooms.Invite {
|
||||
room := s.getOrCreateRoom(roomID)
|
||||
for _, event := range roomData.State.Events {
|
||||
event.RoomID = roomID
|
||||
room.UpdateState(&event)
|
||||
s.notifyListeners(&event)
|
||||
}
|
||||
}
|
||||
for roomID, roomData := range res.Rooms.Leave {
|
||||
room := s.getOrCreateRoom(roomID)
|
||||
for _, event := range roomData.Timeline.Events {
|
||||
if event.StateKey != nil {
|
||||
event.RoomID = roomID
|
||||
room.UpdateState(&event)
|
||||
s.notifyListeners(&event)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// OnEventType allows callers to be notified when there are new events for the given event type.
|
||||
// There are no duplicate checks.
|
||||
func (s *DefaultSyncer) OnEventType(eventType string, callback OnEventListener) {
|
||||
_, exists := s.listeners[eventType]
|
||||
if !exists {
|
||||
s.listeners[eventType] = []OnEventListener{}
|
||||
}
|
||||
s.listeners[eventType] = append(s.listeners[eventType], callback)
|
||||
}
|
||||
|
||||
// shouldProcessResponse returns true if the response should be processed. May modify the response to remove
|
||||
// stuff that shouldn't be processed.
|
||||
func (s *DefaultSyncer) shouldProcessResponse(resp *RespSync, since string) bool {
|
||||
if since == "" {
|
||||
return false
|
||||
}
|
||||
// This is a horrible hack because /sync will return the most recent messages for a room
|
||||
// as soon as you /join it. We do NOT want to process those events in that particular room
|
||||
// because they may have already been processed (if you toggle the bot in/out of the room).
|
||||
//
|
||||
// Work around this by inspecting each room's timeline and seeing if an m.room.member event for us
|
||||
// exists and is "join" and then discard processing that room entirely if so.
|
||||
// TODO: We probably want to process messages from after the last join event in the timeline.
|
||||
for roomID, roomData := range resp.Rooms.Join {
|
||||
for i := len(roomData.Timeline.Events) - 1; i >= 0; i-- {
|
||||
e := roomData.Timeline.Events[i]
|
||||
if e.Type == "m.room.member" && e.StateKey != nil && *e.StateKey == s.UserID {
|
||||
m := e.Content["membership"]
|
||||
mship, ok := m.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if mship == "join" {
|
||||
_, ok := resp.Rooms.Join[roomID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
delete(resp.Rooms.Join, roomID) // don't re-process messages
|
||||
delete(resp.Rooms.Invite, roomID) // don't re-process invites
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// getOrCreateRoom must only be called by the Sync() goroutine which calls ProcessResponse()
|
||||
func (s *DefaultSyncer) getOrCreateRoom(roomID string) *Room {
|
||||
room := s.Store.LoadRoom(roomID)
|
||||
if room == nil { // create a new Room
|
||||
room = NewRoom(roomID)
|
||||
s.Store.SaveRoom(room)
|
||||
}
|
||||
return room
|
||||
}
|
||||
|
||||
func (s *DefaultSyncer) notifyListeners(event *Event) {
|
||||
listeners, exists := s.listeners[event.Type]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
for _, fn := range listeners {
|
||||
fn(event)
|
||||
}
|
||||
}
|
||||
|
||||
// OnFailedSync always returns a 10 second wait period between failed /syncs, never a fatal error.
|
||||
func (s *DefaultSyncer) OnFailedSync(res *RespSync, err error) (time.Duration, error) {
|
||||
return 10 * time.Second, nil
|
||||
}
|
||||
|
||||
// GetFilterJSON returns a filter with a timeline limit of 50.
|
||||
func (s *DefaultSyncer) GetFilterJSON(userID string) json.RawMessage {
|
||||
return json.RawMessage(`{"room":{"timeline":{"limit":50}}}`)
|
||||
}
|
130
vendor/github.com/matrix-org/gomatrix/userids.go
generated
vendored
Normal file
130
vendor/github.com/matrix-org/gomatrix/userids.go
generated
vendored
Normal file
@ -0,0 +1,130 @@
|
||||
package gomatrix
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const lowerhex = "0123456789abcdef"
|
||||
|
||||
// encode the given byte using quoted-printable encoding (e.g "=2f")
|
||||
// and writes it to the buffer
|
||||
// See https://golang.org/src/mime/quotedprintable/writer.go
|
||||
func encode(buf *bytes.Buffer, b byte) {
|
||||
buf.WriteByte('=')
|
||||
buf.WriteByte(lowerhex[b>>4])
|
||||
buf.WriteByte(lowerhex[b&0x0f])
|
||||
}
|
||||
|
||||
// escape the given alpha character and writes it to the buffer
|
||||
func escape(buf *bytes.Buffer, b byte) {
|
||||
buf.WriteByte('_')
|
||||
if b == '_' {
|
||||
buf.WriteByte('_') // another _
|
||||
} else {
|
||||
buf.WriteByte(b + 0x20) // ASCII shift A-Z to a-z
|
||||
}
|
||||
}
|
||||
|
||||
func shouldEncode(b byte) bool {
|
||||
return b != '-' && b != '.' && b != '_' && !(b >= '0' && b <= '9') && !(b >= 'a' && b <= 'z') && !(b >= 'A' && b <= 'Z')
|
||||
}
|
||||
|
||||
func shouldEscape(b byte) bool {
|
||||
return (b >= 'A' && b <= 'Z') || b == '_'
|
||||
}
|
||||
|
||||
func isValidByte(b byte) bool {
|
||||
return isValidEscapedChar(b) || (b >= '0' && b <= '9') || b == '.' || b == '=' || b == '-'
|
||||
}
|
||||
|
||||
func isValidEscapedChar(b byte) bool {
|
||||
return b == '_' || (b >= 'a' && b <= 'z')
|
||||
}
|
||||
|
||||
// EncodeUserLocalpart encodes the given string into Matrix-compliant user ID localpart form.
|
||||
// See http://matrix.org/docs/spec/intro.html#mapping-from-other-character-sets
|
||||
//
|
||||
// This returns a string with only the characters "a-z0-9._=-". The uppercase range A-Z
|
||||
// are encoded using leading underscores ("_"). Characters outside the aforementioned ranges
|
||||
// (including literal underscores ("_") and equals ("=")) are encoded as UTF8 code points (NOT NCRs)
|
||||
// and converted to lower-case hex with a leading "=". For example:
|
||||
// Alph@Bet_50up => _alph=40_bet=5f50up
|
||||
func EncodeUserLocalpart(str string) string {
|
||||
strBytes := []byte(str)
|
||||
var outputBuffer bytes.Buffer
|
||||
for _, b := range strBytes {
|
||||
if shouldEncode(b) {
|
||||
encode(&outputBuffer, b)
|
||||
} else if shouldEscape(b) {
|
||||
escape(&outputBuffer, b)
|
||||
} else {
|
||||
outputBuffer.WriteByte(b)
|
||||
}
|
||||
}
|
||||
return outputBuffer.String()
|
||||
}
|
||||
|
||||
// DecodeUserLocalpart decodes the given string back into the original input string.
|
||||
// Returns an error if the given string is not a valid user ID localpart encoding.
|
||||
// See http://matrix.org/docs/spec/intro.html#mapping-from-other-character-sets
|
||||
//
|
||||
// This decodes quoted-printable bytes back into UTF8, and unescapes casing. For
|
||||
// example:
|
||||
// _alph=40_bet=5f50up => Alph@Bet_50up
|
||||
// Returns an error if the input string contains characters outside the
|
||||
// range "a-z0-9._=-", has an invalid quote-printable byte (e.g. not hex), or has
|
||||
// an invalid _ escaped byte (e.g. "_5").
|
||||
func DecodeUserLocalpart(str string) (string, error) {
|
||||
strBytes := []byte(str)
|
||||
var outputBuffer bytes.Buffer
|
||||
for i := 0; i < len(strBytes); i++ {
|
||||
b := strBytes[i]
|
||||
if !isValidByte(b) {
|
||||
return "", fmt.Errorf("Byte pos %d: Invalid byte", i)
|
||||
}
|
||||
|
||||
if b == '_' { // next byte is a-z and should be upper-case or is another _ and should be a literal _
|
||||
if i+1 >= len(strBytes) {
|
||||
return "", fmt.Errorf("Byte pos %d: expected _[a-z_] encoding but ran out of string", i)
|
||||
}
|
||||
if !isValidEscapedChar(strBytes[i+1]) { // invalid escaping
|
||||
return "", fmt.Errorf("Byte pos %d: expected _[a-z_] encoding", i)
|
||||
}
|
||||
if strBytes[i+1] == '_' {
|
||||
outputBuffer.WriteByte('_')
|
||||
} else {
|
||||
outputBuffer.WriteByte(strBytes[i+1] - 0x20) // ASCII shift a-z to A-Z
|
||||
}
|
||||
i++ // skip next byte since we just handled it
|
||||
} else if b == '=' { // next 2 bytes are hex and should be buffered ready to be read as utf8
|
||||
if i+2 >= len(strBytes) {
|
||||
return "", fmt.Errorf("Byte pos: %d: expected quote-printable encoding but ran out of string", i)
|
||||
}
|
||||
dst := make([]byte, 1)
|
||||
_, err := hex.Decode(dst, strBytes[i+1:i+3])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
outputBuffer.WriteByte(dst[0])
|
||||
i += 2 // skip next 2 bytes since we just handled it
|
||||
} else { // pass through
|
||||
outputBuffer.WriteByte(b)
|
||||
}
|
||||
}
|
||||
return outputBuffer.String(), nil
|
||||
}
|
||||
|
||||
// ExtractUserLocalpart extracts the localpart portion of a user ID.
|
||||
// See http://matrix.org/docs/spec/intro.html#user-identifiers
|
||||
func ExtractUserLocalpart(userID string) (string, error) {
|
||||
if len(userID) == 0 || userID[0] != '@' {
|
||||
return "", fmt.Errorf("%s is not a valid user id", userID)
|
||||
}
|
||||
return strings.TrimPrefix(
|
||||
strings.SplitN(userID, ":", 2)[0], // @foo:bar:8448 => [ "@foo", "bar:8448" ]
|
||||
"@", // remove "@" prefix
|
||||
), nil
|
||||
}
|
27
vendor/github.com/matrix-org/gomatrix/userids_examples_test.go
generated
vendored
Normal file
27
vendor/github.com/matrix-org/gomatrix/userids_examples_test.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
package gomatrix
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleEncodeUserLocalpart() {
|
||||
localpart := EncodeUserLocalpart("Alph@Bet_50up")
|
||||
fmt.Println(localpart)
|
||||
// Output: _alph=40_bet__50up
|
||||
}
|
||||
|
||||
func ExampleDecodeUserLocalpart() {
|
||||
localpart, err := DecodeUserLocalpart("_alph=40_bet__50up")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(localpart)
|
||||
// Output: Alph@Bet_50up
|
||||
}
|
||||
|
||||
func ExampleExtractUserLocalpart() {
|
||||
localpart, err := ExtractUserLocalpart("@alice:matrix.org")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(localpart)
|
||||
// Output: alice
|
||||
}
|
86
vendor/github.com/matrix-org/gomatrix/userids_test.go
generated
vendored
Normal file
86
vendor/github.com/matrix-org/gomatrix/userids_test.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
package gomatrix
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var useridtests = []struct {
|
||||
Input string
|
||||
Output string
|
||||
}{
|
||||
{"Alph@Bet_50up", "_alph=40_bet__50up"}, // The doc example
|
||||
{"abcdef", "abcdef"}, // no-op
|
||||
{"i_like_pie_", "i__like__pie__"}, // double underscore escaping
|
||||
{"ABCDEF", "_a_b_c_d_e_f"}, // all-caps
|
||||
{"!£", "=21=c2=a3"}, // punctuation and outside ascii range (U+00A3 => c2 a3)
|
||||
{"___", "______"}, // literal underscores
|
||||
{"hello-world.", "hello-world."}, // allowed punctuation
|
||||
{"5+5=10", "5=2b5=3d10"}, // equals sign
|
||||
{"東方Project", "=e6=9d=b1=e6=96=b9_project"}, // CJK mixed
|
||||
{" foo bar", "=09foo=20bar"}, // whitespace (tab and space)
|
||||
}
|
||||
|
||||
func TestEncodeUserLocalpart(t *testing.T) {
|
||||
for _, u := range useridtests {
|
||||
out := EncodeUserLocalpart(u.Input)
|
||||
if out != u.Output {
|
||||
t.Fatalf("TestEncodeUserLocalpart(%s) => Got: %s Expected: %s", u.Input, out, u.Output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeUserLocalpart(t *testing.T) {
|
||||
for _, u := range useridtests {
|
||||
in, _ := DecodeUserLocalpart(u.Output)
|
||||
if in != u.Input {
|
||||
t.Fatalf("TestDecodeUserLocalpart(%s) => Got: %s Expected: %s", u.Output, in, u.Input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var errtests = []struct {
|
||||
Input string
|
||||
}{
|
||||
{"foo@bar"}, // invalid character @
|
||||
{"foo_5bar"}, // invalid character after _
|
||||
{"foo_._-bar"}, // multiple invalid characters after _
|
||||
{"foo=2Hbar"}, // invalid hex after =
|
||||
{"foo=2hbar"}, // invalid hex after = (lower-case)
|
||||
{"foo=======2fbar"}, // multiple invalid hex after =
|
||||
{"foo=2"}, // end of string after =
|
||||
{"foo_"}, // end of string after _
|
||||
}
|
||||
|
||||
func TestDecodeUserLocalpartErrors(t *testing.T) {
|
||||
for _, u := range errtests {
|
||||
out, err := DecodeUserLocalpart(u.Input)
|
||||
if out != "" {
|
||||
t.Fatalf("TestDecodeUserLocalpartErrors(%s) => Got: %s Expected: empty string", u.Input, out)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("TestDecodeUserLocalpartErrors(%s) => Got: nil error Expected: error", u.Input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var localparttests = []struct {
|
||||
Input string
|
||||
ExpectOutput string
|
||||
}{
|
||||
{"@foo:bar", "foo"},
|
||||
{"@foo:bar:8448", "foo"},
|
||||
{"@foo.bar:baz.quuz", "foo.bar"},
|
||||
}
|
||||
|
||||
func TestExtractUserLocalpart(t *testing.T) {
|
||||
for _, u := range localparttests {
|
||||
out, err := ExtractUserLocalpart(u.Input)
|
||||
if err != nil {
|
||||
t.Errorf("TestExtractUserLocalpart(%s) => Error: %s", u.Input, err)
|
||||
continue
|
||||
}
|
||||
if out != u.ExpectOutput {
|
||||
t.Errorf("TestExtractUserLocalpart(%s) => Got: %s, Want %s", u.Input, out, u.ExpectOutput)
|
||||
}
|
||||
}
|
||||
}
|
2
vendor/github.com/urfave/cli/.flake8
generated
vendored
Normal file
2
vendor/github.com/urfave/cli/.flake8
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
[flake8]
|
||||
max-line-length = 120
|
2
vendor/github.com/urfave/cli/.gitignore
generated
vendored
Normal file
2
vendor/github.com/urfave/cli/.gitignore
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.coverprofile
|
||||
node_modules/
|
27
vendor/github.com/urfave/cli/.travis.yml
generated
vendored
Normal file
27
vendor/github.com/urfave/cli/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
language: go
|
||||
sudo: false
|
||||
dist: trusty
|
||||
osx_image: xcode8.3
|
||||
go: 1.8.x
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
before_script:
|
||||
- go get github.com/urfave/gfmrun/... || true
|
||||
- go get golang.org/x/tools/cmd/goimports
|
||||
- if [ ! -f node_modules/.bin/markdown-toc ] ; then
|
||||
npm install markdown-toc ;
|
||||
fi
|
||||
|
||||
script:
|
||||
- ./runtests gen
|
||||
- ./runtests vet
|
||||
- ./runtests test
|
||||
- ./runtests gfmrun
|
||||
- ./runtests toc
|
435
vendor/github.com/urfave/cli/CHANGELOG.md
generated
vendored
Normal file
435
vendor/github.com/urfave/cli/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,435 @@
|
||||
# Change Log
|
||||
|
||||
**ATTN**: This project uses [semantic versioning](http://semver.org/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## 1.20.0 - 2017-08-10
|
||||
|
||||
### Fixed
|
||||
|
||||
* `HandleExitCoder` is now correctly iterates over all errors in
|
||||
a `MultiError`. The exit code is the exit code of the last error or `1` if
|
||||
there are no `ExitCoder`s in the `MultiError`.
|
||||
* Fixed YAML file loading on Windows (previously would fail validate the file path)
|
||||
* Subcommand `Usage`, `Description`, `ArgsUsage`, `OnUsageError` correctly
|
||||
propogated
|
||||
* `ErrWriter` is now passed downwards through command structure to avoid the
|
||||
need to redefine it
|
||||
* Pass `Command` context into `OnUsageError` rather than parent context so that
|
||||
all fields are avaiable
|
||||
* Errors occuring in `Before` funcs are no longer double printed
|
||||
* Use `UsageText` in the help templates for commands and subcommands if
|
||||
defined; otherwise build the usage as before (was previously ignoring this
|
||||
field)
|
||||
* `IsSet` and `GlobalIsSet` now correctly return whether a flag is set if
|
||||
a program calls `Set` or `GlobalSet` directly after flag parsing (would
|
||||
previously only return `true` if the flag was set during parsing)
|
||||
|
||||
### Changed
|
||||
|
||||
* No longer exit the program on command/subcommand error if the error raised is
|
||||
not an `OsExiter`. This exiting behavior was introduced in 1.19.0, but was
|
||||
determined to be a regression in functionality. See [the
|
||||
PR](https://github.com/urfave/cli/pull/595) for discussion.
|
||||
|
||||
### Added
|
||||
|
||||
* `CommandsByName` type was added to make it easy to sort `Command`s by name,
|
||||
alphabetically
|
||||
* `altsrc` now handles loading of string and int arrays from TOML
|
||||
* Support for definition of custom help templates for `App` via
|
||||
`CustomAppHelpTemplate`
|
||||
* Support for arbitrary key/value fields on `App` to be used with
|
||||
`CustomAppHelpTemplate` via `ExtraInfo`
|
||||
* `HelpFlag`, `VersionFlag`, and `BashCompletionFlag` changed to explictly be
|
||||
`cli.Flag`s allowing for the use of custom flags satisfying the `cli.Flag`
|
||||
interface to be used.
|
||||
|
||||
|
||||
## [1.19.1] - 2016-11-21
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixes regression introduced in 1.19.0 where using an `ActionFunc` as
|
||||
the `Action` for a command would cause it to error rather than calling the
|
||||
function. Should not have a affected declarative cases using `func(c
|
||||
*cli.Context) err)`.
|
||||
- Shell completion now handles the case where the user specifies
|
||||
`--generate-bash-completion` immediately after a flag that takes an argument.
|
||||
Previously it call the application with `--generate-bash-completion` as the
|
||||
flag value.
|
||||
|
||||
## [1.19.0] - 2016-11-19
|
||||
### Added
|
||||
- `FlagsByName` was added to make it easy to sort flags (e.g. `sort.Sort(cli.FlagsByName(app.Flags))`)
|
||||
- A `Description` field was added to `App` for a more detailed description of
|
||||
the application (similar to the existing `Description` field on `Command`)
|
||||
- Flag type code generation via `go generate`
|
||||
- Write to stderr and exit 1 if action returns non-nil error
|
||||
- Added support for TOML to the `altsrc` loader
|
||||
- `SkipArgReorder` was added to allow users to skip the argument reordering.
|
||||
This is useful if you want to consider all "flags" after an argument as
|
||||
arguments rather than flags (the default behavior of the stdlib `flag`
|
||||
library). This is backported functionality from the [removal of the flag
|
||||
reordering](https://github.com/urfave/cli/pull/398) in the unreleased version
|
||||
2
|
||||
- For formatted errors (those implementing `ErrorFormatter`), the errors will
|
||||
be formatted during output. Compatible with `pkg/errors`.
|
||||
|
||||
### Changed
|
||||
- Raise minimum tested/supported Go version to 1.2+
|
||||
|
||||
### Fixed
|
||||
- Consider empty environment variables as set (previously environment variables
|
||||
with the equivalent of `""` would be skipped rather than their value used).
|
||||
- Return an error if the value in a given environment variable cannot be parsed
|
||||
as the flag type. Previously these errors were silently swallowed.
|
||||
- Print full error when an invalid flag is specified (which includes the invalid flag)
|
||||
- `App.Writer` defaults to `stdout` when `nil`
|
||||
- If no action is specified on a command or app, the help is now printed instead of `panic`ing
|
||||
- `App.Metadata` is initialized automatically now (previously was `nil` unless initialized)
|
||||
- Correctly show help message if `-h` is provided to a subcommand
|
||||
- `context.(Global)IsSet` now respects environment variables. Previously it
|
||||
would return `false` if a flag was specified in the environment rather than
|
||||
as an argument
|
||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
|
||||
- `altsrc`s import paths were updated to use `gopkg.in/urfave/cli.v1`. This
|
||||
fixes issues that occurred when `gopkg.in/urfave/cli.v1` was imported as well
|
||||
as `altsrc` where Go would complain that the types didn't match
|
||||
|
||||
## [1.18.1] - 2016-08-28
|
||||
### Fixed
|
||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user (backported)
|
||||
|
||||
## [1.18.0] - 2016-06-27
|
||||
### Added
|
||||
- `./runtests` test runner with coverage tracking by default
|
||||
- testing on OS X
|
||||
- testing on Windows
|
||||
- `UintFlag`, `Uint64Flag`, and `Int64Flag` types and supporting code
|
||||
|
||||
### Changed
|
||||
- Use spaces for alignment in help/usage output instead of tabs, making the
|
||||
output alignment consistent regardless of tab width
|
||||
|
||||
### Fixed
|
||||
- Printing of command aliases in help text
|
||||
- Printing of visible flags for both struct and struct pointer flags
|
||||
- Display the `help` subcommand when using `CommandCategories`
|
||||
- No longer swallows `panic`s that occur within the `Action`s themselves when
|
||||
detecting the signature of the `Action` field
|
||||
|
||||
## [1.17.1] - 2016-08-28
|
||||
### Fixed
|
||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
|
||||
|
||||
## [1.17.0] - 2016-05-09
|
||||
### Added
|
||||
- Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc`
|
||||
- `context.GlobalBoolT` was added as an analogue to `context.GlobalBool`
|
||||
- Support for hiding commands by setting `Hidden: true` -- this will hide the
|
||||
commands in help output
|
||||
|
||||
### Changed
|
||||
- `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer
|
||||
quoted in help text output.
|
||||
- All flag types now include `(default: {value})` strings following usage when a
|
||||
default value can be (reasonably) detected.
|
||||
- `IntSliceFlag` and `StringSliceFlag` usage strings are now more consistent
|
||||
with non-slice flag types
|
||||
- Apps now exit with a code of 3 if an unknown subcommand is specified
|
||||
(previously they printed "No help topic for...", but still exited 0. This
|
||||
makes it easier to script around apps built using `cli` since they can trust
|
||||
that a 0 exit code indicated a successful execution.
|
||||
- cleanups based on [Go Report Card
|
||||
feedback](https://goreportcard.com/report/github.com/urfave/cli)
|
||||
|
||||
## [1.16.1] - 2016-08-28
|
||||
### Fixed
|
||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user
|
||||
|
||||
## [1.16.0] - 2016-05-02
|
||||
### Added
|
||||
- `Hidden` field on all flag struct types to omit from generated help text
|
||||
|
||||
### Changed
|
||||
- `BashCompletionFlag` (`--enable-bash-completion`) is now omitted from
|
||||
generated help text via the `Hidden` field
|
||||
|
||||
### Fixed
|
||||
- handling of error values in `HandleAction` and `HandleExitCoder`
|
||||
|
||||
## [1.15.0] - 2016-04-30
|
||||
### Added
|
||||
- This file!
|
||||
- Support for placeholders in flag usage strings
|
||||
- `App.Metadata` map for arbitrary data/state management
|
||||
- `Set` and `GlobalSet` methods on `*cli.Context` for altering values after
|
||||
parsing.
|
||||
- Support for nested lookup of dot-delimited keys in structures loaded from
|
||||
YAML.
|
||||
|
||||
### Changed
|
||||
- The `App.Action` and `Command.Action` now prefer a return signature of
|
||||
`func(*cli.Context) error`, as defined by `cli.ActionFunc`. If a non-nil
|
||||
`error` is returned, there may be two outcomes:
|
||||
- If the error fulfills `cli.ExitCoder`, then `os.Exit` will be called
|
||||
automatically
|
||||
- Else the error is bubbled up and returned from `App.Run`
|
||||
- Specifying an `Action` with the legacy return signature of
|
||||
`func(*cli.Context)` will produce a deprecation message to stderr
|
||||
- Specifying an `Action` that is not a `func` type will produce a non-zero exit
|
||||
from `App.Run`
|
||||
- Specifying an `Action` func that has an invalid (input) signature will
|
||||
produce a non-zero exit from `App.Run`
|
||||
|
||||
### Deprecated
|
||||
- <a name="deprecated-cli-app-runandexitonerror"></a>
|
||||
`cli.App.RunAndExitOnError`, which should now be done by returning an error
|
||||
that fulfills `cli.ExitCoder` to `cli.App.Run`.
|
||||
- <a name="deprecated-cli-app-action-signature"></a> the legacy signature for
|
||||
`cli.App.Action` of `func(*cli.Context)`, which should now have a return
|
||||
signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`.
|
||||
|
||||
### Fixed
|
||||
- Added missing `*cli.Context.GlobalFloat64` method
|
||||
|
||||
## [1.14.0] - 2016-04-03 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Codebeat badge
|
||||
- Support for categorization via `CategorizedHelp` and `Categories` on app.
|
||||
|
||||
### Changed
|
||||
- Use `filepath.Base` instead of `path.Base` in `Name` and `HelpName`.
|
||||
|
||||
### Fixed
|
||||
- Ensure version is not shown in help text when `HideVersion` set.
|
||||
|
||||
## [1.13.0] - 2016-03-06 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- YAML file input support.
|
||||
- `NArg` method on context.
|
||||
|
||||
## [1.12.0] - 2016-02-17 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Custom usage error handling.
|
||||
- Custom text support in `USAGE` section of help output.
|
||||
- Improved help messages for empty strings.
|
||||
- AppVeyor CI configuration.
|
||||
|
||||
### Changed
|
||||
- Removed `panic` from default help printer func.
|
||||
- De-duping and optimizations.
|
||||
|
||||
### Fixed
|
||||
- Correctly handle `Before`/`After` at command level when no subcommands.
|
||||
- Case of literal `-` argument causing flag reordering.
|
||||
- Environment variable hints on Windows.
|
||||
- Docs updates.
|
||||
|
||||
## [1.11.1] - 2015-12-21 (backfilled 2016-04-25)
|
||||
### Changed
|
||||
- Use `path.Base` in `Name` and `HelpName`
|
||||
- Export `GetName` on flag types.
|
||||
|
||||
### Fixed
|
||||
- Flag parsing when skipping is enabled.
|
||||
- Test output cleanup.
|
||||
- Move completion check to account for empty input case.
|
||||
|
||||
## [1.11.0] - 2015-11-15 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Destination scan support for flags.
|
||||
- Testing against `tip` in Travis CI config.
|
||||
|
||||
### Changed
|
||||
- Go version in Travis CI config.
|
||||
|
||||
### Fixed
|
||||
- Removed redundant tests.
|
||||
- Use correct example naming in tests.
|
||||
|
||||
## [1.10.2] - 2015-10-29 (backfilled 2016-04-25)
|
||||
### Fixed
|
||||
- Remove unused var in bash completion.
|
||||
|
||||
## [1.10.1] - 2015-10-21 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Coverage and reference logos in README.
|
||||
|
||||
### Fixed
|
||||
- Use specified values in help and version parsing.
|
||||
- Only display app version and help message once.
|
||||
|
||||
## [1.10.0] - 2015-10-06 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- More tests for existing functionality.
|
||||
- `ArgsUsage` at app and command level for help text flexibility.
|
||||
|
||||
### Fixed
|
||||
- Honor `HideHelp` and `HideVersion` in `App.Run`.
|
||||
- Remove juvenile word from README.
|
||||
|
||||
## [1.9.0] - 2015-09-08 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- `FullName` on command with accompanying help output update.
|
||||
- Set default `$PROG` in bash completion.
|
||||
|
||||
### Changed
|
||||
- Docs formatting.
|
||||
|
||||
### Fixed
|
||||
- Removed self-referential imports in tests.
|
||||
|
||||
## [1.8.0] - 2015-06-30 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Support for `Copyright` at app level.
|
||||
- `Parent` func at context level to walk up context lineage.
|
||||
|
||||
### Fixed
|
||||
- Global flag processing at top level.
|
||||
|
||||
## [1.7.1] - 2015-06-11 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Aggregate errors from `Before`/`After` funcs.
|
||||
- Doc comments on flag structs.
|
||||
- Include non-global flags when checking version and help.
|
||||
- Travis CI config updates.
|
||||
|
||||
### Fixed
|
||||
- Ensure slice type flags have non-nil values.
|
||||
- Collect global flags from the full command hierarchy.
|
||||
- Docs prose.
|
||||
|
||||
## [1.7.0] - 2015-05-03 (backfilled 2016-04-25)
|
||||
### Changed
|
||||
- `HelpPrinter` signature includes output writer.
|
||||
|
||||
### Fixed
|
||||
- Specify go 1.1+ in docs.
|
||||
- Set `Writer` when running command as app.
|
||||
|
||||
## [1.6.0] - 2015-03-23 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Multiple author support.
|
||||
- `NumFlags` at context level.
|
||||
- `Aliases` at command level.
|
||||
|
||||
### Deprecated
|
||||
- `ShortName` at command level.
|
||||
|
||||
### Fixed
|
||||
- Subcommand help output.
|
||||
- Backward compatible support for deprecated `Author` and `Email` fields.
|
||||
- Docs regarding `Names`/`Aliases`.
|
||||
|
||||
## [1.5.0] - 2015-02-20 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- `After` hook func support at app and command level.
|
||||
|
||||
### Fixed
|
||||
- Use parsed context when running command as subcommand.
|
||||
- Docs prose.
|
||||
|
||||
## [1.4.1] - 2015-01-09 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Support for hiding `-h / --help` flags, but not `help` subcommand.
|
||||
- Stop flag parsing after `--`.
|
||||
|
||||
### Fixed
|
||||
- Help text for generic flags to specify single value.
|
||||
- Use double quotes in output for defaults.
|
||||
- Use `ParseInt` instead of `ParseUint` for int environment var values.
|
||||
- Use `0` as base when parsing int environment var values.
|
||||
|
||||
## [1.4.0] - 2014-12-12 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Support for environment variable lookup "cascade".
|
||||
- Support for `Stdout` on app for output redirection.
|
||||
|
||||
### Fixed
|
||||
- Print command help instead of app help in `ShowCommandHelp`.
|
||||
|
||||
## [1.3.1] - 2014-11-13 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Docs and example code updates.
|
||||
|
||||
### Changed
|
||||
- Default `-v / --version` flag made optional.
|
||||
|
||||
## [1.3.0] - 2014-08-10 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- `FlagNames` at context level.
|
||||
- Exposed `VersionPrinter` var for more control over version output.
|
||||
- Zsh completion hook.
|
||||
- `AUTHOR` section in default app help template.
|
||||
- Contribution guidelines.
|
||||
- `DurationFlag` type.
|
||||
|
||||
## [1.2.0] - 2014-08-02
|
||||
### Added
|
||||
- Support for environment variable defaults on flags plus tests.
|
||||
|
||||
## [1.1.0] - 2014-07-15
|
||||
### Added
|
||||
- Bash completion.
|
||||
- Optional hiding of built-in help command.
|
||||
- Optional skipping of flag parsing at command level.
|
||||
- `Author`, `Email`, and `Compiled` metadata on app.
|
||||
- `Before` hook func support at app and command level.
|
||||
- `CommandNotFound` func support at app level.
|
||||
- Command reference available on context.
|
||||
- `GenericFlag` type.
|
||||
- `Float64Flag` type.
|
||||
- `BoolTFlag` type.
|
||||
- `IsSet` flag helper on context.
|
||||
- More flag lookup funcs at context level.
|
||||
- More tests & docs.
|
||||
|
||||
### Changed
|
||||
- Help template updates to account for presence/absence of flags.
|
||||
- Separated subcommand help template.
|
||||
- Exposed `HelpPrinter` var for more control over help output.
|
||||
|
||||
## [1.0.0] - 2013-11-01
|
||||
### Added
|
||||
- `help` flag in default app flag set and each command flag set.
|
||||
- Custom handling of argument parsing errors.
|
||||
- Command lookup by name at app level.
|
||||
- `StringSliceFlag` type and supporting `StringSlice` type.
|
||||
- `IntSliceFlag` type and supporting `IntSlice` type.
|
||||
- Slice type flag lookups by name at context level.
|
||||
- Export of app and command help functions.
|
||||
- More tests & docs.
|
||||
|
||||
## 0.1.0 - 2013-07-22
|
||||
### Added
|
||||
- Initial implementation.
|
||||
|
||||
[Unreleased]: https://github.com/urfave/cli/compare/v1.18.0...HEAD
|
||||
[1.18.0]: https://github.com/urfave/cli/compare/v1.17.0...v1.18.0
|
||||
[1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0
|
||||
[1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0
|
||||
[1.15.0]: https://github.com/urfave/cli/compare/v1.14.0...v1.15.0
|
||||
[1.14.0]: https://github.com/urfave/cli/compare/v1.13.0...v1.14.0
|
||||
[1.13.0]: https://github.com/urfave/cli/compare/v1.12.0...v1.13.0
|
||||
[1.12.0]: https://github.com/urfave/cli/compare/v1.11.1...v1.12.0
|
||||
[1.11.1]: https://github.com/urfave/cli/compare/v1.11.0...v1.11.1
|
||||
[1.11.0]: https://github.com/urfave/cli/compare/v1.10.2...v1.11.0
|
||||
[1.10.2]: https://github.com/urfave/cli/compare/v1.10.1...v1.10.2
|
||||
[1.10.1]: https://github.com/urfave/cli/compare/v1.10.0...v1.10.1
|
||||
[1.10.0]: https://github.com/urfave/cli/compare/v1.9.0...v1.10.0
|
||||
[1.9.0]: https://github.com/urfave/cli/compare/v1.8.0...v1.9.0
|
||||
[1.8.0]: https://github.com/urfave/cli/compare/v1.7.1...v1.8.0
|
||||
[1.7.1]: https://github.com/urfave/cli/compare/v1.7.0...v1.7.1
|
||||
[1.7.0]: https://github.com/urfave/cli/compare/v1.6.0...v1.7.0
|
||||
[1.6.0]: https://github.com/urfave/cli/compare/v1.5.0...v1.6.0
|
||||
[1.5.0]: https://github.com/urfave/cli/compare/v1.4.1...v1.5.0
|
||||
[1.4.1]: https://github.com/urfave/cli/compare/v1.4.0...v1.4.1
|
||||
[1.4.0]: https://github.com/urfave/cli/compare/v1.3.1...v1.4.0
|
||||
[1.3.1]: https://github.com/urfave/cli/compare/v1.3.0...v1.3.1
|
||||
[1.3.0]: https://github.com/urfave/cli/compare/v1.2.0...v1.3.0
|
||||
[1.2.0]: https://github.com/urfave/cli/compare/v1.1.0...v1.2.0
|
||||
[1.1.0]: https://github.com/urfave/cli/compare/v1.0.0...v1.1.0
|
||||
[1.0.0]: https://github.com/urfave/cli/compare/v0.1.0...v1.0.0
|
21
vendor/github.com/urfave/cli/LICENSE
generated
vendored
Normal file
21
vendor/github.com/urfave/cli/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Jeremy Saenz & Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
1381
vendor/github.com/urfave/cli/README.md
generated
vendored
Normal file
1381
vendor/github.com/urfave/cli/README.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3
vendor/github.com/urfave/cli/altsrc/altsrc.go
generated
vendored
Normal file
3
vendor/github.com/urfave/cli/altsrc/altsrc.go
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
package altsrc
|
||||
|
||||
//go:generate python ../generate-flag-types altsrc -i ../flag-types.json -o flag_generated.go
|
261
vendor/github.com/urfave/cli/altsrc/flag.go
generated
vendored
Normal file
261
vendor/github.com/urfave/cli/altsrc/flag.go
generated
vendored
Normal file
@ -0,0 +1,261 @@
|
||||
package altsrc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
// FlagInputSourceExtension is an extension interface of cli.Flag that
|
||||
// allows a value to be set on the existing parsed flags.
|
||||
type FlagInputSourceExtension interface {
|
||||
cli.Flag
|
||||
ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error
|
||||
}
|
||||
|
||||
// ApplyInputSourceValues iterates over all provided flags and
|
||||
// executes ApplyInputSourceValue on flags implementing the
|
||||
// FlagInputSourceExtension interface to initialize these flags
|
||||
// to an alternate input source.
|
||||
func ApplyInputSourceValues(context *cli.Context, inputSourceContext InputSourceContext, flags []cli.Flag) error {
|
||||
for _, f := range flags {
|
||||
inputSourceExtendedFlag, isType := f.(FlagInputSourceExtension)
|
||||
if isType {
|
||||
err := inputSourceExtendedFlag.ApplyInputSourceValue(context, inputSourceContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitInputSource is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new
|
||||
// input source based on the func provided. If there is no error it will then apply the new input source to any flags
|
||||
// that are supported by the input source
|
||||
func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceContext, error)) cli.BeforeFunc {
|
||||
return func(context *cli.Context) error {
|
||||
inputSource, err := createInputSource()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create input source: inner error: \n'%v'", err.Error())
|
||||
}
|
||||
|
||||
return ApplyInputSourceValues(context, inputSource, flags)
|
||||
}
|
||||
}
|
||||
|
||||
// InitInputSourceWithContext is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new
|
||||
// input source based on the func provided with potentially using existing cli.Context values to initialize itself. If there is
|
||||
// no error it will then apply the new input source to any flags that are supported by the input source
|
||||
func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context *cli.Context) (InputSourceContext, error)) cli.BeforeFunc {
|
||||
return func(context *cli.Context) error {
|
||||
inputSource, err := createInputSource(context)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create input source with context: inner error: \n'%v'", err.Error())
|
||||
}
|
||||
|
||||
return ApplyInputSourceValues(context, inputSource, flags)
|
||||
}
|
||||
}
|
||||
|
||||
// ApplyInputSourceValue applies a generic value to the flagSet if required
|
||||
func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil {
|
||||
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
|
||||
value, err := isc.Generic(f.GenericFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value != nil {
|
||||
eachName(f.Name, func(name string) {
|
||||
f.set.Set(f.Name, value.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyInputSourceValue applies a StringSlice value to the flagSet if required
|
||||
func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil {
|
||||
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
|
||||
value, err := isc.StringSlice(f.StringSliceFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value != nil {
|
||||
var sliceValue cli.StringSlice = value
|
||||
eachName(f.Name, func(name string) {
|
||||
underlyingFlag := f.set.Lookup(f.Name)
|
||||
if underlyingFlag != nil {
|
||||
underlyingFlag.Value = &sliceValue
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyInputSourceValue applies a IntSlice value if required
|
||||
func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil {
|
||||
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
|
||||
value, err := isc.IntSlice(f.IntSliceFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value != nil {
|
||||
var sliceValue cli.IntSlice = value
|
||||
eachName(f.Name, func(name string) {
|
||||
underlyingFlag := f.set.Lookup(f.Name)
|
||||
if underlyingFlag != nil {
|
||||
underlyingFlag.Value = &sliceValue
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyInputSourceValue applies a Bool value to the flagSet if required
|
||||
func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil {
|
||||
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
|
||||
value, err := isc.Bool(f.BoolFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value {
|
||||
eachName(f.Name, func(name string) {
|
||||
f.set.Set(f.Name, strconv.FormatBool(value))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyInputSourceValue applies a BoolT value to the flagSet if required
|
||||
func (f *BoolTFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil {
|
||||
if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) {
|
||||
value, err := isc.BoolT(f.BoolTFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !value {
|
||||
eachName(f.Name, func(name string) {
|
||||
f.set.Set(f.Name, strconv.FormatBool(value))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyInputSourceValue applies a String value to the flagSet if required
|
||||
func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil {
|
||||
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) {
|
||||
value, err := isc.String(f.StringFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value != "" {
|
||||
eachName(f.Name, func(name string) {
|
||||
f.set.Set(f.Name, value)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyInputSourceValue applies a int value to the flagSet if required
|
||||
func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil {
|
||||
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) {
|
||||
value, err := isc.Int(f.IntFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value > 0 {
|
||||
eachName(f.Name, func(name string) {
|
||||
f.set.Set(f.Name, strconv.FormatInt(int64(value), 10))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyInputSourceValue applies a Duration value to the flagSet if required
|
||||
func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil {
|
||||
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) {
|
||||
value, err := isc.Duration(f.DurationFlag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value > 0 {
|
||||
eachName(f.Name, func(name string) {
|
||||
f.set.Set(f.Name, value.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyInputSourceValue applies a Float64 value to the flagSet if required
|
||||
func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error {
|
||||
if f.set != nil {
|
||||
if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) {
|
||||
value, err := isc.Float64(f.Float64Flag.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value > 0 {
|
||||
floatStr := float64ToString(value)
|
||||
eachName(f.Name, func(name string) {
|
||||
f.set.Set(f.Name, floatStr)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isEnvVarSet(envVars string) bool {
|
||||
for _, envVar := range strings.Split(envVars, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if _, ok := syscall.Getenv(envVar); ok {
|
||||
// TODO: Can't use this for bools as
|
||||
// set means that it was true or false based on
|
||||
// Bool flag type, should work for other types
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func float64ToString(f float64) string {
|
||||
return fmt.Sprintf("%v", f)
|
||||
}
|
||||
|
||||
func eachName(longName string, fn func(string)) {
|
||||
parts := strings.Split(longName, ",")
|
||||
for _, name := range parts {
|
||||
name = strings.Trim(name, " ")
|
||||
fn(name)
|
||||
}
|
||||
}
|
347
vendor/github.com/urfave/cli/altsrc/flag_generated.go
generated
vendored
Normal file
347
vendor/github.com/urfave/cli/altsrc/flag_generated.go
generated
vendored
Normal file
@ -0,0 +1,347 @@
|
||||
package altsrc
|
||||
|
||||
import (
|
||||
"flag"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
// WARNING: This file is generated!
|
||||
|
||||
// BoolFlag is the flag type that wraps cli.BoolFlag to allow
|
||||
// for other values to be specified
|
||||
type BoolFlag struct {
|
||||
cli.BoolFlag
|
||||
set *flag.FlagSet
|
||||
}
|
||||
|
||||
// NewBoolFlag creates a new BoolFlag
|
||||
func NewBoolFlag(fl cli.BoolFlag) *BoolFlag {
|
||||
return &BoolFlag{BoolFlag: fl, set: nil}
|
||||
}
|
||||
|
||||
// Apply saves the flagSet for later usage calls, then calls the
|
||||
// wrapped BoolFlag.Apply
|
||||
func (f *BoolFlag) Apply(set *flag.FlagSet) {
|
||||
f.set = set
|
||||
f.BoolFlag.Apply(set)
|
||||
}
|
||||
|
||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
||||
// wrapped BoolFlag.ApplyWithError
|
||||
func (f *BoolFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
f.set = set
|
||||
return f.BoolFlag.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// BoolTFlag is the flag type that wraps cli.BoolTFlag to allow
|
||||
// for other values to be specified
|
||||
type BoolTFlag struct {
|
||||
cli.BoolTFlag
|
||||
set *flag.FlagSet
|
||||
}
|
||||
|
||||
// NewBoolTFlag creates a new BoolTFlag
|
||||
func NewBoolTFlag(fl cli.BoolTFlag) *BoolTFlag {
|
||||
return &BoolTFlag{BoolTFlag: fl, set: nil}
|
||||
}
|
||||
|
||||
// Apply saves the flagSet for later usage calls, then calls the
|
||||
// wrapped BoolTFlag.Apply
|
||||
func (f *BoolTFlag) Apply(set *flag.FlagSet) {
|
||||
f.set = set
|
||||
f.BoolTFlag.Apply(set)
|
||||
}
|
||||
|
||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
||||
// wrapped BoolTFlag.ApplyWithError
|
||||
func (f *BoolTFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
f.set = set
|
||||
return f.BoolTFlag.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// DurationFlag is the flag type that wraps cli.DurationFlag to allow
|
||||
// for other values to be specified
|
||||
type DurationFlag struct {
|
||||
cli.DurationFlag
|
||||
set *flag.FlagSet
|
||||
}
|
||||
|
||||
// NewDurationFlag creates a new DurationFlag
|
||||
func NewDurationFlag(fl cli.DurationFlag) *DurationFlag {
|
||||
return &DurationFlag{DurationFlag: fl, set: nil}
|
||||
}
|
||||
|
||||
// Apply saves the flagSet for later usage calls, then calls the
|
||||
// wrapped DurationFlag.Apply
|
||||
func (f *DurationFlag) Apply(set *flag.FlagSet) {
|
||||
f.set = set
|
||||
f.DurationFlag.Apply(set)
|
||||
}
|
||||
|
||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
||||
// wrapped DurationFlag.ApplyWithError
|
||||
func (f *DurationFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
f.set = set
|
||||
return f.DurationFlag.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// Float64Flag is the flag type that wraps cli.Float64Flag to allow
|
||||
// for other values to be specified
|
||||
type Float64Flag struct {
|
||||
cli.Float64Flag
|
||||
set *flag.FlagSet
|
||||
}
|
||||
|
||||
// NewFloat64Flag creates a new Float64Flag
|
||||
func NewFloat64Flag(fl cli.Float64Flag) *Float64Flag {
|
||||
return &Float64Flag{Float64Flag: fl, set: nil}
|
||||
}
|
||||
|
||||
// Apply saves the flagSet for later usage calls, then calls the
|
||||
// wrapped Float64Flag.Apply
|
||||
func (f *Float64Flag) Apply(set *flag.FlagSet) {
|
||||
f.set = set
|
||||
f.Float64Flag.Apply(set)
|
||||
}
|
||||
|
||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
||||
// wrapped Float64Flag.ApplyWithError
|
||||
func (f *Float64Flag) ApplyWithError(set *flag.FlagSet) error {
|
||||
f.set = set
|
||||
return f.Float64Flag.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// GenericFlag is the flag type that wraps cli.GenericFlag to allow
|
||||
// for other values to be specified
|
||||
type GenericFlag struct {
|
||||
cli.GenericFlag
|
||||
set *flag.FlagSet
|
||||
}
|
||||
|
||||
// NewGenericFlag creates a new GenericFlag
|
||||
func NewGenericFlag(fl cli.GenericFlag) *GenericFlag {
|
||||
return &GenericFlag{GenericFlag: fl, set: nil}
|
||||
}
|
||||
|
||||
// Apply saves the flagSet for later usage calls, then calls the
|
||||
// wrapped GenericFlag.Apply
|
||||
func (f *GenericFlag) Apply(set *flag.FlagSet) {
|
||||
f.set = set
|
||||
f.GenericFlag.Apply(set)
|
||||
}
|
||||
|
||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
||||
// wrapped GenericFlag.ApplyWithError
|
||||
func (f *GenericFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
f.set = set
|
||||
return f.GenericFlag.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// Int64Flag is the flag type that wraps cli.Int64Flag to allow
|
||||
// for other values to be specified
|
||||
type Int64Flag struct {
|
||||
cli.Int64Flag
|
||||
set *flag.FlagSet
|
||||
}
|
||||
|
||||
// NewInt64Flag creates a new Int64Flag
|
||||
func NewInt64Flag(fl cli.Int64Flag) *Int64Flag {
|
||||
return &Int64Flag{Int64Flag: fl, set: nil}
|
||||
}
|
||||
|
||||
// Apply saves the flagSet for later usage calls, then calls the
|
||||
// wrapped Int64Flag.Apply
|
||||
func (f *Int64Flag) Apply(set *flag.FlagSet) {
|
||||
f.set = set
|
||||
f.Int64Flag.Apply(set)
|
||||
}
|
||||
|
||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
||||
// wrapped Int64Flag.ApplyWithError
|
||||
func (f *Int64Flag) ApplyWithError(set *flag.FlagSet) error {
|
||||
f.set = set
|
||||
return f.Int64Flag.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// IntFlag is the flag type that wraps cli.IntFlag to allow
|
||||
// for other values to be specified
|
||||
type IntFlag struct {
|
||||
cli.IntFlag
|
||||
set *flag.FlagSet
|
||||
}
|
||||
|
||||
// NewIntFlag creates a new IntFlag
|
||||
func NewIntFlag(fl cli.IntFlag) *IntFlag {
|
||||
return &IntFlag{IntFlag: fl, set: nil}
|
||||
}
|
||||
|
||||
// Apply saves the flagSet for later usage calls, then calls the
|
||||
// wrapped IntFlag.Apply
|
||||
func (f *IntFlag) Apply(set *flag.FlagSet) {
|
||||
f.set = set
|
||||
f.IntFlag.Apply(set)
|
||||
}
|
||||
|
||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
||||
// wrapped IntFlag.ApplyWithError
|
||||
func (f *IntFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
f.set = set
|
||||
return f.IntFlag.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// IntSliceFlag is the flag type that wraps cli.IntSliceFlag to allow
|
||||
// for other values to be specified
|
||||
type IntSliceFlag struct {
|
||||
cli.IntSliceFlag
|
||||
set *flag.FlagSet
|
||||
}
|
||||
|
||||
// NewIntSliceFlag creates a new IntSliceFlag
|
||||
func NewIntSliceFlag(fl cli.IntSliceFlag) *IntSliceFlag {
|
||||
return &IntSliceFlag{IntSliceFlag: fl, set: nil}
|
||||
}
|
||||
|
||||
// Apply saves the flagSet for later usage calls, then calls the
|
||||
// wrapped IntSliceFlag.Apply
|
||||
func (f *IntSliceFlag) Apply(set *flag.FlagSet) {
|
||||
f.set = set
|
||||
f.IntSliceFlag.Apply(set)
|
||||
}
|
||||
|
||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
||||
// wrapped IntSliceFlag.ApplyWithError
|
||||
func (f *IntSliceFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
f.set = set
|
||||
return f.IntSliceFlag.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// Int64SliceFlag is the flag type that wraps cli.Int64SliceFlag to allow
|
||||
// for other values to be specified
|
||||
type Int64SliceFlag struct {
|
||||
cli.Int64SliceFlag
|
||||
set *flag.FlagSet
|
||||
}
|
||||
|
||||
// NewInt64SliceFlag creates a new Int64SliceFlag
|
||||
func NewInt64SliceFlag(fl cli.Int64SliceFlag) *Int64SliceFlag {
|
||||
return &Int64SliceFlag{Int64SliceFlag: fl, set: nil}
|
||||
}
|
||||
|
||||
// Apply saves the flagSet for later usage calls, then calls the
|
||||
// wrapped Int64SliceFlag.Apply
|
||||
func (f *Int64SliceFlag) Apply(set *flag.FlagSet) {
|
||||
f.set = set
|
||||
f.Int64SliceFlag.Apply(set)
|
||||
}
|
||||
|
||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
||||
// wrapped Int64SliceFlag.ApplyWithError
|
||||
func (f *Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
f.set = set
|
||||
return f.Int64SliceFlag.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// StringFlag is the flag type that wraps cli.StringFlag to allow
|
||||
// for other values to be specified
|
||||
type StringFlag struct {
|
||||
cli.StringFlag
|
||||
set *flag.FlagSet
|
||||
}
|
||||
|
||||
// NewStringFlag creates a new StringFlag
|
||||
func NewStringFlag(fl cli.StringFlag) *StringFlag {
|
||||
return &StringFlag{StringFlag: fl, set: nil}
|
||||
}
|
||||
|
||||
// Apply saves the flagSet for later usage calls, then calls the
|
||||
// wrapped StringFlag.Apply
|
||||
func (f *StringFlag) Apply(set *flag.FlagSet) {
|
||||
f.set = set
|
||||
f.StringFlag.Apply(set)
|
||||
}
|
||||
|
||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
||||
// wrapped StringFlag.ApplyWithError
|
||||
func (f *StringFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
f.set = set
|
||||
return f.StringFlag.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// StringSliceFlag is the flag type that wraps cli.StringSliceFlag to allow
|
||||
// for other values to be specified
|
||||
type StringSliceFlag struct {
|
||||
cli.StringSliceFlag
|
||||
set *flag.FlagSet
|
||||
}
|
||||
|
||||
// NewStringSliceFlag creates a new StringSliceFlag
|
||||
func NewStringSliceFlag(fl cli.StringSliceFlag) *StringSliceFlag {
|
||||
return &StringSliceFlag{StringSliceFlag: fl, set: nil}
|
||||
}
|
||||
|
||||
// Apply saves the flagSet for later usage calls, then calls the
|
||||
// wrapped StringSliceFlag.Apply
|
||||
func (f *StringSliceFlag) Apply(set *flag.FlagSet) {
|
||||
f.set = set
|
||||
f.StringSliceFlag.Apply(set)
|
||||
}
|
||||
|
||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
||||
// wrapped StringSliceFlag.ApplyWithError
|
||||
func (f *StringSliceFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
f.set = set
|
||||
return f.StringSliceFlag.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// Uint64Flag is the flag type that wraps cli.Uint64Flag to allow
|
||||
// for other values to be specified
|
||||
type Uint64Flag struct {
|
||||
cli.Uint64Flag
|
||||
set *flag.FlagSet
|
||||
}
|
||||
|
||||
// NewUint64Flag creates a new Uint64Flag
|
||||
func NewUint64Flag(fl cli.Uint64Flag) *Uint64Flag {
|
||||
return &Uint64Flag{Uint64Flag: fl, set: nil}
|
||||
}
|
||||
|
||||
// Apply saves the flagSet for later usage calls, then calls the
|
||||
// wrapped Uint64Flag.Apply
|
||||
func (f *Uint64Flag) Apply(set *flag.FlagSet) {
|
||||
f.set = set
|
||||
f.Uint64Flag.Apply(set)
|
||||
}
|
||||
|
||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
||||
// wrapped Uint64Flag.ApplyWithError
|
||||
func (f *Uint64Flag) ApplyWithError(set *flag.FlagSet) error {
|
||||
f.set = set
|
||||
return f.Uint64Flag.ApplyWithError(set)
|
||||
}
|
||||
|
||||
// UintFlag is the flag type that wraps cli.UintFlag to allow
|
||||
// for other values to be specified
|
||||
type UintFlag struct {
|
||||
cli.UintFlag
|
||||
set *flag.FlagSet
|
||||
}
|
||||
|
||||
// NewUintFlag creates a new UintFlag
|
||||
func NewUintFlag(fl cli.UintFlag) *UintFlag {
|
||||
return &UintFlag{UintFlag: fl, set: nil}
|
||||
}
|
||||
|
||||
// Apply saves the flagSet for later usage calls, then calls the
|
||||
// wrapped UintFlag.Apply
|
||||
func (f *UintFlag) Apply(set *flag.FlagSet) {
|
||||
f.set = set
|
||||
f.UintFlag.Apply(set)
|
||||
}
|
||||
|
||||
// ApplyWithError saves the flagSet for later usage calls, then calls the
|
||||
// wrapped UintFlag.ApplyWithError
|
||||
func (f *UintFlag) ApplyWithError(set *flag.FlagSet) error {
|
||||
f.set = set
|
||||
return f.UintFlag.ApplyWithError(set)
|
||||
}
|
336
vendor/github.com/urfave/cli/altsrc/flag_test.go
generated
vendored
Normal file
336
vendor/github.com/urfave/cli/altsrc/flag_test.go
generated
vendored
Normal file
@ -0,0 +1,336 @@
|
||||
package altsrc
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
type testApplyInputSource struct {
|
||||
Flag FlagInputSourceExtension
|
||||
FlagName string
|
||||
FlagSetName string
|
||||
Expected string
|
||||
ContextValueString string
|
||||
ContextValue flag.Value
|
||||
EnvVarValue string
|
||||
EnvVarName string
|
||||
MapValue interface{}
|
||||
}
|
||||
|
||||
func TestGenericApplyInputSourceValue(t *testing.T) {
|
||||
v := &Parser{"abc", "def"}
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}),
|
||||
FlagName: "test",
|
||||
MapValue: v,
|
||||
})
|
||||
expect(t, v, c.Generic("test"))
|
||||
}
|
||||
|
||||
func TestGenericApplyInputSourceMethodContextSet(t *testing.T) {
|
||||
p := &Parser{"abc", "def"}
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}),
|
||||
FlagName: "test",
|
||||
MapValue: &Parser{"efg", "hig"},
|
||||
ContextValueString: p.String(),
|
||||
})
|
||||
expect(t, p, c.Generic("test"))
|
||||
}
|
||||
|
||||
func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}, EnvVar: "TEST"}),
|
||||
FlagName: "test",
|
||||
MapValue: &Parser{"efg", "hij"},
|
||||
EnvVarName: "TEST",
|
||||
EnvVarValue: "abc,def",
|
||||
})
|
||||
expect(t, &Parser{"abc", "def"}, c.Generic("test"))
|
||||
}
|
||||
|
||||
func TestStringSliceApplyInputSourceValue(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: []interface{}{"hello", "world"},
|
||||
})
|
||||
expect(t, c.StringSlice("test"), []string{"hello", "world"})
|
||||
}
|
||||
|
||||
func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: []interface{}{"hello", "world"},
|
||||
ContextValueString: "ohno",
|
||||
})
|
||||
expect(t, c.StringSlice("test"), []string{"ohno"})
|
||||
}
|
||||
|
||||
func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test", EnvVar: "TEST"}),
|
||||
FlagName: "test",
|
||||
MapValue: []interface{}{"hello", "world"},
|
||||
EnvVarName: "TEST",
|
||||
EnvVarValue: "oh,no",
|
||||
})
|
||||
expect(t, c.StringSlice("test"), []string{"oh", "no"})
|
||||
}
|
||||
|
||||
func TestIntSliceApplyInputSourceValue(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: []interface{}{1, 2},
|
||||
})
|
||||
expect(t, c.IntSlice("test"), []int{1, 2})
|
||||
}
|
||||
|
||||
func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: []interface{}{1, 2},
|
||||
ContextValueString: "3",
|
||||
})
|
||||
expect(t, c.IntSlice("test"), []int{3})
|
||||
}
|
||||
|
||||
func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test", EnvVar: "TEST"}),
|
||||
FlagName: "test",
|
||||
MapValue: []interface{}{1, 2},
|
||||
EnvVarName: "TEST",
|
||||
EnvVarValue: "3,4",
|
||||
})
|
||||
expect(t, c.IntSlice("test"), []int{3, 4})
|
||||
}
|
||||
|
||||
func TestBoolApplyInputSourceMethodSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: true,
|
||||
})
|
||||
expect(t, true, c.Bool("test"))
|
||||
}
|
||||
|
||||
func TestBoolApplyInputSourceMethodContextSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: false,
|
||||
ContextValueString: "true",
|
||||
})
|
||||
expect(t, true, c.Bool("test"))
|
||||
}
|
||||
|
||||
func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewBoolFlag(cli.BoolFlag{Name: "test", EnvVar: "TEST"}),
|
||||
FlagName: "test",
|
||||
MapValue: false,
|
||||
EnvVarName: "TEST",
|
||||
EnvVarValue: "true",
|
||||
})
|
||||
expect(t, true, c.Bool("test"))
|
||||
}
|
||||
|
||||
func TestBoolTApplyInputSourceMethodSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: false,
|
||||
})
|
||||
expect(t, false, c.BoolT("test"))
|
||||
}
|
||||
|
||||
func TestBoolTApplyInputSourceMethodContextSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: true,
|
||||
ContextValueString: "false",
|
||||
})
|
||||
expect(t, false, c.BoolT("test"))
|
||||
}
|
||||
|
||||
func TestBoolTApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test", EnvVar: "TEST"}),
|
||||
FlagName: "test",
|
||||
MapValue: true,
|
||||
EnvVarName: "TEST",
|
||||
EnvVarValue: "false",
|
||||
})
|
||||
expect(t, false, c.BoolT("test"))
|
||||
}
|
||||
|
||||
func TestStringApplyInputSourceMethodSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewStringFlag(cli.StringFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: "hello",
|
||||
})
|
||||
expect(t, "hello", c.String("test"))
|
||||
}
|
||||
|
||||
func TestStringApplyInputSourceMethodContextSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewStringFlag(cli.StringFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: "hello",
|
||||
ContextValueString: "goodbye",
|
||||
})
|
||||
expect(t, "goodbye", c.String("test"))
|
||||
}
|
||||
|
||||
func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewStringFlag(cli.StringFlag{Name: "test", EnvVar: "TEST"}),
|
||||
FlagName: "test",
|
||||
MapValue: "hello",
|
||||
EnvVarName: "TEST",
|
||||
EnvVarValue: "goodbye",
|
||||
})
|
||||
expect(t, "goodbye", c.String("test"))
|
||||
}
|
||||
|
||||
func TestIntApplyInputSourceMethodSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewIntFlag(cli.IntFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: 15,
|
||||
})
|
||||
expect(t, 15, c.Int("test"))
|
||||
}
|
||||
|
||||
func TestIntApplyInputSourceMethodContextSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewIntFlag(cli.IntFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: 15,
|
||||
ContextValueString: "7",
|
||||
})
|
||||
expect(t, 7, c.Int("test"))
|
||||
}
|
||||
|
||||
func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "TEST"}),
|
||||
FlagName: "test",
|
||||
MapValue: 15,
|
||||
EnvVarName: "TEST",
|
||||
EnvVarValue: "12",
|
||||
})
|
||||
expect(t, 12, c.Int("test"))
|
||||
}
|
||||
|
||||
func TestDurationApplyInputSourceMethodSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: time.Duration(30 * time.Second),
|
||||
})
|
||||
expect(t, time.Duration(30*time.Second), c.Duration("test"))
|
||||
}
|
||||
|
||||
func TestDurationApplyInputSourceMethodContextSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: time.Duration(30 * time.Second),
|
||||
ContextValueString: time.Duration(15 * time.Second).String(),
|
||||
})
|
||||
expect(t, time.Duration(15*time.Second), c.Duration("test"))
|
||||
}
|
||||
|
||||
func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewDurationFlag(cli.DurationFlag{Name: "test", EnvVar: "TEST"}),
|
||||
FlagName: "test",
|
||||
MapValue: time.Duration(30 * time.Second),
|
||||
EnvVarName: "TEST",
|
||||
EnvVarValue: time.Duration(15 * time.Second).String(),
|
||||
})
|
||||
expect(t, time.Duration(15*time.Second), c.Duration("test"))
|
||||
}
|
||||
|
||||
func TestFloat64ApplyInputSourceMethodSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: 1.3,
|
||||
})
|
||||
expect(t, 1.3, c.Float64("test"))
|
||||
}
|
||||
|
||||
func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}),
|
||||
FlagName: "test",
|
||||
MapValue: 1.3,
|
||||
ContextValueString: fmt.Sprintf("%v", 1.4),
|
||||
})
|
||||
expect(t, 1.4, c.Float64("test"))
|
||||
}
|
||||
|
||||
func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) {
|
||||
c := runTest(t, testApplyInputSource{
|
||||
Flag: NewFloat64Flag(cli.Float64Flag{Name: "test", EnvVar: "TEST"}),
|
||||
FlagName: "test",
|
||||
MapValue: 1.3,
|
||||
EnvVarName: "TEST",
|
||||
EnvVarValue: fmt.Sprintf("%v", 1.4),
|
||||
})
|
||||
expect(t, 1.4, c.Float64("test"))
|
||||
}
|
||||
|
||||
func runTest(t *testing.T, test testApplyInputSource) *cli.Context {
|
||||
inputSource := &MapInputSource{valueMap: map[interface{}]interface{}{test.FlagName: test.MapValue}}
|
||||
set := flag.NewFlagSet(test.FlagSetName, flag.ContinueOnError)
|
||||
c := cli.NewContext(nil, set, nil)
|
||||
if test.EnvVarName != "" && test.EnvVarValue != "" {
|
||||
os.Setenv(test.EnvVarName, test.EnvVarValue)
|
||||
defer os.Setenv(test.EnvVarName, "")
|
||||
}
|
||||
|
||||
test.Flag.Apply(set)
|
||||
if test.ContextValue != nil {
|
||||
flag := set.Lookup(test.FlagName)
|
||||
flag.Value = test.ContextValue
|
||||
}
|
||||
if test.ContextValueString != "" {
|
||||
set.Set(test.FlagName, test.ContextValueString)
|
||||
}
|
||||
test.Flag.ApplyInputSourceValue(c, inputSource)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
type Parser [2]string
|
||||
|
||||
func (p *Parser) Set(value string) error {
|
||||
parts := strings.Split(value, ",")
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid format")
|
||||
}
|
||||
|
||||
(*p)[0] = parts[0]
|
||||
(*p)[1] = parts[1]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Parser) String() string {
|
||||
return fmt.Sprintf("%s,%s", p[0], p[1])
|
||||
}
|
18
vendor/github.com/urfave/cli/altsrc/helpers_test.go
generated
vendored
Normal file
18
vendor/github.com/urfave/cli/altsrc/helpers_test.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
package altsrc
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func expect(t *testing.T, a interface{}, b interface{}) {
|
||||
if !reflect.DeepEqual(b, a) {
|
||||
t.Errorf("Expected %#v (type %v) - Got %#v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
|
||||
}
|
||||
}
|
||||
|
||||
func refute(t *testing.T, a interface{}, b interface{}) {
|
||||
if a == b {
|
||||
t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
|
||||
}
|
||||
}
|
21
vendor/github.com/urfave/cli/altsrc/input_source_context.go
generated
vendored
Normal file
21
vendor/github.com/urfave/cli/altsrc/input_source_context.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
package altsrc
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
// InputSourceContext is an interface used to allow
|
||||
// other input sources to be implemented as needed.
|
||||
type InputSourceContext interface {
|
||||
Int(name string) (int, error)
|
||||
Duration(name string) (time.Duration, error)
|
||||
Float64(name string) (float64, error)
|
||||
String(name string) (string, error)
|
||||
StringSlice(name string) ([]string, error)
|
||||
IntSlice(name string) ([]int, error)
|
||||
Generic(name string) (cli.Generic, error)
|
||||
Bool(name string) (bool, error)
|
||||
BoolT(name string) (bool, error)
|
||||
}
|
262
vendor/github.com/urfave/cli/altsrc/map_input_source.go
generated
vendored
Normal file
262
vendor/github.com/urfave/cli/altsrc/map_input_source.go
generated
vendored
Normal file
@ -0,0 +1,262 @@
|
||||
package altsrc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
// MapInputSource implements InputSourceContext to return
|
||||
// data from the map that is loaded.
|
||||
type MapInputSource struct {
|
||||
valueMap map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// nestedVal checks if the name has '.' delimiters.
|
||||
// If so, it tries to traverse the tree by the '.' delimited sections to find
|
||||
// a nested value for the key.
|
||||
func nestedVal(name string, tree map[interface{}]interface{}) (interface{}, bool) {
|
||||
if sections := strings.Split(name, "."); len(sections) > 1 {
|
||||
node := tree
|
||||
for _, section := range sections[:len(sections)-1] {
|
||||
if child, ok := node[section]; !ok {
|
||||
return nil, false
|
||||
} else {
|
||||
if ctype, ok := child.(map[interface{}]interface{}); !ok {
|
||||
return nil, false
|
||||
} else {
|
||||
node = ctype
|
||||
}
|
||||
}
|
||||
}
|
||||
if val, ok := node[sections[len(sections)-1]]; ok {
|
||||
return val, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Int returns an int from the map if it exists otherwise returns 0
|
||||
func (fsm *MapInputSource) Int(name string) (int, error) {
|
||||
otherGenericValue, exists := fsm.valueMap[name]
|
||||
if exists {
|
||||
otherValue, isType := otherGenericValue.(int)
|
||||
if !isType {
|
||||
return 0, incorrectTypeForFlagError(name, "int", otherGenericValue)
|
||||
}
|
||||
return otherValue, nil
|
||||
}
|
||||
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
||||
if exists {
|
||||
otherValue, isType := nestedGenericValue.(int)
|
||||
if !isType {
|
||||
return 0, incorrectTypeForFlagError(name, "int", nestedGenericValue)
|
||||
}
|
||||
return otherValue, nil
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Duration returns a duration from the map if it exists otherwise returns 0
|
||||
func (fsm *MapInputSource) Duration(name string) (time.Duration, error) {
|
||||
otherGenericValue, exists := fsm.valueMap[name]
|
||||
if exists {
|
||||
otherValue, isType := otherGenericValue.(time.Duration)
|
||||
if !isType {
|
||||
return 0, incorrectTypeForFlagError(name, "duration", otherGenericValue)
|
||||
}
|
||||
return otherValue, nil
|
||||
}
|
||||
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
||||
if exists {
|
||||
otherValue, isType := nestedGenericValue.(time.Duration)
|
||||
if !isType {
|
||||
return 0, incorrectTypeForFlagError(name, "duration", nestedGenericValue)
|
||||
}
|
||||
return otherValue, nil
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Float64 returns an float64 from the map if it exists otherwise returns 0
|
||||
func (fsm *MapInputSource) Float64(name string) (float64, error) {
|
||||
otherGenericValue, exists := fsm.valueMap[name]
|
||||
if exists {
|
||||
otherValue, isType := otherGenericValue.(float64)
|
||||
if !isType {
|
||||
return 0, incorrectTypeForFlagError(name, "float64", otherGenericValue)
|
||||
}
|
||||
return otherValue, nil
|
||||
}
|
||||
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
||||
if exists {
|
||||
otherValue, isType := nestedGenericValue.(float64)
|
||||
if !isType {
|
||||
return 0, incorrectTypeForFlagError(name, "float64", nestedGenericValue)
|
||||
}
|
||||
return otherValue, nil
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// String returns a string from the map if it exists otherwise returns an empty string
|
||||
func (fsm *MapInputSource) String(name string) (string, error) {
|
||||
otherGenericValue, exists := fsm.valueMap[name]
|
||||
if exists {
|
||||
otherValue, isType := otherGenericValue.(string)
|
||||
if !isType {
|
||||
return "", incorrectTypeForFlagError(name, "string", otherGenericValue)
|
||||
}
|
||||
return otherValue, nil
|
||||
}
|
||||
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
||||
if exists {
|
||||
otherValue, isType := nestedGenericValue.(string)
|
||||
if !isType {
|
||||
return "", incorrectTypeForFlagError(name, "string", nestedGenericValue)
|
||||
}
|
||||
return otherValue, nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// StringSlice returns an []string from the map if it exists otherwise returns nil
|
||||
func (fsm *MapInputSource) StringSlice(name string) ([]string, error) {
|
||||
otherGenericValue, exists := fsm.valueMap[name]
|
||||
if !exists {
|
||||
otherGenericValue, exists = nestedVal(name, fsm.valueMap)
|
||||
if !exists {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
otherValue, isType := otherGenericValue.([]interface{})
|
||||
if !isType {
|
||||
return nil, incorrectTypeForFlagError(name, "[]interface{}", otherGenericValue)
|
||||
}
|
||||
|
||||
var stringSlice = make([]string, 0, len(otherValue))
|
||||
for i, v := range otherValue {
|
||||
stringValue, isType := v.(string)
|
||||
|
||||
if !isType {
|
||||
return nil, incorrectTypeForFlagError(fmt.Sprintf("%s[%d]", name, i), "string", v)
|
||||
}
|
||||
|
||||
stringSlice = append(stringSlice, stringValue)
|
||||
}
|
||||
|
||||
return stringSlice, nil
|
||||
}
|
||||
|
||||
// IntSlice returns an []int from the map if it exists otherwise returns nil
|
||||
func (fsm *MapInputSource) IntSlice(name string) ([]int, error) {
|
||||
otherGenericValue, exists := fsm.valueMap[name]
|
||||
if !exists {
|
||||
otherGenericValue, exists = nestedVal(name, fsm.valueMap)
|
||||
if !exists {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
otherValue, isType := otherGenericValue.([]interface{})
|
||||
if !isType {
|
||||
return nil, incorrectTypeForFlagError(name, "[]interface{}", otherGenericValue)
|
||||
}
|
||||
|
||||
var intSlice = make([]int, 0, len(otherValue))
|
||||
for i, v := range otherValue {
|
||||
intValue, isType := v.(int)
|
||||
|
||||
if !isType {
|
||||
return nil, incorrectTypeForFlagError(fmt.Sprintf("%s[%d]", name, i), "int", v)
|
||||
}
|
||||
|
||||
intSlice = append(intSlice, intValue)
|
||||
}
|
||||
|
||||
return intSlice, nil
|
||||
}
|
||||
|
||||
// Generic returns an cli.Generic from the map if it exists otherwise returns nil
|
||||
func (fsm *MapInputSource) Generic(name string) (cli.Generic, error) {
|
||||
otherGenericValue, exists := fsm.valueMap[name]
|
||||
if exists {
|
||||
otherValue, isType := otherGenericValue.(cli.Generic)
|
||||
if !isType {
|
||||
return nil, incorrectTypeForFlagError(name, "cli.Generic", otherGenericValue)
|
||||
}
|
||||
return otherValue, nil
|
||||
}
|
||||
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
||||
if exists {
|
||||
otherValue, isType := nestedGenericValue.(cli.Generic)
|
||||
if !isType {
|
||||
return nil, incorrectTypeForFlagError(name, "cli.Generic", nestedGenericValue)
|
||||
}
|
||||
return otherValue, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Bool returns an bool from the map otherwise returns false
|
||||
func (fsm *MapInputSource) Bool(name string) (bool, error) {
|
||||
otherGenericValue, exists := fsm.valueMap[name]
|
||||
if exists {
|
||||
otherValue, isType := otherGenericValue.(bool)
|
||||
if !isType {
|
||||
return false, incorrectTypeForFlagError(name, "bool", otherGenericValue)
|
||||
}
|
||||
return otherValue, nil
|
||||
}
|
||||
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
||||
if exists {
|
||||
otherValue, isType := nestedGenericValue.(bool)
|
||||
if !isType {
|
||||
return false, incorrectTypeForFlagError(name, "bool", nestedGenericValue)
|
||||
}
|
||||
return otherValue, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// BoolT returns an bool from the map otherwise returns true
|
||||
func (fsm *MapInputSource) BoolT(name string) (bool, error) {
|
||||
otherGenericValue, exists := fsm.valueMap[name]
|
||||
if exists {
|
||||
otherValue, isType := otherGenericValue.(bool)
|
||||
if !isType {
|
||||
return true, incorrectTypeForFlagError(name, "bool", otherGenericValue)
|
||||
}
|
||||
return otherValue, nil
|
||||
}
|
||||
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
||||
if exists {
|
||||
otherValue, isType := nestedGenericValue.(bool)
|
||||
if !isType {
|
||||
return true, incorrectTypeForFlagError(name, "bool", nestedGenericValue)
|
||||
}
|
||||
return otherValue, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func incorrectTypeForFlagError(name, expectedTypeName string, value interface{}) error {
|
||||
valueType := reflect.TypeOf(value)
|
||||
valueTypeName := ""
|
||||
if valueType != nil {
|
||||
valueTypeName = valueType.Name()
|
||||
}
|
||||
|
||||
return fmt.Errorf("Mismatched type for flag '%s'. Expected '%s' but actual is '%s'", name, expectedTypeName, valueTypeName)
|
||||
}
|
310
vendor/github.com/urfave/cli/altsrc/toml_command_test.go
generated
vendored
Normal file
310
vendor/github.com/urfave/cli/altsrc/toml_command_test.go
generated
vendored
Normal file
@ -0,0 +1,310 @@
|
||||
// Disabling building of toml support in cases where golang is 1.0 or 1.1
|
||||
// as the encoding library is not implemented or supported.
|
||||
|
||||
// +build go1.2
|
||||
|
||||
package altsrc
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
func TestCommandTomFileTest(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
|
||||
defer os.Remove("current.toml")
|
||||
test := []string{"test-cmd", "--load", "current.toml"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("test")
|
||||
expect(t, val, 15)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "test"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandTomlFileTestGlobalEnvVarWins(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
|
||||
defer os.Remove("current.toml")
|
||||
|
||||
os.Setenv("THE_TEST", "10")
|
||||
defer os.Setenv("THE_TEST", "")
|
||||
test := []string{"test-cmd", "--load", "current.toml"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("test")
|
||||
expect(t, val, 10)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "THE_TEST"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
|
||||
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandTomlFileTestGlobalEnvVarWinsNested(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666)
|
||||
defer os.Remove("current.toml")
|
||||
|
||||
os.Setenv("THE_TEST", "10")
|
||||
defer os.Setenv("THE_TEST", "")
|
||||
test := []string{"test-cmd", "--load", "current.toml"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("top.test")
|
||||
expect(t, val, 10)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "top.test", EnvVar: "THE_TEST"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
|
||||
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandTomlFileTestSpecifiedFlagWins(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
|
||||
defer os.Remove("current.toml")
|
||||
|
||||
test := []string{"test-cmd", "--load", "current.toml", "--test", "7"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("test")
|
||||
expect(t, val, 7)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "test"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
|
||||
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandTomlFileTestSpecifiedFlagWinsNested(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.toml", []byte(`[top]
|
||||
test = 15`), 0666)
|
||||
defer os.Remove("current.toml")
|
||||
|
||||
test := []string{"test-cmd", "--load", "current.toml", "--top.test", "7"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("top.test")
|
||||
expect(t, val, 7)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "top.test"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
|
||||
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandTomlFileTestDefaultValueFileWins(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
|
||||
defer os.Remove("current.toml")
|
||||
|
||||
test := []string{"test-cmd", "--load", "current.toml"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("test")
|
||||
expect(t, val, 15)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "test", Value: 7}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
|
||||
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandTomlFileTestDefaultValueFileWinsNested(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666)
|
||||
defer os.Remove("current.toml")
|
||||
|
||||
test := []string{"test-cmd", "--load", "current.toml"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("top.test")
|
||||
expect(t, val, 15)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
|
||||
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWins(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
|
||||
defer os.Remove("current.toml")
|
||||
|
||||
os.Setenv("THE_TEST", "11")
|
||||
defer os.Setenv("THE_TEST", "")
|
||||
|
||||
test := []string{"test-cmd", "--load", "current.toml"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("test")
|
||||
expect(t, val, 11)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVar: "THE_TEST"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWinsNested(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666)
|
||||
defer os.Remove("current.toml")
|
||||
|
||||
os.Setenv("THE_TEST", "11")
|
||||
defer os.Setenv("THE_TEST", "")
|
||||
|
||||
test := []string{"test-cmd", "--load", "current.toml"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("top.test")
|
||||
expect(t, val, 11)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7, EnvVar: "THE_TEST"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load"))
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
113
vendor/github.com/urfave/cli/altsrc/toml_file_loader.go
generated
vendored
Normal file
113
vendor/github.com/urfave/cli/altsrc/toml_file_loader.go
generated
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
// Disabling building of toml support in cases where golang is 1.0 or 1.1
|
||||
// as the encoding library is not implemented or supported.
|
||||
|
||||
// +build go1.2
|
||||
|
||||
package altsrc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
type tomlMap struct {
|
||||
Map map[interface{}]interface{}
|
||||
}
|
||||
|
||||
func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) {
|
||||
ret = make(map[interface{}]interface{})
|
||||
m := i.(map[string]interface{})
|
||||
for key, val := range m {
|
||||
v := reflect.ValueOf(val)
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
ret[key] = val.(bool)
|
||||
case reflect.String:
|
||||
ret[key] = val.(string)
|
||||
case reflect.Int:
|
||||
ret[key] = int(val.(int))
|
||||
case reflect.Int8:
|
||||
ret[key] = int(val.(int8))
|
||||
case reflect.Int16:
|
||||
ret[key] = int(val.(int16))
|
||||
case reflect.Int32:
|
||||
ret[key] = int(val.(int32))
|
||||
case reflect.Int64:
|
||||
ret[key] = int(val.(int64))
|
||||
case reflect.Uint:
|
||||
ret[key] = int(val.(uint))
|
||||
case reflect.Uint8:
|
||||
ret[key] = int(val.(uint8))
|
||||
case reflect.Uint16:
|
||||
ret[key] = int(val.(uint16))
|
||||
case reflect.Uint32:
|
||||
ret[key] = int(val.(uint32))
|
||||
case reflect.Uint64:
|
||||
ret[key] = int(val.(uint64))
|
||||
case reflect.Float32:
|
||||
ret[key] = float64(val.(float32))
|
||||
case reflect.Float64:
|
||||
ret[key] = float64(val.(float64))
|
||||
case reflect.Map:
|
||||
if tmp, err := unmarshalMap(val); err == nil {
|
||||
ret[key] = tmp
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
case reflect.Array, reflect.Slice:
|
||||
ret[key] = val.([]interface{})
|
||||
default:
|
||||
return nil, fmt.Errorf("Unsupported: type = %#v", v.Kind())
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (self *tomlMap) UnmarshalTOML(i interface{}) error {
|
||||
if tmp, err := unmarshalMap(i); err == nil {
|
||||
self.Map = tmp
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type tomlSourceContext struct {
|
||||
FilePath string
|
||||
}
|
||||
|
||||
// NewTomlSourceFromFile creates a new TOML InputSourceContext from a filepath.
|
||||
func NewTomlSourceFromFile(file string) (InputSourceContext, error) {
|
||||
tsc := &tomlSourceContext{FilePath: file}
|
||||
var results tomlMap = tomlMap{}
|
||||
if err := readCommandToml(tsc.FilePath, &results); err != nil {
|
||||
return nil, fmt.Errorf("Unable to load TOML file '%s': inner error: \n'%v'", tsc.FilePath, err.Error())
|
||||
}
|
||||
return &MapInputSource{valueMap: results.Map}, nil
|
||||
}
|
||||
|
||||
// NewTomlSourceFromFlagFunc creates a new TOML InputSourceContext from a provided flag name and source context.
|
||||
func NewTomlSourceFromFlagFunc(flagFileName string) func(context *cli.Context) (InputSourceContext, error) {
|
||||
return func(context *cli.Context) (InputSourceContext, error) {
|
||||
filePath := context.String(flagFileName)
|
||||
return NewTomlSourceFromFile(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
func readCommandToml(filePath string, container interface{}) (err error) {
|
||||
b, err := loadDataFrom(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = toml.Unmarshal(b, container)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = nil
|
||||
return
|
||||
}
|
313
vendor/github.com/urfave/cli/altsrc/yaml_command_test.go
generated
vendored
Normal file
313
vendor/github.com/urfave/cli/altsrc/yaml_command_test.go
generated
vendored
Normal file
@ -0,0 +1,313 @@
|
||||
// Disabling building of yaml support in cases where golang is 1.0 or 1.1
|
||||
// as the encoding library is not implemented or supported.
|
||||
|
||||
// +build go1.2
|
||||
|
||||
package altsrc
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
func TestCommandYamlFileTest(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
|
||||
defer os.Remove("current.yaml")
|
||||
test := []string{"test-cmd", "--load", "current.yaml"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("test")
|
||||
expect(t, val, 15)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "test"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
|
||||
defer os.Remove("current.yaml")
|
||||
|
||||
os.Setenv("THE_TEST", "10")
|
||||
defer os.Setenv("THE_TEST", "")
|
||||
test := []string{"test-cmd", "--load", "current.yaml"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("test")
|
||||
expect(t, val, 10)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "THE_TEST"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandYamlFileTestGlobalEnvVarWinsNested(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.yaml", []byte(`top:
|
||||
test: 15`), 0666)
|
||||
defer os.Remove("current.yaml")
|
||||
|
||||
os.Setenv("THE_TEST", "10")
|
||||
defer os.Setenv("THE_TEST", "")
|
||||
test := []string{"test-cmd", "--load", "current.yaml"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("top.test")
|
||||
expect(t, val, 10)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "top.test", EnvVar: "THE_TEST"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
|
||||
defer os.Remove("current.yaml")
|
||||
|
||||
test := []string{"test-cmd", "--load", "current.yaml", "--test", "7"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("test")
|
||||
expect(t, val, 7)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "test"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandYamlFileTestSpecifiedFlagWinsNested(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.yaml", []byte(`top:
|
||||
test: 15`), 0666)
|
||||
defer os.Remove("current.yaml")
|
||||
|
||||
test := []string{"test-cmd", "--load", "current.yaml", "--top.test", "7"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("top.test")
|
||||
expect(t, val, 7)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "top.test"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
|
||||
defer os.Remove("current.yaml")
|
||||
|
||||
test := []string{"test-cmd", "--load", "current.yaml"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("test")
|
||||
expect(t, val, 15)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "test", Value: 7}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandYamlFileTestDefaultValueFileWinsNested(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.yaml", []byte(`top:
|
||||
test: 15`), 0666)
|
||||
defer os.Remove("current.yaml")
|
||||
|
||||
test := []string{"test-cmd", "--load", "current.yaml"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("top.test")
|
||||
expect(t, val, 15)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
|
||||
defer os.Remove("current.yaml")
|
||||
|
||||
os.Setenv("THE_TEST", "11")
|
||||
defer os.Setenv("THE_TEST", "")
|
||||
|
||||
test := []string{"test-cmd", "--load", "current.yaml"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("test")
|
||||
expect(t, val, 11)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVar: "THE_TEST"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWinsNested(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ioutil.WriteFile("current.yaml", []byte(`top:
|
||||
test: 15`), 0666)
|
||||
defer os.Remove("current.yaml")
|
||||
|
||||
os.Setenv("THE_TEST", "11")
|
||||
defer os.Setenv("THE_TEST", "")
|
||||
|
||||
test := []string{"test-cmd", "--load", "current.yaml"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
val := c.Int("top.test")
|
||||
expect(t, val, 11)
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7, EnvVar: "THE_TEST"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
92
vendor/github.com/urfave/cli/altsrc/yaml_file_loader.go
generated
vendored
Normal file
92
vendor/github.com/urfave/cli/altsrc/yaml_file_loader.go
generated
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
// Disabling building of yaml support in cases where golang is 1.0 or 1.1
|
||||
// as the encoding library is not implemented or supported.
|
||||
|
||||
// +build go1.2
|
||||
|
||||
package altsrc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type yamlSourceContext struct {
|
||||
FilePath string
|
||||
}
|
||||
|
||||
// NewYamlSourceFromFile creates a new Yaml InputSourceContext from a filepath.
|
||||
func NewYamlSourceFromFile(file string) (InputSourceContext, error) {
|
||||
ysc := &yamlSourceContext{FilePath: file}
|
||||
var results map[interface{}]interface{}
|
||||
err := readCommandYaml(ysc.FilePath, &results)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to load Yaml file '%s': inner error: \n'%v'", ysc.FilePath, err.Error())
|
||||
}
|
||||
|
||||
return &MapInputSource{valueMap: results}, nil
|
||||
}
|
||||
|
||||
// NewYamlSourceFromFlagFunc creates a new Yaml InputSourceContext from a provided flag name and source context.
|
||||
func NewYamlSourceFromFlagFunc(flagFileName string) func(context *cli.Context) (InputSourceContext, error) {
|
||||
return func(context *cli.Context) (InputSourceContext, error) {
|
||||
filePath := context.String(flagFileName)
|
||||
return NewYamlSourceFromFile(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
func readCommandYaml(filePath string, container interface{}) (err error) {
|
||||
b, err := loadDataFrom(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(b, container)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
|
||||
func loadDataFrom(filePath string) ([]byte, error) {
|
||||
u, err := url.Parse(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if u.Host != "" { // i have a host, now do i support the scheme?
|
||||
switch u.Scheme {
|
||||
case "http", "https":
|
||||
res, err := http.Get(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ioutil.ReadAll(res.Body)
|
||||
default:
|
||||
return nil, fmt.Errorf("scheme of %s is unsupported", filePath)
|
||||
}
|
||||
} else if u.Path != "" { // i dont have a host, but I have a path. I am a local file.
|
||||
if _, notFoundFileErr := os.Stat(filePath); notFoundFileErr != nil {
|
||||
return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath)
|
||||
}
|
||||
return ioutil.ReadFile(filePath)
|
||||
} else if runtime.GOOS == "windows" && strings.Contains(u.String(), "\\") {
|
||||
// on Windows systems u.Path is always empty, so we need to check the string directly.
|
||||
if _, notFoundFileErr := os.Stat(filePath); notFoundFileErr != nil {
|
||||
return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath)
|
||||
}
|
||||
return ioutil.ReadFile(filePath)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unable to determine how to load from path %s", filePath)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user