mirror of
https://github.com/grafana/grafana.git
synced 2025-12-20 11:40:21 +08:00
Compare commits
140 Commits
v2.0.0-bet
...
v2.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9540e12e2e | ||
|
|
9766106686 | ||
|
|
3d8beb918b | ||
|
|
123ea331e6 | ||
|
|
b5a77d4f4a | ||
|
|
9852972db8 | ||
|
|
b286ed6bb8 | ||
|
|
b56a084810 | ||
|
|
c9a28ddd38 | ||
|
|
bb7ee1c5a3 | ||
|
|
1772e15b46 | ||
|
|
360efdf71f | ||
|
|
56d1e6834a | ||
|
|
f1e267498e | ||
|
|
4ba6bf8e27 | ||
|
|
101c44f22d | ||
|
|
7f967524b9 | ||
|
|
01e171373c | ||
|
|
229a4a8aab | ||
|
|
f6feb455a9 | ||
|
|
fbf4eac257 | ||
|
|
48b5d235d8 | ||
|
|
ebdbbbc000 | ||
|
|
1edb13d715 | ||
|
|
aa8beda4ec | ||
|
|
7178dcad35 | ||
|
|
ddaac50a25 | ||
|
|
a446286869 | ||
|
|
c54f0fe58b | ||
|
|
7c8166cad2 | ||
|
|
3db2aaada6 | ||
|
|
649d40ed66 | ||
|
|
d71b626114 | ||
|
|
fc47fb8f64 | ||
|
|
10e5e5c400 | ||
|
|
942e8fdba8 | ||
|
|
6ba8854854 | ||
|
|
b7c0d99cdf | ||
|
|
bc4584d9fe | ||
|
|
53b1d6a5b2 | ||
|
|
836e4b4911 | ||
|
|
e9afd30b47 | ||
|
|
aefbcb7049 | ||
|
|
4fd2622e55 | ||
|
|
d048e39cde | ||
|
|
140f1307b4 | ||
|
|
9d10b5ab52 | ||
|
|
edb39a072e | ||
|
|
d66d989308 | ||
|
|
05c3b96202 | ||
|
|
e9c7264fd9 | ||
|
|
0b1c1bb8b4 | ||
|
|
47a18bba99 | ||
|
|
7ffdb488c1 | ||
|
|
2c1188f664 | ||
|
|
60ab08ed1b | ||
|
|
2bb85d216e | ||
|
|
7be8fb8432 | ||
|
|
a4ef1e22d6 | ||
|
|
3335041d39 | ||
|
|
19e881513b | ||
|
|
41777c01f1 | ||
|
|
6b26c8d46f | ||
|
|
d4339ae9d2 | ||
|
|
bdc4d14036 | ||
|
|
ed82f6c681 | ||
|
|
d4e6ae2804 | ||
|
|
a60bf642e1 | ||
|
|
be8a72c35b | ||
|
|
cc14705404 | ||
|
|
6de584aafc | ||
|
|
cc427b1307 | ||
|
|
00cdb5e36a | ||
|
|
437cd50039 | ||
|
|
61add0ecc6 | ||
|
|
8accb8865a | ||
|
|
c44248440e | ||
|
|
d32c03ed11 | ||
|
|
3e49609099 | ||
|
|
c47f4bc97c | ||
|
|
8ee7d5c2d5 | ||
|
|
2020fedfdb | ||
|
|
40b2535728 | ||
|
|
2d016c5a3f | ||
|
|
ba3bb57926 | ||
|
|
4c6d7630cd | ||
|
|
d1767144a8 | ||
|
|
a991cda233 | ||
|
|
9c2040aa9b | ||
|
|
96ee1c17a3 | ||
|
|
6911572fa1 | ||
|
|
059db533d5 | ||
|
|
f28af4f369 | ||
|
|
1bd238191c | ||
|
|
c07d48d930 | ||
|
|
00a713c42e | ||
|
|
01c97e69dd | ||
|
|
646298f5c5 | ||
|
|
8f73328e25 | ||
|
|
b5112aeee2 | ||
|
|
22adf0d06e | ||
|
|
382f7066d9 | ||
|
|
08c19692ea | ||
|
|
eb575685aa | ||
|
|
7a95451288 | ||
|
|
538ec7c0a0 | ||
|
|
ea800dd838 | ||
|
|
b8c378f2f0 | ||
|
|
e86bedddb2 | ||
|
|
1e1a6df480 | ||
|
|
7aa1ab855e | ||
|
|
776f15055a | ||
|
|
b291b18a87 | ||
|
|
e6492f7db9 | ||
|
|
3b737999d6 | ||
|
|
00fa7f5e86 | ||
|
|
fde5ba85a0 | ||
|
|
a96e4a343c | ||
|
|
36110d0977 | ||
|
|
1f330d7753 | ||
|
|
cf877e6567 | ||
|
|
c3fa68ade8 | ||
|
|
5422d13607 | ||
|
|
d8f614ed98 | ||
|
|
4ca125da41 | ||
|
|
3c2bfbfc1c | ||
|
|
03e336ba9e | ||
|
|
76ea0f432a | ||
|
|
7eb45e1799 | ||
|
|
4af1dcd54f | ||
|
|
463c519954 | ||
|
|
1d0a3660bd | ||
|
|
b83367063e | ||
|
|
aa724fc11e | ||
|
|
cedb11ebab | ||
|
|
ffe389af4a | ||
|
|
b313637848 | ||
|
|
2eeae21777 | ||
|
|
2af9dfebea | ||
|
|
dade6fc191 |
@@ -1,7 +1,7 @@
|
||||
[run]
|
||||
init_cmds = [
|
||||
["go", "build", "-o", "./bin/grafana"],
|
||||
["./bin/grafana", "web"]
|
||||
["go", "build", "-o", "./bin/grafana-server"],
|
||||
["./bin/grafana-server"]
|
||||
]
|
||||
watch_all = true
|
||||
watch_dirs = [
|
||||
@@ -12,6 +12,6 @@ watch_dirs = [
|
||||
watch_exts = [".go", ".ini"]
|
||||
build_delay = 1500
|
||||
cmds = [
|
||||
["go", "build", "-o", "./bin/grafana"],
|
||||
["./bin/grafana", "web"]
|
||||
["go", "build", "-o", "./bin/grafana-server"],
|
||||
["./bin/grafana-server"]
|
||||
]
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -13,7 +13,7 @@ docs/changed-files
|
||||
docs/changed-files
|
||||
|
||||
# locally required config files
|
||||
src/css/*.min.css
|
||||
public/css/*.min.css
|
||||
|
||||
# Editor junk
|
||||
*.sublime-workspace
|
||||
|
||||
47
CHANGELOG.md
47
CHANGELOG.md
@@ -1,3 +1,50 @@
|
||||
# 2.0.0 (2015-04-20)
|
||||
|
||||
**Fixes**
|
||||
- [Issue #1784](https://github.com/grafana/grafana/issues/1784). Data source proxy: Fixed issue with using data source proxy when grafana is behind nginx suburl
|
||||
- [Issue #1749](https://github.com/grafana/grafana/issues/1749). Graph Panel: Table legends are now visible when rendered to PNG
|
||||
- [Issue #1786](https://github.com/grafana/grafana/issues/1786). Graph Panel: Legend in table mode now aligns, graph area is reduced depending on how many series
|
||||
- [Issue #1734](https://github.com/grafana/grafana/issues/1734). Support for unicode / international characters in dashboard title (improved slugify)
|
||||
- [Issue #1782](https://github.com/grafana/grafana/issues/1782). Github OAuth: Now works with Github for Enterprise, thanks @williamjoy
|
||||
- [Issue #1780](https://github.com/grafana/grafana/issues/1780). Dashboard snapshot: Should not require login to view snapshot, Fixes #1780
|
||||
|
||||
# 2.0.0-Beta3 (2015-04-12)
|
||||
|
||||
**RPM / DEB Package changes (to follow HFS)**
|
||||
- binary name changed to grafana-server
|
||||
- does not install to `/opt/grafana` any more, installs to `/usr/share/grafana`
|
||||
- binary to `/usr/sbin/grafana-server`
|
||||
- init.d script improvements, renamed to `/etc/init.d/grafana-server`
|
||||
- added default file with environment variables,
|
||||
- `/etc/default/grafana-server` (deb/ubuntu)
|
||||
- `/etc/sysconfig/grafana-server` (centos/redhat)
|
||||
|
||||
- added systemd service file, tested on debian jessie and centos7
|
||||
- config file in same location `/etc/grafana/grafana.ini` (now complete config file but with every setting commented out)
|
||||
- data directory (where sqlite3) file is stored is now by default `/var/lib/grafana`
|
||||
- no symlinking current to versions anymore
|
||||
- For more info see [Issue #1758](https://github.com/grafana/grafana/issues/1758).
|
||||
|
||||
**Config breaking change (setting rename)**
|
||||
- `[log] root_path` has changed to `[paths] logs`
|
||||
|
||||
# 2.0.0-Beta2 (...)
|
||||
|
||||
**Enhancements**
|
||||
- [Issue #1701](https://github.com/grafana/grafana/issues/1701). Share modal: Override UI theme via URL param for Share link, rendered panel, or embedded panel
|
||||
- [Issue #1660](https://github.com/grafana/grafana/issues/1660). OAuth: Specify allowed email address domains for google or and github oauth logins
|
||||
|
||||
**Fixes**
|
||||
- [Issue #1649](https://github.com/grafana/grafana/issues/1649). HTTP API: grafana /render calls nows with api keys
|
||||
- [Issue #1667](https://github.com/grafana/grafana/issues/1667). Datasource proxy & session timeout fix (casued 401 Unauthorized error after a while)
|
||||
- [Issue #1707](https://github.com/grafana/grafana/issues/1707). Unsaved changes: Do not show for snapshots, scripted and file based dashboards
|
||||
- [Issue #1703](https://github.com/grafana/grafana/issues/1703). Unsaved changes: Do not show for users with role `Viewer`
|
||||
- [Issue #1675](https://github.com/grafana/grafana/issues/1675). Data source proxy: Fixed issue with Gzip enabled and data source proxy
|
||||
- [Issue #1681](https://github.com/grafana/grafana/issues/1681). MySQL session: fixed problem using mysql as session store
|
||||
- [Issue #1671](https://github.com/grafana/grafana/issues/1671). Data sources: Fixed issue with changing default data source (should not require full page load to take effect, now fixed)
|
||||
- [Issue #1685](https://github.com/grafana/grafana/issues/1685). Search: Dashboard results should be sorted alphabetically
|
||||
- [Issue #1673](https://github.com/grafana/grafana/issues/1673). Basic auth: Fixed issue when using basic auth proxy infront of Grafana
|
||||
|
||||
# 2.0.0-Beta1 (2015-03-30)
|
||||
|
||||
**New features**
|
||||
|
||||
35
Godeps/Godeps.json
generated
35
Godeps/Godeps.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"ImportPath": "github.com/torkelo/grafana-pro",
|
||||
"GoVersion": "go1.3",
|
||||
"ImportPath": "github.com/grafana/grafana",
|
||||
"GoVersion": "go1.4.2",
|
||||
"Packages": [
|
||||
"./pkg/..."
|
||||
],
|
||||
@@ -13,11 +13,6 @@
|
||||
"ImportPath": "github.com/Unknwon/macaron",
|
||||
"Rev": "93de4f3fad97bf246b838f828e2348f46f21f20a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/codegangsta/cli",
|
||||
"Comment": "1.2.0-38-g9908e96",
|
||||
"Rev": "9908e96513e5a94de37004098a3974a567f18111"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/go-sql-driver/mysql",
|
||||
"Comment": "v1.2-26-g9543750",
|
||||
@@ -32,6 +27,10 @@
|
||||
"Comment": "v0.4.2-58-ge2889e5",
|
||||
"Rev": "e2889e5517600b82905f1d2ba8b70deb71823ffe"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gosimple/slug",
|
||||
"Rev": "a2392a4a87fa0366cbff131d3fd421f83f52492f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/jtolds/gls",
|
||||
"Rev": "f1ac7f4f24f50328e6bc838ca4437d1612a0243c"
|
||||
@@ -47,7 +46,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/macaron-contrib/session",
|
||||
"Rev": "65b8817c40cb5bdce08673a15fd2a648c2ba0e16"
|
||||
"Rev": "31e841d95c7302b9ac456c830ea2d6dfcef4f84a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mattn/go-sqlite3",
|
||||
@@ -68,12 +67,30 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/oauth2",
|
||||
"Rev": "e5909d4679a1926c774c712b343f10b8298687a3"
|
||||
"Rev": "c58fcf0ffc1c772aa2e1ee4894bc19f2649263b2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/bufio.v1",
|
||||
"Comment": "v1",
|
||||
"Rev": "567b2bfa514e796916c4747494d6ff5132a1dfce"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/ini.v1",
|
||||
"Comment": "v0-16-g1772191",
|
||||
"Rev": "177219109c97e7920c933e21c9b25f874357b237"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/redis.v2",
|
||||
"Comment": "v2.3.2",
|
||||
"Rev": "e6179049628164864e6e84e973cfb56335748dea"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkgs.com/pool.v1",
|
||||
"Rev": "c850f092aad1780cbffff25f471c5cc32097932a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkgs.com/unidecode.v1",
|
||||
"Rev": "4deae2c05236b41cc39f8144ac87a837ba974d40"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
6
Godeps/_workspace/src/github.com/codegangsta/cli/.travis.yml
generated
vendored
6
Godeps/_workspace/src/github.com/codegangsta/cli/.travis.yml
generated
vendored
@@ -1,6 +0,0 @@
|
||||
language: go
|
||||
go: 1.1
|
||||
|
||||
script:
|
||||
- go vet ./...
|
||||
- go test -v ./...
|
||||
21
Godeps/_workspace/src/github.com/codegangsta/cli/LICENSE
generated
vendored
21
Godeps/_workspace/src/github.com/codegangsta/cli/LICENSE
generated
vendored
@@ -1,21 +0,0 @@
|
||||
Copyright (C) 2013 Jeremy Saenz
|
||||
All Rights Reserved.
|
||||
|
||||
MIT LICENSE
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
285
Godeps/_workspace/src/github.com/codegangsta/cli/README.md
generated
vendored
285
Godeps/_workspace/src/github.com/codegangsta/cli/README.md
generated
vendored
@@ -1,285 +0,0 @@
|
||||
[](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)
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
#### 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",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### 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",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
### Subcommands
|
||||
|
||||
Subcommands can be defined for a more git-like command line app.
|
||||
```go
|
||||
...
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "add",
|
||||
ShortName: "a",
|
||||
Usage: "add a task to the list",
|
||||
Action: func(c *cli.Context) {
|
||||
println("added task: ", c.Args().First())
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "complete",
|
||||
ShortName: "c",
|
||||
Usage: "complete a task on the list",
|
||||
Action: func(c *cli.Context) {
|
||||
println("completed task: ", c.Args().First())
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "template",
|
||||
ShortName: "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",
|
||||
ShortName: "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`
|
||||
|
||||
|
||||
## 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 are 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.
|
||||
251
Godeps/_workspace/src/github.com/codegangsta/cli/app.go
generated
vendored
251
Godeps/_workspace/src/github.com/codegangsta/cli/app.go
generated
vendored
@@ -1,251 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// App is the main structure of a cli application. It is recomended that
|
||||
// and 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
|
||||
// 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
|
||||
// Author
|
||||
Author string
|
||||
// Author e-mail
|
||||
Email string
|
||||
}
|
||||
|
||||
// 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(),
|
||||
}
|
||||
}
|
||||
|
||||
// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
|
||||
func (a *App) Run(arguments []string) error {
|
||||
// append help to commands
|
||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
||||
a.Commands = append(a.Commands, helpCommand)
|
||||
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.Println(nerr)
|
||||
context := NewContext(a, set, set)
|
||||
ShowAppHelp(context)
|
||||
fmt.Println("")
|
||||
return nerr
|
||||
}
|
||||
context := NewContext(a, set, set)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Incorrect Usage.\n\n")
|
||||
ShowAppHelp(context)
|
||||
fmt.Println("")
|
||||
return err
|
||||
}
|
||||
|
||||
if checkCompletions(context) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if checkHelp(context) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if checkVersion(context) {
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
os.Stderr.WriteString(fmt.Sprintln(err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags
|
||||
func (a *App) RunAsSubcommand(ctx *Context) error {
|
||||
// append help to commands
|
||||
if len(a.Commands) > 0 {
|
||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
||||
a.Commands = append(a.Commands, helpCommand)
|
||||
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.globalSet)
|
||||
|
||||
if nerr != nil {
|
||||
fmt.Println(nerr)
|
||||
if len(a.Commands) > 0 {
|
||||
ShowSubcommandHelp(context)
|
||||
} else {
|
||||
ShowCommandHelp(ctx, context.Args().First())
|
||||
}
|
||||
fmt.Println("")
|
||||
return nerr
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Incorrect Usage.\n\n")
|
||||
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.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
|
||||
if len(a.Commands) > 0 {
|
||||
a.Action(context)
|
||||
} else {
|
||||
a.Action(ctx)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
423
Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go
generated
vendored
423
Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go
generated
vendored
@@ -1,423 +0,0 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
func ExampleApp() {
|
||||
// set args for examples sake
|
||||
os.Args = []string{"greet", "--name", "Jeremy"}
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Name = "greet"
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
|
||||
}
|
||||
app.Action = func(c *cli.Context) {
|
||||
fmt.Printf("Hello %v\n", c.String("name"))
|
||||
}
|
||||
app.Run(os.Args)
|
||||
// Output:
|
||||
// Hello Jeremy
|
||||
}
|
||||
|
||||
func ExampleAppSubcommand() {
|
||||
// set args for examples sake
|
||||
os.Args = []string{"say", "hi", "english", "--name", "Jeremy"}
|
||||
app := cli.NewApp()
|
||||
app.Name = "say"
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "hello",
|
||||
ShortName: "hi",
|
||||
Usage: "use it to see a description",
|
||||
Description: "This is how we describe hello the function",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "english",
|
||||
ShortName: "en",
|
||||
Usage: "sends a greeting in english",
|
||||
Description: "greets someone in english",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "name",
|
||||
Value: "Bob",
|
||||
Usage: "Name of the person to greet",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.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 := cli.NewApp()
|
||||
app.Name = "greet"
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
|
||||
}
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "describeit",
|
||||
ShortName: "d",
|
||||
Usage: "use it to see a description",
|
||||
Description: "This is how we describe describeit the function",
|
||||
Action: func(c *cli.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 := cli.NewApp()
|
||||
app.Name = "greet"
|
||||
app.EnableBashCompletion = true
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "describeit",
|
||||
ShortName: "d",
|
||||
Usage: "use it to see a description",
|
||||
Description: "This is how we describe describeit the function",
|
||||
Action: func(c *cli.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 *cli.Context) {
|
||||
fmt.Printf("the next example")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.Run(os.Args)
|
||||
// Output:
|
||||
// describeit
|
||||
// d
|
||||
// next
|
||||
// help
|
||||
// h
|
||||
}
|
||||
|
||||
func TestApp_Run(t *testing.T) {
|
||||
s := ""
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Action = func(c *cli.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 := cli.NewApp()
|
||||
fooCommand := cli.Command{Name: "foobar", ShortName: "f"}
|
||||
batCommand := cli.Command{Name: "batbaz", ShortName: "b"}
|
||||
app.Commands = []cli.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 := cli.NewApp()
|
||||
command := cli.Command{
|
||||
Name: "cmd",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "option", Value: "", Usage: "some option"},
|
||||
},
|
||||
Action: func(c *cli.Context) {
|
||||
parsedOption = c.String("option")
|
||||
firstArg = c.Args().First()
|
||||
},
|
||||
}
|
||||
app.Commands = []cli.Command{command}
|
||||
|
||||
app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"})
|
||||
|
||||
expect(t, parsedOption, "my-option")
|
||||
expect(t, firstArg, "my-arg")
|
||||
}
|
||||
|
||||
func TestApp_Float64Flag(t *testing.T) {
|
||||
var meters float64
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Flags = []cli.Flag{
|
||||
cli.Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"},
|
||||
}
|
||||
app.Action = func(c *cli.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 := cli.NewApp()
|
||||
command := cli.Command{
|
||||
Name: "cmd",
|
||||
Flags: []cli.Flag{
|
||||
cli.IntSliceFlag{Name: "p", Value: &cli.IntSlice{}, Usage: "set one or more ip addr"},
|
||||
cli.StringSliceFlag{Name: "ip", Value: &cli.StringSlice{}, Usage: "set one or more ports to open"},
|
||||
},
|
||||
Action: func(c *cli.Context) {
|
||||
parsedIntSlice = c.IntSlice("p")
|
||||
parsedStringSlice = c.StringSlice("ip")
|
||||
parsedOption = c.String("option")
|
||||
firstArg = c.Args().First()
|
||||
},
|
||||
}
|
||||
app.Commands = []cli.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_BeforeFunc(t *testing.T) {
|
||||
beforeRun, subcommandRun := false, false
|
||||
beforeError := fmt.Errorf("fail")
|
||||
var err error
|
||||
|
||||
app := cli.NewApp()
|
||||
|
||||
app.Before = func(c *cli.Context) error {
|
||||
beforeRun = true
|
||||
s := c.String("opt")
|
||||
if s == "fail" {
|
||||
return beforeError
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
app.Commands = []cli.Command{
|
||||
cli.Command{
|
||||
Name: "sub",
|
||||
Action: func(c *cli.Context) {
|
||||
subcommandRun = true
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
cli.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 TestAppHelpPrinter(t *testing.T) {
|
||||
oldPrinter := cli.HelpPrinter
|
||||
defer func() {
|
||||
cli.HelpPrinter = oldPrinter
|
||||
}()
|
||||
|
||||
var wasCalled = false
|
||||
cli.HelpPrinter = func(template string, data interface{}) {
|
||||
wasCalled = true
|
||||
}
|
||||
|
||||
app := cli.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 := cli.VersionPrinter
|
||||
defer func() {
|
||||
cli.VersionPrinter = oldPrinter
|
||||
}()
|
||||
|
||||
var wasCalled = false
|
||||
cli.VersionPrinter = func(c *cli.Context) {
|
||||
wasCalled = true
|
||||
}
|
||||
|
||||
app := cli.NewApp()
|
||||
ctx := cli.NewContext(app, nil, nil)
|
||||
cli.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 := cli.NewApp()
|
||||
|
||||
app.CommandNotFound = func(c *cli.Context, command string) {
|
||||
beforeRun = true
|
||||
}
|
||||
|
||||
app.Commands = []cli.Command{
|
||||
cli.Command{
|
||||
Name: "bar",
|
||||
Action: func(c *cli.Context) {
|
||||
subcommandRun = true
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.Run([]string{"command", "foo"})
|
||||
|
||||
expect(t, beforeRun, true)
|
||||
expect(t, subcommandRun, false)
|
||||
}
|
||||
|
||||
func TestGlobalFlagsInSubcommands(t *testing.T) {
|
||||
subcommandRun := false
|
||||
app := cli.NewApp()
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
cli.BoolFlag{Name: "debug, d", Usage: "Enable debugging"},
|
||||
}
|
||||
|
||||
app.Commands = []cli.Command{
|
||||
cli.Command{
|
||||
Name: "foo",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "bar",
|
||||
Action: func(c *cli.Context) {
|
||||
if c.GlobalBool("debug") {
|
||||
subcommandRun = true
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.Run([]string{"command", "-d", "foo", "bar"})
|
||||
|
||||
expect(t, subcommandRun, true)
|
||||
}
|
||||
13
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete
generated
vendored
13
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete
generated
vendored
@@ -1,13 +0,0 @@
|
||||
#! /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
5
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/zsh_autocomplete
generated
vendored
@@ -1,5 +0,0 @@
|
||||
autoload -U compinit && compinit
|
||||
autoload -U bashcompinit && bashcompinit
|
||||
|
||||
script_dir=$(dirname $0)
|
||||
source ${script_dir}/bash_autocomplete
|
||||
19
Godeps/_workspace/src/github.com/codegangsta/cli/cli.go
generated
vendored
19
Godeps/_workspace/src/github.com/codegangsta/cli/cli.go
generated
vendored
@@ -1,19 +0,0 @@
|
||||
// Package cli provides a minimal framework for creating and organizing command line
|
||||
// Go applications. cli is designed to be easy to understand and write, the most simple
|
||||
// cli application can be written as follows:
|
||||
// func main() {
|
||||
// cli.NewApp().Run(os.Args)
|
||||
// }
|
||||
//
|
||||
// Of course this application does not do much, so let's make this an actual application:
|
||||
// func main() {
|
||||
// app := cli.NewApp()
|
||||
// app.Name = "greet"
|
||||
// app.Usage = "say a greeting"
|
||||
// app.Action = func(c *cli.Context) {
|
||||
// println("Greetings")
|
||||
// }
|
||||
//
|
||||
// app.Run(os.Args)
|
||||
// }
|
||||
package cli
|
||||
100
Godeps/_workspace/src/github.com/codegangsta/cli/cli_test.go
generated
vendored
100
Godeps/_workspace/src/github.com/codegangsta/cli/cli_test.go
generated
vendored
@@ -1,100 +0,0 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "todo"
|
||||
app.Usage = "task list on the command line"
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "add",
|
||||
ShortName: "a",
|
||||
Usage: "add a task to the list",
|
||||
Action: func(c *cli.Context) {
|
||||
println("added task: ", c.Args().First())
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "complete",
|
||||
ShortName: "c",
|
||||
Usage: "complete a task on the list",
|
||||
Action: func(c *cli.Context) {
|
||||
println("completed task: ", c.Args().First())
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.Run(os.Args)
|
||||
}
|
||||
|
||||
func ExampleSubcommand() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "say"
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "hello",
|
||||
ShortName: "hi",
|
||||
Usage: "use it to see a description",
|
||||
Description: "This is how we describe hello the function",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "english",
|
||||
ShortName: "en",
|
||||
Usage: "sends a greeting in english",
|
||||
Description: "greets someone in english",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "name",
|
||||
Value: "Bob",
|
||||
Usage: "Name of the person to greet",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) {
|
||||
println("Hello, ", c.String("name"))
|
||||
},
|
||||
}, {
|
||||
Name: "spanish",
|
||||
ShortName: "sp",
|
||||
Usage: "sends a greeting in spanish",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "surname",
|
||||
Value: "Jones",
|
||||
Usage: "Surname of the person to greet",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) {
|
||||
println("Hola, ", c.String("surname"))
|
||||
},
|
||||
}, {
|
||||
Name: "french",
|
||||
ShortName: "fr",
|
||||
Usage: "sends a greeting in french",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "nickname",
|
||||
Value: "Stevie",
|
||||
Usage: "Nickname of the person to greet",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) {
|
||||
println("Bonjour, ", c.String("nickname"))
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Name: "bye",
|
||||
Usage: "says goodbye",
|
||||
Action: func(c *cli.Context) {
|
||||
println("bye")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.Run(os.Args)
|
||||
}
|
||||
144
Godeps/_workspace/src/github.com/codegangsta/cli/command.go
generated
vendored
144
Godeps/_workspace/src/github.com/codegangsta/cli/command.go
generated
vendored
@@ -1,144 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Command is a subcommand for a cli.App.
|
||||
type Command struct {
|
||||
// The name of the command
|
||||
Name string
|
||||
// short name of the command. Typically one character
|
||||
ShortName 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
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return c.startApp(ctx)
|
||||
}
|
||||
|
||||
if !c.HideHelp {
|
||||
// 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
|
||||
for index, arg := range ctx.Args() {
|
||||
if strings.HasPrefix(arg, "-") {
|
||||
firstFlagIndex = index
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
if firstFlagIndex > -1 && !c.SkipFlagParsing {
|
||||
args := ctx.Args()
|
||||
regularArgs := args[1:firstFlagIndex]
|
||||
flagArgs := args[firstFlagIndex:]
|
||||
err = set.Parse(append(flagArgs, regularArgs...))
|
||||
} else {
|
||||
err = set.Parse(ctx.Args().Tail())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Incorrect Usage.\n\n")
|
||||
ShowCommandHelp(ctx, c.Name)
|
||||
fmt.Println("")
|
||||
return err
|
||||
}
|
||||
|
||||
nerr := normalizeFlags(c.Flags, set)
|
||||
if nerr != nil {
|
||||
fmt.Println(nerr)
|
||||
fmt.Println("")
|
||||
ShowCommandHelp(ctx, c.Name)
|
||||
fmt.Println("")
|
||||
return nerr
|
||||
}
|
||||
context := NewContext(ctx.App, set, ctx.globalSet)
|
||||
|
||||
if checkCommandCompletions(context, c.Name) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if checkCommandHelp(context, c.Name) {
|
||||
return nil
|
||||
}
|
||||
context.Command = c
|
||||
c.Action(context)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns true if Command.Name or Command.ShortName matches given name
|
||||
func (c Command) HasName(name string) bool {
|
||||
return c.Name == name || c.ShortName == name
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// bash completion
|
||||
app.EnableBashCompletion = ctx.App.EnableBashCompletion
|
||||
if c.BashComplete != nil {
|
||||
app.BashComplete = c.BashComplete
|
||||
}
|
||||
|
||||
// set the actions
|
||||
app.Before = c.Before
|
||||
if c.Action != nil {
|
||||
app.Action = c.Action
|
||||
} else {
|
||||
app.Action = helpSubcommand.Action
|
||||
}
|
||||
|
||||
return app.RunAsSubcommand(ctx)
|
||||
}
|
||||
49
Godeps/_workspace/src/github.com/codegangsta/cli/command_test.go
generated
vendored
49
Godeps/_workspace/src/github.com/codegangsta/cli/command_test.go
generated
vendored
@@ -1,49 +0,0 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
func TestCommandDoNotIgnoreFlags(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
test := []string{"blah", "blah", "-break"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, set)
|
||||
|
||||
command := cli.Command{
|
||||
Name: "test-cmd",
|
||||
ShortName: "tc",
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(_ *cli.Context) {},
|
||||
}
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err.Error(), "flag provided but not defined: -break")
|
||||
}
|
||||
|
||||
func TestCommandIgnoreFlags(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
test := []string{"blah", "blah"}
|
||||
set.Parse(test)
|
||||
|
||||
c := cli.NewContext(app, set, set)
|
||||
|
||||
command := cli.Command{
|
||||
Name: "test-cmd",
|
||||
ShortName: "tc",
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(_ *cli.Context) {},
|
||||
SkipFlagParsing: true,
|
||||
}
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
339
Godeps/_workspace/src/github.com/codegangsta/cli/context.go
generated
vendored
339
Godeps/_workspace/src/github.com/codegangsta/cli/context.go
generated
vendored
@@ -1,339 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Context is a type that is passed through to
|
||||
// each Handler action in a cli application. Context
|
||||
// can be used to retrieve context-specific Args and
|
||||
// parsed command-line options.
|
||||
type Context struct {
|
||||
App *App
|
||||
Command Command
|
||||
flagSet *flag.FlagSet
|
||||
globalSet *flag.FlagSet
|
||||
setFlags map[string]bool
|
||||
globalSetFlags map[string]bool
|
||||
}
|
||||
|
||||
// Creates a new context. For use in when invoking an App or Command action.
|
||||
func NewContext(app *App, set *flag.FlagSet, globalSet *flag.FlagSet) *Context {
|
||||
return &Context{App: app, flagSet: set, globalSet: globalSet}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return lookupInt(name, c.globalSet)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return lookupDuration(name, c.globalSet)
|
||||
}
|
||||
|
||||
// Looks up the value of a global bool flag, returns false if no bool flag exists
|
||||
func (c *Context) GlobalBool(name string) bool {
|
||||
return lookupBool(name, c.globalSet)
|
||||
}
|
||||
|
||||
// Looks up the value of a global string flag, returns "" if no string flag exists
|
||||
func (c *Context) GlobalString(name string) string {
|
||||
return lookupString(name, c.globalSet)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return lookupStringSlice(name, c.globalSet)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return lookupIntSlice(name, c.globalSet)
|
||||
}
|
||||
|
||||
// Looks up the value of a global generic flag, returns nil if no generic flag exists
|
||||
func (c *Context) GlobalGeneric(name string) interface{} {
|
||||
return lookupGeneric(name, c.globalSet)
|
||||
}
|
||||
|
||||
// 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)
|
||||
c.globalSet.Visit(func(f *flag.Flag) {
|
||||
c.globalSetFlags[f.Name] = true
|
||||
})
|
||||
}
|
||||
return c.globalSetFlags[name] == true
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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 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
|
||||
}
|
||||
99
Godeps/_workspace/src/github.com/codegangsta/cli/context_test.go
generated
vendored
99
Godeps/_workspace/src/github.com/codegangsta/cli/context_test.go
generated
vendored
@@ -1,99 +0,0 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
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")
|
||||
command := cli.Command{Name: "mycommand"}
|
||||
c := cli.NewContext(nil, set, globalSet)
|
||||
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 := cli.NewContext(nil, set, set)
|
||||
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 := cli.NewContext(nil, set, set)
|
||||
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 := cli.NewContext(nil, set, set)
|
||||
expect(t, c.String("myflag"), "hello world")
|
||||
}
|
||||
|
||||
func TestContext_Bool(t *testing.T) {
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.Bool("myflag", false, "doc")
|
||||
c := cli.NewContext(nil, set, set)
|
||||
expect(t, c.Bool("myflag"), false)
|
||||
}
|
||||
|
||||
func TestContext_BoolT(t *testing.T) {
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.Bool("myflag", true, "doc")
|
||||
c := cli.NewContext(nil, set, set)
|
||||
expect(t, c.BoolT("myflag"), true)
|
||||
}
|
||||
|
||||
func TestContext_Args(t *testing.T) {
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.Bool("myflag", false, "doc")
|
||||
c := cli.NewContext(nil, set, set)
|
||||
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")
|
||||
c := cli.NewContext(nil, set, globalSet)
|
||||
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")
|
||||
c := cli.NewContext(nil, set, globalSet)
|
||||
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)
|
||||
}
|
||||
410
Godeps/_workspace/src/github.com/codegangsta/cli/flag.go
generated
vendored
410
Godeps/_workspace/src/github.com/codegangsta/cli/flag.go
generated
vendored
@@ -1,410 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// This flag enables bash-completion for all commands and subcommands
|
||||
var BashCompletionFlag = BoolFlag{
|
||||
Name: "generate-bash-completion",
|
||||
}
|
||||
|
||||
// This flag prints the version for the application
|
||||
var VersionFlag = BoolFlag{
|
||||
Name: "version, v",
|
||||
Usage: "print the version",
|
||||
}
|
||||
|
||||
// This flag prints the help for all commands and subcommands
|
||||
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
|
||||
}
|
||||
|
||||
func (f GenericFlag) String() string {
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s %v\t`%v` %s", prefixFor(f.Name), f.Name, f.Value, "-"+f.Name+" option -"+f.Name+" option", f.Usage))
|
||||
}
|
||||
|
||||
func (f GenericFlag) Apply(set *flag.FlagSet) {
|
||||
val := f.Value
|
||||
if f.EnvVar != "" {
|
||||
if envVal := os.Getenv(f.EnvVar); envVal != "" {
|
||||
val.Set(envVal)
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f GenericFlag) getName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
type StringSlice []string
|
||||
|
||||
func (f *StringSlice) Set(value string) error {
|
||||
*f = append(*f, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *StringSlice) String() string {
|
||||
return fmt.Sprintf("%s", *f)
|
||||
}
|
||||
|
||||
func (f *StringSlice) Value() []string {
|
||||
return *f
|
||||
}
|
||||
|
||||
type StringSliceFlag struct {
|
||||
Name string
|
||||
Value *StringSlice
|
||||
Usage string
|
||||
EnvVar string
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
func (f StringSliceFlag) Apply(set *flag.FlagSet) {
|
||||
if f.EnvVar != "" {
|
||||
if envVal := os.Getenv(f.EnvVar); envVal != "" {
|
||||
newVal := &StringSlice{}
|
||||
for _, s := range strings.Split(envVal, ",") {
|
||||
newVal.Set(s)
|
||||
}
|
||||
f.Value = newVal
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f StringSliceFlag) getName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
type IntSlice []int
|
||||
|
||||
func (f *IntSlice) Set(value string) error {
|
||||
|
||||
tmp, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
*f = append(*f, tmp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *IntSlice) String() string {
|
||||
return fmt.Sprintf("%d", *f)
|
||||
}
|
||||
|
||||
func (f *IntSlice) Value() []int {
|
||||
return *f
|
||||
}
|
||||
|
||||
type IntSliceFlag struct {
|
||||
Name string
|
||||
Value *IntSlice
|
||||
Usage string
|
||||
EnvVar string
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
func (f IntSliceFlag) Apply(set *flag.FlagSet) {
|
||||
if f.EnvVar != "" {
|
||||
if envVal := os.Getenv(f.EnvVar); envVal != "" {
|
||||
newVal := &IntSlice{}
|
||||
for _, s := range strings.Split(envVal, ",") {
|
||||
err := newVal.Set(s)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
}
|
||||
}
|
||||
f.Value = newVal
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f IntSliceFlag) getName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
type BoolFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
}
|
||||
|
||||
func (f BoolFlag) String() string {
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
|
||||
}
|
||||
|
||||
func (f BoolFlag) Apply(set *flag.FlagSet) {
|
||||
val := false
|
||||
if f.EnvVar != "" {
|
||||
if envVal := os.Getenv(f.EnvVar); envVal != "" {
|
||||
envValBool, err := strconv.ParseBool(envVal)
|
||||
if err == nil {
|
||||
val = envValBool
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
set.Bool(name, val, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f BoolFlag) getName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
type BoolTFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
}
|
||||
|
||||
func (f BoolTFlag) String() string {
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
|
||||
}
|
||||
|
||||
func (f BoolTFlag) Apply(set *flag.FlagSet) {
|
||||
val := true
|
||||
if f.EnvVar != "" {
|
||||
if envVal := os.Getenv(f.EnvVar); envVal != "" {
|
||||
envValBool, err := strconv.ParseBool(envVal)
|
||||
if err == nil {
|
||||
val = envValBool
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
set.Bool(name, val, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f BoolTFlag) getName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
type StringFlag struct {
|
||||
Name string
|
||||
Value string
|
||||
Usage string
|
||||
EnvVar string
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
func (f StringFlag) Apply(set *flag.FlagSet) {
|
||||
if f.EnvVar != "" {
|
||||
if envVal := os.Getenv(f.EnvVar); envVal != "" {
|
||||
f.Value = envVal
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
set.String(name, f.Value, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f StringFlag) getName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
type IntFlag struct {
|
||||
Name string
|
||||
Value int
|
||||
Usage string
|
||||
EnvVar string
|
||||
}
|
||||
|
||||
func (f IntFlag) String() string {
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage))
|
||||
}
|
||||
|
||||
func (f IntFlag) Apply(set *flag.FlagSet) {
|
||||
if f.EnvVar != "" {
|
||||
if envVal := os.Getenv(f.EnvVar); envVal != "" {
|
||||
envValInt, err := strconv.ParseUint(envVal, 10, 64)
|
||||
if err == nil {
|
||||
f.Value = int(envValInt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
set.Int(name, f.Value, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f IntFlag) getName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
type DurationFlag struct {
|
||||
Name string
|
||||
Value time.Duration
|
||||
Usage string
|
||||
EnvVar string
|
||||
}
|
||||
|
||||
func (f DurationFlag) String() string {
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage))
|
||||
}
|
||||
|
||||
func (f DurationFlag) Apply(set *flag.FlagSet) {
|
||||
if f.EnvVar != "" {
|
||||
if envVal := os.Getenv(f.EnvVar); envVal != "" {
|
||||
envValDuration, err := time.ParseDuration(envVal)
|
||||
if err == nil {
|
||||
f.Value = envValDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
set.Duration(name, f.Value, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f DurationFlag) getName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
type Float64Flag struct {
|
||||
Name string
|
||||
Value float64
|
||||
Usage string
|
||||
EnvVar string
|
||||
}
|
||||
|
||||
func (f Float64Flag) String() string {
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage))
|
||||
}
|
||||
|
||||
func (f Float64Flag) Apply(set *flag.FlagSet) {
|
||||
if f.EnvVar != "" {
|
||||
if envVal := os.Getenv(f.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]", envVar)
|
||||
}
|
||||
return str + envText
|
||||
}
|
||||
587
Godeps/_workspace/src/github.com/codegangsta/cli/flag_test.go
generated
vendored
587
Godeps/_workspace/src/github.com/codegangsta/cli/flag_test.go
generated
vendored
@@ -1,587 +0,0 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
var boolFlagTests = []struct {
|
||||
name string
|
||||
expected string
|
||||
}{
|
||||
{"help", "--help\t"},
|
||||
{"h", "-h\t"},
|
||||
}
|
||||
|
||||
func TestBoolFlagHelpOutput(t *testing.T) {
|
||||
|
||||
for _, test := range boolFlagTests {
|
||||
flag := cli.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 := cli.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.Setenv("APP_FOO", "derp")
|
||||
for _, test := range stringFlagTests {
|
||||
flag := cli.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 *cli.StringSlice
|
||||
expected string
|
||||
}{
|
||||
{"help", func() *cli.StringSlice {
|
||||
s := &cli.StringSlice{}
|
||||
s.Set("")
|
||||
return s
|
||||
}(), "--help '--help option --help option'\t"},
|
||||
{"h", func() *cli.StringSlice {
|
||||
s := &cli.StringSlice{}
|
||||
s.Set("")
|
||||
return s
|
||||
}(), "-h '-h option -h option'\t"},
|
||||
{"h", func() *cli.StringSlice {
|
||||
s := &cli.StringSlice{}
|
||||
s.Set("")
|
||||
return s
|
||||
}(), "-h '-h option -h option'\t"},
|
||||
{"test", func() *cli.StringSlice {
|
||||
s := &cli.StringSlice{}
|
||||
s.Set("Something")
|
||||
return s
|
||||
}(), "--test '--test option --test option'\t"},
|
||||
}
|
||||
|
||||
func TestStringSliceFlagHelpOutput(t *testing.T) {
|
||||
|
||||
for _, test := range stringSliceFlagTests {
|
||||
flag := cli.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.Setenv("APP_QWWX", "11,4")
|
||||
for _, test := range stringSliceFlagTests {
|
||||
flag := cli.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 := cli.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.Setenv("APP_BAR", "2")
|
||||
for _, test := range intFlagTests {
|
||||
flag := cli.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 := cli.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.Setenv("APP_BAR", "2h3m6s")
|
||||
for _, test := range durationFlagTests {
|
||||
flag := cli.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 *cli.IntSlice
|
||||
expected string
|
||||
}{
|
||||
{"help", &cli.IntSlice{}, "--help '--help option --help option'\t"},
|
||||
{"h", &cli.IntSlice{}, "-h '-h option -h option'\t"},
|
||||
{"h", &cli.IntSlice{}, "-h '-h option -h option'\t"},
|
||||
{"test", func() *cli.IntSlice {
|
||||
i := &cli.IntSlice{}
|
||||
i.Set("9")
|
||||
return i
|
||||
}(), "--test '--test option --test option'\t"},
|
||||
}
|
||||
|
||||
func TestIntSliceFlagHelpOutput(t *testing.T) {
|
||||
|
||||
for _, test := range intSliceFlagTests {
|
||||
flag := cli.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.Setenv("APP_SMURF", "42,3")
|
||||
for _, test := range intSliceFlagTests {
|
||||
flag := cli.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 := cli.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.Setenv("APP_BAZ", "99.4")
|
||||
for _, test := range float64FlagTests {
|
||||
flag := cli.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 cli.Generic
|
||||
expected string
|
||||
}{
|
||||
{"help", &Parser{}, "--help <nil>\t`-help option -help option` "},
|
||||
{"h", &Parser{}, "-h <nil>\t`-h option -h option` "},
|
||||
{"test", &Parser{}, "--test <nil>\t`-test option -test option` "},
|
||||
}
|
||||
|
||||
func TestGenericFlagHelpOutput(t *testing.T) {
|
||||
|
||||
for _, test := range genericFlagTests {
|
||||
flag := cli.GenericFlag{Name: test.name}
|
||||
output := flag.String()
|
||||
|
||||
if output != test.expected {
|
||||
t.Errorf("%q does not match %q", output, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||
|
||||
os.Setenv("APP_ZAP", "3")
|
||||
for _, test := range genericFlagTests {
|
||||
flag := cli.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) {
|
||||
(&cli.App{
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "serve, s"},
|
||||
},
|
||||
Action: func(ctx *cli.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.Setenv("APP_COUNT", "20")
|
||||
(&cli.App{
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "count, c", EnvVar: "APP_COUNT"},
|
||||
},
|
||||
Action: func(ctx *cli.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) {
|
||||
(&cli.App{
|
||||
Flags: []cli.Flag{
|
||||
cli.StringSliceFlag{Name: "serve, s", Value: &cli.StringSlice{}},
|
||||
},
|
||||
Action: func(ctx *cli.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.Setenv("APP_INTERVALS", "20,30,40")
|
||||
|
||||
(&cli.App{
|
||||
Flags: []cli.Flag{
|
||||
cli.StringSliceFlag{Name: "intervals, i", Value: &cli.StringSlice{}, EnvVar: "APP_INTERVALS"},
|
||||
},
|
||||
Action: func(ctx *cli.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 := cli.App{
|
||||
Flags: []cli.Flag{
|
||||
cli.IntFlag{Name: "serve, s"},
|
||||
},
|
||||
Action: func(ctx *cli.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.Setenv("APP_TIMEOUT_SECONDS", "10")
|
||||
a := cli.App{
|
||||
Flags: []cli.Flag{
|
||||
cli.IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
|
||||
},
|
||||
Action: func(ctx *cli.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) {
|
||||
(&cli.App{
|
||||
Flags: []cli.Flag{
|
||||
cli.IntSliceFlag{Name: "serve, s", Value: &cli.IntSlice{}},
|
||||
},
|
||||
Action: func(ctx *cli.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.Setenv("APP_INTERVALS", "20,30,40")
|
||||
|
||||
(&cli.App{
|
||||
Flags: []cli.Flag{
|
||||
cli.IntSliceFlag{Name: "intervals, i", Value: &cli.IntSlice{}, EnvVar: "APP_INTERVALS"},
|
||||
},
|
||||
Action: func(ctx *cli.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 := cli.App{
|
||||
Flags: []cli.Flag{
|
||||
cli.Float64Flag{Name: "serve, s"},
|
||||
},
|
||||
Action: func(ctx *cli.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.Setenv("APP_TIMEOUT_SECONDS", "15.5")
|
||||
a := cli.App{
|
||||
Flags: []cli.Flag{
|
||||
cli.Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
|
||||
},
|
||||
Action: func(ctx *cli.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 := cli.App{
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{Name: "serve, s"},
|
||||
},
|
||||
Action: func(ctx *cli.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.Setenv("APP_DEBUG", "1")
|
||||
a := cli.App{
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"},
|
||||
},
|
||||
Action: func(ctx *cli.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 := cli.App{
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolTFlag{Name: "serve, s"},
|
||||
},
|
||||
Action: func(ctx *cli.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.Setenv("APP_DEBUG", "0")
|
||||
a := cli.App{
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"},
|
||||
},
|
||||
Action: func(ctx *cli.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 := cli.App{
|
||||
Flags: []cli.Flag{
|
||||
cli.GenericFlag{Name: "serve, s", Value: &Parser{}},
|
||||
},
|
||||
Action: func(ctx *cli.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.Setenv("APP_SERVE", "20,30")
|
||||
a := cli.App{
|
||||
Flags: []cli.Flag{
|
||||
cli.GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"},
|
||||
},
|
||||
Action: func(ctx *cli.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"})
|
||||
}
|
||||
224
Godeps/_workspace/src/github.com/codegangsta/cli/help.go
generated
vendored
224
Godeps/_workspace/src/github.com/codegangsta/cli/help.go
generated
vendored
@@ -1,224 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"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}}command{{if .Flags}} [command options]{{end}} [arguments...]
|
||||
|
||||
VERSION:
|
||||
{{.Version}}{{if or .Author .Email}}
|
||||
|
||||
AUTHOR:{{if .Author}}
|
||||
{{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}}
|
||||
{{.Email}}{{end}}{{end}}
|
||||
|
||||
COMMANDS:
|
||||
{{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
|
||||
{{end}}{{if .Flags}}
|
||||
GLOBAL OPTIONS:
|
||||
{{range .Flags}}{{.}}
|
||||
{{end}}{{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:
|
||||
{{.Name}} - {{.Usage}}
|
||||
|
||||
USAGE:
|
||||
command {{.Name}}{{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}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
|
||||
{{end}}{{if .Flags}}
|
||||
OPTIONS:
|
||||
{{range .Flags}}{{.}}
|
||||
{{end}}{{end}}
|
||||
`
|
||||
|
||||
var helpCommand = Command{
|
||||
Name: "help",
|
||||
ShortName: "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",
|
||||
ShortName: "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
|
||||
var HelpPrinter = printHelp
|
||||
|
||||
// Prints version for the App
|
||||
var VersionPrinter = printVersion
|
||||
|
||||
func ShowAppHelp(c *Context) {
|
||||
HelpPrinter(AppHelpTemplate, c.App)
|
||||
}
|
||||
|
||||
// Prints the list of subcommands as the default app completion method
|
||||
func DefaultAppComplete(c *Context) {
|
||||
for _, command := range c.App.Commands {
|
||||
fmt.Println(command.Name)
|
||||
if command.ShortName != "" {
|
||||
fmt.Println(command.ShortName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prints help for the given command
|
||||
func ShowCommandHelp(c *Context, command string) {
|
||||
for _, c := range c.App.Commands {
|
||||
if c.HasName(command) {
|
||||
HelpPrinter(CommandHelpTemplate, c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if c.App.CommandNotFound != nil {
|
||||
c.App.CommandNotFound(c, command)
|
||||
} else {
|
||||
fmt.Printf("No help topic for '%v'\n", command)
|
||||
}
|
||||
}
|
||||
|
||||
// Prints help for the given subcommand
|
||||
func ShowSubcommandHelp(c *Context) {
|
||||
HelpPrinter(SubcommandHelpTemplate, c.App)
|
||||
}
|
||||
|
||||
// Prints the version number of the App
|
||||
func ShowVersion(c *Context) {
|
||||
VersionPrinter(c)
|
||||
}
|
||||
|
||||
func printVersion(c *Context) {
|
||||
fmt.Printf("%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(templ string, data interface{}) {
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
|
||||
t := template.Must(template.New("help").Parse(templ))
|
||||
err := t.Execute(w, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
func checkVersion(c *Context) bool {
|
||||
if c.GlobalBool("version") {
|
||||
ShowVersion(c)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkHelp(c *Context) bool {
|
||||
if c.GlobalBool("h") || c.GlobalBool("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
|
||||
}
|
||||
19
Godeps/_workspace/src/github.com/codegangsta/cli/helpers_test.go
generated
vendored
19
Godeps/_workspace/src/github.com/codegangsta/cli/helpers_test.go
generated
vendored
@@ -1,19 +0,0 @@
|
||||
package cli_test
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
2
Godeps/_workspace/src/github.com/gosimple/slug/.gitignore
generated
vendored
Normal file
2
Godeps/_workspace/src/github.com/gosimple/slug/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
_*
|
||||
cover*.out
|
||||
54
Godeps/_workspace/src/github.com/gosimple/slug/README.md
generated
vendored
Normal file
54
Godeps/_workspace/src/github.com/gosimple/slug/README.md
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
slug
|
||||
====
|
||||
|
||||
Package `slug` generate slug from unicode string, URL-friendly slugify with
|
||||
multiple languages support.
|
||||
|
||||
[](https://godoc.org/github.com/gosimple/slug)
|
||||
[](https://drone.io/github.com/gosimple/slug/latest)
|
||||
|
||||
[Documentation online](http://godoc.org/github.com/gosimple/slug)
|
||||
|
||||
## Example
|
||||
|
||||
package main
|
||||
|
||||
import(
|
||||
"github.com/gosimple/slug"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main () {
|
||||
text := slug.Make("Hellö Wörld хелло ворлд")
|
||||
fmt.Println(text) // Will print hello-world-khello-vorld
|
||||
|
||||
someText := slug.Make("影師")
|
||||
fmt.Println(someText) // Will print: ying-shi
|
||||
|
||||
enText := slug.MakeLang("This & that", "en")
|
||||
fmt.Println(enText) // Will print 'this-and-that'
|
||||
|
||||
deText := slug.MakeLang("Diese & Dass", "de")
|
||||
fmt.Println(deText) // Will print 'diese-und-dass'
|
||||
|
||||
slug.CustomSub = map[string]string{
|
||||
"water": "sand",
|
||||
}
|
||||
textSub := slug.Make("water is hot")
|
||||
fmt.Println(textSub) // Will print 'sand-is-hot'
|
||||
}
|
||||
|
||||
### Requests or bugs?
|
||||
<https://github.com/gosimple/slug/issues>
|
||||
|
||||
## Installation
|
||||
|
||||
go get -u github.com/gosimple/slug
|
||||
|
||||
## License
|
||||
|
||||
The source files are distributed under the
|
||||
[Mozilla Public License, version 2.0](http://mozilla.org/MPL/2.0/),
|
||||
unless otherwise noted.
|
||||
Please read the [FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html)
|
||||
if you have further questions regarding the license.
|
||||
16
Godeps/_workspace/src/github.com/gosimple/slug/default_substitution.go
generated
vendored
Normal file
16
Godeps/_workspace/src/github.com/gosimple/slug/default_substitution.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2013 by Dobrosław Żybort. All rights reserved.
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package slug
|
||||
|
||||
var defaultSub = map[rune]string{
|
||||
'"': "",
|
||||
'\'': "",
|
||||
'’': "",
|
||||
'‒': "-", // figure dash
|
||||
'–': "-", // en dash
|
||||
'—': "-", // em dash
|
||||
'―': "-", // horizontal bar
|
||||
}
|
||||
43
Godeps/_workspace/src/github.com/gosimple/slug/doc.go
generated
vendored
Normal file
43
Godeps/_workspace/src/github.com/gosimple/slug/doc.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2013 by Dobrosław Żybort. All rights reserved.
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
/*
|
||||
Package slug generate slug from unicode string, URL-friendly slugify with
|
||||
multiple languages support.
|
||||
|
||||
Example:
|
||||
|
||||
package main
|
||||
|
||||
import(
|
||||
"github.com/gosimple/slug"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main () {
|
||||
text := slug.Make("Hellö Wörld хелло ворлд")
|
||||
fmt.Println(text) // Will print hello-world-khello-vorld
|
||||
|
||||
someText := slug.Make("影師")
|
||||
fmt.Println(someText) // Will print: ying-shi
|
||||
|
||||
enText := slug.MakeLang("This & that", "en")
|
||||
fmt.Println(enText) // Will print 'this-and-that'
|
||||
|
||||
deText := slug.MakeLang("Diese & Dass", "de")
|
||||
fmt.Println(deText) // Will print 'diese-und-dass'
|
||||
|
||||
slug.CustomSub = map[string]string{
|
||||
"water": "sand",
|
||||
}
|
||||
textSub := slug.Make("water is hot")
|
||||
fmt.Println(textSub) // Will print 'sand-is-hot'
|
||||
}
|
||||
|
||||
Requests or bugs?
|
||||
|
||||
https://github.com/gosimple/slug/issues
|
||||
*/
|
||||
package slug
|
||||
26
Godeps/_workspace/src/github.com/gosimple/slug/languages_substitution.go
generated
vendored
Normal file
26
Godeps/_workspace/src/github.com/gosimple/slug/languages_substitution.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2013 by Dobrosław Żybort. All rights reserved.
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package slug
|
||||
|
||||
var deSub = map[rune]string{
|
||||
'&': "und",
|
||||
'@': "an",
|
||||
}
|
||||
|
||||
var enSub = map[rune]string{
|
||||
'&': "and",
|
||||
'@': "at",
|
||||
}
|
||||
|
||||
var plSub = map[rune]string{
|
||||
'&': "i",
|
||||
'@': "na",
|
||||
}
|
||||
|
||||
var esSub = map[rune]string{
|
||||
'&': "y",
|
||||
'@': "en",
|
||||
}
|
||||
122
Godeps/_workspace/src/github.com/gosimple/slug/slug.go
generated
vendored
Normal file
122
Godeps/_workspace/src/github.com/gosimple/slug/slug.go
generated
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright 2013 by Dobrosław Żybort. All rights reserved.
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package slug
|
||||
|
||||
import (
|
||||
"gopkgs.com/unidecode.v1"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// Custom substitution map
|
||||
CustomSub map[string]string
|
||||
// Custom rune substitution map
|
||||
CustomRuneSub map[rune]string
|
||||
|
||||
// Maximum slug length. It's smart so it will cat slug after full word.
|
||||
// By default slugs aren't shortened.
|
||||
// If MaxLength is smaller than length of the first word, then returned
|
||||
// slug will contain only substring from the first word truncated
|
||||
// after MaxLength.
|
||||
MaxLength int
|
||||
)
|
||||
|
||||
//=============================================================================
|
||||
|
||||
// Make returns slug generated from provided string. Will use "en" as language
|
||||
// substitution.
|
||||
func Make(s string) (slug string) {
|
||||
return MakeLang(s, "en")
|
||||
}
|
||||
|
||||
// MakeLang returns slug generated from provided string and will use provided
|
||||
// language for chars substitution.
|
||||
func MakeLang(s string, lang string) (slug string) {
|
||||
slug = strings.TrimSpace(s)
|
||||
|
||||
// Custom substitutions
|
||||
// Always substitute runes first
|
||||
slug = SubstituteRune(slug, CustomRuneSub)
|
||||
slug = Substitute(slug, CustomSub)
|
||||
|
||||
// Process string with selected substitution language
|
||||
switch lang {
|
||||
case "de":
|
||||
slug = SubstituteRune(slug, deSub)
|
||||
case "en":
|
||||
slug = SubstituteRune(slug, enSub)
|
||||
case "pl":
|
||||
slug = SubstituteRune(slug, plSub)
|
||||
case "es":
|
||||
slug = SubstituteRune(slug, esSub)
|
||||
default: // fallback to "en" if lang not found
|
||||
slug = SubstituteRune(slug, enSub)
|
||||
}
|
||||
|
||||
slug = SubstituteRune(slug, defaultSub)
|
||||
|
||||
// Process all non ASCII symbols
|
||||
slug = unidecode.Unidecode(slug)
|
||||
|
||||
slug = strings.ToLower(slug)
|
||||
|
||||
// Process all remaining symbols
|
||||
slug = regexp.MustCompile("[^a-z0-9-_]").ReplaceAllString(slug, "-")
|
||||
slug = regexp.MustCompile("-+").ReplaceAllString(slug, "-")
|
||||
slug = strings.Trim(slug, "-")
|
||||
|
||||
if MaxLength > 0 {
|
||||
slug = smartTruncate(slug)
|
||||
}
|
||||
|
||||
return slug
|
||||
}
|
||||
|
||||
// Substitute returns string with superseded all substrings from
|
||||
// provided substitution map.
|
||||
func Substitute(s string, sub map[string]string) (buf string) {
|
||||
buf = s
|
||||
for key, val := range sub {
|
||||
buf = strings.Replace(s, key, val, -1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SubstituteRune substitutes string chars with provided rune
|
||||
// substitution map.
|
||||
func SubstituteRune(s string, sub map[rune]string) (buf string) {
|
||||
for _, c := range s {
|
||||
if d, ok := sub[c]; ok {
|
||||
buf += d
|
||||
} else {
|
||||
buf += string(c)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func smartTruncate(text string) string {
|
||||
if len(text) < MaxLength {
|
||||
return text
|
||||
}
|
||||
|
||||
var truncated string
|
||||
words := strings.SplitAfter(text, "-")
|
||||
// If MaxLength is smaller than length of the first word return word
|
||||
// truncated after MaxLength.
|
||||
if len(words[0]) > MaxLength {
|
||||
return words[0][:MaxLength]
|
||||
}
|
||||
for _, word := range words {
|
||||
if len(truncated)+len(word)-1 <= MaxLength {
|
||||
truncated = truncated + word
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return strings.Trim(truncated, "-")
|
||||
}
|
||||
337
Godeps/_workspace/src/github.com/gosimple/slug/slug_test.go
generated
vendored
Normal file
337
Godeps/_workspace/src/github.com/gosimple/slug/slug_test.go
generated
vendored
Normal file
@@ -0,0 +1,337 @@
|
||||
// Copyright 2013 by Dobrosław Żybort. All rights reserved.
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package slug
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
//=============================================================================
|
||||
|
||||
func TestSlugMake(t *testing.T) {
|
||||
var testCases = []struct {
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{"DOBROSLAWZYBORT", "dobroslawzybort"},
|
||||
{"Dobroslaw Zybort", "dobroslaw-zybort"},
|
||||
{" Dobroslaw Zybort ?", "dobroslaw-zybort"},
|
||||
{"Dobrosław Żybort", "dobroslaw-zybort"},
|
||||
{"Ala ma 6 kotów.", "ala-ma-6-kotow"},
|
||||
|
||||
{"áÁàÀãÃâÂäÄąĄą̊Ą̊", "aaaaaaaaaaaaaa"},
|
||||
{"ćĆĉĈçÇ", "cccccc"},
|
||||
{"éÉèÈẽẼêÊëËęĘ", "eeeeeeeeeeee"},
|
||||
{"íÍìÌĩĨîÎïÏįĮ", "iiiiiiiiiiii"},
|
||||
{"łŁ", "ll"},
|
||||
{"ńŃ", "nn"},
|
||||
{"óÓòÒõÕôÔöÖǫǪǭǬø", "ooooooooooooooo"},
|
||||
{"śŚ", "ss"},
|
||||
{"úÚùÙũŨûÛüÜųŲ", "uuuuuuuuuuuu"},
|
||||
{"y̨Y̨", "yy"},
|
||||
{"źŹżŹ", "zzzz"},
|
||||
{"·/,:;`˜'\"", ""},
|
||||
{"2000–2013", "2000-2013"},
|
||||
{"style—not", "style-not"},
|
||||
{"test_slug", "test_slug"},
|
||||
{"Æ", "ae"},
|
||||
{"Ich heiße", "ich-heisse"},
|
||||
|
||||
{"This & that", "this-and-that"},
|
||||
{"fácil €", "facil-eu"},
|
||||
{"smile ☺", "smile"},
|
||||
{"Hellö Wörld хелло ворлд", "hello-world-khello-vorld"},
|
||||
{"\"C'est déjà l’été.\"", "cest-deja-lete"},
|
||||
{"jaja---lol-méméméoo--a", "jaja-lol-mememeoo-a"},
|
||||
{"影師", "ying-shi"},
|
||||
}
|
||||
|
||||
for index, st := range testCases {
|
||||
got := Make(st.in)
|
||||
if got != st.want {
|
||||
t.Errorf(
|
||||
"%d. Make(%#v) = %#v; want %#v",
|
||||
index, st.in, got, st.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlugMakeLang(t *testing.T) {
|
||||
var testCases = []struct {
|
||||
lang string
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{"en", "This & that", "this-and-that"},
|
||||
{"de", "This & that", "this-und-that"},
|
||||
{"pl", "This & that", "this-i-that"},
|
||||
{"es", "This & that", "this-y-that"},
|
||||
{"test", "This & that", "this-and-that"}, // unknown lang, fallback to "en"
|
||||
}
|
||||
|
||||
for index, smlt := range testCases {
|
||||
got := MakeLang(smlt.in, smlt.lang)
|
||||
if got != smlt.want {
|
||||
t.Errorf(
|
||||
"%d. MakeLang(%#v, %#v) = %#v; want %#v",
|
||||
index, smlt.in, smlt.lang, got, smlt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlugMakeUserSubstituteLang(t *testing.T) {
|
||||
var testCases = []struct {
|
||||
cSub map[string]string
|
||||
lang string
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{map[string]string{"'": " "}, "en", "That's great", "that-s-great"},
|
||||
{map[string]string{"&": "or"}, "en", "This & that", "this-or-that"}, // by default "&" => "and"
|
||||
{map[string]string{"&": "or"}, "de", "This & that", "this-or-that"}, // by default "&" => "und"
|
||||
}
|
||||
|
||||
for index, smust := range testCases {
|
||||
CustomSub = smust.cSub
|
||||
got := MakeLang(smust.in, smust.lang)
|
||||
if got != smust.want {
|
||||
t.Errorf(
|
||||
"%d. %#v; MakeLang(%#v, %#v) = %#v; want %#v",
|
||||
index, smust.cSub, smust.in, smust.lang,
|
||||
got, smust.want)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlugMakeSubstituteOrderLang(t *testing.T) {
|
||||
// Always substitute runes first
|
||||
var testCases = []struct {
|
||||
rSub map[rune]string
|
||||
sSub map[string]string
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{map[rune]string{'o': "left"}, map[string]string{"o": "right"}, "o o", "left-left"},
|
||||
{map[rune]string{'&': "down"}, map[string]string{"&": "up"}, "&", "down"},
|
||||
}
|
||||
|
||||
for index, smsot := range testCases {
|
||||
CustomRuneSub = smsot.rSub
|
||||
CustomSub = smsot.sSub
|
||||
got := Make(smsot.in)
|
||||
if got != smsot.want {
|
||||
t.Errorf(
|
||||
"%d. %#v; %#v; Make(%#v) = %#v; want %#v",
|
||||
index, smsot.rSub, smsot.sSub, smsot.in,
|
||||
got, smsot.want)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubstituteLang(t *testing.T) {
|
||||
var testCases = []struct {
|
||||
cSub map[string]string
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{map[string]string{"o": "no"}, "o o o", "no no no"},
|
||||
{map[string]string{"'": " "}, "That's great", "That s great"},
|
||||
}
|
||||
|
||||
for index, sst := range testCases {
|
||||
got := Substitute(sst.in, sst.cSub)
|
||||
if got != sst.want {
|
||||
t.Errorf(
|
||||
"%d. Substitute(%#v, %#v) = %#v; want %#v",
|
||||
index, sst.in, sst.cSub, got, sst.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubstituteRuneLang(t *testing.T) {
|
||||
var testCases = []struct {
|
||||
cSub map[rune]string
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{map[rune]string{'o': "no"}, "o o o", "no no no"},
|
||||
{map[rune]string{'\'': " "}, "That's great", "That s great"},
|
||||
}
|
||||
|
||||
for index, ssrt := range testCases {
|
||||
got := SubstituteRune(ssrt.in, ssrt.cSub)
|
||||
if got != ssrt.want {
|
||||
t.Errorf(
|
||||
"%d. SubstituteRune(%#v, %#v) = %#v; want %#v",
|
||||
index, ssrt.in, ssrt.cSub, got, ssrt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSlugMakeSmartTruncate(t *testing.T) {
|
||||
var testCases = []struct {
|
||||
in string
|
||||
maxLength int
|
||||
want string
|
||||
}{
|
||||
{"DOBROSLAWZYBORT", 100, "dobroslawzybort"},
|
||||
{"Dobroslaw Zybort", 100, "dobroslaw-zybort"},
|
||||
{"Dobroslaw Zybort", 12, "dobroslaw"},
|
||||
{" Dobroslaw Zybort ?", 12, "dobroslaw"},
|
||||
{"Ala ma 6 kotów.", 10, "ala-ma-6"},
|
||||
{"Dobrosław Żybort", 5, "dobro"},
|
||||
}
|
||||
|
||||
for index, smstt := range testCases {
|
||||
MaxLength = smstt.maxLength
|
||||
got := Make(smstt.in)
|
||||
if got != smstt.want {
|
||||
t.Errorf(
|
||||
"%d. MaxLength = %v; Make(%#v) = %#v; want %#v",
|
||||
index, smstt.maxLength, smstt.in, got, smstt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMakeShortAscii(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for n := 0; n < b.N; n++ {
|
||||
Make("Hello world")
|
||||
}
|
||||
}
|
||||
func BenchmarkMakeShort(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for n := 0; n < b.N; n++ {
|
||||
Make("хелло ворлд")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMakeShortSymbols(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for n := 0; n < b.N; n++ {
|
||||
Make("·/,:;`˜'\" &€£¥")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMakeMediumAscii(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for n := 0; n < b.N; n++ {
|
||||
Make("ABCDE FGHIJ KLMNO PQRST UWXYZ ABCDE FGHIJ KLMNO PQRST UWXYZ ABCDE")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMakeMedium(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for n := 0; n < b.N; n++ {
|
||||
Make("ヲァィゥェ ォャュョッ ーアイウエ オカキクケ コサシスセ ソタチツテ トナニヌネ ノハヒフヘ ホマミムメ モヤユヨラ リルレロワ")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMakeLongAscii(b *testing.B) {
|
||||
longStr := "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi " +
|
||||
"pulvinar sodales ultrices. Nulla facilisi. Sed at vestibulum erat. Ut " +
|
||||
"sit amet urna posuere, sagittis eros ac, varius nisi. Morbi ullamcorper " +
|
||||
"odio at nunc pulvinar mattis. Vestibulum rutrum, ante eu dictum mattis, " +
|
||||
"elit risus finibus nunc, consectetur facilisis eros leo ut sapien. Sed " +
|
||||
"pulvinar volutpat mi. Cras semper mi ac eros accumsan, at feugiat massa " +
|
||||
"elementum. Morbi eget dolor sit amet purus condimentum egestas non ut " +
|
||||
"sapien. Duis feugiat magna vitae nisi lobortis, quis finibus sem " +
|
||||
"sollicitudin. Pellentesque eleifend blandit ipsum, ut porta arcu " +
|
||||
"ultricies et. Fusce vel ipsum porta, placerat diam ac, consectetur " +
|
||||
"magna. Nulla in porta sem. Suspendisse commodo, felis in molestie " +
|
||||
"ultricies, arcu ipsum aliquet turpis, elementum dapibus ipsum lorem a " +
|
||||
"nisl. Etiam varius imperdiet placerat. Aliquam euismod lacus arcu, " +
|
||||
"ultrices hendrerit est pellentesque vel. Aliquam sit amet laoreet leo. " +
|
||||
"Integer eros libero, mollis sed posuere."
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
Make(longStr)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSubstituteRuneShort(b *testing.B) {
|
||||
shortStr := "Hello/Hi world"
|
||||
subs := map[rune]string{'o': "no", '/': "slash"}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
SubstituteRune(shortStr, subs)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSubstituteRuneLong(b *testing.B) {
|
||||
longStr := "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi " +
|
||||
"pulvinar sodales ultrices. Nulla facilisi. Sed at vestibulum erat. Ut " +
|
||||
"sit amet urna posuere, sagittis eros ac, varius nisi. Morbi ullamcorper " +
|
||||
"odio at nunc pulvinar mattis. Vestibulum rutrum, ante eu dictum mattis, " +
|
||||
"elit risus finibus nunc, consectetur facilisis eros leo ut sapien. Sed " +
|
||||
"pulvinar volutpat mi. Cras semper mi ac eros accumsan, at feugiat massa " +
|
||||
"elementum. Morbi eget dolor sit amet purus condimentum egestas non ut " +
|
||||
"sapien. Duis feugiat magna vitae nisi lobortis, quis finibus sem " +
|
||||
"sollicitudin. Pellentesque eleifend blandit ipsum, ut porta arcu " +
|
||||
"ultricies et. Fusce vel ipsum porta, placerat diam ac, consectetur " +
|
||||
"magna. Nulla in porta sem. Suspendisse commodo, felis in molestie " +
|
||||
"ultricies, arcu ipsum aliquet turpis, elementum dapibus ipsum lorem a " +
|
||||
"nisl. Etiam varius imperdiet placerat. Aliquam euismod lacus arcu, " +
|
||||
"ultrices hendrerit est pellentesque vel. Aliquam sit amet laoreet leo. " +
|
||||
"Integer eros libero, mollis sed posuere."
|
||||
subs := map[rune]string{
|
||||
'o': "no",
|
||||
'/': "slash",
|
||||
'i': "done",
|
||||
'E': "es",
|
||||
'a': "ASD",
|
||||
'1': "one",
|
||||
'l': "onetwo",
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
SubstituteRune(longStr, subs)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSmartTruncateShort(b *testing.B) {
|
||||
shortStr := "Hello-world"
|
||||
MaxLength = 8
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
smartTruncate(shortStr)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSmartTruncateLong(b *testing.B) {
|
||||
longStr := "Lorem-ipsum-dolor-sit-amet,-consectetur-adipiscing-elit.-Morbi-" +
|
||||
"pulvinar-sodales-ultrices.-Nulla-facilisi.-Sed-at-vestibulum-erat.-Ut-" +
|
||||
"sit-amet-urna-posuere,-sagittis-eros-ac,-varius-nisi.-Morbi-ullamcorper-" +
|
||||
"odio-at-nunc-pulvinar-mattis.-Vestibulum-rutrum,-ante-eu-dictum-mattis,-" +
|
||||
"elit-risus-finibus-nunc,-consectetur-facilisis-eros-leo-ut-sapien.-Sed-" +
|
||||
"pulvinar-volutpat-mi.-Cras-semper-mi-ac-eros-accumsan,-at-feugiat-massa-" +
|
||||
"elementum.-Morbi-eget-dolor-sit-amet-purus-condimentum-egestas-non-ut-" +
|
||||
"sapien.-Duis-feugiat-magna-vitae-nisi-lobortis,-quis-finibus-sem-" +
|
||||
"sollicitudin.-Pellentesque-eleifend-blandit-ipsum,-ut-porta-arcu-" +
|
||||
"ultricies-et.-Fusce-vel-ipsum-porta,-placerat-diam-ac,-consectetur-" +
|
||||
"magna.-Nulla-in-porta-sem.-Suspendisse-commodo,-felis-in-molestie-" +
|
||||
"ultricies,-arcu-ipsum-aliquet-turpis,-elementum-dapibus-ipsum-lorem-a-" +
|
||||
"nisl.-Etiam-varius-imperdiet-placerat.-Aliquam-euismod-lacus-arcu,-" +
|
||||
"ultrices-hendrerit-est-pellentesque-vel.-Aliquam-sit-amet-laoreet-leo.-" +
|
||||
"Integer-eros-libero,-mollis-sed-posuere."
|
||||
MaxLength = 256
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
smartTruncate(longStr)
|
||||
}
|
||||
}
|
||||
2
Godeps/_workspace/src/github.com/macaron-contrib/session/.gitignore
generated
vendored
Normal file
2
Godeps/_workspace/src/github.com/macaron-contrib/session/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
ledis/tmp.db
|
||||
nodb/tmp.db
|
||||
6
Godeps/_workspace/src/github.com/macaron-contrib/session/README.md
generated
vendored
6
Godeps/_workspace/src/github.com/macaron-contrib/session/README.md
generated
vendored
@@ -1,7 +1,7 @@
|
||||
session [](https://drone.io/github.com/macaron-contrib/session/latest) [](http://gocover.io/github.com/macaron-contrib/session)
|
||||
=======
|
||||
|
||||
Middleware session provides session management for [Macaron](https://github.com/Unknwon/macaron). It can use many session providers, including memory, file, Redis, Memcache, PostgreSQL, MySQL, Couchbase and Ledis.
|
||||
Middleware session provides session management for [Macaron](https://github.com/Unknwon/macaron). It can use many session providers, including memory, file, Redis, Memcache, PostgreSQL, MySQL, Couchbase, Ledis and Nodb.
|
||||
|
||||
### Installation
|
||||
|
||||
@@ -12,6 +12,10 @@ Middleware session provides session management for [Macaron](https://github.com/
|
||||
- [API Reference](https://gowalker.org/github.com/macaron-contrib/session)
|
||||
- [Documentation](http://macaron.gogs.io/docs/middlewares/session)
|
||||
|
||||
## Credits
|
||||
|
||||
This package is forked from [beego/session](https://github.com/astaxie/beego/tree/master/session) with reconstruction(over 80%).
|
||||
|
||||
## License
|
||||
|
||||
This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
|
||||
|
||||
40
Godeps/_workspace/src/github.com/macaron-contrib/session/file.go
generated
vendored
40
Godeps/_workspace/src/github.com/macaron-contrib/session/file.go
generated
vendored
@@ -28,17 +28,17 @@ import (
|
||||
"github.com/Unknwon/com"
|
||||
)
|
||||
|
||||
// FileSessionStore represents a file session store implementation.
|
||||
type FileSessionStore struct {
|
||||
// FileStore represents a file session store implementation.
|
||||
type FileStore struct {
|
||||
p *FileProvider
|
||||
sid string
|
||||
lock sync.RWMutex
|
||||
data map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// NewFileSessionStore creates and returns a file session store.
|
||||
func NewFileSessionStore(p *FileProvider, sid string, kv map[interface{}]interface{}) *FileSessionStore {
|
||||
return &FileSessionStore{
|
||||
// NewFileStore creates and returns a file session store.
|
||||
func NewFileStore(p *FileProvider, sid string, kv map[interface{}]interface{}) *FileStore {
|
||||
return &FileStore{
|
||||
p: p,
|
||||
sid: sid,
|
||||
data: kv,
|
||||
@@ -46,7 +46,7 @@ func NewFileSessionStore(p *FileProvider, sid string, kv map[interface{}]interfa
|
||||
}
|
||||
|
||||
// Set sets value to given key in session.
|
||||
func (s *FileSessionStore) Set(key, val interface{}) error {
|
||||
func (s *FileStore) Set(key, val interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
@@ -55,7 +55,7 @@ func (s *FileSessionStore) Set(key, val interface{}) error {
|
||||
}
|
||||
|
||||
// Get gets value by given key in session.
|
||||
func (s *FileSessionStore) Get(key interface{}) interface{} {
|
||||
func (s *FileStore) Get(key interface{}) interface{} {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
@@ -63,7 +63,7 @@ func (s *FileSessionStore) Get(key interface{}) interface{} {
|
||||
}
|
||||
|
||||
// Delete delete a key from session.
|
||||
func (s *FileSessionStore) Delete(key interface{}) error {
|
||||
func (s *FileStore) Delete(key interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
@@ -72,12 +72,12 @@ func (s *FileSessionStore) Delete(key interface{}) error {
|
||||
}
|
||||
|
||||
// ID returns current session ID.
|
||||
func (s *FileSessionStore) ID() string {
|
||||
func (s *FileStore) ID() string {
|
||||
return s.sid
|
||||
}
|
||||
|
||||
// Release releases resource and save data to provider.
|
||||
func (s *FileSessionStore) Release() error {
|
||||
func (s *FileStore) Release() error {
|
||||
data, err := EncodeGob(s.data)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -87,7 +87,7 @@ func (s *FileSessionStore) Release() error {
|
||||
}
|
||||
|
||||
// Flush deletes all session data.
|
||||
func (s *FileSessionStore) Flush() error {
|
||||
func (s *FileStore) Flush() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
@@ -97,7 +97,6 @@ func (s *FileSessionStore) Flush() error {
|
||||
|
||||
// FileProvider represents a file session provider implementation.
|
||||
type FileProvider struct {
|
||||
lock sync.RWMutex
|
||||
maxlifetime int64
|
||||
rootPath string
|
||||
}
|
||||
@@ -115,9 +114,6 @@ func (p *FileProvider) filepath(sid string) string {
|
||||
|
||||
// Read returns raw session store by session ID.
|
||||
func (p *FileProvider) Read(sid string) (_ RawStore, err error) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
filename := p.filepath(sid)
|
||||
if err = os.MkdirAll(path.Dir(filename), os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
@@ -151,22 +147,16 @@ func (p *FileProvider) Read(sid string) (_ RawStore, err error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return NewFileSessionStore(p, sid, kv), nil
|
||||
return NewFileStore(p, sid, kv), nil
|
||||
}
|
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (p *FileProvider) Exist(sid string) bool {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
return com.IsFile(p.filepath(sid))
|
||||
}
|
||||
|
||||
// Destory deletes a session by session ID.
|
||||
func (p *FileProvider) Destory(sid string) error {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
return os.Remove(p.filepath(sid))
|
||||
}
|
||||
|
||||
@@ -201,12 +191,9 @@ func (p *FileProvider) regenerate(oldsid, sid string) (err error) {
|
||||
|
||||
// Regenerate regenerates a session store from old session ID to new one.
|
||||
func (p *FileProvider) Regenerate(oldsid, sid string) (_ RawStore, err error) {
|
||||
p.lock.Lock()
|
||||
if err := p.regenerate(oldsid, sid); err != nil {
|
||||
p.lock.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
p.lock.Unlock()
|
||||
|
||||
return p.Read(sid)
|
||||
}
|
||||
@@ -236,9 +223,6 @@ func (p *FileProvider) GC() {
|
||||
return
|
||||
}
|
||||
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
if err := filepath.Walk(p.rootPath, func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
153
Godeps/_workspace/src/github.com/macaron-contrib/session/ledis/ledis.go
generated
vendored
153
Godeps/_workspace/src/github.com/macaron-contrib/session/ledis/ledis.go
generated
vendored
@@ -16,26 +16,39 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/siddontang/ledisdb/config"
|
||||
"github.com/siddontang/ledisdb/ledis"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/macaron-contrib/session"
|
||||
)
|
||||
|
||||
var c *ledis.DB
|
||||
// LedisStore represents a ledis session store implementation.
|
||||
type LedisStore struct {
|
||||
c *ledis.DB
|
||||
sid string
|
||||
expire int64
|
||||
lock sync.RWMutex
|
||||
data map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// LedisSessionStore represents a ledis session store implementation.
|
||||
type LedisSessionStore struct {
|
||||
sid string
|
||||
lock sync.RWMutex
|
||||
data map[interface{}]interface{}
|
||||
maxlifetime int64
|
||||
// NewLedisStore creates and returns a ledis session store.
|
||||
func NewLedisStore(c *ledis.DB, sid string, expire int64, kv map[interface{}]interface{}) *LedisStore {
|
||||
return &LedisStore{
|
||||
c: c,
|
||||
expire: expire,
|
||||
sid: sid,
|
||||
data: kv,
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets value to given key in session.
|
||||
func (s *LedisSessionStore) Set(key, val interface{}) error {
|
||||
func (s *LedisStore) Set(key, val interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
@@ -44,7 +57,7 @@ func (s *LedisSessionStore) Set(key, val interface{}) error {
|
||||
}
|
||||
|
||||
// Get gets value by given key in session.
|
||||
func (s *LedisSessionStore) Get(key interface{}) interface{} {
|
||||
func (s *LedisStore) Get(key interface{}) interface{} {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
@@ -52,7 +65,7 @@ func (s *LedisSessionStore) Get(key interface{}) interface{} {
|
||||
}
|
||||
|
||||
// Delete delete a key from session.
|
||||
func (s *LedisSessionStore) Delete(key interface{}) error {
|
||||
func (s *LedisStore) Delete(key interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
@@ -61,25 +74,26 @@ func (s *LedisSessionStore) Delete(key interface{}) error {
|
||||
}
|
||||
|
||||
// ID returns current session ID.
|
||||
func (s *LedisSessionStore) ID() string {
|
||||
func (s *LedisStore) ID() string {
|
||||
return s.sid
|
||||
}
|
||||
|
||||
// Release releases resource and save data to provider.
|
||||
func (s *LedisSessionStore) Release() error {
|
||||
func (s *LedisStore) Release() error {
|
||||
data, err := session.EncodeGob(s.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = c.Set([]byte(s.sid), data); err != nil {
|
||||
|
||||
if err = s.c.Set([]byte(s.sid), data); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = c.Expire([]byte(s.sid), s.maxlifetime)
|
||||
_, err = s.c.Expire([]byte(s.sid), s.expire)
|
||||
return err
|
||||
}
|
||||
|
||||
// Flush deletes all session data.
|
||||
func (s *LedisSessionStore) Flush() error {
|
||||
func (s *LedisStore) Flush() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
@@ -89,30 +103,54 @@ func (s *LedisSessionStore) Flush() error {
|
||||
|
||||
// LedisProvider represents a ledis session provider implementation.
|
||||
type LedisProvider struct {
|
||||
maxlifetime int64
|
||||
savePath string
|
||||
c *ledis.DB
|
||||
expire int64
|
||||
}
|
||||
|
||||
// Init initializes memory session provider.
|
||||
func (p *LedisProvider) Init(maxlifetime int64, savePath string) error {
|
||||
p.maxlifetime = maxlifetime
|
||||
p.savePath = savePath
|
||||
cfg := new(config.Config)
|
||||
cfg.DataDir = p.savePath
|
||||
var err error
|
||||
nowLedis, err := ledis.Open(cfg)
|
||||
c, err = nowLedis.Select(0)
|
||||
// Init initializes ledis session provider.
|
||||
// configs: data_dir=./app.db,db=0
|
||||
func (p *LedisProvider) Init(expire int64, configs string) error {
|
||||
p.expire = expire
|
||||
|
||||
cfg, err := ini.Load([]byte(strings.Replace(configs, ",", "\n", -1)))
|
||||
if err != nil {
|
||||
println(err)
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
db := 0
|
||||
opt := new(config.Config)
|
||||
for k, v := range cfg.Section("").KeysHash() {
|
||||
switch k {
|
||||
case "data_dir":
|
||||
opt.DataDir = v
|
||||
case "db":
|
||||
db = com.StrTo(v).MustInt()
|
||||
default:
|
||||
return fmt.Errorf("session/ledis: unsupported option '%s'", k)
|
||||
}
|
||||
}
|
||||
|
||||
l, err := ledis.Open(opt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("session/ledis: error opening db: %v", err)
|
||||
}
|
||||
p.c, err = l.Select(db)
|
||||
return err
|
||||
}
|
||||
|
||||
// Read returns raw session store by session ID.
|
||||
func (p *LedisProvider) Read(sid string) (session.RawStore, error) {
|
||||
kvs, err := c.Get([]byte(sid))
|
||||
if !p.Exist(sid) {
|
||||
if err := p.c.Set([]byte(sid), []byte("")); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var kv map[interface{}]interface{}
|
||||
kvs, err := p.c.Get([]byte(sid))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(kvs) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
@@ -121,41 +159,40 @@ func (p *LedisProvider) Read(sid string) (session.RawStore, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
ls := &LedisSessionStore{sid: sid, data: kv, maxlifetime: p.maxlifetime}
|
||||
return ls, nil
|
||||
|
||||
return NewLedisStore(p.c, sid, p.expire, kv), nil
|
||||
}
|
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (p *LedisProvider) Exist(sid string) bool {
|
||||
count, _ := c.Exists([]byte(sid))
|
||||
if count == 0 {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
count, err := p.c.Exists([]byte(sid))
|
||||
return err == nil && count > 0
|
||||
}
|
||||
|
||||
// Destory deletes a session by session ID.
|
||||
func (p *LedisProvider) Destory(sid string) error {
|
||||
_, err := c.Del([]byte(sid))
|
||||
_, err := p.c.Del([]byte(sid))
|
||||
return err
|
||||
}
|
||||
|
||||
// Regenerate regenerates a session store from old session ID to new one.
|
||||
func (p *LedisProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
|
||||
count, _ := c.Exists([]byte(sid))
|
||||
if count == 0 {
|
||||
// oldsid doesn't exists, set the new sid directly
|
||||
// ignore error here, since if it return error
|
||||
// the existed value will be 0
|
||||
c.Set([]byte(sid), []byte(""))
|
||||
c.Expire([]byte(sid), p.maxlifetime)
|
||||
} else {
|
||||
data, _ := c.Get([]byte(oldsid))
|
||||
c.Set([]byte(sid), data)
|
||||
c.Expire([]byte(sid), p.maxlifetime)
|
||||
func (p *LedisProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
|
||||
if p.Exist(sid) {
|
||||
return nil, fmt.Errorf("new sid '%s' already exists", sid)
|
||||
}
|
||||
kvs, err := c.Get([]byte(sid))
|
||||
|
||||
kvs := make([]byte, 0)
|
||||
if p.Exist(oldsid) {
|
||||
if kvs, err = p.c.Get([]byte(oldsid)); err != nil {
|
||||
return nil, err
|
||||
} else if _, err = p.c.Del([]byte(oldsid)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err = p.c.SetEX([]byte(sid), p.expire, kvs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var kv map[interface{}]interface{}
|
||||
if len(kvs) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
@@ -165,18 +202,20 @@ func (p *LedisProvider) Regenerate(oldsid, sid string) (session.RawStore, error)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
ls := &LedisSessionStore{sid: sid, data: kv, maxlifetime: p.maxlifetime}
|
||||
return ls, nil
|
||||
|
||||
return NewLedisStore(p.c, sid, p.expire, kv), nil
|
||||
}
|
||||
|
||||
// Count counts and returns number of sessions.
|
||||
func (p *LedisProvider) Count() int {
|
||||
// FIXME
|
||||
return 0
|
||||
// FIXME: how come this library does not have DbSize() method?
|
||||
return -1
|
||||
}
|
||||
|
||||
// GC calls GC to clean expired sessions.
|
||||
func (p *LedisProvider) GC() {}
|
||||
func (p *LedisProvider) GC() {
|
||||
// FIXME: wtf???
|
||||
}
|
||||
|
||||
func init() {
|
||||
session.Register("ledis", &LedisProvider{})
|
||||
|
||||
1
Godeps/_workspace/src/github.com/macaron-contrib/session/ledis/ledis.goconvey
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/macaron-contrib/session/ledis/ledis.goconvey
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ignore
|
||||
105
Godeps/_workspace/src/github.com/macaron-contrib/session/ledis/ledis_test.go
generated
vendored
Normal file
105
Godeps/_workspace/src/github.com/macaron-contrib/session/ledis/ledis_test.go
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/macaron-contrib/session"
|
||||
)
|
||||
|
||||
func Test_LedisProvider(t *testing.T) {
|
||||
Convey("Test ledis session provider", t, func() {
|
||||
opt := session.Options{
|
||||
Provider: "ledis",
|
||||
ProviderConfig: "data_dir=./tmp.db",
|
||||
}
|
||||
|
||||
Convey("Basic operation", func() {
|
||||
m := macaron.New()
|
||||
m.Use(session.Sessioner(opt))
|
||||
|
||||
m.Get("/", func(ctx *macaron.Context, sess session.Store) {
|
||||
sess.Set("uname", "unknwon")
|
||||
})
|
||||
m.Get("/reg", func(ctx *macaron.Context, sess session.Store) {
|
||||
raw, err := sess.RegenerateId(ctx)
|
||||
So(err, ShouldBeNil)
|
||||
So(raw, ShouldNotBeNil)
|
||||
|
||||
uname := raw.Get("uname")
|
||||
So(uname, ShouldNotBeNil)
|
||||
So(uname, ShouldEqual, "unknwon")
|
||||
})
|
||||
m.Get("/get", func(ctx *macaron.Context, sess session.Store) {
|
||||
sid := sess.ID()
|
||||
So(sid, ShouldNotBeEmpty)
|
||||
|
||||
raw, err := sess.Read(sid)
|
||||
So(err, ShouldBeNil)
|
||||
So(raw, ShouldNotBeNil)
|
||||
|
||||
uname := sess.Get("uname")
|
||||
So(uname, ShouldNotBeNil)
|
||||
So(uname, ShouldEqual, "unknwon")
|
||||
|
||||
So(sess.Delete("uname"), ShouldBeNil)
|
||||
So(sess.Get("uname"), ShouldBeNil)
|
||||
|
||||
So(sess.Destory(ctx), ShouldBeNil)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
cookie := resp.Header().Get("Set-Cookie")
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/reg", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req.Header.Set("Cookie", cookie)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
cookie = resp.Header().Get("Set-Cookie")
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/get", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req.Header.Set("Cookie", cookie)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
Convey("Regenrate empty session", func() {
|
||||
m.Get("/empty", func(ctx *macaron.Context, sess session.Store) {
|
||||
raw, err := sess.RegenerateId(ctx)
|
||||
So(err, ShouldBeNil)
|
||||
So(raw, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/empty", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req.Header.Set("Cookie", "MacaronSession=ad2c7e3cbecfcf486; Path=/;")
|
||||
m.ServeHTTP(resp, req)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
210
Godeps/_workspace/src/github.com/macaron-contrib/session/memcache/memcache.go
generated
vendored
210
Godeps/_workspace/src/github.com/macaron-contrib/session/memcache/memcache.go
generated
vendored
@@ -16,6 +16,7 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -24,20 +25,35 @@ import (
|
||||
"github.com/macaron-contrib/session"
|
||||
)
|
||||
|
||||
var (
|
||||
client *memcache.Client
|
||||
)
|
||||
// MemcacheStore represents a memcache session store implementation.
|
||||
type MemcacheStore struct {
|
||||
c *memcache.Client
|
||||
sid string
|
||||
expire int32
|
||||
lock sync.RWMutex
|
||||
data map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// MemcacheSessionStore represents a memcache session store implementation.
|
||||
type MemcacheSessionStore struct {
|
||||
sid string
|
||||
lock sync.RWMutex
|
||||
data map[interface{}]interface{}
|
||||
maxlifetime int64
|
||||
// NewMemcacheStore creates and returns a memcache session store.
|
||||
func NewMemcacheStore(c *memcache.Client, sid string, expire int32, kv map[interface{}]interface{}) *MemcacheStore {
|
||||
return &MemcacheStore{
|
||||
c: c,
|
||||
sid: sid,
|
||||
expire: expire,
|
||||
data: kv,
|
||||
}
|
||||
}
|
||||
|
||||
func NewItem(sid string, data []byte, expire int32) *memcache.Item {
|
||||
return &memcache.Item{
|
||||
Key: sid,
|
||||
Value: data,
|
||||
Expiration: expire,
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets value to given key in session.
|
||||
func (s *MemcacheSessionStore) Set(key, val interface{}) error {
|
||||
func (s *MemcacheStore) Set(key, val interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
@@ -46,7 +62,7 @@ func (s *MemcacheSessionStore) Set(key, val interface{}) error {
|
||||
}
|
||||
|
||||
// Get gets value by given key in session.
|
||||
func (s *MemcacheSessionStore) Get(key interface{}) interface{} {
|
||||
func (s *MemcacheStore) Get(key interface{}) interface{} {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
@@ -54,7 +70,7 @@ func (s *MemcacheSessionStore) Get(key interface{}) interface{} {
|
||||
}
|
||||
|
||||
// Delete delete a key from session.
|
||||
func (s *MemcacheSessionStore) Delete(key interface{}) error {
|
||||
func (s *MemcacheStore) Delete(key interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
@@ -63,26 +79,22 @@ func (s *MemcacheSessionStore) Delete(key interface{}) error {
|
||||
}
|
||||
|
||||
// ID returns current session ID.
|
||||
func (s *MemcacheSessionStore) ID() string {
|
||||
func (s *MemcacheStore) ID() string {
|
||||
return s.sid
|
||||
}
|
||||
|
||||
// Release releases resource and save data to provider.
|
||||
func (s *MemcacheSessionStore) Release() error {
|
||||
func (s *MemcacheStore) Release() error {
|
||||
data, err := session.EncodeGob(s.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return client.Set(&memcache.Item{
|
||||
Key: s.sid,
|
||||
Value: data,
|
||||
Expiration: int32(s.maxlifetime),
|
||||
})
|
||||
return s.c.Set(NewItem(s.sid, data, s.expire))
|
||||
}
|
||||
|
||||
// Flush deletes all session data.
|
||||
func (s *MemcacheSessionStore) Flush() error {
|
||||
func (s *MemcacheStore) Flush() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
@@ -90,41 +102,75 @@ func (s *MemcacheSessionStore) Flush() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MemProvider represents a memcache session provider implementation.
|
||||
type MemProvider struct {
|
||||
maxlifetime int64
|
||||
conninfo []string
|
||||
poolsize int
|
||||
password string
|
||||
// MemcacheProvider represents a memcache session provider implementation.
|
||||
type MemcacheProvider struct {
|
||||
c *memcache.Client
|
||||
expire int32
|
||||
}
|
||||
|
||||
// Init initializes memory session provider.
|
||||
// connStrs can be multiple connection strings separate by ;
|
||||
// e.g. 127.0.0.1:9090
|
||||
func (p *MemProvider) Init(maxlifetime int64, connStrs string) error {
|
||||
p.maxlifetime = maxlifetime
|
||||
p.conninfo = strings.Split(connStrs, ";")
|
||||
client = memcache.New(p.conninfo...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *MemProvider) connectInit() error {
|
||||
client = memcache.New(p.conninfo...)
|
||||
// Init initializes memcache session provider.
|
||||
// connStrs: 127.0.0.1:9090;127.0.0.1:9091
|
||||
func (p *MemcacheProvider) Init(expire int64, connStrs string) error {
|
||||
p.expire = int32(expire)
|
||||
p.c = memcache.New(strings.Split(connStrs, ";")...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read returns raw session store by session ID.
|
||||
func (p *MemProvider) Read(sid string) (session.RawStore, error) {
|
||||
if client == nil {
|
||||
if err := p.connectInit(); err != nil {
|
||||
func (p *MemcacheProvider) Read(sid string) (session.RawStore, error) {
|
||||
if !p.Exist(sid) {
|
||||
if err := p.c.Set(NewItem(sid, []byte(""), p.expire)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
item, err := client.Get(sid)
|
||||
var kv map[interface{}]interface{}
|
||||
item, err := p.c.Get(sid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(item.Value) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
kv, err = session.DecodeGob(item.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return NewMemcacheStore(p.c, sid, p.expire, kv), nil
|
||||
}
|
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (p *MemcacheProvider) Exist(sid string) bool {
|
||||
_, err := p.c.Get(sid)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Destory deletes a session by session ID.
|
||||
func (p *MemcacheProvider) Destory(sid string) error {
|
||||
return p.c.Delete(sid)
|
||||
}
|
||||
|
||||
// Regenerate regenerates a session store from old session ID to new one.
|
||||
func (p *MemcacheProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
|
||||
if p.Exist(sid) {
|
||||
return nil, fmt.Errorf("new sid '%s' already exists", sid)
|
||||
}
|
||||
|
||||
item := NewItem(sid, []byte(""), p.expire)
|
||||
if p.Exist(oldsid) {
|
||||
item, err = p.c.Get(oldsid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if err = p.c.Delete(oldsid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item.Key = sid
|
||||
}
|
||||
if err = p.c.Set(item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var kv map[interface{}]interface{}
|
||||
if len(item.Value) == 0 {
|
||||
@@ -136,86 +182,18 @@ func (p *MemProvider) Read(sid string) (session.RawStore, error) {
|
||||
}
|
||||
}
|
||||
|
||||
rs := &MemcacheSessionStore{sid: sid, data: kv, maxlifetime: p.maxlifetime}
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (p *MemProvider) Exist(sid string) bool {
|
||||
if client == nil {
|
||||
if err := p.connectInit(); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if item, err := client.Get(sid); err != nil || len(item.Value) == 0 {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Destory deletes a session by session ID.
|
||||
func (p *MemProvider) Destory(sid string) error {
|
||||
if client == nil {
|
||||
if err := p.connectInit(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return client.Delete(sid)
|
||||
}
|
||||
|
||||
// Regenerate regenerates a session store from old session ID to new one.
|
||||
func (p *MemProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
|
||||
if client == nil {
|
||||
if err := p.connectInit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var contain []byte
|
||||
if item, err := client.Get(sid); err != nil || len(item.Value) == 0 {
|
||||
// oldsid doesn't exists, set the new sid directly
|
||||
// ignore error here, since if it return error
|
||||
// the existed value will be 0
|
||||
item.Key = sid
|
||||
item.Value = []byte("")
|
||||
item.Expiration = int32(p.maxlifetime)
|
||||
client.Set(item)
|
||||
} else {
|
||||
client.Delete(oldsid)
|
||||
item.Key = sid
|
||||
item.Value = item.Value
|
||||
item.Expiration = int32(p.maxlifetime)
|
||||
client.Set(item)
|
||||
contain = item.Value
|
||||
}
|
||||
|
||||
var kv map[interface{}]interface{}
|
||||
if len(contain) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
var err error
|
||||
kv, err = session.DecodeGob(contain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
rs := &MemcacheSessionStore{sid: sid, data: kv, maxlifetime: p.maxlifetime}
|
||||
return rs, nil
|
||||
return NewMemcacheStore(p.c, sid, p.expire, kv), nil
|
||||
}
|
||||
|
||||
// Count counts and returns number of sessions.
|
||||
func (p *MemProvider) Count() int {
|
||||
// FIXME
|
||||
return 0
|
||||
func (p *MemcacheProvider) Count() int {
|
||||
// FIXME: how come this library does not have Stats method?
|
||||
return -1
|
||||
}
|
||||
|
||||
// GC calls GC to clean expired sessions.
|
||||
func (p *MemProvider) GC() {}
|
||||
func (p *MemcacheProvider) GC() {}
|
||||
|
||||
func init() {
|
||||
session.Register("memcache", &MemProvider{})
|
||||
session.Register("memcache", &MemcacheProvider{})
|
||||
}
|
||||
|
||||
1
Godeps/_workspace/src/github.com/macaron-contrib/session/memcache/memcache.goconvey
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/macaron-contrib/session/memcache/memcache.goconvey
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ignore
|
||||
107
Godeps/_workspace/src/github.com/macaron-contrib/session/memcache/memcache_test.go
generated
vendored
Normal file
107
Godeps/_workspace/src/github.com/macaron-contrib/session/memcache/memcache_test.go
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/macaron-contrib/session"
|
||||
)
|
||||
|
||||
func Test_MemcacheProvider(t *testing.T) {
|
||||
Convey("Test memcache session provider", t, func() {
|
||||
opt := session.Options{
|
||||
Provider: "memcache",
|
||||
ProviderConfig: "127.0.0.1:9090",
|
||||
}
|
||||
|
||||
Convey("Basic operation", func() {
|
||||
m := macaron.New()
|
||||
m.Use(session.Sessioner(opt))
|
||||
|
||||
m.Get("/", func(ctx *macaron.Context, sess session.Store) {
|
||||
sess.Set("uname", "unknwon")
|
||||
})
|
||||
m.Get("/reg", func(ctx *macaron.Context, sess session.Store) {
|
||||
raw, err := sess.RegenerateId(ctx)
|
||||
So(err, ShouldBeNil)
|
||||
So(raw, ShouldNotBeNil)
|
||||
|
||||
uname := raw.Get("uname")
|
||||
So(uname, ShouldNotBeNil)
|
||||
So(uname, ShouldEqual, "unknwon")
|
||||
})
|
||||
m.Get("/get", func(ctx *macaron.Context, sess session.Store) {
|
||||
sid := sess.ID()
|
||||
So(sid, ShouldNotBeEmpty)
|
||||
|
||||
raw, err := sess.Read(sid)
|
||||
So(err, ShouldBeNil)
|
||||
So(raw, ShouldNotBeNil)
|
||||
|
||||
uname := sess.Get("uname")
|
||||
So(uname, ShouldNotBeNil)
|
||||
So(uname, ShouldEqual, "unknwon")
|
||||
|
||||
So(sess.Delete("uname"), ShouldBeNil)
|
||||
So(sess.Get("uname"), ShouldBeNil)
|
||||
|
||||
So(sess.Destory(ctx), ShouldBeNil)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
cookie := resp.Header().Get("Set-Cookie")
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/reg", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req.Header.Set("Cookie", cookie)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
cookie = resp.Header().Get("Set-Cookie")
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/get", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req.Header.Set("Cookie", cookie)
|
||||
m.ServeHTTP(resp, req)
|
||||
})
|
||||
|
||||
Convey("Regenrate empty session", func() {
|
||||
m := macaron.New()
|
||||
m.Use(session.Sessioner(opt))
|
||||
m.Get("/", func(ctx *macaron.Context, sess session.Store) {
|
||||
raw, err := sess.RegenerateId(ctx)
|
||||
So(err, ShouldBeNil)
|
||||
So(raw, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req.Header.Set("Cookie", "MacaronSession=ad2c7e3cbecfcf486; Path=/;")
|
||||
m.ServeHTTP(resp, req)
|
||||
})
|
||||
})
|
||||
}
|
||||
36
Godeps/_workspace/src/github.com/macaron-contrib/session/memory.go
generated
vendored
36
Godeps/_workspace/src/github.com/macaron-contrib/session/memory.go
generated
vendored
@@ -22,17 +22,17 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// MemSessionStore represents a in-memory session store implementation.
|
||||
type MemSessionStore struct {
|
||||
// MemStore represents a in-memory session store implementation.
|
||||
type MemStore struct {
|
||||
sid string
|
||||
lock sync.RWMutex
|
||||
data map[interface{}]interface{}
|
||||
lastAccess time.Time
|
||||
}
|
||||
|
||||
// NewMemSessionStore creates and returns a memory session store.
|
||||
func NewMemSessionStore(sid string) *MemSessionStore {
|
||||
return &MemSessionStore{
|
||||
// NewMemStore creates and returns a memory session store.
|
||||
func NewMemStore(sid string) *MemStore {
|
||||
return &MemStore{
|
||||
sid: sid,
|
||||
data: make(map[interface{}]interface{}),
|
||||
lastAccess: time.Now(),
|
||||
@@ -40,7 +40,7 @@ func NewMemSessionStore(sid string) *MemSessionStore {
|
||||
}
|
||||
|
||||
// Set sets value to given key in session.
|
||||
func (s *MemSessionStore) Set(key, val interface{}) error {
|
||||
func (s *MemStore) Set(key, val interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
@@ -49,15 +49,15 @@ func (s *MemSessionStore) Set(key, val interface{}) error {
|
||||
}
|
||||
|
||||
// Get gets value by given key in session.
|
||||
func (s *MemSessionStore) Get(key interface{}) interface{} {
|
||||
func (s *MemStore) Get(key interface{}) interface{} {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
return s.data[key]
|
||||
}
|
||||
|
||||
// Delete delete a key from session.
|
||||
func (s *MemSessionStore) Delete(key interface{}) error {
|
||||
// Delete deletes a key from session.
|
||||
func (s *MemStore) Delete(key interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
@@ -66,17 +66,17 @@ func (s *MemSessionStore) Delete(key interface{}) error {
|
||||
}
|
||||
|
||||
// ID returns current session ID.
|
||||
func (s *MemSessionStore) ID() string {
|
||||
func (s *MemStore) ID() string {
|
||||
return s.sid
|
||||
}
|
||||
|
||||
// Release releases resource and save data to provider.
|
||||
func (_ *MemSessionStore) Release() error {
|
||||
func (_ *MemStore) Release() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush deletes all session data.
|
||||
func (s *MemSessionStore) Flush() error {
|
||||
func (s *MemStore) Flush() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
@@ -105,7 +105,7 @@ func (p *MemProvider) update(sid string) error {
|
||||
defer p.lock.Unlock()
|
||||
|
||||
if e, ok := p.data[sid]; ok {
|
||||
e.Value.(*MemSessionStore).lastAccess = time.Now()
|
||||
e.Value.(*MemStore).lastAccess = time.Now()
|
||||
p.list.MoveToFront(e)
|
||||
return nil
|
||||
}
|
||||
@@ -122,14 +122,14 @@ func (p *MemProvider) Read(sid string) (_ RawStore, err error) {
|
||||
if err = p.update(sid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e.Value.(*MemSessionStore), nil
|
||||
return e.Value.(*MemStore), nil
|
||||
}
|
||||
|
||||
// Create a new session.
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
s := NewMemSessionStore(sid)
|
||||
s := NewMemStore(sid)
|
||||
p.data[sid] = p.list.PushBack(s)
|
||||
return s, nil
|
||||
}
|
||||
@@ -173,7 +173,7 @@ func (p *MemProvider) Regenerate(oldsid, sid string) (RawStore, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.(*MemSessionStore).sid = sid
|
||||
s.(*MemStore).sid = sid
|
||||
p.data[sid] = p.list.PushBack(s)
|
||||
return s, nil
|
||||
}
|
||||
@@ -193,11 +193,11 @@ func (p *MemProvider) GC() {
|
||||
break
|
||||
}
|
||||
|
||||
if (e.Value.(*MemSessionStore).lastAccess.Unix() + p.maxLifetime) < time.Now().Unix() {
|
||||
if (e.Value.(*MemStore).lastAccess.Unix() + p.maxLifetime) < time.Now().Unix() {
|
||||
p.lock.RUnlock()
|
||||
p.lock.Lock()
|
||||
p.list.Remove(e)
|
||||
delete(p.data, e.Value.(*MemSessionStore).sid)
|
||||
delete(p.data, e.Value.(*MemStore).sid)
|
||||
p.lock.Unlock()
|
||||
p.lock.RLock()
|
||||
} else {
|
||||
|
||||
148
Godeps/_workspace/src/github.com/macaron-contrib/session/mysql/mysql.go
generated
vendored
148
Godeps/_workspace/src/github.com/macaron-contrib/session/mysql/mysql.go
generated
vendored
@@ -17,6 +17,8 @@ package session
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -25,16 +27,25 @@ import (
|
||||
"github.com/macaron-contrib/session"
|
||||
)
|
||||
|
||||
// MysqlSessionStore represents a mysql session store implementation.
|
||||
type MysqlSessionStore struct {
|
||||
// MysqlStore represents a mysql session store implementation.
|
||||
type MysqlStore struct {
|
||||
c *sql.DB
|
||||
sid string
|
||||
lock sync.RWMutex
|
||||
data map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// NewMysqlStore creates and returns a mysql session store.
|
||||
func NewMysqlStore(c *sql.DB, sid string, kv map[interface{}]interface{}) *MysqlStore {
|
||||
return &MysqlStore{
|
||||
c: c,
|
||||
sid: sid,
|
||||
data: kv,
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets value to given key in session.
|
||||
func (s *MysqlSessionStore) Set(key, val interface{}) error {
|
||||
func (s *MysqlStore) Set(key, val interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
@@ -43,7 +54,7 @@ func (s *MysqlSessionStore) Set(key, val interface{}) error {
|
||||
}
|
||||
|
||||
// Get gets value by given key in session.
|
||||
func (s *MysqlSessionStore) Get(key interface{}) interface{} {
|
||||
func (s *MysqlStore) Get(key interface{}) interface{} {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
@@ -51,7 +62,7 @@ func (s *MysqlSessionStore) Get(key interface{}) interface{} {
|
||||
}
|
||||
|
||||
// Delete delete a key from session.
|
||||
func (s *MysqlSessionStore) Delete(key interface{}) error {
|
||||
func (s *MysqlStore) Delete(key interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
@@ -60,24 +71,24 @@ func (s *MysqlSessionStore) Delete(key interface{}) error {
|
||||
}
|
||||
|
||||
// ID returns current session ID.
|
||||
func (s *MysqlSessionStore) ID() string {
|
||||
func (s *MysqlStore) ID() string {
|
||||
return s.sid
|
||||
}
|
||||
|
||||
// Release releases resource and save data to provider.
|
||||
func (s *MysqlSessionStore) Release() error {
|
||||
defer s.c.Close()
|
||||
func (s *MysqlStore) Release() error {
|
||||
data, err := session.EncodeGob(s.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.c.Exec("UPDATE session set `session_data`=?, `session_expiry`=? where session_key=?",
|
||||
|
||||
_, err = s.c.Exec("UPDATE session SET data=?, expiry=? WHERE `key`=?",
|
||||
data, time.Now().Unix(), s.sid)
|
||||
return err
|
||||
}
|
||||
|
||||
// Flush deletes all session data.
|
||||
func (s *MysqlSessionStore) Flush() error {
|
||||
func (s *MysqlStore) Flush() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
@@ -87,113 +98,96 @@ func (s *MysqlSessionStore) Flush() error {
|
||||
|
||||
// MysqlProvider represents a mysql session provider implementation.
|
||||
type MysqlProvider struct {
|
||||
maxlifetime int64
|
||||
connStr string
|
||||
c *sql.DB
|
||||
expire int64
|
||||
}
|
||||
|
||||
func (p *MysqlProvider) connectInit() *sql.DB {
|
||||
db, e := sql.Open("mysql", p.connStr)
|
||||
if e != nil {
|
||||
return nil
|
||||
// Init initializes mysql session provider.
|
||||
// connStr: username:password@protocol(address)/dbname?param=value
|
||||
func (p *MysqlProvider) Init(expire int64, connStr string) (err error) {
|
||||
p.expire = expire
|
||||
|
||||
p.c, err = sql.Open("mysql", connStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
// Init initializes memory session provider.
|
||||
func (p *MysqlProvider) Init(maxlifetime int64, connStr string) error {
|
||||
p.maxlifetime = maxlifetime
|
||||
p.connStr = connStr
|
||||
return nil
|
||||
return p.c.Ping()
|
||||
}
|
||||
|
||||
// Read returns raw session store by session ID.
|
||||
func (p *MysqlProvider) Read(sid string) (session.RawStore, error) {
|
||||
c := p.connectInit()
|
||||
row := c.QueryRow("select session_data from session where session_key=?", sid)
|
||||
var sessiondata []byte
|
||||
err := row.Scan(&sessiondata)
|
||||
var data []byte
|
||||
err := p.c.QueryRow("SELECT data FROM session WHERE `key`=?", sid).Scan(&data)
|
||||
if err == sql.ErrNoRows {
|
||||
c.Exec("insert into session(`session_key`,`session_data`,`session_expiry`) values(?,?,?)",
|
||||
_, err = p.c.Exec("INSERT INTO session(`key`,data,expiry) VALUES(?,?,?)",
|
||||
sid, "", time.Now().Unix())
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var kv map[interface{}]interface{}
|
||||
if len(sessiondata) == 0 {
|
||||
if len(data) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
kv, err = session.DecodeGob(sessiondata)
|
||||
kv, err = session.DecodeGob(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
rs := &MysqlSessionStore{c: c, sid: sid, data: kv}
|
||||
return rs, nil
|
||||
|
||||
return NewMysqlStore(p.c, sid, kv), nil
|
||||
}
|
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (p *MysqlProvider) Exist(sid string) bool {
|
||||
c := p.connectInit()
|
||||
defer c.Close()
|
||||
|
||||
row := c.QueryRow("select session_data from session where session_key=?", sid)
|
||||
var sessiondata []byte
|
||||
err := row.Scan(&sessiondata)
|
||||
if err == sql.ErrNoRows {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
var data []byte
|
||||
err := p.c.QueryRow("SELECT data FROM session WHERE `key`=?", sid).Scan(&data)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
panic("session/mysql: error checking existence: " + err.Error())
|
||||
}
|
||||
return err != sql.ErrNoRows
|
||||
}
|
||||
|
||||
// Destory deletes a session by session ID.
|
||||
func (p *MysqlProvider) Destory(sid string) (err error) {
|
||||
c := p.connectInit()
|
||||
if _, err = c.Exec("DELETE FROM session where session_key=?", sid); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Close()
|
||||
func (p *MysqlProvider) Destory(sid string) error {
|
||||
_, err := p.c.Exec("DELETE FROM session WHERE `key`=?", sid)
|
||||
return err
|
||||
}
|
||||
|
||||
// Regenerate regenerates a session store from old session ID to new one.
|
||||
func (p *MysqlProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
|
||||
c := p.connectInit()
|
||||
row := c.QueryRow("select session_data from session where session_key=?", oldsid)
|
||||
var sessiondata []byte
|
||||
err := row.Scan(&sessiondata)
|
||||
if err == sql.ErrNoRows {
|
||||
c.Exec("insert into session(`session_key`,`session_data`,`session_expiry`) values(?,?,?)", oldsid, "", time.Now().Unix())
|
||||
func (p *MysqlProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
|
||||
if p.Exist(sid) {
|
||||
return nil, fmt.Errorf("new sid '%s' already exists", sid)
|
||||
}
|
||||
c.Exec("update session set `session_key`=? where session_key=?", sid, oldsid)
|
||||
var kv map[interface{}]interface{}
|
||||
if len(sessiondata) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
kv, err = session.DecodeGob(sessiondata)
|
||||
if err != nil {
|
||||
|
||||
if !p.Exist(oldsid) {
|
||||
if _, err = p.c.Exec("INSERT INTO session(`key`,data,expiry) VALUES(?,?,?)",
|
||||
oldsid, "", time.Now().Unix()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
rs := &MysqlSessionStore{c: c, sid: sid, data: kv}
|
||||
return rs, nil
|
||||
|
||||
if _, err = p.c.Exec("UPDATE session SET `key`=? WHERE `key`=?", sid, oldsid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.Read(sid)
|
||||
}
|
||||
|
||||
// Count counts and returns number of sessions.
|
||||
func (p *MysqlProvider) Count() int {
|
||||
c := p.connectInit()
|
||||
defer c.Close()
|
||||
|
||||
var total int
|
||||
err := c.QueryRow("SELECT count(*) as num from session").Scan(&total)
|
||||
if err != nil {
|
||||
return 0
|
||||
func (p *MysqlProvider) Count() (total int) {
|
||||
if err := p.c.QueryRow("SELECT COUNT(*) AS NUM FROM session").Scan(&total); err != nil {
|
||||
panic("session/mysql: error counting records: " + err.Error())
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// GC calls GC to clean expired sessions.
|
||||
func (mp *MysqlProvider) GC() {
|
||||
c := mp.connectInit()
|
||||
c.Exec("DELETE from session where session_expiry < ?", time.Now().Unix()-mp.maxlifetime)
|
||||
c.Close()
|
||||
func (p *MysqlProvider) GC() {
|
||||
if _, err := p.c.Exec("DELETE FROM session WHERE UNIX_TIMESTAMP(NOW()) - expiry > ?", p.expire); err != nil {
|
||||
log.Printf("session/mysql: error garbage collecting: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
1
Godeps/_workspace/src/github.com/macaron-contrib/session/mysql/mysql.goconvey
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/macaron-contrib/session/mysql/mysql.goconvey
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ignore
|
||||
138
Godeps/_workspace/src/github.com/macaron-contrib/session/mysql/mysql_test.go
generated
vendored
Normal file
138
Godeps/_workspace/src/github.com/macaron-contrib/session/mysql/mysql_test.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/macaron-contrib/session"
|
||||
)
|
||||
|
||||
func Test_MysqlProvider(t *testing.T) {
|
||||
Convey("Test mysql session provider", t, func() {
|
||||
opt := session.Options{
|
||||
Provider: "mysql",
|
||||
ProviderConfig: "root:@tcp(localhost:3306)/macaron?charset=utf8",
|
||||
}
|
||||
|
||||
Convey("Basic operation", func() {
|
||||
m := macaron.New()
|
||||
m.Use(session.Sessioner(opt))
|
||||
|
||||
m.Get("/", func(ctx *macaron.Context, sess session.Store) {
|
||||
sess.Set("uname", "unknwon")
|
||||
})
|
||||
m.Get("/reg", func(ctx *macaron.Context, sess session.Store) {
|
||||
raw, err := sess.RegenerateId(ctx)
|
||||
So(err, ShouldBeNil)
|
||||
So(raw, ShouldNotBeNil)
|
||||
|
||||
uname := raw.Get("uname")
|
||||
So(uname, ShouldNotBeNil)
|
||||
So(uname, ShouldEqual, "unknwon")
|
||||
})
|
||||
m.Get("/get", func(ctx *macaron.Context, sess session.Store) {
|
||||
sid := sess.ID()
|
||||
So(sid, ShouldNotBeEmpty)
|
||||
|
||||
raw, err := sess.Read(sid)
|
||||
So(err, ShouldBeNil)
|
||||
So(raw, ShouldNotBeNil)
|
||||
So(raw.Release(), ShouldBeNil)
|
||||
|
||||
uname := sess.Get("uname")
|
||||
So(uname, ShouldNotBeNil)
|
||||
So(uname, ShouldEqual, "unknwon")
|
||||
|
||||
So(sess.Delete("uname"), ShouldBeNil)
|
||||
So(sess.Get("uname"), ShouldBeNil)
|
||||
|
||||
So(sess.Destory(ctx), ShouldBeNil)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
cookie := resp.Header().Get("Set-Cookie")
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/reg", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req.Header.Set("Cookie", cookie)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
cookie = resp.Header().Get("Set-Cookie")
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/get", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req.Header.Set("Cookie", cookie)
|
||||
m.ServeHTTP(resp, req)
|
||||
})
|
||||
|
||||
Convey("Regenrate empty session", func() {
|
||||
m := macaron.New()
|
||||
m.Use(session.Sessioner(opt))
|
||||
m.Get("/", func(ctx *macaron.Context, sess session.Store) {
|
||||
raw, err := sess.RegenerateId(ctx)
|
||||
So(err, ShouldBeNil)
|
||||
So(raw, ShouldNotBeNil)
|
||||
|
||||
So(sess.Destory(ctx), ShouldBeNil)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req.Header.Set("Cookie", "MacaronSession=ad2c7e3cbecfcf48; Path=/;")
|
||||
m.ServeHTTP(resp, req)
|
||||
})
|
||||
|
||||
Convey("GC session", func() {
|
||||
m := macaron.New()
|
||||
opt2 := opt
|
||||
opt2.Gclifetime = 1
|
||||
m.Use(session.Sessioner(opt2))
|
||||
|
||||
m.Get("/", func(sess session.Store) {
|
||||
sess.Set("uname", "unknwon")
|
||||
So(sess.ID(), ShouldNotBeEmpty)
|
||||
uname := sess.Get("uname")
|
||||
So(uname, ShouldNotBeNil)
|
||||
So(uname, ShouldEqual, "unknwon")
|
||||
|
||||
So(sess.Flush(), ShouldBeNil)
|
||||
So(sess.Get("uname"), ShouldBeNil)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
sess.GC()
|
||||
So(sess.Count(), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
})
|
||||
})
|
||||
}
|
||||
203
Godeps/_workspace/src/github.com/macaron-contrib/session/nodb/nodb.go
generated
vendored
Normal file
203
Godeps/_workspace/src/github.com/macaron-contrib/session/nodb/nodb.go
generated
vendored
Normal file
@@ -0,0 +1,203 @@
|
||||
// Copyright 2015 Unknwon
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/lunny/nodb"
|
||||
"github.com/lunny/nodb/config"
|
||||
|
||||
"github.com/macaron-contrib/session"
|
||||
)
|
||||
|
||||
// NodbStore represents a nodb session store implementation.
|
||||
type NodbStore struct {
|
||||
c *nodb.DB
|
||||
sid string
|
||||
expire int64
|
||||
lock sync.RWMutex
|
||||
data map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// NewNodbStore creates and returns a ledis session store.
|
||||
func NewNodbStore(c *nodb.DB, sid string, expire int64, kv map[interface{}]interface{}) *NodbStore {
|
||||
return &NodbStore{
|
||||
c: c,
|
||||
expire: expire,
|
||||
sid: sid,
|
||||
data: kv,
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets value to given key in session.
|
||||
func (s *NodbStore) Set(key, val interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data[key] = val
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get gets value by given key in session.
|
||||
func (s *NodbStore) Get(key interface{}) interface{} {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
return s.data[key]
|
||||
}
|
||||
|
||||
// Delete delete a key from session.
|
||||
func (s *NodbStore) Delete(key interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
delete(s.data, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID returns current session ID.
|
||||
func (s *NodbStore) ID() string {
|
||||
return s.sid
|
||||
}
|
||||
|
||||
// Release releases resource and save data to provider.
|
||||
func (s *NodbStore) Release() error {
|
||||
data, err := session.EncodeGob(s.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = s.c.Set([]byte(s.sid), data); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.c.Expire([]byte(s.sid), s.expire)
|
||||
return err
|
||||
}
|
||||
|
||||
// Flush deletes all session data.
|
||||
func (s *NodbStore) Flush() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data = make(map[interface{}]interface{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// NodbProvider represents a ledis session provider implementation.
|
||||
type NodbProvider struct {
|
||||
c *nodb.DB
|
||||
expire int64
|
||||
}
|
||||
|
||||
// Init initializes nodb session provider.
|
||||
func (p *NodbProvider) Init(expire int64, configs string) error {
|
||||
p.expire = expire
|
||||
|
||||
cfg := new(config.Config)
|
||||
cfg.DataDir = configs
|
||||
dbs, err := nodb.Open(cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("session/nodb: error opening db: %v", err)
|
||||
}
|
||||
|
||||
p.c, err = dbs.Select(0)
|
||||
return err
|
||||
}
|
||||
|
||||
// Read returns raw session store by session ID.
|
||||
func (p *NodbProvider) Read(sid string) (session.RawStore, error) {
|
||||
if !p.Exist(sid) {
|
||||
if err := p.c.Set([]byte(sid), []byte("")); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var kv map[interface{}]interface{}
|
||||
kvs, err := p.c.Get([]byte(sid))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(kvs) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
kv, err = session.DecodeGob(kvs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return NewNodbStore(p.c, sid, p.expire, kv), nil
|
||||
}
|
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (p *NodbProvider) Exist(sid string) bool {
|
||||
count, err := p.c.Exists([]byte(sid))
|
||||
return err == nil && count > 0
|
||||
}
|
||||
|
||||
// Destory deletes a session by session ID.
|
||||
func (p *NodbProvider) Destory(sid string) error {
|
||||
_, err := p.c.Del([]byte(sid))
|
||||
return err
|
||||
}
|
||||
|
||||
// Regenerate regenerates a session store from old session ID to new one.
|
||||
func (p *NodbProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
|
||||
if p.Exist(sid) {
|
||||
return nil, fmt.Errorf("new sid '%s' already exists", sid)
|
||||
}
|
||||
|
||||
kvs := make([]byte, 0)
|
||||
if p.Exist(oldsid) {
|
||||
if kvs, err = p.c.Get([]byte(oldsid)); err != nil {
|
||||
return nil, err
|
||||
} else if _, err = p.c.Del([]byte(oldsid)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err = p.c.Set([]byte(sid), kvs); err != nil {
|
||||
return nil, err
|
||||
} else if _, err = p.c.Expire([]byte(sid), p.expire); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var kv map[interface{}]interface{}
|
||||
if len(kvs) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
kv, err = session.DecodeGob([]byte(kvs))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return NewNodbStore(p.c, sid, p.expire, kv), nil
|
||||
}
|
||||
|
||||
// Count counts and returns number of sessions.
|
||||
func (p *NodbProvider) Count() int {
|
||||
// FIXME: how come this library does not have DbSize() method?
|
||||
return -1
|
||||
}
|
||||
|
||||
// GC calls GC to clean expired sessions.
|
||||
func (p *NodbProvider) GC() {}
|
||||
|
||||
func init() {
|
||||
session.Register("nodb", &NodbProvider{})
|
||||
}
|
||||
1
Godeps/_workspace/src/github.com/macaron-contrib/session/nodb/nodb.goconvey
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/macaron-contrib/session/nodb/nodb.goconvey
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ignore
|
||||
105
Godeps/_workspace/src/github.com/macaron-contrib/session/nodb/nodb_test.go
generated
vendored
Normal file
105
Godeps/_workspace/src/github.com/macaron-contrib/session/nodb/nodb_test.go
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright 2015 Unknwon
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/macaron-contrib/session"
|
||||
)
|
||||
|
||||
func Test_LedisProvider(t *testing.T) {
|
||||
Convey("Test nodb session provider", t, func() {
|
||||
opt := session.Options{
|
||||
Provider: "nodb",
|
||||
ProviderConfig: "./tmp.db",
|
||||
}
|
||||
|
||||
Convey("Basic operation", func() {
|
||||
m := macaron.New()
|
||||
m.Use(session.Sessioner(opt))
|
||||
|
||||
m.Get("/", func(ctx *macaron.Context, sess session.Store) {
|
||||
sess.Set("uname", "unknwon")
|
||||
})
|
||||
m.Get("/reg", func(ctx *macaron.Context, sess session.Store) {
|
||||
raw, err := sess.RegenerateId(ctx)
|
||||
So(err, ShouldBeNil)
|
||||
So(raw, ShouldNotBeNil)
|
||||
|
||||
uname := raw.Get("uname")
|
||||
So(uname, ShouldNotBeNil)
|
||||
So(uname, ShouldEqual, "unknwon")
|
||||
})
|
||||
m.Get("/get", func(ctx *macaron.Context, sess session.Store) {
|
||||
sid := sess.ID()
|
||||
So(sid, ShouldNotBeEmpty)
|
||||
|
||||
raw, err := sess.Read(sid)
|
||||
So(err, ShouldBeNil)
|
||||
So(raw, ShouldNotBeNil)
|
||||
|
||||
uname := sess.Get("uname")
|
||||
So(uname, ShouldNotBeNil)
|
||||
So(uname, ShouldEqual, "unknwon")
|
||||
|
||||
So(sess.Delete("uname"), ShouldBeNil)
|
||||
So(sess.Get("uname"), ShouldBeNil)
|
||||
|
||||
So(sess.Destory(ctx), ShouldBeNil)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
cookie := resp.Header().Get("Set-Cookie")
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/reg", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req.Header.Set("Cookie", cookie)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
cookie = resp.Header().Get("Set-Cookie")
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/get", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req.Header.Set("Cookie", cookie)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
Convey("Regenrate empty session", func() {
|
||||
m.Get("/empty", func(ctx *macaron.Context, sess session.Store) {
|
||||
raw, err := sess.RegenerateId(ctx)
|
||||
So(err, ShouldBeNil)
|
||||
So(raw, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/empty", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req.Header.Set("Cookie", "MacaronSession=ad2c7e3cbecfcf486; Path=/;")
|
||||
m.ServeHTTP(resp, req)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
196
Godeps/_workspace/src/github.com/macaron-contrib/session/postgres/postgres.go
generated
vendored
Normal file
196
Godeps/_workspace/src/github.com/macaron-contrib/session/postgres/postgres.go
generated
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
|
||||
"github.com/macaron-contrib/session"
|
||||
)
|
||||
|
||||
// PostgresStore represents a postgres session store implementation.
|
||||
type PostgresStore struct {
|
||||
c *sql.DB
|
||||
sid string
|
||||
lock sync.RWMutex
|
||||
data map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// NewPostgresStore creates and returns a postgres session store.
|
||||
func NewPostgresStore(c *sql.DB, sid string, kv map[interface{}]interface{}) *PostgresStore {
|
||||
return &PostgresStore{
|
||||
c: c,
|
||||
sid: sid,
|
||||
data: kv,
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets value to given key in session.
|
||||
func (s *PostgresStore) Set(key, value interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data[key] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get gets value by given key in session.
|
||||
func (s *PostgresStore) Get(key interface{}) interface{} {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
return s.data[key]
|
||||
}
|
||||
|
||||
// Delete delete a key from session.
|
||||
func (s *PostgresStore) Delete(key interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
delete(s.data, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID returns current session ID.
|
||||
func (s *PostgresStore) ID() string {
|
||||
return s.sid
|
||||
}
|
||||
|
||||
// save postgres session values to database.
|
||||
// must call this method to save values to database.
|
||||
func (s *PostgresStore) Release() error {
|
||||
data, err := session.EncodeGob(s.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = s.c.Exec("UPDATE session SET data=$1, expiry=$2 WHERE key=$3",
|
||||
data, time.Now().Unix(), s.sid)
|
||||
return err
|
||||
}
|
||||
|
||||
// Flush deletes all session data.
|
||||
func (s *PostgresStore) Flush() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data = make(map[interface{}]interface{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// PostgresProvider represents a postgres session provider implementation.
|
||||
type PostgresProvider struct {
|
||||
c *sql.DB
|
||||
maxlifetime int64
|
||||
}
|
||||
|
||||
// Init initializes postgres session provider.
|
||||
// connStr: user=a password=b host=localhost port=5432 dbname=c sslmode=disable
|
||||
func (p *PostgresProvider) Init(maxlifetime int64, connStr string) (err error) {
|
||||
p.maxlifetime = maxlifetime
|
||||
|
||||
p.c, err = sql.Open("postgres", connStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.c.Ping()
|
||||
}
|
||||
|
||||
// Read returns raw session store by session ID.
|
||||
func (p *PostgresProvider) Read(sid string) (session.RawStore, error) {
|
||||
var data []byte
|
||||
err := p.c.QueryRow("SELECT data FROM session WHERE key=$1", sid).Scan(&data)
|
||||
if err == sql.ErrNoRows {
|
||||
_, err = p.c.Exec("INSERT INTO session(key,data,expiry) VALUES($1,$2,$3)",
|
||||
sid, "", time.Now().Unix())
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var kv map[interface{}]interface{}
|
||||
if len(data) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
kv, err = session.DecodeGob(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return NewPostgresStore(p.c, sid, kv), nil
|
||||
}
|
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (p *PostgresProvider) Exist(sid string) bool {
|
||||
var data []byte
|
||||
err := p.c.QueryRow("SELECT data FROM session WHERE key=$1", sid).Scan(&data)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
panic("session/postgres: error checking existence: " + err.Error())
|
||||
}
|
||||
return err != sql.ErrNoRows
|
||||
}
|
||||
|
||||
// Destory deletes a session by session ID.
|
||||
func (p *PostgresProvider) Destory(sid string) error {
|
||||
_, err := p.c.Exec("DELETE FROM session WHERE key=$1", sid)
|
||||
return err
|
||||
}
|
||||
|
||||
// Regenerate regenerates a session store from old session ID to new one.
|
||||
func (p *PostgresProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
|
||||
if p.Exist(sid) {
|
||||
return nil, fmt.Errorf("new sid '%s' already exists", sid)
|
||||
}
|
||||
|
||||
if !p.Exist(oldsid) {
|
||||
if _, err = p.c.Exec("INSERT INTO session(key,data,expiry) VALUES($1,$2,$3)",
|
||||
oldsid, "", time.Now().Unix()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = p.c.Exec("UPDATE session SET key=$1 WHERE key=$2", sid, oldsid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.Read(sid)
|
||||
}
|
||||
|
||||
// Count counts and returns number of sessions.
|
||||
func (p *PostgresProvider) Count() (total int) {
|
||||
if err := p.c.QueryRow("SELECT COUNT(*) AS NUM FROM session").Scan(&total); err != nil {
|
||||
panic("session/postgres: error counting records: " + err.Error())
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// GC calls GC to clean expired sessions.
|
||||
func (p *PostgresProvider) GC() {
|
||||
if _, err := p.c.Exec("DELETE FROM session WHERE EXTRACT(EPOCH FROM NOW()) - expiry > $1", p.maxlifetime); err != nil {
|
||||
log.Printf("session/postgres: error garbage collecting: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
session.Register("postgres", &PostgresProvider{})
|
||||
}
|
||||
1
Godeps/_workspace/src/github.com/macaron-contrib/session/postgres/postgres.goconvey
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/macaron-contrib/session/postgres/postgres.goconvey
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ignore
|
||||
138
Godeps/_workspace/src/github.com/macaron-contrib/session/postgres/postgres_test.go
generated
vendored
Normal file
138
Godeps/_workspace/src/github.com/macaron-contrib/session/postgres/postgres_test.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/macaron-contrib/session"
|
||||
)
|
||||
|
||||
func Test_PostgresProvider(t *testing.T) {
|
||||
Convey("Test postgres session provider", t, func() {
|
||||
opt := session.Options{
|
||||
Provider: "postgres",
|
||||
ProviderConfig: "user=jiahuachen dbname=macaron port=5432 sslmode=disable",
|
||||
}
|
||||
|
||||
Convey("Basic operation", func() {
|
||||
m := macaron.New()
|
||||
m.Use(session.Sessioner(opt))
|
||||
|
||||
m.Get("/", func(ctx *macaron.Context, sess session.Store) {
|
||||
sess.Set("uname", "unknwon")
|
||||
})
|
||||
m.Get("/reg", func(ctx *macaron.Context, sess session.Store) {
|
||||
raw, err := sess.RegenerateId(ctx)
|
||||
So(err, ShouldBeNil)
|
||||
So(raw, ShouldNotBeNil)
|
||||
|
||||
uname := raw.Get("uname")
|
||||
So(uname, ShouldNotBeNil)
|
||||
So(uname, ShouldEqual, "unknwon")
|
||||
})
|
||||
m.Get("/get", func(ctx *macaron.Context, sess session.Store) {
|
||||
sid := sess.ID()
|
||||
So(sid, ShouldNotBeEmpty)
|
||||
|
||||
raw, err := sess.Read(sid)
|
||||
So(err, ShouldBeNil)
|
||||
So(raw, ShouldNotBeNil)
|
||||
So(raw.Release(), ShouldBeNil)
|
||||
|
||||
uname := sess.Get("uname")
|
||||
So(uname, ShouldNotBeNil)
|
||||
So(uname, ShouldEqual, "unknwon")
|
||||
|
||||
So(sess.Delete("uname"), ShouldBeNil)
|
||||
So(sess.Get("uname"), ShouldBeNil)
|
||||
|
||||
So(sess.Destory(ctx), ShouldBeNil)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
cookie := resp.Header().Get("Set-Cookie")
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/reg", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req.Header.Set("Cookie", cookie)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
cookie = resp.Header().Get("Set-Cookie")
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/get", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req.Header.Set("Cookie", cookie)
|
||||
m.ServeHTTP(resp, req)
|
||||
})
|
||||
|
||||
Convey("Regenrate empty session", func() {
|
||||
m := macaron.New()
|
||||
m.Use(session.Sessioner(opt))
|
||||
m.Get("/", func(ctx *macaron.Context, sess session.Store) {
|
||||
raw, err := sess.RegenerateId(ctx)
|
||||
So(err, ShouldBeNil)
|
||||
So(raw, ShouldNotBeNil)
|
||||
|
||||
So(sess.Destory(ctx), ShouldBeNil)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req.Header.Set("Cookie", "MacaronSession=ad2c7e3cbecfcf48; Path=/;")
|
||||
m.ServeHTTP(resp, req)
|
||||
})
|
||||
|
||||
Convey("GC session", func() {
|
||||
m := macaron.New()
|
||||
opt2 := opt
|
||||
opt2.Gclifetime = 1
|
||||
m.Use(session.Sessioner(opt2))
|
||||
|
||||
m.Get("/", func(sess session.Store) {
|
||||
sess.Set("uname", "unknwon")
|
||||
So(sess.ID(), ShouldNotBeEmpty)
|
||||
uname := sess.Get("uname")
|
||||
So(uname, ShouldNotBeNil)
|
||||
So(uname, ShouldEqual, "unknwon")
|
||||
|
||||
So(sess.Flush(), ShouldBeNil)
|
||||
So(sess.Get("uname"), ShouldBeNil)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
sess.GC()
|
||||
So(sess.Count(), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
})
|
||||
})
|
||||
}
|
||||
211
Godeps/_workspace/src/github.com/macaron-contrib/session/postgres/postgresql.go
generated
vendored
211
Godeps/_workspace/src/github.com/macaron-contrib/session/postgres/postgresql.go
generated
vendored
@@ -1,211 +0,0 @@
|
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
|
||||
"github.com/macaron-contrib/session"
|
||||
)
|
||||
|
||||
// PostgresqlSessionStore represents a postgresql session store implementation.
|
||||
type PostgresqlSessionStore struct {
|
||||
c *sql.DB
|
||||
sid string
|
||||
lock sync.RWMutex
|
||||
data map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// Set sets value to given key in session.
|
||||
func (s *PostgresqlSessionStore) Set(key, value interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data[key] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get gets value by given key in session.
|
||||
func (s *PostgresqlSessionStore) Get(key interface{}) interface{} {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
return s.data[key]
|
||||
}
|
||||
|
||||
// Delete delete a key from session.
|
||||
func (s *PostgresqlSessionStore) Delete(key interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
delete(s.data, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID returns current session ID.
|
||||
func (s *PostgresqlSessionStore) ID() string {
|
||||
return s.sid
|
||||
}
|
||||
|
||||
// save postgresql session values to database.
|
||||
// must call this method to save values to database.
|
||||
func (s *PostgresqlSessionStore) Release() error {
|
||||
defer s.c.Close()
|
||||
|
||||
data, err := session.EncodeGob(s.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = s.c.Exec("UPDATE session set session_data=$1, session_expiry=$2 where session_key=$3",
|
||||
data, time.Now().Format(time.RFC3339), s.sid)
|
||||
return err
|
||||
}
|
||||
|
||||
// Flush deletes all session data.
|
||||
func (s *PostgresqlSessionStore) Flush() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data = make(map[interface{}]interface{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// PostgresqlProvider represents a postgresql session provider implementation.
|
||||
type PostgresqlProvider struct {
|
||||
maxlifetime int64
|
||||
connStr string
|
||||
}
|
||||
|
||||
func (p *PostgresqlProvider) connectInit() *sql.DB {
|
||||
db, e := sql.Open("postgres", p.connStr)
|
||||
if e != nil {
|
||||
return nil
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
// Init initializes memory session provider.
|
||||
func (p *PostgresqlProvider) Init(maxlifetime int64, connStr string) error {
|
||||
p.maxlifetime = maxlifetime
|
||||
p.connStr = connStr
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read returns raw session store by session ID.
|
||||
func (p *PostgresqlProvider) Read(sid string) (session.RawStore, error) {
|
||||
c := p.connectInit()
|
||||
row := c.QueryRow("select session_data from session where session_key=$1", sid)
|
||||
var sessiondata []byte
|
||||
err := row.Scan(&sessiondata)
|
||||
if err == sql.ErrNoRows {
|
||||
_, err = c.Exec("insert into session(session_key,session_data,session_expiry) values($1,$2,$3)",
|
||||
sid, "", time.Now().Format(time.RFC3339))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var kv map[interface{}]interface{}
|
||||
if len(sessiondata) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
kv, err = session.DecodeGob(sessiondata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
rs := &PostgresqlSessionStore{c: c, sid: sid, data: kv}
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (p *PostgresqlProvider) Exist(sid string) bool {
|
||||
c := p.connectInit()
|
||||
defer c.Close()
|
||||
row := c.QueryRow("select session_data from session where session_key=$1", sid)
|
||||
var sessiondata []byte
|
||||
err := row.Scan(&sessiondata)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Destory deletes a session by session ID.
|
||||
func (p *PostgresqlProvider) Destory(sid string) (err error) {
|
||||
c := p.connectInit()
|
||||
if _, err = c.Exec("DELETE FROM session where session_key=$1", sid); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Close()
|
||||
}
|
||||
|
||||
// Regenerate regenerates a session store from old session ID to new one.
|
||||
func (p *PostgresqlProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
|
||||
c := p.connectInit()
|
||||
row := c.QueryRow("select session_data from session where session_key=$1", oldsid)
|
||||
var sessiondata []byte
|
||||
err := row.Scan(&sessiondata)
|
||||
if err == sql.ErrNoRows {
|
||||
c.Exec("insert into session(session_key,session_data,session_expiry) values($1,$2,$3)",
|
||||
oldsid, "", time.Now().Format(time.RFC3339))
|
||||
}
|
||||
c.Exec("update session set session_key=$1 where session_key=$2", sid, oldsid)
|
||||
var kv map[interface{}]interface{}
|
||||
if len(sessiondata) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
kv, err = session.DecodeGob(sessiondata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
rs := &PostgresqlSessionStore{c: c, sid: sid, data: kv}
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
// Count counts and returns number of sessions.
|
||||
func (p *PostgresqlProvider) Count() int {
|
||||
c := p.connectInit()
|
||||
defer c.Close()
|
||||
var total int
|
||||
err := c.QueryRow("SELECT count(*) as num from session").Scan(&total)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// GC calls GC to clean expired sessions.
|
||||
func (mp *PostgresqlProvider) GC() {
|
||||
c := mp.connectInit()
|
||||
c.Exec("DELETE from session where EXTRACT(EPOCH FROM (current_timestamp - session_expiry)) > $1", mp.maxlifetime)
|
||||
c.Close()
|
||||
}
|
||||
|
||||
func init() {
|
||||
session.Register("postgresql", &PostgresqlProvider{})
|
||||
}
|
||||
199
Godeps/_workspace/src/github.com/macaron-contrib/session/redis/redis.go
generated
vendored
199
Godeps/_workspace/src/github.com/macaron-contrib/session/redis/redis.go
generated
vendored
@@ -16,31 +16,39 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/beego/redigo/redis"
|
||||
"github.com/Unknwon/com"
|
||||
"gopkg.in/ini.v1"
|
||||
"gopkg.in/redis.v2"
|
||||
|
||||
"github.com/macaron-contrib/session"
|
||||
)
|
||||
|
||||
// redis max pool size
|
||||
var MAX_POOL_SIZE = 100
|
||||
// RedisStore represents a redis session store implementation.
|
||||
type RedisStore struct {
|
||||
c *redis.Client
|
||||
sid string
|
||||
duration time.Duration
|
||||
lock sync.RWMutex
|
||||
data map[interface{}]interface{}
|
||||
}
|
||||
|
||||
var redisPool chan redis.Conn
|
||||
|
||||
// RedisSessionStore represents a redis session store implementation.
|
||||
type RedisSessionStore struct {
|
||||
p *redis.Pool
|
||||
sid string
|
||||
lock sync.RWMutex
|
||||
data map[interface{}]interface{}
|
||||
maxlifetime int64
|
||||
// NewRedisStore creates and returns a redis session store.
|
||||
func NewRedisStore(c *redis.Client, sid string, dur time.Duration, kv map[interface{}]interface{}) *RedisStore {
|
||||
return &RedisStore{
|
||||
c: c,
|
||||
sid: sid,
|
||||
duration: dur,
|
||||
data: kv,
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets value to given key in session.
|
||||
func (s *RedisSessionStore) Set(key, val interface{}) error {
|
||||
func (s *RedisStore) Set(key, val interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
@@ -49,7 +57,7 @@ func (s *RedisSessionStore) Set(key, val interface{}) error {
|
||||
}
|
||||
|
||||
// Get gets value by given key in session.
|
||||
func (s *RedisSessionStore) Get(key interface{}) interface{} {
|
||||
func (s *RedisStore) Get(key interface{}) interface{} {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
@@ -57,7 +65,7 @@ func (s *RedisSessionStore) Get(key interface{}) interface{} {
|
||||
}
|
||||
|
||||
// Delete delete a key from session.
|
||||
func (s *RedisSessionStore) Delete(key interface{}) error {
|
||||
func (s *RedisStore) Delete(key interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
@@ -66,26 +74,22 @@ func (s *RedisSessionStore) Delete(key interface{}) error {
|
||||
}
|
||||
|
||||
// ID returns current session ID.
|
||||
func (s *RedisSessionStore) ID() string {
|
||||
func (s *RedisStore) ID() string {
|
||||
return s.sid
|
||||
}
|
||||
|
||||
// Release releases resource and save data to provider.
|
||||
func (s *RedisSessionStore) Release() error {
|
||||
c := s.p.Get()
|
||||
defer c.Close()
|
||||
|
||||
func (s *RedisStore) Release() error {
|
||||
data, err := session.EncodeGob(s.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.Do("SETEX", s.sid, s.maxlifetime, string(data))
|
||||
return err
|
||||
return s.c.SetEx(s.sid, s.duration, string(data)).Err()
|
||||
}
|
||||
|
||||
// Flush deletes all session data.
|
||||
func (s *RedisSessionStore) Flush() error {
|
||||
func (s *RedisStore) Flush() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
@@ -95,59 +99,65 @@ func (s *RedisSessionStore) Flush() error {
|
||||
|
||||
// RedisProvider represents a redis session provider implementation.
|
||||
type RedisProvider struct {
|
||||
maxlifetime int64
|
||||
connAddr string
|
||||
poolsize int
|
||||
password string
|
||||
poollist *redis.Pool
|
||||
c *redis.Client
|
||||
duration time.Duration
|
||||
}
|
||||
|
||||
// Init initializes memory session provider.
|
||||
// connStr: <redis server addr>,<pool size>,<password>
|
||||
// e.g. 127.0.0.1:6379,100,macaron
|
||||
func (p *RedisProvider) Init(maxlifetime int64, connStr string) error {
|
||||
p.maxlifetime = maxlifetime
|
||||
configs := strings.Split(connStr, ",")
|
||||
if len(configs) > 0 {
|
||||
p.connAddr = configs[0]
|
||||
// Init initializes redis session provider.
|
||||
// configs: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180
|
||||
func (p *RedisProvider) Init(maxlifetime int64, configs string) (err error) {
|
||||
p.duration, err = time.ParseDuration(fmt.Sprintf("%ds", maxlifetime))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(configs) > 1 {
|
||||
poolsize, err := strconv.Atoi(configs[1])
|
||||
if err != nil || poolsize <= 0 {
|
||||
p.poolsize = MAX_POOL_SIZE
|
||||
} else {
|
||||
p.poolsize = poolsize
|
||||
}
|
||||
} else {
|
||||
p.poolsize = MAX_POOL_SIZE
|
||||
}
|
||||
if len(configs) > 2 {
|
||||
p.password = configs[2]
|
||||
}
|
||||
p.poollist = redis.NewPool(func() (redis.Conn, error) {
|
||||
c, err := redis.Dial("tcp", p.connAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p.password != "" {
|
||||
if _, err := c.Do("AUTH", p.password); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return c, err
|
||||
}, p.poolsize)
|
||||
|
||||
return p.poollist.Get().Err()
|
||||
cfg, err := ini.Load([]byte(strings.Replace(configs, ",", "\n", -1)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opt := &redis.Options{
|
||||
Network: "tcp",
|
||||
}
|
||||
for k, v := range cfg.Section("").KeysHash() {
|
||||
switch k {
|
||||
case "network":
|
||||
opt.Network = v
|
||||
case "addr":
|
||||
opt.Addr = v
|
||||
case "password":
|
||||
opt.Password = v
|
||||
case "db":
|
||||
opt.DB = com.StrTo(v).MustInt64()
|
||||
case "pool_size":
|
||||
opt.PoolSize = com.StrTo(v).MustInt()
|
||||
case "idle_timeout":
|
||||
opt.IdleTimeout, err = time.ParseDuration(v + "s")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing idle timeout: %v", err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("session/redis: unsupported option '%s'", k)
|
||||
}
|
||||
}
|
||||
|
||||
p.c = redis.NewClient(opt)
|
||||
return p.c.Ping().Err()
|
||||
}
|
||||
|
||||
// Read returns raw session store by session ID.
|
||||
func (p *RedisProvider) Read(sid string) (session.RawStore, error) {
|
||||
c := p.poollist.Get()
|
||||
defer c.Close()
|
||||
if !p.Exist(sid) {
|
||||
if err := p.c.Set(sid, "").Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
kvs, err := redis.String(c.Do("GET", sid))
|
||||
var kv map[interface{}]interface{}
|
||||
kvs, err := p.c.Get(sid).Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(kvs) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
@@ -157,48 +167,41 @@ func (p *RedisProvider) Read(sid string) (session.RawStore, error) {
|
||||
}
|
||||
}
|
||||
|
||||
rs := &RedisSessionStore{p: p.poollist, sid: sid, data: kv, maxlifetime: p.maxlifetime}
|
||||
return rs, nil
|
||||
return NewRedisStore(p.c, sid, p.duration, kv), nil
|
||||
}
|
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (p *RedisProvider) Exist(sid string) bool {
|
||||
c := p.poollist.Get()
|
||||
defer c.Close()
|
||||
|
||||
if existed, err := redis.Int(c.Do("EXISTS", sid)); err != nil || existed == 0 {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
has, err := p.c.Exists(sid).Result()
|
||||
return err == nil && has
|
||||
}
|
||||
|
||||
// Destory deletes a session by session ID.
|
||||
func (p *RedisProvider) Destory(sid string) error {
|
||||
c := p.poollist.Get()
|
||||
defer c.Close()
|
||||
|
||||
_, err := c.Do("DEL", sid)
|
||||
return err
|
||||
return p.c.Del(sid).Err()
|
||||
}
|
||||
|
||||
// Regenerate regenerates a session store from old session ID to new one.
|
||||
func (p *RedisProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
|
||||
c := p.poollist.Get()
|
||||
defer c.Close()
|
||||
|
||||
if existed, _ := redis.Int(c.Do("EXISTS", oldsid)); existed == 0 {
|
||||
// oldsid doesn't exists, set the new sid directly
|
||||
// ignore error here, since if it return error
|
||||
// the existed value will be 0
|
||||
c.Do("SET", sid, "", "EX", p.maxlifetime)
|
||||
} else {
|
||||
c.Do("RENAME", oldsid, sid)
|
||||
c.Do("EXPIRE", sid, p.maxlifetime)
|
||||
func (p *RedisProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
|
||||
if p.Exist(sid) {
|
||||
return nil, fmt.Errorf("new sid '%s' already exists", sid)
|
||||
} else if !p.Exist(oldsid) {
|
||||
// Make a fake old session.
|
||||
if err = p.c.SetEx(oldsid, p.duration, "").Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err = p.c.Rename(oldsid, sid).Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kvs, err := redis.String(c.Do("GET", sid))
|
||||
var kv map[interface{}]interface{}
|
||||
kvs, err := p.c.Get(sid).Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(kvs) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
@@ -208,14 +211,12 @@ func (p *RedisProvider) Regenerate(oldsid, sid string) (session.RawStore, error)
|
||||
}
|
||||
}
|
||||
|
||||
rs := &RedisSessionStore{p: p.poollist, sid: sid, data: kv, maxlifetime: p.maxlifetime}
|
||||
return rs, nil
|
||||
return NewRedisStore(p.c, sid, p.duration, kv), nil
|
||||
}
|
||||
|
||||
// Count counts and returns number of sessions.
|
||||
func (p *RedisProvider) Count() int {
|
||||
// FIXME
|
||||
return 0
|
||||
return int(p.c.DbSize().Val())
|
||||
}
|
||||
|
||||
// GC calls GC to clean expired sessions.
|
||||
|
||||
1
Godeps/_workspace/src/github.com/macaron-contrib/session/redis/redis.goconvey
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/macaron-contrib/session/redis/redis.goconvey
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ignore
|
||||
107
Godeps/_workspace/src/github.com/macaron-contrib/session/redis/redis_test.go
generated
vendored
Normal file
107
Godeps/_workspace/src/github.com/macaron-contrib/session/redis/redis_test.go
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/macaron-contrib/session"
|
||||
)
|
||||
|
||||
func Test_RedisProvider(t *testing.T) {
|
||||
Convey("Test redis session provider", t, func() {
|
||||
opt := session.Options{
|
||||
Provider: "redis",
|
||||
ProviderConfig: "addr=:6379",
|
||||
}
|
||||
|
||||
Convey("Basic operation", func() {
|
||||
m := macaron.New()
|
||||
m.Use(session.Sessioner(opt))
|
||||
|
||||
m.Get("/", func(ctx *macaron.Context, sess session.Store) {
|
||||
sess.Set("uname", "unknwon")
|
||||
})
|
||||
m.Get("/reg", func(ctx *macaron.Context, sess session.Store) {
|
||||
raw, err := sess.RegenerateId(ctx)
|
||||
So(err, ShouldBeNil)
|
||||
So(raw, ShouldNotBeNil)
|
||||
|
||||
uname := raw.Get("uname")
|
||||
So(uname, ShouldNotBeNil)
|
||||
So(uname, ShouldEqual, "unknwon")
|
||||
})
|
||||
m.Get("/get", func(ctx *macaron.Context, sess session.Store) {
|
||||
sid := sess.ID()
|
||||
So(sid, ShouldNotBeEmpty)
|
||||
|
||||
raw, err := sess.Read(sid)
|
||||
So(err, ShouldBeNil)
|
||||
So(raw, ShouldNotBeNil)
|
||||
|
||||
uname := sess.Get("uname")
|
||||
So(uname, ShouldNotBeNil)
|
||||
So(uname, ShouldEqual, "unknwon")
|
||||
|
||||
So(sess.Delete("uname"), ShouldBeNil)
|
||||
So(sess.Get("uname"), ShouldBeNil)
|
||||
|
||||
So(sess.Destory(ctx), ShouldBeNil)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
cookie := resp.Header().Get("Set-Cookie")
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/reg", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req.Header.Set("Cookie", cookie)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
cookie = resp.Header().Get("Set-Cookie")
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/get", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req.Header.Set("Cookie", cookie)
|
||||
m.ServeHTTP(resp, req)
|
||||
})
|
||||
|
||||
Convey("Regenrate empty session", func() {
|
||||
m := macaron.New()
|
||||
m.Use(session.Sessioner(opt))
|
||||
m.Get("/", func(ctx *macaron.Context, sess session.Store) {
|
||||
raw, err := sess.RegenerateId(ctx)
|
||||
So(err, ShouldBeNil)
|
||||
So(raw, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req.Header.Set("Cookie", "MacaronSession=ad2c7e3cbecfcf486; Path=/;")
|
||||
m.ServeHTTP(resp, req)
|
||||
})
|
||||
})
|
||||
}
|
||||
44
Godeps/_workspace/src/github.com/macaron-contrib/session/session.go
generated
vendored
44
Godeps/_workspace/src/github.com/macaron-contrib/session/session.go
generated
vendored
@@ -13,7 +13,7 @@
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
// Package session a middleware that provides the session manager of Macaron.
|
||||
// Package session a middleware that provides the session management of Macaron.
|
||||
package session
|
||||
|
||||
// NOTE: last sync 000033e on Nov 4, 2014.
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
"github.com/Unknwon/macaron"
|
||||
)
|
||||
|
||||
const _VERSION = "0.1.1"
|
||||
const _VERSION = "0.1.6"
|
||||
|
||||
func Version() string {
|
||||
return _VERSION
|
||||
@@ -37,11 +37,11 @@ func Version() string {
|
||||
// RawStore is the interface that operates the session data.
|
||||
type RawStore interface {
|
||||
// Set sets value to given key in session.
|
||||
Set(key, value interface{}) error
|
||||
Set(interface{}, interface{}) error
|
||||
// Get gets value by given key in session.
|
||||
Get(key interface{}) interface{}
|
||||
// Delete delete a key from session.
|
||||
Delete(key interface{}) error
|
||||
Get(interface{}) interface{}
|
||||
// Delete deletes a key from session.
|
||||
Delete(interface{}) error
|
||||
// ID returns current session ID.
|
||||
ID() string
|
||||
// Release releases session resource and save data to provider.
|
||||
@@ -54,7 +54,7 @@ type RawStore interface {
|
||||
type Store interface {
|
||||
RawStore
|
||||
// Read returns raw session store by session ID.
|
||||
Read(sid string) (RawStore, error)
|
||||
Read(string) (RawStore, error)
|
||||
// Destory deletes a session.
|
||||
Destory(*macaron.Context) error
|
||||
// RegenerateId regenerates a session store from old session ID to new one.
|
||||
@@ -111,7 +111,7 @@ func prepareOptions(options []Options) Options {
|
||||
if len(opt.Provider) == 0 {
|
||||
opt.Provider = sec.Key("PROVIDER").MustString("memory")
|
||||
}
|
||||
if len(opt.ProviderConfig) == 0 && opt.Provider == "file" {
|
||||
if len(opt.ProviderConfig) == 0 {
|
||||
opt.ProviderConfig = sec.Key("PROVIDER_CONFIG").MustString("data/sessions")
|
||||
}
|
||||
if len(opt.CookieName) == 0 {
|
||||
@@ -155,7 +155,7 @@ func Sessioner(options ...Options) macaron.Handler {
|
||||
return func(ctx *macaron.Context) {
|
||||
sess, err := manager.Start(ctx)
|
||||
if err != nil {
|
||||
panic("session: " + err.Error())
|
||||
panic("session(start): " + err.Error())
|
||||
}
|
||||
|
||||
// Get flash.
|
||||
@@ -187,8 +187,8 @@ func Sessioner(options ...Options) macaron.Handler {
|
||||
|
||||
ctx.Next()
|
||||
|
||||
if sess.Release() != nil {
|
||||
panic("session: " + err.Error())
|
||||
if err = sess.Release(); err != nil {
|
||||
panic("session(release): " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,17 +242,14 @@ type Manager struct {
|
||||
func NewManager(name string, opt Options) (*Manager, error) {
|
||||
p, ok := providers[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("session: unknown provider ‘%q’(forgotten import?)", name)
|
||||
return nil, fmt.Errorf("session: unknown provider '%s'(forgotten import?)", name)
|
||||
}
|
||||
if err := p.Init(opt.Maxlifetime, opt.ProviderConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Manager{p, opt}, nil
|
||||
return &Manager{p, opt}, p.Init(opt.Maxlifetime, opt.ProviderConfig)
|
||||
}
|
||||
|
||||
// sessionId generates a new session ID with rand string, unix nano time, remote addr by hash function.
|
||||
func (m *Manager) sessionId() string {
|
||||
return hex.EncodeToString(generateRandomKey(m.opt.IDLength))
|
||||
return hex.EncodeToString(generateRandomKey(m.opt.IDLength / 2))
|
||||
}
|
||||
|
||||
// Start starts a session by generating new one
|
||||
@@ -315,16 +312,9 @@ func (m *Manager) Destory(ctx *macaron.Context) error {
|
||||
func (m *Manager) RegenerateId(ctx *macaron.Context) (sess RawStore, err error) {
|
||||
sid := m.sessionId()
|
||||
oldsid := ctx.GetCookie(m.opt.CookieName)
|
||||
if len(oldsid) == 0 {
|
||||
sess, err = m.provider.Read(oldsid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
sess, err = m.provider.Regenerate(oldsid, sid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sess, err = m.provider.Regenerate(oldsid, sid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ck := &http.Cookie{
|
||||
Name: m.opt.CookieName,
|
||||
|
||||
2
Godeps/_workspace/src/github.com/macaron-contrib/session/session_test.go
generated
vendored
2
Godeps/_workspace/src/github.com/macaron-contrib/session/session_test.go
generated
vendored
@@ -42,7 +42,7 @@ func Test_Sessioner(t *testing.T) {
|
||||
m.ServeHTTP(resp, req)
|
||||
})
|
||||
|
||||
Convey("Register invalid provider that", t, func() {
|
||||
Convey("Register invalid provider", t, func() {
|
||||
Convey("Provider not exists", func() {
|
||||
defer func() {
|
||||
So(recover(), ShouldNotBeNil)
|
||||
|
||||
30
Godeps/_workspace/src/github.com/macaron-contrib/session/utils.go
generated
vendored
30
Godeps/_workspace/src/github.com/macaron-contrib/session/utils.go
generated
vendored
@@ -24,39 +24,19 @@ import (
|
||||
"github.com/Unknwon/com"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gob.Register([]interface{}{})
|
||||
gob.Register(map[int]interface{}{})
|
||||
gob.Register(map[string]interface{}{})
|
||||
gob.Register(map[interface{}]interface{}{})
|
||||
gob.Register(map[string]string{})
|
||||
gob.Register(map[int]string{})
|
||||
gob.Register(map[int]int{})
|
||||
gob.Register(map[int]int64{})
|
||||
}
|
||||
|
||||
func EncodeGob(obj map[interface{}]interface{}) ([]byte, error) {
|
||||
for _, v := range obj {
|
||||
gob.Register(v)
|
||||
}
|
||||
buf := bytes.NewBuffer(nil)
|
||||
enc := gob.NewEncoder(buf)
|
||||
err := enc.Encode(obj)
|
||||
if err != nil {
|
||||
return []byte(""), err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
err := gob.NewEncoder(buf).Encode(obj)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
func DecodeGob(encoded []byte) (map[interface{}]interface{}, error) {
|
||||
func DecodeGob(encoded []byte) (out map[interface{}]interface{}, err error) {
|
||||
buf := bytes.NewBuffer(encoded)
|
||||
dec := gob.NewDecoder(buf)
|
||||
var out map[interface{}]interface{}
|
||||
err := dec.Decode(&out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
err = gob.NewDecoder(buf).Decode(&out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// generateRandomKey creates a random key with the given strength.
|
||||
|
||||
4
Godeps/_workspace/src/golang.org/x/oauth2/.travis.yml
generated
vendored
4
Godeps/_workspace/src/golang.org/x/oauth2/.travis.yml
generated
vendored
@@ -8,7 +8,7 @@ install:
|
||||
- export GOPATH="$HOME/gopath"
|
||||
- mkdir -p "$GOPATH/src/golang.org/x"
|
||||
- mv "$TRAVIS_BUILD_DIR" "$GOPATH/src/golang.org/x/oauth2"
|
||||
- go get -v -t -d -tags='appengine appenginevm' golang.org/x/oauth2/...
|
||||
- go get -v -t -d golang.org/x/oauth2/...
|
||||
|
||||
script:
|
||||
- go test -v -tags='appengine appenginevm' golang.org/x/oauth2/...
|
||||
- go test -v golang.org/x/oauth2/...
|
||||
|
||||
44
Godeps/_workspace/src/golang.org/x/oauth2/CONTRIBUTING.md
generated
vendored
44
Godeps/_workspace/src/golang.org/x/oauth2/CONTRIBUTING.md
generated
vendored
@@ -1,25 +1,31 @@
|
||||
# Contributing
|
||||
# Contributing to Go
|
||||
|
||||
We don't use GitHub pull requests but use Gerrit for code reviews,
|
||||
similar to the Go project.
|
||||
Go is an open source project.
|
||||
|
||||
1. Sign one of the contributor license agreements below.
|
||||
2. `go get golang.org/x/review/git-codereview` to install the code reviewing tool.
|
||||
3. Get the package by running `go get -d golang.org/x/oauth2`.
|
||||
Make changes and create a change by running `git codereview change <name>`, provide a command message, and use `git codereview mail` to create a Gerrit CL.
|
||||
Keep amending to the change and mail as your recieve feedback.
|
||||
It is the work of hundreds of contributors. We appreciate your help!
|
||||
|
||||
For more information about the workflow, see Go's [Contribution Guidelines](https://golang.org/doc/contribute.html).
|
||||
|
||||
Before we can accept any pull requests
|
||||
we have to jump through a couple of legal hurdles,
|
||||
primarily a Contributor License Agreement (CLA):
|
||||
## Filing issues
|
||||
|
||||
- **If you are an individual writing original source code**
|
||||
and you're sure you own the intellectual property,
|
||||
then you'll need to sign an [individual CLA](http://code.google.com/legal/individual-cla-v1.0.html).
|
||||
- **If you work for a company that wants to allow you to contribute your work**,
|
||||
then you'll need to sign a [corporate CLA](http://code.google.com/legal/corporate-cla-v1.0.html).
|
||||
When [filing an issue](https://github.com/golang/oauth2/issues), make sure to answer these five questions:
|
||||
|
||||
1. What version of Go are you using (`go version`)?
|
||||
2. What operating system and processor architecture are you using?
|
||||
3. What did you do?
|
||||
4. What did you expect to see?
|
||||
5. What did you see instead?
|
||||
|
||||
General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker.
|
||||
The gophers there will answer or ask you to file an issue if you've tripped over a bug.
|
||||
|
||||
## Contributing code
|
||||
|
||||
Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
|
||||
before sending patches.
|
||||
|
||||
**We do not accept GitHub pull requests**
|
||||
(we use [Gerrit](https://code.google.com/p/gerrit/) instead for code review).
|
||||
|
||||
Unless otherwise noted, the Go source files are distributed under
|
||||
the BSD-style license found in the LICENSE file.
|
||||
|
||||
You can sign these electronically (just scroll to the bottom).
|
||||
After that, we'll be able to accept your pull requests.
|
||||
|
||||
46
Godeps/_workspace/src/golang.org/x/oauth2/README.md
generated
vendored
46
Godeps/_workspace/src/golang.org/x/oauth2/README.md
generated
vendored
@@ -16,3 +16,49 @@ See godoc for further documentation and examples.
|
||||
* [godoc.org/golang.org/x/oauth2/google](http://godoc.org/golang.org/x/oauth2/google)
|
||||
|
||||
|
||||
## App Engine
|
||||
|
||||
In change 96e89be (March 2015) we removed the `oauth2.Context2` type in favor
|
||||
of the [`context.Context`](https://golang.org/x/net/context#Context) type from
|
||||
the `golang.org/x/net/context` package
|
||||
|
||||
This means its no longer possible to use the "Classic App Engine"
|
||||
`appengine.Context` type with the `oauth2` package. (You're using
|
||||
Classic App Engine if you import the package `"appengine"`.)
|
||||
|
||||
To work around this, you may use the new `"google.golang.org/appengine"`
|
||||
package. This package has almost the same API as the `"appengine"` package,
|
||||
but it can be fetched with `go get` and used on "Managed VMs" and well as
|
||||
Classic App Engine.
|
||||
|
||||
See the [new `appengine` package's readme](https://github.com/golang/appengine#updating-a-go-app-engine-app)
|
||||
for information on updating your app.
|
||||
|
||||
If you don't want to update your entire app to use the new App Engine packages,
|
||||
you may use both sets of packages in parallel, using only the new packages
|
||||
with the `oauth2` package.
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
newappengine "google.golang.org/appengine"
|
||||
newurlftech "google.golang.org/urlfetch"
|
||||
|
||||
"appengine"
|
||||
)
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
var c appengine.Context = appengine.NewContext(r)
|
||||
c.Infof("Logging a message with the old package")
|
||||
|
||||
var ctx context.Context = newappengine.NewContext(r)
|
||||
client := &http.Client{
|
||||
Transport: &oauth2.Transport{
|
||||
Source: google.AppEngineTokenSource(ctx, "scope"),
|
||||
Base: &newurlfetch.Transport{Context: ctx},
|
||||
},
|
||||
}
|
||||
client.Get("...")
|
||||
}
|
||||
|
||||
|
||||
25
Godeps/_workspace/src/golang.org/x/oauth2/client_appengine.go
generated
vendored
25
Godeps/_workspace/src/golang.org/x/oauth2/client_appengine.go
generated
vendored
@@ -2,38 +2,23 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build appengine,!appenginevm
|
||||
// +build appengine appenginevm
|
||||
|
||||
// App Engine hooks.
|
||||
|
||||
package oauth2
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"appengine"
|
||||
"appengine/urlfetch"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/appengine/urlfetch"
|
||||
)
|
||||
|
||||
var warnOnce sync.Once
|
||||
|
||||
func init() {
|
||||
registerContextClientFunc(contextClientAppEngine)
|
||||
}
|
||||
|
||||
func contextClientAppEngine(ctx Context) (*http.Client, error) {
|
||||
if actx, ok := ctx.(appengine.Context); ok {
|
||||
return urlfetch.Client(actx), nil
|
||||
}
|
||||
// The user did it wrong. We'll log once (and hope they see it
|
||||
// in dev_appserver), but stil return (nil, nil) in case some
|
||||
// other contextClientFunc hook finds a way to proceed.
|
||||
warnOnce.Do(gaeDoingItWrongHelp)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func gaeDoingItWrongHelp() {
|
||||
log.Printf("WARNING: you attempted to use the oauth2 package without passing a valid appengine.Context or *http.Request as the oauth2.Context. App Engine requires that all service RPCs (including urlfetch) be associated with an *http.Request/appengine.Context.")
|
||||
func contextClientAppEngine(ctx context.Context) (*http.Client, error) {
|
||||
return urlfetch.Client(ctx), nil
|
||||
}
|
||||
|
||||
27
Godeps/_workspace/src/golang.org/x/oauth2/example_test.go
generated
vendored
27
Godeps/_workspace/src/golang.org/x/oauth2/example_test.go
generated
vendored
@@ -7,15 +7,10 @@ package oauth2_test
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// TODO(jbd): Remove after Go 1.4.
|
||||
// Related to https://codereview.appspot.com/107320046
|
||||
func TestA(t *testing.T) {}
|
||||
|
||||
func ExampleConfig() {
|
||||
conf := &oauth2.Config{
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
@@ -48,25 +43,3 @@ func ExampleConfig() {
|
||||
client := conf.Client(oauth2.NoContext, tok)
|
||||
client.Get("...")
|
||||
}
|
||||
|
||||
func ExampleJWTConfig() {
|
||||
var initialToken *oauth2.Token // nil means no initial token
|
||||
conf := &oauth2.JWTConfig{
|
||||
Email: "xxx@developer.com",
|
||||
// The contents of your RSA private key or your PEM file
|
||||
// that contains a private key.
|
||||
// If you have a p12 file instead, you
|
||||
// can use `openssl` to export the private key into a pem file.
|
||||
//
|
||||
// $ openssl pkcs12 -in key.p12 -out key.pem -nodes
|
||||
//
|
||||
// It only supports PEM containers with no passphrase.
|
||||
PrivateKey: []byte("-----BEGIN RSA PRIVATE KEY-----..."),
|
||||
Subject: "user@example.com",
|
||||
TokenURL: "https://provider.com/o/oauth2/token",
|
||||
}
|
||||
// Initiate an http.Client, the following GET request will be
|
||||
// authorized and authenticated on the behalf of user@example.com.
|
||||
client := conf.Client(oauth2.NoContext, initialToken)
|
||||
client.Get("...")
|
||||
}
|
||||
|
||||
16
Godeps/_workspace/src/golang.org/x/oauth2/facebook/facebook.go
generated
vendored
Normal file
16
Godeps/_workspace/src/golang.org/x/oauth2/facebook/facebook.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2015 The oauth2 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 facebook provides constants for using OAuth2 to access Facebook.
|
||||
package facebook // import "golang.org/x/oauth2/facebook"
|
||||
|
||||
import (
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// Endpoint is Facebook's OAuth 2.0 endpoint.
|
||||
var Endpoint = oauth2.Endpoint{
|
||||
AuthURL: "https://www.facebook.com/dialog/oauth",
|
||||
TokenURL: "https://graph.facebook.com/oauth/access_token",
|
||||
}
|
||||
2
Godeps/_workspace/src/golang.org/x/oauth2/github/github.go
generated
vendored
2
Godeps/_workspace/src/golang.org/x/oauth2/github/github.go
generated
vendored
@@ -3,7 +3,7 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package github provides constants for using OAuth2 to access Github.
|
||||
package github
|
||||
package github // import "golang.org/x/oauth2/github"
|
||||
|
||||
import (
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
76
Godeps/_workspace/src/golang.org/x/oauth2/google/appengine.go
generated
vendored
76
Godeps/_workspace/src/golang.org/x/oauth2/google/appengine.go
generated
vendored
@@ -2,36 +2,82 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build appengine,!appenginevm
|
||||
|
||||
package google
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"appengine"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// Set at init time by appengine_hook.go. If nil, we're not on App Engine.
|
||||
var appengineTokenFunc func(c context.Context, scopes ...string) (token string, expiry time.Time, err error)
|
||||
|
||||
// AppEngineTokenSource returns a token source that fetches tokens
|
||||
// issued to the current App Engine application's service account.
|
||||
// If you are implementing a 3-legged OAuth 2.0 flow on App Engine
|
||||
// that involves user accounts, see oauth2.Config instead.
|
||||
//
|
||||
// You are required to provide a valid appengine.Context as context.
|
||||
func AppEngineTokenSource(ctx appengine.Context, scope ...string) oauth2.TokenSource {
|
||||
// The provided context must have come from appengine.NewContext.
|
||||
func AppEngineTokenSource(ctx context.Context, scope ...string) oauth2.TokenSource {
|
||||
if appengineTokenFunc == nil {
|
||||
panic("google: AppEngineTokenSource can only be used on App Engine.")
|
||||
}
|
||||
scopes := append([]string{}, scope...)
|
||||
sort.Strings(scopes)
|
||||
return &appEngineTokenSource{
|
||||
ctx: ctx,
|
||||
scopes: scope,
|
||||
fetcherFunc: aeFetcherFunc,
|
||||
ctx: ctx,
|
||||
scopes: scopes,
|
||||
key: strings.Join(scopes, " "),
|
||||
}
|
||||
}
|
||||
|
||||
var aeFetcherFunc = func(ctx oauth2.Context, scope ...string) (string, time.Time, error) {
|
||||
c, ok := ctx.(appengine.Context)
|
||||
if !ok {
|
||||
return "", time.Time{}, errInvalidContext
|
||||
}
|
||||
return appengine.AccessToken(c, scope...)
|
||||
// aeTokens helps the fetched tokens to be reused until their expiration.
|
||||
var (
|
||||
aeTokensMu sync.Mutex
|
||||
aeTokens = make(map[string]*tokenLock) // key is space-separated scopes
|
||||
)
|
||||
|
||||
type tokenLock struct {
|
||||
mu sync.Mutex // guards t; held while fetching or updating t
|
||||
t *oauth2.Token
|
||||
}
|
||||
|
||||
type appEngineTokenSource struct {
|
||||
ctx context.Context
|
||||
scopes []string
|
||||
key string // to aeTokens map; space-separated scopes
|
||||
}
|
||||
|
||||
func (ts *appEngineTokenSource) Token() (*oauth2.Token, error) {
|
||||
if appengineTokenFunc == nil {
|
||||
panic("google: AppEngineTokenSource can only be used on App Engine.")
|
||||
}
|
||||
|
||||
aeTokensMu.Lock()
|
||||
tok, ok := aeTokens[ts.key]
|
||||
if !ok {
|
||||
tok = &tokenLock{}
|
||||
aeTokens[ts.key] = tok
|
||||
}
|
||||
aeTokensMu.Unlock()
|
||||
|
||||
tok.mu.Lock()
|
||||
defer tok.mu.Unlock()
|
||||
if tok.t.Valid() {
|
||||
return tok.t, nil
|
||||
}
|
||||
access, exp, err := appengineTokenFunc(ts.ctx, ts.scopes...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tok.t = &oauth2.Token{
|
||||
AccessToken: access,
|
||||
Expiry: exp,
|
||||
}
|
||||
return tok.t, nil
|
||||
}
|
||||
|
||||
13
Godeps/_workspace/src/golang.org/x/oauth2/google/appengine_hook.go
generated
vendored
Normal file
13
Godeps/_workspace/src/golang.org/x/oauth2/google/appengine_hook.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright 2015 The oauth2 Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build appengine appenginevm
|
||||
|
||||
package google
|
||||
|
||||
import "google.golang.org/appengine"
|
||||
|
||||
func init() {
|
||||
appengineTokenFunc = appengine.AccessToken
|
||||
}
|
||||
36
Godeps/_workspace/src/golang.org/x/oauth2/google/appenginevm.go
generated
vendored
36
Godeps/_workspace/src/golang.org/x/oauth2/google/appenginevm.go
generated
vendored
@@ -1,36 +0,0 @@
|
||||
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build appenginevm !appengine
|
||||
|
||||
package google
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"google.golang.org/appengine"
|
||||
)
|
||||
|
||||
// AppEngineTokenSource returns a token source that fetches tokens
|
||||
// issued to the current App Engine application's service account.
|
||||
// If you are implementing a 3-legged OAuth 2.0 flow on App Engine
|
||||
// that involves user accounts, see oauth2.Config instead.
|
||||
//
|
||||
// You are required to provide a valid appengine.Context as context.
|
||||
func AppEngineTokenSource(ctx appengine.Context, scope ...string) oauth2.TokenSource {
|
||||
return &appEngineTokenSource{
|
||||
ctx: ctx,
|
||||
scopes: scope,
|
||||
fetcherFunc: aeVMFetcherFunc,
|
||||
}
|
||||
}
|
||||
|
||||
var aeVMFetcherFunc = func(ctx oauth2.Context, scope ...string) (string, time.Time, error) {
|
||||
c, ok := ctx.(appengine.Context)
|
||||
if !ok {
|
||||
return "", time.Time{}, errInvalidContext
|
||||
}
|
||||
return appengine.AccessToken(c, scope...)
|
||||
}
|
||||
154
Godeps/_workspace/src/golang.org/x/oauth2/google/default.go
generated
vendored
Normal file
154
Godeps/_workspace/src/golang.org/x/oauth2/google/default.go
generated
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
// Copyright 2015 The oauth2 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 google
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/jwt"
|
||||
"google.golang.org/cloud/compute/metadata"
|
||||
)
|
||||
|
||||
// DefaultClient returns an HTTP Client that uses the
|
||||
// DefaultTokenSource to obtain authentication credentials.
|
||||
//
|
||||
// This client should be used when developing services
|
||||
// that run on Google App Engine or Google Compute Engine
|
||||
// and use "Application Default Credentials."
|
||||
//
|
||||
// For more details, see:
|
||||
// https://developers.google.com/accounts/application-default-credentials
|
||||
//
|
||||
func DefaultClient(ctx context.Context, scope ...string) (*http.Client, error) {
|
||||
ts, err := DefaultTokenSource(ctx, scope...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return oauth2.NewClient(ctx, ts), nil
|
||||
}
|
||||
|
||||
// DefaultTokenSource is a token source that uses
|
||||
// "Application Default Credentials".
|
||||
//
|
||||
// It looks for credentials in the following places,
|
||||
// preferring the first location found:
|
||||
//
|
||||
// 1. A JSON file whose path is specified by the
|
||||
// GOOGLE_APPLICATION_CREDENTIALS environment variable.
|
||||
// 2. A JSON file in a location known to the gcloud command-line tool.
|
||||
// On Windows, this is %APPDATA%/gcloud/application_default_credentials.json.
|
||||
// On other systems, $HOME/.config/gcloud/application_default_credentials.json.
|
||||
// 3. On Google App Engine it uses the appengine.AccessToken function.
|
||||
// 4. On Google Compute Engine, it fetches credentials from the metadata server.
|
||||
// (In this final case any provided scopes are ignored.)
|
||||
//
|
||||
// For more details, see:
|
||||
// https://developers.google.com/accounts/application-default-credentials
|
||||
//
|
||||
func DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSource, error) {
|
||||
// First, try the environment variable.
|
||||
const envVar = "GOOGLE_APPLICATION_CREDENTIALS"
|
||||
if filename := os.Getenv(envVar); filename != "" {
|
||||
ts, err := tokenSourceFromFile(ctx, filename, scope)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("google: error getting credentials using %v environment variable: %v", envVar, err)
|
||||
}
|
||||
return ts, nil
|
||||
}
|
||||
|
||||
// Second, try a well-known file.
|
||||
filename := wellKnownFile()
|
||||
_, err := os.Stat(filename)
|
||||
if err == nil {
|
||||
ts, err2 := tokenSourceFromFile(ctx, filename, scope)
|
||||
if err2 == nil {
|
||||
return ts, nil
|
||||
}
|
||||
err = err2
|
||||
} else if os.IsNotExist(err) {
|
||||
err = nil // ignore this error
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("google: error getting credentials using well-known file (%v): %v", filename, err)
|
||||
}
|
||||
|
||||
// Third, if we're on Google App Engine use those credentials.
|
||||
if appengineTokenFunc != nil {
|
||||
return AppEngineTokenSource(ctx, scope...), nil
|
||||
}
|
||||
|
||||
// Fourth, if we're on Google Compute Engine use the metadata server.
|
||||
if metadata.OnGCE() {
|
||||
return ComputeTokenSource(""), nil
|
||||
}
|
||||
|
||||
// None are found; return helpful error.
|
||||
const url = "https://developers.google.com/accounts/application-default-credentials"
|
||||
return nil, fmt.Errorf("google: could not find default credentials. See %v for more information.", url)
|
||||
}
|
||||
|
||||
func wellKnownFile() string {
|
||||
const f = "application_default_credentials.json"
|
||||
if runtime.GOOS == "windows" {
|
||||
return filepath.Join(os.Getenv("APPDATA"), "gcloud", f)
|
||||
}
|
||||
return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", f)
|
||||
}
|
||||
|
||||
func tokenSourceFromFile(ctx context.Context, filename string, scopes []string) (oauth2.TokenSource, error) {
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var d struct {
|
||||
// Common fields
|
||||
Type string
|
||||
ClientID string `json:"client_id"`
|
||||
|
||||
// User Credential fields
|
||||
ClientSecret string `json:"client_secret"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
|
||||
// Service Account fields
|
||||
ClientEmail string `json:"client_email"`
|
||||
PrivateKeyID string `json:"private_key_id"`
|
||||
PrivateKey string `json:"private_key"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &d); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch d.Type {
|
||||
case "authorized_user":
|
||||
cfg := &oauth2.Config{
|
||||
ClientID: d.ClientID,
|
||||
ClientSecret: d.ClientSecret,
|
||||
Scopes: append([]string{}, scopes...), // copy
|
||||
Endpoint: Endpoint,
|
||||
}
|
||||
tok := &oauth2.Token{RefreshToken: d.RefreshToken}
|
||||
return cfg.TokenSource(ctx, tok), nil
|
||||
case "service_account":
|
||||
cfg := &jwt.Config{
|
||||
Email: d.ClientEmail,
|
||||
PrivateKey: []byte(d.PrivateKey),
|
||||
Scopes: append([]string{}, scopes...), // copy
|
||||
TokenURL: JWTTokenURL,
|
||||
}
|
||||
return cfg.TokenSource(ctx), nil
|
||||
case "":
|
||||
return nil, errors.New("missing 'type' field in credentials")
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown credential type: %q", d.Type)
|
||||
}
|
||||
}
|
||||
34
Godeps/_workspace/src/golang.org/x/oauth2/google/example_test.go
generated
vendored
34
Godeps/_workspace/src/golang.org/x/oauth2/google/example_test.go
generated
vendored
@@ -11,17 +11,22 @@ import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
"golang.org/x/oauth2/jwt"
|
||||
"google.golang.org/appengine"
|
||||
"google.golang.org/appengine/urlfetch"
|
||||
)
|
||||
|
||||
// Remove after Go 1.4.
|
||||
// Related to https://codereview.appspot.com/107320046
|
||||
func TestA(t *testing.T) {}
|
||||
func ExampleDefaultClient() {
|
||||
client, err := google.DefaultClient(oauth2.NoContext,
|
||||
"https://www.googleapis.com/auth/devstorage.full_control")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
client.Get("...")
|
||||
}
|
||||
|
||||
func Example_webServer() {
|
||||
// Your credentials should be obtained from the Google
|
||||
@@ -62,21 +67,34 @@ func ExampleJWTConfigFromJSON() {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
conf, err := google.JWTConfigFromJSON(oauth2.NoContext, data, "https://www.googleapis.com/auth/bigquery")
|
||||
conf, err := google.JWTConfigFromJSON(data, "https://www.googleapis.com/auth/bigquery")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Initiate an http.Client. The following GET request will be
|
||||
// authorized and authenticated on the behalf of
|
||||
// your service account.
|
||||
client := conf.Client(oauth2.NoContext, nil)
|
||||
client := conf.Client(oauth2.NoContext)
|
||||
client.Get("...")
|
||||
}
|
||||
|
||||
func ExampleSDKConfig() {
|
||||
// The credentials will be obtained from the first account that
|
||||
// has been authorized with `gcloud auth login`.
|
||||
conf, err := google.NewSDKConfig("")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Initiate an http.Client. The following GET request will be
|
||||
// authorized and authenticated on the behalf of the SDK user.
|
||||
client := conf.Client(oauth2.NoContext)
|
||||
client.Get("...")
|
||||
}
|
||||
|
||||
func Example_serviceAccount() {
|
||||
// Your credentials should be obtained from the Google
|
||||
// Developer Console (https://console.developers.google.com).
|
||||
conf := &oauth2.JWTConfig{
|
||||
conf := &jwt.Config{
|
||||
Email: "xxx@developer.gserviceaccount.com",
|
||||
// The contents of your RSA private key or your PEM file
|
||||
// that contains a private key.
|
||||
@@ -101,7 +119,7 @@ func Example_serviceAccount() {
|
||||
}
|
||||
// Initiate an http.Client, the following GET request will be
|
||||
// authorized and authenticated on the behalf of user@example.com.
|
||||
client := conf.Client(oauth2.NoContext, nil)
|
||||
client := conf.Client(oauth2.NoContext)
|
||||
client.Get("...")
|
||||
}
|
||||
|
||||
|
||||
124
Godeps/_workspace/src/golang.org/x/oauth2/google/google.go
generated
vendored
124
Godeps/_workspace/src/golang.org/x/oauth2/google/google.go
generated
vendored
@@ -2,26 +2,28 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package google provides support for making
|
||||
// OAuth2 authorized and authenticated HTTP requests
|
||||
// to Google APIs. It supports Web server, client-side,
|
||||
// service accounts, Google Compute Engine service accounts,
|
||||
// and Google App Engine service accounts authorization
|
||||
// and authentications flows:
|
||||
// Package google provides support for making OAuth2 authorized and
|
||||
// authenticated HTTP requests to Google APIs.
|
||||
// It supports the Web server flow, client-side credentials, service accounts,
|
||||
// Google Compute Engine service accounts, and Google App Engine service
|
||||
// accounts.
|
||||
//
|
||||
// For more information, please read
|
||||
// https://developers.google.com/accounts/docs/OAuth2.
|
||||
package google
|
||||
// https://developers.google.com/accounts/docs/OAuth2
|
||||
// and
|
||||
// https://developers.google.com/accounts/application-default-credentials.
|
||||
package google // import "golang.org/x/oauth2/google"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/jwt"
|
||||
"google.golang.org/cloud/compute/metadata"
|
||||
)
|
||||
|
||||
// Endpoint is Google's OAuth 2.0 endpoint.
|
||||
@@ -33,11 +35,55 @@ var Endpoint = oauth2.Endpoint{
|
||||
// JWTTokenURL is Google's OAuth 2.0 token URL to use with the JWT flow.
|
||||
const JWTTokenURL = "https://accounts.google.com/o/oauth2/token"
|
||||
|
||||
// ConfigFromJSON uses a Google Developers Console client_credentials.json
|
||||
// file to construct a config.
|
||||
// client_credentials.json can be downloadable from https://console.developers.google.com,
|
||||
// under "APIs & Auth" > "Credentials". Download the Web application credentials in the
|
||||
// JSON format and provide the contents of the file as jsonKey.
|
||||
func ConfigFromJSON(jsonKey []byte, scope ...string) (*oauth2.Config, error) {
|
||||
type cred struct {
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
RedirectURIs []string `json:"redirect_uris"`
|
||||
AuthURI string `json:"auth_uri"`
|
||||
TokenURI string `json:"token_uri"`
|
||||
}
|
||||
var j struct {
|
||||
Web *cred `json:"web"`
|
||||
Installed *cred `json:"installed"`
|
||||
}
|
||||
if err := json.Unmarshal(jsonKey, &j); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var c *cred
|
||||
switch {
|
||||
case j.Web != nil:
|
||||
c = j.Web
|
||||
case j.Installed != nil:
|
||||
c = j.Installed
|
||||
default:
|
||||
return nil, fmt.Errorf("oauth2/google: no credentials found")
|
||||
}
|
||||
if len(c.RedirectURIs) < 1 {
|
||||
return nil, errors.New("oauth2/google: missing redirect URL in the client_credentials.json")
|
||||
}
|
||||
return &oauth2.Config{
|
||||
ClientID: c.ClientID,
|
||||
ClientSecret: c.ClientSecret,
|
||||
RedirectURL: c.RedirectURIs[0],
|
||||
Scopes: scope,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: c.AuthURI,
|
||||
TokenURL: c.TokenURI,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// JWTConfigFromJSON uses a Google Developers service account JSON key file to read
|
||||
// the credentials that authorize and authenticate the requests.
|
||||
// Create a service account on "Credentials" page under "APIs & Auth" for your
|
||||
// project at https://console.developers.google.com to download a JSON key file.
|
||||
func JWTConfigFromJSON(ctx oauth2.Context, jsonKey []byte, scope ...string) (*oauth2.JWTConfig, error) {
|
||||
func JWTConfigFromJSON(jsonKey []byte, scope ...string) (*jwt.Config, error) {
|
||||
var key struct {
|
||||
Email string `json:"client_email"`
|
||||
PrivateKey string `json:"private_key"`
|
||||
@@ -45,7 +91,7 @@ func JWTConfigFromJSON(ctx oauth2.Context, jsonKey []byte, scope ...string) (*oa
|
||||
if err := json.Unmarshal(jsonKey, &key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &oauth2.JWTConfig{
|
||||
return &jwt.Config{
|
||||
Email: key.Email,
|
||||
PrivateKey: []byte(key.PrivateKey),
|
||||
Scopes: scope,
|
||||
@@ -53,12 +99,6 @@ func JWTConfigFromJSON(ctx oauth2.Context, jsonKey []byte, scope ...string) (*oa
|
||||
}, nil
|
||||
}
|
||||
|
||||
type metaTokenRespBody struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn time.Duration `json:"expires_in"`
|
||||
TokenType string `json:"token_type"`
|
||||
}
|
||||
|
||||
// ComputeTokenSource returns a token source that fetches access tokens
|
||||
// from Google Compute Engine (GCE)'s metadata server. It's only valid to use
|
||||
// this token source if your program is running on a GCE instance.
|
||||
@@ -66,50 +106,40 @@ type metaTokenRespBody struct {
|
||||
// Further information about retrieving access tokens from the GCE metadata
|
||||
// server can be found at https://cloud.google.com/compute/docs/authentication.
|
||||
func ComputeTokenSource(account string) oauth2.TokenSource {
|
||||
return &computeSource{account: account}
|
||||
return oauth2.ReuseTokenSource(nil, computeSource{account: account})
|
||||
}
|
||||
|
||||
type computeSource struct {
|
||||
account string
|
||||
}
|
||||
|
||||
var metaClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 750 * time.Millisecond,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
ResponseHeaderTimeout: 750 * time.Millisecond,
|
||||
},
|
||||
}
|
||||
|
||||
func (cs *computeSource) Token() (*oauth2.Token, error) {
|
||||
func (cs computeSource) Token() (*oauth2.Token, error) {
|
||||
if !metadata.OnGCE() {
|
||||
return nil, errors.New("oauth2/google: can't get a token from the metadata service; not running on GCE")
|
||||
}
|
||||
acct := cs.account
|
||||
if acct == "" {
|
||||
acct = "default"
|
||||
}
|
||||
u := "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/" + acct + "/token"
|
||||
req, err := http.NewRequest("GET", u, nil)
|
||||
tokenJSON, err := metadata.Get("instance/service-accounts/" + acct + "/token")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Add("X-Google-Metadata-Request", "True")
|
||||
resp, err := metaClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var res struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresInSec int `json:"expires_in"`
|
||||
TokenType string `json:"token_type"`
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||
return nil, fmt.Errorf("oauth2: can't retrieve a token from metadata server, status code: %d", resp.StatusCode)
|
||||
}
|
||||
var tokenResp metaTokenRespBody
|
||||
err = json.NewDecoder(resp.Body).Decode(&tokenResp)
|
||||
err = json.NewDecoder(strings.NewReader(tokenJSON)).Decode(&res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("oauth2/google: invalid token JSON from metadata: %v", err)
|
||||
}
|
||||
if res.ExpiresInSec == 0 || res.AccessToken == "" {
|
||||
return nil, fmt.Errorf("oauth2/google: incomplete token received from metadata")
|
||||
}
|
||||
return &oauth2.Token{
|
||||
AccessToken: tokenResp.AccessToken,
|
||||
TokenType: tokenResp.TokenType,
|
||||
Expiry: time.Now().Add(tokenResp.ExpiresIn * time.Second),
|
||||
AccessToken: res.AccessToken,
|
||||
TokenType: res.TokenType,
|
||||
Expiry: time.Now().Add(time.Duration(res.ExpiresInSec) * time.Second),
|
||||
}, nil
|
||||
}
|
||||
|
||||
67
Godeps/_workspace/src/golang.org/x/oauth2/google/google_test.go
generated
vendored
Normal file
67
Godeps/_workspace/src/golang.org/x/oauth2/google/google_test.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2015 The oauth2 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 google
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var webJSONKey = []byte(`
|
||||
{
|
||||
"web": {
|
||||
"auth_uri": "https://google.com/o/oauth2/auth",
|
||||
"client_secret": "3Oknc4jS_wA2r9i",
|
||||
"token_uri": "https://google.com/o/oauth2/token",
|
||||
"client_email": "222-nprqovg5k43uum874cs9osjt2koe97g8@developer.gserviceaccount.com",
|
||||
"redirect_uris": ["https://www.example.com/oauth2callback"],
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/222-nprqovg5k43uum874cs9osjt2koe97g8@developer.gserviceaccount.com",
|
||||
"client_id": "222-nprqovg5k43uum874cs9osjt2koe97g8.apps.googleusercontent.com",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"javascript_origins": ["https://www.example.com"]
|
||||
}
|
||||
}`)
|
||||
|
||||
var installedJSONKey = []byte(`{
|
||||
"installed": {
|
||||
"client_id": "222-installed.apps.googleusercontent.com",
|
||||
"redirect_uris": ["https://www.example.com/oauth2callback"]
|
||||
}
|
||||
}`)
|
||||
|
||||
func TestConfigFromJSON(t *testing.T) {
|
||||
conf, err := ConfigFromJSON(webJSONKey, "scope1", "scope2")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if got, want := conf.ClientID, "222-nprqovg5k43uum874cs9osjt2koe97g8.apps.googleusercontent.com"; got != want {
|
||||
t.Errorf("ClientID = %q; want %q", got, want)
|
||||
}
|
||||
if got, want := conf.ClientSecret, "3Oknc4jS_wA2r9i"; got != want {
|
||||
t.Errorf("ClientSecret = %q; want %q", got, want)
|
||||
}
|
||||
if got, want := conf.RedirectURL, "https://www.example.com/oauth2callback"; got != want {
|
||||
t.Errorf("RedictURL = %q; want %q", got, want)
|
||||
}
|
||||
if got, want := strings.Join(conf.Scopes, ","), "scope1,scope2"; got != want {
|
||||
t.Errorf("Scopes = %q; want %q", got, want)
|
||||
}
|
||||
if got, want := conf.Endpoint.AuthURL, "https://google.com/o/oauth2/auth"; got != want {
|
||||
t.Errorf("AuthURL = %q; want %q", got, want)
|
||||
}
|
||||
if got, want := conf.Endpoint.TokenURL, "https://google.com/o/oauth2/token"; got != want {
|
||||
t.Errorf("TokenURL = %q; want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigFromJSON_Installed(t *testing.T) {
|
||||
conf, err := ConfigFromJSON(installedJSONKey)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if got, want := conf.ClientID, "222-installed.apps.googleusercontent.com"; got != want {
|
||||
t.Errorf("ClientID = %q; want %q", got, want)
|
||||
}
|
||||
}
|
||||
168
Godeps/_workspace/src/golang.org/x/oauth2/google/sdk.go
generated
vendored
Normal file
168
Godeps/_workspace/src/golang.org/x/oauth2/google/sdk.go
generated
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
// Copyright 2015 The oauth2 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 google
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/internal"
|
||||
)
|
||||
|
||||
type sdkCredentials struct {
|
||||
Data []struct {
|
||||
Credential struct {
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
TokenExpiry *time.Time `json:"token_expiry"`
|
||||
} `json:"credential"`
|
||||
Key struct {
|
||||
Account string `json:"account"`
|
||||
Scope string `json:"scope"`
|
||||
} `json:"key"`
|
||||
}
|
||||
}
|
||||
|
||||
// An SDKConfig provides access to tokens from an account already
|
||||
// authorized via the Google Cloud SDK.
|
||||
type SDKConfig struct {
|
||||
conf oauth2.Config
|
||||
initialToken *oauth2.Token
|
||||
}
|
||||
|
||||
// NewSDKConfig creates an SDKConfig for the given Google Cloud SDK
|
||||
// account. If account is empty, the account currently active in
|
||||
// Google Cloud SDK properties is used.
|
||||
// Google Cloud SDK credentials must be created by running `gcloud auth`
|
||||
// before using this function.
|
||||
// The Google Cloud SDK is available at https://cloud.google.com/sdk/.
|
||||
func NewSDKConfig(account string) (*SDKConfig, error) {
|
||||
configPath, err := sdkConfigPath()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("oauth2/google: error getting SDK config path: %v", err)
|
||||
}
|
||||
credentialsPath := filepath.Join(configPath, "credentials")
|
||||
f, err := os.Open(credentialsPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("oauth2/google: failed to load SDK credentials: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var c sdkCredentials
|
||||
if err := json.NewDecoder(f).Decode(&c); err != nil {
|
||||
return nil, fmt.Errorf("oauth2/google: failed to decode SDK credentials from %q: %v", credentialsPath, err)
|
||||
}
|
||||
if len(c.Data) == 0 {
|
||||
return nil, fmt.Errorf("oauth2/google: no credentials found in %q, run `gcloud auth login` to create one", credentialsPath)
|
||||
}
|
||||
if account == "" {
|
||||
propertiesPath := filepath.Join(configPath, "properties")
|
||||
f, err := os.Open(propertiesPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("oauth2/google: failed to load SDK properties: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
ini, err := internal.ParseINI(f)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("oauth2/google: failed to parse SDK properties %q: %v", propertiesPath, err)
|
||||
}
|
||||
core, ok := ini["core"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("oauth2/google: failed to find [core] section in %v", ini)
|
||||
}
|
||||
active, ok := core["account"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("oauth2/google: failed to find %q attribute in %v", "account", core)
|
||||
}
|
||||
account = active
|
||||
}
|
||||
|
||||
for _, d := range c.Data {
|
||||
if account == "" || d.Key.Account == account {
|
||||
if d.Credential.AccessToken == "" && d.Credential.RefreshToken == "" {
|
||||
return nil, fmt.Errorf("oauth2/google: no token available for account %q", account)
|
||||
}
|
||||
var expiry time.Time
|
||||
if d.Credential.TokenExpiry != nil {
|
||||
expiry = *d.Credential.TokenExpiry
|
||||
}
|
||||
return &SDKConfig{
|
||||
conf: oauth2.Config{
|
||||
ClientID: d.Credential.ClientID,
|
||||
ClientSecret: d.Credential.ClientSecret,
|
||||
Scopes: strings.Split(d.Key.Scope, " "),
|
||||
Endpoint: Endpoint,
|
||||
RedirectURL: "oob",
|
||||
},
|
||||
initialToken: &oauth2.Token{
|
||||
AccessToken: d.Credential.AccessToken,
|
||||
RefreshToken: d.Credential.RefreshToken,
|
||||
Expiry: expiry,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("oauth2/google: no such credentials for account %q", account)
|
||||
}
|
||||
|
||||
// Client returns an HTTP client using Google Cloud SDK credentials to
|
||||
// authorize requests. The token will auto-refresh as necessary. The
|
||||
// underlying http.RoundTripper will be obtained using the provided
|
||||
// context. The returned client and its Transport should not be
|
||||
// modified.
|
||||
func (c *SDKConfig) Client(ctx context.Context) *http.Client {
|
||||
return &http.Client{
|
||||
Transport: &oauth2.Transport{
|
||||
Source: c.TokenSource(ctx),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TokenSource returns an oauth2.TokenSource that retrieve tokens from
|
||||
// Google Cloud SDK credentials using the provided context.
|
||||
// It will returns the current access token stored in the credentials,
|
||||
// and refresh it when it expires, but it won't update the credentials
|
||||
// with the new access token.
|
||||
func (c *SDKConfig) TokenSource(ctx context.Context) oauth2.TokenSource {
|
||||
return c.conf.TokenSource(ctx, c.initialToken)
|
||||
}
|
||||
|
||||
// Scopes are the OAuth 2.0 scopes the current account is authorized for.
|
||||
func (c *SDKConfig) Scopes() []string {
|
||||
return c.conf.Scopes
|
||||
}
|
||||
|
||||
// sdkConfigPath tries to guess where the gcloud config is located.
|
||||
// It can be overridden during tests.
|
||||
var sdkConfigPath = func() (string, error) {
|
||||
if runtime.GOOS == "windows" {
|
||||
return filepath.Join(os.Getenv("APPDATA"), "gcloud"), nil
|
||||
}
|
||||
homeDir := guessUnixHomeDir()
|
||||
if homeDir == "" {
|
||||
return "", errors.New("unable to get current user home directory: os/user lookup failed; $HOME is empty")
|
||||
}
|
||||
return filepath.Join(homeDir, ".config", "gcloud"), nil
|
||||
}
|
||||
|
||||
func guessUnixHomeDir() string {
|
||||
usr, err := user.Current()
|
||||
if err == nil {
|
||||
return usr.HomeDir
|
||||
}
|
||||
return os.Getenv("HOME")
|
||||
}
|
||||
46
Godeps/_workspace/src/golang.org/x/oauth2/google/sdk_test.go
generated
vendored
Normal file
46
Godeps/_workspace/src/golang.org/x/oauth2/google/sdk_test.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2015 The oauth2 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 google
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSDKConfig(t *testing.T) {
|
||||
sdkConfigPath = func() (string, error) {
|
||||
return "testdata/gcloud", nil
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
account string
|
||||
accessToken string
|
||||
err bool
|
||||
}{
|
||||
{"", "bar_access_token", false},
|
||||
{"foo@example.com", "foo_access_token", false},
|
||||
{"bar@example.com", "bar_access_token", false},
|
||||
{"baz@serviceaccount.example.com", "", true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
c, err := NewSDKConfig(tt.account)
|
||||
if got, want := err != nil, tt.err; got != want {
|
||||
if !tt.err {
|
||||
t.Errorf("expected no error, got error: %v", tt.err, err)
|
||||
} else {
|
||||
t.Errorf("expected error, got none")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
tok := c.initialToken
|
||||
if tok == nil {
|
||||
t.Errorf("expected token %q, got: nil", tt.accessToken)
|
||||
continue
|
||||
}
|
||||
if tok.AccessToken != tt.accessToken {
|
||||
t.Errorf("expected token %q, got: %q", tt.accessToken, tok.AccessToken)
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Godeps/_workspace/src/golang.org/x/oauth2/google/source_appengine.go
generated
vendored
68
Godeps/_workspace/src/golang.org/x/oauth2/google/source_appengine.go
generated
vendored
@@ -1,68 +0,0 @@
|
||||
// Copyright 2014 The oauth2 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 google
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var (
|
||||
aeTokensMu sync.Mutex // guards aeTokens and appEngineTokenSource.key
|
||||
|
||||
// aeTokens helps the fetched tokens to be reused until their expiration.
|
||||
aeTokens = make(map[string]*tokenLock) // key is '\0'-separated scopes
|
||||
)
|
||||
|
||||
var errInvalidContext = errors.New("oauth2: a valid appengine.Context is required")
|
||||
|
||||
type tokenLock struct {
|
||||
mu sync.Mutex // guards t; held while updating t
|
||||
t *oauth2.Token
|
||||
}
|
||||
|
||||
type appEngineTokenSource struct {
|
||||
ctx oauth2.Context
|
||||
scopes []string
|
||||
key string // guarded by package-level mutex, aeTokensMu
|
||||
|
||||
// fetcherFunc makes the actual RPC to fetch a new access token with an expiry time.
|
||||
// Provider of this function is responsible to assert that the given context is valid.
|
||||
fetcherFunc func(ctx oauth2.Context, scope ...string) (string, time.Time, error)
|
||||
}
|
||||
|
||||
func (ts *appEngineTokenSource) Token() (*oauth2.Token, error) {
|
||||
aeTokensMu.Lock()
|
||||
if ts.key == "" {
|
||||
sort.Sort(sort.StringSlice(ts.scopes))
|
||||
ts.key = strings.Join(ts.scopes, string(0))
|
||||
}
|
||||
tok, ok := aeTokens[ts.key]
|
||||
if !ok {
|
||||
tok = &tokenLock{}
|
||||
aeTokens[ts.key] = tok
|
||||
}
|
||||
aeTokensMu.Unlock()
|
||||
|
||||
tok.mu.Lock()
|
||||
defer tok.mu.Unlock()
|
||||
if tok.t != nil && !tok.t.Expired() {
|
||||
return tok.t, nil
|
||||
}
|
||||
access, exp, err := ts.fetcherFunc(ts.ctx, ts.scopes...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tok.t = &oauth2.Token{
|
||||
AccessToken: access,
|
||||
Expiry: exp,
|
||||
}
|
||||
return tok.t, nil
|
||||
}
|
||||
122
Godeps/_workspace/src/golang.org/x/oauth2/google/testdata/gcloud/credentials
generated
vendored
Normal file
122
Godeps/_workspace/src/golang.org/x/oauth2/google/testdata/gcloud/credentials
generated
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"credential": {
|
||||
"_class": "OAuth2Credentials",
|
||||
"_module": "oauth2client.client",
|
||||
"access_token": "foo_access_token",
|
||||
"client_id": "foo_client_id",
|
||||
"client_secret": "foo_client_secret",
|
||||
"id_token": {
|
||||
"at_hash": "foo_at_hash",
|
||||
"aud": "foo_aud",
|
||||
"azp": "foo_azp",
|
||||
"cid": "foo_cid",
|
||||
"email": "foo@example.com",
|
||||
"email_verified": true,
|
||||
"exp": 1420573614,
|
||||
"iat": 1420569714,
|
||||
"id": "1337",
|
||||
"iss": "accounts.google.com",
|
||||
"sub": "1337",
|
||||
"token_hash": "foo_token_hash",
|
||||
"verified_email": true
|
||||
},
|
||||
"invalid": false,
|
||||
"refresh_token": "foo_refresh_token",
|
||||
"revoke_uri": "https://accounts.google.com/o/oauth2/revoke",
|
||||
"token_expiry": "2015-01-09T00:51:51Z",
|
||||
"token_response": {
|
||||
"access_token": "foo_access_token",
|
||||
"expires_in": 3600,
|
||||
"id_token": "foo_id_token",
|
||||
"token_type": "Bearer"
|
||||
},
|
||||
"token_uri": "https://accounts.google.com/o/oauth2/token",
|
||||
"user_agent": "Cloud SDK Command Line Tool"
|
||||
},
|
||||
"key": {
|
||||
"account": "foo@example.com",
|
||||
"clientId": "foo_client_id",
|
||||
"scope": "https://www.googleapis.com/auth/appengine.admin https://www.googleapis.com/auth/bigquery https://www.googleapis.com/auth/compute https://www.googleapis.com/auth/devstorage.full_control https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/ndev.cloudman https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/sqlservice.admin https://www.googleapis.com/auth/prediction https://www.googleapis.com/auth/projecthosting",
|
||||
"type": "google-cloud-sdk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"credential": {
|
||||
"_class": "OAuth2Credentials",
|
||||
"_module": "oauth2client.client",
|
||||
"access_token": "bar_access_token",
|
||||
"client_id": "bar_client_id",
|
||||
"client_secret": "bar_client_secret",
|
||||
"id_token": {
|
||||
"at_hash": "bar_at_hash",
|
||||
"aud": "bar_aud",
|
||||
"azp": "bar_azp",
|
||||
"cid": "bar_cid",
|
||||
"email": "bar@example.com",
|
||||
"email_verified": true,
|
||||
"exp": 1420573614,
|
||||
"iat": 1420569714,
|
||||
"id": "1337",
|
||||
"iss": "accounts.google.com",
|
||||
"sub": "1337",
|
||||
"token_hash": "bar_token_hash",
|
||||
"verified_email": true
|
||||
},
|
||||
"invalid": false,
|
||||
"refresh_token": "bar_refresh_token",
|
||||
"revoke_uri": "https://accounts.google.com/o/oauth2/revoke",
|
||||
"token_expiry": "2015-01-09T00:51:51Z",
|
||||
"token_response": {
|
||||
"access_token": "bar_access_token",
|
||||
"expires_in": 3600,
|
||||
"id_token": "bar_id_token",
|
||||
"token_type": "Bearer"
|
||||
},
|
||||
"token_uri": "https://accounts.google.com/o/oauth2/token",
|
||||
"user_agent": "Cloud SDK Command Line Tool"
|
||||
},
|
||||
"key": {
|
||||
"account": "bar@example.com",
|
||||
"clientId": "bar_client_id",
|
||||
"scope": "https://www.googleapis.com/auth/appengine.admin https://www.googleapis.com/auth/bigquery https://www.googleapis.com/auth/compute https://www.googleapis.com/auth/devstorage.full_control https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/ndev.cloudman https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/sqlservice.admin https://www.googleapis.com/auth/prediction https://www.googleapis.com/auth/projecthosting",
|
||||
"type": "google-cloud-sdk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"credential": {
|
||||
"_class": "ServiceAccountCredentials",
|
||||
"_kwargs": {},
|
||||
"_module": "oauth2client.client",
|
||||
"_private_key_id": "00000000000000000000000000000000",
|
||||
"_private_key_pkcs8_text": "-----BEGIN RSA PRIVATE KEY-----\nMIICWwIBAAKBgQCt3fpiynPSaUhWSIKMGV331zudwJ6GkGmvQtwsoK2S2LbvnSwU\nNxgj4fp08kIDR5p26wF4+t/HrKydMwzftXBfZ9UmLVJgRdSswmS5SmChCrfDS5OE\nvFFcN5+6w1w8/Nu657PF/dse8T0bV95YrqyoR0Osy8WHrUOMSIIbC3hRuwIDAQAB\nAoGAJrGE/KFjn0sQ7yrZ6sXmdLawrM3mObo/2uI9T60+k7SpGbBX0/Pi6nFrJMWZ\nTVONG7P3Mu5aCPzzuVRYJB0j8aldSfzABTY3HKoWCczqw1OztJiEseXGiYz4QOyr\nYU3qDyEpdhS6q6wcoLKGH+hqRmz6pcSEsc8XzOOu7s4xW8kCQQDkc75HjhbarCnd\nJJGMe3U76+6UGmdK67ltZj6k6xoB5WbTNChY9TAyI2JC+ppYV89zv3ssj4L+02u3\nHIHFGxsHAkEAwtU1qYb1tScpchPobnYUFiVKJ7KA8EZaHVaJJODW/cghTCV7BxcJ\nbgVvlmk4lFKn3lPKAgWw7PdQsBTVBUcCrQJATPwoIirizrv3u5soJUQxZIkENAqV\nxmybZx9uetrzP7JTrVbFRf0SScMcyN90hdLJiQL8+i4+gaszgFht7sNMnwJAAbfj\nq0UXcauQwALQ7/h2oONfTg5S+MuGC/AxcXPSMZbMRGGoPh3D5YaCv27aIuS/ukQ+\n6dmm/9AGlCb64fsIWQJAPaokbjIifo+LwC5gyK73Mc4t8nAOSZDenzd/2f6TCq76\nS1dcnKiPxaED7W/y6LJiuBT2rbZiQ2L93NJpFZD/UA==\n-----END RSA PRIVATE KEY-----\n",
|
||||
"_revoke_uri": "https://accounts.google.com/o/oauth2/revoke",
|
||||
"_scopes": "https://www.googleapis.com/auth/appengine.admin https://www.googleapis.com/auth/bigquery https://www.googleapis.com/auth/compute https://www.googleapis.com/auth/devstorage.full_control https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/ndev.cloudman https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/sqlservice.admin https://www.googleapis.com/auth/prediction https://www.googleapis.com/auth/projecthosting",
|
||||
"_service_account_email": "baz@serviceaccount.example.com",
|
||||
"_service_account_id": "baz.serviceaccount.example.com",
|
||||
"_token_uri": "https://accounts.google.com/o/oauth2/token",
|
||||
"_user_agent": "Cloud SDK Command Line Tool",
|
||||
"access_token": null,
|
||||
"assertion_type": null,
|
||||
"client_id": null,
|
||||
"client_secret": null,
|
||||
"id_token": null,
|
||||
"invalid": false,
|
||||
"refresh_token": null,
|
||||
"revoke_uri": "https://accounts.google.com/o/oauth2/revoke",
|
||||
"service_account_name": "baz@serviceaccount.example.com",
|
||||
"token_expiry": null,
|
||||
"token_response": null,
|
||||
"user_agent": "Cloud SDK Command Line Tool"
|
||||
},
|
||||
"key": {
|
||||
"account": "baz@serviceaccount.example.com",
|
||||
"clientId": "baz_client_id",
|
||||
"scope": "https://www.googleapis.com/auth/appengine.admin https://www.googleapis.com/auth/bigquery https://www.googleapis.com/auth/compute https://www.googleapis.com/auth/devstorage.full_control https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/ndev.cloudman https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/sqlservice.admin https://www.googleapis.com/auth/prediction https://www.googleapis.com/auth/projecthosting",
|
||||
"type": "google-cloud-sdk"
|
||||
}
|
||||
}
|
||||
],
|
||||
"file_version": 1
|
||||
}
|
||||
2
Godeps/_workspace/src/golang.org/x/oauth2/google/testdata/gcloud/properties
generated
vendored
Normal file
2
Godeps/_workspace/src/golang.org/x/oauth2/google/testdata/gcloud/properties
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
[core]
|
||||
account = bar@example.com
|
||||
36
Godeps/_workspace/src/golang.org/x/oauth2/internal/oauth2.go
generated
vendored
36
Godeps/_workspace/src/golang.org/x/oauth2/internal/oauth2.go
generated
vendored
@@ -6,10 +6,14 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseKey converts the binary contents of a private key file
|
||||
@@ -26,12 +30,40 @@ func ParseKey(key []byte) (*rsa.PrivateKey, error) {
|
||||
if err != nil {
|
||||
parsedKey, err = x509.ParsePKCS1PrivateKey(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("private key should be a PEM or plain PKSC1 or PKCS8; parse error: %v", err)
|
||||
}
|
||||
}
|
||||
parsed, ok := parsedKey.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("oauth2: private key is invalid")
|
||||
return nil, errors.New("private key is invalid")
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func ParseINI(ini io.Reader) (map[string]map[string]string, error) {
|
||||
result := map[string]map[string]string{
|
||||
"": map[string]string{}, // root section
|
||||
}
|
||||
scanner := bufio.NewScanner(ini)
|
||||
currentSection := ""
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if strings.HasPrefix(line, ";") {
|
||||
// comment.
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
|
||||
currentSection = strings.TrimSpace(line[1 : len(line)-1])
|
||||
result[currentSection] = map[string]string{}
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
if len(parts) == 2 && parts[0] != "" {
|
||||
result[currentSection][strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error scanning ini: %v", err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
62
Godeps/_workspace/src/golang.org/x/oauth2/internal/oauth2_test.go
generated
vendored
Normal file
62
Godeps/_workspace/src/golang.org/x/oauth2/internal/oauth2_test.go
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2014 The oauth2 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 internal contains support packages for oauth2 package.
|
||||
package internal
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseINI(t *testing.T) {
|
||||
tests := []struct {
|
||||
ini string
|
||||
want map[string]map[string]string
|
||||
}{
|
||||
{
|
||||
`root = toor
|
||||
[foo]
|
||||
bar = hop
|
||||
ini = nin
|
||||
`,
|
||||
map[string]map[string]string{
|
||||
"": map[string]string{"root": "toor"},
|
||||
"foo": map[string]string{"bar": "hop", "ini": "nin"},
|
||||
},
|
||||
},
|
||||
{
|
||||
`[empty]
|
||||
[section]
|
||||
empty=
|
||||
`,
|
||||
map[string]map[string]string{
|
||||
"": map[string]string{},
|
||||
"empty": map[string]string{},
|
||||
"section": map[string]string{"empty": ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
`ignore
|
||||
[invalid
|
||||
=stuff
|
||||
;comment=true
|
||||
`,
|
||||
map[string]map[string]string{
|
||||
"": map[string]string{},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
result, err := ParseINI(strings.NewReader(tt.ini))
|
||||
if err != nil {
|
||||
t.Errorf("ParseINI(%q) error %v, want: no error", tt.ini, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(result, tt.want) {
|
||||
t.Errorf("ParseINI(%q) = %#v, want: %#v", tt.ini, result, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Godeps/_workspace/src/golang.org/x/oauth2/jws/jws.go
generated
vendored
2
Godeps/_workspace/src/golang.org/x/oauth2/jws/jws.go
generated
vendored
@@ -4,7 +4,7 @@
|
||||
|
||||
// Package jws provides encoding and decoding utilities for
|
||||
// signed JWS messages.
|
||||
package jws
|
||||
package jws // import "golang.org/x/oauth2/jws"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
31
Godeps/_workspace/src/golang.org/x/oauth2/jwt/example_test.go
generated
vendored
Normal file
31
Godeps/_workspace/src/golang.org/x/oauth2/jwt/example_test.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2014 The oauth2 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 jwt_test
|
||||
|
||||
import (
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/jwt"
|
||||
)
|
||||
|
||||
func ExampleJWTConfig() {
|
||||
conf := &jwt.Config{
|
||||
Email: "xxx@developer.com",
|
||||
// The contents of your RSA private key or your PEM file
|
||||
// that contains a private key.
|
||||
// If you have a p12 file instead, you
|
||||
// can use `openssl` to export the private key into a pem file.
|
||||
//
|
||||
// $ openssl pkcs12 -in key.p12 -out key.pem -nodes
|
||||
//
|
||||
// It only supports PEM containers with no passphrase.
|
||||
PrivateKey: []byte("-----BEGIN RSA PRIVATE KEY-----..."),
|
||||
Subject: "user@example.com",
|
||||
TokenURL: "https://provider.com/o/oauth2/token",
|
||||
}
|
||||
// Initiate an http.Client, the following GET request will be
|
||||
// authorized and authenticated on the behalf of user@example.com.
|
||||
client := conf.Client(oauth2.NoContext)
|
||||
client.Get("...")
|
||||
}
|
||||
@@ -2,7 +2,11 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package oauth2
|
||||
// Package jwt implements the OAuth 2.0 JSON Web Token flow, commonly
|
||||
// known as "two-legged OAuth 2.0".
|
||||
//
|
||||
// See: https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -14,6 +18,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/internal"
|
||||
"golang.org/x/oauth2/jws"
|
||||
)
|
||||
@@ -23,9 +29,9 @@ var (
|
||||
defaultHeader = &jws.Header{Algorithm: "RS256", Typ: "JWT"}
|
||||
)
|
||||
|
||||
// JWTConfig is the configuration for using JWT to fetch tokens,
|
||||
// commonly known as "two-legged OAuth".
|
||||
type JWTConfig struct {
|
||||
// Config is the configuration for using JWT to fetch tokens,
|
||||
// commonly known as "two-legged OAuth 2.0".
|
||||
type Config struct {
|
||||
// Email is the OAuth client identifier used when communicating with
|
||||
// the configured OAuth provider.
|
||||
Email string
|
||||
@@ -52,47 +58,32 @@ type JWTConfig struct {
|
||||
|
||||
// TokenSource returns a JWT TokenSource using the configuration
|
||||
// in c and the HTTP client from the provided context.
|
||||
//
|
||||
// The returned TokenSource only does JWT requests when necessary but
|
||||
// otherwise returns the same token repeatedly until it expires.
|
||||
//
|
||||
// The provided initialToken may be nil, in which case the first
|
||||
// call to TokenSource will do a new JWT request.
|
||||
func (c *JWTConfig) TokenSource(ctx Context, initialToken *Token) TokenSource {
|
||||
return &newWhenNeededSource{
|
||||
t: initialToken,
|
||||
new: jwtSource{ctx, c},
|
||||
}
|
||||
func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource {
|
||||
return oauth2.ReuseTokenSource(nil, jwtSource{ctx, c})
|
||||
}
|
||||
|
||||
// Client returns an HTTP client wrapping the context's
|
||||
// HTTP transport and adding Authorization headers with tokens
|
||||
// obtained from c.
|
||||
//
|
||||
// The provided initialToken may be nil, in which case the first
|
||||
// call to TokenSource will do a new JWT request.
|
||||
//
|
||||
// The returned client and its Transport should not be modified.
|
||||
func (c *JWTConfig) Client(ctx Context, initialToken *Token) *http.Client {
|
||||
return NewClient(ctx, c.TokenSource(ctx, initialToken))
|
||||
func (c *Config) Client(ctx context.Context) *http.Client {
|
||||
return oauth2.NewClient(ctx, c.TokenSource(ctx))
|
||||
}
|
||||
|
||||
// jwtSource is a source that always does a signed JWT request for a token.
|
||||
// It should typically be wrapped with a newWhenNeededSource.
|
||||
// It should typically be wrapped with a reuseTokenSource.
|
||||
type jwtSource struct {
|
||||
ctx Context
|
||||
conf *JWTConfig
|
||||
ctx context.Context
|
||||
conf *Config
|
||||
}
|
||||
|
||||
func (js jwtSource) Token() (*Token, error) {
|
||||
func (js jwtSource) Token() (*oauth2.Token, error) {
|
||||
pk, err := internal.ParseKey(js.conf.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hc, err := contextClient(js.ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hc := oauth2.NewClient(js.ctx, nil)
|
||||
claimSet := &jws.ClaimSet{
|
||||
Iss: js.conf.Email,
|
||||
Scope: strings.Join(js.conf.Scopes, " "),
|
||||
@@ -133,12 +124,13 @@ func (js jwtSource) Token() (*Token, error) {
|
||||
if err := json.Unmarshal(body, &tokenRes); err != nil {
|
||||
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
||||
}
|
||||
token := &Token{
|
||||
token := &oauth2.Token{
|
||||
AccessToken: tokenRes.AccessToken,
|
||||
TokenType: tokenRes.TokenType,
|
||||
raw: make(map[string]interface{}),
|
||||
}
|
||||
json.Unmarshal(body, &token.raw) // no error checks for optional fields
|
||||
raw := make(map[string]interface{})
|
||||
json.Unmarshal(body, &raw) // no error checks for optional fields
|
||||
token = token.WithExtra(raw)
|
||||
|
||||
if secs := tokenRes.ExpiresIn; secs > 0 {
|
||||
token.Expiry = time.Now().Add(time.Duration(secs) * time.Second)
|
||||
@@ -2,12 +2,14 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package oauth2
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var dummyPrivateKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
@@ -50,17 +52,17 @@ func TestJWTFetch_JSONResponse(t *testing.T) {
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
conf := &JWTConfig{
|
||||
conf := &Config{
|
||||
Email: "aaa@xxx.com",
|
||||
PrivateKey: dummyPrivateKey,
|
||||
TokenURL: ts.URL,
|
||||
}
|
||||
tok, err := conf.TokenSource(NoContext, nil).Token()
|
||||
tok, err := conf.TokenSource(oauth2.NoContext).Token()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tok.Expired() {
|
||||
t.Errorf("Token shouldn't be expired")
|
||||
if !tok.Valid() {
|
||||
t.Errorf("Token invalid")
|
||||
}
|
||||
if tok.AccessToken != "90d64460d14870c08c81352a05dedd3465940a7c" {
|
||||
t.Errorf("Unexpected access token, %#v", tok.AccessToken)
|
||||
@@ -84,24 +86,30 @@ func TestJWTFetch_BadResponse(t *testing.T) {
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
conf := &JWTConfig{
|
||||
conf := &Config{
|
||||
Email: "aaa@xxx.com",
|
||||
PrivateKey: dummyPrivateKey,
|
||||
TokenURL: ts.URL,
|
||||
}
|
||||
tok, err := conf.TokenSource(NoContext, nil).Token()
|
||||
tok, err := conf.TokenSource(oauth2.NoContext).Token()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tok.AccessToken != "" {
|
||||
t.Errorf("Unexpected access token, %#v.", tok.AccessToken)
|
||||
if tok == nil {
|
||||
t.Fatalf("token is nil")
|
||||
}
|
||||
if tok.TokenType != "bearer" {
|
||||
t.Errorf("Unexpected token type, %#v.", tok.TokenType)
|
||||
if tok.Valid() {
|
||||
t.Errorf("token is valid. want invalid.")
|
||||
}
|
||||
if tok.AccessToken != "" {
|
||||
t.Errorf("Unexpected non-empty access token %q.", tok.AccessToken)
|
||||
}
|
||||
if want := "bearer"; tok.TokenType != want {
|
||||
t.Errorf("TokenType = %q; want %q", tok.TokenType, want)
|
||||
}
|
||||
scope := tok.Extra("scope")
|
||||
if scope != "user" {
|
||||
t.Errorf("Unexpected value for scope: %v", scope)
|
||||
if want := "user"; scope != want {
|
||||
t.Errorf("token scope = %q; want %q", scope, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,12 +119,12 @@ func TestJWTFetch_BadResponseType(t *testing.T) {
|
||||
w.Write([]byte(`{"access_token":123, "scope": "user", "token_type": "bearer"}`))
|
||||
}))
|
||||
defer ts.Close()
|
||||
conf := &JWTConfig{
|
||||
conf := &Config{
|
||||
Email: "aaa@xxx.com",
|
||||
PrivateKey: dummyPrivateKey,
|
||||
TokenURL: ts.URL,
|
||||
}
|
||||
tok, err := conf.TokenSource(NoContext, nil).Token()
|
||||
tok, err := conf.TokenSource(oauth2.NoContext).Token()
|
||||
if err == nil {
|
||||
t.Error("got a token; expected error")
|
||||
if tok.AccessToken != "" {
|
||||
16
Godeps/_workspace/src/golang.org/x/oauth2/linkedin/linkedin.go
generated
vendored
Normal file
16
Godeps/_workspace/src/golang.org/x/oauth2/linkedin/linkedin.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2015 The oauth2 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 linkedin provides constants for using OAuth2 to access LinkedIn.
|
||||
package linkedin // import "golang.org/x/oauth2/linkedin"
|
||||
|
||||
import (
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// Endpoint is LinkedIn's OAuth 2.0 endpoint.
|
||||
var Endpoint = oauth2.Endpoint{
|
||||
AuthURL: "https://www.linkedin.com/uas/oauth2/authorization",
|
||||
TokenURL: "https://www.linkedin.com/uas/oauth2/accessToken",
|
||||
}
|
||||
262
Godeps/_workspace/src/golang.org/x/oauth2/oauth2.go
generated
vendored
262
Godeps/_workspace/src/golang.org/x/oauth2/oauth2.go
generated
vendored
@@ -5,7 +5,7 @@
|
||||
// Package oauth2 provides support for making
|
||||
// OAuth2 authorized and authenticated HTTP requests.
|
||||
// It can additionally grant authorization with Bearer JWT.
|
||||
package oauth2
|
||||
package oauth2 // import "golang.org/x/oauth2"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -25,18 +25,12 @@ import (
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Context can be an golang.org/x/net.Context, or an App Engine Context.
|
||||
// In the future these will be unified.
|
||||
// If you don't care and aren't running on App Engine, you may use NoContext.
|
||||
type Context interface{}
|
||||
|
||||
// NoContext is the default context. If you're not running this code
|
||||
// on App Engine or not using golang.org/x/net.Context to provide a custom
|
||||
// HTTP client, you should use NoContext.
|
||||
var NoContext Context = nil
|
||||
// NoContext is the default context you should supply if not using
|
||||
// your own context.Context (see https://golang.org/x/net/context).
|
||||
var NoContext = context.TODO()
|
||||
|
||||
// Config describes a typical 3-legged OAuth2 flow, with both the
|
||||
// client application information and the server's URLs.
|
||||
// client application information and the server's endpoint URLs.
|
||||
type Config struct {
|
||||
// ClientID is the application's ID.
|
||||
ClientID string
|
||||
@@ -45,9 +39,9 @@ type Config struct {
|
||||
ClientSecret string
|
||||
|
||||
// Endpoint contains the resource server's token endpoint
|
||||
// URLs. These are supplied by the server and are often
|
||||
// available via site-specific packages (for example,
|
||||
// google.Endpoint or github.Endpoint)
|
||||
// URLs. These are constants specific to each server and are
|
||||
// often available via site-specific packages, such as
|
||||
// google.Endpoint or github.Endpoint.
|
||||
Endpoint Endpoint
|
||||
|
||||
// RedirectURL is the URL to redirect users going through
|
||||
@@ -61,6 +55,8 @@ type Config struct {
|
||||
// A TokenSource is anything that can return a token.
|
||||
type TokenSource interface {
|
||||
// Token returns a token or an error.
|
||||
// Token must be safe for concurrent use by multiple goroutines.
|
||||
// The returned Token must not be modified.
|
||||
Token() (*Token, error)
|
||||
}
|
||||
|
||||
@@ -77,28 +73,34 @@ var (
|
||||
// "access_type" field that gets sent in the URL returned by
|
||||
// AuthCodeURL.
|
||||
//
|
||||
// Online (the default if neither is specified) is the default.
|
||||
// If your application needs to refresh access tokens when the
|
||||
// user is not present at the browser, then use offline. This
|
||||
// will result in your application obtaining a refresh token
|
||||
// the first time your application exchanges an authorization
|
||||
// Online is the default if neither is specified. If your
|
||||
// application needs to refresh access tokens when the user
|
||||
// is not present at the browser, then use offline. This will
|
||||
// result in your application obtaining a refresh token the
|
||||
// first time your application exchanges an authorization
|
||||
// code for a user.
|
||||
AccessTypeOnline AuthCodeOption = setParam{"access_type", "online"}
|
||||
AccessTypeOffline AuthCodeOption = setParam{"access_type", "offline"}
|
||||
AccessTypeOnline AuthCodeOption = SetParam("access_type", "online")
|
||||
AccessTypeOffline AuthCodeOption = SetParam("access_type", "offline")
|
||||
|
||||
// ApprovalForce forces the users to view the consent dialog
|
||||
// and confirm the permissions request at the URL returned
|
||||
// from AuthCodeURL, even if they've already done so.
|
||||
ApprovalForce AuthCodeOption = setParam{"approval_prompt", "force"}
|
||||
ApprovalForce AuthCodeOption = SetParam("approval_prompt", "force")
|
||||
)
|
||||
|
||||
// An AuthCodeOption is passed to Config.AuthCodeURL.
|
||||
type AuthCodeOption interface {
|
||||
setValue(url.Values)
|
||||
}
|
||||
|
||||
type setParam struct{ k, v string }
|
||||
|
||||
func (p setParam) setValue(m url.Values) { m.Set(p.k, p.v) }
|
||||
|
||||
// An AuthCodeOption is passed to Config.AuthCodeURL.
|
||||
type AuthCodeOption interface {
|
||||
setValue(url.Values)
|
||||
// SetParam builds an AuthCodeOption which passes key/value parameters
|
||||
// to a provider's authorization endpoint.
|
||||
func SetParam(key, value string) AuthCodeOption {
|
||||
return setParam{key, value}
|
||||
}
|
||||
|
||||
// AuthCodeURL returns a URL to OAuth 2.0 provider's consent page
|
||||
@@ -133,17 +135,37 @@ func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// PasswordCredentialsToken converts a resource owner username and password
|
||||
// pair into a token.
|
||||
//
|
||||
// Per the RFC, this grant type should only be used "when there is a high
|
||||
// degree of trust between the resource owner and the client (e.g., the client
|
||||
// is part of the device operating system or a highly privileged application),
|
||||
// and when other authorization grant types are not available."
|
||||
// See https://tools.ietf.org/html/rfc6749#section-4.3 for more info.
|
||||
//
|
||||
// The HTTP client to use is derived from the context.
|
||||
// If nil, http.DefaultClient is used.
|
||||
func (c *Config) PasswordCredentialsToken(ctx context.Context, username, password string) (*Token, error) {
|
||||
return retrieveToken(ctx, c, url.Values{
|
||||
"grant_type": {"password"},
|
||||
"username": {username},
|
||||
"password": {password},
|
||||
"scope": condVal(strings.Join(c.Scopes, " ")),
|
||||
})
|
||||
}
|
||||
|
||||
// Exchange converts an authorization code into a token.
|
||||
//
|
||||
// It is used after a resource provider redirects the user back
|
||||
// to the Redirect URI (the URL obtained from AuthCodeURL).
|
||||
//
|
||||
// The HTTP client to use is derived from the context. If nil,
|
||||
// http.DefaultClient is used. See the Context type's documentation.
|
||||
// The HTTP client to use is derived from the context.
|
||||
// If a client is not provided via the context, http.DefaultClient is used.
|
||||
//
|
||||
// The code will be in the *http.Request.FormValue("code"). Before
|
||||
// calling Exchange, be sure to validate FormValue("state").
|
||||
func (c *Config) Exchange(ctx Context, code string) (*Token, error) {
|
||||
func (c *Config) Exchange(ctx context.Context, code string) (*Token, error) {
|
||||
return retrieveToken(ctx, c, url.Values{
|
||||
"grant_type": {"authorization_code"},
|
||||
"code": {code},
|
||||
@@ -156,7 +178,7 @@ func (c *Config) Exchange(ctx Context, code string) (*Token, error) {
|
||||
// given a Context value. If it returns an error, the search stops
|
||||
// with that error. If it returns (nil, nil), the search continues
|
||||
// down the list of registered funcs.
|
||||
type contextClientFunc func(Context) (*http.Client, error)
|
||||
type contextClientFunc func(context.Context) (*http.Client, error)
|
||||
|
||||
var contextClientFuncs []contextClientFunc
|
||||
|
||||
@@ -164,7 +186,7 @@ func registerContextClientFunc(fn contextClientFunc) {
|
||||
contextClientFuncs = append(contextClientFuncs, fn)
|
||||
}
|
||||
|
||||
func contextClient(ctx Context) (*http.Client, error) {
|
||||
func contextClient(ctx context.Context) (*http.Client, error) {
|
||||
for _, fn := range contextClientFuncs {
|
||||
c, err := fn(ctx)
|
||||
if err != nil {
|
||||
@@ -174,15 +196,13 @@ func contextClient(ctx Context) (*http.Client, error) {
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
if xc, ok := ctx.(context.Context); ok {
|
||||
if hc, ok := xc.Value(HTTPClient).(*http.Client); ok {
|
||||
return hc, nil
|
||||
}
|
||||
if hc, ok := ctx.Value(HTTPClient).(*http.Client); ok {
|
||||
return hc, nil
|
||||
}
|
||||
return http.DefaultClient, nil
|
||||
}
|
||||
|
||||
func contextTransport(ctx Context) http.RoundTripper {
|
||||
func contextTransport(ctx context.Context) http.RoundTripper {
|
||||
hc, err := contextClient(ctx)
|
||||
if err != nil {
|
||||
// This is a rare error case (somebody using nil on App Engine),
|
||||
@@ -198,54 +218,64 @@ func contextTransport(ctx Context) http.RoundTripper {
|
||||
// The token will auto-refresh as necessary. The underlying
|
||||
// HTTP transport will be obtained using the provided context.
|
||||
// The returned client and its Transport should not be modified.
|
||||
func (c *Config) Client(ctx Context, t *Token) *http.Client {
|
||||
func (c *Config) Client(ctx context.Context, t *Token) *http.Client {
|
||||
return NewClient(ctx, c.TokenSource(ctx, t))
|
||||
}
|
||||
|
||||
// TokenSource returns a TokenSource that returns t until t expires,
|
||||
// automatically refreshing it as necessary using the provided context.
|
||||
// See the the Context documentation.
|
||||
//
|
||||
// Most users will use Config.Client instead.
|
||||
func (c *Config) TokenSource(ctx Context, t *Token) TokenSource {
|
||||
nwn := &newWhenNeededSource{t: t}
|
||||
nwn.new = tokenRefresher{
|
||||
ctx: ctx,
|
||||
conf: c,
|
||||
oldToken: &nwn.t,
|
||||
func (c *Config) TokenSource(ctx context.Context, t *Token) TokenSource {
|
||||
tkr := &tokenRefresher{
|
||||
ctx: ctx,
|
||||
conf: c,
|
||||
}
|
||||
if t != nil {
|
||||
tkr.refreshToken = t.RefreshToken
|
||||
}
|
||||
return &reuseTokenSource{
|
||||
t: t,
|
||||
new: tkr,
|
||||
}
|
||||
return nwn
|
||||
}
|
||||
|
||||
// tokenRefresher is a TokenSource that makes "grant_type"=="refresh_token"
|
||||
// HTTP requests to renew a token using a RefreshToken.
|
||||
type tokenRefresher struct {
|
||||
ctx Context // used to get HTTP requests
|
||||
conf *Config
|
||||
oldToken **Token // pointer to old *Token w/ RefreshToken
|
||||
ctx context.Context // used to get HTTP requests
|
||||
conf *Config
|
||||
refreshToken string
|
||||
}
|
||||
|
||||
func (tf tokenRefresher) Token() (*Token, error) {
|
||||
t := *tf.oldToken
|
||||
if t == nil {
|
||||
return nil, errors.New("oauth2: attempted use of nil Token")
|
||||
}
|
||||
if t.RefreshToken == "" {
|
||||
// WARNING: Token is not safe for concurrent access, as it
|
||||
// updates the tokenRefresher's refreshToken field.
|
||||
// Within this package, it is used by reuseTokenSource which
|
||||
// synchronizes calls to this method with its own mutex.
|
||||
func (tf *tokenRefresher) Token() (*Token, error) {
|
||||
if tf.refreshToken == "" {
|
||||
return nil, errors.New("oauth2: token expired and refresh token is not set")
|
||||
}
|
||||
return retrieveToken(tf.ctx, tf.conf, url.Values{
|
||||
|
||||
tk, err := retrieveToken(tf.ctx, tf.conf, url.Values{
|
||||
"grant_type": {"refresh_token"},
|
||||
"refresh_token": {t.RefreshToken},
|
||||
"refresh_token": {tf.refreshToken},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tf.refreshToken != tk.RefreshToken {
|
||||
tf.refreshToken = tk.RefreshToken
|
||||
}
|
||||
return tk, err
|
||||
}
|
||||
|
||||
// newWhenNeededSource is a TokenSource that holds a single token in memory
|
||||
// reuseTokenSource is a TokenSource that holds a single token in memory
|
||||
// and validates its expiry before each call to retrieve it with
|
||||
// Token. If it's expired, it will be auto-refreshed using the
|
||||
// new TokenSource.
|
||||
//
|
||||
// The first call to TokenRefresher must be SetToken.
|
||||
type newWhenNeededSource struct {
|
||||
type reuseTokenSource struct {
|
||||
new TokenSource // called when t is expired.
|
||||
|
||||
mu sync.Mutex // guards t
|
||||
@@ -255,10 +285,10 @@ type newWhenNeededSource struct {
|
||||
// Token returns the current token if it's still valid, else will
|
||||
// refresh the current token (using r.Context for HTTP client
|
||||
// information) and return the new one.
|
||||
func (s *newWhenNeededSource) Token() (*Token, error) {
|
||||
func (s *reuseTokenSource) Token() (*Token, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.t != nil && !s.t.Expired() {
|
||||
if s.t.Valid() {
|
||||
return s.t, nil
|
||||
}
|
||||
t, err := s.new.Token()
|
||||
@@ -269,7 +299,7 @@ func (s *newWhenNeededSource) Token() (*Token, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func retrieveToken(ctx Context, c *Config, v url.Values) (*Token, error) {
|
||||
func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) {
|
||||
hc, err := contextClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -284,7 +314,7 @@ func retrieveToken(ctx Context, c *Config, v url.Values) (*Token, error) {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
if !bustedAuth && c.ClientSecret != "" {
|
||||
if !bustedAuth {
|
||||
req.SetBasicAuth(c.ClientID, c.ClientSecret)
|
||||
}
|
||||
r, err := hc.Do(req)
|
||||
@@ -350,11 +380,11 @@ func retrieveToken(ctx Context, c *Config, v url.Values) (*Token, error) {
|
||||
// tokenJSON is the struct representing the HTTP response from OAuth2
|
||||
// providers returning a token in JSON form.
|
||||
type tokenJSON struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
ExpiresIn int32 `json:"expires_in"`
|
||||
Expires int32 `json:"expires"` // broken Facebook spelling of expires_in
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
ExpiresIn expirationTime `json:"expires_in"` // at least PayPal returns string, while most return number
|
||||
Expires expirationTime `json:"expires"` // broken Facebook spelling of expires_in
|
||||
}
|
||||
|
||||
func (e *tokenJSON) expiry() (t time.Time) {
|
||||
@@ -367,6 +397,22 @@ func (e *tokenJSON) expiry() (t time.Time) {
|
||||
return
|
||||
}
|
||||
|
||||
type expirationTime int32
|
||||
|
||||
func (e *expirationTime) UnmarshalJSON(b []byte) error {
|
||||
var n json.Number
|
||||
err := json.Unmarshal(b, &n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i, err := n.Int64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*e = expirationTime(i)
|
||||
return nil
|
||||
}
|
||||
|
||||
func condVal(v string) []string {
|
||||
if v == "" {
|
||||
return nil
|
||||
@@ -374,6 +420,25 @@ func condVal(v string) []string {
|
||||
return []string{v}
|
||||
}
|
||||
|
||||
var brokenAuthHeaderProviders = []string{
|
||||
"https://accounts.google.com/",
|
||||
"https://www.googleapis.com/",
|
||||
"https://github.com/",
|
||||
"https://api.instagram.com/",
|
||||
"https://www.douban.com/",
|
||||
"https://api.dropbox.com/",
|
||||
"https://api.soundcloud.com/",
|
||||
"https://www.linkedin.com/",
|
||||
"https://api.twitch.tv/",
|
||||
"https://oauth.vk.com/",
|
||||
"https://api.odnoklassniki.ru/",
|
||||
"https://connect.stripe.com/",
|
||||
"https://api.pushbullet.com/",
|
||||
"https://oauth.sandbox.trainingpeaks.com/",
|
||||
"https://oauth.trainingpeaks.com/",
|
||||
"https://www.strava.com/oauth/",
|
||||
}
|
||||
|
||||
// providerAuthHeaderWorks reports whether the OAuth2 server identified by the tokenURL
|
||||
// implements the OAuth2 spec correctly
|
||||
// See https://code.google.com/p/goauth2/issues/detail?id=31 for background.
|
||||
@@ -381,16 +446,13 @@ func condVal(v string) []string {
|
||||
// - Reddit only accepts client secret in the Authorization header
|
||||
// - Dropbox accepts either it in URL param or Auth header, but not both.
|
||||
// - Google only accepts URL param (not spec compliant?), not Auth header
|
||||
// - Stripe only accepts client secret in Auth header with Bearer method, not Basic
|
||||
func providerAuthHeaderWorks(tokenURL string) bool {
|
||||
if strings.HasPrefix(tokenURL, "https://accounts.google.com/") ||
|
||||
strings.HasPrefix(tokenURL, "https://github.com/") ||
|
||||
strings.HasPrefix(tokenURL, "https://api.instagram.com/") ||
|
||||
strings.HasPrefix(tokenURL, "https://www.douban.com/") ||
|
||||
strings.HasPrefix(tokenURL, "https://api.dropbox.com/") ||
|
||||
strings.HasPrefix(tokenURL, "https://api.soundcloud.com/") ||
|
||||
strings.HasPrefix(tokenURL, "https://www.linkedin.com/") {
|
||||
// Some sites fail to implement the OAuth2 spec fully.
|
||||
return false
|
||||
for _, s := range brokenAuthHeaderProviders {
|
||||
if strings.HasPrefix(tokenURL, s) {
|
||||
// Some sites fail to implement the OAuth2 spec fully.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Assume the provider implements the spec properly
|
||||
@@ -410,12 +472,52 @@ var HTTPClient contextKey
|
||||
type contextKey struct{}
|
||||
|
||||
// NewClient creates an *http.Client from a Context and TokenSource.
|
||||
// The client's lifetime does not extend beyond the lifetime of the context.
|
||||
func NewClient(ctx Context, src TokenSource) *http.Client {
|
||||
// The returned client is not valid beyond the lifetime of the context.
|
||||
//
|
||||
// As a special case, if src is nil, a non-OAuth2 client is returned
|
||||
// using the provided context. This exists to support related OAuth2
|
||||
// packages.
|
||||
func NewClient(ctx context.Context, src TokenSource) *http.Client {
|
||||
if src == nil {
|
||||
c, err := contextClient(ctx)
|
||||
if err != nil {
|
||||
return &http.Client{Transport: errorTransport{err}}
|
||||
}
|
||||
return c
|
||||
}
|
||||
return &http.Client{
|
||||
Transport: &Transport{
|
||||
Base: contextTransport(ctx),
|
||||
Source: src,
|
||||
Source: ReuseTokenSource(nil, src),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ReuseTokenSource returns a TokenSource which repeatedly returns the
|
||||
// same token as long as it's valid, starting with t.
|
||||
// When its cached token is invalid, a new token is obtained from src.
|
||||
//
|
||||
// ReuseTokenSource is typically used to reuse tokens from a cache
|
||||
// (such as a file on disk) between runs of a program, rather than
|
||||
// obtaining new tokens unnecessarily.
|
||||
//
|
||||
// The initial token t may be nil, in which case the TokenSource is
|
||||
// wrapped in a caching version if it isn't one already. This also
|
||||
// means it's always safe to wrap ReuseTokenSource around any other
|
||||
// TokenSource without adverse effects.
|
||||
func ReuseTokenSource(t *Token, src TokenSource) TokenSource {
|
||||
// Don't wrap a reuseTokenSource in itself. That would work,
|
||||
// but cause an unnecessary number of mutex operations.
|
||||
// Just build the equivalent one.
|
||||
if rt, ok := src.(*reuseTokenSource); ok {
|
||||
if t == nil {
|
||||
// Just use it directly.
|
||||
return rt
|
||||
}
|
||||
src = rt.new
|
||||
}
|
||||
return &reuseTokenSource{
|
||||
t: t,
|
||||
new: src,
|
||||
}
|
||||
}
|
||||
|
||||
187
Godeps/_workspace/src/golang.org/x/oauth2/oauth2_test.go
generated
vendored
187
Godeps/_workspace/src/golang.org/x/oauth2/oauth2_test.go
generated
vendored
@@ -5,11 +5,16 @@
|
||||
package oauth2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
@@ -56,6 +61,15 @@ func TestAuthCodeURL(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthCodeURL_CustomParam(t *testing.T) {
|
||||
conf := newConf("server")
|
||||
param := SetParam("foo", "bar")
|
||||
url := conf.AuthCodeURL("baz", param)
|
||||
if url != "server/auth?client_id=CLIENT_ID&foo=bar&redirect_uri=REDIRECT_URL&response_type=code&scope=scope1+scope2&state=baz" {
|
||||
t.Errorf("Auth code URL doesn't match the expected, found: %v", url)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthCodeURL_Optional(t *testing.T) {
|
||||
conf := &Config{
|
||||
ClientID: "CLIENT_ID",
|
||||
@@ -99,8 +113,8 @@ func TestExchangeRequest(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if tok.Expired() {
|
||||
t.Errorf("Token shouldn't be expired.")
|
||||
if !tok.Valid() {
|
||||
t.Fatalf("Token invalid. Got: %#v", tok)
|
||||
}
|
||||
if tok.AccessToken != "90d64460d14870c08c81352a05dedd3465940a7c" {
|
||||
t.Errorf("Unexpected access token, %#v.", tok.AccessToken)
|
||||
@@ -143,8 +157,8 @@ func TestExchangeRequest_JSONResponse(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if tok.Expired() {
|
||||
t.Errorf("Token shouldn't be expired.")
|
||||
if !tok.Valid() {
|
||||
t.Fatalf("Token invalid. Got: %#v", tok)
|
||||
}
|
||||
if tok.AccessToken != "90d64460d14870c08c81352a05dedd3465940a7c" {
|
||||
t.Errorf("Unexpected access token, %#v.", tok.AccessToken)
|
||||
@@ -158,6 +172,60 @@ func TestExchangeRequest_JSONResponse(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
const day = 24 * time.Hour
|
||||
|
||||
func TestExchangeRequest_JSONResponse_Expiry(t *testing.T) {
|
||||
seconds := int32(day.Seconds())
|
||||
jsonNumberType := reflect.TypeOf(json.Number("0"))
|
||||
for _, c := range []struct {
|
||||
expires string
|
||||
expect error
|
||||
}{
|
||||
{fmt.Sprintf(`"expires_in": %d`, seconds), nil},
|
||||
{fmt.Sprintf(`"expires_in": "%d"`, seconds), nil}, // PayPal case
|
||||
{fmt.Sprintf(`"expires": %d`, seconds), nil}, // Facebook case
|
||||
{`"expires": false`, &json.UnmarshalTypeError{Value: "bool", Type: jsonNumberType}}, // wrong type
|
||||
{`"expires": {}`, &json.UnmarshalTypeError{Value: "object", Type: jsonNumberType}}, // wrong type
|
||||
{`"expires": "zzz"`, &strconv.NumError{Func: "ParseInt", Num: "zzz", Err: strconv.ErrSyntax}}, // wrong value
|
||||
} {
|
||||
testExchangeRequest_JSONResponse_expiry(t, c.expires, c.expect)
|
||||
}
|
||||
}
|
||||
|
||||
func testExchangeRequest_JSONResponse_expiry(t *testing.T, exp string, expect error) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(fmt.Sprintf(`{"access_token": "90d", "scope": "user", "token_type": "bearer", %s}`, exp)))
|
||||
}))
|
||||
defer ts.Close()
|
||||
conf := newConf(ts.URL)
|
||||
t1 := time.Now().Add(day)
|
||||
tok, err := conf.Exchange(NoContext, "exchange-code")
|
||||
t2 := time.Now().Add(day)
|
||||
// Do a fmt.Sprint comparison so either side can be
|
||||
// nil. fmt.Sprint just stringifies them to "<nil>", and no
|
||||
// non-nil expected error ever stringifies as "<nil>", so this
|
||||
// isn't terribly disgusting. We do this because Go 1.4 and
|
||||
// Go 1.5 return a different deep value for
|
||||
// json.UnmarshalTypeError. In Go 1.5, the
|
||||
// json.UnmarshalTypeError contains a new field with a new
|
||||
// non-zero value. Rather than ignore it here with reflect or
|
||||
// add new files and +build tags, just look at the strings.
|
||||
if fmt.Sprint(err) != fmt.Sprint(expect) {
|
||||
t.Errorf("Error = %v; want %v", err, expect)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !tok.Valid() {
|
||||
t.Fatalf("Token invalid. Got: %#v", tok)
|
||||
}
|
||||
expiry := tok.Expiry
|
||||
if expiry.Before(t1) || expiry.After(t2) {
|
||||
t.Errorf("Unexpected value for Expiry: %v (shold be between %v and %v)", expiry, t1, t2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExchangeRequest_BadResponse(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@@ -210,6 +278,53 @@ func TestExchangeRequest_NonBasicAuth(t *testing.T) {
|
||||
conf.Exchange(ctx, "code")
|
||||
}
|
||||
|
||||
func TestPasswordCredentialsTokenRequest(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
expected := "/token"
|
||||
if r.URL.String() != expected {
|
||||
t.Errorf("URL = %q; want %q", r.URL, expected)
|
||||
}
|
||||
headerAuth := r.Header.Get("Authorization")
|
||||
expected = "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ="
|
||||
if headerAuth != expected {
|
||||
t.Errorf("Authorization header = %q; want %q", headerAuth, expected)
|
||||
}
|
||||
headerContentType := r.Header.Get("Content-Type")
|
||||
expected = "application/x-www-form-urlencoded"
|
||||
if headerContentType != expected {
|
||||
t.Errorf("Content-Type header = %q; want %q", headerContentType, expected)
|
||||
}
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Errorf("Failed reading request body: %s.", err)
|
||||
}
|
||||
expected = "client_id=CLIENT_ID&grant_type=password&password=password1&scope=scope1+scope2&username=user1"
|
||||
if string(body) != expected {
|
||||
t.Errorf("res.Body = %q; want %q", string(body), expected)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
w.Write([]byte("access_token=90d64460d14870c08c81352a05dedd3465940a7c&scope=user&token_type=bearer"))
|
||||
}))
|
||||
defer ts.Close()
|
||||
conf := newConf(ts.URL)
|
||||
tok, err := conf.PasswordCredentialsToken(NoContext, "user1", "password1")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !tok.Valid() {
|
||||
t.Fatalf("Token invalid. Got: %#v", tok)
|
||||
}
|
||||
expected := "90d64460d14870c08c81352a05dedd3465940a7c"
|
||||
if tok.AccessToken != expected {
|
||||
t.Errorf("AccessToken = %q; want %q", tok.AccessToken, expected)
|
||||
}
|
||||
expected = "bearer"
|
||||
if tok.TokenType != expected {
|
||||
t.Errorf("TokenType = %q; want %q", tok.TokenType, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenRefreshRequest(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.String() == "/somethingelse" {
|
||||
@@ -258,3 +373,67 @@ func TestFetchWithNoRefreshToken(t *testing.T) {
|
||||
t.Errorf("Fetch should return an error if no refresh token is set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshToken_RefreshTokenReplacement(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"access_token":"ACCESS TOKEN", "scope": "user", "token_type": "bearer", "refresh_token": "NEW REFRESH TOKEN"}`))
|
||||
return
|
||||
}))
|
||||
defer ts.Close()
|
||||
conf := newConf(ts.URL)
|
||||
tkr := tokenRefresher{
|
||||
conf: conf,
|
||||
ctx: NoContext,
|
||||
refreshToken: "OLD REFRESH TOKEN",
|
||||
}
|
||||
tk, err := tkr.Token()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected refreshToken error returned: %v", err)
|
||||
return
|
||||
}
|
||||
if tk.RefreshToken != tkr.refreshToken {
|
||||
t.Errorf("tokenRefresher.refresh_token = %s; want %s", tkr.refreshToken, tk.RefreshToken)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigClientWithToken(t *testing.T) {
|
||||
tok := &Token{
|
||||
AccessToken: "abc123",
|
||||
}
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, want := r.Header.Get("Authorization"), fmt.Sprintf("Bearer %s", tok.AccessToken); got != want {
|
||||
t.Errorf("Authorization header = %q; want %q", got, want)
|
||||
}
|
||||
return
|
||||
}))
|
||||
defer ts.Close()
|
||||
conf := newConf(ts.URL)
|
||||
|
||||
c := conf.Client(NoContext, tok)
|
||||
req, err := http.NewRequest("GET", ts.URL, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
_, err = c.Do(req)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_providerAuthHeaderWorks(t *testing.T) {
|
||||
for _, p := range brokenAuthHeaderProviders {
|
||||
if providerAuthHeaderWorks(p) {
|
||||
t.Errorf("URL: %s not found in list", p)
|
||||
}
|
||||
p := fmt.Sprintf("%ssomesuffix", p)
|
||||
if providerAuthHeaderWorks(p) {
|
||||
t.Errorf("URL: %s not found in list", p)
|
||||
}
|
||||
}
|
||||
p := "https://api.not-in-the-list-example.com/"
|
||||
if !providerAuthHeaderWorks(p) {
|
||||
t.Errorf("URL: %s found in list", p)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
16
Godeps/_workspace/src/golang.org/x/oauth2/odnoklassniki/odnoklassniki.go
generated
vendored
Normal file
16
Godeps/_workspace/src/golang.org/x/oauth2/odnoklassniki/odnoklassniki.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2015 The oauth2 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 odnoklassniki provides constants for using OAuth2 to access Odnoklassniki.
|
||||
package odnoklassniki // import "golang.org/x/oauth2/odnoklassniki"
|
||||
|
||||
import (
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// Endpoint is Odnoklassniki's OAuth 2.0 endpoint.
|
||||
var Endpoint = oauth2.Endpoint{
|
||||
AuthURL: "https://www.odnoklassniki.ru/oauth/authorize",
|
||||
TokenURL: "https://api.odnoklassniki.ru/oauth/token.do",
|
||||
}
|
||||
22
Godeps/_workspace/src/golang.org/x/oauth2/paypal/paypal.go
generated
vendored
Normal file
22
Godeps/_workspace/src/golang.org/x/oauth2/paypal/paypal.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2015 The oauth2 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 paypal provides constants for using OAuth2 to access PayPal.
|
||||
package paypal // import "golang.org/x/oauth2/paypal"
|
||||
|
||||
import (
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// Endpoint is PayPal's OAuth 2.0 endpoint in live (production) environment.
|
||||
var Endpoint = oauth2.Endpoint{
|
||||
AuthURL: "https://www.paypal.com/webapps/auth/protocol/openidconnect/v1/authorize",
|
||||
TokenURL: "https://api.paypal.com/v1/identity/openidconnect/tokenservice",
|
||||
}
|
||||
|
||||
// SandboxEndpoint is PayPal's OAuth 2.0 endpoint in sandbox (testing) environment.
|
||||
var SandboxEndpoint = oauth2.Endpoint{
|
||||
AuthURL: "https://www.sandbox.paypal.com/webapps/auth/protocol/openidconnect/v1/authorize",
|
||||
TokenURL: "https://api.sandbox.paypal.com/v1/identity/openidconnect/tokenservice",
|
||||
}
|
||||
47
Godeps/_workspace/src/golang.org/x/oauth2/token.go
generated
vendored
47
Godeps/_workspace/src/golang.org/x/oauth2/token.go
generated
vendored
@@ -10,13 +10,18 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// expiryDelta determines how earlier a token should be considered
|
||||
// expired than its actual expiration time. It is used to avoid late
|
||||
// expirations due to client-server time mismatches.
|
||||
const expiryDelta = 10 * time.Second
|
||||
|
||||
// Token represents the crendentials used to authorize
|
||||
// the requests to access protected resources on the OAuth 2.0
|
||||
// provider's backend.
|
||||
//
|
||||
// Most users of this package should not access fields of Token
|
||||
// directly. They're exported mostly for use by related packages
|
||||
// implementing derivate OAuth2 flows.
|
||||
// implementing derivative OAuth2 flows.
|
||||
type Token struct {
|
||||
// AccessToken is the token that authorizes and authenticates
|
||||
// the requests.
|
||||
@@ -60,28 +65,40 @@ func (t *Token) SetAuthHeader(r *http.Request) {
|
||||
r.Header.Set("Authorization", t.Type()+" "+t.AccessToken)
|
||||
}
|
||||
|
||||
// Extra returns an extra field returned from the server during token
|
||||
// retrieval.
|
||||
func (t *Token) Extra(key string) string {
|
||||
// WithExtra returns a new Token that's a clone of t, but using the
|
||||
// provided raw extra map. This is only intended for use by packages
|
||||
// implementing derivative OAuth2 flows.
|
||||
func (t *Token) WithExtra(extra interface{}) *Token {
|
||||
t2 := new(Token)
|
||||
*t2 = *t
|
||||
t2.raw = extra
|
||||
return t2
|
||||
}
|
||||
|
||||
// Extra returns an extra field.
|
||||
// Extra fields are key-value pairs returned by the server as a
|
||||
// part of the token retrieval response.
|
||||
func (t *Token) Extra(key string) interface{} {
|
||||
if vals, ok := t.raw.(url.Values); ok {
|
||||
// TODO(jbd): Cast numeric values to int64 or float64.
|
||||
return vals.Get(key)
|
||||
}
|
||||
if raw, ok := t.raw.(map[string]interface{}); ok {
|
||||
if val, ok := raw[key].(string); ok {
|
||||
return val
|
||||
}
|
||||
return raw[key]
|
||||
}
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
// Expired returns true if there is no access token or the
|
||||
// access token is expired.
|
||||
func (t *Token) Expired() bool {
|
||||
if t.AccessToken == "" {
|
||||
return true
|
||||
}
|
||||
// expired reports whether the token is expired.
|
||||
// t must be non-nil.
|
||||
func (t *Token) expired() bool {
|
||||
if t.Expiry.IsZero() {
|
||||
return false
|
||||
}
|
||||
return t.Expiry.Before(time.Now())
|
||||
return t.Expiry.Add(-expiryDelta).Before(time.Now())
|
||||
}
|
||||
|
||||
// Valid reports whether t is non-nil, has an AccessToken, and is not expired.
|
||||
func (t *Token) Valid() bool {
|
||||
return t != nil && t.AccessToken != "" && !t.expired()
|
||||
}
|
||||
|
||||
50
Godeps/_workspace/src/golang.org/x/oauth2/token_test.go
generated
vendored
Normal file
50
Godeps/_workspace/src/golang.org/x/oauth2/token_test.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2014 The oauth2 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 oauth2
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTokenExtra(t *testing.T) {
|
||||
type testCase struct {
|
||||
key string
|
||||
val interface{}
|
||||
want interface{}
|
||||
}
|
||||
const key = "extra-key"
|
||||
cases := []testCase{
|
||||
{key: key, val: "abc", want: "abc"},
|
||||
{key: key, val: 123, want: 123},
|
||||
{key: key, val: "", want: ""},
|
||||
{key: "other-key", val: "def", want: nil},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
extra := make(map[string]interface{})
|
||||
extra[tc.key] = tc.val
|
||||
tok := &Token{raw: extra}
|
||||
if got, want := tok.Extra(key), tc.want; got != want {
|
||||
t.Errorf("Extra(%q) = %q; want %q", key, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenExpiry(t *testing.T) {
|
||||
now := time.Now()
|
||||
cases := []struct {
|
||||
name string
|
||||
tok *Token
|
||||
want bool
|
||||
}{
|
||||
{name: "12 seconds", tok: &Token{Expiry: now.Add(12 * time.Second)}, want: false},
|
||||
{name: "10 seconds", tok: &Token{Expiry: now.Add(expiryDelta)}, want: true},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
if got, want := tc.tok.expired(), tc.want; got != want {
|
||||
t.Errorf("expired (%q) = %v; want %v", tc.name, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
10
Godeps/_workspace/src/golang.org/x/oauth2/transport_test.go
generated
vendored
10
Godeps/_workspace/src/golang.org/x/oauth2/transport_test.go
generated
vendored
@@ -32,10 +32,10 @@ func TestTransportTokenSource(t *testing.T) {
|
||||
client.Get(server.URL)
|
||||
}
|
||||
|
||||
func TestExpiredWithNoAccessToken(t *testing.T) {
|
||||
func TestTokenValidNoAccessToken(t *testing.T) {
|
||||
token := &Token{}
|
||||
if !token.Expired() {
|
||||
t.Errorf("Token should be expired if no access token is provided")
|
||||
if token.Valid() {
|
||||
t.Errorf("Token should not be valid with no access token")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ func TestExpiredWithExpiry(t *testing.T) {
|
||||
token := &Token{
|
||||
Expiry: time.Now().Add(-5 * time.Hour),
|
||||
}
|
||||
if !token.Expired() {
|
||||
t.Errorf("Token should be expired if no access token is provided")
|
||||
if token.Valid() {
|
||||
t.Errorf("Token should not be valid if it expired in the past")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
Godeps/_workspace/src/golang.org/x/oauth2/vk/vk.go
generated
vendored
Normal file
16
Godeps/_workspace/src/golang.org/x/oauth2/vk/vk.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2015 The oauth2 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 vk provides constants for using OAuth2 to access VK.com.
|
||||
package vk // import "golang.org/x/oauth2/vk"
|
||||
|
||||
import (
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// Endpoint is VK's OAuth 2.0 endpoint.
|
||||
var Endpoint = oauth2.Endpoint{
|
||||
AuthURL: "https://oauth.vk.com/authorize",
|
||||
TokenURL: "https://oauth.vk.com/access_token",
|
||||
}
|
||||
11
Godeps/_workspace/src/gopkg.in/bufio.v1/.travis.yml
generated
vendored
Normal file
11
Godeps/_workspace/src/gopkg.in/bufio.v1/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.0
|
||||
- 1.1
|
||||
- 1.2
|
||||
- tip
|
||||
|
||||
install:
|
||||
- go get launchpad.net/gocheck
|
||||
- go get gopkg.in/bufio.v1
|
||||
27
Godeps/_workspace/src/gopkg.in/bufio.v1/LICENSE
generated
vendored
Normal file
27
Godeps/_workspace/src/gopkg.in/bufio.v1/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2013 The bufio Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
2
Godeps/_workspace/src/gopkg.in/bufio.v1/Makefile
generated
vendored
Normal file
2
Godeps/_workspace/src/gopkg.in/bufio.v1/Makefile
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
all:
|
||||
go test gopkg.in/bufio.v1
|
||||
4
Godeps/_workspace/src/gopkg.in/bufio.v1/README.md
generated
vendored
Normal file
4
Godeps/_workspace/src/gopkg.in/bufio.v1/README.md
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
bufio
|
||||
=====
|
||||
|
||||
This is a fork of the http://golang.org/pkg/bufio/ package. It adds `ReadN` method that allows reading next `n` bytes from the internal buffer without allocating intermediate buffer. This method works just like the [Buffer.Next](http://golang.org/pkg/bytes/#Buffer.Next) method, but has slightly different signature.
|
||||
413
Godeps/_workspace/src/gopkg.in/bufio.v1/buffer.go
generated
vendored
Normal file
413
Godeps/_workspace/src/gopkg.in/bufio.v1/buffer.go
generated
vendored
Normal file
@@ -0,0 +1,413 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bufio
|
||||
|
||||
// Simple byte buffer for marshaling data.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// A Buffer is a variable-sized buffer of bytes with Read and Write methods.
|
||||
// The zero value for Buffer is an empty buffer ready to use.
|
||||
type Buffer struct {
|
||||
buf []byte // contents are the bytes buf[off : len(buf)]
|
||||
off int // read at &buf[off], write at &buf[len(buf)]
|
||||
runeBytes [utf8.UTFMax]byte // avoid allocation of slice on each WriteByte or Rune
|
||||
bootstrap [64]byte // memory to hold first slice; helps small buffers (Printf) avoid allocation.
|
||||
lastRead readOp // last read operation, so that Unread* can work correctly.
|
||||
}
|
||||
|
||||
// The readOp constants describe the last action performed on
|
||||
// the buffer, so that UnreadRune and UnreadByte can
|
||||
// check for invalid usage.
|
||||
type readOp int
|
||||
|
||||
const (
|
||||
opInvalid readOp = iota // Non-read operation.
|
||||
opReadRune // Read rune.
|
||||
opRead // Any other read operation.
|
||||
)
|
||||
|
||||
// ErrTooLarge is passed to panic if memory cannot be allocated to store data in a buffer.
|
||||
var ErrTooLarge = errors.New("bytes.Buffer: too large")
|
||||
|
||||
// Bytes returns a slice of the contents of the unread portion of the buffer;
|
||||
// len(b.Bytes()) == b.Len(). If the caller changes the contents of the
|
||||
// returned slice, the contents of the buffer will change provided there
|
||||
// are no intervening method calls on the Buffer.
|
||||
func (b *Buffer) Bytes() []byte { return b.buf[b.off:] }
|
||||
|
||||
// String returns the contents of the unread portion of the buffer
|
||||
// as a string. If the Buffer is a nil pointer, it returns "<nil>".
|
||||
func (b *Buffer) String() string {
|
||||
if b == nil {
|
||||
// Special case, useful in debugging.
|
||||
return "<nil>"
|
||||
}
|
||||
return string(b.buf[b.off:])
|
||||
}
|
||||
|
||||
// Len returns the number of bytes of the unread portion of the buffer;
|
||||
// b.Len() == len(b.Bytes()).
|
||||
func (b *Buffer) Len() int { return len(b.buf) - b.off }
|
||||
|
||||
// Truncate discards all but the first n unread bytes from the buffer.
|
||||
// It panics if n is negative or greater than the length of the buffer.
|
||||
func (b *Buffer) Truncate(n int) {
|
||||
b.lastRead = opInvalid
|
||||
switch {
|
||||
case n < 0 || n > b.Len():
|
||||
panic("bytes.Buffer: truncation out of range")
|
||||
case n == 0:
|
||||
// Reuse buffer space.
|
||||
b.off = 0
|
||||
}
|
||||
b.buf = b.buf[0 : b.off+n]
|
||||
}
|
||||
|
||||
// Reset resets the buffer so it has no content.
|
||||
// b.Reset() is the same as b.Truncate(0).
|
||||
func (b *Buffer) Reset() { b.Truncate(0) }
|
||||
|
||||
// grow grows the buffer to guarantee space for n more bytes.
|
||||
// It returns the index where bytes should be written.
|
||||
// If the buffer can't grow it will panic with ErrTooLarge.
|
||||
func (b *Buffer) grow(n int) int {
|
||||
m := b.Len()
|
||||
// If buffer is empty, reset to recover space.
|
||||
if m == 0 && b.off != 0 {
|
||||
b.Truncate(0)
|
||||
}
|
||||
if len(b.buf)+n > cap(b.buf) {
|
||||
var buf []byte
|
||||
if b.buf == nil && n <= len(b.bootstrap) {
|
||||
buf = b.bootstrap[0:]
|
||||
} else if m+n <= cap(b.buf)/2 {
|
||||
// We can slide things down instead of allocating a new
|
||||
// slice. We only need m+n <= cap(b.buf) to slide, but
|
||||
// we instead let capacity get twice as large so we
|
||||
// don't spend all our time copying.
|
||||
copy(b.buf[:], b.buf[b.off:])
|
||||
buf = b.buf[:m]
|
||||
} else {
|
||||
// not enough space anywhere
|
||||
buf = makeSlice(2*cap(b.buf) + n)
|
||||
copy(buf, b.buf[b.off:])
|
||||
}
|
||||
b.buf = buf
|
||||
b.off = 0
|
||||
}
|
||||
b.buf = b.buf[0 : b.off+m+n]
|
||||
return b.off + m
|
||||
}
|
||||
|
||||
// Grow grows the buffer's capacity, if necessary, to guarantee space for
|
||||
// another n bytes. After Grow(n), at least n bytes can be written to the
|
||||
// buffer without another allocation.
|
||||
// If n is negative, Grow will panic.
|
||||
// If the buffer can't grow it will panic with ErrTooLarge.
|
||||
func (b *Buffer) Grow(n int) {
|
||||
if n < 0 {
|
||||
panic("bytes.Buffer.Grow: negative count")
|
||||
}
|
||||
m := b.grow(n)
|
||||
b.buf = b.buf[0:m]
|
||||
}
|
||||
|
||||
// Write appends the contents of p to the buffer, growing the buffer as
|
||||
// needed. The return value n is the length of p; err is always nil. If the
|
||||
// buffer becomes too large, Write will panic with ErrTooLarge.
|
||||
func (b *Buffer) Write(p []byte) (n int, err error) {
|
||||
b.lastRead = opInvalid
|
||||
m := b.grow(len(p))
|
||||
return copy(b.buf[m:], p), nil
|
||||
}
|
||||
|
||||
// WriteString appends the contents of s to the buffer, growing the buffer as
|
||||
// needed. The return value n is the length of s; err is always nil. If the
|
||||
// buffer becomes too large, WriteString will panic with ErrTooLarge.
|
||||
func (b *Buffer) WriteString(s string) (n int, err error) {
|
||||
b.lastRead = opInvalid
|
||||
m := b.grow(len(s))
|
||||
return copy(b.buf[m:], s), nil
|
||||
}
|
||||
|
||||
// MinRead is the minimum slice size passed to a Read call by
|
||||
// Buffer.ReadFrom. As long as the Buffer has at least MinRead bytes beyond
|
||||
// what is required to hold the contents of r, ReadFrom will not grow the
|
||||
// underlying buffer.
|
||||
const MinRead = 512
|
||||
|
||||
// ReadFrom reads data from r until EOF and appends it to the buffer, growing
|
||||
// the buffer as needed. The return value n is the number of bytes read. Any
|
||||
// error except io.EOF encountered during the read is also returned. If the
|
||||
// buffer becomes too large, ReadFrom will panic with ErrTooLarge.
|
||||
func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
b.lastRead = opInvalid
|
||||
// If buffer is empty, reset to recover space.
|
||||
if b.off >= len(b.buf) {
|
||||
b.Truncate(0)
|
||||
}
|
||||
for {
|
||||
if free := cap(b.buf) - len(b.buf); free < MinRead {
|
||||
// not enough space at end
|
||||
newBuf := b.buf
|
||||
if b.off+free < MinRead {
|
||||
// not enough space using beginning of buffer;
|
||||
// double buffer capacity
|
||||
newBuf = makeSlice(2*cap(b.buf) + MinRead)
|
||||
}
|
||||
copy(newBuf, b.buf[b.off:])
|
||||
b.buf = newBuf[:len(b.buf)-b.off]
|
||||
b.off = 0
|
||||
}
|
||||
m, e := r.Read(b.buf[len(b.buf):cap(b.buf)])
|
||||
b.buf = b.buf[0 : len(b.buf)+m]
|
||||
n += int64(m)
|
||||
if e == io.EOF {
|
||||
break
|
||||
}
|
||||
if e != nil {
|
||||
return n, e
|
||||
}
|
||||
}
|
||||
return n, nil // err is EOF, so return nil explicitly
|
||||
}
|
||||
|
||||
// makeSlice allocates a slice of size n. If the allocation fails, it panics
|
||||
// with ErrTooLarge.
|
||||
func makeSlice(n int) []byte {
|
||||
// If the make fails, give a known error.
|
||||
defer func() {
|
||||
if recover() != nil {
|
||||
panic(ErrTooLarge)
|
||||
}
|
||||
}()
|
||||
return make([]byte, n)
|
||||
}
|
||||
|
||||
// WriteTo writes data to w until the buffer is drained or an error occurs.
|
||||
// The return value n is the number of bytes written; it always fits into an
|
||||
// int, but it is int64 to match the io.WriterTo interface. Any error
|
||||
// encountered during the write is also returned.
|
||||
func (b *Buffer) WriteTo(w io.Writer) (n int64, err error) {
|
||||
b.lastRead = opInvalid
|
||||
if b.off < len(b.buf) {
|
||||
nBytes := b.Len()
|
||||
m, e := w.Write(b.buf[b.off:])
|
||||
if m > nBytes {
|
||||
panic("bytes.Buffer.WriteTo: invalid Write count")
|
||||
}
|
||||
b.off += m
|
||||
n = int64(m)
|
||||
if e != nil {
|
||||
return n, e
|
||||
}
|
||||
// all bytes should have been written, by definition of
|
||||
// Write method in io.Writer
|
||||
if m != nBytes {
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
}
|
||||
// Buffer is now empty; reset.
|
||||
b.Truncate(0)
|
||||
return
|
||||
}
|
||||
|
||||
// WriteByte appends the byte c to the buffer, growing the buffer as needed.
|
||||
// The returned error is always nil, but is included to match bufio.Writer's
|
||||
// WriteByte. If the buffer becomes too large, WriteByte will panic with
|
||||
// ErrTooLarge.
|
||||
func (b *Buffer) WriteByte(c byte) error {
|
||||
b.lastRead = opInvalid
|
||||
m := b.grow(1)
|
||||
b.buf[m] = c
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteRune appends the UTF-8 encoding of Unicode code point r to the
|
||||
// buffer, returning its length and an error, which is always nil but is
|
||||
// included to match bufio.Writer's WriteRune. The buffer is grown as needed;
|
||||
// if it becomes too large, WriteRune will panic with ErrTooLarge.
|
||||
func (b *Buffer) WriteRune(r rune) (n int, err error) {
|
||||
if r < utf8.RuneSelf {
|
||||
b.WriteByte(byte(r))
|
||||
return 1, nil
|
||||
}
|
||||
n = utf8.EncodeRune(b.runeBytes[0:], r)
|
||||
b.Write(b.runeBytes[0:n])
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Read reads the next len(p) bytes from the buffer or until the buffer
|
||||
// is drained. The return value n is the number of bytes read. If the
|
||||
// buffer has no data to return, err is io.EOF (unless len(p) is zero);
|
||||
// otherwise it is nil.
|
||||
func (b *Buffer) Read(p []byte) (n int, err error) {
|
||||
b.lastRead = opInvalid
|
||||
if b.off >= len(b.buf) {
|
||||
// Buffer is empty, reset to recover space.
|
||||
b.Truncate(0)
|
||||
if len(p) == 0 {
|
||||
return
|
||||
}
|
||||
return 0, io.EOF
|
||||
}
|
||||
n = copy(p, b.buf[b.off:])
|
||||
b.off += n
|
||||
if n > 0 {
|
||||
b.lastRead = opRead
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Next returns a slice containing the next n bytes from the buffer,
|
||||
// advancing the buffer as if the bytes had been returned by Read.
|
||||
// If there are fewer than n bytes in the buffer, Next returns the entire buffer.
|
||||
// The slice is only valid until the next call to a read or write method.
|
||||
func (b *Buffer) Next(n int) []byte {
|
||||
b.lastRead = opInvalid
|
||||
m := b.Len()
|
||||
if n > m {
|
||||
n = m
|
||||
}
|
||||
data := b.buf[b.off : b.off+n]
|
||||
b.off += n
|
||||
if n > 0 {
|
||||
b.lastRead = opRead
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// ReadByte reads and returns the next byte from the buffer.
|
||||
// If no byte is available, it returns error io.EOF.
|
||||
func (b *Buffer) ReadByte() (c byte, err error) {
|
||||
b.lastRead = opInvalid
|
||||
if b.off >= len(b.buf) {
|
||||
// Buffer is empty, reset to recover space.
|
||||
b.Truncate(0)
|
||||
return 0, io.EOF
|
||||
}
|
||||
c = b.buf[b.off]
|
||||
b.off++
|
||||
b.lastRead = opRead
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// ReadRune reads and returns the next UTF-8-encoded
|
||||
// Unicode code point from the buffer.
|
||||
// If no bytes are available, the error returned is io.EOF.
|
||||
// If the bytes are an erroneous UTF-8 encoding, it
|
||||
// consumes one byte and returns U+FFFD, 1.
|
||||
func (b *Buffer) ReadRune() (r rune, size int, err error) {
|
||||
b.lastRead = opInvalid
|
||||
if b.off >= len(b.buf) {
|
||||
// Buffer is empty, reset to recover space.
|
||||
b.Truncate(0)
|
||||
return 0, 0, io.EOF
|
||||
}
|
||||
b.lastRead = opReadRune
|
||||
c := b.buf[b.off]
|
||||
if c < utf8.RuneSelf {
|
||||
b.off++
|
||||
return rune(c), 1, nil
|
||||
}
|
||||
r, n := utf8.DecodeRune(b.buf[b.off:])
|
||||
b.off += n
|
||||
return r, n, nil
|
||||
}
|
||||
|
||||
// UnreadRune unreads the last rune returned by ReadRune.
|
||||
// If the most recent read or write operation on the buffer was
|
||||
// not a ReadRune, UnreadRune returns an error. (In this regard
|
||||
// it is stricter than UnreadByte, which will unread the last byte
|
||||
// from any read operation.)
|
||||
func (b *Buffer) UnreadRune() error {
|
||||
if b.lastRead != opReadRune {
|
||||
return errors.New("bytes.Buffer: UnreadRune: previous operation was not ReadRune")
|
||||
}
|
||||
b.lastRead = opInvalid
|
||||
if b.off > 0 {
|
||||
_, n := utf8.DecodeLastRune(b.buf[0:b.off])
|
||||
b.off -= n
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnreadByte unreads the last byte returned by the most recent
|
||||
// read operation. If write has happened since the last read, UnreadByte
|
||||
// returns an error.
|
||||
func (b *Buffer) UnreadByte() error {
|
||||
if b.lastRead != opReadRune && b.lastRead != opRead {
|
||||
return errors.New("bytes.Buffer: UnreadByte: previous operation was not a read")
|
||||
}
|
||||
b.lastRead = opInvalid
|
||||
if b.off > 0 {
|
||||
b.off--
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadBytes reads until the first occurrence of delim in the input,
|
||||
// returning a slice containing the data up to and including the delimiter.
|
||||
// If ReadBytes encounters an error before finding a delimiter,
|
||||
// it returns the data read before the error and the error itself (often io.EOF).
|
||||
// ReadBytes returns err != nil if and only if the returned data does not end in
|
||||
// delim.
|
||||
func (b *Buffer) ReadBytes(delim byte) (line []byte, err error) {
|
||||
slice, err := b.readSlice(delim)
|
||||
// return a copy of slice. The buffer's backing array may
|
||||
// be overwritten by later calls.
|
||||
line = append(line, slice...)
|
||||
return
|
||||
}
|
||||
|
||||
// readSlice is like ReadBytes but returns a reference to internal buffer data.
|
||||
func (b *Buffer) readSlice(delim byte) (line []byte, err error) {
|
||||
i := bytes.IndexByte(b.buf[b.off:], delim)
|
||||
end := b.off + i + 1
|
||||
if i < 0 {
|
||||
end = len(b.buf)
|
||||
err = io.EOF
|
||||
}
|
||||
line = b.buf[b.off:end]
|
||||
b.off = end
|
||||
b.lastRead = opRead
|
||||
return line, err
|
||||
}
|
||||
|
||||
// ReadString reads until the first occurrence of delim in the input,
|
||||
// returning a string containing the data up to and including the delimiter.
|
||||
// If ReadString encounters an error before finding a delimiter,
|
||||
// it returns the data read before the error and the error itself (often io.EOF).
|
||||
// ReadString returns err != nil if and only if the returned data does not end
|
||||
// in delim.
|
||||
func (b *Buffer) ReadString(delim byte) (line string, err error) {
|
||||
slice, err := b.readSlice(delim)
|
||||
return string(slice), err
|
||||
}
|
||||
|
||||
// NewBuffer creates and initializes a new Buffer using buf as its initial
|
||||
// contents. It is intended to prepare a Buffer to read existing data. It
|
||||
// can also be used to size the internal buffer for writing. To do that,
|
||||
// buf should have the desired capacity but a length of zero.
|
||||
//
|
||||
// In most cases, new(Buffer) (or just declaring a Buffer variable) is
|
||||
// sufficient to initialize a Buffer.
|
||||
func NewBuffer(buf []byte) *Buffer { return &Buffer{buf: buf} }
|
||||
|
||||
// NewBufferString creates and initializes a new Buffer using string s as its
|
||||
// initial contents. It is intended to prepare a buffer to read an existing
|
||||
// string.
|
||||
//
|
||||
// In most cases, new(Buffer) (or just declaring a Buffer variable) is
|
||||
// sufficient to initialize a Buffer.
|
||||
func NewBufferString(s string) *Buffer {
|
||||
return &Buffer{buf: []byte(s)}
|
||||
}
|
||||
527
Godeps/_workspace/src/gopkg.in/bufio.v1/buffer_test.go
generated
vendored
Normal file
527
Godeps/_workspace/src/gopkg.in/bufio.v1/buffer_test.go
generated
vendored
Normal file
@@ -0,0 +1,527 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bufio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const N = 10000 // make this bigger for a larger (and slower) test
|
||||
var data string // test data for write tests
|
||||
var testBytes []byte // test data; same as data but as a slice.
|
||||
|
||||
func init() {
|
||||
testBytes = make([]byte, N)
|
||||
for i := 0; i < N; i++ {
|
||||
testBytes[i] = 'a' + byte(i%26)
|
||||
}
|
||||
data = string(testBytes)
|
||||
}
|
||||
|
||||
// Verify that contents of buf match the string s.
|
||||
func check(t *testing.T, testname string, buf *Buffer, s string) {
|
||||
bytes := buf.Bytes()
|
||||
str := buf.String()
|
||||
if buf.Len() != len(bytes) {
|
||||
t.Errorf("%s: buf.Len() == %d, len(buf.Bytes()) == %d", testname, buf.Len(), len(bytes))
|
||||
}
|
||||
|
||||
if buf.Len() != len(str) {
|
||||
t.Errorf("%s: buf.Len() == %d, len(buf.String()) == %d", testname, buf.Len(), len(str))
|
||||
}
|
||||
|
||||
if buf.Len() != len(s) {
|
||||
t.Errorf("%s: buf.Len() == %d, len(s) == %d", testname, buf.Len(), len(s))
|
||||
}
|
||||
|
||||
if string(bytes) != s {
|
||||
t.Errorf("%s: string(buf.Bytes()) == %q, s == %q", testname, string(bytes), s)
|
||||
}
|
||||
}
|
||||
|
||||
// Fill buf through n writes of string fus.
|
||||
// The initial contents of buf corresponds to the string s;
|
||||
// the result is the final contents of buf returned as a string.
|
||||
func fillString(t *testing.T, testname string, buf *Buffer, s string, n int, fus string) string {
|
||||
check(t, testname+" (fill 1)", buf, s)
|
||||
for ; n > 0; n-- {
|
||||
m, err := buf.WriteString(fus)
|
||||
if m != len(fus) {
|
||||
t.Errorf(testname+" (fill 2): m == %d, expected %d", m, len(fus))
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf(testname+" (fill 3): err should always be nil, found err == %s", err)
|
||||
}
|
||||
s += fus
|
||||
check(t, testname+" (fill 4)", buf, s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Fill buf through n writes of byte slice fub.
|
||||
// The initial contents of buf corresponds to the string s;
|
||||
// the result is the final contents of buf returned as a string.
|
||||
func fillBytes(t *testing.T, testname string, buf *Buffer, s string, n int, fub []byte) string {
|
||||
check(t, testname+" (fill 1)", buf, s)
|
||||
for ; n > 0; n-- {
|
||||
m, err := buf.Write(fub)
|
||||
if m != len(fub) {
|
||||
t.Errorf(testname+" (fill 2): m == %d, expected %d", m, len(fub))
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf(testname+" (fill 3): err should always be nil, found err == %s", err)
|
||||
}
|
||||
s += string(fub)
|
||||
check(t, testname+" (fill 4)", buf, s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func TestNewBuffer(t *testing.T) {
|
||||
buf := NewBuffer(testBytes)
|
||||
check(t, "NewBuffer", buf, data)
|
||||
}
|
||||
|
||||
func TestNewBufferString(t *testing.T) {
|
||||
buf := NewBufferString(data)
|
||||
check(t, "NewBufferString", buf, data)
|
||||
}
|
||||
|
||||
// Empty buf through repeated reads into fub.
|
||||
// The initial contents of buf corresponds to the string s.
|
||||
func empty(t *testing.T, testname string, buf *Buffer, s string, fub []byte) {
|
||||
check(t, testname+" (empty 1)", buf, s)
|
||||
|
||||
for {
|
||||
n, err := buf.Read(fub)
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf(testname+" (empty 2): err should always be nil, found err == %s", err)
|
||||
}
|
||||
s = s[n:]
|
||||
check(t, testname+" (empty 3)", buf, s)
|
||||
}
|
||||
|
||||
check(t, testname+" (empty 4)", buf, "")
|
||||
}
|
||||
|
||||
func TestBasicOperations(t *testing.T) {
|
||||
var buf Buffer
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
check(t, "TestBasicOperations (1)", &buf, "")
|
||||
|
||||
buf.Reset()
|
||||
check(t, "TestBasicOperations (2)", &buf, "")
|
||||
|
||||
buf.Truncate(0)
|
||||
check(t, "TestBasicOperations (3)", &buf, "")
|
||||
|
||||
n, err := buf.Write([]byte(data[0:1]))
|
||||
if n != 1 {
|
||||
t.Errorf("wrote 1 byte, but n == %d", n)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("err should always be nil, but err == %s", err)
|
||||
}
|
||||
check(t, "TestBasicOperations (4)", &buf, "a")
|
||||
|
||||
buf.WriteByte(data[1])
|
||||
check(t, "TestBasicOperations (5)", &buf, "ab")
|
||||
|
||||
n, err = buf.Write([]byte(data[2:26]))
|
||||
if n != 24 {
|
||||
t.Errorf("wrote 25 bytes, but n == %d", n)
|
||||
}
|
||||
check(t, "TestBasicOperations (6)", &buf, string(data[0:26]))
|
||||
|
||||
buf.Truncate(26)
|
||||
check(t, "TestBasicOperations (7)", &buf, string(data[0:26]))
|
||||
|
||||
buf.Truncate(20)
|
||||
check(t, "TestBasicOperations (8)", &buf, string(data[0:20]))
|
||||
|
||||
empty(t, "TestBasicOperations (9)", &buf, string(data[0:20]), make([]byte, 5))
|
||||
empty(t, "TestBasicOperations (10)", &buf, "", make([]byte, 100))
|
||||
|
||||
buf.WriteByte(data[1])
|
||||
c, err := buf.ReadByte()
|
||||
if err != nil {
|
||||
t.Error("ReadByte unexpected eof")
|
||||
}
|
||||
if c != data[1] {
|
||||
t.Errorf("ReadByte wrong value c=%v", c)
|
||||
}
|
||||
c, err = buf.ReadByte()
|
||||
if err == nil {
|
||||
t.Error("ReadByte unexpected not eof")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLargeStringWrites(t *testing.T) {
|
||||
var buf Buffer
|
||||
limit := 30
|
||||
if testing.Short() {
|
||||
limit = 9
|
||||
}
|
||||
for i := 3; i < limit; i += 3 {
|
||||
s := fillString(t, "TestLargeWrites (1)", &buf, "", 5, data)
|
||||
empty(t, "TestLargeStringWrites (2)", &buf, s, make([]byte, len(data)/i))
|
||||
}
|
||||
check(t, "TestLargeStringWrites (3)", &buf, "")
|
||||
}
|
||||
|
||||
func TestLargeByteWrites(t *testing.T) {
|
||||
var buf Buffer
|
||||
limit := 30
|
||||
if testing.Short() {
|
||||
limit = 9
|
||||
}
|
||||
for i := 3; i < limit; i += 3 {
|
||||
s := fillBytes(t, "TestLargeWrites (1)", &buf, "", 5, testBytes)
|
||||
empty(t, "TestLargeByteWrites (2)", &buf, s, make([]byte, len(data)/i))
|
||||
}
|
||||
check(t, "TestLargeByteWrites (3)", &buf, "")
|
||||
}
|
||||
|
||||
func TestLargeStringReads(t *testing.T) {
|
||||
var buf Buffer
|
||||
for i := 3; i < 30; i += 3 {
|
||||
s := fillString(t, "TestLargeReads (1)", &buf, "", 5, data[0:len(data)/i])
|
||||
empty(t, "TestLargeReads (2)", &buf, s, make([]byte, len(data)))
|
||||
}
|
||||
check(t, "TestLargeStringReads (3)", &buf, "")
|
||||
}
|
||||
|
||||
func TestLargeByteReads(t *testing.T) {
|
||||
var buf Buffer
|
||||
for i := 3; i < 30; i += 3 {
|
||||
s := fillBytes(t, "TestLargeReads (1)", &buf, "", 5, testBytes[0:len(testBytes)/i])
|
||||
empty(t, "TestLargeReads (2)", &buf, s, make([]byte, len(data)))
|
||||
}
|
||||
check(t, "TestLargeByteReads (3)", &buf, "")
|
||||
}
|
||||
|
||||
func TestMixedReadsAndWrites(t *testing.T) {
|
||||
var buf Buffer
|
||||
s := ""
|
||||
for i := 0; i < 50; i++ {
|
||||
wlen := rand.Intn(len(data))
|
||||
if i%2 == 0 {
|
||||
s = fillString(t, "TestMixedReadsAndWrites (1)", &buf, s, 1, data[0:wlen])
|
||||
} else {
|
||||
s = fillBytes(t, "TestMixedReadsAndWrites (1)", &buf, s, 1, testBytes[0:wlen])
|
||||
}
|
||||
|
||||
rlen := rand.Intn(len(data))
|
||||
fub := make([]byte, rlen)
|
||||
n, _ := buf.Read(fub)
|
||||
s = s[n:]
|
||||
}
|
||||
empty(t, "TestMixedReadsAndWrites (2)", &buf, s, make([]byte, buf.Len()))
|
||||
}
|
||||
|
||||
func TestNil(t *testing.T) {
|
||||
var b *Buffer
|
||||
if b.String() != "<nil>" {
|
||||
t.Errorf("expected <nil>; got %q", b.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadFrom(t *testing.T) {
|
||||
var buf Buffer
|
||||
for i := 3; i < 30; i += 3 {
|
||||
s := fillBytes(t, "TestReadFrom (1)", &buf, "", 5, testBytes[0:len(testBytes)/i])
|
||||
var b Buffer
|
||||
b.ReadFrom(&buf)
|
||||
empty(t, "TestReadFrom (2)", &b, s, make([]byte, len(data)))
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteTo(t *testing.T) {
|
||||
var buf Buffer
|
||||
for i := 3; i < 30; i += 3 {
|
||||
s := fillBytes(t, "TestWriteTo (1)", &buf, "", 5, testBytes[0:len(testBytes)/i])
|
||||
var b Buffer
|
||||
buf.WriteTo(&b)
|
||||
empty(t, "TestWriteTo (2)", &b, s, make([]byte, len(data)))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuneIO(t *testing.T) {
|
||||
const NRune = 1000
|
||||
// Built a test slice while we write the data
|
||||
b := make([]byte, utf8.UTFMax*NRune)
|
||||
var buf Buffer
|
||||
n := 0
|
||||
for r := rune(0); r < NRune; r++ {
|
||||
size := utf8.EncodeRune(b[n:], r)
|
||||
nbytes, err := buf.WriteRune(r)
|
||||
if err != nil {
|
||||
t.Fatalf("WriteRune(%U) error: %s", r, err)
|
||||
}
|
||||
if nbytes != size {
|
||||
t.Fatalf("WriteRune(%U) expected %d, got %d", r, size, nbytes)
|
||||
}
|
||||
n += size
|
||||
}
|
||||
b = b[0:n]
|
||||
|
||||
// Check the resulting bytes
|
||||
if !bytes.Equal(buf.Bytes(), b) {
|
||||
t.Fatalf("incorrect result from WriteRune: %q not %q", buf.Bytes(), b)
|
||||
}
|
||||
|
||||
p := make([]byte, utf8.UTFMax)
|
||||
// Read it back with ReadRune
|
||||
for r := rune(0); r < NRune; r++ {
|
||||
size := utf8.EncodeRune(p, r)
|
||||
nr, nbytes, err := buf.ReadRune()
|
||||
if nr != r || nbytes != size || err != nil {
|
||||
t.Fatalf("ReadRune(%U) got %U,%d not %U,%d (err=%s)", r, nr, nbytes, r, size, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Check that UnreadRune works
|
||||
buf.Reset()
|
||||
buf.Write(b)
|
||||
for r := rune(0); r < NRune; r++ {
|
||||
r1, size, _ := buf.ReadRune()
|
||||
if err := buf.UnreadRune(); err != nil {
|
||||
t.Fatalf("UnreadRune(%U) got error %q", r, err)
|
||||
}
|
||||
r2, nbytes, err := buf.ReadRune()
|
||||
if r1 != r2 || r1 != r || nbytes != size || err != nil {
|
||||
t.Fatalf("ReadRune(%U) after UnreadRune got %U,%d not %U,%d (err=%s)", r, r2, nbytes, r, size, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNext(t *testing.T) {
|
||||
b := []byte{0, 1, 2, 3, 4}
|
||||
tmp := make([]byte, 5)
|
||||
for i := 0; i <= 5; i++ {
|
||||
for j := i; j <= 5; j++ {
|
||||
for k := 0; k <= 6; k++ {
|
||||
// 0 <= i <= j <= 5; 0 <= k <= 6
|
||||
// Check that if we start with a buffer
|
||||
// of length j at offset i and ask for
|
||||
// Next(k), we get the right bytes.
|
||||
buf := NewBuffer(b[0:j])
|
||||
n, _ := buf.Read(tmp[0:i])
|
||||
if n != i {
|
||||
t.Fatalf("Read %d returned %d", i, n)
|
||||
}
|
||||
bb := buf.Next(k)
|
||||
want := k
|
||||
if want > j-i {
|
||||
want = j - i
|
||||
}
|
||||
if len(bb) != want {
|
||||
t.Fatalf("in %d,%d: len(Next(%d)) == %d", i, j, k, len(bb))
|
||||
}
|
||||
for l, v := range bb {
|
||||
if v != byte(l+i) {
|
||||
t.Fatalf("in %d,%d: Next(%d)[%d] = %d, want %d", i, j, k, l, v, l+i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var readBytesTests = []struct {
|
||||
buffer string
|
||||
delim byte
|
||||
expected []string
|
||||
err error
|
||||
}{
|
||||
{"", 0, []string{""}, io.EOF},
|
||||
{"a\x00", 0, []string{"a\x00"}, nil},
|
||||
{"abbbaaaba", 'b', []string{"ab", "b", "b", "aaab"}, nil},
|
||||
{"hello\x01world", 1, []string{"hello\x01"}, nil},
|
||||
{"foo\nbar", 0, []string{"foo\nbar"}, io.EOF},
|
||||
{"alpha\nbeta\ngamma\n", '\n', []string{"alpha\n", "beta\n", "gamma\n"}, nil},
|
||||
{"alpha\nbeta\ngamma", '\n', []string{"alpha\n", "beta\n", "gamma"}, io.EOF},
|
||||
}
|
||||
|
||||
func TestReadBytes(t *testing.T) {
|
||||
for _, test := range readBytesTests {
|
||||
buf := NewBufferString(test.buffer)
|
||||
var err error
|
||||
for _, expected := range test.expected {
|
||||
var bytes []byte
|
||||
bytes, err = buf.ReadBytes(test.delim)
|
||||
if string(bytes) != expected {
|
||||
t.Errorf("expected %q, got %q", expected, bytes)
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != test.err {
|
||||
t.Errorf("expected error %v, got %v", test.err, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadString(t *testing.T) {
|
||||
for _, test := range readBytesTests {
|
||||
buf := NewBufferString(test.buffer)
|
||||
var err error
|
||||
for _, expected := range test.expected {
|
||||
var s string
|
||||
s, err = buf.ReadString(test.delim)
|
||||
if s != expected {
|
||||
t.Errorf("expected %q, got %q", expected, s)
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != test.err {
|
||||
t.Errorf("expected error %v, got %v", test.err, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkReadString(b *testing.B) {
|
||||
const n = 32 << 10
|
||||
|
||||
data := make([]byte, n)
|
||||
data[n-1] = 'x'
|
||||
b.SetBytes(int64(n))
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf := NewBuffer(data)
|
||||
_, err := buf.ReadString('x')
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGrow(t *testing.T) {
|
||||
x := []byte{'x'}
|
||||
y := []byte{'y'}
|
||||
tmp := make([]byte, 72)
|
||||
for _, startLen := range []int{0, 100, 1000, 10000, 100000} {
|
||||
xBytes := bytes.Repeat(x, startLen)
|
||||
for _, growLen := range []int{0, 100, 1000, 10000, 100000} {
|
||||
buf := NewBuffer(xBytes)
|
||||
// If we read, this affects buf.off, which is good to test.
|
||||
readBytes, _ := buf.Read(tmp)
|
||||
buf.Grow(growLen)
|
||||
yBytes := bytes.Repeat(y, growLen)
|
||||
// Check no allocation occurs in write, as long as we're single-threaded.
|
||||
var m1, m2 runtime.MemStats
|
||||
runtime.ReadMemStats(&m1)
|
||||
buf.Write(yBytes)
|
||||
runtime.ReadMemStats(&m2)
|
||||
if runtime.GOMAXPROCS(-1) == 1 && m1.Mallocs != m2.Mallocs {
|
||||
t.Errorf("allocation occurred during write")
|
||||
}
|
||||
// Check that buffer has correct data.
|
||||
if !bytes.Equal(buf.Bytes()[0:startLen-readBytes], xBytes[readBytes:]) {
|
||||
t.Errorf("bad initial data at %d %d", startLen, growLen)
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes()[startLen-readBytes:startLen-readBytes+growLen], yBytes) {
|
||||
t.Errorf("bad written data at %d %d", startLen, growLen)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Was a bug: used to give EOF reading empty slice at EOF.
|
||||
func TestReadEmptyAtEOF(t *testing.T) {
|
||||
b := new(Buffer)
|
||||
slice := make([]byte, 0)
|
||||
n, err := b.Read(slice)
|
||||
if err != nil {
|
||||
t.Errorf("read error: %v", err)
|
||||
}
|
||||
if n != 0 {
|
||||
t.Errorf("wrong count; got %d want 0", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBufferUnreadByte(t *testing.T) {
|
||||
b := new(Buffer)
|
||||
b.WriteString("abcdefghijklmnopqrstuvwxyz")
|
||||
|
||||
_, err := b.ReadBytes('m')
|
||||
if err != nil {
|
||||
t.Fatalf("ReadBytes: %v", err)
|
||||
}
|
||||
|
||||
err = b.UnreadByte()
|
||||
if err != nil {
|
||||
t.Fatalf("UnreadByte: %v", err)
|
||||
}
|
||||
c, err := b.ReadByte()
|
||||
if err != nil {
|
||||
t.Fatalf("ReadByte: %v", err)
|
||||
}
|
||||
if c != 'm' {
|
||||
t.Errorf("ReadByte = %q; want %q", c, 'm')
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that we occasionally compact. Issue 5154.
|
||||
func TestBufferGrowth(t *testing.T) {
|
||||
var b Buffer
|
||||
buf := make([]byte, 1024)
|
||||
b.Write(buf[0:1])
|
||||
var cap0 int
|
||||
for i := 0; i < 5<<10; i++ {
|
||||
b.Write(buf)
|
||||
b.Read(buf)
|
||||
if i == 0 {
|
||||
cap0 = b.Cap()
|
||||
}
|
||||
}
|
||||
cap1 := b.Cap()
|
||||
// (*Buffer).grow allows for 2x capacity slop before sliding,
|
||||
// so set our error threshold at 3x.
|
||||
if cap1 > cap0*3 {
|
||||
t.Errorf("buffer cap = %d; too big (grew from %d)", cap1, cap0)
|
||||
}
|
||||
}
|
||||
|
||||
// From Issue 5154.
|
||||
func BenchmarkBufferNotEmptyWriteRead(b *testing.B) {
|
||||
buf := make([]byte, 1024)
|
||||
for i := 0; i < b.N; i++ {
|
||||
var b Buffer
|
||||
b.Write(buf[0:1])
|
||||
for i := 0; i < 5<<10; i++ {
|
||||
b.Write(buf)
|
||||
b.Read(buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that we don't compact too often. From Issue 5154.
|
||||
func BenchmarkBufferFullSmallReads(b *testing.B) {
|
||||
buf := make([]byte, 1024)
|
||||
for i := 0; i < b.N; i++ {
|
||||
var b Buffer
|
||||
b.Write(buf)
|
||||
for b.Len()+20 < b.Cap() {
|
||||
b.Write(buf[:10])
|
||||
}
|
||||
for i := 0; i < 5<<10; i++ {
|
||||
b.Read(buf[:1])
|
||||
b.Write(buf[:1])
|
||||
}
|
||||
}
|
||||
}
|
||||
728
Godeps/_workspace/src/gopkg.in/bufio.v1/bufio.go
generated
vendored
Normal file
728
Godeps/_workspace/src/gopkg.in/bufio.v1/bufio.go
generated
vendored
Normal file
@@ -0,0 +1,728 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package bufio implements buffered I/O. It wraps an io.Reader or io.Writer
|
||||
// object, creating another object (Reader or Writer) that also implements
|
||||
// the interface but provides buffering and some help for textual I/O.
|
||||
package bufio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBufSize = 4096
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidUnreadByte = errors.New("bufio: invalid use of UnreadByte")
|
||||
ErrInvalidUnreadRune = errors.New("bufio: invalid use of UnreadRune")
|
||||
ErrBufferFull = errors.New("bufio: buffer full")
|
||||
ErrNegativeCount = errors.New("bufio: negative count")
|
||||
)
|
||||
|
||||
// Buffered input.
|
||||
|
||||
// Reader implements buffering for an io.Reader object.
|
||||
type Reader struct {
|
||||
buf []byte
|
||||
rd io.Reader
|
||||
r, w int
|
||||
err error
|
||||
lastByte int
|
||||
lastRuneSize int
|
||||
}
|
||||
|
||||
const minReadBufferSize = 16
|
||||
const maxConsecutiveEmptyReads = 100
|
||||
|
||||
// NewReaderSize returns a new Reader whose buffer has at least the specified
|
||||
// size. If the argument io.Reader is already a Reader with large enough
|
||||
// size, it returns the underlying Reader.
|
||||
func NewReaderSize(rd io.Reader, size int) *Reader {
|
||||
// Is it already a Reader?
|
||||
b, ok := rd.(*Reader)
|
||||
if ok && len(b.buf) >= size {
|
||||
return b
|
||||
}
|
||||
if size < minReadBufferSize {
|
||||
size = minReadBufferSize
|
||||
}
|
||||
r := new(Reader)
|
||||
r.reset(make([]byte, size), rd)
|
||||
return r
|
||||
}
|
||||
|
||||
// NewReader returns a new Reader whose buffer has the default size.
|
||||
func NewReader(rd io.Reader) *Reader {
|
||||
return NewReaderSize(rd, defaultBufSize)
|
||||
}
|
||||
|
||||
// Reset discards any buffered data, resets all state, and switches
|
||||
// the buffered reader to read from r.
|
||||
func (b *Reader) Reset(r io.Reader) {
|
||||
b.reset(b.buf, r)
|
||||
}
|
||||
|
||||
func (b *Reader) reset(buf []byte, r io.Reader) {
|
||||
*b = Reader{
|
||||
buf: buf,
|
||||
rd: r,
|
||||
lastByte: -1,
|
||||
lastRuneSize: -1,
|
||||
}
|
||||
}
|
||||
|
||||
var errNegativeRead = errors.New("bufio: reader returned negative count from Read")
|
||||
|
||||
// fill reads a new chunk into the buffer.
|
||||
func (b *Reader) fill() {
|
||||
// Slide existing data to beginning.
|
||||
if b.r > 0 {
|
||||
copy(b.buf, b.buf[b.r:b.w])
|
||||
b.w -= b.r
|
||||
b.r = 0
|
||||
}
|
||||
|
||||
if b.w >= len(b.buf) {
|
||||
panic("bufio: tried to fill full buffer")
|
||||
}
|
||||
|
||||
// Read new data: try a limited number of times.
|
||||
for i := maxConsecutiveEmptyReads; i > 0; i-- {
|
||||
n, err := b.rd.Read(b.buf[b.w:])
|
||||
if n < 0 {
|
||||
panic(errNegativeRead)
|
||||
}
|
||||
b.w += n
|
||||
if err != nil {
|
||||
b.err = err
|
||||
return
|
||||
}
|
||||
if n > 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
b.err = io.ErrNoProgress
|
||||
}
|
||||
|
||||
func (b *Reader) readErr() error {
|
||||
err := b.err
|
||||
b.err = nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Peek returns the next n bytes without advancing the reader. The bytes stop
|
||||
// being valid at the next read call. If Peek returns fewer than n bytes, it
|
||||
// also returns an error explaining why the read is short. The error is
|
||||
// ErrBufferFull if n is larger than b's buffer size.
|
||||
func (b *Reader) Peek(n int) ([]byte, error) {
|
||||
if n < 0 {
|
||||
return nil, ErrNegativeCount
|
||||
}
|
||||
if n > len(b.buf) {
|
||||
return nil, ErrBufferFull
|
||||
}
|
||||
// 0 <= n <= len(b.buf)
|
||||
for b.w-b.r < n && b.err == nil {
|
||||
b.fill() // b.w-b.r < len(b.buf) => buffer is not full
|
||||
}
|
||||
m := b.w - b.r
|
||||
if m > n {
|
||||
m = n
|
||||
}
|
||||
var err error
|
||||
if m < n {
|
||||
err = b.readErr()
|
||||
if err == nil {
|
||||
err = ErrBufferFull
|
||||
}
|
||||
}
|
||||
return b.buf[b.r : b.r+m], err
|
||||
}
|
||||
|
||||
// Read reads data into p.
|
||||
// It returns the number of bytes read into p.
|
||||
// It calls Read at most once on the underlying Reader,
|
||||
// hence n may be less than len(p).
|
||||
// At EOF, the count will be zero and err will be io.EOF.
|
||||
func (b *Reader) Read(p []byte) (n int, err error) {
|
||||
n = len(p)
|
||||
if n == 0 {
|
||||
return 0, b.readErr()
|
||||
}
|
||||
if b.r == b.w {
|
||||
if b.err != nil {
|
||||
return 0, b.readErr()
|
||||
}
|
||||
if len(p) >= len(b.buf) {
|
||||
// Large read, empty buffer.
|
||||
// Read directly into p to avoid copy.
|
||||
n, b.err = b.rd.Read(p)
|
||||
if n < 0 {
|
||||
panic(errNegativeRead)
|
||||
}
|
||||
if n > 0 {
|
||||
b.lastByte = int(p[n-1])
|
||||
b.lastRuneSize = -1
|
||||
}
|
||||
return n, b.readErr()
|
||||
}
|
||||
b.fill() // buffer is empty
|
||||
if b.w == b.r {
|
||||
return 0, b.readErr()
|
||||
}
|
||||
}
|
||||
|
||||
if n > b.w-b.r {
|
||||
n = b.w - b.r
|
||||
}
|
||||
copy(p[0:n], b.buf[b.r:])
|
||||
b.r += n
|
||||
b.lastByte = int(b.buf[b.r-1])
|
||||
b.lastRuneSize = -1
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// ReadByte reads and returns a single byte.
|
||||
// If no byte is available, returns an error.
|
||||
func (b *Reader) ReadByte() (c byte, err error) {
|
||||
b.lastRuneSize = -1
|
||||
for b.r == b.w {
|
||||
if b.err != nil {
|
||||
return 0, b.readErr()
|
||||
}
|
||||
b.fill() // buffer is empty
|
||||
}
|
||||
c = b.buf[b.r]
|
||||
b.r++
|
||||
b.lastByte = int(c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// UnreadByte unreads the last byte. Only the most recently read byte can be unread.
|
||||
func (b *Reader) UnreadByte() error {
|
||||
if b.lastByte < 0 || b.r == 0 && b.w > 0 {
|
||||
return ErrInvalidUnreadByte
|
||||
}
|
||||
// b.r > 0 || b.w == 0
|
||||
if b.r > 0 {
|
||||
b.r--
|
||||
} else {
|
||||
// b.r == 0 && b.w == 0
|
||||
b.w = 1
|
||||
}
|
||||
b.buf[b.r] = byte(b.lastByte)
|
||||
b.lastByte = -1
|
||||
b.lastRuneSize = -1
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadRune reads a single UTF-8 encoded Unicode character and returns the
|
||||
// rune and its size in bytes. If the encoded rune is invalid, it consumes one byte
|
||||
// and returns unicode.ReplacementChar (U+FFFD) with a size of 1.
|
||||
func (b *Reader) ReadRune() (r rune, size int, err error) {
|
||||
for b.r+utf8.UTFMax > b.w && !utf8.FullRune(b.buf[b.r:b.w]) && b.err == nil && b.w-b.r < len(b.buf) {
|
||||
b.fill() // b.w-b.r < len(buf) => buffer is not full
|
||||
}
|
||||
b.lastRuneSize = -1
|
||||
if b.r == b.w {
|
||||
return 0, 0, b.readErr()
|
||||
}
|
||||
r, size = rune(b.buf[b.r]), 1
|
||||
if r >= 0x80 {
|
||||
r, size = utf8.DecodeRune(b.buf[b.r:b.w])
|
||||
}
|
||||
b.r += size
|
||||
b.lastByte = int(b.buf[b.r-1])
|
||||
b.lastRuneSize = size
|
||||
return r, size, nil
|
||||
}
|
||||
|
||||
// UnreadRune unreads the last rune. If the most recent read operation on
|
||||
// the buffer was not a ReadRune, UnreadRune returns an error. (In this
|
||||
// regard it is stricter than UnreadByte, which will unread the last byte
|
||||
// from any read operation.)
|
||||
func (b *Reader) UnreadRune() error {
|
||||
if b.lastRuneSize < 0 || b.r < b.lastRuneSize {
|
||||
return ErrInvalidUnreadRune
|
||||
}
|
||||
b.r -= b.lastRuneSize
|
||||
b.lastByte = -1
|
||||
b.lastRuneSize = -1
|
||||
return nil
|
||||
}
|
||||
|
||||
// Buffered returns the number of bytes that can be read from the current buffer.
|
||||
func (b *Reader) Buffered() int { return b.w - b.r }
|
||||
|
||||
// ReadSlice reads until the first occurrence of delim in the input,
|
||||
// returning a slice pointing at the bytes in the buffer.
|
||||
// The bytes stop being valid at the next read.
|
||||
// If ReadSlice encounters an error before finding a delimiter,
|
||||
// it returns all the data in the buffer and the error itself (often io.EOF).
|
||||
// ReadSlice fails with error ErrBufferFull if the buffer fills without a delim.
|
||||
// Because the data returned from ReadSlice will be overwritten
|
||||
// by the next I/O operation, most clients should use
|
||||
// ReadBytes or ReadString instead.
|
||||
// ReadSlice returns err != nil if and only if line does not end in delim.
|
||||
func (b *Reader) ReadSlice(delim byte) (line []byte, err error) {
|
||||
for {
|
||||
// Search buffer.
|
||||
if i := bytes.IndexByte(b.buf[b.r:b.w], delim); i >= 0 {
|
||||
line = b.buf[b.r : b.r+i+1]
|
||||
b.r += i + 1
|
||||
break
|
||||
}
|
||||
|
||||
// Pending error?
|
||||
if b.err != nil {
|
||||
line = b.buf[b.r:b.w]
|
||||
b.r = b.w
|
||||
err = b.readErr()
|
||||
break
|
||||
}
|
||||
|
||||
// Buffer full?
|
||||
if n := b.Buffered(); n >= len(b.buf) {
|
||||
b.r = b.w
|
||||
line = b.buf
|
||||
err = ErrBufferFull
|
||||
break
|
||||
}
|
||||
|
||||
b.fill() // buffer is not full
|
||||
}
|
||||
|
||||
// Handle last byte, if any.
|
||||
if i := len(line) - 1; i >= 0 {
|
||||
b.lastByte = int(line[i])
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ReadN tries to read exactly n bytes.
|
||||
// The bytes stop being valid at the next read call.
|
||||
// If ReadN encounters an error before reading n bytes,
|
||||
// it returns all the data in the buffer and the error itself (often io.EOF).
|
||||
// ReadN fails with error ErrBufferFull if the buffer fills
|
||||
// without reading N bytes.
|
||||
// Because the data returned from ReadN will be overwritten
|
||||
// by the next I/O operation, most clients should use
|
||||
// ReadBytes or ReadString instead.
|
||||
func (b *Reader) ReadN(n int) ([]byte, error) {
|
||||
for b.Buffered() < n {
|
||||
if b.err != nil {
|
||||
buf := b.buf[b.r:b.w]
|
||||
b.r = b.w
|
||||
return buf, b.readErr()
|
||||
}
|
||||
|
||||
// Buffer is full?
|
||||
if b.Buffered() >= len(b.buf) {
|
||||
b.r = b.w
|
||||
return b.buf, ErrBufferFull
|
||||
}
|
||||
|
||||
b.fill()
|
||||
}
|
||||
buf := b.buf[b.r : b.r+n]
|
||||
b.r += n
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// ReadLine is a low-level line-reading primitive. Most callers should use
|
||||
// ReadBytes('\n') or ReadString('\n') instead or use a Scanner.
|
||||
//
|
||||
// ReadLine tries to return a single line, not including the end-of-line bytes.
|
||||
// If the line was too long for the buffer then isPrefix is set and the
|
||||
// beginning of the line is returned. The rest of the line will be returned
|
||||
// from future calls. isPrefix will be false when returning the last fragment
|
||||
// of the line. The returned buffer is only valid until the next call to
|
||||
// ReadLine. ReadLine either returns a non-nil line or it returns an error,
|
||||
// never both.
|
||||
//
|
||||
// The text returned from ReadLine does not include the line end ("\r\n" or "\n").
|
||||
// No indication or error is given if the input ends without a final line end.
|
||||
// Calling UnreadByte after ReadLine will always unread the last byte read
|
||||
// (possibly a character belonging to the line end) even if that byte is not
|
||||
// part of the line returned by ReadLine.
|
||||
func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error) {
|
||||
line, err = b.ReadSlice('\n')
|
||||
if err == ErrBufferFull {
|
||||
// Handle the case where "\r\n" straddles the buffer.
|
||||
if len(line) > 0 && line[len(line)-1] == '\r' {
|
||||
// Put the '\r' back on buf and drop it from line.
|
||||
// Let the next call to ReadLine check for "\r\n".
|
||||
if b.r == 0 {
|
||||
// should be unreachable
|
||||
panic("bufio: tried to rewind past start of buffer")
|
||||
}
|
||||
b.r--
|
||||
line = line[:len(line)-1]
|
||||
}
|
||||
return line, true, nil
|
||||
}
|
||||
|
||||
if len(line) == 0 {
|
||||
if err != nil {
|
||||
line = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
err = nil
|
||||
|
||||
if line[len(line)-1] == '\n' {
|
||||
drop := 1
|
||||
if len(line) > 1 && line[len(line)-2] == '\r' {
|
||||
drop = 2
|
||||
}
|
||||
line = line[:len(line)-drop]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ReadBytes reads until the first occurrence of delim in the input,
|
||||
// returning a slice containing the data up to and including the delimiter.
|
||||
// If ReadBytes encounters an error before finding a delimiter,
|
||||
// it returns the data read before the error and the error itself (often io.EOF).
|
||||
// ReadBytes returns err != nil if and only if the returned data does not end in
|
||||
// delim.
|
||||
// For simple uses, a Scanner may be more convenient.
|
||||
func (b *Reader) ReadBytes(delim byte) (line []byte, err error) {
|
||||
// Use ReadSlice to look for array,
|
||||
// accumulating full buffers.
|
||||
var frag []byte
|
||||
var full [][]byte
|
||||
err = nil
|
||||
|
||||
for {
|
||||
var e error
|
||||
frag, e = b.ReadSlice(delim)
|
||||
if e == nil { // got final fragment
|
||||
break
|
||||
}
|
||||
if e != ErrBufferFull { // unexpected error
|
||||
err = e
|
||||
break
|
||||
}
|
||||
|
||||
// Make a copy of the buffer.
|
||||
buf := make([]byte, len(frag))
|
||||
copy(buf, frag)
|
||||
full = append(full, buf)
|
||||
}
|
||||
|
||||
// Allocate new buffer to hold the full pieces and the fragment.
|
||||
n := 0
|
||||
for i := range full {
|
||||
n += len(full[i])
|
||||
}
|
||||
n += len(frag)
|
||||
|
||||
// Copy full pieces and fragment in.
|
||||
buf := make([]byte, n)
|
||||
n = 0
|
||||
for i := range full {
|
||||
n += copy(buf[n:], full[i])
|
||||
}
|
||||
copy(buf[n:], frag)
|
||||
return buf, err
|
||||
}
|
||||
|
||||
// ReadString reads until the first occurrence of delim in the input,
|
||||
// returning a string containing the data up to and including the delimiter.
|
||||
// If ReadString encounters an error before finding a delimiter,
|
||||
// it returns the data read before the error and the error itself (often io.EOF).
|
||||
// ReadString returns err != nil if and only if the returned data does not end in
|
||||
// delim.
|
||||
// For simple uses, a Scanner may be more convenient.
|
||||
func (b *Reader) ReadString(delim byte) (line string, err error) {
|
||||
bytes, err := b.ReadBytes(delim)
|
||||
line = string(bytes)
|
||||
return line, err
|
||||
}
|
||||
|
||||
// WriteTo implements io.WriterTo.
|
||||
func (b *Reader) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n, err = b.writeBuf(w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if r, ok := b.rd.(io.WriterTo); ok {
|
||||
m, err := r.WriteTo(w)
|
||||
n += m
|
||||
return n, err
|
||||
}
|
||||
|
||||
if w, ok := w.(io.ReaderFrom); ok {
|
||||
m, err := w.ReadFrom(b.rd)
|
||||
n += m
|
||||
return n, err
|
||||
}
|
||||
|
||||
if b.w-b.r < len(b.buf) {
|
||||
b.fill() // buffer not full
|
||||
}
|
||||
|
||||
for b.r < b.w {
|
||||
// b.r < b.w => buffer is not empty
|
||||
m, err := b.writeBuf(w)
|
||||
n += m
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
b.fill() // buffer is empty
|
||||
}
|
||||
|
||||
if b.err == io.EOF {
|
||||
b.err = nil
|
||||
}
|
||||
|
||||
return n, b.readErr()
|
||||
}
|
||||
|
||||
// writeBuf writes the Reader's buffer to the writer.
|
||||
func (b *Reader) writeBuf(w io.Writer) (int64, error) {
|
||||
n, err := w.Write(b.buf[b.r:b.w])
|
||||
if n < b.r-b.w {
|
||||
panic(errors.New("bufio: writer did not write all data"))
|
||||
}
|
||||
b.r += n
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
// buffered output
|
||||
|
||||
// Writer implements buffering for an io.Writer object.
|
||||
// If an error occurs writing to a Writer, no more data will be
|
||||
// accepted and all subsequent writes will return the error.
|
||||
// After all data has been written, the client should call the
|
||||
// Flush method to guarantee all data has been forwarded to
|
||||
// the underlying io.Writer.
|
||||
type Writer struct {
|
||||
err error
|
||||
buf []byte
|
||||
n int
|
||||
wr io.Writer
|
||||
}
|
||||
|
||||
// NewWriterSize returns a new Writer whose buffer has at least the specified
|
||||
// size. If the argument io.Writer is already a Writer with large enough
|
||||
// size, it returns the underlying Writer.
|
||||
func NewWriterSize(w io.Writer, size int) *Writer {
|
||||
// Is it already a Writer?
|
||||
b, ok := w.(*Writer)
|
||||
if ok && len(b.buf) >= size {
|
||||
return b
|
||||
}
|
||||
if size <= 0 {
|
||||
size = defaultBufSize
|
||||
}
|
||||
return &Writer{
|
||||
buf: make([]byte, size),
|
||||
wr: w,
|
||||
}
|
||||
}
|
||||
|
||||
// NewWriter returns a new Writer whose buffer has the default size.
|
||||
func NewWriter(w io.Writer) *Writer {
|
||||
return NewWriterSize(w, defaultBufSize)
|
||||
}
|
||||
|
||||
// Reset discards any unflushed buffered data, clears any error, and
|
||||
// resets b to write its output to w.
|
||||
func (b *Writer) Reset(w io.Writer) {
|
||||
b.err = nil
|
||||
b.n = 0
|
||||
b.wr = w
|
||||
}
|
||||
|
||||
// Flush writes any buffered data to the underlying io.Writer.
|
||||
func (b *Writer) Flush() error {
|
||||
err := b.flush()
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *Writer) flush() error {
|
||||
if b.err != nil {
|
||||
return b.err
|
||||
}
|
||||
if b.n == 0 {
|
||||
return nil
|
||||
}
|
||||
n, err := b.wr.Write(b.buf[0:b.n])
|
||||
if n < b.n && err == nil {
|
||||
err = io.ErrShortWrite
|
||||
}
|
||||
if err != nil {
|
||||
if n > 0 && n < b.n {
|
||||
copy(b.buf[0:b.n-n], b.buf[n:b.n])
|
||||
}
|
||||
b.n -= n
|
||||
b.err = err
|
||||
return err
|
||||
}
|
||||
b.n = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// Available returns how many bytes are unused in the buffer.
|
||||
func (b *Writer) Available() int { return len(b.buf) - b.n }
|
||||
|
||||
// Buffered returns the number of bytes that have been written into the current buffer.
|
||||
func (b *Writer) Buffered() int { return b.n }
|
||||
|
||||
// Write writes the contents of p into the buffer.
|
||||
// It returns the number of bytes written.
|
||||
// If nn < len(p), it also returns an error explaining
|
||||
// why the write is short.
|
||||
func (b *Writer) Write(p []byte) (nn int, err error) {
|
||||
for len(p) > b.Available() && b.err == nil {
|
||||
var n int
|
||||
if b.Buffered() == 0 {
|
||||
// Large write, empty buffer.
|
||||
// Write directly from p to avoid copy.
|
||||
n, b.err = b.wr.Write(p)
|
||||
} else {
|
||||
n = copy(b.buf[b.n:], p)
|
||||
b.n += n
|
||||
b.flush()
|
||||
}
|
||||
nn += n
|
||||
p = p[n:]
|
||||
}
|
||||
if b.err != nil {
|
||||
return nn, b.err
|
||||
}
|
||||
n := copy(b.buf[b.n:], p)
|
||||
b.n += n
|
||||
nn += n
|
||||
return nn, nil
|
||||
}
|
||||
|
||||
// WriteByte writes a single byte.
|
||||
func (b *Writer) WriteByte(c byte) error {
|
||||
if b.err != nil {
|
||||
return b.err
|
||||
}
|
||||
if b.Available() <= 0 && b.flush() != nil {
|
||||
return b.err
|
||||
}
|
||||
b.buf[b.n] = c
|
||||
b.n++
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteRune writes a single Unicode code point, returning
|
||||
// the number of bytes written and any error.
|
||||
func (b *Writer) WriteRune(r rune) (size int, err error) {
|
||||
if r < utf8.RuneSelf {
|
||||
err = b.WriteByte(byte(r))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return 1, nil
|
||||
}
|
||||
if b.err != nil {
|
||||
return 0, b.err
|
||||
}
|
||||
n := b.Available()
|
||||
if n < utf8.UTFMax {
|
||||
if b.flush(); b.err != nil {
|
||||
return 0, b.err
|
||||
}
|
||||
n = b.Available()
|
||||
if n < utf8.UTFMax {
|
||||
// Can only happen if buffer is silly small.
|
||||
return b.WriteString(string(r))
|
||||
}
|
||||
}
|
||||
size = utf8.EncodeRune(b.buf[b.n:], r)
|
||||
b.n += size
|
||||
return size, nil
|
||||
}
|
||||
|
||||
// WriteString writes a string.
|
||||
// It returns the number of bytes written.
|
||||
// If the count is less than len(s), it also returns an error explaining
|
||||
// why the write is short.
|
||||
func (b *Writer) WriteString(s string) (int, error) {
|
||||
nn := 0
|
||||
for len(s) > b.Available() && b.err == nil {
|
||||
n := copy(b.buf[b.n:], s)
|
||||
b.n += n
|
||||
nn += n
|
||||
s = s[n:]
|
||||
b.flush()
|
||||
}
|
||||
if b.err != nil {
|
||||
return nn, b.err
|
||||
}
|
||||
n := copy(b.buf[b.n:], s)
|
||||
b.n += n
|
||||
nn += n
|
||||
return nn, nil
|
||||
}
|
||||
|
||||
// ReadFrom implements io.ReaderFrom.
|
||||
func (b *Writer) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
if b.Buffered() == 0 {
|
||||
if w, ok := b.wr.(io.ReaderFrom); ok {
|
||||
return w.ReadFrom(r)
|
||||
}
|
||||
}
|
||||
var m int
|
||||
for {
|
||||
if b.Available() == 0 {
|
||||
if err1 := b.flush(); err1 != nil {
|
||||
return n, err1
|
||||
}
|
||||
}
|
||||
nr := 0
|
||||
for nr < maxConsecutiveEmptyReads {
|
||||
m, err = r.Read(b.buf[b.n:])
|
||||
if m != 0 || err != nil {
|
||||
break
|
||||
}
|
||||
nr++
|
||||
}
|
||||
if nr == maxConsecutiveEmptyReads {
|
||||
return n, io.ErrNoProgress
|
||||
}
|
||||
b.n += m
|
||||
n += int64(m)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err == io.EOF {
|
||||
// If we filled the buffer exactly, flush pre-emptively.
|
||||
if b.Available() == 0 {
|
||||
err = b.flush()
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// buffered input and output
|
||||
|
||||
// ReadWriter stores pointers to a Reader and a Writer.
|
||||
// It implements io.ReadWriter.
|
||||
type ReadWriter struct {
|
||||
*Reader
|
||||
*Writer
|
||||
}
|
||||
|
||||
// NewReadWriter allocates a new ReadWriter that dispatches to r and w.
|
||||
func NewReadWriter(r *Reader, w *Writer) *ReadWriter {
|
||||
return &ReadWriter{r, w}
|
||||
}
|
||||
1418
Godeps/_workspace/src/gopkg.in/bufio.v1/bufio_test.go
generated
vendored
Normal file
1418
Godeps/_workspace/src/gopkg.in/bufio.v1/bufio_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
9
Godeps/_workspace/src/gopkg.in/bufio.v1/export_test.go
generated
vendored
Normal file
9
Godeps/_workspace/src/gopkg.in/bufio.v1/export_test.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bufio
|
||||
|
||||
func (b *Buffer) Cap() int {
|
||||
return cap(b.buf)
|
||||
}
|
||||
19
Godeps/_workspace/src/gopkg.in/redis.v2/.travis.yml
generated
vendored
Normal file
19
Godeps/_workspace/src/gopkg.in/redis.v2/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
language: go
|
||||
|
||||
services:
|
||||
- redis-server
|
||||
|
||||
go:
|
||||
- 1.1
|
||||
- 1.2
|
||||
- 1.3
|
||||
- tip
|
||||
|
||||
install:
|
||||
- go get gopkg.in/bufio.v1
|
||||
- go get gopkg.in/check.v1
|
||||
- mkdir -p $HOME/gopath/src/gopkg.in
|
||||
- ln -s `pwd` $HOME/gopath/src/gopkg.in/redis.v2
|
||||
|
||||
before_script:
|
||||
- redis-server testdata/sentinel.conf --sentinel &
|
||||
27
Godeps/_workspace/src/gopkg.in/redis.v2/LICENSE
generated
vendored
Normal file
27
Godeps/_workspace/src/gopkg.in/redis.v2/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2012 The Redis Go Client Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
3
Godeps/_workspace/src/gopkg.in/redis.v2/Makefile
generated
vendored
Normal file
3
Godeps/_workspace/src/gopkg.in/redis.v2/Makefile
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
all:
|
||||
go test gopkg.in/redis.v2 -cpu=1,2,4
|
||||
go test gopkg.in/redis.v2 -short -race
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user