mirror of
https://github.com/thegeeklab/drone-matrix.git
synced 2024-11-01 01:00: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
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
*~
|
*.o
|
||||||
/drone-plugin-matrix
|
*.a
|
||||||
/vendor
|
*.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 plugins/base:multiarch
|
||||||
FROM alpine:3.6
|
|
||||||
# Author with no obligation to maintain
|
|
||||||
MAINTAINER Paul Tötterman <paul.totterman@gmail.com>
|
|
||||||
|
|
||||||
RUN apk --no-cache add ca-certificates
|
LABEL maintainer="Drone.IO Community <drone-dev@googlegroups.com>" \
|
||||||
ADD drone-plugin-matrix /
|
org.label-schema.name="Drone Matrix" \
|
||||||
ENTRYPOINT /drone-plugin-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'.
|
# 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]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/matrix-org/gomatrix"
|
name = "github.com/matrix-org/gomatrix"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "a7fc80c8060c2544fe5d4dae465b584f8e9b4e27"
|
revision = "a7fc80c8060c2544fe5d4dae465b584f8e9b4e27"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/urfave/cli"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1"
|
||||||
|
version = "v1.20.0"
|
||||||
|
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "d631b7f46070377e77e160dda36075f4421695f6149e974427eafc8458012b3c"
|
inputs-digest = "02adaa1a61f60449825943c7e92d6d66d4b88b225834f75dc6c181a96836f25b"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
@ -21,6 +21,14 @@
|
|||||||
# version = "2.4.0"
|
# version = "2.4.0"
|
||||||
|
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/aymerick/raymond"
|
||||||
|
version = "2.0.1"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/matrix-org/gomatrix"
|
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
|
## Build
|
||||||
matrix:
|
|
||||||
image: ptman/drone-plugin-matrix
|
Build the binary with the following commands:
|
||||||
homeserver: https://matrix.org # defaults to https://matrix.org
|
|
||||||
roomid: '!0123456789abcdef:matrix.org' # room has to already be joined
|
```
|
||||||
secrets:
|
go build
|
||||||
- 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')
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -7,79 +5,183 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/matrix-org/gomatrix"
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
version = "0.0.0"
|
||||||
|
build = "0"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Secrets
|
app := cli.NewApp()
|
||||||
password := os.Getenv("MATRIX_PASSWORD")
|
app.Name = "codecov plugin"
|
||||||
accessToken := os.Getenv("MATRIX_ACCESSTOKEN")
|
app.Usage = "codecov plugin"
|
||||||
// Not sure if these are secrets or nice to have close to them
|
app.Version = fmt.Sprintf("%s+%s", version, build)
|
||||||
userName := os.Getenv("MATRIX_USERNAME")
|
app.Action = run
|
||||||
userID := os.Getenv("MATRIX_USERID")
|
app.Flags = []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
// Override secrets if present
|
Name: "username",
|
||||||
if pw := os.Getenv("PLUGIN_PASSWORD"); pw != "" {
|
Usage: "username for authentication",
|
||||||
password = pw
|
EnvVar: "PLUGIN_USERNAME,MATRIX_USERNAME",
|
||||||
}
|
},
|
||||||
if at := os.Getenv("PLUGIN_ACCESSTOKEN"); at != "" {
|
cli.StringFlag{
|
||||||
accessToken = at
|
Name: "password",
|
||||||
}
|
Usage: "password for authentication",
|
||||||
if un := os.Getenv("PLUGIN_USERNAME"); un != "" {
|
EnvVar: "PLUGIN_PASSWORD,MATRIX_PASSWORD",
|
||||||
userName = un
|
},
|
||||||
}
|
cli.StringFlag{
|
||||||
if ui := os.Getenv("PLUGIN_USERID"); ui != "" {
|
Name: "userid",
|
||||||
userID = ui
|
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 err := app.Run(os.Args); err != nil {
|
||||||
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 {
|
|
||||||
log.Fatal(err)
|
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