mirror of
https://github.com/ipedrazas/drone-helm.git
synced 2026-06-04 10:15:19 +08:00
update to use go dep for the dependencies
This commit is contained in:
+2
-1
@@ -27,4 +27,5 @@ drone-helm
|
||||
*.test
|
||||
*.out
|
||||
.vscode
|
||||
.idea
|
||||
.idea
|
||||
.vendor
|
||||
|
||||
Generated
+56
@@ -0,0 +1,56 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3b8f5d88e2b182b77f04355ed40c8810e516913bedc51b4ffd17ec506613a90e"
|
||||
name = "github.com/Sirupsen/logrus"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "a3f95b5c423586578a4e099b11a46c2479628cac"
|
||||
version = "1.0.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:315c5f2f60c76d89b871c73f9bd5fe689cad96597afd50fb9992228ef80bdd34"
|
||||
name = "github.com/alecthomas/template"
|
||||
packages = [
|
||||
".",
|
||||
"parse",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "a0175ee3bccc567396460bf5acd36800cb10c49c"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d8cadef127871902b9aaa711a74bf6dd74feeb3f97b67c31b9dcdd02b8284b45"
|
||||
name = "github.com/joho/godotenv"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "726cc8b906e3d31c70a9671c90a13716a8d3f50d"
|
||||
version = "v1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:01d3c3dfb7ef9da24cd00cbd5b9e2c147fa7a3f44a83028bc737da9cac7ed60b"
|
||||
name = "github.com/urfave/cli"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "0bdeddeeb0f650497d603c4ad7b20cfe685682f6"
|
||||
version = "v1.19.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:1823f145d46c33abc0d1b15711d24839b2266f8e23c2b8dca1d35be3b0a84029"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix"]
|
||||
pruneopts = "UT"
|
||||
revision = "c4489faa6e5ab84c0ef40d6ee878f7a030281f0f"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/Sirupsen/logrus",
|
||||
"github.com/alecthomas/template",
|
||||
"github.com/joho/godotenv",
|
||||
"github.com/urfave/cli",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
ignored = ["sort"]
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/Sirupsen/logrus"
|
||||
version = "1.0.2"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/alecthomas/template"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/joho/godotenv"
|
||||
version = "1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/urfave/cli"
|
||||
version = "1.19.1"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
@@ -4,5 +4,7 @@ set -e
|
||||
|
||||
export GOOS=linux
|
||||
export GOARCH=386
|
||||
go get -u github.com/golang/dep/cmd/dep
|
||||
dep ensure
|
||||
go build
|
||||
docker build -t drone-helm .
|
||||
|
||||
-1
@@ -1 +0,0 @@
|
||||
logrus
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- tip
|
||||
env:
|
||||
- GOMAXPROCS=4 GORACE=halt_on_error=1
|
||||
install:
|
||||
- go get github.com/stretchr/testify/assert
|
||||
script:
|
||||
- go test -race -v .
|
||||
-109
@@ -1,109 +0,0 @@
|
||||
# 1.0.2
|
||||
|
||||
* bug: quote non-string values in text formatter (#583)
|
||||
* Make (*Logger) SetLevel a public method
|
||||
|
||||
# 1.0.1
|
||||
|
||||
* bug: fix escaping in text formatter (#575)
|
||||
|
||||
# 1.0.0
|
||||
|
||||
* Officially changed name to lower-case
|
||||
* bug: colors on Windows 10 (#541)
|
||||
* bug: fix race in accessing level (#512)
|
||||
|
||||
# 0.11.5
|
||||
|
||||
* feature: add writer and writerlevel to entry (#372)
|
||||
|
||||
# 0.11.4
|
||||
|
||||
* bug: fix undefined variable on solaris (#493)
|
||||
|
||||
# 0.11.3
|
||||
|
||||
* formatter: configure quoting of empty values (#484)
|
||||
* formatter: configure quoting character (default is `"`) (#484)
|
||||
* bug: fix not importing io correctly in non-linux environments (#481)
|
||||
|
||||
# 0.11.2
|
||||
|
||||
* bug: fix windows terminal detection (#476)
|
||||
|
||||
# 0.11.1
|
||||
|
||||
* bug: fix tty detection with custom out (#471)
|
||||
|
||||
# 0.11.0
|
||||
|
||||
* performance: Use bufferpool to allocate (#370)
|
||||
* terminal: terminal detection for app-engine (#343)
|
||||
* feature: exit handler (#375)
|
||||
|
||||
# 0.10.0
|
||||
|
||||
* feature: Add a test hook (#180)
|
||||
* feature: `ParseLevel` is now case-insensitive (#326)
|
||||
* feature: `FieldLogger` interface that generalizes `Logger` and `Entry` (#308)
|
||||
* performance: avoid re-allocations on `WithFields` (#335)
|
||||
|
||||
# 0.9.0
|
||||
|
||||
* logrus/text_formatter: don't emit empty msg
|
||||
* logrus/hooks/airbrake: move out of main repository
|
||||
* logrus/hooks/sentry: move out of main repository
|
||||
* logrus/hooks/papertrail: move out of main repository
|
||||
* logrus/hooks/bugsnag: move out of main repository
|
||||
* logrus/core: run tests with `-race`
|
||||
* logrus/core: detect TTY based on `stderr`
|
||||
* logrus/core: support `WithError` on logger
|
||||
* logrus/core: Solaris support
|
||||
|
||||
# 0.8.7
|
||||
|
||||
* logrus/core: fix possible race (#216)
|
||||
* logrus/doc: small typo fixes and doc improvements
|
||||
|
||||
|
||||
# 0.8.6
|
||||
|
||||
* hooks/raven: allow passing an initialized client
|
||||
|
||||
# 0.8.5
|
||||
|
||||
* logrus/core: revert #208
|
||||
|
||||
# 0.8.4
|
||||
|
||||
* formatter/text: fix data race (#218)
|
||||
|
||||
# 0.8.3
|
||||
|
||||
* logrus/core: fix entry log level (#208)
|
||||
* logrus/core: improve performance of text formatter by 40%
|
||||
* logrus/core: expose `LevelHooks` type
|
||||
* logrus/core: add support for DragonflyBSD and NetBSD
|
||||
* formatter/text: print structs more verbosely
|
||||
|
||||
# 0.8.2
|
||||
|
||||
* logrus: fix more Fatal family functions
|
||||
|
||||
# 0.8.1
|
||||
|
||||
* logrus: fix not exiting on `Fatalf` and `Fatalln`
|
||||
|
||||
# 0.8.0
|
||||
|
||||
* logrus: defaults to stderr instead of stdout
|
||||
* hooks/sentry: add special field for `*http.Request`
|
||||
* formatter/text: ignore Windows for colors
|
||||
|
||||
# 0.7.3
|
||||
|
||||
* formatter/\*: allow configuration of timestamp layout
|
||||
|
||||
# 0.7.2
|
||||
|
||||
* formatter/text: Add configuration option for time format (#158)
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Simon Eskildsen
|
||||
|
||||
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.
|
||||
-504
@@ -1,504 +0,0 @@
|
||||
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [](https://travis-ci.org/sirupsen/logrus) [](https://godoc.org/github.com/sirupsen/logrus)
|
||||
|
||||
Logrus is a structured logger for Go (golang), completely API compatible with
|
||||
the standard library logger. [Godoc][godoc].
|
||||
|
||||
**Seeing weird case-sensitive problems?** It's in the past been possible to
|
||||
import Logrus as both upper- and lower-case. Due to the Go package environment,
|
||||
this caused issues in the community and we needed a standard. Some environments
|
||||
experienced problems with the upper-case variant, so the lower-case was decided.
|
||||
Everything using `logrus` will need to use the lower-case:
|
||||
`github.com/sirupsen/logrus`. Any package that isn't, should be changed.
|
||||
|
||||
To fix Glide, see [these
|
||||
comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437).
|
||||
For an in-depth explanation of the casing issue, see [this
|
||||
comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276).
|
||||
|
||||
**Are you interested in assisting in maintaining Logrus?** Currently I have a
|
||||
lot of obligations, and I am unable to provide Logrus with the maintainership it
|
||||
needs. If you'd like to help, please reach out to me at `simon at author's
|
||||
username dot com`.
|
||||
|
||||
Nicely color-coded in development (when a TTY is attached, otherwise just
|
||||
plain text):
|
||||
|
||||

|
||||
|
||||
With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash
|
||||
or Splunk:
|
||||
|
||||
```json
|
||||
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
|
||||
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
|
||||
|
||||
{"level":"warning","msg":"The group's number increased tremendously!",
|
||||
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
|
||||
|
||||
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
|
||||
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
|
||||
|
||||
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
|
||||
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
|
||||
|
||||
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
|
||||
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
|
||||
```
|
||||
|
||||
With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not
|
||||
attached, the output is compatible with the
|
||||
[logfmt](http://godoc.org/github.com/kr/logfmt) format:
|
||||
|
||||
```text
|
||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
|
||||
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
||||
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
|
||||
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
|
||||
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
|
||||
exit status 1
|
||||
```
|
||||
|
||||
#### Case-sensitivity
|
||||
|
||||
The organization's name was changed to lower-case--and this will not be changed
|
||||
back. If you are getting import conflicts due to case sensitivity, please use
|
||||
the lower-case import: `github.com/sirupsen/logrus`.
|
||||
|
||||
#### Example
|
||||
|
||||
The simplest way to use Logrus is simply the package-level exported logger:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
}).Info("A walrus appears")
|
||||
}
|
||||
```
|
||||
|
||||
Note that it's completely api-compatible with the stdlib logger, so you can
|
||||
replace your `log` imports everywhere with `log "github.com/sirupsen/logrus"`
|
||||
and you'll now have the flexibility of Logrus. You can customize it all you
|
||||
want:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Log as JSON instead of the default ASCII formatter.
|
||||
log.SetFormatter(&log.JSONFormatter{})
|
||||
|
||||
// Output to stdout instead of the default stderr
|
||||
// Can be any io.Writer, see below for File example
|
||||
log.SetOutput(os.Stdout)
|
||||
|
||||
// Only log the warning severity or above.
|
||||
log.SetLevel(log.WarnLevel)
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"omg": true,
|
||||
"number": 100,
|
||||
}).Fatal("The ice breaks!")
|
||||
|
||||
// A common pattern is to re-use fields between logging statements by re-using
|
||||
// the logrus.Entry returned from WithFields()
|
||||
contextLogger := log.WithFields(log.Fields{
|
||||
"common": "this is a common field",
|
||||
"other": "I also should be logged always",
|
||||
})
|
||||
|
||||
contextLogger.Info("I'll be logged with common and other field")
|
||||
contextLogger.Info("Me too")
|
||||
}
|
||||
```
|
||||
|
||||
For more advanced usage such as logging to multiple locations from the same
|
||||
application, you can also create an instance of the `logrus` Logger:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Create a new instance of the logger. You can have any number of instances.
|
||||
var log = logrus.New()
|
||||
|
||||
func main() {
|
||||
// The API for setting attributes is a little different than the package level
|
||||
// exported logger. See Godoc.
|
||||
log.Out = os.Stdout
|
||||
|
||||
// You could set this to any `io.Writer` such as a file
|
||||
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
|
||||
// if err == nil {
|
||||
// log.Out = file
|
||||
// } else {
|
||||
// log.Info("Failed to log to file, using default stderr")
|
||||
// }
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
}
|
||||
```
|
||||
|
||||
#### Fields
|
||||
|
||||
Logrus encourages careful, structured logging through logging fields instead of
|
||||
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
||||
to send event %s to topic %s with key %d")`, you should log the much more
|
||||
discoverable:
|
||||
|
||||
```go
|
||||
log.WithFields(log.Fields{
|
||||
"event": event,
|
||||
"topic": topic,
|
||||
"key": key,
|
||||
}).Fatal("Failed to send event")
|
||||
```
|
||||
|
||||
We've found this API forces you to think about logging in a way that produces
|
||||
much more useful logging messages. We've been in countless situations where just
|
||||
a single added field to a log statement that was already there would've saved us
|
||||
hours. The `WithFields` call is optional.
|
||||
|
||||
In general, with Logrus using any of the `printf`-family functions should be
|
||||
seen as a hint you should add a field, however, you can still use the
|
||||
`printf`-family functions with Logrus.
|
||||
|
||||
#### Default Fields
|
||||
|
||||
Often it's helpful to have fields _always_ attached to log statements in an
|
||||
application or parts of one. For example, you may want to always log the
|
||||
`request_id` and `user_ip` in the context of a request. Instead of writing
|
||||
`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})` on
|
||||
every line, you can create a `logrus.Entry` to pass around instead:
|
||||
|
||||
```go
|
||||
requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
|
||||
requestLogger.Info("something happened on that request") # will log request_id and user_ip
|
||||
requestLogger.Warn("something not great happened")
|
||||
```
|
||||
|
||||
#### Hooks
|
||||
|
||||
You can add hooks for logging levels. For example to send errors to an exception
|
||||
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
|
||||
multiple places simultaneously, e.g. syslog.
|
||||
|
||||
Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
|
||||
`init`:
|
||||
|
||||
```go
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "aibrake"
|
||||
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
|
||||
"log/syslog"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
// Use the Airbrake hook to report errors that have Error severity or above to
|
||||
// an exception tracker. You can create custom hooks, see the Hooks section.
|
||||
log.AddHook(airbrake.NewHook(123, "xyz", "production"))
|
||||
|
||||
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||
if err != nil {
|
||||
log.Error("Unable to connect to local syslog daemon")
|
||||
} else {
|
||||
log.AddHook(hook)
|
||||
}
|
||||
}
|
||||
```
|
||||
Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
|
||||
|
||||
| Hook | Description |
|
||||
| ----- | ----------- |
|
||||
| [Airbrake "legacy"](https://github.com/gemnasium/logrus-airbrake-legacy-hook) | Send errors to an exception tracking service compatible with the Airbrake API V2. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. |
|
||||
| [Airbrake](https://github.com/gemnasium/logrus-airbrake-hook) | Send errors to the Airbrake API V3. Uses the official [`gobrake`](https://github.com/airbrake/gobrake) behind the scenes. |
|
||||
| [Amazon Kinesis](https://github.com/evalphobia/logrus_kinesis) | Hook for logging to [Amazon Kinesis](https://aws.amazon.com/kinesis/) |
|
||||
| [Amqp-Hook](https://github.com/vladoatanasov/logrus_amqp) | Hook for logging to Amqp broker (Like RabbitMQ) |
|
||||
| [Bugsnag](https://github.com/Shopify/logrus-bugsnag/blob/master/bugsnag.go) | Send errors to the Bugsnag exception tracking service. |
|
||||
| [DeferPanic](https://github.com/deferpanic/dp-logrus) | Hook for logging to DeferPanic |
|
||||
| [Discordrus](https://github.com/kz/discordrus) | Hook for logging to [Discord](https://discordapp.com/) |
|
||||
| [ElasticSearch](https://github.com/sohlich/elogrus) | Hook for logging to ElasticSearch|
|
||||
| [Firehose](https://github.com/beaubrewer/logrus_firehose) | Hook for logging to [Amazon Firehose](https://aws.amazon.com/kinesis/firehose/)
|
||||
| [Fluentd](https://github.com/evalphobia/logrus_fluent) | Hook for logging to fluentd |
|
||||
| [Go-Slack](https://github.com/multiplay/go-slack) | Hook for logging to [Slack](https://slack.com) |
|
||||
| [Graylog](https://github.com/gemnasium/logrus-graylog-hook) | Hook for logging to [Graylog](http://graylog2.org/) |
|
||||
| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. |
|
||||
| [Honeybadger](https://github.com/agonzalezro/logrus_honeybadger) | Hook for sending exceptions to Honeybadger |
|
||||
| [InfluxDB](https://github.com/Abramovic/logrus_influxdb) | Hook for logging to influxdb |
|
||||
| [Influxus](http://github.com/vlad-doru/influxus) | Hook for concurrently logging to [InfluxDB](http://influxdata.com/) |
|
||||
| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` |
|
||||
| [KafkaLogrus](https://github.com/goibibo/KafkaLogrus) | Hook for logging to kafka |
|
||||
| [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem |
|
||||
| [Logentries](https://github.com/jcftang/logentriesrus) | Hook for logging to [Logentries](https://logentries.com/) |
|
||||
| [Logentrus](https://github.com/puddingfactory/logentrus) | Hook for logging to [Logentries](https://logentries.com/) |
|
||||
| [Logmatic.io](https://github.com/logmatic/logmatic-go) | Hook for logging to [Logmatic.io](http://logmatic.io/) |
|
||||
| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) |
|
||||
| [Logstash](https://github.com/bshuster-repo/logrus-logstash-hook) | Hook for logging to [Logstash](https://www.elastic.co/products/logstash) |
|
||||
| [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail |
|
||||
| [Mattermost](https://github.com/shuLhan/mattermost-integration/tree/master/hooks/logrus) | Hook for logging to [Mattermost](https://mattermost.com/) |
|
||||
| [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb |
|
||||
| [NATS-Hook](https://github.com/rybit/nats_logrus_hook) | Hook for logging to [NATS](https://nats.io) |
|
||||
| [Octokit](https://github.com/dorajistyle/logrus-octokit-hook) | Hook for logging to github via octokit |
|
||||
| [Papertrail](https://github.com/polds/logrus-papertrail-hook) | Send errors to the [Papertrail](https://papertrailapp.com) hosted logging service via UDP. |
|
||||
| [PostgreSQL](https://github.com/gemnasium/logrus-postgresql-hook) | Send logs to [PostgreSQL](http://postgresql.org) |
|
||||
| [Pushover](https://github.com/toorop/logrus_pushover) | Send error via [Pushover](https://pushover.net) |
|
||||
| [Raygun](https://github.com/squirkle/logrus-raygun-hook) | Hook for logging to [Raygun.io](http://raygun.io/) |
|
||||
| [Redis-Hook](https://github.com/rogierlommers/logrus-redis-hook) | Hook for logging to a ELK stack (through Redis) |
|
||||
| [Rollrus](https://github.com/heroku/rollrus) | Hook for sending errors to rollbar |
|
||||
| [Scribe](https://github.com/sagar8192/logrus-scribe-hook) | Hook for logging to [Scribe](https://github.com/facebookarchive/scribe)|
|
||||
| [Sentry](https://github.com/evalphobia/logrus_sentry) | Send errors to the Sentry error logging and aggregation service. |
|
||||
| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. |
|
||||
| [Stackdriver](https://github.com/knq/sdhook) | Hook for logging to [Google Stackdriver](https://cloud.google.com/logging/) |
|
||||
| [Sumorus](https://github.com/doublefree/sumorus) | Hook for logging to [SumoLogic](https://www.sumologic.com/)|
|
||||
| [Syslog](https://github.com/sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. |
|
||||
| [Syslog TLS](https://github.com/shinji62/logrus-syslog-ng) | Send errors to remote syslog server with TLS support. |
|
||||
| [TraceView](https://github.com/evalphobia/logrus_appneta) | Hook for logging to [AppNeta TraceView](https://www.appneta.com/products/traceview/) |
|
||||
| [Typetalk](https://github.com/dragon3/logrus-typetalk-hook) | Hook for logging to [Typetalk](https://www.typetalk.in/) |
|
||||
| [logz.io](https://github.com/ripcurld00d/logrus-logzio-hook) | Hook for logging to [logz.io](https://logz.io), a Log as a Service using Logstash |
|
||||
| [SQS-Hook](https://github.com/tsarpaul/logrus_sqs) | Hook for logging to [Amazon Simple Queue Service (SQS)](https://aws.amazon.com/sqs/) |
|
||||
|
||||
#### Level logging
|
||||
|
||||
Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.
|
||||
|
||||
```go
|
||||
log.Debug("Useful debugging information.")
|
||||
log.Info("Something noteworthy happened!")
|
||||
log.Warn("You should probably take a look at this.")
|
||||
log.Error("Something failed but I'm not quitting.")
|
||||
// Calls os.Exit(1) after logging
|
||||
log.Fatal("Bye.")
|
||||
// Calls panic() after logging
|
||||
log.Panic("I'm bailing.")
|
||||
```
|
||||
|
||||
You can set the logging level on a `Logger`, then it will only log entries with
|
||||
that severity or anything above it:
|
||||
|
||||
```go
|
||||
// Will log anything that is info or above (warn, error, fatal, panic). Default.
|
||||
log.SetLevel(log.InfoLevel)
|
||||
```
|
||||
|
||||
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
|
||||
environment if your application has that.
|
||||
|
||||
#### Entries
|
||||
|
||||
Besides the fields added with `WithField` or `WithFields` some fields are
|
||||
automatically added to all logging events:
|
||||
|
||||
1. `time`. The timestamp when the entry was created.
|
||||
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
|
||||
the `AddFields` call. E.g. `Failed to send event.`
|
||||
3. `level`. The logging level. E.g. `info`.
|
||||
|
||||
#### Environments
|
||||
|
||||
Logrus has no notion of environment.
|
||||
|
||||
If you wish for hooks and formatters to only be used in specific environments,
|
||||
you should handle that yourself. For example, if your application has a global
|
||||
variable `Environment`, which is a string representation of the environment you
|
||||
could do:
|
||||
|
||||
```go
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
init() {
|
||||
// do something here to set environment depending on an environment variable
|
||||
// or command-line flag
|
||||
if Environment == "production" {
|
||||
log.SetFormatter(&log.JSONFormatter{})
|
||||
} else {
|
||||
// The TextFormatter is default, you don't actually have to do this.
|
||||
log.SetFormatter(&log.TextFormatter{})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This configuration is how `logrus` was intended to be used, but JSON in
|
||||
production is mostly only useful if you do log aggregation with tools like
|
||||
Splunk or Logstash.
|
||||
|
||||
#### Formatters
|
||||
|
||||
The built-in logging formatters are:
|
||||
|
||||
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
|
||||
without colors.
|
||||
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
||||
field to `true`. To force no colored output even if there is a TTY set the
|
||||
`DisableColors` field to `true`. For Windows, see
|
||||
[github.com/mattn/go-colorable](https://github.com/mattn/go-colorable).
|
||||
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#TextFormatter).
|
||||
* `logrus.JSONFormatter`. Logs fields as JSON.
|
||||
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#JSONFormatter).
|
||||
|
||||
Third party logging formatters:
|
||||
|
||||
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
|
||||
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
|
||||
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
|
||||
|
||||
You can define your formatter by implementing the `Formatter` interface,
|
||||
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
|
||||
`Fields` type (`map[string]interface{}`) with all your fields as well as the
|
||||
default ones (see Entries section above):
|
||||
|
||||
```go
|
||||
type MyJSONFormatter struct {
|
||||
}
|
||||
|
||||
log.SetFormatter(new(MyJSONFormatter))
|
||||
|
||||
func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
// Note this doesn't include Time, Level and Message which are available on
|
||||
// the Entry. Consult `godoc` on information about those fields or read the
|
||||
// source of the official loggers.
|
||||
serialized, err := json.Marshal(entry.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||
}
|
||||
return append(serialized, '\n'), nil
|
||||
}
|
||||
```
|
||||
|
||||
#### Logger as an `io.Writer`
|
||||
|
||||
Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it.
|
||||
|
||||
```go
|
||||
w := logger.Writer()
|
||||
defer w.Close()
|
||||
|
||||
srv := http.Server{
|
||||
// create a stdlib log.Logger that writes to
|
||||
// logrus.Logger.
|
||||
ErrorLog: log.New(w, "", 0),
|
||||
}
|
||||
```
|
||||
|
||||
Each line written to that writer will be printed the usual way, using formatters
|
||||
and hooks. The level for those entries is `info`.
|
||||
|
||||
This means that we can override the standard library logger easily:
|
||||
|
||||
```go
|
||||
logger := logrus.New()
|
||||
logger.Formatter = &logrus.JSONFormatter{}
|
||||
|
||||
// Use logrus for standard log output
|
||||
// Note that `log` here references stdlib's log
|
||||
// Not logrus imported under the name `log`.
|
||||
log.SetOutput(logger.Writer())
|
||||
```
|
||||
|
||||
#### Rotation
|
||||
|
||||
Log rotation is not provided with Logrus. Log rotation should be done by an
|
||||
external program (like `logrotate(8)`) that can compress and delete old log
|
||||
entries. It should not be a feature of the application-level logger.
|
||||
|
||||
#### Tools
|
||||
|
||||
| Tool | Description |
|
||||
| ---- | ----------- |
|
||||
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will generated with different config at different environment.|
|
||||
|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper around Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) |
|
||||
|
||||
#### Testing
|
||||
|
||||
Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides:
|
||||
|
||||
* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just add the `test` hook
|
||||
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
|
||||
|
||||
```go
|
||||
import(
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus/hooks/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSomething(t*testing.T){
|
||||
logger, hook := test.NewNullLogger()
|
||||
logger.Error("Helloerror")
|
||||
|
||||
assert.Equal(t, 1, len(hook.Entries))
|
||||
assert.Equal(t, logrus.ErrorLevel, hook.LastEntry().Level)
|
||||
assert.Equal(t, "Helloerror", hook.LastEntry().Message)
|
||||
|
||||
hook.Reset()
|
||||
assert.Nil(t, hook.LastEntry())
|
||||
}
|
||||
```
|
||||
|
||||
#### Fatal handlers
|
||||
|
||||
Logrus can register one or more functions that will be called when any `fatal`
|
||||
level message is logged. The registered handlers will be executed before
|
||||
logrus performs a `os.Exit(1)`. This behavior may be helpful if callers need
|
||||
to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted.
|
||||
|
||||
```
|
||||
...
|
||||
handler := func() {
|
||||
// gracefully shutdown something...
|
||||
}
|
||||
logrus.RegisterExitHandler(handler)
|
||||
...
|
||||
```
|
||||
|
||||
#### Thread safety
|
||||
|
||||
By default Logger is protected by mutex for concurrent writes, this mutex is invoked when calling hooks and writing logs.
|
||||
If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking.
|
||||
|
||||
Situation when locking is not needed includes:
|
||||
|
||||
* You have no hooks registered, or hooks calling is already thread-safe.
|
||||
|
||||
* Writing to logger.Out is already thread-safe, for example:
|
||||
|
||||
1) logger.Out is protected by locks.
|
||||
|
||||
2) logger.Out is a os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allow multi-thread/multi-process writing)
|
||||
|
||||
(Refer to http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
package logrus
|
||||
|
||||
// The following code was sourced and modified from the
|
||||
// https://github.com/tebeka/atexit package governed by the following license:
|
||||
//
|
||||
// Copyright (c) 2012 Miki Tebeka <miki.tebeka@gmail.com>.
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
var handlers = []func(){}
|
||||
|
||||
func runHandler(handler func()) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error: Logrus exit handler error:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
handler()
|
||||
}
|
||||
|
||||
func runHandlers() {
|
||||
for _, handler := range handlers {
|
||||
runHandler(handler)
|
||||
}
|
||||
}
|
||||
|
||||
// Exit runs all the Logrus atexit handlers and then terminates the program using os.Exit(code)
|
||||
func Exit(code int) {
|
||||
runHandlers()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
// RegisterExitHandler adds a Logrus Exit handler, call logrus.Exit to invoke
|
||||
// all handlers. The handlers will also be invoked when any Fatal log entry is
|
||||
// made.
|
||||
//
|
||||
// This method is useful when a caller wishes to use logrus to log a fatal
|
||||
// message but also needs to gracefully shutdown. An example usecase could be
|
||||
// closing database connections, or sending a alert that the application is
|
||||
// closing.
|
||||
func RegisterExitHandler(handler func()) {
|
||||
handlers = append(handlers, handler)
|
||||
}
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
current := len(handlers)
|
||||
RegisterExitHandler(func() {})
|
||||
if len(handlers) != current+1 {
|
||||
t.Fatalf("can't add handler")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandler(t *testing.T) {
|
||||
gofile := "/tmp/testprog.go"
|
||||
if err := ioutil.WriteFile(gofile, testprog, 0666); err != nil {
|
||||
t.Fatalf("can't create go file")
|
||||
}
|
||||
|
||||
outfile := "/tmp/testprog.out"
|
||||
arg := time.Now().UTC().String()
|
||||
err := exec.Command("go", "run", gofile, outfile, arg).Run()
|
||||
if err == nil {
|
||||
t.Fatalf("completed normally, should have failed")
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(outfile)
|
||||
if err != nil {
|
||||
t.Fatalf("can't read output file %s", outfile)
|
||||
}
|
||||
|
||||
if string(data) != arg {
|
||||
t.Fatalf("bad data")
|
||||
}
|
||||
}
|
||||
|
||||
var testprog = []byte(`
|
||||
// Test program for atexit, gets output file and data as arguments and writes
|
||||
// data to output file in atexit handler.
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
var outfile = ""
|
||||
var data = ""
|
||||
|
||||
func handler() {
|
||||
ioutil.WriteFile(outfile, []byte(data), 0666)
|
||||
}
|
||||
|
||||
func badHandler() {
|
||||
n := 0
|
||||
fmt.Println(1/n)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
outfile = flag.Arg(0)
|
||||
data = flag.Arg(1)
|
||||
|
||||
logrus.RegisterExitHandler(handler)
|
||||
logrus.RegisterExitHandler(badHandler)
|
||||
logrus.Fatal("Bye bye")
|
||||
}
|
||||
`)
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
Package logrus is a structured logger for Go, completely API compatible with the standard library logger.
|
||||
|
||||
|
||||
The simplest way to use Logrus is simply the package-level exported logger:
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
"number": 1,
|
||||
"size": 10,
|
||||
}).Info("A walrus appears")
|
||||
}
|
||||
|
||||
Output:
|
||||
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
|
||||
|
||||
For a full guide visit https://github.com/sirupsen/logrus
|
||||
*/
|
||||
package logrus
|
||||
-275
@@ -1,275 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var bufferPool *sync.Pool
|
||||
|
||||
func init() {
|
||||
bufferPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Defines the key when adding errors using WithError.
|
||||
var ErrorKey = "error"
|
||||
|
||||
// An entry is the final or intermediate Logrus logging entry. It contains all
|
||||
// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
|
||||
// Warn, Error, Fatal or Panic is called on it. These objects can be reused and
|
||||
// passed around as much as you wish to avoid field duplication.
|
||||
type Entry struct {
|
||||
Logger *Logger
|
||||
|
||||
// Contains all the fields set by the user.
|
||||
Data Fields
|
||||
|
||||
// Time at which the log entry was created
|
||||
Time time.Time
|
||||
|
||||
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
|
||||
Level Level
|
||||
|
||||
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
|
||||
Message string
|
||||
|
||||
// When formatter is called in entry.log(), an Buffer may be set to entry
|
||||
Buffer *bytes.Buffer
|
||||
}
|
||||
|
||||
func NewEntry(logger *Logger) *Entry {
|
||||
return &Entry{
|
||||
Logger: logger,
|
||||
// Default is three fields, give a little extra room
|
||||
Data: make(Fields, 5),
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the string representation from the reader and ultimately the
|
||||
// formatter.
|
||||
func (entry *Entry) String() (string, error) {
|
||||
serialized, err := entry.Logger.Formatter.Format(entry)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
str := string(serialized)
|
||||
return str, nil
|
||||
}
|
||||
|
||||
// Add an error as single field (using the key defined in ErrorKey) to the Entry.
|
||||
func (entry *Entry) WithError(err error) *Entry {
|
||||
return entry.WithField(ErrorKey, err)
|
||||
}
|
||||
|
||||
// Add a single field to the Entry.
|
||||
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
||||
return entry.WithFields(Fields{key: value})
|
||||
}
|
||||
|
||||
// Add a map of fields to the Entry.
|
||||
func (entry *Entry) WithFields(fields Fields) *Entry {
|
||||
data := make(Fields, len(entry.Data)+len(fields))
|
||||
for k, v := range entry.Data {
|
||||
data[k] = v
|
||||
}
|
||||
for k, v := range fields {
|
||||
data[k] = v
|
||||
}
|
||||
return &Entry{Logger: entry.Logger, Data: data}
|
||||
}
|
||||
|
||||
// This function is not declared with a pointer value because otherwise
|
||||
// race conditions will occur when using multiple goroutines
|
||||
func (entry Entry) log(level Level, msg string) {
|
||||
var buffer *bytes.Buffer
|
||||
entry.Time = time.Now()
|
||||
entry.Level = level
|
||||
entry.Message = msg
|
||||
|
||||
if err := entry.Logger.Hooks.Fire(level, &entry); err != nil {
|
||||
entry.Logger.mu.Lock()
|
||||
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
||||
entry.Logger.mu.Unlock()
|
||||
}
|
||||
buffer = bufferPool.Get().(*bytes.Buffer)
|
||||
buffer.Reset()
|
||||
defer bufferPool.Put(buffer)
|
||||
entry.Buffer = buffer
|
||||
serialized, err := entry.Logger.Formatter.Format(&entry)
|
||||
entry.Buffer = nil
|
||||
if err != nil {
|
||||
entry.Logger.mu.Lock()
|
||||
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
||||
entry.Logger.mu.Unlock()
|
||||
} else {
|
||||
entry.Logger.mu.Lock()
|
||||
_, err = entry.Logger.Out.Write(serialized)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
||||
}
|
||||
entry.Logger.mu.Unlock()
|
||||
}
|
||||
|
||||
// To avoid Entry#log() returning a value that only would make sense for
|
||||
// panic() to use in Entry#Panic(), we avoid the allocation by checking
|
||||
// directly here.
|
||||
if level <= PanicLevel {
|
||||
panic(&entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Debug(args ...interface{}) {
|
||||
if entry.Logger.level() >= DebugLevel {
|
||||
entry.log(DebugLevel, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Print(args ...interface{}) {
|
||||
entry.Info(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Info(args ...interface{}) {
|
||||
if entry.Logger.level() >= InfoLevel {
|
||||
entry.log(InfoLevel, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Warn(args ...interface{}) {
|
||||
if entry.Logger.level() >= WarnLevel {
|
||||
entry.log(WarnLevel, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Warning(args ...interface{}) {
|
||||
entry.Warn(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Error(args ...interface{}) {
|
||||
if entry.Logger.level() >= ErrorLevel {
|
||||
entry.log(ErrorLevel, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatal(args ...interface{}) {
|
||||
if entry.Logger.level() >= FatalLevel {
|
||||
entry.log(FatalLevel, fmt.Sprint(args...))
|
||||
}
|
||||
Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panic(args ...interface{}) {
|
||||
if entry.Logger.level() >= PanicLevel {
|
||||
entry.log(PanicLevel, fmt.Sprint(args...))
|
||||
}
|
||||
panic(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
// Entry Printf family functions
|
||||
|
||||
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
||||
if entry.Logger.level() >= DebugLevel {
|
||||
entry.Debug(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Infof(format string, args ...interface{}) {
|
||||
if entry.Logger.level() >= InfoLevel {
|
||||
entry.Info(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Printf(format string, args ...interface{}) {
|
||||
entry.Infof(format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
||||
if entry.Logger.level() >= WarnLevel {
|
||||
entry.Warn(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Warningf(format string, args ...interface{}) {
|
||||
entry.Warnf(format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
||||
if entry.Logger.level() >= ErrorLevel {
|
||||
entry.Error(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
||||
if entry.Logger.level() >= FatalLevel {
|
||||
entry.Fatal(fmt.Sprintf(format, args...))
|
||||
}
|
||||
Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
||||
if entry.Logger.level() >= PanicLevel {
|
||||
entry.Panic(fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
// Entry Println family functions
|
||||
|
||||
func (entry *Entry) Debugln(args ...interface{}) {
|
||||
if entry.Logger.level() >= DebugLevel {
|
||||
entry.Debug(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Infoln(args ...interface{}) {
|
||||
if entry.Logger.level() >= InfoLevel {
|
||||
entry.Info(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Println(args ...interface{}) {
|
||||
entry.Infoln(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warnln(args ...interface{}) {
|
||||
if entry.Logger.level() >= WarnLevel {
|
||||
entry.Warn(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Warningln(args ...interface{}) {
|
||||
entry.Warnln(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Errorln(args ...interface{}) {
|
||||
if entry.Logger.level() >= ErrorLevel {
|
||||
entry.Error(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatalln(args ...interface{}) {
|
||||
if entry.Logger.level() >= FatalLevel {
|
||||
entry.Fatal(entry.sprintlnn(args...))
|
||||
}
|
||||
Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panicln(args ...interface{}) {
|
||||
if entry.Logger.level() >= PanicLevel {
|
||||
entry.Panic(entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
// Sprintlnn => Sprint no newline. This is to get the behavior of how
|
||||
// fmt.Sprintln where spaces are always added between operands, regardless of
|
||||
// their type. Instead of vendoring the Sprintln implementation to spare a
|
||||
// string allocation, we do the simplest thing.
|
||||
func (entry *Entry) sprintlnn(args ...interface{}) string {
|
||||
msg := fmt.Sprintln(args...)
|
||||
return msg[:len(msg)-1]
|
||||
}
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEntryWithError(t *testing.T) {
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
defer func() {
|
||||
ErrorKey = "error"
|
||||
}()
|
||||
|
||||
err := fmt.Errorf("kaboom at layer %d", 4711)
|
||||
|
||||
assert.Equal(err, WithError(err).Data["error"])
|
||||
|
||||
logger := New()
|
||||
logger.Out = &bytes.Buffer{}
|
||||
entry := NewEntry(logger)
|
||||
|
||||
assert.Equal(err, entry.WithError(err).Data["error"])
|
||||
|
||||
ErrorKey = "err"
|
||||
|
||||
assert.Equal(err, entry.WithError(err).Data["err"])
|
||||
|
||||
}
|
||||
|
||||
func TestEntryPanicln(t *testing.T) {
|
||||
errBoom := fmt.Errorf("boom time")
|
||||
|
||||
defer func() {
|
||||
p := recover()
|
||||
assert.NotNil(t, p)
|
||||
|
||||
switch pVal := p.(type) {
|
||||
case *Entry:
|
||||
assert.Equal(t, "kaboom", pVal.Message)
|
||||
assert.Equal(t, errBoom, pVal.Data["err"])
|
||||
default:
|
||||
t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
|
||||
}
|
||||
}()
|
||||
|
||||
logger := New()
|
||||
logger.Out = &bytes.Buffer{}
|
||||
entry := NewEntry(logger)
|
||||
entry.WithField("err", errBoom).Panicln("kaboom")
|
||||
}
|
||||
|
||||
func TestEntryPanicf(t *testing.T) {
|
||||
errBoom := fmt.Errorf("boom again")
|
||||
|
||||
defer func() {
|
||||
p := recover()
|
||||
assert.NotNil(t, p)
|
||||
|
||||
switch pVal := p.(type) {
|
||||
case *Entry:
|
||||
assert.Equal(t, "kaboom true", pVal.Message)
|
||||
assert.Equal(t, errBoom, pVal.Data["err"])
|
||||
default:
|
||||
t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal)
|
||||
}
|
||||
}()
|
||||
|
||||
logger := New()
|
||||
logger.Out = &bytes.Buffer{}
|
||||
entry := NewEntry(logger)
|
||||
entry.WithField("err", errBoom).Panicf("kaboom %v", true)
|
||||
}
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
// "os"
|
||||
)
|
||||
|
||||
var log = logrus.New()
|
||||
|
||||
func init() {
|
||||
log.Formatter = new(logrus.JSONFormatter)
|
||||
log.Formatter = new(logrus.TextFormatter) // default
|
||||
|
||||
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
|
||||
// if err == nil {
|
||||
// log.Out = file
|
||||
// } else {
|
||||
// log.Info("Failed to log to file, using default stderr")
|
||||
// }
|
||||
|
||||
log.Level = logrus.DebugLevel
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"err": err,
|
||||
"number": 100,
|
||||
}).Fatal("The ice breaks!")
|
||||
}
|
||||
}()
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"number": 8,
|
||||
}).Debug("Started observing beach")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"temperature": -4,
|
||||
}).Debug("Temperature changes")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "orca",
|
||||
"size": 9009,
|
||||
}).Panic("It's over 9000!")
|
||||
}
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/gemnasium/logrus-airbrake-hook.v2"
|
||||
)
|
||||
|
||||
var log = logrus.New()
|
||||
|
||||
func init() {
|
||||
log.Formatter = new(logrus.TextFormatter) // default
|
||||
log.Hooks.Add(airbrake.NewHook(123, "xyz", "development"))
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"omg": true,
|
||||
"number": 100,
|
||||
}).Fatal("The ice breaks!")
|
||||
}
|
||||
-193
@@ -1,193 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
// std is the name of the standard logger in stdlib `log`
|
||||
std = New()
|
||||
)
|
||||
|
||||
func StandardLogger() *Logger {
|
||||
return std
|
||||
}
|
||||
|
||||
// SetOutput sets the standard logger output.
|
||||
func SetOutput(out io.Writer) {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
std.Out = out
|
||||
}
|
||||
|
||||
// SetFormatter sets the standard logger formatter.
|
||||
func SetFormatter(formatter Formatter) {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
std.Formatter = formatter
|
||||
}
|
||||
|
||||
// SetLevel sets the standard logger level.
|
||||
func SetLevel(level Level) {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
std.SetLevel(level)
|
||||
}
|
||||
|
||||
// GetLevel returns the standard logger level.
|
||||
func GetLevel() Level {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
return std.level()
|
||||
}
|
||||
|
||||
// AddHook adds a hook to the standard logger hooks.
|
||||
func AddHook(hook Hook) {
|
||||
std.mu.Lock()
|
||||
defer std.mu.Unlock()
|
||||
std.Hooks.Add(hook)
|
||||
}
|
||||
|
||||
// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
|
||||
func WithError(err error) *Entry {
|
||||
return std.WithField(ErrorKey, err)
|
||||
}
|
||||
|
||||
// WithField creates an entry from the standard logger and adds a field to
|
||||
// it. If you want multiple fields, use `WithFields`.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
func WithField(key string, value interface{}) *Entry {
|
||||
return std.WithField(key, value)
|
||||
}
|
||||
|
||||
// WithFields creates an entry from the standard logger and adds multiple
|
||||
// fields to it. This is simply a helper for `WithField`, invoking it
|
||||
// once for each field.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
func WithFields(fields Fields) *Entry {
|
||||
return std.WithFields(fields)
|
||||
}
|
||||
|
||||
// Debug logs a message at level Debug on the standard logger.
|
||||
func Debug(args ...interface{}) {
|
||||
std.Debug(args...)
|
||||
}
|
||||
|
||||
// Print logs a message at level Info on the standard logger.
|
||||
func Print(args ...interface{}) {
|
||||
std.Print(args...)
|
||||
}
|
||||
|
||||
// Info logs a message at level Info on the standard logger.
|
||||
func Info(args ...interface{}) {
|
||||
std.Info(args...)
|
||||
}
|
||||
|
||||
// Warn logs a message at level Warn on the standard logger.
|
||||
func Warn(args ...interface{}) {
|
||||
std.Warn(args...)
|
||||
}
|
||||
|
||||
// Warning logs a message at level Warn on the standard logger.
|
||||
func Warning(args ...interface{}) {
|
||||
std.Warning(args...)
|
||||
}
|
||||
|
||||
// Error logs a message at level Error on the standard logger.
|
||||
func Error(args ...interface{}) {
|
||||
std.Error(args...)
|
||||
}
|
||||
|
||||
// Panic logs a message at level Panic on the standard logger.
|
||||
func Panic(args ...interface{}) {
|
||||
std.Panic(args...)
|
||||
}
|
||||
|
||||
// Fatal logs a message at level Fatal on the standard logger.
|
||||
func Fatal(args ...interface{}) {
|
||||
std.Fatal(args...)
|
||||
}
|
||||
|
||||
// Debugf logs a message at level Debug on the standard logger.
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
std.Debugf(format, args...)
|
||||
}
|
||||
|
||||
// Printf logs a message at level Info on the standard logger.
|
||||
func Printf(format string, args ...interface{}) {
|
||||
std.Printf(format, args...)
|
||||
}
|
||||
|
||||
// Infof logs a message at level Info on the standard logger.
|
||||
func Infof(format string, args ...interface{}) {
|
||||
std.Infof(format, args...)
|
||||
}
|
||||
|
||||
// Warnf logs a message at level Warn on the standard logger.
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
std.Warnf(format, args...)
|
||||
}
|
||||
|
||||
// Warningf logs a message at level Warn on the standard logger.
|
||||
func Warningf(format string, args ...interface{}) {
|
||||
std.Warningf(format, args...)
|
||||
}
|
||||
|
||||
// Errorf logs a message at level Error on the standard logger.
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
std.Errorf(format, args...)
|
||||
}
|
||||
|
||||
// Panicf logs a message at level Panic on the standard logger.
|
||||
func Panicf(format string, args ...interface{}) {
|
||||
std.Panicf(format, args...)
|
||||
}
|
||||
|
||||
// Fatalf logs a message at level Fatal on the standard logger.
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
std.Fatalf(format, args...)
|
||||
}
|
||||
|
||||
// Debugln logs a message at level Debug on the standard logger.
|
||||
func Debugln(args ...interface{}) {
|
||||
std.Debugln(args...)
|
||||
}
|
||||
|
||||
// Println logs a message at level Info on the standard logger.
|
||||
func Println(args ...interface{}) {
|
||||
std.Println(args...)
|
||||
}
|
||||
|
||||
// Infoln logs a message at level Info on the standard logger.
|
||||
func Infoln(args ...interface{}) {
|
||||
std.Infoln(args...)
|
||||
}
|
||||
|
||||
// Warnln logs a message at level Warn on the standard logger.
|
||||
func Warnln(args ...interface{}) {
|
||||
std.Warnln(args...)
|
||||
}
|
||||
|
||||
// Warningln logs a message at level Warn on the standard logger.
|
||||
func Warningln(args ...interface{}) {
|
||||
std.Warningln(args...)
|
||||
}
|
||||
|
||||
// Errorln logs a message at level Error on the standard logger.
|
||||
func Errorln(args ...interface{}) {
|
||||
std.Errorln(args...)
|
||||
}
|
||||
|
||||
// Panicln logs a message at level Panic on the standard logger.
|
||||
func Panicln(args ...interface{}) {
|
||||
std.Panicln(args...)
|
||||
}
|
||||
|
||||
// Fatalln logs a message at level Fatal on the standard logger.
|
||||
func Fatalln(args ...interface{}) {
|
||||
std.Fatalln(args...)
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import "time"
|
||||
|
||||
const DefaultTimestampFormat = time.RFC3339
|
||||
|
||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||
// `Entry`. It exposes all the fields, including the default ones:
|
||||
//
|
||||
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
|
||||
// * `entry.Data["time"]`. The timestamp.
|
||||
// * `entry.Data["level"]. The level the entry was logged at.
|
||||
//
|
||||
// Any additional fields added with `WithField` or `WithFields` are also in
|
||||
// `entry.Data`. Format is expected to return an array of bytes which are then
|
||||
// logged to `logger.Out`.
|
||||
type Formatter interface {
|
||||
Format(*Entry) ([]byte, error)
|
||||
}
|
||||
|
||||
// This is to not silently overwrite `time`, `msg` and `level` fields when
|
||||
// dumping it. If this code wasn't there doing:
|
||||
//
|
||||
// logrus.WithField("level", 1).Info("hello")
|
||||
//
|
||||
// Would just silently drop the user provided level. Instead with this code
|
||||
// it'll logged as:
|
||||
//
|
||||
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
||||
//
|
||||
// It's not exported because it's still using Data in an opinionated way. It's to
|
||||
// avoid code duplication between the two default formatters.
|
||||
func prefixFieldClashes(data Fields) {
|
||||
if t, ok := data["time"]; ok {
|
||||
data["fields.time"] = t
|
||||
}
|
||||
|
||||
if m, ok := data["msg"]; ok {
|
||||
data["fields.msg"] = m
|
||||
}
|
||||
|
||||
if l, ok := data["level"]; ok {
|
||||
data["fields.level"] = l
|
||||
}
|
||||
}
|
||||
-101
@@ -1,101 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// smallFields is a small size data set for benchmarking
|
||||
var smallFields = Fields{
|
||||
"foo": "bar",
|
||||
"baz": "qux",
|
||||
"one": "two",
|
||||
"three": "four",
|
||||
}
|
||||
|
||||
// largeFields is a large size data set for benchmarking
|
||||
var largeFields = Fields{
|
||||
"foo": "bar",
|
||||
"baz": "qux",
|
||||
"one": "two",
|
||||
"three": "four",
|
||||
"five": "six",
|
||||
"seven": "eight",
|
||||
"nine": "ten",
|
||||
"eleven": "twelve",
|
||||
"thirteen": "fourteen",
|
||||
"fifteen": "sixteen",
|
||||
"seventeen": "eighteen",
|
||||
"nineteen": "twenty",
|
||||
"a": "b",
|
||||
"c": "d",
|
||||
"e": "f",
|
||||
"g": "h",
|
||||
"i": "j",
|
||||
"k": "l",
|
||||
"m": "n",
|
||||
"o": "p",
|
||||
"q": "r",
|
||||
"s": "t",
|
||||
"u": "v",
|
||||
"w": "x",
|
||||
"y": "z",
|
||||
"this": "will",
|
||||
"make": "thirty",
|
||||
"entries": "yeah",
|
||||
}
|
||||
|
||||
var errorFields = Fields{
|
||||
"foo": fmt.Errorf("bar"),
|
||||
"baz": fmt.Errorf("qux"),
|
||||
}
|
||||
|
||||
func BenchmarkErrorTextFormatter(b *testing.B) {
|
||||
doBenchmark(b, &TextFormatter{DisableColors: true}, errorFields)
|
||||
}
|
||||
|
||||
func BenchmarkSmallTextFormatter(b *testing.B) {
|
||||
doBenchmark(b, &TextFormatter{DisableColors: true}, smallFields)
|
||||
}
|
||||
|
||||
func BenchmarkLargeTextFormatter(b *testing.B) {
|
||||
doBenchmark(b, &TextFormatter{DisableColors: true}, largeFields)
|
||||
}
|
||||
|
||||
func BenchmarkSmallColoredTextFormatter(b *testing.B) {
|
||||
doBenchmark(b, &TextFormatter{ForceColors: true}, smallFields)
|
||||
}
|
||||
|
||||
func BenchmarkLargeColoredTextFormatter(b *testing.B) {
|
||||
doBenchmark(b, &TextFormatter{ForceColors: true}, largeFields)
|
||||
}
|
||||
|
||||
func BenchmarkSmallJSONFormatter(b *testing.B) {
|
||||
doBenchmark(b, &JSONFormatter{}, smallFields)
|
||||
}
|
||||
|
||||
func BenchmarkLargeJSONFormatter(b *testing.B) {
|
||||
doBenchmark(b, &JSONFormatter{}, largeFields)
|
||||
}
|
||||
|
||||
func doBenchmark(b *testing.B, formatter Formatter, fields Fields) {
|
||||
logger := New()
|
||||
|
||||
entry := &Entry{
|
||||
Time: time.Time{},
|
||||
Level: InfoLevel,
|
||||
Message: "message",
|
||||
Data: fields,
|
||||
Logger: logger,
|
||||
}
|
||||
var d []byte
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
d, err = formatter.Format(entry)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.SetBytes(int64(len(d)))
|
||||
}
|
||||
}
|
||||
-122
@@ -1,122 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type TestHook struct {
|
||||
Fired bool
|
||||
}
|
||||
|
||||
func (hook *TestHook) Fire(entry *Entry) error {
|
||||
hook.Fired = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hook *TestHook) Levels() []Level {
|
||||
return []Level{
|
||||
DebugLevel,
|
||||
InfoLevel,
|
||||
WarnLevel,
|
||||
ErrorLevel,
|
||||
FatalLevel,
|
||||
PanicLevel,
|
||||
}
|
||||
}
|
||||
|
||||
func TestHookFires(t *testing.T) {
|
||||
hook := new(TestHook)
|
||||
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Hooks.Add(hook)
|
||||
assert.Equal(t, hook.Fired, false)
|
||||
|
||||
log.Print("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, hook.Fired, true)
|
||||
})
|
||||
}
|
||||
|
||||
type ModifyHook struct {
|
||||
}
|
||||
|
||||
func (hook *ModifyHook) Fire(entry *Entry) error {
|
||||
entry.Data["wow"] = "whale"
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hook *ModifyHook) Levels() []Level {
|
||||
return []Level{
|
||||
DebugLevel,
|
||||
InfoLevel,
|
||||
WarnLevel,
|
||||
ErrorLevel,
|
||||
FatalLevel,
|
||||
PanicLevel,
|
||||
}
|
||||
}
|
||||
|
||||
func TestHookCanModifyEntry(t *testing.T) {
|
||||
hook := new(ModifyHook)
|
||||
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Hooks.Add(hook)
|
||||
log.WithField("wow", "elephant").Print("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["wow"], "whale")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCanFireMultipleHooks(t *testing.T) {
|
||||
hook1 := new(ModifyHook)
|
||||
hook2 := new(TestHook)
|
||||
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Hooks.Add(hook1)
|
||||
log.Hooks.Add(hook2)
|
||||
|
||||
log.WithField("wow", "elephant").Print("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["wow"], "whale")
|
||||
assert.Equal(t, hook2.Fired, true)
|
||||
})
|
||||
}
|
||||
|
||||
type ErrorHook struct {
|
||||
Fired bool
|
||||
}
|
||||
|
||||
func (hook *ErrorHook) Fire(entry *Entry) error {
|
||||
hook.Fired = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hook *ErrorHook) Levels() []Level {
|
||||
return []Level{
|
||||
ErrorLevel,
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorHookShouldntFireOnInfo(t *testing.T) {
|
||||
hook := new(ErrorHook)
|
||||
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Hooks.Add(hook)
|
||||
log.Info("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, hook.Fired, false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestErrorHookShouldFireOnError(t *testing.T) {
|
||||
hook := new(ErrorHook)
|
||||
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Hooks.Add(hook)
|
||||
log.Error("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, hook.Fired, true)
|
||||
})
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
package logrus
|
||||
|
||||
// A hook to be fired when logging on the logging levels returned from
|
||||
// `Levels()` on your implementation of the interface. Note that this is not
|
||||
// fired in a goroutine or a channel with workers, you should handle such
|
||||
// functionality yourself if your call is non-blocking and you don't wish for
|
||||
// the logging calls for levels returned from `Levels()` to block.
|
||||
type Hook interface {
|
||||
Levels() []Level
|
||||
Fire(*Entry) error
|
||||
}
|
||||
|
||||
// Internal type for storing the hooks on a logger instance.
|
||||
type LevelHooks map[Level][]Hook
|
||||
|
||||
// Add a hook to an instance of logger. This is called with
|
||||
// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
|
||||
func (hooks LevelHooks) Add(hook Hook) {
|
||||
for _, level := range hook.Levels() {
|
||||
hooks[level] = append(hooks[level], hook)
|
||||
}
|
||||
}
|
||||
|
||||
// Fire all the hooks for the passed level. Used by `entry.log` to fire
|
||||
// appropriate hooks for a log entry.
|
||||
func (hooks LevelHooks) Fire(level Level, entry *Entry) error {
|
||||
for _, hook := range hooks[level] {
|
||||
if err := hook.Fire(entry); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
# Syslog Hooks for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
import (
|
||||
"log/syslog"
|
||||
"github.com/sirupsen/logrus"
|
||||
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log := logrus.New()
|
||||
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||
|
||||
if err == nil {
|
||||
log.Hooks.Add(hook)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you want to connect to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). Just assign empty string to the first two parameters of `NewSyslogHook`. It should look like the following.
|
||||
|
||||
```go
|
||||
import (
|
||||
"log/syslog"
|
||||
"github.com/sirupsen/logrus"
|
||||
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log := logrus.New()
|
||||
hook, err := logrus_syslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
|
||||
|
||||
if err == nil {
|
||||
log.Hooks.Add(hook)
|
||||
}
|
||||
}
|
||||
```
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
// +build !windows,!nacl,!plan9
|
||||
|
||||
package logrus_syslog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"log/syslog"
|
||||
"os"
|
||||
)
|
||||
|
||||
// SyslogHook to send logs via syslog.
|
||||
type SyslogHook struct {
|
||||
Writer *syslog.Writer
|
||||
SyslogNetwork string
|
||||
SyslogRaddr string
|
||||
}
|
||||
|
||||
// Creates a hook to be added to an instance of logger. This is called with
|
||||
// `hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG, "")`
|
||||
// `if err == nil { log.Hooks.Add(hook) }`
|
||||
func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) {
|
||||
w, err := syslog.Dial(network, raddr, priority, tag)
|
||||
return &SyslogHook{w, network, raddr}, err
|
||||
}
|
||||
|
||||
func (hook *SyslogHook) Fire(entry *logrus.Entry) error {
|
||||
line, err := entry.String()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
switch entry.Level {
|
||||
case logrus.PanicLevel:
|
||||
return hook.Writer.Crit(line)
|
||||
case logrus.FatalLevel:
|
||||
return hook.Writer.Crit(line)
|
||||
case logrus.ErrorLevel:
|
||||
return hook.Writer.Err(line)
|
||||
case logrus.WarnLevel:
|
||||
return hook.Writer.Warning(line)
|
||||
case logrus.InfoLevel:
|
||||
return hook.Writer.Info(line)
|
||||
case logrus.DebugLevel:
|
||||
return hook.Writer.Debug(line)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (hook *SyslogHook) Levels() []logrus.Level {
|
||||
return logrus.AllLevels
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
package logrus_syslog
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"log/syslog"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLocalhostAddAndPrint(t *testing.T) {
|
||||
log := logrus.New()
|
||||
hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Unable to connect to local syslog.")
|
||||
}
|
||||
|
||||
log.Hooks.Add(hook)
|
||||
|
||||
for _, level := range hook.Levels() {
|
||||
if len(log.Hooks[level]) != 1 {
|
||||
t.Errorf("SyslogHook was not added. The length of log.Hooks[%v]: %v", level, len(log.Hooks[level]))
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Congratulations!")
|
||||
}
|
||||
-95
@@ -1,95 +0,0 @@
|
||||
// The Test package is used for testing logrus. It is here for backwards
|
||||
// compatibility from when logrus' organization was upper-case. Please use
|
||||
// lower-case logrus and the `null` package instead of this one.
|
||||
package test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Hook is a hook designed for dealing with logs in test scenarios.
|
||||
type Hook struct {
|
||||
// Entries is an array of all entries that have been received by this hook.
|
||||
// For safe access, use the AllEntries() method, rather than reading this
|
||||
// value directly.
|
||||
Entries []*logrus.Entry
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewGlobal installs a test hook for the global logger.
|
||||
func NewGlobal() *Hook {
|
||||
|
||||
hook := new(Hook)
|
||||
logrus.AddHook(hook)
|
||||
|
||||
return hook
|
||||
|
||||
}
|
||||
|
||||
// NewLocal installs a test hook for a given local logger.
|
||||
func NewLocal(logger *logrus.Logger) *Hook {
|
||||
|
||||
hook := new(Hook)
|
||||
logger.Hooks.Add(hook)
|
||||
|
||||
return hook
|
||||
|
||||
}
|
||||
|
||||
// NewNullLogger creates a discarding logger and installs the test hook.
|
||||
func NewNullLogger() (*logrus.Logger, *Hook) {
|
||||
|
||||
logger := logrus.New()
|
||||
logger.Out = ioutil.Discard
|
||||
|
||||
return logger, NewLocal(logger)
|
||||
|
||||
}
|
||||
|
||||
func (t *Hook) Fire(e *logrus.Entry) error {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.Entries = append(t.Entries, e)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Hook) Levels() []logrus.Level {
|
||||
return logrus.AllLevels
|
||||
}
|
||||
|
||||
// LastEntry returns the last entry that was logged or nil.
|
||||
func (t *Hook) LastEntry() *logrus.Entry {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
i := len(t.Entries) - 1
|
||||
if i < 0 {
|
||||
return nil
|
||||
}
|
||||
// Make a copy, for safety
|
||||
e := *t.Entries[i]
|
||||
return &e
|
||||
}
|
||||
|
||||
// AllEntries returns all entries that were logged.
|
||||
func (t *Hook) AllEntries() []*logrus.Entry {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
// Make a copy so the returned value won't race with future log requests
|
||||
entries := make([]*logrus.Entry, len(t.Entries))
|
||||
for i, entry := range t.Entries {
|
||||
// Make a copy, for safety
|
||||
e := *entry
|
||||
entries[i] = &e
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
// Reset removes all Entries from this test hook.
|
||||
func (t *Hook) Reset() {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.Entries = make([]*logrus.Entry, 0)
|
||||
}
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAllHooks(t *testing.T) {
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
logger, hook := NewNullLogger()
|
||||
assert.Nil(hook.LastEntry())
|
||||
assert.Equal(0, len(hook.Entries))
|
||||
|
||||
logger.Error("Hello error")
|
||||
assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
|
||||
assert.Equal("Hello error", hook.LastEntry().Message)
|
||||
assert.Equal(1, len(hook.Entries))
|
||||
|
||||
logger.Warn("Hello warning")
|
||||
assert.Equal(logrus.WarnLevel, hook.LastEntry().Level)
|
||||
assert.Equal("Hello warning", hook.LastEntry().Message)
|
||||
assert.Equal(2, len(hook.Entries))
|
||||
|
||||
hook.Reset()
|
||||
assert.Nil(hook.LastEntry())
|
||||
assert.Equal(0, len(hook.Entries))
|
||||
|
||||
hook = NewGlobal()
|
||||
|
||||
logrus.Error("Hello error")
|
||||
assert.Equal(logrus.ErrorLevel, hook.LastEntry().Level)
|
||||
assert.Equal("Hello error", hook.LastEntry().Message)
|
||||
assert.Equal(1, len(hook.Entries))
|
||||
|
||||
}
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type fieldKey string
|
||||
type FieldMap map[fieldKey]string
|
||||
|
||||
const (
|
||||
FieldKeyMsg = "msg"
|
||||
FieldKeyLevel = "level"
|
||||
FieldKeyTime = "time"
|
||||
)
|
||||
|
||||
func (f FieldMap) resolve(key fieldKey) string {
|
||||
if k, ok := f[key]; ok {
|
||||
return k
|
||||
}
|
||||
|
||||
return string(key)
|
||||
}
|
||||
|
||||
type JSONFormatter struct {
|
||||
// TimestampFormat sets the format used for marshaling timestamps.
|
||||
TimestampFormat string
|
||||
|
||||
// DisableTimestamp allows disabling automatic timestamps in output
|
||||
DisableTimestamp bool
|
||||
|
||||
// FieldMap allows users to customize the names of keys for various fields.
|
||||
// As an example:
|
||||
// formatter := &JSONFormatter{
|
||||
// FieldMap: FieldMap{
|
||||
// FieldKeyTime: "@timestamp",
|
||||
// FieldKeyLevel: "@level",
|
||||
// FieldKeyMsg: "@message",
|
||||
// },
|
||||
// }
|
||||
FieldMap FieldMap
|
||||
}
|
||||
|
||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
data := make(Fields, len(entry.Data)+3)
|
||||
for k, v := range entry.Data {
|
||||
switch v := v.(type) {
|
||||
case error:
|
||||
// Otherwise errors are ignored by `encoding/json`
|
||||
// https://github.com/sirupsen/logrus/issues/137
|
||||
data[k] = v.Error()
|
||||
default:
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
prefixFieldClashes(data)
|
||||
|
||||
timestampFormat := f.TimestampFormat
|
||||
if timestampFormat == "" {
|
||||
timestampFormat = DefaultTimestampFormat
|
||||
}
|
||||
|
||||
if !f.DisableTimestamp {
|
||||
data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat)
|
||||
}
|
||||
data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
|
||||
data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
|
||||
|
||||
serialized, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||
}
|
||||
return append(serialized, '\n'), nil
|
||||
}
|
||||
-199
@@ -1,199 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestErrorNotLost(t *testing.T) {
|
||||
formatter := &JSONFormatter{}
|
||||
|
||||
b, err := formatter.Format(WithField("error", errors.New("wild walrus")))
|
||||
if err != nil {
|
||||
t.Fatal("Unable to format entry: ", err)
|
||||
}
|
||||
|
||||
entry := make(map[string]interface{})
|
||||
err = json.Unmarshal(b, &entry)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
||||
}
|
||||
|
||||
if entry["error"] != "wild walrus" {
|
||||
t.Fatal("Error field not set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorNotLostOnFieldNotNamedError(t *testing.T) {
|
||||
formatter := &JSONFormatter{}
|
||||
|
||||
b, err := formatter.Format(WithField("omg", errors.New("wild walrus")))
|
||||
if err != nil {
|
||||
t.Fatal("Unable to format entry: ", err)
|
||||
}
|
||||
|
||||
entry := make(map[string]interface{})
|
||||
err = json.Unmarshal(b, &entry)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
||||
}
|
||||
|
||||
if entry["omg"] != "wild walrus" {
|
||||
t.Fatal("Error field not set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldClashWithTime(t *testing.T) {
|
||||
formatter := &JSONFormatter{}
|
||||
|
||||
b, err := formatter.Format(WithField("time", "right now!"))
|
||||
if err != nil {
|
||||
t.Fatal("Unable to format entry: ", err)
|
||||
}
|
||||
|
||||
entry := make(map[string]interface{})
|
||||
err = json.Unmarshal(b, &entry)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
||||
}
|
||||
|
||||
if entry["fields.time"] != "right now!" {
|
||||
t.Fatal("fields.time not set to original time field")
|
||||
}
|
||||
|
||||
if entry["time"] != "0001-01-01T00:00:00Z" {
|
||||
t.Fatal("time field not set to current time, was: ", entry["time"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldClashWithMsg(t *testing.T) {
|
||||
formatter := &JSONFormatter{}
|
||||
|
||||
b, err := formatter.Format(WithField("msg", "something"))
|
||||
if err != nil {
|
||||
t.Fatal("Unable to format entry: ", err)
|
||||
}
|
||||
|
||||
entry := make(map[string]interface{})
|
||||
err = json.Unmarshal(b, &entry)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
||||
}
|
||||
|
||||
if entry["fields.msg"] != "something" {
|
||||
t.Fatal("fields.msg not set to original msg field")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldClashWithLevel(t *testing.T) {
|
||||
formatter := &JSONFormatter{}
|
||||
|
||||
b, err := formatter.Format(WithField("level", "something"))
|
||||
if err != nil {
|
||||
t.Fatal("Unable to format entry: ", err)
|
||||
}
|
||||
|
||||
entry := make(map[string]interface{})
|
||||
err = json.Unmarshal(b, &entry)
|
||||
if err != nil {
|
||||
t.Fatal("Unable to unmarshal formatted entry: ", err)
|
||||
}
|
||||
|
||||
if entry["fields.level"] != "something" {
|
||||
t.Fatal("fields.level not set to original level field")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONEntryEndsWithNewline(t *testing.T) {
|
||||
formatter := &JSONFormatter{}
|
||||
|
||||
b, err := formatter.Format(WithField("level", "something"))
|
||||
if err != nil {
|
||||
t.Fatal("Unable to format entry: ", err)
|
||||
}
|
||||
|
||||
if b[len(b)-1] != '\n' {
|
||||
t.Fatal("Expected JSON log entry to end with a newline")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONMessageKey(t *testing.T) {
|
||||
formatter := &JSONFormatter{
|
||||
FieldMap: FieldMap{
|
||||
FieldKeyMsg: "message",
|
||||
},
|
||||
}
|
||||
|
||||
b, err := formatter.Format(&Entry{Message: "oh hai"})
|
||||
if err != nil {
|
||||
t.Fatal("Unable to format entry: ", err)
|
||||
}
|
||||
s := string(b)
|
||||
if !(strings.Contains(s, "message") && strings.Contains(s, "oh hai")) {
|
||||
t.Fatal("Expected JSON to format message key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONLevelKey(t *testing.T) {
|
||||
formatter := &JSONFormatter{
|
||||
FieldMap: FieldMap{
|
||||
FieldKeyLevel: "somelevel",
|
||||
},
|
||||
}
|
||||
|
||||
b, err := formatter.Format(WithField("level", "something"))
|
||||
if err != nil {
|
||||
t.Fatal("Unable to format entry: ", err)
|
||||
}
|
||||
s := string(b)
|
||||
if !strings.Contains(s, "somelevel") {
|
||||
t.Fatal("Expected JSON to format level key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONTimeKey(t *testing.T) {
|
||||
formatter := &JSONFormatter{
|
||||
FieldMap: FieldMap{
|
||||
FieldKeyTime: "timeywimey",
|
||||
},
|
||||
}
|
||||
|
||||
b, err := formatter.Format(WithField("level", "something"))
|
||||
if err != nil {
|
||||
t.Fatal("Unable to format entry: ", err)
|
||||
}
|
||||
s := string(b)
|
||||
if !strings.Contains(s, "timeywimey") {
|
||||
t.Fatal("Expected JSON to format time key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONDisableTimestamp(t *testing.T) {
|
||||
formatter := &JSONFormatter{
|
||||
DisableTimestamp: true,
|
||||
}
|
||||
|
||||
b, err := formatter.Format(WithField("level", "something"))
|
||||
if err != nil {
|
||||
t.Fatal("Unable to format entry: ", err)
|
||||
}
|
||||
s := string(b)
|
||||
if strings.Contains(s, FieldKeyTime) {
|
||||
t.Error("Did not prevent timestamp", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONEnableTimestamp(t *testing.T) {
|
||||
formatter := &JSONFormatter{}
|
||||
|
||||
b, err := formatter.Format(WithField("level", "something"))
|
||||
if err != nil {
|
||||
t.Fatal("Unable to format entry: ", err)
|
||||
}
|
||||
s := string(b)
|
||||
if !strings.Contains(s, FieldKeyTime) {
|
||||
t.Error("Timestamp not present", s)
|
||||
}
|
||||
}
|
||||
-317
@@ -1,317 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type Logger struct {
|
||||
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
|
||||
// file, or leave it default which is `os.Stderr`. You can also set this to
|
||||
// something more adventorous, such as logging to Kafka.
|
||||
Out io.Writer
|
||||
// Hooks for the logger instance. These allow firing events based on logging
|
||||
// levels and log entries. For example, to send errors to an error tracking
|
||||
// service, log to StatsD or dump the core on fatal errors.
|
||||
Hooks LevelHooks
|
||||
// All log entries pass through the formatter before logged to Out. The
|
||||
// included formatters are `TextFormatter` and `JSONFormatter` for which
|
||||
// TextFormatter is the default. In development (when a TTY is attached) it
|
||||
// logs with colors, but to a file it wouldn't. You can easily implement your
|
||||
// own that implements the `Formatter` interface, see the `README` or included
|
||||
// formatters for examples.
|
||||
Formatter Formatter
|
||||
// The logging level the logger should log at. This is typically (and defaults
|
||||
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
||||
// logged. `logrus.Debug` is useful in
|
||||
Level Level
|
||||
// Used to sync writing to the log. Locking is enabled by Default
|
||||
mu MutexWrap
|
||||
// Reusable empty entry
|
||||
entryPool sync.Pool
|
||||
}
|
||||
|
||||
type MutexWrap struct {
|
||||
lock sync.Mutex
|
||||
disabled bool
|
||||
}
|
||||
|
||||
func (mw *MutexWrap) Lock() {
|
||||
if !mw.disabled {
|
||||
mw.lock.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
func (mw *MutexWrap) Unlock() {
|
||||
if !mw.disabled {
|
||||
mw.lock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (mw *MutexWrap) Disable() {
|
||||
mw.disabled = true
|
||||
}
|
||||
|
||||
// Creates a new logger. Configuration should be set by changing `Formatter`,
|
||||
// `Out` and `Hooks` directly on the default logger instance. You can also just
|
||||
// instantiate your own:
|
||||
//
|
||||
// var log = &Logger{
|
||||
// Out: os.Stderr,
|
||||
// Formatter: new(JSONFormatter),
|
||||
// Hooks: make(LevelHooks),
|
||||
// Level: logrus.DebugLevel,
|
||||
// }
|
||||
//
|
||||
// It's recommended to make this a global instance called `log`.
|
||||
func New() *Logger {
|
||||
return &Logger{
|
||||
Out: os.Stderr,
|
||||
Formatter: new(TextFormatter),
|
||||
Hooks: make(LevelHooks),
|
||||
Level: InfoLevel,
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) newEntry() *Entry {
|
||||
entry, ok := logger.entryPool.Get().(*Entry)
|
||||
if ok {
|
||||
return entry
|
||||
}
|
||||
return NewEntry(logger)
|
||||
}
|
||||
|
||||
func (logger *Logger) releaseEntry(entry *Entry) {
|
||||
logger.entryPool.Put(entry)
|
||||
}
|
||||
|
||||
// Adds a field to the log entry, note that it doesn't log until you call
|
||||
// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry.
|
||||
// If you want multiple fields, use `WithFields`.
|
||||
func (logger *Logger) WithField(key string, value interface{}) *Entry {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
return entry.WithField(key, value)
|
||||
}
|
||||
|
||||
// Adds a struct of fields to the log entry. All it does is call `WithField` for
|
||||
// each `Field`.
|
||||
func (logger *Logger) WithFields(fields Fields) *Entry {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
return entry.WithFields(fields)
|
||||
}
|
||||
|
||||
// Add an error as single field to the log entry. All it does is call
|
||||
// `WithError` for the given `error`.
|
||||
func (logger *Logger) WithError(err error) *Entry {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
return entry.WithError(err)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||
if logger.level() >= DebugLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Debugf(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Infof(format string, args ...interface{}) {
|
||||
if logger.level() >= InfoLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Infof(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Printf(format string, args ...interface{}) {
|
||||
entry := logger.newEntry()
|
||||
entry.Printf(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
||||
if logger.level() >= WarnLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Warnf(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||
if logger.level() >= WarnLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Warnf(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
||||
if logger.level() >= ErrorLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Errorf(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
||||
if logger.level() >= FatalLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Fatalf(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||
if logger.level() >= PanicLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Panicf(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Debug(args ...interface{}) {
|
||||
if logger.level() >= DebugLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Debug(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Info(args ...interface{}) {
|
||||
if logger.level() >= InfoLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Info(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Print(args ...interface{}) {
|
||||
entry := logger.newEntry()
|
||||
entry.Info(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warn(args ...interface{}) {
|
||||
if logger.level() >= WarnLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Warn(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Warning(args ...interface{}) {
|
||||
if logger.level() >= WarnLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Warn(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Error(args ...interface{}) {
|
||||
if logger.level() >= ErrorLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Error(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatal(args ...interface{}) {
|
||||
if logger.level() >= FatalLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Fatal(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panic(args ...interface{}) {
|
||||
if logger.level() >= PanicLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Panic(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Debugln(args ...interface{}) {
|
||||
if logger.level() >= DebugLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Debugln(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Infoln(args ...interface{}) {
|
||||
if logger.level() >= InfoLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Infoln(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Println(args ...interface{}) {
|
||||
entry := logger.newEntry()
|
||||
entry.Println(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warnln(args ...interface{}) {
|
||||
if logger.level() >= WarnLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Warnln(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Warningln(args ...interface{}) {
|
||||
if logger.level() >= WarnLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Warnln(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Errorln(args ...interface{}) {
|
||||
if logger.level() >= ErrorLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Errorln(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatalln(args ...interface{}) {
|
||||
if logger.level() >= FatalLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Fatalln(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panicln(args ...interface{}) {
|
||||
if logger.level() >= PanicLevel {
|
||||
entry := logger.newEntry()
|
||||
entry.Panicln(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
//When file is opened with appending mode, it's safe to
|
||||
//write concurrently to a file (within 4k message on Linux).
|
||||
//In these cases user can choose to disable the lock.
|
||||
func (logger *Logger) SetNoLock() {
|
||||
logger.mu.Disable()
|
||||
}
|
||||
|
||||
func (logger *Logger) level() Level {
|
||||
return Level(atomic.LoadUint32((*uint32)(&logger.Level)))
|
||||
}
|
||||
|
||||
func (logger *Logger) SetLevel(level Level) {
|
||||
atomic.StoreUint32((*uint32)(&logger.Level), uint32(level))
|
||||
}
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// smallFields is a small size data set for benchmarking
|
||||
var loggerFields = Fields{
|
||||
"foo": "bar",
|
||||
"baz": "qux",
|
||||
"one": "two",
|
||||
"three": "four",
|
||||
}
|
||||
|
||||
func BenchmarkDummyLogger(b *testing.B) {
|
||||
nullf, err := os.OpenFile("/dev/null", os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
b.Fatalf("%v", err)
|
||||
}
|
||||
defer nullf.Close()
|
||||
doLoggerBenchmark(b, nullf, &TextFormatter{DisableColors: true}, smallFields)
|
||||
}
|
||||
|
||||
func BenchmarkDummyLoggerNoLock(b *testing.B) {
|
||||
nullf, err := os.OpenFile("/dev/null", os.O_WRONLY|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
b.Fatalf("%v", err)
|
||||
}
|
||||
defer nullf.Close()
|
||||
doLoggerBenchmarkNoLock(b, nullf, &TextFormatter{DisableColors: true}, smallFields)
|
||||
}
|
||||
|
||||
func doLoggerBenchmark(b *testing.B, out *os.File, formatter Formatter, fields Fields) {
|
||||
logger := Logger{
|
||||
Out: out,
|
||||
Level: InfoLevel,
|
||||
Formatter: formatter,
|
||||
}
|
||||
entry := logger.WithFields(fields)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
entry.Info("aaa")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func doLoggerBenchmarkNoLock(b *testing.B, out *os.File, formatter Formatter, fields Fields) {
|
||||
logger := Logger{
|
||||
Out: out,
|
||||
Level: InfoLevel,
|
||||
Formatter: formatter,
|
||||
}
|
||||
logger.SetNoLock()
|
||||
entry := logger.WithFields(fields)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
entry.Info("aaa")
|
||||
}
|
||||
})
|
||||
}
|
||||
-143
@@ -1,143 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Fields type, used to pass to `WithFields`.
|
||||
type Fields map[string]interface{}
|
||||
|
||||
// Level type
|
||||
type Level uint32
|
||||
|
||||
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
||||
func (level Level) String() string {
|
||||
switch level {
|
||||
case DebugLevel:
|
||||
return "debug"
|
||||
case InfoLevel:
|
||||
return "info"
|
||||
case WarnLevel:
|
||||
return "warning"
|
||||
case ErrorLevel:
|
||||
return "error"
|
||||
case FatalLevel:
|
||||
return "fatal"
|
||||
case PanicLevel:
|
||||
return "panic"
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// ParseLevel takes a string level and returns the Logrus log level constant.
|
||||
func ParseLevel(lvl string) (Level, error) {
|
||||
switch strings.ToLower(lvl) {
|
||||
case "panic":
|
||||
return PanicLevel, nil
|
||||
case "fatal":
|
||||
return FatalLevel, nil
|
||||
case "error":
|
||||
return ErrorLevel, nil
|
||||
case "warn", "warning":
|
||||
return WarnLevel, nil
|
||||
case "info":
|
||||
return InfoLevel, nil
|
||||
case "debug":
|
||||
return DebugLevel, nil
|
||||
}
|
||||
|
||||
var l Level
|
||||
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
|
||||
}
|
||||
|
||||
// A constant exposing all logging levels
|
||||
var AllLevels = []Level{
|
||||
PanicLevel,
|
||||
FatalLevel,
|
||||
ErrorLevel,
|
||||
WarnLevel,
|
||||
InfoLevel,
|
||||
DebugLevel,
|
||||
}
|
||||
|
||||
// These are the different logging levels. You can set the logging level to log
|
||||
// on your instance of logger, obtained with `logrus.New()`.
|
||||
const (
|
||||
// PanicLevel level, highest level of severity. Logs and then calls panic with the
|
||||
// message passed to Debug, Info, ...
|
||||
PanicLevel Level = iota
|
||||
// FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the
|
||||
// logging level is set to Panic.
|
||||
FatalLevel
|
||||
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
|
||||
// Commonly used for hooks to send errors to an error tracking service.
|
||||
ErrorLevel
|
||||
// WarnLevel level. Non-critical entries that deserve eyes.
|
||||
WarnLevel
|
||||
// InfoLevel level. General operational entries about what's going on inside the
|
||||
// application.
|
||||
InfoLevel
|
||||
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
|
||||
DebugLevel
|
||||
)
|
||||
|
||||
// Won't compile if StdLogger can't be realized by a log.Logger
|
||||
var (
|
||||
_ StdLogger = &log.Logger{}
|
||||
_ StdLogger = &Entry{}
|
||||
_ StdLogger = &Logger{}
|
||||
)
|
||||
|
||||
// StdLogger is what your logrus-enabled library should take, that way
|
||||
// it'll accept a stdlib logger and a logrus logger. There's no standard
|
||||
// interface, this is the closest we get, unfortunately.
|
||||
type StdLogger interface {
|
||||
Print(...interface{})
|
||||
Printf(string, ...interface{})
|
||||
Println(...interface{})
|
||||
|
||||
Fatal(...interface{})
|
||||
Fatalf(string, ...interface{})
|
||||
Fatalln(...interface{})
|
||||
|
||||
Panic(...interface{})
|
||||
Panicf(string, ...interface{})
|
||||
Panicln(...interface{})
|
||||
}
|
||||
|
||||
// The FieldLogger interface generalizes the Entry and Logger types
|
||||
type FieldLogger interface {
|
||||
WithField(key string, value interface{}) *Entry
|
||||
WithFields(fields Fields) *Entry
|
||||
WithError(err error) *Entry
|
||||
|
||||
Debugf(format string, args ...interface{})
|
||||
Infof(format string, args ...interface{})
|
||||
Printf(format string, args ...interface{})
|
||||
Warnf(format string, args ...interface{})
|
||||
Warningf(format string, args ...interface{})
|
||||
Errorf(format string, args ...interface{})
|
||||
Fatalf(format string, args ...interface{})
|
||||
Panicf(format string, args ...interface{})
|
||||
|
||||
Debug(args ...interface{})
|
||||
Info(args ...interface{})
|
||||
Print(args ...interface{})
|
||||
Warn(args ...interface{})
|
||||
Warning(args ...interface{})
|
||||
Error(args ...interface{})
|
||||
Fatal(args ...interface{})
|
||||
Panic(args ...interface{})
|
||||
|
||||
Debugln(args ...interface{})
|
||||
Infoln(args ...interface{})
|
||||
Println(args ...interface{})
|
||||
Warnln(args ...interface{})
|
||||
Warningln(args ...interface{})
|
||||
Errorln(args ...interface{})
|
||||
Fatalln(args ...interface{})
|
||||
Panicln(args ...interface{})
|
||||
}
|
||||
-386
@@ -1,386 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func LogAndAssertJSON(t *testing.T, log func(*Logger), assertions func(fields Fields)) {
|
||||
var buffer bytes.Buffer
|
||||
var fields Fields
|
||||
|
||||
logger := New()
|
||||
logger.Out = &buffer
|
||||
logger.Formatter = new(JSONFormatter)
|
||||
|
||||
log(logger)
|
||||
|
||||
err := json.Unmarshal(buffer.Bytes(), &fields)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assertions(fields)
|
||||
}
|
||||
|
||||
func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields map[string]string)) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
logger := New()
|
||||
logger.Out = &buffer
|
||||
logger.Formatter = &TextFormatter{
|
||||
DisableColors: true,
|
||||
}
|
||||
|
||||
log(logger)
|
||||
|
||||
fields := make(map[string]string)
|
||||
for _, kv := range strings.Split(buffer.String(), " ") {
|
||||
if !strings.Contains(kv, "=") {
|
||||
continue
|
||||
}
|
||||
kvArr := strings.Split(kv, "=")
|
||||
key := strings.TrimSpace(kvArr[0])
|
||||
val := kvArr[1]
|
||||
if kvArr[1][0] == '"' {
|
||||
var err error
|
||||
val, err = strconv.Unquote(val)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
fields[key] = val
|
||||
}
|
||||
assertions(fields)
|
||||
}
|
||||
|
||||
func TestPrint(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Print("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test")
|
||||
assert.Equal(t, fields["level"], "info")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInfo(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Info("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test")
|
||||
assert.Equal(t, fields["level"], "info")
|
||||
})
|
||||
}
|
||||
|
||||
func TestWarn(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Warn("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test")
|
||||
assert.Equal(t, fields["level"], "warning")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInfolnShouldAddSpacesBetweenStrings(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Infoln("test", "test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test test")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInfolnShouldAddSpacesBetweenStringAndNonstring(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Infoln("test", 10)
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test 10")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInfolnShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Infoln(10, 10)
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "10 10")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInfoShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Infoln(10, 10)
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "10 10")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInfoShouldNotAddSpacesBetweenStringAndNonstring(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Info("test", 10)
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test10")
|
||||
})
|
||||
}
|
||||
|
||||
func TestInfoShouldNotAddSpacesBetweenStrings(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.Info("test", "test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "testtest")
|
||||
})
|
||||
}
|
||||
|
||||
func TestWithFieldsShouldAllowAssignments(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
var fields Fields
|
||||
|
||||
logger := New()
|
||||
logger.Out = &buffer
|
||||
logger.Formatter = new(JSONFormatter)
|
||||
|
||||
localLog := logger.WithFields(Fields{
|
||||
"key1": "value1",
|
||||
})
|
||||
|
||||
localLog.WithField("key2", "value2").Info("test")
|
||||
err := json.Unmarshal(buffer.Bytes(), &fields)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, "value2", fields["key2"])
|
||||
assert.Equal(t, "value1", fields["key1"])
|
||||
|
||||
buffer = bytes.Buffer{}
|
||||
fields = Fields{}
|
||||
localLog.Info("test")
|
||||
err = json.Unmarshal(buffer.Bytes(), &fields)
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, ok := fields["key2"]
|
||||
assert.Equal(t, false, ok)
|
||||
assert.Equal(t, "value1", fields["key1"])
|
||||
}
|
||||
|
||||
func TestUserSuppliedFieldDoesNotOverwriteDefaults(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.WithField("msg", "hello").Info("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test")
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserSuppliedMsgFieldHasPrefix(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.WithField("msg", "hello").Info("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["msg"], "test")
|
||||
assert.Equal(t, fields["fields.msg"], "hello")
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserSuppliedTimeFieldHasPrefix(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.WithField("time", "hello").Info("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["fields.time"], "hello")
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserSuppliedLevelFieldHasPrefix(t *testing.T) {
|
||||
LogAndAssertJSON(t, func(log *Logger) {
|
||||
log.WithField("level", 1).Info("test")
|
||||
}, func(fields Fields) {
|
||||
assert.Equal(t, fields["level"], "info")
|
||||
assert.Equal(t, fields["fields.level"], 1.0) // JSON has floats only
|
||||
})
|
||||
}
|
||||
|
||||
func TestDefaultFieldsAreNotPrefixed(t *testing.T) {
|
||||
LogAndAssertText(t, func(log *Logger) {
|
||||
ll := log.WithField("herp", "derp")
|
||||
ll.Info("hello")
|
||||
ll.Info("bye")
|
||||
}, func(fields map[string]string) {
|
||||
for _, fieldName := range []string{"fields.level", "fields.time", "fields.msg"} {
|
||||
if _, ok := fields[fieldName]; ok {
|
||||
t.Fatalf("should not have prefixed %q: %v", fieldName, fields)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) {
|
||||
|
||||
var buffer bytes.Buffer
|
||||
var fields Fields
|
||||
|
||||
logger := New()
|
||||
logger.Out = &buffer
|
||||
logger.Formatter = new(JSONFormatter)
|
||||
|
||||
llog := logger.WithField("context", "eating raw fish")
|
||||
|
||||
llog.Info("looks delicious")
|
||||
|
||||
err := json.Unmarshal(buffer.Bytes(), &fields)
|
||||
assert.NoError(t, err, "should have decoded first message")
|
||||
assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields")
|
||||
assert.Equal(t, fields["msg"], "looks delicious")
|
||||
assert.Equal(t, fields["context"], "eating raw fish")
|
||||
|
||||
buffer.Reset()
|
||||
|
||||
llog.Warn("omg it is!")
|
||||
|
||||
err = json.Unmarshal(buffer.Bytes(), &fields)
|
||||
assert.NoError(t, err, "should have decoded second message")
|
||||
assert.Equal(t, len(fields), 4, "should only have msg/time/level/context fields")
|
||||
assert.Equal(t, fields["msg"], "omg it is!")
|
||||
assert.Equal(t, fields["context"], "eating raw fish")
|
||||
assert.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry")
|
||||
|
||||
}
|
||||
|
||||
func TestConvertLevelToString(t *testing.T) {
|
||||
assert.Equal(t, "debug", DebugLevel.String())
|
||||
assert.Equal(t, "info", InfoLevel.String())
|
||||
assert.Equal(t, "warning", WarnLevel.String())
|
||||
assert.Equal(t, "error", ErrorLevel.String())
|
||||
assert.Equal(t, "fatal", FatalLevel.String())
|
||||
assert.Equal(t, "panic", PanicLevel.String())
|
||||
}
|
||||
|
||||
func TestParseLevel(t *testing.T) {
|
||||
l, err := ParseLevel("panic")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, PanicLevel, l)
|
||||
|
||||
l, err = ParseLevel("PANIC")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, PanicLevel, l)
|
||||
|
||||
l, err = ParseLevel("fatal")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, FatalLevel, l)
|
||||
|
||||
l, err = ParseLevel("FATAL")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, FatalLevel, l)
|
||||
|
||||
l, err = ParseLevel("error")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, ErrorLevel, l)
|
||||
|
||||
l, err = ParseLevel("ERROR")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, ErrorLevel, l)
|
||||
|
||||
l, err = ParseLevel("warn")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, WarnLevel, l)
|
||||
|
||||
l, err = ParseLevel("WARN")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, WarnLevel, l)
|
||||
|
||||
l, err = ParseLevel("warning")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, WarnLevel, l)
|
||||
|
||||
l, err = ParseLevel("WARNING")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, WarnLevel, l)
|
||||
|
||||
l, err = ParseLevel("info")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, InfoLevel, l)
|
||||
|
||||
l, err = ParseLevel("INFO")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, InfoLevel, l)
|
||||
|
||||
l, err = ParseLevel("debug")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, DebugLevel, l)
|
||||
|
||||
l, err = ParseLevel("DEBUG")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, DebugLevel, l)
|
||||
|
||||
l, err = ParseLevel("invalid")
|
||||
assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error())
|
||||
}
|
||||
|
||||
func TestGetSetLevelRace(t *testing.T) {
|
||||
wg := sync.WaitGroup{}
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
if i%2 == 0 {
|
||||
SetLevel(InfoLevel)
|
||||
} else {
|
||||
GetLevel()
|
||||
}
|
||||
}(i)
|
||||
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestLoggingRace(t *testing.T) {
|
||||
logger := New()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(100)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
go func() {
|
||||
logger.Info("info")
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// Compile test
|
||||
func TestLogrusInterface(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
fn := func(l FieldLogger) {
|
||||
b := l.WithField("key", "value")
|
||||
b.Debug("Test")
|
||||
}
|
||||
// test logger
|
||||
logger := New()
|
||||
logger.Out = &buffer
|
||||
fn(logger)
|
||||
|
||||
// test Entry
|
||||
e := logger.WithField("another", "value")
|
||||
fn(e)
|
||||
}
|
||||
|
||||
// Implements io.Writer using channels for synchronization, so we can wait on
|
||||
// the Entry.Writer goroutine to write in a non-racey way. This does assume that
|
||||
// there is a single call to Logger.Out for each message.
|
||||
type channelWriter chan []byte
|
||||
|
||||
func (cw channelWriter) Write(p []byte) (int, error) {
|
||||
cw <- p
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func TestEntryWriter(t *testing.T) {
|
||||
cw := channelWriter(make(chan []byte, 1))
|
||||
log := New()
|
||||
log.Out = cw
|
||||
log.Formatter = new(JSONFormatter)
|
||||
log.WithField("foo", "bar").WriterLevel(WarnLevel).Write([]byte("hello\n"))
|
||||
|
||||
bs := <-cw
|
||||
var fields Fields
|
||||
err := json.Unmarshal(bs, &fields)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, fields["foo"], "bar")
|
||||
assert.Equal(t, fields["level"], "warning")
|
||||
}
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
// +build appengine
|
||||
|
||||
package logrus
|
||||
|
||||
import "io"
|
||||
|
||||
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
||||
func IsTerminal(f io.Writer) bool {
|
||||
return true
|
||||
}
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
// +build darwin freebsd openbsd netbsd dragonfly
|
||||
// +build !appengine
|
||||
|
||||
package logrus
|
||||
|
||||
import "syscall"
|
||||
|
||||
const ioctlReadTermios = syscall.TIOCGETA
|
||||
|
||||
type Termios syscall.Termios
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
// Based on ssh/terminal:
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !appengine
|
||||
|
||||
package logrus
|
||||
|
||||
import "syscall"
|
||||
|
||||
const ioctlReadTermios = syscall.TCGETS
|
||||
|
||||
type Termios syscall.Termios
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
// Based on ssh/terminal:
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux darwin freebsd openbsd netbsd dragonfly
|
||||
// +build !appengine
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
||||
func IsTerminal(f io.Writer) bool {
|
||||
var termios Termios
|
||||
switch v := f.(type) {
|
||||
case *os.File:
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(v.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
||||
return err == 0
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
// +build solaris,!appengine
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(f io.Writer) bool {
|
||||
switch v := f.(type) {
|
||||
case *os.File:
|
||||
_, err := unix.IoctlGetTermios(int(v.Fd()), unix.TCGETA)
|
||||
return err == nil
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
-82
@@ -1,82 +0,0 @@
|
||||
// Based on ssh/terminal:
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build windows,!appengine
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
|
||||
var (
|
||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
|
||||
)
|
||||
|
||||
const (
|
||||
enableProcessedOutput = 0x0001
|
||||
enableWrapAtEolOutput = 0x0002
|
||||
enableVirtualTerminalProcessing = 0x0004
|
||||
)
|
||||
|
||||
func getVersion() (float64, error) {
|
||||
stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
|
||||
cmd := exec.Command("cmd", "ver")
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// The output should be like "Microsoft Windows [Version XX.X.XXXXXX]"
|
||||
version := strings.Replace(stdout.String(), "\n", "", -1)
|
||||
version = strings.Replace(version, "\r\n", "", -1)
|
||||
|
||||
x1 := strings.Index(version, "[Version")
|
||||
|
||||
if x1 == -1 || strings.Index(version, "]") == -1 {
|
||||
return -1, errors.New("Can't determine Windows version")
|
||||
}
|
||||
|
||||
return strconv.ParseFloat(version[x1+9:x1+13], 64)
|
||||
}
|
||||
|
||||
func init() {
|
||||
ver, err := getVersion()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Activate Virtual Processing for Windows CMD
|
||||
// Info: https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
|
||||
if ver >= 10 {
|
||||
handle := syscall.Handle(os.Stderr.Fd())
|
||||
procSetConsoleMode.Call(uintptr(handle), enableProcessedOutput|enableWrapAtEolOutput|enableVirtualTerminalProcessing)
|
||||
}
|
||||
}
|
||||
|
||||
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
||||
func IsTerminal(f io.Writer) bool {
|
||||
switch v := f.(type) {
|
||||
case *os.File:
|
||||
var st uint32
|
||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(v.Fd()), uintptr(unsafe.Pointer(&st)), 0)
|
||||
return r != 0 && e == 0
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
-189
@@ -1,189 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
nocolor = 0
|
||||
red = 31
|
||||
green = 32
|
||||
yellow = 33
|
||||
blue = 36
|
||||
gray = 37
|
||||
)
|
||||
|
||||
var (
|
||||
baseTimestamp time.Time
|
||||
)
|
||||
|
||||
func init() {
|
||||
baseTimestamp = time.Now()
|
||||
}
|
||||
|
||||
type TextFormatter struct {
|
||||
// Set to true to bypass checking for a TTY before outputting colors.
|
||||
ForceColors bool
|
||||
|
||||
// Force disabling colors.
|
||||
DisableColors bool
|
||||
|
||||
// Disable timestamp logging. useful when output is redirected to logging
|
||||
// system that already adds timestamps.
|
||||
DisableTimestamp bool
|
||||
|
||||
// Enable logging the full timestamp when a TTY is attached instead of just
|
||||
// the time passed since beginning of execution.
|
||||
FullTimestamp bool
|
||||
|
||||
// TimestampFormat to use for display when a full timestamp is printed
|
||||
TimestampFormat string
|
||||
|
||||
// The fields are sorted by default for a consistent output. For applications
|
||||
// that log extremely frequently and don't use the JSON formatter this may not
|
||||
// be desired.
|
||||
DisableSorting bool
|
||||
|
||||
// QuoteEmptyFields will wrap empty fields in quotes if true
|
||||
QuoteEmptyFields bool
|
||||
|
||||
// QuoteCharacter can be set to the override the default quoting character "
|
||||
// with something else. For example: ', or `.
|
||||
QuoteCharacter string
|
||||
|
||||
// Whether the logger's out is to a terminal
|
||||
isTerminal bool
|
||||
|
||||
sync.Once
|
||||
}
|
||||
|
||||
func (f *TextFormatter) init(entry *Entry) {
|
||||
if len(f.QuoteCharacter) == 0 {
|
||||
f.QuoteCharacter = "\""
|
||||
}
|
||||
if entry.Logger != nil {
|
||||
f.isTerminal = IsTerminal(entry.Logger.Out)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
var b *bytes.Buffer
|
||||
keys := make([]string, 0, len(entry.Data))
|
||||
for k := range entry.Data {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
if !f.DisableSorting {
|
||||
sort.Strings(keys)
|
||||
}
|
||||
if entry.Buffer != nil {
|
||||
b = entry.Buffer
|
||||
} else {
|
||||
b = &bytes.Buffer{}
|
||||
}
|
||||
|
||||
prefixFieldClashes(entry.Data)
|
||||
|
||||
f.Do(func() { f.init(entry) })
|
||||
|
||||
isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors
|
||||
|
||||
timestampFormat := f.TimestampFormat
|
||||
if timestampFormat == "" {
|
||||
timestampFormat = DefaultTimestampFormat
|
||||
}
|
||||
if isColored {
|
||||
f.printColored(b, entry, keys, timestampFormat)
|
||||
} else {
|
||||
if !f.DisableTimestamp {
|
||||
f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat))
|
||||
}
|
||||
f.appendKeyValue(b, "level", entry.Level.String())
|
||||
if entry.Message != "" {
|
||||
f.appendKeyValue(b, "msg", entry.Message)
|
||||
}
|
||||
for _, key := range keys {
|
||||
f.appendKeyValue(b, key, entry.Data[key])
|
||||
}
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) {
|
||||
var levelColor int
|
||||
switch entry.Level {
|
||||
case DebugLevel:
|
||||
levelColor = gray
|
||||
case WarnLevel:
|
||||
levelColor = yellow
|
||||
case ErrorLevel, FatalLevel, PanicLevel:
|
||||
levelColor = red
|
||||
default:
|
||||
levelColor = blue
|
||||
}
|
||||
|
||||
levelText := strings.ToUpper(entry.Level.String())[0:4]
|
||||
|
||||
if f.DisableTimestamp {
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m %-44s ", levelColor, levelText, entry.Message)
|
||||
} else if !f.FullTimestamp {
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), entry.Message)
|
||||
} else {
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message)
|
||||
}
|
||||
for _, k := range keys {
|
||||
v := entry.Data[k]
|
||||
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
|
||||
f.appendValue(b, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *TextFormatter) needsQuoting(text string) bool {
|
||||
if f.QuoteEmptyFields && len(text) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, ch := range text {
|
||||
if !((ch >= 'a' && ch <= 'z') ||
|
||||
(ch >= 'A' && ch <= 'Z') ||
|
||||
(ch >= '0' && ch <= '9') ||
|
||||
ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
|
||||
|
||||
b.WriteString(key)
|
||||
b.WriteByte('=')
|
||||
f.appendValue(b, value)
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
|
||||
func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
|
||||
stringVal, ok := value.(string)
|
||||
if !ok {
|
||||
stringVal = fmt.Sprint(value)
|
||||
}
|
||||
|
||||
if !f.needsQuoting(stringVal) {
|
||||
b.WriteString(stringVal)
|
||||
} else {
|
||||
b.WriteString(f.quoteString(stringVal))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *TextFormatter) quoteString(v string) string {
|
||||
escapedQuote := fmt.Sprintf("\\%s", f.QuoteCharacter)
|
||||
escapedValue := strings.Replace(v, f.QuoteCharacter, escapedQuote, -1)
|
||||
|
||||
return fmt.Sprintf("%s%v%s", f.QuoteCharacter, escapedValue, f.QuoteCharacter)
|
||||
}
|
||||
-161
@@ -1,161 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func TestQuoting(t *testing.T) {
|
||||
tf := &TextFormatter{DisableColors: true}
|
||||
|
||||
checkQuoting := func(q bool, value interface{}) {
|
||||
b, _ := tf.Format(WithField("test", value))
|
||||
idx := bytes.Index(b, ([]byte)("test="))
|
||||
cont := bytes.Contains(b[idx+5:], []byte(tf.QuoteCharacter))
|
||||
if cont != q {
|
||||
if q {
|
||||
t.Errorf("quoting expected for: %#v", value)
|
||||
} else {
|
||||
t.Errorf("quoting not expected for: %#v", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkQuoting(false, "")
|
||||
checkQuoting(false, "abcd")
|
||||
checkQuoting(false, "v1.0")
|
||||
checkQuoting(false, "1234567890")
|
||||
checkQuoting(false, "/foobar")
|
||||
checkQuoting(false, "foo_bar")
|
||||
checkQuoting(false, "foo@bar")
|
||||
checkQuoting(false, "foobar^")
|
||||
checkQuoting(false, "+/-_^@f.oobar")
|
||||
checkQuoting(true, "foobar$")
|
||||
checkQuoting(true, "&foobar")
|
||||
checkQuoting(true, "x y")
|
||||
checkQuoting(true, "x,y")
|
||||
checkQuoting(false, errors.New("invalid"))
|
||||
checkQuoting(true, errors.New("invalid argument"))
|
||||
|
||||
// Test for custom quote character.
|
||||
tf.QuoteCharacter = "`"
|
||||
checkQuoting(false, "")
|
||||
checkQuoting(false, "abcd")
|
||||
checkQuoting(false, "/foobar")
|
||||
checkQuoting(false, "foo_bar")
|
||||
checkQuoting(false, "foo@bar")
|
||||
checkQuoting(false, "foobar^")
|
||||
checkQuoting(true, "foobar$")
|
||||
checkQuoting(true, "&foobar")
|
||||
checkQuoting(true, errors.New("invalid argument"))
|
||||
|
||||
// Test for multi-character quotes.
|
||||
tf.QuoteCharacter = "§~±"
|
||||
checkQuoting(false, "abcd")
|
||||
checkQuoting(true, errors.New("invalid argument"))
|
||||
|
||||
// Test for quoting empty fields.
|
||||
tf.QuoteEmptyFields = true
|
||||
checkQuoting(true, "")
|
||||
checkQuoting(false, "abcd")
|
||||
checkQuoting(true, errors.New("invalid argument"))
|
||||
}
|
||||
|
||||
func TestEscaping_DefaultQuoteCharacter(t *testing.T) {
|
||||
tf := &TextFormatter{DisableColors: true}
|
||||
|
||||
testCases := []struct {
|
||||
value string
|
||||
expected string
|
||||
}{
|
||||
{`ba"r`, `ba\"r`},
|
||||
{`ba'r`, `ba'r`},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
b, _ := tf.Format(WithField("test", tc.value))
|
||||
if !bytes.Contains(b, []byte(tc.expected)) {
|
||||
t.Errorf("escaping expected for %q (result was %q instead of %q)", tc.value, string(b), tc.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEscaping_Interface(t *testing.T) {
|
||||
tf := &TextFormatter{DisableColors: true}
|
||||
|
||||
ts := time.Now()
|
||||
|
||||
testCases := []struct {
|
||||
value interface{}
|
||||
expected string
|
||||
}{
|
||||
{ts, fmt.Sprintf("\"%s\"", ts.String())},
|
||||
{errors.New("error: something went wrong"), "\"error: something went wrong\""},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
b, _ := tf.Format(WithField("test", tc.value))
|
||||
if !bytes.Contains(b, []byte(tc.expected)) {
|
||||
t.Errorf("escaping expected for %q (result was %q instead of %q)", tc.value, string(b), tc.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEscaping_CustomQuoteCharacter(t *testing.T) {
|
||||
tf := &TextFormatter{DisableColors: true}
|
||||
|
||||
testCases := []struct {
|
||||
value string
|
||||
expected string
|
||||
quoteChar string
|
||||
}{
|
||||
{`ba"r`, `ba"r`, `'`},
|
||||
{`ba'r`, `ba\'r`, `'`},
|
||||
{`ba'r`, `ba'r`, `^`},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tf.QuoteCharacter = tc.quoteChar
|
||||
b, _ := tf.Format(WithField("test", tc.value))
|
||||
if !bytes.Contains(b, []byte(tc.expected)) {
|
||||
t.Errorf("escaping expected for %q (result was %q instead of %q)", tc.value, string(b), tc.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimestampFormat(t *testing.T) {
|
||||
checkTimeStr := func(format string) {
|
||||
customFormatter := &TextFormatter{DisableColors: true, TimestampFormat: format}
|
||||
customStr, _ := customFormatter.Format(WithField("test", "test"))
|
||||
timeStart := bytes.Index(customStr, ([]byte)("time="))
|
||||
timeEnd := bytes.Index(customStr, ([]byte)("level="))
|
||||
timeStr := customStr[timeStart+5+len(customFormatter.QuoteCharacter) : timeEnd-1-len(customFormatter.QuoteCharacter)]
|
||||
if format == "" {
|
||||
format = time.RFC3339
|
||||
}
|
||||
_, e := time.Parse(format, (string)(timeStr))
|
||||
if e != nil {
|
||||
t.Errorf("time string \"%s\" did not match provided time format \"%s\": %s", timeStr, format, e)
|
||||
}
|
||||
}
|
||||
|
||||
checkTimeStr("2006-01-02T15:04:05.000000000Z07:00")
|
||||
checkTimeStr("Mon Jan _2 15:04:05 2006")
|
||||
checkTimeStr("")
|
||||
}
|
||||
|
||||
func TestDisableTimestampWithColoredOutput(t *testing.T) {
|
||||
tf := &TextFormatter{DisableTimestamp: true, ForceColors: true}
|
||||
|
||||
b, _ := tf.Format(WithField("test", "test"))
|
||||
if strings.Contains(string(b), "[0000]") {
|
||||
t.Error("timestamp not expected when DisableTimestamp is true")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO add tests for sorting etc., this requires a parser for the text
|
||||
// formatter output.
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func (logger *Logger) Writer() *io.PipeWriter {
|
||||
return logger.WriterLevel(InfoLevel)
|
||||
}
|
||||
|
||||
func (logger *Logger) WriterLevel(level Level) *io.PipeWriter {
|
||||
return NewEntry(logger).WriterLevel(level)
|
||||
}
|
||||
|
||||
func (entry *Entry) Writer() *io.PipeWriter {
|
||||
return entry.WriterLevel(InfoLevel)
|
||||
}
|
||||
|
||||
func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
|
||||
reader, writer := io.Pipe()
|
||||
|
||||
var printFunc func(args ...interface{})
|
||||
|
||||
switch level {
|
||||
case DebugLevel:
|
||||
printFunc = entry.Debug
|
||||
case InfoLevel:
|
||||
printFunc = entry.Info
|
||||
case WarnLevel:
|
||||
printFunc = entry.Warn
|
||||
case ErrorLevel:
|
||||
printFunc = entry.Error
|
||||
case FatalLevel:
|
||||
printFunc = entry.Fatal
|
||||
case PanicLevel:
|
||||
printFunc = entry.Panic
|
||||
default:
|
||||
printFunc = entry.Print
|
||||
}
|
||||
|
||||
go entry.writerScanner(reader, printFunc)
|
||||
runtime.SetFinalizer(writer, writerFinalizer)
|
||||
|
||||
return writer
|
||||
}
|
||||
|
||||
func (entry *Entry) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
for scanner.Scan() {
|
||||
printFunc(scanner.Text())
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
entry.Errorf("Error while reading from Writer: %s", err)
|
||||
}
|
||||
reader.Close()
|
||||
}
|
||||
|
||||
func writerFinalizer(writer *io.PipeWriter) {
|
||||
writer.Close()
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
# Go's `text/template` package with newline elision
|
||||
|
||||
This is a fork of Go 1.4's [text/template](http://golang.org/pkg/text/template/) package with one addition: a backslash immediately after a closing delimiter will delete all subsequent newlines until a non-newline.
|
||||
|
||||
eg.
|
||||
|
||||
```
|
||||
{{if true}}\
|
||||
hello
|
||||
{{end}}\
|
||||
```
|
||||
|
||||
Will result in:
|
||||
|
||||
```
|
||||
hello\n
|
||||
```
|
||||
|
||||
Rather than:
|
||||
|
||||
```
|
||||
\n
|
||||
hello\n
|
||||
\n
|
||||
```
|
||||
-406
@@ -1,406 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package template implements data-driven templates for generating textual output.
|
||||
|
||||
To generate HTML output, see package html/template, which has the same interface
|
||||
as this package but automatically secures HTML output against certain attacks.
|
||||
|
||||
Templates are executed by applying them to a data structure. Annotations in the
|
||||
template refer to elements of the data structure (typically a field of a struct
|
||||
or a key in a map) to control execution and derive values to be displayed.
|
||||
Execution of the template walks the structure and sets the cursor, represented
|
||||
by a period '.' and called "dot", to the value at the current location in the
|
||||
structure as execution proceeds.
|
||||
|
||||
The input text for a template is UTF-8-encoded text in any format.
|
||||
"Actions"--data evaluations or control structures--are delimited by
|
||||
"{{" and "}}"; all text outside actions is copied to the output unchanged.
|
||||
Actions may not span newlines, although comments can.
|
||||
|
||||
Once parsed, a template may be executed safely in parallel.
|
||||
|
||||
Here is a trivial example that prints "17 items are made of wool".
|
||||
|
||||
type Inventory struct {
|
||||
Material string
|
||||
Count uint
|
||||
}
|
||||
sweaters := Inventory{"wool", 17}
|
||||
tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}")
|
||||
if err != nil { panic(err) }
|
||||
err = tmpl.Execute(os.Stdout, sweaters)
|
||||
if err != nil { panic(err) }
|
||||
|
||||
More intricate examples appear below.
|
||||
|
||||
Actions
|
||||
|
||||
Here is the list of actions. "Arguments" and "pipelines" are evaluations of
|
||||
data, defined in detail below.
|
||||
|
||||
*/
|
||||
// {{/* a comment */}}
|
||||
// A comment; discarded. May contain newlines.
|
||||
// Comments do not nest and must start and end at the
|
||||
// delimiters, as shown here.
|
||||
/*
|
||||
|
||||
{{pipeline}}
|
||||
The default textual representation of the value of the pipeline
|
||||
is copied to the output.
|
||||
|
||||
{{if pipeline}} T1 {{end}}
|
||||
If the value of the pipeline is empty, no output is generated;
|
||||
otherwise, T1 is executed. The empty values are false, 0, any
|
||||
nil pointer or interface value, and any array, slice, map, or
|
||||
string of length zero.
|
||||
Dot is unaffected.
|
||||
|
||||
{{if pipeline}} T1 {{else}} T0 {{end}}
|
||||
If the value of the pipeline is empty, T0 is executed;
|
||||
otherwise, T1 is executed. Dot is unaffected.
|
||||
|
||||
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
|
||||
To simplify the appearance of if-else chains, the else action
|
||||
of an if may include another if directly; the effect is exactly
|
||||
the same as writing
|
||||
{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
|
||||
|
||||
{{range pipeline}} T1 {{end}}
|
||||
The value of the pipeline must be an array, slice, map, or channel.
|
||||
If the value of the pipeline has length zero, nothing is output;
|
||||
otherwise, dot is set to the successive elements of the array,
|
||||
slice, or map and T1 is executed. If the value is a map and the
|
||||
keys are of basic type with a defined order ("comparable"), the
|
||||
elements will be visited in sorted key order.
|
||||
|
||||
{{range pipeline}} T1 {{else}} T0 {{end}}
|
||||
The value of the pipeline must be an array, slice, map, or channel.
|
||||
If the value of the pipeline has length zero, dot is unaffected and
|
||||
T0 is executed; otherwise, dot is set to the successive elements
|
||||
of the array, slice, or map and T1 is executed.
|
||||
|
||||
{{template "name"}}
|
||||
The template with the specified name is executed with nil data.
|
||||
|
||||
{{template "name" pipeline}}
|
||||
The template with the specified name is executed with dot set
|
||||
to the value of the pipeline.
|
||||
|
||||
{{with pipeline}} T1 {{end}}
|
||||
If the value of the pipeline is empty, no output is generated;
|
||||
otherwise, dot is set to the value of the pipeline and T1 is
|
||||
executed.
|
||||
|
||||
{{with pipeline}} T1 {{else}} T0 {{end}}
|
||||
If the value of the pipeline is empty, dot is unaffected and T0
|
||||
is executed; otherwise, dot is set to the value of the pipeline
|
||||
and T1 is executed.
|
||||
|
||||
Arguments
|
||||
|
||||
An argument is a simple value, denoted by one of the following.
|
||||
|
||||
- A boolean, string, character, integer, floating-point, imaginary
|
||||
or complex constant in Go syntax. These behave like Go's untyped
|
||||
constants, although raw strings may not span newlines.
|
||||
- The keyword nil, representing an untyped Go nil.
|
||||
- The character '.' (period):
|
||||
.
|
||||
The result is the value of dot.
|
||||
- A variable name, which is a (possibly empty) alphanumeric string
|
||||
preceded by a dollar sign, such as
|
||||
$piOver2
|
||||
or
|
||||
$
|
||||
The result is the value of the variable.
|
||||
Variables are described below.
|
||||
- The name of a field of the data, which must be a struct, preceded
|
||||
by a period, such as
|
||||
.Field
|
||||
The result is the value of the field. Field invocations may be
|
||||
chained:
|
||||
.Field1.Field2
|
||||
Fields can also be evaluated on variables, including chaining:
|
||||
$x.Field1.Field2
|
||||
- The name of a key of the data, which must be a map, preceded
|
||||
by a period, such as
|
||||
.Key
|
||||
The result is the map element value indexed by the key.
|
||||
Key invocations may be chained and combined with fields to any
|
||||
depth:
|
||||
.Field1.Key1.Field2.Key2
|
||||
Although the key must be an alphanumeric identifier, unlike with
|
||||
field names they do not need to start with an upper case letter.
|
||||
Keys can also be evaluated on variables, including chaining:
|
||||
$x.key1.key2
|
||||
- The name of a niladic method of the data, preceded by a period,
|
||||
such as
|
||||
.Method
|
||||
The result is the value of invoking the method with dot as the
|
||||
receiver, dot.Method(). Such a method must have one return value (of
|
||||
any type) or two return values, the second of which is an error.
|
||||
If it has two and the returned error is non-nil, execution terminates
|
||||
and an error is returned to the caller as the value of Execute.
|
||||
Method invocations may be chained and combined with fields and keys
|
||||
to any depth:
|
||||
.Field1.Key1.Method1.Field2.Key2.Method2
|
||||
Methods can also be evaluated on variables, including chaining:
|
||||
$x.Method1.Field
|
||||
- The name of a niladic function, such as
|
||||
fun
|
||||
The result is the value of invoking the function, fun(). The return
|
||||
types and values behave as in methods. Functions and function
|
||||
names are described below.
|
||||
- A parenthesized instance of one the above, for grouping. The result
|
||||
may be accessed by a field or map key invocation.
|
||||
print (.F1 arg1) (.F2 arg2)
|
||||
(.StructValuedMethod "arg").Field
|
||||
|
||||
Arguments may evaluate to any type; if they are pointers the implementation
|
||||
automatically indirects to the base type when required.
|
||||
If an evaluation yields a function value, such as a function-valued
|
||||
field of a struct, the function is not invoked automatically, but it
|
||||
can be used as a truth value for an if action and the like. To invoke
|
||||
it, use the call function, defined below.
|
||||
|
||||
A pipeline is a possibly chained sequence of "commands". A command is a simple
|
||||
value (argument) or a function or method call, possibly with multiple arguments:
|
||||
|
||||
Argument
|
||||
The result is the value of evaluating the argument.
|
||||
.Method [Argument...]
|
||||
The method can be alone or the last element of a chain but,
|
||||
unlike methods in the middle of a chain, it can take arguments.
|
||||
The result is the value of calling the method with the
|
||||
arguments:
|
||||
dot.Method(Argument1, etc.)
|
||||
functionName [Argument...]
|
||||
The result is the value of calling the function associated
|
||||
with the name:
|
||||
function(Argument1, etc.)
|
||||
Functions and function names are described below.
|
||||
|
||||
Pipelines
|
||||
|
||||
A pipeline may be "chained" by separating a sequence of commands with pipeline
|
||||
characters '|'. In a chained pipeline, the result of the each command is
|
||||
passed as the last argument of the following command. The output of the final
|
||||
command in the pipeline is the value of the pipeline.
|
||||
|
||||
The output of a command will be either one value or two values, the second of
|
||||
which has type error. If that second value is present and evaluates to
|
||||
non-nil, execution terminates and the error is returned to the caller of
|
||||
Execute.
|
||||
|
||||
Variables
|
||||
|
||||
A pipeline inside an action may initialize a variable to capture the result.
|
||||
The initialization has syntax
|
||||
|
||||
$variable := pipeline
|
||||
|
||||
where $variable is the name of the variable. An action that declares a
|
||||
variable produces no output.
|
||||
|
||||
If a "range" action initializes a variable, the variable is set to the
|
||||
successive elements of the iteration. Also, a "range" may declare two
|
||||
variables, separated by a comma:
|
||||
|
||||
range $index, $element := pipeline
|
||||
|
||||
in which case $index and $element are set to the successive values of the
|
||||
array/slice index or map key and element, respectively. Note that if there is
|
||||
only one variable, it is assigned the element; this is opposite to the
|
||||
convention in Go range clauses.
|
||||
|
||||
A variable's scope extends to the "end" action of the control structure ("if",
|
||||
"with", or "range") in which it is declared, or to the end of the template if
|
||||
there is no such control structure. A template invocation does not inherit
|
||||
variables from the point of its invocation.
|
||||
|
||||
When execution begins, $ is set to the data argument passed to Execute, that is,
|
||||
to the starting value of dot.
|
||||
|
||||
Examples
|
||||
|
||||
Here are some example one-line templates demonstrating pipelines and variables.
|
||||
All produce the quoted word "output":
|
||||
|
||||
{{"\"output\""}}
|
||||
A string constant.
|
||||
{{`"output"`}}
|
||||
A raw string constant.
|
||||
{{printf "%q" "output"}}
|
||||
A function call.
|
||||
{{"output" | printf "%q"}}
|
||||
A function call whose final argument comes from the previous
|
||||
command.
|
||||
{{printf "%q" (print "out" "put")}}
|
||||
A parenthesized argument.
|
||||
{{"put" | printf "%s%s" "out" | printf "%q"}}
|
||||
A more elaborate call.
|
||||
{{"output" | printf "%s" | printf "%q"}}
|
||||
A longer chain.
|
||||
{{with "output"}}{{printf "%q" .}}{{end}}
|
||||
A with action using dot.
|
||||
{{with $x := "output" | printf "%q"}}{{$x}}{{end}}
|
||||
A with action that creates and uses a variable.
|
||||
{{with $x := "output"}}{{printf "%q" $x}}{{end}}
|
||||
A with action that uses the variable in another action.
|
||||
{{with $x := "output"}}{{$x | printf "%q"}}{{end}}
|
||||
The same, but pipelined.
|
||||
|
||||
Functions
|
||||
|
||||
During execution functions are found in two function maps: first in the
|
||||
template, then in the global function map. By default, no functions are defined
|
||||
in the template but the Funcs method can be used to add them.
|
||||
|
||||
Predefined global functions are named as follows.
|
||||
|
||||
and
|
||||
Returns the boolean AND of its arguments by returning the
|
||||
first empty argument or the last argument, that is,
|
||||
"and x y" behaves as "if x then y else x". All the
|
||||
arguments are evaluated.
|
||||
call
|
||||
Returns the result of calling the first argument, which
|
||||
must be a function, with the remaining arguments as parameters.
|
||||
Thus "call .X.Y 1 2" is, in Go notation, dot.X.Y(1, 2) where
|
||||
Y is a func-valued field, map entry, or the like.
|
||||
The first argument must be the result of an evaluation
|
||||
that yields a value of function type (as distinct from
|
||||
a predefined function such as print). The function must
|
||||
return either one or two result values, the second of which
|
||||
is of type error. If the arguments don't match the function
|
||||
or the returned error value is non-nil, execution stops.
|
||||
html
|
||||
Returns the escaped HTML equivalent of the textual
|
||||
representation of its arguments.
|
||||
index
|
||||
Returns the result of indexing its first argument by the
|
||||
following arguments. Thus "index x 1 2 3" is, in Go syntax,
|
||||
x[1][2][3]. Each indexed item must be a map, slice, or array.
|
||||
js
|
||||
Returns the escaped JavaScript equivalent of the textual
|
||||
representation of its arguments.
|
||||
len
|
||||
Returns the integer length of its argument.
|
||||
not
|
||||
Returns the boolean negation of its single argument.
|
||||
or
|
||||
Returns the boolean OR of its arguments by returning the
|
||||
first non-empty argument or the last argument, that is,
|
||||
"or x y" behaves as "if x then x else y". All the
|
||||
arguments are evaluated.
|
||||
print
|
||||
An alias for fmt.Sprint
|
||||
printf
|
||||
An alias for fmt.Sprintf
|
||||
println
|
||||
An alias for fmt.Sprintln
|
||||
urlquery
|
||||
Returns the escaped value of the textual representation of
|
||||
its arguments in a form suitable for embedding in a URL query.
|
||||
|
||||
The boolean functions take any zero value to be false and a non-zero
|
||||
value to be true.
|
||||
|
||||
There is also a set of binary comparison operators defined as
|
||||
functions:
|
||||
|
||||
eq
|
||||
Returns the boolean truth of arg1 == arg2
|
||||
ne
|
||||
Returns the boolean truth of arg1 != arg2
|
||||
lt
|
||||
Returns the boolean truth of arg1 < arg2
|
||||
le
|
||||
Returns the boolean truth of arg1 <= arg2
|
||||
gt
|
||||
Returns the boolean truth of arg1 > arg2
|
||||
ge
|
||||
Returns the boolean truth of arg1 >= arg2
|
||||
|
||||
For simpler multi-way equality tests, eq (only) accepts two or more
|
||||
arguments and compares the second and subsequent to the first,
|
||||
returning in effect
|
||||
|
||||
arg1==arg2 || arg1==arg3 || arg1==arg4 ...
|
||||
|
||||
(Unlike with || in Go, however, eq is a function call and all the
|
||||
arguments will be evaluated.)
|
||||
|
||||
The comparison functions work on basic types only (or named basic
|
||||
types, such as "type Celsius float32"). They implement the Go rules
|
||||
for comparison of values, except that size and exact type are
|
||||
ignored, so any integer value, signed or unsigned, may be compared
|
||||
with any other integer value. (The arithmetic value is compared,
|
||||
not the bit pattern, so all negative integers are less than all
|
||||
unsigned integers.) However, as usual, one may not compare an int
|
||||
with a float32 and so on.
|
||||
|
||||
Associated templates
|
||||
|
||||
Each template is named by a string specified when it is created. Also, each
|
||||
template is associated with zero or more other templates that it may invoke by
|
||||
name; such associations are transitive and form a name space of templates.
|
||||
|
||||
A template may use a template invocation to instantiate another associated
|
||||
template; see the explanation of the "template" action above. The name must be
|
||||
that of a template associated with the template that contains the invocation.
|
||||
|
||||
Nested template definitions
|
||||
|
||||
When parsing a template, another template may be defined and associated with the
|
||||
template being parsed. Template definitions must appear at the top level of the
|
||||
template, much like global variables in a Go program.
|
||||
|
||||
The syntax of such definitions is to surround each template declaration with a
|
||||
"define" and "end" action.
|
||||
|
||||
The define action names the template being created by providing a string
|
||||
constant. Here is a simple example:
|
||||
|
||||
`{{define "T1"}}ONE{{end}}
|
||||
{{define "T2"}}TWO{{end}}
|
||||
{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
|
||||
{{template "T3"}}`
|
||||
|
||||
This defines two templates, T1 and T2, and a third T3 that invokes the other two
|
||||
when it is executed. Finally it invokes T3. If executed this template will
|
||||
produce the text
|
||||
|
||||
ONE TWO
|
||||
|
||||
By construction, a template may reside in only one association. If it's
|
||||
necessary to have a template addressable from multiple associations, the
|
||||
template definition must be parsed multiple times to create distinct *Template
|
||||
values, or must be copied with the Clone or AddParseTree method.
|
||||
|
||||
Parse may be called multiple times to assemble the various associated templates;
|
||||
see the ParseFiles and ParseGlob functions and methods for simple ways to parse
|
||||
related templates stored in files.
|
||||
|
||||
A template may be executed directly or through ExecuteTemplate, which executes
|
||||
an associated template identified by name. To invoke our example above, we
|
||||
might write,
|
||||
|
||||
err := tmpl.Execute(os.Stdout, "no data needed")
|
||||
if err != nil {
|
||||
log.Fatalf("execution failed: %s", err)
|
||||
}
|
||||
|
||||
or to invoke a particular template explicitly by name,
|
||||
|
||||
err := tmpl.ExecuteTemplate(os.Stdout, "T2", "no data needed")
|
||||
if err != nil {
|
||||
log.Fatalf("execution failed: %s", err)
|
||||
}
|
||||
|
||||
*/
|
||||
package template
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package template_test
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/alecthomas/template"
|
||||
)
|
||||
|
||||
func ExampleTemplate() {
|
||||
// Define a template.
|
||||
const letter = `
|
||||
Dear {{.Name}},
|
||||
{{if .Attended}}
|
||||
It was a pleasure to see you at the wedding.{{else}}
|
||||
It is a shame you couldn't make it to the wedding.{{end}}
|
||||
{{with .Gift}}Thank you for the lovely {{.}}.
|
||||
{{end}}
|
||||
Best wishes,
|
||||
Josie
|
||||
`
|
||||
|
||||
// Prepare some data to insert into the template.
|
||||
type Recipient struct {
|
||||
Name, Gift string
|
||||
Attended bool
|
||||
}
|
||||
var recipients = []Recipient{
|
||||
{"Aunt Mildred", "bone china tea set", true},
|
||||
{"Uncle John", "moleskin pants", false},
|
||||
{"Cousin Rodney", "", false},
|
||||
}
|
||||
|
||||
// Create a new template and parse the letter into it.
|
||||
t := template.Must(template.New("letter").Parse(letter))
|
||||
|
||||
// Execute the template for each recipient.
|
||||
for _, r := range recipients {
|
||||
err := t.Execute(os.Stdout, r)
|
||||
if err != nil {
|
||||
log.Println("executing template:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Dear Aunt Mildred,
|
||||
//
|
||||
// It was a pleasure to see you at the wedding.
|
||||
// Thank you for the lovely bone china tea set.
|
||||
//
|
||||
// Best wishes,
|
||||
// Josie
|
||||
//
|
||||
// Dear Uncle John,
|
||||
//
|
||||
// It is a shame you couldn't make it to the wedding.
|
||||
// Thank you for the lovely moleskin pants.
|
||||
//
|
||||
// Best wishes,
|
||||
// Josie
|
||||
//
|
||||
// Dear Cousin Rodney,
|
||||
//
|
||||
// It is a shame you couldn't make it to the wedding.
|
||||
//
|
||||
// Best wishes,
|
||||
// Josie
|
||||
}
|
||||
-183
@@ -1,183 +0,0 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package template_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/alecthomas/template"
|
||||
)
|
||||
|
||||
// templateFile defines the contents of a template to be stored in a file, for testing.
|
||||
type templateFile struct {
|
||||
name string
|
||||
contents string
|
||||
}
|
||||
|
||||
func createTestDir(files []templateFile) string {
|
||||
dir, err := ioutil.TempDir("", "template")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, file := range files {
|
||||
f, err := os.Create(filepath.Join(dir, file.name))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = io.WriteString(f, file.contents)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
// Here we demonstrate loading a set of templates from a directory.
|
||||
func ExampleTemplate_glob() {
|
||||
// Here we create a temporary directory and populate it with our sample
|
||||
// template definition files; usually the template files would already
|
||||
// exist in some location known to the program.
|
||||
dir := createTestDir([]templateFile{
|
||||
// T0.tmpl is a plain template file that just invokes T1.
|
||||
{"T0.tmpl", `T0 invokes T1: ({{template "T1"}})`},
|
||||
// T1.tmpl defines a template, T1 that invokes T2.
|
||||
{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
|
||||
// T2.tmpl defines a template T2.
|
||||
{"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
|
||||
})
|
||||
// Clean up after the test; another quirk of running as an example.
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// pattern is the glob pattern used to find all the template files.
|
||||
pattern := filepath.Join(dir, "*.tmpl")
|
||||
|
||||
// Here starts the example proper.
|
||||
// T0.tmpl is the first name matched, so it becomes the starting template,
|
||||
// the value returned by ParseGlob.
|
||||
tmpl := template.Must(template.ParseGlob(pattern))
|
||||
|
||||
err := tmpl.Execute(os.Stdout, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("template execution: %s", err)
|
||||
}
|
||||
// Output:
|
||||
// T0 invokes T1: (T1 invokes T2: (This is T2))
|
||||
}
|
||||
|
||||
// This example demonstrates one way to share some templates
|
||||
// and use them in different contexts. In this variant we add multiple driver
|
||||
// templates by hand to an existing bundle of templates.
|
||||
func ExampleTemplate_helpers() {
|
||||
// Here we create a temporary directory and populate it with our sample
|
||||
// template definition files; usually the template files would already
|
||||
// exist in some location known to the program.
|
||||
dir := createTestDir([]templateFile{
|
||||
// T1.tmpl defines a template, T1 that invokes T2.
|
||||
{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
|
||||
// T2.tmpl defines a template T2.
|
||||
{"T2.tmpl", `{{define "T2"}}This is T2{{end}}`},
|
||||
})
|
||||
// Clean up after the test; another quirk of running as an example.
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// pattern is the glob pattern used to find all the template files.
|
||||
pattern := filepath.Join(dir, "*.tmpl")
|
||||
|
||||
// Here starts the example proper.
|
||||
// Load the helpers.
|
||||
templates := template.Must(template.ParseGlob(pattern))
|
||||
// Add one driver template to the bunch; we do this with an explicit template definition.
|
||||
_, err := templates.Parse("{{define `driver1`}}Driver 1 calls T1: ({{template `T1`}})\n{{end}}")
|
||||
if err != nil {
|
||||
log.Fatal("parsing driver1: ", err)
|
||||
}
|
||||
// Add another driver template.
|
||||
_, err = templates.Parse("{{define `driver2`}}Driver 2 calls T2: ({{template `T2`}})\n{{end}}")
|
||||
if err != nil {
|
||||
log.Fatal("parsing driver2: ", err)
|
||||
}
|
||||
// We load all the templates before execution. This package does not require
|
||||
// that behavior but html/template's escaping does, so it's a good habit.
|
||||
err = templates.ExecuteTemplate(os.Stdout, "driver1", nil)
|
||||
if err != nil {
|
||||
log.Fatalf("driver1 execution: %s", err)
|
||||
}
|
||||
err = templates.ExecuteTemplate(os.Stdout, "driver2", nil)
|
||||
if err != nil {
|
||||
log.Fatalf("driver2 execution: %s", err)
|
||||
}
|
||||
// Output:
|
||||
// Driver 1 calls T1: (T1 invokes T2: (This is T2))
|
||||
// Driver 2 calls T2: (This is T2)
|
||||
}
|
||||
|
||||
// This example demonstrates how to use one group of driver
|
||||
// templates with distinct sets of helper templates.
|
||||
func ExampleTemplate_share() {
|
||||
// Here we create a temporary directory and populate it with our sample
|
||||
// template definition files; usually the template files would already
|
||||
// exist in some location known to the program.
|
||||
dir := createTestDir([]templateFile{
|
||||
// T0.tmpl is a plain template file that just invokes T1.
|
||||
{"T0.tmpl", "T0 ({{.}} version) invokes T1: ({{template `T1`}})\n"},
|
||||
// T1.tmpl defines a template, T1 that invokes T2. Note T2 is not defined
|
||||
{"T1.tmpl", `{{define "T1"}}T1 invokes T2: ({{template "T2"}}){{end}}`},
|
||||
})
|
||||
// Clean up after the test; another quirk of running as an example.
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// pattern is the glob pattern used to find all the template files.
|
||||
pattern := filepath.Join(dir, "*.tmpl")
|
||||
|
||||
// Here starts the example proper.
|
||||
// Load the drivers.
|
||||
drivers := template.Must(template.ParseGlob(pattern))
|
||||
|
||||
// We must define an implementation of the T2 template. First we clone
|
||||
// the drivers, then add a definition of T2 to the template name space.
|
||||
|
||||
// 1. Clone the helper set to create a new name space from which to run them.
|
||||
first, err := drivers.Clone()
|
||||
if err != nil {
|
||||
log.Fatal("cloning helpers: ", err)
|
||||
}
|
||||
// 2. Define T2, version A, and parse it.
|
||||
_, err = first.Parse("{{define `T2`}}T2, version A{{end}}")
|
||||
if err != nil {
|
||||
log.Fatal("parsing T2: ", err)
|
||||
}
|
||||
|
||||
// Now repeat the whole thing, using a different version of T2.
|
||||
// 1. Clone the drivers.
|
||||
second, err := drivers.Clone()
|
||||
if err != nil {
|
||||
log.Fatal("cloning drivers: ", err)
|
||||
}
|
||||
// 2. Define T2, version B, and parse it.
|
||||
_, err = second.Parse("{{define `T2`}}T2, version B{{end}}")
|
||||
if err != nil {
|
||||
log.Fatal("parsing T2: ", err)
|
||||
}
|
||||
|
||||
// Execute the templates in the reverse order to verify the
|
||||
// first is unaffected by the second.
|
||||
err = second.ExecuteTemplate(os.Stdout, "T0.tmpl", "second")
|
||||
if err != nil {
|
||||
log.Fatalf("second execution: %s", err)
|
||||
}
|
||||
err = first.ExecuteTemplate(os.Stdout, "T0.tmpl", "first")
|
||||
if err != nil {
|
||||
log.Fatalf("first: execution: %s", err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// T0 (second version) invokes T1: (T1 invokes T2: (T2, version B))
|
||||
// T0 (first version) invokes T1: (T1 invokes T2: (T2, version A))
|
||||
}
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package template_test
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/template"
|
||||
)
|
||||
|
||||
// This example demonstrates a custom function to process template text.
|
||||
// It installs the strings.Title function and uses it to
|
||||
// Make Title Text Look Good In Our Template's Output.
|
||||
func ExampleTemplate_func() {
|
||||
// First we create a FuncMap with which to register the function.
|
||||
funcMap := template.FuncMap{
|
||||
// The name "title" is what the function will be called in the template text.
|
||||
"title": strings.Title,
|
||||
}
|
||||
|
||||
// A simple template definition to test our function.
|
||||
// We print the input text several ways:
|
||||
// - the original
|
||||
// - title-cased
|
||||
// - title-cased and then printed with %q
|
||||
// - printed with %q and then title-cased.
|
||||
const templateText = `
|
||||
Input: {{printf "%q" .}}
|
||||
Output 0: {{title .}}
|
||||
Output 1: {{title . | printf "%q"}}
|
||||
Output 2: {{printf "%q" . | title}}
|
||||
`
|
||||
|
||||
// Create a template, add the function map, and parse the text.
|
||||
tmpl, err := template.New("titleTest").Funcs(funcMap).Parse(templateText)
|
||||
if err != nil {
|
||||
log.Fatalf("parsing: %s", err)
|
||||
}
|
||||
|
||||
// Run the template to verify the output.
|
||||
err = tmpl.Execute(os.Stdout, "the go programming language")
|
||||
if err != nil {
|
||||
log.Fatalf("execution: %s", err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Input: "the go programming language"
|
||||
// Output 0: The Go Programming Language
|
||||
// Output 1: "The Go Programming Language"
|
||||
// Output 2: "The Go Programming Language"
|
||||
}
|
||||
-845
@@ -1,845 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package template
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/template/parse"
|
||||
)
|
||||
|
||||
// state represents the state of an execution. It's not part of the
|
||||
// template so that multiple executions of the same template
|
||||
// can execute in parallel.
|
||||
type state struct {
|
||||
tmpl *Template
|
||||
wr io.Writer
|
||||
node parse.Node // current node, for errors
|
||||
vars []variable // push-down stack of variable values.
|
||||
}
|
||||
|
||||
// variable holds the dynamic value of a variable such as $, $x etc.
|
||||
type variable struct {
|
||||
name string
|
||||
value reflect.Value
|
||||
}
|
||||
|
||||
// push pushes a new variable on the stack.
|
||||
func (s *state) push(name string, value reflect.Value) {
|
||||
s.vars = append(s.vars, variable{name, value})
|
||||
}
|
||||
|
||||
// mark returns the length of the variable stack.
|
||||
func (s *state) mark() int {
|
||||
return len(s.vars)
|
||||
}
|
||||
|
||||
// pop pops the variable stack up to the mark.
|
||||
func (s *state) pop(mark int) {
|
||||
s.vars = s.vars[0:mark]
|
||||
}
|
||||
|
||||
// setVar overwrites the top-nth variable on the stack. Used by range iterations.
|
||||
func (s *state) setVar(n int, value reflect.Value) {
|
||||
s.vars[len(s.vars)-n].value = value
|
||||
}
|
||||
|
||||
// varValue returns the value of the named variable.
|
||||
func (s *state) varValue(name string) reflect.Value {
|
||||
for i := s.mark() - 1; i >= 0; i-- {
|
||||
if s.vars[i].name == name {
|
||||
return s.vars[i].value
|
||||
}
|
||||
}
|
||||
s.errorf("undefined variable: %s", name)
|
||||
return zero
|
||||
}
|
||||
|
||||
var zero reflect.Value
|
||||
|
||||
// at marks the state to be on node n, for error reporting.
|
||||
func (s *state) at(node parse.Node) {
|
||||
s.node = node
|
||||
}
|
||||
|
||||
// doublePercent returns the string with %'s replaced by %%, if necessary,
|
||||
// so it can be used safely inside a Printf format string.
|
||||
func doublePercent(str string) string {
|
||||
if strings.Contains(str, "%") {
|
||||
str = strings.Replace(str, "%", "%%", -1)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// errorf formats the error and terminates processing.
|
||||
func (s *state) errorf(format string, args ...interface{}) {
|
||||
name := doublePercent(s.tmpl.Name())
|
||||
if s.node == nil {
|
||||
format = fmt.Sprintf("template: %s: %s", name, format)
|
||||
} else {
|
||||
location, context := s.tmpl.ErrorContext(s.node)
|
||||
format = fmt.Sprintf("template: %s: executing %q at <%s>: %s", location, name, doublePercent(context), format)
|
||||
}
|
||||
panic(fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
// errRecover is the handler that turns panics into returns from the top
|
||||
// level of Parse.
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ExecuteTemplate applies the template associated with t that has the given name
|
||||
// to the specified data object and writes the output to wr.
|
||||
// If an error occurs executing the template or writing its output,
|
||||
// execution stops, but partial results may already have been written to
|
||||
// the output writer.
|
||||
// A template may be executed safely in parallel.
|
||||
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
|
||||
tmpl := t.tmpl[name]
|
||||
if tmpl == nil {
|
||||
return fmt.Errorf("template: no template %q associated with template %q", name, t.name)
|
||||
}
|
||||
return tmpl.Execute(wr, data)
|
||||
}
|
||||
|
||||
// Execute applies a parsed template to the specified data object,
|
||||
// and writes the output to wr.
|
||||
// If an error occurs executing the template or writing its output,
|
||||
// execution stops, but partial results may already have been written to
|
||||
// the output writer.
|
||||
// A template may be executed safely in parallel.
|
||||
func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
|
||||
defer errRecover(&err)
|
||||
value := reflect.ValueOf(data)
|
||||
state := &state{
|
||||
tmpl: t,
|
||||
wr: wr,
|
||||
vars: []variable{{"$", value}},
|
||||
}
|
||||
t.init()
|
||||
if t.Tree == nil || t.Root == nil {
|
||||
var b bytes.Buffer
|
||||
for name, tmpl := range t.tmpl {
|
||||
if tmpl.Tree == nil || tmpl.Root == nil {
|
||||
continue
|
||||
}
|
||||
if b.Len() > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
fmt.Fprintf(&b, "%q", name)
|
||||
}
|
||||
var s string
|
||||
if b.Len() > 0 {
|
||||
s = "; defined templates are: " + b.String()
|
||||
}
|
||||
state.errorf("%q is an incomplete or empty template%s", t.Name(), s)
|
||||
}
|
||||
state.walk(value, t.Root)
|
||||
return
|
||||
}
|
||||
|
||||
// Walk functions step through the major pieces of the template structure,
|
||||
// generating output as they go.
|
||||
func (s *state) walk(dot reflect.Value, node parse.Node) {
|
||||
s.at(node)
|
||||
switch node := node.(type) {
|
||||
case *parse.ActionNode:
|
||||
// Do not pop variables so they persist until next end.
|
||||
// Also, if the action declares variables, don't print the result.
|
||||
val := s.evalPipeline(dot, node.Pipe)
|
||||
if len(node.Pipe.Decl) == 0 {
|
||||
s.printValue(node, val)
|
||||
}
|
||||
case *parse.IfNode:
|
||||
s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList)
|
||||
case *parse.ListNode:
|
||||
for _, node := range node.Nodes {
|
||||
s.walk(dot, node)
|
||||
}
|
||||
case *parse.RangeNode:
|
||||
s.walkRange(dot, node)
|
||||
case *parse.TemplateNode:
|
||||
s.walkTemplate(dot, node)
|
||||
case *parse.TextNode:
|
||||
if _, err := s.wr.Write(node.Text); err != nil {
|
||||
s.errorf("%s", err)
|
||||
}
|
||||
case *parse.WithNode:
|
||||
s.walkIfOrWith(parse.NodeWith, dot, node.Pipe, node.List, node.ElseList)
|
||||
default:
|
||||
s.errorf("unknown node: %s", node)
|
||||
}
|
||||
}
|
||||
|
||||
// walkIfOrWith walks an 'if' or 'with' node. The two control structures
|
||||
// are identical in behavior except that 'with' sets dot.
|
||||
func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse.PipeNode, list, elseList *parse.ListNode) {
|
||||
defer s.pop(s.mark())
|
||||
val := s.evalPipeline(dot, pipe)
|
||||
truth, ok := isTrue(val)
|
||||
if !ok {
|
||||
s.errorf("if/with can't use %v", val)
|
||||
}
|
||||
if truth {
|
||||
if typ == parse.NodeWith {
|
||||
s.walk(val, list)
|
||||
} else {
|
||||
s.walk(dot, list)
|
||||
}
|
||||
} else if elseList != nil {
|
||||
s.walk(dot, elseList)
|
||||
}
|
||||
}
|
||||
|
||||
// isTrue 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.
|
||||
func isTrue(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
|
||||
}
|
||||
|
||||
func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
|
||||
s.at(r)
|
||||
defer s.pop(s.mark())
|
||||
val, _ := indirect(s.evalPipeline(dot, r.Pipe))
|
||||
// mark top of stack before any variables in the body are pushed.
|
||||
mark := s.mark()
|
||||
oneIteration := func(index, elem reflect.Value) {
|
||||
// Set top var (lexically the second if there are two) to the element.
|
||||
if len(r.Pipe.Decl) > 0 {
|
||||
s.setVar(1, elem)
|
||||
}
|
||||
// Set next var (lexically the first if there are two) to the index.
|
||||
if len(r.Pipe.Decl) > 1 {
|
||||
s.setVar(2, index)
|
||||
}
|
||||
s.walk(elem, r.List)
|
||||
s.pop(mark)
|
||||
}
|
||||
switch val.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
if val.Len() == 0 {
|
||||
break
|
||||
}
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
oneIteration(reflect.ValueOf(i), val.Index(i))
|
||||
}
|
||||
return
|
||||
case reflect.Map:
|
||||
if val.Len() == 0 {
|
||||
break
|
||||
}
|
||||
for _, key := range sortKeys(val.MapKeys()) {
|
||||
oneIteration(key, val.MapIndex(key))
|
||||
}
|
||||
return
|
||||
case reflect.Chan:
|
||||
if val.IsNil() {
|
||||
break
|
||||
}
|
||||
i := 0
|
||||
for ; ; i++ {
|
||||
elem, ok := val.Recv()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
oneIteration(reflect.ValueOf(i), elem)
|
||||
}
|
||||
if i == 0 {
|
||||
break
|
||||
}
|
||||
return
|
||||
case reflect.Invalid:
|
||||
break // An invalid value is likely a nil map, etc. and acts like an empty map.
|
||||
default:
|
||||
s.errorf("range can't iterate over %v", val)
|
||||
}
|
||||
if r.ElseList != nil {
|
||||
s.walk(dot, r.ElseList)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) {
|
||||
s.at(t)
|
||||
tmpl := s.tmpl.tmpl[t.Name]
|
||||
if tmpl == nil {
|
||||
s.errorf("template %q not defined", t.Name)
|
||||
}
|
||||
// Variables declared by the pipeline persist.
|
||||
dot = s.evalPipeline(dot, t.Pipe)
|
||||
newState := *s
|
||||
newState.tmpl = tmpl
|
||||
// No dynamic scoping: template invocations inherit no variables.
|
||||
newState.vars = []variable{{"$", dot}}
|
||||
newState.walk(dot, tmpl.Root)
|
||||
}
|
||||
|
||||
// Eval functions evaluate pipelines, commands, and their elements and extract
|
||||
// values from the data structure by examining fields, calling methods, and so on.
|
||||
// The printing of those values happens only through walk functions.
|
||||
|
||||
// evalPipeline returns the value acquired by evaluating a pipeline. If the
|
||||
// pipeline has a variable declaration, the variable will be pushed on the
|
||||
// stack. Callers should therefore pop the stack after they are finished
|
||||
// executing commands depending on the pipeline value.
|
||||
func (s *state) evalPipeline(dot reflect.Value, pipe *parse.PipeNode) (value reflect.Value) {
|
||||
if pipe == nil {
|
||||
return
|
||||
}
|
||||
s.at(pipe)
|
||||
for _, cmd := range pipe.Cmds {
|
||||
value = s.evalCommand(dot, cmd, value) // previous value is this one's final arg.
|
||||
// If the object has type interface{}, dig down one level to the thing inside.
|
||||
if value.Kind() == reflect.Interface && value.Type().NumMethod() == 0 {
|
||||
value = reflect.ValueOf(value.Interface()) // lovely!
|
||||
}
|
||||
}
|
||||
for _, variable := range pipe.Decl {
|
||||
s.push(variable.Ident[0], value)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (s *state) notAFunction(args []parse.Node, final reflect.Value) {
|
||||
if len(args) > 1 || final.IsValid() {
|
||||
s.errorf("can't give argument to non-function %s", args[0])
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) evalCommand(dot reflect.Value, cmd *parse.CommandNode, final reflect.Value) reflect.Value {
|
||||
firstWord := cmd.Args[0]
|
||||
switch n := firstWord.(type) {
|
||||
case *parse.FieldNode:
|
||||
return s.evalFieldNode(dot, n, cmd.Args, final)
|
||||
case *parse.ChainNode:
|
||||
return s.evalChainNode(dot, n, cmd.Args, final)
|
||||
case *parse.IdentifierNode:
|
||||
// Must be a function.
|
||||
return s.evalFunction(dot, n, cmd, cmd.Args, final)
|
||||
case *parse.PipeNode:
|
||||
// Parenthesized pipeline. The arguments are all inside the pipeline; final is ignored.
|
||||
return s.evalPipeline(dot, n)
|
||||
case *parse.VariableNode:
|
||||
return s.evalVariableNode(dot, n, cmd.Args, final)
|
||||
}
|
||||
s.at(firstWord)
|
||||
s.notAFunction(cmd.Args, final)
|
||||
switch word := firstWord.(type) {
|
||||
case *parse.BoolNode:
|
||||
return reflect.ValueOf(word.True)
|
||||
case *parse.DotNode:
|
||||
return dot
|
||||
case *parse.NilNode:
|
||||
s.errorf("nil is not a command")
|
||||
case *parse.NumberNode:
|
||||
return s.idealConstant(word)
|
||||
case *parse.StringNode:
|
||||
return reflect.ValueOf(word.Text)
|
||||
}
|
||||
s.errorf("can't evaluate command %q", firstWord)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
// idealConstant is called to return the value of a number in a context where
|
||||
// we don't know the type. In that case, the syntax of the number tells us
|
||||
// its type, and we use Go rules to resolve. Note there is no such thing as
|
||||
// a uint ideal constant in this situation - the value must be of int type.
|
||||
func (s *state) idealConstant(constant *parse.NumberNode) reflect.Value {
|
||||
// These are ideal constants but we don't know the type
|
||||
// and we have no context. (If it was a method argument,
|
||||
// we'd know what we need.) The syntax guides us to some extent.
|
||||
s.at(constant)
|
||||
switch {
|
||||
case constant.IsComplex:
|
||||
return reflect.ValueOf(constant.Complex128) // incontrovertible.
|
||||
case constant.IsFloat && !isHexConstant(constant.Text) && strings.IndexAny(constant.Text, ".eE") >= 0:
|
||||
return reflect.ValueOf(constant.Float64)
|
||||
case constant.IsInt:
|
||||
n := int(constant.Int64)
|
||||
if int64(n) != constant.Int64 {
|
||||
s.errorf("%s overflows int", constant.Text)
|
||||
}
|
||||
return reflect.ValueOf(n)
|
||||
case constant.IsUint:
|
||||
s.errorf("%s overflows int", constant.Text)
|
||||
}
|
||||
return zero
|
||||
}
|
||||
|
||||
func isHexConstant(s string) bool {
|
||||
return len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')
|
||||
}
|
||||
|
||||
func (s *state) evalFieldNode(dot reflect.Value, field *parse.FieldNode, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
s.at(field)
|
||||
return s.evalFieldChain(dot, dot, field, field.Ident, args, final)
|
||||
}
|
||||
|
||||
func (s *state) evalChainNode(dot reflect.Value, chain *parse.ChainNode, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
s.at(chain)
|
||||
// (pipe).Field1.Field2 has pipe as .Node, fields as .Field. Eval the pipeline, then the fields.
|
||||
pipe := s.evalArg(dot, nil, chain.Node)
|
||||
if len(chain.Field) == 0 {
|
||||
s.errorf("internal error: no fields in evalChainNode")
|
||||
}
|
||||
return s.evalFieldChain(dot, pipe, chain, chain.Field, args, final)
|
||||
}
|
||||
|
||||
func (s *state) evalVariableNode(dot reflect.Value, variable *parse.VariableNode, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
// $x.Field has $x as the first ident, Field as the second. Eval the var, then the fields.
|
||||
s.at(variable)
|
||||
value := s.varValue(variable.Ident[0])
|
||||
if len(variable.Ident) == 1 {
|
||||
s.notAFunction(args, final)
|
||||
return value
|
||||
}
|
||||
return s.evalFieldChain(dot, value, variable, variable.Ident[1:], args, final)
|
||||
}
|
||||
|
||||
// evalFieldChain evaluates .X.Y.Z possibly followed by arguments.
|
||||
// dot is the environment in which to evaluate arguments, while
|
||||
// receiver is the value being walked along the chain.
|
||||
func (s *state) evalFieldChain(dot, receiver reflect.Value, node parse.Node, ident []string, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
n := len(ident)
|
||||
for i := 0; i < n-1; i++ {
|
||||
receiver = s.evalField(dot, ident[i], node, nil, zero, receiver)
|
||||
}
|
||||
// Now if it's a method, it gets the arguments.
|
||||
return s.evalField(dot, ident[n-1], node, args, final, receiver)
|
||||
}
|
||||
|
||||
func (s *state) evalFunction(dot reflect.Value, node *parse.IdentifierNode, cmd parse.Node, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
s.at(node)
|
||||
name := node.Ident
|
||||
function, ok := findFunction(name, s.tmpl)
|
||||
if !ok {
|
||||
s.errorf("%q is not a defined function", name)
|
||||
}
|
||||
return s.evalCall(dot, function, cmd, name, args, final)
|
||||
}
|
||||
|
||||
// evalField evaluates an expression like (.Field) or (.Field arg1 arg2).
|
||||
// The 'final' argument represents the return value from the preceding
|
||||
// value of the pipeline, if any.
|
||||
func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, args []parse.Node, final, receiver reflect.Value) reflect.Value {
|
||||
if !receiver.IsValid() {
|
||||
return zero
|
||||
}
|
||||
typ := receiver.Type()
|
||||
receiver, _ = indirect(receiver)
|
||||
// Unless it's an interface, need to get to a value of type *T to guarantee
|
||||
// we see all methods of T and *T.
|
||||
ptr := receiver
|
||||
if ptr.Kind() != reflect.Interface && ptr.CanAddr() {
|
||||
ptr = ptr.Addr()
|
||||
}
|
||||
if method := ptr.MethodByName(fieldName); method.IsValid() {
|
||||
return s.evalCall(dot, method, node, fieldName, args, final)
|
||||
}
|
||||
hasArgs := len(args) > 1 || final.IsValid()
|
||||
// It's not a method; must be a field of a struct or an element of a map. The receiver must not be nil.
|
||||
receiver, isNil := indirect(receiver)
|
||||
if isNil {
|
||||
s.errorf("nil pointer evaluating %s.%s", typ, fieldName)
|
||||
}
|
||||
switch receiver.Kind() {
|
||||
case reflect.Struct:
|
||||
tField, ok := receiver.Type().FieldByName(fieldName)
|
||||
if ok {
|
||||
field := receiver.FieldByIndex(tField.Index)
|
||||
if tField.PkgPath != "" { // field is unexported
|
||||
s.errorf("%s is an unexported field of struct type %s", fieldName, typ)
|
||||
}
|
||||
// If it's a function, we must call it.
|
||||
if hasArgs {
|
||||
s.errorf("%s has arguments but cannot be invoked as function", fieldName)
|
||||
}
|
||||
return field
|
||||
}
|
||||
s.errorf("%s is not a field of struct type %s", fieldName, typ)
|
||||
case reflect.Map:
|
||||
// If it's a map, attempt to use the field name as a key.
|
||||
nameVal := reflect.ValueOf(fieldName)
|
||||
if nameVal.Type().AssignableTo(receiver.Type().Key()) {
|
||||
if hasArgs {
|
||||
s.errorf("%s is not a method but has arguments", fieldName)
|
||||
}
|
||||
return receiver.MapIndex(nameVal)
|
||||
}
|
||||
}
|
||||
s.errorf("can't evaluate field %s in type %s", fieldName, typ)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
var (
|
||||
errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
|
||||
)
|
||||
|
||||
// evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so
|
||||
// it looks just like a function call. The arg list, if non-nil, includes (in the manner of the shell), arg[0]
|
||||
// as the function itself.
|
||||
func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
if args != nil {
|
||||
args = args[1:] // Zeroth arg is function name/node; not passed to function.
|
||||
}
|
||||
typ := fun.Type()
|
||||
numIn := len(args)
|
||||
if final.IsValid() {
|
||||
numIn++
|
||||
}
|
||||
numFixed := len(args)
|
||||
if typ.IsVariadic() {
|
||||
numFixed = typ.NumIn() - 1 // last arg is the variadic one.
|
||||
if numIn < numFixed {
|
||||
s.errorf("wrong number of args for %s: want at least %d got %d", name, typ.NumIn()-1, len(args))
|
||||
}
|
||||
} else if numIn < typ.NumIn()-1 || !typ.IsVariadic() && numIn != typ.NumIn() {
|
||||
s.errorf("wrong number of args for %s: want %d got %d", name, typ.NumIn(), len(args))
|
||||
}
|
||||
if !goodFunc(typ) {
|
||||
// TODO: This could still be a confusing error; maybe goodFunc should provide info.
|
||||
s.errorf("can't call method/function %q with %d results", name, typ.NumOut())
|
||||
}
|
||||
// Build the arg list.
|
||||
argv := make([]reflect.Value, numIn)
|
||||
// Args must be evaluated. Fixed args first.
|
||||
i := 0
|
||||
for ; i < numFixed && i < len(args); i++ {
|
||||
argv[i] = s.evalArg(dot, typ.In(i), args[i])
|
||||
}
|
||||
// Now the ... args.
|
||||
if typ.IsVariadic() {
|
||||
argType := typ.In(typ.NumIn() - 1).Elem() // Argument is a slice.
|
||||
for ; i < len(args); i++ {
|
||||
argv[i] = s.evalArg(dot, argType, args[i])
|
||||
}
|
||||
}
|
||||
// Add final value if necessary.
|
||||
if final.IsValid() {
|
||||
t := typ.In(typ.NumIn() - 1)
|
||||
if typ.IsVariadic() {
|
||||
t = t.Elem()
|
||||
}
|
||||
argv[i] = s.validateType(final, t)
|
||||
}
|
||||
result := fun.Call(argv)
|
||||
// If we have an error that is not nil, stop execution and return that error to the caller.
|
||||
if len(result) == 2 && !result[1].IsNil() {
|
||||
s.at(node)
|
||||
s.errorf("error calling %s: %s", name, result[1].Interface().(error))
|
||||
}
|
||||
return result[0]
|
||||
}
|
||||
|
||||
// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
|
||||
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
|
||||
}
|
||||
|
||||
// validateType guarantees that the value is valid and assignable to the type.
|
||||
func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Value {
|
||||
if !value.IsValid() {
|
||||
if typ == nil || canBeNil(typ) {
|
||||
// An untyped nil interface{}. Accept as a proper nil value.
|
||||
return reflect.Zero(typ)
|
||||
}
|
||||
s.errorf("invalid value; expected %s", typ)
|
||||
}
|
||||
if typ != nil && !value.Type().AssignableTo(typ) {
|
||||
if value.Kind() == reflect.Interface && !value.IsNil() {
|
||||
value = value.Elem()
|
||||
if value.Type().AssignableTo(typ) {
|
||||
return value
|
||||
}
|
||||
// fallthrough
|
||||
}
|
||||
// Does one dereference or indirection work? We could do more, as we
|
||||
// do with method receivers, but that gets messy and method receivers
|
||||
// are much more constrained, so it makes more sense there than here.
|
||||
// Besides, one is almost always all you need.
|
||||
switch {
|
||||
case value.Kind() == reflect.Ptr && value.Type().Elem().AssignableTo(typ):
|
||||
value = value.Elem()
|
||||
if !value.IsValid() {
|
||||
s.errorf("dereference of nil pointer of type %s", typ)
|
||||
}
|
||||
case reflect.PtrTo(value.Type()).AssignableTo(typ) && value.CanAddr():
|
||||
value = value.Addr()
|
||||
default:
|
||||
s.errorf("wrong type for value; expected %s; got %s", typ, value.Type())
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
switch arg := n.(type) {
|
||||
case *parse.DotNode:
|
||||
return s.validateType(dot, typ)
|
||||
case *parse.NilNode:
|
||||
if canBeNil(typ) {
|
||||
return reflect.Zero(typ)
|
||||
}
|
||||
s.errorf("cannot assign nil to %s", typ)
|
||||
case *parse.FieldNode:
|
||||
return s.validateType(s.evalFieldNode(dot, arg, []parse.Node{n}, zero), typ)
|
||||
case *parse.VariableNode:
|
||||
return s.validateType(s.evalVariableNode(dot, arg, nil, zero), typ)
|
||||
case *parse.PipeNode:
|
||||
return s.validateType(s.evalPipeline(dot, arg), typ)
|
||||
case *parse.IdentifierNode:
|
||||
return s.evalFunction(dot, arg, arg, nil, zero)
|
||||
case *parse.ChainNode:
|
||||
return s.validateType(s.evalChainNode(dot, arg, nil, zero), typ)
|
||||
}
|
||||
switch typ.Kind() {
|
||||
case reflect.Bool:
|
||||
return s.evalBool(typ, n)
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return s.evalComplex(typ, n)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return s.evalFloat(typ, n)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return s.evalInteger(typ, n)
|
||||
case reflect.Interface:
|
||||
if typ.NumMethod() == 0 {
|
||||
return s.evalEmptyInterface(dot, n)
|
||||
}
|
||||
case reflect.String:
|
||||
return s.evalString(typ, n)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return s.evalUnsignedInteger(typ, n)
|
||||
}
|
||||
s.errorf("can't handle %s for arg of type %s", n, typ)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalBool(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
if n, ok := n.(*parse.BoolNode); ok {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetBool(n.True)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected bool; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalString(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
if n, ok := n.(*parse.StringNode); ok {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetString(n.Text)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected string; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalInteger(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
if n, ok := n.(*parse.NumberNode); ok && n.IsInt {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetInt(n.Int64)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected integer; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalUnsignedInteger(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
if n, ok := n.(*parse.NumberNode); ok && n.IsUint {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetUint(n.Uint64)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected unsigned integer; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalFloat(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
if n, ok := n.(*parse.NumberNode); ok && n.IsFloat {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetFloat(n.Float64)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected float; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalComplex(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
if n, ok := n.(*parse.NumberNode); ok && n.IsComplex {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetComplex(n.Complex128)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected complex; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalEmptyInterface(dot reflect.Value, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
switch n := n.(type) {
|
||||
case *parse.BoolNode:
|
||||
return reflect.ValueOf(n.True)
|
||||
case *parse.DotNode:
|
||||
return dot
|
||||
case *parse.FieldNode:
|
||||
return s.evalFieldNode(dot, n, nil, zero)
|
||||
case *parse.IdentifierNode:
|
||||
return s.evalFunction(dot, n, n, nil, zero)
|
||||
case *parse.NilNode:
|
||||
// NilNode is handled in evalArg, the only place that calls here.
|
||||
s.errorf("evalEmptyInterface: nil (can't happen)")
|
||||
case *parse.NumberNode:
|
||||
return s.idealConstant(n)
|
||||
case *parse.StringNode:
|
||||
return reflect.ValueOf(n.Text)
|
||||
case *parse.VariableNode:
|
||||
return s.evalVariableNode(dot, n, nil, zero)
|
||||
case *parse.PipeNode:
|
||||
return s.evalPipeline(dot, n)
|
||||
}
|
||||
s.errorf("can't handle assignment of %s to empty interface argument", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
// 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.
|
||||
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
|
||||
}
|
||||
|
||||
// printValue writes the textual representation of the value to the output of
|
||||
// the template.
|
||||
func (s *state) printValue(n parse.Node, v reflect.Value) {
|
||||
s.at(n)
|
||||
iface, ok := printableValue(v)
|
||||
if !ok {
|
||||
s.errorf("can't print %s of type %s", n, v.Type())
|
||||
}
|
||||
fmt.Fprint(s.wr, iface)
|
||||
}
|
||||
|
||||
// printableValue returns the, possibly indirected, interface value inside v that
|
||||
// is best for a call to formatted printer.
|
||||
func printableValue(v reflect.Value) (interface{}, bool) {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v, _ = indirect(v) // fmt.Fprint handles nil.
|
||||
}
|
||||
if !v.IsValid() {
|
||||
return "<no value>", 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
|
||||
}
|
||||
|
||||
// Types to help sort the keys in a map for reproducible output.
|
||||
|
||||
type rvs []reflect.Value
|
||||
|
||||
func (x rvs) Len() int { return len(x) }
|
||||
func (x rvs) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
|
||||
type rvInts struct{ rvs }
|
||||
|
||||
func (x rvInts) Less(i, j int) bool { return x.rvs[i].Int() < x.rvs[j].Int() }
|
||||
|
||||
type rvUints struct{ rvs }
|
||||
|
||||
func (x rvUints) Less(i, j int) bool { return x.rvs[i].Uint() < x.rvs[j].Uint() }
|
||||
|
||||
type rvFloats struct{ rvs }
|
||||
|
||||
func (x rvFloats) Less(i, j int) bool { return x.rvs[i].Float() < x.rvs[j].Float() }
|
||||
|
||||
type rvStrings struct{ rvs }
|
||||
|
||||
func (x rvStrings) Less(i, j int) bool { return x.rvs[i].String() < x.rvs[j].String() }
|
||||
|
||||
// sortKeys sorts (if it can) the slice of reflect.Values, which is a slice of map keys.
|
||||
func sortKeys(v []reflect.Value) []reflect.Value {
|
||||
if len(v) <= 1 {
|
||||
return v
|
||||
}
|
||||
switch v[0].Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
sort.Sort(rvFloats{v})
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
sort.Sort(rvInts{v})
|
||||
case reflect.String:
|
||||
sort.Sort(rvStrings{v})
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
sort.Sort(rvUints{v})
|
||||
}
|
||||
return v
|
||||
}
|
||||
-1044
File diff suppressed because it is too large
Load Diff
-598
@@ -1,598 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package template
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// FuncMap is the type of the map defining the mapping from names to functions.
|
||||
// Each function must have either a single return value, or two return values of
|
||||
// which the second has type error. In that case, if the second (error)
|
||||
// return value evaluates to non-nil during execution, execution terminates and
|
||||
// Execute returns that error.
|
||||
type FuncMap map[string]interface{}
|
||||
|
||||
var builtins = FuncMap{
|
||||
"and": and,
|
||||
"call": call,
|
||||
"html": HTMLEscaper,
|
||||
"index": index,
|
||||
"js": JSEscaper,
|
||||
"len": length,
|
||||
"not": not,
|
||||
"or": or,
|
||||
"print": fmt.Sprint,
|
||||
"printf": fmt.Sprintf,
|
||||
"println": fmt.Sprintln,
|
||||
"urlquery": URLQueryEscaper,
|
||||
|
||||
// Comparisons
|
||||
"eq": eq, // ==
|
||||
"ge": ge, // >=
|
||||
"gt": gt, // >
|
||||
"le": le, // <=
|
||||
"lt": lt, // <
|
||||
"ne": ne, // !=
|
||||
}
|
||||
|
||||
var builtinFuncs = createValueFuncs(builtins)
|
||||
|
||||
// createValueFuncs turns a FuncMap into a map[string]reflect.Value
|
||||
func createValueFuncs(funcMap FuncMap) map[string]reflect.Value {
|
||||
m := make(map[string]reflect.Value)
|
||||
addValueFuncs(m, funcMap)
|
||||
return m
|
||||
}
|
||||
|
||||
// addValueFuncs adds to values the functions in funcs, converting them to reflect.Values.
|
||||
func addValueFuncs(out map[string]reflect.Value, in FuncMap) {
|
||||
for name, fn := range in {
|
||||
v := reflect.ValueOf(fn)
|
||||
if v.Kind() != reflect.Func {
|
||||
panic("value for " + name + " not a function")
|
||||
}
|
||||
if !goodFunc(v.Type()) {
|
||||
panic(fmt.Errorf("can't install method/function %q with %d results", name, v.Type().NumOut()))
|
||||
}
|
||||
out[name] = v
|
||||
}
|
||||
}
|
||||
|
||||
// addFuncs adds to values the functions in funcs. It does no checking of the input -
|
||||
// call addValueFuncs first.
|
||||
func addFuncs(out, in FuncMap) {
|
||||
for name, fn := range in {
|
||||
out[name] = fn
|
||||
}
|
||||
}
|
||||
|
||||
// goodFunc checks that the function or method has the right result signature.
|
||||
func goodFunc(typ reflect.Type) bool {
|
||||
// We allow functions with 1 result or 2 results where the second is an error.
|
||||
switch {
|
||||
case typ.NumOut() == 1:
|
||||
return true
|
||||
case typ.NumOut() == 2 && typ.Out(1) == errorType:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// findFunction looks for a function in the template, and global map.
|
||||
func findFunction(name string, tmpl *Template) (reflect.Value, bool) {
|
||||
if tmpl != nil && tmpl.common != nil {
|
||||
if fn := tmpl.execFuncs[name]; fn.IsValid() {
|
||||
return fn, true
|
||||
}
|
||||
}
|
||||
if fn := builtinFuncs[name]; fn.IsValid() {
|
||||
return fn, true
|
||||
}
|
||||
return reflect.Value{}, false
|
||||
}
|
||||
|
||||
// Indexing.
|
||||
|
||||
// index returns the result of indexing its first argument by the following
|
||||
// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
|
||||
// indexed item must be a map, slice, or array.
|
||||
func index(item interface{}, indices ...interface{}) (interface{}, error) {
|
||||
v := reflect.ValueOf(item)
|
||||
for _, i := range indices {
|
||||
index := reflect.ValueOf(i)
|
||||
var isNil bool
|
||||
if v, isNil = indirect(v); isNil {
|
||||
return nil, fmt.Errorf("index of nil pointer")
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.String:
|
||||
var x int64
|
||||
switch index.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
x = index.Int()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
x = int64(index.Uint())
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type())
|
||||
}
|
||||
if x < 0 || x >= int64(v.Len()) {
|
||||
return nil, fmt.Errorf("index out of range: %d", x)
|
||||
}
|
||||
v = v.Index(int(x))
|
||||
case reflect.Map:
|
||||
if !index.IsValid() {
|
||||
index = reflect.Zero(v.Type().Key())
|
||||
}
|
||||
if !index.Type().AssignableTo(v.Type().Key()) {
|
||||
return nil, fmt.Errorf("%s is not index type for %s", index.Type(), v.Type())
|
||||
}
|
||||
if x := v.MapIndex(index); x.IsValid() {
|
||||
v = x
|
||||
} else {
|
||||
v = reflect.Zero(v.Type().Elem())
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("can't index item of type %s", v.Type())
|
||||
}
|
||||
}
|
||||
return v.Interface(), nil
|
||||
}
|
||||
|
||||
// Length
|
||||
|
||||
// length returns the length of the item, with an error if it has no defined length.
|
||||
func length(item interface{}) (int, error) {
|
||||
v, isNil := indirect(reflect.ValueOf(item))
|
||||
if isNil {
|
||||
return 0, fmt.Errorf("len of nil pointer")
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len(), nil
|
||||
}
|
||||
return 0, fmt.Errorf("len of type %s", v.Type())
|
||||
}
|
||||
|
||||
// Function invocation
|
||||
|
||||
// call returns the result of evaluating the first argument as a function.
|
||||
// The function must return 1 result, or 2 results, the second of which is an error.
|
||||
func call(fn interface{}, args ...interface{}) (interface{}, error) {
|
||||
v := reflect.ValueOf(fn)
|
||||
typ := v.Type()
|
||||
if typ.Kind() != reflect.Func {
|
||||
return nil, fmt.Errorf("non-function of type %s", typ)
|
||||
}
|
||||
if !goodFunc(typ) {
|
||||
return nil, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut())
|
||||
}
|
||||
numIn := typ.NumIn()
|
||||
var dddType reflect.Type
|
||||
if typ.IsVariadic() {
|
||||
if len(args) < numIn-1 {
|
||||
return nil, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1)
|
||||
}
|
||||
dddType = typ.In(numIn - 1).Elem()
|
||||
} else {
|
||||
if len(args) != numIn {
|
||||
return nil, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn)
|
||||
}
|
||||
}
|
||||
argv := make([]reflect.Value, len(args))
|
||||
for i, arg := range args {
|
||||
value := reflect.ValueOf(arg)
|
||||
// Compute the expected type. Clumsy because of variadics.
|
||||
var argType reflect.Type
|
||||
if !typ.IsVariadic() || i < numIn-1 {
|
||||
argType = typ.In(i)
|
||||
} else {
|
||||
argType = dddType
|
||||
}
|
||||
if !value.IsValid() && canBeNil(argType) {
|
||||
value = reflect.Zero(argType)
|
||||
}
|
||||
if !value.Type().AssignableTo(argType) {
|
||||
return nil, fmt.Errorf("arg %d has type %s; should be %s", i, value.Type(), argType)
|
||||
}
|
||||
argv[i] = value
|
||||
}
|
||||
result := v.Call(argv)
|
||||
if len(result) == 2 && !result[1].IsNil() {
|
||||
return result[0].Interface(), result[1].Interface().(error)
|
||||
}
|
||||
return result[0].Interface(), nil
|
||||
}
|
||||
|
||||
// Boolean logic.
|
||||
|
||||
func truth(a interface{}) bool {
|
||||
t, _ := isTrue(reflect.ValueOf(a))
|
||||
return t
|
||||
}
|
||||
|
||||
// and computes the Boolean AND of its arguments, returning
|
||||
// the first false argument it encounters, or the last argument.
|
||||
func and(arg0 interface{}, args ...interface{}) interface{} {
|
||||
if !truth(arg0) {
|
||||
return arg0
|
||||
}
|
||||
for i := range args {
|
||||
arg0 = args[i]
|
||||
if !truth(arg0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return arg0
|
||||
}
|
||||
|
||||
// or computes the Boolean OR of its arguments, returning
|
||||
// the first true argument it encounters, or the last argument.
|
||||
func or(arg0 interface{}, args ...interface{}) interface{} {
|
||||
if truth(arg0) {
|
||||
return arg0
|
||||
}
|
||||
for i := range args {
|
||||
arg0 = args[i]
|
||||
if truth(arg0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return arg0
|
||||
}
|
||||
|
||||
// not returns the Boolean negation of its argument.
|
||||
func not(arg interface{}) (truth bool) {
|
||||
truth, _ = isTrue(reflect.ValueOf(arg))
|
||||
return !truth
|
||||
}
|
||||
|
||||
// Comparison.
|
||||
|
||||
// TODO: Perhaps allow comparison between signed and unsigned integers.
|
||||
|
||||
var (
|
||||
errBadComparisonType = errors.New("invalid type for comparison")
|
||||
errBadComparison = errors.New("incompatible types for comparison")
|
||||
errNoComparison = errors.New("missing argument for comparison")
|
||||
)
|
||||
|
||||
type kind int
|
||||
|
||||
const (
|
||||
invalidKind kind = iota
|
||||
boolKind
|
||||
complexKind
|
||||
intKind
|
||||
floatKind
|
||||
integerKind
|
||||
stringKind
|
||||
uintKind
|
||||
)
|
||||
|
||||
func basicKind(v reflect.Value) (kind, error) {
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
return boolKind, nil
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return intKind, nil
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return uintKind, nil
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return floatKind, nil
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return complexKind, nil
|
||||
case reflect.String:
|
||||
return stringKind, nil
|
||||
}
|
||||
return invalidKind, errBadComparisonType
|
||||
}
|
||||
|
||||
// eq evaluates the comparison a == b || a == c || ...
|
||||
func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) {
|
||||
v1 := reflect.ValueOf(arg1)
|
||||
k1, err := basicKind(v1)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(arg2) == 0 {
|
||||
return false, errNoComparison
|
||||
}
|
||||
for _, arg := range arg2 {
|
||||
v2 := reflect.ValueOf(arg)
|
||||
k2, err := basicKind(v2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
truth := false
|
||||
if k1 != k2 {
|
||||
// Special case: Can compare integer values regardless of type's sign.
|
||||
switch {
|
||||
case k1 == intKind && k2 == uintKind:
|
||||
truth = v1.Int() >= 0 && uint64(v1.Int()) == v2.Uint()
|
||||
case k1 == uintKind && k2 == intKind:
|
||||
truth = v2.Int() >= 0 && v1.Uint() == uint64(v2.Int())
|
||||
default:
|
||||
return false, errBadComparison
|
||||
}
|
||||
} else {
|
||||
switch k1 {
|
||||
case boolKind:
|
||||
truth = v1.Bool() == v2.Bool()
|
||||
case complexKind:
|
||||
truth = v1.Complex() == v2.Complex()
|
||||
case floatKind:
|
||||
truth = v1.Float() == v2.Float()
|
||||
case intKind:
|
||||
truth = v1.Int() == v2.Int()
|
||||
case stringKind:
|
||||
truth = v1.String() == v2.String()
|
||||
case uintKind:
|
||||
truth = v1.Uint() == v2.Uint()
|
||||
default:
|
||||
panic("invalid kind")
|
||||
}
|
||||
}
|
||||
if truth {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// ne evaluates the comparison a != b.
|
||||
func ne(arg1, arg2 interface{}) (bool, error) {
|
||||
// != is the inverse of ==.
|
||||
equal, err := eq(arg1, arg2)
|
||||
return !equal, err
|
||||
}
|
||||
|
||||
// lt evaluates the comparison a < b.
|
||||
func lt(arg1, arg2 interface{}) (bool, error) {
|
||||
v1 := reflect.ValueOf(arg1)
|
||||
k1, err := basicKind(v1)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
v2 := reflect.ValueOf(arg2)
|
||||
k2, err := basicKind(v2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
truth := false
|
||||
if k1 != k2 {
|
||||
// Special case: Can compare integer values regardless of type's sign.
|
||||
switch {
|
||||
case k1 == intKind && k2 == uintKind:
|
||||
truth = v1.Int() < 0 || uint64(v1.Int()) < v2.Uint()
|
||||
case k1 == uintKind && k2 == intKind:
|
||||
truth = v2.Int() >= 0 && v1.Uint() < uint64(v2.Int())
|
||||
default:
|
||||
return false, errBadComparison
|
||||
}
|
||||
} else {
|
||||
switch k1 {
|
||||
case boolKind, complexKind:
|
||||
return false, errBadComparisonType
|
||||
case floatKind:
|
||||
truth = v1.Float() < v2.Float()
|
||||
case intKind:
|
||||
truth = v1.Int() < v2.Int()
|
||||
case stringKind:
|
||||
truth = v1.String() < v2.String()
|
||||
case uintKind:
|
||||
truth = v1.Uint() < v2.Uint()
|
||||
default:
|
||||
panic("invalid kind")
|
||||
}
|
||||
}
|
||||
return truth, nil
|
||||
}
|
||||
|
||||
// le evaluates the comparison <= b.
|
||||
func le(arg1, arg2 interface{}) (bool, error) {
|
||||
// <= is < or ==.
|
||||
lessThan, err := lt(arg1, arg2)
|
||||
if lessThan || err != nil {
|
||||
return lessThan, err
|
||||
}
|
||||
return eq(arg1, arg2)
|
||||
}
|
||||
|
||||
// gt evaluates the comparison a > b.
|
||||
func gt(arg1, arg2 interface{}) (bool, error) {
|
||||
// > is the inverse of <=.
|
||||
lessOrEqual, err := le(arg1, arg2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return !lessOrEqual, nil
|
||||
}
|
||||
|
||||
// ge evaluates the comparison a >= b.
|
||||
func ge(arg1, arg2 interface{}) (bool, error) {
|
||||
// >= is the inverse of <.
|
||||
lessThan, err := lt(arg1, arg2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return !lessThan, nil
|
||||
}
|
||||
|
||||
// HTML escaping.
|
||||
|
||||
var (
|
||||
htmlQuot = []byte(""") // shorter than """
|
||||
htmlApos = []byte("'") // shorter than "'" and apos was not in HTML until HTML5
|
||||
htmlAmp = []byte("&")
|
||||
htmlLt = []byte("<")
|
||||
htmlGt = []byte(">")
|
||||
)
|
||||
|
||||
// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.
|
||||
func HTMLEscape(w io.Writer, b []byte) {
|
||||
last := 0
|
||||
for i, c := range b {
|
||||
var html []byte
|
||||
switch c {
|
||||
case '"':
|
||||
html = htmlQuot
|
||||
case '\'':
|
||||
html = htmlApos
|
||||
case '&':
|
||||
html = htmlAmp
|
||||
case '<':
|
||||
html = htmlLt
|
||||
case '>':
|
||||
html = htmlGt
|
||||
default:
|
||||
continue
|
||||
}
|
||||
w.Write(b[last:i])
|
||||
w.Write(html)
|
||||
last = i + 1
|
||||
}
|
||||
w.Write(b[last:])
|
||||
}
|
||||
|
||||
// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s.
|
||||
func HTMLEscapeString(s string) string {
|
||||
// Avoid allocation if we can.
|
||||
if strings.IndexAny(s, `'"&<>`) < 0 {
|
||||
return s
|
||||
}
|
||||
var b bytes.Buffer
|
||||
HTMLEscape(&b, []byte(s))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// HTMLEscaper returns the escaped HTML equivalent of the textual
|
||||
// representation of its arguments.
|
||||
func HTMLEscaper(args ...interface{}) string {
|
||||
return HTMLEscapeString(evalArgs(args))
|
||||
}
|
||||
|
||||
// JavaScript escaping.
|
||||
|
||||
var (
|
||||
jsLowUni = []byte(`\u00`)
|
||||
hex = []byte("0123456789ABCDEF")
|
||||
|
||||
jsBackslash = []byte(`\\`)
|
||||
jsApos = []byte(`\'`)
|
||||
jsQuot = []byte(`\"`)
|
||||
jsLt = []byte(`\x3C`)
|
||||
jsGt = []byte(`\x3E`)
|
||||
)
|
||||
|
||||
// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.
|
||||
func JSEscape(w io.Writer, b []byte) {
|
||||
last := 0
|
||||
for i := 0; i < len(b); i++ {
|
||||
c := b[i]
|
||||
|
||||
if !jsIsSpecial(rune(c)) {
|
||||
// fast path: nothing to do
|
||||
continue
|
||||
}
|
||||
w.Write(b[last:i])
|
||||
|
||||
if c < utf8.RuneSelf {
|
||||
// Quotes, slashes and angle brackets get quoted.
|
||||
// Control characters get written as \u00XX.
|
||||
switch c {
|
||||
case '\\':
|
||||
w.Write(jsBackslash)
|
||||
case '\'':
|
||||
w.Write(jsApos)
|
||||
case '"':
|
||||
w.Write(jsQuot)
|
||||
case '<':
|
||||
w.Write(jsLt)
|
||||
case '>':
|
||||
w.Write(jsGt)
|
||||
default:
|
||||
w.Write(jsLowUni)
|
||||
t, b := c>>4, c&0x0f
|
||||
w.Write(hex[t : t+1])
|
||||
w.Write(hex[b : b+1])
|
||||
}
|
||||
} else {
|
||||
// Unicode rune.
|
||||
r, size := utf8.DecodeRune(b[i:])
|
||||
if unicode.IsPrint(r) {
|
||||
w.Write(b[i : i+size])
|
||||
} else {
|
||||
fmt.Fprintf(w, "\\u%04X", r)
|
||||
}
|
||||
i += size - 1
|
||||
}
|
||||
last = i + 1
|
||||
}
|
||||
w.Write(b[last:])
|
||||
}
|
||||
|
||||
// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s.
|
||||
func JSEscapeString(s string) string {
|
||||
// Avoid allocation if we can.
|
||||
if strings.IndexFunc(s, jsIsSpecial) < 0 {
|
||||
return s
|
||||
}
|
||||
var b bytes.Buffer
|
||||
JSEscape(&b, []byte(s))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func jsIsSpecial(r rune) bool {
|
||||
switch r {
|
||||
case '\\', '\'', '"', '<', '>':
|
||||
return true
|
||||
}
|
||||
return r < ' ' || utf8.RuneSelf <= r
|
||||
}
|
||||
|
||||
// JSEscaper returns the escaped JavaScript equivalent of the textual
|
||||
// representation of its arguments.
|
||||
func JSEscaper(args ...interface{}) string {
|
||||
return JSEscapeString(evalArgs(args))
|
||||
}
|
||||
|
||||
// URLQueryEscaper returns the escaped value of the textual representation of
|
||||
// its arguments in a form suitable for embedding in a URL query.
|
||||
func URLQueryEscaper(args ...interface{}) string {
|
||||
return url.QueryEscape(evalArgs(args))
|
||||
}
|
||||
|
||||
// evalArgs formats the list of arguments into a string. It is therefore equivalent to
|
||||
// fmt.Sprint(args...)
|
||||
// except that each argument is indirected (if a pointer), as required,
|
||||
// using the same rules as the default string evaluation during template
|
||||
// execution.
|
||||
func evalArgs(args []interface{}) string {
|
||||
ok := false
|
||||
var s string
|
||||
// Fast path for simple common case.
|
||||
if len(args) == 1 {
|
||||
s, ok = args[0].(string)
|
||||
}
|
||||
if !ok {
|
||||
for i, arg := range args {
|
||||
a, ok := printableValue(reflect.ValueOf(arg))
|
||||
if ok {
|
||||
args[i] = a
|
||||
} // else left fmt do its thing
|
||||
}
|
||||
s = fmt.Sprint(args...)
|
||||
}
|
||||
return s
|
||||
}
|
||||
-108
@@ -1,108 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Helper functions to make constructing templates easier.
|
||||
|
||||
package template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Functions and methods to parse templates.
|
||||
|
||||
// Must is a helper that wraps a call to a function returning (*Template, error)
|
||||
// and panics if the error is non-nil. It is intended for use in variable
|
||||
// initializations such as
|
||||
// var t = template.Must(template.New("name").Parse("text"))
|
||||
func Must(t *Template, err error) *Template {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// ParseFiles creates a new Template and parses the template definitions from
|
||||
// the named files. The returned template's name will have the (base) name and
|
||||
// (parsed) contents of the first file. There must be at least one file.
|
||||
// If an error occurs, parsing stops and the returned *Template is nil.
|
||||
func ParseFiles(filenames ...string) (*Template, error) {
|
||||
return parseFiles(nil, filenames...)
|
||||
}
|
||||
|
||||
// ParseFiles parses the named files and associates the resulting templates with
|
||||
// t. If an error occurs, parsing stops and the returned template is nil;
|
||||
// otherwise it is t. There must be at least one file.
|
||||
func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
|
||||
return parseFiles(t, filenames...)
|
||||
}
|
||||
|
||||
// parseFiles is the helper for the method and function. If the argument
|
||||
// template is nil, it is created from the first file.
|
||||
func parseFiles(t *Template, filenames ...string) (*Template, error) {
|
||||
if len(filenames) == 0 {
|
||||
// Not really a problem, but be consistent.
|
||||
return nil, fmt.Errorf("template: no files named in call to ParseFiles")
|
||||
}
|
||||
for _, filename := range filenames {
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := string(b)
|
||||
name := filepath.Base(filename)
|
||||
// First template becomes return value if not already defined,
|
||||
// and we use that one for subsequent New calls to associate
|
||||
// all the templates together. Also, if this file has the same name
|
||||
// as t, this file becomes the contents of t, so
|
||||
// t, err := New(name).Funcs(xxx).ParseFiles(name)
|
||||
// works. Otherwise we create a new template associated with t.
|
||||
var tmpl *Template
|
||||
if t == nil {
|
||||
t = New(name)
|
||||
}
|
||||
if name == t.Name() {
|
||||
tmpl = t
|
||||
} else {
|
||||
tmpl = t.New(name)
|
||||
}
|
||||
_, err = tmpl.Parse(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// ParseGlob creates a new Template and parses the template definitions from the
|
||||
// files identified by the pattern, which must match at least one file. The
|
||||
// returned template will have the (base) name and (parsed) contents of the
|
||||
// first file matched by the pattern. ParseGlob is equivalent to calling
|
||||
// ParseFiles with the list of files matched by the pattern.
|
||||
func ParseGlob(pattern string) (*Template, error) {
|
||||
return parseGlob(nil, pattern)
|
||||
}
|
||||
|
||||
// ParseGlob parses the template definitions in the files identified by the
|
||||
// pattern and associates the resulting templates with t. The pattern is
|
||||
// processed by filepath.Glob and must match at least one file. ParseGlob is
|
||||
// equivalent to calling t.ParseFiles with the list of files matched by the
|
||||
// pattern.
|
||||
func (t *Template) ParseGlob(pattern string) (*Template, error) {
|
||||
return parseGlob(t, pattern)
|
||||
}
|
||||
|
||||
// parseGlob is the implementation of the function and method ParseGlob.
|
||||
func parseGlob(t *Template, pattern string) (*Template, error) {
|
||||
filenames, err := filepath.Glob(pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(filenames) == 0 {
|
||||
return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
|
||||
}
|
||||
return parseFiles(t, filenames...)
|
||||
}
|
||||
-293
@@ -1,293 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package template
|
||||
|
||||
// Tests for mulitple-template parsing and execution.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/alecthomas/template/parse"
|
||||
)
|
||||
|
||||
const (
|
||||
noError = true
|
||||
hasError = false
|
||||
)
|
||||
|
||||
type multiParseTest struct {
|
||||
name string
|
||||
input string
|
||||
ok bool
|
||||
names []string
|
||||
results []string
|
||||
}
|
||||
|
||||
var multiParseTests = []multiParseTest{
|
||||
{"empty", "", noError,
|
||||
nil,
|
||||
nil},
|
||||
{"one", `{{define "foo"}} FOO {{end}}`, noError,
|
||||
[]string{"foo"},
|
||||
[]string{" FOO "}},
|
||||
{"two", `{{define "foo"}} FOO {{end}}{{define "bar"}} BAR {{end}}`, noError,
|
||||
[]string{"foo", "bar"},
|
||||
[]string{" FOO ", " BAR "}},
|
||||
// errors
|
||||
{"missing end", `{{define "foo"}} FOO `, hasError,
|
||||
nil,
|
||||
nil},
|
||||
{"malformed name", `{{define "foo}} FOO `, hasError,
|
||||
nil,
|
||||
nil},
|
||||
}
|
||||
|
||||
func TestMultiParse(t *testing.T) {
|
||||
for _, test := range multiParseTests {
|
||||
template, err := New("root").Parse(test.input)
|
||||
switch {
|
||||
case err == nil && !test.ok:
|
||||
t.Errorf("%q: expected error; got none", test.name)
|
||||
continue
|
||||
case err != nil && test.ok:
|
||||
t.Errorf("%q: unexpected error: %v", test.name, err)
|
||||
continue
|
||||
case err != nil && !test.ok:
|
||||
// expected error, got one
|
||||
if *debug {
|
||||
fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if template == nil {
|
||||
continue
|
||||
}
|
||||
if len(template.tmpl) != len(test.names)+1 { // +1 for root
|
||||
t.Errorf("%s: wrong number of templates; wanted %d got %d", test.name, len(test.names), len(template.tmpl))
|
||||
continue
|
||||
}
|
||||
for i, name := range test.names {
|
||||
tmpl, ok := template.tmpl[name]
|
||||
if !ok {
|
||||
t.Errorf("%s: can't find template %q", test.name, name)
|
||||
continue
|
||||
}
|
||||
result := tmpl.Root.String()
|
||||
if result != test.results[i] {
|
||||
t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.results[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var multiExecTests = []execTest{
|
||||
{"empty", "", "", nil, true},
|
||||
{"text", "some text", "some text", nil, true},
|
||||
{"invoke x", `{{template "x" .SI}}`, "TEXT", tVal, true},
|
||||
{"invoke x no args", `{{template "x"}}`, "TEXT", tVal, true},
|
||||
{"invoke dot int", `{{template "dot" .I}}`, "17", tVal, true},
|
||||
{"invoke dot []int", `{{template "dot" .SI}}`, "[3 4 5]", tVal, true},
|
||||
{"invoke dotV", `{{template "dotV" .U}}`, "v", tVal, true},
|
||||
{"invoke nested int", `{{template "nested" .I}}`, "17", tVal, true},
|
||||
{"variable declared by template", `{{template "nested" $x:=.SI}},{{index $x 1}}`, "[3 4 5],4", tVal, true},
|
||||
|
||||
// User-defined function: test argument evaluator.
|
||||
{"testFunc literal", `{{oneArg "joe"}}`, "oneArg=joe", tVal, true},
|
||||
{"testFunc .", `{{oneArg .}}`, "oneArg=joe", "joe", true},
|
||||
}
|
||||
|
||||
// These strings are also in testdata/*.
|
||||
const multiText1 = `
|
||||
{{define "x"}}TEXT{{end}}
|
||||
{{define "dotV"}}{{.V}}{{end}}
|
||||
`
|
||||
|
||||
const multiText2 = `
|
||||
{{define "dot"}}{{.}}{{end}}
|
||||
{{define "nested"}}{{template "dot" .}}{{end}}
|
||||
`
|
||||
|
||||
func TestMultiExecute(t *testing.T) {
|
||||
// Declare a couple of templates first.
|
||||
template, err := New("root").Parse(multiText1)
|
||||
if err != nil {
|
||||
t.Fatalf("parse error for 1: %s", err)
|
||||
}
|
||||
_, err = template.Parse(multiText2)
|
||||
if err != nil {
|
||||
t.Fatalf("parse error for 2: %s", err)
|
||||
}
|
||||
testExecute(multiExecTests, template, t)
|
||||
}
|
||||
|
||||
func TestParseFiles(t *testing.T) {
|
||||
_, err := ParseFiles("DOES NOT EXIST")
|
||||
if err == nil {
|
||||
t.Error("expected error for non-existent file; got none")
|
||||
}
|
||||
template := New("root")
|
||||
_, err = template.ParseFiles("testdata/file1.tmpl", "testdata/file2.tmpl")
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing files: %v", err)
|
||||
}
|
||||
testExecute(multiExecTests, template, t)
|
||||
}
|
||||
|
||||
func TestParseGlob(t *testing.T) {
|
||||
_, err := ParseGlob("DOES NOT EXIST")
|
||||
if err == nil {
|
||||
t.Error("expected error for non-existent file; got none")
|
||||
}
|
||||
_, err = New("error").ParseGlob("[x")
|
||||
if err == nil {
|
||||
t.Error("expected error for bad pattern; got none")
|
||||
}
|
||||
template := New("root")
|
||||
_, err = template.ParseGlob("testdata/file*.tmpl")
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing files: %v", err)
|
||||
}
|
||||
testExecute(multiExecTests, template, t)
|
||||
}
|
||||
|
||||
// In these tests, actual content (not just template definitions) comes from the parsed files.
|
||||
|
||||
var templateFileExecTests = []execTest{
|
||||
{"test", `{{template "tmpl1.tmpl"}}{{template "tmpl2.tmpl"}}`, "template1\n\ny\ntemplate2\n\nx\n", 0, true},
|
||||
}
|
||||
|
||||
func TestParseFilesWithData(t *testing.T) {
|
||||
template, err := New("root").ParseFiles("testdata/tmpl1.tmpl", "testdata/tmpl2.tmpl")
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing files: %v", err)
|
||||
}
|
||||
testExecute(templateFileExecTests, template, t)
|
||||
}
|
||||
|
||||
func TestParseGlobWithData(t *testing.T) {
|
||||
template, err := New("root").ParseGlob("testdata/tmpl*.tmpl")
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing files: %v", err)
|
||||
}
|
||||
testExecute(templateFileExecTests, template, t)
|
||||
}
|
||||
|
||||
const (
|
||||
cloneText1 = `{{define "a"}}{{template "b"}}{{template "c"}}{{end}}`
|
||||
cloneText2 = `{{define "b"}}b{{end}}`
|
||||
cloneText3 = `{{define "c"}}root{{end}}`
|
||||
cloneText4 = `{{define "c"}}clone{{end}}`
|
||||
)
|
||||
|
||||
func TestClone(t *testing.T) {
|
||||
// Create some templates and clone the root.
|
||||
root, err := New("root").Parse(cloneText1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = root.Parse(cloneText2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
clone := Must(root.Clone())
|
||||
// Add variants to both.
|
||||
_, err = root.Parse(cloneText3)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = clone.Parse(cloneText4)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Verify that the clone is self-consistent.
|
||||
for k, v := range clone.tmpl {
|
||||
if k == clone.name && v.tmpl[k] != clone {
|
||||
t.Error("clone does not contain root")
|
||||
}
|
||||
if v != v.tmpl[v.name] {
|
||||
t.Errorf("clone does not contain self for %q", k)
|
||||
}
|
||||
}
|
||||
// Execute root.
|
||||
var b bytes.Buffer
|
||||
err = root.ExecuteTemplate(&b, "a", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if b.String() != "broot" {
|
||||
t.Errorf("expected %q got %q", "broot", b.String())
|
||||
}
|
||||
// Execute copy.
|
||||
b.Reset()
|
||||
err = clone.ExecuteTemplate(&b, "a", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if b.String() != "bclone" {
|
||||
t.Errorf("expected %q got %q", "bclone", b.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddParseTree(t *testing.T) {
|
||||
// Create some templates.
|
||||
root, err := New("root").Parse(cloneText1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = root.Parse(cloneText2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Add a new parse tree.
|
||||
tree, err := parse.Parse("cloneText3", cloneText3, "", "", nil, builtins)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
added, err := root.AddParseTree("c", tree["c"])
|
||||
// Execute.
|
||||
var b bytes.Buffer
|
||||
err = added.ExecuteTemplate(&b, "a", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if b.String() != "broot" {
|
||||
t.Errorf("expected %q got %q", "broot", b.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Issue 7032
|
||||
func TestAddParseTreeToUnparsedTemplate(t *testing.T) {
|
||||
master := "{{define \"master\"}}{{end}}"
|
||||
tmpl := New("master")
|
||||
tree, err := parse.Parse("master", master, "", "", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected parse err: %v", err)
|
||||
}
|
||||
masterTree := tree["master"]
|
||||
tmpl.AddParseTree("master", masterTree) // used to panic
|
||||
}
|
||||
|
||||
func TestRedefinition(t *testing.T) {
|
||||
var tmpl *Template
|
||||
var err error
|
||||
if tmpl, err = New("tmpl1").Parse(`{{define "test"}}foo{{end}}`); err != nil {
|
||||
t.Fatalf("parse 1: %v", err)
|
||||
}
|
||||
if _, err = tmpl.Parse(`{{define "test"}}bar{{end}}`); err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "redefinition") {
|
||||
t.Fatalf("expected redefinition error; got %v", err)
|
||||
}
|
||||
if _, err = tmpl.New("tmpl2").Parse(`{{define "test"}}bar{{end}}`); err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "redefinition") {
|
||||
t.Fatalf("expected redefinition error; got %v", err)
|
||||
}
|
||||
}
|
||||
-556
@@ -1,556 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// item represents a token or text string returned from the scanner.
|
||||
type item struct {
|
||||
typ itemType // The type of this item.
|
||||
pos Pos // The starting position, in bytes, of this item in the input string.
|
||||
val string // The value of this item.
|
||||
}
|
||||
|
||||
func (i item) String() string {
|
||||
switch {
|
||||
case i.typ == itemEOF:
|
||||
return "EOF"
|
||||
case i.typ == itemError:
|
||||
return i.val
|
||||
case i.typ > itemKeyword:
|
||||
return fmt.Sprintf("<%s>", i.val)
|
||||
case len(i.val) > 10:
|
||||
return fmt.Sprintf("%.10q...", i.val)
|
||||
}
|
||||
return fmt.Sprintf("%q", i.val)
|
||||
}
|
||||
|
||||
// itemType identifies the type of lex items.
|
||||
type itemType int
|
||||
|
||||
const (
|
||||
itemError itemType = iota // error occurred; value is text of error
|
||||
itemBool // boolean constant
|
||||
itemChar // printable ASCII character; grab bag for comma etc.
|
||||
itemCharConstant // character constant
|
||||
itemComplex // complex constant (1+2i); imaginary is just a number
|
||||
itemColonEquals // colon-equals (':=') introducing a declaration
|
||||
itemEOF
|
||||
itemField // alphanumeric identifier starting with '.'
|
||||
itemIdentifier // alphanumeric identifier not starting with '.'
|
||||
itemLeftDelim // left action delimiter
|
||||
itemLeftParen // '(' inside action
|
||||
itemNumber // simple number, including imaginary
|
||||
itemPipe // pipe symbol
|
||||
itemRawString // raw quoted string (includes quotes)
|
||||
itemRightDelim // right action delimiter
|
||||
itemElideNewline // elide newline after right delim
|
||||
itemRightParen // ')' inside action
|
||||
itemSpace // run of spaces separating arguments
|
||||
itemString // quoted string (includes quotes)
|
||||
itemText // plain text
|
||||
itemVariable // variable starting with '$', such as '$' or '$1' or '$hello'
|
||||
// Keywords appear after all the rest.
|
||||
itemKeyword // used only to delimit the keywords
|
||||
itemDot // the cursor, spelled '.'
|
||||
itemDefine // define keyword
|
||||
itemElse // else keyword
|
||||
itemEnd // end keyword
|
||||
itemIf // if keyword
|
||||
itemNil // the untyped nil constant, easiest to treat as a keyword
|
||||
itemRange // range keyword
|
||||
itemTemplate // template keyword
|
||||
itemWith // with keyword
|
||||
)
|
||||
|
||||
var key = map[string]itemType{
|
||||
".": itemDot,
|
||||
"define": itemDefine,
|
||||
"else": itemElse,
|
||||
"end": itemEnd,
|
||||
"if": itemIf,
|
||||
"range": itemRange,
|
||||
"nil": itemNil,
|
||||
"template": itemTemplate,
|
||||
"with": itemWith,
|
||||
}
|
||||
|
||||
const eof = -1
|
||||
|
||||
// stateFn represents the state of the scanner as a function that returns the next state.
|
||||
type stateFn func(*lexer) stateFn
|
||||
|
||||
// lexer holds the state of the scanner.
|
||||
type lexer struct {
|
||||
name string // the name of the input; used only for error reports
|
||||
input string // the string being scanned
|
||||
leftDelim string // start of action
|
||||
rightDelim string // end of action
|
||||
state stateFn // the next lexing function to enter
|
||||
pos Pos // current position in the input
|
||||
start Pos // start position of this item
|
||||
width Pos // width of last rune read from input
|
||||
lastPos Pos // position of most recent item returned by nextItem
|
||||
items chan item // channel of scanned items
|
||||
parenDepth int // nesting depth of ( ) exprs
|
||||
}
|
||||
|
||||
// next returns the next rune in the input.
|
||||
func (l *lexer) next() rune {
|
||||
if int(l.pos) >= len(l.input) {
|
||||
l.width = 0
|
||||
return eof
|
||||
}
|
||||
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
|
||||
l.width = Pos(w)
|
||||
l.pos += l.width
|
||||
return r
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next rune in the input.
|
||||
func (l *lexer) peek() rune {
|
||||
r := l.next()
|
||||
l.backup()
|
||||
return r
|
||||
}
|
||||
|
||||
// backup steps back one rune. Can only be called once per call of next.
|
||||
func (l *lexer) backup() {
|
||||
l.pos -= l.width
|
||||
}
|
||||
|
||||
// emit passes an item back to the client.
|
||||
func (l *lexer) emit(t itemType) {
|
||||
l.items <- item{t, l.start, l.input[l.start:l.pos]}
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
// ignore skips over the pending input before this point.
|
||||
func (l *lexer) ignore() {
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
// accept consumes the next rune if it's from the valid set.
|
||||
func (l *lexer) accept(valid string) bool {
|
||||
if strings.IndexRune(valid, l.next()) >= 0 {
|
||||
return true
|
||||
}
|
||||
l.backup()
|
||||
return false
|
||||
}
|
||||
|
||||
// acceptRun consumes a run of runes from the valid set.
|
||||
func (l *lexer) acceptRun(valid string) {
|
||||
for strings.IndexRune(valid, l.next()) >= 0 {
|
||||
}
|
||||
l.backup()
|
||||
}
|
||||
|
||||
// lineNumber reports which line we're on, based on the position of
|
||||
// the previous item returned by nextItem. Doing it this way
|
||||
// means we don't have to worry about peek double counting.
|
||||
func (l *lexer) lineNumber() int {
|
||||
return 1 + strings.Count(l.input[:l.lastPos], "\n")
|
||||
}
|
||||
|
||||
// errorf returns an error token and terminates the scan by passing
|
||||
// back a nil pointer that will be the next state, terminating l.nextItem.
|
||||
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
|
||||
l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextItem returns the next item from the input.
|
||||
func (l *lexer) nextItem() item {
|
||||
item := <-l.items
|
||||
l.lastPos = item.pos
|
||||
return item
|
||||
}
|
||||
|
||||
// lex creates a new scanner for the input string.
|
||||
func lex(name, input, left, right string) *lexer {
|
||||
if left == "" {
|
||||
left = leftDelim
|
||||
}
|
||||
if right == "" {
|
||||
right = rightDelim
|
||||
}
|
||||
l := &lexer{
|
||||
name: name,
|
||||
input: input,
|
||||
leftDelim: left,
|
||||
rightDelim: right,
|
||||
items: make(chan item),
|
||||
}
|
||||
go l.run()
|
||||
return l
|
||||
}
|
||||
|
||||
// run runs the state machine for the lexer.
|
||||
func (l *lexer) run() {
|
||||
for l.state = lexText; l.state != nil; {
|
||||
l.state = l.state(l)
|
||||
}
|
||||
}
|
||||
|
||||
// state functions
|
||||
|
||||
const (
|
||||
leftDelim = "{{"
|
||||
rightDelim = "}}"
|
||||
leftComment = "/*"
|
||||
rightComment = "*/"
|
||||
)
|
||||
|
||||
// lexText scans until an opening action delimiter, "{{".
|
||||
func lexText(l *lexer) stateFn {
|
||||
for {
|
||||
if strings.HasPrefix(l.input[l.pos:], l.leftDelim) {
|
||||
if l.pos > l.start {
|
||||
l.emit(itemText)
|
||||
}
|
||||
return lexLeftDelim
|
||||
}
|
||||
if l.next() == eof {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Correctly reached EOF.
|
||||
if l.pos > l.start {
|
||||
l.emit(itemText)
|
||||
}
|
||||
l.emit(itemEOF)
|
||||
return nil
|
||||
}
|
||||
|
||||
// lexLeftDelim scans the left delimiter, which is known to be present.
|
||||
func lexLeftDelim(l *lexer) stateFn {
|
||||
l.pos += Pos(len(l.leftDelim))
|
||||
if strings.HasPrefix(l.input[l.pos:], leftComment) {
|
||||
return lexComment
|
||||
}
|
||||
l.emit(itemLeftDelim)
|
||||
l.parenDepth = 0
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexComment scans a comment. The left comment marker is known to be present.
|
||||
func lexComment(l *lexer) stateFn {
|
||||
l.pos += Pos(len(leftComment))
|
||||
i := strings.Index(l.input[l.pos:], rightComment)
|
||||
if i < 0 {
|
||||
return l.errorf("unclosed comment")
|
||||
}
|
||||
l.pos += Pos(i + len(rightComment))
|
||||
if !strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
|
||||
return l.errorf("comment ends before closing delimiter")
|
||||
|
||||
}
|
||||
l.pos += Pos(len(l.rightDelim))
|
||||
l.ignore()
|
||||
return lexText
|
||||
}
|
||||
|
||||
// lexRightDelim scans the right delimiter, which is known to be present.
|
||||
func lexRightDelim(l *lexer) stateFn {
|
||||
l.pos += Pos(len(l.rightDelim))
|
||||
l.emit(itemRightDelim)
|
||||
if l.peek() == '\\' {
|
||||
l.pos++
|
||||
l.emit(itemElideNewline)
|
||||
}
|
||||
return lexText
|
||||
}
|
||||
|
||||
// lexInsideAction scans the elements inside action delimiters.
|
||||
func lexInsideAction(l *lexer) stateFn {
|
||||
// Either number, quoted string, or identifier.
|
||||
// Spaces separate arguments; runs of spaces turn into itemSpace.
|
||||
// Pipe symbols separate and are emitted.
|
||||
if strings.HasPrefix(l.input[l.pos:], l.rightDelim+"\\") || strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
|
||||
if l.parenDepth == 0 {
|
||||
return lexRightDelim
|
||||
}
|
||||
return l.errorf("unclosed left paren")
|
||||
}
|
||||
switch r := l.next(); {
|
||||
case r == eof || isEndOfLine(r):
|
||||
return l.errorf("unclosed action")
|
||||
case isSpace(r):
|
||||
return lexSpace
|
||||
case r == ':':
|
||||
if l.next() != '=' {
|
||||
return l.errorf("expected :=")
|
||||
}
|
||||
l.emit(itemColonEquals)
|
||||
case r == '|':
|
||||
l.emit(itemPipe)
|
||||
case r == '"':
|
||||
return lexQuote
|
||||
case r == '`':
|
||||
return lexRawQuote
|
||||
case r == '$':
|
||||
return lexVariable
|
||||
case r == '\'':
|
||||
return lexChar
|
||||
case r == '.':
|
||||
// special look-ahead for ".field" so we don't break l.backup().
|
||||
if l.pos < Pos(len(l.input)) {
|
||||
r := l.input[l.pos]
|
||||
if r < '0' || '9' < r {
|
||||
return lexField
|
||||
}
|
||||
}
|
||||
fallthrough // '.' can start a number.
|
||||
case r == '+' || r == '-' || ('0' <= r && r <= '9'):
|
||||
l.backup()
|
||||
return lexNumber
|
||||
case isAlphaNumeric(r):
|
||||
l.backup()
|
||||
return lexIdentifier
|
||||
case r == '(':
|
||||
l.emit(itemLeftParen)
|
||||
l.parenDepth++
|
||||
return lexInsideAction
|
||||
case r == ')':
|
||||
l.emit(itemRightParen)
|
||||
l.parenDepth--
|
||||
if l.parenDepth < 0 {
|
||||
return l.errorf("unexpected right paren %#U", r)
|
||||
}
|
||||
return lexInsideAction
|
||||
case r <= unicode.MaxASCII && unicode.IsPrint(r):
|
||||
l.emit(itemChar)
|
||||
return lexInsideAction
|
||||
default:
|
||||
return l.errorf("unrecognized character in action: %#U", r)
|
||||
}
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexSpace scans a run of space characters.
|
||||
// One space has already been seen.
|
||||
func lexSpace(l *lexer) stateFn {
|
||||
for isSpace(l.peek()) {
|
||||
l.next()
|
||||
}
|
||||
l.emit(itemSpace)
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexIdentifier scans an alphanumeric.
|
||||
func lexIdentifier(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch r := l.next(); {
|
||||
case isAlphaNumeric(r):
|
||||
// absorb.
|
||||
default:
|
||||
l.backup()
|
||||
word := l.input[l.start:l.pos]
|
||||
if !l.atTerminator() {
|
||||
return l.errorf("bad character %#U", r)
|
||||
}
|
||||
switch {
|
||||
case key[word] > itemKeyword:
|
||||
l.emit(key[word])
|
||||
case word[0] == '.':
|
||||
l.emit(itemField)
|
||||
case word == "true", word == "false":
|
||||
l.emit(itemBool)
|
||||
default:
|
||||
l.emit(itemIdentifier)
|
||||
}
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexField scans a field: .Alphanumeric.
|
||||
// The . has been scanned.
|
||||
func lexField(l *lexer) stateFn {
|
||||
return lexFieldOrVariable(l, itemField)
|
||||
}
|
||||
|
||||
// lexVariable scans a Variable: $Alphanumeric.
|
||||
// The $ has been scanned.
|
||||
func lexVariable(l *lexer) stateFn {
|
||||
if l.atTerminator() { // Nothing interesting follows -> "$".
|
||||
l.emit(itemVariable)
|
||||
return lexInsideAction
|
||||
}
|
||||
return lexFieldOrVariable(l, itemVariable)
|
||||
}
|
||||
|
||||
// lexVariable scans a field or variable: [.$]Alphanumeric.
|
||||
// The . or $ has been scanned.
|
||||
func lexFieldOrVariable(l *lexer, typ itemType) stateFn {
|
||||
if l.atTerminator() { // Nothing interesting follows -> "." or "$".
|
||||
if typ == itemVariable {
|
||||
l.emit(itemVariable)
|
||||
} else {
|
||||
l.emit(itemDot)
|
||||
}
|
||||
return lexInsideAction
|
||||
}
|
||||
var r rune
|
||||
for {
|
||||
r = l.next()
|
||||
if !isAlphaNumeric(r) {
|
||||
l.backup()
|
||||
break
|
||||
}
|
||||
}
|
||||
if !l.atTerminator() {
|
||||
return l.errorf("bad character %#U", r)
|
||||
}
|
||||
l.emit(typ)
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// atTerminator reports whether the input is at valid termination character to
|
||||
// appear after an identifier. Breaks .X.Y into two pieces. Also catches cases
|
||||
// like "$x+2" not being acceptable without a space, in case we decide one
|
||||
// day to implement arithmetic.
|
||||
func (l *lexer) atTerminator() bool {
|
||||
r := l.peek()
|
||||
if isSpace(r) || isEndOfLine(r) {
|
||||
return true
|
||||
}
|
||||
switch r {
|
||||
case eof, '.', ',', '|', ':', ')', '(':
|
||||
return true
|
||||
}
|
||||
// Does r start the delimiter? This can be ambiguous (with delim=="//", $x/2 will
|
||||
// succeed but should fail) but only in extremely rare cases caused by willfully
|
||||
// bad choice of delimiter.
|
||||
if rd, _ := utf8.DecodeRuneInString(l.rightDelim); rd == r {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// lexChar scans a character constant. The initial quote is already
|
||||
// scanned. Syntax checking is done by the parser.
|
||||
func lexChar(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch l.next() {
|
||||
case '\\':
|
||||
if r := l.next(); r != eof && r != '\n' {
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case eof, '\n':
|
||||
return l.errorf("unterminated character constant")
|
||||
case '\'':
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
l.emit(itemCharConstant)
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// 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.
|
||||
func lexNumber(l *lexer) stateFn {
|
||||
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(itemComplex)
|
||||
} else {
|
||||
l.emit(itemNumber)
|
||||
}
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// lexQuote scans a quoted string.
|
||||
func lexQuote(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch l.next() {
|
||||
case '\\':
|
||||
if r := l.next(); r != eof && r != '\n' {
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case eof, '\n':
|
||||
return l.errorf("unterminated quoted string")
|
||||
case '"':
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
l.emit(itemString)
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexRawQuote scans a raw quoted string.
|
||||
func lexRawQuote(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch l.next() {
|
||||
case eof, '\n':
|
||||
return l.errorf("unterminated raw quoted string")
|
||||
case '`':
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
l.emit(itemRawString)
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// isSpace reports whether r is a space character.
|
||||
func isSpace(r rune) bool {
|
||||
return r == ' ' || r == '\t'
|
||||
}
|
||||
|
||||
// isEndOfLine reports whether r is an end-of-line character.
|
||||
func isEndOfLine(r rune) bool {
|
||||
return r == '\r' || r == '\n'
|
||||
}
|
||||
|
||||
// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
|
||||
func isAlphaNumeric(r rune) bool {
|
||||
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
|
||||
}
|
||||
-468
@@ -1,468 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Make the types prettyprint.
|
||||
var itemName = map[itemType]string{
|
||||
itemError: "error",
|
||||
itemBool: "bool",
|
||||
itemChar: "char",
|
||||
itemCharConstant: "charconst",
|
||||
itemComplex: "complex",
|
||||
itemColonEquals: ":=",
|
||||
itemEOF: "EOF",
|
||||
itemField: "field",
|
||||
itemIdentifier: "identifier",
|
||||
itemLeftDelim: "left delim",
|
||||
itemLeftParen: "(",
|
||||
itemNumber: "number",
|
||||
itemPipe: "pipe",
|
||||
itemRawString: "raw string",
|
||||
itemRightDelim: "right delim",
|
||||
itemElideNewline: "elide newline",
|
||||
itemRightParen: ")",
|
||||
itemSpace: "space",
|
||||
itemString: "string",
|
||||
itemVariable: "variable",
|
||||
|
||||
// keywords
|
||||
itemDot: ".",
|
||||
itemDefine: "define",
|
||||
itemElse: "else",
|
||||
itemIf: "if",
|
||||
itemEnd: "end",
|
||||
itemNil: "nil",
|
||||
itemRange: "range",
|
||||
itemTemplate: "template",
|
||||
itemWith: "with",
|
||||
}
|
||||
|
||||
func (i itemType) String() string {
|
||||
s := itemName[i]
|
||||
if s == "" {
|
||||
return fmt.Sprintf("item%d", int(i))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type lexTest struct {
|
||||
name string
|
||||
input string
|
||||
items []item
|
||||
}
|
||||
|
||||
var (
|
||||
tEOF = item{itemEOF, 0, ""}
|
||||
tFor = item{itemIdentifier, 0, "for"}
|
||||
tLeft = item{itemLeftDelim, 0, "{{"}
|
||||
tLpar = item{itemLeftParen, 0, "("}
|
||||
tPipe = item{itemPipe, 0, "|"}
|
||||
tQuote = item{itemString, 0, `"abc \n\t\" "`}
|
||||
tRange = item{itemRange, 0, "range"}
|
||||
tRight = item{itemRightDelim, 0, "}}"}
|
||||
tElideNewline = item{itemElideNewline, 0, "\\"}
|
||||
tRpar = item{itemRightParen, 0, ")"}
|
||||
tSpace = item{itemSpace, 0, " "}
|
||||
raw = "`" + `abc\n\t\" ` + "`"
|
||||
tRawQuote = item{itemRawString, 0, raw}
|
||||
)
|
||||
|
||||
var lexTests = []lexTest{
|
||||
{"empty", "", []item{tEOF}},
|
||||
{"spaces", " \t\n", []item{{itemText, 0, " \t\n"}, tEOF}},
|
||||
{"text", `now is the time`, []item{{itemText, 0, "now is the time"}, tEOF}},
|
||||
{"elide newline", "{{}}\\", []item{tLeft, tRight, tElideNewline, tEOF}},
|
||||
{"text with comment", "hello-{{/* this is a comment */}}-world", []item{
|
||||
{itemText, 0, "hello-"},
|
||||
{itemText, 0, "-world"},
|
||||
tEOF,
|
||||
}},
|
||||
{"punctuation", "{{,@% }}", []item{
|
||||
tLeft,
|
||||
{itemChar, 0, ","},
|
||||
{itemChar, 0, "@"},
|
||||
{itemChar, 0, "%"},
|
||||
tSpace,
|
||||
tRight,
|
||||
tEOF,
|
||||
}},
|
||||
{"parens", "{{((3))}}", []item{
|
||||
tLeft,
|
||||
tLpar,
|
||||
tLpar,
|
||||
{itemNumber, 0, "3"},
|
||||
tRpar,
|
||||
tRpar,
|
||||
tRight,
|
||||
tEOF,
|
||||
}},
|
||||
{"empty action", `{{}}`, []item{tLeft, tRight, tEOF}},
|
||||
{"for", `{{for}}`, []item{tLeft, tFor, tRight, tEOF}},
|
||||
{"quote", `{{"abc \n\t\" "}}`, []item{tLeft, tQuote, tRight, tEOF}},
|
||||
{"raw quote", "{{" + raw + "}}", []item{tLeft, tRawQuote, tRight, tEOF}},
|
||||
{"numbers", "{{1 02 0x14 -7.2i 1e3 +1.2e-4 4.2i 1+2i}}", []item{
|
||||
tLeft,
|
||||
{itemNumber, 0, "1"},
|
||||
tSpace,
|
||||
{itemNumber, 0, "02"},
|
||||
tSpace,
|
||||
{itemNumber, 0, "0x14"},
|
||||
tSpace,
|
||||
{itemNumber, 0, "-7.2i"},
|
||||
tSpace,
|
||||
{itemNumber, 0, "1e3"},
|
||||
tSpace,
|
||||
{itemNumber, 0, "+1.2e-4"},
|
||||
tSpace,
|
||||
{itemNumber, 0, "4.2i"},
|
||||
tSpace,
|
||||
{itemComplex, 0, "1+2i"},
|
||||
tRight,
|
||||
tEOF,
|
||||
}},
|
||||
{"characters", `{{'a' '\n' '\'' '\\' '\u00FF' '\xFF' '本'}}`, []item{
|
||||
tLeft,
|
||||
{itemCharConstant, 0, `'a'`},
|
||||
tSpace,
|
||||
{itemCharConstant, 0, `'\n'`},
|
||||
tSpace,
|
||||
{itemCharConstant, 0, `'\''`},
|
||||
tSpace,
|
||||
{itemCharConstant, 0, `'\\'`},
|
||||
tSpace,
|
||||
{itemCharConstant, 0, `'\u00FF'`},
|
||||
tSpace,
|
||||
{itemCharConstant, 0, `'\xFF'`},
|
||||
tSpace,
|
||||
{itemCharConstant, 0, `'本'`},
|
||||
tRight,
|
||||
tEOF,
|
||||
}},
|
||||
{"bools", "{{true false}}", []item{
|
||||
tLeft,
|
||||
{itemBool, 0, "true"},
|
||||
tSpace,
|
||||
{itemBool, 0, "false"},
|
||||
tRight,
|
||||
tEOF,
|
||||
}},
|
||||
{"dot", "{{.}}", []item{
|
||||
tLeft,
|
||||
{itemDot, 0, "."},
|
||||
tRight,
|
||||
tEOF,
|
||||
}},
|
||||
{"nil", "{{nil}}", []item{
|
||||
tLeft,
|
||||
{itemNil, 0, "nil"},
|
||||
tRight,
|
||||
tEOF,
|
||||
}},
|
||||
{"dots", "{{.x . .2 .x.y.z}}", []item{
|
||||
tLeft,
|
||||
{itemField, 0, ".x"},
|
||||
tSpace,
|
||||
{itemDot, 0, "."},
|
||||
tSpace,
|
||||
{itemNumber, 0, ".2"},
|
||||
tSpace,
|
||||
{itemField, 0, ".x"},
|
||||
{itemField, 0, ".y"},
|
||||
{itemField, 0, ".z"},
|
||||
tRight,
|
||||
tEOF,
|
||||
}},
|
||||
{"keywords", "{{range if else end with}}", []item{
|
||||
tLeft,
|
||||
{itemRange, 0, "range"},
|
||||
tSpace,
|
||||
{itemIf, 0, "if"},
|
||||
tSpace,
|
||||
{itemElse, 0, "else"},
|
||||
tSpace,
|
||||
{itemEnd, 0, "end"},
|
||||
tSpace,
|
||||
{itemWith, 0, "with"},
|
||||
tRight,
|
||||
tEOF,
|
||||
}},
|
||||
{"variables", "{{$c := printf $ $hello $23 $ $var.Field .Method}}", []item{
|
||||
tLeft,
|
||||
{itemVariable, 0, "$c"},
|
||||
tSpace,
|
||||
{itemColonEquals, 0, ":="},
|
||||
tSpace,
|
||||
{itemIdentifier, 0, "printf"},
|
||||
tSpace,
|
||||
{itemVariable, 0, "$"},
|
||||
tSpace,
|
||||
{itemVariable, 0, "$hello"},
|
||||
tSpace,
|
||||
{itemVariable, 0, "$23"},
|
||||
tSpace,
|
||||
{itemVariable, 0, "$"},
|
||||
tSpace,
|
||||
{itemVariable, 0, "$var"},
|
||||
{itemField, 0, ".Field"},
|
||||
tSpace,
|
||||
{itemField, 0, ".Method"},
|
||||
tRight,
|
||||
tEOF,
|
||||
}},
|
||||
{"variable invocation", "{{$x 23}}", []item{
|
||||
tLeft,
|
||||
{itemVariable, 0, "$x"},
|
||||
tSpace,
|
||||
{itemNumber, 0, "23"},
|
||||
tRight,
|
||||
tEOF,
|
||||
}},
|
||||
{"pipeline", `intro {{echo hi 1.2 |noargs|args 1 "hi"}} outro`, []item{
|
||||
{itemText, 0, "intro "},
|
||||
tLeft,
|
||||
{itemIdentifier, 0, "echo"},
|
||||
tSpace,
|
||||
{itemIdentifier, 0, "hi"},
|
||||
tSpace,
|
||||
{itemNumber, 0, "1.2"},
|
||||
tSpace,
|
||||
tPipe,
|
||||
{itemIdentifier, 0, "noargs"},
|
||||
tPipe,
|
||||
{itemIdentifier, 0, "args"},
|
||||
tSpace,
|
||||
{itemNumber, 0, "1"},
|
||||
tSpace,
|
||||
{itemString, 0, `"hi"`},
|
||||
tRight,
|
||||
{itemText, 0, " outro"},
|
||||
tEOF,
|
||||
}},
|
||||
{"declaration", "{{$v := 3}}", []item{
|
||||
tLeft,
|
||||
{itemVariable, 0, "$v"},
|
||||
tSpace,
|
||||
{itemColonEquals, 0, ":="},
|
||||
tSpace,
|
||||
{itemNumber, 0, "3"},
|
||||
tRight,
|
||||
tEOF,
|
||||
}},
|
||||
{"2 declarations", "{{$v , $w := 3}}", []item{
|
||||
tLeft,
|
||||
{itemVariable, 0, "$v"},
|
||||
tSpace,
|
||||
{itemChar, 0, ","},
|
||||
tSpace,
|
||||
{itemVariable, 0, "$w"},
|
||||
tSpace,
|
||||
{itemColonEquals, 0, ":="},
|
||||
tSpace,
|
||||
{itemNumber, 0, "3"},
|
||||
tRight,
|
||||
tEOF,
|
||||
}},
|
||||
{"field of parenthesized expression", "{{(.X).Y}}", []item{
|
||||
tLeft,
|
||||
tLpar,
|
||||
{itemField, 0, ".X"},
|
||||
tRpar,
|
||||
{itemField, 0, ".Y"},
|
||||
tRight,
|
||||
tEOF,
|
||||
}},
|
||||
// errors
|
||||
{"badchar", "#{{\x01}}", []item{
|
||||
{itemText, 0, "#"},
|
||||
tLeft,
|
||||
{itemError, 0, "unrecognized character in action: U+0001"},
|
||||
}},
|
||||
{"unclosed action", "{{\n}}", []item{
|
||||
tLeft,
|
||||
{itemError, 0, "unclosed action"},
|
||||
}},
|
||||
{"EOF in action", "{{range", []item{
|
||||
tLeft,
|
||||
tRange,
|
||||
{itemError, 0, "unclosed action"},
|
||||
}},
|
||||
{"unclosed quote", "{{\"\n\"}}", []item{
|
||||
tLeft,
|
||||
{itemError, 0, "unterminated quoted string"},
|
||||
}},
|
||||
{"unclosed raw quote", "{{`xx\n`}}", []item{
|
||||
tLeft,
|
||||
{itemError, 0, "unterminated raw quoted string"},
|
||||
}},
|
||||
{"unclosed char constant", "{{'\n}}", []item{
|
||||
tLeft,
|
||||
{itemError, 0, "unterminated character constant"},
|
||||
}},
|
||||
{"bad number", "{{3k}}", []item{
|
||||
tLeft,
|
||||
{itemError, 0, `bad number syntax: "3k"`},
|
||||
}},
|
||||
{"unclosed paren", "{{(3}}", []item{
|
||||
tLeft,
|
||||
tLpar,
|
||||
{itemNumber, 0, "3"},
|
||||
{itemError, 0, `unclosed left paren`},
|
||||
}},
|
||||
{"extra right paren", "{{3)}}", []item{
|
||||
tLeft,
|
||||
{itemNumber, 0, "3"},
|
||||
tRpar,
|
||||
{itemError, 0, `unexpected right paren U+0029 ')'`},
|
||||
}},
|
||||
|
||||
// Fixed bugs
|
||||
// Many elements in an action blew the lookahead until
|
||||
// we made lexInsideAction not loop.
|
||||
{"long pipeline deadlock", "{{|||||}}", []item{
|
||||
tLeft,
|
||||
tPipe,
|
||||
tPipe,
|
||||
tPipe,
|
||||
tPipe,
|
||||
tPipe,
|
||||
tRight,
|
||||
tEOF,
|
||||
}},
|
||||
{"text with bad comment", "hello-{{/*/}}-world", []item{
|
||||
{itemText, 0, "hello-"},
|
||||
{itemError, 0, `unclosed comment`},
|
||||
}},
|
||||
{"text with comment close separted from delim", "hello-{{/* */ }}-world", []item{
|
||||
{itemText, 0, "hello-"},
|
||||
{itemError, 0, `comment ends before closing delimiter`},
|
||||
}},
|
||||
// This one is an error that we can't catch because it breaks templates with
|
||||
// minimized JavaScript. Should have fixed it before Go 1.1.
|
||||
{"unmatched right delimiter", "hello-{.}}-world", []item{
|
||||
{itemText, 0, "hello-{.}}-world"},
|
||||
tEOF,
|
||||
}},
|
||||
}
|
||||
|
||||
// collect gathers the emitted items into a slice.
|
||||
func collect(t *lexTest, left, right string) (items []item) {
|
||||
l := lex(t.name, t.input, left, right)
|
||||
for {
|
||||
item := l.nextItem()
|
||||
items = append(items, item)
|
||||
if item.typ == itemEOF || item.typ == itemError {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func equal(i1, i2 []item, checkPos bool) bool {
|
||||
if len(i1) != len(i2) {
|
||||
return false
|
||||
}
|
||||
for k := range i1 {
|
||||
if i1[k].typ != i2[k].typ {
|
||||
return false
|
||||
}
|
||||
if i1[k].val != i2[k].val {
|
||||
return false
|
||||
}
|
||||
if checkPos && i1[k].pos != i2[k].pos {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestLex(t *testing.T) {
|
||||
for _, test := range lexTests {
|
||||
items := collect(&test, "", "")
|
||||
if !equal(items, test.items, false) {
|
||||
t.Errorf("%s: got\n\t%+v\nexpected\n\t%v", test.name, items, test.items)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Some easy cases from above, but with delimiters $$ and @@
|
||||
var lexDelimTests = []lexTest{
|
||||
{"punctuation", "$$,@%{{}}@@", []item{
|
||||
tLeftDelim,
|
||||
{itemChar, 0, ","},
|
||||
{itemChar, 0, "@"},
|
||||
{itemChar, 0, "%"},
|
||||
{itemChar, 0, "{"},
|
||||
{itemChar, 0, "{"},
|
||||
{itemChar, 0, "}"},
|
||||
{itemChar, 0, "}"},
|
||||
tRightDelim,
|
||||
tEOF,
|
||||
}},
|
||||
{"empty action", `$$@@`, []item{tLeftDelim, tRightDelim, tEOF}},
|
||||
{"for", `$$for@@`, []item{tLeftDelim, tFor, tRightDelim, tEOF}},
|
||||
{"quote", `$$"abc \n\t\" "@@`, []item{tLeftDelim, tQuote, tRightDelim, tEOF}},
|
||||
{"raw quote", "$$" + raw + "@@", []item{tLeftDelim, tRawQuote, tRightDelim, tEOF}},
|
||||
}
|
||||
|
||||
var (
|
||||
tLeftDelim = item{itemLeftDelim, 0, "$$"}
|
||||
tRightDelim = item{itemRightDelim, 0, "@@"}
|
||||
)
|
||||
|
||||
func TestDelims(t *testing.T) {
|
||||
for _, test := range lexDelimTests {
|
||||
items := collect(&test, "$$", "@@")
|
||||
if !equal(items, test.items, false) {
|
||||
t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var lexPosTests = []lexTest{
|
||||
{"empty", "", []item{tEOF}},
|
||||
{"punctuation", "{{,@%#}}", []item{
|
||||
{itemLeftDelim, 0, "{{"},
|
||||
{itemChar, 2, ","},
|
||||
{itemChar, 3, "@"},
|
||||
{itemChar, 4, "%"},
|
||||
{itemChar, 5, "#"},
|
||||
{itemRightDelim, 6, "}}"},
|
||||
{itemEOF, 8, ""},
|
||||
}},
|
||||
{"sample", "0123{{hello}}xyz", []item{
|
||||
{itemText, 0, "0123"},
|
||||
{itemLeftDelim, 4, "{{"},
|
||||
{itemIdentifier, 6, "hello"},
|
||||
{itemRightDelim, 11, "}}"},
|
||||
{itemText, 13, "xyz"},
|
||||
{itemEOF, 16, ""},
|
||||
}},
|
||||
}
|
||||
|
||||
// The other tests don't check position, to make the test cases easier to construct.
|
||||
// This one does.
|
||||
func TestPos(t *testing.T) {
|
||||
for _, test := range lexPosTests {
|
||||
items := collect(&test, "", "")
|
||||
if !equal(items, test.items, true) {
|
||||
t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items)
|
||||
if len(items) == len(test.items) {
|
||||
// Detailed print; avoid item.String() to expose the position value.
|
||||
for i := range items {
|
||||
if !equal(items[i:i+1], test.items[i:i+1], true) {
|
||||
i1 := items[i]
|
||||
i2 := test.items[i]
|
||||
t.Errorf("\t#%d: got {%v %d %q} expected {%v %d %q}", i, i1.typ, i1.pos, i1.val, i2.typ, i2.pos, i2.val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-834
@@ -1,834 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Parse nodes.
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var textFormat = "%s" // Changed to "%q" in tests for better error messages.
|
||||
|
||||
// A Node is an element in the parse tree. The interface is trivial.
|
||||
// The interface contains an unexported method so that only
|
||||
// types local to this package can satisfy it.
|
||||
type Node interface {
|
||||
Type() NodeType
|
||||
String() string
|
||||
// Copy does a deep copy of the Node and all its components.
|
||||
// To avoid type assertions, some XxxNodes also have specialized
|
||||
// CopyXxx methods that return *XxxNode.
|
||||
Copy() Node
|
||||
Position() Pos // byte position of start of node in full original input string
|
||||
// tree returns the containing *Tree.
|
||||
// It is unexported so all implementations of Node are in this package.
|
||||
tree() *Tree
|
||||
}
|
||||
|
||||
// NodeType identifies the type of a parse tree node.
|
||||
type NodeType int
|
||||
|
||||
// Pos represents a byte position in the original input text from which
|
||||
// this template was parsed.
|
||||
type Pos int
|
||||
|
||||
func (p Pos) Position() Pos {
|
||||
return p
|
||||
}
|
||||
|
||||
// Type returns itself and provides an easy default implementation
|
||||
// for embedding in a Node. Embedded in all non-trivial Nodes.
|
||||
func (t NodeType) Type() NodeType {
|
||||
return t
|
||||
}
|
||||
|
||||
const (
|
||||
NodeText NodeType = iota // Plain text.
|
||||
NodeAction // A non-control action such as a field evaluation.
|
||||
NodeBool // A boolean constant.
|
||||
NodeChain // A sequence of field accesses.
|
||||
NodeCommand // An element of a pipeline.
|
||||
NodeDot // The cursor, dot.
|
||||
nodeElse // An else action. Not added to tree.
|
||||
nodeEnd // An end action. Not added to tree.
|
||||
NodeField // A field or method name.
|
||||
NodeIdentifier // An identifier; always a function name.
|
||||
NodeIf // An if action.
|
||||
NodeList // A list of Nodes.
|
||||
NodeNil // An untyped nil constant.
|
||||
NodeNumber // A numerical constant.
|
||||
NodePipe // A pipeline of commands.
|
||||
NodeRange // A range action.
|
||||
NodeString // A string constant.
|
||||
NodeTemplate // A template invocation action.
|
||||
NodeVariable // A $ variable.
|
||||
NodeWith // A with action.
|
||||
)
|
||||
|
||||
// Nodes.
|
||||
|
||||
// ListNode holds a sequence of nodes.
|
||||
type ListNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Nodes []Node // The element nodes in lexical order.
|
||||
}
|
||||
|
||||
func (t *Tree) newList(pos Pos) *ListNode {
|
||||
return &ListNode{tr: t, NodeType: NodeList, Pos: pos}
|
||||
}
|
||||
|
||||
func (l *ListNode) append(n Node) {
|
||||
l.Nodes = append(l.Nodes, n)
|
||||
}
|
||||
|
||||
func (l *ListNode) tree() *Tree {
|
||||
return l.tr
|
||||
}
|
||||
|
||||
func (l *ListNode) String() string {
|
||||
b := new(bytes.Buffer)
|
||||
for _, n := range l.Nodes {
|
||||
fmt.Fprint(b, n)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (l *ListNode) CopyList() *ListNode {
|
||||
if l == nil {
|
||||
return l
|
||||
}
|
||||
n := l.tr.newList(l.Pos)
|
||||
for _, elem := range l.Nodes {
|
||||
n.append(elem.Copy())
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (l *ListNode) Copy() Node {
|
||||
return l.CopyList()
|
||||
}
|
||||
|
||||
// TextNode holds plain text.
|
||||
type TextNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Text []byte // The text; may span newlines.
|
||||
}
|
||||
|
||||
func (t *Tree) newText(pos Pos, text string) *TextNode {
|
||||
return &TextNode{tr: t, NodeType: NodeText, Pos: pos, Text: []byte(text)}
|
||||
}
|
||||
|
||||
func (t *TextNode) String() string {
|
||||
return fmt.Sprintf(textFormat, t.Text)
|
||||
}
|
||||
|
||||
func (t *TextNode) tree() *Tree {
|
||||
return t.tr
|
||||
}
|
||||
|
||||
func (t *TextNode) Copy() Node {
|
||||
return &TextNode{tr: t.tr, NodeType: NodeText, Pos: t.Pos, Text: append([]byte{}, t.Text...)}
|
||||
}
|
||||
|
||||
// PipeNode holds a pipeline with optional declaration
|
||||
type PipeNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||
Decl []*VariableNode // Variable declarations in lexical order.
|
||||
Cmds []*CommandNode // The commands in lexical order.
|
||||
}
|
||||
|
||||
func (t *Tree) newPipeline(pos Pos, line int, decl []*VariableNode) *PipeNode {
|
||||
return &PipeNode{tr: t, NodeType: NodePipe, Pos: pos, Line: line, Decl: decl}
|
||||
}
|
||||
|
||||
func (p *PipeNode) append(command *CommandNode) {
|
||||
p.Cmds = append(p.Cmds, command)
|
||||
}
|
||||
|
||||
func (p *PipeNode) String() string {
|
||||
s := ""
|
||||
if len(p.Decl) > 0 {
|
||||
for i, v := range p.Decl {
|
||||
if i > 0 {
|
||||
s += ", "
|
||||
}
|
||||
s += v.String()
|
||||
}
|
||||
s += " := "
|
||||
}
|
||||
for i, c := range p.Cmds {
|
||||
if i > 0 {
|
||||
s += " | "
|
||||
}
|
||||
s += c.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (p *PipeNode) tree() *Tree {
|
||||
return p.tr
|
||||
}
|
||||
|
||||
func (p *PipeNode) CopyPipe() *PipeNode {
|
||||
if p == nil {
|
||||
return p
|
||||
}
|
||||
var decl []*VariableNode
|
||||
for _, d := range p.Decl {
|
||||
decl = append(decl, d.Copy().(*VariableNode))
|
||||
}
|
||||
n := p.tr.newPipeline(p.Pos, p.Line, decl)
|
||||
for _, c := range p.Cmds {
|
||||
n.append(c.Copy().(*CommandNode))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *PipeNode) Copy() Node {
|
||||
return p.CopyPipe()
|
||||
}
|
||||
|
||||
// ActionNode holds an action (something bounded by delimiters).
|
||||
// Control actions have their own nodes; ActionNode represents simple
|
||||
// ones such as field evaluations and parenthesized pipelines.
|
||||
type ActionNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||
Pipe *PipeNode // The pipeline in the action.
|
||||
}
|
||||
|
||||
func (t *Tree) newAction(pos Pos, line int, pipe *PipeNode) *ActionNode {
|
||||
return &ActionNode{tr: t, NodeType: NodeAction, Pos: pos, Line: line, Pipe: pipe}
|
||||
}
|
||||
|
||||
func (a *ActionNode) String() string {
|
||||
return fmt.Sprintf("{{%s}}", a.Pipe)
|
||||
|
||||
}
|
||||
|
||||
func (a *ActionNode) tree() *Tree {
|
||||
return a.tr
|
||||
}
|
||||
|
||||
func (a *ActionNode) Copy() Node {
|
||||
return a.tr.newAction(a.Pos, a.Line, a.Pipe.CopyPipe())
|
||||
|
||||
}
|
||||
|
||||
// CommandNode holds a command (a pipeline inside an evaluating action).
|
||||
type CommandNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Args []Node // Arguments in lexical order: Identifier, field, or constant.
|
||||
}
|
||||
|
||||
func (t *Tree) newCommand(pos Pos) *CommandNode {
|
||||
return &CommandNode{tr: t, NodeType: NodeCommand, Pos: pos}
|
||||
}
|
||||
|
||||
func (c *CommandNode) append(arg Node) {
|
||||
c.Args = append(c.Args, arg)
|
||||
}
|
||||
|
||||
func (c *CommandNode) String() string {
|
||||
s := ""
|
||||
for i, arg := range c.Args {
|
||||
if i > 0 {
|
||||
s += " "
|
||||
}
|
||||
if arg, ok := arg.(*PipeNode); ok {
|
||||
s += "(" + arg.String() + ")"
|
||||
continue
|
||||
}
|
||||
s += arg.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *CommandNode) tree() *Tree {
|
||||
return c.tr
|
||||
}
|
||||
|
||||
func (c *CommandNode) Copy() Node {
|
||||
if c == nil {
|
||||
return c
|
||||
}
|
||||
n := c.tr.newCommand(c.Pos)
|
||||
for _, c := range c.Args {
|
||||
n.append(c.Copy())
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// IdentifierNode holds an identifier.
|
||||
type IdentifierNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Ident string // The identifier's name.
|
||||
}
|
||||
|
||||
// NewIdentifier returns a new IdentifierNode with the given identifier name.
|
||||
func NewIdentifier(ident string) *IdentifierNode {
|
||||
return &IdentifierNode{NodeType: NodeIdentifier, Ident: ident}
|
||||
}
|
||||
|
||||
// SetPos sets the position. NewIdentifier is a public method so we can't modify its signature.
|
||||
// Chained for convenience.
|
||||
// TODO: fix one day?
|
||||
func (i *IdentifierNode) SetPos(pos Pos) *IdentifierNode {
|
||||
i.Pos = pos
|
||||
return i
|
||||
}
|
||||
|
||||
// SetTree sets the parent tree for the node. NewIdentifier is a public method so we can't modify its signature.
|
||||
// Chained for convenience.
|
||||
// TODO: fix one day?
|
||||
func (i *IdentifierNode) SetTree(t *Tree) *IdentifierNode {
|
||||
i.tr = t
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *IdentifierNode) String() string {
|
||||
return i.Ident
|
||||
}
|
||||
|
||||
func (i *IdentifierNode) tree() *Tree {
|
||||
return i.tr
|
||||
}
|
||||
|
||||
func (i *IdentifierNode) Copy() Node {
|
||||
return NewIdentifier(i.Ident).SetTree(i.tr).SetPos(i.Pos)
|
||||
}
|
||||
|
||||
// VariableNode holds a list of variable names, possibly with chained field
|
||||
// accesses. The dollar sign is part of the (first) name.
|
||||
type VariableNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Ident []string // Variable name and fields in lexical order.
|
||||
}
|
||||
|
||||
func (t *Tree) newVariable(pos Pos, ident string) *VariableNode {
|
||||
return &VariableNode{tr: t, NodeType: NodeVariable, Pos: pos, Ident: strings.Split(ident, ".")}
|
||||
}
|
||||
|
||||
func (v *VariableNode) String() string {
|
||||
s := ""
|
||||
for i, id := range v.Ident {
|
||||
if i > 0 {
|
||||
s += "."
|
||||
}
|
||||
s += id
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (v *VariableNode) tree() *Tree {
|
||||
return v.tr
|
||||
}
|
||||
|
||||
func (v *VariableNode) Copy() Node {
|
||||
return &VariableNode{tr: v.tr, NodeType: NodeVariable, Pos: v.Pos, Ident: append([]string{}, v.Ident...)}
|
||||
}
|
||||
|
||||
// DotNode holds the special identifier '.'.
|
||||
type DotNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
}
|
||||
|
||||
func (t *Tree) newDot(pos Pos) *DotNode {
|
||||
return &DotNode{tr: t, NodeType: NodeDot, Pos: pos}
|
||||
}
|
||||
|
||||
func (d *DotNode) Type() NodeType {
|
||||
// Override method on embedded NodeType for API compatibility.
|
||||
// TODO: Not really a problem; could change API without effect but
|
||||
// api tool complains.
|
||||
return NodeDot
|
||||
}
|
||||
|
||||
func (d *DotNode) String() string {
|
||||
return "."
|
||||
}
|
||||
|
||||
func (d *DotNode) tree() *Tree {
|
||||
return d.tr
|
||||
}
|
||||
|
||||
func (d *DotNode) Copy() Node {
|
||||
return d.tr.newDot(d.Pos)
|
||||
}
|
||||
|
||||
// NilNode holds the special identifier 'nil' representing an untyped nil constant.
|
||||
type NilNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
}
|
||||
|
||||
func (t *Tree) newNil(pos Pos) *NilNode {
|
||||
return &NilNode{tr: t, NodeType: NodeNil, Pos: pos}
|
||||
}
|
||||
|
||||
func (n *NilNode) Type() NodeType {
|
||||
// Override method on embedded NodeType for API compatibility.
|
||||
// TODO: Not really a problem; could change API without effect but
|
||||
// api tool complains.
|
||||
return NodeNil
|
||||
}
|
||||
|
||||
func (n *NilNode) String() string {
|
||||
return "nil"
|
||||
}
|
||||
|
||||
func (n *NilNode) tree() *Tree {
|
||||
return n.tr
|
||||
}
|
||||
|
||||
func (n *NilNode) Copy() Node {
|
||||
return n.tr.newNil(n.Pos)
|
||||
}
|
||||
|
||||
// FieldNode holds a field (identifier starting with '.').
|
||||
// The names may be chained ('.x.y').
|
||||
// The period is dropped from each ident.
|
||||
type FieldNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Ident []string // The identifiers in lexical order.
|
||||
}
|
||||
|
||||
func (t *Tree) newField(pos Pos, ident string) *FieldNode {
|
||||
return &FieldNode{tr: t, NodeType: NodeField, Pos: pos, Ident: strings.Split(ident[1:], ".")} // [1:] to drop leading period
|
||||
}
|
||||
|
||||
func (f *FieldNode) String() string {
|
||||
s := ""
|
||||
for _, id := range f.Ident {
|
||||
s += "." + id
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (f *FieldNode) tree() *Tree {
|
||||
return f.tr
|
||||
}
|
||||
|
||||
func (f *FieldNode) Copy() Node {
|
||||
return &FieldNode{tr: f.tr, NodeType: NodeField, Pos: f.Pos, Ident: append([]string{}, f.Ident...)}
|
||||
}
|
||||
|
||||
// ChainNode holds a term followed by a chain of field accesses (identifier starting with '.').
|
||||
// The names may be chained ('.x.y').
|
||||
// The periods are dropped from each ident.
|
||||
type ChainNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Node Node
|
||||
Field []string // The identifiers in lexical order.
|
||||
}
|
||||
|
||||
func (t *Tree) newChain(pos Pos, node Node) *ChainNode {
|
||||
return &ChainNode{tr: t, NodeType: NodeChain, Pos: pos, Node: node}
|
||||
}
|
||||
|
||||
// Add adds the named field (which should start with a period) to the end of the chain.
|
||||
func (c *ChainNode) Add(field string) {
|
||||
if len(field) == 0 || field[0] != '.' {
|
||||
panic("no dot in field")
|
||||
}
|
||||
field = field[1:] // Remove leading dot.
|
||||
if field == "" {
|
||||
panic("empty field")
|
||||
}
|
||||
c.Field = append(c.Field, field)
|
||||
}
|
||||
|
||||
func (c *ChainNode) String() string {
|
||||
s := c.Node.String()
|
||||
if _, ok := c.Node.(*PipeNode); ok {
|
||||
s = "(" + s + ")"
|
||||
}
|
||||
for _, field := range c.Field {
|
||||
s += "." + field
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *ChainNode) tree() *Tree {
|
||||
return c.tr
|
||||
}
|
||||
|
||||
func (c *ChainNode) Copy() Node {
|
||||
return &ChainNode{tr: c.tr, NodeType: NodeChain, Pos: c.Pos, Node: c.Node, Field: append([]string{}, c.Field...)}
|
||||
}
|
||||
|
||||
// BoolNode holds a boolean constant.
|
||||
type BoolNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
True bool // The value of the boolean constant.
|
||||
}
|
||||
|
||||
func (t *Tree) newBool(pos Pos, true bool) *BoolNode {
|
||||
return &BoolNode{tr: t, NodeType: NodeBool, Pos: pos, True: true}
|
||||
}
|
||||
|
||||
func (b *BoolNode) String() string {
|
||||
if b.True {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
func (b *BoolNode) tree() *Tree {
|
||||
return b.tr
|
||||
}
|
||||
|
||||
func (b *BoolNode) Copy() Node {
|
||||
return b.tr.newBool(b.Pos, b.True)
|
||||
}
|
||||
|
||||
// NumberNode holds a number: signed or unsigned integer, float, or complex.
|
||||
// The value is parsed and stored under all the types that can represent the value.
|
||||
// This simulates in a small amount of code the behavior of Go's ideal constants.
|
||||
type NumberNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
IsInt bool // Number has an integral value.
|
||||
IsUint bool // Number has an unsigned integral value.
|
||||
IsFloat bool // Number has a floating-point value.
|
||||
IsComplex bool // Number is complex.
|
||||
Int64 int64 // The signed integer value.
|
||||
Uint64 uint64 // The unsigned integer value.
|
||||
Float64 float64 // The floating-point value.
|
||||
Complex128 complex128 // The complex value.
|
||||
Text string // The original textual representation from the input.
|
||||
}
|
||||
|
||||
func (t *Tree) newNumber(pos Pos, text string, typ itemType) (*NumberNode, error) {
|
||||
n := &NumberNode{tr: t, NodeType: NodeNumber, Pos: pos, Text: text}
|
||||
switch typ {
|
||||
case itemCharConstant:
|
||||
rune, _, tail, err := strconv.UnquoteChar(text[1:], text[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tail != "'" {
|
||||
return nil, fmt.Errorf("malformed character constant: %s", text)
|
||||
}
|
||||
n.Int64 = int64(rune)
|
||||
n.IsInt = true
|
||||
n.Uint64 = uint64(rune)
|
||||
n.IsUint = true
|
||||
n.Float64 = float64(rune) // odd but those are the rules.
|
||||
n.IsFloat = true
|
||||
return n, nil
|
||||
case itemComplex:
|
||||
// fmt.Sscan can parse the pair, so let it do the work.
|
||||
if _, err := fmt.Sscan(text, &n.Complex128); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n.IsComplex = true
|
||||
n.simplifyComplex()
|
||||
return n, nil
|
||||
}
|
||||
// Imaginary constants can only be complex unless they are zero.
|
||||
if len(text) > 0 && text[len(text)-1] == 'i' {
|
||||
f, err := strconv.ParseFloat(text[:len(text)-1], 64)
|
||||
if err == nil {
|
||||
n.IsComplex = true
|
||||
n.Complex128 = complex(0, f)
|
||||
n.simplifyComplex()
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
// Do integer test first so we get 0x123 etc.
|
||||
u, err := strconv.ParseUint(text, 0, 64) // will fail for -0; fixed below.
|
||||
if err == nil {
|
||||
n.IsUint = true
|
||||
n.Uint64 = u
|
||||
}
|
||||
i, err := strconv.ParseInt(text, 0, 64)
|
||||
if err == nil {
|
||||
n.IsInt = true
|
||||
n.Int64 = i
|
||||
if i == 0 {
|
||||
n.IsUint = true // in case of -0.
|
||||
n.Uint64 = u
|
||||
}
|
||||
}
|
||||
// If an integer extraction succeeded, promote the float.
|
||||
if n.IsInt {
|
||||
n.IsFloat = true
|
||||
n.Float64 = float64(n.Int64)
|
||||
} else if n.IsUint {
|
||||
n.IsFloat = true
|
||||
n.Float64 = float64(n.Uint64)
|
||||
} else {
|
||||
f, err := strconv.ParseFloat(text, 64)
|
||||
if err == nil {
|
||||
n.IsFloat = true
|
||||
n.Float64 = f
|
||||
// If a floating-point extraction succeeded, extract the int if needed.
|
||||
if !n.IsInt && float64(int64(f)) == f {
|
||||
n.IsInt = true
|
||||
n.Int64 = int64(f)
|
||||
}
|
||||
if !n.IsUint && float64(uint64(f)) == f {
|
||||
n.IsUint = true
|
||||
n.Uint64 = uint64(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !n.IsInt && !n.IsUint && !n.IsFloat {
|
||||
return nil, fmt.Errorf("illegal number syntax: %q", text)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// simplifyComplex pulls out any other types that are represented by the complex number.
|
||||
// These all require that the imaginary part be zero.
|
||||
func (n *NumberNode) simplifyComplex() {
|
||||
n.IsFloat = imag(n.Complex128) == 0
|
||||
if n.IsFloat {
|
||||
n.Float64 = real(n.Complex128)
|
||||
n.IsInt = float64(int64(n.Float64)) == n.Float64
|
||||
if n.IsInt {
|
||||
n.Int64 = int64(n.Float64)
|
||||
}
|
||||
n.IsUint = float64(uint64(n.Float64)) == n.Float64
|
||||
if n.IsUint {
|
||||
n.Uint64 = uint64(n.Float64)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NumberNode) String() string {
|
||||
return n.Text
|
||||
}
|
||||
|
||||
func (n *NumberNode) tree() *Tree {
|
||||
return n.tr
|
||||
}
|
||||
|
||||
func (n *NumberNode) Copy() Node {
|
||||
nn := new(NumberNode)
|
||||
*nn = *n // Easy, fast, correct.
|
||||
return nn
|
||||
}
|
||||
|
||||
// StringNode holds a string constant. The value has been "unquoted".
|
||||
type StringNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Quoted string // The original text of the string, with quotes.
|
||||
Text string // The string, after quote processing.
|
||||
}
|
||||
|
||||
func (t *Tree) newString(pos Pos, orig, text string) *StringNode {
|
||||
return &StringNode{tr: t, NodeType: NodeString, Pos: pos, Quoted: orig, Text: text}
|
||||
}
|
||||
|
||||
func (s *StringNode) String() string {
|
||||
return s.Quoted
|
||||
}
|
||||
|
||||
func (s *StringNode) tree() *Tree {
|
||||
return s.tr
|
||||
}
|
||||
|
||||
func (s *StringNode) Copy() Node {
|
||||
return s.tr.newString(s.Pos, s.Quoted, s.Text)
|
||||
}
|
||||
|
||||
// endNode represents an {{end}} action.
|
||||
// It does not appear in the final parse tree.
|
||||
type endNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
}
|
||||
|
||||
func (t *Tree) newEnd(pos Pos) *endNode {
|
||||
return &endNode{tr: t, NodeType: nodeEnd, Pos: pos}
|
||||
}
|
||||
|
||||
func (e *endNode) String() string {
|
||||
return "{{end}}"
|
||||
}
|
||||
|
||||
func (e *endNode) tree() *Tree {
|
||||
return e.tr
|
||||
}
|
||||
|
||||
func (e *endNode) Copy() Node {
|
||||
return e.tr.newEnd(e.Pos)
|
||||
}
|
||||
|
||||
// elseNode represents an {{else}} action. Does not appear in the final tree.
|
||||
type elseNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||
}
|
||||
|
||||
func (t *Tree) newElse(pos Pos, line int) *elseNode {
|
||||
return &elseNode{tr: t, NodeType: nodeElse, Pos: pos, Line: line}
|
||||
}
|
||||
|
||||
func (e *elseNode) Type() NodeType {
|
||||
return nodeElse
|
||||
}
|
||||
|
||||
func (e *elseNode) String() string {
|
||||
return "{{else}}"
|
||||
}
|
||||
|
||||
func (e *elseNode) tree() *Tree {
|
||||
return e.tr
|
||||
}
|
||||
|
||||
func (e *elseNode) Copy() Node {
|
||||
return e.tr.newElse(e.Pos, e.Line)
|
||||
}
|
||||
|
||||
// BranchNode is the common representation of if, range, and with.
|
||||
type BranchNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||
Pipe *PipeNode // The pipeline to be evaluated.
|
||||
List *ListNode // What to execute if the value is non-empty.
|
||||
ElseList *ListNode // What to execute if the value is empty (nil if absent).
|
||||
}
|
||||
|
||||
func (b *BranchNode) String() string {
|
||||
name := ""
|
||||
switch b.NodeType {
|
||||
case NodeIf:
|
||||
name = "if"
|
||||
case NodeRange:
|
||||
name = "range"
|
||||
case NodeWith:
|
||||
name = "with"
|
||||
default:
|
||||
panic("unknown branch type")
|
||||
}
|
||||
if b.ElseList != nil {
|
||||
return fmt.Sprintf("{{%s %s}}%s{{else}}%s{{end}}", name, b.Pipe, b.List, b.ElseList)
|
||||
}
|
||||
return fmt.Sprintf("{{%s %s}}%s{{end}}", name, b.Pipe, b.List)
|
||||
}
|
||||
|
||||
func (b *BranchNode) tree() *Tree {
|
||||
return b.tr
|
||||
}
|
||||
|
||||
func (b *BranchNode) Copy() Node {
|
||||
switch b.NodeType {
|
||||
case NodeIf:
|
||||
return b.tr.newIf(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
||||
case NodeRange:
|
||||
return b.tr.newRange(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
||||
case NodeWith:
|
||||
return b.tr.newWith(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
||||
default:
|
||||
panic("unknown branch type")
|
||||
}
|
||||
}
|
||||
|
||||
// IfNode represents an {{if}} action and its commands.
|
||||
type IfNode struct {
|
||||
BranchNode
|
||||
}
|
||||
|
||||
func (t *Tree) newIf(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *IfNode {
|
||||
return &IfNode{BranchNode{tr: t, NodeType: NodeIf, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
||||
}
|
||||
|
||||
func (i *IfNode) Copy() Node {
|
||||
return i.tr.newIf(i.Pos, i.Line, i.Pipe.CopyPipe(), i.List.CopyList(), i.ElseList.CopyList())
|
||||
}
|
||||
|
||||
// RangeNode represents a {{range}} action and its commands.
|
||||
type RangeNode struct {
|
||||
BranchNode
|
||||
}
|
||||
|
||||
func (t *Tree) newRange(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *RangeNode {
|
||||
return &RangeNode{BranchNode{tr: t, NodeType: NodeRange, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
||||
}
|
||||
|
||||
func (r *RangeNode) Copy() Node {
|
||||
return r.tr.newRange(r.Pos, r.Line, r.Pipe.CopyPipe(), r.List.CopyList(), r.ElseList.CopyList())
|
||||
}
|
||||
|
||||
// WithNode represents a {{with}} action and its commands.
|
||||
type WithNode struct {
|
||||
BranchNode
|
||||
}
|
||||
|
||||
func (t *Tree) newWith(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *WithNode {
|
||||
return &WithNode{BranchNode{tr: t, NodeType: NodeWith, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
||||
}
|
||||
|
||||
func (w *WithNode) Copy() Node {
|
||||
return w.tr.newWith(w.Pos, w.Line, w.Pipe.CopyPipe(), w.List.CopyList(), w.ElseList.CopyList())
|
||||
}
|
||||
|
||||
// TemplateNode represents a {{template}} action.
|
||||
type TemplateNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||
Name string // The name of the template (unquoted).
|
||||
Pipe *PipeNode // The command to evaluate as dot for the template.
|
||||
}
|
||||
|
||||
func (t *Tree) newTemplate(pos Pos, line int, name string, pipe *PipeNode) *TemplateNode {
|
||||
return &TemplateNode{tr: t, NodeType: NodeTemplate, Pos: pos, Line: line, Name: name, Pipe: pipe}
|
||||
}
|
||||
|
||||
func (t *TemplateNode) String() string {
|
||||
if t.Pipe == nil {
|
||||
return fmt.Sprintf("{{template %q}}", t.Name)
|
||||
}
|
||||
return fmt.Sprintf("{{template %q %s}}", t.Name, t.Pipe)
|
||||
}
|
||||
|
||||
func (t *TemplateNode) tree() *Tree {
|
||||
return t.tr
|
||||
}
|
||||
|
||||
func (t *TemplateNode) Copy() Node {
|
||||
return t.tr.newTemplate(t.Pos, t.Line, t.Name, t.Pipe.CopyPipe())
|
||||
}
|
||||
-700
@@ -1,700 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package parse builds parse trees for templates as defined by text/template
|
||||
// and html/template. Clients should use those packages to construct templates
|
||||
// rather than this one, which provides shared internal data structures not
|
||||
// intended for general use.
|
||||
package parse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Tree is the representation of a single parsed template.
|
||||
type Tree struct {
|
||||
Name string // name of the template represented by the tree.
|
||||
ParseName string // name of the top-level template during parsing, for error messages.
|
||||
Root *ListNode // top-level root of the tree.
|
||||
text string // text parsed to create the template (or its parent)
|
||||
// Parsing only; cleared after parse.
|
||||
funcs []map[string]interface{}
|
||||
lex *lexer
|
||||
token [3]item // three-token lookahead for parser.
|
||||
peekCount int
|
||||
vars []string // variables defined at the moment.
|
||||
}
|
||||
|
||||
// Copy returns a copy of the Tree. Any parsing state is discarded.
|
||||
func (t *Tree) Copy() *Tree {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
return &Tree{
|
||||
Name: t.Name,
|
||||
ParseName: t.ParseName,
|
||||
Root: t.Root.CopyList(),
|
||||
text: t.text,
|
||||
}
|
||||
}
|
||||
|
||||
// Parse returns a map from template name to parse.Tree, created by parsing the
|
||||
// templates described in the argument string. The top-level template will be
|
||||
// given the specified name. If an error is encountered, parsing stops and an
|
||||
// empty map is returned with the error.
|
||||
func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (treeSet map[string]*Tree, err error) {
|
||||
treeSet = make(map[string]*Tree)
|
||||
t := New(name)
|
||||
t.text = text
|
||||
_, err = t.Parse(text, leftDelim, rightDelim, treeSet, funcs...)
|
||||
return
|
||||
}
|
||||
|
||||
// next returns the next token.
|
||||
func (t *Tree) next() item {
|
||||
if t.peekCount > 0 {
|
||||
t.peekCount--
|
||||
} else {
|
||||
t.token[0] = t.lex.nextItem()
|
||||
}
|
||||
return t.token[t.peekCount]
|
||||
}
|
||||
|
||||
// backup backs the input stream up one token.
|
||||
func (t *Tree) backup() {
|
||||
t.peekCount++
|
||||
}
|
||||
|
||||
// backup2 backs the input stream up two tokens.
|
||||
// The zeroth token is already there.
|
||||
func (t *Tree) backup2(t1 item) {
|
||||
t.token[1] = t1
|
||||
t.peekCount = 2
|
||||
}
|
||||
|
||||
// backup3 backs the input stream up three tokens
|
||||
// The zeroth token is already there.
|
||||
func (t *Tree) backup3(t2, t1 item) { // Reverse order: we're pushing back.
|
||||
t.token[1] = t1
|
||||
t.token[2] = t2
|
||||
t.peekCount = 3
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next token.
|
||||
func (t *Tree) peek() item {
|
||||
if t.peekCount > 0 {
|
||||
return t.token[t.peekCount-1]
|
||||
}
|
||||
t.peekCount = 1
|
||||
t.token[0] = t.lex.nextItem()
|
||||
return t.token[0]
|
||||
}
|
||||
|
||||
// nextNonSpace returns the next non-space token.
|
||||
func (t *Tree) nextNonSpace() (token item) {
|
||||
for {
|
||||
token = t.next()
|
||||
if token.typ != itemSpace {
|
||||
break
|
||||
}
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
// peekNonSpace returns but does not consume the next non-space token.
|
||||
func (t *Tree) peekNonSpace() (token item) {
|
||||
for {
|
||||
token = t.next()
|
||||
if token.typ != itemSpace {
|
||||
break
|
||||
}
|
||||
}
|
||||
t.backup()
|
||||
return token
|
||||
}
|
||||
|
||||
// Parsing.
|
||||
|
||||
// New allocates a new parse tree with the given name.
|
||||
func New(name string, funcs ...map[string]interface{}) *Tree {
|
||||
return &Tree{
|
||||
Name: name,
|
||||
funcs: funcs,
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorContext returns a textual representation of the location of the node in the input text.
|
||||
// The receiver is only used when the node does not have a pointer to the tree inside,
|
||||
// which can occur in old code.
|
||||
func (t *Tree) ErrorContext(n Node) (location, context string) {
|
||||
pos := int(n.Position())
|
||||
tree := n.tree()
|
||||
if tree == nil {
|
||||
tree = t
|
||||
}
|
||||
text := tree.text[:pos]
|
||||
byteNum := strings.LastIndex(text, "\n")
|
||||
if byteNum == -1 {
|
||||
byteNum = pos // On first line.
|
||||
} else {
|
||||
byteNum++ // After the newline.
|
||||
byteNum = pos - byteNum
|
||||
}
|
||||
lineNum := 1 + strings.Count(text, "\n")
|
||||
context = n.String()
|
||||
if len(context) > 20 {
|
||||
context = fmt.Sprintf("%.20s...", context)
|
||||
}
|
||||
return fmt.Sprintf("%s:%d:%d", tree.ParseName, lineNum, byteNum), context
|
||||
}
|
||||
|
||||
// errorf formats the error and terminates processing.
|
||||
func (t *Tree) errorf(format string, args ...interface{}) {
|
||||
t.Root = nil
|
||||
format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.lex.lineNumber(), format)
|
||||
panic(fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
// error terminates processing.
|
||||
func (t *Tree) error(err error) {
|
||||
t.errorf("%s", err)
|
||||
}
|
||||
|
||||
// expect consumes the next token and guarantees it has the required type.
|
||||
func (t *Tree) expect(expected itemType, context string) item {
|
||||
token := t.nextNonSpace()
|
||||
if token.typ != expected {
|
||||
t.unexpected(token, context)
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
// expectOneOf consumes the next token and guarantees it has one of the required types.
|
||||
func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item {
|
||||
token := t.nextNonSpace()
|
||||
if token.typ != expected1 && token.typ != expected2 {
|
||||
t.unexpected(token, context)
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
// unexpected complains about the token and terminates processing.
|
||||
func (t *Tree) unexpected(token item, context string) {
|
||||
t.errorf("unexpected %s in %s", token, context)
|
||||
}
|
||||
|
||||
// recover is the handler that turns panics into returns from the top level of Parse.
|
||||
func (t *Tree) recover(errp *error) {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
if _, ok := e.(runtime.Error); ok {
|
||||
panic(e)
|
||||
}
|
||||
if t != nil {
|
||||
t.stopParse()
|
||||
}
|
||||
*errp = e.(error)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// startParse initializes the parser, using the lexer.
|
||||
func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer) {
|
||||
t.Root = nil
|
||||
t.lex = lex
|
||||
t.vars = []string{"$"}
|
||||
t.funcs = funcs
|
||||
}
|
||||
|
||||
// stopParse terminates parsing.
|
||||
func (t *Tree) stopParse() {
|
||||
t.lex = nil
|
||||
t.vars = nil
|
||||
t.funcs = nil
|
||||
}
|
||||
|
||||
// Parse parses the template definition string to construct a representation of
|
||||
// the template for execution. If either action delimiter string is empty, the
|
||||
// default ("{{" or "}}") is used. Embedded template definitions are added to
|
||||
// the treeSet map.
|
||||
func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) {
|
||||
defer t.recover(&err)
|
||||
t.ParseName = t.Name
|
||||
t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim))
|
||||
t.text = text
|
||||
t.parse(treeSet)
|
||||
t.add(treeSet)
|
||||
t.stopParse()
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// add adds tree to the treeSet.
|
||||
func (t *Tree) add(treeSet map[string]*Tree) {
|
||||
tree := treeSet[t.Name]
|
||||
if tree == nil || IsEmptyTree(tree.Root) {
|
||||
treeSet[t.Name] = t
|
||||
return
|
||||
}
|
||||
if !IsEmptyTree(t.Root) {
|
||||
t.errorf("template: multiple definition of template %q", t.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// IsEmptyTree reports whether this tree (node) is empty of everything but space.
|
||||
func IsEmptyTree(n Node) bool {
|
||||
switch n := n.(type) {
|
||||
case nil:
|
||||
return true
|
||||
case *ActionNode:
|
||||
case *IfNode:
|
||||
case *ListNode:
|
||||
for _, node := range n.Nodes {
|
||||
if !IsEmptyTree(node) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case *RangeNode:
|
||||
case *TemplateNode:
|
||||
case *TextNode:
|
||||
return len(bytes.TrimSpace(n.Text)) == 0
|
||||
case *WithNode:
|
||||
default:
|
||||
panic("unknown node: " + n.String())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// parse is the top-level parser for a template, essentially the same
|
||||
// as itemList except it also parses {{define}} actions.
|
||||
// It runs to EOF.
|
||||
func (t *Tree) parse(treeSet map[string]*Tree) (next Node) {
|
||||
t.Root = t.newList(t.peek().pos)
|
||||
for t.peek().typ != itemEOF {
|
||||
if t.peek().typ == itemLeftDelim {
|
||||
delim := t.next()
|
||||
if t.nextNonSpace().typ == itemDefine {
|
||||
newT := New("definition") // name will be updated once we know it.
|
||||
newT.text = t.text
|
||||
newT.ParseName = t.ParseName
|
||||
newT.startParse(t.funcs, t.lex)
|
||||
newT.parseDefinition(treeSet)
|
||||
continue
|
||||
}
|
||||
t.backup2(delim)
|
||||
}
|
||||
n := t.textOrAction()
|
||||
if n.Type() == nodeEnd {
|
||||
t.errorf("unexpected %s", n)
|
||||
}
|
||||
t.Root.append(n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseDefinition parses a {{define}} ... {{end}} template definition and
|
||||
// installs the definition in the treeSet map. The "define" keyword has already
|
||||
// been scanned.
|
||||
func (t *Tree) parseDefinition(treeSet map[string]*Tree) {
|
||||
const context = "define clause"
|
||||
name := t.expectOneOf(itemString, itemRawString, context)
|
||||
var err error
|
||||
t.Name, err = strconv.Unquote(name.val)
|
||||
if err != nil {
|
||||
t.error(err)
|
||||
}
|
||||
t.expect(itemRightDelim, context)
|
||||
var end Node
|
||||
t.Root, end = t.itemList()
|
||||
if end.Type() != nodeEnd {
|
||||
t.errorf("unexpected %s in %s", end, context)
|
||||
}
|
||||
t.add(treeSet)
|
||||
t.stopParse()
|
||||
}
|
||||
|
||||
// itemList:
|
||||
// textOrAction*
|
||||
// Terminates at {{end}} or {{else}}, returned separately.
|
||||
func (t *Tree) itemList() (list *ListNode, next Node) {
|
||||
list = t.newList(t.peekNonSpace().pos)
|
||||
for t.peekNonSpace().typ != itemEOF {
|
||||
n := t.textOrAction()
|
||||
switch n.Type() {
|
||||
case nodeEnd, nodeElse:
|
||||
return list, n
|
||||
}
|
||||
list.append(n)
|
||||
}
|
||||
t.errorf("unexpected EOF")
|
||||
return
|
||||
}
|
||||
|
||||
// textOrAction:
|
||||
// text | action
|
||||
func (t *Tree) textOrAction() Node {
|
||||
switch token := t.nextNonSpace(); token.typ {
|
||||
case itemElideNewline:
|
||||
return t.elideNewline()
|
||||
case itemText:
|
||||
return t.newText(token.pos, token.val)
|
||||
case itemLeftDelim:
|
||||
return t.action()
|
||||
default:
|
||||
t.unexpected(token, "input")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// elideNewline:
|
||||
// Remove newlines trailing rightDelim if \\ is present.
|
||||
func (t *Tree) elideNewline() Node {
|
||||
token := t.peek()
|
||||
if token.typ != itemText {
|
||||
t.unexpected(token, "input")
|
||||
return nil
|
||||
}
|
||||
|
||||
t.next()
|
||||
stripped := strings.TrimLeft(token.val, "\n\r")
|
||||
diff := len(token.val) - len(stripped)
|
||||
if diff > 0 {
|
||||
// This is a bit nasty. We mutate the token in-place to remove
|
||||
// preceding newlines.
|
||||
token.pos += Pos(diff)
|
||||
token.val = stripped
|
||||
}
|
||||
return t.newText(token.pos, token.val)
|
||||
}
|
||||
|
||||
// Action:
|
||||
// control
|
||||
// command ("|" command)*
|
||||
// Left delim is past. Now get actions.
|
||||
// First word could be a keyword such as range.
|
||||
func (t *Tree) action() (n Node) {
|
||||
switch token := t.nextNonSpace(); token.typ {
|
||||
case itemElse:
|
||||
return t.elseControl()
|
||||
case itemEnd:
|
||||
return t.endControl()
|
||||
case itemIf:
|
||||
return t.ifControl()
|
||||
case itemRange:
|
||||
return t.rangeControl()
|
||||
case itemTemplate:
|
||||
return t.templateControl()
|
||||
case itemWith:
|
||||
return t.withControl()
|
||||
}
|
||||
t.backup()
|
||||
// Do not pop variables; they persist until "end".
|
||||
return t.newAction(t.peek().pos, t.lex.lineNumber(), t.pipeline("command"))
|
||||
}
|
||||
|
||||
// Pipeline:
|
||||
// declarations? command ('|' command)*
|
||||
func (t *Tree) pipeline(context string) (pipe *PipeNode) {
|
||||
var decl []*VariableNode
|
||||
pos := t.peekNonSpace().pos
|
||||
// Are there declarations?
|
||||
for {
|
||||
if v := t.peekNonSpace(); v.typ == itemVariable {
|
||||
t.next()
|
||||
// Since space is a token, we need 3-token look-ahead here in the worst case:
|
||||
// in "$x foo" we need to read "foo" (as opposed to ":=") to know that $x is an
|
||||
// argument variable rather than a declaration. So remember the token
|
||||
// adjacent to the variable so we can push it back if necessary.
|
||||
tokenAfterVariable := t.peek()
|
||||
if next := t.peekNonSpace(); next.typ == itemColonEquals || (next.typ == itemChar && next.val == ",") {
|
||||
t.nextNonSpace()
|
||||
variable := t.newVariable(v.pos, v.val)
|
||||
decl = append(decl, variable)
|
||||
t.vars = append(t.vars, v.val)
|
||||
if next.typ == itemChar && next.val == "," {
|
||||
if context == "range" && len(decl) < 2 {
|
||||
continue
|
||||
}
|
||||
t.errorf("too many declarations in %s", context)
|
||||
}
|
||||
} else if tokenAfterVariable.typ == itemSpace {
|
||||
t.backup3(v, tokenAfterVariable)
|
||||
} else {
|
||||
t.backup2(v)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
pipe = t.newPipeline(pos, t.lex.lineNumber(), decl)
|
||||
for {
|
||||
switch token := t.nextNonSpace(); token.typ {
|
||||
case itemRightDelim, itemRightParen:
|
||||
if len(pipe.Cmds) == 0 {
|
||||
t.errorf("missing value for %s", context)
|
||||
}
|
||||
if token.typ == itemRightParen {
|
||||
t.backup()
|
||||
}
|
||||
return
|
||||
case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier,
|
||||
itemNumber, itemNil, itemRawString, itemString, itemVariable, itemLeftParen:
|
||||
t.backup()
|
||||
pipe.append(t.command())
|
||||
default:
|
||||
t.unexpected(token, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) {
|
||||
defer t.popVars(len(t.vars))
|
||||
line = t.lex.lineNumber()
|
||||
pipe = t.pipeline(context)
|
||||
var next Node
|
||||
list, next = t.itemList()
|
||||
switch next.Type() {
|
||||
case nodeEnd: //done
|
||||
case nodeElse:
|
||||
if allowElseIf {
|
||||
// Special case for "else if". If the "else" is followed immediately by an "if",
|
||||
// the elseControl will have left the "if" token pending. Treat
|
||||
// {{if a}}_{{else if b}}_{{end}}
|
||||
// as
|
||||
// {{if a}}_{{else}}{{if b}}_{{end}}{{end}}.
|
||||
// To do this, parse the if as usual and stop at it {{end}}; the subsequent{{end}}
|
||||
// is assumed. This technique works even for long if-else-if chains.
|
||||
// TODO: Should we allow else-if in with and range?
|
||||
if t.peek().typ == itemIf {
|
||||
t.next() // Consume the "if" token.
|
||||
elseList = t.newList(next.Position())
|
||||
elseList.append(t.ifControl())
|
||||
// Do not consume the next item - only one {{end}} required.
|
||||
break
|
||||
}
|
||||
}
|
||||
elseList, next = t.itemList()
|
||||
if next.Type() != nodeEnd {
|
||||
t.errorf("expected end; found %s", next)
|
||||
}
|
||||
}
|
||||
return pipe.Position(), line, pipe, list, elseList
|
||||
}
|
||||
|
||||
// If:
|
||||
// {{if pipeline}} itemList {{end}}
|
||||
// {{if pipeline}} itemList {{else}} itemList {{end}}
|
||||
// If keyword is past.
|
||||
func (t *Tree) ifControl() Node {
|
||||
return t.newIf(t.parseControl(true, "if"))
|
||||
}
|
||||
|
||||
// Range:
|
||||
// {{range pipeline}} itemList {{end}}
|
||||
// {{range pipeline}} itemList {{else}} itemList {{end}}
|
||||
// Range keyword is past.
|
||||
func (t *Tree) rangeControl() Node {
|
||||
return t.newRange(t.parseControl(false, "range"))
|
||||
}
|
||||
|
||||
// With:
|
||||
// {{with pipeline}} itemList {{end}}
|
||||
// {{with pipeline}} itemList {{else}} itemList {{end}}
|
||||
// If keyword is past.
|
||||
func (t *Tree) withControl() Node {
|
||||
return t.newWith(t.parseControl(false, "with"))
|
||||
}
|
||||
|
||||
// End:
|
||||
// {{end}}
|
||||
// End keyword is past.
|
||||
func (t *Tree) endControl() Node {
|
||||
return t.newEnd(t.expect(itemRightDelim, "end").pos)
|
||||
}
|
||||
|
||||
// Else:
|
||||
// {{else}}
|
||||
// Else keyword is past.
|
||||
func (t *Tree) elseControl() Node {
|
||||
// Special case for "else if".
|
||||
peek := t.peekNonSpace()
|
||||
if peek.typ == itemIf {
|
||||
// We see "{{else if ... " but in effect rewrite it to {{else}}{{if ... ".
|
||||
return t.newElse(peek.pos, t.lex.lineNumber())
|
||||
}
|
||||
return t.newElse(t.expect(itemRightDelim, "else").pos, t.lex.lineNumber())
|
||||
}
|
||||
|
||||
// Template:
|
||||
// {{template stringValue pipeline}}
|
||||
// Template keyword is past. The name must be something that can evaluate
|
||||
// to a string.
|
||||
func (t *Tree) templateControl() Node {
|
||||
var name string
|
||||
token := t.nextNonSpace()
|
||||
switch token.typ {
|
||||
case itemString, itemRawString:
|
||||
s, err := strconv.Unquote(token.val)
|
||||
if err != nil {
|
||||
t.error(err)
|
||||
}
|
||||
name = s
|
||||
default:
|
||||
t.unexpected(token, "template invocation")
|
||||
}
|
||||
var pipe *PipeNode
|
||||
if t.nextNonSpace().typ != itemRightDelim {
|
||||
t.backup()
|
||||
// Do not pop variables; they persist until "end".
|
||||
pipe = t.pipeline("template")
|
||||
}
|
||||
return t.newTemplate(token.pos, t.lex.lineNumber(), name, pipe)
|
||||
}
|
||||
|
||||
// command:
|
||||
// operand (space operand)*
|
||||
// space-separated arguments up to a pipeline character or right delimiter.
|
||||
// we consume the pipe character but leave the right delim to terminate the action.
|
||||
func (t *Tree) command() *CommandNode {
|
||||
cmd := t.newCommand(t.peekNonSpace().pos)
|
||||
for {
|
||||
t.peekNonSpace() // skip leading spaces.
|
||||
operand := t.operand()
|
||||
if operand != nil {
|
||||
cmd.append(operand)
|
||||
}
|
||||
switch token := t.next(); token.typ {
|
||||
case itemSpace:
|
||||
continue
|
||||
case itemError:
|
||||
t.errorf("%s", token.val)
|
||||
case itemRightDelim, itemRightParen:
|
||||
t.backup()
|
||||
case itemPipe:
|
||||
default:
|
||||
t.errorf("unexpected %s in operand; missing space?", token)
|
||||
}
|
||||
break
|
||||
}
|
||||
if len(cmd.Args) == 0 {
|
||||
t.errorf("empty command")
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// operand:
|
||||
// term .Field*
|
||||
// An operand is a space-separated component of a command,
|
||||
// a term possibly followed by field accesses.
|
||||
// A nil return means the next item is not an operand.
|
||||
func (t *Tree) operand() Node {
|
||||
node := t.term()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
if t.peek().typ == itemField {
|
||||
chain := t.newChain(t.peek().pos, node)
|
||||
for t.peek().typ == itemField {
|
||||
chain.Add(t.next().val)
|
||||
}
|
||||
// Compatibility with original API: If the term is of type NodeField
|
||||
// or NodeVariable, just put more fields on the original.
|
||||
// Otherwise, keep the Chain node.
|
||||
// TODO: Switch to Chains always when we can.
|
||||
switch node.Type() {
|
||||
case NodeField:
|
||||
node = t.newField(chain.Position(), chain.String())
|
||||
case NodeVariable:
|
||||
node = t.newVariable(chain.Position(), chain.String())
|
||||
default:
|
||||
node = chain
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// term:
|
||||
// literal (number, string, nil, boolean)
|
||||
// function (identifier)
|
||||
// .
|
||||
// .Field
|
||||
// $
|
||||
// '(' pipeline ')'
|
||||
// A term is a simple "expression".
|
||||
// A nil return means the next item is not a term.
|
||||
func (t *Tree) term() Node {
|
||||
switch token := t.nextNonSpace(); token.typ {
|
||||
case itemError:
|
||||
t.errorf("%s", token.val)
|
||||
case itemIdentifier:
|
||||
if !t.hasFunction(token.val) {
|
||||
t.errorf("function %q not defined", token.val)
|
||||
}
|
||||
return NewIdentifier(token.val).SetTree(t).SetPos(token.pos)
|
||||
case itemDot:
|
||||
return t.newDot(token.pos)
|
||||
case itemNil:
|
||||
return t.newNil(token.pos)
|
||||
case itemVariable:
|
||||
return t.useVar(token.pos, token.val)
|
||||
case itemField:
|
||||
return t.newField(token.pos, token.val)
|
||||
case itemBool:
|
||||
return t.newBool(token.pos, token.val == "true")
|
||||
case itemCharConstant, itemComplex, itemNumber:
|
||||
number, err := t.newNumber(token.pos, token.val, token.typ)
|
||||
if err != nil {
|
||||
t.error(err)
|
||||
}
|
||||
return number
|
||||
case itemLeftParen:
|
||||
pipe := t.pipeline("parenthesized pipeline")
|
||||
if token := t.next(); token.typ != itemRightParen {
|
||||
t.errorf("unclosed right paren: unexpected %s", token)
|
||||
}
|
||||
return pipe
|
||||
case itemString, itemRawString:
|
||||
s, err := strconv.Unquote(token.val)
|
||||
if err != nil {
|
||||
t.error(err)
|
||||
}
|
||||
return t.newString(token.pos, token.val, s)
|
||||
}
|
||||
t.backup()
|
||||
return nil
|
||||
}
|
||||
|
||||
// hasFunction reports if a function name exists in the Tree's maps.
|
||||
func (t *Tree) hasFunction(name string) bool {
|
||||
for _, funcMap := range t.funcs {
|
||||
if funcMap == nil {
|
||||
continue
|
||||
}
|
||||
if funcMap[name] != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// popVars trims the variable list to the specified length
|
||||
func (t *Tree) popVars(n int) {
|
||||
t.vars = t.vars[:n]
|
||||
}
|
||||
|
||||
// useVar returns a node for a variable reference. It errors if the
|
||||
// variable is not defined.
|
||||
func (t *Tree) useVar(pos Pos, name string) Node {
|
||||
v := t.newVariable(pos, name)
|
||||
for _, varName := range t.vars {
|
||||
if varName == v.Ident[0] {
|
||||
return v
|
||||
}
|
||||
}
|
||||
t.errorf("undefined variable %q", v.Ident[0])
|
||||
return nil
|
||||
}
|
||||
-426
@@ -1,426 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var debug = flag.Bool("debug", false, "show the errors produced by the main tests")
|
||||
|
||||
type numberTest struct {
|
||||
text string
|
||||
isInt bool
|
||||
isUint bool
|
||||
isFloat bool
|
||||
isComplex bool
|
||||
int64
|
||||
uint64
|
||||
float64
|
||||
complex128
|
||||
}
|
||||
|
||||
var numberTests = []numberTest{
|
||||
// basics
|
||||
{"0", true, true, true, false, 0, 0, 0, 0},
|
||||
{"-0", true, true, true, false, 0, 0, 0, 0}, // check that -0 is a uint.
|
||||
{"73", true, true, true, false, 73, 73, 73, 0},
|
||||
{"073", true, true, true, false, 073, 073, 073, 0},
|
||||
{"0x73", true, true, true, false, 0x73, 0x73, 0x73, 0},
|
||||
{"-73", true, false, true, false, -73, 0, -73, 0},
|
||||
{"+73", true, false, true, false, 73, 0, 73, 0},
|
||||
{"100", true, true, true, false, 100, 100, 100, 0},
|
||||
{"1e9", true, true, true, false, 1e9, 1e9, 1e9, 0},
|
||||
{"-1e9", true, false, true, false, -1e9, 0, -1e9, 0},
|
||||
{"-1.2", false, false, true, false, 0, 0, -1.2, 0},
|
||||
{"1e19", false, true, true, false, 0, 1e19, 1e19, 0},
|
||||
{"-1e19", false, false, true, false, 0, 0, -1e19, 0},
|
||||
{"4i", false, false, false, true, 0, 0, 0, 4i},
|
||||
{"-1.2+4.2i", false, false, false, true, 0, 0, 0, -1.2 + 4.2i},
|
||||
{"073i", false, false, false, true, 0, 0, 0, 73i}, // not octal!
|
||||
// complex with 0 imaginary are float (and maybe integer)
|
||||
{"0i", true, true, true, true, 0, 0, 0, 0},
|
||||
{"-1.2+0i", false, false, true, true, 0, 0, -1.2, -1.2},
|
||||
{"-12+0i", true, false, true, true, -12, 0, -12, -12},
|
||||
{"13+0i", true, true, true, true, 13, 13, 13, 13},
|
||||
// funny bases
|
||||
{"0123", true, true, true, false, 0123, 0123, 0123, 0},
|
||||
{"-0x0", true, true, true, false, 0, 0, 0, 0},
|
||||
{"0xdeadbeef", true, true, true, false, 0xdeadbeef, 0xdeadbeef, 0xdeadbeef, 0},
|
||||
// character constants
|
||||
{`'a'`, true, true, true, false, 'a', 'a', 'a', 0},
|
||||
{`'\n'`, true, true, true, false, '\n', '\n', '\n', 0},
|
||||
{`'\\'`, true, true, true, false, '\\', '\\', '\\', 0},
|
||||
{`'\''`, true, true, true, false, '\'', '\'', '\'', 0},
|
||||
{`'\xFF'`, true, true, true, false, 0xFF, 0xFF, 0xFF, 0},
|
||||
{`'パ'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0},
|
||||
{`'\u30d1'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0},
|
||||
{`'\U000030d1'`, true, true, true, false, 0x30d1, 0x30d1, 0x30d1, 0},
|
||||
// some broken syntax
|
||||
{text: "+-2"},
|
||||
{text: "0x123."},
|
||||
{text: "1e."},
|
||||
{text: "0xi."},
|
||||
{text: "1+2."},
|
||||
{text: "'x"},
|
||||
{text: "'xx'"},
|
||||
// Issue 8622 - 0xe parsed as floating point. Very embarrassing.
|
||||
{"0xef", true, true, true, false, 0xef, 0xef, 0xef, 0},
|
||||
}
|
||||
|
||||
func TestNumberParse(t *testing.T) {
|
||||
for _, test := range numberTests {
|
||||
// If fmt.Sscan thinks it's complex, it's complex. We can't trust the output
|
||||
// because imaginary comes out as a number.
|
||||
var c complex128
|
||||
typ := itemNumber
|
||||
var tree *Tree
|
||||
if test.text[0] == '\'' {
|
||||
typ = itemCharConstant
|
||||
} else {
|
||||
_, err := fmt.Sscan(test.text, &c)
|
||||
if err == nil {
|
||||
typ = itemComplex
|
||||
}
|
||||
}
|
||||
n, err := tree.newNumber(0, test.text, typ)
|
||||
ok := test.isInt || test.isUint || test.isFloat || test.isComplex
|
||||
if ok && err != nil {
|
||||
t.Errorf("unexpected error for %q: %s", test.text, err)
|
||||
continue
|
||||
}
|
||||
if !ok && err == nil {
|
||||
t.Errorf("expected error for %q", test.text)
|
||||
continue
|
||||
}
|
||||
if !ok {
|
||||
if *debug {
|
||||
fmt.Printf("%s\n\t%s\n", test.text, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if n.IsComplex != test.isComplex {
|
||||
t.Errorf("complex incorrect for %q; should be %t", test.text, test.isComplex)
|
||||
}
|
||||
if test.isInt {
|
||||
if !n.IsInt {
|
||||
t.Errorf("expected integer for %q", test.text)
|
||||
}
|
||||
if n.Int64 != test.int64 {
|
||||
t.Errorf("int64 for %q should be %d Is %d", test.text, test.int64, n.Int64)
|
||||
}
|
||||
} else if n.IsInt {
|
||||
t.Errorf("did not expect integer for %q", test.text)
|
||||
}
|
||||
if test.isUint {
|
||||
if !n.IsUint {
|
||||
t.Errorf("expected unsigned integer for %q", test.text)
|
||||
}
|
||||
if n.Uint64 != test.uint64 {
|
||||
t.Errorf("uint64 for %q should be %d Is %d", test.text, test.uint64, n.Uint64)
|
||||
}
|
||||
} else if n.IsUint {
|
||||
t.Errorf("did not expect unsigned integer for %q", test.text)
|
||||
}
|
||||
if test.isFloat {
|
||||
if !n.IsFloat {
|
||||
t.Errorf("expected float for %q", test.text)
|
||||
}
|
||||
if n.Float64 != test.float64 {
|
||||
t.Errorf("float64 for %q should be %g Is %g", test.text, test.float64, n.Float64)
|
||||
}
|
||||
} else if n.IsFloat {
|
||||
t.Errorf("did not expect float for %q", test.text)
|
||||
}
|
||||
if test.isComplex {
|
||||
if !n.IsComplex {
|
||||
t.Errorf("expected complex for %q", test.text)
|
||||
}
|
||||
if n.Complex128 != test.complex128 {
|
||||
t.Errorf("complex128 for %q should be %g Is %g", test.text, test.complex128, n.Complex128)
|
||||
}
|
||||
} else if n.IsComplex {
|
||||
t.Errorf("did not expect complex for %q", test.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type parseTest struct {
|
||||
name string
|
||||
input string
|
||||
ok bool
|
||||
result string // what the user would see in an error message.
|
||||
}
|
||||
|
||||
const (
|
||||
noError = true
|
||||
hasError = false
|
||||
)
|
||||
|
||||
var parseTests = []parseTest{
|
||||
{"empty", "", noError,
|
||||
``},
|
||||
{"comment", "{{/*\n\n\n*/}}", noError,
|
||||
``},
|
||||
{"spaces", " \t\n", noError,
|
||||
`" \t\n"`},
|
||||
{"text", "some text", noError,
|
||||
`"some text"`},
|
||||
{"emptyAction", "{{}}", hasError,
|
||||
`{{}}`},
|
||||
{"field", "{{.X}}", noError,
|
||||
`{{.X}}`},
|
||||
{"simple command", "{{printf}}", noError,
|
||||
`{{printf}}`},
|
||||
{"$ invocation", "{{$}}", noError,
|
||||
"{{$}}"},
|
||||
{"variable invocation", "{{with $x := 3}}{{$x 23}}{{end}}", noError,
|
||||
"{{with $x := 3}}{{$x 23}}{{end}}"},
|
||||
{"variable with fields", "{{$.I}}", noError,
|
||||
"{{$.I}}"},
|
||||
{"multi-word command", "{{printf `%d` 23}}", noError,
|
||||
"{{printf `%d` 23}}"},
|
||||
{"pipeline", "{{.X|.Y}}", noError,
|
||||
`{{.X | .Y}}`},
|
||||
{"pipeline with decl", "{{$x := .X|.Y}}", noError,
|
||||
`{{$x := .X | .Y}}`},
|
||||
{"nested pipeline", "{{.X (.Y .Z) (.A | .B .C) (.E)}}", noError,
|
||||
`{{.X (.Y .Z) (.A | .B .C) (.E)}}`},
|
||||
{"field applied to parentheses", "{{(.Y .Z).Field}}", noError,
|
||||
`{{(.Y .Z).Field}}`},
|
||||
{"simple if", "{{if .X}}hello{{end}}", noError,
|
||||
`{{if .X}}"hello"{{end}}`},
|
||||
{"if with else", "{{if .X}}true{{else}}false{{end}}", noError,
|
||||
`{{if .X}}"true"{{else}}"false"{{end}}`},
|
||||
{"if with else if", "{{if .X}}true{{else if .Y}}false{{end}}", noError,
|
||||
`{{if .X}}"true"{{else}}{{if .Y}}"false"{{end}}{{end}}`},
|
||||
{"if else chain", "+{{if .X}}X{{else if .Y}}Y{{else if .Z}}Z{{end}}+", noError,
|
||||
`"+"{{if .X}}"X"{{else}}{{if .Y}}"Y"{{else}}{{if .Z}}"Z"{{end}}{{end}}{{end}}"+"`},
|
||||
{"simple range", "{{range .X}}hello{{end}}", noError,
|
||||
`{{range .X}}"hello"{{end}}`},
|
||||
{"chained field range", "{{range .X.Y.Z}}hello{{end}}", noError,
|
||||
`{{range .X.Y.Z}}"hello"{{end}}`},
|
||||
{"nested range", "{{range .X}}hello{{range .Y}}goodbye{{end}}{{end}}", noError,
|
||||
`{{range .X}}"hello"{{range .Y}}"goodbye"{{end}}{{end}}`},
|
||||
{"range with else", "{{range .X}}true{{else}}false{{end}}", noError,
|
||||
`{{range .X}}"true"{{else}}"false"{{end}}`},
|
||||
{"range over pipeline", "{{range .X|.M}}true{{else}}false{{end}}", noError,
|
||||
`{{range .X | .M}}"true"{{else}}"false"{{end}}`},
|
||||
{"range []int", "{{range .SI}}{{.}}{{end}}", noError,
|
||||
`{{range .SI}}{{.}}{{end}}`},
|
||||
{"range 1 var", "{{range $x := .SI}}{{.}}{{end}}", noError,
|
||||
`{{range $x := .SI}}{{.}}{{end}}`},
|
||||
{"range 2 vars", "{{range $x, $y := .SI}}{{.}}{{end}}", noError,
|
||||
`{{range $x, $y := .SI}}{{.}}{{end}}`},
|
||||
{"constants", "{{range .SI 1 -3.2i true false 'a' nil}}{{end}}", noError,
|
||||
`{{range .SI 1 -3.2i true false 'a' nil}}{{end}}`},
|
||||
{"template", "{{template `x`}}", noError,
|
||||
`{{template "x"}}`},
|
||||
{"template with arg", "{{template `x` .Y}}", noError,
|
||||
`{{template "x" .Y}}`},
|
||||
{"with", "{{with .X}}hello{{end}}", noError,
|
||||
`{{with .X}}"hello"{{end}}`},
|
||||
{"with with else", "{{with .X}}hello{{else}}goodbye{{end}}", noError,
|
||||
`{{with .X}}"hello"{{else}}"goodbye"{{end}}`},
|
||||
{"elide newline", "{{true}}\\\n ", noError,
|
||||
`{{true}}" "`},
|
||||
// Errors.
|
||||
{"unclosed action", "hello{{range", hasError, ""},
|
||||
{"unmatched end", "{{end}}", hasError, ""},
|
||||
{"missing end", "hello{{range .x}}", hasError, ""},
|
||||
{"missing end after else", "hello{{range .x}}{{else}}", hasError, ""},
|
||||
{"undefined function", "hello{{undefined}}", hasError, ""},
|
||||
{"undefined variable", "{{$x}}", hasError, ""},
|
||||
{"variable undefined after end", "{{with $x := 4}}{{end}}{{$x}}", hasError, ""},
|
||||
{"variable undefined in template", "{{template $v}}", hasError, ""},
|
||||
{"declare with field", "{{with $x.Y := 4}}{{end}}", hasError, ""},
|
||||
{"template with field ref", "{{template .X}}", hasError, ""},
|
||||
{"template with var", "{{template $v}}", hasError, ""},
|
||||
{"invalid punctuation", "{{printf 3, 4}}", hasError, ""},
|
||||
{"multidecl outside range", "{{with $v, $u := 3}}{{end}}", hasError, ""},
|
||||
{"too many decls in range", "{{range $u, $v, $w := 3}}{{end}}", hasError, ""},
|
||||
{"dot applied to parentheses", "{{printf (printf .).}}", hasError, ""},
|
||||
{"adjacent args", "{{printf 3`x`}}", hasError, ""},
|
||||
{"adjacent args with .", "{{printf `x`.}}", hasError, ""},
|
||||
{"extra end after if", "{{if .X}}a{{else if .Y}}b{{end}}{{end}}", hasError, ""},
|
||||
{"invalid newline elision", "{{true}}\\{{true}}", hasError, ""},
|
||||
// Equals (and other chars) do not assignments make (yet).
|
||||
{"bug0a", "{{$x := 0}}{{$x}}", noError, "{{$x := 0}}{{$x}}"},
|
||||
{"bug0b", "{{$x = 1}}{{$x}}", hasError, ""},
|
||||
{"bug0c", "{{$x ! 2}}{{$x}}", hasError, ""},
|
||||
{"bug0d", "{{$x % 3}}{{$x}}", hasError, ""},
|
||||
// Check the parse fails for := rather than comma.
|
||||
{"bug0e", "{{range $x := $y := 3}}{{end}}", hasError, ""},
|
||||
// Another bug: variable read must ignore following punctuation.
|
||||
{"bug1a", "{{$x:=.}}{{$x!2}}", hasError, ""}, // ! is just illegal here.
|
||||
{"bug1b", "{{$x:=.}}{{$x+2}}", hasError, ""}, // $x+2 should not parse as ($x) (+2).
|
||||
{"bug1c", "{{$x:=.}}{{$x +2}}", noError, "{{$x := .}}{{$x +2}}"}, // It's OK with a space.
|
||||
}
|
||||
|
||||
var builtins = map[string]interface{}{
|
||||
"printf": fmt.Sprintf,
|
||||
}
|
||||
|
||||
func testParse(doCopy bool, t *testing.T) {
|
||||
textFormat = "%q"
|
||||
defer func() { textFormat = "%s" }()
|
||||
for _, test := range parseTests {
|
||||
tmpl, err := New(test.name).Parse(test.input, "", "", make(map[string]*Tree), builtins)
|
||||
switch {
|
||||
case err == nil && !test.ok:
|
||||
t.Errorf("%q: expected error; got none", test.name)
|
||||
continue
|
||||
case err != nil && test.ok:
|
||||
t.Errorf("%q: unexpected error: %v", test.name, err)
|
||||
continue
|
||||
case err != nil && !test.ok:
|
||||
// expected error, got one
|
||||
if *debug {
|
||||
fmt.Printf("%s: %s\n\t%s\n", test.name, test.input, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
var result string
|
||||
if doCopy {
|
||||
result = tmpl.Root.Copy().String()
|
||||
} else {
|
||||
result = tmpl.Root.String()
|
||||
}
|
||||
if result != test.result {
|
||||
t.Errorf("%s=(%q): got\n\t%v\nexpected\n\t%v", test.name, test.input, result, test.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
testParse(false, t)
|
||||
}
|
||||
|
||||
// Same as TestParse, but we copy the node first
|
||||
func TestParseCopy(t *testing.T) {
|
||||
testParse(true, t)
|
||||
}
|
||||
|
||||
type isEmptyTest struct {
|
||||
name string
|
||||
input string
|
||||
empty bool
|
||||
}
|
||||
|
||||
var isEmptyTests = []isEmptyTest{
|
||||
{"empty", ``, true},
|
||||
{"nonempty", `hello`, false},
|
||||
{"spaces only", " \t\n \t\n", true},
|
||||
{"definition", `{{define "x"}}something{{end}}`, true},
|
||||
{"definitions and space", "{{define `x`}}something{{end}}\n\n{{define `y`}}something{{end}}\n\n", true},
|
||||
{"definitions and text", "{{define `x`}}something{{end}}\nx\n{{define `y`}}something{{end}}\ny\n", false},
|
||||
{"definition and action", "{{define `x`}}something{{end}}{{if 3}}foo{{end}}", false},
|
||||
}
|
||||
|
||||
func TestIsEmpty(t *testing.T) {
|
||||
if !IsEmptyTree(nil) {
|
||||
t.Errorf("nil tree is not empty")
|
||||
}
|
||||
for _, test := range isEmptyTests {
|
||||
tree, err := New("root").Parse(test.input, "", "", make(map[string]*Tree), nil)
|
||||
if err != nil {
|
||||
t.Errorf("%q: unexpected error: %v", test.name, err)
|
||||
continue
|
||||
}
|
||||
if empty := IsEmptyTree(tree.Root); empty != test.empty {
|
||||
t.Errorf("%q: expected %t got %t", test.name, test.empty, empty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorContextWithTreeCopy(t *testing.T) {
|
||||
tree, err := New("root").Parse("{{if true}}{{end}}", "", "", make(map[string]*Tree), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected tree parse failure: %v", err)
|
||||
}
|
||||
treeCopy := tree.Copy()
|
||||
wantLocation, wantContext := tree.ErrorContext(tree.Root.Nodes[0])
|
||||
gotLocation, gotContext := treeCopy.ErrorContext(treeCopy.Root.Nodes[0])
|
||||
if wantLocation != gotLocation {
|
||||
t.Errorf("wrong error location want %q got %q", wantLocation, gotLocation)
|
||||
}
|
||||
if wantContext != gotContext {
|
||||
t.Errorf("wrong error location want %q got %q", wantContext, gotContext)
|
||||
}
|
||||
}
|
||||
|
||||
// All failures, and the result is a string that must appear in the error message.
|
||||
var errorTests = []parseTest{
|
||||
// Check line numbers are accurate.
|
||||
{"unclosed1",
|
||||
"line1\n{{",
|
||||
hasError, `unclosed1:2: unexpected unclosed action in command`},
|
||||
{"unclosed2",
|
||||
"line1\n{{define `x`}}line2\n{{",
|
||||
hasError, `unclosed2:3: unexpected unclosed action in command`},
|
||||
// Specific errors.
|
||||
{"function",
|
||||
"{{foo}}",
|
||||
hasError, `function "foo" not defined`},
|
||||
{"comment",
|
||||
"{{/*}}",
|
||||
hasError, `unclosed comment`},
|
||||
{"lparen",
|
||||
"{{.X (1 2 3}}",
|
||||
hasError, `unclosed left paren`},
|
||||
{"rparen",
|
||||
"{{.X 1 2 3)}}",
|
||||
hasError, `unexpected ")"`},
|
||||
{"space",
|
||||
"{{`x`3}}",
|
||||
hasError, `missing space?`},
|
||||
{"idchar",
|
||||
"{{a#}}",
|
||||
hasError, `'#'`},
|
||||
{"charconst",
|
||||
"{{'a}}",
|
||||
hasError, `unterminated character constant`},
|
||||
{"stringconst",
|
||||
`{{"a}}`,
|
||||
hasError, `unterminated quoted string`},
|
||||
{"rawstringconst",
|
||||
"{{`a}}",
|
||||
hasError, `unterminated raw quoted string`},
|
||||
{"number",
|
||||
"{{0xi}}",
|
||||
hasError, `number syntax`},
|
||||
{"multidefine",
|
||||
"{{define `a`}}a{{end}}{{define `a`}}b{{end}}",
|
||||
hasError, `multiple definition of template`},
|
||||
{"eof",
|
||||
"{{range .X}}",
|
||||
hasError, `unexpected EOF`},
|
||||
{"variable",
|
||||
// Declare $x so it's defined, to avoid that error, and then check we don't parse a declaration.
|
||||
"{{$x := 23}}{{with $x.y := 3}}{{$x 23}}{{end}}",
|
||||
hasError, `unexpected ":="`},
|
||||
{"multidecl",
|
||||
"{{$a,$b,$c := 23}}",
|
||||
hasError, `too many declarations`},
|
||||
{"undefvar",
|
||||
"{{$a}}",
|
||||
hasError, `undefined variable`},
|
||||
}
|
||||
|
||||
func TestErrors(t *testing.T) {
|
||||
for _, test := range errorTests {
|
||||
_, err := New(test.name).Parse(test.input, "", "", make(map[string]*Tree))
|
||||
if err == nil {
|
||||
t.Errorf("%q: expected error", test.name)
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(err.Error(), test.result) {
|
||||
t.Errorf("%q: error %q does not contain %q", test.name, err, test.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
-218
@@ -1,218 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/alecthomas/template/parse"
|
||||
)
|
||||
|
||||
// common holds the information shared by related templates.
|
||||
type common struct {
|
||||
tmpl map[string]*Template
|
||||
// We use two maps, one for parsing and one for execution.
|
||||
// This separation makes the API cleaner since it doesn't
|
||||
// expose reflection to the client.
|
||||
parseFuncs FuncMap
|
||||
execFuncs map[string]reflect.Value
|
||||
}
|
||||
|
||||
// Template is the representation of a parsed template. The *parse.Tree
|
||||
// field is exported only for use by html/template and should be treated
|
||||
// as unexported by all other clients.
|
||||
type Template struct {
|
||||
name string
|
||||
*parse.Tree
|
||||
*common
|
||||
leftDelim string
|
||||
rightDelim string
|
||||
}
|
||||
|
||||
// New allocates a new template with the given name.
|
||||
func New(name string) *Template {
|
||||
return &Template{
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the name of the template.
|
||||
func (t *Template) Name() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
// New allocates a new template associated with the given one and with the same
|
||||
// delimiters. The association, which is transitive, allows one template to
|
||||
// invoke another with a {{template}} action.
|
||||
func (t *Template) New(name string) *Template {
|
||||
t.init()
|
||||
return &Template{
|
||||
name: name,
|
||||
common: t.common,
|
||||
leftDelim: t.leftDelim,
|
||||
rightDelim: t.rightDelim,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Template) init() {
|
||||
if t.common == nil {
|
||||
t.common = new(common)
|
||||
t.tmpl = make(map[string]*Template)
|
||||
t.parseFuncs = make(FuncMap)
|
||||
t.execFuncs = make(map[string]reflect.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a duplicate of the template, including all associated
|
||||
// templates. The actual representation is not copied, but the name space of
|
||||
// associated templates is, so further calls to Parse in the copy will add
|
||||
// templates to the copy but not to the original. Clone can be used to prepare
|
||||
// common templates and use them with variant definitions for other templates
|
||||
// by adding the variants after the clone is made.
|
||||
func (t *Template) Clone() (*Template, error) {
|
||||
nt := t.copy(nil)
|
||||
nt.init()
|
||||
nt.tmpl[t.name] = nt
|
||||
for k, v := range t.tmpl {
|
||||
if k == t.name { // Already installed.
|
||||
continue
|
||||
}
|
||||
// The associated templates share nt's common structure.
|
||||
tmpl := v.copy(nt.common)
|
||||
nt.tmpl[k] = tmpl
|
||||
}
|
||||
for k, v := range t.parseFuncs {
|
||||
nt.parseFuncs[k] = v
|
||||
}
|
||||
for k, v := range t.execFuncs {
|
||||
nt.execFuncs[k] = v
|
||||
}
|
||||
return nt, nil
|
||||
}
|
||||
|
||||
// copy returns a shallow copy of t, with common set to the argument.
|
||||
func (t *Template) copy(c *common) *Template {
|
||||
nt := New(t.name)
|
||||
nt.Tree = t.Tree
|
||||
nt.common = c
|
||||
nt.leftDelim = t.leftDelim
|
||||
nt.rightDelim = t.rightDelim
|
||||
return nt
|
||||
}
|
||||
|
||||
// AddParseTree creates a new template with the name and parse tree
|
||||
// and associates it with t.
|
||||
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
|
||||
if t.common != nil && t.tmpl[name] != nil {
|
||||
return nil, fmt.Errorf("template: redefinition of template %q", name)
|
||||
}
|
||||
nt := t.New(name)
|
||||
nt.Tree = tree
|
||||
t.tmpl[name] = nt
|
||||
return nt, nil
|
||||
}
|
||||
|
||||
// Templates returns a slice of the templates associated with t, including t
|
||||
// itself.
|
||||
func (t *Template) Templates() []*Template {
|
||||
if t.common == nil {
|
||||
return nil
|
||||
}
|
||||
// Return a slice so we don't expose the map.
|
||||
m := make([]*Template, 0, len(t.tmpl))
|
||||
for _, v := range t.tmpl {
|
||||
m = append(m, v)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Delims sets the action delimiters to the specified strings, to be used in
|
||||
// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
|
||||
// definitions will inherit the settings. An empty delimiter stands for the
|
||||
// corresponding default: {{ or }}.
|
||||
// The return value is the template, so calls can be chained.
|
||||
func (t *Template) Delims(left, right string) *Template {
|
||||
t.leftDelim = left
|
||||
t.rightDelim = right
|
||||
return t
|
||||
}
|
||||
|
||||
// Funcs adds the elements of the argument map to the template's function map.
|
||||
// It panics if a value in the map is not a function with appropriate return
|
||||
// type. However, it is legal to overwrite elements of the map. The return
|
||||
// value is the template, so calls can be chained.
|
||||
func (t *Template) Funcs(funcMap FuncMap) *Template {
|
||||
t.init()
|
||||
addValueFuncs(t.execFuncs, funcMap)
|
||||
addFuncs(t.parseFuncs, funcMap)
|
||||
return t
|
||||
}
|
||||
|
||||
// Lookup returns the template with the given name that is associated with t,
|
||||
// or nil if there is no such template.
|
||||
func (t *Template) Lookup(name string) *Template {
|
||||
if t.common == nil {
|
||||
return nil
|
||||
}
|
||||
return t.tmpl[name]
|
||||
}
|
||||
|
||||
// Parse parses a string into a template. Nested template definitions will be
|
||||
// associated with the top-level template t. Parse may be called multiple times
|
||||
// to parse definitions of templates to associate with t. It is an error if a
|
||||
// resulting template is non-empty (contains content other than template
|
||||
// definitions) and would replace a non-empty template with the same name.
|
||||
// (In multiple calls to Parse with the same receiver template, only one call
|
||||
// can contain text other than space, comments, and template definitions.)
|
||||
func (t *Template) Parse(text string) (*Template, error) {
|
||||
t.init()
|
||||
trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Add the newly parsed trees, including the one for t, into our common structure.
|
||||
for name, tree := range trees {
|
||||
// If the name we parsed is the name of this template, overwrite this template.
|
||||
// The associate method checks it's not a redefinition.
|
||||
tmpl := t
|
||||
if name != t.name {
|
||||
tmpl = t.New(name)
|
||||
}
|
||||
// Even if t == tmpl, we need to install it in the common.tmpl map.
|
||||
if replace, err := t.associate(tmpl, tree); err != nil {
|
||||
return nil, err
|
||||
} else if replace {
|
||||
tmpl.Tree = tree
|
||||
}
|
||||
tmpl.leftDelim = t.leftDelim
|
||||
tmpl.rightDelim = t.rightDelim
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// associate installs the new template into the group of templates associated
|
||||
// with t. It is an error to reuse a name except to overwrite an empty
|
||||
// template. The two are already known to share the common structure.
|
||||
// The boolean return value reports wither to store this tree as t.Tree.
|
||||
func (t *Template) associate(new *Template, tree *parse.Tree) (bool, error) {
|
||||
if new.common != t.common {
|
||||
panic("internal error: associate not common")
|
||||
}
|
||||
name := new.name
|
||||
if old := t.tmpl[name]; old != nil {
|
||||
oldIsEmpty := parse.IsEmptyTree(old.Root)
|
||||
newIsEmpty := parse.IsEmptyTree(tree.Root)
|
||||
if newIsEmpty {
|
||||
// Whether old is empty or not, new is empty; no reason to replace old.
|
||||
return false, nil
|
||||
}
|
||||
if !oldIsEmpty {
|
||||
return false, fmt.Errorf("template: redefinition of template %q", name)
|
||||
}
|
||||
}
|
||||
t.tmpl[name] = new
|
||||
return true, nil
|
||||
}
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
{{define "x"}}TEXT{{end}}
|
||||
{{define "dotV"}}{{.V}}{{end}}
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
{{define "dot"}}{{.}}{{end}}
|
||||
{{define "nested"}}{{template "dot" .}}{{end}}
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
template1
|
||||
{{define "x"}}x{{end}}
|
||||
{{template "y"}}
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
template2
|
||||
{{define "y"}}y{{end}}
|
||||
{{template "x"}}
|
||||
-1
@@ -1 +0,0 @@
|
||||
.DS_Store
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
Copyright (c) 2013 John Barton
|
||||
|
||||
MIT License
|
||||
|
||||
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.
|
||||
|
||||
-127
@@ -1,127 +0,0 @@
|
||||
# GoDotEnv [](https://app.wercker.com/project/bykey/507594c2ec7e60f19403a568dfea0f78)
|
||||
|
||||
A Go (golang) port of the Ruby dotenv project (which loads env vars from a .env file)
|
||||
|
||||
From the original Library:
|
||||
|
||||
> Storing configuration in the environment is one of the tenets of a twelve-factor app. Anything that is likely to change between deployment environments–such as resource handles for databases or credentials for external services–should be extracted from the code into environment variables.
|
||||
>
|
||||
> But it is not always practical to set environment variables on development machines or continuous integration servers where multiple projects are run. Dotenv load variables from a .env file into ENV when the environment is bootstrapped.
|
||||
|
||||
It can be used as a library (for loading in env for your own daemons etc) or as a bin command.
|
||||
|
||||
There is test coverage and CI for both linuxish and windows environments, but I make no guarantees about the bin version working on windows.
|
||||
|
||||
## Installation
|
||||
|
||||
As a library
|
||||
|
||||
```shell
|
||||
go get github.com/joho/godotenv
|
||||
```
|
||||
|
||||
or if you want to use it as a bin command
|
||||
```shell
|
||||
go get github.com/joho/godotenv/cmd/godotenv
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Add your application configuration to your `.env` file in the root of your project:
|
||||
|
||||
```shell
|
||||
S3_BUCKET=YOURS3BUCKET
|
||||
SECRET_KEY=YOURSECRETKEYGOESHERE
|
||||
```
|
||||
|
||||
Then in your Go app you can do something like
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/joho/godotenv"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := godotenv.Load()
|
||||
if err != nil {
|
||||
log.Fatal("Error loading .env file")
|
||||
}
|
||||
|
||||
s3Bucket := os.Getenv("S3_BUCKET")
|
||||
secretKey := os.Getenv("SECRET_KEY")
|
||||
|
||||
// now do something with s3 or whatever
|
||||
}
|
||||
```
|
||||
|
||||
If you're even lazier than that, you can just take advantage of the autoload package which will read in `.env` on import
|
||||
|
||||
```go
|
||||
import _ "github.com/joho/godotenv/autoload"
|
||||
```
|
||||
|
||||
While `.env` in the project root is the default, you don't have to be constrained, both examples below are 100% legit
|
||||
|
||||
```go
|
||||
_ = godotenv.Load("somerandomfile")
|
||||
_ = godotenv.Load("filenumberone.env", "filenumbertwo.env")
|
||||
```
|
||||
|
||||
If you want to be really fancy with your env file you can do comments and exports (below is a valid env file)
|
||||
|
||||
```shell
|
||||
# I am a comment and that is OK
|
||||
SOME_VAR=someval
|
||||
FOO=BAR # comments at line end are OK too
|
||||
export BAR=BAZ
|
||||
```
|
||||
|
||||
Or finally you can do YAML(ish) style
|
||||
|
||||
```yaml
|
||||
FOO: bar
|
||||
BAR: baz
|
||||
```
|
||||
|
||||
as a final aside, if you don't want godotenv munging your env you can just get a map back instead
|
||||
|
||||
```go
|
||||
var myEnv map[string]string
|
||||
myEnv, err := godotenv.Read()
|
||||
|
||||
s3Bucket := myEnv["S3_BUCKET"]
|
||||
```
|
||||
|
||||
### Command Mode
|
||||
|
||||
Assuming you've installed the command as above and you've got `$GOPATH/bin` in your `$PATH`
|
||||
|
||||
```
|
||||
godotenv -f /some/path/to/.env some_command with some args
|
||||
```
|
||||
|
||||
If you don't specify `-f` it will fall back on the default of loading `.env` in `PWD`
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are most welcome! The parser itself is pretty stupidly naive and I wouldn't be surprised if it breaks with edge cases.
|
||||
|
||||
*code changes without tests will not be accepted*
|
||||
|
||||
1. Fork it
|
||||
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||
3. Commit your changes (`git commit -am 'Added some feature'`)
|
||||
4. Push to the branch (`git push origin my-new-feature`)
|
||||
5. Create new Pull Request
|
||||
|
||||
## CI
|
||||
|
||||
Linux: [](https://app.wercker.com/project/bykey/507594c2ec7e60f19403a568dfea0f78) Windows: [](https://ci.appveyor.com/project/joho/godotenv)
|
||||
|
||||
## Who?
|
||||
|
||||
The original library [dotenv](https://github.com/bkeepers/dotenv) was written by [Brandon Keepers](http://opensoul.org/), and this port was done by [John Barton](http://whoisjohnbarton.com) based off the tests/fixtures in the original library.
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
package autoload
|
||||
|
||||
/*
|
||||
You can just read the .env file on import just by doing
|
||||
|
||||
import _ "github.com/joho/godotenv/autoload"
|
||||
|
||||
And bob's your mother's brother
|
||||
*/
|
||||
|
||||
import "github.com/joho/godotenv"
|
||||
|
||||
func init() {
|
||||
godotenv.Load()
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var showHelp bool
|
||||
flag.BoolVar(&showHelp, "h", false, "show help")
|
||||
var rawEnvFilenames string
|
||||
flag.StringVar(&rawEnvFilenames, "f", "", "comma separated paths to .env files")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
usage := `
|
||||
Run a process with a env setup from a .env file
|
||||
|
||||
godotenv [-f ENV_FILE_PATHS] COMMAND_ARGS
|
||||
|
||||
ENV_FILE_PATHS: comma separated paths to .env files
|
||||
COMMAND_ARGS: command and args you want to run
|
||||
|
||||
example
|
||||
godotenv -f /path/to/something/.env,/another/path/.env fortune
|
||||
`
|
||||
// if no args or -h flag
|
||||
// print usage and return
|
||||
args := flag.Args()
|
||||
if showHelp || len(args) == 0 {
|
||||
fmt.Println(usage)
|
||||
return
|
||||
}
|
||||
|
||||
// load env
|
||||
var envFilenames []string
|
||||
if rawEnvFilenames != "" {
|
||||
envFilenames = strings.Split(rawEnvFilenames, ",")
|
||||
}
|
||||
|
||||
// take rest of args and "exec" them
|
||||
cmd := args[0]
|
||||
cmdArgs := args[1:]
|
||||
|
||||
err := godotenv.Exec(envFilenames, cmd, cmdArgs)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
export OPTION_A='postgres://localhost:5432/database?sslmode=disable'
|
||||
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
export OPTION_A=2
|
||||
export OPTION_B='\n'
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
INVALID LINE
|
||||
foo=bar
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
OPTION_A=1
|
||||
OPTION_B=2
|
||||
OPTION_C= 3
|
||||
OPTION_D =4
|
||||
OPTION_E = 5
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
OPTION_A='1'
|
||||
OPTION_B='2'
|
||||
OPTION_C=''
|
||||
OPTION_D='\n'
|
||||
OPTION_E="1"
|
||||
OPTION_F="2"
|
||||
OPTION_G=""
|
||||
OPTION_H="\n"
|
||||
-235
@@ -1,235 +0,0 @@
|
||||
// Package godotenv is a go port of the ruby dotenv library (https://github.com/bkeepers/dotenv)
|
||||
//
|
||||
// Examples/readme can be found on the github page at https://github.com/joho/godotenv
|
||||
//
|
||||
// The TL;DR is that you make a .env file that looks something like
|
||||
//
|
||||
// SOME_ENV_VAR=somevalue
|
||||
//
|
||||
// and then in your go code you can call
|
||||
//
|
||||
// godotenv.Load()
|
||||
//
|
||||
// and all the env vars declared in .env will be avaiable through os.Getenv("SOME_ENV_VAR")
|
||||
package godotenv
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Load will read your env file(s) and load them into ENV for this process.
|
||||
//
|
||||
// Call this function as close as possible to the start of your program (ideally in main)
|
||||
//
|
||||
// If you call Load without any args it will default to loading .env in the current path
|
||||
//
|
||||
// You can otherwise tell it which files to load (there can be more than one) like
|
||||
//
|
||||
// godotenv.Load("fileone", "filetwo")
|
||||
//
|
||||
// It's important to note that it WILL NOT OVERRIDE an env variable that already exists - consider the .env file to set dev vars or sensible defaults
|
||||
func Load(filenames ...string) (err error) {
|
||||
filenames = filenamesOrDefault(filenames)
|
||||
|
||||
for _, filename := range filenames {
|
||||
err = loadFile(filename, false)
|
||||
if err != nil {
|
||||
return // return early on a spazout
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Overload will read your env file(s) and load them into ENV for this process.
|
||||
//
|
||||
// Call this function as close as possible to the start of your program (ideally in main)
|
||||
//
|
||||
// If you call Overload without any args it will default to loading .env in the current path
|
||||
//
|
||||
// You can otherwise tell it which files to load (there can be more than one) like
|
||||
//
|
||||
// godotenv.Overload("fileone", "filetwo")
|
||||
//
|
||||
// It's important to note this WILL OVERRIDE an env variable that already exists - consider the .env file to forcefilly set all vars.
|
||||
func Overload(filenames ...string) (err error) {
|
||||
filenames = filenamesOrDefault(filenames)
|
||||
|
||||
for _, filename := range filenames {
|
||||
err = loadFile(filename, true)
|
||||
if err != nil {
|
||||
return // return early on a spazout
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Read all env (with same file loading semantics as Load) but return values as
|
||||
// a map rather than automatically writing values into env
|
||||
func Read(filenames ...string) (envMap map[string]string, err error) {
|
||||
filenames = filenamesOrDefault(filenames)
|
||||
envMap = make(map[string]string)
|
||||
|
||||
for _, filename := range filenames {
|
||||
individualEnvMap, individualErr := readFile(filename)
|
||||
|
||||
if individualErr != nil {
|
||||
err = individualErr
|
||||
return // return early on a spazout
|
||||
}
|
||||
|
||||
for key, value := range individualEnvMap {
|
||||
envMap[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Exec loads env vars from the specified filenames (empty map falls back to default)
|
||||
// then executes the cmd specified.
|
||||
//
|
||||
// Simply hooks up os.Stdin/err/out to the command and calls Run()
|
||||
//
|
||||
// If you want more fine grained control over your command it's recommended
|
||||
// that you use `Load()` or `Read()` and the `os/exec` package yourself.
|
||||
func Exec(filenames []string, cmd string, cmdArgs []string) error {
|
||||
Load(filenames...)
|
||||
|
||||
command := exec.Command(cmd, cmdArgs...)
|
||||
command.Stdin = os.Stdin
|
||||
command.Stdout = os.Stdout
|
||||
command.Stderr = os.Stderr
|
||||
return command.Run()
|
||||
}
|
||||
|
||||
func filenamesOrDefault(filenames []string) []string {
|
||||
if len(filenames) == 0 {
|
||||
return []string{".env"}
|
||||
}
|
||||
return filenames
|
||||
}
|
||||
|
||||
func loadFile(filename string, overload bool) error {
|
||||
envMap, err := readFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for key, value := range envMap {
|
||||
if os.Getenv(key) == "" || overload {
|
||||
os.Setenv(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readFile(filename string) (envMap map[string]string, err error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
envMap = make(map[string]string)
|
||||
|
||||
var lines []string
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
lines = append(lines, scanner.Text())
|
||||
}
|
||||
|
||||
if err = scanner.Err(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, fullLine := range lines {
|
||||
if !isIgnoredLine(fullLine) {
|
||||
var key, value string
|
||||
key, value, err = parseLine(fullLine)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
envMap[key] = value
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseLine(line string) (key string, value string, err error) {
|
||||
if len(line) == 0 {
|
||||
err = errors.New("zero length string")
|
||||
return
|
||||
}
|
||||
|
||||
// ditch the comments (but keep quoted hashes)
|
||||
if strings.Contains(line, "#") {
|
||||
segmentsBetweenHashes := strings.Split(line, "#")
|
||||
quotesAreOpen := false
|
||||
var segmentsToKeep []string
|
||||
for _, segment := range segmentsBetweenHashes {
|
||||
if strings.Count(segment, "\"") == 1 || strings.Count(segment, "'") == 1 {
|
||||
if quotesAreOpen {
|
||||
quotesAreOpen = false
|
||||
segmentsToKeep = append(segmentsToKeep, segment)
|
||||
} else {
|
||||
quotesAreOpen = true
|
||||
}
|
||||
}
|
||||
|
||||
if len(segmentsToKeep) == 0 || quotesAreOpen {
|
||||
segmentsToKeep = append(segmentsToKeep, segment)
|
||||
}
|
||||
}
|
||||
|
||||
line = strings.Join(segmentsToKeep, "#")
|
||||
}
|
||||
|
||||
// now split key from value
|
||||
splitString := strings.SplitN(line, "=", 2)
|
||||
|
||||
if len(splitString) != 2 {
|
||||
// try yaml mode!
|
||||
splitString = strings.SplitN(line, ":", 2)
|
||||
}
|
||||
|
||||
if len(splitString) != 2 {
|
||||
err = errors.New("Can't separate key from value")
|
||||
return
|
||||
}
|
||||
|
||||
// Parse the key
|
||||
key = splitString[0]
|
||||
if strings.HasPrefix(key, "export") {
|
||||
key = strings.TrimPrefix(key, "export")
|
||||
}
|
||||
key = strings.Trim(key, " ")
|
||||
|
||||
// Parse the value
|
||||
value = splitString[1]
|
||||
// trim
|
||||
value = strings.Trim(value, " ")
|
||||
|
||||
// check if we've got quoted values
|
||||
if strings.Count(value, "\"") == 2 || strings.Count(value, "'") == 2 {
|
||||
// pull the quotes off the edges
|
||||
value = strings.Trim(value, "\"'")
|
||||
|
||||
// expand quotes
|
||||
value = strings.Replace(value, "\\\"", "\"", -1)
|
||||
// expand newlines
|
||||
value = strings.Replace(value, "\\n", "\n", -1)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func isIgnoredLine(line string) bool {
|
||||
trimmedLine := strings.Trim(line, " \n\t")
|
||||
return len(trimmedLine) == 0 || strings.HasPrefix(trimmedLine, "#")
|
||||
}
|
||||
-288
@@ -1,288 +0,0 @@
|
||||
package godotenv
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var noopPresets = make(map[string]string)
|
||||
|
||||
func parseAndCompare(t *testing.T, rawEnvLine string, expectedKey string, expectedValue string) {
|
||||
key, value, _ := parseLine(rawEnvLine)
|
||||
if key != expectedKey || value != expectedValue {
|
||||
t.Errorf("Expected '%v' to parse as '%v' => '%v', got '%v' => '%v' instead", rawEnvLine, expectedKey, expectedValue, key, value)
|
||||
}
|
||||
}
|
||||
|
||||
func loadEnvAndCompareValues(t *testing.T, loader func(files ...string) error, envFileName string, expectedValues map[string]string, presets map[string]string) {
|
||||
// first up, clear the env
|
||||
os.Clearenv()
|
||||
|
||||
for k, v := range presets {
|
||||
os.Setenv(k, v)
|
||||
}
|
||||
|
||||
err := loader(envFileName)
|
||||
if err != nil {
|
||||
t.Fatalf("Error loading %v", envFileName)
|
||||
}
|
||||
|
||||
for k := range expectedValues {
|
||||
envValue := os.Getenv(k)
|
||||
v := expectedValues[k]
|
||||
if envValue != v {
|
||||
t.Errorf("Mismatch for key '%v': expected '%v' got '%v'", k, v, envValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadWithNoArgsLoadsDotEnv(t *testing.T) {
|
||||
err := Load()
|
||||
pathError := err.(*os.PathError)
|
||||
if pathError == nil || pathError.Op != "open" || pathError.Path != ".env" {
|
||||
t.Errorf("Didn't try and open .env by default")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverloadWithNoArgsOverloadsDotEnv(t *testing.T) {
|
||||
err := Overload()
|
||||
pathError := err.(*os.PathError)
|
||||
if pathError == nil || pathError.Op != "open" || pathError.Path != ".env" {
|
||||
t.Errorf("Didn't try and open .env by default")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadFileNotFound(t *testing.T) {
|
||||
err := Load("somefilethatwillneverexistever.env")
|
||||
if err == nil {
|
||||
t.Error("File wasn't found but Load didn't return an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverloadFileNotFound(t *testing.T) {
|
||||
err := Overload("somefilethatwillneverexistever.env")
|
||||
if err == nil {
|
||||
t.Error("File wasn't found but Overload didn't return an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadPlainEnv(t *testing.T) {
|
||||
envFileName := "fixtures/plain.env"
|
||||
expectedValues := map[string]string{
|
||||
"OPTION_A": "1",
|
||||
"OPTION_B": "2",
|
||||
"OPTION_C": "3",
|
||||
"OPTION_D": "4",
|
||||
"OPTION_E": "5",
|
||||
}
|
||||
|
||||
envMap, err := Read(envFileName)
|
||||
if err != nil {
|
||||
t.Error("Error reading file")
|
||||
}
|
||||
|
||||
if len(envMap) != len(expectedValues) {
|
||||
t.Error("Didn't get the right size map back")
|
||||
}
|
||||
|
||||
for key, value := range expectedValues {
|
||||
if envMap[key] != value {
|
||||
t.Error("Read got one of the keys wrong")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadDoesNotOverride(t *testing.T) {
|
||||
envFileName := "fixtures/plain.env"
|
||||
|
||||
// ensure NO overload
|
||||
presets := map[string]string{
|
||||
"OPTION_A": "do_not_override",
|
||||
}
|
||||
|
||||
expectedValues := map[string]string{
|
||||
"OPTION_A": "do_not_override",
|
||||
}
|
||||
loadEnvAndCompareValues(t, Load, envFileName, expectedValues, presets)
|
||||
}
|
||||
|
||||
func TestOveroadDoesOverride(t *testing.T) {
|
||||
envFileName := "fixtures/plain.env"
|
||||
|
||||
// ensure NO overload
|
||||
presets := map[string]string{
|
||||
"OPTION_A": "do_not_override",
|
||||
}
|
||||
|
||||
expectedValues := map[string]string{
|
||||
"OPTION_A": "1",
|
||||
}
|
||||
loadEnvAndCompareValues(t, Overload, envFileName, expectedValues, presets)
|
||||
}
|
||||
|
||||
func TestLoadPlainEnv(t *testing.T) {
|
||||
envFileName := "fixtures/plain.env"
|
||||
expectedValues := map[string]string{
|
||||
"OPTION_A": "1",
|
||||
"OPTION_B": "2",
|
||||
"OPTION_C": "3",
|
||||
"OPTION_D": "4",
|
||||
"OPTION_E": "5",
|
||||
}
|
||||
|
||||
loadEnvAndCompareValues(t, Load, envFileName, expectedValues, noopPresets)
|
||||
}
|
||||
|
||||
func TestLoadExportedEnv(t *testing.T) {
|
||||
envFileName := "fixtures/exported.env"
|
||||
expectedValues := map[string]string{
|
||||
"OPTION_A": "2",
|
||||
"OPTION_B": "\n",
|
||||
}
|
||||
|
||||
loadEnvAndCompareValues(t, Load, envFileName, expectedValues, noopPresets)
|
||||
}
|
||||
|
||||
func TestLoadEqualsEnv(t *testing.T) {
|
||||
envFileName := "fixtures/equals.env"
|
||||
expectedValues := map[string]string{
|
||||
"OPTION_A": "postgres://localhost:5432/database?sslmode=disable",
|
||||
}
|
||||
|
||||
loadEnvAndCompareValues(t, Load, envFileName, expectedValues, noopPresets)
|
||||
}
|
||||
|
||||
func TestLoadQuotedEnv(t *testing.T) {
|
||||
envFileName := "fixtures/quoted.env"
|
||||
expectedValues := map[string]string{
|
||||
"OPTION_A": "1",
|
||||
"OPTION_B": "2",
|
||||
"OPTION_C": "",
|
||||
"OPTION_D": "\n",
|
||||
"OPTION_E": "1",
|
||||
"OPTION_F": "2",
|
||||
"OPTION_G": "",
|
||||
"OPTION_H": "\n",
|
||||
}
|
||||
|
||||
loadEnvAndCompareValues(t, Load, envFileName, expectedValues, noopPresets)
|
||||
}
|
||||
|
||||
func TestActualEnvVarsAreLeftAlone(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("OPTION_A", "actualenv")
|
||||
_ = Load("fixtures/plain.env")
|
||||
|
||||
if os.Getenv("OPTION_A") != "actualenv" {
|
||||
t.Error("An ENV var set earlier was overwritten")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsing(t *testing.T) {
|
||||
// unquoted values
|
||||
parseAndCompare(t, "FOO=bar", "FOO", "bar")
|
||||
|
||||
// parses values with spaces around equal sign
|
||||
parseAndCompare(t, "FOO =bar", "FOO", "bar")
|
||||
parseAndCompare(t, "FOO= bar", "FOO", "bar")
|
||||
|
||||
// parses double quoted values
|
||||
parseAndCompare(t, "FOO=\"bar\"", "FOO", "bar")
|
||||
|
||||
// parses single quoted values
|
||||
parseAndCompare(t, "FOO='bar'", "FOO", "bar")
|
||||
|
||||
// parses escaped double quotes
|
||||
parseAndCompare(t, "FOO=escaped\\\"bar\"", "FOO", "escaped\"bar")
|
||||
|
||||
// parses yaml style options
|
||||
parseAndCompare(t, "OPTION_A: 1", "OPTION_A", "1")
|
||||
|
||||
// parses export keyword
|
||||
parseAndCompare(t, "export OPTION_A=2", "OPTION_A", "2")
|
||||
parseAndCompare(t, "export OPTION_B='\\n'", "OPTION_B", "\n")
|
||||
|
||||
// it 'expands newlines in quoted strings' do
|
||||
// expect(env('FOO="bar\nbaz"')).to eql('FOO' => "bar\nbaz")
|
||||
parseAndCompare(t, "FOO=\"bar\\nbaz\"", "FOO", "bar\nbaz")
|
||||
|
||||
// it 'parses varibales with "." in the name' do
|
||||
// expect(env('FOO.BAR=foobar')).to eql('FOO.BAR' => 'foobar')
|
||||
parseAndCompare(t, "FOO.BAR=foobar", "FOO.BAR", "foobar")
|
||||
|
||||
// it 'parses varibales with several "=" in the value' do
|
||||
// expect(env('FOO=foobar=')).to eql('FOO' => 'foobar=')
|
||||
parseAndCompare(t, "FOO=foobar=", "FOO", "foobar=")
|
||||
|
||||
// it 'strips unquoted values' do
|
||||
// expect(env('foo=bar ')).to eql('foo' => 'bar') # not 'bar '
|
||||
parseAndCompare(t, "FOO=bar ", "FOO", "bar")
|
||||
|
||||
// it 'ignores inline comments' do
|
||||
// expect(env("foo=bar # this is foo")).to eql('foo' => 'bar')
|
||||
parseAndCompare(t, "FOO=bar # this is foo", "FOO", "bar")
|
||||
|
||||
// it 'allows # in quoted value' do
|
||||
// expect(env('foo="bar#baz" # comment')).to eql('foo' => 'bar#baz')
|
||||
parseAndCompare(t, "FOO=\"bar#baz\" # comment", "FOO", "bar#baz")
|
||||
parseAndCompare(t, "FOO='bar#baz' # comment", "FOO", "bar#baz")
|
||||
parseAndCompare(t, "FOO=\"bar#baz#bang\" # comment", "FOO", "bar#baz#bang")
|
||||
|
||||
// it 'parses # in quoted values' do
|
||||
// expect(env('foo="ba#r"')).to eql('foo' => 'ba#r')
|
||||
// expect(env("foo='ba#r'")).to eql('foo' => 'ba#r')
|
||||
parseAndCompare(t, "FOO=\"ba#r\"", "FOO", "ba#r")
|
||||
parseAndCompare(t, "FOO='ba#r'", "FOO", "ba#r")
|
||||
|
||||
// it 'throws an error if line format is incorrect' do
|
||||
// expect{env('lol$wut')}.to raise_error(Dotenv::FormatError)
|
||||
badlyFormattedLine := "lol$wut"
|
||||
_, _, err := parseLine(badlyFormattedLine)
|
||||
if err == nil {
|
||||
t.Errorf("Expected \"%v\" to return error, but it didn't", badlyFormattedLine)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLinesToIgnore(t *testing.T) {
|
||||
// it 'ignores empty lines' do
|
||||
// expect(env("\n \t \nfoo=bar\n \nfizz=buzz")).to eql('foo' => 'bar', 'fizz' => 'buzz')
|
||||
if !isIgnoredLine("\n") {
|
||||
t.Error("Line with nothing but line break wasn't ignored")
|
||||
}
|
||||
|
||||
if !isIgnoredLine("\t\t ") {
|
||||
t.Error("Line full of whitespace wasn't ignored")
|
||||
}
|
||||
|
||||
// it 'ignores comment lines' do
|
||||
// expect(env("\n\n\n # HERE GOES FOO \nfoo=bar")).to eql('foo' => 'bar')
|
||||
if !isIgnoredLine("# comment") {
|
||||
t.Error("Comment wasn't ignored")
|
||||
}
|
||||
|
||||
if !isIgnoredLine("\t#comment") {
|
||||
t.Error("Indented comment wasn't ignored")
|
||||
}
|
||||
|
||||
// make sure we're not getting false positives
|
||||
if isIgnoredLine("export OPTION_B='\\n'") {
|
||||
t.Error("ignoring a perfectly valid line to parse")
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorReadDirectory(t *testing.T) {
|
||||
envFileName := "fixtures/"
|
||||
envMap, err := Read(envFileName)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, got %v", envMap)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorParsing(t *testing.T) {
|
||||
envFileName := "fixtures/invalid1.env"
|
||||
envMap, err := Read(envFileName)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, got %v", envMap)
|
||||
}
|
||||
}
|
||||
-1
@@ -1 +0,0 @@
|
||||
box: pjvds/golang
|
||||
Generated
-2
@@ -1,2 +0,0 @@
|
||||
*.coverprofile
|
||||
node_modules/
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
language: go
|
||||
|
||||
sudo: false
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
go:
|
||||
- 1.2.x
|
||||
- 1.3.x
|
||||
- 1.4.2
|
||||
- 1.5.x
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- master
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: master
|
||||
include:
|
||||
- go: 1.6.x
|
||||
os: osx
|
||||
- go: 1.7.x
|
||||
os: osx
|
||||
|
||||
before_script:
|
||||
- go get github.com/urfave/gfmrun/... || true
|
||||
- go get golang.org/x/tools/... || true
|
||||
- if [ ! -f node_modules/.bin/markdown-toc ] ; then
|
||||
npm install markdown-toc ;
|
||||
fi
|
||||
|
||||
script:
|
||||
- ./runtests gen
|
||||
- ./runtests vet
|
||||
- ./runtests test
|
||||
- ./runtests gfmrun
|
||||
- ./runtests toc
|
||||
-392
@@ -1,392 +0,0 @@
|
||||
# Change Log
|
||||
|
||||
**ATTN**: This project uses [semantic versioning](http://semver.org/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [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
@@ -1,21 +0,0 @@
|
||||
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.
|
||||
-1364
File diff suppressed because it is too large
Load Diff
-3
@@ -1,3 +0,0 @@
|
||||
package altsrc
|
||||
|
||||
//go:generate python ../generate-flag-types altsrc -i ../flag-types.json -o flag_generated.go
|
||||
-261
@@ -1,261 +0,0 @@
|
||||
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
@@ -1,347 +0,0 @@
|
||||
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
@@ -1,336 +0,0 @@
|
||||
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: []string{"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: []string{"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: []string{"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: []int{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: []int{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: []int{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
@@ -1,18 +0,0 @@
|
||||
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
@@ -1,21 +0,0 @@
|
||||
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)
|
||||
}
|
||||
-248
@@ -1,248 +0,0 @@
|
||||
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 {
|
||||
otherValue, isType := otherGenericValue.([]string)
|
||||
if !isType {
|
||||
return nil, incorrectTypeForFlagError(name, "[]string", otherGenericValue)
|
||||
}
|
||||
return otherValue, nil
|
||||
}
|
||||
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
||||
if exists {
|
||||
otherValue, isType := nestedGenericValue.([]string)
|
||||
if !isType {
|
||||
return nil, incorrectTypeForFlagError(name, "[]string", nestedGenericValue)
|
||||
}
|
||||
return otherValue, nil
|
||||
}
|
||||
|
||||
return nil, 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 {
|
||||
otherValue, isType := otherGenericValue.([]int)
|
||||
if !isType {
|
||||
return nil, incorrectTypeForFlagError(name, "[]int", otherGenericValue)
|
||||
}
|
||||
return otherValue, nil
|
||||
}
|
||||
nestedGenericValue, exists := nestedVal(name, fsm.valueMap)
|
||||
if exists {
|
||||
otherValue, isType := nestedGenericValue.([]int)
|
||||
if !isType {
|
||||
return nil, incorrectTypeForFlagError(name, "[]int", nestedGenericValue)
|
||||
}
|
||||
return otherValue, nil
|
||||
}
|
||||
|
||||
return nil, 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
@@ -1,310 +0,0 @@
|
||||
// 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
@@ -1,113 +0,0 @@
|
||||
// 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:
|
||||
fallthrough // [todo] - Support array type
|
||||
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
@@ -1,313 +0,0 @@
|
||||
// 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)
|
||||
}
|
||||
-84
@@ -1,84 +0,0 @@
|
||||
// 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"
|
||||
|
||||
"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 {
|
||||
return nil, fmt.Errorf("unable to determine how to load from path %s", filePath)
|
||||
}
|
||||
}
|
||||
-492
@@ -1,492 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md"
|
||||
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
|
||||
runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL)
|
||||
|
||||
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
|
||||
|
||||
errInvalidActionType = NewExitError("ERROR invalid Action type. "+
|
||||
fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+
|
||||
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
|
||||
)
|
||||
|
||||
// App is the main structure of a cli application. It is recommended that
|
||||
// an app be created with the cli.NewApp() function
|
||||
type App struct {
|
||||
// The name of the program. Defaults to path.Base(os.Args[0])
|
||||
Name string
|
||||
// Full name of command for help, defaults to Name
|
||||
HelpName string
|
||||
// Description of the program.
|
||||
Usage string
|
||||
// Text to override the USAGE section of help
|
||||
UsageText string
|
||||
// Description of the program argument format.
|
||||
ArgsUsage string
|
||||
// Version of the program
|
||||
Version string
|
||||
// Description of the program
|
||||
Description string
|
||||
// List of commands to execute
|
||||
Commands []Command
|
||||
// List of flags to parse
|
||||
Flags []Flag
|
||||
// Boolean to enable bash completion commands
|
||||
EnableBashCompletion bool
|
||||
// Boolean to hide built-in help command
|
||||
HideHelp bool
|
||||
// Boolean to hide built-in version flag and the VERSION section of help
|
||||
HideVersion bool
|
||||
// Populate on app startup, only gettable through method Categories()
|
||||
categories CommandCategories
|
||||
// An action to execute when the bash-completion flag is set
|
||||
BashComplete BashCompleteFunc
|
||||
// An action to execute before any subcommands are run, but after the context is ready
|
||||
// If a non-nil error is returned, no subcommands are run
|
||||
Before BeforeFunc
|
||||
// An action to execute after any subcommands are run, but after the subcommand has finished
|
||||
// It is run even if Action() panics
|
||||
After AfterFunc
|
||||
|
||||
// The action to execute when no subcommands are specified
|
||||
// Expects a `cli.ActionFunc` but will accept the *deprecated* signature of `func(*cli.Context) {}`
|
||||
// *Note*: support for the deprecated `Action` signature will be removed in a future version
|
||||
Action interface{}
|
||||
|
||||
// Execute this function if the proper command cannot be found
|
||||
CommandNotFound CommandNotFoundFunc
|
||||
// Execute this function if an usage error occurs
|
||||
OnUsageError OnUsageErrorFunc
|
||||
// Compilation date
|
||||
Compiled time.Time
|
||||
// List of all authors who contributed
|
||||
Authors []Author
|
||||
// Copyright of the binary if any
|
||||
Copyright string
|
||||
// Name of Author (Note: Use App.Authors, this is deprecated)
|
||||
Author string
|
||||
// Email of Author (Note: Use App.Authors, this is deprecated)
|
||||
Email string
|
||||
// Writer writer to write output to
|
||||
Writer io.Writer
|
||||
// ErrWriter writes error output
|
||||
ErrWriter io.Writer
|
||||
// Other custom info
|
||||
Metadata map[string]interface{}
|
||||
|
||||
didSetup bool
|
||||
}
|
||||
|
||||
// Tries to find out when this binary was compiled.
|
||||
// Returns the current time if it fails to find it.
|
||||
func compileTime() time.Time {
|
||||
info, err := os.Stat(os.Args[0])
|
||||
if err != nil {
|
||||
return time.Now()
|
||||
}
|
||||
return info.ModTime()
|
||||
}
|
||||
|
||||
// NewApp creates a new cli Application with some reasonable defaults for Name,
|
||||
// Usage, Version and Action.
|
||||
func NewApp() *App {
|
||||
return &App{
|
||||
Name: filepath.Base(os.Args[0]),
|
||||
HelpName: filepath.Base(os.Args[0]),
|
||||
Usage: "A new cli application",
|
||||
UsageText: "",
|
||||
Version: "0.0.0",
|
||||
BashComplete: DefaultAppComplete,
|
||||
Action: helpCommand.Action,
|
||||
Compiled: compileTime(),
|
||||
Writer: os.Stdout,
|
||||
}
|
||||
}
|
||||
|
||||
// Setup runs initialization code to ensure all data structures are ready for
|
||||
// `Run` or inspection prior to `Run`. It is internally called by `Run`, but
|
||||
// will return early if setup has already happened.
|
||||
func (a *App) Setup() {
|
||||
if a.didSetup {
|
||||
return
|
||||
}
|
||||
|
||||
a.didSetup = true
|
||||
|
||||
if a.Author != "" || a.Email != "" {
|
||||
a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email})
|
||||
}
|
||||
|
||||
newCmds := []Command{}
|
||||
for _, c := range a.Commands {
|
||||
if c.HelpName == "" {
|
||||
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
|
||||
}
|
||||
newCmds = append(newCmds, c)
|
||||
}
|
||||
a.Commands = newCmds
|
||||
|
||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
||||
a.Commands = append(a.Commands, helpCommand)
|
||||
if (HelpFlag != BoolFlag{}) {
|
||||
a.appendFlag(HelpFlag)
|
||||
}
|
||||
}
|
||||
|
||||
if !a.HideVersion {
|
||||
a.appendFlag(VersionFlag)
|
||||
}
|
||||
|
||||
a.categories = CommandCategories{}
|
||||
for _, command := range a.Commands {
|
||||
a.categories = a.categories.AddCommand(command.Category, command)
|
||||
}
|
||||
sort.Sort(a.categories)
|
||||
|
||||
if a.Metadata == nil {
|
||||
a.Metadata = make(map[string]interface{})
|
||||
}
|
||||
|
||||
if a.Writer == nil {
|
||||
a.Writer = os.Stdout
|
||||
}
|
||||
}
|
||||
|
||||
// Run is the entry point to the cli app. Parses the arguments slice and routes
|
||||
// to the proper flag/args combination
|
||||
func (a *App) Run(arguments []string) (err error) {
|
||||
a.Setup()
|
||||
|
||||
// handle the completion flag separately from the flagset since
|
||||
// completion could be attempted after a flag, but before its value was put
|
||||
// on the command line. this causes the flagset to interpret the completion
|
||||
// flag name as the value of the flag before it which is undesirable
|
||||
// note that we can only do this because the shell autocomplete function
|
||||
// always appends the completion flag at the end of the command
|
||||
shellComplete, arguments := checkShellCompleteFlag(a, arguments)
|
||||
|
||||
// parse flags
|
||||
set, err := flagSet(a.Name, a.Flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
set.SetOutput(ioutil.Discard)
|
||||
err = set.Parse(arguments[1:])
|
||||
nerr := normalizeFlags(a.Flags, set)
|
||||
context := NewContext(a, set, nil)
|
||||
if nerr != nil {
|
||||
fmt.Fprintln(a.Writer, nerr)
|
||||
ShowAppHelp(context)
|
||||
return nerr
|
||||
}
|
||||
context.shellComplete = shellComplete
|
||||
|
||||
if checkCompletions(context) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if a.OnUsageError != nil {
|
||||
err := a.OnUsageError(context, err, false)
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
||||
ShowAppHelp(context)
|
||||
return err
|
||||
}
|
||||
|
||||
if !a.HideHelp && checkHelp(context) {
|
||||
ShowAppHelp(context)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !a.HideVersion && checkVersion(context) {
|
||||
ShowVersion(context)
|
||||
return nil
|
||||
}
|
||||
|
||||
if a.After != nil {
|
||||
defer func() {
|
||||
if afterErr := a.After(context); afterErr != nil {
|
||||
if err != nil {
|
||||
err = NewMultiError(err, afterErr)
|
||||
} else {
|
||||
err = afterErr
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if a.Before != nil {
|
||||
beforeErr := a.Before(context)
|
||||
if beforeErr != nil {
|
||||
fmt.Fprintf(a.Writer, "%v\n\n", beforeErr)
|
||||
ShowAppHelp(context)
|
||||
HandleExitCoder(beforeErr)
|
||||
err = beforeErr
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
args := context.Args()
|
||||
if args.Present() {
|
||||
name := args.First()
|
||||
c := a.Command(name)
|
||||
if c != nil {
|
||||
return c.Run(context)
|
||||
}
|
||||
}
|
||||
|
||||
if a.Action == nil {
|
||||
a.Action = helpCommand.Action
|
||||
}
|
||||
|
||||
// Run default Action
|
||||
err = HandleAction(a.Action, context)
|
||||
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// RunAndExitOnError calls .Run() and exits non-zero if an error was returned
|
||||
//
|
||||
// Deprecated: instead you should return an error that fulfills cli.ExitCoder
|
||||
// to cli.App.Run. This will cause the application to exit with the given eror
|
||||
// code in the cli.ExitCoder
|
||||
func (a *App) RunAndExitOnError() {
|
||||
if err := a.Run(os.Args); err != nil {
|
||||
fmt.Fprintln(a.errWriter(), err)
|
||||
OsExiter(1)
|
||||
}
|
||||
}
|
||||
|
||||
// RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to
|
||||
// generate command-specific flags
|
||||
func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
||||
// append help to commands
|
||||
if len(a.Commands) > 0 {
|
||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
||||
a.Commands = append(a.Commands, helpCommand)
|
||||
if (HelpFlag != BoolFlag{}) {
|
||||
a.appendFlag(HelpFlag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newCmds := []Command{}
|
||||
for _, c := range a.Commands {
|
||||
if c.HelpName == "" {
|
||||
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
|
||||
}
|
||||
newCmds = append(newCmds, c)
|
||||
}
|
||||
a.Commands = newCmds
|
||||
|
||||
// parse flags
|
||||
set, err := flagSet(a.Name, a.Flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
set.SetOutput(ioutil.Discard)
|
||||
err = set.Parse(ctx.Args().Tail())
|
||||
nerr := normalizeFlags(a.Flags, set)
|
||||
context := NewContext(a, set, ctx)
|
||||
|
||||
if nerr != nil {
|
||||
fmt.Fprintln(a.Writer, nerr)
|
||||
fmt.Fprintln(a.Writer)
|
||||
if len(a.Commands) > 0 {
|
||||
ShowSubcommandHelp(context)
|
||||
} else {
|
||||
ShowCommandHelp(ctx, context.Args().First())
|
||||
}
|
||||
return nerr
|
||||
}
|
||||
|
||||
if checkCompletions(context) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if a.OnUsageError != nil {
|
||||
err = a.OnUsageError(context, err, true)
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
|
||||
ShowSubcommandHelp(context)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(a.Commands) > 0 {
|
||||
if checkSubcommandHelp(context) {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if checkCommandHelp(ctx, context.Args().First()) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if a.After != nil {
|
||||
defer func() {
|
||||
afterErr := a.After(context)
|
||||
if afterErr != nil {
|
||||
HandleExitCoder(err)
|
||||
if err != nil {
|
||||
err = NewMultiError(err, afterErr)
|
||||
} else {
|
||||
err = afterErr
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if a.Before != nil {
|
||||
beforeErr := a.Before(context)
|
||||
if beforeErr != nil {
|
||||
HandleExitCoder(beforeErr)
|
||||
err = beforeErr
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
args := context.Args()
|
||||
if args.Present() {
|
||||
name := args.First()
|
||||
c := a.Command(name)
|
||||
if c != nil {
|
||||
return c.Run(context)
|
||||
}
|
||||
}
|
||||
|
||||
// Run default Action
|
||||
err = HandleAction(a.Action, context)
|
||||
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Command returns the named command on App. Returns nil if the command does not exist
|
||||
func (a *App) Command(name string) *Command {
|
||||
for _, c := range a.Commands {
|
||||
if c.HasName(name) {
|
||||
return &c
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Categories returns a slice containing all the categories with the commands they contain
|
||||
func (a *App) Categories() CommandCategories {
|
||||
return a.categories
|
||||
}
|
||||
|
||||
// VisibleCategories returns a slice of categories and commands that are
|
||||
// Hidden=false
|
||||
func (a *App) VisibleCategories() []*CommandCategory {
|
||||
ret := []*CommandCategory{}
|
||||
for _, category := range a.categories {
|
||||
if visible := func() *CommandCategory {
|
||||
for _, command := range category.Commands {
|
||||
if !command.Hidden {
|
||||
return category
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); visible != nil {
|
||||
ret = append(ret, visible)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
||||
func (a *App) VisibleCommands() []Command {
|
||||
ret := []Command{}
|
||||
for _, command := range a.Commands {
|
||||
if !command.Hidden {
|
||||
ret = append(ret, command)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// VisibleFlags returns a slice of the Flags with Hidden=false
|
||||
func (a *App) VisibleFlags() []Flag {
|
||||
return visibleFlags(a.Flags)
|
||||
}
|
||||
|
||||
func (a *App) hasFlag(flag Flag) bool {
|
||||
for _, f := range a.Flags {
|
||||
if flag == f {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *App) errWriter() io.Writer {
|
||||
|
||||
// When the app ErrWriter is nil use the package level one.
|
||||
if a.ErrWriter == nil {
|
||||
return ErrWriter
|
||||
}
|
||||
|
||||
return a.ErrWriter
|
||||
}
|
||||
|
||||
func (a *App) appendFlag(flag Flag) {
|
||||
if !a.hasFlag(flag) {
|
||||
a.Flags = append(a.Flags, flag)
|
||||
}
|
||||
}
|
||||
|
||||
// Author represents someone who has contributed to a cli project.
|
||||
type Author struct {
|
||||
Name string // The Authors name
|
||||
Email string // The Authors email
|
||||
}
|
||||
|
||||
// String makes Author comply to the Stringer interface, to allow an easy print in the templating process
|
||||
func (a Author) String() string {
|
||||
e := ""
|
||||
if a.Email != "" {
|
||||
e = " <" + a.Email + ">"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v%v", a.Name, e)
|
||||
}
|
||||
|
||||
// HandleAction attempts to figure out which Action signature was used. If
|
||||
// it's an ActionFunc or a func with the legacy signature for Action, the func
|
||||
// is run!
|
||||
func HandleAction(action interface{}, context *Context) (err error) {
|
||||
if a, ok := action.(ActionFunc); ok {
|
||||
return a(context)
|
||||
} else if a, ok := action.(func(*Context) error); ok {
|
||||
return a(context)
|
||||
} else if a, ok := action.(func(*Context)); ok { // deprecated function signature
|
||||
a(context)
|
||||
return nil
|
||||
} else {
|
||||
return errInvalidActionType
|
||||
}
|
||||
}
|
||||
-1685
File diff suppressed because it is too large
Load Diff
-24
@@ -1,24 +0,0 @@
|
||||
version: "{build}"
|
||||
|
||||
os: Windows Server 2012 R2
|
||||
|
||||
clone_folder: c:\gopath\src\github.com\urfave\cli
|
||||
|
||||
environment:
|
||||
GOPATH: C:\gopath
|
||||
GOVERSION: 1.6
|
||||
PYTHON: C:\Python27-x64
|
||||
PYTHON_VERSION: 2.7.x
|
||||
PYTHON_ARCH: 64
|
||||
|
||||
install:
|
||||
- set PATH=%GOPATH%\bin;C:\go\bin;%PATH%
|
||||
- go version
|
||||
- go env
|
||||
- go get github.com/urfave/gfmrun/...
|
||||
- go get -v -t ./...
|
||||
|
||||
build_script:
|
||||
- python runtests vet
|
||||
- python runtests test
|
||||
- python runtests gfmrun
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
#! /bin/bash
|
||||
|
||||
: ${PROG:=$(basename ${BASH_SOURCE})}
|
||||
|
||||
_cli_bash_autocomplete() {
|
||||
local cur opts base
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -F _cli_bash_autocomplete $PROG
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
autoload -U compinit && compinit
|
||||
autoload -U bashcompinit && bashcompinit
|
||||
|
||||
script_dir=$(dirname $0)
|
||||
source ${script_dir}/bash_autocomplete
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
package cli
|
||||
|
||||
// CommandCategories is a slice of *CommandCategory.
|
||||
type CommandCategories []*CommandCategory
|
||||
|
||||
// CommandCategory is a category containing commands.
|
||||
type CommandCategory struct {
|
||||
Name string
|
||||
Commands Commands
|
||||
}
|
||||
|
||||
func (c CommandCategories) Less(i, j int) bool {
|
||||
return c[i].Name < c[j].Name
|
||||
}
|
||||
|
||||
func (c CommandCategories) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
func (c CommandCategories) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
|
||||
// AddCommand adds a command to a category.
|
||||
func (c CommandCategories) AddCommand(category string, command Command) CommandCategories {
|
||||
for _, commandCategory := range c {
|
||||
if commandCategory.Name == category {
|
||||
commandCategory.Commands = append(commandCategory.Commands, command)
|
||||
return c
|
||||
}
|
||||
}
|
||||
return append(c, &CommandCategory{Name: category, Commands: []Command{command}})
|
||||
}
|
||||
|
||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
||||
func (c *CommandCategory) VisibleCommands() []Command {
|
||||
ret := []Command{}
|
||||
for _, command := range c.Commands {
|
||||
if !command.Hidden {
|
||||
ret = append(ret, command)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
// Package cli provides a minimal framework for creating and organizing command line
|
||||
// Go applications. cli is designed to be easy to understand and write, the most simple
|
||||
// cli application can be written as follows:
|
||||
// func main() {
|
||||
// cli.NewApp().Run(os.Args)
|
||||
// }
|
||||
//
|
||||
// Of course this application does not do much, so let's make this an actual application:
|
||||
// func main() {
|
||||
// app := cli.NewApp()
|
||||
// app.Name = "greet"
|
||||
// app.Usage = "say a greeting"
|
||||
// app.Action = func(c *cli.Context) error {
|
||||
// println("Greetings")
|
||||
// }
|
||||
//
|
||||
// app.Run(os.Args)
|
||||
// }
|
||||
package cli
|
||||
|
||||
//go:generate python ./generate-flag-types cli -i flag-types.json -o flag_generated.go
|
||||
-286
@@ -1,286 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Command is a subcommand for a cli.App.
|
||||
type Command struct {
|
||||
// The name of the command
|
||||
Name string
|
||||
// short name of the command. Typically one character (deprecated, use `Aliases`)
|
||||
ShortName string
|
||||
// A list of aliases for the command
|
||||
Aliases []string
|
||||
// A short description of the usage of this command
|
||||
Usage string
|
||||
// Custom text to show on USAGE section of help
|
||||
UsageText string
|
||||
// A longer explanation of how the command works
|
||||
Description string
|
||||
// A short description of the arguments of this command
|
||||
ArgsUsage string
|
||||
// The category the command is part of
|
||||
Category string
|
||||
// The function to call when checking for bash command completions
|
||||
BashComplete BashCompleteFunc
|
||||
// An action to execute before any sub-subcommands are run, but after the context is ready
|
||||
// If a non-nil error is returned, no sub-subcommands are run
|
||||
Before BeforeFunc
|
||||
// An action to execute after any subcommands are run, but after the subcommand has finished
|
||||
// It is run even if Action() panics
|
||||
After AfterFunc
|
||||
// The function to call when this command is invoked
|
||||
Action interface{}
|
||||
// TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
|
||||
// of deprecation period has passed, maybe?
|
||||
|
||||
// Execute this function if a usage error occurs.
|
||||
OnUsageError OnUsageErrorFunc
|
||||
// List of child commands
|
||||
Subcommands Commands
|
||||
// List of flags to parse
|
||||
Flags []Flag
|
||||
// Treat all flags as normal arguments if true
|
||||
SkipFlagParsing bool
|
||||
// Skip argument reordering which attempts to move flags before arguments,
|
||||
// but only works if all flags appear after all arguments. This behavior was
|
||||
// removed n version 2 since it only works under specific conditions so we
|
||||
// backport here by exposing it as an option for compatibility.
|
||||
SkipArgReorder bool
|
||||
// Boolean to hide built-in help command
|
||||
HideHelp bool
|
||||
// Boolean to hide this command from help or completion
|
||||
Hidden bool
|
||||
|
||||
// Full name of command for help, defaults to full command name, including parent commands.
|
||||
HelpName string
|
||||
commandNamePath []string
|
||||
}
|
||||
|
||||
// FullName returns the full name of the command.
|
||||
// For subcommands this ensures that parent commands are part of the command path
|
||||
func (c Command) FullName() string {
|
||||
if c.commandNamePath == nil {
|
||||
return c.Name
|
||||
}
|
||||
return strings.Join(c.commandNamePath, " ")
|
||||
}
|
||||
|
||||
// Commands is a slice of Command
|
||||
type Commands []Command
|
||||
|
||||
// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
|
||||
func (c Command) Run(ctx *Context) (err error) {
|
||||
if len(c.Subcommands) > 0 {
|
||||
return c.startApp(ctx)
|
||||
}
|
||||
|
||||
if !c.HideHelp && (HelpFlag != BoolFlag{}) {
|
||||
// append help to flags
|
||||
c.Flags = append(
|
||||
c.Flags,
|
||||
HelpFlag,
|
||||
)
|
||||
}
|
||||
|
||||
set, err := flagSet(c.Name, c.Flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
set.SetOutput(ioutil.Discard)
|
||||
|
||||
if c.SkipFlagParsing {
|
||||
err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
|
||||
} else if !c.SkipArgReorder {
|
||||
firstFlagIndex := -1
|
||||
terminatorIndex := -1
|
||||
for index, arg := range ctx.Args() {
|
||||
if arg == "--" {
|
||||
terminatorIndex = index
|
||||
break
|
||||
} else if arg == "-" {
|
||||
// Do nothing. A dash alone is not really a flag.
|
||||
continue
|
||||
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
|
||||
firstFlagIndex = index
|
||||
}
|
||||
}
|
||||
|
||||
if firstFlagIndex > -1 {
|
||||
args := ctx.Args()
|
||||
regularArgs := make([]string, len(args[1:firstFlagIndex]))
|
||||
copy(regularArgs, args[1:firstFlagIndex])
|
||||
|
||||
var flagArgs []string
|
||||
if terminatorIndex > -1 {
|
||||
flagArgs = args[firstFlagIndex:terminatorIndex]
|
||||
regularArgs = append(regularArgs, args[terminatorIndex:]...)
|
||||
} else {
|
||||
flagArgs = args[firstFlagIndex:]
|
||||
}
|
||||
|
||||
err = set.Parse(append(flagArgs, regularArgs...))
|
||||
} else {
|
||||
err = set.Parse(ctx.Args().Tail())
|
||||
}
|
||||
} else {
|
||||
err = set.Parse(ctx.Args().Tail())
|
||||
}
|
||||
|
||||
nerr := normalizeFlags(c.Flags, set)
|
||||
if nerr != nil {
|
||||
fmt.Fprintln(ctx.App.Writer, nerr)
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
ShowCommandHelp(ctx, c.Name)
|
||||
return nerr
|
||||
}
|
||||
|
||||
context := NewContext(ctx.App, set, ctx)
|
||||
if checkCommandCompletions(context, c.Name) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if c.OnUsageError != nil {
|
||||
err := c.OnUsageError(ctx, err, false)
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage:", err.Error())
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
ShowCommandHelp(ctx, c.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
if checkCommandHelp(context, c.Name) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.After != nil {
|
||||
defer func() {
|
||||
afterErr := c.After(context)
|
||||
if afterErr != nil {
|
||||
HandleExitCoder(err)
|
||||
if err != nil {
|
||||
err = NewMultiError(err, afterErr)
|
||||
} else {
|
||||
err = afterErr
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if c.Before != nil {
|
||||
err = c.Before(context)
|
||||
if err != nil {
|
||||
fmt.Fprintln(ctx.App.Writer, err)
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
ShowCommandHelp(ctx, c.Name)
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.Action == nil {
|
||||
c.Action = helpSubcommand.Action
|
||||
}
|
||||
|
||||
context.Command = c
|
||||
err = HandleAction(c.Action, context)
|
||||
|
||||
if err != nil {
|
||||
HandleExitCoder(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Names returns the names including short names and aliases.
|
||||
func (c Command) Names() []string {
|
||||
names := []string{c.Name}
|
||||
|
||||
if c.ShortName != "" {
|
||||
names = append(names, c.ShortName)
|
||||
}
|
||||
|
||||
return append(names, c.Aliases...)
|
||||
}
|
||||
|
||||
// HasName returns true if Command.Name or Command.ShortName matches given name
|
||||
func (c Command) HasName(name string) bool {
|
||||
for _, n := range c.Names() {
|
||||
if n == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c Command) startApp(ctx *Context) error {
|
||||
app := NewApp()
|
||||
app.Metadata = ctx.App.Metadata
|
||||
// set the name and usage
|
||||
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
|
||||
if c.HelpName == "" {
|
||||
app.HelpName = c.HelpName
|
||||
} else {
|
||||
app.HelpName = app.Name
|
||||
}
|
||||
|
||||
if c.Description != "" {
|
||||
app.Usage = c.Description
|
||||
} else {
|
||||
app.Usage = c.Usage
|
||||
}
|
||||
|
||||
// set CommandNotFound
|
||||
app.CommandNotFound = ctx.App.CommandNotFound
|
||||
|
||||
// set the flags and commands
|
||||
app.Commands = c.Subcommands
|
||||
app.Flags = c.Flags
|
||||
app.HideHelp = c.HideHelp
|
||||
|
||||
app.Version = ctx.App.Version
|
||||
app.HideVersion = ctx.App.HideVersion
|
||||
app.Compiled = ctx.App.Compiled
|
||||
app.Author = ctx.App.Author
|
||||
app.Email = ctx.App.Email
|
||||
app.Writer = ctx.App.Writer
|
||||
|
||||
app.categories = CommandCategories{}
|
||||
for _, command := range c.Subcommands {
|
||||
app.categories = app.categories.AddCommand(command.Category, command)
|
||||
}
|
||||
|
||||
sort.Sort(app.categories)
|
||||
|
||||
// bash completion
|
||||
app.EnableBashCompletion = ctx.App.EnableBashCompletion
|
||||
if c.BashComplete != nil {
|
||||
app.BashComplete = c.BashComplete
|
||||
}
|
||||
|
||||
// set the actions
|
||||
app.Before = c.Before
|
||||
app.After = c.After
|
||||
if c.Action != nil {
|
||||
app.Action = c.Action
|
||||
} else {
|
||||
app.Action = helpSubcommand.Action
|
||||
}
|
||||
|
||||
for index, cc := range app.Commands {
|
||||
app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
|
||||
}
|
||||
|
||||
return app.RunAsSubcommand(ctx)
|
||||
}
|
||||
|
||||
// VisibleFlags returns a slice of the Flags with Hidden=false
|
||||
func (c Command) VisibleFlags() []Flag {
|
||||
return visibleFlags(c.Flags)
|
||||
}
|
||||
-155
@@ -1,155 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCommandFlagParsing(t *testing.T) {
|
||||
cases := []struct {
|
||||
testArgs []string
|
||||
skipFlagParsing bool
|
||||
skipArgReorder bool
|
||||
expectedErr error
|
||||
}{
|
||||
// Test normal "not ignoring flags" flow
|
||||
{[]string{"test-cmd", "blah", "blah", "-break"}, false, false, errors.New("flag provided but not defined: -break")},
|
||||
|
||||
// Test no arg reorder
|
||||
{[]string{"test-cmd", "blah", "blah", "-break"}, false, true, nil},
|
||||
|
||||
{[]string{"test-cmd", "blah", "blah"}, true, false, nil}, // Test SkipFlagParsing without any args that look like flags
|
||||
{[]string{"test-cmd", "blah", "-break"}, true, false, nil}, // Test SkipFlagParsing with random flag arg
|
||||
{[]string{"test-cmd", "blah", "-help"}, true, false, nil}, // Test SkipFlagParsing with "special" help flag arg
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
app := NewApp()
|
||||
app.Writer = ioutil.Discard
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.Parse(c.testArgs)
|
||||
|
||||
context := NewContext(app, set, nil)
|
||||
|
||||
command := Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(_ *Context) error { return nil },
|
||||
SkipFlagParsing: c.skipFlagParsing,
|
||||
SkipArgReorder: c.skipArgReorder,
|
||||
}
|
||||
|
||||
err := command.Run(context)
|
||||
|
||||
expect(t, err, c.expectedErr)
|
||||
expect(t, []string(context.Args()), c.testArgs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
|
||||
app := NewApp()
|
||||
app.Commands = []Command{
|
||||
{
|
||||
Name: "bar",
|
||||
Before: func(c *Context) error {
|
||||
return fmt.Errorf("before error")
|
||||
},
|
||||
After: func(c *Context) error {
|
||||
return fmt.Errorf("after error")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := app.Run([]string{"foo", "bar"})
|
||||
if err == nil {
|
||||
t.Fatalf("expected to receive error from Run, got none")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "before error") {
|
||||
t.Errorf("expected text of error from Before method, but got none in \"%v\"", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "after error") {
|
||||
t.Errorf("expected text of error from After method, but got none in \"%v\"", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommand_Run_BeforeSavesMetadata(t *testing.T) {
|
||||
var receivedMsgFromAction string
|
||||
var receivedMsgFromAfter string
|
||||
|
||||
app := NewApp()
|
||||
app.Commands = []Command{
|
||||
{
|
||||
Name: "bar",
|
||||
Before: func(c *Context) error {
|
||||
c.App.Metadata["msg"] = "hello world"
|
||||
return nil
|
||||
},
|
||||
Action: func(c *Context) error {
|
||||
msg, ok := c.App.Metadata["msg"]
|
||||
if !ok {
|
||||
return errors.New("msg not found")
|
||||
}
|
||||
receivedMsgFromAction = msg.(string)
|
||||
return nil
|
||||
},
|
||||
After: func(c *Context) error {
|
||||
msg, ok := c.App.Metadata["msg"]
|
||||
if !ok {
|
||||
return errors.New("msg not found")
|
||||
}
|
||||
receivedMsgFromAfter = msg.(string)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := app.Run([]string{"foo", "bar"})
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error from Run, got %s", err)
|
||||
}
|
||||
|
||||
expectedMsg := "hello world"
|
||||
|
||||
if receivedMsgFromAction != expectedMsg {
|
||||
t.Fatalf("expected msg from Action to match. Given: %q\nExpected: %q",
|
||||
receivedMsgFromAction, expectedMsg)
|
||||
}
|
||||
if receivedMsgFromAfter != expectedMsg {
|
||||
t.Fatalf("expected msg from After to match. Given: %q\nExpected: %q",
|
||||
receivedMsgFromAction, expectedMsg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) {
|
||||
app := NewApp()
|
||||
app.Commands = []Command{
|
||||
{
|
||||
Name: "bar",
|
||||
Flags: []Flag{
|
||||
IntFlag{Name: "flag"},
|
||||
},
|
||||
OnUsageError: func(c *Context, err error, _ bool) error {
|
||||
if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") {
|
||||
t.Errorf("Expect an invalid value error, but got \"%v\"", err)
|
||||
}
|
||||
return errors.New("intercepted: " + err.Error())
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := app.Run([]string{"foo", "bar", "--flag=wrong"})
|
||||
if err == nil {
|
||||
t.Fatalf("expected to receive error from Run, got none")
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(err.Error(), "intercepted: invalid value") {
|
||||
t.Errorf("Expect an intercepted error, but got \"%v\"", err)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user