@ -1,186 +0,0 @@ | |||||
{ | |||||
"ImportPath": "github.com/tendermint/netmon", | |||||
"GoVersion": "go1.5", | |||||
"Packages": [ | |||||
"./..." | |||||
], | |||||
"Deps": [ | |||||
{ | |||||
"ImportPath": "github.com/codegangsta/cli", | |||||
"Comment": "1.2.0-193-gf9cc300", | |||||
"Rev": "f9cc3001e04f9783cb4ad08ca6791aa07134787c" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/golang/snappy", | |||||
"Rev": "1963d058044b19e16595f80d5050fa54e2070438" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/gorilla/websocket", | |||||
"Rev": "234959944d9cf05229b02e8b386e5cffe1e4e04a" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/inconshreveable/log15", | |||||
"Comment": "v2.3-48-g210d6fd", | |||||
"Rev": "210d6fdc4d979ef6579778f1b6ed84571454abb4" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/mattn/go-colorable", | |||||
"Rev": "4af63d73f5bea08b682ad2cd198b1e607afd6be0" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/mattn/go-isatty", | |||||
"Rev": "56b76bdf51f7708750eac80fa38b952bb9f32639" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/naoina/go-stringutil", | |||||
"Rev": "6b638e95a32d0c1131db0e7fe83775cbea4a0d0b" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/naoina/toml", | |||||
"Rev": "751171607256bb66e64c9f0220c00662420c38e9" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/onsi/ginkgo", | |||||
"Comment": "v1.2.0-42-g07d85e6", | |||||
"Rev": "07d85e6b10c4289c7d612f9b13f45ba36f66d55b" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/onsi/gomega", | |||||
"Comment": "v1.0-81-gad93e46", | |||||
"Rev": "ad93e463829d54602c66e94813bc1eb9b10d454c" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/rcrowley/go-metrics", | |||||
"Rev": "51425a2415d21afadfd55cd93432c0bc69e9598d" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/sfreiberg/gotwilio", | |||||
"Rev": "f024bbefe80fdb7bcc8c43b6add05dae97744e0e" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/stathat/go", | |||||
"Rev": "cf69b0bcb80478755dc0ea1120b36000e35dcbbb" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/syndtr/goleveldb/leveldb", | |||||
"Rev": "b7c1cafa822344831a63ad9c8fafb1556e66d33d" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/tendermint/ed25519", | |||||
"Rev": "fdac6641497281ed1cc368687ec6377e96e02b24" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/tendermint/flowcontrol", | |||||
"Rev": "84d9671090430e8ec80e35b339907e0579b999eb" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/tendermint/go-alert", | |||||
"Rev": "b824a5721dd8bdda6e5f3033ea35df3d82e4516d" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/tendermint/go-common", | |||||
"Rev": "02022e356aab724d17b8cc2e4e035ef49b6099af" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/tendermint/go-config", | |||||
"Rev": "3b895c7ce4999ee6fff7b7ca6253f0b41d9bf85c" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/tendermint/go-crypto", | |||||
"Rev": "1fb7234ff5138df3df93678dd62cc4bc892b2f06" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/tendermint/go-db", | |||||
"Rev": "a7878f1d0d8eaebf15f87bc2df15f7a1088cce7f" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/tendermint/go-event-meter", | |||||
"Rev": "b94cb932ca63a80e711b8b8f4dc16baad0c54b98" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/tendermint/go-events", | |||||
"Rev": "7b75ca7bb55aa25e9ef765eb8c0b69486b227357" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/tendermint/go-logger", | |||||
"Rev": "980f02a5001b46f02ab3fbb036531d4ea789d2bf" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/tendermint/go-merkle", | |||||
"Rev": "67b535ce9633be7df575dc3a7833fa2301020c25" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/tendermint/go-p2p", | |||||
"Rev": "9bc75eaf24c4f77031c067f9e09258f74f744aa7" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/tendermint/go-process", | |||||
"Rev": "ba01cfbb58d446673beff17e72883cb49c835fb9" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/tendermint/go-rpc/client", | |||||
"Rev": "fbc5ac80524e467d542ea9b4a55034255ee0691a" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/tendermint/go-rpc/server", | |||||
"Rev": "fbc5ac80524e467d542ea9b4a55034255ee0691a" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/tendermint/go-rpc/types", | |||||
"Rev": "fbc5ac80524e467d542ea9b4a55034255ee0691a" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/tendermint/go-wire", | |||||
"Comment": "1.0rc-26-gc7ef03a", | |||||
"Rev": "c7ef03a4e85532dd21bfa644bd8dd4a89afd44e0" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/tendermint/log15", | |||||
"Comment": "v2.3-36-g6e46075", | |||||
"Rev": "6e460758f10ef42a4724b8e4a82fee59aaa0e41d" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/tendermint/tendermint/config/tendermint", | |||||
"Comment": "0.2-116-g85f6db2", | |||||
"Rev": "85f6db2435393a7426c6be2cbfb658f98932b52d" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/tendermint/tendermint/rpc/core/types", | |||||
"Comment": "0.2-116-g85f6db2", | |||||
"Rev": "85f6db2435393a7426c6be2cbfb658f98932b52d" | |||||
}, | |||||
{ | |||||
"ImportPath": "github.com/tendermint/tendermint/types", | |||||
"Comment": "0.2-116-g85f6db2", | |||||
"Rev": "85f6db2435393a7426c6be2cbfb658f98932b52d" | |||||
}, | |||||
{ | |||||
"ImportPath": "golang.org/x/crypto/curve25519", | |||||
"Rev": "1f22c0103821b9390939b6776727195525381532" | |||||
}, | |||||
{ | |||||
"ImportPath": "golang.org/x/crypto/nacl/box", | |||||
"Rev": "1f22c0103821b9390939b6776727195525381532" | |||||
}, | |||||
{ | |||||
"ImportPath": "golang.org/x/crypto/nacl/secretbox", | |||||
"Rev": "1f22c0103821b9390939b6776727195525381532" | |||||
}, | |||||
{ | |||||
"ImportPath": "golang.org/x/crypto/poly1305", | |||||
"Rev": "1f22c0103821b9390939b6776727195525381532" | |||||
}, | |||||
{ | |||||
"ImportPath": "golang.org/x/crypto/ripemd160", | |||||
"Rev": "1f22c0103821b9390939b6776727195525381532" | |||||
}, | |||||
{ | |||||
"ImportPath": "golang.org/x/crypto/salsa20/salsa", | |||||
"Rev": "1f22c0103821b9390939b6776727195525381532" | |||||
}, | |||||
{ | |||||
"ImportPath": "golang.org/x/sys/unix", | |||||
"Rev": "eb2c74142fd19a79b3f237334c7384d5167b1b46" | |||||
} | |||||
] | |||||
} |
@ -1,5 +0,0 @@ | |||||
This directory tree is generated automatically by godep. | |||||
Please do not edit. | |||||
See https://github.com/tools/godep for more information. |
@ -1,19 +0,0 @@ | |||||
language: go | |||||
sudo: false | |||||
go: | |||||
- 1.0.3 | |||||
- 1.1.2 | |||||
- 1.2.2 | |||||
- 1.3.3 | |||||
- 1.4.2 | |||||
- 1.5.1 | |||||
- tip | |||||
matrix: | |||||
allow_failures: | |||||
- go: tip | |||||
script: | |||||
- go vet ./... | |||||
- go test -v ./... |
@ -1,21 +0,0 @@ | |||||
Copyright (C) 2013 Jeremy Saenz | |||||
All Rights Reserved. | |||||
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. |
@ -1,341 +0,0 @@ | |||||
[![Coverage](http://gocover.io/_badge/github.com/codegangsta/cli?0)](http://gocover.io/github.com/codegangsta/cli) | |||||
[![Build Status](https://travis-ci.org/codegangsta/cli.png?branch=master)](https://travis-ci.org/codegangsta/cli) | |||||
[![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli) | |||||
# cli.go | |||||
`cli.go` is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. | |||||
## Overview | |||||
Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app. | |||||
**This is where `cli.go` comes into play.** `cli.go` makes command line programming fun, organized, and expressive! | |||||
## Installation | |||||
Make sure you have a working Go environment (go 1.1+ is *required*). [See the install instructions](http://golang.org/doc/install.html). | |||||
To install `cli.go`, simply run: | |||||
``` | |||||
$ go get github.com/codegangsta/cli | |||||
``` | |||||
Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used: | |||||
``` | |||||
export PATH=$PATH:$GOPATH/bin | |||||
``` | |||||
## Getting Started | |||||
One of the philosophies behind `cli.go` is that an API should be playful and full of discovery. So a `cli.go` app can be as little as one line of code in `main()`. | |||||
``` go | |||||
package main | |||||
import ( | |||||
"os" | |||||
"github.com/codegangsta/cli" | |||||
) | |||||
func main() { | |||||
cli.NewApp().Run(os.Args) | |||||
} | |||||
``` | |||||
This app will run and show help text, but is not very useful. Let's give an action to execute and some help documentation: | |||||
``` go | |||||
package main | |||||
import ( | |||||
"os" | |||||
"github.com/codegangsta/cli" | |||||
) | |||||
func main() { | |||||
app := cli.NewApp() | |||||
app.Name = "boom" | |||||
app.Usage = "make an explosive entrance" | |||||
app.Action = func(c *cli.Context) { | |||||
println("boom! I say!") | |||||
} | |||||
app.Run(os.Args) | |||||
} | |||||
``` | |||||
Running this already gives you a ton of functionality, plus support for things like subcommands and flags, which are covered below. | |||||
## Example | |||||
Being a programmer can be a lonely job. Thankfully by the power of automation that is not the case! Let's create a greeter app to fend off our demons of loneliness! | |||||
Start by creating a directory named `greet`, and within it, add a file, `greet.go` with the following code in it: | |||||
``` go | |||||
package main | |||||
import ( | |||||
"os" | |||||
"github.com/codegangsta/cli" | |||||
) | |||||
func main() { | |||||
app := cli.NewApp() | |||||
app.Name = "greet" | |||||
app.Usage = "fight the loneliness!" | |||||
app.Action = func(c *cli.Context) { | |||||
println("Hello friend!") | |||||
} | |||||
app.Run(os.Args) | |||||
} | |||||
``` | |||||
Install our command to the `$GOPATH/bin` directory: | |||||
``` | |||||
$ go install | |||||
``` | |||||
Finally run our new command: | |||||
``` | |||||
$ greet | |||||
Hello friend! | |||||
``` | |||||
`cli.go` also generates neat help text: | |||||
``` | |||||
$ greet help | |||||
NAME: | |||||
greet - fight the loneliness! | |||||
USAGE: | |||||
greet [global options] command [command options] [arguments...] | |||||
VERSION: | |||||
0.0.0 | |||||
COMMANDS: | |||||
help, h Shows a list of commands or help for one command | |||||
GLOBAL OPTIONS | |||||
--version Shows version information | |||||
``` | |||||
### Arguments | |||||
You can lookup arguments by calling the `Args` function on `cli.Context`. | |||||
``` go | |||||
... | |||||
app.Action = func(c *cli.Context) { | |||||
println("Hello", c.Args()[0]) | |||||
} | |||||
... | |||||
``` | |||||
### Flags | |||||
Setting and querying flags is simple. | |||||
``` go | |||||
... | |||||
app.Flags = []cli.Flag { | |||||
cli.StringFlag{ | |||||
Name: "lang", | |||||
Value: "english", | |||||
Usage: "language for the greeting", | |||||
}, | |||||
} | |||||
app.Action = func(c *cli.Context) { | |||||
name := "someone" | |||||
if len(c.Args()) > 0 { | |||||
name = c.Args()[0] | |||||
} | |||||
if c.String("lang") == "spanish" { | |||||
println("Hola", name) | |||||
} else { | |||||
println("Hello", name) | |||||
} | |||||
} | |||||
... | |||||
``` | |||||
You can also set a destination variable for a flag, to which the content will be scanned. | |||||
``` go | |||||
... | |||||
var language string | |||||
app.Flags = []cli.Flag { | |||||
cli.StringFlag{ | |||||
Name: "lang", | |||||
Value: "english", | |||||
Usage: "language for the greeting", | |||||
Destination: &language, | |||||
}, | |||||
} | |||||
app.Action = func(c *cli.Context) { | |||||
name := "someone" | |||||
if len(c.Args()) > 0 { | |||||
name = c.Args()[0] | |||||
} | |||||
if language == "spanish" { | |||||
println("Hola", name) | |||||
} else { | |||||
println("Hello", name) | |||||
} | |||||
} | |||||
... | |||||
``` | |||||
See full list of flags at http://godoc.org/github.com/codegangsta/cli | |||||
#### Alternate Names | |||||
You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g. | |||||
``` go | |||||
app.Flags = []cli.Flag { | |||||
cli.StringFlag{ | |||||
Name: "lang, l", | |||||
Value: "english", | |||||
Usage: "language for the greeting", | |||||
}, | |||||
} | |||||
``` | |||||
That flag can then be set with `--lang spanish` or `-l spanish`. Note that giving two different forms of the same flag in the same command invocation is an error. | |||||
#### Values from the Environment | |||||
You can also have the default value set from the environment via `EnvVar`. e.g. | |||||
``` go | |||||
app.Flags = []cli.Flag { | |||||
cli.StringFlag{ | |||||
Name: "lang, l", | |||||
Value: "english", | |||||
Usage: "language for the greeting", | |||||
EnvVar: "APP_LANG", | |||||
}, | |||||
} | |||||
``` | |||||
The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default. | |||||
``` go | |||||
app.Flags = []cli.Flag { | |||||
cli.StringFlag{ | |||||
Name: "lang, l", | |||||
Value: "english", | |||||
Usage: "language for the greeting", | |||||
EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG", | |||||
}, | |||||
} | |||||
``` | |||||
### Subcommands | |||||
Subcommands can be defined for a more git-like command line app. | |||||
```go | |||||
... | |||||
app.Commands = []cli.Command{ | |||||
{ | |||||
Name: "add", | |||||
Aliases: []string{"a"}, | |||||
Usage: "add a task to the list", | |||||
Action: func(c *cli.Context) { | |||||
println("added task: ", c.Args().First()) | |||||
}, | |||||
}, | |||||
{ | |||||
Name: "complete", | |||||
Aliases: []string{"c"}, | |||||
Usage: "complete a task on the list", | |||||
Action: func(c *cli.Context) { | |||||
println("completed task: ", c.Args().First()) | |||||
}, | |||||
}, | |||||
{ | |||||
Name: "template", | |||||
Aliases: []string{"r"}, | |||||
Usage: "options for task templates", | |||||
Subcommands: []cli.Command{ | |||||
{ | |||||
Name: "add", | |||||
Usage: "add a new template", | |||||
Action: func(c *cli.Context) { | |||||
println("new task template: ", c.Args().First()) | |||||
}, | |||||
}, | |||||
{ | |||||
Name: "remove", | |||||
Usage: "remove an existing template", | |||||
Action: func(c *cli.Context) { | |||||
println("removed task template: ", c.Args().First()) | |||||
}, | |||||
}, | |||||
}, | |||||
}, | |||||
} | |||||
... | |||||
``` | |||||
### Bash Completion | |||||
You can enable completion commands by setting the `EnableBashCompletion` | |||||
flag on the `App` object. By default, this setting will only auto-complete to | |||||
show an app's subcommands, but you can write your own completion methods for | |||||
the App or its subcommands. | |||||
```go | |||||
... | |||||
var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"} | |||||
app := cli.NewApp() | |||||
app.EnableBashCompletion = true | |||||
app.Commands = []cli.Command{ | |||||
{ | |||||
Name: "complete", | |||||
Aliases: []string{"c"}, | |||||
Usage: "complete a task on the list", | |||||
Action: func(c *cli.Context) { | |||||
println("completed task: ", c.Args().First()) | |||||
}, | |||||
BashComplete: func(c *cli.Context) { | |||||
// This will complete if no args are passed | |||||
if len(c.Args()) > 0 { | |||||
return | |||||
} | |||||
for _, t := range tasks { | |||||
fmt.Println(t) | |||||
} | |||||
}, | |||||
} | |||||
} | |||||
... | |||||
``` | |||||
#### To Enable | |||||
Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while | |||||
setting the `PROG` variable to the name of your program: | |||||
`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` | |||||
#### To Distribute | |||||
Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename | |||||
it to the name of the program you wish to add autocomplete support for (or | |||||
automatically install it there if you are distributing a package). Don't forget | |||||
to source the file to make it active in the current shell. | |||||
``` | |||||
sudo cp src/bash_autocomplete /etc/bash_completion.d/<myprogram> | |||||
source /etc/bash_completion.d/<myprogram> | |||||
``` | |||||
Alternatively, you can just document that users should source the generic | |||||
`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set | |||||
to the name of their program (as above). | |||||
## Contribution Guidelines | |||||
Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch. | |||||
If you have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together. | |||||
If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out. |
@ -1,337 +0,0 @@ | |||||
package cli | |||||
import ( | |||||
"fmt" | |||||
"io" | |||||
"io/ioutil" | |||||
"os" | |||||
"path" | |||||
"time" | |||||
) | |||||
// App is the main structure of a cli application. It is recomended 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 | |||||
// Description of the program argument format. | |||||
ArgsUsage string | |||||
// Version of the program | |||||
Version 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 | |||||
HideVersion bool | |||||
// An action to execute when the bash-completion flag is set | |||||
BashComplete func(context *Context) | |||||
// 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 func(context *Context) error | |||||
// An action to execute after any subcommands are run, but after the subcommand has finished | |||||
// It is run even if Action() panics | |||||
After func(context *Context) error | |||||
// The action to execute when no subcommands are specified | |||||
Action func(context *Context) | |||||
// Execute this function if the proper command cannot be found | |||||
CommandNotFound func(context *Context, command string) | |||||
// 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 | |||||
} | |||||
// 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() | |||||
} | |||||
// Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action. | |||||
func NewApp() *App { | |||||
return &App{ | |||||
Name: path.Base(os.Args[0]), | |||||
HelpName: path.Base(os.Args[0]), | |||||
Usage: "A new cli application", | |||||
Version: "0.0.0", | |||||
BashComplete: DefaultAppComplete, | |||||
Action: helpCommand.Action, | |||||
Compiled: compileTime(), | |||||
Writer: os.Stdout, | |||||
} | |||||
} | |||||
// 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) { | |||||
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 | |||||
// append help to commands | |||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp { | |||||
a.Commands = append(a.Commands, helpCommand) | |||||
if (HelpFlag != BoolFlag{}) { | |||||
a.appendFlag(HelpFlag) | |||||
} | |||||
} | |||||
//append version/help flags | |||||
if a.EnableBashCompletion { | |||||
a.appendFlag(BashCompletionFlag) | |||||
} | |||||
if !a.HideVersion { | |||||
a.appendFlag(VersionFlag) | |||||
} | |||||
// parse flags | |||||
set := flagSet(a.Name, a.Flags) | |||||
set.SetOutput(ioutil.Discard) | |||||
err = set.Parse(arguments[1:]) | |||||
nerr := normalizeFlags(a.Flags, set) | |||||
if nerr != nil { | |||||
fmt.Fprintln(a.Writer, nerr) | |||||
context := NewContext(a, set, nil) | |||||
ShowAppHelp(context) | |||||
return nerr | |||||
} | |||||
context := NewContext(a, set, nil) | |||||
if checkCompletions(context) { | |||||
return nil | |||||
} | |||||
if err != nil { | |||||
fmt.Fprintln(a.Writer, "Incorrect Usage.") | |||||
fmt.Fprintln(a.Writer) | |||||
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() { | |||||
afterErr := a.After(context) | |||||
if afterErr != nil { | |||||
if err != nil { | |||||
err = NewMultiError(err, afterErr) | |||||
} else { | |||||
err = afterErr | |||||
} | |||||
} | |||||
}() | |||||
} | |||||
if a.Before != nil { | |||||
err := a.Before(context) | |||||
if err != nil { | |||||
fmt.Fprintln(a.Writer, err) | |||||
fmt.Fprintln(a.Writer) | |||||
ShowAppHelp(context) | |||||
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 | |||||
a.Action(context) | |||||
return nil | |||||
} | |||||
// Another entry point to the cli app, takes care of passing arguments and error handling | |||||
func (a *App) RunAndExitOnError() { | |||||
if err := a.Run(os.Args); err != nil { | |||||
fmt.Fprintln(os.Stderr, err) | |||||
os.Exit(1) | |||||
} | |||||
} | |||||
// 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 | |||||
// append flags | |||||
if a.EnableBashCompletion { | |||||
a.appendFlag(BashCompletionFlag) | |||||
} | |||||
// parse flags | |||||
set := flagSet(a.Name, a.Flags) | |||||
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 { | |||||
fmt.Fprintln(a.Writer, "Incorrect Usage.") | |||||
fmt.Fprintln(a.Writer) | |||||
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 { | |||||
if err != nil { | |||||
err = NewMultiError(err, afterErr) | |||||
} else { | |||||
err = afterErr | |||||
} | |||||
} | |||||
}() | |||||
} | |||||
if a.Before != nil { | |||||
err := a.Before(context) | |||||
if err != nil { | |||||
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 | |||||
a.Action(context) | |||||
return nil | |||||
} | |||||
// 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 | |||||
} | |||||
func (a *App) hasFlag(flag Flag) bool { | |||||
for _, f := range a.Flags { | |||||
if flag == f { | |||||
return true | |||||
} | |||||
} | |||||
return false | |||||
} | |||||
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) | |||||
} |
@ -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 |
@ -1,5 +0,0 @@ | |||||
autoload -U compinit && compinit | |||||
autoload -U bashcompinit && bashcompinit | |||||
script_dir=$(dirname $0) | |||||
source ${script_dir}/bash_autocomplete |
@ -1,40 +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) { | |||||
// println("Greetings") | |||||
// } | |||||
// | |||||
// app.Run(os.Args) | |||||
// } | |||||
package cli | |||||
import ( | |||||
"strings" | |||||
) | |||||
type MultiError struct { | |||||
Errors []error | |||||
} | |||||
func NewMultiError(err ...error) MultiError { | |||||
return MultiError{Errors: err} | |||||
} | |||||
func (m MultiError) Error() string { | |||||
errs := make([]string, len(m.Errors)) | |||||
for i, err := range m.Errors { | |||||
errs[i] = err.Error() | |||||
} | |||||
return strings.Join(errs, "\n") | |||||
} |
@ -1,239 +0,0 @@ | |||||
package cli | |||||
import ( | |||||
"fmt" | |||||
"io/ioutil" | |||||
"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 | |||||
// A longer explanation of how the command works | |||||
Description string | |||||
// A short description of the arguments of this command | |||||
ArgsUsage string | |||||
// The function to call when checking for bash command completions | |||||
BashComplete func(context *Context) | |||||
// 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 func(context *Context) error | |||||
// An action to execute after any subcommands are run, but after the subcommand has finished | |||||
// It is run even if Action() panics | |||||
After func(context *Context) error | |||||
// The function to call when this command is invoked | |||||
Action func(context *Context) | |||||
// List of child commands | |||||
Subcommands []Command | |||||
// List of flags to parse | |||||
Flags []Flag | |||||
// Treat all flags as normal arguments if true | |||||
SkipFlagParsing bool | |||||
// Boolean to hide built-in help command | |||||
HideHelp bool | |||||
// Full name of command for help, defaults to full command name, including parent commands. | |||||
HelpName string | |||||
commandNamePath []string | |||||
} | |||||
// 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, " ") | |||||
} | |||||
// 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, | |||||
) | |||||
} | |||||
if ctx.App.EnableBashCompletion { | |||||
c.Flags = append(c.Flags, BashCompletionFlag) | |||||
} | |||||
set := flagSet(c.Name, c.Flags) | |||||
set.SetOutput(ioutil.Discard) | |||||
if !c.SkipFlagParsing { | |||||
firstFlagIndex := -1 | |||||
terminatorIndex := -1 | |||||
for index, arg := range ctx.Args() { | |||||
if arg == "--" { | |||||
terminatorIndex = index | |||||
break | |||||
} 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 { | |||||
if c.SkipFlagParsing { | |||||
err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...)) | |||||
} | |||||
} | |||||
if err != nil { | |||||
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") | |||||
fmt.Fprintln(ctx.App.Writer) | |||||
ShowCommandHelp(ctx, c.Name) | |||||
return err | |||||
} | |||||
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 checkCommandHelp(context, c.Name) { | |||||
return nil | |||||
} | |||||
if c.After != nil { | |||||
defer func() { | |||||
afterErr := c.After(context) | |||||
if afterErr != nil { | |||||
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) | |||||
return err | |||||
} | |||||
} | |||||
context.Command = c | |||||
c.Action(context) | |||||
return nil | |||||
} | |||||
func (c Command) Names() []string { | |||||
names := []string{c.Name} | |||||
if c.ShortName != "" { | |||||
names = append(names, c.ShortName) | |||||
} | |||||
return append(names, c.Aliases...) | |||||
} | |||||
// 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() | |||||
// 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 = fmt.Sprintf("%s %s", ctx.App.Name, c.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 | |||||
// 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 | |||||
} | |||||
var newCmds []Command | |||||
for _, cc := range app.Commands { | |||||
cc.commandNamePath = []string{c.Name, cc.Name} | |||||
newCmds = append(newCmds, cc) | |||||
} | |||||
app.Commands = newCmds | |||||
return app.RunAsSubcommand(ctx) | |||||
} |
@ -1,388 +0,0 @@ | |||||
package cli | |||||
import ( | |||||
"errors" | |||||
"flag" | |||||
"strconv" | |||||
"strings" | |||||
"time" | |||||
) | |||||
// Context is a type that is passed through to | |||||
// each Handler action in a cli application. Context | |||||
// can be used to retrieve context-specific Args and | |||||
// parsed command-line options. | |||||
type Context struct { | |||||
App *App | |||||
Command Command | |||||
flagSet *flag.FlagSet | |||||
setFlags map[string]bool | |||||
globalSetFlags map[string]bool | |||||
parentContext *Context | |||||
} | |||||
// Creates a new context. For use in when invoking an App or Command action. | |||||
func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { | |||||
return &Context{App: app, flagSet: set, parentContext: parentCtx} | |||||
} | |||||
// Looks up the value of a local int flag, returns 0 if no int flag exists | |||||
func (c *Context) Int(name string) int { | |||||
return lookupInt(name, c.flagSet) | |||||
} | |||||
// Looks up the value of a local time.Duration flag, returns 0 if no time.Duration flag exists | |||||
func (c *Context) Duration(name string) time.Duration { | |||||
return lookupDuration(name, c.flagSet) | |||||
} | |||||
// Looks up the value of a local float64 flag, returns 0 if no float64 flag exists | |||||
func (c *Context) Float64(name string) float64 { | |||||
return lookupFloat64(name, c.flagSet) | |||||
} | |||||
// Looks up the value of a local bool flag, returns false if no bool flag exists | |||||
func (c *Context) Bool(name string) bool { | |||||
return lookupBool(name, c.flagSet) | |||||
} | |||||
// Looks up the value of a local boolT flag, returns false if no bool flag exists | |||||
func (c *Context) BoolT(name string) bool { | |||||
return lookupBoolT(name, c.flagSet) | |||||
} | |||||
// Looks up the value of a local string flag, returns "" if no string flag exists | |||||
func (c *Context) String(name string) string { | |||||
return lookupString(name, c.flagSet) | |||||
} | |||||
// Looks up the value of a local string slice flag, returns nil if no string slice flag exists | |||||
func (c *Context) StringSlice(name string) []string { | |||||
return lookupStringSlice(name, c.flagSet) | |||||
} | |||||
// Looks up the value of a local int slice flag, returns nil if no int slice flag exists | |||||
func (c *Context) IntSlice(name string) []int { | |||||
return lookupIntSlice(name, c.flagSet) | |||||
} | |||||
// Looks up the value of a local generic flag, returns nil if no generic flag exists | |||||
func (c *Context) Generic(name string) interface{} { | |||||
return lookupGeneric(name, c.flagSet) | |||||
} | |||||
// Looks up the value of a global int flag, returns 0 if no int flag exists | |||||
func (c *Context) GlobalInt(name string) int { | |||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { | |||||
return lookupInt(name, fs) | |||||
} | |||||
return 0 | |||||
} | |||||
// Looks up the value of a global time.Duration flag, returns 0 if no time.Duration flag exists | |||||
func (c *Context) GlobalDuration(name string) time.Duration { | |||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { | |||||
return lookupDuration(name, fs) | |||||
} | |||||
return 0 | |||||
} | |||||
// Looks up the value of a global bool flag, returns false if no bool flag exists | |||||
func (c *Context) GlobalBool(name string) bool { | |||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { | |||||
return lookupBool(name, fs) | |||||
} | |||||
return false | |||||
} | |||||
// Looks up the value of a global string flag, returns "" if no string flag exists | |||||
func (c *Context) GlobalString(name string) string { | |||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { | |||||
return lookupString(name, fs) | |||||
} | |||||
return "" | |||||
} | |||||
// Looks up the value of a global string slice flag, returns nil if no string slice flag exists | |||||
func (c *Context) GlobalStringSlice(name string) []string { | |||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { | |||||
return lookupStringSlice(name, fs) | |||||
} | |||||
return nil | |||||
} | |||||
// Looks up the value of a global int slice flag, returns nil if no int slice flag exists | |||||
func (c *Context) GlobalIntSlice(name string) []int { | |||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { | |||||
return lookupIntSlice(name, fs) | |||||
} | |||||
return nil | |||||
} | |||||
// Looks up the value of a global generic flag, returns nil if no generic flag exists | |||||
func (c *Context) GlobalGeneric(name string) interface{} { | |||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { | |||||
return lookupGeneric(name, fs) | |||||
} | |||||
return nil | |||||
} | |||||
// Returns the number of flags set | |||||
func (c *Context) NumFlags() int { | |||||
return c.flagSet.NFlag() | |||||
} | |||||
// Determines if the flag was actually set | |||||
func (c *Context) IsSet(name string) bool { | |||||
if c.setFlags == nil { | |||||
c.setFlags = make(map[string]bool) | |||||
c.flagSet.Visit(func(f *flag.Flag) { | |||||
c.setFlags[f.Name] = true | |||||
}) | |||||
} | |||||
return c.setFlags[name] == true | |||||
} | |||||
// Determines if the global flag was actually set | |||||
func (c *Context) GlobalIsSet(name string) bool { | |||||
if c.globalSetFlags == nil { | |||||
c.globalSetFlags = make(map[string]bool) | |||||
ctx := c | |||||
if ctx.parentContext != nil { | |||||
ctx = ctx.parentContext | |||||
} | |||||
for ; ctx != nil && c.globalSetFlags[name] == false; ctx = ctx.parentContext { | |||||
ctx.flagSet.Visit(func(f *flag.Flag) { | |||||
c.globalSetFlags[f.Name] = true | |||||
}) | |||||
} | |||||
} | |||||
return c.globalSetFlags[name] | |||||
} | |||||
// Returns a slice of flag names used in this context. | |||||
func (c *Context) FlagNames() (names []string) { | |||||
for _, flag := range c.Command.Flags { | |||||
name := strings.Split(flag.GetName(), ",")[0] | |||||
if name == "help" { | |||||
continue | |||||
} | |||||
names = append(names, name) | |||||
} | |||||
return | |||||
} | |||||
// Returns a slice of global flag names used by the app. | |||||
func (c *Context) GlobalFlagNames() (names []string) { | |||||
for _, flag := range c.App.Flags { | |||||
name := strings.Split(flag.GetName(), ",")[0] | |||||
if name == "help" || name == "version" { | |||||
continue | |||||
} | |||||
names = append(names, name) | |||||
} | |||||
return | |||||
} | |||||
// Returns the parent context, if any | |||||
func (c *Context) Parent() *Context { | |||||
return c.parentContext | |||||
} | |||||
type Args []string | |||||
// Returns the command line arguments associated with the context. | |||||
func (c *Context) Args() Args { | |||||
args := Args(c.flagSet.Args()) | |||||
return args | |||||
} | |||||
// Returns the nth argument, or else a blank string | |||||
func (a Args) Get(n int) string { | |||||
if len(a) > n { | |||||
return a[n] | |||||
} | |||||
return "" | |||||
} | |||||
// Returns the first argument, or else a blank string | |||||
func (a Args) First() string { | |||||
return a.Get(0) | |||||
} | |||||
// Return the rest of the arguments (not the first one) | |||||
// or else an empty string slice | |||||
func (a Args) Tail() []string { | |||||
if len(a) >= 2 { | |||||
return []string(a)[1:] | |||||
} | |||||
return []string{} | |||||
} | |||||
// Checks if there are any arguments present | |||||
func (a Args) Present() bool { | |||||
return len(a) != 0 | |||||
} | |||||
// Swaps arguments at the given indexes | |||||
func (a Args) Swap(from, to int) error { | |||||
if from >= len(a) || to >= len(a) { | |||||
return errors.New("index out of range") | |||||
} | |||||
a[from], a[to] = a[to], a[from] | |||||
return nil | |||||
} | |||||
func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet { | |||||
if ctx.parentContext != nil { | |||||
ctx = ctx.parentContext | |||||
} | |||||
for ; ctx != nil; ctx = ctx.parentContext { | |||||
if f := ctx.flagSet.Lookup(name); f != nil { | |||||
return ctx.flagSet | |||||
} | |||||
} | |||||
return nil | |||||
} | |||||
func lookupInt(name string, set *flag.FlagSet) int { | |||||
f := set.Lookup(name) | |||||
if f != nil { | |||||
val, err := strconv.Atoi(f.Value.String()) | |||||
if err != nil { | |||||
return 0 | |||||
} | |||||
return val | |||||
} | |||||
return 0 | |||||
} | |||||
func lookupDuration(name string, set *flag.FlagSet) time.Duration { | |||||
f := set.Lookup(name) | |||||
if f != nil { | |||||
val, err := time.ParseDuration(f.Value.String()) | |||||
if err == nil { | |||||
return val | |||||
} | |||||
} | |||||
return 0 | |||||
} | |||||
func lookupFloat64(name string, set *flag.FlagSet) float64 { | |||||
f := set.Lookup(name) | |||||
if f != nil { | |||||
val, err := strconv.ParseFloat(f.Value.String(), 64) | |||||
if err != nil { | |||||
return 0 | |||||
} | |||||
return val | |||||
} | |||||
return 0 | |||||
} | |||||
func lookupString(name string, set *flag.FlagSet) string { | |||||
f := set.Lookup(name) | |||||
if f != nil { | |||||
return f.Value.String() | |||||
} | |||||
return "" | |||||
} | |||||
func lookupStringSlice(name string, set *flag.FlagSet) []string { | |||||
f := set.Lookup(name) | |||||
if f != nil { | |||||
return (f.Value.(*StringSlice)).Value() | |||||
} | |||||
return nil | |||||
} | |||||
func lookupIntSlice(name string, set *flag.FlagSet) []int { | |||||
f := set.Lookup(name) | |||||
if f != nil { | |||||
return (f.Value.(*IntSlice)).Value() | |||||
} | |||||
return nil | |||||
} | |||||
func lookupGeneric(name string, set *flag.FlagSet) interface{} { | |||||
f := set.Lookup(name) | |||||
if f != nil { | |||||
return f.Value | |||||
} | |||||
return nil | |||||
} | |||||
func lookupBool(name string, set *flag.FlagSet) bool { | |||||
f := set.Lookup(name) | |||||
if f != nil { | |||||
val, err := strconv.ParseBool(f.Value.String()) | |||||
if err != nil { | |||||
return false | |||||
} | |||||
return val | |||||
} | |||||
return false | |||||
} | |||||
func lookupBoolT(name string, set *flag.FlagSet) bool { | |||||
f := set.Lookup(name) | |||||
if f != nil { | |||||
val, err := strconv.ParseBool(f.Value.String()) | |||||
if err != nil { | |||||
return true | |||||
} | |||||
return val | |||||
} | |||||
return false | |||||
} | |||||
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { | |||||
switch ff.Value.(type) { | |||||
case *StringSlice: | |||||
default: | |||||
set.Set(name, ff.Value.String()) | |||||
} | |||||
} | |||||
func normalizeFlags(flags []Flag, set *flag.FlagSet) error { | |||||
visited := make(map[string]bool) | |||||
set.Visit(func(f *flag.Flag) { | |||||
visited[f.Name] = true | |||||
}) | |||||
for _, f := range flags { | |||||
parts := strings.Split(f.GetName(), ",") | |||||
if len(parts) == 1 { | |||||
continue | |||||
} | |||||
var ff *flag.Flag | |||||
for _, name := range parts { | |||||
name = strings.Trim(name, " ") | |||||
if visited[name] { | |||||
if ff != nil { | |||||
return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name) | |||||
} | |||||
ff = set.Lookup(name) | |||||
} | |||||
} | |||||
if ff == nil { | |||||
continue | |||||
} | |||||
for _, name := range parts { | |||||
name = strings.Trim(name, " ") | |||||
if !visited[name] { | |||||
copyFlag(name, ff, set) | |||||
} | |||||
} | |||||
} | |||||
return nil | |||||
} |
@ -1,527 +0,0 @@ | |||||
package cli | |||||
import ( | |||||
"flag" | |||||
"fmt" | |||||
"os" | |||||
"strconv" | |||||
"strings" | |||||
"time" | |||||
) | |||||
// This flag enables bash-completion for all commands and subcommands | |||||
var BashCompletionFlag = BoolFlag{ | |||||
Name: "generate-bash-completion", | |||||
} | |||||
// This flag prints the version for the application | |||||
var VersionFlag = BoolFlag{ | |||||
Name: "version, v", | |||||
Usage: "print the version", | |||||
} | |||||
// This flag prints the help for all commands and subcommands | |||||
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand | |||||
// unless HideHelp is set to true) | |||||
var HelpFlag = BoolFlag{ | |||||
Name: "help, h", | |||||
Usage: "show help", | |||||
} | |||||
// Flag is a common interface related to parsing flags in cli. | |||||
// For more advanced flag parsing techniques, it is recomended that | |||||
// this interface be implemented. | |||||
type Flag interface { | |||||
fmt.Stringer | |||||
// Apply Flag settings to the given flag set | |||||
Apply(*flag.FlagSet) | |||||
GetName() string | |||||
} | |||||
func flagSet(name string, flags []Flag) *flag.FlagSet { | |||||
set := flag.NewFlagSet(name, flag.ContinueOnError) | |||||
for _, f := range flags { | |||||
f.Apply(set) | |||||
} | |||||
return set | |||||
} | |||||
func eachName(longName string, fn func(string)) { | |||||
parts := strings.Split(longName, ",") | |||||
for _, name := range parts { | |||||
name = strings.Trim(name, " ") | |||||
fn(name) | |||||
} | |||||
} | |||||
// Generic is a generic parseable type identified by a specific flag | |||||
type Generic interface { | |||||
Set(value string) error | |||||
String() string | |||||
} | |||||
// GenericFlag is the flag type for types implementing Generic | |||||
type GenericFlag struct { | |||||
Name string | |||||
Value Generic | |||||
Usage string | |||||
EnvVar string | |||||
} | |||||
// String returns the string representation of the generic flag to display the | |||||
// help text to the user (uses the String() method of the generic flag to show | |||||
// the value) | |||||
func (f GenericFlag) String() string { | |||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s \"%v\"\t%v", prefixFor(f.Name), f.Name, f.Value, f.Usage)) | |||||
} | |||||
// Apply takes the flagset and calls Set on the generic flag with the value | |||||
// provided by the user for parsing by the flag | |||||
func (f GenericFlag) Apply(set *flag.FlagSet) { | |||||
val := f.Value | |||||
if f.EnvVar != "" { | |||||
for _, envVar := range strings.Split(f.EnvVar, ",") { | |||||
envVar = strings.TrimSpace(envVar) | |||||
if envVal := os.Getenv(envVar); envVal != "" { | |||||
val.Set(envVal) | |||||
break | |||||
} | |||||
} | |||||
} | |||||
eachName(f.Name, func(name string) { | |||||
set.Var(f.Value, name, f.Usage) | |||||
}) | |||||
} | |||||
func (f GenericFlag) GetName() string { | |||||
return f.Name | |||||
} | |||||
// StringSlice is an opaque type for []string to satisfy flag.Value | |||||
type StringSlice []string | |||||
// Set appends the string value to the list of values | |||||
func (f *StringSlice) Set(value string) error { | |||||
*f = append(*f, value) | |||||
return nil | |||||
} | |||||
// String returns a readable representation of this value (for usage defaults) | |||||
func (f *StringSlice) String() string { | |||||
return fmt.Sprintf("%s", *f) | |||||
} | |||||
// Value returns the slice of strings set by this flag | |||||
func (f *StringSlice) Value() []string { | |||||
return *f | |||||
} | |||||
// StringSlice is a string flag that can be specified multiple times on the | |||||
// command-line | |||||
type StringSliceFlag struct { | |||||
Name string | |||||
Value *StringSlice | |||||
Usage string | |||||
EnvVar string | |||||
} | |||||
// String returns the usage | |||||
func (f StringSliceFlag) String() string { | |||||
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") | |||||
pref := prefixFor(firstName) | |||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)) | |||||
} | |||||
// Apply populates the flag given the flag set and environment | |||||
func (f StringSliceFlag) Apply(set *flag.FlagSet) { | |||||
if f.EnvVar != "" { | |||||
for _, envVar := range strings.Split(f.EnvVar, ",") { | |||||
envVar = strings.TrimSpace(envVar) | |||||
if envVal := os.Getenv(envVar); envVal != "" { | |||||
newVal := &StringSlice{} | |||||
for _, s := range strings.Split(envVal, ",") { | |||||
s = strings.TrimSpace(s) | |||||
newVal.Set(s) | |||||
} | |||||
f.Value = newVal | |||||
break | |||||
} | |||||
} | |||||
} | |||||
eachName(f.Name, func(name string) { | |||||
if f.Value == nil { | |||||
f.Value = &StringSlice{} | |||||
} | |||||
set.Var(f.Value, name, f.Usage) | |||||
}) | |||||
} | |||||
func (f StringSliceFlag) GetName() string { | |||||
return f.Name | |||||
} | |||||
// StringSlice is an opaque type for []int to satisfy flag.Value | |||||
type IntSlice []int | |||||
// Set parses the value into an integer and appends it to the list of values | |||||
func (f *IntSlice) Set(value string) error { | |||||
tmp, err := strconv.Atoi(value) | |||||
if err != nil { | |||||
return err | |||||
} else { | |||||
*f = append(*f, tmp) | |||||
} | |||||
return nil | |||||
} | |||||
// String returns a readable representation of this value (for usage defaults) | |||||
func (f *IntSlice) String() string { | |||||
return fmt.Sprintf("%d", *f) | |||||
} | |||||
// Value returns the slice of ints set by this flag | |||||
func (f *IntSlice) Value() []int { | |||||
return *f | |||||
} | |||||
// IntSliceFlag is an int flag that can be specified multiple times on the | |||||
// command-line | |||||
type IntSliceFlag struct { | |||||
Name string | |||||
Value *IntSlice | |||||
Usage string | |||||
EnvVar string | |||||
} | |||||
// String returns the usage | |||||
func (f IntSliceFlag) String() string { | |||||
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") | |||||
pref := prefixFor(firstName) | |||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)) | |||||
} | |||||
// Apply populates the flag given the flag set and environment | |||||
func (f IntSliceFlag) Apply(set *flag.FlagSet) { | |||||
if f.EnvVar != "" { | |||||
for _, envVar := range strings.Split(f.EnvVar, ",") { | |||||
envVar = strings.TrimSpace(envVar) | |||||
if envVal := os.Getenv(envVar); envVal != "" { | |||||
newVal := &IntSlice{} | |||||
for _, s := range strings.Split(envVal, ",") { | |||||
s = strings.TrimSpace(s) | |||||
err := newVal.Set(s) | |||||
if err != nil { | |||||
fmt.Fprintf(os.Stderr, err.Error()) | |||||
} | |||||
} | |||||
f.Value = newVal | |||||
break | |||||
} | |||||
} | |||||
} | |||||
eachName(f.Name, func(name string) { | |||||
if f.Value == nil { | |||||
f.Value = &IntSlice{} | |||||
} | |||||
set.Var(f.Value, name, f.Usage) | |||||
}) | |||||
} | |||||
func (f IntSliceFlag) GetName() string { | |||||
return f.Name | |||||
} | |||||
// BoolFlag is a switch that defaults to false | |||||
type BoolFlag struct { | |||||
Name string | |||||
Usage string | |||||
EnvVar string | |||||
Destination *bool | |||||
} | |||||
// String returns a readable representation of this value (for usage defaults) | |||||
func (f BoolFlag) String() string { | |||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) | |||||
} | |||||
// Apply populates the flag given the flag set and environment | |||||
func (f BoolFlag) Apply(set *flag.FlagSet) { | |||||
val := false | |||||
if f.EnvVar != "" { | |||||
for _, envVar := range strings.Split(f.EnvVar, ",") { | |||||
envVar = strings.TrimSpace(envVar) | |||||
if envVal := os.Getenv(envVar); envVal != "" { | |||||
envValBool, err := strconv.ParseBool(envVal) | |||||
if err == nil { | |||||
val = envValBool | |||||
} | |||||
break | |||||
} | |||||
} | |||||
} | |||||
eachName(f.Name, func(name string) { | |||||
if f.Destination != nil { | |||||
set.BoolVar(f.Destination, name, val, f.Usage) | |||||
return | |||||
} | |||||
set.Bool(name, val, f.Usage) | |||||
}) | |||||
} | |||||
func (f BoolFlag) GetName() string { | |||||
return f.Name | |||||
} | |||||
// BoolTFlag this represents a boolean flag that is true by default, but can | |||||
// still be set to false by --some-flag=false | |||||
type BoolTFlag struct { | |||||
Name string | |||||
Usage string | |||||
EnvVar string | |||||
Destination *bool | |||||
} | |||||
// String returns a readable representation of this value (for usage defaults) | |||||
func (f BoolTFlag) String() string { | |||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) | |||||
} | |||||
// Apply populates the flag given the flag set and environment | |||||
func (f BoolTFlag) Apply(set *flag.FlagSet) { | |||||
val := true | |||||
if f.EnvVar != "" { | |||||
for _, envVar := range strings.Split(f.EnvVar, ",") { | |||||
envVar = strings.TrimSpace(envVar) | |||||
if envVal := os.Getenv(envVar); envVal != "" { | |||||
envValBool, err := strconv.ParseBool(envVal) | |||||
if err == nil { | |||||
val = envValBool | |||||
break | |||||
} | |||||
} | |||||
} | |||||
} | |||||
eachName(f.Name, func(name string) { | |||||
if f.Destination != nil { | |||||
set.BoolVar(f.Destination, name, val, f.Usage) | |||||
return | |||||
} | |||||
set.Bool(name, val, f.Usage) | |||||
}) | |||||
} | |||||
func (f BoolTFlag) GetName() string { | |||||
return f.Name | |||||
} | |||||
// StringFlag represents a flag that takes as string value | |||||
type StringFlag struct { | |||||
Name string | |||||
Value string | |||||
Usage string | |||||
EnvVar string | |||||
Destination *string | |||||
} | |||||
// String returns the usage | |||||
func (f StringFlag) String() string { | |||||
var fmtString string | |||||
fmtString = "%s %v\t%v" | |||||
if len(f.Value) > 0 { | |||||
fmtString = "%s \"%v\"\t%v" | |||||
} else { | |||||
fmtString = "%s %v\t%v" | |||||
} | |||||
return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage)) | |||||
} | |||||
// Apply populates the flag given the flag set and environment | |||||
func (f StringFlag) Apply(set *flag.FlagSet) { | |||||
if f.EnvVar != "" { | |||||
for _, envVar := range strings.Split(f.EnvVar, ",") { | |||||
envVar = strings.TrimSpace(envVar) | |||||
if envVal := os.Getenv(envVar); envVal != "" { | |||||
f.Value = envVal | |||||
break | |||||
} | |||||
} | |||||
} | |||||
eachName(f.Name, func(name string) { | |||||
if f.Destination != nil { | |||||
set.StringVar(f.Destination, name, f.Value, f.Usage) | |||||
return | |||||
} | |||||
set.String(name, f.Value, f.Usage) | |||||
}) | |||||
} | |||||
func (f StringFlag) GetName() string { | |||||
return f.Name | |||||
} | |||||
// IntFlag is a flag that takes an integer | |||||
// Errors if the value provided cannot be parsed | |||||
type IntFlag struct { | |||||
Name string | |||||
Value int | |||||
Usage string | |||||
EnvVar string | |||||
Destination *int | |||||
} | |||||
// String returns the usage | |||||
func (f IntFlag) String() string { | |||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) | |||||
} | |||||
// Apply populates the flag given the flag set and environment | |||||
func (f IntFlag) Apply(set *flag.FlagSet) { | |||||
if f.EnvVar != "" { | |||||
for _, envVar := range strings.Split(f.EnvVar, ",") { | |||||
envVar = strings.TrimSpace(envVar) | |||||
if envVal := os.Getenv(envVar); envVal != "" { | |||||
envValInt, err := strconv.ParseInt(envVal, 0, 64) | |||||
if err == nil { | |||||
f.Value = int(envValInt) | |||||
break | |||||
} | |||||
} | |||||
} | |||||
} | |||||
eachName(f.Name, func(name string) { | |||||
if f.Destination != nil { | |||||
set.IntVar(f.Destination, name, f.Value, f.Usage) | |||||
return | |||||
} | |||||
set.Int(name, f.Value, f.Usage) | |||||
}) | |||||
} | |||||
func (f IntFlag) GetName() string { | |||||
return f.Name | |||||
} | |||||
// DurationFlag is a flag that takes a duration specified in Go's duration | |||||
// format: https://golang.org/pkg/time/#ParseDuration | |||||
type DurationFlag struct { | |||||
Name string | |||||
Value time.Duration | |||||
Usage string | |||||
EnvVar string | |||||
Destination *time.Duration | |||||
} | |||||
// String returns a readable representation of this value (for usage defaults) | |||||
func (f DurationFlag) String() string { | |||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) | |||||
} | |||||
// Apply populates the flag given the flag set and environment | |||||
func (f DurationFlag) Apply(set *flag.FlagSet) { | |||||
if f.EnvVar != "" { | |||||
for _, envVar := range strings.Split(f.EnvVar, ",") { | |||||
envVar = strings.TrimSpace(envVar) | |||||
if envVal := os.Getenv(envVar); envVal != "" { | |||||
envValDuration, err := time.ParseDuration(envVal) | |||||
if err == nil { | |||||
f.Value = envValDuration | |||||
break | |||||
} | |||||
} | |||||
} | |||||
} | |||||
eachName(f.Name, func(name string) { | |||||
if f.Destination != nil { | |||||
set.DurationVar(f.Destination, name, f.Value, f.Usage) | |||||
return | |||||
} | |||||
set.Duration(name, f.Value, f.Usage) | |||||
}) | |||||
} | |||||
func (f DurationFlag) GetName() string { | |||||
return f.Name | |||||
} | |||||
// Float64Flag is a flag that takes an float value | |||||
// Errors if the value provided cannot be parsed | |||||
type Float64Flag struct { | |||||
Name string | |||||
Value float64 | |||||
Usage string | |||||
EnvVar string | |||||
Destination *float64 | |||||
} | |||||
// String returns the usage | |||||
func (f Float64Flag) String() string { | |||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) | |||||
} | |||||
// Apply populates the flag given the flag set and environment | |||||
func (f Float64Flag) Apply(set *flag.FlagSet) { | |||||
if f.EnvVar != "" { | |||||
for _, envVar := range strings.Split(f.EnvVar, ",") { | |||||
envVar = strings.TrimSpace(envVar) | |||||
if envVal := os.Getenv(envVar); envVal != "" { | |||||
envValFloat, err := strconv.ParseFloat(envVal, 10) | |||||
if err == nil { | |||||
f.Value = float64(envValFloat) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
eachName(f.Name, func(name string) { | |||||
if f.Destination != nil { | |||||
set.Float64Var(f.Destination, name, f.Value, f.Usage) | |||||
return | |||||
} | |||||
set.Float64(name, f.Value, f.Usage) | |||||
}) | |||||
} | |||||
func (f Float64Flag) GetName() string { | |||||
return f.Name | |||||
} | |||||
func prefixFor(name string) (prefix string) { | |||||
if len(name) == 1 { | |||||
prefix = "-" | |||||
} else { | |||||
prefix = "--" | |||||
} | |||||
return | |||||
} | |||||
func prefixedNames(fullName string) (prefixed string) { | |||||
parts := strings.Split(fullName, ",") | |||||
for i, name := range parts { | |||||
name = strings.Trim(name, " ") | |||||
prefixed += prefixFor(name) + name | |||||
if i < len(parts)-1 { | |||||
prefixed += ", " | |||||
} | |||||
} | |||||
return | |||||
} | |||||
func withEnvHint(envVar, str string) string { | |||||
envText := "" | |||||
if envVar != "" { | |||||
envText = fmt.Sprintf(" [$%s]", strings.Join(strings.Split(envVar, ","), ", $")) | |||||
} | |||||
return str + envText | |||||
} |
@ -1,248 +0,0 @@ | |||||
package cli | |||||
import ( | |||||
"fmt" | |||||
"io" | |||||
"strings" | |||||
"text/tabwriter" | |||||
"text/template" | |||||
) | |||||
// The text template for the Default help topic. | |||||
// cli.go uses text/template to render templates. You can | |||||
// render custom help text by setting this variable. | |||||
var AppHelpTemplate = `NAME: | |||||
{{.Name}} - {{.Usage}} | |||||
USAGE: | |||||
{{.HelpName}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} | |||||
{{if .Version}} | |||||
VERSION: | |||||
{{.Version}} | |||||
{{end}}{{if len .Authors}} | |||||
AUTHOR(S): | |||||
{{range .Authors}}{{ . }}{{end}} | |||||
{{end}}{{if .Commands}} | |||||
COMMANDS: | |||||
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} | |||||
{{end}}{{end}}{{if .Flags}} | |||||
GLOBAL OPTIONS: | |||||
{{range .Flags}}{{.}} | |||||
{{end}}{{end}}{{if .Copyright }} | |||||
COPYRIGHT: | |||||
{{.Copyright}} | |||||
{{end}} | |||||
` | |||||
// The text template for the command help topic. | |||||
// cli.go uses text/template to render templates. You can | |||||
// render custom help text by setting this variable. | |||||
var CommandHelpTemplate = `NAME: | |||||
{{.HelpName}} - {{.Usage}} | |||||
USAGE: | |||||
{{.HelpName}}{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Description}} | |||||
DESCRIPTION: | |||||
{{.Description}}{{end}}{{if .Flags}} | |||||
OPTIONS: | |||||
{{range .Flags}}{{.}} | |||||
{{end}}{{ end }} | |||||
` | |||||
// The text template for the subcommand help topic. | |||||
// cli.go uses text/template to render templates. You can | |||||
// render custom help text by setting this variable. | |||||
var SubcommandHelpTemplate = `NAME: | |||||
{{.HelpName}} - {{.Usage}} | |||||
USAGE: | |||||
{{.HelpName}} command{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} | |||||
COMMANDS: | |||||
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} | |||||
{{end}}{{if .Flags}} | |||||
OPTIONS: | |||||
{{range .Flags}}{{.}} | |||||
{{end}}{{end}} | |||||
` | |||||
var helpCommand = Command{ | |||||
Name: "help", | |||||
Aliases: []string{"h"}, | |||||
Usage: "Shows a list of commands or help for one command", | |||||
ArgsUsage: "[command]", | |||||
Action: func(c *Context) { | |||||
args := c.Args() | |||||
if args.Present() { | |||||
ShowCommandHelp(c, args.First()) | |||||
} else { | |||||
ShowAppHelp(c) | |||||
} | |||||
}, | |||||
} | |||||
var helpSubcommand = Command{ | |||||
Name: "help", | |||||
Aliases: []string{"h"}, | |||||
Usage: "Shows a list of commands or help for one command", | |||||
ArgsUsage: "[command]", | |||||
Action: func(c *Context) { | |||||
args := c.Args() | |||||
if args.Present() { | |||||
ShowCommandHelp(c, args.First()) | |||||
} else { | |||||
ShowSubcommandHelp(c) | |||||
} | |||||
}, | |||||
} | |||||
// Prints help for the App or Command | |||||
type helpPrinter func(w io.Writer, templ string, data interface{}) | |||||
var HelpPrinter helpPrinter = printHelp | |||||
// Prints version for the App | |||||
var VersionPrinter = printVersion | |||||
func ShowAppHelp(c *Context) { | |||||
HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) | |||||
} | |||||
// Prints the list of subcommands as the default app completion method | |||||
func DefaultAppComplete(c *Context) { | |||||
for _, command := range c.App.Commands { | |||||
for _, name := range command.Names() { | |||||
fmt.Fprintln(c.App.Writer, name) | |||||
} | |||||
} | |||||
} | |||||
// Prints help for the given command | |||||
func ShowCommandHelp(ctx *Context, command string) { | |||||
// show the subcommand help for a command with subcommands | |||||
if command == "" { | |||||
HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App) | |||||
return | |||||
} | |||||
for _, c := range ctx.App.Commands { | |||||
if c.HasName(command) { | |||||
HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c) | |||||
return | |||||
} | |||||
} | |||||
if ctx.App.CommandNotFound != nil { | |||||
ctx.App.CommandNotFound(ctx, command) | |||||
} else { | |||||
fmt.Fprintf(ctx.App.Writer, "No help topic for '%v'\n", command) | |||||
} | |||||
} | |||||
// Prints help for the given subcommand | |||||
func ShowSubcommandHelp(c *Context) { | |||||
ShowCommandHelp(c, c.Command.Name) | |||||
} | |||||
// Prints the version number of the App | |||||
func ShowVersion(c *Context) { | |||||
VersionPrinter(c) | |||||
} | |||||
func printVersion(c *Context) { | |||||
fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version) | |||||
} | |||||
// Prints the lists of commands within a given context | |||||
func ShowCompletions(c *Context) { | |||||
a := c.App | |||||
if a != nil && a.BashComplete != nil { | |||||
a.BashComplete(c) | |||||
} | |||||
} | |||||
// Prints the custom completions for a given command | |||||
func ShowCommandCompletions(ctx *Context, command string) { | |||||
c := ctx.App.Command(command) | |||||
if c != nil && c.BashComplete != nil { | |||||
c.BashComplete(ctx) | |||||
} | |||||
} | |||||
func printHelp(out io.Writer, templ string, data interface{}) { | |||||
funcMap := template.FuncMap{ | |||||
"join": strings.Join, | |||||
} | |||||
w := tabwriter.NewWriter(out, 0, 8, 1, '\t', 0) | |||||
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) | |||||
err := t.Execute(w, data) | |||||
if err != nil { | |||||
// If the writer is closed, t.Execute will fail, and there's nothing | |||||
// we can do to recover. We could send this to os.Stderr if we need. | |||||
return | |||||
} | |||||
w.Flush() | |||||
} | |||||
func checkVersion(c *Context) bool { | |||||
found := false | |||||
if VersionFlag.Name != "" { | |||||
eachName(VersionFlag.Name, func(name string) { | |||||
if c.GlobalBool(name) || c.Bool(name) { | |||||
found = true | |||||
} | |||||
}) | |||||
} | |||||
return found | |||||
} | |||||
func checkHelp(c *Context) bool { | |||||
found := false | |||||
if HelpFlag.Name != "" { | |||||
eachName(HelpFlag.Name, func(name string) { | |||||
if c.GlobalBool(name) || c.Bool(name) { | |||||
found = true | |||||
} | |||||
}) | |||||
} | |||||
return found | |||||
} | |||||
func checkCommandHelp(c *Context, name string) bool { | |||||
if c.Bool("h") || c.Bool("help") { | |||||
ShowCommandHelp(c, name) | |||||
return true | |||||
} | |||||
return false | |||||
} | |||||
func checkSubcommandHelp(c *Context) bool { | |||||
if c.GlobalBool("h") || c.GlobalBool("help") { | |||||
ShowSubcommandHelp(c) | |||||
return true | |||||
} | |||||
return false | |||||
} | |||||
func checkCompletions(c *Context) bool { | |||||
if (c.GlobalBool(BashCompletionFlag.Name) || c.Bool(BashCompletionFlag.Name)) && c.App.EnableBashCompletion { | |||||
ShowCompletions(c) | |||||
return true | |||||
} | |||||
return false | |||||
} | |||||
func checkCommandCompletions(c *Context, name string) bool { | |||||
if c.Bool(BashCompletionFlag.Name) && c.App.EnableBashCompletion { | |||||
ShowCommandCompletions(c, name) | |||||
return true | |||||
} | |||||
return false | |||||
} |
@ -1,15 +0,0 @@ | |||||
# This is the official list of Snappy-Go authors for copyright purposes. | |||||
# This file is distinct from the CONTRIBUTORS files. | |||||
# See the latter for an explanation. | |||||
# Names should be added to this file as | |||||
# Name or Organization <email address> | |||||
# The email address is not required for organizations. | |||||
# Please keep the list sorted. | |||||
Damian Gryski <dgryski@gmail.com> | |||||
Google Inc. | |||||
Jan Mercl <0xjnml@gmail.com> | |||||
Rodolfo Carvalho <rhcarvalho@gmail.com> | |||||
Sebastien Binet <seb.binet@gmail.com> |
@ -1,37 +0,0 @@ | |||||
# This is the official list of people who can contribute | |||||
# (and typically have contributed) code to the Snappy-Go repository. | |||||
# The AUTHORS file lists the copyright holders; this file | |||||
# lists people. For example, Google employees are listed here | |||||
# but not in AUTHORS, because Google holds the copyright. | |||||
# | |||||
# The submission process automatically checks to make sure | |||||
# that people submitting code are listed in this file (by email address). | |||||
# | |||||
# Names should be added to this file only after verifying that | |||||
# the individual or the individual's organization has agreed to | |||||
# the appropriate Contributor License Agreement, found here: | |||||
# | |||||
# http://code.google.com/legal/individual-cla-v1.0.html | |||||
# http://code.google.com/legal/corporate-cla-v1.0.html | |||||
# | |||||
# The agreement for individuals can be filled out on the web. | |||||
# | |||||
# When adding J Random Contributor's name to this file, | |||||
# either J's name or J's organization's name should be | |||||
# added to the AUTHORS file, depending on whether the | |||||
# individual or corporate CLA was used. | |||||
# Names should be added to this file like so: | |||||
# Name <email address> | |||||
# Please keep the list sorted. | |||||
Damian Gryski <dgryski@gmail.com> | |||||
Jan Mercl <0xjnml@gmail.com> | |||||
Kai Backman <kaib@golang.org> | |||||
Marc-Antoine Ruel <maruel@chromium.org> | |||||
Nigel Tao <nigeltao@golang.org> | |||||
Rob Pike <r@golang.org> | |||||
Rodolfo Carvalho <rhcarvalho@gmail.com> | |||||
Russ Cox <rsc@golang.org> | |||||
Sebastien Binet <seb.binet@gmail.com> |
@ -1,27 +0,0 @@ | |||||
Copyright (c) 2011 The Snappy-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. |
@ -1,7 +0,0 @@ | |||||
The Snappy compression format in the Go programming language. | |||||
To download and install from source: | |||||
$ go get github.com/golang/snappy | |||||
Unless otherwise noted, the Snappy-Go source files are distributed | |||||
under the BSD-style license found in the LICENSE file. |
@ -1,294 +0,0 @@ | |||||
// Copyright 2011 The Snappy-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 snappy | |||||
import ( | |||||
"encoding/binary" | |||||
"errors" | |||||
"io" | |||||
) | |||||
var ( | |||||
// ErrCorrupt reports that the input is invalid. | |||||
ErrCorrupt = errors.New("snappy: corrupt input") | |||||
// ErrTooLarge reports that the uncompressed length is too large. | |||||
ErrTooLarge = errors.New("snappy: decoded block is too large") | |||||
// ErrUnsupported reports that the input isn't supported. | |||||
ErrUnsupported = errors.New("snappy: unsupported input") | |||||
) | |||||
// DecodedLen returns the length of the decoded block. | |||||
func DecodedLen(src []byte) (int, error) { | |||||
v, _, err := decodedLen(src) | |||||
return v, err | |||||
} | |||||
// decodedLen returns the length of the decoded block and the number of bytes | |||||
// that the length header occupied. | |||||
func decodedLen(src []byte) (blockLen, headerLen int, err error) { | |||||
v, n := binary.Uvarint(src) | |||||
if n <= 0 || v > 0xffffffff { | |||||
return 0, 0, ErrCorrupt | |||||
} | |||||
const wordSize = 32 << (^uint(0) >> 32 & 1) | |||||
if wordSize == 32 && v > 0x7fffffff { | |||||
return 0, 0, ErrTooLarge | |||||
} | |||||
return int(v), n, nil | |||||
} | |||||
// Decode returns the decoded form of src. The returned slice may be a sub- | |||||
// slice of dst if dst was large enough to hold the entire decoded block. | |||||
// Otherwise, a newly allocated slice will be returned. | |||||
// It is valid to pass a nil dst. | |||||
func Decode(dst, src []byte) ([]byte, error) { | |||||
dLen, s, err := decodedLen(src) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
if len(dst) < dLen { | |||||
dst = make([]byte, dLen) | |||||
} | |||||
var d, offset, length int | |||||
for s < len(src) { | |||||
switch src[s] & 0x03 { | |||||
case tagLiteral: | |||||
x := uint(src[s] >> 2) | |||||
switch { | |||||
case x < 60: | |||||
s++ | |||||
case x == 60: | |||||
s += 2 | |||||
if s > len(src) { | |||||
return nil, ErrCorrupt | |||||
} | |||||
x = uint(src[s-1]) | |||||
case x == 61: | |||||
s += 3 | |||||
if s > len(src) { | |||||
return nil, ErrCorrupt | |||||
} | |||||
x = uint(src[s-2]) | uint(src[s-1])<<8 | |||||
case x == 62: | |||||
s += 4 | |||||
if s > len(src) { | |||||
return nil, ErrCorrupt | |||||
} | |||||
x = uint(src[s-3]) | uint(src[s-2])<<8 | uint(src[s-1])<<16 | |||||
case x == 63: | |||||
s += 5 | |||||
if s > len(src) { | |||||
return nil, ErrCorrupt | |||||
} | |||||
x = uint(src[s-4]) | uint(src[s-3])<<8 | uint(src[s-2])<<16 | uint(src[s-1])<<24 | |||||
} | |||||
length = int(x + 1) | |||||
if length <= 0 { | |||||
return nil, errors.New("snappy: unsupported literal length") | |||||
} | |||||
if length > len(dst)-d || length > len(src)-s { | |||||
return nil, ErrCorrupt | |||||
} | |||||
copy(dst[d:], src[s:s+length]) | |||||
d += length | |||||
s += length | |||||
continue | |||||
case tagCopy1: | |||||
s += 2 | |||||
if s > len(src) { | |||||
return nil, ErrCorrupt | |||||
} | |||||
length = 4 + int(src[s-2])>>2&0x7 | |||||
offset = int(src[s-2])&0xe0<<3 | int(src[s-1]) | |||||
case tagCopy2: | |||||
s += 3 | |||||
if s > len(src) { | |||||
return nil, ErrCorrupt | |||||
} | |||||
length = 1 + int(src[s-3])>>2 | |||||
offset = int(src[s-2]) | int(src[s-1])<<8 | |||||
case tagCopy4: | |||||
return nil, errors.New("snappy: unsupported COPY_4 tag") | |||||
} | |||||
end := d + length | |||||
if offset > d || end > len(dst) { | |||||
return nil, ErrCorrupt | |||||
} | |||||
for ; d < end; d++ { | |||||
dst[d] = dst[d-offset] | |||||
} | |||||
} | |||||
if d != dLen { | |||||
return nil, ErrCorrupt | |||||
} | |||||
return dst[:d], nil | |||||
} | |||||
// NewReader returns a new Reader that decompresses from r, using the framing | |||||
// format described at | |||||
// https://github.com/google/snappy/blob/master/framing_format.txt | |||||
func NewReader(r io.Reader) *Reader { | |||||
return &Reader{ | |||||
r: r, | |||||
decoded: make([]byte, maxUncompressedChunkLen), | |||||
buf: make([]byte, MaxEncodedLen(maxUncompressedChunkLen)+checksumSize), | |||||
} | |||||
} | |||||
// Reader is an io.Reader that can read Snappy-compressed bytes. | |||||
type Reader struct { | |||||
r io.Reader | |||||
err error | |||||
decoded []byte | |||||
buf []byte | |||||
// decoded[i:j] contains decoded bytes that have not yet been passed on. | |||||
i, j int | |||||
readHeader bool | |||||
} | |||||
// Reset discards any buffered data, resets all state, and switches the Snappy | |||||
// reader to read from r. This permits reusing a Reader rather than allocating | |||||
// a new one. | |||||
func (r *Reader) Reset(reader io.Reader) { | |||||
r.r = reader | |||||
r.err = nil | |||||
r.i = 0 | |||||
r.j = 0 | |||||
r.readHeader = false | |||||
} | |||||
func (r *Reader) readFull(p []byte) (ok bool) { | |||||
if _, r.err = io.ReadFull(r.r, p); r.err != nil { | |||||
if r.err == io.ErrUnexpectedEOF { | |||||
r.err = ErrCorrupt | |||||
} | |||||
return false | |||||
} | |||||
return true | |||||
} | |||||
// Read satisfies the io.Reader interface. | |||||
func (r *Reader) Read(p []byte) (int, error) { | |||||
if r.err != nil { | |||||
return 0, r.err | |||||
} | |||||
for { | |||||
if r.i < r.j { | |||||
n := copy(p, r.decoded[r.i:r.j]) | |||||
r.i += n | |||||
return n, nil | |||||
} | |||||
if !r.readFull(r.buf[:4]) { | |||||
return 0, r.err | |||||
} | |||||
chunkType := r.buf[0] | |||||
if !r.readHeader { | |||||
if chunkType != chunkTypeStreamIdentifier { | |||||
r.err = ErrCorrupt | |||||
return 0, r.err | |||||
} | |||||
r.readHeader = true | |||||
} | |||||
chunkLen := int(r.buf[1]) | int(r.buf[2])<<8 | int(r.buf[3])<<16 | |||||
if chunkLen > len(r.buf) { | |||||
r.err = ErrUnsupported | |||||
return 0, r.err | |||||
} | |||||
// The chunk types are specified at | |||||
// https://github.com/google/snappy/blob/master/framing_format.txt | |||||
switch chunkType { | |||||
case chunkTypeCompressedData: | |||||
// Section 4.2. Compressed data (chunk type 0x00). | |||||
if chunkLen < checksumSize { | |||||
r.err = ErrCorrupt | |||||
return 0, r.err | |||||
} | |||||
buf := r.buf[:chunkLen] | |||||
if !r.readFull(buf) { | |||||
return 0, r.err | |||||
} | |||||
checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24 | |||||
buf = buf[checksumSize:] | |||||
n, err := DecodedLen(buf) | |||||
if err != nil { | |||||
r.err = err | |||||
return 0, r.err | |||||
} | |||||
if n > len(r.decoded) { | |||||
r.err = ErrCorrupt | |||||
return 0, r.err | |||||
} | |||||
if _, err := Decode(r.decoded, buf); err != nil { | |||||
r.err = err | |||||
return 0, r.err | |||||
} | |||||
if crc(r.decoded[:n]) != checksum { | |||||
r.err = ErrCorrupt | |||||
return 0, r.err | |||||
} | |||||
r.i, r.j = 0, n | |||||
continue | |||||
case chunkTypeUncompressedData: | |||||
// Section 4.3. Uncompressed data (chunk type 0x01). | |||||
if chunkLen < checksumSize { | |||||
r.err = ErrCorrupt | |||||
return 0, r.err | |||||
} | |||||
buf := r.buf[:checksumSize] | |||||
if !r.readFull(buf) { | |||||
return 0, r.err | |||||
} | |||||
checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24 | |||||
// Read directly into r.decoded instead of via r.buf. | |||||
n := chunkLen - checksumSize | |||||
if !r.readFull(r.decoded[:n]) { | |||||
return 0, r.err | |||||
} | |||||
if crc(r.decoded[:n]) != checksum { | |||||
r.err = ErrCorrupt | |||||
return 0, r.err | |||||
} | |||||
r.i, r.j = 0, n | |||||
continue | |||||
case chunkTypeStreamIdentifier: | |||||
// Section 4.1. Stream identifier (chunk type 0xff). | |||||
if chunkLen != len(magicBody) { | |||||
r.err = ErrCorrupt | |||||
return 0, r.err | |||||
} | |||||
if !r.readFull(r.buf[:len(magicBody)]) { | |||||
return 0, r.err | |||||
} | |||||
for i := 0; i < len(magicBody); i++ { | |||||
if r.buf[i] != magicBody[i] { | |||||
r.err = ErrCorrupt | |||||
return 0, r.err | |||||
} | |||||
} | |||||
continue | |||||
} | |||||
if chunkType <= 0x7f { | |||||
// Section 4.5. Reserved unskippable chunks (chunk types 0x02-0x7f). | |||||
r.err = ErrUnsupported | |||||
return 0, r.err | |||||
} | |||||
// Section 4.4 Padding (chunk type 0xfe). | |||||
// Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd). | |||||
if !r.readFull(r.buf[:chunkLen]) { | |||||
return 0, r.err | |||||
} | |||||
} | |||||
} |
@ -1,254 +0,0 @@ | |||||
// Copyright 2011 The Snappy-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 snappy | |||||
import ( | |||||
"encoding/binary" | |||||
"io" | |||||
) | |||||
// We limit how far copy back-references can go, the same as the C++ code. | |||||
const maxOffset = 1 << 15 | |||||
// emitLiteral writes a literal chunk and returns the number of bytes written. | |||||
func emitLiteral(dst, lit []byte) int { | |||||
i, n := 0, uint(len(lit)-1) | |||||
switch { | |||||
case n < 60: | |||||
dst[0] = uint8(n)<<2 | tagLiteral | |||||
i = 1 | |||||
case n < 1<<8: | |||||
dst[0] = 60<<2 | tagLiteral | |||||
dst[1] = uint8(n) | |||||
i = 2 | |||||
case n < 1<<16: | |||||
dst[0] = 61<<2 | tagLiteral | |||||
dst[1] = uint8(n) | |||||
dst[2] = uint8(n >> 8) | |||||
i = 3 | |||||
case n < 1<<24: | |||||
dst[0] = 62<<2 | tagLiteral | |||||
dst[1] = uint8(n) | |||||
dst[2] = uint8(n >> 8) | |||||
dst[3] = uint8(n >> 16) | |||||
i = 4 | |||||
case int64(n) < 1<<32: | |||||
dst[0] = 63<<2 | tagLiteral | |||||
dst[1] = uint8(n) | |||||
dst[2] = uint8(n >> 8) | |||||
dst[3] = uint8(n >> 16) | |||||
dst[4] = uint8(n >> 24) | |||||
i = 5 | |||||
default: | |||||
panic("snappy: source buffer is too long") | |||||
} | |||||
if copy(dst[i:], lit) != len(lit) { | |||||
panic("snappy: destination buffer is too short") | |||||
} | |||||
return i + len(lit) | |||||
} | |||||
// emitCopy writes a copy chunk and returns the number of bytes written. | |||||
func emitCopy(dst []byte, offset, length int) int { | |||||
i := 0 | |||||
for length > 0 { | |||||
x := length - 4 | |||||
if 0 <= x && x < 1<<3 && offset < 1<<11 { | |||||
dst[i+0] = uint8(offset>>8)&0x07<<5 | uint8(x)<<2 | tagCopy1 | |||||
dst[i+1] = uint8(offset) | |||||
i += 2 | |||||
break | |||||
} | |||||
x = length | |||||
if x > 1<<6 { | |||||
x = 1 << 6 | |||||
} | |||||
dst[i+0] = uint8(x-1)<<2 | tagCopy2 | |||||
dst[i+1] = uint8(offset) | |||||
dst[i+2] = uint8(offset >> 8) | |||||
i += 3 | |||||
length -= x | |||||
} | |||||
return i | |||||
} | |||||
// Encode returns the encoded form of src. The returned slice may be a sub- | |||||
// slice of dst if dst was large enough to hold the entire encoded block. | |||||
// Otherwise, a newly allocated slice will be returned. | |||||
// It is valid to pass a nil dst. | |||||
func Encode(dst, src []byte) []byte { | |||||
if n := MaxEncodedLen(len(src)); len(dst) < n { | |||||
dst = make([]byte, n) | |||||
} | |||||
// The block starts with the varint-encoded length of the decompressed bytes. | |||||
d := binary.PutUvarint(dst, uint64(len(src))) | |||||
// Return early if src is short. | |||||
if len(src) <= 4 { | |||||
if len(src) != 0 { | |||||
d += emitLiteral(dst[d:], src) | |||||
} | |||||
return dst[:d] | |||||
} | |||||
// Initialize the hash table. Its size ranges from 1<<8 to 1<<14 inclusive. | |||||
const maxTableSize = 1 << 14 | |||||
shift, tableSize := uint(32-8), 1<<8 | |||||
for tableSize < maxTableSize && tableSize < len(src) { | |||||
shift-- | |||||
tableSize *= 2 | |||||
} | |||||
var table [maxTableSize]int | |||||
// Iterate over the source bytes. | |||||
var ( | |||||
s int // The iterator position. | |||||
t int // The last position with the same hash as s. | |||||
lit int // The start position of any pending literal bytes. | |||||
) | |||||
for s+3 < len(src) { | |||||
// Update the hash table. | |||||
b0, b1, b2, b3 := src[s], src[s+1], src[s+2], src[s+3] | |||||
h := uint32(b0) | uint32(b1)<<8 | uint32(b2)<<16 | uint32(b3)<<24 | |||||
p := &table[(h*0x1e35a7bd)>>shift] | |||||
// We need to to store values in [-1, inf) in table. To save | |||||
// some initialization time, (re)use the table's zero value | |||||
// and shift the values against this zero: add 1 on writes, | |||||
// subtract 1 on reads. | |||||
t, *p = *p-1, s+1 | |||||
// If t is invalid or src[s:s+4] differs from src[t:t+4], accumulate a literal byte. | |||||
if t < 0 || s-t >= maxOffset || b0 != src[t] || b1 != src[t+1] || b2 != src[t+2] || b3 != src[t+3] { | |||||
s++ | |||||
continue | |||||
} | |||||
// Otherwise, we have a match. First, emit any pending literal bytes. | |||||
if lit != s { | |||||
d += emitLiteral(dst[d:], src[lit:s]) | |||||
} | |||||
// Extend the match to be as long as possible. | |||||
s0 := s | |||||
s, t = s+4, t+4 | |||||
for s < len(src) && src[s] == src[t] { | |||||
s++ | |||||
t++ | |||||
} | |||||
// Emit the copied bytes. | |||||
d += emitCopy(dst[d:], s-t, s-s0) | |||||
lit = s | |||||
} | |||||
// Emit any final pending literal bytes and return. | |||||
if lit != len(src) { | |||||
d += emitLiteral(dst[d:], src[lit:]) | |||||
} | |||||
return dst[:d] | |||||
} | |||||
// MaxEncodedLen returns the maximum length of a snappy block, given its | |||||
// uncompressed length. | |||||
func MaxEncodedLen(srcLen int) int { | |||||
// Compressed data can be defined as: | |||||
// compressed := item* literal* | |||||
// item := literal* copy | |||||
// | |||||
// The trailing literal sequence has a space blowup of at most 62/60 | |||||
// since a literal of length 60 needs one tag byte + one extra byte | |||||
// for length information. | |||||
// | |||||
// Item blowup is trickier to measure. Suppose the "copy" op copies | |||||
// 4 bytes of data. Because of a special check in the encoding code, | |||||
// we produce a 4-byte copy only if the offset is < 65536. Therefore | |||||
// the copy op takes 3 bytes to encode, and this type of item leads | |||||
// to at most the 62/60 blowup for representing literals. | |||||
// | |||||
// Suppose the "copy" op copies 5 bytes of data. If the offset is big | |||||
// enough, it will take 5 bytes to encode the copy op. Therefore the | |||||
// worst case here is a one-byte literal followed by a five-byte copy. | |||||
// That is, 6 bytes of input turn into 7 bytes of "compressed" data. | |||||
// | |||||
// This last factor dominates the blowup, so the final estimate is: | |||||
return 32 + srcLen + srcLen/6 | |||||
} | |||||
// NewWriter returns a new Writer that compresses to w, using the framing | |||||
// format described at | |||||
// https://github.com/google/snappy/blob/master/framing_format.txt | |||||
func NewWriter(w io.Writer) *Writer { | |||||
return &Writer{ | |||||
w: w, | |||||
enc: make([]byte, MaxEncodedLen(maxUncompressedChunkLen)), | |||||
} | |||||
} | |||||
// Writer is an io.Writer than can write Snappy-compressed bytes. | |||||
type Writer struct { | |||||
w io.Writer | |||||
err error | |||||
enc []byte | |||||
buf [checksumSize + chunkHeaderSize]byte | |||||
wroteHeader bool | |||||
} | |||||
// Reset discards the writer's state and switches the Snappy writer to write to | |||||
// w. This permits reusing a Writer rather than allocating a new one. | |||||
func (w *Writer) Reset(writer io.Writer) { | |||||
w.w = writer | |||||
w.err = nil | |||||
w.wroteHeader = false | |||||
} | |||||
// Write satisfies the io.Writer interface. | |||||
func (w *Writer) Write(p []byte) (n int, errRet error) { | |||||
if w.err != nil { | |||||
return 0, w.err | |||||
} | |||||
if !w.wroteHeader { | |||||
copy(w.enc, magicChunk) | |||||
if _, err := w.w.Write(w.enc[:len(magicChunk)]); err != nil { | |||||
w.err = err | |||||
return n, err | |||||
} | |||||
w.wroteHeader = true | |||||
} | |||||
for len(p) > 0 { | |||||
var uncompressed []byte | |||||
if len(p) > maxUncompressedChunkLen { | |||||
uncompressed, p = p[:maxUncompressedChunkLen], p[maxUncompressedChunkLen:] | |||||
} else { | |||||
uncompressed, p = p, nil | |||||
} | |||||
checksum := crc(uncompressed) | |||||
// Compress the buffer, discarding the result if the improvement | |||||
// isn't at least 12.5%. | |||||
chunkType := uint8(chunkTypeCompressedData) | |||||
chunkBody := Encode(w.enc, uncompressed) | |||||
if len(chunkBody) >= len(uncompressed)-len(uncompressed)/8 { | |||||
chunkType, chunkBody = chunkTypeUncompressedData, uncompressed | |||||
} | |||||
chunkLen := 4 + len(chunkBody) | |||||
w.buf[0] = chunkType | |||||
w.buf[1] = uint8(chunkLen >> 0) | |||||
w.buf[2] = uint8(chunkLen >> 8) | |||||
w.buf[3] = uint8(chunkLen >> 16) | |||||
w.buf[4] = uint8(checksum >> 0) | |||||
w.buf[5] = uint8(checksum >> 8) | |||||
w.buf[6] = uint8(checksum >> 16) | |||||
w.buf[7] = uint8(checksum >> 24) | |||||
if _, err := w.w.Write(w.buf[:]); err != nil { | |||||
w.err = err | |||||
return n, err | |||||
} | |||||
if _, err := w.w.Write(chunkBody); err != nil { | |||||
w.err = err | |||||
return n, err | |||||
} | |||||
n += len(uncompressed) | |||||
} | |||||
return n, nil | |||||
} |
@ -1,68 +0,0 @@ | |||||
// Copyright 2011 The Snappy-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 snappy implements the snappy block-based compression format. | |||||
// It aims for very high speeds and reasonable compression. | |||||
// | |||||
// The C++ snappy implementation is at https://github.com/google/snappy | |||||
package snappy | |||||
import ( | |||||
"hash/crc32" | |||||
) | |||||
/* | |||||
Each encoded block begins with the varint-encoded length of the decoded data, | |||||
followed by a sequence of chunks. Chunks begin and end on byte boundaries. The | |||||
first byte of each chunk is broken into its 2 least and 6 most significant bits | |||||
called l and m: l ranges in [0, 4) and m ranges in [0, 64). l is the chunk tag. | |||||
Zero means a literal tag. All other values mean a copy tag. | |||||
For literal tags: | |||||
- If m < 60, the next 1 + m bytes are literal bytes. | |||||
- Otherwise, let n be the little-endian unsigned integer denoted by the next | |||||
m - 59 bytes. The next 1 + n bytes after that are literal bytes. | |||||
For copy tags, length bytes are copied from offset bytes ago, in the style of | |||||
Lempel-Ziv compression algorithms. In particular: | |||||
- For l == 1, the offset ranges in [0, 1<<11) and the length in [4, 12). | |||||
The length is 4 + the low 3 bits of m. The high 3 bits of m form bits 8-10 | |||||
of the offset. The next byte is bits 0-7 of the offset. | |||||
- For l == 2, the offset ranges in [0, 1<<16) and the length in [1, 65). | |||||
The length is 1 + m. The offset is the little-endian unsigned integer | |||||
denoted by the next 2 bytes. | |||||
- For l == 3, this tag is a legacy format that is no longer supported. | |||||
*/ | |||||
const ( | |||||
tagLiteral = 0x00 | |||||
tagCopy1 = 0x01 | |||||
tagCopy2 = 0x02 | |||||
tagCopy4 = 0x03 | |||||
) | |||||
const ( | |||||
checksumSize = 4 | |||||
chunkHeaderSize = 4 | |||||
magicChunk = "\xff\x06\x00\x00" + magicBody | |||||
magicBody = "sNaPpY" | |||||
// https://github.com/google/snappy/blob/master/framing_format.txt says | |||||
// that "the uncompressed data in a chunk must be no longer than 65536 bytes". | |||||
maxUncompressedChunkLen = 65536 | |||||
) | |||||
const ( | |||||
chunkTypeCompressedData = 0x00 | |||||
chunkTypeUncompressedData = 0x01 | |||||
chunkTypePadding = 0xfe | |||||
chunkTypeStreamIdentifier = 0xff | |||||
) | |||||
var crcTable = crc32.MakeTable(crc32.Castagnoli) | |||||
// crc implements the checksum specified in section 3 of | |||||
// https://github.com/google/snappy/blob/master/framing_format.txt | |||||
func crc(b []byte) uint32 { | |||||
c := crc32.Update(0, crcTable, b) | |||||
return uint32(c>>15|c<<17) + 0xa282ead8 | |||||
} |
@ -1,22 +0,0 @@ | |||||
# Compiled Object files, Static and Dynamic libs (Shared Objects) | |||||
*.o | |||||
*.a | |||||
*.so | |||||
# Folders | |||||
_obj | |||||
_test | |||||
# Architecture specific extensions/prefixes | |||||
*.[568vq] | |||||
[568vq].out | |||||
*.cgo1.go | |||||
*.cgo2.c | |||||
_cgo_defun.c | |||||
_cgo_gotypes.go | |||||
_cgo_export.* | |||||
_testmain.go | |||||
*.exe |
@ -1,6 +0,0 @@ | |||||
language: go | |||||
go: | |||||
- 1.1 | |||||
- 1.2 | |||||
- tip |
@ -1,8 +0,0 @@ | |||||
# This is the official list of Gorilla WebSocket authors for copyright | |||||
# purposes. | |||||
# | |||||
# Please keep the list sorted. | |||||
Gary Burd <gary@beagledreams.com> | |||||
Joachim Bauch <mail@joachim-bauch.de> | |||||
@ -1,22 +0,0 @@ | |||||
Copyright (c) 2013 The Gorilla WebSocket 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. | |||||
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 HOLDER 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. |
@ -1,61 +0,0 @@ | |||||
# Gorilla WebSocket | |||||
Gorilla WebSocket is a [Go](http://golang.org/) implementation of the | |||||
[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. | |||||
### Documentation | |||||
* [API Reference](http://godoc.org/github.com/gorilla/websocket) | |||||
* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat) | |||||
* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command) | |||||
* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo) | |||||
* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch) | |||||
### Status | |||||
The Gorilla WebSocket package provides a complete and tested implementation of | |||||
the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. The | |||||
package API is stable. | |||||
### Installation | |||||
go get github.com/gorilla/websocket | |||||
### Protocol Compliance | |||||
The Gorilla WebSocket package passes the server tests in the [Autobahn Test | |||||
Suite](http://autobahn.ws/testsuite) using the application in the [examples/autobahn | |||||
subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn). | |||||
### Gorilla WebSocket compared with other packages | |||||
<table> | |||||
<tr> | |||||
<th></th> | |||||
<th><a href="http://godoc.org/github.com/gorilla/websocket">github.com/gorilla</a></th> | |||||
<th><a href="http://godoc.org/golang.org/x/net/websocket">golang.org/x/net</a></th> | |||||
</tr> | |||||
<tr> | |||||
<tr><td colspan="3"><a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a> Features</td></tr> | |||||
<tr><td>Passes <a href="http://autobahn.ws/testsuite/">Autobahn Test Suite</a></td><td><a href="https://github.com/gorilla/websocket/tree/master/examples/autobahn">Yes</a></td><td>No</td></tr> | |||||
<tr><td>Receive <a href="https://tools.ietf.org/html/rfc6455#section-5.4">fragmented</a> message<td>Yes</td><td><a href="https://code.google.com/p/go/issues/detail?id=7632">No</a>, see note 1</td></tr> | |||||
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.1">close</a> message</td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td><a href="https://code.google.com/p/go/issues/detail?id=4588">No</a></td></tr> | |||||
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">pings</a> and receive <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">pongs</a></td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td>No</td></tr> | |||||
<tr><td>Get the <a href="https://tools.ietf.org/html/rfc6455#section-5.6">type</a> of a received data message</td><td>Yes</td><td>Yes, see note 2</td></tr> | |||||
<tr><td colspan="3">Other Features</tr></td> | |||||
<tr><td>Limit size of received message</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.SetReadLimit">Yes</a></td><td><a href="https://code.google.com/p/go/issues/detail?id=5082">No</a></td></tr> | |||||
<tr><td>Read message using io.Reader</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextReader">Yes</a></td><td>No, see note 3</td></tr> | |||||
<tr><td>Write message using io.WriteCloser</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextWriter">Yes</a></td><td>No, see note 3</td></tr> | |||||
</table> | |||||
Notes: | |||||
1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html). | |||||
2. The application can get the type of a received data message by implementing | |||||
a [Codec marshal](http://godoc.org/golang.org/x/net/websocket#Codec.Marshal) | |||||
function. | |||||
3. The go.net io.Reader and io.Writer operate across WebSocket frame boundaries. | |||||
Read returns when the input buffer is full or a frame boundary is | |||||
encountered. Each call to Write sends a single frame message. The Gorilla | |||||
io.Reader and io.WriteCloser operate on a single WebSocket message. | |||||
@ -1,341 +0,0 @@ | |||||
// Copyright 2013 The Gorilla WebSocket 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 websocket | |||||
import ( | |||||
"bufio" | |||||
"bytes" | |||||
"crypto/tls" | |||||
"errors" | |||||
"io" | |||||
"io/ioutil" | |||||
"net" | |||||
"net/http" | |||||
"net/url" | |||||
"strings" | |||||
"time" | |||||
) | |||||
// ErrBadHandshake is returned when the server response to opening handshake is | |||||
// invalid. | |||||
var ErrBadHandshake = errors.New("websocket: bad handshake") | |||||
// NewClient creates a new client connection using the given net connection. | |||||
// The URL u specifies the host and request URI. Use requestHeader to specify | |||||
// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies | |||||
// (Cookie). Use the response.Header to get the selected subprotocol | |||||
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie). | |||||
// | |||||
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a | |||||
// non-nil *http.Response so that callers can handle redirects, authentication, | |||||
// etc. | |||||
// | |||||
// Deprecated: Use Dialer instead. | |||||
func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) { | |||||
d := Dialer{ | |||||
ReadBufferSize: readBufSize, | |||||
WriteBufferSize: writeBufSize, | |||||
NetDial: func(net, addr string) (net.Conn, error) { | |||||
return netConn, nil | |||||
}, | |||||
} | |||||
return d.Dial(u.String(), requestHeader) | |||||
} | |||||
// A Dialer contains options for connecting to WebSocket server. | |||||
type Dialer struct { | |||||
// NetDial specifies the dial function for creating TCP connections. If | |||||
// NetDial is nil, net.Dial is used. | |||||
NetDial func(network, addr string) (net.Conn, error) | |||||
// Proxy specifies a function to return a proxy for a given | |||||
// Request. If the function returns a non-nil error, the | |||||
// request is aborted with the provided error. | |||||
// If Proxy is nil or returns a nil *URL, no proxy is used. | |||||
Proxy func(*http.Request) (*url.URL, error) | |||||
// TLSClientConfig specifies the TLS configuration to use with tls.Client. | |||||
// If nil, the default configuration is used. | |||||
TLSClientConfig *tls.Config | |||||
// HandshakeTimeout specifies the duration for the handshake to complete. | |||||
HandshakeTimeout time.Duration | |||||
// Input and output buffer sizes. If the buffer size is zero, then a | |||||
// default value of 4096 is used. | |||||
ReadBufferSize, WriteBufferSize int | |||||
// Subprotocols specifies the client's requested subprotocols. | |||||
Subprotocols []string | |||||
} | |||||
var errMalformedURL = errors.New("malformed ws or wss URL") | |||||
// parseURL parses the URL. | |||||
// | |||||
// This function is a replacement for the standard library url.Parse function. | |||||
// In Go 1.4 and earlier, url.Parse loses information from the path. | |||||
func parseURL(s string) (*url.URL, error) { | |||||
// From the RFC: | |||||
// | |||||
// ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ] | |||||
// wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ] | |||||
var u url.URL | |||||
switch { | |||||
case strings.HasPrefix(s, "ws://"): | |||||
u.Scheme = "ws" | |||||
s = s[len("ws://"):] | |||||
case strings.HasPrefix(s, "wss://"): | |||||
u.Scheme = "wss" | |||||
s = s[len("wss://"):] | |||||
default: | |||||
return nil, errMalformedURL | |||||
} | |||||
if i := strings.Index(s, "?"); i >= 0 { | |||||
u.RawQuery = s[i+1:] | |||||
s = s[:i] | |||||
} | |||||
if i := strings.Index(s, "/"); i >= 0 { | |||||
u.Opaque = s[i:] | |||||
s = s[:i] | |||||
} else { | |||||
u.Opaque = "/" | |||||
} | |||||
u.Host = s | |||||
if strings.Contains(u.Host, "@") { | |||||
// Don't bother parsing user information because user information is | |||||
// not allowed in websocket URIs. | |||||
return nil, errMalformedURL | |||||
} | |||||
return &u, nil | |||||
} | |||||
func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) { | |||||
hostPort = u.Host | |||||
hostNoPort = u.Host | |||||
if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") { | |||||
hostNoPort = hostNoPort[:i] | |||||
} else { | |||||
switch u.Scheme { | |||||
case "wss": | |||||
hostPort += ":443" | |||||
case "https": | |||||
hostPort += ":443" | |||||
default: | |||||
hostPort += ":80" | |||||
} | |||||
} | |||||
return hostPort, hostNoPort | |||||
} | |||||
// DefaultDialer is a dialer with all fields set to the default zero values. | |||||
var DefaultDialer = &Dialer{ | |||||
Proxy: http.ProxyFromEnvironment, | |||||
} | |||||
// Dial creates a new client connection. Use requestHeader to specify the | |||||
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie). | |||||
// Use the response.Header to get the selected subprotocol | |||||
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie). | |||||
// | |||||
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a | |||||
// non-nil *http.Response so that callers can handle redirects, authentication, | |||||
// etcetera. The response body may not contain the entire response and does not | |||||
// need to be closed by the application. | |||||
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) { | |||||
if d == nil { | |||||
d = &Dialer{ | |||||
Proxy: http.ProxyFromEnvironment, | |||||
} | |||||
} | |||||
challengeKey, err := generateChallengeKey() | |||||
if err != nil { | |||||
return nil, nil, err | |||||
} | |||||
u, err := parseURL(urlStr) | |||||
if err != nil { | |||||
return nil, nil, err | |||||
} | |||||
switch u.Scheme { | |||||
case "ws": | |||||
u.Scheme = "http" | |||||
case "wss": | |||||
u.Scheme = "https" | |||||
default: | |||||
return nil, nil, errMalformedURL | |||||
} | |||||
if u.User != nil { | |||||
// User name and password are not allowed in websocket URIs. | |||||
return nil, nil, errMalformedURL | |||||
} | |||||
req := &http.Request{ | |||||
Method: "GET", | |||||
URL: u, | |||||
Proto: "HTTP/1.1", | |||||
ProtoMajor: 1, | |||||
ProtoMinor: 1, | |||||
Header: make(http.Header), | |||||
Host: u.Host, | |||||
} | |||||
// Set the request headers using the capitalization for names and values in | |||||
// RFC examples. Although the capitalization shouldn't matter, there are | |||||
// servers that depend on it. The Header.Set method is not used because the | |||||
// method canonicalizes the header names. | |||||
req.Header["Upgrade"] = []string{"websocket"} | |||||
req.Header["Connection"] = []string{"Upgrade"} | |||||
req.Header["Sec-WebSocket-Key"] = []string{challengeKey} | |||||
req.Header["Sec-WebSocket-Version"] = []string{"13"} | |||||
if len(d.Subprotocols) > 0 { | |||||
req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")} | |||||
} | |||||
for k, vs := range requestHeader { | |||||
switch { | |||||
case k == "Host": | |||||
if len(vs) > 0 { | |||||
req.Host = vs[0] | |||||
} | |||||
case k == "Upgrade" || | |||||
k == "Connection" || | |||||
k == "Sec-Websocket-Key" || | |||||
k == "Sec-Websocket-Version" || | |||||
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0): | |||||
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k) | |||||
default: | |||||
req.Header[k] = vs | |||||
} | |||||
} | |||||
hostPort, hostNoPort := hostPortNoPort(u) | |||||
var proxyURL *url.URL | |||||
// Check wether the proxy method has been configured | |||||
if d.Proxy != nil { | |||||
proxyURL, err = d.Proxy(req) | |||||
} | |||||
if err != nil { | |||||
return nil, nil, err | |||||
} | |||||
var targetHostPort string | |||||
if proxyURL != nil { | |||||
targetHostPort, _ = hostPortNoPort(proxyURL) | |||||
} else { | |||||
targetHostPort = hostPort | |||||
} | |||||
var deadline time.Time | |||||
if d.HandshakeTimeout != 0 { | |||||
deadline = time.Now().Add(d.HandshakeTimeout) | |||||
} | |||||
netDial := d.NetDial | |||||
if netDial == nil { | |||||
netDialer := &net.Dialer{Deadline: deadline} | |||||
netDial = netDialer.Dial | |||||
} | |||||
netConn, err := netDial("tcp", targetHostPort) | |||||
if err != nil { | |||||
return nil, nil, err | |||||
} | |||||
defer func() { | |||||
if netConn != nil { | |||||
netConn.Close() | |||||
} | |||||
}() | |||||
if err := netConn.SetDeadline(deadline); err != nil { | |||||
return nil, nil, err | |||||
} | |||||
if proxyURL != nil { | |||||
connectReq := &http.Request{ | |||||
Method: "CONNECT", | |||||
URL: &url.URL{Opaque: hostPort}, | |||||
Host: hostPort, | |||||
Header: make(http.Header), | |||||
} | |||||
connectReq.Write(netConn) | |||||
// Read response. | |||||
// Okay to use and discard buffered reader here, because | |||||
// TLS server will not speak until spoken to. | |||||
br := bufio.NewReader(netConn) | |||||
resp, err := http.ReadResponse(br, connectReq) | |||||
if err != nil { | |||||
return nil, nil, err | |||||
} | |||||
if resp.StatusCode != 200 { | |||||
f := strings.SplitN(resp.Status, " ", 2) | |||||
return nil, nil, errors.New(f[1]) | |||||
} | |||||
} | |||||
if u.Scheme == "https" { | |||||
cfg := d.TLSClientConfig | |||||
if cfg == nil { | |||||
cfg = &tls.Config{ServerName: hostNoPort} | |||||
} else if cfg.ServerName == "" { | |||||
shallowCopy := *cfg | |||||
cfg = &shallowCopy | |||||
cfg.ServerName = hostNoPort | |||||
} | |||||
tlsConn := tls.Client(netConn, cfg) | |||||
netConn = tlsConn | |||||
if err := tlsConn.Handshake(); err != nil { | |||||
return nil, nil, err | |||||
} | |||||
if !cfg.InsecureSkipVerify { | |||||
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil { | |||||
return nil, nil, err | |||||
} | |||||
} | |||||
} | |||||
conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize) | |||||
if err := req.Write(netConn); err != nil { | |||||
return nil, nil, err | |||||
} | |||||
resp, err := http.ReadResponse(conn.br, req) | |||||
if err != nil { | |||||
return nil, nil, err | |||||
} | |||||
if resp.StatusCode != 101 || | |||||
!strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") || | |||||
!strings.EqualFold(resp.Header.Get("Connection"), "upgrade") || | |||||
resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) { | |||||
// Before closing the network connection on return from this | |||||
// function, slurp up some of the response to aid application | |||||
// debugging. | |||||
buf := make([]byte, 1024) | |||||
n, _ := io.ReadFull(resp.Body, buf) | |||||
resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n])) | |||||
return nil, resp, ErrBadHandshake | |||||
} | |||||
resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) | |||||
conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol") | |||||
netConn.SetDeadline(time.Time{}) | |||||
netConn = nil // to avoid close in defer. | |||||
return conn, resp, nil | |||||
} |
@ -1,888 +0,0 @@ | |||||
// Copyright 2013 The Gorilla WebSocket 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 websocket | |||||
import ( | |||||
"bufio" | |||||
"encoding/binary" | |||||
"errors" | |||||
"io" | |||||
"io/ioutil" | |||||
"math/rand" | |||||
"net" | |||||
"strconv" | |||||
"time" | |||||
) | |||||
const ( | |||||
maxFrameHeaderSize = 2 + 8 + 4 // Fixed header + length + mask | |||||
maxControlFramePayloadSize = 125 | |||||
finalBit = 1 << 7 | |||||
maskBit = 1 << 7 | |||||
writeWait = time.Second | |||||
defaultReadBufferSize = 4096 | |||||
defaultWriteBufferSize = 4096 | |||||
continuationFrame = 0 | |||||
noFrame = -1 | |||||
) | |||||
// Close codes defined in RFC 6455, section 11.7. | |||||
const ( | |||||
CloseNormalClosure = 1000 | |||||
CloseGoingAway = 1001 | |||||
CloseProtocolError = 1002 | |||||
CloseUnsupportedData = 1003 | |||||
CloseNoStatusReceived = 1005 | |||||
CloseAbnormalClosure = 1006 | |||||
CloseInvalidFramePayloadData = 1007 | |||||
ClosePolicyViolation = 1008 | |||||
CloseMessageTooBig = 1009 | |||||
CloseMandatoryExtension = 1010 | |||||
CloseInternalServerErr = 1011 | |||||
CloseTLSHandshake = 1015 | |||||
) | |||||
// The message types are defined in RFC 6455, section 11.8. | |||||
const ( | |||||
// TextMessage denotes a text data message. The text message payload is | |||||
// interpreted as UTF-8 encoded text data. | |||||
TextMessage = 1 | |||||
// BinaryMessage denotes a binary data message. | |||||
BinaryMessage = 2 | |||||
// CloseMessage denotes a close control message. The optional message | |||||
// payload contains a numeric code and text. Use the FormatCloseMessage | |||||
// function to format a close message payload. | |||||
CloseMessage = 8 | |||||
// PingMessage denotes a ping control message. The optional message payload | |||||
// is UTF-8 encoded text. | |||||
PingMessage = 9 | |||||
// PongMessage denotes a ping control message. The optional message payload | |||||
// is UTF-8 encoded text. | |||||
PongMessage = 10 | |||||
) | |||||
// ErrCloseSent is returned when the application writes a message to the | |||||
// connection after sending a close message. | |||||
var ErrCloseSent = errors.New("websocket: close sent") | |||||
// ErrReadLimit is returned when reading a message that is larger than the | |||||
// read limit set for the connection. | |||||
var ErrReadLimit = errors.New("websocket: read limit exceeded") | |||||
// netError satisfies the net Error interface. | |||||
type netError struct { | |||||
msg string | |||||
temporary bool | |||||
timeout bool | |||||
} | |||||
func (e *netError) Error() string { return e.msg } | |||||
func (e *netError) Temporary() bool { return e.temporary } | |||||
func (e *netError) Timeout() bool { return e.timeout } | |||||
// CloseError represents close frame. | |||||
type CloseError struct { | |||||
// Code is defined in RFC 6455, section 11.7. | |||||
Code int | |||||
// Text is the optional text payload. | |||||
Text string | |||||
} | |||||
func (e *CloseError) Error() string { | |||||
s := []byte("websocket: close ") | |||||
s = strconv.AppendInt(s, int64(e.Code), 10) | |||||
switch e.Code { | |||||
case CloseNormalClosure: | |||||
s = append(s, " (normal)"...) | |||||
case CloseGoingAway: | |||||
s = append(s, " (going away)"...) | |||||
case CloseProtocolError: | |||||
s = append(s, " (protocol error)"...) | |||||
case CloseUnsupportedData: | |||||
s = append(s, " (unsupported data)"...) | |||||
case CloseNoStatusReceived: | |||||
s = append(s, " (no status)"...) | |||||
case CloseAbnormalClosure: | |||||
s = append(s, " (abnormal closure)"...) | |||||
case CloseInvalidFramePayloadData: | |||||
s = append(s, " (invalid payload data)"...) | |||||
case ClosePolicyViolation: | |||||
s = append(s, " (policy violation)"...) | |||||
case CloseMessageTooBig: | |||||
s = append(s, " (message too big)"...) | |||||
case CloseMandatoryExtension: | |||||
s = append(s, " (mandatory extension missing)"...) | |||||
case CloseInternalServerErr: | |||||
s = append(s, " (internal server error)"...) | |||||
case CloseTLSHandshake: | |||||
s = append(s, " (TLS handshake error)"...) | |||||
} | |||||
if e.Text != "" { | |||||
s = append(s, ": "...) | |||||
s = append(s, e.Text...) | |||||
} | |||||
return string(s) | |||||
} | |||||
// IsCloseError returns boolean indicating whether the error is a *CloseError | |||||
// with one of the specified codes. | |||||
func IsCloseError(err error, codes ...int) bool { | |||||
if e, ok := err.(*CloseError); ok { | |||||
for _, code := range codes { | |||||
if e.Code == code { | |||||
return true | |||||
} | |||||
} | |||||
} | |||||
return false | |||||
} | |||||
// IsUnexpectedCloseError returns boolean indicating whether the error is a | |||||
// *CloseError with a code not in the list of expected codes. | |||||
func IsUnexpectedCloseError(err error, expectedCodes ...int) bool { | |||||
if e, ok := err.(*CloseError); ok { | |||||
for _, code := range expectedCodes { | |||||
if e.Code == code { | |||||
return false | |||||
} | |||||
} | |||||
return true | |||||
} | |||||
return false | |||||
} | |||||
var ( | |||||
errWriteTimeout = &netError{msg: "websocket: write timeout", timeout: true, temporary: true} | |||||
errUnexpectedEOF = &CloseError{Code: CloseAbnormalClosure, Text: io.ErrUnexpectedEOF.Error()} | |||||
errBadWriteOpCode = errors.New("websocket: bad write message type") | |||||
errWriteClosed = errors.New("websocket: write closed") | |||||
errInvalidControlFrame = errors.New("websocket: invalid control frame") | |||||
) | |||||
func hideTempErr(err error) error { | |||||
if e, ok := err.(net.Error); ok && e.Temporary() { | |||||
err = &netError{msg: e.Error(), timeout: e.Timeout()} | |||||
} | |||||
return err | |||||
} | |||||
func isControl(frameType int) bool { | |||||
return frameType == CloseMessage || frameType == PingMessage || frameType == PongMessage | |||||
} | |||||
func isData(frameType int) bool { | |||||
return frameType == TextMessage || frameType == BinaryMessage | |||||
} | |||||
func maskBytes(key [4]byte, pos int, b []byte) int { | |||||
for i := range b { | |||||
b[i] ^= key[pos&3] | |||||
pos++ | |||||
} | |||||
return pos & 3 | |||||
} | |||||
func newMaskKey() [4]byte { | |||||
n := rand.Uint32() | |||||
return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)} | |||||
} | |||||
// Conn represents a WebSocket connection. | |||||
type Conn struct { | |||||
conn net.Conn | |||||
isServer bool | |||||
subprotocol string | |||||
// Write fields | |||||
mu chan bool // used as mutex to protect write to conn and closeSent | |||||
closeSent bool // true if close message was sent | |||||
// Message writer fields. | |||||
writeErr error | |||||
writeBuf []byte // frame is constructed in this buffer. | |||||
writePos int // end of data in writeBuf. | |||||
writeFrameType int // type of the current frame. | |||||
writeSeq int // incremented to invalidate message writers. | |||||
writeDeadline time.Time | |||||
// Read fields | |||||
readErr error | |||||
br *bufio.Reader | |||||
readRemaining int64 // bytes remaining in current frame. | |||||
readFinal bool // true the current message has more frames. | |||||
readSeq int // incremented to invalidate message readers. | |||||
readLength int64 // Message size. | |||||
readLimit int64 // Maximum message size. | |||||
readMaskPos int | |||||
readMaskKey [4]byte | |||||
handlePong func(string) error | |||||
handlePing func(string) error | |||||
} | |||||
func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn { | |||||
mu := make(chan bool, 1) | |||||
mu <- true | |||||
if readBufferSize == 0 { | |||||
readBufferSize = defaultReadBufferSize | |||||
} | |||||
if writeBufferSize == 0 { | |||||
writeBufferSize = defaultWriteBufferSize | |||||
} | |||||
c := &Conn{ | |||||
isServer: isServer, | |||||
br: bufio.NewReaderSize(conn, readBufferSize), | |||||
conn: conn, | |||||
mu: mu, | |||||
readFinal: true, | |||||
writeBuf: make([]byte, writeBufferSize+maxFrameHeaderSize), | |||||
writeFrameType: noFrame, | |||||
writePos: maxFrameHeaderSize, | |||||
} | |||||
c.SetPingHandler(nil) | |||||
c.SetPongHandler(nil) | |||||
return c | |||||
} | |||||
// Subprotocol returns the negotiated protocol for the connection. | |||||
func (c *Conn) Subprotocol() string { | |||||
return c.subprotocol | |||||
} | |||||
// Close closes the underlying network connection without sending or waiting for a close frame. | |||||
func (c *Conn) Close() error { | |||||
return c.conn.Close() | |||||
} | |||||
// LocalAddr returns the local network address. | |||||
func (c *Conn) LocalAddr() net.Addr { | |||||
return c.conn.LocalAddr() | |||||
} | |||||
// RemoteAddr returns the remote network address. | |||||
func (c *Conn) RemoteAddr() net.Addr { | |||||
return c.conn.RemoteAddr() | |||||
} | |||||
// Write methods | |||||
func (c *Conn) write(frameType int, deadline time.Time, bufs ...[]byte) error { | |||||
<-c.mu | |||||
defer func() { c.mu <- true }() | |||||
if c.closeSent { | |||||
return ErrCloseSent | |||||
} else if frameType == CloseMessage { | |||||
c.closeSent = true | |||||
} | |||||
c.conn.SetWriteDeadline(deadline) | |||||
for _, buf := range bufs { | |||||
if len(buf) > 0 { | |||||
n, err := c.conn.Write(buf) | |||||
if n != len(buf) { | |||||
// Close on partial write. | |||||
c.conn.Close() | |||||
} | |||||
if err != nil { | |||||
return err | |||||
} | |||||
} | |||||
} | |||||
return nil | |||||
} | |||||
// WriteControl writes a control message with the given deadline. The allowed | |||||
// message types are CloseMessage, PingMessage and PongMessage. | |||||
func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) error { | |||||
if !isControl(messageType) { | |||||
return errBadWriteOpCode | |||||
} | |||||
if len(data) > maxControlFramePayloadSize { | |||||
return errInvalidControlFrame | |||||
} | |||||
b0 := byte(messageType) | finalBit | |||||
b1 := byte(len(data)) | |||||
if !c.isServer { | |||||
b1 |= maskBit | |||||
} | |||||
buf := make([]byte, 0, maxFrameHeaderSize+maxControlFramePayloadSize) | |||||
buf = append(buf, b0, b1) | |||||
if c.isServer { | |||||
buf = append(buf, data...) | |||||
} else { | |||||
key := newMaskKey() | |||||
buf = append(buf, key[:]...) | |||||
buf = append(buf, data...) | |||||
maskBytes(key, 0, buf[6:]) | |||||
} | |||||
d := time.Hour * 1000 | |||||
if !deadline.IsZero() { | |||||
d = deadline.Sub(time.Now()) | |||||
if d < 0 { | |||||
return errWriteTimeout | |||||
} | |||||
} | |||||
timer := time.NewTimer(d) | |||||
select { | |||||
case <-c.mu: | |||||
timer.Stop() | |||||
case <-timer.C: | |||||
return errWriteTimeout | |||||
} | |||||
defer func() { c.mu <- true }() | |||||
if c.closeSent { | |||||
return ErrCloseSent | |||||
} else if messageType == CloseMessage { | |||||
c.closeSent = true | |||||
} | |||||
c.conn.SetWriteDeadline(deadline) | |||||
n, err := c.conn.Write(buf) | |||||
if n != 0 && n != len(buf) { | |||||
c.conn.Close() | |||||
} | |||||
return hideTempErr(err) | |||||
} | |||||
// NextWriter returns a writer for the next message to send. The writer's | |||||
// Close method flushes the complete message to the network. | |||||
// | |||||
// There can be at most one open writer on a connection. NextWriter closes the | |||||
// previous writer if the application has not already done so. | |||||
func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) { | |||||
if c.writeErr != nil { | |||||
return nil, c.writeErr | |||||
} | |||||
if c.writeFrameType != noFrame { | |||||
if err := c.flushFrame(true, nil); err != nil { | |||||
return nil, err | |||||
} | |||||
} | |||||
if !isControl(messageType) && !isData(messageType) { | |||||
return nil, errBadWriteOpCode | |||||
} | |||||
c.writeFrameType = messageType | |||||
return messageWriter{c, c.writeSeq}, nil | |||||
} | |||||
func (c *Conn) flushFrame(final bool, extra []byte) error { | |||||
length := c.writePos - maxFrameHeaderSize + len(extra) | |||||
// Check for invalid control frames. | |||||
if isControl(c.writeFrameType) && | |||||
(!final || length > maxControlFramePayloadSize) { | |||||
c.writeSeq++ | |||||
c.writeFrameType = noFrame | |||||
c.writePos = maxFrameHeaderSize | |||||
return errInvalidControlFrame | |||||
} | |||||
b0 := byte(c.writeFrameType) | |||||
if final { | |||||
b0 |= finalBit | |||||
} | |||||
b1 := byte(0) | |||||
if !c.isServer { | |||||
b1 |= maskBit | |||||
} | |||||
// Assume that the frame starts at beginning of c.writeBuf. | |||||
framePos := 0 | |||||
if c.isServer { | |||||
// Adjust up if mask not included in the header. | |||||
framePos = 4 | |||||
} | |||||
switch { | |||||
case length >= 65536: | |||||
c.writeBuf[framePos] = b0 | |||||
c.writeBuf[framePos+1] = b1 | 127 | |||||
binary.BigEndian.PutUint64(c.writeBuf[framePos+2:], uint64(length)) | |||||
case length > 125: | |||||
framePos += 6 | |||||
c.writeBuf[framePos] = b0 | |||||
c.writeBuf[framePos+1] = b1 | 126 | |||||
binary.BigEndian.PutUint16(c.writeBuf[framePos+2:], uint16(length)) | |||||
default: | |||||
framePos += 8 | |||||
c.writeBuf[framePos] = b0 | |||||
c.writeBuf[framePos+1] = b1 | byte(length) | |||||
} | |||||
if !c.isServer { | |||||
key := newMaskKey() | |||||
copy(c.writeBuf[maxFrameHeaderSize-4:], key[:]) | |||||
maskBytes(key, 0, c.writeBuf[maxFrameHeaderSize:c.writePos]) | |||||
if len(extra) > 0 { | |||||
c.writeErr = errors.New("websocket: internal error, extra used in client mode") | |||||
return c.writeErr | |||||
} | |||||
} | |||||
// Write the buffers to the connection. | |||||
c.writeErr = c.write(c.writeFrameType, c.writeDeadline, c.writeBuf[framePos:c.writePos], extra) | |||||
// Setup for next frame. | |||||
c.writePos = maxFrameHeaderSize | |||||
c.writeFrameType = continuationFrame | |||||
if final { | |||||
c.writeSeq++ | |||||
c.writeFrameType = noFrame | |||||
} | |||||
return c.writeErr | |||||
} | |||||
type messageWriter struct { | |||||
c *Conn | |||||
seq int | |||||
} | |||||
func (w messageWriter) err() error { | |||||
c := w.c | |||||
if c.writeSeq != w.seq { | |||||
return errWriteClosed | |||||
} | |||||
if c.writeErr != nil { | |||||
return c.writeErr | |||||
} | |||||
return nil | |||||
} | |||||
func (w messageWriter) ncopy(max int) (int, error) { | |||||
n := len(w.c.writeBuf) - w.c.writePos | |||||
if n <= 0 { | |||||
if err := w.c.flushFrame(false, nil); err != nil { | |||||
return 0, err | |||||
} | |||||
n = len(w.c.writeBuf) - w.c.writePos | |||||
} | |||||
if n > max { | |||||
n = max | |||||
} | |||||
return n, nil | |||||
} | |||||
func (w messageWriter) write(final bool, p []byte) (int, error) { | |||||
if err := w.err(); err != nil { | |||||
return 0, err | |||||
} | |||||
if len(p) > 2*len(w.c.writeBuf) && w.c.isServer { | |||||
// Don't buffer large messages. | |||||
err := w.c.flushFrame(final, p) | |||||
if err != nil { | |||||
return 0, err | |||||
} | |||||
return len(p), nil | |||||
} | |||||
nn := len(p) | |||||
for len(p) > 0 { | |||||
n, err := w.ncopy(len(p)) | |||||
if err != nil { | |||||
return 0, err | |||||
} | |||||
copy(w.c.writeBuf[w.c.writePos:], p[:n]) | |||||
w.c.writePos += n | |||||
p = p[n:] | |||||
} | |||||
return nn, nil | |||||
} | |||||
func (w messageWriter) Write(p []byte) (int, error) { | |||||
return w.write(false, p) | |||||
} | |||||
func (w messageWriter) WriteString(p string) (int, error) { | |||||
if err := w.err(); err != nil { | |||||
return 0, err | |||||
} | |||||
nn := len(p) | |||||
for len(p) > 0 { | |||||
n, err := w.ncopy(len(p)) | |||||
if err != nil { | |||||
return 0, err | |||||
} | |||||
copy(w.c.writeBuf[w.c.writePos:], p[:n]) | |||||
w.c.writePos += n | |||||
p = p[n:] | |||||
} | |||||
return nn, nil | |||||
} | |||||
func (w messageWriter) ReadFrom(r io.Reader) (nn int64, err error) { | |||||
if err := w.err(); err != nil { | |||||
return 0, err | |||||
} | |||||
for { | |||||
if w.c.writePos == len(w.c.writeBuf) { | |||||
err = w.c.flushFrame(false, nil) | |||||
if err != nil { | |||||
break | |||||
} | |||||
} | |||||
var n int | |||||
n, err = r.Read(w.c.writeBuf[w.c.writePos:]) | |||||
w.c.writePos += n | |||||
nn += int64(n) | |||||
if err != nil { | |||||
if err == io.EOF { | |||||
err = nil | |||||
} | |||||
break | |||||
} | |||||
} | |||||
return nn, err | |||||
} | |||||
func (w messageWriter) Close() error { | |||||
if err := w.err(); err != nil { | |||||
return err | |||||
} | |||||
return w.c.flushFrame(true, nil) | |||||
} | |||||
// WriteMessage is a helper method for getting a writer using NextWriter, | |||||
// writing the message and closing the writer. | |||||
func (c *Conn) WriteMessage(messageType int, data []byte) error { | |||||
wr, err := c.NextWriter(messageType) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
w := wr.(messageWriter) | |||||
if _, err := w.write(true, data); err != nil { | |||||
return err | |||||
} | |||||
if c.writeSeq == w.seq { | |||||
if err := c.flushFrame(true, nil); err != nil { | |||||
return err | |||||
} | |||||
} | |||||
return nil | |||||
} | |||||
// SetWriteDeadline sets the write deadline on the underlying network | |||||
// connection. After a write has timed out, the websocket state is corrupt and | |||||
// all future writes will return an error. A zero value for t means writes will | |||||
// not time out. | |||||
func (c *Conn) SetWriteDeadline(t time.Time) error { | |||||
c.writeDeadline = t | |||||
return nil | |||||
} | |||||
// Read methods | |||||
// readFull is like io.ReadFull except that io.EOF is never returned. | |||||
func (c *Conn) readFull(p []byte) (err error) { | |||||
var n int | |||||
for n < len(p) && err == nil { | |||||
var nn int | |||||
nn, err = c.br.Read(p[n:]) | |||||
n += nn | |||||
} | |||||
if n == len(p) { | |||||
err = nil | |||||
} else if err == io.EOF { | |||||
err = errUnexpectedEOF | |||||
} | |||||
return | |||||
} | |||||
func (c *Conn) advanceFrame() (int, error) { | |||||
// 1. Skip remainder of previous frame. | |||||
if c.readRemaining > 0 { | |||||
if _, err := io.CopyN(ioutil.Discard, c.br, c.readRemaining); err != nil { | |||||
return noFrame, err | |||||
} | |||||
} | |||||
// 2. Read and parse first two bytes of frame header. | |||||
var b [8]byte | |||||
if err := c.readFull(b[:2]); err != nil { | |||||
return noFrame, err | |||||
} | |||||
final := b[0]&finalBit != 0 | |||||
frameType := int(b[0] & 0xf) | |||||
reserved := int((b[0] >> 4) & 0x7) | |||||
mask := b[1]&maskBit != 0 | |||||
c.readRemaining = int64(b[1] & 0x7f) | |||||
if reserved != 0 { | |||||
return noFrame, c.handleProtocolError("unexpected reserved bits " + strconv.Itoa(reserved)) | |||||
} | |||||
switch frameType { | |||||
case CloseMessage, PingMessage, PongMessage: | |||||
if c.readRemaining > maxControlFramePayloadSize { | |||||
return noFrame, c.handleProtocolError("control frame length > 125") | |||||
} | |||||
if !final { | |||||
return noFrame, c.handleProtocolError("control frame not final") | |||||
} | |||||
case TextMessage, BinaryMessage: | |||||
if !c.readFinal { | |||||
return noFrame, c.handleProtocolError("message start before final message frame") | |||||
} | |||||
c.readFinal = final | |||||
case continuationFrame: | |||||
if c.readFinal { | |||||
return noFrame, c.handleProtocolError("continuation after final message frame") | |||||
} | |||||
c.readFinal = final | |||||
default: | |||||
return noFrame, c.handleProtocolError("unknown opcode " + strconv.Itoa(frameType)) | |||||
} | |||||
// 3. Read and parse frame length. | |||||
switch c.readRemaining { | |||||
case 126: | |||||
if err := c.readFull(b[:2]); err != nil { | |||||
return noFrame, err | |||||
} | |||||
c.readRemaining = int64(binary.BigEndian.Uint16(b[:2])) | |||||
case 127: | |||||
if err := c.readFull(b[:8]); err != nil { | |||||
return noFrame, err | |||||
} | |||||
c.readRemaining = int64(binary.BigEndian.Uint64(b[:8])) | |||||
} | |||||
// 4. Handle frame masking. | |||||
if mask != c.isServer { | |||||
return noFrame, c.handleProtocolError("incorrect mask flag") | |||||
} | |||||
if mask { | |||||
c.readMaskPos = 0 | |||||
if err := c.readFull(c.readMaskKey[:]); err != nil { | |||||
return noFrame, err | |||||
} | |||||
} | |||||
// 5. For text and binary messages, enforce read limit and return. | |||||
if frameType == continuationFrame || frameType == TextMessage || frameType == BinaryMessage { | |||||
c.readLength += c.readRemaining | |||||
if c.readLimit > 0 && c.readLength > c.readLimit { | |||||
c.WriteControl(CloseMessage, FormatCloseMessage(CloseMessageTooBig, ""), time.Now().Add(writeWait)) | |||||
return noFrame, ErrReadLimit | |||||
} | |||||
return frameType, nil | |||||
} | |||||
// 6. Read control frame payload. | |||||
var payload []byte | |||||
if c.readRemaining > 0 { | |||||
payload = make([]byte, c.readRemaining) | |||||
c.readRemaining = 0 | |||||
if err := c.readFull(payload); err != nil { | |||||
return noFrame, err | |||||
} | |||||
if c.isServer { | |||||
maskBytes(c.readMaskKey, 0, payload) | |||||
} | |||||
} | |||||
// 7. Process control frame payload. | |||||
switch frameType { | |||||
case PongMessage: | |||||
if err := c.handlePong(string(payload)); err != nil { | |||||
return noFrame, err | |||||
} | |||||
case PingMessage: | |||||
if err := c.handlePing(string(payload)); err != nil { | |||||
return noFrame, err | |||||
} | |||||
case CloseMessage: | |||||
c.WriteControl(CloseMessage, []byte{}, time.Now().Add(writeWait)) | |||||
closeCode := CloseNoStatusReceived | |||||
closeText := "" | |||||
if len(payload) >= 2 { | |||||
closeCode = int(binary.BigEndian.Uint16(payload)) | |||||
closeText = string(payload[2:]) | |||||
} | |||||
return noFrame, &CloseError{Code: closeCode, Text: closeText} | |||||
} | |||||
return frameType, nil | |||||
} | |||||
func (c *Conn) handleProtocolError(message string) error { | |||||
c.WriteControl(CloseMessage, FormatCloseMessage(CloseProtocolError, message), time.Now().Add(writeWait)) | |||||
return errors.New("websocket: " + message) | |||||
} | |||||
// NextReader returns the next data message received from the peer. The | |||||
// returned messageType is either TextMessage or BinaryMessage. | |||||
// | |||||
// There can be at most one open reader on a connection. NextReader discards | |||||
// the previous message if the application has not already consumed it. | |||||
// | |||||
// Errors returned from NextReader are permanent. If NextReader returns a | |||||
// non-nil error, then all subsequent calls to NextReader return the same | |||||
// error. | |||||
func (c *Conn) NextReader() (messageType int, r io.Reader, err error) { | |||||
c.readSeq++ | |||||
c.readLength = 0 | |||||
for c.readErr == nil { | |||||
frameType, err := c.advanceFrame() | |||||
if err != nil { | |||||
c.readErr = hideTempErr(err) | |||||
break | |||||
} | |||||
if frameType == TextMessage || frameType == BinaryMessage { | |||||
return frameType, messageReader{c, c.readSeq}, nil | |||||
} | |||||
} | |||||
return noFrame, nil, c.readErr | |||||
} | |||||
type messageReader struct { | |||||
c *Conn | |||||
seq int | |||||
} | |||||
func (r messageReader) Read(b []byte) (int, error) { | |||||
if r.seq != r.c.readSeq { | |||||
return 0, io.EOF | |||||
} | |||||
for r.c.readErr == nil { | |||||
if r.c.readRemaining > 0 { | |||||
if int64(len(b)) > r.c.readRemaining { | |||||
b = b[:r.c.readRemaining] | |||||
} | |||||
n, err := r.c.br.Read(b) | |||||
r.c.readErr = hideTempErr(err) | |||||
if r.c.isServer { | |||||
r.c.readMaskPos = maskBytes(r.c.readMaskKey, r.c.readMaskPos, b[:n]) | |||||
} | |||||
r.c.readRemaining -= int64(n) | |||||
return n, r.c.readErr | |||||
} | |||||
if r.c.readFinal { | |||||
r.c.readSeq++ | |||||
return 0, io.EOF | |||||
} | |||||
frameType, err := r.c.advanceFrame() | |||||
switch { | |||||
case err != nil: | |||||
r.c.readErr = hideTempErr(err) | |||||
case frameType == TextMessage || frameType == BinaryMessage: | |||||
r.c.readErr = errors.New("websocket: internal error, unexpected text or binary in Reader") | |||||
} | |||||
} | |||||
err := r.c.readErr | |||||
if err == io.EOF && r.seq == r.c.readSeq { | |||||
err = errUnexpectedEOF | |||||
} | |||||
return 0, err | |||||
} | |||||
// ReadMessage is a helper method for getting a reader using NextReader and | |||||
// reading from that reader to a buffer. | |||||
func (c *Conn) ReadMessage() (messageType int, p []byte, err error) { | |||||
var r io.Reader | |||||
messageType, r, err = c.NextReader() | |||||
if err != nil { | |||||
return messageType, nil, err | |||||
} | |||||
p, err = ioutil.ReadAll(r) | |||||
return messageType, p, err | |||||
} | |||||
// SetReadDeadline sets the read deadline on the underlying network connection. | |||||
// After a read has timed out, the websocket connection state is corrupt and | |||||
// all future reads will return an error. A zero value for t means reads will | |||||
// not time out. | |||||
func (c *Conn) SetReadDeadline(t time.Time) error { | |||||
return c.conn.SetReadDeadline(t) | |||||
} | |||||
// SetReadLimit sets the maximum size for a message read from the peer. If a | |||||
// message exceeds the limit, the connection sends a close frame to the peer | |||||
// and returns ErrReadLimit to the application. | |||||
func (c *Conn) SetReadLimit(limit int64) { | |||||
c.readLimit = limit | |||||
} | |||||
// SetPingHandler sets the handler for ping messages received from the peer. | |||||
// The appData argument to h is the PING frame application data. The default | |||||
// ping handler sends a pong to the peer. | |||||
func (c *Conn) SetPingHandler(h func(appData string) error) { | |||||
if h == nil { | |||||
h = func(message string) error { | |||||
err := c.WriteControl(PongMessage, []byte(message), time.Now().Add(writeWait)) | |||||
if err == ErrCloseSent { | |||||
return nil | |||||
} else if e, ok := err.(net.Error); ok && e.Temporary() { | |||||
return nil | |||||
} | |||||
return err | |||||
} | |||||
} | |||||
c.handlePing = h | |||||
} | |||||
// SetPongHandler sets the handler for pong messages received from the peer. | |||||
// The appData argument to h is the PONG frame application data. The default | |||||
// pong handler does nothing. | |||||
func (c *Conn) SetPongHandler(h func(appData string) error) { | |||||
if h == nil { | |||||
h = func(string) error { return nil } | |||||
} | |||||
c.handlePong = h | |||||
} | |||||
// UnderlyingConn returns the internal net.Conn. This can be used to further | |||||
// modifications to connection specific flags. | |||||
func (c *Conn) UnderlyingConn() net.Conn { | |||||
return c.conn | |||||
} | |||||
// FormatCloseMessage formats closeCode and text as a WebSocket close message. | |||||
func FormatCloseMessage(closeCode int, text string) []byte { | |||||
buf := make([]byte, 2+len(text)) | |||||
binary.BigEndian.PutUint16(buf, uint16(closeCode)) | |||||
copy(buf[2:], text) | |||||
return buf | |||||
} |
@ -1,151 +0,0 @@ | |||||
// Copyright 2013 The Gorilla WebSocket 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 websocket implements the WebSocket protocol defined in RFC 6455. | |||||
// | |||||
// Overview | |||||
// | |||||
// The Conn type represents a WebSocket connection. A server application uses | |||||
// the Upgrade function from an Upgrader object with a HTTP request handler | |||||
// to get a pointer to a Conn: | |||||
// | |||||
// var upgrader = websocket.Upgrader{ | |||||
// ReadBufferSize: 1024, | |||||
// WriteBufferSize: 1024, | |||||
// } | |||||
// | |||||
// func handler(w http.ResponseWriter, r *http.Request) { | |||||
// conn, err := upgrader.Upgrade(w, r, nil) | |||||
// if err != nil { | |||||
// log.Println(err) | |||||
// return | |||||
// } | |||||
// ... Use conn to send and receive messages. | |||||
// } | |||||
// | |||||
// Call the connection's WriteMessage and ReadMessage methods to send and | |||||
// receive messages as a slice of bytes. This snippet of code shows how to echo | |||||
// messages using these methods: | |||||
// | |||||
// for { | |||||
// messageType, p, err := conn.ReadMessage() | |||||
// if err != nil { | |||||
// return | |||||
// } | |||||
// if err = conn.WriteMessage(messageType, p); err != nil { | |||||
// return err | |||||
// } | |||||
// } | |||||
// | |||||
// In above snippet of code, p is a []byte and messageType is an int with value | |||||
// websocket.BinaryMessage or websocket.TextMessage. | |||||
// | |||||
// An application can also send and receive messages using the io.WriteCloser | |||||
// and io.Reader interfaces. To send a message, call the connection NextWriter | |||||
// method to get an io.WriteCloser, write the message to the writer and close | |||||
// the writer when done. To receive a message, call the connection NextReader | |||||
// method to get an io.Reader and read until io.EOF is returned. This snippet | |||||
// snippet shows how to echo messages using the NextWriter and NextReader | |||||
// methods: | |||||
// | |||||
// for { | |||||
// messageType, r, err := conn.NextReader() | |||||
// if err != nil { | |||||
// return | |||||
// } | |||||
// w, err := conn.NextWriter(messageType) | |||||
// if err != nil { | |||||
// return err | |||||
// } | |||||
// if _, err := io.Copy(w, r); err != nil { | |||||
// return err | |||||
// } | |||||
// if err := w.Close(); err != nil { | |||||
// return err | |||||
// } | |||||
// } | |||||
// | |||||
// Data Messages | |||||
// | |||||
// The WebSocket protocol distinguishes between text and binary data messages. | |||||
// Text messages are interpreted as UTF-8 encoded text. The interpretation of | |||||
// binary messages is left to the application. | |||||
// | |||||
// This package uses the TextMessage and BinaryMessage integer constants to | |||||
// identify the two data message types. The ReadMessage and NextReader methods | |||||
// return the type of the received message. The messageType argument to the | |||||
// WriteMessage and NextWriter methods specifies the type of a sent message. | |||||
// | |||||
// It is the application's responsibility to ensure that text messages are | |||||
// valid UTF-8 encoded text. | |||||
// | |||||
// Control Messages | |||||
// | |||||
// The WebSocket protocol defines three types of control messages: close, ping | |||||
// and pong. Call the connection WriteControl, WriteMessage or NextWriter | |||||
// methods to send a control message to the peer. | |||||
// | |||||
// Connections handle received ping and pong messages by invoking a callback | |||||
// function set with SetPingHandler and SetPongHandler methods. These callback | |||||
// functions can be invoked from the ReadMessage method, the NextReader method | |||||
// or from a call to the data message reader returned from NextReader. | |||||
// | |||||
// Connections handle received close messages by returning an error from the | |||||
// ReadMessage method, the NextReader method or from a call to the data message | |||||
// reader returned from NextReader. | |||||
// | |||||
// Concurrency | |||||
// | |||||
// Connections support one concurrent reader and one concurrent writer. | |||||
// | |||||
// Applications are responsible for ensuring that no more than one goroutine | |||||
// calls the write methods (NextWriter, SetWriteDeadline, WriteMessage, | |||||
// WriteJSON) concurrently and that no more than one goroutine calls the read | |||||
// methods (NextReader, SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, | |||||
// SetPingHandler) concurrently. | |||||
// | |||||
// The Close and WriteControl methods can be called concurrently with all other | |||||
// methods. | |||||
// | |||||
// Read is Required | |||||
// | |||||
// The application must read the connection to process ping and close messages | |||||
// sent from the peer. If the application is not otherwise interested in | |||||
// messages from the peer, then the application should start a goroutine to read | |||||
// and discard messages from the peer. A simple example is: | |||||
// | |||||
// func readLoop(c *websocket.Conn) { | |||||
// for { | |||||
// if _, _, err := c.NextReader(); err != nil { | |||||
// c.Close() | |||||
// break | |||||
// } | |||||
// } | |||||
// } | |||||
// | |||||
// Origin Considerations | |||||
// | |||||
// Web browsers allow Javascript applications to open a WebSocket connection to | |||||
// any host. It's up to the server to enforce an origin policy using the Origin | |||||
// request header sent by the browser. | |||||
// | |||||
// The Upgrader calls the function specified in the CheckOrigin field to check | |||||
// the origin. If the CheckOrigin function returns false, then the Upgrade | |||||
// method fails the WebSocket handshake with HTTP status 403. | |||||
// | |||||
// If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail | |||||
// the handshake if the Origin request header is present and not equal to the | |||||
// Host request header. | |||||
// | |||||
// An application can allow connections from any origin by specifying a | |||||
// function that always returns true: | |||||
// | |||||
// var upgrader = websocket.Upgrader{ | |||||
// CheckOrigin: func(r *http.Request) bool { return true }, | |||||
// } | |||||
// | |||||
// The deprecated Upgrade function does not enforce an origin policy. It's the | |||||
// application's responsibility to check the Origin header before calling | |||||
// Upgrade. | |||||
package websocket |
@ -1,13 +0,0 @@ | |||||
# Test Server | |||||
This package contains a server for the [Autobahn WebSockets Test Suite](http://autobahn.ws/testsuite). | |||||
To test the server, run | |||||
go run server.go | |||||
and start the client test driver | |||||
wstest -m fuzzingclient -s fuzzingclient.json | |||||
When the client completes, it writes a report to reports/clients/index.html. |
@ -1,14 +0,0 @@ | |||||
{ | |||||
"options": {"failByDrop": false}, | |||||
"outdir": "./reports/clients", | |||||
"servers": [ | |||||
{"agent": "ReadAllWriteMessage", "url": "ws://localhost:9000/m", "options": {"version": 18}}, | |||||
{"agent": "ReadAllWrite", "url": "ws://localhost:9000/r", "options": {"version": 18}}, | |||||
{"agent": "CopyFull", "url": "ws://localhost:9000/f", "options": {"version": 18}}, | |||||
{"agent": "CopyWriterOnly", "url": "ws://localhost:9000/c", "options": {"version": 18}} | |||||
], | |||||
"cases": ["*"], | |||||
"exclude-cases": [], | |||||
"exclude-agent-cases": {} | |||||
} |
@ -1,246 +0,0 @@ | |||||
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
// Command server is a test server for the Autobahn WebSockets Test Suite. | |||||
package main | |||||
import ( | |||||
"errors" | |||||
"flag" | |||||
"github.com/gorilla/websocket" | |||||
"io" | |||||
"log" | |||||
"net/http" | |||||
"time" | |||||
"unicode/utf8" | |||||
) | |||||
var upgrader = websocket.Upgrader{ | |||||
ReadBufferSize: 4096, | |||||
WriteBufferSize: 4096, | |||||
CheckOrigin: func(r *http.Request) bool { | |||||
return true | |||||
}, | |||||
} | |||||
// echoCopy echoes messages from the client using io.Copy. | |||||
func echoCopy(w http.ResponseWriter, r *http.Request, writerOnly bool) { | |||||
conn, err := upgrader.Upgrade(w, r, nil) | |||||
if err != nil { | |||||
log.Println("Upgrade:", err) | |||||
return | |||||
} | |||||
defer conn.Close() | |||||
for { | |||||
mt, r, err := conn.NextReader() | |||||
if err != nil { | |||||
if err != io.EOF { | |||||
log.Println("NextReader:", err) | |||||
} | |||||
return | |||||
} | |||||
if mt == websocket.TextMessage { | |||||
r = &validator{r: r} | |||||
} | |||||
w, err := conn.NextWriter(mt) | |||||
if err != nil { | |||||
log.Println("NextWriter:", err) | |||||
return | |||||
} | |||||
if mt == websocket.TextMessage { | |||||
r = &validator{r: r} | |||||
} | |||||
if writerOnly { | |||||
_, err = io.Copy(struct{ io.Writer }{w}, r) | |||||
} else { | |||||
_, err = io.Copy(w, r) | |||||
} | |||||
if err != nil { | |||||
if err == errInvalidUTF8 { | |||||
conn.WriteControl(websocket.CloseMessage, | |||||
websocket.FormatCloseMessage(websocket.CloseInvalidFramePayloadData, ""), | |||||
time.Time{}) | |||||
} | |||||
log.Println("Copy:", err) | |||||
return | |||||
} | |||||
err = w.Close() | |||||
if err != nil { | |||||
log.Println("Close:", err) | |||||
return | |||||
} | |||||
} | |||||
} | |||||
func echoCopyWriterOnly(w http.ResponseWriter, r *http.Request) { | |||||
echoCopy(w, r, true) | |||||
} | |||||
func echoCopyFull(w http.ResponseWriter, r *http.Request) { | |||||
echoCopy(w, r, false) | |||||
} | |||||
// echoReadAll echoes messages from the client by reading the entire message | |||||
// with ioutil.ReadAll. | |||||
func echoReadAll(w http.ResponseWriter, r *http.Request, writeMessage bool) { | |||||
conn, err := upgrader.Upgrade(w, r, nil) | |||||
if err != nil { | |||||
log.Println("Upgrade:", err) | |||||
return | |||||
} | |||||
defer conn.Close() | |||||
for { | |||||
mt, b, err := conn.ReadMessage() | |||||
if err != nil { | |||||
if err != io.EOF { | |||||
log.Println("NextReader:", err) | |||||
} | |||||
return | |||||
} | |||||
if mt == websocket.TextMessage { | |||||
if !utf8.Valid(b) { | |||||
conn.WriteControl(websocket.CloseMessage, | |||||
websocket.FormatCloseMessage(websocket.CloseInvalidFramePayloadData, ""), | |||||
time.Time{}) | |||||
log.Println("ReadAll: invalid utf8") | |||||
} | |||||
} | |||||
if writeMessage { | |||||
err = conn.WriteMessage(mt, b) | |||||
if err != nil { | |||||
log.Println("WriteMessage:", err) | |||||
} | |||||
} else { | |||||
w, err := conn.NextWriter(mt) | |||||
if err != nil { | |||||
log.Println("NextWriter:", err) | |||||
return | |||||
} | |||||
if _, err := w.Write(b); err != nil { | |||||
log.Println("Writer:", err) | |||||
return | |||||
} | |||||
if err := w.Close(); err != nil { | |||||
log.Println("Close:", err) | |||||
return | |||||
} | |||||
} | |||||
} | |||||
} | |||||
func echoReadAllWriter(w http.ResponseWriter, r *http.Request) { | |||||
echoReadAll(w, r, false) | |||||
} | |||||
func echoReadAllWriteMessage(w http.ResponseWriter, r *http.Request) { | |||||
echoReadAll(w, r, true) | |||||
} | |||||
func serveHome(w http.ResponseWriter, r *http.Request) { | |||||
if r.URL.Path != "/" { | |||||
http.Error(w, "Not found.", 404) | |||||
return | |||||
} | |||||
if r.Method != "GET" { | |||||
http.Error(w, "Method not allowed", 405) | |||||
return | |||||
} | |||||
w.Header().Set("Content-Type", "text/html; charset=utf-8") | |||||
io.WriteString(w, "<html><body>Echo Server</body></html>") | |||||
} | |||||
var addr = flag.String("addr", ":9000", "http service address") | |||||
func main() { | |||||
flag.Parse() | |||||
http.HandleFunc("/", serveHome) | |||||
http.HandleFunc("/c", echoCopyWriterOnly) | |||||
http.HandleFunc("/f", echoCopyFull) | |||||
http.HandleFunc("/r", echoReadAllWriter) | |||||
http.HandleFunc("/m", echoReadAllWriteMessage) | |||||
err := http.ListenAndServe(*addr, nil) | |||||
if err != nil { | |||||
log.Fatal("ListenAndServe: ", err) | |||||
} | |||||
} | |||||
type validator struct { | |||||
state int | |||||
x rune | |||||
r io.Reader | |||||
} | |||||
var errInvalidUTF8 = errors.New("invalid utf8") | |||||
func (r *validator) Read(p []byte) (int, error) { | |||||
n, err := r.r.Read(p) | |||||
state := r.state | |||||
x := r.x | |||||
for _, b := range p[:n] { | |||||
state, x = decode(state, x, b) | |||||
if state == utf8Reject { | |||||
break | |||||
} | |||||
} | |||||
r.state = state | |||||
r.x = x | |||||
if state == utf8Reject || (err == io.EOF && state != utf8Accept) { | |||||
return n, errInvalidUTF8 | |||||
} | |||||
return n, err | |||||
} | |||||
// UTF-8 decoder from http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ | |||||
// | |||||
// Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de> | |||||
// | |||||
// 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. | |||||
var utf8d = [...]byte{ | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1f | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3f | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5f | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7f | |||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9f | |||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..bf | |||||
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..df | |||||
0xa, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // e0..ef | |||||
0xb, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // f0..ff | |||||
0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 | |||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 | |||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 | |||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 | |||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s7..s8 | |||||
} | |||||
const ( | |||||
utf8Accept = 0 | |||||
utf8Reject = 1 | |||||
) | |||||
func decode(state int, x rune, b byte) (int, rune) { | |||||
t := utf8d[b] | |||||
if state != utf8Accept { | |||||
x = rune(b&0x3f) | (x << 6) | |||||
} else { | |||||
x = rune((0xff >> t) & b) | |||||
} | |||||
state = int(utf8d[256+state*16+int(t)]) | |||||
return state, x | |||||
} |
@ -1,20 +0,0 @@ | |||||
# Chat Example | |||||
This application shows how to use use the | |||||
[websocket](https://github.com/gorilla/websocket) package and | |||||
[jQuery](http://jquery.com) to implement a simple web chat application. | |||||
## Running the example | |||||
The example requires a working Go development environment. The [Getting | |||||
Started](http://golang.org/doc/install) page describes how to install the | |||||
development environment. | |||||
Once you have Go up and running, you can download, build and run the example | |||||
using the following commands. | |||||
$ go get github.com/gorilla/websocket | |||||
$ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/chat` | |||||
$ go run *.go | |||||
To use the chat example, open http://localhost:8080/ in your browser. |
@ -1,105 +0,0 @@ | |||||
// Copyright 2013 The Gorilla WebSocket 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 main | |||||
import ( | |||||
"github.com/gorilla/websocket" | |||||
"log" | |||||
"net/http" | |||||
"time" | |||||
) | |||||
const ( | |||||
// Time allowed to write a message to the peer. | |||||
writeWait = 10 * time.Second | |||||
// Time allowed to read the next pong message from the peer. | |||||
pongWait = 60 * time.Second | |||||
// Send pings to peer with this period. Must be less than pongWait. | |||||
pingPeriod = (pongWait * 9) / 10 | |||||
// Maximum message size allowed from peer. | |||||
maxMessageSize = 512 | |||||
) | |||||
var upgrader = websocket.Upgrader{ | |||||
ReadBufferSize: 1024, | |||||
WriteBufferSize: 1024, | |||||
} | |||||
// connection is an middleman between the websocket connection and the hub. | |||||
type connection struct { | |||||
// The websocket connection. | |||||
ws *websocket.Conn | |||||
// Buffered channel of outbound messages. | |||||
send chan []byte | |||||
} | |||||
// readPump pumps messages from the websocket connection to the hub. | |||||
func (c *connection) readPump() { | |||||
defer func() { | |||||
h.unregister <- c | |||||
c.ws.Close() | |||||
}() | |||||
c.ws.SetReadLimit(maxMessageSize) | |||||
c.ws.SetReadDeadline(time.Now().Add(pongWait)) | |||||
c.ws.SetPongHandler(func(string) error { c.ws.SetReadDeadline(time.Now().Add(pongWait)); return nil }) | |||||
for { | |||||
_, message, err := c.ws.ReadMessage() | |||||
if err != nil { | |||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) { | |||||
log.Printf("error: %v", err) | |||||
} | |||||
break | |||||
} | |||||
h.broadcast <- message | |||||
} | |||||
} | |||||
// write writes a message with the given message type and payload. | |||||
func (c *connection) write(mt int, payload []byte) error { | |||||
c.ws.SetWriteDeadline(time.Now().Add(writeWait)) | |||||
return c.ws.WriteMessage(mt, payload) | |||||
} | |||||
// writePump pumps messages from the hub to the websocket connection. | |||||
func (c *connection) writePump() { | |||||
ticker := time.NewTicker(pingPeriod) | |||||
defer func() { | |||||
ticker.Stop() | |||||
c.ws.Close() | |||||
}() | |||||
for { | |||||
select { | |||||
case message, ok := <-c.send: | |||||
if !ok { | |||||
c.write(websocket.CloseMessage, []byte{}) | |||||
return | |||||
} | |||||
if err := c.write(websocket.TextMessage, message); err != nil { | |||||
return | |||||
} | |||||
case <-ticker.C: | |||||
if err := c.write(websocket.PingMessage, []byte{}); err != nil { | |||||
return | |||||
} | |||||
} | |||||
} | |||||
} | |||||
// serveWs handles websocket requests from the peer. | |||||
func serveWs(w http.ResponseWriter, r *http.Request) { | |||||
ws, err := upgrader.Upgrade(w, r, nil) | |||||
if err != nil { | |||||
log.Println(err) | |||||
return | |||||
} | |||||
c := &connection{send: make(chan []byte, 256), ws: ws} | |||||
h.register <- c | |||||
go c.writePump() | |||||
c.readPump() | |||||
} |
@ -1,92 +0,0 @@ | |||||
<!DOCTYPE html> | |||||
<html lang="en"> | |||||
<head> | |||||
<title>Chat Example</title> | |||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script> | |||||
<script type="text/javascript"> | |||||
$(function() { | |||||
var conn; | |||||
var msg = $("#msg"); | |||||
var log = $("#log"); | |||||
function appendLog(msg) { | |||||
var d = log[0] | |||||
var doScroll = d.scrollTop == d.scrollHeight - d.clientHeight; | |||||
msg.appendTo(log) | |||||
if (doScroll) { | |||||
d.scrollTop = d.scrollHeight - d.clientHeight; | |||||
} | |||||
} | |||||
$("#form").submit(function() { | |||||
if (!conn) { | |||||
return false; | |||||
} | |||||
if (!msg.val()) { | |||||
return false; | |||||
} | |||||
conn.send(msg.val()); | |||||
msg.val(""); | |||||
return false | |||||
}); | |||||
if (window["WebSocket"]) { | |||||
conn = new WebSocket("ws://{{$}}/ws"); | |||||
conn.onclose = function(evt) { | |||||
appendLog($("<div><b>Connection closed.</b></div>")) | |||||
} | |||||
conn.onmessage = function(evt) { | |||||
appendLog($("<div/>").text(evt.data)) | |||||
} | |||||
} else { | |||||
appendLog($("<div><b>Your browser does not support WebSockets.</b></div>")) | |||||
} | |||||
}); | |||||
</script> | |||||
<style type="text/css"> | |||||
html { | |||||
overflow: hidden; | |||||
} | |||||
body { | |||||
overflow: hidden; | |||||
padding: 0; | |||||
margin: 0; | |||||
width: 100%; | |||||
height: 100%; | |||||
background: gray; | |||||
} | |||||
#log { | |||||
background: white; | |||||
margin: 0; | |||||
padding: 0.5em 0.5em 0.5em 0.5em; | |||||
position: absolute; | |||||
top: 0.5em; | |||||
left: 0.5em; | |||||
right: 0.5em; | |||||
bottom: 3em; | |||||
overflow: auto; | |||||
} | |||||
#form { | |||||
padding: 0 0.5em 0 0.5em; | |||||
margin: 0; | |||||
position: absolute; | |||||
bottom: 1em; | |||||
left: 0px; | |||||
width: 100%; | |||||
overflow: hidden; | |||||
} | |||||
</style> | |||||
</head> | |||||
<body> | |||||
<div id="log"></div> | |||||
<form id="form"> | |||||
<input type="submit" value="Send" /> | |||||
<input type="text" id="msg" size="64"/> | |||||
</form> | |||||
</body> | |||||
</html> |
@ -1,51 +0,0 @@ | |||||
// Copyright 2013 The Gorilla WebSocket 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 main | |||||
// hub maintains the set of active connections and broadcasts messages to the | |||||
// connections. | |||||
type hub struct { | |||||
// Registered connections. | |||||
connections map[*connection]bool | |||||
// Inbound messages from the connections. | |||||
broadcast chan []byte | |||||
// Register requests from the connections. | |||||
register chan *connection | |||||
// Unregister requests from connections. | |||||
unregister chan *connection | |||||
} | |||||
var h = hub{ | |||||
broadcast: make(chan []byte), | |||||
register: make(chan *connection), | |||||
unregister: make(chan *connection), | |||||
connections: make(map[*connection]bool), | |||||
} | |||||
func (h *hub) run() { | |||||
for { | |||||
select { | |||||
case c := <-h.register: | |||||
h.connections[c] = true | |||||
case c := <-h.unregister: | |||||
if _, ok := h.connections[c]; ok { | |||||
delete(h.connections, c) | |||||
close(c.send) | |||||
} | |||||
case m := <-h.broadcast: | |||||
for c := range h.connections { | |||||
select { | |||||
case c.send <- m: | |||||
default: | |||||
close(c.send) | |||||
delete(h.connections, c) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@ -1,39 +0,0 @@ | |||||
// Copyright 2013 The Gorilla WebSocket 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 main | |||||
import ( | |||||
"flag" | |||||
"log" | |||||
"net/http" | |||||
"text/template" | |||||
) | |||||
var addr = flag.String("addr", ":8080", "http service address") | |||||
var homeTempl = template.Must(template.ParseFiles("home.html")) | |||||
func serveHome(w http.ResponseWriter, r *http.Request) { | |||||
if r.URL.Path != "/" { | |||||
http.Error(w, "Not found", 404) | |||||
return | |||||
} | |||||
if r.Method != "GET" { | |||||
http.Error(w, "Method not allowed", 405) | |||||
return | |||||
} | |||||
w.Header().Set("Content-Type", "text/html; charset=utf-8") | |||||
homeTempl.Execute(w, r.Host) | |||||
} | |||||
func main() { | |||||
flag.Parse() | |||||
go h.run() | |||||
http.HandleFunc("/", serveHome) | |||||
http.HandleFunc("/ws", serveWs) | |||||
err := http.ListenAndServe(*addr, nil) | |||||
if err != nil { | |||||
log.Fatal("ListenAndServe: ", err) | |||||
} | |||||
} |
@ -1,19 +0,0 @@ | |||||
# Command example | |||||
This example connects a websocket connection to stdin and stdout of a command. | |||||
Received messages are written to stdin followed by a `\n`. Each line read from | |||||
from standard out is sent as a message to the client. | |||||
$ go get github.com/gorilla/websocket | |||||
$ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/command` | |||||
$ go run main.go <command and arguments to run> | |||||
# Open http://localhost:8080/ . | |||||
Try the following commands. | |||||
# Echo sent messages to the output area. | |||||
$ go run main.go cat | |||||
# Run a shell.Try sending "ls" and "cat main.go". | |||||
$ go run main.go sh | |||||
@ -1,96 +0,0 @@ | |||||
<!DOCTYPE html> | |||||
<html lang="en"> | |||||
<head> | |||||
<title>Command Example</title> | |||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script> | |||||
<script type="text/javascript"> | |||||
$(function() { | |||||
var conn; | |||||
var msg = $("#msg"); | |||||
var log = $("#log"); | |||||
function appendLog(msg) { | |||||
var d = log[0] | |||||
var doScroll = d.scrollTop == d.scrollHeight - d.clientHeight; | |||||
msg.appendTo(log) | |||||
if (doScroll) { | |||||
d.scrollTop = d.scrollHeight - d.clientHeight; | |||||
} | |||||
} | |||||
$("#form").submit(function() { | |||||
if (!conn) { | |||||
return false; | |||||
} | |||||
if (!msg.val()) { | |||||
return false; | |||||
} | |||||
conn.send(msg.val()); | |||||
msg.val(""); | |||||
return false | |||||
}); | |||||
if (window["WebSocket"]) { | |||||
conn = new WebSocket("ws://{{$}}/ws"); | |||||
conn.onclose = function(evt) { | |||||
appendLog($("<div><b>Connection closed.</b></div>")) | |||||
} | |||||
conn.onmessage = function(evt) { | |||||
appendLog($("<pre/>").text(evt.data)) | |||||
} | |||||
} else { | |||||
appendLog($("<div><b>Your browser does not support WebSockets.</b></div>")) | |||||
} | |||||
}); | |||||
</script> | |||||
<style type="text/css"> | |||||
html { | |||||
overflow: hidden; | |||||
} | |||||
body { | |||||
overflow: hidden; | |||||
padding: 0; | |||||
margin: 0; | |||||
width: 100%; | |||||
height: 100%; | |||||
background: gray; | |||||
} | |||||
#log { | |||||
background: white; | |||||
margin: 0; | |||||
padding: 0.5em 0.5em 0.5em 0.5em; | |||||
position: absolute; | |||||
top: 0.5em; | |||||
left: 0.5em; | |||||
right: 0.5em; | |||||
bottom: 3em; | |||||
overflow: auto; | |||||
} | |||||
#log pre { | |||||
margin: 0; | |||||
} | |||||
#form { | |||||
padding: 0 0.5em 0 0.5em; | |||||
margin: 0; | |||||
position: absolute; | |||||
bottom: 1em; | |||||
left: 0px; | |||||
width: 100%; | |||||
overflow: hidden; | |||||
} | |||||
</style> | |||||
</head> | |||||
<body> | |||||
<div id="log"></div> | |||||
<form id="form"> | |||||
<input type="submit" value="Send" /> | |||||
<input type="text" id="msg" size="64"/> | |||||
</form> | |||||
</body> | |||||
</html> |
@ -1,188 +0,0 @@ | |||||
// Copyright 2015 The Gorilla WebSocket 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 main | |||||
import ( | |||||
"bufio" | |||||
"flag" | |||||
"io" | |||||
"log" | |||||
"net/http" | |||||
"os" | |||||
"os/exec" | |||||
"text/template" | |||||
"time" | |||||
"github.com/gorilla/websocket" | |||||
) | |||||
var ( | |||||
addr = flag.String("addr", "127.0.0.1:8080", "http service address") | |||||
cmdPath string | |||||
homeTempl = template.Must(template.ParseFiles("home.html")) | |||||
) | |||||
const ( | |||||
// Time allowed to write a message to the peer. | |||||
writeWait = 10 * time.Second | |||||
// Maximum message size allowed from peer. | |||||
maxMessageSize = 8192 | |||||
// Time allowed to read the next pong message from the peer. | |||||
pongWait = 60 * time.Second | |||||
// Send pings to peer with this period. Must be less than pongWait. | |||||
pingPeriod = (pongWait * 9) / 10 | |||||
) | |||||
func pumpStdin(ws *websocket.Conn, w io.Writer) { | |||||
defer ws.Close() | |||||
ws.SetReadLimit(maxMessageSize) | |||||
ws.SetReadDeadline(time.Now().Add(pongWait)) | |||||
ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil }) | |||||
for { | |||||
_, message, err := ws.ReadMessage() | |||||
if err != nil { | |||||
break | |||||
} | |||||
message = append(message, '\n') | |||||
if _, err := w.Write(message); err != nil { | |||||
break | |||||
} | |||||
} | |||||
} | |||||
func pumpStdout(ws *websocket.Conn, r io.Reader, done chan struct{}) { | |||||
defer func() { | |||||
ws.Close() | |||||
close(done) | |||||
}() | |||||
s := bufio.NewScanner(r) | |||||
for s.Scan() { | |||||
ws.SetWriteDeadline(time.Now().Add(writeWait)) | |||||
if err := ws.WriteMessage(websocket.TextMessage, s.Bytes()); err != nil { | |||||
break | |||||
} | |||||
} | |||||
if s.Err() != nil { | |||||
log.Println("scan:", s.Err()) | |||||
} | |||||
} | |||||
func ping(ws *websocket.Conn, done chan struct{}) { | |||||
ticker := time.NewTicker(pingPeriod) | |||||
defer ticker.Stop() | |||||
for { | |||||
select { | |||||
case <-ticker.C: | |||||
if err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait)); err != nil { | |||||
log.Println("ping:", err) | |||||
} | |||||
case <-done: | |||||
return | |||||
} | |||||
} | |||||
} | |||||
func internalError(ws *websocket.Conn, msg string, err error) { | |||||
log.Println(msg, err) | |||||
ws.WriteMessage(websocket.TextMessage, []byte("Internal server error.")) | |||||
} | |||||
var upgrader = websocket.Upgrader{} | |||||
func serveWs(w http.ResponseWriter, r *http.Request) { | |||||
ws, err := upgrader.Upgrade(w, r, nil) | |||||
if err != nil { | |||||
log.Println("upgrade:", err) | |||||
return | |||||
} | |||||
defer ws.Close() | |||||
outr, outw, err := os.Pipe() | |||||
if err != nil { | |||||
internalError(ws, "stdout:", err) | |||||
return | |||||
} | |||||
defer outr.Close() | |||||
defer outw.Close() | |||||
inr, inw, err := os.Pipe() | |||||
if err != nil { | |||||
internalError(ws, "stdin:", err) | |||||
return | |||||
} | |||||
defer inr.Close() | |||||
defer inw.Close() | |||||
proc, err := os.StartProcess(cmdPath, flag.Args(), &os.ProcAttr{ | |||||
Files: []*os.File{inr, outw, outw}, | |||||
}) | |||||
if err != nil { | |||||
internalError(ws, "start:", err) | |||||
return | |||||
} | |||||
inr.Close() | |||||
outw.Close() | |||||
stdoutDone := make(chan struct{}) | |||||
go pumpStdout(ws, outr, stdoutDone) | |||||
go ping(ws, stdoutDone) | |||||
pumpStdin(ws, inw) | |||||
// Some commands will exit when stdin is closed. | |||||
inw.Close() | |||||
// Other commands need a bonk on the head. | |||||
if err := proc.Signal(os.Interrupt); err != nil { | |||||
log.Println("inter:", err) | |||||
} | |||||
select { | |||||
case <-stdoutDone: | |||||
case <-time.After(time.Second): | |||||
// A bigger bonk on the head. | |||||
if err := proc.Signal(os.Kill); err != nil { | |||||
log.Println("term:", err) | |||||
} | |||||
<-stdoutDone | |||||
} | |||||
if _, err := proc.Wait(); err != nil { | |||||
log.Println("wait:", err) | |||||
} | |||||
} | |||||
func serveHome(w http.ResponseWriter, r *http.Request) { | |||||
if r.URL.Path != "/" { | |||||
http.Error(w, "Not found", 404) | |||||
return | |||||
} | |||||
if r.Method != "GET" { | |||||
http.Error(w, "Method not allowed", 405) | |||||
return | |||||
} | |||||
w.Header().Set("Content-Type", "text/html; charset=utf-8") | |||||
homeTempl.Execute(w, r.Host) | |||||
} | |||||
func main() { | |||||
flag.Parse() | |||||
if len(flag.Args()) < 1 { | |||||
log.Fatal("must specify at least one argument") | |||||
} | |||||
var err error | |||||
cmdPath, err = exec.LookPath(flag.Args()[0]) | |||||
if err != nil { | |||||
log.Fatal(err) | |||||
} | |||||
http.HandleFunc("/", serveHome) | |||||
http.HandleFunc("/ws", serveWs) | |||||
log.Fatal(http.ListenAndServe(*addr, nil)) | |||||
} |
@ -1,17 +0,0 @@ | |||||
# Client and server example | |||||
This example shows a simple client and server. | |||||
The server echoes messages sent to it. The client sends a message every second | |||||
and prints all messages received. | |||||
To run the example, start the server: | |||||
$ go run server.go | |||||
Next, start the client: | |||||
$ go run client.go | |||||
The server includes a simple web client. To use the client, open | |||||
http://127.0.0.1:8080 in the browser and follow the instructions on the page. |
@ -1,81 +0,0 @@ | |||||
// Copyright 2015 The Gorilla WebSocket 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 ignore | |||||
package main | |||||
import ( | |||||
"flag" | |||||
"log" | |||||
"net/url" | |||||
"os" | |||||
"os/signal" | |||||
"time" | |||||
"github.com/gorilla/websocket" | |||||
) | |||||
var addr = flag.String("addr", "localhost:8080", "http service address") | |||||
func main() { | |||||
flag.Parse() | |||||
log.SetFlags(0) | |||||
interrupt := make(chan os.Signal, 1) | |||||
signal.Notify(interrupt, os.Interrupt) | |||||
u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo"} | |||||
log.Printf("connecting to %s", u.String()) | |||||
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) | |||||
if err != nil { | |||||
log.Fatal("dial:", err) | |||||
} | |||||
defer c.Close() | |||||
done := make(chan struct{}) | |||||
go func() { | |||||
defer c.Close() | |||||
defer close(done) | |||||
for { | |||||
_, message, err := c.ReadMessage() | |||||
if err != nil { | |||||
log.Println("read:", err) | |||||
return | |||||
} | |||||
log.Printf("recv: %s", message) | |||||
} | |||||
}() | |||||
ticker := time.NewTicker(time.Second) | |||||
defer ticker.Stop() | |||||
for { | |||||
select { | |||||
case t := <-ticker.C: | |||||
err := c.WriteMessage(websocket.TextMessage, []byte(t.String())) | |||||
if err != nil { | |||||
log.Println("write:", err) | |||||
return | |||||
} | |||||
case <-interrupt: | |||||
log.Println("interrupt") | |||||
// To cleanly close a connection, a client should send a close | |||||
// frame and wait for the server to close the connection. | |||||
err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) | |||||
if err != nil { | |||||
log.Println("write close:", err) | |||||
return | |||||
} | |||||
select { | |||||
case <-done: | |||||
case <-time.After(time.Second): | |||||
} | |||||
c.Close() | |||||
return | |||||
} | |||||
} | |||||
} |
@ -1,132 +0,0 @@ | |||||
// Copyright 2015 The Gorilla WebSocket 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 ignore | |||||
package main | |||||
import ( | |||||
"flag" | |||||
"html/template" | |||||
"log" | |||||
"net/http" | |||||
"github.com/gorilla/websocket" | |||||
) | |||||
var addr = flag.String("addr", "localhost:8080", "http service address") | |||||
var upgrader = websocket.Upgrader{} // use default options | |||||
func echo(w http.ResponseWriter, r *http.Request) { | |||||
c, err := upgrader.Upgrade(w, r, nil) | |||||
if err != nil { | |||||
log.Print("upgrade:", err) | |||||
return | |||||
} | |||||
defer c.Close() | |||||
for { | |||||
mt, message, err := c.ReadMessage() | |||||
if err != nil { | |||||
log.Println("read:", err) | |||||
break | |||||
} | |||||
log.Printf("recv: %s", message) | |||||
err = c.WriteMessage(mt, message) | |||||
if err != nil { | |||||
log.Println("write:", err) | |||||
break | |||||
} | |||||
} | |||||
} | |||||
func home(w http.ResponseWriter, r *http.Request) { | |||||
homeTemplate.Execute(w, "ws://"+r.Host+"/echo") | |||||
} | |||||
func main() { | |||||
flag.Parse() | |||||
log.SetFlags(0) | |||||
http.HandleFunc("/echo", echo) | |||||
http.HandleFunc("/", home) | |||||
log.Fatal(http.ListenAndServe(*addr, nil)) | |||||
} | |||||
var homeTemplate = template.Must(template.New("").Parse(` | |||||
<!DOCTYPE html> | |||||
<head> | |||||
<meta charset="utf-8"> | |||||
<script> | |||||
window.addEventListener("load", function(evt) { | |||||
var output = document.getElementById("output"); | |||||
var input = document.getElementById("input"); | |||||
var ws; | |||||
var print = function(message) { | |||||
var d = document.createElement("div"); | |||||
d.innerHTML = message; | |||||
output.appendChild(d); | |||||
}; | |||||
document.getElementById("open").onclick = function(evt) { | |||||
if (ws) { | |||||
return false; | |||||
} | |||||
ws = new WebSocket("{{.}}"); | |||||
ws.onopen = function(evt) { | |||||
print("OPEN"); | |||||
} | |||||
ws.onclose = function(evt) { | |||||
print("CLOSE"); | |||||
ws = null; | |||||
} | |||||
ws.onmessage = function(evt) { | |||||
print("RESPONSE: " + evt.data); | |||||
} | |||||
ws.onerror = function(evt) { | |||||
print("ERROR: " + evt.data); | |||||
} | |||||
return false; | |||||
}; | |||||
document.getElementById("send").onclick = function(evt) { | |||||
if (!ws) { | |||||
return false; | |||||
} | |||||
print("SEND: " + input.value); | |||||
ws.send(input.value); | |||||
return false; | |||||
}; | |||||
document.getElementById("close").onclick = function(evt) { | |||||
if (!ws) { | |||||
return false; | |||||
} | |||||
ws.close(); | |||||
return false; | |||||
}; | |||||
}); | |||||
</script> | |||||
</head> | |||||
<body> | |||||
<table> | |||||
<tr><td valign="top" width="50%"> | |||||
<p>Click "Open" to create a connection to the server, | |||||
"Send" to send a message to the server and "Close" to close the connection. | |||||
You can change the message and send multiple times. | |||||
<p> | |||||
<form> | |||||
<button id="open">Open</button> | |||||
<button id="close">Close</button> | |||||
<p><input id="input" type="text" value="Hello world!"> | |||||
<button id="send">Send</button> | |||||
</form> | |||||
</td><td valign="top" width="50%"> | |||||
<div id="output"></div> | |||||
</td></tr></table> | |||||
</body> | |||||
</html> | |||||
`)) |
@ -1,9 +0,0 @@ | |||||
# File Watch example. | |||||
This example sends a file to the browser client for display whenever the file is modified. | |||||
$ go get github.com/gorilla/websocket | |||||
$ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/filewatch` | |||||
$ go run main.go <name of file to watch> | |||||
# Open http://localhost:8080/ . | |||||
# Modify the file to see it update in the browser. |
@ -1,193 +0,0 @@ | |||||
// Copyright 2013 The Gorilla WebSocket 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 main | |||||
import ( | |||||
"flag" | |||||
"io/ioutil" | |||||
"log" | |||||
"net/http" | |||||
"os" | |||||
"strconv" | |||||
"text/template" | |||||
"time" | |||||
"github.com/gorilla/websocket" | |||||
) | |||||
const ( | |||||
// Time allowed to write the file to the client. | |||||
writeWait = 10 * time.Second | |||||
// Time allowed to read the next pong message from the client. | |||||
pongWait = 60 * time.Second | |||||
// Send pings to client with this period. Must be less than pongWait. | |||||
pingPeriod = (pongWait * 9) / 10 | |||||
// Poll file for changes with this period. | |||||
filePeriod = 10 * time.Second | |||||
) | |||||
var ( | |||||
addr = flag.String("addr", ":8080", "http service address") | |||||
homeTempl = template.Must(template.New("").Parse(homeHTML)) | |||||
filename string | |||||
upgrader = websocket.Upgrader{ | |||||
ReadBufferSize: 1024, | |||||
WriteBufferSize: 1024, | |||||
} | |||||
) | |||||
func readFileIfModified(lastMod time.Time) ([]byte, time.Time, error) { | |||||
fi, err := os.Stat(filename) | |||||
if err != nil { | |||||
return nil, lastMod, err | |||||
} | |||||
if !fi.ModTime().After(lastMod) { | |||||
return nil, lastMod, nil | |||||
} | |||||
p, err := ioutil.ReadFile(filename) | |||||
if err != nil { | |||||
return nil, fi.ModTime(), err | |||||
} | |||||
return p, fi.ModTime(), nil | |||||
} | |||||
func reader(ws *websocket.Conn) { | |||||
defer ws.Close() | |||||
ws.SetReadLimit(512) | |||||
ws.SetReadDeadline(time.Now().Add(pongWait)) | |||||
ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil }) | |||||
for { | |||||
_, _, err := ws.ReadMessage() | |||||
if err != nil { | |||||
break | |||||
} | |||||
} | |||||
} | |||||
func writer(ws *websocket.Conn, lastMod time.Time) { | |||||
lastError := "" | |||||
pingTicker := time.NewTicker(pingPeriod) | |||||
fileTicker := time.NewTicker(filePeriod) | |||||
defer func() { | |||||
pingTicker.Stop() | |||||
fileTicker.Stop() | |||||
ws.Close() | |||||
}() | |||||
for { | |||||
select { | |||||
case <-fileTicker.C: | |||||
var p []byte | |||||
var err error | |||||
p, lastMod, err = readFileIfModified(lastMod) | |||||
if err != nil { | |||||
if s := err.Error(); s != lastError { | |||||
lastError = s | |||||
p = []byte(lastError) | |||||
} | |||||
} else { | |||||
lastError = "" | |||||
} | |||||
if p != nil { | |||||
ws.SetWriteDeadline(time.Now().Add(writeWait)) | |||||
if err := ws.WriteMessage(websocket.TextMessage, p); err != nil { | |||||
return | |||||
} | |||||
} | |||||
case <-pingTicker.C: | |||||
ws.SetWriteDeadline(time.Now().Add(writeWait)) | |||||
if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil { | |||||
return | |||||
} | |||||
} | |||||
} | |||||
} | |||||
func serveWs(w http.ResponseWriter, r *http.Request) { | |||||
ws, err := upgrader.Upgrade(w, r, nil) | |||||
if err != nil { | |||||
if _, ok := err.(websocket.HandshakeError); !ok { | |||||
log.Println(err) | |||||
} | |||||
return | |||||
} | |||||
var lastMod time.Time | |||||
if n, err := strconv.ParseInt(r.FormValue("lastMod"), 16, 64); err != nil { | |||||
lastMod = time.Unix(0, n) | |||||
} | |||||
go writer(ws, lastMod) | |||||
reader(ws) | |||||
} | |||||
func serveHome(w http.ResponseWriter, r *http.Request) { | |||||
if r.URL.Path != "/" { | |||||
http.Error(w, "Not found", 404) | |||||
return | |||||
} | |||||
if r.Method != "GET" { | |||||
http.Error(w, "Method not allowed", 405) | |||||
return | |||||
} | |||||
w.Header().Set("Content-Type", "text/html; charset=utf-8") | |||||
p, lastMod, err := readFileIfModified(time.Time{}) | |||||
if err != nil { | |||||
p = []byte(err.Error()) | |||||
lastMod = time.Unix(0, 0) | |||||
} | |||||
var v = struct { | |||||
Host string | |||||
Data string | |||||
LastMod string | |||||
}{ | |||||
r.Host, | |||||
string(p), | |||||
strconv.FormatInt(lastMod.UnixNano(), 16), | |||||
} | |||||
homeTempl.Execute(w, &v) | |||||
} | |||||
func main() { | |||||
flag.Parse() | |||||
if flag.NArg() != 1 { | |||||
log.Fatal("filename not specified") | |||||
} | |||||
filename = flag.Args()[0] | |||||
http.HandleFunc("/", serveHome) | |||||
http.HandleFunc("/ws", serveWs) | |||||
if err := http.ListenAndServe(*addr, nil); err != nil { | |||||
log.Fatal(err) | |||||
} | |||||
} | |||||
const homeHTML = `<!DOCTYPE html> | |||||
<html lang="en"> | |||||
<head> | |||||
<title>WebSocket Example</title> | |||||
</head> | |||||
<body> | |||||
<pre id="fileData">{{.Data}}</pre> | |||||
<script type="text/javascript"> | |||||
(function() { | |||||
var data = document.getElementById("fileData"); | |||||
var conn = new WebSocket("ws://{{.Host}}/ws?lastMod={{.LastMod}}"); | |||||
conn.onclose = function(evt) { | |||||
data.textContent = 'Connection closed'; | |||||
} | |||||
conn.onmessage = function(evt) { | |||||
console.log('file updated'); | |||||
data.textContent = evt.data; | |||||
} | |||||
})(); | |||||
</script> | |||||
</body> | |||||
</html> | |||||
` |
@ -1,55 +0,0 @@ | |||||
// Copyright 2013 The Gorilla WebSocket 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 websocket | |||||
import ( | |||||
"encoding/json" | |||||
"io" | |||||
) | |||||
// WriteJSON is deprecated, use c.WriteJSON instead. | |||||
func WriteJSON(c *Conn, v interface{}) error { | |||||
return c.WriteJSON(v) | |||||
} | |||||
// WriteJSON writes the JSON encoding of v to the connection. | |||||
// | |||||
// See the documentation for encoding/json Marshal for details about the | |||||
// conversion of Go values to JSON. | |||||
func (c *Conn) WriteJSON(v interface{}) error { | |||||
w, err := c.NextWriter(TextMessage) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
err1 := json.NewEncoder(w).Encode(v) | |||||
err2 := w.Close() | |||||
if err1 != nil { | |||||
return err1 | |||||
} | |||||
return err2 | |||||
} | |||||
// ReadJSON is deprecated, use c.ReadJSON instead. | |||||
func ReadJSON(c *Conn, v interface{}) error { | |||||
return c.ReadJSON(v) | |||||
} | |||||
// ReadJSON reads the next JSON-encoded message from the connection and stores | |||||
// it in the value pointed to by v. | |||||
// | |||||
// See the documentation for the encoding/json Unmarshal function for details | |||||
// about the conversion of JSON to a Go value. | |||||
func (c *Conn) ReadJSON(v interface{}) error { | |||||
_, r, err := c.NextReader() | |||||
if err != nil { | |||||
return err | |||||
} | |||||
err = json.NewDecoder(r).Decode(v) | |||||
if err == io.EOF { | |||||
// One value is expected in the message. | |||||
err = io.ErrUnexpectedEOF | |||||
} | |||||
return err | |||||
} |
@ -1,250 +0,0 @@ | |||||
// Copyright 2013 The Gorilla WebSocket 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 websocket | |||||
import ( | |||||
"bufio" | |||||
"errors" | |||||
"net" | |||||
"net/http" | |||||
"net/url" | |||||
"strings" | |||||
"time" | |||||
) | |||||
// HandshakeError describes an error with the handshake from the peer. | |||||
type HandshakeError struct { | |||||
message string | |||||
} | |||||
func (e HandshakeError) Error() string { return e.message } | |||||
// Upgrader specifies parameters for upgrading an HTTP connection to a | |||||
// WebSocket connection. | |||||
type Upgrader struct { | |||||
// HandshakeTimeout specifies the duration for the handshake to complete. | |||||
HandshakeTimeout time.Duration | |||||
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer | |||||
// size is zero, then a default value of 4096 is used. The I/O buffer sizes | |||||
// do not limit the size of the messages that can be sent or received. | |||||
ReadBufferSize, WriteBufferSize int | |||||
// Subprotocols specifies the server's supported protocols in order of | |||||
// preference. If this field is set, then the Upgrade method negotiates a | |||||
// subprotocol by selecting the first match in this list with a protocol | |||||
// requested by the client. | |||||
Subprotocols []string | |||||
// Error specifies the function for generating HTTP error responses. If Error | |||||
// is nil, then http.Error is used to generate the HTTP response. | |||||
Error func(w http.ResponseWriter, r *http.Request, status int, reason error) | |||||
// CheckOrigin returns true if the request Origin header is acceptable. If | |||||
// CheckOrigin is nil, the host in the Origin header must not be set or | |||||
// must match the host of the request. | |||||
CheckOrigin func(r *http.Request) bool | |||||
} | |||||
func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) { | |||||
err := HandshakeError{reason} | |||||
if u.Error != nil { | |||||
u.Error(w, r, status, err) | |||||
} else { | |||||
http.Error(w, http.StatusText(status), status) | |||||
} | |||||
return nil, err | |||||
} | |||||
// checkSameOrigin returns true if the origin is not set or is equal to the request host. | |||||
func checkSameOrigin(r *http.Request) bool { | |||||
origin := r.Header["Origin"] | |||||
if len(origin) == 0 { | |||||
return true | |||||
} | |||||
u, err := url.Parse(origin[0]) | |||||
if err != nil { | |||||
return false | |||||
} | |||||
return u.Host == r.Host | |||||
} | |||||
func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string { | |||||
if u.Subprotocols != nil { | |||||
clientProtocols := Subprotocols(r) | |||||
for _, serverProtocol := range u.Subprotocols { | |||||
for _, clientProtocol := range clientProtocols { | |||||
if clientProtocol == serverProtocol { | |||||
return clientProtocol | |||||
} | |||||
} | |||||
} | |||||
} else if responseHeader != nil { | |||||
return responseHeader.Get("Sec-Websocket-Protocol") | |||||
} | |||||
return "" | |||||
} | |||||
// Upgrade upgrades the HTTP server connection to the WebSocket protocol. | |||||
// | |||||
// The responseHeader is included in the response to the client's upgrade | |||||
// request. Use the responseHeader to specify cookies (Set-Cookie) and the | |||||
// application negotiated subprotocol (Sec-Websocket-Protocol). | |||||
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) { | |||||
if r.Method != "GET" { | |||||
return u.returnError(w, r, http.StatusMethodNotAllowed, "websocket: method not GET") | |||||
} | |||||
if values := r.Header["Sec-Websocket-Version"]; len(values) == 0 || values[0] != "13" { | |||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: version != 13") | |||||
} | |||||
if !tokenListContainsValue(r.Header, "Connection", "upgrade") { | |||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: could not find connection header with token 'upgrade'") | |||||
} | |||||
if !tokenListContainsValue(r.Header, "Upgrade", "websocket") { | |||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: could not find upgrade header with token 'websocket'") | |||||
} | |||||
checkOrigin := u.CheckOrigin | |||||
if checkOrigin == nil { | |||||
checkOrigin = checkSameOrigin | |||||
} | |||||
if !checkOrigin(r) { | |||||
return u.returnError(w, r, http.StatusForbidden, "websocket: origin not allowed") | |||||
} | |||||
challengeKey := r.Header.Get("Sec-Websocket-Key") | |||||
if challengeKey == "" { | |||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: key missing or blank") | |||||
} | |||||
subprotocol := u.selectSubprotocol(r, responseHeader) | |||||
var ( | |||||
netConn net.Conn | |||||
br *bufio.Reader | |||||
err error | |||||
) | |||||
h, ok := w.(http.Hijacker) | |||||
if !ok { | |||||
return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker") | |||||
} | |||||
var rw *bufio.ReadWriter | |||||
netConn, rw, err = h.Hijack() | |||||
if err != nil { | |||||
return u.returnError(w, r, http.StatusInternalServerError, err.Error()) | |||||
} | |||||
br = rw.Reader | |||||
if br.Buffered() > 0 { | |||||
netConn.Close() | |||||
return nil, errors.New("websocket: client sent data before handshake is complete") | |||||
} | |||||
c := newConn(netConn, true, u.ReadBufferSize, u.WriteBufferSize) | |||||
c.subprotocol = subprotocol | |||||
p := c.writeBuf[:0] | |||||
p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...) | |||||
p = append(p, computeAcceptKey(challengeKey)...) | |||||
p = append(p, "\r\n"...) | |||||
if c.subprotocol != "" { | |||||
p = append(p, "Sec-Websocket-Protocol: "...) | |||||
p = append(p, c.subprotocol...) | |||||
p = append(p, "\r\n"...) | |||||
} | |||||
for k, vs := range responseHeader { | |||||
if k == "Sec-Websocket-Protocol" { | |||||
continue | |||||
} | |||||
for _, v := range vs { | |||||
p = append(p, k...) | |||||
p = append(p, ": "...) | |||||
for i := 0; i < len(v); i++ { | |||||
b := v[i] | |||||
if b <= 31 { | |||||
// prevent response splitting. | |||||
b = ' ' | |||||
} | |||||
p = append(p, b) | |||||
} | |||||
p = append(p, "\r\n"...) | |||||
} | |||||
} | |||||
p = append(p, "\r\n"...) | |||||
// Clear deadlines set by HTTP server. | |||||
netConn.SetDeadline(time.Time{}) | |||||
if u.HandshakeTimeout > 0 { | |||||
netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout)) | |||||
} | |||||
if _, err = netConn.Write(p); err != nil { | |||||
netConn.Close() | |||||
return nil, err | |||||
} | |||||
if u.HandshakeTimeout > 0 { | |||||
netConn.SetWriteDeadline(time.Time{}) | |||||
} | |||||
return c, nil | |||||
} | |||||
// Upgrade upgrades the HTTP server connection to the WebSocket protocol. | |||||
// | |||||
// This function is deprecated, use websocket.Upgrader instead. | |||||
// | |||||
// The application is responsible for checking the request origin before | |||||
// calling Upgrade. An example implementation of the same origin policy is: | |||||
// | |||||
// if req.Header.Get("Origin") != "http://"+req.Host { | |||||
// http.Error(w, "Origin not allowed", 403) | |||||
// return | |||||
// } | |||||
// | |||||
// If the endpoint supports subprotocols, then the application is responsible | |||||
// for negotiating the protocol used on the connection. Use the Subprotocols() | |||||
// function to get the subprotocols requested by the client. Use the | |||||
// Sec-Websocket-Protocol response header to specify the subprotocol selected | |||||
// by the application. | |||||
// | |||||
// The responseHeader is included in the response to the client's upgrade | |||||
// request. Use the responseHeader to specify cookies (Set-Cookie) and the | |||||
// negotiated subprotocol (Sec-Websocket-Protocol). | |||||
// | |||||
// The connection buffers IO to the underlying network connection. The | |||||
// readBufSize and writeBufSize parameters specify the size of the buffers to | |||||
// use. Messages can be larger than the buffers. | |||||
// | |||||
// If the request is not a valid WebSocket handshake, then Upgrade returns an | |||||
// error of type HandshakeError. Applications should handle this error by | |||||
// replying to the client with an HTTP error response. | |||||
func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header, readBufSize, writeBufSize int) (*Conn, error) { | |||||
u := Upgrader{ReadBufferSize: readBufSize, WriteBufferSize: writeBufSize} | |||||
u.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) { | |||||
// don't return errors to maintain backwards compatibility | |||||
} | |||||
u.CheckOrigin = func(r *http.Request) bool { | |||||
// allow all connections by default | |||||
return true | |||||
} | |||||
return u.Upgrade(w, r, responseHeader) | |||||
} | |||||
// Subprotocols returns the subprotocols requested by the client in the | |||||
// Sec-Websocket-Protocol header. | |||||
func Subprotocols(r *http.Request) []string { | |||||
h := strings.TrimSpace(r.Header.Get("Sec-Websocket-Protocol")) | |||||
if h == "" { | |||||
return nil | |||||
} | |||||
protocols := strings.Split(h, ",") | |||||
for i := range protocols { | |||||
protocols[i] = strings.TrimSpace(protocols[i]) | |||||
} | |||||
return protocols | |||||
} |
@ -1,44 +0,0 @@ | |||||
// Copyright 2013 The Gorilla WebSocket 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 websocket | |||||
import ( | |||||
"crypto/rand" | |||||
"crypto/sha1" | |||||
"encoding/base64" | |||||
"io" | |||||
"net/http" | |||||
"strings" | |||||
) | |||||
// tokenListContainsValue returns true if the 1#token header with the given | |||||
// name contains token. | |||||
func tokenListContainsValue(header http.Header, name string, value string) bool { | |||||
for _, v := range header[name] { | |||||
for _, s := range strings.Split(v, ",") { | |||||
if strings.EqualFold(value, strings.TrimSpace(s)) { | |||||
return true | |||||
} | |||||
} | |||||
} | |||||
return false | |||||
} | |||||
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") | |||||
func computeAcceptKey(challengeKey string) string { | |||||
h := sha1.New() | |||||
h.Write([]byte(challengeKey)) | |||||
h.Write(keyGUID) | |||||
return base64.StdEncoding.EncodeToString(h.Sum(nil)) | |||||
} | |||||
func generateChallengeKey() (string, error) { | |||||
p := make([]byte, 16) | |||||
if _, err := io.ReadFull(rand.Reader, p); err != nil { | |||||
return "", err | |||||
} | |||||
return base64.StdEncoding.EncodeToString(p), nil | |||||
} |
@ -1,9 +0,0 @@ | |||||
language: go | |||||
go: | |||||
- 1.0 | |||||
- 1.1 | |||||
- 1.2 | |||||
- 1.3 | |||||
- release | |||||
- tip |
@ -1,11 +0,0 @@ | |||||
Contributors to log15: | |||||
- Aaron L | |||||
- Alan Shreve | |||||
- Chris Hines | |||||
- Ciaran Downey | |||||
- Dmitry Chestnykh | |||||
- Evan Shaw | |||||
- Péter Szilágyi | |||||
- Trevor Gattis | |||||
- Vincent Vanackere |
@ -1,13 +0,0 @@ | |||||
Copyright 2014 Alan Shreve | |||||
Licensed under the Apache License, Version 2.0 (the "License"); | |||||
you may not use this file except in compliance with the License. | |||||
You may obtain a copy of the License at | |||||
http://www.apache.org/licenses/LICENSE-2.0 | |||||
Unless required by applicable law or agreed to in writing, software | |||||
distributed under the License is distributed on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
See the License for the specific language governing permissions and | |||||
limitations under the License. |
@ -1,60 +0,0 @@ | |||||
![obligatory xkcd](http://imgs.xkcd.com/comics/standards.png) | |||||
# log15 [![godoc reference](https://godoc.org/gopkg.in/inconshreveable/log15.v2?status.png)](https://godoc.org/gopkg.in/inconshreveable/log15.v2) | |||||
Package log15 provides an opinionated, simple toolkit for best-practice logging in Go (golang) that is both human and machine readable. It is modeled after the Go standard library's [`io`](http://golang.org/pkg/io/) and [`net/http`](http://golang.org/pkg/net/http/) packages and is an alternative to the standard library's [`log`](http://golang.org/pkg/log/) package. | |||||
## Features | |||||
- A simple, easy-to-understand API | |||||
- Promotes structured logging by encouraging use of key/value pairs | |||||
- Child loggers which inherit and add their own private context | |||||
- Lazy evaluation of expensive operations | |||||
- Simple Handler interface allowing for construction of flexible, custom logging configurations with a tiny API. | |||||
- Color terminal support | |||||
- Built-in support for logging to files, streams, syslog, and the network | |||||
- Support for forking records to multiple handlers, buffering records for output, failing over from failed handler writes, + more | |||||
## Versioning | |||||
The API of the master branch of log15 should always be considered unstable. Using a stable version | |||||
of the log15 package is supported by gopkg.in. Include your dependency like so: | |||||
```go | |||||
import log "gopkg.in/inconshreveable/log15.v2" | |||||
``` | |||||
## Examples | |||||
```go | |||||
// all loggers can have key/value context | |||||
srvlog := log.New("module", "app/server") | |||||
// all log messages can have key/value context | |||||
srvlog.Warn("abnormal conn rate", "rate", curRate, "low", lowRate, "high", highRate) | |||||
// child loggers with inherited context | |||||
connlog := srvlog.New("raddr", c.RemoteAddr()) | |||||
connlog.Info("connection open") | |||||
// lazy evaluation | |||||
connlog.Debug("ping remote", "latency", log.Lazy(pingRemote)) | |||||
// flexible configuration | |||||
srvlog.SetHandler(log.MultiHandler( | |||||
log.StreamHandler(os.Stderr, log.LogfmtFormat()), | |||||
log.LvlFilterHandler( | |||||
log.LvlError, | |||||
log.Must.FileHandler("errors.json", log.JsonFormat()))) | |||||
``` | |||||
## FAQ | |||||
### The varargs style is brittle and error prone! Can I have type safety please? | |||||
Yes. Use `log.Ctx`: | |||||
```go | |||||
srvlog := log.New(log.Ctx{"module": "app/server"}) | |||||
srvlog.Warn("abnormal conn rate", log.Ctx{"rate": curRate, "low": lowRate, "high": highRate}) | |||||
``` | |||||
## License | |||||
Apache |
@ -1,19 +0,0 @@ | |||||
# log15's release strategy | |||||
log15 uses gopkg.in to manage versioning releases so that consumers who don't vendor dependencies can rely upon a stable API. | |||||
## Master | |||||
Master is considered to have no API stability guarantee, so merging new code that passes tests into master is always okay. | |||||
## Releasing a new API-compatible version | |||||
The process to release a new API-compatible version is described below. For the purposes of this example, we'll assume you're trying to release a new version of v2 | |||||
1. `git checkout v2` | |||||
1. `git merge master` | |||||
1. Audit the code for any imports of sub-packages. Modify any import references from `github.com/inconshrevealbe/log15/<pkg>` -> `gopkg.in/inconshreveable/log15.v2/<pkg>` | |||||
1. `git commit` | |||||
1. `git tag`, find the latest tag of the style v2.X. | |||||
1. `git tag v2.X+1` If the last version was v2.6, you would run `git tag v2.7` | |||||
1. `git push --tags git@github.com:inconshreveable/log15.git v2` |
@ -1,333 +0,0 @@ | |||||
/* | |||||
Package log15 provides an opinionated, simple toolkit for best-practice logging that is | |||||
both human and machine readable. It is modeled after the standard library's io and net/http | |||||
packages. | |||||
This package enforces you to only log key/value pairs. Keys must be strings. Values may be | |||||
any type that you like. The default output format is logfmt, but you may also choose to use | |||||
JSON instead if that suits you. Here's how you log: | |||||
log.Info("page accessed", "path", r.URL.Path, "user_id", user.id) | |||||
This will output a line that looks like: | |||||
lvl=info t=2014-05-02T16:07:23-0700 msg="page accessed" path=/org/71/profile user_id=9 | |||||
Getting Started | |||||
To get started, you'll want to import the library: | |||||
import log "gopkg.in/inconshreveable/log15.v2" | |||||
Now you're ready to start logging: | |||||
func main() { | |||||
log.Info("Program starting", "args", os.Args()) | |||||
} | |||||
Convention | |||||
Because recording a human-meaningful message is common and good practice, the first argument to every | |||||
logging method is the value to the *implicit* key 'msg'. | |||||
Additionally, the level you choose for a message will be automatically added with the key 'lvl', and so | |||||
will the current timestamp with key 't'. | |||||
You may supply any additional context as a set of key/value pairs to the logging function. log15 allows | |||||
you to favor terseness, ordering, and speed over safety. This is a reasonable tradeoff for | |||||
logging functions. You don't need to explicitly state keys/values, log15 understands that they alternate | |||||
in the variadic argument list: | |||||
log.Warn("size out of bounds", "low", lowBound, "high", highBound, "val", val) | |||||
If you really do favor your type-safety, you may choose to pass a log.Ctx instead: | |||||
log.Warn("size out of bounds", log.Ctx{"low": lowBound, "high": highBound, "val": val}) | |||||
Context loggers | |||||
Frequently, you want to add context to a logger so that you can track actions associated with it. An http | |||||
request is a good example. You can easily create new loggers that have context that is automatically included | |||||
with each log line: | |||||
requestlogger := log.New("path", r.URL.Path) | |||||
// later | |||||
requestlogger.Debug("db txn commit", "duration", txnTimer.Finish()) | |||||
This will output a log line that includes the path context that is attached to the logger: | |||||
lvl=dbug t=2014-05-02T16:07:23-0700 path=/repo/12/add_hook msg="db txn commit" duration=0.12 | |||||
Handlers | |||||
The Handler interface defines where log lines are printed to and how they are formated. Handler is a | |||||
single interface that is inspired by net/http's handler interface: | |||||
type Handler interface { | |||||
Log(r *Record) | |||||
} | |||||
Handlers can filter records, format them, or dispatch to multiple other Handlers. | |||||
This package implements a number of Handlers for common logging patterns that are | |||||
easily composed to create flexible, custom logging structures. | |||||
Here's an example handler that prints logfmt output to Stdout: | |||||
handler := log.StreamHandler(os.Stdout, log.LogfmtFormat()) | |||||
Here's an example handler that defers to two other handlers. One handler only prints records | |||||
from the rpc package in logfmt to standard out. The other prints records at Error level | |||||
or above in JSON formatted output to the file /var/log/service.json | |||||
handler := log.MultiHandler( | |||||
log.LvlFilterHandler(log.LvlError, log.Must.FileHandler("/var/log/service.json", log.JsonFormat())), | |||||
log.MatchFilterHandler("pkg", "app/rpc" log.StdoutHandler()) | |||||
) | |||||
Logging File Names and Line Numbers | |||||
This package implements three Handlers that add debugging information to the | |||||
context, CallerFileHandler, CallerFuncHandler and CallerStackHandler. Here's | |||||
an example that adds the source file and line number of each logging call to | |||||
the context. | |||||
h := log.CallerFileHandler(log.StdoutHandler()) | |||||
log.Root().SetHandler(h) | |||||
... | |||||
log.Error("open file", "err", err) | |||||
This will output a line that looks like: | |||||
lvl=eror t=2014-05-02T16:07:23-0700 msg="open file" err="file not found" caller=data.go:42 | |||||
Here's an example that logs the call stack rather than just the call site. | |||||
h := log.CallerStackHandler("%+v", log.StdoutHandler()) | |||||
log.Root().SetHandler(h) | |||||
... | |||||
log.Error("open file", "err", err) | |||||
This will output a line that looks like: | |||||
lvl=eror t=2014-05-02T16:07:23-0700 msg="open file" err="file not found" stack="[pkg/data.go:42 pkg/cmd/main.go]" | |||||
The "%+v" format instructs the handler to include the path of the source file | |||||
relative to the compile time GOPATH. The log15/stack package documents the | |||||
full list of formatting verbs and modifiers available. | |||||
Custom Handlers | |||||
The Handler interface is so simple that it's also trivial to write your own. Let's create an | |||||
example handler which tries to write to one handler, but if that fails it falls back to | |||||
writing to another handler and includes the error that it encountered when trying to write | |||||
to the primary. This might be useful when trying to log over a network socket, but if that | |||||
fails you want to log those records to a file on disk. | |||||
type BackupHandler struct { | |||||
Primary Handler | |||||
Secondary Handler | |||||
} | |||||
func (h *BackupHandler) Log (r *Record) error { | |||||
err := h.Primary.Log(r) | |||||
if err != nil { | |||||
r.Ctx = append(ctx, "primary_err", err) | |||||
return h.Secondary.Log(r) | |||||
} | |||||
return nil | |||||
} | |||||
This pattern is so useful that a generic version that handles an arbitrary number of Handlers | |||||
is included as part of this library called FailoverHandler. | |||||
Logging Expensive Operations | |||||
Sometimes, you want to log values that are extremely expensive to compute, but you don't want to pay | |||||
the price of computing them if you haven't turned up your logging level to a high level of detail. | |||||
This package provides a simple type to annotate a logging operation that you want to be evaluated | |||||
lazily, just when it is about to be logged, so that it would not be evaluated if an upstream Handler | |||||
filters it out. Just wrap any function which takes no arguments with the log.Lazy type. For example: | |||||
func factorRSAKey() (factors []int) { | |||||
// return the factors of a very large number | |||||
} | |||||
log.Debug("factors", log.Lazy{factorRSAKey}) | |||||
If this message is not logged for any reason (like logging at the Error level), then | |||||
factorRSAKey is never evaluated. | |||||
Dynamic context values | |||||
The same log.Lazy mechanism can be used to attach context to a logger which you want to be | |||||
evaluated when the message is logged, but not when the logger is created. For example, let's imagine | |||||
a game where you have Player objects: | |||||
type Player struct { | |||||
name string | |||||
alive bool | |||||
log.Logger | |||||
} | |||||
You always want to log a player's name and whether they're alive or dead, so when you create the player | |||||
object, you might do: | |||||
p := &Player{name: name, alive: true} | |||||
p.Logger = log.New("name", p.name, "alive", p.alive) | |||||
Only now, even after a player has died, the logger will still report they are alive because the logging | |||||
context is evaluated when the logger was created. By using the Lazy wrapper, we can defer the evaluation | |||||
of whether the player is alive or not to each log message, so that the log records will reflect the player's | |||||
current state no matter when the log message is written: | |||||
p := &Player{name: name, alive: true} | |||||
isAlive := func() bool { return p.alive } | |||||
player.Logger = log.New("name", p.name, "alive", log.Lazy{isAlive}) | |||||
Terminal Format | |||||
If log15 detects that stdout is a terminal, it will configure the default | |||||
handler for it (which is log.StdoutHandler) to use TerminalFormat. This format | |||||
logs records nicely for your terminal, including color-coded output based | |||||
on log level. | |||||
Error Handling | |||||
Becasuse log15 allows you to step around the type system, there are a few ways you can specify | |||||
invalid arguments to the logging functions. You could, for example, wrap something that is not | |||||
a zero-argument function with log.Lazy or pass a context key that is not a string. Since logging libraries | |||||
are typically the mechanism by which errors are reported, it would be onerous for the logging functions | |||||
to return errors. Instead, log15 handles errors by making these guarantees to you: | |||||
- Any log record containing an error will still be printed with the error explained to you as part of the log record. | |||||
- Any log record containing an error will include the context key LOG15_ERROR, enabling you to easily | |||||
(and if you like, automatically) detect if any of your logging calls are passing bad values. | |||||
Understanding this, you might wonder why the Handler interface can return an error value in its Log method. Handlers | |||||
are encouraged to return errors only if they fail to write their log records out to an external source like if the | |||||
syslog daemon is not responding. This allows the construction of useful handlers which cope with those failures | |||||
like the FailoverHandler. | |||||
Library Use | |||||
log15 is intended to be useful for library authors as a way to provide configurable logging to | |||||
users of their library. Best practice for use in a library is to always disable all output for your logger | |||||
by default and to provide a public Logger instance that consumers of your library can configure. Like so: | |||||
package yourlib | |||||
import "gopkg.in/inconshreveable/log15.v2" | |||||
var Log = log.New() | |||||
func init() { | |||||
Log.SetHandler(log.DiscardHandler()) | |||||
} | |||||
Users of your library may then enable it if they like: | |||||
import "gopkg.in/inconshreveable/log15.v2" | |||||
import "example.com/yourlib" | |||||
func main() { | |||||
handler := // custom handler setup | |||||
yourlib.Log.SetHandler(handler) | |||||
} | |||||
Best practices attaching logger context | |||||
The ability to attach context to a logger is a powerful one. Where should you do it and why? | |||||
I favor embedding a Logger directly into any persistent object in my application and adding | |||||
unique, tracing context keys to it. For instance, imagine I am writing a web browser: | |||||
type Tab struct { | |||||
url string | |||||
render *RenderingContext | |||||
// ... | |||||
Logger | |||||
} | |||||
func NewTab(url string) *Tab { | |||||
return &Tab { | |||||
// ... | |||||
url: url, | |||||
Logger: log.New("url", url), | |||||
} | |||||
} | |||||
When a new tab is created, I assign a logger to it with the url of | |||||
the tab as context so it can easily be traced through the logs. | |||||
Now, whenever we perform any operation with the tab, we'll log with its | |||||
embedded logger and it will include the tab title automatically: | |||||
tab.Debug("moved position", "idx", tab.idx) | |||||
There's only one problem. What if the tab url changes? We could | |||||
use log.Lazy to make sure the current url is always written, but that | |||||
would mean that we couldn't trace a tab's full lifetime through our | |||||
logs after the user navigate to a new URL. | |||||
Instead, think about what values to attach to your loggers the | |||||
same way you think about what to use as a key in a SQL database schema. | |||||
If it's possible to use a natural key that is unique for the lifetime of the | |||||
object, do so. But otherwise, log15's ext package has a handy RandId | |||||
function to let you generate what you might call "surrogate keys" | |||||
They're just random hex identifiers to use for tracing. Back to our | |||||
Tab example, we would prefer to set up our Logger like so: | |||||
import logext "gopkg.in/inconshreveable/log15.v2/ext" | |||||
t := &Tab { | |||||
// ... | |||||
url: url, | |||||
} | |||||
t.Logger = log.New("id", logext.RandId(8), "url", log.Lazy{t.getUrl}) | |||||
return t | |||||
Now we'll have a unique traceable identifier even across loading new urls, but | |||||
we'll still be able to see the tab's current url in the log messages. | |||||
Must | |||||
For all Handler functions which can return an error, there is a version of that | |||||
function which will return no error but panics on failure. They are all available | |||||
on the Must object. For example: | |||||
log.Must.FileHandler("/path", log.JsonFormat) | |||||
log.Must.NetHandler("tcp", ":1234", log.JsonFormat) | |||||
Inspiration and Credit | |||||
All of the following excellent projects inspired the design of this library: | |||||
code.google.com/p/log4go | |||||
github.com/op/go-logging | |||||
github.com/technoweenie/grohl | |||||
github.com/Sirupsen/logrus | |||||
github.com/kr/logfmt | |||||
github.com/spacemonkeygo/spacelog | |||||
golang's stdlib, notably io and net/http | |||||
The Name | |||||
https://xkcd.com/927/ | |||||
*/ | |||||
package log15 |
@ -1,130 +0,0 @@ | |||||
package ext | |||||
import ( | |||||
"os" | |||||
"sync" | |||||
"sync/atomic" | |||||
"unsafe" | |||||
log "github.com/inconshreveable/log15" | |||||
) | |||||
// EscalateErrHandler wraps another handler and passes all records through | |||||
// unchanged except if the logged context contains a non-nil error | |||||
// value in its context. In that case, the record's level is raised | |||||
// to LvlError unless it was already more serious (LvlCrit). | |||||
// | |||||
// This allows you to log the result of all functions for debugging | |||||
// and still capture error conditions when in production with a single | |||||
// log line. As an example, the following the log record will be written | |||||
// out only if there was an error writing a value to redis: | |||||
// | |||||
// logger := logext.EscalateErrHandler( | |||||
// log.LvlFilterHandler(log.LvlInfo, log.StdoutHandler)) | |||||
// | |||||
// reply, err := redisConn.Do("SET", "foo", "bar") | |||||
// logger.Debug("Wrote value to redis", "reply", reply, "err", err) | |||||
// if err != nil { | |||||
// return err | |||||
// } | |||||
// | |||||
func EscalateErrHandler(h log.Handler) log.Handler { | |||||
return log.FuncHandler(func(r *log.Record) error { | |||||
if r.Lvl > log.LvlError { | |||||
for i := 1; i < len(r.Ctx); i++ { | |||||
if v, ok := r.Ctx[i].(error); ok && v != nil { | |||||
r.Lvl = log.LvlError | |||||
break | |||||
} | |||||
} | |||||
} | |||||
return h.Log(r) | |||||
}) | |||||
} | |||||
// SpeculativeHandler is a handler for speculative logging. It | |||||
// keeps a ring buffer of the given size full of the last events | |||||
// logged into it. When Flush is called, all buffered log records | |||||
// are written to the wrapped handler. This is extremely for | |||||
// continuosly capturing debug level output, but only flushing those | |||||
// log records if an exceptional condition is encountered. | |||||
func SpeculativeHandler(size int, h log.Handler) *Speculative { | |||||
return &Speculative{ | |||||
handler: h, | |||||
recs: make([]*log.Record, size), | |||||
} | |||||
} | |||||
type Speculative struct { | |||||
mu sync.Mutex | |||||
idx int | |||||
recs []*log.Record | |||||
handler log.Handler | |||||
full bool | |||||
} | |||||
func (h *Speculative) Log(r *log.Record) error { | |||||
h.mu.Lock() | |||||
defer h.mu.Unlock() | |||||
h.recs[h.idx] = r | |||||
h.idx = (h.idx + 1) % len(h.recs) | |||||
h.full = h.full || h.idx == 0 | |||||
return nil | |||||
} | |||||
func (h *Speculative) Flush() { | |||||
recs := make([]*log.Record, 0) | |||||
func() { | |||||
h.mu.Lock() | |||||
defer h.mu.Unlock() | |||||
if h.full { | |||||
recs = append(recs, h.recs[h.idx:]...) | |||||
} | |||||
recs = append(recs, h.recs[:h.idx]...) | |||||
// reset state | |||||
h.full = false | |||||
h.idx = 0 | |||||
}() | |||||
// don't hold the lock while we flush to the wrapped handler | |||||
for _, r := range recs { | |||||
h.handler.Log(r) | |||||
} | |||||
} | |||||
// HotSwapHandler wraps another handler that may swapped out | |||||
// dynamically at runtime in a thread-safe fashion. | |||||
// HotSwapHandler is the same functionality | |||||
// used to implement the SetHandler method for the default | |||||
// implementation of Logger. | |||||
func HotSwapHandler(h log.Handler) *HotSwap { | |||||
hs := new(HotSwap) | |||||
hs.Swap(h) | |||||
return hs | |||||
} | |||||
type HotSwap struct { | |||||
handler unsafe.Pointer | |||||
} | |||||
func (h *HotSwap) Log(r *log.Record) error { | |||||
return (*(*log.Handler)(atomic.LoadPointer(&h.handler))).Log(r) | |||||
} | |||||
func (h *HotSwap) Swap(newHandler log.Handler) { | |||||
atomic.StorePointer(&h.handler, unsafe.Pointer(&newHandler)) | |||||
} | |||||
// FatalHandler makes critical errors exit the program | |||||
// immediately, much like the log.Fatal* methods from the | |||||
// standard log package | |||||
func FatalHandler(h log.Handler) log.Handler { | |||||
return log.FuncHandler(func(r *log.Record) error { | |||||
err := h.Log(r) | |||||
if r.Lvl == log.LvlCrit { | |||||
os.Exit(1) | |||||
} | |||||
return err | |||||
}) | |||||
} |
@ -1,47 +0,0 @@ | |||||
package ext | |||||
import ( | |||||
"fmt" | |||||
"math/rand" | |||||
"sync" | |||||
"time" | |||||
) | |||||
var r = rand.New(&lockedSource{src: rand.NewSource(time.Now().Unix())}) | |||||
// RandId creates a random identifier of the requested length. | |||||
// Useful for assigning mostly-unique identifiers for logging | |||||
// and identification that are unlikely to collide because of | |||||
// short lifespan or low set cardinality | |||||
func RandId(idlen int) string { | |||||
b := make([]byte, idlen) | |||||
var randVal uint32 | |||||
for i := 0; i < idlen; i++ { | |||||
byteIdx := i % 4 | |||||
if byteIdx == 0 { | |||||
randVal = r.Uint32() | |||||
} | |||||
b[i] = byte((randVal >> (8 * uint(byteIdx))) & 0xFF) | |||||
} | |||||
return fmt.Sprintf("%x", b) | |||||
} | |||||
// lockedSource is a wrapper to allow a rand.Source to be used | |||||
// concurrently (same type as the one used internally in math/rand). | |||||
type lockedSource struct { | |||||
lk sync.Mutex | |||||
src rand.Source | |||||
} | |||||
func (r *lockedSource) Int63() (n int64) { | |||||
r.lk.Lock() | |||||
n = r.src.Int63() | |||||
r.lk.Unlock() | |||||
return | |||||
} | |||||
func (r *lockedSource) Seed(seed int64) { | |||||
r.lk.Lock() | |||||
r.src.Seed(seed) | |||||
r.lk.Unlock() | |||||
} |
@ -1,257 +0,0 @@ | |||||
package log15 | |||||
import ( | |||||
"bytes" | |||||
"encoding/json" | |||||
"fmt" | |||||
"reflect" | |||||
"strconv" | |||||
"strings" | |||||
"time" | |||||
) | |||||
const ( | |||||
timeFormat = "2006-01-02T15:04:05-0700" | |||||
termTimeFormat = "01-02|15:04:05" | |||||
floatFormat = 'f' | |||||
termMsgJust = 40 | |||||
) | |||||
type Format interface { | |||||
Format(r *Record) []byte | |||||
} | |||||
// FormatFunc returns a new Format object which uses | |||||
// the given function to perform record formatting. | |||||
func FormatFunc(f func(*Record) []byte) Format { | |||||
return formatFunc(f) | |||||
} | |||||
type formatFunc func(*Record) []byte | |||||
func (f formatFunc) Format(r *Record) []byte { | |||||
return f(r) | |||||
} | |||||
// TerminalFormat formats log records optimized for human readability on | |||||
// a terminal with color-coded level output and terser human friendly timestamp. | |||||
// This format should only be used for interactive programs or while developing. | |||||
// | |||||
// [TIME] [LEVEL] MESAGE key=value key=value ... | |||||
// | |||||
// Example: | |||||
// | |||||
// [May 16 20:58:45] [DBUG] remove route ns=haproxy addr=127.0.0.1:50002 | |||||
// | |||||
func TerminalFormat() Format { | |||||
return FormatFunc(func(r *Record) []byte { | |||||
var color = 0 | |||||
switch r.Lvl { | |||||
case LvlCrit: | |||||
color = 35 | |||||
case LvlError: | |||||
color = 31 | |||||
case LvlWarn: | |||||
color = 33 | |||||
case LvlInfo: | |||||
color = 32 | |||||
case LvlDebug: | |||||
color = 36 | |||||
} | |||||
b := &bytes.Buffer{} | |||||
lvl := strings.ToUpper(r.Lvl.String()) | |||||
if color > 0 { | |||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %s ", color, lvl, r.Time.Format(termTimeFormat), r.Msg) | |||||
} else { | |||||
fmt.Fprintf(b, "[%s] [%s] %s ", lvl, r.Time.Format(termTimeFormat), r.Msg) | |||||
} | |||||
// try to justify the log output for short messages | |||||
if len(r.Ctx) > 0 && len(r.Msg) < termMsgJust { | |||||
b.Write(bytes.Repeat([]byte{' '}, termMsgJust-len(r.Msg))) | |||||
} | |||||
// print the keys logfmt style | |||||
logfmt(b, r.Ctx, color) | |||||
return b.Bytes() | |||||
}) | |||||
} | |||||
// LogfmtFormat prints records in logfmt format, an easy machine-parseable but human-readable | |||||
// format for key/value pairs. | |||||
// | |||||
// For more details see: http://godoc.org/github.com/kr/logfmt | |||||
// | |||||
func LogfmtFormat() Format { | |||||
return FormatFunc(func(r *Record) []byte { | |||||
common := []interface{}{r.KeyNames.Time, r.Time, r.KeyNames.Lvl, r.Lvl, r.KeyNames.Msg, r.Msg} | |||||
buf := &bytes.Buffer{} | |||||
logfmt(buf, append(common, r.Ctx...), 0) | |||||
return buf.Bytes() | |||||
}) | |||||
} | |||||
func logfmt(buf *bytes.Buffer, ctx []interface{}, color int) { | |||||
for i := 0; i < len(ctx); i += 2 { | |||||
if i != 0 { | |||||
buf.WriteByte(' ') | |||||
} | |||||
k, ok := ctx[i].(string) | |||||
v := formatLogfmtValue(ctx[i+1]) | |||||
if !ok { | |||||
k, v = errorKey, formatLogfmtValue(k) | |||||
} | |||||
// XXX: we should probably check that all of your key bytes aren't invalid | |||||
if color > 0 { | |||||
fmt.Fprintf(buf, "\x1b[%dm%s\x1b[0m=%s", color, k, v) | |||||
} else { | |||||
fmt.Fprintf(buf, "%s=%s", k, v) | |||||
} | |||||
} | |||||
buf.WriteByte('\n') | |||||
} | |||||
// JsonFormat formats log records as JSON objects separated by newlines. | |||||
// It is the equivalent of JsonFormatEx(false, true). | |||||
func JsonFormat() Format { | |||||
return JsonFormatEx(false, true) | |||||
} | |||||
// JsonFormatEx formats log records as JSON objects. If pretty is true, | |||||
// records will be pretty-printed. If lineSeparated is true, records | |||||
// will be logged with a new line between each record. | |||||
func JsonFormatEx(pretty, lineSeparated bool) Format { | |||||
jsonMarshal := json.Marshal | |||||
if pretty { | |||||
jsonMarshal = func(v interface{}) ([]byte, error) { | |||||
return json.MarshalIndent(v, "", " ") | |||||
} | |||||
} | |||||
return FormatFunc(func(r *Record) []byte { | |||||
props := make(map[string]interface{}) | |||||
props[r.KeyNames.Time] = r.Time | |||||
props[r.KeyNames.Lvl] = r.Lvl.String() | |||||
props[r.KeyNames.Msg] = r.Msg | |||||
for i := 0; i < len(r.Ctx); i += 2 { | |||||
k, ok := r.Ctx[i].(string) | |||||
if !ok { | |||||
props[errorKey] = fmt.Sprintf("%+v is not a string key", r.Ctx[i]) | |||||
} | |||||
props[k] = formatJsonValue(r.Ctx[i+1]) | |||||
} | |||||
b, err := jsonMarshal(props) | |||||
if err != nil { | |||||
b, _ = jsonMarshal(map[string]string{ | |||||
errorKey: err.Error(), | |||||
}) | |||||
return b | |||||
} | |||||
if lineSeparated { | |||||
b = append(b, '\n') | |||||
} | |||||
return b | |||||
}) | |||||
} | |||||
func formatShared(value interface{}) (result interface{}) { | |||||
defer func() { | |||||
if err := recover(); err != nil { | |||||
if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() { | |||||
result = "nil" | |||||
} else { | |||||
panic(err) | |||||
} | |||||
} | |||||
}() | |||||
switch v := value.(type) { | |||||
case time.Time: | |||||
return v.Format(timeFormat) | |||||
case error: | |||||
return v.Error() | |||||
case fmt.Stringer: | |||||
return v.String() | |||||
default: | |||||
return v | |||||
} | |||||
} | |||||
func formatJsonValue(value interface{}) interface{} { | |||||
value = formatShared(value) | |||||
switch value.(type) { | |||||
case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string: | |||||
return value | |||||
default: | |||||
return fmt.Sprintf("%+v", value) | |||||
} | |||||
} | |||||
// formatValue formats a value for serialization | |||||
func formatLogfmtValue(value interface{}) string { | |||||
if value == nil { | |||||
return "nil" | |||||
} | |||||
value = formatShared(value) | |||||
switch v := value.(type) { | |||||
case bool: | |||||
return strconv.FormatBool(v) | |||||
case float32: | |||||
return strconv.FormatFloat(float64(v), floatFormat, 3, 64) | |||||
case float64: | |||||
return strconv.FormatFloat(v, floatFormat, 3, 64) | |||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: | |||||
return fmt.Sprintf("%d", value) | |||||
case string: | |||||
return escapeString(v) | |||||
default: | |||||
return escapeString(fmt.Sprintf("%+v", value)) | |||||
} | |||||
} | |||||
func escapeString(s string) string { | |||||
needQuotes := false | |||||
e := bytes.Buffer{} | |||||
e.WriteByte('"') | |||||
for _, r := range s { | |||||
if r <= ' ' || r == '=' || r == '"' { | |||||
needQuotes = true | |||||
} | |||||
switch r { | |||||
case '\\', '"': | |||||
e.WriteByte('\\') | |||||
e.WriteByte(byte(r)) | |||||
case '\n': | |||||
e.WriteByte('\\') | |||||
e.WriteByte('n') | |||||
case '\r': | |||||
e.WriteByte('\\') | |||||
e.WriteByte('r') | |||||
case '\t': | |||||
e.WriteByte('\\') | |||||
e.WriteByte('t') | |||||
default: | |||||
e.WriteRune(r) | |||||
} | |||||
} | |||||
e.WriteByte('"') | |||||
start, stop := 0, e.Len() | |||||
if !needQuotes { | |||||
start, stop = 1, stop-1 | |||||
} | |||||
return string(e.Bytes()[start:stop]) | |||||
} |
@ -1,371 +0,0 @@ | |||||
package log15 | |||||
import ( | |||||
"bytes" | |||||
"fmt" | |||||
"io" | |||||
"net" | |||||
"os" | |||||
"reflect" | |||||
"sync" | |||||
"github.com/inconshreveable/log15/stack" | |||||
) | |||||
// A Logger prints its log records by writing to a Handler. | |||||
// The Handler interface defines where and how log records are written. | |||||
// Handlers are composable, providing you great flexibility in combining | |||||
// them to achieve the logging structure that suits your applications. | |||||
type Handler interface { | |||||
Log(r *Record) error | |||||
} | |||||
// FuncHandler returns a Handler that logs records with the given | |||||
// function. | |||||
func FuncHandler(fn func(r *Record) error) Handler { | |||||
return funcHandler(fn) | |||||
} | |||||
type funcHandler func(r *Record) error | |||||
func (h funcHandler) Log(r *Record) error { | |||||
return h(r) | |||||
} | |||||
// StreamHandler writes log records to an io.Writer | |||||
// with the given format. StreamHandler can be used | |||||
// to easily begin writing log records to other | |||||
// outputs. | |||||
// | |||||
// StreamHandler wraps itself with LazyHandler and SyncHandler | |||||
// to evaluate Lazy objects and perform safe concurrent writes. | |||||
func StreamHandler(wr io.Writer, fmtr Format) Handler { | |||||
h := FuncHandler(func(r *Record) error { | |||||
_, err := wr.Write(fmtr.Format(r)) | |||||
return err | |||||
}) | |||||
return LazyHandler(SyncHandler(h)) | |||||
} | |||||
// SyncHandler can be wrapped around a handler to guarantee that | |||||
// only a single Log operation can proceed at a time. It's necessary | |||||
// for thread-safe concurrent writes. | |||||
func SyncHandler(h Handler) Handler { | |||||
var mu sync.Mutex | |||||
return FuncHandler(func(r *Record) error { | |||||
defer mu.Unlock() | |||||
mu.Lock() | |||||
return h.Log(r) | |||||
}) | |||||
} | |||||
// FileHandler returns a handler which writes log records to the give file | |||||
// using the given format. If the path | |||||
// already exists, FileHandler will append to the given file. If it does not, | |||||
// FileHandler will create the file with mode 0644. | |||||
func FileHandler(path string, fmtr Format) (Handler, error) { | |||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
return closingHandler{f, StreamHandler(f, fmtr)}, nil | |||||
} | |||||
// NetHandler opens a socket to the given address and writes records | |||||
// over the connection. | |||||
func NetHandler(network, addr string, fmtr Format) (Handler, error) { | |||||
conn, err := net.Dial(network, addr) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
return closingHandler{conn, StreamHandler(conn, fmtr)}, nil | |||||
} | |||||
// XXX: closingHandler is essentially unused at the moment | |||||
// it's meant for a future time when the Handler interface supports | |||||
// a possible Close() operation | |||||
type closingHandler struct { | |||||
io.WriteCloser | |||||
Handler | |||||
} | |||||
func (h *closingHandler) Close() error { | |||||
return h.WriteCloser.Close() | |||||
} | |||||
// CallerFileHandler returns a Handler that adds the line number and file of | |||||
// the calling function to the context with key "caller". | |||||
func CallerFileHandler(h Handler) Handler { | |||||
return FuncHandler(func(r *Record) error { | |||||
call := stack.Call(r.CallPC[0]) | |||||
r.Ctx = append(r.Ctx, "caller", fmt.Sprint(call)) | |||||
return h.Log(r) | |||||
}) | |||||
} | |||||
// CallerFuncHandler returns a Handler that adds the calling function name to | |||||
// the context with key "fn". | |||||
func CallerFuncHandler(h Handler) Handler { | |||||
return FuncHandler(func(r *Record) error { | |||||
call := stack.Call(r.CallPC[0]) | |||||
r.Ctx = append(r.Ctx, "fn", fmt.Sprintf("%+n", call)) | |||||
return h.Log(r) | |||||
}) | |||||
} | |||||
// CallerStackHandler returns a Handler that adds a stack trace to the context | |||||
// with key "stack". The stack trace is formated as a space separated list of | |||||
// call sites inside matching []'s. The most recent call site is listed first. | |||||
// Each call site is formatted according to format. See the documentation of | |||||
// log15/stack.Call.Format for the list of supported formats. | |||||
func CallerStackHandler(format string, h Handler) Handler { | |||||
return FuncHandler(func(r *Record) error { | |||||
s := stack.Callers(). | |||||
TrimBelow(stack.Call(r.CallPC[0])). | |||||
TrimRuntime() | |||||
if len(s) > 0 { | |||||
buf := &bytes.Buffer{} | |||||
buf.WriteByte('[') | |||||
for i, pc := range s { | |||||
if i > 0 { | |||||
buf.WriteByte(' ') | |||||
} | |||||
fmt.Fprintf(buf, format, pc) | |||||
} | |||||
buf.WriteByte(']') | |||||
r.Ctx = append(r.Ctx, "stack", buf.String()) | |||||
} | |||||
return h.Log(r) | |||||
}) | |||||
} | |||||
// FilterHandler returns a Handler that only writes records to the | |||||
// wrapped Handler if the given function evaluates true. For example, | |||||
// to only log records where the 'err' key is not nil: | |||||
// | |||||
// logger.SetHandler(FilterHandler(func(r *Record) bool { | |||||
// for i := 0; i < len(r.Ctx); i += 2 { | |||||
// if r.Ctx[i] == "err" { | |||||
// return r.Ctx[i+1] != nil | |||||
// } | |||||
// } | |||||
// return false | |||||
// }, h)) | |||||
// | |||||
func FilterHandler(fn func(r *Record) bool, h Handler) Handler { | |||||
return FuncHandler(func(r *Record) error { | |||||
if fn(r) { | |||||
return h.Log(r) | |||||
} | |||||
return nil | |||||
}) | |||||
} | |||||
// MatchFilterHandler returns a Handler that only writes records | |||||
// to the wrapped Handler if the given key in the logged | |||||
// context matches the value. For example, to only log records | |||||
// from your ui package: | |||||
// | |||||
// log.MatchFilterHandler("pkg", "app/ui", log.StdoutHandler) | |||||
// | |||||
func MatchFilterHandler(key string, value interface{}, h Handler) Handler { | |||||
return FilterHandler(func(r *Record) (pass bool) { | |||||
switch key { | |||||
case r.KeyNames.Lvl: | |||||
return r.Lvl == value | |||||
case r.KeyNames.Time: | |||||
return r.Time == value | |||||
case r.KeyNames.Msg: | |||||
return r.Msg == value | |||||
} | |||||
for i := 0; i < len(r.Ctx); i += 2 { | |||||
if r.Ctx[i] == key { | |||||
return r.Ctx[i+1] == value | |||||
} | |||||
} | |||||
return false | |||||
}, h) | |||||
} | |||||
// LvlFilterHandler returns a Handler that only writes | |||||
// records which are less than the given verbosity | |||||
// level to the wrapped Handler. For example, to only | |||||
// log Error/Crit records: | |||||
// | |||||
// log.LvlFilterHandler(log.Error, log.StdoutHandler) | |||||
// | |||||
func LvlFilterHandler(maxLvl Lvl, h Handler) Handler { | |||||
return FilterHandler(func(r *Record) (pass bool) { | |||||
return r.Lvl <= maxLvl | |||||
}, h) | |||||
} | |||||
// A MultiHandler dispatches any write to each of its handlers. | |||||
// This is useful for writing different types of log information | |||||
// to different locations. For example, to log to a file and | |||||
// standard error: | |||||
// | |||||
// log.MultiHandler( | |||||
// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()), | |||||
// log.StderrHandler) | |||||
// | |||||
func MultiHandler(hs ...Handler) Handler { | |||||
return FuncHandler(func(r *Record) error { | |||||
for _, h := range hs { | |||||
// what to do about failures? | |||||
h.Log(r) | |||||
} | |||||
return nil | |||||
}) | |||||
} | |||||
// A FailoverHandler writes all log records to the first handler | |||||
// specified, but will failover and write to the second handler if | |||||
// the first handler has failed, and so on for all handlers specified. | |||||
// For example you might want to log to a network socket, but failover | |||||
// to writing to a file if the network fails, and then to | |||||
// standard out if the file write fails: | |||||
// | |||||
// log.FailoverHandler( | |||||
// log.Must.NetHandler("tcp", ":9090", log.JsonFormat()), | |||||
// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()), | |||||
// log.StdoutHandler) | |||||
// | |||||
// All writes that do not go to the first handler will add context with keys of | |||||
// the form "failover_err_{idx}" which explain the error encountered while | |||||
// trying to write to the handlers before them in the list. | |||||
func FailoverHandler(hs ...Handler) Handler { | |||||
return FuncHandler(func(r *Record) error { | |||||
var err error | |||||
for i, h := range hs { | |||||
err = h.Log(r) | |||||
if err == nil { | |||||
return nil | |||||
} else { | |||||
r.Ctx = append(r.Ctx, fmt.Sprintf("failover_err_%d", i), err) | |||||
} | |||||
} | |||||
return err | |||||
}) | |||||
} | |||||
// ChannelHandler writes all records to the given channel. | |||||
// It blocks if the channel is full. Useful for async processing | |||||
// of log messages, it's used by BufferedHandler. | |||||
func ChannelHandler(recs chan<- *Record) Handler { | |||||
return FuncHandler(func(r *Record) error { | |||||
recs <- r | |||||
return nil | |||||
}) | |||||
} | |||||
// BufferedHandler writes all records to a buffered | |||||
// channel of the given size which flushes into the wrapped | |||||
// handler whenever it is available for writing. Since these | |||||
// writes happen asynchronously, all writes to a BufferedHandler | |||||
// never return an error and any errors from the wrapped handler are ignored. | |||||
func BufferedHandler(bufSize int, h Handler) Handler { | |||||
recs := make(chan *Record, bufSize) | |||||
go func() { | |||||
for m := range recs { | |||||
_ = h.Log(m) | |||||
} | |||||
}() | |||||
return ChannelHandler(recs) | |||||
} | |||||
// LazyHandler writes all values to the wrapped handler after evaluating | |||||
// any lazy functions in the record's context. It is already wrapped | |||||
// around StreamHandler and SyslogHandler in this library, you'll only need | |||||
// it if you write your own Handler. | |||||
func LazyHandler(h Handler) Handler { | |||||
return FuncHandler(func(r *Record) error { | |||||
// go through the values (odd indices) and reassign | |||||
// the values of any lazy fn to the result of its execution | |||||
hadErr := false | |||||
for i := 1; i < len(r.Ctx); i += 2 { | |||||
lz, ok := r.Ctx[i].(Lazy) | |||||
if ok { | |||||
v, err := evaluateLazy(lz) | |||||
if err != nil { | |||||
hadErr = true | |||||
r.Ctx[i] = err | |||||
} else { | |||||
if cs, ok := v.(stack.Trace); ok { | |||||
v = cs.TrimBelow(stack.Call(r.CallPC[0])). | |||||
TrimRuntime() | |||||
} | |||||
r.Ctx[i] = v | |||||
} | |||||
} | |||||
} | |||||
if hadErr { | |||||
r.Ctx = append(r.Ctx, errorKey, "bad lazy") | |||||
} | |||||
return h.Log(r) | |||||
}) | |||||
} | |||||
func evaluateLazy(lz Lazy) (interface{}, error) { | |||||
t := reflect.TypeOf(lz.Fn) | |||||
if t.Kind() != reflect.Func { | |||||
return nil, fmt.Errorf("INVALID_LAZY, not func: %+v", lz.Fn) | |||||
} | |||||
if t.NumIn() > 0 { | |||||
return nil, fmt.Errorf("INVALID_LAZY, func takes args: %+v", lz.Fn) | |||||
} | |||||
if t.NumOut() == 0 { | |||||
return nil, fmt.Errorf("INVALID_LAZY, no func return val: %+v", lz.Fn) | |||||
} | |||||
value := reflect.ValueOf(lz.Fn) | |||||
results := value.Call([]reflect.Value{}) | |||||
if len(results) == 1 { | |||||
return results[0].Interface(), nil | |||||
} else { | |||||
values := make([]interface{}, len(results)) | |||||
for i, v := range results { | |||||
values[i] = v.Interface() | |||||
} | |||||
return values, nil | |||||
} | |||||
} | |||||
// DiscardHandler reports success for all writes but does nothing. | |||||
// It is useful for dynamically disabling logging at runtime via | |||||
// a Logger's SetHandler method. | |||||
func DiscardHandler() Handler { | |||||
return FuncHandler(func(r *Record) error { | |||||
return nil | |||||
}) | |||||
} | |||||
// The Must object provides the following Handler creation functions | |||||
// which instead of returning an error parameter only return a Handler | |||||
// and panic on failure: FileHandler, NetHandler, SyslogHandler, SyslogNetHandler | |||||
var Must muster | |||||
func must(h Handler, err error) Handler { | |||||
if err != nil { | |||||
panic(err) | |||||
} | |||||
return h | |||||
} | |||||
type muster struct{} | |||||
func (m muster) FileHandler(path string, fmtr Format) Handler { | |||||
return must(FileHandler(path, fmtr)) | |||||
} | |||||
func (m muster) NetHandler(network, addr string, fmtr Format) Handler { | |||||
return must(NetHandler(network, addr, fmtr)) | |||||
} |
@ -1,26 +0,0 @@ | |||||
// +build appengine | |||||
package log15 | |||||
import "sync" | |||||
// swapHandler wraps another handler that may be swapped out | |||||
// dynamically at runtime in a thread-safe fashion. | |||||
type swapHandler struct { | |||||
handler interface{} | |||||
lock sync.RWMutex | |||||
} | |||||
func (h *swapHandler) Log(r *Record) error { | |||||
h.lock.RLock() | |||||
defer h.lock.RUnlock() | |||||
return h.handler.(Handler).Log(r) | |||||
} | |||||
func (h *swapHandler) Swap(newHandler Handler) { | |||||
h.lock.Lock() | |||||
defer h.lock.Unlock() | |||||
h.handler = newHandler | |||||
} |
@ -1,22 +0,0 @@ | |||||
// +build !appengine | |||||
package log15 | |||||
import ( | |||||
"sync/atomic" | |||||
"unsafe" | |||||
) | |||||
// swapHandler wraps another handler that may be swapped out | |||||
// dynamically at runtime in a thread-safe fashion. | |||||
type swapHandler struct { | |||||
handler unsafe.Pointer | |||||
} | |||||
func (h *swapHandler) Log(r *Record) error { | |||||
return (*(*Handler)(atomic.LoadPointer(&h.handler))).Log(r) | |||||
} | |||||
func (h *swapHandler) Swap(newHandler Handler) { | |||||
atomic.StorePointer(&h.handler, unsafe.Pointer(&newHandler)) | |||||
} |
@ -1,201 +0,0 @@ | |||||
package log15 | |||||
import ( | |||||
"fmt" | |||||
"runtime" | |||||
"time" | |||||
) | |||||
const timeKey = "t" | |||||
const lvlKey = "lvl" | |||||
const msgKey = "msg" | |||||
const errorKey = "LOG15_ERROR" | |||||
type Lvl int | |||||
const ( | |||||
LvlCrit Lvl = iota | |||||
LvlError | |||||
LvlWarn | |||||
LvlInfo | |||||
LvlDebug | |||||
) | |||||
// Returns the name of a Lvl | |||||
func (l Lvl) String() string { | |||||
switch l { | |||||
case LvlDebug: | |||||
return "dbug" | |||||
case LvlInfo: | |||||
return "info" | |||||
case LvlWarn: | |||||
return "warn" | |||||
case LvlError: | |||||
return "eror" | |||||
case LvlCrit: | |||||
return "crit" | |||||
default: | |||||
panic("bad level") | |||||
} | |||||
} | |||||
// Returns the appropriate Lvl from a string name. | |||||
// Useful for parsing command line args and configuration files. | |||||
func LvlFromString(lvlString string) (Lvl, error) { | |||||
switch lvlString { | |||||
case "debug", "dbug": | |||||
return LvlDebug, nil | |||||
case "info": | |||||
return LvlInfo, nil | |||||
case "warn": | |||||
return LvlWarn, nil | |||||
case "error", "eror": | |||||
return LvlError, nil | |||||
case "crit": | |||||
return LvlCrit, nil | |||||
default: | |||||
return LvlDebug, fmt.Errorf("Unknown level: %v", lvlString) | |||||
} | |||||
} | |||||
// A Record is what a Logger asks its handler to write | |||||
type Record struct { | |||||
Time time.Time | |||||
Lvl Lvl | |||||
Msg string | |||||
Ctx []interface{} | |||||
CallPC [1]uintptr | |||||
KeyNames RecordKeyNames | |||||
} | |||||
type RecordKeyNames struct { | |||||
Time string | |||||
Msg string | |||||
Lvl string | |||||
} | |||||
// A Logger writes key/value pairs to a Handler | |||||
type Logger interface { | |||||
// New returns a new Logger that has this logger's context plus the given context | |||||
New(ctx ...interface{}) Logger | |||||
// SetHandler updates the logger to write records to the specified handler. | |||||
SetHandler(h Handler) | |||||
// Log a message at the given level with context key/value pairs | |||||
Debug(msg string, ctx ...interface{}) | |||||
Info(msg string, ctx ...interface{}) | |||||
Warn(msg string, ctx ...interface{}) | |||||
Error(msg string, ctx ...interface{}) | |||||
Crit(msg string, ctx ...interface{}) | |||||
} | |||||
type logger struct { | |||||
ctx []interface{} | |||||
h *swapHandler | |||||
} | |||||
func (l *logger) write(msg string, lvl Lvl, ctx []interface{}) { | |||||
r := Record{ | |||||
Time: time.Now(), | |||||
Lvl: lvl, | |||||
Msg: msg, | |||||
Ctx: newContext(l.ctx, ctx), | |||||
KeyNames: RecordKeyNames{ | |||||
Time: timeKey, | |||||
Msg: msgKey, | |||||
Lvl: lvlKey, | |||||
}, | |||||
} | |||||
runtime.Callers(3, r.CallPC[:]) | |||||
l.h.Log(&r) | |||||
} | |||||
func (l *logger) New(ctx ...interface{}) Logger { | |||||
child := &logger{newContext(l.ctx, ctx), new(swapHandler)} | |||||
child.SetHandler(l.h) | |||||
return child | |||||
} | |||||
func newContext(prefix []interface{}, suffix []interface{}) []interface{} { | |||||
normalizedSuffix := normalize(suffix) | |||||
newCtx := make([]interface{}, len(prefix)+len(normalizedSuffix)) | |||||
n := copy(newCtx, prefix) | |||||
copy(newCtx[n:], normalizedSuffix) | |||||
return newCtx | |||||
} | |||||
func (l *logger) Debug(msg string, ctx ...interface{}) { | |||||
l.write(msg, LvlDebug, ctx) | |||||
} | |||||
func (l *logger) Info(msg string, ctx ...interface{}) { | |||||
l.write(msg, LvlInfo, ctx) | |||||
} | |||||
func (l *logger) Warn(msg string, ctx ...interface{}) { | |||||
l.write(msg, LvlWarn, ctx) | |||||
} | |||||
func (l *logger) Error(msg string, ctx ...interface{}) { | |||||
l.write(msg, LvlError, ctx) | |||||
} | |||||
func (l *logger) Crit(msg string, ctx ...interface{}) { | |||||
l.write(msg, LvlCrit, ctx) | |||||
} | |||||
func (l *logger) SetHandler(h Handler) { | |||||
l.h.Swap(h) | |||||
} | |||||
func normalize(ctx []interface{}) []interface{} { | |||||
// if the caller passed a Ctx object, then expand it | |||||
if len(ctx) == 1 { | |||||
if ctxMap, ok := ctx[0].(Ctx); ok { | |||||
ctx = ctxMap.toArray() | |||||
} | |||||
} | |||||
// ctx needs to be even because it's a series of key/value pairs | |||||
// no one wants to check for errors on logging functions, | |||||
// so instead of erroring on bad input, we'll just make sure | |||||
// that things are the right length and users can fix bugs | |||||
// when they see the output looks wrong | |||||
if len(ctx)%2 != 0 { | |||||
ctx = append(ctx, nil, errorKey, "Normalized odd number of arguments by adding nil") | |||||
} | |||||
return ctx | |||||
} | |||||
// Lazy allows you to defer calculation of a logged value that is expensive | |||||
// to compute until it is certain that it must be evaluated with the given filters. | |||||
// | |||||
// Lazy may also be used in conjunction with a Logger's New() function | |||||
// to generate a child logger which always reports the current value of changing | |||||
// state. | |||||
// | |||||
// You may wrap any function which takes no arguments to Lazy. It may return any | |||||
// number of values of any type. | |||||
type Lazy struct { | |||||
Fn interface{} | |||||
} | |||||
// Ctx is a map of key/value pairs to pass as context to a log function | |||||
// Use this only if you really need greater safety around the arguments you pass | |||||
// to the logging functions. | |||||
type Ctx map[string]interface{} | |||||
func (c Ctx) toArray() []interface{} { | |||||
arr := make([]interface{}, len(c)*2) | |||||
i := 0 | |||||
for k, v := range c { | |||||
arr[i] = k | |||||
arr[i+1] = v | |||||
i += 2 | |||||
} | |||||
return arr | |||||
} |
@ -1,67 +0,0 @@ | |||||
package log15 | |||||
import ( | |||||
"os" | |||||
"github.com/inconshreveable/log15/term" | |||||
"github.com/mattn/go-colorable" | |||||
) | |||||
var ( | |||||
root *logger | |||||
StdoutHandler = StreamHandler(os.Stdout, LogfmtFormat()) | |||||
StderrHandler = StreamHandler(os.Stderr, LogfmtFormat()) | |||||
) | |||||
func init() { | |||||
if term.IsTty(os.Stdout.Fd()) { | |||||
StdoutHandler = StreamHandler(colorable.NewColorableStdout(), TerminalFormat()) | |||||
} | |||||
if term.IsTty(os.Stderr.Fd()) { | |||||
StderrHandler = StreamHandler(colorable.NewColorableStderr(), TerminalFormat()) | |||||
} | |||||
root = &logger{[]interface{}{}, new(swapHandler)} | |||||
root.SetHandler(StdoutHandler) | |||||
} | |||||
// New returns a new logger with the given context. | |||||
// New is a convenient alias for Root().New | |||||
func New(ctx ...interface{}) Logger { | |||||
return root.New(ctx...) | |||||
} | |||||
// Root returns the root logger | |||||
func Root() Logger { | |||||
return root | |||||
} | |||||
// The following functions bypass the exported logger methods (logger.Debug, | |||||
// etc.) to keep the call depth the same for all paths to logger.write so | |||||
// runtime.Caller(2) always refers to the call site in client code. | |||||
// Debug is a convenient alias for Root().Debug | |||||
func Debug(msg string, ctx ...interface{}) { | |||||
root.write(msg, LvlDebug, ctx) | |||||
} | |||||
// Info is a convenient alias for Root().Info | |||||
func Info(msg string, ctx ...interface{}) { | |||||
root.write(msg, LvlInfo, ctx) | |||||
} | |||||
// Warn is a convenient alias for Root().Warn | |||||
func Warn(msg string, ctx ...interface{}) { | |||||
root.write(msg, LvlWarn, ctx) | |||||
} | |||||
// Error is a convenient alias for Root().Error | |||||
func Error(msg string, ctx ...interface{}) { | |||||
root.write(msg, LvlError, ctx) | |||||
} | |||||
// Crit is a convenient alias for Root().Crit | |||||
func Crit(msg string, ctx ...interface{}) { | |||||
root.write(msg, LvlCrit, ctx) | |||||
} |
@ -1,225 +0,0 @@ | |||||
// Package stack implements utilities to capture, manipulate, and format call | |||||
// stacks. | |||||
package stack | |||||
import ( | |||||
"fmt" | |||||
"path/filepath" | |||||
"runtime" | |||||
"strings" | |||||
) | |||||
// Call records a single function invocation from a goroutine stack. It is a | |||||
// wrapper for the program counter values returned by runtime.Caller and | |||||
// runtime.Callers and consumed by runtime.FuncForPC. | |||||
type Call uintptr | |||||
// Format implements fmt.Formatter with support for the following verbs. | |||||
// | |||||
// %s source file | |||||
// %d line number | |||||
// %n function name | |||||
// %v equivalent to %s:%d | |||||
// | |||||
// It accepts the '+' and '#' flags for most of the verbs as follows. | |||||
// | |||||
// %+s path of source file relative to the compile time GOPATH | |||||
// %#s full path of source file | |||||
// %+n import path qualified function name | |||||
// %+v equivalent to %+s:%d | |||||
// %#v equivalent to %#s:%d | |||||
func (pc Call) Format(s fmt.State, c rune) { | |||||
// BUG(ChrisHines): Subtracting one from pc is a work around for | |||||
// https://code.google.com/p/go/issues/detail?id=7690. The idea for this | |||||
// work around comes from rsc's initial patch at | |||||
// https://codereview.appspot.com/84100043/#ps20001, but as noted in the | |||||
// issue discussion, it is not a complete fix since it doesn't handle some | |||||
// cases involving signals. Just the same, it handles all of the other | |||||
// cases I have tested. | |||||
pcFix := uintptr(pc) - 1 | |||||
fn := runtime.FuncForPC(pcFix) | |||||
if fn == nil { | |||||
fmt.Fprintf(s, "%%!%c(NOFUNC)", c) | |||||
return | |||||
} | |||||
switch c { | |||||
case 's', 'v': | |||||
file, line := fn.FileLine(pcFix) | |||||
switch { | |||||
case s.Flag('#'): | |||||
// done | |||||
case s.Flag('+'): | |||||
// Here we want to get the source file path relative to the | |||||
// compile time GOPATH. As of Go 1.3.x there is no direct way to | |||||
// know the compiled GOPATH at runtime, but we can infer the | |||||
// number of path segments in the GOPATH. We note that fn.Name() | |||||
// returns the function name qualified by the import path, which | |||||
// does not include the GOPATH. Thus we can trim segments from the | |||||
// beginning of the file path until the number of path separators | |||||
// remaining is one more than the number of path separators in the | |||||
// function name. For example, given: | |||||
// | |||||
// GOPATH /home/user | |||||
// file /home/user/src/pkg/sub/file.go | |||||
// fn.Name() pkg/sub.Type.Method | |||||
// | |||||
// We want to produce: | |||||
// | |||||
// pkg/sub/file.go | |||||
// | |||||
// From this we can easily see that fn.Name() has one less path | |||||
// separator than our desired output. | |||||
const sep = "/" | |||||
impCnt := strings.Count(fn.Name(), sep) + 1 | |||||
pathCnt := strings.Count(file, sep) | |||||
for pathCnt > impCnt { | |||||
i := strings.Index(file, sep) | |||||
if i == -1 { | |||||
break | |||||
} | |||||
file = file[i+len(sep):] | |||||
pathCnt-- | |||||
} | |||||
default: | |||||
const sep = "/" | |||||
if i := strings.LastIndex(file, sep); i != -1 { | |||||
file = file[i+len(sep):] | |||||
} | |||||
} | |||||
fmt.Fprint(s, file) | |||||
if c == 'v' { | |||||
fmt.Fprint(s, ":", line) | |||||
} | |||||
case 'd': | |||||
_, line := fn.FileLine(pcFix) | |||||
fmt.Fprint(s, line) | |||||
case 'n': | |||||
name := fn.Name() | |||||
if !s.Flag('+') { | |||||
const pathSep = "/" | |||||
if i := strings.LastIndex(name, pathSep); i != -1 { | |||||
name = name[i+len(pathSep):] | |||||
} | |||||
const pkgSep = "." | |||||
if i := strings.Index(name, pkgSep); i != -1 { | |||||
name = name[i+len(pkgSep):] | |||||
} | |||||
} | |||||
fmt.Fprint(s, name) | |||||
} | |||||
} | |||||
// Callers returns a Trace for the current goroutine with element 0 | |||||
// identifying the calling function. | |||||
func Callers() Trace { | |||||
pcs := poolBuf() | |||||
pcs = pcs[:cap(pcs)] | |||||
n := runtime.Callers(2, pcs) | |||||
cs := make([]Call, n) | |||||
for i, pc := range pcs[:n] { | |||||
cs[i] = Call(pc) | |||||
} | |||||
putPoolBuf(pcs) | |||||
return cs | |||||
} | |||||
// name returns the import path qualified name of the function containing the | |||||
// call. | |||||
func (pc Call) name() string { | |||||
pcFix := uintptr(pc) - 1 // work around for go issue #7690 | |||||
fn := runtime.FuncForPC(pcFix) | |||||
if fn == nil { | |||||
return "???" | |||||
} | |||||
return fn.Name() | |||||
} | |||||
func (pc Call) file() string { | |||||
pcFix := uintptr(pc) - 1 // work around for go issue #7690 | |||||
fn := runtime.FuncForPC(pcFix) | |||||
if fn == nil { | |||||
return "???" | |||||
} | |||||
file, _ := fn.FileLine(pcFix) | |||||
return file | |||||
} | |||||
// Trace records a sequence of function invocations from a goroutine stack. | |||||
type Trace []Call | |||||
// Format implements fmt.Formatter by printing the Trace as square brackes ([, | |||||
// ]) surrounding a space separated list of Calls each formatted with the | |||||
// supplied verb and options. | |||||
func (pcs Trace) Format(s fmt.State, c rune) { | |||||
s.Write([]byte("[")) | |||||
for i, pc := range pcs { | |||||
if i > 0 { | |||||
s.Write([]byte(" ")) | |||||
} | |||||
pc.Format(s, c) | |||||
} | |||||
s.Write([]byte("]")) | |||||
} | |||||
// TrimBelow returns a slice of the Trace with all entries below pc removed. | |||||
func (pcs Trace) TrimBelow(pc Call) Trace { | |||||
for len(pcs) > 0 && pcs[0] != pc { | |||||
pcs = pcs[1:] | |||||
} | |||||
return pcs | |||||
} | |||||
// TrimAbove returns a slice of the Trace with all entries above pc removed. | |||||
func (pcs Trace) TrimAbove(pc Call) Trace { | |||||
for len(pcs) > 0 && pcs[len(pcs)-1] != pc { | |||||
pcs = pcs[:len(pcs)-1] | |||||
} | |||||
return pcs | |||||
} | |||||
// TrimBelowName returns a slice of the Trace with all entries below the | |||||
// lowest with function name name removed. | |||||
func (pcs Trace) TrimBelowName(name string) Trace { | |||||
for len(pcs) > 0 && pcs[0].name() != name { | |||||
pcs = pcs[1:] | |||||
} | |||||
return pcs | |||||
} | |||||
// TrimAboveName returns a slice of the Trace with all entries above the | |||||
// highest with function name name removed. | |||||
func (pcs Trace) TrimAboveName(name string) Trace { | |||||
for len(pcs) > 0 && pcs[len(pcs)-1].name() != name { | |||||
pcs = pcs[:len(pcs)-1] | |||||
} | |||||
return pcs | |||||
} | |||||
var goroot string | |||||
func init() { | |||||
goroot = filepath.ToSlash(runtime.GOROOT()) | |||||
if runtime.GOOS == "windows" { | |||||
goroot = strings.ToLower(goroot) | |||||
} | |||||
} | |||||
func inGoroot(path string) bool { | |||||
if runtime.GOOS == "windows" { | |||||
path = strings.ToLower(path) | |||||
} | |||||
return strings.HasPrefix(path, goroot) | |||||
} | |||||
// TrimRuntime returns a slice of the Trace with the topmost entries from the | |||||
// go runtime removed. It considers any calls originating from files under | |||||
// GOROOT as part of the runtime. | |||||
func (pcs Trace) TrimRuntime() Trace { | |||||
for len(pcs) > 0 && inGoroot(pcs[len(pcs)-1].file()) { | |||||
pcs = pcs[:len(pcs)-1] | |||||
} | |||||
return pcs | |||||
} |
@ -1,19 +0,0 @@ | |||||
// +build go1.3 | |||||
package stack | |||||
import ( | |||||
"sync" | |||||
) | |||||
var pcStackPool = sync.Pool{ | |||||
New: func() interface{} { return make([]uintptr, 1000) }, | |||||
} | |||||
func poolBuf() []uintptr { | |||||
return pcStackPool.Get().([]uintptr) | |||||
} | |||||
func putPoolBuf(p []uintptr) { | |||||
pcStackPool.Put(p) | |||||
} |
@ -1,27 +0,0 @@ | |||||
// +build !go1.3 appengine | |||||
package stack | |||||
const ( | |||||
stackPoolSize = 64 | |||||
) | |||||
var ( | |||||
pcStackPool = make(chan []uintptr, stackPoolSize) | |||||
) | |||||
func poolBuf() []uintptr { | |||||
select { | |||||
case p := <-pcStackPool: | |||||
return p | |||||
default: | |||||
return make([]uintptr, 1000) | |||||
} | |||||
} | |||||
func putPoolBuf(p []uintptr) { | |||||
select { | |||||
case pcStackPool <- p: | |||||
default: | |||||
} | |||||
} |
@ -1,55 +0,0 @@ | |||||
// +build !windows,!plan9 | |||||
package log15 | |||||
import ( | |||||
"log/syslog" | |||||
"strings" | |||||
) | |||||
// SyslogHandler opens a connection to the system syslog daemon by calling | |||||
// syslog.New and writes all records to it. | |||||
func SyslogHandler(tag string, fmtr Format) (Handler, error) { | |||||
wr, err := syslog.New(syslog.LOG_INFO, tag) | |||||
return sharedSyslog(fmtr, wr, err) | |||||
} | |||||
// SyslogHandler opens a connection to a log daemon over the network and writes | |||||
// all log records to it. | |||||
func SyslogNetHandler(net, addr string, tag string, fmtr Format) (Handler, error) { | |||||
wr, err := syslog.Dial(net, addr, syslog.LOG_INFO, tag) | |||||
return sharedSyslog(fmtr, wr, err) | |||||
} | |||||
func sharedSyslog(fmtr Format, sysWr *syslog.Writer, err error) (Handler, error) { | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
h := FuncHandler(func(r *Record) error { | |||||
var syslogFn = sysWr.Info | |||||
switch r.Lvl { | |||||
case LvlCrit: | |||||
syslogFn = sysWr.Crit | |||||
case LvlError: | |||||
syslogFn = sysWr.Err | |||||
case LvlWarn: | |||||
syslogFn = sysWr.Warning | |||||
case LvlInfo: | |||||
syslogFn = sysWr.Info | |||||
case LvlDebug: | |||||
syslogFn = sysWr.Debug | |||||
} | |||||
s := strings.TrimSpace(string(fmtr.Format(r))) | |||||
return syslogFn(s) | |||||
}) | |||||
return LazyHandler(&closingHandler{sysWr, h}), nil | |||||
} | |||||
func (m muster) SyslogHandler(tag string, fmtr Format) Handler { | |||||
return must(SyslogHandler(tag, fmtr)) | |||||
} | |||||
func (m muster) SyslogNetHandler(net, addr string, tag string, fmtr Format) Handler { | |||||
return must(SyslogNetHandler(net, addr, tag, fmtr)) | |||||
} |
@ -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. |
@ -1,13 +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 term | |||||
// IsTty always returns false on AppEngine. | |||||
func IsTty(fd uintptr) bool { | |||||
return false | |||||
} |
@ -1,12 +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. | |||||
package term | |||||
import "syscall" | |||||
const ioctlReadTermios = syscall.TIOCGETA | |||||
type Termios syscall.Termios |
@ -1,18 +0,0 @@ | |||||
package term | |||||
import ( | |||||
"syscall" | |||||
) | |||||
const ioctlReadTermios = syscall.TIOCGETA | |||||
// Go 1.2 doesn't include Termios for FreeBSD. This should be added in 1.3 and this could be merged with terminal_darwin. | |||||
type Termios struct { | |||||
Iflag uint32 | |||||
Oflag uint32 | |||||
Cflag uint32 | |||||
Lflag uint32 | |||||
Cc [20]uint8 | |||||
Ispeed uint32 | |||||
Ospeed uint32 | |||||
} |
@ -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 term | |||||
import "syscall" | |||||
const ioctlReadTermios = syscall.TCGETS | |||||
type Termios syscall.Termios |
@ -1,20 +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,!appengine darwin freebsd openbsd | |||||
package term | |||||
import ( | |||||
"syscall" | |||||
"unsafe" | |||||
) | |||||
// IsTty returns true if the given file descriptor is a terminal. | |||||
func IsTty(fd uintptr) bool { | |||||
var termios Termios | |||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) | |||||
return err == 0 | |||||
} |
@ -1,7 +0,0 @@ | |||||
package term | |||||
import "syscall" | |||||
const ioctlReadTermios = syscall.TIOCGETA | |||||
type Termios syscall.Termios |
@ -1,26 +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 | |||||
package term | |||||
import ( | |||||
"syscall" | |||||
"unsafe" | |||||
) | |||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll") | |||||
var ( | |||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode") | |||||
) | |||||
// IsTty returns true if the given file descriptor is a terminal. | |||||
func IsTty(fd uintptr) bool { | |||||
var st uint32 | |||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) | |||||
return r != 0 && e == 0 | |||||
} |
@ -1,43 +0,0 @@ | |||||
# go-colorable | |||||
Colorable writer for windows. | |||||
For example, most of logger packages doesn't show colors on windows. (I know we can do it with ansicon. But I don't want.) | |||||
This package is possible to handle escape sequence for ansi color on windows. | |||||
## Too Bad! | |||||
![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/bad.png) | |||||
## So Good! | |||||
![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/good.png) | |||||
## Usage | |||||
```go | |||||
logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true}) | |||||
logrus.SetOutput(colorable.NewColorableStdout()) | |||||
logrus.Info("succeeded") | |||||
logrus.Warn("not correct") | |||||
logrus.Error("something error") | |||||
logrus.Fatal("panic") | |||||
``` | |||||
You can compile above code on non-windows OSs. | |||||
## Installation | |||||
``` | |||||
$ go get github.com/mattn/go-colorable | |||||
``` | |||||
# License | |||||
MIT | |||||
# Author | |||||
Yasuhiro Matsumoto (a.k.a mattn) |
@ -1,16 +0,0 @@ | |||||
// +build !windows | |||||
package colorable | |||||
import ( | |||||
"io" | |||||
"os" | |||||
) | |||||
func NewColorableStdout() io.Writer { | |||||
return os.Stdout | |||||
} | |||||
func NewColorableStderr() io.Writer { | |||||
return os.Stderr | |||||
} |
@ -1,782 +0,0 @@ | |||||
package colorable | |||||
import ( | |||||
"bytes" | |||||
"fmt" | |||||
"io" | |||||
"math" | |||||
"os" | |||||
"strconv" | |||||
"strings" | |||||
"syscall" | |||||
"unsafe" | |||||
"github.com/mattn/go-isatty" | |||||
) | |||||
const ( | |||||
foregroundBlue = 0x1 | |||||
foregroundGreen = 0x2 | |||||
foregroundRed = 0x4 | |||||
foregroundIntensity = 0x8 | |||||
foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity) | |||||
backgroundBlue = 0x10 | |||||
backgroundGreen = 0x20 | |||||
backgroundRed = 0x40 | |||||
backgroundIntensity = 0x80 | |||||
backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity) | |||||
) | |||||
type wchar uint16 | |||||
type short int16 | |||||
type dword uint32 | |||||
type word uint16 | |||||
type coord struct { | |||||
x short | |||||
y short | |||||
} | |||||
type smallRect struct { | |||||
left short | |||||
top short | |||||
right short | |||||
bottom short | |||||
} | |||||
type consoleScreenBufferInfo struct { | |||||
size coord | |||||
cursorPosition coord | |||||
attributes word | |||||
window smallRect | |||||
maximumWindowSize coord | |||||
} | |||||
var ( | |||||
kernel32 = syscall.NewLazyDLL("kernel32.dll") | |||||
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") | |||||
procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") | |||||
procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") | |||||
procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") | |||||
procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute") | |||||
) | |||||
type Writer struct { | |||||
out io.Writer | |||||
handle syscall.Handle | |||||
lastbuf bytes.Buffer | |||||
oldattr word | |||||
} | |||||
func NewColorableStdout() io.Writer { | |||||
var csbi consoleScreenBufferInfo | |||||
out := os.Stdout | |||||
if !isatty.IsTerminal(out.Fd()) { | |||||
return out | |||||
} | |||||
handle := syscall.Handle(out.Fd()) | |||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) | |||||
return &Writer{out: out, handle: handle, oldattr: csbi.attributes} | |||||
} | |||||
func NewColorableStderr() io.Writer { | |||||
var csbi consoleScreenBufferInfo | |||||
out := os.Stderr | |||||
if !isatty.IsTerminal(out.Fd()) { | |||||
return out | |||||
} | |||||
handle := syscall.Handle(out.Fd()) | |||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) | |||||
return &Writer{out: out, handle: handle, oldattr: csbi.attributes} | |||||
} | |||||
var color256 = map[int]int{ | |||||
0: 0x000000, | |||||
1: 0x800000, | |||||
2: 0x008000, | |||||
3: 0x808000, | |||||
4: 0x000080, | |||||
5: 0x800080, | |||||
6: 0x008080, | |||||
7: 0xc0c0c0, | |||||
8: 0x808080, | |||||
9: 0xff0000, | |||||
10: 0x00ff00, | |||||
11: 0xffff00, | |||||
12: 0x0000ff, | |||||
13: 0xff00ff, | |||||
14: 0x00ffff, | |||||
15: 0xffffff, | |||||
16: 0x000000, | |||||
17: 0x00005f, | |||||
18: 0x000087, | |||||
19: 0x0000af, | |||||
20: 0x0000d7, | |||||
21: 0x0000ff, | |||||
22: 0x005f00, | |||||
23: 0x005f5f, | |||||
24: 0x005f87, | |||||
25: 0x005faf, | |||||
26: 0x005fd7, | |||||
27: 0x005fff, | |||||
28: 0x008700, | |||||
29: 0x00875f, | |||||
30: 0x008787, | |||||
31: 0x0087af, | |||||
32: 0x0087d7, | |||||
33: 0x0087ff, | |||||
34: 0x00af00, | |||||
35: 0x00af5f, | |||||
36: 0x00af87, | |||||
37: 0x00afaf, | |||||
38: 0x00afd7, | |||||
39: 0x00afff, | |||||
40: 0x00d700, | |||||
41: 0x00d75f, | |||||
42: 0x00d787, | |||||
43: 0x00d7af, | |||||
44: 0x00d7d7, | |||||
45: 0x00d7ff, | |||||
46: 0x00ff00, | |||||
47: 0x00ff5f, | |||||
48: 0x00ff87, | |||||
49: 0x00ffaf, | |||||
50: 0x00ffd7, | |||||
51: 0x00ffff, | |||||
52: 0x5f0000, | |||||
53: 0x5f005f, | |||||
54: 0x5f0087, | |||||
55: 0x5f00af, | |||||
56: 0x5f00d7, | |||||
57: 0x5f00ff, | |||||
58: 0x5f5f00, | |||||
59: 0x5f5f5f, | |||||
60: 0x5f5f87, | |||||
61: 0x5f5faf, | |||||
62: 0x5f5fd7, | |||||
63: 0x5f5fff, | |||||
64: 0x5f8700, | |||||
65: 0x5f875f, | |||||
66: 0x5f8787, | |||||
67: 0x5f87af, | |||||
68: 0x5f87d7, | |||||
69: 0x5f87ff, | |||||
70: 0x5faf00, | |||||
71: 0x5faf5f, | |||||
72: 0x5faf87, | |||||
73: 0x5fafaf, | |||||
74: 0x5fafd7, | |||||
75: 0x5fafff, | |||||
76: 0x5fd700, | |||||
77: 0x5fd75f, | |||||
78: 0x5fd787, | |||||
79: 0x5fd7af, | |||||
80: 0x5fd7d7, | |||||
81: 0x5fd7ff, | |||||
82: 0x5fff00, | |||||
83: 0x5fff5f, | |||||
84: 0x5fff87, | |||||
85: 0x5fffaf, | |||||
86: 0x5fffd7, | |||||
87: 0x5fffff, | |||||
88: 0x870000, | |||||
89: 0x87005f, | |||||
90: 0x870087, | |||||
91: 0x8700af, | |||||
92: 0x8700d7, | |||||
93: 0x8700ff, | |||||
94: 0x875f00, | |||||
95: 0x875f5f, | |||||
96: 0x875f87, | |||||
97: 0x875faf, | |||||
98: 0x875fd7, | |||||
99: 0x875fff, | |||||
100: 0x878700, | |||||
101: 0x87875f, | |||||
102: 0x878787, | |||||
103: 0x8787af, | |||||
104: 0x8787d7, | |||||
105: 0x8787ff, | |||||
106: 0x87af00, | |||||
107: 0x87af5f, | |||||
108: 0x87af87, | |||||
109: 0x87afaf, | |||||
110: 0x87afd7, | |||||
111: 0x87afff, | |||||
112: 0x87d700, | |||||
113: 0x87d75f, | |||||
114: 0x87d787, | |||||
115: 0x87d7af, | |||||
116: 0x87d7d7, | |||||
117: 0x87d7ff, | |||||
118: 0x87ff00, | |||||
119: 0x87ff5f, | |||||
120: 0x87ff87, | |||||
121: 0x87ffaf, | |||||
122: 0x87ffd7, | |||||
123: 0x87ffff, | |||||
124: 0xaf0000, | |||||
125: 0xaf005f, | |||||
126: 0xaf0087, | |||||
127: 0xaf00af, | |||||
128: 0xaf00d7, | |||||
129: 0xaf00ff, | |||||
130: 0xaf5f00, | |||||
131: 0xaf5f5f, | |||||
132: 0xaf5f87, | |||||
133: 0xaf5faf, | |||||
134: 0xaf5fd7, | |||||
135: 0xaf5fff, | |||||
136: 0xaf8700, | |||||
137: 0xaf875f, | |||||
138: 0xaf8787, | |||||
139: 0xaf87af, | |||||
140: 0xaf87d7, | |||||
141: 0xaf87ff, | |||||
142: 0xafaf00, | |||||
143: 0xafaf5f, | |||||
144: 0xafaf87, | |||||
145: 0xafafaf, | |||||
146: 0xafafd7, | |||||
147: 0xafafff, | |||||
148: 0xafd700, | |||||
149: 0xafd75f, | |||||
150: 0xafd787, | |||||
151: 0xafd7af, | |||||
152: 0xafd7d7, | |||||
153: 0xafd7ff, | |||||
154: 0xafff00, | |||||
155: 0xafff5f, | |||||
156: 0xafff87, | |||||
157: 0xafffaf, | |||||
158: 0xafffd7, | |||||
159: 0xafffff, | |||||
160: 0xd70000, | |||||
161: 0xd7005f, | |||||
162: 0xd70087, | |||||
163: 0xd700af, | |||||
164: 0xd700d7, | |||||
165: 0xd700ff, | |||||
166: 0xd75f00, | |||||
167: 0xd75f5f, | |||||
168: 0xd75f87, | |||||
169: 0xd75faf, | |||||
170: 0xd75fd7, | |||||
171: 0xd75fff, | |||||
172: 0xd78700, | |||||
173: 0xd7875f, | |||||
174: 0xd78787, | |||||
175: 0xd787af, | |||||
176: 0xd787d7, | |||||
177: 0xd787ff, | |||||
178: 0xd7af00, | |||||
179: 0xd7af5f, | |||||
180: 0xd7af87, | |||||
181: 0xd7afaf, | |||||
182: 0xd7afd7, | |||||
183: 0xd7afff, | |||||
184: 0xd7d700, | |||||
185: 0xd7d75f, | |||||
186: 0xd7d787, | |||||
187: 0xd7d7af, | |||||
188: 0xd7d7d7, | |||||
189: 0xd7d7ff, | |||||
190: 0xd7ff00, | |||||
191: 0xd7ff5f, | |||||
192: 0xd7ff87, | |||||
193: 0xd7ffaf, | |||||
194: 0xd7ffd7, | |||||
195: 0xd7ffff, | |||||
196: 0xff0000, | |||||
197: 0xff005f, | |||||
198: 0xff0087, | |||||
199: 0xff00af, | |||||
200: 0xff00d7, | |||||
201: 0xff00ff, | |||||
202: 0xff5f00, | |||||
203: 0xff5f5f, | |||||
204: 0xff5f87, | |||||
205: 0xff5faf, | |||||
206: 0xff5fd7, | |||||
207: 0xff5fff, | |||||
208: 0xff8700, | |||||
209: 0xff875f, | |||||
210: 0xff8787, | |||||
211: 0xff87af, | |||||
212: 0xff87d7, | |||||
213: 0xff87ff, | |||||
214: 0xffaf00, | |||||
215: 0xffaf5f, | |||||
216: 0xffaf87, | |||||
217: 0xffafaf, | |||||
218: 0xffafd7, | |||||
219: 0xffafff, | |||||
220: 0xffd700, | |||||
221: 0xffd75f, | |||||
222: 0xffd787, | |||||
223: 0xffd7af, | |||||
224: 0xffd7d7, | |||||
225: 0xffd7ff, | |||||
226: 0xffff00, | |||||
227: 0xffff5f, | |||||
228: 0xffff87, | |||||
229: 0xffffaf, | |||||
230: 0xffffd7, | |||||
231: 0xffffff, | |||||
232: 0x080808, | |||||
233: 0x121212, | |||||
234: 0x1c1c1c, | |||||
235: 0x262626, | |||||
236: 0x303030, | |||||
237: 0x3a3a3a, | |||||
238: 0x444444, | |||||
239: 0x4e4e4e, | |||||
240: 0x585858, | |||||
241: 0x626262, | |||||
242: 0x6c6c6c, | |||||
243: 0x767676, | |||||
244: 0x808080, | |||||
245: 0x8a8a8a, | |||||
246: 0x949494, | |||||
247: 0x9e9e9e, | |||||
248: 0xa8a8a8, | |||||
249: 0xb2b2b2, | |||||
250: 0xbcbcbc, | |||||
251: 0xc6c6c6, | |||||
252: 0xd0d0d0, | |||||
253: 0xdadada, | |||||
254: 0xe4e4e4, | |||||
255: 0xeeeeee, | |||||
} | |||||
func (w *Writer) Write(data []byte) (n int, err error) { | |||||
var csbi consoleScreenBufferInfo | |||||
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) | |||||
er := bytes.NewBuffer(data) | |||||
loop: | |||||
for { | |||||
r1, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) | |||||
if r1 == 0 { | |||||
break loop | |||||
} | |||||
c1, _, err := er.ReadRune() | |||||
if err != nil { | |||||
break loop | |||||
} | |||||
if c1 != 0x1b { | |||||
fmt.Fprint(w.out, string(c1)) | |||||
continue | |||||
} | |||||
c2, _, err := er.ReadRune() | |||||
if err != nil { | |||||
w.lastbuf.WriteRune(c1) | |||||
break loop | |||||
} | |||||
if c2 != 0x5b { | |||||
w.lastbuf.WriteRune(c1) | |||||
w.lastbuf.WriteRune(c2) | |||||
continue | |||||
} | |||||
var buf bytes.Buffer | |||||
var m rune | |||||
for { | |||||
c, _, err := er.ReadRune() | |||||
if err != nil { | |||||
w.lastbuf.WriteRune(c1) | |||||
w.lastbuf.WriteRune(c2) | |||||
w.lastbuf.Write(buf.Bytes()) | |||||
break loop | |||||
} | |||||
if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { | |||||
m = c | |||||
break | |||||
} | |||||
buf.Write([]byte(string(c))) | |||||
} | |||||
var csbi consoleScreenBufferInfo | |||||
switch m { | |||||
case 'A': | |||||
n, err = strconv.Atoi(buf.String()) | |||||
if err != nil { | |||||
continue | |||||
} | |||||
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) | |||||
csbi.cursorPosition.y -= short(n) | |||||
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) | |||||
case 'B': | |||||
n, err = strconv.Atoi(buf.String()) | |||||
if err != nil { | |||||
continue | |||||
} | |||||
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) | |||||
csbi.cursorPosition.y += short(n) | |||||
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) | |||||
case 'C': | |||||
n, err = strconv.Atoi(buf.String()) | |||||
if err != nil { | |||||
continue | |||||
} | |||||
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) | |||||
csbi.cursorPosition.x -= short(n) | |||||
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) | |||||
case 'D': | |||||
n, err = strconv.Atoi(buf.String()) | |||||
if err != nil { | |||||
continue | |||||
} | |||||
if n, err = strconv.Atoi(buf.String()); err == nil { | |||||
var csbi consoleScreenBufferInfo | |||||
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) | |||||
csbi.cursorPosition.x += short(n) | |||||
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) | |||||
} | |||||
case 'E': | |||||
n, err = strconv.Atoi(buf.String()) | |||||
if err != nil { | |||||
continue | |||||
} | |||||
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) | |||||
csbi.cursorPosition.x = 0 | |||||
csbi.cursorPosition.y += short(n) | |||||
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) | |||||
case 'F': | |||||
n, err = strconv.Atoi(buf.String()) | |||||
if err != nil { | |||||
continue | |||||
} | |||||
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) | |||||
csbi.cursorPosition.x = 0 | |||||
csbi.cursorPosition.y -= short(n) | |||||
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) | |||||
case 'G': | |||||
n, err = strconv.Atoi(buf.String()) | |||||
if err != nil { | |||||
continue | |||||
} | |||||
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) | |||||
csbi.cursorPosition.x = short(n) | |||||
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) | |||||
case 'H': | |||||
token := strings.Split(buf.String(), ";") | |||||
if len(token) != 2 { | |||||
continue | |||||
} | |||||
n1, err := strconv.Atoi(token[0]) | |||||
if err != nil { | |||||
continue | |||||
} | |||||
n2, err := strconv.Atoi(token[1]) | |||||
if err != nil { | |||||
continue | |||||
} | |||||
csbi.cursorPosition.x = short(n2) | |||||
csbi.cursorPosition.x = short(n1) | |||||
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) | |||||
case 'J': | |||||
n, err := strconv.Atoi(buf.String()) | |||||
if err != nil { | |||||
continue | |||||
} | |||||
var cursor coord | |||||
switch n { | |||||
case 0: | |||||
cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} | |||||
case 1: | |||||
cursor = coord{x: csbi.window.left, y: csbi.window.top} | |||||
case 2: | |||||
cursor = coord{x: csbi.window.left, y: csbi.window.top} | |||||
} | |||||
var count, written dword | |||||
count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x) | |||||
procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) | |||||
procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) | |||||
case 'K': | |||||
n, err := strconv.Atoi(buf.String()) | |||||
if err != nil { | |||||
continue | |||||
} | |||||
var cursor coord | |||||
switch n { | |||||
case 0: | |||||
cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} | |||||
case 1: | |||||
cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} | |||||
case 2: | |||||
cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} | |||||
} | |||||
var count, written dword | |||||
count = dword(csbi.size.x - csbi.cursorPosition.x) | |||||
procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) | |||||
procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) | |||||
case 'm': | |||||
attr := csbi.attributes | |||||
cs := buf.String() | |||||
if cs == "" { | |||||
procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.oldattr)) | |||||
continue | |||||
} | |||||
token := strings.Split(cs, ";") | |||||
for i := 0; i < len(token); i += 1 { | |||||
ns := token[i] | |||||
if n, err = strconv.Atoi(ns); err == nil { | |||||
switch { | |||||
case n == 0 || n == 100: | |||||
attr = w.oldattr | |||||
case 1 <= n && n <= 5: | |||||
attr |= foregroundIntensity | |||||
case n == 7: | |||||
attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) | |||||
case 22 == n || n == 25 || n == 25: | |||||
attr |= foregroundIntensity | |||||
case n == 27: | |||||
attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) | |||||
case 30 <= n && n <= 37: | |||||
attr = (attr & backgroundMask) | |||||
if (n-30)&1 != 0 { | |||||
attr |= foregroundRed | |||||
} | |||||
if (n-30)&2 != 0 { | |||||
attr |= foregroundGreen | |||||
} | |||||
if (n-30)&4 != 0 { | |||||
attr |= foregroundBlue | |||||
} | |||||
case n == 38: // set foreground color. | |||||
if i < len(token)-2 && (token[i+1] == "5" || token[i+1] == "05") { | |||||
if n256, err := strconv.Atoi(token[i+2]); err == nil { | |||||
if n256foreAttr == nil { | |||||
n256setup() | |||||
} | |||||
attr &= backgroundMask | |||||
attr |= n256foreAttr[n256] | |||||
i += 2 | |||||
} | |||||
} else { | |||||
attr = attr & (w.oldattr & backgroundMask) | |||||
} | |||||
case n == 39: // reset foreground color. | |||||
attr &= backgroundMask | |||||
attr |= w.oldattr & foregroundMask | |||||
case 40 <= n && n <= 47: | |||||
attr = (attr & foregroundMask) | |||||
if (n-40)&1 != 0 { | |||||
attr |= backgroundRed | |||||
} | |||||
if (n-40)&2 != 0 { | |||||
attr |= backgroundGreen | |||||
} | |||||
if (n-40)&4 != 0 { | |||||
attr |= backgroundBlue | |||||
} | |||||
case n == 48: // set background color. | |||||
if i < len(token)-2 && token[i+1] == "5" { | |||||
if n256, err := strconv.Atoi(token[i+2]); err == nil { | |||||
if n256backAttr == nil { | |||||
n256setup() | |||||
} | |||||
attr &= foregroundMask | |||||
attr |= n256backAttr[n256] | |||||
i += 2 | |||||
} | |||||
} else { | |||||
attr = attr & (w.oldattr & foregroundMask) | |||||
} | |||||
case n == 49: // reset foreground color. | |||||
attr &= foregroundMask | |||||
attr |= w.oldattr & backgroundMask | |||||
case 90 <= n && n <= 97: | |||||
attr = (attr & backgroundMask) | |||||
attr |= foregroundIntensity | |||||
if (n-90)&1 != 0 { | |||||
attr |= foregroundRed | |||||
} | |||||
if (n-90)&2 != 0 { | |||||
attr |= foregroundGreen | |||||
} | |||||
if (n-90)&4 != 0 { | |||||
attr |= foregroundBlue | |||||
} | |||||
case 100 <= n && n <= 107: | |||||
attr = (attr & foregroundMask) | |||||
attr |= backgroundIntensity | |||||
if (n-100)&1 != 0 { | |||||
attr |= backgroundRed | |||||
} | |||||
if (n-100)&2 != 0 { | |||||
attr |= backgroundGreen | |||||
} | |||||
if (n-100)&4 != 0 { | |||||
attr |= backgroundBlue | |||||
} | |||||
} | |||||
procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr)) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
return len(data) - w.lastbuf.Len(), nil | |||||
} | |||||
type consoleColor struct { | |||||
rgb int | |||||
red bool | |||||
green bool | |||||
blue bool | |||||
intensity bool | |||||
} | |||||
func (c consoleColor) foregroundAttr() (attr word) { | |||||
if c.red { | |||||
attr |= foregroundRed | |||||
} | |||||
if c.green { | |||||
attr |= foregroundGreen | |||||
} | |||||
if c.blue { | |||||
attr |= foregroundBlue | |||||
} | |||||
if c.intensity { | |||||
attr |= foregroundIntensity | |||||
} | |||||
return | |||||
} | |||||
func (c consoleColor) backgroundAttr() (attr word) { | |||||
if c.red { | |||||
attr |= backgroundRed | |||||
} | |||||
if c.green { | |||||
attr |= backgroundGreen | |||||
} | |||||
if c.blue { | |||||
attr |= backgroundBlue | |||||
} | |||||
if c.intensity { | |||||
attr |= backgroundIntensity | |||||
} | |||||
return | |||||
} | |||||
var color16 = []consoleColor{ | |||||
consoleColor{0x000000, false, false, false, false}, | |||||
consoleColor{0x000080, false, false, true, false}, | |||||
consoleColor{0x008000, false, true, false, false}, | |||||
consoleColor{0x008080, false, true, true, false}, | |||||
consoleColor{0x800000, true, false, false, false}, | |||||
consoleColor{0x800080, true, false, true, false}, | |||||
consoleColor{0x808000, true, true, false, false}, | |||||
consoleColor{0xc0c0c0, true, true, true, false}, | |||||
consoleColor{0x808080, false, false, false, true}, | |||||
consoleColor{0x0000ff, false, false, true, true}, | |||||
consoleColor{0x00ff00, false, true, false, true}, | |||||
consoleColor{0x00ffff, false, true, true, true}, | |||||
consoleColor{0xff0000, true, false, false, true}, | |||||
consoleColor{0xff00ff, true, false, true, true}, | |||||
consoleColor{0xffff00, true, true, false, true}, | |||||
consoleColor{0xffffff, true, true, true, true}, | |||||
} | |||||
type hsv struct { | |||||
h, s, v float32 | |||||
} | |||||
func (a hsv) dist(b hsv) float32 { | |||||
dh := a.h - b.h | |||||
switch { | |||||
case dh > 0.5: | |||||
dh = 1 - dh | |||||
case dh < -0.5: | |||||
dh = -1 - dh | |||||
} | |||||
ds := a.s - b.s | |||||
dv := a.v - b.v | |||||
return float32(math.Sqrt(float64(dh*dh + ds*ds + dv*dv))) | |||||
} | |||||
func toHSV(rgb int) hsv { | |||||
r, g, b := float32((rgb&0xFF0000)>>16)/256.0, | |||||
float32((rgb&0x00FF00)>>8)/256.0, | |||||
float32(rgb&0x0000FF)/256.0 | |||||
min, max := minmax3f(r, g, b) | |||||
h := max - min | |||||
if h > 0 { | |||||
if max == r { | |||||
h = (g - b) / h | |||||
if h < 0 { | |||||
h += 6 | |||||
} | |||||
} else if max == g { | |||||
h = 2 + (b-r)/h | |||||
} else { | |||||
h = 4 + (r-g)/h | |||||
} | |||||
} | |||||
h /= 6.0 | |||||
s := max - min | |||||
if max != 0 { | |||||
s /= max | |||||
} | |||||
v := max | |||||
return hsv{h: h, s: s, v: v} | |||||
} | |||||
type hsvTable []hsv | |||||
func toHSVTable(rgbTable []consoleColor) hsvTable { | |||||
t := make(hsvTable, len(rgbTable)) | |||||
for i, c := range rgbTable { | |||||
t[i] = toHSV(c.rgb) | |||||
} | |||||
return t | |||||
} | |||||
func (t hsvTable) find(rgb int) consoleColor { | |||||
hsv := toHSV(rgb) | |||||
n := 7 | |||||
l := float32(5.0) | |||||
for i, p := range t { | |||||
d := hsv.dist(p) | |||||
if d < l { | |||||
l, n = d, i | |||||
} | |||||
} | |||||
return color16[n] | |||||
} | |||||
func minmax3f(a, b, c float32) (min, max float32) { | |||||
if a < b { | |||||
if b < c { | |||||
return a, c | |||||
} else if a < c { | |||||
return a, b | |||||
} else { | |||||
return c, b | |||||
} | |||||
} else { | |||||
if a < c { | |||||
return b, c | |||||
} else if b < c { | |||||
return b, a | |||||
} else { | |||||
return c, a | |||||
} | |||||
} | |||||
} | |||||
var n256foreAttr []word | |||||
var n256backAttr []word | |||||
func n256setup() { | |||||
n256foreAttr = make([]word, 256) | |||||
n256backAttr = make([]word, 256) | |||||
t := toHSVTable(color16) | |||||
for i, rgb := range color256 { | |||||
c := t.find(rgb) | |||||
n256foreAttr[i] = c.foregroundAttr() | |||||
n256backAttr[i] = c.backgroundAttr() | |||||
} | |||||
} |
@ -1,9 +0,0 @@ | |||||
Copyright (c) Yasuhiro MATSUMOTO <mattn.jp@gmail.com> | |||||
MIT License (Expat) | |||||
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. |
@ -1,37 +0,0 @@ | |||||
# go-isatty | |||||
isatty for golang | |||||
## Usage | |||||
```go | |||||
package main | |||||
import ( | |||||
"fmt" | |||||
"github.com/mattn/go-isatty" | |||||
"os" | |||||
) | |||||
func main() { | |||||
if isatty.IsTerminal(os.Stdout.Fd()) { | |||||
fmt.Println("Is Terminal") | |||||
} else { | |||||
fmt.Println("Is Not Terminal") | |||||
} | |||||
} | |||||
``` | |||||
## Installation | |||||
``` | |||||
$ go get github.com/mattn/go-isatty | |||||
``` | |||||
# License | |||||
MIT | |||||
# Author | |||||
Yasuhiro Matsumoto (a.k.a mattn) |
@ -1,2 +0,0 @@ | |||||
// Package isatty implements interface to isatty | |||||
package isatty |
@ -1,9 +0,0 @@ | |||||
// +build appengine | |||||
package isatty | |||||
// IsTerminal returns true if the file descriptor is terminal which | |||||
// is always false on on appengine classic which is a sandboxed PaaS. | |||||
func IsTerminal(fd uintptr) bool { | |||||
return false | |||||
} |
@ -1,18 +0,0 @@ | |||||
// +build darwin freebsd openbsd netbsd | |||||
// +build !appengine | |||||
package isatty | |||||
import ( | |||||
"syscall" | |||||
"unsafe" | |||||
) | |||||
const ioctlReadTermios = syscall.TIOCGETA | |||||
// IsTerminal return true if the file descriptor is terminal. | |||||
func IsTerminal(fd uintptr) bool { | |||||
var termios syscall.Termios | |||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) | |||||
return err == 0 | |||||
} |
@ -1,18 +0,0 @@ | |||||
// +build linux | |||||
// +build !appengine | |||||
package isatty | |||||
import ( | |||||
"syscall" | |||||
"unsafe" | |||||
) | |||||
const ioctlReadTermios = syscall.TCGETS | |||||
// IsTerminal return true if the file descriptor is terminal. | |||||
func IsTerminal(fd uintptr) bool { | |||||
var termios syscall.Termios | |||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) | |||||
return err == 0 | |||||
} |
@ -1,16 +0,0 @@ | |||||
// +build solaris | |||||
// +build !appengine | |||||
package isatty | |||||
import ( | |||||
"golang.org/x/sys/unix" | |||||
) | |||||
// IsTerminal returns true if the given file descriptor is a terminal. | |||||
// see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c | |||||
func IsTerminal(fd uintptr) bool { | |||||
var termio unix.Termio | |||||
err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio) | |||||
return err == nil | |||||
} |
@ -1,19 +0,0 @@ | |||||
// +build windows | |||||
// +build !appengine | |||||
package isatty | |||||
import ( | |||||
"syscall" | |||||
"unsafe" | |||||
) | |||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll") | |||||
var procGetConsoleMode = kernel32.NewProc("GetConsoleMode") | |||||
// IsTerminal return true if the file descriptor is terminal. | |||||
func IsTerminal(fd uintptr) bool { | |||||
var st uint32 | |||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) | |||||
return r != 0 && e == 0 | |||||
} |
@ -1,9 +0,0 @@ | |||||
language: go | |||||
go: | |||||
- 1.4 | |||||
- 1.5 | |||||
- tip | |||||
install: | |||||
- go get -v github.com/naoina/go-stringutil | |||||
script: | |||||
- go test -v -bench . -benchmem ./... |
@ -1,19 +0,0 @@ | |||||
Copyright (c) 2015 Naoya Inada <naoina@kuune.org> | |||||
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. |
@ -1,13 +0,0 @@ | |||||
# stringutil [![Build Status](https://travis-ci.org/naoina/go-stringutil.svg?branch=master)](https://travis-ci.org/naoina/go-stringutil) | |||||
## Installation | |||||
go get -u github.com/naoina/go-stringutil | |||||
## Documentation | |||||
See https://godoc.org/github.com/naoina/go-stringutil | |||||
## License | |||||
MIT |
@ -1,253 +0,0 @@ | |||||
package stringutil | |||||
import ( | |||||
"fmt" | |||||
"sort" | |||||
"unicode/utf8" | |||||
) | |||||
const ( | |||||
terminationCharacter = '#' | |||||
) | |||||
func mustDoubleArray(da *doubleArray, err error) *doubleArray { | |||||
if err != nil { | |||||
panic(err) | |||||
} | |||||
return da | |||||
} | |||||
func (da *doubleArray) Build(keys []string) error { | |||||
records := makeRecords(keys) | |||||
if err := da.build(records, 1, 0, make(map[int]struct{})); err != nil { | |||||
return err | |||||
} | |||||
return nil | |||||
} | |||||
type doubleArray struct { | |||||
bc []baseCheck | |||||
node []int | |||||
} | |||||
func newDoubleArray(keys []string) (*doubleArray, error) { | |||||
da := &doubleArray{ | |||||
bc: []baseCheck{0}, | |||||
node: []int{-1}, // A start index is adjusting to 1 because 0 will be used as a mark of non-existent node. | |||||
} | |||||
if err := da.Build(keys); err != nil { | |||||
return nil, err | |||||
} | |||||
return da, nil | |||||
} | |||||
// baseCheck contains BASE, CHECK and Extra flags. | |||||
// From the top, 22bits of BASE, 2bits of Extra flags and 8bits of CHECK. | |||||
// | |||||
// BASE (22bit) | Extra flags (2bit) | CHECK (8bit) | |||||
// |----------------------|--|--------| | |||||
// 32 10 8 0 | |||||
type baseCheck uint32 | |||||
func (bc baseCheck) Base() int { | |||||
return int(bc >> 10) | |||||
} | |||||
func (bc *baseCheck) SetBase(base int) { | |||||
*bc |= baseCheck(base) << 10 | |||||
} | |||||
func (bc baseCheck) Check() byte { | |||||
return byte(bc) | |||||
} | |||||
func (bc *baseCheck) SetCheck(check byte) { | |||||
*bc |= baseCheck(check) | |||||
} | |||||
func (bc baseCheck) IsEmpty() bool { | |||||
return bc&0xfffffcff == 0 | |||||
} | |||||
func (da *doubleArray) Lookup(path string) (length int) { | |||||
idx := 1 | |||||
tmpIdx := idx | |||||
for i := 0; i < len(path); i++ { | |||||
c := path[i] | |||||
tmpIdx = da.nextIndex(da.bc[tmpIdx].Base(), c) | |||||
if tmpIdx >= len(da.bc) || da.bc[tmpIdx].Check() != c { | |||||
break | |||||
} | |||||
idx = tmpIdx | |||||
} | |||||
if next := da.nextIndex(da.bc[idx].Base(), terminationCharacter); next < len(da.bc) && da.bc[next].Check() == terminationCharacter { | |||||
return da.node[da.bc[next].Base()] | |||||
} | |||||
return -1 | |||||
} | |||||
func (da *doubleArray) LookupByBytes(path []byte) (length int) { | |||||
idx := 1 | |||||
tmpIdx := idx | |||||
for i := 0; i < len(path); i++ { | |||||
c := path[i] | |||||
tmpIdx = da.nextIndex(da.bc[tmpIdx].Base(), c) | |||||
if tmpIdx >= len(da.bc) || da.bc[tmpIdx].Check() != c { | |||||
break | |||||
} | |||||
idx = tmpIdx | |||||
} | |||||
if next := da.nextIndex(da.bc[idx].Base(), terminationCharacter); next < len(da.bc) && da.bc[next].Check() == terminationCharacter { | |||||
return da.node[da.bc[next].Base()] | |||||
} | |||||
return -1 | |||||
} | |||||
func (da *doubleArray) build(srcs []record, idx, depth int, usedBase map[int]struct{}) error { | |||||
sort.Stable(recordSlice(srcs)) | |||||
base, siblings, leaf, err := da.arrange(srcs, idx, depth, usedBase) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
if leaf != nil { | |||||
da.bc[idx].SetBase(len(da.node)) | |||||
da.node = append(da.node, leaf.value) | |||||
} | |||||
for _, sib := range siblings { | |||||
da.setCheck(da.nextIndex(base, sib.c), sib.c) | |||||
} | |||||
for _, sib := range siblings { | |||||
if err := da.build(srcs[sib.start:sib.end], da.nextIndex(base, sib.c), depth+1, usedBase); err != nil { | |||||
return err | |||||
} | |||||
} | |||||
return nil | |||||
} | |||||
func (da *doubleArray) setBase(i, base int) { | |||||
da.bc[i].SetBase(base) | |||||
} | |||||
func (da *doubleArray) setCheck(i int, check byte) { | |||||
da.bc[i].SetCheck(check) | |||||
} | |||||
func (da *doubleArray) findEmptyIndex(start int) int { | |||||
i := start | |||||
for ; i < len(da.bc); i++ { | |||||
if da.bc[i].IsEmpty() { | |||||
break | |||||
} | |||||
} | |||||
return i | |||||
} | |||||
// findBase returns good BASE. | |||||
func (da *doubleArray) findBase(siblings []sibling, start int, usedBase map[int]struct{}) (base int) { | |||||
for idx, firstChar := start+1, siblings[0].c; ; idx = da.findEmptyIndex(idx + 1) { | |||||
base = da.nextIndex(idx, firstChar) | |||||
if _, used := usedBase[base]; used { | |||||
continue | |||||
} | |||||
i := 0 | |||||
for ; i < len(siblings); i++ { | |||||
next := da.nextIndex(base, siblings[i].c) | |||||
if len(da.bc) <= next { | |||||
da.bc = append(da.bc, make([]baseCheck, next-len(da.bc)+1)...) | |||||
} | |||||
if !da.bc[next].IsEmpty() { | |||||
break | |||||
} | |||||
} | |||||
if i == len(siblings) { | |||||
break | |||||
} | |||||
} | |||||
usedBase[base] = struct{}{} | |||||
return base | |||||
} | |||||
func (da *doubleArray) arrange(records []record, idx, depth int, usedBase map[int]struct{}) (base int, siblings []sibling, leaf *record, err error) { | |||||
siblings, leaf, err = makeSiblings(records, depth) | |||||
if err != nil { | |||||
return -1, nil, nil, err | |||||
} | |||||
if len(siblings) < 1 { | |||||
return -1, nil, leaf, nil | |||||
} | |||||
base = da.findBase(siblings, idx, usedBase) | |||||
da.setBase(idx, base) | |||||
return base, siblings, leaf, err | |||||
} | |||||
type sibling struct { | |||||
start int | |||||
end int | |||||
c byte | |||||
} | |||||
func (da *doubleArray) nextIndex(base int, c byte) int { | |||||
return base ^ int(c) | |||||
} | |||||
func makeSiblings(records []record, depth int) (sib []sibling, leaf *record, err error) { | |||||
var ( | |||||
pc byte | |||||
n int | |||||
) | |||||
for i, r := range records { | |||||
if len(r.key) <= depth { | |||||
leaf = &r | |||||
continue | |||||
} | |||||
c := r.key[depth] | |||||
switch { | |||||
case pc < c: | |||||
sib = append(sib, sibling{start: i, c: c}) | |||||
case pc == c: | |||||
continue | |||||
default: | |||||
return nil, nil, fmt.Errorf("stringutil: BUG: records hasn't been sorted") | |||||
} | |||||
if n > 0 { | |||||
sib[n-1].end = i | |||||
} | |||||
pc = c | |||||
n++ | |||||
} | |||||
if n == 0 { | |||||
return nil, leaf, nil | |||||
} | |||||
sib[n-1].end = len(records) | |||||
return sib, leaf, nil | |||||
} | |||||
type record struct { | |||||
key string | |||||
value int | |||||
} | |||||
func makeRecords(srcs []string) (records []record) { | |||||
termChar := string(terminationCharacter) | |||||
for _, s := range srcs { | |||||
records = append(records, record{ | |||||
key: string(s + termChar), | |||||
value: utf8.RuneCountInString(s), | |||||
}) | |||||
} | |||||
return records | |||||
} | |||||
type recordSlice []record | |||||
func (rs recordSlice) Len() int { | |||||
return len(rs) | |||||
} | |||||
func (rs recordSlice) Less(i, j int) bool { | |||||
return rs[i].key < rs[j].key | |||||
} | |||||
func (rs recordSlice) Swap(i, j int) { | |||||
rs[i], rs[j] = rs[j], rs[i] | |||||
} |
@ -1,320 +0,0 @@ | |||||
package stringutil | |||||
import ( | |||||
"sync" | |||||
"unicode" | |||||
"unicode/utf8" | |||||
) | |||||
var ( | |||||
mu sync.Mutex | |||||
// Based on https://github.com/golang/lint/blob/32a87160691b3c96046c0c678fe57c5bef761456/lint.go#L702 | |||||
commonInitialismMap = map[string]struct{}{ | |||||
"API": struct{}{}, | |||||
"ASCII": struct{}{}, | |||||
"CPU": struct{}{}, | |||||
"CSRF": struct{}{}, | |||||
"CSS": struct{}{}, | |||||
"DNS": struct{}{}, | |||||
"EOF": struct{}{}, | |||||
"GUID": struct{}{}, | |||||
"HTML": struct{}{}, | |||||
"HTTP": struct{}{}, | |||||
"HTTPS": struct{}{}, | |||||
"ID": struct{}{}, | |||||
"IP": struct{}{}, | |||||
"JSON": struct{}{}, | |||||
"LHS": struct{}{}, | |||||
"QPS": struct{}{}, | |||||
"RAM": struct{}{}, | |||||
"RHS": struct{}{}, | |||||
"RPC": struct{}{}, | |||||
"SLA": struct{}{}, | |||||
"SMTP": struct{}{}, | |||||
"SQL": struct{}{}, | |||||
"SSH": struct{}{}, | |||||
"TCP": struct{}{}, | |||||
"TLS": struct{}{}, | |||||
"TTL": struct{}{}, | |||||
"UDP": struct{}{}, | |||||
"UI": struct{}{}, | |||||
"UID": struct{}{}, | |||||
"UUID": struct{}{}, | |||||
"URI": struct{}{}, | |||||
"URL": struct{}{}, | |||||
"UTF8": struct{}{}, | |||||
"VM": struct{}{}, | |||||
"XML": struct{}{}, | |||||
"XSRF": struct{}{}, | |||||
"XSS": struct{}{}, | |||||
} | |||||
commonInitialisms = keys(commonInitialismMap) | |||||
commonInitialism = mustDoubleArray(newDoubleArray(commonInitialisms)) | |||||
longestLen = longestLength(commonInitialisms) | |||||
shortestLen = shortestLength(commonInitialisms, longestLen) | |||||
) | |||||
// ToUpperCamelCase returns a copy of the string s with all Unicode letters mapped to their camel case. | |||||
// It will convert to upper case previous letter of '_' and first letter, and remove letter of '_'. | |||||
func ToUpperCamelCase(s string) string { | |||||
if s == "" { | |||||
return "" | |||||
} | |||||
upper := true | |||||
start := 0 | |||||
result := make([]byte, 0, len(s)) | |||||
var runeBuf [utf8.UTFMax]byte | |||||
var initialism []byte | |||||
for _, c := range s { | |||||
if c == '_' { | |||||
upper = true | |||||
candidate := string(result[start:]) | |||||
initialism = initialism[:0] | |||||
for _, r := range candidate { | |||||
if r < utf8.RuneSelf { | |||||
initialism = append(initialism, toUpperASCII(byte(r))) | |||||
} else { | |||||
n := utf8.EncodeRune(runeBuf[:], unicode.ToUpper(r)) | |||||
initialism = append(initialism, runeBuf[:n]...) | |||||
} | |||||
} | |||||
if length := commonInitialism.LookupByBytes(initialism); length > 0 { | |||||
result = append(result[:start], initialism...) | |||||
} | |||||
start = len(result) | |||||
continue | |||||
} | |||||
if upper { | |||||
if c < utf8.RuneSelf { | |||||
result = append(result, toUpperASCII(byte(c))) | |||||
} else { | |||||
n := utf8.EncodeRune(runeBuf[:], unicode.ToUpper(c)) | |||||
result = append(result, runeBuf[:n]...) | |||||
} | |||||
upper = false | |||||
continue | |||||
} | |||||
if c < utf8.RuneSelf { | |||||
result = append(result, byte(c)) | |||||
} else { | |||||
n := utf8.EncodeRune(runeBuf[:], c) | |||||
result = append(result, runeBuf[:n]...) | |||||
} | |||||
} | |||||
candidate := string(result[start:]) | |||||
initialism = initialism[:0] | |||||
for _, r := range candidate { | |||||
if r < utf8.RuneSelf { | |||||
initialism = append(initialism, toUpperASCII(byte(r))) | |||||
} else { | |||||
n := utf8.EncodeRune(runeBuf[:], unicode.ToUpper(r)) | |||||
initialism = append(initialism, runeBuf[:n]...) | |||||
} | |||||
} | |||||
if length := commonInitialism.LookupByBytes(initialism); length > 0 { | |||||
result = append(result[:start], initialism...) | |||||
} | |||||
return string(result) | |||||
} | |||||
// ToUpperCamelCaseASCII is similar to ToUpperCamelCase, but optimized for | |||||
// only the ASCII characters. | |||||
// ToUpperCamelCaseASCII is faster than ToUpperCamelCase, but doesn't work if | |||||
// contains non-ASCII characters. | |||||
func ToUpperCamelCaseASCII(s string) string { | |||||
if s == "" { | |||||
return "" | |||||
} | |||||
upper := true | |||||
start := 0 | |||||
result := make([]byte, 0, len(s)) | |||||
var initialism []byte | |||||
for i := 0; i < len(s); i++ { | |||||
c := s[i] | |||||
if c == '_' { | |||||
upper = true | |||||
candidate := result[start:] | |||||
initialism = initialism[:0] | |||||
for _, b := range candidate { | |||||
initialism = append(initialism, toUpperASCII(b)) | |||||
} | |||||
if length := commonInitialism.LookupByBytes(initialism); length > 0 { | |||||
result = append(result[:start], initialism...) | |||||
} | |||||
start = len(result) | |||||
continue | |||||
} | |||||
if upper { | |||||
result = append(result, toUpperASCII(c)) | |||||
upper = false | |||||
continue | |||||
} | |||||
result = append(result, c) | |||||
} | |||||
candidate := result[start:] | |||||
initialism = initialism[:0] | |||||
for _, b := range candidate { | |||||
initialism = append(initialism, toUpperASCII(b)) | |||||
} | |||||
if length := commonInitialism.LookupByBytes(initialism); length > 0 { | |||||
result = append(result[:start], initialism...) | |||||
} | |||||
return string(result) | |||||
} | |||||
// ToSnakeCase returns a copy of the string s with all Unicode letters mapped to their snake case. | |||||
// It will insert letter of '_' at position of previous letter of uppercase and all | |||||
// letters convert to lower case. | |||||
// ToSnakeCase does not insert '_' letter into a common initialism word like ID, URL and so on. | |||||
func ToSnakeCase(s string) string { | |||||
if s == "" { | |||||
return "" | |||||
} | |||||
result := make([]byte, 0, len(s)) | |||||
var runeBuf [utf8.UTFMax]byte | |||||
var j, skipCount int | |||||
for i, c := range s { | |||||
if i < skipCount { | |||||
continue | |||||
} | |||||
if unicode.IsUpper(c) { | |||||
if i != 0 { | |||||
result = append(result, '_') | |||||
} | |||||
next := nextIndex(j, len(s)) | |||||
if length := commonInitialism.Lookup(s[j:next]); length > 0 { | |||||
for _, r := range s[j : j+length] { | |||||
if r < utf8.RuneSelf { | |||||
result = append(result, toLowerASCII(byte(r))) | |||||
} else { | |||||
n := utf8.EncodeRune(runeBuf[:], unicode.ToLower(r)) | |||||
result = append(result, runeBuf[:n]...) | |||||
} | |||||
} | |||||
j += length - 1 | |||||
skipCount = i + length | |||||
continue | |||||
} | |||||
} | |||||
if c < utf8.RuneSelf { | |||||
result = append(result, toLowerASCII(byte(c))) | |||||
} else { | |||||
n := utf8.EncodeRune(runeBuf[:], unicode.ToLower(c)) | |||||
result = append(result, runeBuf[:n]...) | |||||
} | |||||
j++ | |||||
} | |||||
return string(result) | |||||
} | |||||
// ToSnakeCaseASCII is similar to ToSnakeCase, but optimized for only the ASCII | |||||
// characters. | |||||
// ToSnakeCaseASCII is faster than ToSnakeCase, but doesn't work correctly if | |||||
// contains non-ASCII characters. | |||||
func ToSnakeCaseASCII(s string) string { | |||||
if s == "" { | |||||
return "" | |||||
} | |||||
result := make([]byte, 0, len(s)) | |||||
for i := 0; i < len(s); i++ { | |||||
c := s[i] | |||||
if isUpperASCII(c) { | |||||
if i != 0 { | |||||
result = append(result, '_') | |||||
} | |||||
if k := i + shortestLen - 1; k < len(s) && isUpperASCII(s[k]) { | |||||
if length := commonInitialism.Lookup(s[i:nextIndex(i, len(s))]); length > 0 { | |||||
for j, buf := 0, s[i:i+length]; j < len(buf); j++ { | |||||
result = append(result, toLowerASCII(buf[j])) | |||||
} | |||||
i += length - 1 | |||||
continue | |||||
} | |||||
} | |||||
} | |||||
result = append(result, toLowerASCII(c)) | |||||
} | |||||
return string(result) | |||||
} | |||||
// AddCommonInitialism adds ss to list of common initialisms. | |||||
func AddCommonInitialism(ss ...string) { | |||||
mu.Lock() | |||||
defer mu.Unlock() | |||||
for _, s := range ss { | |||||
commonInitialismMap[s] = struct{}{} | |||||
} | |||||
commonInitialisms = keys(commonInitialismMap) | |||||
commonInitialism = mustDoubleArray(newDoubleArray(commonInitialisms)) | |||||
longestLen = longestLength(commonInitialisms) | |||||
shortestLen = shortestLength(commonInitialisms, longestLen) | |||||
} | |||||
// DelCommonInitialism deletes ss from list of common initialisms. | |||||
func DelCommonInitialism(ss ...string) { | |||||
mu.Lock() | |||||
defer mu.Unlock() | |||||
for _, s := range ss { | |||||
delete(commonInitialismMap, s) | |||||
} | |||||
commonInitialisms = keys(commonInitialismMap) | |||||
commonInitialism = mustDoubleArray(newDoubleArray(commonInitialisms)) | |||||
longestLen = longestLength(commonInitialisms) | |||||
shortestLen = shortestLength(commonInitialisms, longestLen) | |||||
} | |||||
func isUpperASCII(c byte) bool { | |||||
return 'A' <= c && c <= 'Z' | |||||
} | |||||
func isLowerASCII(c byte) bool { | |||||
return 'a' <= c && c <= 'z' | |||||
} | |||||
func toUpperASCII(c byte) byte { | |||||
if isLowerASCII(c) { | |||||
return c - ('a' - 'A') | |||||
} | |||||
return c | |||||
} | |||||
func toLowerASCII(c byte) byte { | |||||
if isUpperASCII(c) { | |||||
return c + 'a' - 'A' | |||||
} | |||||
return c | |||||
} | |||||
func nextIndex(i, maxlen int) int { | |||||
if n := i + longestLen; n < maxlen { | |||||
return n | |||||
} | |||||
return maxlen | |||||
} | |||||
func keys(m map[string]struct{}) []string { | |||||
result := make([]string, 0, len(m)) | |||||
for k := range m { | |||||
result = append(result, k) | |||||
} | |||||
return result | |||||
} | |||||
func shortestLength(strs []string, shortest int) int { | |||||
for _, s := range strs { | |||||
if candidate := utf8.RuneCountInString(s); candidate < shortest { | |||||
shortest = candidate | |||||
} | |||||
} | |||||
return shortest | |||||
} | |||||
func longestLength(strs []string) (longest int) { | |||||
for _, s := range strs { | |||||
if candidate := utf8.RuneCountInString(s); candidate > longest { | |||||
longest = candidate | |||||
} | |||||
} | |||||
return longest | |||||
} |
@ -1,11 +0,0 @@ | |||||
language: go | |||||
go: | |||||
- 1.3 | |||||
- tip | |||||
install: | |||||
- go get -v ./... | |||||
script: | |||||
- go test ./... |
@ -1,19 +0,0 @@ | |||||
Copyright (c) 2014 Naoya Inada <naoina@kuune.org> | |||||
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. |
@ -1,16 +0,0 @@ | |||||
GO = go | |||||
PEG = peg | |||||
.SUFFIXES: .peg .peg.go | |||||
.PHONY: all test clean | |||||
all: parse.peg.go | |||||
.peg.peg.go: | |||||
$(PEG) -switch -inline $< | |||||
test: all | |||||
$(GO) test ./... | |||||
clean: | |||||
$(RM) *.peg.go |
@ -1,364 +0,0 @@ | |||||
# TOML parser and encoder library for Golang [![Build Status](https://travis-ci.org/naoina/toml.png?branch=master)](https://travis-ci.org/naoina/toml) | |||||
[TOML](https://github.com/toml-lang/toml) parser and encoder library for [Golang](http://golang.org/). | |||||
This library is compatible with TOML version [v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md). | |||||
## Installation | |||||
go get -u github.com/naoina/toml | |||||
## Usage | |||||
The following TOML save as `example.toml`. | |||||
```toml | |||||
# This is a TOML document. Boom. | |||||
title = "TOML Example" | |||||
[owner] | |||||
name = "Lance Uppercut" | |||||
dob = 1979-05-27T07:32:00-08:00 # First class dates? Why not? | |||||
[database] | |||||
server = "192.168.1.1" | |||||
ports = [ 8001, 8001, 8002 ] | |||||
connection_max = 5000 | |||||
enabled = true | |||||
[servers] | |||||
# You can indent as you please. Tabs or spaces. TOML don't care. | |||||
[servers.alpha] | |||||
ip = "10.0.0.1" | |||||
dc = "eqdc10" | |||||
[servers.beta] | |||||
ip = "10.0.0.2" | |||||
dc = "eqdc10" | |||||
[clients] | |||||
data = [ ["gamma", "delta"], [1, 2] ] | |||||
# Line breaks are OK when inside arrays | |||||
hosts = [ | |||||
"alpha", | |||||
"omega" | |||||
] | |||||
``` | |||||
Then above TOML will mapping to `tomlConfig` struct using `toml.Unmarshal`. | |||||
```go | |||||
package main | |||||
import ( | |||||
"io/ioutil" | |||||
"os" | |||||
"time" | |||||
"github.com/naoina/toml" | |||||
) | |||||
type tomlConfig struct { | |||||
Title string | |||||
Owner struct { | |||||
Name string | |||||
Dob time.Time | |||||
} | |||||
Database struct { | |||||
Server string | |||||
Ports []int | |||||
ConnectionMax uint | |||||
Enabled bool | |||||
} | |||||
Servers map[string]Server | |||||
Clients struct { | |||||
Data [][]interface{} | |||||
Hosts []string | |||||
} | |||||
} | |||||
type Server struct { | |||||
IP string | |||||
DC string | |||||
} | |||||
func main() { | |||||
f, err := os.Open("example.toml") | |||||
if err != nil { | |||||
panic(err) | |||||
} | |||||
defer f.Close() | |||||
buf, err := ioutil.ReadAll(f) | |||||
if err != nil { | |||||
panic(err) | |||||
} | |||||
var config tomlConfig | |||||
if err := toml.Unmarshal(buf, &config); err != nil { | |||||
panic(err) | |||||
} | |||||
// then to use the unmarshaled config... | |||||
} | |||||
``` | |||||
## Mappings | |||||
A key and value of TOML will map to the corresponding field. | |||||
The fields of struct for mapping must be exported. | |||||
The rules of the mapping of key are following: | |||||
#### Exact matching | |||||
```toml | |||||
timeout_seconds = 256 | |||||
``` | |||||
```go | |||||
type Config struct { | |||||
Timeout_seconds int | |||||
} | |||||
``` | |||||
#### Camelcase matching | |||||
```toml | |||||
server_name = "srv1" | |||||
``` | |||||
```go | |||||
type Config struct { | |||||
ServerName string | |||||
} | |||||
``` | |||||
#### Uppercase matching | |||||
```toml | |||||
ip = "10.0.0.1" | |||||
``` | |||||
```go | |||||
type Config struct { | |||||
IP string | |||||
} | |||||
``` | |||||
See the following examples for the value mappings. | |||||
### String | |||||
```toml | |||||
val = "string" | |||||
``` | |||||
```go | |||||
type Config struct { | |||||
Val string | |||||
} | |||||
``` | |||||
### Integer | |||||
```toml | |||||
val = 100 | |||||
``` | |||||
```go | |||||
type Config struct { | |||||
Val int | |||||
} | |||||
``` | |||||
All types that can be used are following: | |||||
* int8 (from `-128` to `127`) | |||||
* int16 (from `-32768` to `32767`) | |||||
* int32 (from `-2147483648` to `2147483647`) | |||||
* int64 (from `-9223372036854775808` to `9223372036854775807`) | |||||
* int (same as `int32` on 32bit environment, or `int64` on 64bit environment) | |||||
* uint8 (from `0` to `255`) | |||||
* uint16 (from `0` to `65535`) | |||||
* uint32 (from `0` to `4294967295`) | |||||
* uint64 (from `0` to `18446744073709551615`) | |||||
* uint (same as `uint` on 32bit environment, or `uint64` on 64bit environment) | |||||
### Float | |||||
```toml | |||||
val = 3.1415 | |||||
``` | |||||
```go | |||||
type Config struct { | |||||
Val float32 | |||||
} | |||||
``` | |||||
All types that can be used are following: | |||||
* float32 | |||||
* float64 | |||||
### Boolean | |||||
```toml | |||||
val = true | |||||
``` | |||||
```go | |||||
type Config struct { | |||||
Val bool | |||||
} | |||||
``` | |||||
### Datetime | |||||
```toml | |||||
val = 2014-09-28T21:27:39Z | |||||
``` | |||||
```go | |||||
type Config struct { | |||||
Val time.Time | |||||
} | |||||
``` | |||||
### Array | |||||
```toml | |||||
val = ["a", "b", "c"] | |||||
``` | |||||
```go | |||||
type Config struct { | |||||
Val []string | |||||
} | |||||
``` | |||||
Also following examples all can be mapped: | |||||
```toml | |||||
val1 = [1, 2, 3] | |||||
val2 = [["a", "b"], ["c", "d"]] | |||||
val3 = [[1, 2, 3], ["a", "b", "c"]] | |||||
val4 = [[1, 2, 3], [["a", "b"], [true, false]]] | |||||
``` | |||||
```go | |||||
type Config struct { | |||||
Val1 []int | |||||
Val2 [][]string | |||||
Val3 [][]interface{} | |||||
Val4 [][]interface{} | |||||
} | |||||
``` | |||||
### Table | |||||
```toml | |||||
[server] | |||||
type = "app" | |||||
[server.development] | |||||
ip = "10.0.0.1" | |||||
[server.production] | |||||
ip = "10.0.0.2" | |||||
``` | |||||
```go | |||||
type Config struct { | |||||
Server map[string]Server | |||||
} | |||||
type Server struct { | |||||
IP string | |||||
} | |||||
``` | |||||
You can also use the following struct instead of map of struct. | |||||
```go | |||||
type Config struct { | |||||
Server struct { | |||||
Development Server | |||||
Production Server | |||||
} | |||||
} | |||||
type Server struct { | |||||
IP string | |||||
} | |||||
``` | |||||
### Array of Tables | |||||
```toml | |||||
[[fruit]] | |||||
name = "apple" | |||||
[fruit.physical] | |||||
color = "red" | |||||
shape = "round" | |||||
[[fruit.variety]] | |||||
name = "red delicious" | |||||
[[fruit.variety]] | |||||
name = "granny smith" | |||||
[[fruit]] | |||||
name = "banana" | |||||
[[fruit.variety]] | |||||
name = "plantain" | |||||
``` | |||||
```go | |||||
type Config struct { | |||||
Fruit []struct { | |||||
Name string | |||||
Physical struct { | |||||
Color string | |||||
Shape string | |||||
} | |||||
Variety []struct { | |||||
Name string | |||||
} | |||||
} | |||||
} | |||||
``` | |||||
### Using `toml.UnmarshalTOML` interface | |||||
```toml | |||||
duration = "10s" | |||||
``` | |||||
```go | |||||
import time | |||||
type Config struct { | |||||
Duration Duration | |||||
} | |||||
type Duration struct { | |||||
time.Duration | |||||
} | |||||
func (d *Duration) UnmarshalTOML(data []byte) error { | |||||
d.Duration, err := time.ParseDuration(string(data)) | |||||
return err | |||||
} | |||||
``` | |||||
## API documentation | |||||
See [Godoc](http://godoc.org/github.com/naoina/toml). | |||||
## License | |||||
MIT |
@ -1,184 +0,0 @@ | |||||
package ast | |||||
import ( | |||||
"strconv" | |||||
"time" | |||||
) | |||||
type Position struct { | |||||
Begin int | |||||
End int | |||||
} | |||||
type Value interface { | |||||
Pos() int | |||||
End() int | |||||
Source() string | |||||
} | |||||
type String struct { | |||||
Position Position | |||||
Value string | |||||
Data []rune | |||||
} | |||||
func (s *String) Pos() int { | |||||
return s.Position.Begin | |||||
} | |||||
func (s *String) End() int { | |||||
return s.Position.End | |||||
} | |||||
func (s *String) Source() string { | |||||
return string(s.Data) | |||||
} | |||||
type Integer struct { | |||||
Position Position | |||||
Value string | |||||
Data []rune | |||||
} | |||||
func (i *Integer) Pos() int { | |||||
return i.Position.Begin | |||||
} | |||||
func (i *Integer) End() int { | |||||
return i.Position.End | |||||
} | |||||
func (i *Integer) Source() string { | |||||
return string(i.Data) | |||||
} | |||||
func (i *Integer) Int() (int64, error) { | |||||
return strconv.ParseInt(i.Value, 10, 64) | |||||
} | |||||
type Float struct { | |||||
Position Position | |||||
Value string | |||||
Data []rune | |||||
} | |||||
func (f *Float) Pos() int { | |||||
return f.Position.Begin | |||||
} | |||||
func (f *Float) End() int { | |||||
return f.Position.End | |||||
} | |||||
func (f *Float) Source() string { | |||||
return string(f.Data) | |||||
} | |||||
func (f *Float) Float() (float64, error) { | |||||
return strconv.ParseFloat(f.Value, 64) | |||||
} | |||||
type Boolean struct { | |||||
Position Position | |||||
Value string | |||||
Data []rune | |||||
} | |||||
func (b *Boolean) Pos() int { | |||||
return b.Position.Begin | |||||
} | |||||
func (b *Boolean) End() int { | |||||
return b.Position.End | |||||
} | |||||
func (b *Boolean) Source() string { | |||||
return string(b.Data) | |||||
} | |||||
func (b *Boolean) Boolean() (bool, error) { | |||||
return strconv.ParseBool(b.Value) | |||||
} | |||||
type Datetime struct { | |||||
Position Position | |||||
Value string | |||||
Data []rune | |||||
} | |||||
func (d *Datetime) Pos() int { | |||||
return d.Position.Begin | |||||
} | |||||
func (d *Datetime) End() int { | |||||
return d.Position.End | |||||
} | |||||
func (d *Datetime) Source() string { | |||||
return string(d.Data) | |||||
} | |||||
func (d *Datetime) Time() (time.Time, error) { | |||||
return time.Parse(time.RFC3339Nano, d.Value) | |||||
} | |||||
type Array struct { | |||||
Position Position | |||||
Value []Value | |||||
Data []rune | |||||
} | |||||
func (a *Array) Pos() int { | |||||
return a.Position.Begin | |||||
} | |||||
func (a *Array) End() int { | |||||
return a.Position.End | |||||
} | |||||
func (a *Array) Source() string { | |||||
return string(a.Data) | |||||
} | |||||
type TableType uint8 | |||||
const ( | |||||
TableTypeNormal TableType = iota | |||||
TableTypeArray | |||||
) | |||||
var tableTypes = [...]string{ | |||||
"normal", | |||||
"array", | |||||
} | |||||
func (t TableType) String() string { | |||||
return tableTypes[t] | |||||
} | |||||
type Table struct { | |||||
Position Position | |||||
Line int | |||||
Name string | |||||
Fields map[string]interface{} | |||||
Type TableType | |||||
Data []rune | |||||
} | |||||
func (t *Table) Pos() int { | |||||
return t.Position.Begin | |||||
} | |||||
func (t *Table) End() int { | |||||
return t.Position.End | |||||
} | |||||
func (t *Table) Source() string { | |||||
return string(t.Data) | |||||
} | |||||
type KeyValue struct { | |||||
Key string | |||||
Value Value | |||||
Line int | |||||
} |
@ -1,678 +0,0 @@ | |||||
package toml | |||||
import ( | |||||
"fmt" | |||||
"io" | |||||
"io/ioutil" | |||||
"reflect" | |||||
"strconv" | |||||
"strings" | |||||
"github.com/naoina/toml/ast" | |||||
) | |||||
const ( | |||||
tableSeparator = '.' | |||||
) | |||||
var ( | |||||
escapeReplacer = strings.NewReplacer( | |||||
"\b", "\\n", | |||||
"\f", "\\f", | |||||
"\n", "\\n", | |||||
"\r", "\\r", | |||||
"\t", "\\t", | |||||
) | |||||
underscoreReplacer = strings.NewReplacer( | |||||
"_", "", | |||||
) | |||||
) | |||||
// Unmarshal parses the TOML data and stores the result in the value pointed to by v. | |||||
// | |||||
// Unmarshal will mapped to v that according to following rules: | |||||
// | |||||
// TOML strings to string | |||||
// TOML integers to any int type | |||||
// TOML floats to float32 or float64 | |||||
// TOML booleans to bool | |||||
// TOML datetimes to time.Time | |||||
// TOML arrays to any type of slice or []interface{} | |||||
// TOML tables to struct | |||||
// TOML array of tables to slice of struct | |||||
func Unmarshal(data []byte, v interface{}) error { | |||||
table, err := Parse(data) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
if err := UnmarshalTable(table, v); err != nil { | |||||
return fmt.Errorf("toml: unmarshal: %v", err) | |||||
} | |||||
return nil | |||||
} | |||||
// A Decoder reads and decodes TOML from an input stream. | |||||
type Decoder struct { | |||||
r io.Reader | |||||
} | |||||
// NewDecoder returns a new Decoder that reads from r. | |||||
// Note that it reads all from r before parsing it. | |||||
func NewDecoder(r io.Reader) *Decoder { | |||||
return &Decoder{ | |||||
r: r, | |||||
} | |||||
} | |||||
// Decode parses the TOML data from its input and stores it in the value pointed to by v. | |||||
// See the documentation for Unmarshal for details about the conversion of TOML into a Go value. | |||||
func (d *Decoder) Decode(v interface{}) error { | |||||
b, err := ioutil.ReadAll(d.r) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
return Unmarshal(b, v) | |||||
} | |||||
// Unmarshaler is the interface implemented by objects that can unmarshal a | |||||
// TOML description of themselves. | |||||
// The input can be assumed to be a valid encoding of a TOML value. | |||||
// UnmarshalJSON must copy the TOML data if it wishes to retain the data after | |||||
// returning. | |||||
type Unmarshaler interface { | |||||
UnmarshalTOML([]byte) error | |||||
} | |||||
// UnmarshalTable applies the contents of an ast.Table to the value pointed at by v. | |||||
// | |||||
// UnmarshalTable will mapped to v that according to following rules: | |||||
// | |||||
// TOML strings to string | |||||
// TOML integers to any int type | |||||
// TOML floats to float32 or float64 | |||||
// TOML booleans to bool | |||||
// TOML datetimes to time.Time | |||||
// TOML arrays to any type of slice or []interface{} | |||||
// TOML tables to struct | |||||
// TOML array of tables to slice of struct | |||||
func UnmarshalTable(t *ast.Table, v interface{}) (err error) { | |||||
if v == nil { | |||||
return fmt.Errorf("v must not be nil") | |||||
} | |||||
rv := reflect.ValueOf(v) | |||||
if kind := rv.Kind(); kind != reflect.Ptr && kind != reflect.Map { | |||||
return fmt.Errorf("v must be a pointer or map") | |||||
} | |||||
for rv.Kind() == reflect.Ptr { | |||||
rv = rv.Elem() | |||||
} | |||||
if err, ok := setUnmarshaler(rv, string(t.Data)); ok { | |||||
return err | |||||
} | |||||
for key, val := range t.Fields { | |||||
switch av := val.(type) { | |||||
case *ast.KeyValue: | |||||
fv, fieldName, found := findField(rv, key) | |||||
if !found { | |||||
return fmt.Errorf("line %d: field corresponding to `%s' is not defined in `%T'", av.Line, key, v) | |||||
} | |||||
switch fv.Kind() { | |||||
case reflect.Map: | |||||
mv := reflect.New(fv.Type().Elem()).Elem() | |||||
if err := UnmarshalTable(t, mv.Addr().Interface()); err != nil { | |||||
return err | |||||
} | |||||
fv.SetMapIndex(reflect.ValueOf(fieldName), mv) | |||||
default: | |||||
if err := setValue(fv, av.Value); err != nil { | |||||
return fmt.Errorf("line %d: %v.%s: %v", av.Line, rv.Type(), fieldName, err) | |||||
} | |||||
if rv.Kind() == reflect.Map { | |||||
rv.SetMapIndex(reflect.ValueOf(fieldName), fv) | |||||
} | |||||
} | |||||
case *ast.Table: | |||||
fv, fieldName, found := findField(rv, key) | |||||
if !found { | |||||
return fmt.Errorf("line %d: field corresponding to `%s' is not defined in `%T'", av.Line, key, v) | |||||
} | |||||
if err, ok := setUnmarshaler(fv, string(av.Data)); ok { | |||||
if err != nil { | |||||
return err | |||||
} | |||||
continue | |||||
} | |||||
for fv.Kind() == reflect.Ptr { | |||||
fv.Set(reflect.New(fv.Type().Elem())) | |||||
fv = fv.Elem() | |||||
} | |||||
switch fv.Kind() { | |||||
case reflect.Struct: | |||||
vv := reflect.New(fv.Type()).Elem() | |||||
if err := UnmarshalTable(av, vv.Addr().Interface()); err != nil { | |||||
return err | |||||
} | |||||
fv.Set(vv) | |||||
if rv.Kind() == reflect.Map { | |||||
rv.SetMapIndex(reflect.ValueOf(fieldName), fv) | |||||
} | |||||
case reflect.Map: | |||||
mv := reflect.MakeMap(fv.Type()) | |||||
if err := UnmarshalTable(av, mv.Interface()); err != nil { | |||||
return err | |||||
} | |||||
fv.Set(mv) | |||||
default: | |||||
return fmt.Errorf("line %d: `%v.%s' must be struct or map, but %v given", av.Line, rv.Type(), fieldName, fv.Kind()) | |||||
} | |||||
case []*ast.Table: | |||||
fv, fieldName, found := findField(rv, key) | |||||
if !found { | |||||
return fmt.Errorf("line %d: field corresponding to `%s' is not defined in `%T'", av[0].Line, key, v) | |||||
} | |||||
data := make([]string, 0, len(av)) | |||||
for _, tbl := range av { | |||||
data = append(data, string(tbl.Data)) | |||||
} | |||||
if err, ok := setUnmarshaler(fv, strings.Join(data, "\n")); ok { | |||||
if err != nil { | |||||
return err | |||||
} | |||||
continue | |||||
} | |||||
t := fv.Type().Elem() | |||||
pc := 0 | |||||
for ; t.Kind() == reflect.Ptr; pc++ { | |||||
t = t.Elem() | |||||
} | |||||
if fv.Kind() != reflect.Slice { | |||||
return fmt.Errorf("line %d: `%v.%s' must be slice type, but %v given", av[0].Line, rv.Type(), fieldName, fv.Kind()) | |||||
} | |||||
for _, tbl := range av { | |||||
var vv reflect.Value | |||||
switch t.Kind() { | |||||
case reflect.Map: | |||||
vv = reflect.MakeMap(t) | |||||
if err := UnmarshalTable(tbl, vv.Interface()); err != nil { | |||||
return err | |||||
} | |||||
default: | |||||
vv = reflect.New(t).Elem() | |||||
if err := UnmarshalTable(tbl, vv.Addr().Interface()); err != nil { | |||||
return err | |||||
} | |||||
} | |||||
for i := 0; i < pc; i++ { | |||||
vv = vv.Addr() | |||||
pv := reflect.New(vv.Type()).Elem() | |||||
pv.Set(vv) | |||||
vv = pv | |||||
} | |||||
fv.Set(reflect.Append(fv, vv)) | |||||
} | |||||
if rv.Kind() == reflect.Map { | |||||
rv.SetMapIndex(reflect.ValueOf(fieldName), fv) | |||||
} | |||||
default: | |||||
return fmt.Errorf("BUG: unknown type `%T'", t) | |||||
} | |||||
} | |||||
return nil | |||||
} | |||||
func setUnmarshaler(lhs reflect.Value, data string) (error, bool) { | |||||
for lhs.Kind() == reflect.Ptr { | |||||
lhs.Set(reflect.New(lhs.Type().Elem())) | |||||
lhs = lhs.Elem() | |||||
} | |||||
if lhs.CanAddr() { | |||||
if u, ok := lhs.Addr().Interface().(Unmarshaler); ok { | |||||
return u.UnmarshalTOML([]byte(data)), true | |||||
} | |||||
} | |||||
return nil, false | |||||
} | |||||
func setValue(lhs reflect.Value, val ast.Value) error { | |||||
for lhs.Kind() == reflect.Ptr { | |||||
lhs.Set(reflect.New(lhs.Type().Elem())) | |||||
lhs = lhs.Elem() | |||||
} | |||||
if err, ok := setUnmarshaler(lhs, val.Source()); ok { | |||||
return err | |||||
} | |||||
switch v := val.(type) { | |||||
case *ast.Integer: | |||||
if err := setInt(lhs, v); err != nil { | |||||
return err | |||||
} | |||||
case *ast.Float: | |||||
if err := setFloat(lhs, v); err != nil { | |||||
return err | |||||
} | |||||
case *ast.String: | |||||
if err := setString(lhs, v); err != nil { | |||||
return err | |||||
} | |||||
case *ast.Boolean: | |||||
if err := setBoolean(lhs, v); err != nil { | |||||
return err | |||||
} | |||||
case *ast.Datetime: | |||||
if err := setDatetime(lhs, v); err != nil { | |||||
return err | |||||
} | |||||
case *ast.Array: | |||||
if err := setArray(lhs, v); err != nil { | |||||
return err | |||||
} | |||||
} | |||||
return nil | |||||
} | |||||
func setInt(fv reflect.Value, v *ast.Integer) error { | |||||
i, err := v.Int() | |||||
if err != nil { | |||||
return err | |||||
} | |||||
switch fv.Kind() { | |||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |||||
if fv.OverflowInt(i) { | |||||
return &errorOutOfRange{fv.Kind(), i} | |||||
} | |||||
fv.SetInt(i) | |||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | |||||
fv.SetUint(uint64(i)) | |||||
case reflect.Interface: | |||||
fv.Set(reflect.ValueOf(i)) | |||||
default: | |||||
return fmt.Errorf("`%v' is not any types of int", fv.Type()) | |||||
} | |||||
return nil | |||||
} | |||||
func setFloat(fv reflect.Value, v *ast.Float) error { | |||||
f, err := v.Float() | |||||
if err != nil { | |||||
return err | |||||
} | |||||
switch fv.Kind() { | |||||
case reflect.Float32, reflect.Float64: | |||||
if fv.OverflowFloat(f) { | |||||
return &errorOutOfRange{fv.Kind(), f} | |||||
} | |||||
fv.SetFloat(f) | |||||
case reflect.Interface: | |||||
fv.Set(reflect.ValueOf(f)) | |||||
default: | |||||
return fmt.Errorf("`%v' is not float32 or float64", fv.Type()) | |||||
} | |||||
return nil | |||||
} | |||||
func setString(fv reflect.Value, v *ast.String) error { | |||||
return set(fv, v.Value) | |||||
} | |||||
func setBoolean(fv reflect.Value, v *ast.Boolean) error { | |||||
b, err := v.Boolean() | |||||
if err != nil { | |||||
return err | |||||
} | |||||
return set(fv, b) | |||||
} | |||||
func setDatetime(fv reflect.Value, v *ast.Datetime) error { | |||||
tm, err := v.Time() | |||||
if err != nil { | |||||
return err | |||||
} | |||||
return set(fv, tm) | |||||
} | |||||
func setArray(fv reflect.Value, v *ast.Array) error { | |||||
if len(v.Value) == 0 { | |||||
return nil | |||||
} | |||||
typ := reflect.TypeOf(v.Value[0]) | |||||
for _, vv := range v.Value[1:] { | |||||
if typ != reflect.TypeOf(vv) { | |||||
return fmt.Errorf("array cannot contain multiple types") | |||||
} | |||||
} | |||||
sliceType := fv.Type() | |||||
if fv.Kind() == reflect.Interface { | |||||
sliceType = reflect.SliceOf(sliceType) | |||||
} | |||||
slice := reflect.MakeSlice(sliceType, 0, len(v.Value)) | |||||
t := sliceType.Elem() | |||||
for _, vv := range v.Value { | |||||
tmp := reflect.New(t).Elem() | |||||
if err := setValue(tmp, vv); err != nil { | |||||
return err | |||||
} | |||||
slice = reflect.Append(slice, tmp) | |||||
} | |||||
fv.Set(slice) | |||||
return nil | |||||
} | |||||
func set(fv reflect.Value, v interface{}) error { | |||||
rhs := reflect.ValueOf(v) | |||||
if !rhs.Type().AssignableTo(fv.Type()) { | |||||
return fmt.Errorf("`%v' type is not assignable to `%v' type", rhs.Type(), fv.Type()) | |||||
} | |||||
fv.Set(rhs) | |||||
return nil | |||||
} | |||||
type stack struct { | |||||
key string | |||||
table *ast.Table | |||||
} | |||||
type toml struct { | |||||
table *ast.Table | |||||
line int | |||||
currentTable *ast.Table | |||||
s string | |||||
key string | |||||
val ast.Value | |||||
arr *array | |||||
tableMap map[string]*ast.Table | |||||
stack []*stack | |||||
skip bool | |||||
} | |||||
func (p *toml) init(data []rune) { | |||||
p.line = 1 | |||||
p.table = &ast.Table{ | |||||
Line: p.line, | |||||
Type: ast.TableTypeNormal, | |||||
Data: data[:len(data)-1], // truncate the end_symbol added by PEG parse generator. | |||||
} | |||||
p.tableMap = map[string]*ast.Table{ | |||||
"": p.table, | |||||
} | |||||
p.currentTable = p.table | |||||
} | |||||
func (p *toml) Error(err error) { | |||||
panic(convertError{fmt.Errorf("toml: line %d: %v", p.line, err)}) | |||||
} | |||||
func (p *tomlParser) SetTime(begin, end int) { | |||||
p.val = &ast.Datetime{ | |||||
Position: ast.Position{Begin: begin, End: end}, | |||||
Data: p.buffer[begin:end], | |||||
Value: string(p.buffer[begin:end]), | |||||
} | |||||
} | |||||
func (p *tomlParser) SetFloat64(begin, end int) { | |||||
p.val = &ast.Float{ | |||||
Position: ast.Position{Begin: begin, End: end}, | |||||
Data: p.buffer[begin:end], | |||||
Value: underscoreReplacer.Replace(string(p.buffer[begin:end])), | |||||
} | |||||
} | |||||
func (p *tomlParser) SetInt64(begin, end int) { | |||||
p.val = &ast.Integer{ | |||||
Position: ast.Position{Begin: begin, End: end}, | |||||
Data: p.buffer[begin:end], | |||||
Value: underscoreReplacer.Replace(string(p.buffer[begin:end])), | |||||
} | |||||
} | |||||
func (p *tomlParser) SetString(begin, end int) { | |||||
p.val = &ast.String{ | |||||
Position: ast.Position{Begin: begin, End: end}, | |||||
Data: p.buffer[begin:end], | |||||
Value: p.s, | |||||
} | |||||
p.s = "" | |||||
} | |||||
func (p *tomlParser) SetBool(begin, end int) { | |||||
p.val = &ast.Boolean{ | |||||
Position: ast.Position{Begin: begin, End: end}, | |||||
Data: p.buffer[begin:end], | |||||
Value: string(p.buffer[begin:end]), | |||||
} | |||||
} | |||||
func (p *tomlParser) StartArray() { | |||||
if p.arr == nil { | |||||
p.arr = &array{line: p.line, current: &ast.Array{}} | |||||
return | |||||
} | |||||
p.arr.child = &array{parent: p.arr, line: p.line, current: &ast.Array{}} | |||||
p.arr = p.arr.child | |||||
} | |||||
func (p *tomlParser) AddArrayVal() { | |||||
if p.arr.current == nil { | |||||
p.arr.current = &ast.Array{} | |||||
} | |||||
p.arr.current.Value = append(p.arr.current.Value, p.val) | |||||
} | |||||
func (p *tomlParser) SetArray(begin, end int) { | |||||
p.arr.current.Position = ast.Position{Begin: begin, End: end} | |||||
p.arr.current.Data = p.buffer[begin:end] | |||||
p.val = p.arr.current | |||||
p.arr = p.arr.parent | |||||
} | |||||
func (p *toml) SetTable(buf []rune, begin, end int) { | |||||
p.setTable(p.table, buf, begin, end) | |||||
} | |||||
func (p *toml) setTable(t *ast.Table, buf []rune, begin, end int) { | |||||
name := string(buf[begin:end]) | |||||
names := splitTableKey(name) | |||||
if t, exists := p.tableMap[name]; exists { | |||||
if lt := p.tableMap[names[len(names)-1]]; t.Type == ast.TableTypeArray || lt != nil && lt.Type == ast.TableTypeNormal { | |||||
p.Error(fmt.Errorf("table `%s' is in conflict with %v table in line %d", name, t.Type, t.Line)) | |||||
} | |||||
} | |||||
t, err := p.lookupTable(t, names) | |||||
if err != nil { | |||||
p.Error(err) | |||||
} | |||||
p.currentTable = t | |||||
p.tableMap[name] = p.currentTable | |||||
} | |||||
func (p *tomlParser) SetTableString(begin, end int) { | |||||
p.currentTable.Data = p.buffer[begin:end] | |||||
p.currentTable.Position.Begin = begin | |||||
p.currentTable.Position.End = end | |||||
} | |||||
func (p *toml) SetArrayTable(buf []rune, begin, end int) { | |||||
p.setArrayTable(p.table, buf, begin, end) | |||||
} | |||||
func (p *toml) setArrayTable(t *ast.Table, buf []rune, begin, end int) { | |||||
name := string(buf[begin:end]) | |||||
if t, exists := p.tableMap[name]; exists && t.Type == ast.TableTypeNormal { | |||||
p.Error(fmt.Errorf("table `%s' is in conflict with %v table in line %d", name, t.Type, t.Line)) | |||||
} | |||||
names := splitTableKey(name) | |||||
t, err := p.lookupTable(t, names[:len(names)-1]) | |||||
if err != nil { | |||||
p.Error(err) | |||||
} | |||||
last := names[len(names)-1] | |||||
tbl := &ast.Table{ | |||||
Position: ast.Position{begin, end}, | |||||
Line: p.line, | |||||
Name: last, | |||||
Type: ast.TableTypeArray, | |||||
} | |||||
switch v := t.Fields[last].(type) { | |||||
case nil: | |||||
if t.Fields == nil { | |||||
t.Fields = make(map[string]interface{}) | |||||
} | |||||
t.Fields[last] = []*ast.Table{tbl} | |||||
case []*ast.Table: | |||||
t.Fields[last] = append(v, tbl) | |||||
case *ast.KeyValue: | |||||
p.Error(fmt.Errorf("key `%s' is in conflict with line %d", last, v.Line)) | |||||
default: | |||||
p.Error(fmt.Errorf("BUG: key `%s' is in conflict but it's unknown type `%T'", last, v)) | |||||
} | |||||
p.currentTable = tbl | |||||
p.tableMap[name] = p.currentTable | |||||
} | |||||
func (p *toml) StartInlineTable() { | |||||
p.skip = false | |||||
p.stack = append(p.stack, &stack{p.key, p.currentTable}) | |||||
buf := []rune(p.key) | |||||
if p.arr == nil { | |||||
p.setTable(p.currentTable, buf, 0, len(buf)) | |||||
} else { | |||||
p.setArrayTable(p.currentTable, buf, 0, len(buf)) | |||||
} | |||||
} | |||||
func (p *toml) EndInlineTable() { | |||||
st := p.stack[len(p.stack)-1] | |||||
p.key, p.currentTable = st.key, st.table | |||||
p.stack[len(p.stack)-1] = nil | |||||
p.stack = p.stack[:len(p.stack)-1] | |||||
p.skip = true | |||||
} | |||||
func (p *toml) AddLineCount(i int) { | |||||
p.line += i | |||||
} | |||||
func (p *toml) SetKey(buf []rune, begin, end int) { | |||||
p.key = string(buf[begin:end]) | |||||
} | |||||
func (p *toml) AddKeyValue() { | |||||
if p.skip { | |||||
p.skip = false | |||||
return | |||||
} | |||||
if val, exists := p.currentTable.Fields[p.key]; exists { | |||||
switch v := val.(type) { | |||||
case *ast.Table: | |||||
p.Error(fmt.Errorf("key `%s' is in conflict with %v table in line %d", p.key, v.Type, v.Line)) | |||||
case *ast.KeyValue: | |||||
p.Error(fmt.Errorf("key `%s' is in conflict with line %d", p.key, v.Line)) | |||||
default: | |||||
p.Error(fmt.Errorf("BUG: key `%s' is in conflict but it's unknown type `%T'", p.key, v)) | |||||
} | |||||
} | |||||
if p.currentTable.Fields == nil { | |||||
p.currentTable.Fields = make(map[string]interface{}) | |||||
} | |||||
p.currentTable.Fields[p.key] = &ast.KeyValue{ | |||||
Key: p.key, | |||||
Value: p.val, | |||||
Line: p.line, | |||||
} | |||||
} | |||||
func (p *toml) SetBasicString(buf []rune, begin, end int) { | |||||
p.s = p.unquote(string(buf[begin:end])) | |||||
} | |||||
func (p *toml) SetMultilineString() { | |||||
p.s = p.unquote(`"` + escapeReplacer.Replace(strings.TrimLeft(p.s, "\r\n")) + `"`) | |||||
} | |||||
func (p *toml) AddMultilineBasicBody(buf []rune, begin, end int) { | |||||
p.s += string(buf[begin:end]) | |||||
} | |||||
func (p *toml) SetLiteralString(buf []rune, begin, end int) { | |||||
p.s = string(buf[begin:end]) | |||||
} | |||||
func (p *toml) SetMultilineLiteralString(buf []rune, begin, end int) { | |||||
p.s = strings.TrimLeft(string(buf[begin:end]), "\r\n") | |||||
} | |||||
func (p *toml) unquote(s string) string { | |||||
s, err := strconv.Unquote(s) | |||||
if err != nil { | |||||
p.Error(err) | |||||
} | |||||
return s | |||||
} | |||||
func (p *toml) lookupTable(t *ast.Table, keys []string) (*ast.Table, error) { | |||||
for _, s := range keys { | |||||
val, exists := t.Fields[s] | |||||
if !exists { | |||||
tbl := &ast.Table{ | |||||
Line: p.line, | |||||
Name: s, | |||||
Type: ast.TableTypeNormal, | |||||
} | |||||
if t.Fields == nil { | |||||
t.Fields = make(map[string]interface{}) | |||||
} | |||||
t.Fields[s] = tbl | |||||
t = tbl | |||||
continue | |||||
} | |||||
switch v := val.(type) { | |||||
case *ast.Table: | |||||
t = v | |||||
case []*ast.Table: | |||||
t = v[len(v)-1] | |||||
case *ast.KeyValue: | |||||
return nil, fmt.Errorf("key `%s' is in conflict with line %d", s, v.Line) | |||||
default: | |||||
return nil, fmt.Errorf("BUG: key `%s' is in conflict but it's unknown type `%T'", s, v) | |||||
} | |||||
} | |||||
return t, nil | |||||
} | |||||
func splitTableKey(tk string) []string { | |||||
key := make([]byte, 0, 1) | |||||
keys := make([]string, 0, 1) | |||||
inQuote := false | |||||
for i := 0; i < len(tk); i++ { | |||||
k := tk[i] | |||||
switch { | |||||
case k == tableSeparator && !inQuote: | |||||
keys = append(keys, string(key)) | |||||
key = key[:0] // reuse buffer. | |||||
case k == '"': | |||||
inQuote = !inQuote | |||||
case (k == ' ' || k == '\t') && !inQuote: | |||||
// skip. | |||||
default: | |||||
key = append(key, k) | |||||
} | |||||
} | |||||
keys = append(keys, string(key)) | |||||
return keys | |||||
} | |||||
type convertError struct { | |||||
err error | |||||
} | |||||
func (e convertError) Error() string { | |||||
return e.err.Error() | |||||
} | |||||
type array struct { | |||||
parent *array | |||||
child *array | |||||
current *ast.Array | |||||
line int | |||||
} |
@ -1,235 +0,0 @@ | |||||
package toml | |||||
import ( | |||||
"fmt" | |||||
"io" | |||||
"reflect" | |||||
"strconv" | |||||
"time" | |||||
"go/ast" | |||||
"github.com/naoina/go-stringutil" | |||||
) | |||||
const ( | |||||
tagOmitempty = "omitempty" | |||||
tagSkip = "-" | |||||
) | |||||
// Marshal returns the TOML encoding of v. | |||||
// | |||||
// Struct values encode as TOML. Each exported struct field becomes a field of | |||||
// the TOML structure unless | |||||
// - the field's tag is "-", or | |||||
// - the field is empty and its tag specifies the "omitempty" option. | |||||
// The "toml" key in the struct field's tag value is the key name, followed by | |||||
// an optional comma and options. Examples: | |||||
// | |||||
// // Field is ignored by this package. | |||||
// Field int `toml:"-"` | |||||
// | |||||
// // Field appears in TOML as key "myName". | |||||
// Field int `toml:"myName"` | |||||
// | |||||
// // Field appears in TOML as key "myName" and the field is omitted from the | |||||
// // result of encoding if its value is empty. | |||||
// Field int `toml:"myName,omitempty"` | |||||
// | |||||
// // Field appears in TOML as key "field", but the field is skipped if | |||||
// // empty. | |||||
// // Note the leading comma. | |||||
// Field int `toml:",omitempty"` | |||||
func Marshal(v interface{}) ([]byte, error) { | |||||
return marshal(nil, "", reflect.ValueOf(v), false, false) | |||||
} | |||||
// A Encoder writes TOML to an output stream. | |||||
type Encoder struct { | |||||
w io.Writer | |||||
} | |||||
// NewEncoder returns a new Encoder that writes to w. | |||||
func NewEncoder(w io.Writer) *Encoder { | |||||
return &Encoder{ | |||||
w: w, | |||||
} | |||||
} | |||||
// Encode writes the TOML of v to the stream. | |||||
// See the documentation for Marshal for details about the conversion of Go values to TOML. | |||||
func (e *Encoder) Encode(v interface{}) error { | |||||
b, err := Marshal(v) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
_, err = e.w.Write(b) | |||||
return err | |||||
} | |||||
// Marshaler is the interface implemented by objects that can marshal themselves into valid TOML. | |||||
type Marshaler interface { | |||||
MarshalTOML() ([]byte, error) | |||||
} | |||||
func marshal(buf []byte, prefix string, rv reflect.Value, inArray, arrayTable bool) ([]byte, error) { | |||||
rt := rv.Type() | |||||
for i := 0; i < rv.NumField(); i++ { | |||||
ft := rt.Field(i) | |||||
if !ast.IsExported(ft.Name) { | |||||
continue | |||||
} | |||||
colName, rest := extractTag(rt.Field(i).Tag.Get(fieldTagName)) | |||||
if colName == tagSkip { | |||||
continue | |||||
} | |||||
if colName == "" { | |||||
colName = stringutil.ToSnakeCase(ft.Name) | |||||
} | |||||
fv := rv.Field(i) | |||||
switch rest { | |||||
case tagOmitempty: | |||||
if fv.Interface() == reflect.Zero(ft.Type).Interface() { | |||||
continue | |||||
} | |||||
} | |||||
var err error | |||||
if buf, err = encodeValue(buf, prefix, colName, fv, inArray, arrayTable); err != nil { | |||||
return nil, err | |||||
} | |||||
} | |||||
return buf, nil | |||||
} | |||||
func encodeValue(buf []byte, prefix, name string, fv reflect.Value, inArray, arrayTable bool) ([]byte, error) { | |||||
switch t := fv.Interface().(type) { | |||||
case Marshaler: | |||||
b, err := t.MarshalTOML() | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
return appendNewline(append(appendKey(buf, name, inArray, arrayTable), b...), inArray, arrayTable), nil | |||||
case time.Time: | |||||
return appendNewline(encodeTime(appendKey(buf, name, inArray, arrayTable), t), inArray, arrayTable), nil | |||||
} | |||||
switch fv.Kind() { | |||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |||||
return appendNewline(encodeInt(appendKey(buf, name, inArray, arrayTable), fv.Int()), inArray, arrayTable), nil | |||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | |||||
return appendNewline(encodeUint(appendKey(buf, name, inArray, arrayTable), fv.Uint()), inArray, arrayTable), nil | |||||
case reflect.Float32, reflect.Float64: | |||||
return appendNewline(encodeFloat(appendKey(buf, name, inArray, arrayTable), fv.Float()), inArray, arrayTable), nil | |||||
case reflect.Bool: | |||||
return appendNewline(encodeBool(appendKey(buf, name, inArray, arrayTable), fv.Bool()), inArray, arrayTable), nil | |||||
case reflect.String: | |||||
return appendNewline(encodeString(appendKey(buf, name, inArray, arrayTable), fv.String()), inArray, arrayTable), nil | |||||
case reflect.Slice, reflect.Array: | |||||
ft := fv.Type().Elem() | |||||
for ft.Kind() == reflect.Ptr { | |||||
ft = ft.Elem() | |||||
} | |||||
if ft.Kind() == reflect.Struct { | |||||
name := tableName(prefix, name) | |||||
var err error | |||||
for i := 0; i < fv.Len(); i++ { | |||||
if buf, err = marshal(append(append(append(buf, '[', '['), name...), ']', ']', '\n'), name, fv.Index(i), false, true); err != nil { | |||||
return nil, err | |||||
} | |||||
} | |||||
return buf, nil | |||||
} | |||||
buf = append(appendKey(buf, name, inArray, arrayTable), '[') | |||||
var err error | |||||
for i := 0; i < fv.Len(); i++ { | |||||
if i != 0 { | |||||
buf = append(buf, ',') | |||||
} | |||||
if buf, err = encodeValue(buf, prefix, name, fv.Index(i), true, false); err != nil { | |||||
return nil, err | |||||
} | |||||
} | |||||
return appendNewline(append(buf, ']'), inArray, arrayTable), nil | |||||
case reflect.Struct: | |||||
name := tableName(prefix, name) | |||||
return marshal(append(append(append(buf, '['), name...), ']', '\n'), name, fv, inArray, arrayTable) | |||||
case reflect.Interface: | |||||
var err error | |||||
if buf, err = encodeInterface(appendKey(buf, name, inArray, arrayTable), fv.Interface()); err != nil { | |||||
return nil, err | |||||
} | |||||
return appendNewline(buf, inArray, arrayTable), nil | |||||
} | |||||
return nil, fmt.Errorf("toml: marshal: unsupported type %v", fv.Kind()) | |||||
} | |||||
func appendKey(buf []byte, key string, inArray, arrayTable bool) []byte { | |||||
if !inArray { | |||||
return append(append(buf, key...), '=') | |||||
} | |||||
return buf | |||||
} | |||||
func appendNewline(buf []byte, inArray, arrayTable bool) []byte { | |||||
if !inArray { | |||||
return append(buf, '\n') | |||||
} | |||||
return buf | |||||
} | |||||
func encodeInterface(buf []byte, v interface{}) ([]byte, error) { | |||||
switch v := v.(type) { | |||||
case int: | |||||
return encodeInt(buf, int64(v)), nil | |||||
case int8: | |||||
return encodeInt(buf, int64(v)), nil | |||||
case int16: | |||||
return encodeInt(buf, int64(v)), nil | |||||
case int32: | |||||
return encodeInt(buf, int64(v)), nil | |||||
case int64: | |||||
return encodeInt(buf, v), nil | |||||
case uint: | |||||
return encodeUint(buf, uint64(v)), nil | |||||
case uint8: | |||||
return encodeUint(buf, uint64(v)), nil | |||||
case uint16: | |||||
return encodeUint(buf, uint64(v)), nil | |||||
case uint32: | |||||
return encodeUint(buf, uint64(v)), nil | |||||
case uint64: | |||||
return encodeUint(buf, v), nil | |||||
case float32: | |||||
return encodeFloat(buf, float64(v)), nil | |||||
case float64: | |||||
return encodeFloat(buf, v), nil | |||||
case bool: | |||||
return encodeBool(buf, v), nil | |||||
case string: | |||||
return encodeString(buf, v), nil | |||||
} | |||||
return nil, fmt.Errorf("toml: marshal: unable to detect a type of value `%v'", v) | |||||
} | |||||
func encodeInt(buf []byte, i int64) []byte { | |||||
return strconv.AppendInt(buf, i, 10) | |||||
} | |||||
func encodeUint(buf []byte, u uint64) []byte { | |||||
return strconv.AppendUint(buf, u, 10) | |||||
} | |||||
func encodeFloat(buf []byte, f float64) []byte { | |||||
return strconv.AppendFloat(buf, f, 'e', -1, 64) | |||||
} | |||||
func encodeBool(buf []byte, b bool) []byte { | |||||
return strconv.AppendBool(buf, b) | |||||
} | |||||
func encodeString(buf []byte, s string) []byte { | |||||
return strconv.AppendQuote(buf, s) | |||||
} | |||||
func encodeTime(buf []byte, t time.Time) []byte { | |||||
return append(buf, t.Format(time.RFC3339Nano)...) | |||||
} |
@ -1,31 +0,0 @@ | |||||
package toml | |||||
import ( | |||||
"fmt" | |||||
"reflect" | |||||
) | |||||
func (e *parseError) Line() int { | |||||
tokens := e.p.tokenTree.Error() | |||||
positions := make([]int, len(tokens)*2) | |||||
p := 0 | |||||
for _, token := range tokens { | |||||
positions[p], p = int(token.begin), p+1 | |||||
positions[p], p = int(token.end), p+1 | |||||
} | |||||
for _, t := range translatePositions(e.p.Buffer, positions) { | |||||
if e.p.line < t.line { | |||||
e.p.line = t.line | |||||
} | |||||
} | |||||
return e.p.line | |||||
} | |||||
type errorOutOfRange struct { | |||||
kind reflect.Kind | |||||
v interface{} | |||||
} | |||||
func (err *errorOutOfRange) Error() string { | |||||
return fmt.Sprintf("value %d is out of range for `%v` type", err.v, err.kind) | |||||
} |
@ -1,54 +0,0 @@ | |||||
package toml | |||||
import ( | |||||
"fmt" | |||||
"github.com/naoina/toml/ast" | |||||
) | |||||
// Parse returns an AST representation of TOML. | |||||
// The toplevel is represented by a table. | |||||
func Parse(data []byte) (*ast.Table, error) { | |||||
d := &parseState{p: &tomlParser{Buffer: string(data)}} | |||||
d.init() | |||||
if err := d.parse(); err != nil { | |||||
return nil, err | |||||
} | |||||
return d.p.toml.table, nil | |||||
} | |||||
type parseState struct { | |||||
p *tomlParser | |||||
} | |||||
func (d *parseState) init() { | |||||
d.p.Init() | |||||
d.p.toml.init(d.p.buffer) | |||||
} | |||||
func (d *parseState) parse() error { | |||||
if err := d.p.Parse(); err != nil { | |||||
if err, ok := err.(*parseError); ok { | |||||
return fmt.Errorf("toml: line %d: parse error", err.Line()) | |||||
} | |||||
return err | |||||
} | |||||
return d.execute() | |||||
} | |||||
func (d *parseState) execute() (err error) { | |||||
defer func() { | |||||
e := recover() | |||||
if e != nil { | |||||
cerr, ok := e.(convertError) | |||||
if !ok { | |||||
panic(e) | |||||
} | |||||
err = cerr.err | |||||
} | |||||
}() | |||||
d.p.Execute() | |||||
return nil | |||||
} |
@ -1,138 +0,0 @@ | |||||
package toml | |||||
type tomlParser Peg { | |||||
toml | |||||
} | |||||
TOML <- Expression (newline Expression)* newline? !. { _ = buffer } | |||||
Expression <- ( | |||||
<ws table ws comment? (wsnl keyval ws comment?)*> { p.SetTableString(begin, end) } | |||||
/ ws keyval ws comment? | |||||
/ ws comment? | |||||
/ ws | |||||
) | |||||
newline <- <[\r\n]+> { p.AddLineCount(end - begin) } | |||||
ws <- [ \t]* | |||||
wsnl <- ( | |||||
[ \t] | |||||
/ <[\r\n]> { p.AddLineCount(end - begin) } | |||||
)* | |||||
comment <- '#' <[\t -\0x10FFFF]*> | |||||
keyval <- key ws '=' ws val { p.AddKeyValue() } | |||||
key <- bareKey / quotedKey | |||||
bareKey <- <[0-9A-Za-z\-_]+> { p.SetKey(p.buffer, begin, end) } | |||||
quotedKey <- '"' <basicChar+> '"' { p.SetKey(p.buffer, begin, end) } | |||||
val <- ( | |||||
<datetime> { p.SetTime(begin, end) } | |||||
/ <float> { p.SetFloat64(begin, end) } | |||||
/ <integer> { p.SetInt64(begin, end) } | |||||
/ <string> { p.SetString(begin, end) } | |||||
/ <boolean> { p.SetBool(begin, end) } | |||||
/ <array> { p.SetArray(begin, end) } | |||||
/ inlineTable | |||||
) | |||||
table <- stdTable / arrayTable | |||||
stdTable <- '[' ws <tableKey> ws ']' { p.SetTable(p.buffer, begin, end) } | |||||
arrayTable <- '[[' ws <tableKey> ws ']]' { p.SetArrayTable(p.buffer, begin, end) } | |||||
inlineTable <- ( | |||||
'{' { p.StartInlineTable() } | |||||
ws inlineTableKeyValues ws | |||||
'}' { p.EndInlineTable() } | |||||
) | |||||
inlineTableKeyValues <- (keyval inlineTableValSep?)* | |||||
tableKey <- key (tableKeySep key)* | |||||
tableKeySep <- ws '.' ws | |||||
inlineTableValSep <- ws ',' ws | |||||
integer <- [\-+]? int | |||||
int <- [1-9] (digit / '_' digit)+ / digit | |||||
float <- integer (frac exp? / frac? exp) | |||||
frac <- '.' digit (digit / '_' digit)* | |||||
exp <- [eE] [\-+]? digit (digit / '_' digit)* | |||||
string <- ( | |||||
mlLiteralString | |||||
/ literalString | |||||
/ mlBasicString | |||||
/ basicString | |||||
) | |||||
basicString <- <'"' basicChar* '"'> { p.SetBasicString(p.buffer, begin, end) } | |||||
basicChar <- basicUnescaped / escaped | |||||
escaped <- escape ([btnfr"/\\] / 'u' hexQuad / 'U' hexQuad hexQuad) | |||||
basicUnescaped <- [ -!#-\[\]-\0x10FFFF] | |||||
escape <- '\\' | |||||
mlBasicString <- '"""' mlBasicBody '"""' { p.SetMultilineString() } | |||||
mlBasicBody <- ( | |||||
<basicChar / newline> { p.AddMultilineBasicBody(p.buffer, begin, end) } | |||||
/ escape newline wsnl | |||||
)* | |||||
literalString <- "'" <literalChar*> "'" { p.SetLiteralString(p.buffer, begin, end) } | |||||
literalChar <- [\t -&(-\0x10FFFF] | |||||
mlLiteralString <- "'''" <mlLiteralBody> "'''" { p.SetMultilineLiteralString(p.buffer, begin, end) } | |||||
mlLiteralBody <- (!"'''" (mlLiteralChar / newline))* | |||||
mlLiteralChar <- [\t -\0x10FFFF] | |||||
hexdigit <- [0-9A-Fa-f] | |||||
hexQuad <- hexdigit hexdigit hexdigit hexdigit | |||||
boolean <- 'true' / 'false' | |||||
dateFullYear <- digitQuad | |||||
dateMonth <- digitDual | |||||
dateMDay <- digitDual | |||||
timeHour <- digitDual | |||||
timeMinute <- digitDual | |||||
timeSecond <- digitDual | |||||
timeSecfrac <- '.' digit+ | |||||
timeNumoffset <- [\-+] timeHour ':' timeMinute | |||||
timeOffset <- 'Z' / timeNumoffset | |||||
partialTime <- timeHour ':' timeMinute ':' timeSecond timeSecfrac? | |||||
fullDate <- dateFullYear '-' dateMonth '-' dateMDay | |||||
fullTime <- partialTime timeOffset | |||||
datetime <- fullDate 'T' fullTime | |||||
digit <- [0-9] | |||||
digitDual <- digit digit | |||||
digitQuad <- digitDual digitDual | |||||
array <- ( | |||||
'[' { p.StartArray() } | |||||
wsnl arrayValues wsnl | |||||
']' | |||||
) | |||||
arrayValues <- ( | |||||
val { p.AddArrayVal() } | |||||
arraySep? (comment? newline)? | |||||
)* | |||||
arraySep <- ws ',' wsnl |