mirror of
https://github.com/sorenisanerd/gotty.git
synced 2024-11-23 12:54:24 +00:00
Initialize repository
This commit is contained in:
commit
cba86dd046
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
gotty
|
||||||
|
bindata
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "libapps"]
|
||||||
|
path = libapps
|
||||||
|
url = https://chromium.googlesource.com/apps/libapps
|
24
Godeps/Godeps.json
generated
Normal file
24
Godeps/Godeps.json
generated
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"ImportPath": "github.com/yudai/gotty",
|
||||||
|
"GoVersion": "go1.4",
|
||||||
|
"Deps": [
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/codegangsta/cli",
|
||||||
|
"Comment": "1.2.0-139-g142e6cd",
|
||||||
|
"Rev": "142e6cd241a4dfbf7f07a018f1f8225180018da4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/elazarl/go-bindata-assetfs",
|
||||||
|
"Rev": "d5cac425555ca5cf00694df246e04f05e6a55150"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gorilla/websocket",
|
||||||
|
"Rev": "b6ab76f1fe9803ee1d59e7e5b2a797c1fe897ce5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/kr/pty",
|
||||||
|
"Comment": "release.r56-28-g5cf931e",
|
||||||
|
"Rev": "5cf931ef8f76dccd0910001d74a58a7fca84a83d"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
5
Godeps/Readme
generated
Normal file
5
Godeps/Readme
generated
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
This directory tree is generated automatically by godep.
|
||||||
|
|
||||||
|
Please do not edit.
|
||||||
|
|
||||||
|
See https://github.com/tools/godep for more information.
|
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/pkg
|
||||||
|
/bin
|
13
Godeps/_workspace/src/github.com/codegangsta/cli/.travis.yml
generated
vendored
Normal file
13
Godeps/_workspace/src/github.com/codegangsta/cli/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.0.3
|
||||||
|
- 1.1.2
|
||||||
|
- 1.2.2
|
||||||
|
- 1.3.3
|
||||||
|
- 1.4.2
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go vet ./...
|
||||||
|
- go test -v ./...
|
21
Godeps/_workspace/src/github.com/codegangsta/cli/LICENSE
generated
vendored
Normal file
21
Godeps/_workspace/src/github.com/codegangsta/cli/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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.
|
308
Godeps/_workspace/src/github.com/codegangsta/cli/README.md
generated
vendored
Normal file
308
Godeps/_workspace/src/github.com/codegangsta/cli/README.md
generated
vendored
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
[![Build Status](https://travis-ci.org/codegangsta/cli.png?branch=master)](https://travis-ci.org/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.
|
||||||
|
|
||||||
|
You can view the API docs here:
|
||||||
|
http://godoc.org/github.com/codegangsta/cli
|
||||||
|
|
||||||
|
## 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 some bitchass 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
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 and modify `autocomplete/bash_autocomplete` to use your program name
|
||||||
|
rather than `$PROG` and have the user copy the file into
|
||||||
|
`/etc/bash_completion.d/` (or automatically install it there if you are
|
||||||
|
distributing a package). Alternatively you can just document that users should
|
||||||
|
source the generic `autocomplete/bash_autocomplete` with `$PROG` set to your
|
||||||
|
program name in their bash configuration.
|
||||||
|
|
||||||
|
## 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.
|
308
Godeps/_workspace/src/github.com/codegangsta/cli/app.go
generated
vendored
Normal file
308
Godeps/_workspace/src/github.com/codegangsta/cli/app.go
generated
vendored
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"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 os.Args[0]
|
||||||
|
Name string
|
||||||
|
// Description of the program.
|
||||||
|
Usage 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: 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})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 err != nil {
|
||||||
|
fmt.Fprintln(a.Writer, "Incorrect Usage.")
|
||||||
|
fmt.Fprintln(a.Writer)
|
||||||
|
ShowAppHelp(context)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkCompletions(context) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkHelp(context) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkVersion(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 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 err != nil {
|
||||||
|
fmt.Fprintln(a.Writer, "Incorrect Usage.")
|
||||||
|
fmt.Fprintln(a.Writer)
|
||||||
|
ShowSubcommandHelp(context)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkCompletions(context) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
867
Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go
generated
vendored
Normal file
867
Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go
generated
vendored
Normal file
@ -0,0 +1,867 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleApp() {
|
||||||
|
// set args for examples sake
|
||||||
|
os.Args = []string{"greet", "--name", "Jeremy"}
|
||||||
|
|
||||||
|
app := NewApp()
|
||||||
|
app.Name = "greet"
|
||||||
|
app.Flags = []Flag{
|
||||||
|
StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
|
||||||
|
}
|
||||||
|
app.Action = func(c *Context) {
|
||||||
|
fmt.Printf("Hello %v\n", c.String("name"))
|
||||||
|
}
|
||||||
|
app.Author = "Harrison"
|
||||||
|
app.Email = "harrison@lolwut.com"
|
||||||
|
app.Authors = []Author{Author{Name: "Oliver Allen", Email: "oliver@toyshop.com"}}
|
||||||
|
app.Run(os.Args)
|
||||||
|
// Output:
|
||||||
|
// Hello Jeremy
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleAppSubcommand() {
|
||||||
|
// set args for examples sake
|
||||||
|
os.Args = []string{"say", "hi", "english", "--name", "Jeremy"}
|
||||||
|
app := NewApp()
|
||||||
|
app.Name = "say"
|
||||||
|
app.Commands = []Command{
|
||||||
|
{
|
||||||
|
Name: "hello",
|
||||||
|
Aliases: []string{"hi"},
|
||||||
|
Usage: "use it to see a description",
|
||||||
|
Description: "This is how we describe hello the function",
|
||||||
|
Subcommands: []Command{
|
||||||
|
{
|
||||||
|
Name: "english",
|
||||||
|
Aliases: []string{"en"},
|
||||||
|
Usage: "sends a greeting in english",
|
||||||
|
Description: "greets someone in english",
|
||||||
|
Flags: []Flag{
|
||||||
|
StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Value: "Bob",
|
||||||
|
Usage: "Name of the person to greet",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *Context) {
|
||||||
|
fmt.Println("Hello,", c.String("name"))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run(os.Args)
|
||||||
|
// Output:
|
||||||
|
// Hello, Jeremy
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleAppHelp() {
|
||||||
|
// set args for examples sake
|
||||||
|
os.Args = []string{"greet", "h", "describeit"}
|
||||||
|
|
||||||
|
app := NewApp()
|
||||||
|
app.Name = "greet"
|
||||||
|
app.Flags = []Flag{
|
||||||
|
StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
|
||||||
|
}
|
||||||
|
app.Commands = []Command{
|
||||||
|
{
|
||||||
|
Name: "describeit",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Usage: "use it to see a description",
|
||||||
|
Description: "This is how we describe describeit the function",
|
||||||
|
Action: func(c *Context) {
|
||||||
|
fmt.Printf("i like to describe things")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Run(os.Args)
|
||||||
|
// Output:
|
||||||
|
// NAME:
|
||||||
|
// describeit - use it to see a description
|
||||||
|
//
|
||||||
|
// USAGE:
|
||||||
|
// command describeit [arguments...]
|
||||||
|
//
|
||||||
|
// DESCRIPTION:
|
||||||
|
// This is how we describe describeit the function
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleAppBashComplete() {
|
||||||
|
// set args for examples sake
|
||||||
|
os.Args = []string{"greet", "--generate-bash-completion"}
|
||||||
|
|
||||||
|
app := NewApp()
|
||||||
|
app.Name = "greet"
|
||||||
|
app.EnableBashCompletion = true
|
||||||
|
app.Commands = []Command{
|
||||||
|
{
|
||||||
|
Name: "describeit",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Usage: "use it to see a description",
|
||||||
|
Description: "This is how we describe describeit the function",
|
||||||
|
Action: func(c *Context) {
|
||||||
|
fmt.Printf("i like to describe things")
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Name: "next",
|
||||||
|
Usage: "next example",
|
||||||
|
Description: "more stuff to see when generating bash completion",
|
||||||
|
Action: func(c *Context) {
|
||||||
|
fmt.Printf("the next example")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run(os.Args)
|
||||||
|
// Output:
|
||||||
|
// describeit
|
||||||
|
// d
|
||||||
|
// next
|
||||||
|
// help
|
||||||
|
// h
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_Run(t *testing.T) {
|
||||||
|
s := ""
|
||||||
|
|
||||||
|
app := NewApp()
|
||||||
|
app.Action = func(c *Context) {
|
||||||
|
s = s + c.Args().First()
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.Run([]string{"command", "foo"})
|
||||||
|
expect(t, err, nil)
|
||||||
|
err = app.Run([]string{"command", "bar"})
|
||||||
|
expect(t, err, nil)
|
||||||
|
expect(t, s, "foobar")
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandAppTests = []struct {
|
||||||
|
name string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"foobar", true},
|
||||||
|
{"batbaz", true},
|
||||||
|
{"b", true},
|
||||||
|
{"f", true},
|
||||||
|
{"bat", false},
|
||||||
|
{"nothing", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_Command(t *testing.T) {
|
||||||
|
app := NewApp()
|
||||||
|
fooCommand := Command{Name: "foobar", Aliases: []string{"f"}}
|
||||||
|
batCommand := Command{Name: "batbaz", Aliases: []string{"b"}}
|
||||||
|
app.Commands = []Command{
|
||||||
|
fooCommand,
|
||||||
|
batCommand,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range commandAppTests {
|
||||||
|
expect(t, app.Command(test.name) != nil, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_CommandWithArgBeforeFlags(t *testing.T) {
|
||||||
|
var parsedOption, firstArg string
|
||||||
|
|
||||||
|
app := NewApp()
|
||||||
|
command := Command{
|
||||||
|
Name: "cmd",
|
||||||
|
Flags: []Flag{
|
||||||
|
StringFlag{Name: "option", Value: "", Usage: "some option"},
|
||||||
|
},
|
||||||
|
Action: func(c *Context) {
|
||||||
|
parsedOption = c.String("option")
|
||||||
|
firstArg = c.Args().First()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Commands = []Command{command}
|
||||||
|
|
||||||
|
app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"})
|
||||||
|
|
||||||
|
expect(t, parsedOption, "my-option")
|
||||||
|
expect(t, firstArg, "my-arg")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
|
||||||
|
var context *Context
|
||||||
|
|
||||||
|
a := NewApp()
|
||||||
|
a.Commands = []Command{
|
||||||
|
{
|
||||||
|
Name: "foo",
|
||||||
|
Action: func(c *Context) {
|
||||||
|
context = c
|
||||||
|
},
|
||||||
|
Flags: []Flag{
|
||||||
|
StringFlag{
|
||||||
|
Name: "lang",
|
||||||
|
Value: "english",
|
||||||
|
Usage: "language for the greeting",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Before: func(_ *Context) error { return nil },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"", "foo", "--lang", "spanish", "abcd"})
|
||||||
|
|
||||||
|
expect(t, context.Args().Get(0), "abcd")
|
||||||
|
expect(t, context.String("lang"), "spanish")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
|
||||||
|
var parsedOption string
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
app := NewApp()
|
||||||
|
command := Command{
|
||||||
|
Name: "cmd",
|
||||||
|
Flags: []Flag{
|
||||||
|
StringFlag{Name: "option", Value: "", Usage: "some option"},
|
||||||
|
},
|
||||||
|
Action: func(c *Context) {
|
||||||
|
parsedOption = c.String("option")
|
||||||
|
args = c.Args()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Commands = []Command{command}
|
||||||
|
|
||||||
|
app.Run([]string{"", "cmd", "my-arg", "--option", "my-option", "--", "--notARealFlag"})
|
||||||
|
|
||||||
|
expect(t, parsedOption, "my-option")
|
||||||
|
expect(t, args[0], "my-arg")
|
||||||
|
expect(t, args[1], "--")
|
||||||
|
expect(t, args[2], "--notARealFlag")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) {
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
app := NewApp()
|
||||||
|
command := Command{
|
||||||
|
Name: "cmd",
|
||||||
|
Action: func(c *Context) {
|
||||||
|
args = c.Args()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Commands = []Command{command}
|
||||||
|
|
||||||
|
app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"})
|
||||||
|
|
||||||
|
expect(t, args[0], "my-arg")
|
||||||
|
expect(t, args[1], "--")
|
||||||
|
expect(t, args[2], "notAFlagAtAll")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_Float64Flag(t *testing.T) {
|
||||||
|
var meters float64
|
||||||
|
|
||||||
|
app := NewApp()
|
||||||
|
app.Flags = []Flag{
|
||||||
|
Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"},
|
||||||
|
}
|
||||||
|
app.Action = func(c *Context) {
|
||||||
|
meters = c.Float64("height")
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run([]string{"", "--height", "1.93"})
|
||||||
|
expect(t, meters, 1.93)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_ParseSliceFlags(t *testing.T) {
|
||||||
|
var parsedOption, firstArg string
|
||||||
|
var parsedIntSlice []int
|
||||||
|
var parsedStringSlice []string
|
||||||
|
|
||||||
|
app := NewApp()
|
||||||
|
command := Command{
|
||||||
|
Name: "cmd",
|
||||||
|
Flags: []Flag{
|
||||||
|
IntSliceFlag{Name: "p", Value: &IntSlice{}, Usage: "set one or more ip addr"},
|
||||||
|
StringSliceFlag{Name: "ip", Value: &StringSlice{}, Usage: "set one or more ports to open"},
|
||||||
|
},
|
||||||
|
Action: func(c *Context) {
|
||||||
|
parsedIntSlice = c.IntSlice("p")
|
||||||
|
parsedStringSlice = c.StringSlice("ip")
|
||||||
|
parsedOption = c.String("option")
|
||||||
|
firstArg = c.Args().First()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Commands = []Command{command}
|
||||||
|
|
||||||
|
app.Run([]string{"", "cmd", "my-arg", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4"})
|
||||||
|
|
||||||
|
IntsEquals := func(a, b []int) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, v := range a {
|
||||||
|
if v != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
StrsEquals := func(a, b []string) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, v := range a {
|
||||||
|
if v != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
var expectedIntSlice = []int{22, 80}
|
||||||
|
var expectedStringSlice = []string{"8.8.8.8", "8.8.4.4"}
|
||||||
|
|
||||||
|
if !IntsEquals(parsedIntSlice, expectedIntSlice) {
|
||||||
|
t.Errorf("%v does not match %v", parsedIntSlice, expectedIntSlice)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !StrsEquals(parsedStringSlice, expectedStringSlice) {
|
||||||
|
t.Errorf("%v does not match %v", parsedStringSlice, expectedStringSlice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) {
|
||||||
|
var parsedIntSlice []int
|
||||||
|
var parsedStringSlice []string
|
||||||
|
|
||||||
|
app := NewApp()
|
||||||
|
command := Command{
|
||||||
|
Name: "cmd",
|
||||||
|
Flags: []Flag{
|
||||||
|
IntSliceFlag{Name: "a", Usage: "set numbers"},
|
||||||
|
StringSliceFlag{Name: "str", Usage: "set strings"},
|
||||||
|
},
|
||||||
|
Action: func(c *Context) {
|
||||||
|
parsedIntSlice = c.IntSlice("a")
|
||||||
|
parsedStringSlice = c.StringSlice("str")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Commands = []Command{command}
|
||||||
|
|
||||||
|
app.Run([]string{"", "cmd", "my-arg", "-a", "2", "-str", "A"})
|
||||||
|
|
||||||
|
var expectedIntSlice = []int{2}
|
||||||
|
var expectedStringSlice = []string{"A"}
|
||||||
|
|
||||||
|
if parsedIntSlice[0] != expectedIntSlice[0] {
|
||||||
|
t.Errorf("%v does not match %v", parsedIntSlice[0], expectedIntSlice[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedStringSlice[0] != expectedStringSlice[0] {
|
||||||
|
t.Errorf("%v does not match %v", parsedIntSlice[0], expectedIntSlice[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_DefaultStdout(t *testing.T) {
|
||||||
|
app := NewApp()
|
||||||
|
|
||||||
|
if app.Writer != os.Stdout {
|
||||||
|
t.Error("Default output writer not set.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockWriter struct {
|
||||||
|
written []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fw *mockWriter) Write(p []byte) (n int, err error) {
|
||||||
|
if fw.written == nil {
|
||||||
|
fw.written = p
|
||||||
|
} else {
|
||||||
|
fw.written = append(fw.written, p...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fw *mockWriter) GetWritten() (b []byte) {
|
||||||
|
return fw.written
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_SetStdout(t *testing.T) {
|
||||||
|
w := &mockWriter{}
|
||||||
|
|
||||||
|
app := NewApp()
|
||||||
|
app.Name = "test"
|
||||||
|
app.Writer = w
|
||||||
|
|
||||||
|
err := app.Run([]string{"help"})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Run error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(w.written) == 0 {
|
||||||
|
t.Error("App did not write output to desired writer.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_BeforeFunc(t *testing.T) {
|
||||||
|
beforeRun, subcommandRun := false, false
|
||||||
|
beforeError := fmt.Errorf("fail")
|
||||||
|
var err error
|
||||||
|
|
||||||
|
app := NewApp()
|
||||||
|
|
||||||
|
app.Before = func(c *Context) error {
|
||||||
|
beforeRun = true
|
||||||
|
s := c.String("opt")
|
||||||
|
if s == "fail" {
|
||||||
|
return beforeError
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Commands = []Command{
|
||||||
|
Command{
|
||||||
|
Name: "sub",
|
||||||
|
Action: func(c *Context) {
|
||||||
|
subcommandRun = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Flags = []Flag{
|
||||||
|
StringFlag{Name: "opt"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// run with the Before() func succeeding
|
||||||
|
err = app.Run([]string{"command", "--opt", "succeed", "sub"})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Run error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if beforeRun == false {
|
||||||
|
t.Errorf("Before() not executed when expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
if subcommandRun == false {
|
||||||
|
t.Errorf("Subcommand not executed when expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset
|
||||||
|
beforeRun, subcommandRun = false, false
|
||||||
|
|
||||||
|
// run with the Before() func failing
|
||||||
|
err = app.Run([]string{"command", "--opt", "fail", "sub"})
|
||||||
|
|
||||||
|
// should be the same error produced by the Before func
|
||||||
|
if err != beforeError {
|
||||||
|
t.Errorf("Run error expected, but not received")
|
||||||
|
}
|
||||||
|
|
||||||
|
if beforeRun == false {
|
||||||
|
t.Errorf("Before() not executed when expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
if subcommandRun == true {
|
||||||
|
t.Errorf("Subcommand executed when NOT expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_AfterFunc(t *testing.T) {
|
||||||
|
afterRun, subcommandRun := false, false
|
||||||
|
afterError := fmt.Errorf("fail")
|
||||||
|
var err error
|
||||||
|
|
||||||
|
app := NewApp()
|
||||||
|
|
||||||
|
app.After = func(c *Context) error {
|
||||||
|
afterRun = true
|
||||||
|
s := c.String("opt")
|
||||||
|
if s == "fail" {
|
||||||
|
return afterError
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Commands = []Command{
|
||||||
|
Command{
|
||||||
|
Name: "sub",
|
||||||
|
Action: func(c *Context) {
|
||||||
|
subcommandRun = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Flags = []Flag{
|
||||||
|
StringFlag{Name: "opt"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// run with the After() func succeeding
|
||||||
|
err = app.Run([]string{"command", "--opt", "succeed", "sub"})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Run error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if afterRun == false {
|
||||||
|
t.Errorf("After() not executed when expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
if subcommandRun == false {
|
||||||
|
t.Errorf("Subcommand not executed when expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset
|
||||||
|
afterRun, subcommandRun = false, false
|
||||||
|
|
||||||
|
// run with the Before() func failing
|
||||||
|
err = app.Run([]string{"command", "--opt", "fail", "sub"})
|
||||||
|
|
||||||
|
// should be the same error produced by the Before func
|
||||||
|
if err != afterError {
|
||||||
|
t.Errorf("Run error expected, but not received")
|
||||||
|
}
|
||||||
|
|
||||||
|
if afterRun == false {
|
||||||
|
t.Errorf("After() not executed when expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
if subcommandRun == false {
|
||||||
|
t.Errorf("Subcommand not executed when expected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppNoHelpFlag(t *testing.T) {
|
||||||
|
oldFlag := HelpFlag
|
||||||
|
defer func() {
|
||||||
|
HelpFlag = oldFlag
|
||||||
|
}()
|
||||||
|
|
||||||
|
HelpFlag = BoolFlag{}
|
||||||
|
|
||||||
|
app := NewApp()
|
||||||
|
err := app.Run([]string{"test", "-h"})
|
||||||
|
|
||||||
|
if err != flag.ErrHelp {
|
||||||
|
t.Errorf("expected error about missing help flag, but got: %s (%T)", err, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppHelpPrinter(t *testing.T) {
|
||||||
|
oldPrinter := HelpPrinter
|
||||||
|
defer func() {
|
||||||
|
HelpPrinter = oldPrinter
|
||||||
|
}()
|
||||||
|
|
||||||
|
var wasCalled = false
|
||||||
|
HelpPrinter = func(w io.Writer, template string, data interface{}) {
|
||||||
|
wasCalled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
app := NewApp()
|
||||||
|
app.Run([]string{"-h"})
|
||||||
|
|
||||||
|
if wasCalled == false {
|
||||||
|
t.Errorf("Help printer expected to be called, but was not")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppVersionPrinter(t *testing.T) {
|
||||||
|
oldPrinter := VersionPrinter
|
||||||
|
defer func() {
|
||||||
|
VersionPrinter = oldPrinter
|
||||||
|
}()
|
||||||
|
|
||||||
|
var wasCalled = false
|
||||||
|
VersionPrinter = func(c *Context) {
|
||||||
|
wasCalled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
app := NewApp()
|
||||||
|
ctx := NewContext(app, nil, nil)
|
||||||
|
ShowVersion(ctx)
|
||||||
|
|
||||||
|
if wasCalled == false {
|
||||||
|
t.Errorf("Version printer expected to be called, but was not")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppCommandNotFound(t *testing.T) {
|
||||||
|
beforeRun, subcommandRun := false, false
|
||||||
|
app := NewApp()
|
||||||
|
|
||||||
|
app.CommandNotFound = func(c *Context, command string) {
|
||||||
|
beforeRun = true
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Commands = []Command{
|
||||||
|
Command{
|
||||||
|
Name: "bar",
|
||||||
|
Action: func(c *Context) {
|
||||||
|
subcommandRun = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run([]string{"command", "foo"})
|
||||||
|
|
||||||
|
expect(t, beforeRun, true)
|
||||||
|
expect(t, subcommandRun, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGlobalFlag(t *testing.T) {
|
||||||
|
var globalFlag string
|
||||||
|
var globalFlagSet bool
|
||||||
|
app := NewApp()
|
||||||
|
app.Flags = []Flag{
|
||||||
|
StringFlag{Name: "global, g", Usage: "global"},
|
||||||
|
}
|
||||||
|
app.Action = func(c *Context) {
|
||||||
|
globalFlag = c.GlobalString("global")
|
||||||
|
globalFlagSet = c.GlobalIsSet("global")
|
||||||
|
}
|
||||||
|
app.Run([]string{"command", "-g", "foo"})
|
||||||
|
expect(t, globalFlag, "foo")
|
||||||
|
expect(t, globalFlagSet, true)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGlobalFlagsInSubcommands(t *testing.T) {
|
||||||
|
subcommandRun := false
|
||||||
|
parentFlag := false
|
||||||
|
app := NewApp()
|
||||||
|
|
||||||
|
app.Flags = []Flag{
|
||||||
|
BoolFlag{Name: "debug, d", Usage: "Enable debugging"},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Commands = []Command{
|
||||||
|
Command{
|
||||||
|
Name: "foo",
|
||||||
|
Flags: []Flag{
|
||||||
|
BoolFlag{Name: "parent, p", Usage: "Parent flag"},
|
||||||
|
},
|
||||||
|
Subcommands: []Command{
|
||||||
|
{
|
||||||
|
Name: "bar",
|
||||||
|
Action: func(c *Context) {
|
||||||
|
if c.GlobalBool("debug") {
|
||||||
|
subcommandRun = true
|
||||||
|
}
|
||||||
|
if c.GlobalBool("parent") {
|
||||||
|
parentFlag = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run([]string{"command", "-d", "foo", "-p", "bar"})
|
||||||
|
|
||||||
|
expect(t, subcommandRun, true)
|
||||||
|
expect(t, parentFlag, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) {
|
||||||
|
var subcommandHelpTopics = [][]string{
|
||||||
|
{"command", "foo", "--help"},
|
||||||
|
{"command", "foo", "-h"},
|
||||||
|
{"command", "foo", "help"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, flagSet := range subcommandHelpTopics {
|
||||||
|
t.Logf("==> checking with flags %v", flagSet)
|
||||||
|
|
||||||
|
app := NewApp()
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
app.Writer = buf
|
||||||
|
|
||||||
|
subCmdBar := Command{
|
||||||
|
Name: "bar",
|
||||||
|
Usage: "does bar things",
|
||||||
|
}
|
||||||
|
subCmdBaz := Command{
|
||||||
|
Name: "baz",
|
||||||
|
Usage: "does baz things",
|
||||||
|
}
|
||||||
|
cmd := Command{
|
||||||
|
Name: "foo",
|
||||||
|
Description: "descriptive wall of text about how it does foo things",
|
||||||
|
Subcommands: []Command{subCmdBar, subCmdBaz},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Commands = []Command{cmd}
|
||||||
|
err := app.Run(flagSet)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
output := buf.String()
|
||||||
|
t.Logf("output: %q\n", buf.Bytes())
|
||||||
|
|
||||||
|
if strings.Contains(output, "No help topic for") {
|
||||||
|
t.Errorf("expect a help topic, got none: \n%q", output)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, shouldContain := range []string{
|
||||||
|
cmd.Name, cmd.Description,
|
||||||
|
subCmdBar.Name, subCmdBar.Usage,
|
||||||
|
subCmdBaz.Name, subCmdBaz.Usage,
|
||||||
|
} {
|
||||||
|
if !strings.Contains(output, shouldContain) {
|
||||||
|
t.Errorf("want help to contain %q, did not: \n%q", shouldContain, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_Run_SubcommandFullPath(t *testing.T) {
|
||||||
|
app := NewApp()
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
app.Writer = buf
|
||||||
|
|
||||||
|
subCmd := Command{
|
||||||
|
Name: "bar",
|
||||||
|
Usage: "does bar things",
|
||||||
|
}
|
||||||
|
cmd := Command{
|
||||||
|
Name: "foo",
|
||||||
|
Description: "foo commands",
|
||||||
|
Subcommands: []Command{subCmd},
|
||||||
|
}
|
||||||
|
app.Commands = []Command{cmd}
|
||||||
|
|
||||||
|
err := app.Run([]string{"command", "foo", "bar", "--help"})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
output := buf.String()
|
||||||
|
if !strings.Contains(output, "foo bar - does bar things") {
|
||||||
|
t.Errorf("expected full path to subcommand: %s", output)
|
||||||
|
}
|
||||||
|
if !strings.Contains(output, "command foo bar [arguments...]") {
|
||||||
|
t.Errorf("expected full path to subcommand: %s", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_Run_Help(t *testing.T) {
|
||||||
|
var helpArguments = [][]string{{"boom", "--help"}, {"boom", "-h"}, {"boom", "help"}}
|
||||||
|
|
||||||
|
for _, args := range helpArguments {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
t.Logf("==> checking with arguments %v", args)
|
||||||
|
|
||||||
|
app := NewApp()
|
||||||
|
app.Name = "boom"
|
||||||
|
app.Usage = "make an explosive entrance"
|
||||||
|
app.Writer = buf
|
||||||
|
app.Action = func(c *Context) {
|
||||||
|
buf.WriteString("boom I say!")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.Run(args)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
output := buf.String()
|
||||||
|
t.Logf("output: %q\n", buf.Bytes())
|
||||||
|
|
||||||
|
if !strings.Contains(output, "boom - make an explosive entrance") {
|
||||||
|
t.Errorf("want help to contain %q, did not: \n%q", "boom - make an explosive entrance", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_Run_Version(t *testing.T) {
|
||||||
|
var versionArguments = [][]string{{"boom", "--version"}, {"boom", "-v"}}
|
||||||
|
|
||||||
|
for _, args := range versionArguments {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
t.Logf("==> checking with arguments %v", args)
|
||||||
|
|
||||||
|
app := NewApp()
|
||||||
|
app.Name = "boom"
|
||||||
|
app.Usage = "make an explosive entrance"
|
||||||
|
app.Version = "0.1.0"
|
||||||
|
app.Writer = buf
|
||||||
|
app.Action = func(c *Context) {
|
||||||
|
buf.WriteString("boom I say!")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.Run(args)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
output := buf.String()
|
||||||
|
t.Logf("output: %q\n", buf.Bytes())
|
||||||
|
|
||||||
|
if !strings.Contains(output, "0.1.0") {
|
||||||
|
t.Errorf("want version to contain %q, did not: \n%q", "0.1.0", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
|
||||||
|
app := NewApp()
|
||||||
|
app.Action = func(c *Context) {}
|
||||||
|
app.Before = func(c *Context) error { return fmt.Errorf("before error") }
|
||||||
|
app.After = func(c *Context) error { return fmt.Errorf("after error") }
|
||||||
|
|
||||||
|
err := app.Run([]string{"foo"})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected to recieve error from Run, got none")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(err.Error(), "before error") {
|
||||||
|
t.Errorf("expected text of error from Before method, but got none in \"%v\"", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "after error") {
|
||||||
|
t.Errorf("expected text of error from After method, but got none in \"%v\"", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) {
|
||||||
|
app := NewApp()
|
||||||
|
app.Commands = []Command{
|
||||||
|
Command{
|
||||||
|
Name: "bar",
|
||||||
|
Before: func(c *Context) error { return fmt.Errorf("before error") },
|
||||||
|
After: func(c *Context) error { return fmt.Errorf("after error") },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.Run([]string{"foo", "bar"})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected to recieve error from Run, got none")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(err.Error(), "before error") {
|
||||||
|
t.Errorf("expected text of error from Before method, but got none in \"%v\"", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "after error") {
|
||||||
|
t.Errorf("expected text of error from After method, but got none in \"%v\"", err)
|
||||||
|
}
|
||||||
|
}
|
13
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete
generated
vendored
Normal file
13
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
_cli_bash_autocomplete() {
|
||||||
|
local cur prev opts base
|
||||||
|
COMPREPLY=()
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
|
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
|
||||||
|
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
complete -F _cli_bash_autocomplete $PROG
|
5
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/zsh_autocomplete
generated
vendored
Normal file
5
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/zsh_autocomplete
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
autoload -U compinit && compinit
|
||||||
|
autoload -U bashcompinit && bashcompinit
|
||||||
|
|
||||||
|
script_dir=$(dirname $0)
|
||||||
|
source ${script_dir}/bash_autocomplete
|
40
Godeps/_workspace/src/github.com/codegangsta/cli/cli.go
generated
vendored
Normal file
40
Godeps/_workspace/src/github.com/codegangsta/cli/cli.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// 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")
|
||||||
|
}
|
98
Godeps/_workspace/src/github.com/codegangsta/cli/cli_test.go
generated
vendored
Normal file
98
Godeps/_workspace/src/github.com/codegangsta/cli/cli_test.go
generated
vendored
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example() {
|
||||||
|
app := NewApp()
|
||||||
|
app.Name = "todo"
|
||||||
|
app.Usage = "task list on the command line"
|
||||||
|
app.Commands = []Command{
|
||||||
|
{
|
||||||
|
Name: "add",
|
||||||
|
Aliases: []string{"a"},
|
||||||
|
Usage: "add a task to the list",
|
||||||
|
Action: func(c *Context) {
|
||||||
|
println("added task: ", c.Args().First())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "complete",
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
Usage: "complete a task on the list",
|
||||||
|
Action: func(c *Context) {
|
||||||
|
println("completed task: ", c.Args().First())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run(os.Args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleSubcommand() {
|
||||||
|
app := NewApp()
|
||||||
|
app.Name = "say"
|
||||||
|
app.Commands = []Command{
|
||||||
|
{
|
||||||
|
Name: "hello",
|
||||||
|
Aliases: []string{"hi"},
|
||||||
|
Usage: "use it to see a description",
|
||||||
|
Description: "This is how we describe hello the function",
|
||||||
|
Subcommands: []Command{
|
||||||
|
{
|
||||||
|
Name: "english",
|
||||||
|
Aliases: []string{"en"},
|
||||||
|
Usage: "sends a greeting in english",
|
||||||
|
Description: "greets someone in english",
|
||||||
|
Flags: []Flag{
|
||||||
|
StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Value: "Bob",
|
||||||
|
Usage: "Name of the person to greet",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *Context) {
|
||||||
|
println("Hello, ", c.String("name"))
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Name: "spanish",
|
||||||
|
Aliases: []string{"sp"},
|
||||||
|
Usage: "sends a greeting in spanish",
|
||||||
|
Flags: []Flag{
|
||||||
|
StringFlag{
|
||||||
|
Name: "surname",
|
||||||
|
Value: "Jones",
|
||||||
|
Usage: "Surname of the person to greet",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *Context) {
|
||||||
|
println("Hola, ", c.String("surname"))
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Name: "french",
|
||||||
|
Aliases: []string{"fr"},
|
||||||
|
Usage: "sends a greeting in french",
|
||||||
|
Flags: []Flag{
|
||||||
|
StringFlag{
|
||||||
|
Name: "nickname",
|
||||||
|
Value: "Stevie",
|
||||||
|
Usage: "Nickname of the person to greet",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *Context) {
|
||||||
|
println("Bonjour, ", c.String("nickname"))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Name: "bye",
|
||||||
|
Usage: "says goodbye",
|
||||||
|
Action: func(c *Context) {
|
||||||
|
println("bye")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run(os.Args)
|
||||||
|
}
|
200
Godeps/_workspace/src/github.com/codegangsta/cli/command.go
generated
vendored
Normal file
200
Godeps/_workspace/src/github.com/codegangsta/cli/command.go
generated
vendored
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
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
|
||||||
|
// 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
|
||||||
|
|
||||||
|
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) error {
|
||||||
|
if len(c.Subcommands) > 0 || c.Before != nil || c.After != nil {
|
||||||
|
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)
|
||||||
|
|
||||||
|
firstFlagIndex := -1
|
||||||
|
terminatorIndex := -1
|
||||||
|
for index, arg := range ctx.Args() {
|
||||||
|
if arg == "--" {
|
||||||
|
terminatorIndex = index
|
||||||
|
break
|
||||||
|
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
|
||||||
|
firstFlagIndex = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if firstFlagIndex > -1 && !c.SkipFlagParsing {
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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.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)
|
||||||
|
}
|
47
Godeps/_workspace/src/github.com/codegangsta/cli/command_test.go
generated
vendored
Normal file
47
Godeps/_workspace/src/github.com/codegangsta/cli/command_test.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCommandDoNotIgnoreFlags(t *testing.T) {
|
||||||
|
app := NewApp()
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
test := []string{"blah", "blah", "-break"}
|
||||||
|
set.Parse(test)
|
||||||
|
|
||||||
|
c := NewContext(app, set, nil)
|
||||||
|
|
||||||
|
command := Command{
|
||||||
|
Name: "test-cmd",
|
||||||
|
Aliases: []string{"tc"},
|
||||||
|
Usage: "this is for testing",
|
||||||
|
Description: "testing",
|
||||||
|
Action: func(_ *Context) {},
|
||||||
|
}
|
||||||
|
err := command.Run(c)
|
||||||
|
|
||||||
|
expect(t, err.Error(), "flag provided but not defined: -break")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandIgnoreFlags(t *testing.T) {
|
||||||
|
app := NewApp()
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
test := []string{"blah", "blah"}
|
||||||
|
set.Parse(test)
|
||||||
|
|
||||||
|
c := NewContext(app, set, nil)
|
||||||
|
|
||||||
|
command := Command{
|
||||||
|
Name: "test-cmd",
|
||||||
|
Aliases: []string{"tc"},
|
||||||
|
Usage: "this is for testing",
|
||||||
|
Description: "testing",
|
||||||
|
Action: func(_ *Context) {},
|
||||||
|
SkipFlagParsing: true,
|
||||||
|
}
|
||||||
|
err := command.Run(c)
|
||||||
|
|
||||||
|
expect(t, err, nil)
|
||||||
|
}
|
388
Godeps/_workspace/src/github.com/codegangsta/cli/context.go
generated
vendored
Normal file
388
Godeps/_workspace/src/github.com/codegangsta/cli/context.go
generated
vendored
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
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
|
||||||
|
}
|
113
Godeps/_workspace/src/github.com/codegangsta/cli/context_test.go
generated
vendored
Normal file
113
Godeps/_workspace/src/github.com/codegangsta/cli/context_test.go
generated
vendored
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewContext(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Int("myflag", 12, "doc")
|
||||||
|
globalSet := flag.NewFlagSet("test", 0)
|
||||||
|
globalSet.Int("myflag", 42, "doc")
|
||||||
|
globalCtx := NewContext(nil, globalSet, nil)
|
||||||
|
command := Command{Name: "mycommand"}
|
||||||
|
c := NewContext(nil, set, globalCtx)
|
||||||
|
c.Command = command
|
||||||
|
expect(t, c.Int("myflag"), 12)
|
||||||
|
expect(t, c.GlobalInt("myflag"), 42)
|
||||||
|
expect(t, c.Command.Name, "mycommand")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_Int(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Int("myflag", 12, "doc")
|
||||||
|
c := NewContext(nil, set, nil)
|
||||||
|
expect(t, c.Int("myflag"), 12)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_Duration(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Duration("myflag", time.Duration(12*time.Second), "doc")
|
||||||
|
c := NewContext(nil, set, nil)
|
||||||
|
expect(t, c.Duration("myflag"), time.Duration(12*time.Second))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_String(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.String("myflag", "hello world", "doc")
|
||||||
|
c := NewContext(nil, set, nil)
|
||||||
|
expect(t, c.String("myflag"), "hello world")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_Bool(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Bool("myflag", false, "doc")
|
||||||
|
c := NewContext(nil, set, nil)
|
||||||
|
expect(t, c.Bool("myflag"), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_BoolT(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Bool("myflag", true, "doc")
|
||||||
|
c := NewContext(nil, set, nil)
|
||||||
|
expect(t, c.BoolT("myflag"), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_Args(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Bool("myflag", false, "doc")
|
||||||
|
c := NewContext(nil, set, nil)
|
||||||
|
set.Parse([]string{"--myflag", "bat", "baz"})
|
||||||
|
expect(t, len(c.Args()), 2)
|
||||||
|
expect(t, c.Bool("myflag"), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_IsSet(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Bool("myflag", false, "doc")
|
||||||
|
set.String("otherflag", "hello world", "doc")
|
||||||
|
globalSet := flag.NewFlagSet("test", 0)
|
||||||
|
globalSet.Bool("myflagGlobal", true, "doc")
|
||||||
|
globalCtx := NewContext(nil, globalSet, nil)
|
||||||
|
c := NewContext(nil, set, globalCtx)
|
||||||
|
set.Parse([]string{"--myflag", "bat", "baz"})
|
||||||
|
globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"})
|
||||||
|
expect(t, c.IsSet("myflag"), true)
|
||||||
|
expect(t, c.IsSet("otherflag"), false)
|
||||||
|
expect(t, c.IsSet("bogusflag"), false)
|
||||||
|
expect(t, c.IsSet("myflagGlobal"), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_GlobalIsSet(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Bool("myflag", false, "doc")
|
||||||
|
set.String("otherflag", "hello world", "doc")
|
||||||
|
globalSet := flag.NewFlagSet("test", 0)
|
||||||
|
globalSet.Bool("myflagGlobal", true, "doc")
|
||||||
|
globalSet.Bool("myflagGlobalUnset", true, "doc")
|
||||||
|
globalCtx := NewContext(nil, globalSet, nil)
|
||||||
|
c := NewContext(nil, set, globalCtx)
|
||||||
|
set.Parse([]string{"--myflag", "bat", "baz"})
|
||||||
|
globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"})
|
||||||
|
expect(t, c.GlobalIsSet("myflag"), false)
|
||||||
|
expect(t, c.GlobalIsSet("otherflag"), false)
|
||||||
|
expect(t, c.GlobalIsSet("bogusflag"), false)
|
||||||
|
expect(t, c.GlobalIsSet("myflagGlobal"), true)
|
||||||
|
expect(t, c.GlobalIsSet("myflagGlobalUnset"), false)
|
||||||
|
expect(t, c.GlobalIsSet("bogusGlobal"), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_NumFlags(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Bool("myflag", false, "doc")
|
||||||
|
set.String("otherflag", "hello world", "doc")
|
||||||
|
globalSet := flag.NewFlagSet("test", 0)
|
||||||
|
globalSet.Bool("myflagGlobal", true, "doc")
|
||||||
|
globalCtx := NewContext(nil, globalSet, nil)
|
||||||
|
c := NewContext(nil, set, globalCtx)
|
||||||
|
set.Parse([]string{"--myflag", "--otherflag=foo"})
|
||||||
|
globalSet.Parse([]string{"--myflagGlobal"})
|
||||||
|
expect(t, c.NumFlags(), 2)
|
||||||
|
}
|
497
Godeps/_workspace/src/github.com/codegangsta/cli/flag.go
generated
vendored
Normal file
497
Godeps/_workspace/src/github.com/codegangsta/cli/flag.go
generated
vendored
Normal file
@ -0,0 +1,497 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
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
|
||||||
|
}
|
740
Godeps/_workspace/src/github.com/codegangsta/cli/flag_test.go
generated
vendored
Normal file
740
Godeps/_workspace/src/github.com/codegangsta/cli/flag_test.go
generated
vendored
Normal file
@ -0,0 +1,740 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var boolFlagTests = []struct {
|
||||||
|
name string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"help", "--help\t"},
|
||||||
|
{"h", "-h\t"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolFlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
|
for _, test := range boolFlagTests {
|
||||||
|
flag := BoolFlag{Name: test.name}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if output != test.expected {
|
||||||
|
t.Errorf("%s does not match %s", output, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringFlagTests = []struct {
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"help", "", "--help \t"},
|
||||||
|
{"h", "", "-h \t"},
|
||||||
|
{"h", "", "-h \t"},
|
||||||
|
{"test", "Something", "--test \"Something\"\t"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringFlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
|
for _, test := range stringFlagTests {
|
||||||
|
flag := StringFlag{Name: test.name, Value: test.value}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if output != test.expected {
|
||||||
|
t.Errorf("%s does not match %s", output, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_FOO", "derp")
|
||||||
|
for _, test := range stringFlagTests {
|
||||||
|
flag := StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if !strings.HasSuffix(output, " [$APP_FOO]") {
|
||||||
|
t.Errorf("%s does not end with [$APP_FOO]", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringSliceFlagTests = []struct {
|
||||||
|
name string
|
||||||
|
value *StringSlice
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"help", func() *StringSlice {
|
||||||
|
s := &StringSlice{}
|
||||||
|
s.Set("")
|
||||||
|
return s
|
||||||
|
}(), "--help [--help option --help option]\t"},
|
||||||
|
{"h", func() *StringSlice {
|
||||||
|
s := &StringSlice{}
|
||||||
|
s.Set("")
|
||||||
|
return s
|
||||||
|
}(), "-h [-h option -h option]\t"},
|
||||||
|
{"h", func() *StringSlice {
|
||||||
|
s := &StringSlice{}
|
||||||
|
s.Set("")
|
||||||
|
return s
|
||||||
|
}(), "-h [-h option -h option]\t"},
|
||||||
|
{"test", func() *StringSlice {
|
||||||
|
s := &StringSlice{}
|
||||||
|
s.Set("Something")
|
||||||
|
return s
|
||||||
|
}(), "--test [--test option --test option]\t"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringSliceFlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
|
for _, test := range stringSliceFlagTests {
|
||||||
|
flag := StringSliceFlag{Name: test.name, Value: test.value}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if output != test.expected {
|
||||||
|
t.Errorf("%q does not match %q", output, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_QWWX", "11,4")
|
||||||
|
for _, test := range stringSliceFlagTests {
|
||||||
|
flag := StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if !strings.HasSuffix(output, " [$APP_QWWX]") {
|
||||||
|
t.Errorf("%q does not end with [$APP_QWWX]", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var intFlagTests = []struct {
|
||||||
|
name string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"help", "--help \"0\"\t"},
|
||||||
|
{"h", "-h \"0\"\t"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntFlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
|
for _, test := range intFlagTests {
|
||||||
|
flag := IntFlag{Name: test.name}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if output != test.expected {
|
||||||
|
t.Errorf("%s does not match %s", output, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_BAR", "2")
|
||||||
|
for _, test := range intFlagTests {
|
||||||
|
flag := IntFlag{Name: test.name, EnvVar: "APP_BAR"}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if !strings.HasSuffix(output, " [$APP_BAR]") {
|
||||||
|
t.Errorf("%s does not end with [$APP_BAR]", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var durationFlagTests = []struct {
|
||||||
|
name string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"help", "--help \"0\"\t"},
|
||||||
|
{"h", "-h \"0\"\t"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDurationFlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
|
for _, test := range durationFlagTests {
|
||||||
|
flag := DurationFlag{Name: test.name}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if output != test.expected {
|
||||||
|
t.Errorf("%s does not match %s", output, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_BAR", "2h3m6s")
|
||||||
|
for _, test := range durationFlagTests {
|
||||||
|
flag := DurationFlag{Name: test.name, EnvVar: "APP_BAR"}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if !strings.HasSuffix(output, " [$APP_BAR]") {
|
||||||
|
t.Errorf("%s does not end with [$APP_BAR]", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var intSliceFlagTests = []struct {
|
||||||
|
name string
|
||||||
|
value *IntSlice
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"help", &IntSlice{}, "--help [--help option --help option]\t"},
|
||||||
|
{"h", &IntSlice{}, "-h [-h option -h option]\t"},
|
||||||
|
{"h", &IntSlice{}, "-h [-h option -h option]\t"},
|
||||||
|
{"test", func() *IntSlice {
|
||||||
|
i := &IntSlice{}
|
||||||
|
i.Set("9")
|
||||||
|
return i
|
||||||
|
}(), "--test [--test option --test option]\t"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntSliceFlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
|
for _, test := range intSliceFlagTests {
|
||||||
|
flag := IntSliceFlag{Name: test.name, Value: test.value}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if output != test.expected {
|
||||||
|
t.Errorf("%q does not match %q", output, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_SMURF", "42,3")
|
||||||
|
for _, test := range intSliceFlagTests {
|
||||||
|
flag := IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if !strings.HasSuffix(output, " [$APP_SMURF]") {
|
||||||
|
t.Errorf("%q does not end with [$APP_SMURF]", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var float64FlagTests = []struct {
|
||||||
|
name string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"help", "--help \"0\"\t"},
|
||||||
|
{"h", "-h \"0\"\t"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloat64FlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
|
for _, test := range float64FlagTests {
|
||||||
|
flag := Float64Flag{Name: test.name}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if output != test.expected {
|
||||||
|
t.Errorf("%s does not match %s", output, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_BAZ", "99.4")
|
||||||
|
for _, test := range float64FlagTests {
|
||||||
|
flag := Float64Flag{Name: test.name, EnvVar: "APP_BAZ"}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if !strings.HasSuffix(output, " [$APP_BAZ]") {
|
||||||
|
t.Errorf("%s does not end with [$APP_BAZ]", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var genericFlagTests = []struct {
|
||||||
|
name string
|
||||||
|
value Generic
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"test", &Parser{"abc", "def"}, "--test \"abc,def\"\ttest flag"},
|
||||||
|
{"t", &Parser{"abc", "def"}, "-t \"abc,def\"\ttest flag"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenericFlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
|
for _, test := range genericFlagTests {
|
||||||
|
flag := GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if output != test.expected {
|
||||||
|
t.Errorf("%q does not match %q", output, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_ZAP", "3")
|
||||||
|
for _, test := range genericFlagTests {
|
||||||
|
flag := GenericFlag{Name: test.name, EnvVar: "APP_ZAP"}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if !strings.HasSuffix(output, " [$APP_ZAP]") {
|
||||||
|
t.Errorf("%s does not end with [$APP_ZAP]", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiString(t *testing.T) {
|
||||||
|
(&App{
|
||||||
|
Flags: []Flag{
|
||||||
|
StringFlag{Name: "serve, s"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if ctx.String("serve") != "10" {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.String("s") != "10" {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).Run([]string{"run", "-s", "10"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiStringFromEnv(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_COUNT", "20")
|
||||||
|
(&App{
|
||||||
|
Flags: []Flag{
|
||||||
|
StringFlag{Name: "count, c", EnvVar: "APP_COUNT"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if ctx.String("count") != "20" {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.String("c") != "20" {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiStringFromEnvCascade(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_COUNT", "20")
|
||||||
|
(&App{
|
||||||
|
Flags: []Flag{
|
||||||
|
StringFlag{Name: "count, c", EnvVar: "COMPAT_COUNT,APP_COUNT"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if ctx.String("count") != "20" {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.String("c") != "20" {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiStringSlice(t *testing.T) {
|
||||||
|
(&App{
|
||||||
|
Flags: []Flag{
|
||||||
|
StringSliceFlag{Name: "serve, s", Value: &StringSlice{}},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"10", "20"}) {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"10", "20"}) {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).Run([]string{"run", "-s", "10", "-s", "20"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiStringSliceFromEnv(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_INTERVALS", "20,30,40")
|
||||||
|
|
||||||
|
(&App{
|
||||||
|
Flags: []Flag{
|
||||||
|
StringSliceFlag{Name: "intervals, i", Value: &StringSlice{}, EnvVar: "APP_INTERVALS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
|
||||||
|
t.Errorf("main name not set from env")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) {
|
||||||
|
t.Errorf("short name not set from env")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiStringSliceFromEnvCascade(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_INTERVALS", "20,30,40")
|
||||||
|
|
||||||
|
(&App{
|
||||||
|
Flags: []Flag{
|
||||||
|
StringSliceFlag{Name: "intervals, i", Value: &StringSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
|
||||||
|
t.Errorf("main name not set from env")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) {
|
||||||
|
t.Errorf("short name not set from env")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiInt(t *testing.T) {
|
||||||
|
a := App{
|
||||||
|
Flags: []Flag{
|
||||||
|
IntFlag{Name: "serve, s"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if ctx.Int("serve") != 10 {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.Int("s") != 10 {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run", "-s", "10"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiIntFromEnv(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_TIMEOUT_SECONDS", "10")
|
||||||
|
a := App{
|
||||||
|
Flags: []Flag{
|
||||||
|
IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if ctx.Int("timeout") != 10 {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.Int("t") != 10 {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiIntFromEnvCascade(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_TIMEOUT_SECONDS", "10")
|
||||||
|
a := App{
|
||||||
|
Flags: []Flag{
|
||||||
|
IntFlag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if ctx.Int("timeout") != 10 {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.Int("t") != 10 {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiIntSlice(t *testing.T) {
|
||||||
|
(&App{
|
||||||
|
Flags: []Flag{
|
||||||
|
IntSliceFlag{Name: "serve, s", Value: &IntSlice{}},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ctx.IntSlice("s"), []int{10, 20}) {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).Run([]string{"run", "-s", "10", "-s", "20"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiIntSliceFromEnv(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_INTERVALS", "20,30,40")
|
||||||
|
|
||||||
|
(&App{
|
||||||
|
Flags: []Flag{
|
||||||
|
IntSliceFlag{Name: "intervals, i", Value: &IntSlice{}, EnvVar: "APP_INTERVALS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) {
|
||||||
|
t.Errorf("main name not set from env")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) {
|
||||||
|
t.Errorf("short name not set from env")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiIntSliceFromEnvCascade(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_INTERVALS", "20,30,40")
|
||||||
|
|
||||||
|
(&App{
|
||||||
|
Flags: []Flag{
|
||||||
|
IntSliceFlag{Name: "intervals, i", Value: &IntSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) {
|
||||||
|
t.Errorf("main name not set from env")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) {
|
||||||
|
t.Errorf("short name not set from env")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiFloat64(t *testing.T) {
|
||||||
|
a := App{
|
||||||
|
Flags: []Flag{
|
||||||
|
Float64Flag{Name: "serve, s"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if ctx.Float64("serve") != 10.2 {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.Float64("s") != 10.2 {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run", "-s", "10.2"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiFloat64FromEnv(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
|
||||||
|
a := App{
|
||||||
|
Flags: []Flag{
|
||||||
|
Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if ctx.Float64("timeout") != 15.5 {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.Float64("t") != 15.5 {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiFloat64FromEnvCascade(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
|
||||||
|
a := App{
|
||||||
|
Flags: []Flag{
|
||||||
|
Float64Flag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if ctx.Float64("timeout") != 15.5 {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.Float64("t") != 15.5 {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiBool(t *testing.T) {
|
||||||
|
a := App{
|
||||||
|
Flags: []Flag{
|
||||||
|
BoolFlag{Name: "serve, s"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if ctx.Bool("serve") != true {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.Bool("s") != true {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run", "--serve"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiBoolFromEnv(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_DEBUG", "1")
|
||||||
|
a := App{
|
||||||
|
Flags: []Flag{
|
||||||
|
BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if ctx.Bool("debug") != true {
|
||||||
|
t.Errorf("main name not set from env")
|
||||||
|
}
|
||||||
|
if ctx.Bool("d") != true {
|
||||||
|
t.Errorf("short name not set from env")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiBoolFromEnvCascade(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_DEBUG", "1")
|
||||||
|
a := App{
|
||||||
|
Flags: []Flag{
|
||||||
|
BoolFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if ctx.Bool("debug") != true {
|
||||||
|
t.Errorf("main name not set from env")
|
||||||
|
}
|
||||||
|
if ctx.Bool("d") != true {
|
||||||
|
t.Errorf("short name not set from env")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiBoolT(t *testing.T) {
|
||||||
|
a := App{
|
||||||
|
Flags: []Flag{
|
||||||
|
BoolTFlag{Name: "serve, s"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if ctx.BoolT("serve") != true {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.BoolT("s") != true {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run", "--serve"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiBoolTFromEnv(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_DEBUG", "0")
|
||||||
|
a := App{
|
||||||
|
Flags: []Flag{
|
||||||
|
BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if ctx.BoolT("debug") != false {
|
||||||
|
t.Errorf("main name not set from env")
|
||||||
|
}
|
||||||
|
if ctx.BoolT("d") != false {
|
||||||
|
t.Errorf("short name not set from env")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiBoolTFromEnvCascade(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_DEBUG", "0")
|
||||||
|
a := App{
|
||||||
|
Flags: []Flag{
|
||||||
|
BoolTFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if ctx.BoolT("debug") != false {
|
||||||
|
t.Errorf("main name not set from env")
|
||||||
|
}
|
||||||
|
if ctx.BoolT("d") != false {
|
||||||
|
t.Errorf("short name not set from env")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Parser [2]string
|
||||||
|
|
||||||
|
func (p *Parser) Set(value string) error {
|
||||||
|
parts := strings.Split(value, ",")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return fmt.Errorf("invalid format")
|
||||||
|
}
|
||||||
|
|
||||||
|
(*p)[0] = parts[0]
|
||||||
|
(*p)[1] = parts[1]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) String() string {
|
||||||
|
return fmt.Sprintf("%s,%s", p[0], p[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseGeneric(t *testing.T) {
|
||||||
|
a := App{
|
||||||
|
Flags: []Flag{
|
||||||
|
GenericFlag{Name: "serve, s", Value: &Parser{}},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"10", "20"}) {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run", "-s", "10,20"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseGenericFromEnv(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_SERVE", "20,30")
|
||||||
|
a := App{
|
||||||
|
Flags: []Flag{
|
||||||
|
GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"20", "30"}) {
|
||||||
|
t.Errorf("main name not set from env")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"20", "30"}) {
|
||||||
|
t.Errorf("short name not set from env")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseGenericFromEnvCascade(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_FOO", "99,2000")
|
||||||
|
a := App{
|
||||||
|
Flags: []Flag{
|
||||||
|
GenericFlag{Name: "foos", Value: &Parser{}, EnvVar: "COMPAT_FOO,APP_FOO"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *Context) {
|
||||||
|
if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) {
|
||||||
|
t.Errorf("value not set from env")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run"})
|
||||||
|
}
|
238
Godeps/_workspace/src/github.com/codegangsta/cli/help.go
generated
vendored
Normal file
238
Godeps/_workspace/src/github.com/codegangsta/cli/help.go
generated
vendored
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
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:
|
||||||
|
{{.Name}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} [arguments...]
|
||||||
|
{{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:
|
||||||
|
{{.FullName}} - {{.Usage}}
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
command {{.FullName}}{{if .Flags}} [command options]{{end}} [arguments...]{{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:
|
||||||
|
{{.Name}} - {{.Usage}}
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
{{.Name}} command{{if .Flags}} [command options]{{end}} [arguments...]
|
||||||
|
|
||||||
|
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",
|
||||||
|
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",
|
||||||
|
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 {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
w.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkVersion(c *Context) bool {
|
||||||
|
if c.GlobalBool("version") || c.GlobalBool("v") || c.Bool("version") || c.Bool("v") {
|
||||||
|
ShowVersion(c)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkHelp(c *Context) bool {
|
||||||
|
if c.GlobalBool("h") || c.GlobalBool("help") || c.Bool("h") || c.Bool("help") {
|
||||||
|
ShowAppHelp(c)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
36
Godeps/_workspace/src/github.com/codegangsta/cli/help_test.go
generated
vendored
Normal file
36
Godeps/_workspace/src/github.com/codegangsta/cli/help_test.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_ShowAppHelp_NoAuthor(t *testing.T) {
|
||||||
|
output := new(bytes.Buffer)
|
||||||
|
app := NewApp()
|
||||||
|
app.Writer = output
|
||||||
|
|
||||||
|
c := NewContext(app, nil, nil)
|
||||||
|
|
||||||
|
ShowAppHelp(c)
|
||||||
|
|
||||||
|
if bytes.Index(output.Bytes(), []byte("AUTHOR(S):")) != -1 {
|
||||||
|
t.Errorf("expected\n%snot to include %s", output.String(), "AUTHOR(S):")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ShowAppHelp_NoVersion(t *testing.T) {
|
||||||
|
output := new(bytes.Buffer)
|
||||||
|
app := NewApp()
|
||||||
|
app.Writer = output
|
||||||
|
|
||||||
|
app.Version = ""
|
||||||
|
|
||||||
|
c := NewContext(app, nil, nil)
|
||||||
|
|
||||||
|
ShowAppHelp(c)
|
||||||
|
|
||||||
|
if bytes.Index(output.Bytes(), []byte("VERSION:")) != -1 {
|
||||||
|
t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:")
|
||||||
|
}
|
||||||
|
}
|
19
Godeps/_workspace/src/github.com/codegangsta/cli/helpers_test.go
generated
vendored
Normal file
19
Godeps/_workspace/src/github.com/codegangsta/cli/helpers_test.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Test Helpers */
|
||||||
|
func expect(t *testing.T, a interface{}, b interface{}) {
|
||||||
|
if a != b {
|
||||||
|
t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func refute(t *testing.T, a interface{}, b interface{}) {
|
||||||
|
if a == b {
|
||||||
|
t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
|
||||||
|
}
|
||||||
|
}
|
23
Godeps/_workspace/src/github.com/elazarl/go-bindata-assetfs/LICENSE
generated
vendored
Normal file
23
Godeps/_workspace/src/github.com/elazarl/go-bindata-assetfs/LICENSE
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
Copyright (c) 2014, Elazar Leibovich
|
||||||
|
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.
|
46
Godeps/_workspace/src/github.com/elazarl/go-bindata-assetfs/README.md
generated
vendored
Normal file
46
Godeps/_workspace/src/github.com/elazarl/go-bindata-assetfs/README.md
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# go-bindata-assetfs
|
||||||
|
|
||||||
|
Serve embedded files from [jteeuwen/go-bindata](https://github.com/jteeuwen/go-bindata) with `net/http`.
|
||||||
|
|
||||||
|
[GoDoc](http://godoc.org/github.com/elazarl/go-bindata-assetfs)
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
Install with
|
||||||
|
|
||||||
|
$ go get github.com/jteeuwen/go-bindata/...
|
||||||
|
$ go get github.com/elazarl/go-bindata-assetfs/...
|
||||||
|
|
||||||
|
### Creating embedded data
|
||||||
|
|
||||||
|
Usage is identical to [jteeuwen/go-bindata](https://github.com/jteeuwen/go-bindata) usage,
|
||||||
|
instead of running `go-bindata` run `go-bindata-assetfs`.
|
||||||
|
|
||||||
|
The tool will create a `bindata_assetfs.go` file, which contains the embedded data.
|
||||||
|
|
||||||
|
A typical use case is
|
||||||
|
|
||||||
|
$ go-bindata-assetfs data/...
|
||||||
|
|
||||||
|
### Using assetFS in your code
|
||||||
|
|
||||||
|
The generated file provides an `assetFS()` function that returns a `http.Filesystem`
|
||||||
|
wrapping the embedded files. What you usually want to do is:
|
||||||
|
|
||||||
|
http.Handle("/", http.FileServer(assetFS()))
|
||||||
|
|
||||||
|
This would run an HTTP server serving the embedded files.
|
||||||
|
|
||||||
|
## Without running binary tool
|
||||||
|
|
||||||
|
You can always just run the `go-bindata` tool, and then
|
||||||
|
|
||||||
|
use
|
||||||
|
|
||||||
|
import "github.com/elazarl/go-bindata-assetfs"
|
||||||
|
...
|
||||||
|
http.Handle("/",
|
||||||
|
http.FileServer(
|
||||||
|
&assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "data"}))
|
||||||
|
|
||||||
|
to serve files embedded from the `data` directory.
|
147
Godeps/_workspace/src/github.com/elazarl/go-bindata-assetfs/assetfs.go
generated
vendored
Normal file
147
Godeps/_workspace/src/github.com/elazarl/go-bindata-assetfs/assetfs.go
generated
vendored
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
package assetfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fileTimestamp = time.Now()
|
||||||
|
)
|
||||||
|
|
||||||
|
// FakeFile implements os.FileInfo interface for a given path and size
|
||||||
|
type FakeFile struct {
|
||||||
|
// Path is the path of this file
|
||||||
|
Path string
|
||||||
|
// Dir marks of the path is a directory
|
||||||
|
Dir bool
|
||||||
|
// Len is the length of the fake file, zero if it is a directory
|
||||||
|
Len int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeFile) Name() string {
|
||||||
|
_, name := filepath.Split(f.Path)
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeFile) Mode() os.FileMode {
|
||||||
|
mode := os.FileMode(0644)
|
||||||
|
if f.Dir {
|
||||||
|
return mode | os.ModeDir
|
||||||
|
}
|
||||||
|
return mode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeFile) ModTime() time.Time {
|
||||||
|
return fileTimestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeFile) Size() int64 {
|
||||||
|
return f.Len
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeFile) IsDir() bool {
|
||||||
|
return f.Mode().IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeFile) Sys() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetFile implements http.File interface for a no-directory file with content
|
||||||
|
type AssetFile struct {
|
||||||
|
*bytes.Reader
|
||||||
|
io.Closer
|
||||||
|
FakeFile
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAssetFile(name string, content []byte) *AssetFile {
|
||||||
|
return &AssetFile{
|
||||||
|
bytes.NewReader(content),
|
||||||
|
ioutil.NopCloser(nil),
|
||||||
|
FakeFile{name, false, int64(len(content))}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *AssetFile) Readdir(count int) ([]os.FileInfo, error) {
|
||||||
|
return nil, errors.New("not a directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *AssetFile) Size() int64 {
|
||||||
|
return f.FakeFile.Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *AssetFile) Stat() (os.FileInfo, error) {
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetDirectory implements http.File interface for a directory
|
||||||
|
type AssetDirectory struct {
|
||||||
|
AssetFile
|
||||||
|
ChildrenRead int
|
||||||
|
Children []os.FileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAssetDirectory(name string, children []string, fs *AssetFS) *AssetDirectory {
|
||||||
|
fileinfos := make([]os.FileInfo, 0, len(children))
|
||||||
|
for _, child := range children {
|
||||||
|
_, err := fs.AssetDir(filepath.Join(name, child))
|
||||||
|
fileinfos = append(fileinfos, &FakeFile{child, err == nil, 0})
|
||||||
|
}
|
||||||
|
return &AssetDirectory{
|
||||||
|
AssetFile{
|
||||||
|
bytes.NewReader(nil),
|
||||||
|
ioutil.NopCloser(nil),
|
||||||
|
FakeFile{name, true, 0},
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
fileinfos}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *AssetDirectory) Readdir(count int) ([]os.FileInfo, error) {
|
||||||
|
if count <= 0 {
|
||||||
|
return f.Children, nil
|
||||||
|
}
|
||||||
|
if f.ChildrenRead+count > len(f.Children) {
|
||||||
|
count = len(f.Children) - f.ChildrenRead
|
||||||
|
}
|
||||||
|
rv := f.Children[f.ChildrenRead : f.ChildrenRead+count]
|
||||||
|
f.ChildrenRead += count
|
||||||
|
return rv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *AssetDirectory) Stat() (os.FileInfo, error) {
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssetFS implements http.FileSystem, allowing
|
||||||
|
// embedded files to be served from net/http package.
|
||||||
|
type AssetFS struct {
|
||||||
|
// Asset should return content of file in path if exists
|
||||||
|
Asset func(path string) ([]byte, error)
|
||||||
|
// AssetDir should return list of files in the path
|
||||||
|
AssetDir func(path string) ([]string, error)
|
||||||
|
// Prefix would be prepended to http requests
|
||||||
|
Prefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *AssetFS) Open(name string) (http.File, error) {
|
||||||
|
name = path.Join(fs.Prefix, name)
|
||||||
|
if len(name) > 0 && name[0] == '/' {
|
||||||
|
name = name[1:]
|
||||||
|
}
|
||||||
|
if b, err := fs.Asset(name); err == nil {
|
||||||
|
return NewAssetFile(name, b), nil
|
||||||
|
}
|
||||||
|
if children, err := fs.AssetDir(name); err == nil {
|
||||||
|
return NewAssetDirectory(name, children, fs), nil
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
13
Godeps/_workspace/src/github.com/elazarl/go-bindata-assetfs/doc.go
generated
vendored
Normal file
13
Godeps/_workspace/src/github.com/elazarl/go-bindata-assetfs/doc.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// assetfs allows packages to serve static content embedded
|
||||||
|
// with the go-bindata tool with the standard net/http package.
|
||||||
|
//
|
||||||
|
// See https://github.com/jteeuwen/go-bindata for more information
|
||||||
|
// about embedding binary data with go-bindata.
|
||||||
|
//
|
||||||
|
// Usage example, after running
|
||||||
|
// $ go-bindata data/...
|
||||||
|
// use:
|
||||||
|
// http.Handle("/",
|
||||||
|
// http.FileServer(
|
||||||
|
// &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "data"}))
|
||||||
|
package assetfs
|
97
Godeps/_workspace/src/github.com/elazarl/go-bindata-assetfs/go-bindata-assetfs/main.go
generated
vendored
Normal file
97
Godeps/_workspace/src/github.com/elazarl/go-bindata-assetfs/go-bindata-assetfs/main.go
generated
vendored
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const bindatafile = "bindata.go"
|
||||||
|
|
||||||
|
func isDebug(args []string) bool {
|
||||||
|
flagset := flag.NewFlagSet("", flag.ContinueOnError)
|
||||||
|
debug := flagset.Bool("debug", false, "")
|
||||||
|
debugArgs := make([]string, 0)
|
||||||
|
for _, arg := range args {
|
||||||
|
if strings.HasPrefix(arg, "-debug") {
|
||||||
|
debugArgs = append(debugArgs, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flagset.Parse(debugArgs)
|
||||||
|
if debug == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return *debug
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if _, err := exec.LookPath("go-bindata"); err != nil {
|
||||||
|
fmt.Println("Cannot find go-bindata executable in path")
|
||||||
|
fmt.Println("Maybe you need: go get github.com/elazarl/go-bindata-assetfs/...")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
cmd := exec.Command("go-bindata", os.Args[1:]...)
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
in, err := os.Open(bindatafile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Cannot read", bindatafile, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out, err := os.Create("bindata_assetfs.go")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Cannot write 'bindata_assetfs.go'", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
debug := isDebug(os.Args[1:])
|
||||||
|
r := bufio.NewReader(in)
|
||||||
|
done := false
|
||||||
|
for line, isPrefix, err := r.ReadLine(); err == nil; line, isPrefix, err = r.ReadLine() {
|
||||||
|
if !isPrefix {
|
||||||
|
line = append(line, '\n')
|
||||||
|
}
|
||||||
|
if _, err := out.Write(line); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Cannot write to 'bindata_assetfs.go'", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !done && !isPrefix && bytes.HasPrefix(line, []byte("import (")) {
|
||||||
|
if debug {
|
||||||
|
fmt.Fprintln(out, "\t\"net/http\"")
|
||||||
|
} else {
|
||||||
|
fmt.Fprintln(out, "\t\"github.com/elazarl/go-bindata-assetfs\"")
|
||||||
|
}
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if debug {
|
||||||
|
fmt.Fprintln(out, `
|
||||||
|
func assetFS() http.FileSystem {
|
||||||
|
for k := range _bintree.Children {
|
||||||
|
return http.Dir(k)
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}`)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintln(out, `
|
||||||
|
func assetFS() *assetfs.AssetFS {
|
||||||
|
for k := range _bintree.Children {
|
||||||
|
return &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: k}
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}`)
|
||||||
|
}
|
||||||
|
// Close files BEFORE remove calls (don't use defer).
|
||||||
|
in.Close()
|
||||||
|
out.Close()
|
||||||
|
if err := os.Remove(bindatafile); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Cannot remove", bindatafile, err)
|
||||||
|
}
|
||||||
|
}
|
22
Godeps/_workspace/src/github.com/gorilla/websocket/.gitignore
generated
vendored
Normal file
22
Godeps/_workspace/src/github.com/gorilla/websocket/.gitignore
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# 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
|
6
Godeps/_workspace/src/github.com/gorilla/websocket/.travis.yml
generated
vendored
Normal file
6
Godeps/_workspace/src/github.com/gorilla/websocket/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.1
|
||||||
|
- 1.2
|
||||||
|
- tip
|
8
Godeps/_workspace/src/github.com/gorilla/websocket/AUTHORS
generated
vendored
Normal file
8
Godeps/_workspace/src/github.com/gorilla/websocket/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# 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>
|
||||||
|
|
22
Godeps/_workspace/src/github.com/gorilla/websocket/LICENSE
generated
vendored
Normal file
22
Godeps/_workspace/src/github.com/gorilla/websocket/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
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.
|
59
Godeps/_workspace/src/github.com/gorilla/websocket/README.md
generated
vendored
Normal file
59
Godeps/_workspace/src/github.com/gorilla/websocket/README.md
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# 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)
|
||||||
|
* [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.
|
||||||
|
|
19
Godeps/_workspace/src/github.com/gorilla/websocket/bench_test.go
generated
vendored
Normal file
19
Godeps/_workspace/src/github.com/gorilla/websocket/bench_test.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Copyright 2014 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 (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkMaskBytes(b *testing.B) {
|
||||||
|
var key [4]byte
|
||||||
|
data := make([]byte, 1024)
|
||||||
|
pos := 0
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
pos = maskBytes(key, pos, data)
|
||||||
|
}
|
||||||
|
b.SetBytes(int64(len(data)))
|
||||||
|
}
|
269
Godeps/_workspace/src/github.com/gorilla/websocket/client.go
generated
vendored
Normal file
269
Godeps/_workspace/src/github.com/gorilla/websocket/client.go
generated
vendored
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
// 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 (
|
||||||
|
"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.
|
||||||
|
func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) {
|
||||||
|
challengeKey, err := generateChallengeKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
acceptKey := computeAcceptKey(challengeKey)
|
||||||
|
|
||||||
|
c = newConn(netConn, false, readBufSize, writeBufSize)
|
||||||
|
p := c.writeBuf[:0]
|
||||||
|
p = append(p, "GET "...)
|
||||||
|
p = append(p, u.RequestURI()...)
|
||||||
|
p = append(p, " HTTP/1.1\r\nHost: "...)
|
||||||
|
p = append(p, u.Host...)
|
||||||
|
// "Upgrade" is capitalized for servers that do not use case insensitive
|
||||||
|
// comparisons on header tokens.
|
||||||
|
p = append(p, "\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: "...)
|
||||||
|
p = append(p, challengeKey...)
|
||||||
|
p = append(p, "\r\n"...)
|
||||||
|
for k, vs := range requestHeader {
|
||||||
|
for _, v := range vs {
|
||||||
|
p = append(p, k...)
|
||||||
|
p = append(p, ": "...)
|
||||||
|
p = append(p, v...)
|
||||||
|
p = append(p, "\r\n"...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p = append(p, "\r\n"...)
|
||||||
|
|
||||||
|
if _, err := netConn.Write(p); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.ReadResponse(c.br, &http.Request{Method: "GET", URL: u})
|
||||||
|
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") != acceptKey {
|
||||||
|
return nil, resp, ErrBadHandshake
|
||||||
|
}
|
||||||
|
c.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
|
||||||
|
return c, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
// 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. The url.Parse function is not used here because
|
||||||
|
// url.Parse mangles 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 ]
|
||||||
|
//
|
||||||
|
// We don't use the net/url parser here because the dialer interface does
|
||||||
|
// not provide a way for applications to work around percent deocding in
|
||||||
|
// the net/url parser.
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
u.Host = s
|
||||||
|
u.Opaque = "/"
|
||||||
|
if i := strings.Index(s, "/"); i >= 0 {
|
||||||
|
u.Host = s[:i]
|
||||||
|
u.Opaque = s[i:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(u.Host, "@") {
|
||||||
|
// WebSocket URIs do not contain user information.
|
||||||
|
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 {
|
||||||
|
if u.Scheme == "wss" {
|
||||||
|
hostPort += ":443"
|
||||||
|
} else {
|
||||||
|
hostPort += ":80"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hostPort, hostNoPort
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultDialer is a dialer with all fields set to the default zero values.
|
||||||
|
var DefaultDialer *Dialer
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
u, err := parseURL(urlStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hostPort, hostNoPort := hostPortNoPort(u)
|
||||||
|
|
||||||
|
if d == nil {
|
||||||
|
d = &Dialer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
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", hostPort)
|
||||||
|
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 u.Scheme == "wss" {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(d.Subprotocols) > 0 {
|
||||||
|
h := http.Header{}
|
||||||
|
for k, v := range requestHeader {
|
||||||
|
h[k] = v
|
||||||
|
}
|
||||||
|
h.Set("Sec-Websocket-Protocol", strings.Join(d.Subprotocols, ", "))
|
||||||
|
requestHeader = h
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(requestHeader["Host"]) > 0 {
|
||||||
|
// This can be used to supply a Host: header which is different from
|
||||||
|
// the dial address.
|
||||||
|
u.Host = requestHeader.Get("Host")
|
||||||
|
|
||||||
|
// Drop "Host" header
|
||||||
|
h := http.Header{}
|
||||||
|
for k, v := range requestHeader {
|
||||||
|
if k == "Host" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
h[k] = v
|
||||||
|
}
|
||||||
|
requestHeader = h
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, resp, err := NewClient(netConn, u, requestHeader, d.ReadBufferSize, d.WriteBufferSize)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err == ErrBadHandshake {
|
||||||
|
// 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, err
|
||||||
|
}
|
||||||
|
|
||||||
|
netConn.SetDeadline(time.Time{})
|
||||||
|
netConn = nil // to avoid close in defer.
|
||||||
|
return conn, resp, nil
|
||||||
|
}
|
323
Godeps/_workspace/src/github.com/gorilla/websocket/client_server_test.go
generated
vendored
Normal file
323
Godeps/_workspace/src/github.com/gorilla/websocket/client_server_test.go
generated
vendored
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
// 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/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cstUpgrader = Upgrader{
|
||||||
|
Subprotocols: []string{"p0", "p1"},
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) {
|
||||||
|
http.Error(w, reason.Error(), status)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var cstDialer = Dialer{
|
||||||
|
Subprotocols: []string{"p1", "p2"},
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
type cstHandler struct{ *testing.T }
|
||||||
|
|
||||||
|
type cstServer struct {
|
||||||
|
*httptest.Server
|
||||||
|
URL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newServer(t *testing.T) *cstServer {
|
||||||
|
var s cstServer
|
||||||
|
s.Server = httptest.NewServer(cstHandler{t})
|
||||||
|
s.URL = makeWsProto(s.Server.URL)
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTLSServer(t *testing.T) *cstServer {
|
||||||
|
var s cstServer
|
||||||
|
s.Server = httptest.NewTLSServer(cstHandler{t})
|
||||||
|
s.URL = makeWsProto(s.Server.URL)
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t cstHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != "GET" {
|
||||||
|
t.Logf("method %s not allowed", r.Method)
|
||||||
|
http.Error(w, "method not allowed", 405)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subprotos := Subprotocols(r)
|
||||||
|
if !reflect.DeepEqual(subprotos, cstDialer.Subprotocols) {
|
||||||
|
t.Logf("subprotols=%v, want %v", subprotos, cstDialer.Subprotocols)
|
||||||
|
http.Error(w, "bad protocol", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ws, err := cstUpgrader.Upgrade(w, r, http.Header{"Set-Cookie": {"sessionID=1234"}})
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("Upgrade: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer ws.Close()
|
||||||
|
|
||||||
|
if ws.Subprotocol() != "p1" {
|
||||||
|
t.Logf("Subprotocol() = %s, want p1", ws.Subprotocol())
|
||||||
|
ws.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
op, rd, err := ws.NextReader()
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("NextReader: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wr, err := ws.NextWriter(op)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("NextWriter: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = io.Copy(wr, rd); err != nil {
|
||||||
|
t.Logf("NextWriter: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := wr.Close(); err != nil {
|
||||||
|
t.Logf("Close: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeWsProto(s string) string {
|
||||||
|
return "ws" + strings.TrimPrefix(s, "http")
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendRecv(t *testing.T, ws *Conn) {
|
||||||
|
const message = "Hello World!"
|
||||||
|
if err := ws.SetWriteDeadline(time.Now().Add(time.Second)); err != nil {
|
||||||
|
t.Fatalf("SetWriteDeadline: %v", err)
|
||||||
|
}
|
||||||
|
if err := ws.WriteMessage(TextMessage, []byte(message)); err != nil {
|
||||||
|
t.Fatalf("WriteMessage: %v", err)
|
||||||
|
}
|
||||||
|
if err := ws.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
|
||||||
|
t.Fatalf("SetReadDeadline: %v", err)
|
||||||
|
}
|
||||||
|
_, p, err := ws.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadMessage: %v", err)
|
||||||
|
}
|
||||||
|
if string(p) != message {
|
||||||
|
t.Fatalf("message=%s, want %s", p, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDial(t *testing.T) {
|
||||||
|
s := newServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
ws, _, err := cstDialer.Dial(s.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Dial: %v", err)
|
||||||
|
}
|
||||||
|
defer ws.Close()
|
||||||
|
sendRecv(t, ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialTLS(t *testing.T) {
|
||||||
|
s := newTLSServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
certs := x509.NewCertPool()
|
||||||
|
for _, c := range s.TLS.Certificates {
|
||||||
|
roots, err := x509.ParseCertificates(c.Certificate[len(c.Certificate)-1])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error parsing server's root cert: %v", err)
|
||||||
|
}
|
||||||
|
for _, root := range roots {
|
||||||
|
certs.AddCert(root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u, _ := url.Parse(s.URL)
|
||||||
|
d := cstDialer
|
||||||
|
d.NetDial = func(network, addr string) (net.Conn, error) { return net.Dial(network, u.Host) }
|
||||||
|
d.TLSClientConfig = &tls.Config{RootCAs: certs}
|
||||||
|
ws, _, err := d.Dial("wss://example.com/", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Dial: %v", err)
|
||||||
|
}
|
||||||
|
defer ws.Close()
|
||||||
|
sendRecv(t, ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func xTestDialTLSBadCert(t *testing.T) {
|
||||||
|
// This test is deactivated because of noisy logging from the net/http package.
|
||||||
|
s := newTLSServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
ws, _, err := cstDialer.Dial(s.URL, nil)
|
||||||
|
if err == nil {
|
||||||
|
ws.Close()
|
||||||
|
t.Fatalf("Dial: nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func xTestDialTLSNoVerify(t *testing.T) {
|
||||||
|
s := newTLSServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
d := cstDialer
|
||||||
|
d.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
|
ws, _, err := d.Dial(s.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Dial: %v", err)
|
||||||
|
}
|
||||||
|
defer ws.Close()
|
||||||
|
sendRecv(t, ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialTimeout(t *testing.T) {
|
||||||
|
s := newServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
d := cstDialer
|
||||||
|
d.HandshakeTimeout = -1
|
||||||
|
ws, _, err := d.Dial(s.URL, nil)
|
||||||
|
if err == nil {
|
||||||
|
ws.Close()
|
||||||
|
t.Fatalf("Dial: nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialBadScheme(t *testing.T) {
|
||||||
|
s := newServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
ws, _, err := cstDialer.Dial(s.Server.URL, nil)
|
||||||
|
if err == nil {
|
||||||
|
ws.Close()
|
||||||
|
t.Fatalf("Dial: nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialBadOrigin(t *testing.T) {
|
||||||
|
s := newServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
ws, resp, err := cstDialer.Dial(s.URL, http.Header{"Origin": {"bad"}})
|
||||||
|
if err == nil {
|
||||||
|
ws.Close()
|
||||||
|
t.Fatalf("Dial: nil")
|
||||||
|
}
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatalf("resp=nil, err=%v", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusForbidden {
|
||||||
|
t.Fatalf("status=%d, want %d", resp.StatusCode, http.StatusForbidden)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandshake(t *testing.T) {
|
||||||
|
s := newServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
ws, resp, err := cstDialer.Dial(s.URL, http.Header{"Origin": {s.URL}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Dial: %v", err)
|
||||||
|
}
|
||||||
|
defer ws.Close()
|
||||||
|
|
||||||
|
var sessionID string
|
||||||
|
for _, c := range resp.Cookies() {
|
||||||
|
if c.Name == "sessionID" {
|
||||||
|
sessionID = c.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sessionID != "1234" {
|
||||||
|
t.Error("Set-Cookie not received from the server.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ws.Subprotocol() != "p1" {
|
||||||
|
t.Errorf("ws.Subprotocol() = %s, want p1", ws.Subprotocol())
|
||||||
|
}
|
||||||
|
sendRecv(t, ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRespOnBadHandshake(t *testing.T) {
|
||||||
|
const expectedStatus = http.StatusGone
|
||||||
|
const expectedBody = "This is the response body."
|
||||||
|
|
||||||
|
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(expectedStatus)
|
||||||
|
io.WriteString(w, expectedBody)
|
||||||
|
}))
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
ws, resp, err := cstDialer.Dial(makeWsProto(s.URL), nil)
|
||||||
|
if err == nil {
|
||||||
|
ws.Close()
|
||||||
|
t.Fatalf("Dial: nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatalf("resp=nil, err=%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != expectedStatus {
|
||||||
|
t.Errorf("resp.StatusCode=%d, want %d", resp.StatusCode, expectedStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadFull(resp.Body) returned error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(p) != expectedBody {
|
||||||
|
t.Errorf("resp.Body=%s, want %s", p, expectedBody)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the Host header is specified in `Dial()`, the server must receive it as
|
||||||
|
// the `Host:` header.
|
||||||
|
func TestHostHeader(t *testing.T) {
|
||||||
|
s := newServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
specifiedHost := make(chan string, 1)
|
||||||
|
origHandler := s.Server.Config.Handler
|
||||||
|
|
||||||
|
// Capture the request Host header.
|
||||||
|
s.Server.Config.Handler = http.HandlerFunc(
|
||||||
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
specifiedHost <- r.Host
|
||||||
|
origHandler.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
ws, resp, err := cstDialer.Dial(s.URL, http.Header{"Host": {"testhost"}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Dial: %v", err)
|
||||||
|
}
|
||||||
|
defer ws.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusSwitchingProtocols {
|
||||||
|
t.Fatalf("resp.StatusCode = %v, want http.StatusSwitchingProtocols", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotHost := <-specifiedHost; gotHost != "testhost" {
|
||||||
|
t.Fatalf("gotHost = %q, want \"testhost\"", gotHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
sendRecv(t, ws)
|
||||||
|
}
|
64
Godeps/_workspace/src/github.com/gorilla/websocket/client_test.go
generated
vendored
Normal file
64
Godeps/_workspace/src/github.com/gorilla/websocket/client_test.go
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// Copyright 2014 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 (
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var parseURLTests = []struct {
|
||||||
|
s string
|
||||||
|
u *url.URL
|
||||||
|
}{
|
||||||
|
{"ws://example.com/", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}},
|
||||||
|
{"ws://example.com", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}},
|
||||||
|
{"ws://example.com:7777/", &url.URL{Scheme: "ws", Host: "example.com:7777", Opaque: "/"}},
|
||||||
|
{"wss://example.com/", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/"}},
|
||||||
|
{"wss://example.com/a/b", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/a/b"}},
|
||||||
|
{"ss://example.com/a/b", nil},
|
||||||
|
{"ws://webmaster@example.com/", nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseURL(t *testing.T) {
|
||||||
|
for _, tt := range parseURLTests {
|
||||||
|
u, err := parseURL(tt.s)
|
||||||
|
if tt.u != nil && err != nil {
|
||||||
|
t.Errorf("parseURL(%q) returned error %v", tt.s, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tt.u == nil && err == nil {
|
||||||
|
t.Errorf("parseURL(%q) did not return error", tt.s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(u, tt.u) {
|
||||||
|
t.Errorf("parseURL(%q) returned %v, want %v", tt.s, u, tt.u)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostPortNoPortTests = []struct {
|
||||||
|
u *url.URL
|
||||||
|
hostPort, hostNoPort string
|
||||||
|
}{
|
||||||
|
{&url.URL{Scheme: "ws", Host: "example.com"}, "example.com:80", "example.com"},
|
||||||
|
{&url.URL{Scheme: "wss", Host: "example.com"}, "example.com:443", "example.com"},
|
||||||
|
{&url.URL{Scheme: "ws", Host: "example.com:7777"}, "example.com:7777", "example.com"},
|
||||||
|
{&url.URL{Scheme: "wss", Host: "example.com:7777"}, "example.com:7777", "example.com"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHostPortNoPort(t *testing.T) {
|
||||||
|
for _, tt := range hostPortNoPortTests {
|
||||||
|
hostPort, hostNoPort := hostPortNoPort(tt.u)
|
||||||
|
if hostPort != tt.hostPort {
|
||||||
|
t.Errorf("hostPortNoPort(%v) returned hostPort %q, want %q", tt.u, hostPort, tt.hostPort)
|
||||||
|
}
|
||||||
|
if hostNoPort != tt.hostNoPort {
|
||||||
|
t.Errorf("hostPortNoPort(%v) returned hostNoPort %q, want %q", tt.u, hostNoPort, tt.hostNoPort)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
824
Godeps/_workspace/src/github.com/gorilla/websocket/conn.go
generated
vendored
Normal file
824
Godeps/_workspace/src/github.com/gorilla/websocket/conn.go
generated
vendored
Normal file
@ -0,0 +1,824 @@
|
|||||||
|
// 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 {
|
||||||
|
return "websocket: close " + strconv.Itoa(e.Code) + " " + e.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errWriteTimeout = &netError{msg: "websocket: write timeout", timeout: 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 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.
|
||||||
|
//
|
||||||
|
// The NextWriter method and the writers returned from the method cannot be
|
||||||
|
// accessed by more than one goroutine at a time.
|
||||||
|
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.
|
||||||
|
//
|
||||||
|
// The NextReader method and the readers returned from the method cannot be
|
||||||
|
// accessed by more than one goroutine at a time.
|
||||||
|
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 default ping handler sends a pong to the peer.
|
||||||
|
func (c *Conn) SetPingHandler(h func(string) error) {
|
||||||
|
if h == nil {
|
||||||
|
h = func(message string) error {
|
||||||
|
c.WriteControl(PongMessage, []byte(message), time.Now().Add(writeWait))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.handlePing = h
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPongHandler sets the handler for pong messages received from the peer.
|
||||||
|
// The default pong handler does nothing.
|
||||||
|
func (c *Conn) SetPongHandler(h func(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
|
||||||
|
}
|
241
Godeps/_workspace/src/github.com/gorilla/websocket/conn_test.go
generated
vendored
Normal file
241
Godeps/_workspace/src/github.com/gorilla/websocket/conn_test.go
generated
vendored
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
// 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 (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"testing/iotest"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ net.Error = errWriteTimeout
|
||||||
|
|
||||||
|
type fakeNetConn struct {
|
||||||
|
io.Reader
|
||||||
|
io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c fakeNetConn) Close() error { return nil }
|
||||||
|
func (c fakeNetConn) LocalAddr() net.Addr { return nil }
|
||||||
|
func (c fakeNetConn) RemoteAddr() net.Addr { return nil }
|
||||||
|
func (c fakeNetConn) SetDeadline(t time.Time) error { return nil }
|
||||||
|
func (c fakeNetConn) SetReadDeadline(t time.Time) error { return nil }
|
||||||
|
func (c fakeNetConn) SetWriteDeadline(t time.Time) error { return nil }
|
||||||
|
|
||||||
|
func TestFraming(t *testing.T) {
|
||||||
|
frameSizes := []int{0, 1, 2, 124, 125, 126, 127, 128, 129, 65534, 65535, 65536, 65537}
|
||||||
|
var readChunkers = []struct {
|
||||||
|
name string
|
||||||
|
f func(io.Reader) io.Reader
|
||||||
|
}{
|
||||||
|
{"half", iotest.HalfReader},
|
||||||
|
{"one", iotest.OneByteReader},
|
||||||
|
{"asis", func(r io.Reader) io.Reader { return r }},
|
||||||
|
}
|
||||||
|
|
||||||
|
writeBuf := make([]byte, 65537)
|
||||||
|
for i := range writeBuf {
|
||||||
|
writeBuf[i] = byte(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, isServer := range []bool{true, false} {
|
||||||
|
for _, chunker := range readChunkers {
|
||||||
|
|
||||||
|
var connBuf bytes.Buffer
|
||||||
|
wc := newConn(fakeNetConn{Reader: nil, Writer: &connBuf}, isServer, 1024, 1024)
|
||||||
|
rc := newConn(fakeNetConn{Reader: chunker.f(&connBuf), Writer: nil}, !isServer, 1024, 1024)
|
||||||
|
|
||||||
|
for _, n := range frameSizes {
|
||||||
|
for _, iocopy := range []bool{true, false} {
|
||||||
|
name := fmt.Sprintf("s:%v, r:%s, n:%d c:%v", isServer, chunker.name, n, iocopy)
|
||||||
|
|
||||||
|
w, err := wc.NextWriter(TextMessage)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: wc.NextWriter() returned %v", name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var nn int
|
||||||
|
if iocopy {
|
||||||
|
var n64 int64
|
||||||
|
n64, err = io.Copy(w, bytes.NewReader(writeBuf[:n]))
|
||||||
|
nn = int(n64)
|
||||||
|
} else {
|
||||||
|
nn, err = w.Write(writeBuf[:n])
|
||||||
|
}
|
||||||
|
if err != nil || nn != n {
|
||||||
|
t.Errorf("%s: w.Write(writeBuf[:n]) returned %d, %v", name, nn, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = w.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: w.Close() returned %v", name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
opCode, r, err := rc.NextReader()
|
||||||
|
if err != nil || opCode != TextMessage {
|
||||||
|
t.Errorf("%s: NextReader() returned %d, r, %v", name, opCode, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rbuf, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: ReadFull() returned rbuf, %v", name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rbuf) != n {
|
||||||
|
t.Errorf("%s: len(rbuf) is %d, want %d", name, len(rbuf), n)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, b := range rbuf {
|
||||||
|
if byte(i) != b {
|
||||||
|
t.Errorf("%s: bad byte at offset %d", name, i)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestControl(t *testing.T) {
|
||||||
|
const message = "this is a ping/pong messsage"
|
||||||
|
for _, isServer := range []bool{true, false} {
|
||||||
|
for _, isWriteControl := range []bool{true, false} {
|
||||||
|
name := fmt.Sprintf("s:%v, wc:%v", isServer, isWriteControl)
|
||||||
|
var connBuf bytes.Buffer
|
||||||
|
wc := newConn(fakeNetConn{Reader: nil, Writer: &connBuf}, isServer, 1024, 1024)
|
||||||
|
rc := newConn(fakeNetConn{Reader: &connBuf, Writer: nil}, !isServer, 1024, 1024)
|
||||||
|
if isWriteControl {
|
||||||
|
wc.WriteControl(PongMessage, []byte(message), time.Now().Add(time.Second))
|
||||||
|
} else {
|
||||||
|
w, err := wc.NextWriter(PongMessage)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: wc.NextWriter() returned %v", name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := w.Write([]byte(message)); err != nil {
|
||||||
|
t.Errorf("%s: w.Write() returned %v", name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := w.Close(); err != nil {
|
||||||
|
t.Errorf("%s: w.Close() returned %v", name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var actualMessage string
|
||||||
|
rc.SetPongHandler(func(s string) error { actualMessage = s; return nil })
|
||||||
|
rc.NextReader()
|
||||||
|
if actualMessage != message {
|
||||||
|
t.Errorf("%s: pong=%q, want %q", name, actualMessage, message)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloseBeforeFinalFrame(t *testing.T) {
|
||||||
|
const bufSize = 512
|
||||||
|
|
||||||
|
expectedErr := &CloseError{Code: CloseNormalClosure, Text: "hello"}
|
||||||
|
|
||||||
|
var b1, b2 bytes.Buffer
|
||||||
|
wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, bufSize)
|
||||||
|
rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024)
|
||||||
|
|
||||||
|
w, _ := wc.NextWriter(BinaryMessage)
|
||||||
|
w.Write(make([]byte, bufSize+bufSize/2))
|
||||||
|
wc.WriteControl(CloseMessage, FormatCloseMessage(expectedErr.Code, expectedErr.Text), time.Now().Add(10*time.Second))
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
op, r, err := rc.NextReader()
|
||||||
|
if op != BinaryMessage || err != nil {
|
||||||
|
t.Fatalf("NextReader() returned %d, %v", op, err)
|
||||||
|
}
|
||||||
|
_, err = io.Copy(ioutil.Discard, r)
|
||||||
|
if !reflect.DeepEqual(err, expectedErr) {
|
||||||
|
t.Fatalf("io.Copy() returned %v, want %v", err, expectedErr)
|
||||||
|
}
|
||||||
|
_, _, err = rc.NextReader()
|
||||||
|
if !reflect.DeepEqual(err, expectedErr) {
|
||||||
|
t.Fatalf("NextReader() returned %v, want %v", err, expectedErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEOFBeforeFinalFrame(t *testing.T) {
|
||||||
|
const bufSize = 512
|
||||||
|
|
||||||
|
var b1, b2 bytes.Buffer
|
||||||
|
wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, bufSize)
|
||||||
|
rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024)
|
||||||
|
|
||||||
|
w, _ := wc.NextWriter(BinaryMessage)
|
||||||
|
w.Write(make([]byte, bufSize+bufSize/2))
|
||||||
|
|
||||||
|
op, r, err := rc.NextReader()
|
||||||
|
if op != BinaryMessage || err != nil {
|
||||||
|
t.Fatalf("NextReader() returned %d, %v", op, err)
|
||||||
|
}
|
||||||
|
_, err = io.Copy(ioutil.Discard, r)
|
||||||
|
if err != errUnexpectedEOF {
|
||||||
|
t.Fatalf("io.Copy() returned %v, want %v", err, errUnexpectedEOF)
|
||||||
|
}
|
||||||
|
_, _, err = rc.NextReader()
|
||||||
|
if err != errUnexpectedEOF {
|
||||||
|
t.Fatalf("NextReader() returned %v, want %v", err, errUnexpectedEOF)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadLimit(t *testing.T) {
|
||||||
|
|
||||||
|
const readLimit = 512
|
||||||
|
message := make([]byte, readLimit+1)
|
||||||
|
|
||||||
|
var b1, b2 bytes.Buffer
|
||||||
|
wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, readLimit-2)
|
||||||
|
rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024)
|
||||||
|
rc.SetReadLimit(readLimit)
|
||||||
|
|
||||||
|
// Send message at the limit with interleaved pong.
|
||||||
|
w, _ := wc.NextWriter(BinaryMessage)
|
||||||
|
w.Write(message[:readLimit-1])
|
||||||
|
wc.WriteControl(PongMessage, []byte("this is a pong"), time.Now().Add(10*time.Second))
|
||||||
|
w.Write(message[:1])
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
// Send message larger than the limit.
|
||||||
|
wc.WriteMessage(BinaryMessage, message[:readLimit+1])
|
||||||
|
|
||||||
|
op, _, err := rc.NextReader()
|
||||||
|
if op != BinaryMessage || err != nil {
|
||||||
|
t.Fatalf("1: NextReader() returned %d, %v", op, err)
|
||||||
|
}
|
||||||
|
op, r, err := rc.NextReader()
|
||||||
|
if op != BinaryMessage || err != nil {
|
||||||
|
t.Fatalf("2: NextReader() returned %d, %v", op, err)
|
||||||
|
}
|
||||||
|
_, err = io.Copy(ioutil.Discard, r)
|
||||||
|
if err != ErrReadLimit {
|
||||||
|
t.Fatalf("io.Copy() returned %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnderlyingConn(t *testing.T) {
|
||||||
|
var b1, b2 bytes.Buffer
|
||||||
|
fc := fakeNetConn{Reader: &b1, Writer: &b2}
|
||||||
|
c := newConn(fc, true, 1024, 1024)
|
||||||
|
ul := c.UnderlyingConn()
|
||||||
|
if ul != fc {
|
||||||
|
t.Fatalf("Underlying conn is not what it should be.")
|
||||||
|
}
|
||||||
|
}
|
148
Godeps/_workspace/src/github.com/gorilla/websocket/doc.go
generated
vendored
Normal file
148
Godeps/_workspace/src/github.com/gorilla/websocket/doc.go
generated
vendored
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// 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 do not support concurrent calls to the write methods
|
||||||
|
// (NextWriter, SetWriteDeadline, WriteMessage) or concurrent calls to the read
|
||||||
|
// methods methods (NextReader, SetReadDeadline, ReadMessage). Connections do
|
||||||
|
// support a concurrent reader and writer.
|
||||||
|
//
|
||||||
|
// 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
|
13
Godeps/_workspace/src/github.com/gorilla/websocket/examples/autobahn/README.md
generated
vendored
Normal file
13
Godeps/_workspace/src/github.com/gorilla/websocket/examples/autobahn/README.md
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# 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.
|
14
Godeps/_workspace/src/github.com/gorilla/websocket/examples/autobahn/fuzzingclient.json
generated
vendored
Normal file
14
Godeps/_workspace/src/github.com/gorilla/websocket/examples/autobahn/fuzzingclient.json
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
{
|
||||||
|
"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": {}
|
||||||
|
}
|
246
Godeps/_workspace/src/github.com/gorilla/websocket/examples/autobahn/server.go
generated
vendored
Normal file
246
Godeps/_workspace/src/github.com/gorilla/websocket/examples/autobahn/server.go
generated
vendored
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
// 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
|
||||||
|
}
|
20
Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/README.md
generated
vendored
Normal file
20
Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/README.md
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# 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.
|
106
Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/conn.go
generated
vendored
Normal file
106
Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/conn.go
generated
vendored
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// 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 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// serverWs handles websocket requests from the peer.
|
||||||
|
func serveWs(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != "GET" {
|
||||||
|
http.Error(w, "Method not allowed", 405)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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()
|
||||||
|
}
|
92
Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/home.html
generated
vendored
Normal file
92
Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/home.html
generated
vendored
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
<!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>
|
51
Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/hub.go
generated
vendored
Normal file
51
Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/hub.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/main.go
generated
vendored
Normal file
39
Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/main.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
9
Godeps/_workspace/src/github.com/gorilla/websocket/examples/filewatch/README.md
generated
vendored
Normal file
9
Godeps/_workspace/src/github.com/gorilla/websocket/examples/filewatch/README.md
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# 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.
|
193
Godeps/_workspace/src/github.com/gorilla/websocket/examples/filewatch/main.go
generated
vendored
Normal file
193
Godeps/_workspace/src/github.com/gorilla/websocket/examples/filewatch/main.go
generated
vendored
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
// 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>
|
||||||
|
`
|
55
Godeps/_workspace/src/github.com/gorilla/websocket/json.go
generated
vendored
Normal file
55
Godeps/_workspace/src/github.com/gorilla/websocket/json.go
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// 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
|
||||||
|
}
|
119
Godeps/_workspace/src/github.com/gorilla/websocket/json_test.go
generated
vendored
Normal file
119
Godeps/_workspace/src/github.com/gorilla/websocket/json_test.go
generated
vendored
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
// 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 (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJSON(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
c := fakeNetConn{&buf, &buf}
|
||||||
|
wc := newConn(c, true, 1024, 1024)
|
||||||
|
rc := newConn(c, false, 1024, 1024)
|
||||||
|
|
||||||
|
var actual, expect struct {
|
||||||
|
A int
|
||||||
|
B string
|
||||||
|
}
|
||||||
|
expect.A = 1
|
||||||
|
expect.B = "hello"
|
||||||
|
|
||||||
|
if err := wc.WriteJSON(&expect); err != nil {
|
||||||
|
t.Fatal("write", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rc.ReadJSON(&actual); err != nil {
|
||||||
|
t.Fatal("read", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(&actual, &expect) {
|
||||||
|
t.Fatal("equal", actual, expect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPartialJSONRead(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
c := fakeNetConn{&buf, &buf}
|
||||||
|
wc := newConn(c, true, 1024, 1024)
|
||||||
|
rc := newConn(c, false, 1024, 1024)
|
||||||
|
|
||||||
|
var v struct {
|
||||||
|
A int
|
||||||
|
B string
|
||||||
|
}
|
||||||
|
v.A = 1
|
||||||
|
v.B = "hello"
|
||||||
|
|
||||||
|
messageCount := 0
|
||||||
|
|
||||||
|
// Partial JSON values.
|
||||||
|
|
||||||
|
data, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for i := len(data) - 1; i >= 0; i-- {
|
||||||
|
if err := wc.WriteMessage(TextMessage, data[:i]); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
messageCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whitespace.
|
||||||
|
|
||||||
|
if err := wc.WriteMessage(TextMessage, []byte(" ")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
messageCount++
|
||||||
|
|
||||||
|
// Close.
|
||||||
|
|
||||||
|
if err := wc.WriteMessage(CloseMessage, FormatCloseMessage(CloseNormalClosure, "")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < messageCount; i++ {
|
||||||
|
err := rc.ReadJSON(&v)
|
||||||
|
if err != io.ErrUnexpectedEOF {
|
||||||
|
t.Error("read", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rc.ReadJSON(&v)
|
||||||
|
if _, ok := err.(*CloseError); !ok {
|
||||||
|
t.Error("final", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeprecatedJSON(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
c := fakeNetConn{&buf, &buf}
|
||||||
|
wc := newConn(c, true, 1024, 1024)
|
||||||
|
rc := newConn(c, false, 1024, 1024)
|
||||||
|
|
||||||
|
var actual, expect struct {
|
||||||
|
A int
|
||||||
|
B string
|
||||||
|
}
|
||||||
|
expect.A = 1
|
||||||
|
expect.B = "hello"
|
||||||
|
|
||||||
|
if err := WriteJSON(wc, &expect); err != nil {
|
||||||
|
t.Fatal("write", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ReadJSON(rc, &actual); err != nil {
|
||||||
|
t.Fatal("read", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(&actual, &expect) {
|
||||||
|
t.Fatal("equal", actual, expect)
|
||||||
|
}
|
||||||
|
}
|
247
Godeps/_workspace/src/github.com/gorilla/websocket/server.go
generated
vendored
Normal file
247
Godeps/_workspace/src/github.com/gorilla/websocket/server.go
generated
vendored
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
// 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 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
|
||||||
|
}
|
33
Godeps/_workspace/src/github.com/gorilla/websocket/server_test.go
generated
vendored
Normal file
33
Godeps/_workspace/src/github.com/gorilla/websocket/server_test.go
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// 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 (
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var subprotocolTests = []struct {
|
||||||
|
h string
|
||||||
|
protocols []string
|
||||||
|
}{
|
||||||
|
{"", nil},
|
||||||
|
{"foo", []string{"foo"}},
|
||||||
|
{"foo,bar", []string{"foo", "bar"}},
|
||||||
|
{"foo, bar", []string{"foo", "bar"}},
|
||||||
|
{" foo, bar", []string{"foo", "bar"}},
|
||||||
|
{" foo, bar ", []string{"foo", "bar"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSubprotocols(t *testing.T) {
|
||||||
|
for _, st := range subprotocolTests {
|
||||||
|
r := http.Request{Header: http.Header{"Sec-Websocket-Protocol": {st.h}}}
|
||||||
|
protocols := Subprotocols(&r)
|
||||||
|
if !reflect.DeepEqual(st.protocols, protocols) {
|
||||||
|
t.Errorf("SubProtocols(%q) returned %#v, want %#v", st.h, protocols, st.protocols)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
Godeps/_workspace/src/github.com/gorilla/websocket/util.go
generated
vendored
Normal file
44
Godeps/_workspace/src/github.com/gorilla/websocket/util.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// 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
|
||||||
|
}
|
34
Godeps/_workspace/src/github.com/gorilla/websocket/util_test.go
generated
vendored
Normal file
34
Godeps/_workspace/src/github.com/gorilla/websocket/util_test.go
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2014 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 (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tokenListContainsValueTests = []struct {
|
||||||
|
value string
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{"WebSocket", true},
|
||||||
|
{"WEBSOCKET", true},
|
||||||
|
{"websocket", true},
|
||||||
|
{"websockets", false},
|
||||||
|
{"x websocket", false},
|
||||||
|
{"websocket x", false},
|
||||||
|
{"other,websocket,more", true},
|
||||||
|
{"other, websocket, more", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTokenListContainsValue(t *testing.T) {
|
||||||
|
for _, tt := range tokenListContainsValueTests {
|
||||||
|
h := http.Header{"Upgrade": {tt.value}}
|
||||||
|
ok := tokenListContainsValue(h, "Upgrade", "websocket")
|
||||||
|
if ok != tt.ok {
|
||||||
|
t.Errorf("tokenListContainsValue(h, n, %q) = %v, want %v", tt.value, ok, tt.ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
Godeps/_workspace/src/github.com/kr/pty/.gitignore
generated
vendored
Normal file
4
Godeps/_workspace/src/github.com/kr/pty/.gitignore
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[568].out
|
||||||
|
_go*
|
||||||
|
_test*
|
||||||
|
_obj
|
23
Godeps/_workspace/src/github.com/kr/pty/License
generated
vendored
Normal file
23
Godeps/_workspace/src/github.com/kr/pty/License
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
Copyright (c) 2011 Keith Rarick
|
||||||
|
|
||||||
|
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.
|
36
Godeps/_workspace/src/github.com/kr/pty/README.md
generated
vendored
Normal file
36
Godeps/_workspace/src/github.com/kr/pty/README.md
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# pty
|
||||||
|
|
||||||
|
Pty is a Go package for using unix pseudo-terminals.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
go get github.com/kr/pty
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kr/pty"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c := exec.Command("grep", "--color=auto", "bar")
|
||||||
|
f, err := pty.Start(c)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
f.Write([]byte("foo\n"))
|
||||||
|
f.Write([]byte("bar\n"))
|
||||||
|
f.Write([]byte("baz\n"))
|
||||||
|
f.Write([]byte{4}) // EOT
|
||||||
|
}()
|
||||||
|
io.Copy(os.Stdout, f)
|
||||||
|
}
|
||||||
|
```
|
16
Godeps/_workspace/src/github.com/kr/pty/doc.go
generated
vendored
Normal file
16
Godeps/_workspace/src/github.com/kr/pty/doc.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Package pty provides functions for working with Unix terminals.
|
||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrUnsupported is returned if a function is not
|
||||||
|
// available on the current platform.
|
||||||
|
var ErrUnsupported = errors.New("unsupported")
|
||||||
|
|
||||||
|
// Opens a pty and its corresponding tty.
|
||||||
|
func Open() (pty, tty *os.File, err error) {
|
||||||
|
return open()
|
||||||
|
}
|
11
Godeps/_workspace/src/github.com/kr/pty/ioctl.go
generated
vendored
Normal file
11
Godeps/_workspace/src/github.com/kr/pty/ioctl.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package pty
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
func ioctl(fd, cmd, ptr uintptr) error {
|
||||||
|
_, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr)
|
||||||
|
if e != 0 {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
39
Godeps/_workspace/src/github.com/kr/pty/ioctl_bsd.go
generated
vendored
Normal file
39
Godeps/_workspace/src/github.com/kr/pty/ioctl_bsd.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// +build darwin dragonfly freebsd netbsd openbsd
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
// from <sys/ioccom.h>
|
||||||
|
const (
|
||||||
|
_IOC_VOID uintptr = 0x20000000
|
||||||
|
_IOC_OUT uintptr = 0x40000000
|
||||||
|
_IOC_IN uintptr = 0x80000000
|
||||||
|
_IOC_IN_OUT uintptr = _IOC_OUT | _IOC_IN
|
||||||
|
_IOC_DIRMASK = _IOC_VOID | _IOC_OUT | _IOC_IN
|
||||||
|
|
||||||
|
_IOC_PARAM_SHIFT = 13
|
||||||
|
_IOC_PARAM_MASK = (1 << _IOC_PARAM_SHIFT) - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
func _IOC_PARM_LEN(ioctl uintptr) uintptr {
|
||||||
|
return (ioctl >> 16) & _IOC_PARAM_MASK
|
||||||
|
}
|
||||||
|
|
||||||
|
func _IOC(inout uintptr, group byte, ioctl_num uintptr, param_len uintptr) uintptr {
|
||||||
|
return inout | (param_len&_IOC_PARAM_MASK)<<16 | uintptr(group)<<8 | ioctl_num
|
||||||
|
}
|
||||||
|
|
||||||
|
func _IO(group byte, ioctl_num uintptr) uintptr {
|
||||||
|
return _IOC(_IOC_VOID, group, ioctl_num, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _IOR(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
|
||||||
|
return _IOC(_IOC_OUT, group, ioctl_num, param_len)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _IOW(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
|
||||||
|
return _IOC(_IOC_IN, group, ioctl_num, param_len)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _IOWR(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
|
||||||
|
return _IOC(_IOC_IN_OUT, group, ioctl_num, param_len)
|
||||||
|
}
|
19
Godeps/_workspace/src/github.com/kr/pty/mktypes.bash
generated
vendored
Normal file
19
Godeps/_workspace/src/github.com/kr/pty/mktypes.bash
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
GOOSARCH="${GOOS}_${GOARCH}"
|
||||||
|
case "$GOOSARCH" in
|
||||||
|
_* | *_ | _)
|
||||||
|
echo 'undefined $GOOS_$GOARCH:' "$GOOSARCH" 1>&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
GODEFS="go tool cgo -godefs"
|
||||||
|
|
||||||
|
$GODEFS types.go |gofmt > ztypes_$GOARCH.go
|
||||||
|
|
||||||
|
case $GOOS in
|
||||||
|
freebsd)
|
||||||
|
$GODEFS types_$GOOS.go |gofmt > ztypes_$GOOSARCH.go
|
||||||
|
;;
|
||||||
|
esac
|
60
Godeps/_workspace/src/github.com/kr/pty/pty_darwin.go
generated
vendored
Normal file
60
Godeps/_workspace/src/github.com/kr/pty/pty_darwin.go
generated
vendored
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func open() (pty, tty *os.File, err error) {
|
||||||
|
p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sname, err := ptsname(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = grantpt(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = unlockpt(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := os.OpenFile(sname, os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return p, t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
n := make([]byte, _IOC_PARM_LEN(syscall.TIOCPTYGNAME))
|
||||||
|
|
||||||
|
err := ioctl(f.Fd(), syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n[0])))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range n {
|
||||||
|
if c == 0 {
|
||||||
|
return string(n[:i]), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", errors.New("TIOCPTYGNAME string not NUL-terminated")
|
||||||
|
}
|
||||||
|
|
||||||
|
func grantpt(f *os.File) error {
|
||||||
|
return ioctl(f.Fd(), syscall.TIOCPTYGRANT, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
return ioctl(f.Fd(), syscall.TIOCPTYUNLK, 0)
|
||||||
|
}
|
73
Godeps/_workspace/src/github.com/kr/pty/pty_freebsd.go
generated
vendored
Normal file
73
Godeps/_workspace/src/github.com/kr/pty/pty_freebsd.go
generated
vendored
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func posix_openpt(oflag int) (fd int, err error) {
|
||||||
|
r0, _, e1 := syscall.Syscall(syscall.SYS_POSIX_OPENPT, uintptr(oflag), 0, 0)
|
||||||
|
fd = int(r0)
|
||||||
|
if e1 != 0 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func open() (pty, tty *os.File, err error) {
|
||||||
|
fd, err := posix_openpt(syscall.O_RDWR | syscall.O_CLOEXEC)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p := os.NewFile(uintptr(fd), "/dev/pts")
|
||||||
|
sname, err := ptsname(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := os.OpenFile("/dev/"+sname, os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return p, t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isptmaster(fd uintptr) (bool, error) {
|
||||||
|
err := ioctl(fd, syscall.TIOCPTMASTER, 0)
|
||||||
|
return err == nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
emptyFiodgnameArg fiodgnameArg
|
||||||
|
ioctl_FIODGNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg))
|
||||||
|
)
|
||||||
|
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
master, err := isptmaster(f.Fd())
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !master {
|
||||||
|
return "", syscall.EINVAL
|
||||||
|
}
|
||||||
|
|
||||||
|
const n = _C_SPECNAMELEN + 1
|
||||||
|
var (
|
||||||
|
buf = make([]byte, n)
|
||||||
|
arg = fiodgnameArg{Len: n, Buf: (*byte)(unsafe.Pointer(&buf[0]))}
|
||||||
|
)
|
||||||
|
err = ioctl(f.Fd(), ioctl_FIODGNAME, uintptr(unsafe.Pointer(&arg)))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range buf {
|
||||||
|
if c == 0 {
|
||||||
|
return string(buf[:i]), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", errors.New("FIODGNAME string not NUL-terminated")
|
||||||
|
}
|
46
Godeps/_workspace/src/github.com/kr/pty/pty_linux.go
generated
vendored
Normal file
46
Godeps/_workspace/src/github.com/kr/pty/pty_linux.go
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func open() (pty, tty *os.File, err error) {
|
||||||
|
p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sname, err := ptsname(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = unlockpt(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return p, t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
var n _C_uint
|
||||||
|
err := ioctl(f.Fd(), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return "/dev/pts/" + strconv.Itoa(int(n)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
var u _C_int
|
||||||
|
// use TIOCSPTLCK with a zero valued arg to clear the slave pty lock
|
||||||
|
return ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u)))
|
||||||
|
}
|
11
Godeps/_workspace/src/github.com/kr/pty/pty_unsupported.go
generated
vendored
Normal file
11
Godeps/_workspace/src/github.com/kr/pty/pty_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// +build !linux,!darwin,!freebsd
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func open() (pty, tty *os.File, err error) {
|
||||||
|
return nil, nil, ErrUnsupported
|
||||||
|
}
|
28
Godeps/_workspace/src/github.com/kr/pty/run.go
generated
vendored
Normal file
28
Godeps/_workspace/src/github.com/kr/pty/run.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Start assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout,
|
||||||
|
// and c.Stderr, calls c.Start, and returns the File of the tty's
|
||||||
|
// corresponding pty.
|
||||||
|
func Start(c *exec.Cmd) (pty *os.File, err error) {
|
||||||
|
pty, tty, err := Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer tty.Close()
|
||||||
|
c.Stdout = tty
|
||||||
|
c.Stdin = tty
|
||||||
|
c.Stderr = tty
|
||||||
|
c.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
|
||||||
|
err = c.Start()
|
||||||
|
if err != nil {
|
||||||
|
pty.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pty, err
|
||||||
|
}
|
10
Godeps/_workspace/src/github.com/kr/pty/types.go
generated
vendored
Normal file
10
Godeps/_workspace/src/github.com/kr/pty/types.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int C.int
|
||||||
|
_C_uint C.uint
|
||||||
|
)
|
15
Godeps/_workspace/src/github.com/kr/pty/types_freebsd.go
generated
vendored
Normal file
15
Godeps/_workspace/src/github.com/kr/pty/types_freebsd.go
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <sys/filio.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
const (
|
||||||
|
_C_SPECNAMELEN = C.SPECNAMELEN /* max length of devicename */
|
||||||
|
)
|
||||||
|
|
||||||
|
type fiodgnameArg C.struct_fiodgname_arg
|
35
Godeps/_workspace/src/github.com/kr/pty/util.go
generated
vendored
Normal file
35
Godeps/_workspace/src/github.com/kr/pty/util.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Getsize returns the number of rows (lines) and cols (positions
|
||||||
|
// in each line) in terminal t.
|
||||||
|
func Getsize(t *os.File) (rows, cols int, err error) {
|
||||||
|
var ws winsize
|
||||||
|
err = windowrect(&ws, t.Fd())
|
||||||
|
return int(ws.ws_row), int(ws.ws_col), err
|
||||||
|
}
|
||||||
|
|
||||||
|
type winsize struct {
|
||||||
|
ws_row uint16
|
||||||
|
ws_col uint16
|
||||||
|
ws_xpixel uint16
|
||||||
|
ws_ypixel uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func windowrect(ws *winsize, fd uintptr) error {
|
||||||
|
_, _, errno := syscall.Syscall(
|
||||||
|
syscall.SYS_IOCTL,
|
||||||
|
fd,
|
||||||
|
syscall.TIOCGWINSZ,
|
||||||
|
uintptr(unsafe.Pointer(ws)),
|
||||||
|
)
|
||||||
|
if errno != 0 {
|
||||||
|
return syscall.Errno(errno)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
9
Godeps/_workspace/src/github.com/kr/pty/ztypes_386.go
generated
vendored
Normal file
9
Godeps/_workspace/src/github.com/kr/pty/ztypes_386.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
9
Godeps/_workspace/src/github.com/kr/pty/ztypes_amd64.go
generated
vendored
Normal file
9
Godeps/_workspace/src/github.com/kr/pty/ztypes_amd64.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
9
Godeps/_workspace/src/github.com/kr/pty/ztypes_arm.go
generated
vendored
Normal file
9
Godeps/_workspace/src/github.com/kr/pty/ztypes_arm.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
11
Godeps/_workspace/src/github.com/kr/pty/ztypes_arm64.go
generated
vendored
Normal file
11
Godeps/_workspace/src/github.com/kr/pty/ztypes_arm64.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
// +build arm64
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
13
Godeps/_workspace/src/github.com/kr/pty/ztypes_freebsd_386.go
generated
vendored
Normal file
13
Godeps/_workspace/src/github.com/kr/pty/ztypes_freebsd_386.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types_freebsd.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
const (
|
||||||
|
_C_SPECNAMELEN = 0x3f
|
||||||
|
)
|
||||||
|
|
||||||
|
type fiodgnameArg struct {
|
||||||
|
Len int32
|
||||||
|
Buf *byte
|
||||||
|
}
|
14
Godeps/_workspace/src/github.com/kr/pty/ztypes_freebsd_amd64.go
generated
vendored
Normal file
14
Godeps/_workspace/src/github.com/kr/pty/ztypes_freebsd_amd64.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types_freebsd.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
const (
|
||||||
|
_C_SPECNAMELEN = 0x3f
|
||||||
|
)
|
||||||
|
|
||||||
|
type fiodgnameArg struct {
|
||||||
|
Len int32
|
||||||
|
Pad_cgo_0 [4]byte
|
||||||
|
Buf *byte
|
||||||
|
}
|
13
Godeps/_workspace/src/github.com/kr/pty/ztypes_freebsd_arm.go
generated
vendored
Normal file
13
Godeps/_workspace/src/github.com/kr/pty/ztypes_freebsd_arm.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types_freebsd.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
const (
|
||||||
|
_C_SPECNAMELEN = 0x3f
|
||||||
|
)
|
||||||
|
|
||||||
|
type fiodgnameArg struct {
|
||||||
|
Len int32
|
||||||
|
Buf *byte
|
||||||
|
}
|
11
Godeps/_workspace/src/github.com/kr/pty/ztypes_ppc64.go
generated
vendored
Normal file
11
Godeps/_workspace/src/github.com/kr/pty/ztypes_ppc64.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// +build ppc64
|
||||||
|
|
||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
11
Godeps/_workspace/src/github.com/kr/pty/ztypes_ppc64le.go
generated
vendored
Normal file
11
Godeps/_workspace/src/github.com/kr/pty/ztypes_ppc64le.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// +build ppc64le
|
||||||
|
|
||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
11
Godeps/_workspace/src/github.com/kr/pty/ztypes_s390x.go
generated
vendored
Normal file
11
Godeps/_workspace/src/github.com/kr/pty/ztypes_s390x.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// +build s390x
|
||||||
|
|
||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
160
LICENSE
Normal file
160
LICENSE
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Iwasaki Yudai
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
=============================================================================
|
||||||
|
|
||||||
|
This software is built with following third party open source software.
|
||||||
|
|
||||||
|
|
||||||
|
# libapps/hterm
|
||||||
|
|
||||||
|
Copyright (c) 2014, Google Inc. 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.
|
||||||
|
|
||||||
|
|
||||||
|
# odegangsta/cli
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
# elazarl/go-bindata-assetfs
|
||||||
|
|
||||||
|
Copyright (c) 2014, Elazar Leibovich
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
# gorilla/websocket
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
# kr/pty
|
||||||
|
|
||||||
|
Copyright (c) 2011 Keith Rarick
|
||||||
|
|
||||||
|
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.
|
17
Makefile
Normal file
17
Makefile
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
gotty: resource.go main.go app/*.go
|
||||||
|
go build
|
||||||
|
|
||||||
|
resource.go: bindata/hterm.js bindata/index.html bindata/gotty.js
|
||||||
|
go-bindata -pkg app -ignore=\\.gitkeep -o app/resource.go bindata/
|
||||||
|
|
||||||
|
bindata:
|
||||||
|
mkdir bindata
|
||||||
|
bindata/hterm.js: bindata libapps/hterm/js/*.js
|
||||||
|
cd libapps && \
|
||||||
|
LIBDOT_SEARCH_PATH=`pwd` ./libdot/bin/concat.sh -i ./hterm/concat/hterm_all.concat -o ../bindata/hterm.js
|
||||||
|
|
||||||
|
bindata/index.html: bindata resources/index.html
|
||||||
|
cp resources/index.html bindata/index.html
|
||||||
|
|
||||||
|
bindata/gotty.js: bindata resources/gotty.js
|
||||||
|
cp resources/gotty.js bindata/gotty.js
|
34
README.md
Normal file
34
README.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# GoTTY - Share your terminal as a web application
|
||||||
|
|
||||||
|
GoTTY is a simple command line tool that turns your CLI tools into web applications.
|
||||||
|
|
||||||
|
![Screenshot](https://raw.githubusercontent.com/yudai/gotty/master/screenshot.gif)
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
Download the latest binary file from the [Releases](https://github.com/yudai/gotty/releases) page.
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
|
||||||
|
```sh
|
||||||
|
Usage: gotty COMMAND_NAME [COMMAND_ARGUMENTS...]
|
||||||
|
```
|
||||||
|
|
||||||
|
Run `gotty` with your prefered command as its arguments (e.g. `gotty top`).
|
||||||
|
|
||||||
|
By default, gotty starts a web server at port 8080. Open the URL on your web browser and you can see the running command as if it's running on your terminal.
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
```
|
||||||
|
--addr, -a IP address to listen at
|
||||||
|
--port, -p "8080" Port number to listen at
|
||||||
|
--permit-write, -w Permit write from client (BE CAREFUL)
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, gotty doesn't allow clients to send any keystrokes or commands except terminal window resizing. When you want to permmit clients to write input to the PTY, add the `-w` option. However, accepting input from remote clients is dangerous for most commands. Make sure that only trusted clients can connect to your gotty server when activate this option. If you need interaction with the PTY, consider starting gotty with tmux or GNU Screen and run your main command on it.
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
The MIT License
|
185
app/app.go
Normal file
185
app/app.go
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/elazarl/go-bindata-assetfs"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/kr/pty"
|
||||||
|
)
|
||||||
|
|
||||||
|
type App struct {
|
||||||
|
Address string
|
||||||
|
Port string
|
||||||
|
PermitWrite bool
|
||||||
|
Command []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(address string, port string, permitWrite bool, command []string) *App {
|
||||||
|
return &App{
|
||||||
|
Address: address,
|
||||||
|
Port: port,
|
||||||
|
PermitWrite: permitWrite,
|
||||||
|
Command: command,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) Run() error {
|
||||||
|
http.Handle("/",
|
||||||
|
http.FileServer(
|
||||||
|
&assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "bindata"},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
http.HandleFunc("/ws", app.generateHandler())
|
||||||
|
|
||||||
|
url := app.Address + ":" + app.Port
|
||||||
|
log.Printf("Sever is running at %s, command: %s", url, strings.Join(app.Command, " "))
|
||||||
|
err := http.ListenAndServe(url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) generateHandler() func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log.Printf("New client connected: %s", r.RemoteAddr)
|
||||||
|
|
||||||
|
upgrader := websocket.Upgrader{
|
||||||
|
ReadBufferSize: 0,
|
||||||
|
WriteBufferSize: 0,
|
||||||
|
Subprotocols: []string{"gotty"},
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Method != "GET" {
|
||||||
|
http.Error(w, "Method not allowed", 405)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Print("Failed to upgrade connection")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(app.Command[0], app.Command[1:]...)
|
||||||
|
fio, err := pty.Start(cmd)
|
||||||
|
if err != nil {
|
||||||
|
log.Print("Failed to execute command")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
exit := make(chan bool, 2)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer func() { exit <- true }()
|
||||||
|
|
||||||
|
buf := make([]byte, 512)
|
||||||
|
for {
|
||||||
|
len, err := fio.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("command exited for: %s", r.RemoteAddr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writer, err := conn.NextWriter(1)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writer.Write(buf[0:len])
|
||||||
|
writer.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer func() { exit <- true }()
|
||||||
|
|
||||||
|
for {
|
||||||
|
_, data, err := conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch data[0] {
|
||||||
|
case '0':
|
||||||
|
if !app.PermitWrite {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := fio.Write(data[1:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
case '1':
|
||||||
|
var remoteCmd command
|
||||||
|
err = json.Unmarshal(data[1:], &remoteCmd)
|
||||||
|
if err != nil {
|
||||||
|
log.Print("Malformed remote command")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch remoteCmd.Name {
|
||||||
|
case "resize_terminal":
|
||||||
|
|
||||||
|
rows := remoteCmd.Arguments["rows"]
|
||||||
|
switch rows.(type) {
|
||||||
|
case float64:
|
||||||
|
default:
|
||||||
|
log.Print("Malformed remote command")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cols := remoteCmd.Arguments["columns"]
|
||||||
|
switch cols.(type) {
|
||||||
|
case float64:
|
||||||
|
default:
|
||||||
|
log.Print("Malformed remote command")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
window := struct {
|
||||||
|
row uint16
|
||||||
|
col uint16
|
||||||
|
x uint16
|
||||||
|
y uint16
|
||||||
|
}{
|
||||||
|
uint16(rows.(float64)),
|
||||||
|
uint16(cols.(float64)),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
}
|
||||||
|
syscall.Syscall(
|
||||||
|
syscall.SYS_IOCTL,
|
||||||
|
fio.Fd(),
|
||||||
|
syscall.TIOCSWINSZ,
|
||||||
|
uintptr(unsafe.Pointer(&window)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Print("Unknown message type")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-exit
|
||||||
|
fio.Close()
|
||||||
|
conn.Close()
|
||||||
|
log.Printf("Connection closed: %s", r.RemoteAddr)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type command struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Arguments map[string]interface{} `json:"arguments"`
|
||||||
|
}
|
286
app/resource.go
Normal file
286
app/resource.go
Normal file
File diff suppressed because one or more lines are too long
1
libapps
Submodule
1
libapps
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 9dc111b58c7e5af86c1b3a0cb3b04a22a8dcf584
|
50
main.go
Normal file
50
main.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
|
||||||
|
"github.com/yudai/gotty/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cmd := cli.NewApp()
|
||||||
|
cmd.Version = "0.0.1"
|
||||||
|
cmd.Name = "gotty"
|
||||||
|
cmd.Usage = "Share your terminal as a web application"
|
||||||
|
cmd.Flags = []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "addr, a",
|
||||||
|
Value: "",
|
||||||
|
Usage: "IP address to listen at",
|
||||||
|
EnvVar: "GOTTY_ADDR",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "port, p",
|
||||||
|
Value: "8080",
|
||||||
|
Usage: "Port number to listen at",
|
||||||
|
EnvVar: "GOTTY_PORT",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "permit-write, w",
|
||||||
|
Usage: "Permit write from client (BE CAREFUL)",
|
||||||
|
EnvVar: "GOTTY_PERMIT_WRITE",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmd.Action = func(c *cli.Context) {
|
||||||
|
if len(c.Args()) == 0 {
|
||||||
|
fmt.Println("No command given.\n")
|
||||||
|
cli.ShowAppHelp(c)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
app := app.New(c.String("addr"), c.String("port"), c.Bool("permit-write"), c.Args())
|
||||||
|
err := app.Run()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd.Run(os.Args)
|
||||||
|
}
|
52
resources/gotty.js
Normal file
52
resources/gotty.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
(function() {
|
||||||
|
var httpsEnabled = window.location.protocol == "https:";
|
||||||
|
var url = (httpsEnabled ? 'wss://' : 'ws://') + window.location.host + window.location.pathname + 'ws';
|
||||||
|
var protocols = ["gotty"];
|
||||||
|
var ws = new WebSocket(url, protocols);
|
||||||
|
|
||||||
|
var term;
|
||||||
|
|
||||||
|
|
||||||
|
ws.onopen = function(event) {
|
||||||
|
hterm.defaultStorage = new lib.Storage.Local()
|
||||||
|
|
||||||
|
term = new hterm.Terminal();
|
||||||
|
|
||||||
|
term.onTerminalReady = function() {
|
||||||
|
var io = term.io.push();
|
||||||
|
|
||||||
|
io.onVTKeystroke = function(str) {
|
||||||
|
ws.send("0" + str);
|
||||||
|
};
|
||||||
|
|
||||||
|
io.sendString = io.onVTKeystroke;
|
||||||
|
|
||||||
|
io.onTerminalResize = function(columns, rows) {
|
||||||
|
ws.send(
|
||||||
|
"1" + JSON.stringify(
|
||||||
|
{
|
||||||
|
name: "resize_terminal",
|
||||||
|
arguments: {
|
||||||
|
columns: columns,
|
||||||
|
rows: rows,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
term.installKeyboard();
|
||||||
|
};
|
||||||
|
|
||||||
|
term.decorate(document.body);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onmessage = function(event) {
|
||||||
|
term.io.writeUTF16(event.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onclose = function(event) {
|
||||||
|
term.io.print("Connection closed.");
|
||||||
|
term.uninstallKeyboard();
|
||||||
|
}
|
||||||
|
})()
|
11
resources/index.html
Normal file
11
resources/index.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>GoTTy</title>
|
||||||
|
<style>body {height: 100%; width: 100%; margin: 0px;}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="hterm.js"></script>
|
||||||
|
<script src="gotty.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
screenshot.gif
Normal file
BIN
screenshot.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 MiB |
Loading…
Reference in New Issue
Block a user