Update server dependencies
This commit is contained in:
parent
6865bf2832
commit
f1c3326af1
20
Godeps/Godeps.json
generated
20
Godeps/Godeps.json
generated
@ -29,8 +29,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/boltdb/bolt",
|
"ImportPath": "github.com/boltdb/bolt",
|
||||||
"Comment": "v1.0-43-gcf33c9e",
|
"Comment": "v1.1.0-61-g6465994",
|
||||||
"Rev": "cf33c9e0ca0a23509b8bb8edfc63e4776bb1a330"
|
"Rev": "6465994716bf6400605746e79224cf1e7ed68725"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/coreos/go-etcd/etcd",
|
"ImportPath": "github.com/coreos/go-etcd/etcd",
|
||||||
@ -52,7 +52,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/gorilla/websocket",
|
"ImportPath": "github.com/gorilla/websocket",
|
||||||
"Rev": "ecff5aabe41f13b4cdf897e3c0c9bbccbe552a29"
|
"Rev": "3986be78bf859e01f01af631ad76da5b269d270c"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/inconshreveable/mousetrap",
|
"ImportPath": "github.com/inconshreveable/mousetrap",
|
||||||
@ -82,7 +82,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/mitchellh/go-homedir",
|
"ImportPath": "github.com/mitchellh/go-homedir",
|
||||||
"Rev": "1f6da4a72e57d4e7edd4a7295a585e0a3999a2d4"
|
"Rev": "d682a8f0cf139663a984ff12528da460ca963de9"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/mitchellh/mapstructure",
|
"ImportPath": "github.com/mitchellh/mapstructure",
|
||||||
@ -94,11 +94,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/spf13/cast",
|
"ImportPath": "github.com/spf13/cast",
|
||||||
"Rev": "4d07383ffe94b5e5a6fa3af9211374a4507a0184"
|
"Rev": "ee7b3e0353166ab1f3a605294ac8cd2b77953778"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/spf13/cobra",
|
"ImportPath": "github.com/spf13/cobra",
|
||||||
"Rev": "3ee9552eebbb5db27cb81abcae66c6f1430cad29"
|
"Rev": "9c9300901990faada0c5fb3b5730f452585c7c2b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/spf13/jwalterweatherman",
|
"ImportPath": "github.com/spf13/jwalterweatherman",
|
||||||
@ -106,11 +106,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/spf13/pflag",
|
"ImportPath": "github.com/spf13/pflag",
|
||||||
"Rev": "32bfad653e29e893e4ed3812fdc0294a05126c08"
|
"Rev": "7f60f83a2c81bc3c3c0d5297f61ddfa68da9d3b7"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/spf13/viper",
|
"ImportPath": "github.com/spf13/viper",
|
||||||
"Rev": "d62d4bb4c68a773c3b5f4e72844913a2d5de0de0"
|
"Rev": "a212099cbe6fbe8d07476bfda8d2d39b6ff8f325"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/square/go-jose",
|
"ImportPath": "github.com/square/go-jose",
|
||||||
@ -140,8 +140,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/xenolf/lego/acme",
|
"ImportPath": "github.com/xenolf/lego/acme",
|
||||||
"Comment": "v0.1.1-34-g12b5de7",
|
"Comment": "v0.2.0-6-gdb3a956",
|
||||||
"Rev": "12b5de7e8cb451949aabad64cce93e4b846e2aa7"
|
"Rev": "db3a956d52bf23cc5201fe98bc9c9787d3b32c2d"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/xordataexchange/crypt/backend",
|
"ImportPath": "github.com/xordataexchange/crypt/backend",
|
||||||
|
1
Godeps/_workspace/src/github.com/boltdb/bolt/.gitignore
generated
vendored
1
Godeps/_workspace/src/github.com/boltdb/bolt/.gitignore
generated
vendored
@ -1,3 +1,4 @@
|
|||||||
*.prof
|
*.prof
|
||||||
*.test
|
*.test
|
||||||
|
*.swp
|
||||||
/bin/
|
/bin/
|
||||||
|
50
Godeps/_workspace/src/github.com/boltdb/bolt/Makefile
generated
vendored
50
Godeps/_workspace/src/github.com/boltdb/bolt/Makefile
generated
vendored
@ -1,54 +1,18 @@
|
|||||||
TEST=.
|
|
||||||
BENCH=.
|
|
||||||
COVERPROFILE=/tmp/c.out
|
|
||||||
BRANCH=`git rev-parse --abbrev-ref HEAD`
|
BRANCH=`git rev-parse --abbrev-ref HEAD`
|
||||||
COMMIT=`git rev-parse --short HEAD`
|
COMMIT=`git rev-parse --short HEAD`
|
||||||
GOLDFLAGS="-X main.branch $(BRANCH) -X main.commit $(COMMIT)"
|
GOLDFLAGS="-X main.branch $(BRANCH) -X main.commit $(COMMIT)"
|
||||||
|
|
||||||
default: build
|
default: build
|
||||||
|
|
||||||
bench:
|
race:
|
||||||
go test -v -test.run=NOTHINCONTAINSTHIS -test.bench=$(BENCH)
|
@go test -v -race -test.run="TestSimulate_(100op|1000op)"
|
||||||
|
|
||||||
# http://cloc.sourceforge.net/
|
|
||||||
cloc:
|
|
||||||
@cloc --not-match-f='Makefile|_test.go' .
|
|
||||||
|
|
||||||
cover: fmt
|
|
||||||
go test -coverprofile=$(COVERPROFILE) -test.run=$(TEST) $(COVERFLAG) .
|
|
||||||
go tool cover -html=$(COVERPROFILE)
|
|
||||||
rm $(COVERPROFILE)
|
|
||||||
|
|
||||||
cpuprofile: fmt
|
|
||||||
@go test -c
|
|
||||||
@./bolt.test -test.v -test.run=$(TEST) -test.cpuprofile cpu.prof
|
|
||||||
|
|
||||||
# go get github.com/kisielk/errcheck
|
# go get github.com/kisielk/errcheck
|
||||||
errcheck:
|
errcheck:
|
||||||
@echo "=== errcheck ==="
|
@errcheck -ignorepkg=bytes -ignore=os:Remove github.com/boltdb/bolt
|
||||||
@errcheck github.com/boltdb/bolt
|
|
||||||
|
|
||||||
fmt:
|
test:
|
||||||
@go fmt ./...
|
@go test -v -cover .
|
||||||
|
@go test -v ./cmd/bolt
|
||||||
|
|
||||||
get:
|
.PHONY: fmt test
|
||||||
@go get -d ./...
|
|
||||||
|
|
||||||
build: get
|
|
||||||
@mkdir -p bin
|
|
||||||
@go build -ldflags=$(GOLDFLAGS) -a -o bin/bolt ./cmd/bolt
|
|
||||||
|
|
||||||
test: fmt
|
|
||||||
@go get github.com/stretchr/testify/assert
|
|
||||||
@echo "=== TESTS ==="
|
|
||||||
@go test -v -cover -test.run=$(TEST)
|
|
||||||
@echo ""
|
|
||||||
@echo ""
|
|
||||||
@echo "=== CLI ==="
|
|
||||||
@go test -v -test.run=$(TEST) ./cmd/bolt
|
|
||||||
@echo ""
|
|
||||||
@echo ""
|
|
||||||
@echo "=== RACE DETECTOR ==="
|
|
||||||
@go test -v -race -test.run="TestSimulate_(100op|1000op)"
|
|
||||||
|
|
||||||
.PHONY: bench cloc cover cpuprofile fmt memprofile test
|
|
||||||
|
361
Godeps/_workspace/src/github.com/boltdb/bolt/README.md
generated
vendored
361
Godeps/_workspace/src/github.com/boltdb/bolt/README.md
generated
vendored
@ -1,8 +1,8 @@
|
|||||||
Bolt [](https://drone.io/github.com/boltdb/bolt/latest) [](https://coveralls.io/r/boltdb/bolt?branch=master) [](https://godoc.org/github.com/boltdb/bolt) 
|
Bolt [](https://drone.io/github.com/boltdb/bolt/latest) [](https://coveralls.io/r/boltdb/bolt?branch=master) [](https://godoc.org/github.com/boltdb/bolt) 
|
||||||
====
|
====
|
||||||
|
|
||||||
Bolt is a pure Go key/value store inspired by [Howard Chu's][hyc_symas] and
|
Bolt is a pure Go key/value store inspired by [Howard Chu's][hyc_symas]
|
||||||
the [LMDB project][lmdb]. The goal of the project is to provide a simple,
|
[LMDB project][lmdb]. The goal of the project is to provide a simple,
|
||||||
fast, and reliable database for projects that don't require a full database
|
fast, and reliable database for projects that don't require a full database
|
||||||
server such as Postgres or MySQL.
|
server such as Postgres or MySQL.
|
||||||
|
|
||||||
@ -13,7 +13,6 @@ and setting values. That's it.
|
|||||||
[hyc_symas]: https://twitter.com/hyc_symas
|
[hyc_symas]: https://twitter.com/hyc_symas
|
||||||
[lmdb]: http://symas.com/mdb/
|
[lmdb]: http://symas.com/mdb/
|
||||||
|
|
||||||
|
|
||||||
## Project Status
|
## Project Status
|
||||||
|
|
||||||
Bolt is stable and the API is fixed. Full unit test coverage and randomized
|
Bolt is stable and the API is fixed. Full unit test coverage and randomized
|
||||||
@ -22,6 +21,36 @@ Bolt is currently in high-load production environments serving databases as
|
|||||||
large as 1TB. Many companies such as Shopify and Heroku use Bolt-backed
|
large as 1TB. Many companies such as Shopify and Heroku use Bolt-backed
|
||||||
services every day.
|
services every day.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Getting Started](#getting-started)
|
||||||
|
- [Installing](#installing)
|
||||||
|
- [Opening a database](#opening-a-database)
|
||||||
|
- [Transactions](#transactions)
|
||||||
|
- [Read-write transactions](#read-write-transactions)
|
||||||
|
- [Read-only transactions](#read-only-transactions)
|
||||||
|
- [Batch read-write transactions](#batch-read-write-transactions)
|
||||||
|
- [Managing transactions manually](#managing-transactions-manually)
|
||||||
|
- [Using buckets](#using-buckets)
|
||||||
|
- [Using key/value pairs](#using-keyvalue-pairs)
|
||||||
|
- [Autoincrementing integer for the bucket](#autoincrementing-integer-for-the-bucket)
|
||||||
|
- [Iterating over keys](#iterating-over-keys)
|
||||||
|
- [Prefix scans](#prefix-scans)
|
||||||
|
- [Range scans](#range-scans)
|
||||||
|
- [ForEach()](#foreach)
|
||||||
|
- [Nested buckets](#nested-buckets)
|
||||||
|
- [Database backups](#database-backups)
|
||||||
|
- [Statistics](#statistics)
|
||||||
|
- [Read-Only Mode](#read-only-mode)
|
||||||
|
- [Mobile Use (iOS/Android)](#mobile-use-iosandroid)
|
||||||
|
- [Resources](#resources)
|
||||||
|
- [Comparison with other databases](#comparison-with-other-databases)
|
||||||
|
- [Postgres, MySQL, & other relational databases](#postgres-mysql--other-relational-databases)
|
||||||
|
- [LevelDB, RocksDB](#leveldb-rocksdb)
|
||||||
|
- [LMDB](#lmdb)
|
||||||
|
- [Caveats & Limitations](#caveats--limitations)
|
||||||
|
- [Reading the Source](#reading-the-source)
|
||||||
|
- [Other Projects Using Bolt](#other-projects-using-bolt)
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
@ -87,6 +116,11 @@ are not thread safe. To work with data in multiple goroutines you must start
|
|||||||
a transaction for each one or use locking to ensure only one goroutine accesses
|
a transaction for each one or use locking to ensure only one goroutine accesses
|
||||||
a transaction at a time. Creating transaction from the `DB` is thread safe.
|
a transaction at a time. Creating transaction from the `DB` is thread safe.
|
||||||
|
|
||||||
|
Read-only transactions and read-write transactions should not depend on one
|
||||||
|
another and generally shouldn't be opened simultaneously in the same goroutine.
|
||||||
|
This can cause a deadlock as the read-write transaction needs to periodically
|
||||||
|
re-map the data file but it cannot do so while a read-only transaction is open.
|
||||||
|
|
||||||
|
|
||||||
#### Read-write transactions
|
#### Read-write transactions
|
||||||
|
|
||||||
@ -125,6 +159,48 @@ no mutating operations are allowed within a read-only transaction. You can only
|
|||||||
retrieve buckets, retrieve values, and copy the database within a read-only
|
retrieve buckets, retrieve values, and copy the database within a read-only
|
||||||
transaction.
|
transaction.
|
||||||
|
|
||||||
|
|
||||||
|
#### Batch read-write transactions
|
||||||
|
|
||||||
|
Each `DB.Update()` waits for disk to commit the writes. This overhead
|
||||||
|
can be minimized by combining multiple updates with the `DB.Batch()`
|
||||||
|
function:
|
||||||
|
|
||||||
|
```go
|
||||||
|
err := db.Batch(func(tx *bolt.Tx) error {
|
||||||
|
...
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Concurrent Batch calls are opportunistically combined into larger
|
||||||
|
transactions. Batch is only useful when there are multiple goroutines
|
||||||
|
calling it.
|
||||||
|
|
||||||
|
The trade-off is that `Batch` can call the given
|
||||||
|
function multiple times, if parts of the transaction fail. The
|
||||||
|
function must be idempotent and side effects must take effect only
|
||||||
|
after a successful return from `DB.Batch()`.
|
||||||
|
|
||||||
|
For example: don't display messages from inside the function, instead
|
||||||
|
set variables in the enclosing scope:
|
||||||
|
|
||||||
|
```go
|
||||||
|
var id uint64
|
||||||
|
err := db.Batch(func(tx *bolt.Tx) error {
|
||||||
|
// Find last key in bucket, decode as bigendian uint64, increment
|
||||||
|
// by one, encode back to []byte, and add new key.
|
||||||
|
...
|
||||||
|
id = newValue
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return ...
|
||||||
|
}
|
||||||
|
fmt.Println("Allocated ID %d", id)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
#### Managing transactions manually
|
#### Managing transactions manually
|
||||||
|
|
||||||
The `DB.View()` and `DB.Update()` functions are wrappers around the `DB.Begin()`
|
The `DB.View()` and `DB.Update()` functions are wrappers around the `DB.Begin()`
|
||||||
@ -133,8 +209,8 @@ and then safely close your transaction if an error is returned. This is the
|
|||||||
recommended way to use Bolt transactions.
|
recommended way to use Bolt transactions.
|
||||||
|
|
||||||
However, sometimes you may want to manually start and end your transactions.
|
However, sometimes you may want to manually start and end your transactions.
|
||||||
You can use the `Tx.Begin()` function directly but _please_ be sure to close the
|
You can use the `Tx.Begin()` function directly but **please** be sure to close
|
||||||
transaction.
|
the transaction.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Start a writable transaction.
|
// Start a writable transaction.
|
||||||
@ -209,13 +285,60 @@ db.View(func(tx *bolt.Tx) error {
|
|||||||
```
|
```
|
||||||
|
|
||||||
The `Get()` function does not return an error because its operation is
|
The `Get()` function does not return an error because its operation is
|
||||||
guarenteed to work (unless there is some kind of system failure). If the key
|
guaranteed to work (unless there is some kind of system failure). If the key
|
||||||
exists then it will return its byte slice value. If it doesn't exist then it
|
exists then it will return its byte slice value. If it doesn't exist then it
|
||||||
will return `nil`. It's important to note that you can have a zero-length value
|
will return `nil`. It's important to note that you can have a zero-length value
|
||||||
set to a key which is different than the key not existing.
|
set to a key which is different than the key not existing.
|
||||||
|
|
||||||
Use the `Bucket.Delete()` function to delete a key from the bucket.
|
Use the `Bucket.Delete()` function to delete a key from the bucket.
|
||||||
|
|
||||||
|
Please note that values returned from `Get()` are only valid while the
|
||||||
|
transaction is open. If you need to use a value outside of the transaction
|
||||||
|
then you must use `copy()` to copy it to another byte slice.
|
||||||
|
|
||||||
|
|
||||||
|
### Autoincrementing integer for the bucket
|
||||||
|
By using the `NextSequence()` function, you can let Bolt determine a sequence
|
||||||
|
which can be used as the unique identifier for your key/value pairs. See the
|
||||||
|
example below.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// CreateUser saves u to the store. The new user ID is set on u once the data is persisted.
|
||||||
|
func (s *Store) CreateUser(u *User) error {
|
||||||
|
return s.db.Update(func(tx *bolt.Tx) error {
|
||||||
|
// Retrieve the users bucket.
|
||||||
|
// This should be created when the DB is first opened.
|
||||||
|
b := tx.Bucket([]byte("users"))
|
||||||
|
|
||||||
|
// Generate ID for the user.
|
||||||
|
// This returns an error only if the Tx is closed or not writeable.
|
||||||
|
// That can't happen in an Update() call so I ignore the error check.
|
||||||
|
id, _ = b.NextSequence()
|
||||||
|
u.ID = int(id)
|
||||||
|
|
||||||
|
// Marshal user data into bytes.
|
||||||
|
buf, err := json.Marshal(u)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist bytes to users bucket.
|
||||||
|
return b.Put(itob(u.ID), buf)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// itob returns an 8-byte big endian representation of v.
|
||||||
|
func itob(v int) []byte {
|
||||||
|
b := make([]byte, 8)
|
||||||
|
binary.BigEndian.PutUint64(b, uint64(v))
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID int
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Iterating over keys
|
### Iterating over keys
|
||||||
|
|
||||||
@ -225,7 +348,9 @@ iteration over these keys extremely fast. To iterate over keys we'll use a
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
db.View(func(tx *bolt.Tx) error {
|
db.View(func(tx *bolt.Tx) error {
|
||||||
|
// Assume bucket exists and has keys
|
||||||
b := tx.Bucket([]byte("MyBucket"))
|
b := tx.Bucket([]byte("MyBucket"))
|
||||||
|
|
||||||
c := b.Cursor()
|
c := b.Cursor()
|
||||||
|
|
||||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||||
@ -249,10 +374,15 @@ Next() Move to the next key.
|
|||||||
Prev() Move to the previous key.
|
Prev() Move to the previous key.
|
||||||
```
|
```
|
||||||
|
|
||||||
When you have iterated to the end of the cursor then `Next()` will return `nil`.
|
Each of those functions has a return signature of `(key []byte, value []byte)`.
|
||||||
You must seek to a position using `First()`, `Last()`, or `Seek()` before
|
When you have iterated to the end of the cursor then `Next()` will return a
|
||||||
calling `Next()` or `Prev()`. If you do not seek to a position then these
|
`nil` key. You must seek to a position using `First()`, `Last()`, or `Seek()`
|
||||||
functions will return `nil`.
|
before calling `Next()` or `Prev()`. If you do not seek to a position then
|
||||||
|
these functions will return a `nil` key.
|
||||||
|
|
||||||
|
During iteration, if the key is non-`nil` but the value is `nil`, that means
|
||||||
|
the key refers to a bucket rather than a value. Use `Bucket.Bucket()` to
|
||||||
|
access the sub-bucket.
|
||||||
|
|
||||||
|
|
||||||
#### Prefix scans
|
#### Prefix scans
|
||||||
@ -261,6 +391,7 @@ To iterate over a key prefix, you can combine `Seek()` and `bytes.HasPrefix()`:
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
db.View(func(tx *bolt.Tx) error {
|
db.View(func(tx *bolt.Tx) error {
|
||||||
|
// Assume bucket exists and has keys
|
||||||
c := tx.Bucket([]byte("MyBucket")).Cursor()
|
c := tx.Bucket([]byte("MyBucket")).Cursor()
|
||||||
|
|
||||||
prefix := []byte("1234")
|
prefix := []byte("1234")
|
||||||
@ -280,7 +411,7 @@ date range like this:
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
db.View(func(tx *bolt.Tx) error {
|
db.View(func(tx *bolt.Tx) error {
|
||||||
// Assume our events bucket has RFC3339 encoded time keys.
|
// Assume our events bucket exists and has RFC3339 encoded time keys.
|
||||||
c := tx.Bucket([]byte("Events")).Cursor()
|
c := tx.Bucket([]byte("Events")).Cursor()
|
||||||
|
|
||||||
// Our time range spans the 90's decade.
|
// Our time range spans the 90's decade.
|
||||||
@ -304,7 +435,9 @@ all the keys in a bucket:
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
db.View(func(tx *bolt.Tx) error {
|
db.View(func(tx *bolt.Tx) error {
|
||||||
|
// Assume bucket exists and has keys
|
||||||
b := tx.Bucket([]byte("MyBucket"))
|
b := tx.Bucket([]byte("MyBucket"))
|
||||||
|
|
||||||
b.ForEach(func(k, v []byte) error {
|
b.ForEach(func(k, v []byte) error {
|
||||||
fmt.Printf("key=%s, value=%s\n", k, v)
|
fmt.Printf("key=%s, value=%s\n", k, v)
|
||||||
return nil
|
return nil
|
||||||
@ -328,22 +461,26 @@ func (*Bucket) DeleteBucket(key []byte) error
|
|||||||
|
|
||||||
### Database backups
|
### Database backups
|
||||||
|
|
||||||
Bolt is a single file so it's easy to backup. You can use the `Tx.Copy()`
|
Bolt is a single file so it's easy to backup. You can use the `Tx.WriteTo()`
|
||||||
function to write a consistent view of the database to a writer. If you call
|
function to write a consistent view of the database to a writer. If you call
|
||||||
this from a read-only transaction, it will perform a hot backup and not block
|
this from a read-only transaction, it will perform a hot backup and not block
|
||||||
your other database reads and writes. It will also use `O_DIRECT` when available
|
your other database reads and writes.
|
||||||
to prevent page cache trashing.
|
|
||||||
|
By default, it will use a regular file handle which will utilize the operating
|
||||||
|
system's page cache. See the [`Tx`](https://godoc.org/github.com/boltdb/bolt#Tx)
|
||||||
|
documentation for information about optimizing for larger-than-RAM datasets.
|
||||||
|
|
||||||
One common use case is to backup over HTTP so you can use tools like `cURL` to
|
One common use case is to backup over HTTP so you can use tools like `cURL` to
|
||||||
do database backups:
|
do database backups:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func BackupHandleFunc(w http.ResponseWriter, req *http.Request) {
|
func BackupHandleFunc(w http.ResponseWriter, req *http.Request) {
|
||||||
err := db.View(func(tx bolt.Tx) error {
|
err := db.View(func(tx *bolt.Tx) error {
|
||||||
w.Header().Set("Content-Type", "application/octet-stream")
|
w.Header().Set("Content-Type", "application/octet-stream")
|
||||||
w.Header().Set("Content-Disposition", `attachment; filename="my.db"`)
|
w.Header().Set("Content-Disposition", `attachment; filename="my.db"`)
|
||||||
w.Header().Set("Content-Length", strconv.Itoa(int(tx.Size())))
|
w.Header().Set("Content-Length", strconv.Itoa(int(tx.Size())))
|
||||||
return tx.Copy(w)
|
_, err := tx.WriteTo(w)
|
||||||
|
return err
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
@ -399,12 +536,105 @@ It's also useful to pipe these stats to a service such as statsd for monitoring
|
|||||||
or to provide an HTTP endpoint that will perform a fixed-length sample.
|
or to provide an HTTP endpoint that will perform a fixed-length sample.
|
||||||
|
|
||||||
|
|
||||||
|
### Read-Only Mode
|
||||||
|
|
||||||
|
Sometimes it is useful to create a shared, read-only Bolt database. To this,
|
||||||
|
set the `Options.ReadOnly` flag when opening your database. Read-only mode
|
||||||
|
uses a shared lock to allow multiple processes to read from the database but
|
||||||
|
it will block any processes from opening the database in read-write mode.
|
||||||
|
|
||||||
|
```go
|
||||||
|
db, err := bolt.Open("my.db", 0666, &bolt.Options{ReadOnly: true})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mobile Use (iOS/Android)
|
||||||
|
|
||||||
|
Bolt is able to run on mobile devices by leveraging the binding feature of the
|
||||||
|
[gomobile](https://github.com/golang/mobile) tool. Create a struct that will
|
||||||
|
contain your database logic and a reference to a `*bolt.DB` with a initializing
|
||||||
|
contstructor that takes in a filepath where the database file will be stored.
|
||||||
|
Neither Android nor iOS require extra permissions or cleanup from using this method.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func NewBoltDB(filepath string) *BoltDB {
|
||||||
|
db, err := bolt.Open(filepath+"/demo.db", 0600, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BoltDB{db}
|
||||||
|
}
|
||||||
|
|
||||||
|
type BoltDB struct {
|
||||||
|
db *bolt.DB
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoltDB) Path() string {
|
||||||
|
return b.db.Path()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BoltDB) Close() {
|
||||||
|
b.db.Close()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Database logic should be defined as methods on this wrapper struct.
|
||||||
|
|
||||||
|
To initialize this struct from the native language (both platforms now sync
|
||||||
|
their local storage to the cloud. These snippets disable that functionality for the
|
||||||
|
database file):
|
||||||
|
|
||||||
|
#### Android
|
||||||
|
|
||||||
|
```java
|
||||||
|
String path;
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >=android.os.Build.VERSION_CODES.LOLLIPOP){
|
||||||
|
path = getNoBackupFilesDir().getAbsolutePath();
|
||||||
|
} else{
|
||||||
|
path = getFilesDir().getAbsolutePath();
|
||||||
|
}
|
||||||
|
Boltmobiledemo.BoltDB boltDB = Boltmobiledemo.NewBoltDB(path)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### iOS
|
||||||
|
|
||||||
|
```objc
|
||||||
|
- (void)demo {
|
||||||
|
NSString* path = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
|
||||||
|
NSUserDomainMask,
|
||||||
|
YES) objectAtIndex:0];
|
||||||
|
GoBoltmobiledemoBoltDB * demo = GoBoltmobiledemoNewBoltDB(path);
|
||||||
|
[self addSkipBackupAttributeToItemAtPath:demo.path];
|
||||||
|
//Some DB Logic would go here
|
||||||
|
[demo close];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)addSkipBackupAttributeToItemAtPath:(NSString *) filePathString
|
||||||
|
{
|
||||||
|
NSURL* URL= [NSURL fileURLWithPath: filePathString];
|
||||||
|
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
|
||||||
|
|
||||||
|
NSError *error = nil;
|
||||||
|
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
|
||||||
|
forKey: NSURLIsExcludedFromBackupKey error: &error];
|
||||||
|
if(!success){
|
||||||
|
NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
For more information on getting started with Bolt, check out the following articles:
|
For more information on getting started with Bolt, check out the following articles:
|
||||||
|
|
||||||
* [Intro to BoltDB: Painless Performant Persistence](http://npf.io/2014/07/intro-to-boltdb-painless-performant-persistence/) by [Nate Finch](https://github.com/natefinch).
|
* [Intro to BoltDB: Painless Performant Persistence](http://npf.io/2014/07/intro-to-boltdb-painless-performant-persistence/) by [Nate Finch](https://github.com/natefinch).
|
||||||
|
* [Bolt -- an embedded key/value database for Go](https://www.progville.com/go/bolt-embedded-db-golang/) by Progville
|
||||||
|
|
||||||
|
|
||||||
## Comparison with other databases
|
## Comparison with other databases
|
||||||
@ -433,7 +663,7 @@ they are libraries bundled into the application, however, their underlying
|
|||||||
structure is a log-structured merge-tree (LSM tree). An LSM tree optimizes
|
structure is a log-structured merge-tree (LSM tree). An LSM tree optimizes
|
||||||
random writes by using a write ahead log and multi-tiered, sorted files called
|
random writes by using a write ahead log and multi-tiered, sorted files called
|
||||||
SSTables. Bolt uses a B+tree internally and only a single file. Both approaches
|
SSTables. Bolt uses a B+tree internally and only a single file. Both approaches
|
||||||
have trade offs.
|
have trade-offs.
|
||||||
|
|
||||||
If you require a high random write throughput (>10,000 w/sec) or you need to use
|
If you require a high random write throughput (>10,000 w/sec) or you need to use
|
||||||
spinning disks then LevelDB could be a good choice. If your application is
|
spinning disks then LevelDB could be a good choice. If your application is
|
||||||
@ -469,9 +699,8 @@ It's important to pick the right tool for the job and Bolt is no exception.
|
|||||||
Here are a few things to note when evaluating and using Bolt:
|
Here are a few things to note when evaluating and using Bolt:
|
||||||
|
|
||||||
* Bolt is good for read intensive workloads. Sequential write performance is
|
* Bolt is good for read intensive workloads. Sequential write performance is
|
||||||
also fast but random writes can be slow. You can add a write-ahead log or
|
also fast but random writes can be slow. You can use `DB.Batch()` or add a
|
||||||
[transaction coalescer](https://github.com/boltdb/coalescer) in front of Bolt
|
write-ahead log to help mitigate this issue.
|
||||||
to mitigate this issue.
|
|
||||||
|
|
||||||
* Bolt uses a B+tree internally so there can be a lot of random page access.
|
* Bolt uses a B+tree internally so there can be a lot of random page access.
|
||||||
SSDs provide a significant performance boost over spinning disks.
|
SSDs provide a significant performance boost over spinning disks.
|
||||||
@ -501,7 +730,75 @@ Here are a few things to note when evaluating and using Bolt:
|
|||||||
can in memory and will release memory as needed to other processes. This means
|
can in memory and will release memory as needed to other processes. This means
|
||||||
that Bolt can show very high memory usage when working with large databases.
|
that Bolt can show very high memory usage when working with large databases.
|
||||||
However, this is expected and the OS will release memory as needed. Bolt can
|
However, this is expected and the OS will release memory as needed. Bolt can
|
||||||
handle databases much larger than the available physical RAM.
|
handle databases much larger than the available physical RAM, provided its
|
||||||
|
memory-map fits in the process virtual address space. It may be problematic
|
||||||
|
on 32-bits systems.
|
||||||
|
|
||||||
|
* The data structures in the Bolt database are memory mapped so the data file
|
||||||
|
will be endian specific. This means that you cannot copy a Bolt file from a
|
||||||
|
little endian machine to a big endian machine and have it work. For most
|
||||||
|
users this is not a concern since most modern CPUs are little endian.
|
||||||
|
|
||||||
|
* Because of the way pages are laid out on disk, Bolt cannot truncate data files
|
||||||
|
and return free pages back to the disk. Instead, Bolt maintains a free list
|
||||||
|
of unused pages within its data file. These free pages can be reused by later
|
||||||
|
transactions. This works well for many use cases as databases generally tend
|
||||||
|
to grow. However, it's important to note that deleting large chunks of data
|
||||||
|
will not allow you to reclaim that space on disk.
|
||||||
|
|
||||||
|
For more information on page allocation, [see this comment][page-allocation].
|
||||||
|
|
||||||
|
[page-allocation]: https://github.com/boltdb/bolt/issues/308#issuecomment-74811638
|
||||||
|
|
||||||
|
|
||||||
|
## Reading the Source
|
||||||
|
|
||||||
|
Bolt is a relatively small code base (<3KLOC) for an embedded, serializable,
|
||||||
|
transactional key/value database so it can be a good starting point for people
|
||||||
|
interested in how databases work.
|
||||||
|
|
||||||
|
The best places to start are the main entry points into Bolt:
|
||||||
|
|
||||||
|
- `Open()` - Initializes the reference to the database. It's responsible for
|
||||||
|
creating the database if it doesn't exist, obtaining an exclusive lock on the
|
||||||
|
file, reading the meta pages, & memory-mapping the file.
|
||||||
|
|
||||||
|
- `DB.Begin()` - Starts a read-only or read-write transaction depending on the
|
||||||
|
value of the `writable` argument. This requires briefly obtaining the "meta"
|
||||||
|
lock to keep track of open transactions. Only one read-write transaction can
|
||||||
|
exist at a time so the "rwlock" is acquired during the life of a read-write
|
||||||
|
transaction.
|
||||||
|
|
||||||
|
- `Bucket.Put()` - Writes a key/value pair into a bucket. After validating the
|
||||||
|
arguments, a cursor is used to traverse the B+tree to the page and position
|
||||||
|
where they key & value will be written. Once the position is found, the bucket
|
||||||
|
materializes the underlying page and the page's parent pages into memory as
|
||||||
|
"nodes". These nodes are where mutations occur during read-write transactions.
|
||||||
|
These changes get flushed to disk during commit.
|
||||||
|
|
||||||
|
- `Bucket.Get()` - Retrieves a key/value pair from a bucket. This uses a cursor
|
||||||
|
to move to the page & position of a key/value pair. During a read-only
|
||||||
|
transaction, the key and value data is returned as a direct reference to the
|
||||||
|
underlying mmap file so there's no allocation overhead. For read-write
|
||||||
|
transactions, this data may reference the mmap file or one of the in-memory
|
||||||
|
node values.
|
||||||
|
|
||||||
|
- `Cursor` - This object is simply for traversing the B+tree of on-disk pages
|
||||||
|
or in-memory nodes. It can seek to a specific key, move to the first or last
|
||||||
|
value, or it can move forward or backward. The cursor handles the movement up
|
||||||
|
and down the B+tree transparently to the end user.
|
||||||
|
|
||||||
|
- `Tx.Commit()` - Converts the in-memory dirty nodes and the list of free pages
|
||||||
|
into pages to be written to disk. Writing to disk then occurs in two phases.
|
||||||
|
First, the dirty pages are written to disk and an `fsync()` occurs. Second, a
|
||||||
|
new meta page with an incremented transaction ID is written and another
|
||||||
|
`fsync()` occurs. This two phase write ensures that partially written data
|
||||||
|
pages are ignored in the event of a crash since the meta page pointing to them
|
||||||
|
is never written. Partially written meta pages are invalidated because they
|
||||||
|
are written with a checksum.
|
||||||
|
|
||||||
|
If you have additional notes that could be helpful for others, please submit
|
||||||
|
them via pull request.
|
||||||
|
|
||||||
|
|
||||||
## Other Projects Using Bolt
|
## Other Projects Using Bolt
|
||||||
@ -509,23 +806,35 @@ Here are a few things to note when evaluating and using Bolt:
|
|||||||
Below is a list of public, open source projects that use Bolt:
|
Below is a list of public, open source projects that use Bolt:
|
||||||
|
|
||||||
* [Operation Go: A Routine Mission](http://gocode.io) - An online programming game for Golang using Bolt for user accounts and a leaderboard.
|
* [Operation Go: A Routine Mission](http://gocode.io) - An online programming game for Golang using Bolt for user accounts and a leaderboard.
|
||||||
* [Bazil](https://github.com/bazillion/bazil) - A file system that lets your data reside where it is most convenient for it to reside.
|
* [Bazil](https://bazil.org/) - A file system that lets your data reside where it is most convenient for it to reside.
|
||||||
* [DVID](https://github.com/janelia-flyem/dvid) - Added Bolt as optional storage engine and testing it against Basho-tuned leveldb.
|
* [DVID](https://github.com/janelia-flyem/dvid) - Added Bolt as optional storage engine and testing it against Basho-tuned leveldb.
|
||||||
* [Skybox Analytics](https://github.com/skybox/skybox) - A standalone funnel analysis tool for web analytics.
|
* [Skybox Analytics](https://github.com/skybox/skybox) - A standalone funnel analysis tool for web analytics.
|
||||||
* [Scuttlebutt](https://github.com/benbjohnson/scuttlebutt) - Uses Bolt to store and process all Twitter mentions of GitHub projects.
|
* [Scuttlebutt](https://github.com/benbjohnson/scuttlebutt) - Uses Bolt to store and process all Twitter mentions of GitHub projects.
|
||||||
* [Wiki](https://github.com/peterhellberg/wiki) - A tiny wiki using Goji, BoltDB and Blackfriday.
|
* [Wiki](https://github.com/peterhellberg/wiki) - A tiny wiki using Goji, BoltDB and Blackfriday.
|
||||||
* [ChainStore](https://github.com/nulayer/chainstore) - Simple key-value interface to a variety of storage engines organized as a chain of operations.
|
* [ChainStore](https://github.com/pressly/chainstore) - Simple key-value interface to a variety of storage engines organized as a chain of operations.
|
||||||
* [MetricBase](https://github.com/msiebuhr/MetricBase) - Single-binary version of Graphite.
|
* [MetricBase](https://github.com/msiebuhr/MetricBase) - Single-binary version of Graphite.
|
||||||
* [Gitchain](https://github.com/gitchain/gitchain) - Decentralized, peer-to-peer Git repositories aka "Git meets Bitcoin".
|
* [Gitchain](https://github.com/gitchain/gitchain) - Decentralized, peer-to-peer Git repositories aka "Git meets Bitcoin".
|
||||||
* [event-shuttle](https://github.com/sclasen/event-shuttle) - A Unix system service to collect and reliably deliver messages to Kafka.
|
* [event-shuttle](https://github.com/sclasen/event-shuttle) - A Unix system service to collect and reliably deliver messages to Kafka.
|
||||||
* [ipxed](https://github.com/kelseyhightower/ipxed) - Web interface and api for ipxed.
|
* [ipxed](https://github.com/kelseyhightower/ipxed) - Web interface and api for ipxed.
|
||||||
* [BoltStore](https://github.com/yosssi/boltstore) - Session store using Bolt.
|
* [BoltStore](https://github.com/yosssi/boltstore) - Session store using Bolt.
|
||||||
* [photosite/session](http://godoc.org/bitbucket.org/kardianos/photosite/session) - Sessions for a photo viewing site.
|
* [photosite/session](https://godoc.org/bitbucket.org/kardianos/photosite/session) - Sessions for a photo viewing site.
|
||||||
* [LedisDB](https://github.com/siddontang/ledisdb) - A high performance NoSQL, using Bolt as optional storage.
|
* [LedisDB](https://github.com/siddontang/ledisdb) - A high performance NoSQL, using Bolt as optional storage.
|
||||||
* [ipLocator](https://github.com/AndreasBriese/ipLocator) - A fast ip-geo-location-server using bolt with bloom filters.
|
* [ipLocator](https://github.com/AndreasBriese/ipLocator) - A fast ip-geo-location-server using bolt with bloom filters.
|
||||||
* [cayley](https://github.com/google/cayley) - Cayley is an open-source graph database using Bolt as optional backend.
|
* [cayley](https://github.com/google/cayley) - Cayley is an open-source graph database using Bolt as optional backend.
|
||||||
* [bleve](http://www.blevesearch.com/) - A pure Go search engine similar to ElasticSearch that uses Bolt as the default storage backend.
|
* [bleve](http://www.blevesearch.com/) - A pure Go search engine similar to ElasticSearch that uses Bolt as the default storage backend.
|
||||||
* [tentacool](https://github.com/optiflows/tentacool) - REST api server to manage system stuff (IP, DNS, Gateway...) on a linux server.
|
* [tentacool](https://github.com/optiflows/tentacool) - REST api server to manage system stuff (IP, DNS, Gateway...) on a linux server.
|
||||||
* [SkyDB](https://github.com/skydb/sky) - Behavioral analytics database.
|
* [SkyDB](https://github.com/skydb/sky) - Behavioral analytics database.
|
||||||
|
* [Seaweed File System](https://github.com/chrislusf/seaweedfs) - Highly scalable distributed key~file system with O(1) disk read.
|
||||||
|
* [InfluxDB](https://influxdata.com) - Scalable datastore for metrics, events, and real-time analytics.
|
||||||
|
* [Freehold](http://tshannon.bitbucket.org/freehold/) - An open, secure, and lightweight platform for your files and data.
|
||||||
|
* [Prometheus Annotation Server](https://github.com/oliver006/prom_annotation_server) - Annotation server for PromDash & Prometheus service monitoring system.
|
||||||
|
* [Consul](https://github.com/hashicorp/consul) - Consul is service discovery and configuration made easy. Distributed, highly available, and datacenter-aware.
|
||||||
|
* [Kala](https://github.com/ajvb/kala) - Kala is a modern job scheduler optimized to run on a single node. It is persistent, JSON over HTTP API, ISO 8601 duration notation, and dependent jobs.
|
||||||
|
* [drive](https://github.com/odeke-em/drive) - drive is an unofficial Google Drive command line client for \*NIX operating systems.
|
||||||
|
* [stow](https://github.com/djherbis/stow) - a persistence manager for objects
|
||||||
|
backed by boltdb.
|
||||||
|
* [buckets](https://github.com/joyrexus/buckets) - a bolt wrapper streamlining
|
||||||
|
simple tx and key scans.
|
||||||
|
* [Request Baskets](https://github.com/darklynx/request-baskets) - A web service to collect arbitrary HTTP requests and inspect them via REST API or simple web UI, similar to [RequestBin](http://requestb.in/) service
|
||||||
|
|
||||||
If you are using Bolt in a project please send a pull request to add it to the list.
|
If you are using Bolt in a project please send a pull request to add it to the list.
|
||||||
|
9
Godeps/_workspace/src/github.com/boltdb/bolt/bolt_arm64.go
generated
vendored
Normal file
9
Godeps/_workspace/src/github.com/boltdb/bolt/bolt_arm64.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// +build arm64
|
||||||
|
|
||||||
|
package bolt
|
||||||
|
|
||||||
|
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||||
|
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||||
|
|
||||||
|
// maxAllocSize is the size used when creating array pointers.
|
||||||
|
const maxAllocSize = 0x7FFFFFFF
|
2
Godeps/_workspace/src/github.com/boltdb/bolt/bolt_linux.go
generated
vendored
2
Godeps/_workspace/src/github.com/boltdb/bolt/bolt_linux.go
generated
vendored
@ -4,8 +4,6 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
var odirect = syscall.O_DIRECT
|
|
||||||
|
|
||||||
// fdatasync flushes written data to a file descriptor.
|
// fdatasync flushes written data to a file descriptor.
|
||||||
func fdatasync(db *DB) error {
|
func fdatasync(db *DB) error {
|
||||||
return syscall.Fdatasync(int(db.file.Fd()))
|
return syscall.Fdatasync(int(db.file.Fd()))
|
||||||
|
2
Godeps/_workspace/src/github.com/boltdb/bolt/bolt_openbsd.go
generated
vendored
2
Godeps/_workspace/src/github.com/boltdb/bolt/bolt_openbsd.go
generated
vendored
@ -11,8 +11,6 @@ const (
|
|||||||
msInvalidate // invalidate cached data
|
msInvalidate // invalidate cached data
|
||||||
)
|
)
|
||||||
|
|
||||||
var odirect int
|
|
||||||
|
|
||||||
func msync(db *DB) error {
|
func msync(db *DB) error {
|
||||||
_, _, errno := syscall.Syscall(syscall.SYS_MSYNC, uintptr(unsafe.Pointer(db.data)), uintptr(db.datasz), msInvalidate)
|
_, _, errno := syscall.Syscall(syscall.SYS_MSYNC, uintptr(unsafe.Pointer(db.data)), uintptr(db.datasz), msInvalidate)
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
|
9
Godeps/_workspace/src/github.com/boltdb/bolt/bolt_ppc64le.go
generated
vendored
Normal file
9
Godeps/_workspace/src/github.com/boltdb/bolt/bolt_ppc64le.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// +build ppc64le
|
||||||
|
|
||||||
|
package bolt
|
||||||
|
|
||||||
|
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||||
|
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||||
|
|
||||||
|
// maxAllocSize is the size used when creating array pointers.
|
||||||
|
const maxAllocSize = 0x7FFFFFFF
|
9
Godeps/_workspace/src/github.com/boltdb/bolt/bolt_s390x.go
generated
vendored
Normal file
9
Godeps/_workspace/src/github.com/boltdb/bolt/bolt_s390x.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// +build s390x
|
||||||
|
|
||||||
|
package bolt
|
||||||
|
|
||||||
|
// maxMapSize represents the largest mmap size supported by Bolt.
|
||||||
|
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
|
||||||
|
|
||||||
|
// maxAllocSize is the size used when creating array pointers.
|
||||||
|
const maxAllocSize = 0x7FFFFFFF
|
36
Godeps/_workspace/src/github.com/boltdb/bolt/bolt_test.go
generated
vendored
36
Godeps/_workspace/src/github.com/boltdb/bolt/bolt_test.go
generated
vendored
@ -1,36 +0,0 @@
|
|||||||
package bolt_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// assert fails the test if the condition is false.
|
|
||||||
func assert(tb testing.TB, condition bool, msg string, v ...interface{}) {
|
|
||||||
if !condition {
|
|
||||||
_, file, line, _ := runtime.Caller(1)
|
|
||||||
fmt.Printf("\033[31m%s:%d: "+msg+"\033[39m\n\n", append([]interface{}{filepath.Base(file), line}, v...)...)
|
|
||||||
tb.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ok fails the test if an err is not nil.
|
|
||||||
func ok(tb testing.TB, err error) {
|
|
||||||
if err != nil {
|
|
||||||
_, file, line, _ := runtime.Caller(1)
|
|
||||||
fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error())
|
|
||||||
tb.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// equals fails the test if exp is not equal to act.
|
|
||||||
func equals(tb testing.TB, exp, act interface{}) {
|
|
||||||
if !reflect.DeepEqual(exp, act) {
|
|
||||||
_, file, line, _ := runtime.Caller(1)
|
|
||||||
fmt.Printf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act)
|
|
||||||
tb.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
35
Godeps/_workspace/src/github.com/boltdb/bolt/bolt_unix.go
generated
vendored
35
Godeps/_workspace/src/github.com/boltdb/bolt/bolt_unix.go
generated
vendored
@ -1,4 +1,4 @@
|
|||||||
// +build !windows,!plan9
|
// +build !windows,!plan9,!solaris
|
||||||
|
|
||||||
package bolt
|
package bolt
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// flock acquires an advisory lock on a file descriptor.
|
// flock acquires an advisory lock on a file descriptor.
|
||||||
func flock(f *os.File, timeout time.Duration) error {
|
func flock(f *os.File, exclusive bool, timeout time.Duration) error {
|
||||||
var t time.Time
|
var t time.Time
|
||||||
for {
|
for {
|
||||||
// If we're beyond our timeout then return an error.
|
// If we're beyond our timeout then return an error.
|
||||||
@ -21,9 +21,13 @@ func flock(f *os.File, timeout time.Duration) error {
|
|||||||
} else if timeout > 0 && time.Since(t) > timeout {
|
} else if timeout > 0 && time.Since(t) > timeout {
|
||||||
return ErrTimeout
|
return ErrTimeout
|
||||||
}
|
}
|
||||||
|
flag := syscall.LOCK_SH
|
||||||
|
if exclusive {
|
||||||
|
flag = syscall.LOCK_EX
|
||||||
|
}
|
||||||
|
|
||||||
// Otherwise attempt to obtain an exclusive lock.
|
// Otherwise attempt to obtain an exclusive lock.
|
||||||
err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
|
err := syscall.Flock(int(f.Fd()), flag|syscall.LOCK_NB)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
} else if err != syscall.EWOULDBLOCK {
|
} else if err != syscall.EWOULDBLOCK {
|
||||||
@ -42,21 +46,17 @@ func funlock(f *os.File) error {
|
|||||||
|
|
||||||
// mmap memory maps a DB's data file.
|
// mmap memory maps a DB's data file.
|
||||||
func mmap(db *DB, sz int) error {
|
func mmap(db *DB, sz int) error {
|
||||||
// Truncate and fsync to ensure file size metadata is flushed.
|
|
||||||
// https://github.com/boltdb/bolt/issues/284
|
|
||||||
if err := db.file.Truncate(int64(sz)); err != nil {
|
|
||||||
return fmt.Errorf("file resize error: %s", err)
|
|
||||||
}
|
|
||||||
if err := db.file.Sync(); err != nil {
|
|
||||||
return fmt.Errorf("file sync error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map the data file to memory.
|
// Map the data file to memory.
|
||||||
b, err := syscall.Mmap(int(db.file.Fd()), 0, sz, syscall.PROT_READ, syscall.MAP_SHARED)
|
b, err := syscall.Mmap(int(db.file.Fd()), 0, sz, syscall.PROT_READ, syscall.MAP_SHARED|db.MmapFlags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Advise the kernel that the mmap is accessed randomly.
|
||||||
|
if err := madvise(b, syscall.MADV_RANDOM); err != nil {
|
||||||
|
return fmt.Errorf("madvise: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Save the original byte slice and convert to a byte array pointer.
|
// Save the original byte slice and convert to a byte array pointer.
|
||||||
db.dataref = b
|
db.dataref = b
|
||||||
db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0]))
|
db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0]))
|
||||||
@ -78,3 +78,12 @@ func munmap(db *DB) error {
|
|||||||
db.datasz = 0
|
db.datasz = 0
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: This function is copied from stdlib because it is not available on darwin.
|
||||||
|
func madvise(b []byte, advice int) (err error) {
|
||||||
|
_, _, e1 := syscall.Syscall(syscall.SYS_MADVISE, uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)), uintptr(advice))
|
||||||
|
if e1 != 0 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
90
Godeps/_workspace/src/github.com/boltdb/bolt/bolt_unix_solaris.go
generated
vendored
Normal file
90
Godeps/_workspace/src/github.com/boltdb/bolt/bolt_unix_solaris.go
generated
vendored
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package bolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// flock acquires an advisory lock on a file descriptor.
|
||||||
|
func flock(f *os.File, exclusive bool, timeout time.Duration) error {
|
||||||
|
var t time.Time
|
||||||
|
for {
|
||||||
|
// If we're beyond our timeout then return an error.
|
||||||
|
// This can only occur after we've attempted a flock once.
|
||||||
|
if t.IsZero() {
|
||||||
|
t = time.Now()
|
||||||
|
} else if timeout > 0 && time.Since(t) > timeout {
|
||||||
|
return ErrTimeout
|
||||||
|
}
|
||||||
|
var lock syscall.Flock_t
|
||||||
|
lock.Start = 0
|
||||||
|
lock.Len = 0
|
||||||
|
lock.Pid = 0
|
||||||
|
lock.Whence = 0
|
||||||
|
lock.Pid = 0
|
||||||
|
if exclusive {
|
||||||
|
lock.Type = syscall.F_WRLCK
|
||||||
|
} else {
|
||||||
|
lock.Type = syscall.F_RDLCK
|
||||||
|
}
|
||||||
|
err := syscall.FcntlFlock(f.Fd(), syscall.F_SETLK, &lock)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
} else if err != syscall.EAGAIN {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for a bit and try again.
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// funlock releases an advisory lock on a file descriptor.
|
||||||
|
func funlock(f *os.File) error {
|
||||||
|
var lock syscall.Flock_t
|
||||||
|
lock.Start = 0
|
||||||
|
lock.Len = 0
|
||||||
|
lock.Type = syscall.F_UNLCK
|
||||||
|
lock.Whence = 0
|
||||||
|
return syscall.FcntlFlock(uintptr(f.Fd()), syscall.F_SETLK, &lock)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mmap memory maps a DB's data file.
|
||||||
|
func mmap(db *DB, sz int) error {
|
||||||
|
// Map the data file to memory.
|
||||||
|
b, err := unix.Mmap(int(db.file.Fd()), 0, sz, syscall.PROT_READ, syscall.MAP_SHARED|db.MmapFlags)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advise the kernel that the mmap is accessed randomly.
|
||||||
|
if err := unix.Madvise(b, syscall.MADV_RANDOM); err != nil {
|
||||||
|
return fmt.Errorf("madvise: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the original byte slice and convert to a byte array pointer.
|
||||||
|
db.dataref = b
|
||||||
|
db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0]))
|
||||||
|
db.datasz = sz
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// munmap unmaps a DB's data file from memory.
|
||||||
|
func munmap(db *DB) error {
|
||||||
|
// Ignore the unmap if we have no mapped data.
|
||||||
|
if db.dataref == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmap using the original byte slice.
|
||||||
|
err := unix.Munmap(db.dataref)
|
||||||
|
db.dataref = nil
|
||||||
|
db.data = nil
|
||||||
|
db.datasz = 0
|
||||||
|
return err
|
||||||
|
}
|
62
Godeps/_workspace/src/github.com/boltdb/bolt/bolt_windows.go
generated
vendored
62
Godeps/_workspace/src/github.com/boltdb/bolt/bolt_windows.go
generated
vendored
@ -8,7 +8,37 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
var odirect int
|
// LockFileEx code derived from golang build filemutex_windows.go @ v1.5.1
|
||||||
|
var (
|
||||||
|
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
procLockFileEx = modkernel32.NewProc("LockFileEx")
|
||||||
|
procUnlockFileEx = modkernel32.NewProc("UnlockFileEx")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx
|
||||||
|
flagLockExclusive = 2
|
||||||
|
flagLockFailImmediately = 1
|
||||||
|
|
||||||
|
// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
|
||||||
|
errLockViolation syscall.Errno = 0x21
|
||||||
|
)
|
||||||
|
|
||||||
|
func lockFileEx(h syscall.Handle, flags, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) {
|
||||||
|
r, _, err := procLockFileEx.Call(uintptr(h), uintptr(flags), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)))
|
||||||
|
if r == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlockFileEx(h syscall.Handle, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) {
|
||||||
|
r, _, err := procUnlockFileEx.Call(uintptr(h), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)), 0)
|
||||||
|
if r == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// fdatasync flushes written data to a file descriptor.
|
// fdatasync flushes written data to a file descriptor.
|
||||||
func fdatasync(db *DB) error {
|
func fdatasync(db *DB) error {
|
||||||
@ -16,22 +46,48 @@ func fdatasync(db *DB) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// flock acquires an advisory lock on a file descriptor.
|
// flock acquires an advisory lock on a file descriptor.
|
||||||
func flock(f *os.File, _ time.Duration) error {
|
func flock(f *os.File, exclusive bool, timeout time.Duration) error {
|
||||||
|
var t time.Time
|
||||||
|
for {
|
||||||
|
// If we're beyond our timeout then return an error.
|
||||||
|
// This can only occur after we've attempted a flock once.
|
||||||
|
if t.IsZero() {
|
||||||
|
t = time.Now()
|
||||||
|
} else if timeout > 0 && time.Since(t) > timeout {
|
||||||
|
return ErrTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
var flag uint32 = flagLockFailImmediately
|
||||||
|
if exclusive {
|
||||||
|
flag |= flagLockExclusive
|
||||||
|
}
|
||||||
|
|
||||||
|
err := lockFileEx(syscall.Handle(f.Fd()), flag, 0, 1, 0, &syscall.Overlapped{})
|
||||||
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
|
} else if err != errLockViolation {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for a bit and try again.
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// funlock releases an advisory lock on a file descriptor.
|
// funlock releases an advisory lock on a file descriptor.
|
||||||
func funlock(f *os.File) error {
|
func funlock(f *os.File) error {
|
||||||
return nil
|
return unlockFileEx(syscall.Handle(f.Fd()), 0, 1, 0, &syscall.Overlapped{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// mmap memory maps a DB's data file.
|
// mmap memory maps a DB's data file.
|
||||||
// Based on: https://github.com/edsrzf/mmap-go
|
// Based on: https://github.com/edsrzf/mmap-go
|
||||||
func mmap(db *DB, sz int) error {
|
func mmap(db *DB, sz int) error {
|
||||||
|
if !db.readOnly {
|
||||||
// Truncate the database to the size of the mmap.
|
// Truncate the database to the size of the mmap.
|
||||||
if err := db.file.Truncate(int64(sz)); err != nil {
|
if err := db.file.Truncate(int64(sz)); err != nil {
|
||||||
return fmt.Errorf("truncate: %s", err)
|
return fmt.Errorf("truncate: %s", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Open a file mapping handle.
|
// Open a file mapping handle.
|
||||||
sizelo := uint32(sz >> 32)
|
sizelo := uint32(sz >> 32)
|
||||||
|
2
Godeps/_workspace/src/github.com/boltdb/bolt/boltsync_unix.go
generated
vendored
2
Godeps/_workspace/src/github.com/boltdb/bolt/boltsync_unix.go
generated
vendored
@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
package bolt
|
package bolt
|
||||||
|
|
||||||
var odirect int
|
|
||||||
|
|
||||||
// fdatasync flushes written data to a file descriptor.
|
// fdatasync flushes written data to a file descriptor.
|
||||||
func fdatasync(db *DB) error {
|
func fdatasync(db *DB) error {
|
||||||
return db.file.Sync()
|
return db.file.Sync()
|
||||||
|
10
Godeps/_workspace/src/github.com/boltdb/bolt/bucket.go
generated
vendored
10
Godeps/_workspace/src/github.com/boltdb/bolt/bucket.go
generated
vendored
@ -11,7 +11,7 @@ const (
|
|||||||
MaxKeySize = 32768
|
MaxKeySize = 32768
|
||||||
|
|
||||||
// MaxValueSize is the maximum length of a value, in bytes.
|
// MaxValueSize is the maximum length of a value, in bytes.
|
||||||
MaxValueSize = 4294967295
|
MaxValueSize = (1 << 31) - 2
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -99,6 +99,7 @@ func (b *Bucket) Cursor() *Cursor {
|
|||||||
|
|
||||||
// Bucket retrieves a nested bucket by name.
|
// Bucket retrieves a nested bucket by name.
|
||||||
// Returns nil if the bucket does not exist.
|
// Returns nil if the bucket does not exist.
|
||||||
|
// The bucket instance is only valid for the lifetime of the transaction.
|
||||||
func (b *Bucket) Bucket(name []byte) *Bucket {
|
func (b *Bucket) Bucket(name []byte) *Bucket {
|
||||||
if b.buckets != nil {
|
if b.buckets != nil {
|
||||||
if child := b.buckets[string(name)]; child != nil {
|
if child := b.buckets[string(name)]; child != nil {
|
||||||
@ -148,6 +149,7 @@ func (b *Bucket) openBucket(value []byte) *Bucket {
|
|||||||
|
|
||||||
// CreateBucket creates a new bucket at the given key and returns the new bucket.
|
// CreateBucket creates a new bucket at the given key and returns the new bucket.
|
||||||
// Returns an error if the key already exists, if the bucket name is blank, or if the bucket name is too long.
|
// Returns an error if the key already exists, if the bucket name is blank, or if the bucket name is too long.
|
||||||
|
// The bucket instance is only valid for the lifetime of the transaction.
|
||||||
func (b *Bucket) CreateBucket(key []byte) (*Bucket, error) {
|
func (b *Bucket) CreateBucket(key []byte) (*Bucket, error) {
|
||||||
if b.tx.db == nil {
|
if b.tx.db == nil {
|
||||||
return nil, ErrTxClosed
|
return nil, ErrTxClosed
|
||||||
@ -192,6 +194,7 @@ func (b *Bucket) CreateBucket(key []byte) (*Bucket, error) {
|
|||||||
|
|
||||||
// CreateBucketIfNotExists creates a new bucket if it doesn't already exist and returns a reference to it.
|
// CreateBucketIfNotExists creates a new bucket if it doesn't already exist and returns a reference to it.
|
||||||
// Returns an error if the bucket name is blank, or if the bucket name is too long.
|
// Returns an error if the bucket name is blank, or if the bucket name is too long.
|
||||||
|
// The bucket instance is only valid for the lifetime of the transaction.
|
||||||
func (b *Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error) {
|
func (b *Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error) {
|
||||||
child, err := b.CreateBucket(key)
|
child, err := b.CreateBucket(key)
|
||||||
if err == ErrBucketExists {
|
if err == ErrBucketExists {
|
||||||
@ -252,6 +255,7 @@ func (b *Bucket) DeleteBucket(key []byte) error {
|
|||||||
|
|
||||||
// Get retrieves the value for a key in the bucket.
|
// Get retrieves the value for a key in the bucket.
|
||||||
// Returns a nil value if the key does not exist or if the key is a nested bucket.
|
// Returns a nil value if the key does not exist or if the key is a nested bucket.
|
||||||
|
// The returned value is only valid for the life of the transaction.
|
||||||
func (b *Bucket) Get(key []byte) []byte {
|
func (b *Bucket) Get(key []byte) []byte {
|
||||||
k, v, flags := b.Cursor().seek(key)
|
k, v, flags := b.Cursor().seek(key)
|
||||||
|
|
||||||
@ -269,6 +273,7 @@ func (b *Bucket) Get(key []byte) []byte {
|
|||||||
|
|
||||||
// Put sets the value for a key in the bucket.
|
// Put sets the value for a key in the bucket.
|
||||||
// If the key exist then its previous value will be overwritten.
|
// If the key exist then its previous value will be overwritten.
|
||||||
|
// Supplied value must remain valid for the life of the transaction.
|
||||||
// Returns an error if the bucket was created from a read-only transaction, if the key is blank, if the key is too large, or if the value is too large.
|
// Returns an error if the bucket was created from a read-only transaction, if the key is blank, if the key is too large, or if the value is too large.
|
||||||
func (b *Bucket) Put(key []byte, value []byte) error {
|
func (b *Bucket) Put(key []byte, value []byte) error {
|
||||||
if b.tx.db == nil {
|
if b.tx.db == nil {
|
||||||
@ -345,7 +350,8 @@ func (b *Bucket) NextSequence() (uint64, error) {
|
|||||||
|
|
||||||
// ForEach executes a function for each key/value pair in a bucket.
|
// ForEach executes a function for each key/value pair in a bucket.
|
||||||
// If the provided function returns an error then the iteration is stopped and
|
// If the provided function returns an error then the iteration is stopped and
|
||||||
// the error is returned to the caller.
|
// the error is returned to the caller. The provided function must not modify
|
||||||
|
// the bucket; this will result in undefined behavior.
|
||||||
func (b *Bucket) ForEach(fn func(k, v []byte) error) error {
|
func (b *Bucket) ForEach(fn func(k, v []byte) error) error {
|
||||||
if b.tx.db == nil {
|
if b.tx.db == nil {
|
||||||
return ErrTxClosed
|
return ErrTxClosed
|
||||||
|
1826
Godeps/_workspace/src/github.com/boltdb/bolt/bucket_test.go
generated
vendored
1826
Godeps/_workspace/src/github.com/boltdb/bolt/bucket_test.go
generated
vendored
File diff suppressed because it is too large
Load Diff
421
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/bench.go
generated
vendored
421
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/bench.go
generated
vendored
@ -1,421 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/rand"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"runtime/pprof"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// File handlers for the various profiles.
|
|
||||||
var cpuprofile, memprofile, blockprofile *os.File
|
|
||||||
|
|
||||||
var benchBucketName = []byte("bench")
|
|
||||||
|
|
||||||
// Bench executes a customizable, synthetic benchmark against Bolt.
|
|
||||||
func Bench(options *BenchOptions) {
|
|
||||||
var results BenchResults
|
|
||||||
|
|
||||||
// Validate options.
|
|
||||||
if options.BatchSize == 0 {
|
|
||||||
options.BatchSize = options.Iterations
|
|
||||||
} else if options.Iterations%options.BatchSize != 0 {
|
|
||||||
fatal("number of iterations must be divisible by the batch size")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find temporary location.
|
|
||||||
path := tempfile()
|
|
||||||
|
|
||||||
if options.Clean {
|
|
||||||
defer os.Remove(path)
|
|
||||||
} else {
|
|
||||||
println("work:", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create database.
|
|
||||||
db, err := bolt.Open(path, 0600, nil)
|
|
||||||
if err != nil {
|
|
||||||
fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
db.NoSync = options.NoSync
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
// Enable streaming stats.
|
|
||||||
if options.StatsInterval > 0 {
|
|
||||||
go printStats(db, options.StatsInterval)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start profiling for writes.
|
|
||||||
if options.ProfileMode == "rw" || options.ProfileMode == "w" {
|
|
||||||
benchStartProfiling(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write to the database.
|
|
||||||
if err := benchWrite(db, options, &results); err != nil {
|
|
||||||
fatal("bench: write: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop profiling for writes only.
|
|
||||||
if options.ProfileMode == "w" {
|
|
||||||
benchStopProfiling()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start profiling for reads.
|
|
||||||
if options.ProfileMode == "r" {
|
|
||||||
benchStartProfiling(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read from the database.
|
|
||||||
if err := benchRead(db, options, &results); err != nil {
|
|
||||||
fatal("bench: read: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop profiling for writes only.
|
|
||||||
if options.ProfileMode == "rw" || options.ProfileMode == "r" {
|
|
||||||
benchStopProfiling()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print results.
|
|
||||||
fmt.Fprintf(os.Stderr, "# Write\t%v\t(%v/op)\t(%v op/sec)\n", results.WriteDuration, results.WriteOpDuration(), results.WriteOpsPerSecond())
|
|
||||||
fmt.Fprintf(os.Stderr, "# Read\t%v\t(%v/op)\t(%v op/sec)\n", results.ReadDuration, results.ReadOpDuration(), results.ReadOpsPerSecond())
|
|
||||||
fmt.Fprintln(os.Stderr, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writes to the database.
|
|
||||||
func benchWrite(db *bolt.DB, options *BenchOptions, results *BenchResults) error {
|
|
||||||
var err error
|
|
||||||
var t = time.Now()
|
|
||||||
|
|
||||||
switch options.WriteMode {
|
|
||||||
case "seq":
|
|
||||||
err = benchWriteSequential(db, options, results)
|
|
||||||
case "rnd":
|
|
||||||
err = benchWriteRandom(db, options, results)
|
|
||||||
case "seq-nest":
|
|
||||||
err = benchWriteSequentialNested(db, options, results)
|
|
||||||
case "rnd-nest":
|
|
||||||
err = benchWriteRandomNested(db, options, results)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid write mode: %s", options.WriteMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
results.WriteDuration = time.Since(t)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchWriteSequential(db *bolt.DB, options *BenchOptions, results *BenchResults) error {
|
|
||||||
var i = uint32(0)
|
|
||||||
return benchWriteWithSource(db, options, results, func() uint32 { i++; return i })
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchWriteRandom(db *bolt.DB, options *BenchOptions, results *BenchResults) error {
|
|
||||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
||||||
return benchWriteWithSource(db, options, results, func() uint32 { return r.Uint32() })
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchWriteSequentialNested(db *bolt.DB, options *BenchOptions, results *BenchResults) error {
|
|
||||||
var i = uint32(0)
|
|
||||||
return benchWriteNestedWithSource(db, options, results, func() uint32 { i++; return i })
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchWriteRandomNested(db *bolt.DB, options *BenchOptions, results *BenchResults) error {
|
|
||||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
||||||
return benchWriteNestedWithSource(db, options, results, func() uint32 { return r.Uint32() })
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchWriteWithSource(db *bolt.DB, options *BenchOptions, results *BenchResults, keySource func() uint32) error {
|
|
||||||
results.WriteOps = options.Iterations
|
|
||||||
|
|
||||||
for i := 0; i < options.Iterations; i += options.BatchSize {
|
|
||||||
err := db.Update(func(tx *bolt.Tx) error {
|
|
||||||
b, _ := tx.CreateBucketIfNotExists(benchBucketName)
|
|
||||||
b.FillPercent = options.FillPercent
|
|
||||||
|
|
||||||
for j := 0; j < options.BatchSize; j++ {
|
|
||||||
var key = make([]byte, options.KeySize)
|
|
||||||
var value = make([]byte, options.ValueSize)
|
|
||||||
binary.BigEndian.PutUint32(key, keySource())
|
|
||||||
if err := b.Put(key, value); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchWriteNestedWithSource(db *bolt.DB, options *BenchOptions, results *BenchResults, keySource func() uint32) error {
|
|
||||||
results.WriteOps = options.Iterations
|
|
||||||
|
|
||||||
for i := 0; i < options.Iterations; i += options.BatchSize {
|
|
||||||
err := db.Update(func(tx *bolt.Tx) error {
|
|
||||||
top, _ := tx.CreateBucketIfNotExists(benchBucketName)
|
|
||||||
top.FillPercent = options.FillPercent
|
|
||||||
|
|
||||||
var name = make([]byte, options.KeySize)
|
|
||||||
binary.BigEndian.PutUint32(name, keySource())
|
|
||||||
b, _ := top.CreateBucketIfNotExists(name)
|
|
||||||
b.FillPercent = options.FillPercent
|
|
||||||
|
|
||||||
for j := 0; j < options.BatchSize; j++ {
|
|
||||||
var key = make([]byte, options.KeySize)
|
|
||||||
var value = make([]byte, options.ValueSize)
|
|
||||||
binary.BigEndian.PutUint32(key, keySource())
|
|
||||||
if err := b.Put(key, value); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reads from the database.
|
|
||||||
func benchRead(db *bolt.DB, options *BenchOptions, results *BenchResults) error {
|
|
||||||
var err error
|
|
||||||
var t = time.Now()
|
|
||||||
|
|
||||||
switch options.ReadMode {
|
|
||||||
case "seq":
|
|
||||||
if options.WriteMode == "seq-nest" || options.WriteMode == "rnd-nest" {
|
|
||||||
err = benchReadSequentialNested(db, options, results)
|
|
||||||
} else {
|
|
||||||
err = benchReadSequential(db, options, results)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid read mode: %s", options.ReadMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
results.ReadDuration = time.Since(t)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchReadSequential(db *bolt.DB, options *BenchOptions, results *BenchResults) error {
|
|
||||||
return db.View(func(tx *bolt.Tx) error {
|
|
||||||
var t = time.Now()
|
|
||||||
|
|
||||||
for {
|
|
||||||
c := tx.Bucket(benchBucketName).Cursor()
|
|
||||||
var count int
|
|
||||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
|
||||||
if v == nil {
|
|
||||||
return errors.New("invalid value")
|
|
||||||
}
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.WriteMode == "seq" && count != options.Iterations {
|
|
||||||
return fmt.Errorf("read seq: iter mismatch: expected %d, got %d", options.Iterations, count)
|
|
||||||
}
|
|
||||||
|
|
||||||
results.ReadOps += count
|
|
||||||
|
|
||||||
// Make sure we do this for at least a second.
|
|
||||||
if time.Since(t) >= time.Second {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchReadSequentialNested(db *bolt.DB, options *BenchOptions, results *BenchResults) error {
|
|
||||||
return db.View(func(tx *bolt.Tx) error {
|
|
||||||
var t = time.Now()
|
|
||||||
|
|
||||||
for {
|
|
||||||
var count int
|
|
||||||
var top = tx.Bucket(benchBucketName)
|
|
||||||
top.ForEach(func(name, _ []byte) error {
|
|
||||||
c := top.Bucket(name).Cursor()
|
|
||||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
|
||||||
if v == nil {
|
|
||||||
return errors.New("invalid value")
|
|
||||||
}
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if options.WriteMode == "seq-nest" && count != options.Iterations {
|
|
||||||
return fmt.Errorf("read seq-nest: iter mismatch: expected %d, got %d", options.Iterations, count)
|
|
||||||
}
|
|
||||||
|
|
||||||
results.ReadOps += count
|
|
||||||
|
|
||||||
// Make sure we do this for at least a second.
|
|
||||||
if time.Since(t) >= time.Second {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Starts all profiles set on the options.
|
|
||||||
func benchStartProfiling(options *BenchOptions) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Start CPU profiling.
|
|
||||||
if options.CPUProfile != "" {
|
|
||||||
cpuprofile, err = os.Create(options.CPUProfile)
|
|
||||||
if err != nil {
|
|
||||||
fatalf("bench: could not create cpu profile %q: %v", options.CPUProfile, err)
|
|
||||||
}
|
|
||||||
pprof.StartCPUProfile(cpuprofile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start memory profiling.
|
|
||||||
if options.MemProfile != "" {
|
|
||||||
memprofile, err = os.Create(options.MemProfile)
|
|
||||||
if err != nil {
|
|
||||||
fatalf("bench: could not create memory profile %q: %v", options.MemProfile, err)
|
|
||||||
}
|
|
||||||
runtime.MemProfileRate = 4096
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start fatal profiling.
|
|
||||||
if options.BlockProfile != "" {
|
|
||||||
blockprofile, err = os.Create(options.BlockProfile)
|
|
||||||
if err != nil {
|
|
||||||
fatalf("bench: could not create block profile %q: %v", options.BlockProfile, err)
|
|
||||||
}
|
|
||||||
runtime.SetBlockProfileRate(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stops all profiles.
|
|
||||||
func benchStopProfiling() {
|
|
||||||
if cpuprofile != nil {
|
|
||||||
pprof.StopCPUProfile()
|
|
||||||
cpuprofile.Close()
|
|
||||||
cpuprofile = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if memprofile != nil {
|
|
||||||
pprof.Lookup("heap").WriteTo(memprofile, 0)
|
|
||||||
memprofile.Close()
|
|
||||||
memprofile = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if blockprofile != nil {
|
|
||||||
pprof.Lookup("block").WriteTo(blockprofile, 0)
|
|
||||||
blockprofile.Close()
|
|
||||||
blockprofile = nil
|
|
||||||
runtime.SetBlockProfileRate(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Continuously prints stats on the database at given intervals.
|
|
||||||
func printStats(db *bolt.DB, interval time.Duration) {
|
|
||||||
var prevStats = db.Stats()
|
|
||||||
var encoder = json.NewEncoder(os.Stdout)
|
|
||||||
|
|
||||||
for {
|
|
||||||
// Wait for the stats interval.
|
|
||||||
time.Sleep(interval)
|
|
||||||
|
|
||||||
// Retrieve new stats and find difference from previous iteration.
|
|
||||||
var stats = db.Stats()
|
|
||||||
var diff = stats.Sub(&prevStats)
|
|
||||||
|
|
||||||
// Print as JSON to STDOUT.
|
|
||||||
if err := encoder.Encode(diff); err != nil {
|
|
||||||
fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save stats for next iteration.
|
|
||||||
prevStats = stats
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BenchOptions represents the set of options that can be passed to Bench().
|
|
||||||
type BenchOptions struct {
|
|
||||||
ProfileMode string
|
|
||||||
WriteMode string
|
|
||||||
ReadMode string
|
|
||||||
Iterations int
|
|
||||||
BatchSize int
|
|
||||||
KeySize int
|
|
||||||
ValueSize int
|
|
||||||
CPUProfile string
|
|
||||||
MemProfile string
|
|
||||||
BlockProfile string
|
|
||||||
StatsInterval time.Duration
|
|
||||||
FillPercent float64
|
|
||||||
NoSync bool
|
|
||||||
Clean bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// BenchResults represents the performance results of the benchmark.
|
|
||||||
type BenchResults struct {
|
|
||||||
WriteOps int
|
|
||||||
WriteDuration time.Duration
|
|
||||||
ReadOps int
|
|
||||||
ReadDuration time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the duration for a single write operation.
|
|
||||||
func (r *BenchResults) WriteOpDuration() time.Duration {
|
|
||||||
if r.WriteOps == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return r.WriteDuration / time.Duration(r.WriteOps)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns average number of write operations that can be performed per second.
|
|
||||||
func (r *BenchResults) WriteOpsPerSecond() int {
|
|
||||||
var op = r.WriteOpDuration()
|
|
||||||
if op == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return int(time.Second) / int(op)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the duration for a single read operation.
|
|
||||||
func (r *BenchResults) ReadOpDuration() time.Duration {
|
|
||||||
if r.ReadOps == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return r.ReadDuration / time.Duration(r.ReadOps)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns average number of read operations that can be performed per second.
|
|
||||||
func (r *BenchResults) ReadOpsPerSecond() int {
|
|
||||||
var op = r.ReadOpDuration()
|
|
||||||
if op == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return int(time.Second) / int(op)
|
|
||||||
}
|
|
||||||
|
|
||||||
// tempfile returns a temporary file path.
|
|
||||||
func tempfile() string {
|
|
||||||
f, _ := ioutil.TempFile("", "bolt-bench-")
|
|
||||||
f.Close()
|
|
||||||
os.Remove(f.Name())
|
|
||||||
return f.Name()
|
|
||||||
}
|
|
33
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/buckets.go
generated
vendored
33
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/buckets.go
generated
vendored
@ -1,33 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Buckets prints a list of all buckets.
|
|
||||||
func Buckets(path string) {
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
||||||
fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := bolt.Open(path, 0600, nil)
|
|
||||||
if err != nil {
|
|
||||||
fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
err = db.View(func(tx *bolt.Tx) error {
|
|
||||||
return tx.ForEach(func(name []byte, _ *bolt.Bucket) error {
|
|
||||||
println(string(name))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
31
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/buckets_test.go
generated
vendored
31
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/buckets_test.go
generated
vendored
@ -1,31 +0,0 @@
|
|||||||
package main_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt"
|
|
||||||
. "github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ensure that a list of buckets can be retrieved.
|
|
||||||
func TestBuckets(t *testing.T) {
|
|
||||||
SetTestMode(true)
|
|
||||||
open(func(db *bolt.DB, path string) {
|
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
|
||||||
tx.CreateBucket([]byte("woojits"))
|
|
||||||
tx.CreateBucket([]byte("widgets"))
|
|
||||||
tx.CreateBucket([]byte("whatchits"))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
db.Close()
|
|
||||||
output := run("buckets", path)
|
|
||||||
equals(t, "whatchits\nwidgets\nwoojits", output)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that an error is reported if the database is not found.
|
|
||||||
func TestBucketsDBNotFound(t *testing.T) {
|
|
||||||
SetTestMode(true)
|
|
||||||
output := run("buckets", "no/such/db")
|
|
||||||
equals(t, "stat no/such/db: no such file or directory", output)
|
|
||||||
}
|
|
47
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/check.go
generated
vendored
47
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/check.go
generated
vendored
@ -1,47 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check performs a consistency check on the database and prints any errors found.
|
|
||||||
func Check(path string) {
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
||||||
fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := bolt.Open(path, 0600, nil)
|
|
||||||
if err != nil {
|
|
||||||
fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
// Perform consistency check.
|
|
||||||
_ = db.View(func(tx *bolt.Tx) error {
|
|
||||||
var count int
|
|
||||||
ch := tx.Check()
|
|
||||||
loop:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case err, ok := <-ch:
|
|
||||||
if !ok {
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
println(err)
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print summary of errors.
|
|
||||||
if count > 0 {
|
|
||||||
fatalf("%d errors found", count)
|
|
||||||
} else {
|
|
||||||
println("OK")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
45
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/get.go
generated
vendored
45
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/get.go
generated
vendored
@ -1,45 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Get retrieves the value for a given bucket/key.
|
|
||||||
func Get(path, name, key string) {
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
||||||
fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := bolt.Open(path, 0600, nil)
|
|
||||||
if err != nil {
|
|
||||||
fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
err = db.View(func(tx *bolt.Tx) error {
|
|
||||||
// Find bucket.
|
|
||||||
b := tx.Bucket([]byte(name))
|
|
||||||
if b == nil {
|
|
||||||
fatalf("bucket not found: %s", name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find value for a given key.
|
|
||||||
value := b.Get([]byte(key))
|
|
||||||
if value == nil {
|
|
||||||
fatalf("key not found: %s", key)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
println(string(value))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
54
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/get_test.go
generated
vendored
54
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/get_test.go
generated
vendored
@ -1,54 +0,0 @@
|
|||||||
package main_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt"
|
|
||||||
. "github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ensure that a value can be retrieved from the CLI.
|
|
||||||
func TestGet(t *testing.T) {
|
|
||||||
SetTestMode(true)
|
|
||||||
open(func(db *bolt.DB, path string) {
|
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
|
||||||
tx.CreateBucket([]byte("widgets"))
|
|
||||||
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
db.Close()
|
|
||||||
output := run("get", path, "widgets", "foo")
|
|
||||||
equals(t, "bar", output)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that an error is reported if the database is not found.
|
|
||||||
func TestGetDBNotFound(t *testing.T) {
|
|
||||||
SetTestMode(true)
|
|
||||||
output := run("get", "no/such/db", "widgets", "foo")
|
|
||||||
equals(t, "stat no/such/db: no such file or directory", output)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that an error is reported if the bucket is not found.
|
|
||||||
func TestGetBucketNotFound(t *testing.T) {
|
|
||||||
SetTestMode(true)
|
|
||||||
open(func(db *bolt.DB, path string) {
|
|
||||||
db.Close()
|
|
||||||
output := run("get", path, "widgets", "foo")
|
|
||||||
equals(t, "bucket not found: widgets", output)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that an error is reported if the key is not found.
|
|
||||||
func TestGetKeyNotFound(t *testing.T) {
|
|
||||||
SetTestMode(true)
|
|
||||||
open(func(db *bolt.DB, path string) {
|
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
|
||||||
_, err := tx.CreateBucket([]byte("widgets"))
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
db.Close()
|
|
||||||
output := run("get", path, "widgets", "foo")
|
|
||||||
equals(t, "key not found: foo", output)
|
|
||||||
})
|
|
||||||
}
|
|
26
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/info.go
generated
vendored
26
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/info.go
generated
vendored
@ -1,26 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Info prints basic information about a database.
|
|
||||||
func Info(path string) {
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
||||||
fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := bolt.Open(path, 0600, nil)
|
|
||||||
if err != nil {
|
|
||||||
fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
// Print basic database info.
|
|
||||||
var info = db.Info()
|
|
||||||
printf("Page Size: %d\n", info.PageSize)
|
|
||||||
}
|
|
31
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/info_test.go
generated
vendored
31
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/info_test.go
generated
vendored
@ -1,31 +0,0 @@
|
|||||||
package main_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt"
|
|
||||||
. "github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func // Ensure that a database info can be printed.
|
|
||||||
TestInfo(t *testing.T) {
|
|
||||||
SetTestMode(true)
|
|
||||||
open(func(db *bolt.DB, path string) {
|
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
|
||||||
tx.CreateBucket([]byte("widgets"))
|
|
||||||
b := tx.Bucket([]byte("widgets"))
|
|
||||||
b.Put([]byte("foo"), []byte("0000"))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
db.Close()
|
|
||||||
output := run("info", path)
|
|
||||||
equals(t, `Page Size: 4096`, output)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that an error is reported if the database is not found.
|
|
||||||
func TestInfo_NotFound(t *testing.T) {
|
|
||||||
SetTestMode(true)
|
|
||||||
output := run("info", "no/such/db")
|
|
||||||
equals(t, "stat no/such/db: no such file or directory", output)
|
|
||||||
}
|
|
41
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/keys.go
generated
vendored
41
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/keys.go
generated
vendored
@ -1,41 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Keys retrieves a list of keys for a given bucket.
|
|
||||||
func Keys(path, name string) {
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
||||||
fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := bolt.Open(path, 0600, nil)
|
|
||||||
if err != nil {
|
|
||||||
fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
err = db.View(func(tx *bolt.Tx) error {
|
|
||||||
// Find bucket.
|
|
||||||
b := tx.Bucket([]byte(name))
|
|
||||||
if b == nil {
|
|
||||||
fatalf("bucket not found: %s", name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate over each key.
|
|
||||||
return b.ForEach(func(key, _ []byte) error {
|
|
||||||
println(string(key))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
42
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/keys_test.go
generated
vendored
42
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/keys_test.go
generated
vendored
@ -1,42 +0,0 @@
|
|||||||
package main_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt"
|
|
||||||
. "github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ensure that a list of keys can be retrieved for a given bucket.
|
|
||||||
func TestKeys(t *testing.T) {
|
|
||||||
SetTestMode(true)
|
|
||||||
open(func(db *bolt.DB, path string) {
|
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
|
||||||
tx.CreateBucket([]byte("widgets"))
|
|
||||||
tx.Bucket([]byte("widgets")).Put([]byte("0002"), []byte(""))
|
|
||||||
tx.Bucket([]byte("widgets")).Put([]byte("0001"), []byte(""))
|
|
||||||
tx.Bucket([]byte("widgets")).Put([]byte("0003"), []byte(""))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
db.Close()
|
|
||||||
output := run("keys", path, "widgets")
|
|
||||||
equals(t, "0001\n0002\n0003", output)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that an error is reported if the database is not found.
|
|
||||||
func TestKeysDBNotFound(t *testing.T) {
|
|
||||||
SetTestMode(true)
|
|
||||||
output := run("keys", "no/such/db", "widgets")
|
|
||||||
equals(t, "stat no/such/db: no such file or directory", output)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that an error is reported if the bucket is not found.
|
|
||||||
func TestKeysBucketNotFound(t *testing.T) {
|
|
||||||
SetTestMode(true)
|
|
||||||
open(func(db *bolt.DB, path string) {
|
|
||||||
db.Close()
|
|
||||||
output := run("keys", path, "widgets")
|
|
||||||
equals(t, "bucket not found: widgets", output)
|
|
||||||
})
|
|
||||||
}
|
|
1630
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/main.go
generated
vendored
1630
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/main.go
generated
vendored
File diff suppressed because it is too large
Load Diff
200
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/main_test.go
generated
vendored
200
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/main_test.go
generated
vendored
@ -1,69 +1,185 @@
|
|||||||
package main_test
|
package main_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"bytes"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"strconv"
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt"
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt"
|
||||||
. "github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt"
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// open creates and opens a Bolt database in the temp directory.
|
// Ensure the "info" command can print information about a database.
|
||||||
func open(fn func(*bolt.DB, string)) {
|
func TestInfoCommand_Run(t *testing.T) {
|
||||||
path := tempfile()
|
db := MustOpen(0666, nil)
|
||||||
defer os.RemoveAll(path)
|
db.DB.Close()
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
db, err := bolt.Open(path, 0600, nil)
|
// Run the info command.
|
||||||
|
m := NewMain()
|
||||||
|
if err := m.Run("info", db.Path); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the "stats" command executes correctly with an empty database.
|
||||||
|
func TestStatsCommand_Run_EmptyDatabase(t *testing.T) {
|
||||||
|
// Ignore
|
||||||
|
if os.Getpagesize() != 4096 {
|
||||||
|
t.Skip("system does not use 4KB page size")
|
||||||
|
}
|
||||||
|
|
||||||
|
db := MustOpen(0666, nil)
|
||||||
|
defer db.Close()
|
||||||
|
db.DB.Close()
|
||||||
|
|
||||||
|
// Generate expected result.
|
||||||
|
exp := "Aggregate statistics for 0 buckets\n\n" +
|
||||||
|
"Page count statistics\n" +
|
||||||
|
"\tNumber of logical branch pages: 0\n" +
|
||||||
|
"\tNumber of physical branch overflow pages: 0\n" +
|
||||||
|
"\tNumber of logical leaf pages: 0\n" +
|
||||||
|
"\tNumber of physical leaf overflow pages: 0\n" +
|
||||||
|
"Tree statistics\n" +
|
||||||
|
"\tNumber of keys/value pairs: 0\n" +
|
||||||
|
"\tNumber of levels in B+tree: 0\n" +
|
||||||
|
"Page size utilization\n" +
|
||||||
|
"\tBytes allocated for physical branch pages: 0\n" +
|
||||||
|
"\tBytes actually used for branch data: 0 (0%)\n" +
|
||||||
|
"\tBytes allocated for physical leaf pages: 0\n" +
|
||||||
|
"\tBytes actually used for leaf data: 0 (0%)\n" +
|
||||||
|
"Bucket statistics\n" +
|
||||||
|
"\tTotal number of buckets: 0\n" +
|
||||||
|
"\tTotal number on inlined buckets: 0 (0%)\n" +
|
||||||
|
"\tBytes used for inlined buckets: 0 (0%)\n"
|
||||||
|
|
||||||
|
// Run the command.
|
||||||
|
m := NewMain()
|
||||||
|
if err := m.Run("stats", db.Path); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if m.Stdout.String() != exp {
|
||||||
|
t.Fatalf("unexpected stdout:\n\n%s", m.Stdout.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the "stats" command can execute correctly.
|
||||||
|
func TestStatsCommand_Run(t *testing.T) {
|
||||||
|
// Ignore
|
||||||
|
if os.Getpagesize() != 4096 {
|
||||||
|
t.Skip("system does not use 4KB page size")
|
||||||
|
}
|
||||||
|
|
||||||
|
db := MustOpen(0666, nil)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
|
// Create "foo" bucket.
|
||||||
|
b, err := tx.CreateBucket([]byte("foo"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("db open error: " + err.Error())
|
return err
|
||||||
|
}
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
if err := b.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i))); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
fn(db, path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// run executes a command against the CLI and returns the output.
|
// Create "bar" bucket.
|
||||||
func run(args ...string) string {
|
b, err = tx.CreateBucket([]byte("bar"))
|
||||||
args = append([]string{"bolt"}, args...)
|
if err != nil {
|
||||||
NewApp().Run(args)
|
return err
|
||||||
return strings.TrimSpace(LogBuffer())
|
}
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
if err := b.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// tempfile returns a temporary file path.
|
// Create "baz" bucket.
|
||||||
func tempfile() string {
|
b, err = tx.CreateBucket([]byte("baz"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := b.Put([]byte("key"), []byte("value")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
db.DB.Close()
|
||||||
|
|
||||||
|
// Generate expected result.
|
||||||
|
exp := "Aggregate statistics for 3 buckets\n\n" +
|
||||||
|
"Page count statistics\n" +
|
||||||
|
"\tNumber of logical branch pages: 0\n" +
|
||||||
|
"\tNumber of physical branch overflow pages: 0\n" +
|
||||||
|
"\tNumber of logical leaf pages: 1\n" +
|
||||||
|
"\tNumber of physical leaf overflow pages: 0\n" +
|
||||||
|
"Tree statistics\n" +
|
||||||
|
"\tNumber of keys/value pairs: 111\n" +
|
||||||
|
"\tNumber of levels in B+tree: 1\n" +
|
||||||
|
"Page size utilization\n" +
|
||||||
|
"\tBytes allocated for physical branch pages: 0\n" +
|
||||||
|
"\tBytes actually used for branch data: 0 (0%)\n" +
|
||||||
|
"\tBytes allocated for physical leaf pages: 4096\n" +
|
||||||
|
"\tBytes actually used for leaf data: 1996 (48%)\n" +
|
||||||
|
"Bucket statistics\n" +
|
||||||
|
"\tTotal number of buckets: 3\n" +
|
||||||
|
"\tTotal number on inlined buckets: 2 (66%)\n" +
|
||||||
|
"\tBytes used for inlined buckets: 236 (11%)\n"
|
||||||
|
|
||||||
|
// Run the command.
|
||||||
|
m := NewMain()
|
||||||
|
if err := m.Run("stats", db.Path); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if m.Stdout.String() != exp {
|
||||||
|
t.Fatalf("unexpected stdout:\n\n%s", m.Stdout.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main represents a test wrapper for main.Main that records output.
|
||||||
|
type Main struct {
|
||||||
|
*main.Main
|
||||||
|
Stdin bytes.Buffer
|
||||||
|
Stdout bytes.Buffer
|
||||||
|
Stderr bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMain returns a new instance of Main.
|
||||||
|
func NewMain() *Main {
|
||||||
|
m := &Main{Main: main.NewMain()}
|
||||||
|
m.Main.Stdin = &m.Stdin
|
||||||
|
m.Main.Stdout = &m.Stdout
|
||||||
|
m.Main.Stderr = &m.Stderr
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustOpen creates a Bolt database in a temporary location.
|
||||||
|
func MustOpen(mode os.FileMode, options *bolt.Options) *DB {
|
||||||
|
// Create temporary path.
|
||||||
f, _ := ioutil.TempFile("", "bolt-")
|
f, _ := ioutil.TempFile("", "bolt-")
|
||||||
f.Close()
|
f.Close()
|
||||||
os.Remove(f.Name())
|
os.Remove(f.Name())
|
||||||
return f.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
// assert fails the test if the condition is false.
|
db, err := bolt.Open(f.Name(), mode, options)
|
||||||
func assert(tb testing.TB, condition bool, msg string, v ...interface{}) {
|
|
||||||
if !condition {
|
|
||||||
_, file, line, _ := runtime.Caller(1)
|
|
||||||
fmt.Printf("\033[31m%s:%d: "+msg+"\033[39m\n\n", append([]interface{}{filepath.Base(file), line}, v...)...)
|
|
||||||
tb.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ok fails the test if an err is not nil.
|
|
||||||
func ok(tb testing.TB, err error) {
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_, file, line, _ := runtime.Caller(1)
|
panic(err.Error())
|
||||||
fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error())
|
|
||||||
tb.FailNow()
|
|
||||||
}
|
}
|
||||||
|
return &DB{DB: db, Path: f.Name()}
|
||||||
}
|
}
|
||||||
|
|
||||||
// equals fails the test if exp is not equal to act.
|
// DB is a test wrapper for bolt.DB.
|
||||||
func equals(tb testing.TB, exp, act interface{}) {
|
type DB struct {
|
||||||
if !reflect.DeepEqual(exp, act) {
|
*bolt.DB
|
||||||
_, file, line, _ := runtime.Caller(1)
|
Path string
|
||||||
fmt.Printf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act)
|
|
||||||
tb.FailNow()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close closes and removes the database.
|
||||||
|
func (db *DB) Close() error {
|
||||||
|
defer os.Remove(db.Path)
|
||||||
|
return db.DB.Close()
|
||||||
}
|
}
|
||||||
|
57
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/pages.go
generated
vendored
57
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/pages.go
generated
vendored
@ -1,57 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Pages prints a list of all pages in a database.
|
|
||||||
func Pages(path string) {
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
||||||
fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := bolt.Open(path, 0600, nil)
|
|
||||||
if err != nil {
|
|
||||||
fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
println("ID TYPE ITEMS OVRFLW")
|
|
||||||
println("======== ========== ====== ======")
|
|
||||||
|
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
|
||||||
var id int
|
|
||||||
for {
|
|
||||||
p, err := tx.Page(id)
|
|
||||||
if err != nil {
|
|
||||||
fatalf("page error: %d: %s", id, err)
|
|
||||||
} else if p == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only display count and overflow if this is a non-free page.
|
|
||||||
var count, overflow string
|
|
||||||
if p.Type != "free" {
|
|
||||||
count = strconv.Itoa(p.Count)
|
|
||||||
if p.OverflowCount > 0 {
|
|
||||||
overflow = strconv.Itoa(p.OverflowCount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print table row.
|
|
||||||
printf("%-8d %-10s %-6s %-6s\n", p.ID, p.Type, count, overflow)
|
|
||||||
|
|
||||||
// Move to the next non-overflow page.
|
|
||||||
id += 1
|
|
||||||
if p.Type != "free" {
|
|
||||||
id += p.OverflowCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
77
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/stats.go
generated
vendored
77
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/stats.go
generated
vendored
@ -1,77 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Collect stats for all top level buckets matching the prefix.
|
|
||||||
func Stats(path, prefix string) {
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
||||||
fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := bolt.Open(path, 0600, nil)
|
|
||||||
if err != nil {
|
|
||||||
fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
err = db.View(func(tx *bolt.Tx) error {
|
|
||||||
var s bolt.BucketStats
|
|
||||||
var count int
|
|
||||||
var prefix = []byte(prefix)
|
|
||||||
tx.ForEach(func(name []byte, b *bolt.Bucket) error {
|
|
||||||
if bytes.HasPrefix(name, prefix) {
|
|
||||||
s.Add(b.Stats())
|
|
||||||
count += 1
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
printf("Aggregate statistics for %d buckets\n\n", count)
|
|
||||||
|
|
||||||
println("Page count statistics")
|
|
||||||
printf("\tNumber of logical branch pages: %d\n", s.BranchPageN)
|
|
||||||
printf("\tNumber of physical branch overflow pages: %d\n", s.BranchOverflowN)
|
|
||||||
printf("\tNumber of logical leaf pages: %d\n", s.LeafPageN)
|
|
||||||
printf("\tNumber of physical leaf overflow pages: %d\n", s.LeafOverflowN)
|
|
||||||
|
|
||||||
println("Tree statistics")
|
|
||||||
printf("\tNumber of keys/value pairs: %d\n", s.KeyN)
|
|
||||||
printf("\tNumber of levels in B+tree: %d\n", s.Depth)
|
|
||||||
|
|
||||||
println("Page size utilization")
|
|
||||||
printf("\tBytes allocated for physical branch pages: %d\n", s.BranchAlloc)
|
|
||||||
var percentage int
|
|
||||||
if s.BranchAlloc != 0 {
|
|
||||||
percentage = int(float32(s.BranchInuse) * 100.0 / float32(s.BranchAlloc))
|
|
||||||
}
|
|
||||||
printf("\tBytes actually used for branch data: %d (%d%%)\n", s.BranchInuse, percentage)
|
|
||||||
printf("\tBytes allocated for physical leaf pages: %d\n", s.LeafAlloc)
|
|
||||||
percentage = 0
|
|
||||||
if s.LeafAlloc != 0 {
|
|
||||||
percentage = int(float32(s.LeafInuse) * 100.0 / float32(s.LeafAlloc))
|
|
||||||
}
|
|
||||||
printf("\tBytes actually used for leaf data: %d (%d%%)\n", s.LeafInuse, percentage)
|
|
||||||
|
|
||||||
println("Bucket statistics")
|
|
||||||
printf("\tTotal number of buckets: %d\n", s.BucketN)
|
|
||||||
percentage = int(float32(s.InlineBucketN) * 100.0 / float32(s.BucketN))
|
|
||||||
printf("\tTotal number on inlined buckets: %d (%d%%)\n", s.InlineBucketN, percentage)
|
|
||||||
percentage = 0
|
|
||||||
if s.LeafInuse != 0 {
|
|
||||||
percentage = int(float32(s.InlineBucketInuse) * 100.0 / float32(s.LeafInuse))
|
|
||||||
}
|
|
||||||
printf("\tBytes used for inlined buckets: %d (%d%%)\n", s.InlineBucketInuse, percentage)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
61
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/stats_test.go
generated
vendored
61
Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt/stats_test.go
generated
vendored
@ -1,61 +0,0 @@
|
|||||||
package main_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt"
|
|
||||||
. "github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestStats(t *testing.T) {
|
|
||||||
if os.Getpagesize() != 4096 {
|
|
||||||
t.Skip()
|
|
||||||
}
|
|
||||||
SetTestMode(true)
|
|
||||||
open(func(db *bolt.DB, path string) {
|
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
|
||||||
b, err := tx.CreateBucket([]byte("foo"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
b.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i)))
|
|
||||||
}
|
|
||||||
b, err = tx.CreateBucket([]byte("bar"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for i := 0; i < 100; i++ {
|
|
||||||
b.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i)))
|
|
||||||
}
|
|
||||||
b, err = tx.CreateBucket([]byte("baz"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.Put([]byte("key"), []byte("value"))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
db.Close()
|
|
||||||
output := run("stats", path, "b")
|
|
||||||
equals(t, "Aggregate statistics for 2 buckets\n\n"+
|
|
||||||
"Page count statistics\n"+
|
|
||||||
"\tNumber of logical branch pages: 0\n"+
|
|
||||||
"\tNumber of physical branch overflow pages: 0\n"+
|
|
||||||
"\tNumber of logical leaf pages: 1\n"+
|
|
||||||
"\tNumber of physical leaf overflow pages: 0\n"+
|
|
||||||
"Tree statistics\n"+
|
|
||||||
"\tNumber of keys/value pairs: 101\n"+
|
|
||||||
"\tNumber of levels in B+tree: 1\n"+
|
|
||||||
"Page size utilization\n"+
|
|
||||||
"\tBytes allocated for physical branch pages: 0\n"+
|
|
||||||
"\tBytes actually used for branch data: 0 (0%)\n"+
|
|
||||||
"\tBytes allocated for physical leaf pages: 4096\n"+
|
|
||||||
"\tBytes actually used for leaf data: 1996 (48%)\n"+
|
|
||||||
"Bucket statistics\n"+
|
|
||||||
"\tTotal number of buckets: 2\n"+
|
|
||||||
"\tTotal number on inlined buckets: 1 (50%)\n"+
|
|
||||||
"\tBytes used for inlined buckets: 40 (2%)", output)
|
|
||||||
})
|
|
||||||
}
|
|
23
Godeps/_workspace/src/github.com/boltdb/bolt/cursor.go
generated
vendored
23
Godeps/_workspace/src/github.com/boltdb/bolt/cursor.go
generated
vendored
@ -10,6 +10,8 @@ import (
|
|||||||
// Cursors see nested buckets with value == nil.
|
// Cursors see nested buckets with value == nil.
|
||||||
// Cursors can be obtained from a transaction and are valid as long as the transaction is open.
|
// Cursors can be obtained from a transaction and are valid as long as the transaction is open.
|
||||||
//
|
//
|
||||||
|
// Keys and values returned from the cursor are only valid for the life of the transaction.
|
||||||
|
//
|
||||||
// Changing data while traversing with a cursor may cause it to be invalidated
|
// Changing data while traversing with a cursor may cause it to be invalidated
|
||||||
// and return unexpected keys and/or values. You must reposition your cursor
|
// and return unexpected keys and/or values. You must reposition your cursor
|
||||||
// after mutating data.
|
// after mutating data.
|
||||||
@ -25,12 +27,20 @@ func (c *Cursor) Bucket() *Bucket {
|
|||||||
|
|
||||||
// First moves the cursor to the first item in the bucket and returns its key and value.
|
// First moves the cursor to the first item in the bucket and returns its key and value.
|
||||||
// If the bucket is empty then a nil key and value are returned.
|
// If the bucket is empty then a nil key and value are returned.
|
||||||
|
// The returned key and value are only valid for the life of the transaction.
|
||||||
func (c *Cursor) First() (key []byte, value []byte) {
|
func (c *Cursor) First() (key []byte, value []byte) {
|
||||||
_assert(c.bucket.tx.db != nil, "tx closed")
|
_assert(c.bucket.tx.db != nil, "tx closed")
|
||||||
c.stack = c.stack[:0]
|
c.stack = c.stack[:0]
|
||||||
p, n := c.bucket.pageNode(c.bucket.root)
|
p, n := c.bucket.pageNode(c.bucket.root)
|
||||||
c.stack = append(c.stack, elemRef{page: p, node: n, index: 0})
|
c.stack = append(c.stack, elemRef{page: p, node: n, index: 0})
|
||||||
c.first()
|
c.first()
|
||||||
|
|
||||||
|
// If we land on an empty page then move to the next value.
|
||||||
|
// https://github.com/boltdb/bolt/issues/450
|
||||||
|
if c.stack[len(c.stack)-1].count() == 0 {
|
||||||
|
c.next()
|
||||||
|
}
|
||||||
|
|
||||||
k, v, flags := c.keyValue()
|
k, v, flags := c.keyValue()
|
||||||
if (flags & uint32(bucketLeafFlag)) != 0 {
|
if (flags & uint32(bucketLeafFlag)) != 0 {
|
||||||
return k, nil
|
return k, nil
|
||||||
@ -41,6 +51,7 @@ func (c *Cursor) First() (key []byte, value []byte) {
|
|||||||
|
|
||||||
// Last moves the cursor to the last item in the bucket and returns its key and value.
|
// Last moves the cursor to the last item in the bucket and returns its key and value.
|
||||||
// If the bucket is empty then a nil key and value are returned.
|
// If the bucket is empty then a nil key and value are returned.
|
||||||
|
// The returned key and value are only valid for the life of the transaction.
|
||||||
func (c *Cursor) Last() (key []byte, value []byte) {
|
func (c *Cursor) Last() (key []byte, value []byte) {
|
||||||
_assert(c.bucket.tx.db != nil, "tx closed")
|
_assert(c.bucket.tx.db != nil, "tx closed")
|
||||||
c.stack = c.stack[:0]
|
c.stack = c.stack[:0]
|
||||||
@ -58,6 +69,7 @@ func (c *Cursor) Last() (key []byte, value []byte) {
|
|||||||
|
|
||||||
// Next moves the cursor to the next item in the bucket and returns its key and value.
|
// Next moves the cursor to the next item in the bucket and returns its key and value.
|
||||||
// If the cursor is at the end of the bucket then a nil key and value are returned.
|
// If the cursor is at the end of the bucket then a nil key and value are returned.
|
||||||
|
// The returned key and value are only valid for the life of the transaction.
|
||||||
func (c *Cursor) Next() (key []byte, value []byte) {
|
func (c *Cursor) Next() (key []byte, value []byte) {
|
||||||
_assert(c.bucket.tx.db != nil, "tx closed")
|
_assert(c.bucket.tx.db != nil, "tx closed")
|
||||||
k, v, flags := c.next()
|
k, v, flags := c.next()
|
||||||
@ -69,6 +81,7 @@ func (c *Cursor) Next() (key []byte, value []byte) {
|
|||||||
|
|
||||||
// Prev moves the cursor to the previous item in the bucket and returns its key and value.
|
// Prev moves the cursor to the previous item in the bucket and returns its key and value.
|
||||||
// If the cursor is at the beginning of the bucket then a nil key and value are returned.
|
// If the cursor is at the beginning of the bucket then a nil key and value are returned.
|
||||||
|
// The returned key and value are only valid for the life of the transaction.
|
||||||
func (c *Cursor) Prev() (key []byte, value []byte) {
|
func (c *Cursor) Prev() (key []byte, value []byte) {
|
||||||
_assert(c.bucket.tx.db != nil, "tx closed")
|
_assert(c.bucket.tx.db != nil, "tx closed")
|
||||||
|
|
||||||
@ -100,6 +113,7 @@ func (c *Cursor) Prev() (key []byte, value []byte) {
|
|||||||
// Seek moves the cursor to a given key and returns it.
|
// Seek moves the cursor to a given key and returns it.
|
||||||
// If the key does not exist then the next key is used. If no keys
|
// If the key does not exist then the next key is used. If no keys
|
||||||
// follow, a nil key is returned.
|
// follow, a nil key is returned.
|
||||||
|
// The returned key and value are only valid for the life of the transaction.
|
||||||
func (c *Cursor) Seek(seek []byte) (key []byte, value []byte) {
|
func (c *Cursor) Seek(seek []byte) (key []byte, value []byte) {
|
||||||
k, v, flags := c.seek(seek)
|
k, v, flags := c.seek(seek)
|
||||||
|
|
||||||
@ -202,6 +216,7 @@ func (c *Cursor) last() {
|
|||||||
// next moves to the next leaf element and returns the key and value.
|
// next moves to the next leaf element and returns the key and value.
|
||||||
// If the cursor is at the last leaf element then it stays there and returns nil.
|
// If the cursor is at the last leaf element then it stays there and returns nil.
|
||||||
func (c *Cursor) next() (key []byte, value []byte, flags uint32) {
|
func (c *Cursor) next() (key []byte, value []byte, flags uint32) {
|
||||||
|
for {
|
||||||
// Attempt to move over one element until we're successful.
|
// Attempt to move over one element until we're successful.
|
||||||
// Move up the stack as we hit the end of each page in our stack.
|
// Move up the stack as we hit the end of each page in our stack.
|
||||||
var i int
|
var i int
|
||||||
@ -223,8 +238,16 @@ func (c *Cursor) next() (key []byte, value []byte, flags uint32) {
|
|||||||
// first element of the first leaf page.
|
// first element of the first leaf page.
|
||||||
c.stack = c.stack[:i+1]
|
c.stack = c.stack[:i+1]
|
||||||
c.first()
|
c.first()
|
||||||
|
|
||||||
|
// If this is an empty page then restart and move back up the stack.
|
||||||
|
// https://github.com/boltdb/bolt/issues/450
|
||||||
|
if c.stack[len(c.stack)-1].count() == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
return c.keyValue()
|
return c.keyValue()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// search recursively performs a binary search against a given page/node until it finds a given key.
|
// search recursively performs a binary search against a given page/node until it finds a given key.
|
||||||
func (c *Cursor) search(key []byte, pgid pgid) {
|
func (c *Cursor) search(key []byte, pgid pgid) {
|
||||||
|
738
Godeps/_workspace/src/github.com/boltdb/bolt/cursor_test.go
generated
vendored
738
Godeps/_workspace/src/github.com/boltdb/bolt/cursor_test.go
generated
vendored
@ -4,7 +4,9 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/quick"
|
"testing/quick"
|
||||||
@ -14,100 +16,149 @@ import (
|
|||||||
|
|
||||||
// Ensure that a cursor can return a reference to the bucket that created it.
|
// Ensure that a cursor can return a reference to the bucket that created it.
|
||||||
func TestCursor_Bucket(t *testing.T) {
|
func TestCursor_Bucket(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
b, _ := tx.CreateBucket([]byte("widgets"))
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
c := b.Cursor()
|
if err != nil {
|
||||||
equals(t, b, c.Bucket())
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if cb := b.Cursor().Bucket(); !reflect.DeepEqual(cb, b) {
|
||||||
|
t.Fatal("cursor bucket mismatch")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that a Tx cursor can seek to the appropriate keys.
|
// Ensure that a Tx cursor can seek to the appropriate keys.
|
||||||
func TestCursor_Seek(t *testing.T) {
|
func TestCursor_Seek(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
b, err := tx.CreateBucket([]byte("widgets"))
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
ok(t, err)
|
if err != nil {
|
||||||
ok(t, b.Put([]byte("foo"), []byte("0001")))
|
t.Fatal(err)
|
||||||
ok(t, b.Put([]byte("bar"), []byte("0002")))
|
}
|
||||||
ok(t, b.Put([]byte("baz"), []byte("0003")))
|
if err := b.Put([]byte("foo"), []byte("0001")); err != nil {
|
||||||
_, err = b.CreateBucket([]byte("bkt"))
|
t.Fatal(err)
|
||||||
ok(t, err)
|
}
|
||||||
|
if err := b.Put([]byte("bar"), []byte("0002")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := b.Put([]byte("baz"), []byte("0003")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := b.CreateBucket([]byte("bkt")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
db.View(func(tx *bolt.Tx) error {
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.View(func(tx *bolt.Tx) error {
|
||||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||||
|
|
||||||
// Exact match should go to the key.
|
// Exact match should go to the key.
|
||||||
k, v := c.Seek([]byte("bar"))
|
if k, v := c.Seek([]byte("bar")); !bytes.Equal(k, []byte("bar")) {
|
||||||
equals(t, []byte("bar"), k)
|
t.Fatalf("unexpected key: %v", k)
|
||||||
equals(t, []byte("0002"), v)
|
} else if !bytes.Equal(v, []byte("0002")) {
|
||||||
|
t.Fatalf("unexpected value: %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
// Inexact match should go to the next key.
|
// Inexact match should go to the next key.
|
||||||
k, v = c.Seek([]byte("bas"))
|
if k, v := c.Seek([]byte("bas")); !bytes.Equal(k, []byte("baz")) {
|
||||||
equals(t, []byte("baz"), k)
|
t.Fatalf("unexpected key: %v", k)
|
||||||
equals(t, []byte("0003"), v)
|
} else if !bytes.Equal(v, []byte("0003")) {
|
||||||
|
t.Fatalf("unexpected value: %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
// Low key should go to the first key.
|
// Low key should go to the first key.
|
||||||
k, v = c.Seek([]byte(""))
|
if k, v := c.Seek([]byte("")); !bytes.Equal(k, []byte("bar")) {
|
||||||
equals(t, []byte("bar"), k)
|
t.Fatalf("unexpected key: %v", k)
|
||||||
equals(t, []byte("0002"), v)
|
} else if !bytes.Equal(v, []byte("0002")) {
|
||||||
|
t.Fatalf("unexpected value: %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
// High key should return no key.
|
// High key should return no key.
|
||||||
k, v = c.Seek([]byte("zzz"))
|
if k, v := c.Seek([]byte("zzz")); k != nil {
|
||||||
assert(t, k == nil, "")
|
t.Fatalf("expected nil key: %v", k)
|
||||||
assert(t, v == nil, "")
|
} else if v != nil {
|
||||||
|
t.Fatalf("expected nil value: %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
// Buckets should return their key but no value.
|
// Buckets should return their key but no value.
|
||||||
k, v = c.Seek([]byte("bkt"))
|
if k, v := c.Seek([]byte("bkt")); !bytes.Equal(k, []byte("bkt")) {
|
||||||
equals(t, []byte("bkt"), k)
|
t.Fatalf("unexpected key: %v", k)
|
||||||
assert(t, v == nil, "")
|
} else if v != nil {
|
||||||
|
t.Fatalf("expected nil value: %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCursor_Delete(t *testing.T) {
|
func TestCursor_Delete(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
|
|
||||||
var count = 1000
|
const count = 1000
|
||||||
|
|
||||||
// Insert every other key between 0 and $count.
|
// Insert every other key between 0 and $count.
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
b, _ := tx.CreateBucket([]byte("widgets"))
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
for i := 0; i < count; i += 1 {
|
for i := 0; i < count; i += 1 {
|
||||||
k := make([]byte, 8)
|
k := make([]byte, 8)
|
||||||
binary.BigEndian.PutUint64(k, uint64(i))
|
binary.BigEndian.PutUint64(k, uint64(i))
|
||||||
b.Put(k, make([]byte, 100))
|
if err := b.Put(k, make([]byte, 100)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := b.CreateBucket([]byte("sub")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
b.CreateBucket([]byte("sub"))
|
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||||
bound := make([]byte, 8)
|
bound := make([]byte, 8)
|
||||||
binary.BigEndian.PutUint64(bound, uint64(count/2))
|
binary.BigEndian.PutUint64(bound, uint64(count/2))
|
||||||
for key, _ := c.First(); bytes.Compare(key, bound) < 0; key, _ = c.Next() {
|
for key, _ := c.First(); bytes.Compare(key, bound) < 0; key, _ = c.Next() {
|
||||||
if err := c.Delete(); err != nil {
|
if err := c.Delete(); err != nil {
|
||||||
return err
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.Seek([]byte("sub"))
|
|
||||||
err := c.Delete()
|
|
||||||
equals(t, err, bolt.ErrIncompatibleValue)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
db.View(func(tx *bolt.Tx) error {
|
c.Seek([]byte("sub"))
|
||||||
b := tx.Bucket([]byte("widgets"))
|
if err := c.Delete(); err != bolt.ErrIncompatibleValue {
|
||||||
equals(t, b.Stats().KeyN, count/2+1)
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.View(func(tx *bolt.Tx) error {
|
||||||
|
stats := tx.Bucket([]byte("widgets")).Stats()
|
||||||
|
if stats.KeyN != count/2+1 {
|
||||||
|
t.Fatalf("unexpected KeyN: %d", stats.KeyN)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that a Tx cursor can seek to the appropriate keys when there are a
|
// Ensure that a Tx cursor can seek to the appropriate keys when there are a
|
||||||
@ -116,25 +167,33 @@ func TestCursor_Delete(t *testing.T) {
|
|||||||
//
|
//
|
||||||
// Related: https://github.com/boltdb/bolt/pull/187
|
// Related: https://github.com/boltdb/bolt/pull/187
|
||||||
func TestCursor_Seek_Large(t *testing.T) {
|
func TestCursor_Seek_Large(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
|
|
||||||
var count = 10000
|
var count = 10000
|
||||||
|
|
||||||
// Insert every other key between 0 and $count.
|
// Insert every other key between 0 and $count.
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
b, _ := tx.CreateBucket([]byte("widgets"))
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
for i := 0; i < count; i += 100 {
|
for i := 0; i < count; i += 100 {
|
||||||
for j := i; j < i+100; j += 2 {
|
for j := i; j < i+100; j += 2 {
|
||||||
k := make([]byte, 8)
|
k := make([]byte, 8)
|
||||||
binary.BigEndian.PutUint64(k, uint64(j))
|
binary.BigEndian.PutUint64(k, uint64(j))
|
||||||
b.Put(k, make([]byte, 100))
|
if err := b.Put(k, make([]byte, 100)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
db.View(func(tx *bolt.Tx) error {
|
if err := db.View(func(tx *bolt.Tx) error {
|
||||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
seek := make([]byte, 8)
|
seek := make([]byte, 8)
|
||||||
@ -145,193 +204,359 @@ func TestCursor_Seek_Large(t *testing.T) {
|
|||||||
// The last seek is beyond the end of the the range so
|
// The last seek is beyond the end of the the range so
|
||||||
// it should return nil.
|
// it should return nil.
|
||||||
if i == count-1 {
|
if i == count-1 {
|
||||||
assert(t, k == nil, "")
|
if k != nil {
|
||||||
|
t.Fatal("expected nil key")
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise we should seek to the exact key or the next key.
|
// Otherwise we should seek to the exact key or the next key.
|
||||||
num := binary.BigEndian.Uint64(k)
|
num := binary.BigEndian.Uint64(k)
|
||||||
if i%2 == 0 {
|
if i%2 == 0 {
|
||||||
equals(t, uint64(i), num)
|
if num != uint64(i) {
|
||||||
|
t.Fatalf("unexpected num: %d", num)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
equals(t, uint64(i+1), num)
|
if num != uint64(i+1) {
|
||||||
|
t.Fatalf("unexpected num: %d", num)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that a cursor can iterate over an empty bucket without error.
|
// Ensure that a cursor can iterate over an empty bucket without error.
|
||||||
func TestCursor_EmptyBucket(t *testing.T) {
|
func TestCursor_EmptyBucket(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
_, err := tx.CreateBucket([]byte("widgets"))
|
_, err := tx.CreateBucket([]byte("widgets"))
|
||||||
return err
|
return err
|
||||||
})
|
}); err != nil {
|
||||||
db.View(func(tx *bolt.Tx) error {
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.View(func(tx *bolt.Tx) error {
|
||||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||||
k, v := c.First()
|
k, v := c.First()
|
||||||
assert(t, k == nil, "")
|
if k != nil {
|
||||||
assert(t, v == nil, "")
|
t.Fatalf("unexpected key: %v", k)
|
||||||
|
} else if v != nil {
|
||||||
|
t.Fatalf("unexpected value: %v", v)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that a Tx cursor can reverse iterate over an empty bucket without error.
|
// Ensure that a Tx cursor can reverse iterate over an empty bucket without error.
|
||||||
func TestCursor_EmptyBucketReverse(t *testing.T) {
|
func TestCursor_EmptyBucketReverse(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
|
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
_, err := tx.CreateBucket([]byte("widgets"))
|
_, err := tx.CreateBucket([]byte("widgets"))
|
||||||
return err
|
return err
|
||||||
})
|
}); err != nil {
|
||||||
db.View(func(tx *bolt.Tx) error {
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := db.View(func(tx *bolt.Tx) error {
|
||||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||||
k, v := c.Last()
|
k, v := c.Last()
|
||||||
assert(t, k == nil, "")
|
if k != nil {
|
||||||
assert(t, v == nil, "")
|
t.Fatalf("unexpected key: %v", k)
|
||||||
|
} else if v != nil {
|
||||||
|
t.Fatalf("unexpected value: %v", v)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that a Tx cursor can iterate over a single root with a couple elements.
|
// Ensure that a Tx cursor can iterate over a single root with a couple elements.
|
||||||
func TestCursor_Iterate_Leaf(t *testing.T) {
|
func TestCursor_Iterate_Leaf(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
|
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
tx.CreateBucket([]byte("widgets"))
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
tx.Bucket([]byte("widgets")).Put([]byte("baz"), []byte{})
|
if err != nil {
|
||||||
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte{0})
|
t.Fatal(err)
|
||||||
tx.Bucket([]byte("widgets")).Put([]byte("bar"), []byte{1})
|
}
|
||||||
|
if err := b.Put([]byte("baz"), []byte{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := b.Put([]byte("foo"), []byte{0}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := b.Put([]byte("bar"), []byte{1}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
tx, _ := db.Begin(false)
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
tx, err := db.Begin(false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer func() { _ = tx.Rollback() }()
|
||||||
|
|
||||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||||
|
|
||||||
k, v := c.First()
|
k, v := c.First()
|
||||||
equals(t, string(k), "bar")
|
if !bytes.Equal(k, []byte("bar")) {
|
||||||
equals(t, v, []byte{1})
|
t.Fatalf("unexpected key: %v", k)
|
||||||
|
} else if !bytes.Equal(v, []byte{1}) {
|
||||||
|
t.Fatalf("unexpected value: %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
k, v = c.Next()
|
k, v = c.Next()
|
||||||
equals(t, string(k), "baz")
|
if !bytes.Equal(k, []byte("baz")) {
|
||||||
equals(t, v, []byte{})
|
t.Fatalf("unexpected key: %v", k)
|
||||||
|
} else if !bytes.Equal(v, []byte{}) {
|
||||||
|
t.Fatalf("unexpected value: %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
k, v = c.Next()
|
k, v = c.Next()
|
||||||
equals(t, string(k), "foo")
|
if !bytes.Equal(k, []byte("foo")) {
|
||||||
equals(t, v, []byte{0})
|
t.Fatalf("unexpected key: %v", k)
|
||||||
|
} else if !bytes.Equal(v, []byte{0}) {
|
||||||
|
t.Fatalf("unexpected value: %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
k, v = c.Next()
|
k, v = c.Next()
|
||||||
assert(t, k == nil, "")
|
if k != nil {
|
||||||
assert(t, v == nil, "")
|
t.Fatalf("expected nil key: %v", k)
|
||||||
|
} else if v != nil {
|
||||||
|
t.Fatalf("expected nil value: %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
k, v = c.Next()
|
k, v = c.Next()
|
||||||
assert(t, k == nil, "")
|
if k != nil {
|
||||||
assert(t, v == nil, "")
|
t.Fatalf("expected nil key: %v", k)
|
||||||
|
} else if v != nil {
|
||||||
|
t.Fatalf("expected nil value: %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
tx.Rollback()
|
if err := tx.Rollback(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that a Tx cursor can iterate in reverse over a single root with a couple elements.
|
// Ensure that a Tx cursor can iterate in reverse over a single root with a couple elements.
|
||||||
func TestCursor_LeafRootReverse(t *testing.T) {
|
func TestCursor_LeafRootReverse(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
|
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
tx.CreateBucket([]byte("widgets"))
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
tx.Bucket([]byte("widgets")).Put([]byte("baz"), []byte{})
|
if err != nil {
|
||||||
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte{0})
|
t.Fatal(err)
|
||||||
tx.Bucket([]byte("widgets")).Put([]byte("bar"), []byte{1})
|
}
|
||||||
|
if err := b.Put([]byte("baz"), []byte{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := b.Put([]byte("foo"), []byte{0}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := b.Put([]byte("bar"), []byte{1}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
tx, _ := db.Begin(false)
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
tx, err := db.Begin(false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||||
|
|
||||||
k, v := c.Last()
|
if k, v := c.Last(); !bytes.Equal(k, []byte("foo")) {
|
||||||
equals(t, string(k), "foo")
|
t.Fatalf("unexpected key: %v", k)
|
||||||
equals(t, v, []byte{0})
|
} else if !bytes.Equal(v, []byte{0}) {
|
||||||
|
t.Fatalf("unexpected value: %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
k, v = c.Prev()
|
if k, v := c.Prev(); !bytes.Equal(k, []byte("baz")) {
|
||||||
equals(t, string(k), "baz")
|
t.Fatalf("unexpected key: %v", k)
|
||||||
equals(t, v, []byte{})
|
} else if !bytes.Equal(v, []byte{}) {
|
||||||
|
t.Fatalf("unexpected value: %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
k, v = c.Prev()
|
if k, v := c.Prev(); !bytes.Equal(k, []byte("bar")) {
|
||||||
equals(t, string(k), "bar")
|
t.Fatalf("unexpected key: %v", k)
|
||||||
equals(t, v, []byte{1})
|
} else if !bytes.Equal(v, []byte{1}) {
|
||||||
|
t.Fatalf("unexpected value: %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
k, v = c.Prev()
|
if k, v := c.Prev(); k != nil {
|
||||||
assert(t, k == nil, "")
|
t.Fatalf("expected nil key: %v", k)
|
||||||
assert(t, v == nil, "")
|
} else if v != nil {
|
||||||
|
t.Fatalf("expected nil value: %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
k, v = c.Prev()
|
if k, v := c.Prev(); k != nil {
|
||||||
assert(t, k == nil, "")
|
t.Fatalf("expected nil key: %v", k)
|
||||||
assert(t, v == nil, "")
|
} else if v != nil {
|
||||||
|
t.Fatalf("expected nil value: %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
tx.Rollback()
|
if err := tx.Rollback(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that a Tx cursor can restart from the beginning.
|
// Ensure that a Tx cursor can restart from the beginning.
|
||||||
func TestCursor_Restart(t *testing.T) {
|
func TestCursor_Restart(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
|
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
tx.CreateBucket([]byte("widgets"))
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
tx.Bucket([]byte("widgets")).Put([]byte("bar"), []byte{})
|
if err != nil {
|
||||||
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte{})
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := b.Put([]byte("bar"), []byte{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := b.Put([]byte("foo"), []byte{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
tx, _ := db.Begin(false)
|
tx, err := db.Begin(false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||||
|
|
||||||
k, _ := c.First()
|
if k, _ := c.First(); !bytes.Equal(k, []byte("bar")) {
|
||||||
equals(t, string(k), "bar")
|
t.Fatalf("unexpected key: %v", k)
|
||||||
|
}
|
||||||
|
if k, _ := c.Next(); !bytes.Equal(k, []byte("foo")) {
|
||||||
|
t.Fatalf("unexpected key: %v", k)
|
||||||
|
}
|
||||||
|
|
||||||
k, _ = c.Next()
|
if k, _ := c.First(); !bytes.Equal(k, []byte("bar")) {
|
||||||
equals(t, string(k), "foo")
|
t.Fatalf("unexpected key: %v", k)
|
||||||
|
}
|
||||||
|
if k, _ := c.Next(); !bytes.Equal(k, []byte("foo")) {
|
||||||
|
t.Fatalf("unexpected key: %v", k)
|
||||||
|
}
|
||||||
|
|
||||||
k, _ = c.First()
|
if err := tx.Rollback(); err != nil {
|
||||||
equals(t, string(k), "bar")
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
k, _ = c.Next()
|
// Ensure that a cursor can skip over empty pages that have been deleted.
|
||||||
equals(t, string(k), "foo")
|
func TestCursor_First_EmptyPages(t *testing.T) {
|
||||||
|
db := MustOpenDB()
|
||||||
|
defer db.MustClose()
|
||||||
|
|
||||||
tx.Rollback()
|
// Create 1000 keys in the "widgets" bucket.
|
||||||
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
if err := b.Put(u64tob(uint64(i)), []byte{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete half the keys and then try to iterate.
|
||||||
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
|
b := tx.Bucket([]byte("widgets"))
|
||||||
|
for i := 0; i < 600; i++ {
|
||||||
|
if err := b.Delete(u64tob(uint64(i))); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c := b.Cursor()
|
||||||
|
var n int
|
||||||
|
for k, _ := c.First(); k != nil; k, _ = c.Next() {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
if n != 400 {
|
||||||
|
t.Fatalf("unexpected key count: %d", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that a Tx can iterate over all elements in a bucket.
|
// Ensure that a Tx can iterate over all elements in a bucket.
|
||||||
func TestCursor_QuickCheck(t *testing.T) {
|
func TestCursor_QuickCheck(t *testing.T) {
|
||||||
f := func(items testdata) bool {
|
f := func(items testdata) bool {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
|
|
||||||
// Bulk insert all values.
|
// Bulk insert all values.
|
||||||
tx, _ := db.Begin(true)
|
tx, err := db.Begin(true)
|
||||||
tx.CreateBucket([]byte("widgets"))
|
if err != nil {
|
||||||
b := tx.Bucket([]byte("widgets"))
|
t.Fatal(err)
|
||||||
for _, item := range items {
|
}
|
||||||
ok(t, b.Put(item.Key, item.Value))
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, item := range items {
|
||||||
|
if err := b.Put(item.Key, item.Value); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
ok(t, tx.Commit())
|
|
||||||
|
|
||||||
// Sort test data.
|
// Sort test data.
|
||||||
sort.Sort(items)
|
sort.Sort(items)
|
||||||
|
|
||||||
// Iterate over all items and check consistency.
|
// Iterate over all items and check consistency.
|
||||||
var index = 0
|
var index = 0
|
||||||
tx, _ = db.Begin(false)
|
tx, err = db.Begin(false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||||
for k, v := c.First(); k != nil && index < len(items); k, v = c.Next() {
|
for k, v := c.First(); k != nil && index < len(items); k, v = c.Next() {
|
||||||
equals(t, k, items[index].Key)
|
if !bytes.Equal(k, items[index].Key) {
|
||||||
equals(t, v, items[index].Value)
|
t.Fatalf("unexpected key: %v", k)
|
||||||
|
} else if !bytes.Equal(v, items[index].Value) {
|
||||||
|
t.Fatalf("unexpected value: %v", v)
|
||||||
|
}
|
||||||
index++
|
index++
|
||||||
}
|
}
|
||||||
equals(t, len(items), index)
|
if len(items) != index {
|
||||||
tx.Rollback()
|
t.Fatalf("unexpected item count: %v, expected %v", len(items), index)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Rollback(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -343,32 +568,52 @@ func TestCursor_QuickCheck(t *testing.T) {
|
|||||||
// Ensure that a transaction can iterate over all elements in a bucket in reverse.
|
// Ensure that a transaction can iterate over all elements in a bucket in reverse.
|
||||||
func TestCursor_QuickCheck_Reverse(t *testing.T) {
|
func TestCursor_QuickCheck_Reverse(t *testing.T) {
|
||||||
f := func(items testdata) bool {
|
f := func(items testdata) bool {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
|
|
||||||
// Bulk insert all values.
|
// Bulk insert all values.
|
||||||
tx, _ := db.Begin(true)
|
tx, err := db.Begin(true)
|
||||||
tx.CreateBucket([]byte("widgets"))
|
if err != nil {
|
||||||
b := tx.Bucket([]byte("widgets"))
|
t.Fatal(err)
|
||||||
for _, item := range items {
|
}
|
||||||
ok(t, b.Put(item.Key, item.Value))
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, item := range items {
|
||||||
|
if err := b.Put(item.Key, item.Value); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
ok(t, tx.Commit())
|
|
||||||
|
|
||||||
// Sort test data.
|
// Sort test data.
|
||||||
sort.Sort(revtestdata(items))
|
sort.Sort(revtestdata(items))
|
||||||
|
|
||||||
// Iterate over all items and check consistency.
|
// Iterate over all items and check consistency.
|
||||||
var index = 0
|
var index = 0
|
||||||
tx, _ = db.Begin(false)
|
tx, err = db.Begin(false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||||
for k, v := c.Last(); k != nil && index < len(items); k, v = c.Prev() {
|
for k, v := c.Last(); k != nil && index < len(items); k, v = c.Prev() {
|
||||||
equals(t, k, items[index].Key)
|
if !bytes.Equal(k, items[index].Key) {
|
||||||
equals(t, v, items[index].Value)
|
t.Fatalf("unexpected key: %v", k)
|
||||||
|
} else if !bytes.Equal(v, items[index].Value) {
|
||||||
|
t.Fatalf("unexpected value: %v", v)
|
||||||
|
}
|
||||||
index++
|
index++
|
||||||
}
|
}
|
||||||
equals(t, len(items), index)
|
if len(items) != index {
|
||||||
tx.Rollback()
|
t.Fatalf("unexpected item count: %v, expected %v", len(items), index)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Rollback(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -379,76 +624,114 @@ func TestCursor_QuickCheck_Reverse(t *testing.T) {
|
|||||||
|
|
||||||
// Ensure that a Tx cursor can iterate over subbuckets.
|
// Ensure that a Tx cursor can iterate over subbuckets.
|
||||||
func TestCursor_QuickCheck_BucketsOnly(t *testing.T) {
|
func TestCursor_QuickCheck_BucketsOnly(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
|
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
b, err := tx.CreateBucket([]byte("widgets"))
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
ok(t, err)
|
if err != nil {
|
||||||
_, err = b.CreateBucket([]byte("foo"))
|
t.Fatal(err)
|
||||||
ok(t, err)
|
}
|
||||||
_, err = b.CreateBucket([]byte("bar"))
|
if _, err := b.CreateBucket([]byte("foo")); err != nil {
|
||||||
ok(t, err)
|
t.Fatal(err)
|
||||||
_, err = b.CreateBucket([]byte("baz"))
|
}
|
||||||
ok(t, err)
|
if _, err := b.CreateBucket([]byte("bar")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := b.CreateBucket([]byte("baz")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
db.View(func(tx *bolt.Tx) error {
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.View(func(tx *bolt.Tx) error {
|
||||||
var names []string
|
var names []string
|
||||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||||
names = append(names, string(k))
|
names = append(names, string(k))
|
||||||
assert(t, v == nil, "")
|
if v != nil {
|
||||||
|
t.Fatalf("unexpected value: %v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(names, []string{"bar", "baz", "foo"}) {
|
||||||
|
t.Fatalf("unexpected names: %+v", names)
|
||||||
}
|
}
|
||||||
equals(t, names, []string{"bar", "baz", "foo"})
|
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that a Tx cursor can reverse iterate over subbuckets.
|
// Ensure that a Tx cursor can reverse iterate over subbuckets.
|
||||||
func TestCursor_QuickCheck_BucketsOnly_Reverse(t *testing.T) {
|
func TestCursor_QuickCheck_BucketsOnly_Reverse(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
|
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
b, err := tx.CreateBucket([]byte("widgets"))
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
ok(t, err)
|
if err != nil {
|
||||||
_, err = b.CreateBucket([]byte("foo"))
|
t.Fatal(err)
|
||||||
ok(t, err)
|
}
|
||||||
_, err = b.CreateBucket([]byte("bar"))
|
if _, err := b.CreateBucket([]byte("foo")); err != nil {
|
||||||
ok(t, err)
|
t.Fatal(err)
|
||||||
_, err = b.CreateBucket([]byte("baz"))
|
}
|
||||||
ok(t, err)
|
if _, err := b.CreateBucket([]byte("bar")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := b.CreateBucket([]byte("baz")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
db.View(func(tx *bolt.Tx) error {
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.View(func(tx *bolt.Tx) error {
|
||||||
var names []string
|
var names []string
|
||||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||||
for k, v := c.Last(); k != nil; k, v = c.Prev() {
|
for k, v := c.Last(); k != nil; k, v = c.Prev() {
|
||||||
names = append(names, string(k))
|
names = append(names, string(k))
|
||||||
assert(t, v == nil, "")
|
if v != nil {
|
||||||
|
t.Fatalf("unexpected value: %v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(names, []string{"foo", "baz", "bar"}) {
|
||||||
|
t.Fatalf("unexpected names: %+v", names)
|
||||||
}
|
}
|
||||||
equals(t, names, []string{"foo", "baz", "bar"})
|
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleCursor() {
|
func ExampleCursor() {
|
||||||
// Open the database.
|
// Open the database.
|
||||||
db, _ := bolt.Open(tempfile(), 0666, nil)
|
db, err := bolt.Open(tempfile(), 0666, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
defer os.Remove(db.Path())
|
defer os.Remove(db.Path())
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
// Start a read-write transaction.
|
// Start a read-write transaction.
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
// Create a new bucket.
|
// Create a new bucket.
|
||||||
tx.CreateBucket([]byte("animals"))
|
b, err := tx.CreateBucket([]byte("animals"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Insert data into a bucket.
|
// Insert data into a bucket.
|
||||||
b := tx.Bucket([]byte("animals"))
|
if err := b.Put([]byte("dog"), []byte("fun")); err != nil {
|
||||||
b.Put([]byte("dog"), []byte("fun"))
|
log.Fatal(err)
|
||||||
b.Put([]byte("cat"), []byte("lame"))
|
}
|
||||||
b.Put([]byte("liger"), []byte("awesome"))
|
if err := b.Put([]byte("cat"), []byte("lame")); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := b.Put([]byte("liger"), []byte("awesome")); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Create a cursor for iteration.
|
// Create a cursor for iteration.
|
||||||
c := b.Cursor()
|
c := b.Cursor()
|
||||||
@ -463,7 +746,13 @@ func ExampleCursor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Close(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// A cat is lame.
|
// A cat is lame.
|
||||||
@ -473,20 +762,30 @@ func ExampleCursor() {
|
|||||||
|
|
||||||
func ExampleCursor_reverse() {
|
func ExampleCursor_reverse() {
|
||||||
// Open the database.
|
// Open the database.
|
||||||
db, _ := bolt.Open(tempfile(), 0666, nil)
|
db, err := bolt.Open(tempfile(), 0666, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
defer os.Remove(db.Path())
|
defer os.Remove(db.Path())
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
// Start a read-write transaction.
|
// Start a read-write transaction.
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
// Create a new bucket.
|
// Create a new bucket.
|
||||||
tx.CreateBucket([]byte("animals"))
|
b, err := tx.CreateBucket([]byte("animals"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Insert data into a bucket.
|
// Insert data into a bucket.
|
||||||
b := tx.Bucket([]byte("animals"))
|
if err := b.Put([]byte("dog"), []byte("fun")); err != nil {
|
||||||
b.Put([]byte("dog"), []byte("fun"))
|
log.Fatal(err)
|
||||||
b.Put([]byte("cat"), []byte("lame"))
|
}
|
||||||
b.Put([]byte("liger"), []byte("awesome"))
|
if err := b.Put([]byte("cat"), []byte("lame")); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := b.Put([]byte("liger"), []byte("awesome")); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Create a cursor for iteration.
|
// Create a cursor for iteration.
|
||||||
c := b.Cursor()
|
c := b.Cursor()
|
||||||
@ -502,7 +801,14 @@ func ExampleCursor_reverse() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the database to release the file lock.
|
||||||
|
if err := db.Close(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// A liger is awesome.
|
// A liger is awesome.
|
||||||
|
317
Godeps/_workspace/src/github.com/boltdb/bolt/db.go
generated
vendored
317
Godeps/_workspace/src/github.com/boltdb/bolt/db.go
generated
vendored
@ -1,8 +1,10 @@
|
|||||||
package bolt
|
package bolt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
@ -24,9 +26,16 @@ const magic uint32 = 0xED0CDAED
|
|||||||
// IgnoreNoSync specifies whether the NoSync field of a DB is ignored when
|
// IgnoreNoSync specifies whether the NoSync field of a DB is ignored when
|
||||||
// syncing changes to a file. This is required as some operating systems,
|
// syncing changes to a file. This is required as some operating systems,
|
||||||
// such as OpenBSD, do not have a unified buffer cache (UBC) and writes
|
// such as OpenBSD, do not have a unified buffer cache (UBC) and writes
|
||||||
// must be synchronzied using the msync(2) syscall.
|
// must be synchronized using the msync(2) syscall.
|
||||||
const IgnoreNoSync = runtime.GOOS == "openbsd"
|
const IgnoreNoSync = runtime.GOOS == "openbsd"
|
||||||
|
|
||||||
|
// Default values if not set in a DB instance.
|
||||||
|
const (
|
||||||
|
DefaultMaxBatchSize int = 1000
|
||||||
|
DefaultMaxBatchDelay = 10 * time.Millisecond
|
||||||
|
DefaultAllocSize = 16 * 1024 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
// DB represents a collection of buckets persisted to a file on disk.
|
// DB represents a collection of buckets persisted to a file on disk.
|
||||||
// All data access is performed through transactions which can be obtained through the DB.
|
// All data access is performed through transactions which can be obtained through the DB.
|
||||||
// All the functions on DB will return a ErrDatabaseNotOpen if accessed before Open() is called.
|
// All the functions on DB will return a ErrDatabaseNotOpen if accessed before Open() is called.
|
||||||
@ -49,11 +58,45 @@ type DB struct {
|
|||||||
// THIS IS UNSAFE. PLEASE USE WITH CAUTION.
|
// THIS IS UNSAFE. PLEASE USE WITH CAUTION.
|
||||||
NoSync bool
|
NoSync bool
|
||||||
|
|
||||||
|
// When true, skips the truncate call when growing the database.
|
||||||
|
// Setting this to true is only safe on non-ext3/ext4 systems.
|
||||||
|
// Skipping truncation avoids preallocation of hard drive space and
|
||||||
|
// bypasses a truncate() and fsync() syscall on remapping.
|
||||||
|
//
|
||||||
|
// https://github.com/boltdb/bolt/issues/284
|
||||||
|
NoGrowSync bool
|
||||||
|
|
||||||
|
// If you want to read the entire database fast, you can set MmapFlag to
|
||||||
|
// syscall.MAP_POPULATE on Linux 2.6.23+ for sequential read-ahead.
|
||||||
|
MmapFlags int
|
||||||
|
|
||||||
|
// MaxBatchSize is the maximum size of a batch. Default value is
|
||||||
|
// copied from DefaultMaxBatchSize in Open.
|
||||||
|
//
|
||||||
|
// If <=0, disables batching.
|
||||||
|
//
|
||||||
|
// Do not change concurrently with calls to Batch.
|
||||||
|
MaxBatchSize int
|
||||||
|
|
||||||
|
// MaxBatchDelay is the maximum delay before a batch starts.
|
||||||
|
// Default value is copied from DefaultMaxBatchDelay in Open.
|
||||||
|
//
|
||||||
|
// If <=0, effectively disables batching.
|
||||||
|
//
|
||||||
|
// Do not change concurrently with calls to Batch.
|
||||||
|
MaxBatchDelay time.Duration
|
||||||
|
|
||||||
|
// AllocSize is the amount of space allocated when the database
|
||||||
|
// needs to create new pages. This is done to amortize the cost
|
||||||
|
// of truncate() and fsync() when growing the data file.
|
||||||
|
AllocSize int
|
||||||
|
|
||||||
path string
|
path string
|
||||||
file *os.File
|
file *os.File
|
||||||
dataref []byte
|
dataref []byte // mmap'ed readonly, write throws SEGV
|
||||||
data *[maxMapSize]byte
|
data *[maxMapSize]byte
|
||||||
datasz int
|
datasz int
|
||||||
|
filesz int // current on disk file size
|
||||||
meta0 *meta
|
meta0 *meta
|
||||||
meta1 *meta
|
meta1 *meta
|
||||||
pageSize int
|
pageSize int
|
||||||
@ -63,6 +106,9 @@ type DB struct {
|
|||||||
freelist *freelist
|
freelist *freelist
|
||||||
stats Stats
|
stats Stats
|
||||||
|
|
||||||
|
batchMu sync.Mutex
|
||||||
|
batch *batch
|
||||||
|
|
||||||
rwlock sync.Mutex // Allows only one writer at a time.
|
rwlock sync.Mutex // Allows only one writer at a time.
|
||||||
metalock sync.Mutex // Protects meta page access.
|
metalock sync.Mutex // Protects meta page access.
|
||||||
mmaplock sync.RWMutex // Protects mmap access during remapping.
|
mmaplock sync.RWMutex // Protects mmap access during remapping.
|
||||||
@ -71,6 +117,10 @@ type DB struct {
|
|||||||
ops struct {
|
ops struct {
|
||||||
writeAt func(b []byte, off int64) (n int, err error)
|
writeAt func(b []byte, off int64) (n int, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read only mode.
|
||||||
|
// When true, Update() and Begin(true) return ErrDatabaseReadOnly immediately.
|
||||||
|
readOnly bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path returns the path to currently open database file.
|
// Path returns the path to currently open database file.
|
||||||
@ -98,20 +148,36 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
|||||||
if options == nil {
|
if options == nil {
|
||||||
options = DefaultOptions
|
options = DefaultOptions
|
||||||
}
|
}
|
||||||
|
db.NoGrowSync = options.NoGrowSync
|
||||||
|
db.MmapFlags = options.MmapFlags
|
||||||
|
|
||||||
|
// Set default values for later DB operations.
|
||||||
|
db.MaxBatchSize = DefaultMaxBatchSize
|
||||||
|
db.MaxBatchDelay = DefaultMaxBatchDelay
|
||||||
|
db.AllocSize = DefaultAllocSize
|
||||||
|
|
||||||
|
flag := os.O_RDWR
|
||||||
|
if options.ReadOnly {
|
||||||
|
flag = os.O_RDONLY
|
||||||
|
db.readOnly = true
|
||||||
|
}
|
||||||
|
|
||||||
// Open data file and separate sync handler for metadata writes.
|
// Open data file and separate sync handler for metadata writes.
|
||||||
db.path = path
|
db.path = path
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if db.file, err = os.OpenFile(db.path, os.O_RDWR|os.O_CREATE, mode); err != nil {
|
if db.file, err = os.OpenFile(db.path, flag|os.O_CREATE, mode); err != nil {
|
||||||
_ = db.close()
|
_ = db.close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock file so that other processes using Bolt cannot use the database
|
// Lock file so that other processes using Bolt in read-write mode cannot
|
||||||
// at the same time. This would cause corruption since the two processes
|
// use the database at the same time. This would cause corruption since
|
||||||
// would write meta pages and free pages separately.
|
// the two processes would write meta pages and free pages separately.
|
||||||
if err := flock(db.file, options.Timeout); err != nil {
|
// The database file is locked exclusively (only one process can grab the lock)
|
||||||
|
// if !options.ReadOnly.
|
||||||
|
// The database file is locked using the shared lock (more than one process may
|
||||||
|
// hold a lock at the same time) otherwise (options.ReadOnly is set).
|
||||||
|
if err := flock(db.file, !db.readOnly, options.Timeout); err != nil {
|
||||||
_ = db.close()
|
_ = db.close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -121,7 +187,7 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
|||||||
|
|
||||||
// Initialize the database if it doesn't exist.
|
// Initialize the database if it doesn't exist.
|
||||||
if info, err := db.file.Stat(); err != nil {
|
if info, err := db.file.Stat(); err != nil {
|
||||||
return nil, fmt.Errorf("stat error: %s", err)
|
return nil, err
|
||||||
} else if info.Size() == 0 {
|
} else if info.Size() == 0 {
|
||||||
// Initialize new files with meta pages.
|
// Initialize new files with meta pages.
|
||||||
if err := db.init(); err != nil {
|
if err := db.init(); err != nil {
|
||||||
@ -133,14 +199,14 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
|||||||
if _, err := db.file.ReadAt(buf[:], 0); err == nil {
|
if _, err := db.file.ReadAt(buf[:], 0); err == nil {
|
||||||
m := db.pageInBuffer(buf[:], 0).meta()
|
m := db.pageInBuffer(buf[:], 0).meta()
|
||||||
if err := m.validate(); err != nil {
|
if err := m.validate(); err != nil {
|
||||||
return nil, fmt.Errorf("meta0 error: %s", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
db.pageSize = int(m.pageSize)
|
db.pageSize = int(m.pageSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Memory map the data file.
|
// Memory map the data file.
|
||||||
if err := db.mmap(0); err != nil {
|
if err := db.mmap(options.InitialMmapSize); err != nil {
|
||||||
_ = db.close()
|
_ = db.close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -197,10 +263,10 @@ func (db *DB) mmap(minsz int) error {
|
|||||||
|
|
||||||
// Validate the meta pages.
|
// Validate the meta pages.
|
||||||
if err := db.meta0.validate(); err != nil {
|
if err := db.meta0.validate(); err != nil {
|
||||||
return fmt.Errorf("meta0 error: %s", err)
|
return err
|
||||||
}
|
}
|
||||||
if err := db.meta1.validate(); err != nil {
|
if err := db.meta1.validate(); err != nil {
|
||||||
return fmt.Errorf("meta1 error: %s", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -215,11 +281,11 @@ func (db *DB) munmap() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mmapSize determines the appropriate size for the mmap given the current size
|
// mmapSize determines the appropriate size for the mmap given the current size
|
||||||
// of the database. The minimum size is 4MB and doubles until it reaches 1GB.
|
// of the database. The minimum size is 32KB and doubles until it reaches 1GB.
|
||||||
// Returns an error if the new mmap size is greater than the max allowed.
|
// Returns an error if the new mmap size is greater than the max allowed.
|
||||||
func (db *DB) mmapSize(size int) (int, error) {
|
func (db *DB) mmapSize(size int) (int, error) {
|
||||||
// Double the size from 1MB until 1GB.
|
// Double the size from 32KB until 1GB.
|
||||||
for i := uint(20); i <= 30; i++ {
|
for i := uint(15); i <= 30; i++ {
|
||||||
if size <= 1<<i {
|
if size <= 1<<i {
|
||||||
return 1 << i, nil
|
return 1 << i, nil
|
||||||
}
|
}
|
||||||
@ -300,8 +366,15 @@ func (db *DB) init() error {
|
|||||||
// Close releases all database resources.
|
// Close releases all database resources.
|
||||||
// All transactions must be closed before closing the database.
|
// All transactions must be closed before closing the database.
|
||||||
func (db *DB) Close() error {
|
func (db *DB) Close() error {
|
||||||
|
db.rwlock.Lock()
|
||||||
|
defer db.rwlock.Unlock()
|
||||||
|
|
||||||
db.metalock.Lock()
|
db.metalock.Lock()
|
||||||
defer db.metalock.Unlock()
|
defer db.metalock.Unlock()
|
||||||
|
|
||||||
|
db.mmaplock.RLock()
|
||||||
|
defer db.mmaplock.RUnlock()
|
||||||
|
|
||||||
return db.close()
|
return db.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,8 +394,13 @@ func (db *DB) close() error {
|
|||||||
|
|
||||||
// Close file handles.
|
// Close file handles.
|
||||||
if db.file != nil {
|
if db.file != nil {
|
||||||
|
// No need to unlock read-only file.
|
||||||
|
if !db.readOnly {
|
||||||
// Unlock the file.
|
// Unlock the file.
|
||||||
_ = funlock(db.file)
|
if err := funlock(db.file); err != nil {
|
||||||
|
log.Printf("bolt.Close(): funlock error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Close the file descriptor.
|
// Close the file descriptor.
|
||||||
if err := db.file.Close(); err != nil {
|
if err := db.file.Close(); err != nil {
|
||||||
@ -340,6 +418,15 @@ func (db *DB) close() error {
|
|||||||
// will cause the calls to block and be serialized until the current write
|
// will cause the calls to block and be serialized until the current write
|
||||||
// transaction finishes.
|
// transaction finishes.
|
||||||
//
|
//
|
||||||
|
// Transactions should not be dependent on one another. Opening a read
|
||||||
|
// transaction and a write transaction in the same goroutine can cause the
|
||||||
|
// writer to deadlock because the database periodically needs to re-mmap itself
|
||||||
|
// as it grows and it cannot do that while a read transaction is open.
|
||||||
|
//
|
||||||
|
// If a long running read transaction (for example, a snapshot transaction) is
|
||||||
|
// needed, you might want to set DB.InitialMmapSize to a large enough value
|
||||||
|
// to avoid potential blocking of write transaction.
|
||||||
|
//
|
||||||
// IMPORTANT: You must close read-only transactions after you are finished or
|
// IMPORTANT: You must close read-only transactions after you are finished or
|
||||||
// else the database will not reclaim old pages.
|
// else the database will not reclaim old pages.
|
||||||
func (db *DB) Begin(writable bool) (*Tx, error) {
|
func (db *DB) Begin(writable bool) (*Tx, error) {
|
||||||
@ -388,6 +475,11 @@ func (db *DB) beginTx() (*Tx, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) beginRWTx() (*Tx, error) {
|
func (db *DB) beginRWTx() (*Tx, error) {
|
||||||
|
// If the database was opened with Options.ReadOnly, return an error.
|
||||||
|
if db.readOnly {
|
||||||
|
return nil, ErrDatabaseReadOnly
|
||||||
|
}
|
||||||
|
|
||||||
// Obtain writer lock. This is released by the transaction when it closes.
|
// Obtain writer lock. This is released by the transaction when it closes.
|
||||||
// This enforces only one writer transaction at a time.
|
// This enforces only one writer transaction at a time.
|
||||||
db.rwlock.Lock()
|
db.rwlock.Lock()
|
||||||
@ -518,6 +610,142 @@ func (db *DB) View(fn func(*Tx) error) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Batch calls fn as part of a batch. It behaves similar to Update,
|
||||||
|
// except:
|
||||||
|
//
|
||||||
|
// 1. concurrent Batch calls can be combined into a single Bolt
|
||||||
|
// transaction.
|
||||||
|
//
|
||||||
|
// 2. the function passed to Batch may be called multiple times,
|
||||||
|
// regardless of whether it returns error or not.
|
||||||
|
//
|
||||||
|
// This means that Batch function side effects must be idempotent and
|
||||||
|
// take permanent effect only after a successful return is seen in
|
||||||
|
// caller.
|
||||||
|
//
|
||||||
|
// The maximum batch size and delay can be adjusted with DB.MaxBatchSize
|
||||||
|
// and DB.MaxBatchDelay, respectively.
|
||||||
|
//
|
||||||
|
// Batch is only useful when there are multiple goroutines calling it.
|
||||||
|
func (db *DB) Batch(fn func(*Tx) error) error {
|
||||||
|
errCh := make(chan error, 1)
|
||||||
|
|
||||||
|
db.batchMu.Lock()
|
||||||
|
if (db.batch == nil) || (db.batch != nil && len(db.batch.calls) >= db.MaxBatchSize) {
|
||||||
|
// There is no existing batch, or the existing batch is full; start a new one.
|
||||||
|
db.batch = &batch{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
db.batch.timer = time.AfterFunc(db.MaxBatchDelay, db.batch.trigger)
|
||||||
|
}
|
||||||
|
db.batch.calls = append(db.batch.calls, call{fn: fn, err: errCh})
|
||||||
|
if len(db.batch.calls) >= db.MaxBatchSize {
|
||||||
|
// wake up batch, it's ready to run
|
||||||
|
go db.batch.trigger()
|
||||||
|
}
|
||||||
|
db.batchMu.Unlock()
|
||||||
|
|
||||||
|
err := <-errCh
|
||||||
|
if err == trySolo {
|
||||||
|
err = db.Update(fn)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type call struct {
|
||||||
|
fn func(*Tx) error
|
||||||
|
err chan<- error
|
||||||
|
}
|
||||||
|
|
||||||
|
type batch struct {
|
||||||
|
db *DB
|
||||||
|
timer *time.Timer
|
||||||
|
start sync.Once
|
||||||
|
calls []call
|
||||||
|
}
|
||||||
|
|
||||||
|
// trigger runs the batch if it hasn't already been run.
|
||||||
|
func (b *batch) trigger() {
|
||||||
|
b.start.Do(b.run)
|
||||||
|
}
|
||||||
|
|
||||||
|
// run performs the transactions in the batch and communicates results
|
||||||
|
// back to DB.Batch.
|
||||||
|
func (b *batch) run() {
|
||||||
|
b.db.batchMu.Lock()
|
||||||
|
b.timer.Stop()
|
||||||
|
// Make sure no new work is added to this batch, but don't break
|
||||||
|
// other batches.
|
||||||
|
if b.db.batch == b {
|
||||||
|
b.db.batch = nil
|
||||||
|
}
|
||||||
|
b.db.batchMu.Unlock()
|
||||||
|
|
||||||
|
retry:
|
||||||
|
for len(b.calls) > 0 {
|
||||||
|
var failIdx = -1
|
||||||
|
err := b.db.Update(func(tx *Tx) error {
|
||||||
|
for i, c := range b.calls {
|
||||||
|
if err := safelyCall(c.fn, tx); err != nil {
|
||||||
|
failIdx = i
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if failIdx >= 0 {
|
||||||
|
// take the failing transaction out of the batch. it's
|
||||||
|
// safe to shorten b.calls here because db.batch no longer
|
||||||
|
// points to us, and we hold the mutex anyway.
|
||||||
|
c := b.calls[failIdx]
|
||||||
|
b.calls[failIdx], b.calls = b.calls[len(b.calls)-1], b.calls[:len(b.calls)-1]
|
||||||
|
// tell the submitter re-run it solo, continue with the rest of the batch
|
||||||
|
c.err <- trySolo
|
||||||
|
continue retry
|
||||||
|
}
|
||||||
|
|
||||||
|
// pass success, or bolt internal errors, to all callers
|
||||||
|
for _, c := range b.calls {
|
||||||
|
if c.err != nil {
|
||||||
|
c.err <- err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break retry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// trySolo is a special sentinel error value used for signaling that a
|
||||||
|
// transaction function should be re-run. It should never be seen by
|
||||||
|
// callers.
|
||||||
|
var trySolo = errors.New("batch function returned an error and should be re-run solo")
|
||||||
|
|
||||||
|
type panicked struct {
|
||||||
|
reason interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p panicked) Error() string {
|
||||||
|
if err, ok := p.reason.(error); ok {
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("panic: %v", p.reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
func safelyCall(fn func(*Tx) error, tx *Tx) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if p := recover(); p != nil {
|
||||||
|
err = panicked{p}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return fn(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync executes fdatasync() against the database file handle.
|
||||||
|
//
|
||||||
|
// This is not necessary under normal operation, however, if you use NoSync
|
||||||
|
// then it allows you to force the database file to sync against the disk.
|
||||||
|
func (db *DB) Sync() error { return fdatasync(db) }
|
||||||
|
|
||||||
// Stats retrieves ongoing performance stats for the database.
|
// Stats retrieves ongoing performance stats for the database.
|
||||||
// This is only updated when a transaction closes.
|
// This is only updated when a transaction closes.
|
||||||
func (db *DB) Stats() Stats {
|
func (db *DB) Stats() Stats {
|
||||||
@ -578,18 +806,73 @@ func (db *DB) allocate(count int) (*page, error) {
|
|||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// grow grows the size of the database to the given sz.
|
||||||
|
func (db *DB) grow(sz int) error {
|
||||||
|
// Ignore if the new size is less than available file size.
|
||||||
|
if sz <= db.filesz {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the data is smaller than the alloc size then only allocate what's needed.
|
||||||
|
// Once it goes over the allocation size then allocate in chunks.
|
||||||
|
if db.datasz < db.AllocSize {
|
||||||
|
sz = db.datasz
|
||||||
|
} else {
|
||||||
|
sz += db.AllocSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate and fsync to ensure file size metadata is flushed.
|
||||||
|
// https://github.com/boltdb/bolt/issues/284
|
||||||
|
if !db.NoGrowSync && !db.readOnly {
|
||||||
|
if err := db.file.Truncate(int64(sz)); err != nil {
|
||||||
|
return fmt.Errorf("file resize error: %s", err)
|
||||||
|
}
|
||||||
|
if err := db.file.Sync(); err != nil {
|
||||||
|
return fmt.Errorf("file sync error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
db.filesz = sz
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) IsReadOnly() bool {
|
||||||
|
return db.readOnly
|
||||||
|
}
|
||||||
|
|
||||||
// Options represents the options that can be set when opening a database.
|
// Options represents the options that can be set when opening a database.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
// Timeout is the amount of time to wait to obtain a file lock.
|
// Timeout is the amount of time to wait to obtain a file lock.
|
||||||
// When set to zero it will wait indefinitely. This option is only
|
// When set to zero it will wait indefinitely. This option is only
|
||||||
// available on Darwin and Linux.
|
// available on Darwin and Linux.
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
|
|
||||||
|
// Sets the DB.NoGrowSync flag before memory mapping the file.
|
||||||
|
NoGrowSync bool
|
||||||
|
|
||||||
|
// Open database in read-only mode. Uses flock(..., LOCK_SH |LOCK_NB) to
|
||||||
|
// grab a shared lock (UNIX).
|
||||||
|
ReadOnly bool
|
||||||
|
|
||||||
|
// Sets the DB.MmapFlags flag before memory mapping the file.
|
||||||
|
MmapFlags int
|
||||||
|
|
||||||
|
// InitialMmapSize is the initial mmap size of the database
|
||||||
|
// in bytes. Read transactions won't block write transaction
|
||||||
|
// if the InitialMmapSize is large enough to hold database mmap
|
||||||
|
// size. (See DB.Begin for more information)
|
||||||
|
//
|
||||||
|
// If <=0, the initial map size is 0.
|
||||||
|
// If initialMmapSize is smaller than the previous database size,
|
||||||
|
// it takes no effect.
|
||||||
|
InitialMmapSize int
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultOptions represent the options used if nil options are passed into Open().
|
// DefaultOptions represent the options used if nil options are passed into Open().
|
||||||
// No timeout is used which will cause Bolt to wait indefinitely for a lock.
|
// No timeout is used which will cause Bolt to wait indefinitely for a lock.
|
||||||
var DefaultOptions = &Options{
|
var DefaultOptions = &Options{
|
||||||
Timeout: 0,
|
Timeout: 0,
|
||||||
|
NoGrowSync: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stats represents statistics about the database.
|
// Stats represents statistics about the database.
|
||||||
|
1470
Godeps/_workspace/src/github.com/boltdb/bolt/db_test.go
generated
vendored
1470
Godeps/_workspace/src/github.com/boltdb/bolt/db_test.go
generated
vendored
File diff suppressed because it is too large
Load Diff
4
Godeps/_workspace/src/github.com/boltdb/bolt/errors.go
generated
vendored
4
Godeps/_workspace/src/github.com/boltdb/bolt/errors.go
generated
vendored
@ -36,6 +36,10 @@ var (
|
|||||||
// ErrTxClosed is returned when committing or rolling back a transaction
|
// ErrTxClosed is returned when committing or rolling back a transaction
|
||||||
// that has already been committed or rolled back.
|
// that has already been committed or rolled back.
|
||||||
ErrTxClosed = errors.New("tx closed")
|
ErrTxClosed = errors.New("tx closed")
|
||||||
|
|
||||||
|
// ErrDatabaseReadOnly is returned when a mutating transaction is started on a
|
||||||
|
// read-only database.
|
||||||
|
ErrDatabaseReadOnly = errors.New("database is in read-only mode")
|
||||||
)
|
)
|
||||||
|
|
||||||
// These errors can occur when putting or deleting a value or a bucket.
|
// These errors can occur when putting or deleting a value or a bucket.
|
||||||
|
15
Godeps/_workspace/src/github.com/boltdb/bolt/freelist.go
generated
vendored
15
Godeps/_workspace/src/github.com/boltdb/bolt/freelist.go
generated
vendored
@ -48,15 +48,14 @@ func (f *freelist) pending_count() int {
|
|||||||
|
|
||||||
// all returns a list of all free ids and all pending ids in one sorted list.
|
// all returns a list of all free ids and all pending ids in one sorted list.
|
||||||
func (f *freelist) all() []pgid {
|
func (f *freelist) all() []pgid {
|
||||||
ids := make([]pgid, len(f.ids))
|
m := make(pgids, 0)
|
||||||
copy(ids, f.ids)
|
|
||||||
|
|
||||||
for _, list := range f.pending {
|
for _, list := range f.pending {
|
||||||
ids = append(ids, list...)
|
m = append(m, list...)
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(pgids(ids))
|
sort.Sort(m)
|
||||||
return ids
|
return pgids(f.ids).merge(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// allocate returns the starting page id of a contiguous list of pages of a given size.
|
// allocate returns the starting page id of a contiguous list of pages of a given size.
|
||||||
@ -127,15 +126,17 @@ func (f *freelist) free(txid txid, p *page) {
|
|||||||
|
|
||||||
// release moves all page ids for a transaction id (or older) to the freelist.
|
// release moves all page ids for a transaction id (or older) to the freelist.
|
||||||
func (f *freelist) release(txid txid) {
|
func (f *freelist) release(txid txid) {
|
||||||
|
m := make(pgids, 0)
|
||||||
for tid, ids := range f.pending {
|
for tid, ids := range f.pending {
|
||||||
if tid <= txid {
|
if tid <= txid {
|
||||||
// Move transaction's pending pages to the available freelist.
|
// Move transaction's pending pages to the available freelist.
|
||||||
// Don't remove from the cache since the page is still free.
|
// Don't remove from the cache since the page is still free.
|
||||||
f.ids = append(f.ids, ids...)
|
m = append(m, ids...)
|
||||||
delete(f.pending, tid)
|
delete(f.pending, tid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.Sort(pgids(f.ids))
|
sort.Sort(m)
|
||||||
|
f.ids = pgids(f.ids).merge(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// rollback removes the pages from a given pending tx.
|
// rollback removes the pages from a given pending tx.
|
||||||
|
31
Godeps/_workspace/src/github.com/boltdb/bolt/freelist_test.go
generated
vendored
31
Godeps/_workspace/src/github.com/boltdb/bolt/freelist_test.go
generated
vendored
@ -1,7 +1,9 @@
|
|||||||
package bolt
|
package bolt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/rand"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
@ -115,7 +117,9 @@ func TestFreelist_write(t *testing.T) {
|
|||||||
f.pending[100] = []pgid{28, 11}
|
f.pending[100] = []pgid{28, 11}
|
||||||
f.pending[101] = []pgid{3}
|
f.pending[101] = []pgid{3}
|
||||||
p := (*page)(unsafe.Pointer(&buf[0]))
|
p := (*page)(unsafe.Pointer(&buf[0]))
|
||||||
f.write(p)
|
if err := f.write(p); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Read the page back out.
|
// Read the page back out.
|
||||||
f2 := newFreelist()
|
f2 := newFreelist()
|
||||||
@ -127,3 +131,28 @@ func TestFreelist_write(t *testing.T) {
|
|||||||
t.Fatalf("exp=%v; got=%v", exp, f2.ids)
|
t.Fatalf("exp=%v; got=%v", exp, f2.ids)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Benchmark_FreelistRelease10K(b *testing.B) { benchmark_FreelistRelease(b, 10000) }
|
||||||
|
func Benchmark_FreelistRelease100K(b *testing.B) { benchmark_FreelistRelease(b, 100000) }
|
||||||
|
func Benchmark_FreelistRelease1000K(b *testing.B) { benchmark_FreelistRelease(b, 1000000) }
|
||||||
|
func Benchmark_FreelistRelease10000K(b *testing.B) { benchmark_FreelistRelease(b, 10000000) }
|
||||||
|
|
||||||
|
func benchmark_FreelistRelease(b *testing.B, size int) {
|
||||||
|
ids := randomPgids(size)
|
||||||
|
pending := randomPgids(len(ids) / 400)
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
f := &freelist{ids: ids, pending: map[txid][]pgid{1: pending}}
|
||||||
|
f.release(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomPgids(n int) []pgid {
|
||||||
|
rand.Seed(42)
|
||||||
|
pgids := make(pgids, n)
|
||||||
|
for i := range pgids {
|
||||||
|
pgids[i] = pgid(rand.Int63())
|
||||||
|
}
|
||||||
|
sort.Sort(pgids)
|
||||||
|
return pgids
|
||||||
|
}
|
||||||
|
13
Godeps/_workspace/src/github.com/boltdb/bolt/node.go
generated
vendored
13
Godeps/_workspace/src/github.com/boltdb/bolt/node.go
generated
vendored
@ -221,11 +221,20 @@ func (n *node) write(p *page) {
|
|||||||
_assert(elem.pgid != p.id, "write: circular dependency occurred")
|
_assert(elem.pgid != p.id, "write: circular dependency occurred")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the length of key+value is larger than the max allocation size
|
||||||
|
// then we need to reallocate the byte array pointer.
|
||||||
|
//
|
||||||
|
// See: https://github.com/boltdb/bolt/pull/335
|
||||||
|
klen, vlen := len(item.key), len(item.value)
|
||||||
|
if len(b) < klen+vlen {
|
||||||
|
b = (*[maxAllocSize]byte)(unsafe.Pointer(&b[0]))[:]
|
||||||
|
}
|
||||||
|
|
||||||
// Write data for the element to the end of the page.
|
// Write data for the element to the end of the page.
|
||||||
copy(b[0:], item.key)
|
copy(b[0:], item.key)
|
||||||
b = b[len(item.key):]
|
b = b[klen:]
|
||||||
copy(b[0:], item.value)
|
copy(b[0:], item.value)
|
||||||
b = b[len(item.value):]
|
b = b[vlen:]
|
||||||
}
|
}
|
||||||
|
|
||||||
// DEBUG ONLY: n.dump()
|
// DEBUG ONLY: n.dump()
|
||||||
|
44
Godeps/_workspace/src/github.com/boltdb/bolt/page.go
generated
vendored
44
Godeps/_workspace/src/github.com/boltdb/bolt/page.go
generated
vendored
@ -3,6 +3,7 @@ package bolt
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -96,7 +97,7 @@ type branchPageElement struct {
|
|||||||
// key returns a byte slice of the node key.
|
// key returns a byte slice of the node key.
|
||||||
func (n *branchPageElement) key() []byte {
|
func (n *branchPageElement) key() []byte {
|
||||||
buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
|
buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
|
||||||
return buf[n.pos : n.pos+n.ksize]
|
return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos]))[:n.ksize]
|
||||||
}
|
}
|
||||||
|
|
||||||
// leafPageElement represents a node on a leaf page.
|
// leafPageElement represents a node on a leaf page.
|
||||||
@ -110,13 +111,13 @@ type leafPageElement struct {
|
|||||||
// key returns a byte slice of the node key.
|
// key returns a byte slice of the node key.
|
||||||
func (n *leafPageElement) key() []byte {
|
func (n *leafPageElement) key() []byte {
|
||||||
buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
|
buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
|
||||||
return buf[n.pos : n.pos+n.ksize]
|
return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos]))[:n.ksize]
|
||||||
}
|
}
|
||||||
|
|
||||||
// value returns a byte slice of the node value.
|
// value returns a byte slice of the node value.
|
||||||
func (n *leafPageElement) value() []byte {
|
func (n *leafPageElement) value() []byte {
|
||||||
buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
|
buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
|
||||||
return buf[n.pos+n.ksize : n.pos+n.ksize+n.vsize]
|
return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos+n.ksize]))[:n.vsize]
|
||||||
}
|
}
|
||||||
|
|
||||||
// PageInfo represents human readable information about a page.
|
// PageInfo represents human readable information about a page.
|
||||||
@ -132,3 +133,40 @@ type pgids []pgid
|
|||||||
func (s pgids) Len() int { return len(s) }
|
func (s pgids) Len() int { return len(s) }
|
||||||
func (s pgids) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
func (s pgids) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
func (s pgids) Less(i, j int) bool { return s[i] < s[j] }
|
func (s pgids) Less(i, j int) bool { return s[i] < s[j] }
|
||||||
|
|
||||||
|
// merge returns the sorted union of a and b.
|
||||||
|
func (a pgids) merge(b pgids) pgids {
|
||||||
|
// Return the opposite slice if one is nil.
|
||||||
|
if len(a) == 0 {
|
||||||
|
return b
|
||||||
|
} else if len(b) == 0 {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a list to hold all elements from both lists.
|
||||||
|
merged := make(pgids, 0, len(a)+len(b))
|
||||||
|
|
||||||
|
// Assign lead to the slice with a lower starting value, follow to the higher value.
|
||||||
|
lead, follow := a, b
|
||||||
|
if b[0] < a[0] {
|
||||||
|
lead, follow = b, a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue while there are elements in the lead.
|
||||||
|
for len(lead) > 0 {
|
||||||
|
// Merge largest prefix of lead that is ahead of follow[0].
|
||||||
|
n := sort.Search(len(lead), func(i int) bool { return lead[i] > follow[0] })
|
||||||
|
merged = append(merged, lead[:n]...)
|
||||||
|
if n >= len(lead) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap lead and follow.
|
||||||
|
lead, follow = follow, lead[n:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append what's left in follow.
|
||||||
|
merged = append(merged, follow...)
|
||||||
|
|
||||||
|
return merged
|
||||||
|
}
|
||||||
|
43
Godeps/_workspace/src/github.com/boltdb/bolt/page_test.go
generated
vendored
43
Godeps/_workspace/src/github.com/boltdb/bolt/page_test.go
generated
vendored
@ -1,7 +1,10 @@
|
|||||||
package bolt
|
package bolt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
"testing/quick"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ensure that the page type can be returned in human readable format.
|
// Ensure that the page type can be returned in human readable format.
|
||||||
@ -27,3 +30,43 @@ func TestPage_typ(t *testing.T) {
|
|||||||
func TestPage_dump(t *testing.T) {
|
func TestPage_dump(t *testing.T) {
|
||||||
(&page{id: 256}).hexdump(16)
|
(&page{id: 256}).hexdump(16)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPgids_merge(t *testing.T) {
|
||||||
|
a := pgids{4, 5, 6, 10, 11, 12, 13, 27}
|
||||||
|
b := pgids{1, 3, 8, 9, 25, 30}
|
||||||
|
c := a.merge(b)
|
||||||
|
if !reflect.DeepEqual(c, pgids{1, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 25, 27, 30}) {
|
||||||
|
t.Errorf("mismatch: %v", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
a = pgids{4, 5, 6, 10, 11, 12, 13, 27, 35, 36}
|
||||||
|
b = pgids{8, 9, 25, 30}
|
||||||
|
c = a.merge(b)
|
||||||
|
if !reflect.DeepEqual(c, pgids{4, 5, 6, 8, 9, 10, 11, 12, 13, 25, 27, 30, 35, 36}) {
|
||||||
|
t.Errorf("mismatch: %v", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPgids_merge_quick(t *testing.T) {
|
||||||
|
if err := quick.Check(func(a, b pgids) bool {
|
||||||
|
// Sort incoming lists.
|
||||||
|
sort.Sort(a)
|
||||||
|
sort.Sort(b)
|
||||||
|
|
||||||
|
// Merge the two lists together.
|
||||||
|
got := a.merge(b)
|
||||||
|
|
||||||
|
// The expected value should be the two lists combined and sorted.
|
||||||
|
exp := append(a, b...)
|
||||||
|
sort.Sort(exp)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(exp, got) {
|
||||||
|
t.Errorf("\nexp=%+v\ngot=%+v\n", exp, got)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
12
Godeps/_workspace/src/github.com/boltdb/bolt/simulation_test.go
generated
vendored
12
Godeps/_workspace/src/github.com/boltdb/bolt/simulation_test.go
generated
vendored
@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt"
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSimulate_1op_1p(t *testing.T) { testSimulate(t, 100, 1) }
|
func TestSimulate_1op_1p(t *testing.T) { testSimulate(t, 1, 1) }
|
||||||
func TestSimulate_10op_1p(t *testing.T) { testSimulate(t, 10, 1) }
|
func TestSimulate_10op_1p(t *testing.T) { testSimulate(t, 10, 1) }
|
||||||
func TestSimulate_100op_1p(t *testing.T) { testSimulate(t, 100, 1) }
|
func TestSimulate_100op_1p(t *testing.T) { testSimulate(t, 100, 1) }
|
||||||
func TestSimulate_1000op_1p(t *testing.T) { testSimulate(t, 1000, 1) }
|
func TestSimulate_1000op_1p(t *testing.T) { testSimulate(t, 1000, 1) }
|
||||||
@ -42,8 +42,8 @@ func testSimulate(t *testing.T, threadCount, parallelism int) {
|
|||||||
var versions = make(map[int]*QuickDB)
|
var versions = make(map[int]*QuickDB)
|
||||||
versions[1] = NewQuickDB()
|
versions[1] = NewQuickDB()
|
||||||
|
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
|
|
||||||
var mutex sync.Mutex
|
var mutex sync.Mutex
|
||||||
|
|
||||||
@ -89,10 +89,12 @@ func testSimulate(t *testing.T, threadCount, parallelism int) {
|
|||||||
versions[tx.ID()] = qdb
|
versions[tx.ID()] = qdb
|
||||||
mutex.Unlock()
|
mutex.Unlock()
|
||||||
|
|
||||||
ok(t, tx.Commit())
|
if err := tx.Commit(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
} else {
|
} else {
|
||||||
defer tx.Rollback()
|
defer func() { _ = tx.Rollback() }()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore operation if we don't have data yet.
|
// Ignore operation if we don't have data yet.
|
||||||
|
98
Godeps/_workspace/src/github.com/boltdb/bolt/tx.go
generated
vendored
98
Godeps/_workspace/src/github.com/boltdb/bolt/tx.go
generated
vendored
@ -29,6 +29,14 @@ type Tx struct {
|
|||||||
pages map[pgid]*page
|
pages map[pgid]*page
|
||||||
stats TxStats
|
stats TxStats
|
||||||
commitHandlers []func()
|
commitHandlers []func()
|
||||||
|
|
||||||
|
// WriteFlag specifies the flag for write-related methods like WriteTo().
|
||||||
|
// Tx opens the database file with the specified flag to copy the data.
|
||||||
|
//
|
||||||
|
// By default, the flag is unset, which works well for mostly in-memory
|
||||||
|
// workloads. For databases that are much larger than available RAM,
|
||||||
|
// set the flag to syscall.O_DIRECT to avoid trashing the page cache.
|
||||||
|
WriteFlag int
|
||||||
}
|
}
|
||||||
|
|
||||||
// init initializes the transaction.
|
// init initializes the transaction.
|
||||||
@ -87,18 +95,21 @@ func (tx *Tx) Stats() TxStats {
|
|||||||
|
|
||||||
// Bucket retrieves a bucket by name.
|
// Bucket retrieves a bucket by name.
|
||||||
// Returns nil if the bucket does not exist.
|
// Returns nil if the bucket does not exist.
|
||||||
|
// The bucket instance is only valid for the lifetime of the transaction.
|
||||||
func (tx *Tx) Bucket(name []byte) *Bucket {
|
func (tx *Tx) Bucket(name []byte) *Bucket {
|
||||||
return tx.root.Bucket(name)
|
return tx.root.Bucket(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateBucket creates a new bucket.
|
// CreateBucket creates a new bucket.
|
||||||
// Returns an error if the bucket already exists, if the bucket name is blank, or if the bucket name is too long.
|
// Returns an error if the bucket already exists, if the bucket name is blank, or if the bucket name is too long.
|
||||||
|
// The bucket instance is only valid for the lifetime of the transaction.
|
||||||
func (tx *Tx) CreateBucket(name []byte) (*Bucket, error) {
|
func (tx *Tx) CreateBucket(name []byte) (*Bucket, error) {
|
||||||
return tx.root.CreateBucket(name)
|
return tx.root.CreateBucket(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateBucketIfNotExists creates a new bucket if it doesn't already exist.
|
// CreateBucketIfNotExists creates a new bucket if it doesn't already exist.
|
||||||
// Returns an error if the bucket name is blank, or if the bucket name is too long.
|
// Returns an error if the bucket name is blank, or if the bucket name is too long.
|
||||||
|
// The bucket instance is only valid for the lifetime of the transaction.
|
||||||
func (tx *Tx) CreateBucketIfNotExists(name []byte) (*Bucket, error) {
|
func (tx *Tx) CreateBucketIfNotExists(name []byte) (*Bucket, error) {
|
||||||
return tx.root.CreateBucketIfNotExists(name)
|
return tx.root.CreateBucketIfNotExists(name)
|
||||||
}
|
}
|
||||||
@ -127,7 +138,8 @@ func (tx *Tx) OnCommit(fn func()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Commit writes all changes to disk and updates the meta page.
|
// Commit writes all changes to disk and updates the meta page.
|
||||||
// Returns an error if a disk write error occurs.
|
// Returns an error if a disk write error occurs, or if Commit is
|
||||||
|
// called on a read-only transaction.
|
||||||
func (tx *Tx) Commit() error {
|
func (tx *Tx) Commit() error {
|
||||||
_assert(!tx.managed, "managed tx commit not allowed")
|
_assert(!tx.managed, "managed tx commit not allowed")
|
||||||
if tx.db == nil {
|
if tx.db == nil {
|
||||||
@ -156,6 +168,8 @@ func (tx *Tx) Commit() error {
|
|||||||
// Free the old root bucket.
|
// Free the old root bucket.
|
||||||
tx.meta.root.root = tx.root.root
|
tx.meta.root.root = tx.root.root
|
||||||
|
|
||||||
|
opgid := tx.meta.pgid
|
||||||
|
|
||||||
// Free the freelist and allocate new pages for it. This will overestimate
|
// Free the freelist and allocate new pages for it. This will overestimate
|
||||||
// the size of the freelist but not underestimate the size (which would be bad).
|
// the size of the freelist but not underestimate the size (which would be bad).
|
||||||
tx.db.freelist.free(tx.meta.txid, tx.db.page(tx.meta.freelist))
|
tx.db.freelist.free(tx.meta.txid, tx.db.page(tx.meta.freelist))
|
||||||
@ -170,6 +184,14 @@ func (tx *Tx) Commit() error {
|
|||||||
}
|
}
|
||||||
tx.meta.freelist = p.id
|
tx.meta.freelist = p.id
|
||||||
|
|
||||||
|
// If the high water mark has moved up then attempt to grow the database.
|
||||||
|
if tx.meta.pgid > opgid {
|
||||||
|
if err := tx.db.grow(int(tx.meta.pgid+1) * tx.db.pageSize); err != nil {
|
||||||
|
tx.rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Write dirty pages to disk.
|
// Write dirty pages to disk.
|
||||||
startTime = time.Now()
|
startTime = time.Now()
|
||||||
if err := tx.write(); err != nil {
|
if err := tx.write(); err != nil {
|
||||||
@ -203,7 +225,8 @@ func (tx *Tx) Commit() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rollback closes the transaction and ignores all previous updates.
|
// Rollback closes the transaction and ignores all previous updates. Read-only
|
||||||
|
// transactions must be rolled back and not committed.
|
||||||
func (tx *Tx) Rollback() error {
|
func (tx *Tx) Rollback() error {
|
||||||
_assert(!tx.managed, "managed tx rollback not allowed")
|
_assert(!tx.managed, "managed tx rollback not allowed")
|
||||||
if tx.db == nil {
|
if tx.db == nil {
|
||||||
@ -234,7 +257,8 @@ func (tx *Tx) close() {
|
|||||||
var freelistPendingN = tx.db.freelist.pending_count()
|
var freelistPendingN = tx.db.freelist.pending_count()
|
||||||
var freelistAlloc = tx.db.freelist.size()
|
var freelistAlloc = tx.db.freelist.size()
|
||||||
|
|
||||||
// Remove writer lock.
|
// Remove transaction ref & writer lock.
|
||||||
|
tx.db.rwtx = nil
|
||||||
tx.db.rwlock.Unlock()
|
tx.db.rwlock.Unlock()
|
||||||
|
|
||||||
// Merge statistics.
|
// Merge statistics.
|
||||||
@ -248,41 +272,47 @@ func (tx *Tx) close() {
|
|||||||
} else {
|
} else {
|
||||||
tx.db.removeTx(tx)
|
tx.db.removeTx(tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear all references.
|
||||||
tx.db = nil
|
tx.db = nil
|
||||||
|
tx.meta = nil
|
||||||
|
tx.root = Bucket{tx: tx}
|
||||||
|
tx.pages = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy writes the entire database to a writer.
|
// Copy writes the entire database to a writer.
|
||||||
// A reader transaction is maintained during the copy so it is safe to continue
|
// This function exists for backwards compatibility. Use WriteTo() instead.
|
||||||
// using the database while a copy is in progress.
|
|
||||||
// Copy will write exactly tx.Size() bytes into the writer.
|
|
||||||
func (tx *Tx) Copy(w io.Writer) error {
|
func (tx *Tx) Copy(w io.Writer) error {
|
||||||
var f *os.File
|
_, err := tx.WriteTo(w)
|
||||||
var err error
|
|
||||||
|
|
||||||
// Attempt to open reader directly.
|
|
||||||
if f, err = os.OpenFile(tx.db.path, os.O_RDONLY|odirect, 0); err != nil {
|
|
||||||
// Fallback to a regular open if that doesn't work.
|
|
||||||
if f, err = os.OpenFile(tx.db.path, os.O_RDONLY, 0); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteTo writes the entire database to a writer.
|
||||||
|
// If err == nil then exactly tx.Size() bytes will be written into the writer.
|
||||||
|
func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
// Attempt to open reader with WriteFlag
|
||||||
|
f, err := os.OpenFile(tx.db.path, os.O_RDONLY|tx.WriteFlag, 0)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
|
defer func() { _ = f.Close() }()
|
||||||
|
|
||||||
// Copy the meta pages.
|
// Copy the meta pages.
|
||||||
tx.db.metalock.Lock()
|
tx.db.metalock.Lock()
|
||||||
_, err = io.CopyN(w, f, int64(tx.db.pageSize*2))
|
n, err = io.CopyN(w, f, int64(tx.db.pageSize*2))
|
||||||
tx.db.metalock.Unlock()
|
tx.db.metalock.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = f.Close()
|
return n, fmt.Errorf("meta copy: %s", err)
|
||||||
return fmt.Errorf("meta copy: %s", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy data pages.
|
// Copy data pages.
|
||||||
if _, err := io.CopyN(w, f, tx.Size()-int64(tx.db.pageSize*2)); err != nil {
|
wn, err := io.CopyN(w, f, tx.Size()-int64(tx.db.pageSize*2))
|
||||||
_ = f.Close()
|
n += wn
|
||||||
return err
|
if err != nil {
|
||||||
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return f.Close()
|
return n, f.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyFile copies the entire database to file at the given path.
|
// CopyFile copies the entire database to file at the given path.
|
||||||
@ -416,15 +446,39 @@ func (tx *Tx) write() error {
|
|||||||
// Write pages to disk in order.
|
// Write pages to disk in order.
|
||||||
for _, p := range pages {
|
for _, p := range pages {
|
||||||
size := (int(p.overflow) + 1) * tx.db.pageSize
|
size := (int(p.overflow) + 1) * tx.db.pageSize
|
||||||
buf := (*[maxAllocSize]byte)(unsafe.Pointer(p))[:size]
|
|
||||||
offset := int64(p.id) * int64(tx.db.pageSize)
|
offset := int64(p.id) * int64(tx.db.pageSize)
|
||||||
|
|
||||||
|
// Write out page in "max allocation" sized chunks.
|
||||||
|
ptr := (*[maxAllocSize]byte)(unsafe.Pointer(p))
|
||||||
|
for {
|
||||||
|
// Limit our write to our max allocation size.
|
||||||
|
sz := size
|
||||||
|
if sz > maxAllocSize-1 {
|
||||||
|
sz = maxAllocSize - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write chunk to disk.
|
||||||
|
buf := ptr[:sz]
|
||||||
if _, err := tx.db.ops.writeAt(buf, offset); err != nil {
|
if _, err := tx.db.ops.writeAt(buf, offset); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update statistics.
|
// Update statistics.
|
||||||
tx.stats.Write++
|
tx.stats.Write++
|
||||||
|
|
||||||
|
// Exit inner for loop if we've written all the chunks.
|
||||||
|
size -= sz
|
||||||
|
if size == 0 {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Otherwise move offset forward and move pointer to next chunk.
|
||||||
|
offset += int64(sz)
|
||||||
|
ptr = (*[maxAllocSize]byte)(unsafe.Pointer(&ptr[sz]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore file sync if flag is set on DB.
|
||||||
if !tx.db.NoSync || IgnoreNoSync {
|
if !tx.db.NoSync || IgnoreNoSync {
|
||||||
if err := fdatasync(tx.db); err != nil {
|
if err := fdatasync(tx.db); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -461,7 +515,7 @@ func (tx *Tx) writeMeta() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// page returns a reference to the page with a given id.
|
// page returns a reference to the page with a given id.
|
||||||
// If page has been written to then a temporary bufferred page is returned.
|
// If page has been written to then a temporary buffered page is returned.
|
||||||
func (tx *Tx) page(id pgid) *page {
|
func (tx *Tx) page(id pgid) *page {
|
||||||
// Check the dirty pages first.
|
// Check the dirty pages first.
|
||||||
if tx.pages != nil {
|
if tx.pages != nil {
|
||||||
|
728
Godeps/_workspace/src/github.com/boltdb/bolt/tx_test.go
generated
vendored
728
Godeps/_workspace/src/github.com/boltdb/bolt/tx_test.go
generated
vendored
@ -1,8 +1,10 @@
|
|||||||
package bolt_test
|
package bolt_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -10,299 +12,519 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Ensure that committing a closed transaction returns an error.
|
// Ensure that committing a closed transaction returns an error.
|
||||||
func TestTx_Commit_Closed(t *testing.T) {
|
func TestTx_Commit_ErrTxClosed(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
tx, _ := db.Begin(true)
|
tx, err := db.Begin(true)
|
||||||
tx.CreateBucket([]byte("foo"))
|
if err != nil {
|
||||||
ok(t, tx.Commit())
|
t.Fatal(err)
|
||||||
equals(t, tx.Commit(), bolt.ErrTxClosed)
|
}
|
||||||
|
|
||||||
|
if _, err := tx.CreateBucket([]byte("foo")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(); err != bolt.ErrTxClosed {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that rolling back a closed transaction returns an error.
|
// Ensure that rolling back a closed transaction returns an error.
|
||||||
func TestTx_Rollback_Closed(t *testing.T) {
|
func TestTx_Rollback_ErrTxClosed(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
tx, _ := db.Begin(true)
|
|
||||||
ok(t, tx.Rollback())
|
tx, err := db.Begin(true)
|
||||||
equals(t, tx.Rollback(), bolt.ErrTxClosed)
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Rollback(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := tx.Rollback(); err != bolt.ErrTxClosed {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that committing a read-only transaction returns an error.
|
// Ensure that committing a read-only transaction returns an error.
|
||||||
func TestTx_Commit_ReadOnly(t *testing.T) {
|
func TestTx_Commit_ErrTxNotWritable(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
tx, _ := db.Begin(false)
|
tx, err := db.Begin(false)
|
||||||
equals(t, tx.Commit(), bolt.ErrTxNotWritable)
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := tx.Commit(); err != bolt.ErrTxNotWritable {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that a transaction can retrieve a cursor on the root bucket.
|
// Ensure that a transaction can retrieve a cursor on the root bucket.
|
||||||
func TestTx_Cursor(t *testing.T) {
|
func TestTx_Cursor(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
tx.CreateBucket([]byte("widgets"))
|
if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
|
||||||
tx.CreateBucket([]byte("woojits"))
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := tx.CreateBucket([]byte("woojits")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
c := tx.Cursor()
|
c := tx.Cursor()
|
||||||
|
if k, v := c.First(); !bytes.Equal(k, []byte("widgets")) {
|
||||||
|
t.Fatalf("unexpected key: %v", k)
|
||||||
|
} else if v != nil {
|
||||||
|
t.Fatalf("unexpected value: %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
k, v := c.First()
|
if k, v := c.Next(); !bytes.Equal(k, []byte("woojits")) {
|
||||||
equals(t, "widgets", string(k))
|
t.Fatalf("unexpected key: %v", k)
|
||||||
assert(t, v == nil, "")
|
} else if v != nil {
|
||||||
|
t.Fatalf("unexpected value: %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
k, v = c.Next()
|
if k, v := c.Next(); k != nil {
|
||||||
equals(t, "woojits", string(k))
|
t.Fatalf("unexpected key: %v", k)
|
||||||
assert(t, v == nil, "")
|
} else if v != nil {
|
||||||
|
t.Fatalf("unexpected value: %v", k)
|
||||||
k, v = c.Next()
|
}
|
||||||
assert(t, k == nil, "")
|
|
||||||
assert(t, v == nil, "")
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that creating a bucket with a read-only transaction returns an error.
|
// Ensure that creating a bucket with a read-only transaction returns an error.
|
||||||
func TestTx_CreateBucket_ReadOnly(t *testing.T) {
|
func TestTx_CreateBucket_ErrTxNotWritable(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
db.View(func(tx *bolt.Tx) error {
|
if err := db.View(func(tx *bolt.Tx) error {
|
||||||
b, err := tx.CreateBucket([]byte("foo"))
|
_, err := tx.CreateBucket([]byte("foo"))
|
||||||
assert(t, b == nil, "")
|
if err != bolt.ErrTxNotWritable {
|
||||||
equals(t, bolt.ErrTxNotWritable, err)
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that creating a bucket on a closed transaction returns an error.
|
// Ensure that creating a bucket on a closed transaction returns an error.
|
||||||
func TestTx_CreateBucket_Closed(t *testing.T) {
|
func TestTx_CreateBucket_ErrTxClosed(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
tx, _ := db.Begin(true)
|
tx, err := db.Begin(true)
|
||||||
tx.Commit()
|
if err != nil {
|
||||||
b, err := tx.CreateBucket([]byte("foo"))
|
t.Fatal(err)
|
||||||
assert(t, b == nil, "")
|
}
|
||||||
equals(t, bolt.ErrTxClosed, err)
|
if err := tx.Commit(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := tx.CreateBucket([]byte("foo")); err != bolt.ErrTxClosed {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that a Tx can retrieve a bucket.
|
// Ensure that a Tx can retrieve a bucket.
|
||||||
func TestTx_Bucket(t *testing.T) {
|
func TestTx_Bucket(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
tx.CreateBucket([]byte("widgets"))
|
if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
|
||||||
b := tx.Bucket([]byte("widgets"))
|
t.Fatal(err)
|
||||||
assert(t, b != nil, "")
|
}
|
||||||
|
if tx.Bucket([]byte("widgets")) == nil {
|
||||||
|
t.Fatal("expected bucket")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that a Tx retrieving a non-existent key returns nil.
|
// Ensure that a Tx retrieving a non-existent key returns nil.
|
||||||
func TestTx_Get_Missing(t *testing.T) {
|
func TestTx_Get_NotFound(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
tx.CreateBucket([]byte("widgets"))
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
|
if err != nil {
|
||||||
value := tx.Bucket([]byte("widgets")).Get([]byte("no_such_key"))
|
t.Fatal(err)
|
||||||
assert(t, value == nil, "")
|
}
|
||||||
|
|
||||||
|
if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if b.Get([]byte("no_such_key")) != nil {
|
||||||
|
t.Fatal("expected nil value")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that a bucket can be created and retrieved.
|
// Ensure that a bucket can be created and retrieved.
|
||||||
func TestTx_CreateBucket(t *testing.T) {
|
func TestTx_CreateBucket(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
|
|
||||||
// Create a bucket.
|
// Create a bucket.
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
b, err := tx.CreateBucket([]byte("widgets"))
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
assert(t, b != nil, "")
|
if err != nil {
|
||||||
ok(t, err)
|
t.Fatal(err)
|
||||||
|
} else if b == nil {
|
||||||
|
t.Fatal("expected bucket")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Read the bucket through a separate transaction.
|
// Read the bucket through a separate transaction.
|
||||||
db.View(func(tx *bolt.Tx) error {
|
if err := db.View(func(tx *bolt.Tx) error {
|
||||||
b := tx.Bucket([]byte("widgets"))
|
if tx.Bucket([]byte("widgets")) == nil {
|
||||||
assert(t, b != nil, "")
|
t.Fatal("expected bucket")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that a bucket can be created if it doesn't already exist.
|
// Ensure that a bucket can be created if it doesn't already exist.
|
||||||
func TestTx_CreateBucketIfNotExists(t *testing.T) {
|
func TestTx_CreateBucketIfNotExists(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
b, err := tx.CreateBucketIfNotExists([]byte("widgets"))
|
// Create bucket.
|
||||||
assert(t, b != nil, "")
|
if b, err := tx.CreateBucketIfNotExists([]byte("widgets")); err != nil {
|
||||||
ok(t, err)
|
t.Fatal(err)
|
||||||
|
} else if b == nil {
|
||||||
|
t.Fatal("expected bucket")
|
||||||
|
}
|
||||||
|
|
||||||
b, err = tx.CreateBucketIfNotExists([]byte("widgets"))
|
// Create bucket again.
|
||||||
assert(t, b != nil, "")
|
if b, err := tx.CreateBucketIfNotExists([]byte("widgets")); err != nil {
|
||||||
ok(t, err)
|
t.Fatal(err)
|
||||||
|
} else if b == nil {
|
||||||
|
t.Fatal("expected bucket")
|
||||||
|
}
|
||||||
|
|
||||||
b, err = tx.CreateBucketIfNotExists([]byte{})
|
|
||||||
assert(t, b == nil, "")
|
|
||||||
equals(t, bolt.ErrBucketNameRequired, err)
|
|
||||||
|
|
||||||
b, err = tx.CreateBucketIfNotExists(nil)
|
|
||||||
assert(t, b == nil, "")
|
|
||||||
equals(t, bolt.ErrBucketNameRequired, err)
|
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Read the bucket through a separate transaction.
|
// Read the bucket through a separate transaction.
|
||||||
db.View(func(tx *bolt.Tx) error {
|
if err := db.View(func(tx *bolt.Tx) error {
|
||||||
b := tx.Bucket([]byte("widgets"))
|
if tx.Bucket([]byte("widgets")) == nil {
|
||||||
assert(t, b != nil, "")
|
t.Fatal("expected bucket")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure transaction returns an error if creating an unnamed bucket.
|
||||||
|
func TestTx_CreateBucketIfNotExists_ErrBucketNameRequired(t *testing.T) {
|
||||||
|
db := MustOpenDB()
|
||||||
|
defer db.MustClose()
|
||||||
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
|
if _, err := tx.CreateBucketIfNotExists([]byte{}); err != bolt.ErrBucketNameRequired {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := tx.CreateBucketIfNotExists(nil); err != bolt.ErrBucketNameRequired {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that a bucket cannot be created twice.
|
// Ensure that a bucket cannot be created twice.
|
||||||
func TestTx_CreateBucket_Exists(t *testing.T) {
|
func TestTx_CreateBucket_ErrBucketExists(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
|
|
||||||
// Create a bucket.
|
// Create a bucket.
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
b, err := tx.CreateBucket([]byte("widgets"))
|
if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
|
||||||
assert(t, b != nil, "")
|
t.Fatal(err)
|
||||||
ok(t, err)
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Create the same bucket again.
|
// Create the same bucket again.
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
b, err := tx.CreateBucket([]byte("widgets"))
|
if _, err := tx.CreateBucket([]byte("widgets")); err != bolt.ErrBucketExists {
|
||||||
assert(t, b == nil, "")
|
t.Fatalf("unexpected error: %s", err)
|
||||||
equals(t, bolt.ErrBucketExists, err)
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that a bucket is created with a non-blank name.
|
// Ensure that a bucket is created with a non-blank name.
|
||||||
func TestTx_CreateBucket_NameRequired(t *testing.T) {
|
func TestTx_CreateBucket_ErrBucketNameRequired(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
b, err := tx.CreateBucket(nil)
|
if _, err := tx.CreateBucket(nil); err != bolt.ErrBucketNameRequired {
|
||||||
assert(t, b == nil, "")
|
t.Fatalf("unexpected error: %s", err)
|
||||||
equals(t, bolt.ErrBucketNameRequired, err)
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that a bucket can be deleted.
|
// Ensure that a bucket can be deleted.
|
||||||
func TestTx_DeleteBucket(t *testing.T) {
|
func TestTx_DeleteBucket(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
|
|
||||||
// Create a bucket and add a value.
|
// Create a bucket and add a value.
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
tx.CreateBucket([]byte("widgets"))
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Delete the bucket and make sure we can't get the value.
|
// Delete the bucket and make sure we can't get the value.
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
ok(t, tx.DeleteBucket([]byte("widgets")))
|
if err := tx.DeleteBucket([]byte("widgets")); err != nil {
|
||||||
assert(t, tx.Bucket([]byte("widgets")) == nil, "")
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if tx.Bucket([]byte("widgets")) != nil {
|
||||||
|
t.Fatal("unexpected bucket")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
// Create the bucket again and make sure there's not a phantom value.
|
// Create the bucket again and make sure there's not a phantom value.
|
||||||
b, err := tx.CreateBucket([]byte("widgets"))
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
assert(t, b != nil, "")
|
if err != nil {
|
||||||
ok(t, err)
|
t.Fatal(err)
|
||||||
assert(t, tx.Bucket([]byte("widgets")).Get([]byte("foo")) == nil, "")
|
}
|
||||||
|
if v := b.Get([]byte("foo")); v != nil {
|
||||||
|
t.Fatalf("unexpected phantom value: %v", v)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that deleting a bucket on a closed transaction returns an error.
|
// Ensure that deleting a bucket on a closed transaction returns an error.
|
||||||
func TestTx_DeleteBucket_Closed(t *testing.T) {
|
func TestTx_DeleteBucket_ErrTxClosed(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
tx, _ := db.Begin(true)
|
tx, err := db.Begin(true)
|
||||||
tx.Commit()
|
if err != nil {
|
||||||
equals(t, tx.DeleteBucket([]byte("foo")), bolt.ErrTxClosed)
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := tx.DeleteBucket([]byte("foo")); err != bolt.ErrTxClosed {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that deleting a bucket with a read-only transaction returns an error.
|
// Ensure that deleting a bucket with a read-only transaction returns an error.
|
||||||
func TestTx_DeleteBucket_ReadOnly(t *testing.T) {
|
func TestTx_DeleteBucket_ReadOnly(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
db.View(func(tx *bolt.Tx) error {
|
if err := db.View(func(tx *bolt.Tx) error {
|
||||||
equals(t, tx.DeleteBucket([]byte("foo")), bolt.ErrTxNotWritable)
|
if err := tx.DeleteBucket([]byte("foo")); err != bolt.ErrTxNotWritable {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that nothing happens when deleting a bucket that doesn't exist.
|
// Ensure that nothing happens when deleting a bucket that doesn't exist.
|
||||||
func TestTx_DeleteBucket_NotFound(t *testing.T) {
|
func TestTx_DeleteBucket_NotFound(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
equals(t, bolt.ErrBucketNotFound, tx.DeleteBucket([]byte("widgets")))
|
if err := tx.DeleteBucket([]byte("widgets")); err != bolt.ErrBucketNotFound {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that no error is returned when a tx.ForEach function does not return
|
||||||
|
// an error.
|
||||||
|
func TestTx_ForEach_NoError(t *testing.T) {
|
||||||
|
db := MustOpenDB()
|
||||||
|
defer db.MustClose()
|
||||||
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.ForEach(func(name []byte, b *bolt.Bucket) error {
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that an error is returned when a tx.ForEach function returns an error.
|
||||||
|
func TestTx_ForEach_WithError(t *testing.T) {
|
||||||
|
db := MustOpenDB()
|
||||||
|
defer db.MustClose()
|
||||||
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
marker := errors.New("marker")
|
||||||
|
if err := tx.ForEach(func(name []byte, b *bolt.Bucket) error {
|
||||||
|
return marker
|
||||||
|
}); err != marker {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that Tx commit handlers are called after a transaction successfully commits.
|
// Ensure that Tx commit handlers are called after a transaction successfully commits.
|
||||||
func TestTx_OnCommit(t *testing.T) {
|
func TestTx_OnCommit(t *testing.T) {
|
||||||
|
db := MustOpenDB()
|
||||||
|
defer db.MustClose()
|
||||||
|
|
||||||
var x int
|
var x int
|
||||||
db := NewTestDB()
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
defer db.Close()
|
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
|
||||||
tx.OnCommit(func() { x += 1 })
|
tx.OnCommit(func() { x += 1 })
|
||||||
tx.OnCommit(func() { x += 2 })
|
tx.OnCommit(func() { x += 2 })
|
||||||
_, err := tx.CreateBucket([]byte("widgets"))
|
if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
|
||||||
return err
|
t.Fatal(err)
|
||||||
})
|
}
|
||||||
equals(t, 3, x)
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if x != 3 {
|
||||||
|
t.Fatalf("unexpected x: %d", x)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that Tx commit handlers are NOT called after a transaction rolls back.
|
// Ensure that Tx commit handlers are NOT called after a transaction rolls back.
|
||||||
func TestTx_OnCommit_Rollback(t *testing.T) {
|
func TestTx_OnCommit_Rollback(t *testing.T) {
|
||||||
|
db := MustOpenDB()
|
||||||
|
defer db.MustClose()
|
||||||
|
|
||||||
var x int
|
var x int
|
||||||
db := NewTestDB()
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
defer db.Close()
|
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
|
||||||
tx.OnCommit(func() { x += 1 })
|
tx.OnCommit(func() { x += 1 })
|
||||||
tx.OnCommit(func() { x += 2 })
|
tx.OnCommit(func() { x += 2 })
|
||||||
tx.CreateBucket([]byte("widgets"))
|
if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
return errors.New("rollback this commit")
|
return errors.New("rollback this commit")
|
||||||
})
|
}); err == nil || err.Error() != "rollback this commit" {
|
||||||
equals(t, 0, x)
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
} else if x != 0 {
|
||||||
|
t.Fatalf("unexpected x: %d", x)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that the database can be copied to a file path.
|
// Ensure that the database can be copied to a file path.
|
||||||
func TestTx_CopyFile(t *testing.T) {
|
func TestTx_CopyFile(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
var dest = tempfile()
|
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
path := tempfile()
|
||||||
tx.CreateBucket([]byte("widgets"))
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
tx.Bucket([]byte("widgets")).Put([]byte("baz"), []byte("bat"))
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := b.Put([]byte("baz"), []byte("bat")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
ok(t, db.View(func(tx *bolt.Tx) error { return tx.CopyFile(dest, 0600) }))
|
if err := db.View(func(tx *bolt.Tx) error {
|
||||||
|
return tx.CopyFile(path, 0600)
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
db2, err := bolt.Open(dest, 0600, nil)
|
db2, err := bolt.Open(path, 0600, nil)
|
||||||
ok(t, err)
|
if err != nil {
|
||||||
defer db2.Close()
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
db2.View(func(tx *bolt.Tx) error {
|
if err := db2.View(func(tx *bolt.Tx) error {
|
||||||
equals(t, []byte("bar"), tx.Bucket([]byte("widgets")).Get([]byte("foo")))
|
if v := tx.Bucket([]byte("widgets")).Get([]byte("foo")); !bytes.Equal(v, []byte("bar")) {
|
||||||
equals(t, []byte("bat"), tx.Bucket([]byte("widgets")).Get([]byte("baz")))
|
t.Fatalf("unexpected value: %v", v)
|
||||||
|
}
|
||||||
|
if v := tx.Bucket([]byte("widgets")).Get([]byte("baz")); !bytes.Equal(v, []byte("bat")) {
|
||||||
|
t.Fatalf("unexpected value: %v", v)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db2.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type failWriterError struct{}
|
type failWriterError struct{}
|
||||||
@ -328,63 +550,107 @@ func (f *failWriter) Write(p []byte) (n int, err error) {
|
|||||||
|
|
||||||
// Ensure that Copy handles write errors right.
|
// Ensure that Copy handles write errors right.
|
||||||
func TestTx_CopyFile_Error_Meta(t *testing.T) {
|
func TestTx_CopyFile_Error_Meta(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
tx.CreateBucket([]byte("widgets"))
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
|
if err != nil {
|
||||||
tx.Bucket([]byte("widgets")).Put([]byte("baz"), []byte("bat"))
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := b.Put([]byte("baz"), []byte("bat")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
err := db.View(func(tx *bolt.Tx) error { return tx.Copy(&failWriter{}) })
|
if err := db.View(func(tx *bolt.Tx) error {
|
||||||
equals(t, err.Error(), "meta copy: error injected for tests")
|
return tx.Copy(&failWriter{})
|
||||||
|
}); err == nil || err.Error() != "meta copy: error injected for tests" {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that Copy handles write errors right.
|
// Ensure that Copy handles write errors right.
|
||||||
func TestTx_CopyFile_Error_Normal(t *testing.T) {
|
func TestTx_CopyFile_Error_Normal(t *testing.T) {
|
||||||
db := NewTestDB()
|
db := MustOpenDB()
|
||||||
defer db.Close()
|
defer db.MustClose()
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
tx.CreateBucket([]byte("widgets"))
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
|
if err != nil {
|
||||||
tx.Bucket([]byte("widgets")).Put([]byte("baz"), []byte("bat"))
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := b.Put([]byte("baz"), []byte("bat")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
err := db.View(func(tx *bolt.Tx) error { return tx.Copy(&failWriter{3 * db.Info().PageSize}) })
|
if err := db.View(func(tx *bolt.Tx) error {
|
||||||
equals(t, err.Error(), "error injected for tests")
|
return tx.Copy(&failWriter{3 * db.Info().PageSize})
|
||||||
|
}); err == nil || err.Error() != "error injected for tests" {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleTx_Rollback() {
|
func ExampleTx_Rollback() {
|
||||||
// Open the database.
|
// Open the database.
|
||||||
db, _ := bolt.Open(tempfile(), 0666, nil)
|
db, err := bolt.Open(tempfile(), 0666, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
defer os.Remove(db.Path())
|
defer os.Remove(db.Path())
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
// Create a bucket.
|
// Create a bucket.
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
_, err := tx.CreateBucket([]byte("widgets"))
|
_, err := tx.CreateBucket([]byte("widgets"))
|
||||||
return err
|
return err
|
||||||
})
|
}); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Set a value for a key.
|
// Set a value for a key.
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
return tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
|
return tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
|
||||||
})
|
}); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Update the key but rollback the transaction so it never saves.
|
// Update the key but rollback the transaction so it never saves.
|
||||||
tx, _ := db.Begin(true)
|
tx, err := db.Begin(true)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
b := tx.Bucket([]byte("widgets"))
|
b := tx.Bucket([]byte("widgets"))
|
||||||
b.Put([]byte("foo"), []byte("baz"))
|
if err := b.Put([]byte("foo"), []byte("baz")); err != nil {
|
||||||
tx.Rollback()
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := tx.Rollback(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure that our original value is still set.
|
// Ensure that our original value is still set.
|
||||||
db.View(func(tx *bolt.Tx) error {
|
if err := db.View(func(tx *bolt.Tx) error {
|
||||||
value := tx.Bucket([]byte("widgets")).Get([]byte("foo"))
|
value := tx.Bucket([]byte("widgets")).Get([]byte("foo"))
|
||||||
fmt.Printf("The value for 'foo' is still: %s\n", value)
|
fmt.Printf("The value for 'foo' is still: %s\n", value)
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close database to release file lock.
|
||||||
|
if err := db.Close(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// The value for 'foo' is still: bar
|
// The value for 'foo' is still: bar
|
||||||
@ -392,32 +658,58 @@ func ExampleTx_Rollback() {
|
|||||||
|
|
||||||
func ExampleTx_CopyFile() {
|
func ExampleTx_CopyFile() {
|
||||||
// Open the database.
|
// Open the database.
|
||||||
db, _ := bolt.Open(tempfile(), 0666, nil)
|
db, err := bolt.Open(tempfile(), 0666, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
defer os.Remove(db.Path())
|
defer os.Remove(db.Path())
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
// Create a bucket and a key.
|
// Create a bucket and a key.
|
||||||
db.Update(func(tx *bolt.Tx) error {
|
if err := db.Update(func(tx *bolt.Tx) error {
|
||||||
tx.CreateBucket([]byte("widgets"))
|
b, err := tx.CreateBucket([]byte("widgets"))
|
||||||
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Copy the database to another file.
|
// Copy the database to another file.
|
||||||
toFile := tempfile()
|
toFile := tempfile()
|
||||||
db.View(func(tx *bolt.Tx) error { return tx.CopyFile(toFile, 0666) })
|
if err := db.View(func(tx *bolt.Tx) error {
|
||||||
|
return tx.CopyFile(toFile, 0666)
|
||||||
|
}); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
defer os.Remove(toFile)
|
defer os.Remove(toFile)
|
||||||
|
|
||||||
// Open the cloned database.
|
// Open the cloned database.
|
||||||
db2, _ := bolt.Open(toFile, 0666, nil)
|
db2, err := bolt.Open(toFile, 0666, nil)
|
||||||
defer db2.Close()
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure that the key exists in the copy.
|
// Ensure that the key exists in the copy.
|
||||||
db2.View(func(tx *bolt.Tx) error {
|
if err := db2.View(func(tx *bolt.Tx) error {
|
||||||
value := tx.Bucket([]byte("widgets")).Get([]byte("foo"))
|
value := tx.Bucket([]byte("widgets")).Get([]byte("foo"))
|
||||||
fmt.Printf("The value for 'foo' in the clone is: %s\n", value)
|
fmt.Printf("The value for 'foo' in the clone is: %s\n", value)
|
||||||
return nil
|
return nil
|
||||||
})
|
}); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close database to release file lock.
|
||||||
|
if err := db.Close(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db2.Close(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// The value for 'foo' in the clone is: bar
|
// The value for 'foo' in the clone is: bar
|
||||||
|
2
Godeps/_workspace/src/github.com/gorilla/websocket/README.md
generated
vendored
2
Godeps/_workspace/src/github.com/gorilla/websocket/README.md
generated
vendored
@ -7,6 +7,8 @@ Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
|
|||||||
|
|
||||||
* [API Reference](http://godoc.org/github.com/gorilla/websocket)
|
* [API Reference](http://godoc.org/github.com/gorilla/websocket)
|
||||||
* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat)
|
* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat)
|
||||||
|
* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command)
|
||||||
|
* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo)
|
||||||
* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch)
|
* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch)
|
||||||
|
|
||||||
### Status
|
### Status
|
||||||
|
242
Godeps/_workspace/src/github.com/gorilla/websocket/client.go
generated
vendored
242
Godeps/_workspace/src/github.com/gorilla/websocket/client.go
generated
vendored
@ -5,8 +5,12 @@
|
|||||||
package websocket
|
package websocket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -27,50 +31,17 @@ var ErrBadHandshake = errors.New("websocket: bad handshake")
|
|||||||
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
||||||
// non-nil *http.Response so that callers can handle redirects, authentication,
|
// non-nil *http.Response so that callers can handle redirects, authentication,
|
||||||
// etc.
|
// etc.
|
||||||
|
//
|
||||||
|
// Deprecated: Use Dialer instead.
|
||||||
func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) {
|
func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) {
|
||||||
challengeKey, err := generateChallengeKey()
|
d := Dialer{
|
||||||
if err != nil {
|
ReadBufferSize: readBufSize,
|
||||||
return nil, nil, err
|
WriteBufferSize: writeBufSize,
|
||||||
|
NetDial: func(net, addr string) (net.Conn, error) {
|
||||||
|
return netConn, nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
acceptKey := computeAcceptKey(challengeKey)
|
return d.Dial(u.String(), requestHeader)
|
||||||
|
|
||||||
c = newConn(netConn, false, readBufSize, writeBufSize)
|
|
||||||
p := c.writeBuf[:0]
|
|
||||||
p = append(p, "GET "...)
|
|
||||||
p = append(p, u.RequestURI()...)
|
|
||||||
p = append(p, " HTTP/1.1\r\nHost: "...)
|
|
||||||
p = append(p, u.Host...)
|
|
||||||
// "Upgrade" is capitalized for servers that do not use case insensitive
|
|
||||||
// comparisons on header tokens.
|
|
||||||
p = append(p, "\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: "...)
|
|
||||||
p = append(p, challengeKey...)
|
|
||||||
p = append(p, "\r\n"...)
|
|
||||||
for k, vs := range requestHeader {
|
|
||||||
for _, v := range vs {
|
|
||||||
p = append(p, k...)
|
|
||||||
p = append(p, ": "...)
|
|
||||||
p = append(p, v...)
|
|
||||||
p = append(p, "\r\n"...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p = append(p, "\r\n"...)
|
|
||||||
|
|
||||||
if _, err := netConn.Write(p); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := http.ReadResponse(c.br, &http.Request{Method: "GET", URL: u})
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if resp.StatusCode != 101 ||
|
|
||||||
!strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") ||
|
|
||||||
!strings.EqualFold(resp.Header.Get("Connection"), "upgrade") ||
|
|
||||||
resp.Header.Get("Sec-Websocket-Accept") != acceptKey {
|
|
||||||
return nil, resp, ErrBadHandshake
|
|
||||||
}
|
|
||||||
c.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
|
|
||||||
return c, resp, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Dialer contains options for connecting to WebSocket server.
|
// A Dialer contains options for connecting to WebSocket server.
|
||||||
@ -79,6 +50,12 @@ type Dialer struct {
|
|||||||
// NetDial is nil, net.Dial is used.
|
// NetDial is nil, net.Dial is used.
|
||||||
NetDial func(network, addr string) (net.Conn, error)
|
NetDial func(network, addr string) (net.Conn, error)
|
||||||
|
|
||||||
|
// Proxy specifies a function to return a proxy for a given
|
||||||
|
// Request. If the function returns a non-nil error, the
|
||||||
|
// request is aborted with the provided error.
|
||||||
|
// If Proxy is nil or returns a nil *URL, no proxy is used.
|
||||||
|
Proxy func(*http.Request) (*url.URL, error)
|
||||||
|
|
||||||
// TLSClientConfig specifies the TLS configuration to use with tls.Client.
|
// TLSClientConfig specifies the TLS configuration to use with tls.Client.
|
||||||
// If nil, the default configuration is used.
|
// If nil, the default configuration is used.
|
||||||
TLSClientConfig *tls.Config
|
TLSClientConfig *tls.Config
|
||||||
@ -96,17 +73,15 @@ type Dialer struct {
|
|||||||
|
|
||||||
var errMalformedURL = errors.New("malformed ws or wss URL")
|
var errMalformedURL = errors.New("malformed ws or wss URL")
|
||||||
|
|
||||||
// parseURL parses the URL. The url.Parse function is not used here because
|
// parseURL parses the URL.
|
||||||
// url.Parse mangles the path.
|
//
|
||||||
|
// This function is a replacement for the standard library url.Parse function.
|
||||||
|
// In Go 1.4 and earlier, url.Parse loses information from the path.
|
||||||
func parseURL(s string) (*url.URL, error) {
|
func parseURL(s string) (*url.URL, error) {
|
||||||
// From the RFC:
|
// From the RFC:
|
||||||
//
|
//
|
||||||
// ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
|
// ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
|
||||||
// wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
|
// wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
|
||||||
//
|
|
||||||
// We don't use the net/url parser here because the dialer interface does
|
|
||||||
// not provide a way for applications to work around percent deocding in
|
|
||||||
// the net/url parser.
|
|
||||||
|
|
||||||
var u url.URL
|
var u url.URL
|
||||||
switch {
|
switch {
|
||||||
@ -120,11 +95,24 @@ func parseURL(s string) (*url.URL, error) {
|
|||||||
return nil, errMalformedURL
|
return nil, errMalformedURL
|
||||||
}
|
}
|
||||||
|
|
||||||
u.Host = s
|
if i := strings.Index(s, "?"); i >= 0 {
|
||||||
u.Opaque = "/"
|
u.RawQuery = s[i+1:]
|
||||||
|
s = s[:i]
|
||||||
|
}
|
||||||
|
|
||||||
if i := strings.Index(s, "/"); i >= 0 {
|
if i := strings.Index(s, "/"); i >= 0 {
|
||||||
u.Host = s[:i]
|
|
||||||
u.Opaque = s[i:]
|
u.Opaque = s[i:]
|
||||||
|
s = s[:i]
|
||||||
|
} else {
|
||||||
|
u.Opaque = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
u.Host = s
|
||||||
|
|
||||||
|
if strings.Contains(u.Host, "@") {
|
||||||
|
// Don't bother parsing user information because user information is
|
||||||
|
// not allowed in websocket URIs.
|
||||||
|
return nil, errMalformedURL
|
||||||
}
|
}
|
||||||
|
|
||||||
return &u, nil
|
return &u, nil
|
||||||
@ -136,9 +124,12 @@ func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
|
|||||||
if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") {
|
if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") {
|
||||||
hostNoPort = hostNoPort[:i]
|
hostNoPort = hostNoPort[:i]
|
||||||
} else {
|
} else {
|
||||||
if u.Scheme == "wss" {
|
switch u.Scheme {
|
||||||
|
case "wss":
|
||||||
hostPort += ":443"
|
hostPort += ":443"
|
||||||
} else {
|
case "https":
|
||||||
|
hostPort += ":443"
|
||||||
|
default:
|
||||||
hostPort += ":80"
|
hostPort += ":80"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -146,7 +137,9 @@ func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DefaultDialer is a dialer with all fields set to the default zero values.
|
// DefaultDialer is a dialer with all fields set to the default zero values.
|
||||||
var DefaultDialer *Dialer
|
var DefaultDialer = &Dialer{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
}
|
||||||
|
|
||||||
// Dial creates a new client connection. Use requestHeader to specify the
|
// Dial creates a new client connection. Use requestHeader to specify the
|
||||||
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
|
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
|
||||||
@ -155,17 +148,94 @@ var DefaultDialer *Dialer
|
|||||||
//
|
//
|
||||||
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
||||||
// non-nil *http.Response so that callers can handle redirects, authentication,
|
// non-nil *http.Response so that callers can handle redirects, authentication,
|
||||||
// etc.
|
// etcetera. The response body may not contain the entire response and does not
|
||||||
|
// need to be closed by the application.
|
||||||
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) {
|
||||||
|
|
||||||
|
if d == nil {
|
||||||
|
d = &Dialer{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
challengeKey, err := generateChallengeKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
u, err := parseURL(urlStr)
|
u, err := parseURL(urlStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch u.Scheme {
|
||||||
|
case "ws":
|
||||||
|
u.Scheme = "http"
|
||||||
|
case "wss":
|
||||||
|
u.Scheme = "https"
|
||||||
|
default:
|
||||||
|
return nil, nil, errMalformedURL
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.User != nil {
|
||||||
|
// User name and password are not allowed in websocket URIs.
|
||||||
|
return nil, nil, errMalformedURL
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &http.Request{
|
||||||
|
Method: "GET",
|
||||||
|
URL: u,
|
||||||
|
Proto: "HTTP/1.1",
|
||||||
|
ProtoMajor: 1,
|
||||||
|
ProtoMinor: 1,
|
||||||
|
Header: make(http.Header),
|
||||||
|
Host: u.Host,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the request headers using the capitalization for names and values in
|
||||||
|
// RFC examples. Although the capitalization shouldn't matter, there are
|
||||||
|
// servers that depend on it. The Header.Set method is not used because the
|
||||||
|
// method canonicalizes the header names.
|
||||||
|
req.Header["Upgrade"] = []string{"websocket"}
|
||||||
|
req.Header["Connection"] = []string{"Upgrade"}
|
||||||
|
req.Header["Sec-WebSocket-Key"] = []string{challengeKey}
|
||||||
|
req.Header["Sec-WebSocket-Version"] = []string{"13"}
|
||||||
|
if len(d.Subprotocols) > 0 {
|
||||||
|
req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")}
|
||||||
|
}
|
||||||
|
for k, vs := range requestHeader {
|
||||||
|
switch {
|
||||||
|
case k == "Host":
|
||||||
|
if len(vs) > 0 {
|
||||||
|
req.Host = vs[0]
|
||||||
|
}
|
||||||
|
case k == "Upgrade" ||
|
||||||
|
k == "Connection" ||
|
||||||
|
k == "Sec-Websocket-Key" ||
|
||||||
|
k == "Sec-Websocket-Version" ||
|
||||||
|
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
|
||||||
|
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
|
||||||
|
default:
|
||||||
|
req.Header[k] = vs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hostPort, hostNoPort := hostPortNoPort(u)
|
hostPort, hostNoPort := hostPortNoPort(u)
|
||||||
|
|
||||||
if d == nil {
|
var proxyURL *url.URL
|
||||||
d = &Dialer{}
|
// Check wether the proxy method has been configured
|
||||||
|
if d.Proxy != nil {
|
||||||
|
proxyURL, err = d.Proxy(req)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetHostPort string
|
||||||
|
if proxyURL != nil {
|
||||||
|
targetHostPort, _ = hostPortNoPort(proxyURL)
|
||||||
|
} else {
|
||||||
|
targetHostPort = hostPort
|
||||||
}
|
}
|
||||||
|
|
||||||
var deadline time.Time
|
var deadline time.Time
|
||||||
@ -179,7 +249,7 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|||||||
netDial = netDialer.Dial
|
netDial = netDialer.Dial
|
||||||
}
|
}
|
||||||
|
|
||||||
netConn, err := netDial("tcp", hostPort)
|
netConn, err := netDial("tcp", targetHostPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -194,7 +264,31 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if u.Scheme == "wss" {
|
if proxyURL != nil {
|
||||||
|
connectReq := &http.Request{
|
||||||
|
Method: "CONNECT",
|
||||||
|
URL: &url.URL{Opaque: hostPort},
|
||||||
|
Host: hostPort,
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
|
||||||
|
connectReq.Write(netConn)
|
||||||
|
|
||||||
|
// Read response.
|
||||||
|
// Okay to use and discard buffered reader here, because
|
||||||
|
// TLS server will not speak until spoken to.
|
||||||
|
br := bufio.NewReader(netConn)
|
||||||
|
resp, err := http.ReadResponse(br, connectReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
f := strings.SplitN(resp.Status, " ", 2)
|
||||||
|
return nil, nil, errors.New(f[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme == "https" {
|
||||||
cfg := d.TLSClientConfig
|
cfg := d.TLSClientConfig
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
cfg = &tls.Config{ServerName: hostNoPort}
|
cfg = &tls.Config{ServerName: hostNoPort}
|
||||||
@ -215,19 +309,31 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(d.Subprotocols) > 0 {
|
conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize)
|
||||||
h := http.Header{}
|
|
||||||
for k, v := range requestHeader {
|
if err := req.Write(netConn); err != nil {
|
||||||
h[k] = v
|
return nil, nil, err
|
||||||
}
|
|
||||||
h.Set("Sec-Websocket-Protocol", strings.Join(d.Subprotocols, ", "))
|
|
||||||
requestHeader = h
|
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, resp, err := NewClient(netConn, u, requestHeader, d.ReadBufferSize, d.WriteBufferSize)
|
resp, err := http.ReadResponse(conn.br, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, resp, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
if resp.StatusCode != 101 ||
|
||||||
|
!strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") ||
|
||||||
|
!strings.EqualFold(resp.Header.Get("Connection"), "upgrade") ||
|
||||||
|
resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) {
|
||||||
|
// Before closing the network connection on return from this
|
||||||
|
// function, slurp up some of the response to aid application
|
||||||
|
// debugging.
|
||||||
|
buf := make([]byte, 1024)
|
||||||
|
n, _ := io.ReadFull(resp.Body, buf)
|
||||||
|
resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n]))
|
||||||
|
return nil, resp, ErrBadHandshake
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
|
||||||
|
conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
|
||||||
|
|
||||||
netConn.SetDeadline(time.Time{})
|
netConn.SetDeadline(time.Time{})
|
||||||
netConn = nil // to avoid close in defer.
|
netConn = nil // to avoid close in defer.
|
||||||
|
183
Godeps/_workspace/src/github.com/gorilla/websocket/client_server_test.go
generated
vendored
183
Godeps/_workspace/src/github.com/gorilla/websocket/client_server_test.go
generated
vendored
@ -8,11 +8,13 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -34,29 +36,42 @@ var cstDialer = Dialer{
|
|||||||
|
|
||||||
type cstHandler struct{ *testing.T }
|
type cstHandler struct{ *testing.T }
|
||||||
|
|
||||||
type Server struct {
|
type cstServer struct {
|
||||||
*httptest.Server
|
*httptest.Server
|
||||||
URL string
|
URL string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newServer(t *testing.T) *Server {
|
const (
|
||||||
var s Server
|
cstPath = "/a/b"
|
||||||
|
cstRawQuery = "x=y"
|
||||||
|
cstRequestURI = cstPath + "?" + cstRawQuery
|
||||||
|
)
|
||||||
|
|
||||||
|
func newServer(t *testing.T) *cstServer {
|
||||||
|
var s cstServer
|
||||||
s.Server = httptest.NewServer(cstHandler{t})
|
s.Server = httptest.NewServer(cstHandler{t})
|
||||||
s.URL = "ws" + s.Server.URL[len("http"):]
|
s.Server.URL += cstRequestURI
|
||||||
|
s.URL = makeWsProto(s.Server.URL)
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTLSServer(t *testing.T) *Server {
|
func newTLSServer(t *testing.T) *cstServer {
|
||||||
var s Server
|
var s cstServer
|
||||||
s.Server = httptest.NewTLSServer(cstHandler{t})
|
s.Server = httptest.NewTLSServer(cstHandler{t})
|
||||||
s.URL = "ws" + s.Server.URL[len("http"):]
|
s.Server.URL += cstRequestURI
|
||||||
|
s.URL = makeWsProto(s.Server.URL)
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t cstHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (t cstHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != "GET" {
|
if r.URL.Path != cstPath {
|
||||||
t.Logf("method %s not allowed", r.Method)
|
t.Logf("path=%v, want %v", r.URL.Path, cstPath)
|
||||||
http.Error(w, "method not allowed", 405)
|
http.Error(w, "bad path", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.URL.RawQuery != cstRawQuery {
|
||||||
|
t.Logf("query=%v, want %v", r.URL.RawQuery, cstRawQuery)
|
||||||
|
http.Error(w, "bad path", 400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
subprotos := Subprotocols(r)
|
subprotos := Subprotocols(r)
|
||||||
@ -97,6 +112,10 @@ func (t cstHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeWsProto(s string) string {
|
||||||
|
return "ws" + strings.TrimPrefix(s, "http")
|
||||||
|
}
|
||||||
|
|
||||||
func sendRecv(t *testing.T, ws *Conn) {
|
func sendRecv(t *testing.T, ws *Conn) {
|
||||||
const message = "Hello World!"
|
const message = "Hello World!"
|
||||||
if err := ws.SetWriteDeadline(time.Now().Add(time.Second)); err != nil {
|
if err := ws.SetWriteDeadline(time.Now().Add(time.Second)); err != nil {
|
||||||
@ -117,6 +136,45 @@ func sendRecv(t *testing.T, ws *Conn) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProxyDial(t *testing.T) {
|
||||||
|
|
||||||
|
s := newServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
surl, _ := url.Parse(s.URL)
|
||||||
|
|
||||||
|
cstDialer.Proxy = http.ProxyURL(surl)
|
||||||
|
|
||||||
|
connect := false
|
||||||
|
origHandler := s.Server.Config.Handler
|
||||||
|
|
||||||
|
// Capture the request Host header.
|
||||||
|
s.Server.Config.Handler = http.HandlerFunc(
|
||||||
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == "CONNECT" {
|
||||||
|
connect = true
|
||||||
|
w.WriteHeader(200)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !connect {
|
||||||
|
t.Log("connect not recieved")
|
||||||
|
http.Error(w, "connect not recieved", 405)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
origHandler.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
ws, _, err := cstDialer.Dial(s.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Dial: %v", err)
|
||||||
|
}
|
||||||
|
defer ws.Close()
|
||||||
|
sendRecv(t, ws)
|
||||||
|
|
||||||
|
cstDialer.Proxy = http.ProxyFromEnvironment
|
||||||
|
}
|
||||||
|
|
||||||
func TestDial(t *testing.T) {
|
func TestDial(t *testing.T) {
|
||||||
s := newServer(t)
|
s := newServer(t)
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
@ -148,7 +206,7 @@ func TestDialTLS(t *testing.T) {
|
|||||||
d := cstDialer
|
d := cstDialer
|
||||||
d.NetDial = func(network, addr string) (net.Conn, error) { return net.Dial(network, u.Host) }
|
d.NetDial = func(network, addr string) (net.Conn, error) { return net.Dial(network, u.Host) }
|
||||||
d.TLSClientConfig = &tls.Config{RootCAs: certs}
|
d.TLSClientConfig = &tls.Config{RootCAs: certs}
|
||||||
ws, _, err := d.Dial("wss://example.com/", nil)
|
ws, _, err := d.Dial("wss://example.com"+cstRequestURI, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Dial: %v", err)
|
t.Fatalf("Dial: %v", err)
|
||||||
}
|
}
|
||||||
@ -157,6 +215,7 @@ func TestDialTLS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func xTestDialTLSBadCert(t *testing.T) {
|
func xTestDialTLSBadCert(t *testing.T) {
|
||||||
|
// This test is deactivated because of noisy logging from the net/http package.
|
||||||
s := newTLSServer(t)
|
s := newTLSServer(t)
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
@ -222,6 +281,45 @@ func TestDialBadOrigin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDialBadHeader(t *testing.T) {
|
||||||
|
s := newServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
for _, k := range []string{"Upgrade",
|
||||||
|
"Connection",
|
||||||
|
"Sec-Websocket-Key",
|
||||||
|
"Sec-Websocket-Version",
|
||||||
|
"Sec-Websocket-Protocol"} {
|
||||||
|
h := http.Header{}
|
||||||
|
h.Set(k, "bad")
|
||||||
|
ws, _, err := cstDialer.Dial(s.URL, http.Header{"Origin": {"bad"}})
|
||||||
|
if err == nil {
|
||||||
|
ws.Close()
|
||||||
|
t.Errorf("Dial with header %s returned nil", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadMethod(t *testing.T) {
|
||||||
|
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ws, err := cstUpgrader.Upgrade(w, r, nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("handshake succeeded, expect fail")
|
||||||
|
ws.Close()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
resp, err := http.PostForm(s.URL, url.Values{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("PostForm returned error %v", err)
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusMethodNotAllowed {
|
||||||
|
t.Errorf("Status = %d, want %d", resp.StatusCode, http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHandshake(t *testing.T) {
|
func TestHandshake(t *testing.T) {
|
||||||
s := newServer(t)
|
s := newServer(t)
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
@ -247,3 +345,66 @@ func TestHandshake(t *testing.T) {
|
|||||||
}
|
}
|
||||||
sendRecv(t, ws)
|
sendRecv(t, ws)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRespOnBadHandshake(t *testing.T) {
|
||||||
|
const expectedStatus = http.StatusGone
|
||||||
|
const expectedBody = "This is the response body."
|
||||||
|
|
||||||
|
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(expectedStatus)
|
||||||
|
io.WriteString(w, expectedBody)
|
||||||
|
}))
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
ws, resp, err := cstDialer.Dial(makeWsProto(s.URL), nil)
|
||||||
|
if err == nil {
|
||||||
|
ws.Close()
|
||||||
|
t.Fatalf("Dial: nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatalf("resp=nil, err=%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != expectedStatus {
|
||||||
|
t.Errorf("resp.StatusCode=%d, want %d", resp.StatusCode, expectedStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadFull(resp.Body) returned error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(p) != expectedBody {
|
||||||
|
t.Errorf("resp.Body=%s, want %s", p, expectedBody)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHostHeader confirms that the host header provided in the call to Dial is
|
||||||
|
// sent to the server.
|
||||||
|
func TestHostHeader(t *testing.T) {
|
||||||
|
s := newServer(t)
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
specifiedHost := make(chan string, 1)
|
||||||
|
origHandler := s.Server.Config.Handler
|
||||||
|
|
||||||
|
// Capture the request Host header.
|
||||||
|
s.Server.Config.Handler = http.HandlerFunc(
|
||||||
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
specifiedHost <- r.Host
|
||||||
|
origHandler.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
ws, _, err := cstDialer.Dial(s.URL, http.Header{"Host": {"testhost"}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Dial: %v", err)
|
||||||
|
}
|
||||||
|
defer ws.Close()
|
||||||
|
|
||||||
|
if gotHost := <-specifiedHost; gotHost != "testhost" {
|
||||||
|
t.Fatalf("gotHost = %q, want \"testhost\"", gotHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
sendRecv(t, ws)
|
||||||
|
}
|
||||||
|
25
Godeps/_workspace/src/github.com/gorilla/websocket/client_test.go
generated
vendored
25
Godeps/_workspace/src/github.com/gorilla/websocket/client_test.go
generated
vendored
@ -13,13 +13,17 @@ import (
|
|||||||
var parseURLTests = []struct {
|
var parseURLTests = []struct {
|
||||||
s string
|
s string
|
||||||
u *url.URL
|
u *url.URL
|
||||||
|
rui string
|
||||||
}{
|
}{
|
||||||
{"ws://example.com/", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}},
|
{"ws://example.com/", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}, "/"},
|
||||||
{"ws://example.com", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}},
|
{"ws://example.com", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}, "/"},
|
||||||
{"ws://example.com:7777/", &url.URL{Scheme: "ws", Host: "example.com:7777", Opaque: "/"}},
|
{"ws://example.com:7777/", &url.URL{Scheme: "ws", Host: "example.com:7777", Opaque: "/"}, "/"},
|
||||||
{"wss://example.com/", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/"}},
|
{"wss://example.com/", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/"}, "/"},
|
||||||
{"wss://example.com/a/b", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/a/b"}},
|
{"wss://example.com/a/b", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/a/b"}, "/a/b"},
|
||||||
{"ss://example.com/a/b", nil},
|
{"ss://example.com/a/b", nil, ""},
|
||||||
|
{"ws://webmaster@example.com/", nil, ""},
|
||||||
|
{"wss://example.com/a/b?x=y", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/a/b", RawQuery: "x=y"}, "/a/b?x=y"},
|
||||||
|
{"wss://example.com?x=y", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/", RawQuery: "x=y"}, "/?x=y"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseURL(t *testing.T) {
|
func TestParseURL(t *testing.T) {
|
||||||
@ -29,14 +33,19 @@ func TestParseURL(t *testing.T) {
|
|||||||
t.Errorf("parseURL(%q) returned error %v", tt.s, err)
|
t.Errorf("parseURL(%q) returned error %v", tt.s, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if tt.u == nil && err == nil {
|
if tt.u == nil {
|
||||||
|
if err == nil {
|
||||||
t.Errorf("parseURL(%q) did not return error", tt.s)
|
t.Errorf("parseURL(%q) did not return error", tt.s)
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(u, tt.u) {
|
if !reflect.DeepEqual(u, tt.u) {
|
||||||
t.Errorf("parseURL(%q) returned %v, want %v", tt.s, u, tt.u)
|
t.Errorf("parseURL(%q) = %v, want %v", tt.s, u, tt.u)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if u.RequestURI() != tt.rui {
|
||||||
|
t.Errorf("parseURL(%q).RequestURI() = %v, want %v", tt.s, u.RequestURI(), tt.rui)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
46
Godeps/_workspace/src/github.com/gorilla/websocket/conn.go
generated
vendored
46
Godeps/_workspace/src/github.com/gorilla/websocket/conn.go
generated
vendored
@ -88,19 +88,23 @@ func (e *netError) Error() string { return e.msg }
|
|||||||
func (e *netError) Temporary() bool { return e.temporary }
|
func (e *netError) Temporary() bool { return e.temporary }
|
||||||
func (e *netError) Timeout() bool { return e.timeout }
|
func (e *netError) Timeout() bool { return e.timeout }
|
||||||
|
|
||||||
// closeError represents close frame.
|
// CloseError represents close frame.
|
||||||
type closeError struct {
|
type CloseError struct {
|
||||||
code int
|
|
||||||
text string
|
// Code is defined in RFC 6455, section 11.7.
|
||||||
|
Code int
|
||||||
|
|
||||||
|
// Text is the optional text payload.
|
||||||
|
Text string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *closeError) Error() string {
|
func (e *CloseError) Error() string {
|
||||||
return "websocket: close " + strconv.Itoa(e.code) + " " + e.text
|
return "websocket: close " + strconv.Itoa(e.Code) + " " + e.Text
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errWriteTimeout = &netError{msg: "websocket: write timeout", timeout: true}
|
errWriteTimeout = &netError{msg: "websocket: write timeout", timeout: true, temporary: true}
|
||||||
errUnexpectedEOF = &closeError{code: CloseAbnormalClosure, text: io.ErrUnexpectedEOF.Error()}
|
errUnexpectedEOF = &CloseError{Code: CloseAbnormalClosure, Text: io.ErrUnexpectedEOF.Error()}
|
||||||
errBadWriteOpCode = errors.New("websocket: bad write message type")
|
errBadWriteOpCode = errors.New("websocket: bad write message type")
|
||||||
errWriteClosed = errors.New("websocket: write closed")
|
errWriteClosed = errors.New("websocket: write closed")
|
||||||
errInvalidControlFrame = errors.New("websocket: invalid control frame")
|
errInvalidControlFrame = errors.New("websocket: invalid control frame")
|
||||||
@ -296,7 +300,7 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er
|
|||||||
if n != 0 && n != len(buf) {
|
if n != 0 && n != len(buf) {
|
||||||
c.conn.Close()
|
c.conn.Close()
|
||||||
}
|
}
|
||||||
return err
|
return hideTempErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextWriter returns a writer for the next message to send. The writer's
|
// NextWriter returns a writer for the next message to send. The writer's
|
||||||
@ -673,12 +677,7 @@ func (c *Conn) advanceFrame() (int, error) {
|
|||||||
closeCode = int(binary.BigEndian.Uint16(payload))
|
closeCode = int(binary.BigEndian.Uint16(payload))
|
||||||
closeText = string(payload[2:])
|
closeText = string(payload[2:])
|
||||||
}
|
}
|
||||||
switch closeCode {
|
return noFrame, &CloseError{Code: closeCode, Text: closeText}
|
||||||
case CloseNormalClosure, CloseGoingAway:
|
|
||||||
return noFrame, io.EOF
|
|
||||||
default:
|
|
||||||
return noFrame, &closeError{code: closeCode, text: closeText}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return frameType, nil
|
return frameType, nil
|
||||||
@ -790,20 +789,27 @@ func (c *Conn) SetReadLimit(limit int64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetPingHandler sets the handler for ping messages received from the peer.
|
// SetPingHandler sets the handler for ping messages received from the peer.
|
||||||
// The default ping handler sends a pong to the peer.
|
// The appData argument to h is the PING frame application data. The default
|
||||||
func (c *Conn) SetPingHandler(h func(string) error) {
|
// ping handler sends a pong to the peer.
|
||||||
|
func (c *Conn) SetPingHandler(h func(appData string) error) {
|
||||||
if h == nil {
|
if h == nil {
|
||||||
h = func(message string) error {
|
h = func(message string) error {
|
||||||
c.WriteControl(PongMessage, []byte(message), time.Now().Add(writeWait))
|
err := c.WriteControl(PongMessage, []byte(message), time.Now().Add(writeWait))
|
||||||
|
if err == ErrCloseSent {
|
||||||
return nil
|
return nil
|
||||||
|
} else if e, ok := err.(net.Error); ok && e.Temporary() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.handlePing = h
|
c.handlePing = h
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPongHandler sets the handler for pong messages received from the peer.
|
// SetPongHandler sets the handler for pong messages received from the peer.
|
||||||
// The default pong handler does nothing.
|
// The appData argument to h is the PONG frame application data. The default
|
||||||
func (c *Conn) SetPongHandler(h func(string) error) {
|
// pong handler does nothing.
|
||||||
|
func (c *Conn) SetPongHandler(h func(appData string) error) {
|
||||||
if h == nil {
|
if h == nil {
|
||||||
h = func(string) error { return nil }
|
h = func(string) error { return nil }
|
||||||
}
|
}
|
||||||
|
44
Godeps/_workspace/src/github.com/gorilla/websocket/conn_test.go
generated
vendored
44
Godeps/_workspace/src/github.com/gorilla/websocket/conn_test.go
generated
vendored
@ -5,11 +5,13 @@
|
|||||||
package websocket
|
package websocket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/iotest"
|
"testing/iotest"
|
||||||
"time"
|
"time"
|
||||||
@ -146,13 +148,15 @@ func TestControl(t *testing.T) {
|
|||||||
func TestCloseBeforeFinalFrame(t *testing.T) {
|
func TestCloseBeforeFinalFrame(t *testing.T) {
|
||||||
const bufSize = 512
|
const bufSize = 512
|
||||||
|
|
||||||
|
expectedErr := &CloseError{Code: CloseNormalClosure, Text: "hello"}
|
||||||
|
|
||||||
var b1, b2 bytes.Buffer
|
var b1, b2 bytes.Buffer
|
||||||
wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, bufSize)
|
wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, bufSize)
|
||||||
rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024)
|
rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024)
|
||||||
|
|
||||||
w, _ := wc.NextWriter(BinaryMessage)
|
w, _ := wc.NextWriter(BinaryMessage)
|
||||||
w.Write(make([]byte, bufSize+bufSize/2))
|
w.Write(make([]byte, bufSize+bufSize/2))
|
||||||
wc.WriteControl(CloseMessage, FormatCloseMessage(CloseNormalClosure, ""), time.Now().Add(10*time.Second))
|
wc.WriteControl(CloseMessage, FormatCloseMessage(expectedErr.Code, expectedErr.Text), time.Now().Add(10*time.Second))
|
||||||
w.Close()
|
w.Close()
|
||||||
|
|
||||||
op, r, err := rc.NextReader()
|
op, r, err := rc.NextReader()
|
||||||
@ -160,12 +164,12 @@ func TestCloseBeforeFinalFrame(t *testing.T) {
|
|||||||
t.Fatalf("NextReader() returned %d, %v", op, err)
|
t.Fatalf("NextReader() returned %d, %v", op, err)
|
||||||
}
|
}
|
||||||
_, err = io.Copy(ioutil.Discard, r)
|
_, err = io.Copy(ioutil.Discard, r)
|
||||||
if err != errUnexpectedEOF {
|
if !reflect.DeepEqual(err, expectedErr) {
|
||||||
t.Fatalf("io.Copy() returned %v, want %v", err, errUnexpectedEOF)
|
t.Fatalf("io.Copy() returned %v, want %v", err, expectedErr)
|
||||||
}
|
}
|
||||||
_, _, err = rc.NextReader()
|
_, _, err = rc.NextReader()
|
||||||
if err != io.EOF {
|
if !reflect.DeepEqual(err, expectedErr) {
|
||||||
t.Fatalf("NextReader() returned %v, want %v", err, io.EOF)
|
t.Fatalf("NextReader() returned %v, want %v", err, expectedErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,3 +240,33 @@ func TestUnderlyingConn(t *testing.T) {
|
|||||||
t.Fatalf("Underlying conn is not what it should be.")
|
t.Fatalf("Underlying conn is not what it should be.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBufioReadBytes(t *testing.T) {
|
||||||
|
|
||||||
|
// Test calling bufio.ReadBytes for value longer than read buffer size.
|
||||||
|
|
||||||
|
m := make([]byte, 512)
|
||||||
|
m[len(m)-1] = '\n'
|
||||||
|
|
||||||
|
var b1, b2 bytes.Buffer
|
||||||
|
wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, len(m)+64, len(m)+64)
|
||||||
|
rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, len(m)-64, len(m)-64)
|
||||||
|
|
||||||
|
w, _ := wc.NextWriter(BinaryMessage)
|
||||||
|
w.Write(m)
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
op, r, err := rc.NextReader()
|
||||||
|
if op != BinaryMessage || err != nil {
|
||||||
|
t.Fatalf("NextReader() returned %d, %v", op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
br := bufio.NewReader(r)
|
||||||
|
p, err := br.ReadBytes('\n')
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadBytes() returned %v", err)
|
||||||
|
}
|
||||||
|
if len(p) != len(m) {
|
||||||
|
t.Fatalf("read returnd %d bytes, want %d bytes", len(p), len(m))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
13
Godeps/_workspace/src/github.com/gorilla/websocket/doc.go
generated
vendored
13
Godeps/_workspace/src/github.com/gorilla/websocket/doc.go
generated
vendored
@ -24,7 +24,7 @@
|
|||||||
// ... Use conn to send and receive messages.
|
// ... Use conn to send and receive messages.
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// Call the connection WriteMessage and ReadMessages methods to send and
|
// Call the connection's WriteMessage and ReadMessage methods to send and
|
||||||
// receive messages as a slice of bytes. This snippet of code shows how to echo
|
// receive messages as a slice of bytes. This snippet of code shows how to echo
|
||||||
// messages using these methods:
|
// messages using these methods:
|
||||||
//
|
//
|
||||||
@ -97,10 +97,13 @@
|
|||||||
//
|
//
|
||||||
// Concurrency
|
// Concurrency
|
||||||
//
|
//
|
||||||
// Connections do not support concurrent calls to the write methods
|
// Connections support one concurrent reader and one concurrent writer.
|
||||||
// (NextWriter, SetWriteDeadline, WriteMessage) or concurrent calls to the read
|
//
|
||||||
// methods methods (NextReader, SetReadDeadline, ReadMessage). Connections do
|
// Applications are responsible for ensuring that no more than one goroutine
|
||||||
// support a concurrent reader and writer.
|
// calls the write methods (NextWriter, SetWriteDeadline, WriteMessage,
|
||||||
|
// WriteJSON) concurrently and that no more than one goroutine calls the read
|
||||||
|
// methods (NextReader, SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler,
|
||||||
|
// SetPingHandler) concurrently.
|
||||||
//
|
//
|
||||||
// The Close and WriteControl methods can be called concurrently with all other
|
// The Close and WriteControl methods can be called concurrently with all other
|
||||||
// methods.
|
// methods.
|
||||||
|
1
Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/README.md
generated
vendored
1
Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/README.md
generated
vendored
@ -17,3 +17,4 @@ using the following commands.
|
|||||||
$ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/chat`
|
$ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/chat`
|
||||||
$ go run *.go
|
$ go run *.go
|
||||||
|
|
||||||
|
To use the chat example, open http://localhost:8080/ in your browser.
|
||||||
|
6
Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/conn.go
generated
vendored
6
Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/conn.go
generated
vendored
@ -88,12 +88,8 @@ func (c *connection) writePump() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// serverWs handles websocket requests from the peer.
|
// serveWs handles websocket requests from the peer.
|
||||||
func serveWs(w http.ResponseWriter, r *http.Request) {
|
func serveWs(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != "GET" {
|
|
||||||
http.Error(w, "Method not allowed", 405)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ws, err := upgrader.Upgrade(w, r, nil)
|
ws, err := upgrader.Upgrade(w, r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
|
19
Godeps/_workspace/src/github.com/gorilla/websocket/examples/command/README.md
generated
vendored
Normal file
19
Godeps/_workspace/src/github.com/gorilla/websocket/examples/command/README.md
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Command example
|
||||||
|
|
||||||
|
This example connects a websocket connection to stdin and stdout of a command.
|
||||||
|
Received messages are written to stdin followed by a `\n`. Each line read from
|
||||||
|
from standard out is sent as a message to the client.
|
||||||
|
|
||||||
|
$ go get github.com/gorilla/websocket
|
||||||
|
$ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/command`
|
||||||
|
$ go run main.go <command and arguments to run>
|
||||||
|
# Open http://localhost:8080/ .
|
||||||
|
|
||||||
|
Try the following commands.
|
||||||
|
|
||||||
|
# Echo sent messages to the output area.
|
||||||
|
$ go run main.go cat
|
||||||
|
|
||||||
|
# Run a shell.Try sending "ls" and "cat main.go".
|
||||||
|
$ go run main.go sh
|
||||||
|
|
96
Godeps/_workspace/src/github.com/gorilla/websocket/examples/command/home.html
generated
vendored
Normal file
96
Godeps/_workspace/src/github.com/gorilla/websocket/examples/command/home.html
generated
vendored
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Command Example</title>
|
||||||
|
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(function() {
|
||||||
|
|
||||||
|
var conn;
|
||||||
|
var msg = $("#msg");
|
||||||
|
var log = $("#log");
|
||||||
|
|
||||||
|
function appendLog(msg) {
|
||||||
|
var d = log[0]
|
||||||
|
var doScroll = d.scrollTop == d.scrollHeight - d.clientHeight;
|
||||||
|
msg.appendTo(log)
|
||||||
|
if (doScroll) {
|
||||||
|
d.scrollTop = d.scrollHeight - d.clientHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#form").submit(function() {
|
||||||
|
if (!conn) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!msg.val()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
conn.send(msg.val());
|
||||||
|
msg.val("");
|
||||||
|
return false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (window["WebSocket"]) {
|
||||||
|
conn = new WebSocket("ws://{{$}}/ws");
|
||||||
|
conn.onclose = function(evt) {
|
||||||
|
appendLog($("<div><b>Connection closed.</b></div>"))
|
||||||
|
}
|
||||||
|
conn.onmessage = function(evt) {
|
||||||
|
appendLog($("<pre/>").text(evt.data))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
appendLog($("<div><b>Your browser does not support WebSockets.</b></div>"))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style type="text/css">
|
||||||
|
html {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
#log {
|
||||||
|
background: white;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5em 0.5em 0.5em 0.5em;
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5em;
|
||||||
|
left: 0.5em;
|
||||||
|
right: 0.5em;
|
||||||
|
bottom: 3em;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#log pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#form {
|
||||||
|
padding: 0 0.5em 0 0.5em;
|
||||||
|
margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 1em;
|
||||||
|
left: 0px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="log"></div>
|
||||||
|
<form id="form">
|
||||||
|
<input type="submit" value="Send" />
|
||||||
|
<input type="text" id="msg" size="64"/>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
188
Godeps/_workspace/src/github.com/gorilla/websocket/examples/command/main.go
generated
vendored
Normal file
188
Godeps/_workspace/src/github.com/gorilla/websocket/examples/command/main.go
generated
vendored
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"flag"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
addr = flag.String("addr", "127.0.0.1:8080", "http service address")
|
||||||
|
cmdPath string
|
||||||
|
homeTempl = template.Must(template.ParseFiles("home.html"))
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Time allowed to write a message to the peer.
|
||||||
|
writeWait = 10 * time.Second
|
||||||
|
|
||||||
|
// Maximum message size allowed from peer.
|
||||||
|
maxMessageSize = 8192
|
||||||
|
|
||||||
|
// Time allowed to read the next pong message from the peer.
|
||||||
|
pongWait = 60 * time.Second
|
||||||
|
|
||||||
|
// Send pings to peer with this period. Must be less than pongWait.
|
||||||
|
pingPeriod = (pongWait * 9) / 10
|
||||||
|
)
|
||||||
|
|
||||||
|
func pumpStdin(ws *websocket.Conn, w io.Writer) {
|
||||||
|
defer ws.Close()
|
||||||
|
ws.SetReadLimit(maxMessageSize)
|
||||||
|
ws.SetReadDeadline(time.Now().Add(pongWait))
|
||||||
|
ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
|
||||||
|
for {
|
||||||
|
_, message, err := ws.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
message = append(message, '\n')
|
||||||
|
if _, err := w.Write(message); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pumpStdout(ws *websocket.Conn, r io.Reader, done chan struct{}) {
|
||||||
|
defer func() {
|
||||||
|
ws.Close()
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
s := bufio.NewScanner(r)
|
||||||
|
for s.Scan() {
|
||||||
|
ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
|
if err := ws.WriteMessage(websocket.TextMessage, s.Bytes()); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.Err() != nil {
|
||||||
|
log.Println("scan:", s.Err())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ping(ws *websocket.Conn, done chan struct{}) {
|
||||||
|
ticker := time.NewTicker(pingPeriod)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
if err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait)); err != nil {
|
||||||
|
log.Println("ping:", err)
|
||||||
|
}
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func internalError(ws *websocket.Conn, msg string, err error) {
|
||||||
|
log.Println(msg, err)
|
||||||
|
ws.WriteMessage(websocket.TextMessage, []byte("Internal server error."))
|
||||||
|
}
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{}
|
||||||
|
|
||||||
|
func serveWs(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ws, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("upgrade:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer ws.Close()
|
||||||
|
|
||||||
|
outr, outw, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
internalError(ws, "stdout:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer outr.Close()
|
||||||
|
defer outw.Close()
|
||||||
|
|
||||||
|
inr, inw, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
internalError(ws, "stdin:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer inr.Close()
|
||||||
|
defer inw.Close()
|
||||||
|
|
||||||
|
proc, err := os.StartProcess(cmdPath, flag.Args(), &os.ProcAttr{
|
||||||
|
Files: []*os.File{inr, outw, outw},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
internalError(ws, "start:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
inr.Close()
|
||||||
|
outw.Close()
|
||||||
|
|
||||||
|
stdoutDone := make(chan struct{})
|
||||||
|
go pumpStdout(ws, outr, stdoutDone)
|
||||||
|
go ping(ws, stdoutDone)
|
||||||
|
|
||||||
|
pumpStdin(ws, inw)
|
||||||
|
|
||||||
|
// Some commands will exit when stdin is closed.
|
||||||
|
inw.Close()
|
||||||
|
|
||||||
|
// Other commands need a bonk on the head.
|
||||||
|
if err := proc.Signal(os.Interrupt); err != nil {
|
||||||
|
log.Println("inter:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-stdoutDone:
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
// A bigger bonk on the head.
|
||||||
|
if err := proc.Signal(os.Kill); err != nil {
|
||||||
|
log.Println("term:", err)
|
||||||
|
}
|
||||||
|
<-stdoutDone
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := proc.Wait(); err != nil {
|
||||||
|
log.Println("wait:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveHome(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != "/" {
|
||||||
|
http.Error(w, "Not found", 404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Method != "GET" {
|
||||||
|
http.Error(w, "Method not allowed", 405)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
homeTempl.Execute(w, r.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
if len(flag.Args()) < 1 {
|
||||||
|
log.Fatal("must specify at least one argument")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
cmdPath, err = exec.LookPath(flag.Args()[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
http.HandleFunc("/", serveHome)
|
||||||
|
http.HandleFunc("/ws", serveWs)
|
||||||
|
log.Fatal(http.ListenAndServe(*addr, nil))
|
||||||
|
}
|
17
Godeps/_workspace/src/github.com/gorilla/websocket/examples/echo/README.md
generated
vendored
Normal file
17
Godeps/_workspace/src/github.com/gorilla/websocket/examples/echo/README.md
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Client and server example
|
||||||
|
|
||||||
|
This example shows a simple client and server.
|
||||||
|
|
||||||
|
The server echoes messages sent to it. The client sends a message every second
|
||||||
|
and prints all messages received.
|
||||||
|
|
||||||
|
To run the example, start the server:
|
||||||
|
|
||||||
|
$ go run server.go
|
||||||
|
|
||||||
|
Next, start the client:
|
||||||
|
|
||||||
|
$ go run client.go
|
||||||
|
|
||||||
|
The server includes a simple web client. To use the client, open
|
||||||
|
http://127.0.0.1:8080 in the browser and follow the instructions on the page.
|
81
Godeps/_workspace/src/github.com/gorilla/websocket/examples/echo/client.go
generated
vendored
Normal file
81
Godeps/_workspace/src/github.com/gorilla/websocket/examples/echo/client.go
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
var addr = flag.String("addr", "localhost:8080", "http service address")
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
log.SetFlags(0)
|
||||||
|
|
||||||
|
interrupt := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(interrupt, os.Interrupt)
|
||||||
|
|
||||||
|
u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo"}
|
||||||
|
log.Printf("connecting to %s", u.String())
|
||||||
|
|
||||||
|
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("dial:", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer c.Close()
|
||||||
|
defer close(done)
|
||||||
|
for {
|
||||||
|
_, message, err := c.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("read:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("recv: %s", message)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
ticker := time.NewTicker(time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case t := <-ticker.C:
|
||||||
|
err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("write:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-interrupt:
|
||||||
|
log.Println("interrupt")
|
||||||
|
// To cleanly close a connection, a client should send a close
|
||||||
|
// frame and wait for the server to close the connection.
|
||||||
|
err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("write close:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
}
|
||||||
|
c.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
132
Godeps/_workspace/src/github.com/gorilla/websocket/examples/echo/server.go
generated
vendored
Normal file
132
Godeps/_workspace/src/github.com/gorilla/websocket/examples/echo/server.go
generated
vendored
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"html/template"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
var addr = flag.String("addr", "localhost:8080", "http service address")
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{} // use default options
|
||||||
|
|
||||||
|
func echo(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Print("upgrade:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
for {
|
||||||
|
mt, message, err := c.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("read:", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Printf("recv: %s", message)
|
||||||
|
err = c.WriteMessage(mt, message)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("write:", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func home(w http.ResponseWriter, r *http.Request) {
|
||||||
|
homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
log.SetFlags(0)
|
||||||
|
http.HandleFunc("/echo", echo)
|
||||||
|
http.HandleFunc("/", home)
|
||||||
|
log.Fatal(http.ListenAndServe(*addr, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
var homeTemplate = template.Must(template.New("").Parse(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<script>
|
||||||
|
window.addEventListener("load", function(evt) {
|
||||||
|
|
||||||
|
var output = document.getElementById("output");
|
||||||
|
var input = document.getElementById("input");
|
||||||
|
var ws;
|
||||||
|
|
||||||
|
var print = function(message) {
|
||||||
|
var d = document.createElement("div");
|
||||||
|
d.innerHTML = message;
|
||||||
|
output.appendChild(d);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById("open").onclick = function(evt) {
|
||||||
|
if (ws) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ws = new WebSocket("{{.}}");
|
||||||
|
ws.onopen = function(evt) {
|
||||||
|
print("OPEN");
|
||||||
|
}
|
||||||
|
ws.onclose = function(evt) {
|
||||||
|
print("CLOSE");
|
||||||
|
ws = null;
|
||||||
|
}
|
||||||
|
ws.onmessage = function(evt) {
|
||||||
|
print("RESPONSE: " + evt.data);
|
||||||
|
}
|
||||||
|
ws.onerror = function(evt) {
|
||||||
|
print("ERROR: " + evt.data);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById("send").onclick = function(evt) {
|
||||||
|
if (!ws) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
print("SEND: " + input.value);
|
||||||
|
ws.send(input.value);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById("close").onclick = function(evt) {
|
||||||
|
if (!ws) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ws.close();
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table>
|
||||||
|
<tr><td valign="top" width="50%">
|
||||||
|
<p>Click "Open" to create a connection to the server,
|
||||||
|
"Send" to send a message to the server and "Close" to close the connection.
|
||||||
|
You can change the message and send multiple times.
|
||||||
|
<p>
|
||||||
|
<form>
|
||||||
|
<button id="open">Open</button>
|
||||||
|
<button id="close">Close</button>
|
||||||
|
<p><input id="input" type="text" value="Hello world!">
|
||||||
|
<button id="send">Send</button>
|
||||||
|
</form>
|
||||||
|
</td><td valign="top" width="50%">
|
||||||
|
<div id="output"></div>
|
||||||
|
</td></tr></table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`))
|
4
Godeps/_workspace/src/github.com/gorilla/websocket/json.go
generated
vendored
4
Godeps/_workspace/src/github.com/gorilla/websocket/json.go
generated
vendored
@ -48,9 +48,7 @@ func (c *Conn) ReadJSON(v interface{}) error {
|
|||||||
}
|
}
|
||||||
err = json.NewDecoder(r).Decode(v)
|
err = json.NewDecoder(r).Decode(v)
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
// Decode returns io.EOF when the message is empty or all whitespace.
|
// One value is expected in the message.
|
||||||
// Convert to io.ErrUnexpectedEOF so that application can distinguish
|
|
||||||
// between an error reading the JSON value and the connection closing.
|
|
||||||
err = io.ErrUnexpectedEOF
|
err = io.ErrUnexpectedEOF
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
4
Godeps/_workspace/src/github.com/gorilla/websocket/json_test.go
generated
vendored
4
Godeps/_workspace/src/github.com/gorilla/websocket/json_test.go
generated
vendored
@ -38,7 +38,7 @@ func TestJSON(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPartialJsonRead(t *testing.T) {
|
func TestPartialJSONRead(t *testing.T) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
c := fakeNetConn{&buf, &buf}
|
c := fakeNetConn{&buf, &buf}
|
||||||
wc := newConn(c, true, 1024, 1024)
|
wc := newConn(c, true, 1024, 1024)
|
||||||
@ -87,7 +87,7 @@ func TestPartialJsonRead(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = rc.ReadJSON(&v)
|
err = rc.ReadJSON(&v)
|
||||||
if err != io.EOF {
|
if _, ok := err.(*CloseError); !ok {
|
||||||
t.Error("final", err)
|
t.Error("final", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3
Godeps/_workspace/src/github.com/gorilla/websocket/server.go
generated
vendored
3
Godeps/_workspace/src/github.com/gorilla/websocket/server.go
generated
vendored
@ -93,6 +93,9 @@ func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header
|
|||||||
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
|
// request. Use the responseHeader to specify cookies (Set-Cookie) and the
|
||||||
// application negotiated subprotocol (Sec-Websocket-Protocol).
|
// application negotiated subprotocol (Sec-Websocket-Protocol).
|
||||||
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) {
|
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) {
|
||||||
|
if r.Method != "GET" {
|
||||||
|
return u.returnError(w, r, http.StatusMethodNotAllowed, "websocket: method not GET")
|
||||||
|
}
|
||||||
if values := r.Header["Sec-Websocket-Version"]; len(values) == 0 || values[0] != "13" {
|
if values := r.Header["Sec-Websocket-Version"]; len(values) == 0 || values[0] != "13" {
|
||||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: version != 13")
|
return u.returnError(w, r, http.StatusBadRequest, "websocket: version != 13")
|
||||||
}
|
}
|
||||||
|
58
Godeps/_workspace/src/github.com/mitchellh/go-homedir/homedir.go
generated
vendored
58
Godeps/_workspace/src/github.com/mitchellh/go-homedir/homedir.go
generated
vendored
@ -7,20 +7,49 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DisableCache will disable caching of the home directory. Caching is enabled
|
||||||
|
// by default.
|
||||||
|
var DisableCache bool
|
||||||
|
|
||||||
|
var homedirCache string
|
||||||
|
var cacheLock sync.RWMutex
|
||||||
|
|
||||||
// Dir returns the home directory for the executing user.
|
// Dir returns the home directory for the executing user.
|
||||||
//
|
//
|
||||||
// This uses an OS-specific method for discovering the home directory.
|
// This uses an OS-specific method for discovering the home directory.
|
||||||
// An error is returned if a home directory cannot be detected.
|
// An error is returned if a home directory cannot be detected.
|
||||||
func Dir() (string, error) {
|
func Dir() (string, error) {
|
||||||
if runtime.GOOS == "windows" {
|
if !DisableCache {
|
||||||
return dirWindows()
|
cacheLock.RLock()
|
||||||
|
cached := homedirCache
|
||||||
|
cacheLock.RUnlock()
|
||||||
|
if cached != "" {
|
||||||
|
return cached, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cacheLock.Lock()
|
||||||
|
defer cacheLock.Unlock()
|
||||||
|
|
||||||
|
var result string
|
||||||
|
var err error
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
result, err = dirWindows()
|
||||||
|
} else {
|
||||||
// Unix-like system, so just assume Unix
|
// Unix-like system, so just assume Unix
|
||||||
return dirUnix()
|
result, err = dirUnix()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
homedirCache = result
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expand expands the path to include the home directory if the path
|
// Expand expands the path to include the home directory if the path
|
||||||
@ -53,9 +82,28 @@ func dirUnix() (string, error) {
|
|||||||
return home, nil
|
return home, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If that fails, try the shell
|
// If that fails, try getent
|
||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
cmd := exec.Command("sh", "-c", "eval echo ~$USER")
|
cmd := exec.Command("getent", "passwd", strconv.Itoa(os.Getuid()))
|
||||||
|
cmd.Stdout = &stdout
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
// If "getent" is missing, ignore it
|
||||||
|
if err != exec.ErrNotFound {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if passwd := strings.TrimSpace(stdout.String()); passwd != "" {
|
||||||
|
// username:password:uid:gid:gecos:home:shell
|
||||||
|
passwdParts := strings.SplitN(passwd, ":", 7)
|
||||||
|
if len(passwdParts) > 5 {
|
||||||
|
return passwdParts[5], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all else fails, try the shell
|
||||||
|
stdout.Reset()
|
||||||
|
cmd = exec.Command("sh", "-c", "cd && pwd")
|
||||||
cmd.Stdout = &stdout
|
cmd.Stdout = &stdout
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
14
Godeps/_workspace/src/github.com/mitchellh/go-homedir/homedir_test.go
generated
vendored
14
Godeps/_workspace/src/github.com/mitchellh/go-homedir/homedir_test.go
generated
vendored
@ -17,6 +17,18 @@ func patchEnv(key, value string) func() {
|
|||||||
return deferFunc
|
return deferFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkDir(b *testing.B) {
|
||||||
|
// We do this for any "warmups"
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
Dir()
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Dir()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDir(t *testing.T) {
|
func TestDir(t *testing.T) {
|
||||||
u, err := user.Current()
|
u, err := user.Current()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -86,6 +98,8 @@ func TestExpand(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DisableCache = true
|
||||||
|
defer func() { DisableCache = false }()
|
||||||
defer patchEnv("HOME", "/custom/path/")()
|
defer patchEnv("HOME", "/custom/path/")()
|
||||||
expected := "/custom/path/foo/bar"
|
expected := "/custom/path/foo/bar"
|
||||||
actual, err := Expand("~/foo/bar")
|
actual, err := Expand("~/foo/bar")
|
||||||
|
5
Godeps/_workspace/src/github.com/spf13/cast/cast.go
generated
vendored
5
Godeps/_workspace/src/github.com/spf13/cast/cast.go
generated
vendored
@ -42,6 +42,11 @@ func ToStringMapString(i interface{}) map[string]string {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ToStringMapStringSlice(i interface{}) map[string][]string {
|
||||||
|
v, _ := ToStringMapStringSliceE(i)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
func ToStringMapBool(i interface{}) map[string]bool {
|
func ToStringMapBool(i interface{}) map[string]bool {
|
||||||
v, _ := ToStringMapBoolE(i)
|
v, _ := ToStringMapBoolE(i)
|
||||||
return v
|
return v
|
||||||
|
51
Godeps/_workspace/src/github.com/spf13/cast/cast_test.go
generated
vendored
51
Godeps/_workspace/src/github.com/spf13/cast/cast_test.go
generated
vendored
@ -6,9 +6,9 @@
|
|||||||
package cast
|
package cast
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
|
||||||
|
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/stretchr/testify/assert"
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@ -37,8 +37,11 @@ func TestToString(t *testing.T) {
|
|||||||
assert.Equal(t, ToString(8.12), "8.12")
|
assert.Equal(t, ToString(8.12), "8.12")
|
||||||
assert.Equal(t, ToString([]byte("one time")), "one time")
|
assert.Equal(t, ToString([]byte("one time")), "one time")
|
||||||
assert.Equal(t, ToString(template.HTML("one time")), "one time")
|
assert.Equal(t, ToString(template.HTML("one time")), "one time")
|
||||||
|
assert.Equal(t, ToString(template.URL("http://somehost.foo")), "http://somehost.foo")
|
||||||
assert.Equal(t, ToString(foo), "one more time")
|
assert.Equal(t, ToString(foo), "one more time")
|
||||||
assert.Equal(t, ToString(nil), "")
|
assert.Equal(t, ToString(nil), "")
|
||||||
|
assert.Equal(t, ToString(true), "true")
|
||||||
|
assert.Equal(t, ToString(false), "false")
|
||||||
}
|
}
|
||||||
|
|
||||||
type foo struct {
|
type foo struct {
|
||||||
@ -73,8 +76,43 @@ func TestErrorToString(t *testing.T) {
|
|||||||
func TestMaps(t *testing.T) {
|
func TestMaps(t *testing.T) {
|
||||||
var taxonomies = map[interface{}]interface{}{"tag": "tags", "group": "groups"}
|
var taxonomies = map[interface{}]interface{}{"tag": "tags", "group": "groups"}
|
||||||
var stringMapBool = map[interface{}]interface{}{"v1": true, "v2": false}
|
var stringMapBool = map[interface{}]interface{}{"v1": true, "v2": false}
|
||||||
|
|
||||||
|
// ToStringMapString inputs/outputs
|
||||||
|
var stringMapString = map[string]string{"key 1": "value 1", "key 2": "value 2", "key 3": "value 3"}
|
||||||
|
var stringMapInterface = map[string]interface{}{"key 1": "value 1", "key 2": "value 2", "key 3": "value 3"}
|
||||||
|
var interfaceMapString = map[interface{}]string{"key 1": "value 1", "key 2": "value 2", "key 3": "value 3"}
|
||||||
|
var interfaceMapInterface = map[interface{}]interface{}{"key 1": "value 1", "key 2": "value 2", "key 3": "value 3"}
|
||||||
|
|
||||||
|
// ToStringMapStringSlice inputs/outputs
|
||||||
|
var stringMapStringSlice = map[string][]string{"key 1": []string{"value 1", "value 2", "value 3"}, "key 2": []string{"value 1", "value 2", "value 3"}, "key 3": []string{"value 1", "value 2", "value 3"}}
|
||||||
|
var stringMapInterfaceSlice = map[string][]interface{}{"key 1": []interface{}{"value 1", "value 2", "value 3"}, "key 2": []interface{}{"value 1", "value 2", "value 3"}, "key 3": []interface{}{"value 1", "value 2", "value 3"}}
|
||||||
|
var stringMapStringSingleSliceFieldsResult = map[string][]string{"key 1": []string{"value", "1"}, "key 2": []string{"value", "2"}, "key 3": []string{"value", "3"}}
|
||||||
|
var interfaceMapStringSlice = map[interface{}][]string{"key 1": []string{"value 1", "value 2", "value 3"}, "key 2": []string{"value 1", "value 2", "value 3"}, "key 3": []string{"value 1", "value 2", "value 3"}}
|
||||||
|
var interfaceMapInterfaceSlice = map[interface{}][]interface{}{"key 1": []interface{}{"value 1", "value 2", "value 3"}, "key 2": []interface{}{"value 1", "value 2", "value 3"}, "key 3": []interface{}{"value 1", "value 2", "value 3"}}
|
||||||
|
|
||||||
|
var stringMapStringSliceMultiple = map[string][]string{"key 1": []string{"value 1", "value 2", "value 3"}, "key 2": []string{"value 1", "value 2", "value 3"}, "key 3": []string{"value 1", "value 2", "value 3"}}
|
||||||
|
var stringMapStringSliceSingle = map[string][]string{"key 1": []string{"value 1"}, "key 2": []string{"value 2"}, "key 3": []string{"value 3"}}
|
||||||
|
|
||||||
assert.Equal(t, ToStringMap(taxonomies), map[string]interface{}{"tag": "tags", "group": "groups"})
|
assert.Equal(t, ToStringMap(taxonomies), map[string]interface{}{"tag": "tags", "group": "groups"})
|
||||||
assert.Equal(t, ToStringMapBool(stringMapBool), map[string]bool{"v1": true, "v2": false})
|
assert.Equal(t, ToStringMapBool(stringMapBool), map[string]bool{"v1": true, "v2": false})
|
||||||
|
|
||||||
|
// ToStringMapString tests
|
||||||
|
assert.Equal(t, ToStringMapString(stringMapString), stringMapString)
|
||||||
|
assert.Equal(t, ToStringMapString(stringMapInterface), stringMapString)
|
||||||
|
assert.Equal(t, ToStringMapString(interfaceMapString), stringMapString)
|
||||||
|
assert.Equal(t, ToStringMapString(interfaceMapInterface), stringMapString)
|
||||||
|
|
||||||
|
// ToStringMapStringSlice tests
|
||||||
|
assert.Equal(t, ToStringMapStringSlice(stringMapStringSlice), stringMapStringSlice)
|
||||||
|
assert.Equal(t, ToStringMapStringSlice(stringMapInterfaceSlice), stringMapStringSlice)
|
||||||
|
assert.Equal(t, ToStringMapStringSlice(stringMapStringSliceMultiple), stringMapStringSlice)
|
||||||
|
assert.Equal(t, ToStringMapStringSlice(stringMapStringSliceMultiple), stringMapStringSlice)
|
||||||
|
assert.Equal(t, ToStringMapStringSlice(stringMapString), stringMapStringSliceSingle)
|
||||||
|
assert.Equal(t, ToStringMapStringSlice(stringMapInterface), stringMapStringSliceSingle)
|
||||||
|
assert.Equal(t, ToStringMapStringSlice(interfaceMapStringSlice), stringMapStringSlice)
|
||||||
|
assert.Equal(t, ToStringMapStringSlice(interfaceMapInterfaceSlice), stringMapStringSlice)
|
||||||
|
assert.Equal(t, ToStringMapStringSlice(interfaceMapString), stringMapStringSingleSliceFieldsResult)
|
||||||
|
assert.Equal(t, ToStringMapStringSlice(interfaceMapInterface), stringMapStringSingleSliceFieldsResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSlices(t *testing.T) {
|
func TestSlices(t *testing.T) {
|
||||||
@ -115,3 +153,12 @@ func TestIndirectPointers(t *testing.T) {
|
|||||||
assert.Equal(t, ToInt(y), 13)
|
assert.Equal(t, ToInt(y), 13)
|
||||||
assert.Equal(t, ToInt(z), 13)
|
assert.Equal(t, ToInt(z), 13)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestToDuration(t *testing.T) {
|
||||||
|
a := time.Second * 5
|
||||||
|
ai := int64(a)
|
||||||
|
b := time.Second * 5
|
||||||
|
bf := float64(b)
|
||||||
|
assert.Equal(t, ToDuration(ai), a)
|
||||||
|
assert.Equal(t, ToDuration(bf), b)
|
||||||
|
}
|
||||||
|
128
Godeps/_workspace/src/github.com/spf13/cast/caste.go
generated
vendored
128
Godeps/_workspace/src/github.com/spf13/cast/caste.go
generated
vendored
@ -6,7 +6,6 @@
|
|||||||
package cast
|
package cast
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -17,6 +16,7 @@ import (
|
|||||||
jww "github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/jwalterweatherman"
|
jww "github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/jwalterweatherman"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ToTimeE casts an empty interface to time.Time.
|
||||||
func ToTimeE(i interface{}) (tim time.Time, err error) {
|
func ToTimeE(i interface{}) (tim time.Time, err error) {
|
||||||
i = indirect(i)
|
i = indirect(i)
|
||||||
jww.DEBUG.Println("ToTimeE called on type:", reflect.TypeOf(i))
|
jww.DEBUG.Println("ToTimeE called on type:", reflect.TypeOf(i))
|
||||||
@ -35,6 +35,7 @@ func ToTimeE(i interface{}) (tim time.Time, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToDurationE casts an empty interface to time.Duration.
|
||||||
func ToDurationE(i interface{}) (d time.Duration, err error) {
|
func ToDurationE(i interface{}) (d time.Duration, err error) {
|
||||||
i = indirect(i)
|
i = indirect(i)
|
||||||
jww.DEBUG.Println("ToDurationE called on type:", reflect.TypeOf(i))
|
jww.DEBUG.Println("ToDurationE called on type:", reflect.TypeOf(i))
|
||||||
@ -42,6 +43,12 @@ func ToDurationE(i interface{}) (d time.Duration, err error) {
|
|||||||
switch s := i.(type) {
|
switch s := i.(type) {
|
||||||
case time.Duration:
|
case time.Duration:
|
||||||
return s, nil
|
return s, nil
|
||||||
|
case int64:
|
||||||
|
d = time.Duration(s)
|
||||||
|
return
|
||||||
|
case float64:
|
||||||
|
d = time.Duration(s)
|
||||||
|
return
|
||||||
case string:
|
case string:
|
||||||
d, err = time.ParseDuration(s)
|
d, err = time.ParseDuration(s)
|
||||||
return
|
return
|
||||||
@ -51,6 +58,7 @@ func ToDurationE(i interface{}) (d time.Duration, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToBoolE casts an empty interface to a bool.
|
||||||
func ToBoolE(i interface{}) (bool, error) {
|
func ToBoolE(i interface{}) (bool, error) {
|
||||||
i = indirect(i)
|
i = indirect(i)
|
||||||
jww.DEBUG.Println("ToBoolE called on type:", reflect.TypeOf(i))
|
jww.DEBUG.Println("ToBoolE called on type:", reflect.TypeOf(i))
|
||||||
@ -72,6 +80,7 @@ func ToBoolE(i interface{}) (bool, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToFloat64E casts an empty interface to a float64.
|
||||||
func ToFloat64E(i interface{}) (float64, error) {
|
func ToFloat64E(i interface{}) (float64, error) {
|
||||||
i = indirect(i)
|
i = indirect(i)
|
||||||
jww.DEBUG.Println("ToFloat64E called on type:", reflect.TypeOf(i))
|
jww.DEBUG.Println("ToFloat64E called on type:", reflect.TypeOf(i))
|
||||||
@ -95,14 +104,14 @@ func ToFloat64E(i interface{}) (float64, error) {
|
|||||||
v, err := strconv.ParseFloat(s, 64)
|
v, err := strconv.ParseFloat(s, 64)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return float64(v), nil
|
return float64(v), nil
|
||||||
} else {
|
|
||||||
return 0.0, fmt.Errorf("Unable to Cast %#v to float", i)
|
|
||||||
}
|
}
|
||||||
|
return 0.0, fmt.Errorf("Unable to Cast %#v to float", i)
|
||||||
default:
|
default:
|
||||||
return 0.0, fmt.Errorf("Unable to Cast %#v to float", i)
|
return 0.0, fmt.Errorf("Unable to Cast %#v to float", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToIntE casts an empty interface to an int.
|
||||||
func ToIntE(i interface{}) (int, error) {
|
func ToIntE(i interface{}) (int, error) {
|
||||||
i = indirect(i)
|
i = indirect(i)
|
||||||
jww.DEBUG.Println("ToIntE called on type:", reflect.TypeOf(i))
|
jww.DEBUG.Println("ToIntE called on type:", reflect.TypeOf(i))
|
||||||
@ -122,17 +131,15 @@ func ToIntE(i interface{}) (int, error) {
|
|||||||
v, err := strconv.ParseInt(s, 0, 0)
|
v, err := strconv.ParseInt(s, 0, 0)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return int(v), nil
|
return int(v), nil
|
||||||
} else {
|
|
||||||
return 0, fmt.Errorf("Unable to Cast %#v to int", i)
|
|
||||||
}
|
}
|
||||||
|
return 0, fmt.Errorf("Unable to Cast %#v to int", i)
|
||||||
case float64:
|
case float64:
|
||||||
return int(s), nil
|
return int(s), nil
|
||||||
case bool:
|
case bool:
|
||||||
if bool(s) {
|
if bool(s) {
|
||||||
return 1, nil
|
return 1, nil
|
||||||
} else {
|
|
||||||
return 0, nil
|
|
||||||
}
|
}
|
||||||
|
return 0, nil
|
||||||
case nil:
|
case nil:
|
||||||
return 0, nil
|
return 0, nil
|
||||||
default:
|
default:
|
||||||
@ -179,6 +186,7 @@ func indirectToStringerOrError(a interface{}) interface{} {
|
|||||||
return v.Interface()
|
return v.Interface()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToStringE casts an empty interface to a string.
|
||||||
func ToStringE(i interface{}) (string, error) {
|
func ToStringE(i interface{}) (string, error) {
|
||||||
i = indirectToStringerOrError(i)
|
i = indirectToStringerOrError(i)
|
||||||
jww.DEBUG.Println("ToStringE called on type:", reflect.TypeOf(i))
|
jww.DEBUG.Println("ToStringE called on type:", reflect.TypeOf(i))
|
||||||
@ -186,6 +194,8 @@ func ToStringE(i interface{}) (string, error) {
|
|||||||
switch s := i.(type) {
|
switch s := i.(type) {
|
||||||
case string:
|
case string:
|
||||||
return s, nil
|
return s, nil
|
||||||
|
case bool:
|
||||||
|
return strconv.FormatBool(s), nil
|
||||||
case float64:
|
case float64:
|
||||||
return strconv.FormatFloat(i.(float64), 'f', -1, 64), nil
|
return strconv.FormatFloat(i.(float64), 'f', -1, 64), nil
|
||||||
case int:
|
case int:
|
||||||
@ -194,6 +204,8 @@ func ToStringE(i interface{}) (string, error) {
|
|||||||
return string(s), nil
|
return string(s), nil
|
||||||
case template.HTML:
|
case template.HTML:
|
||||||
return string(s), nil
|
return string(s), nil
|
||||||
|
case template.URL:
|
||||||
|
return string(s), nil
|
||||||
case nil:
|
case nil:
|
||||||
return "", nil
|
return "", nil
|
||||||
case fmt.Stringer:
|
case fmt.Stringer:
|
||||||
@ -205,30 +217,92 @@ func ToStringE(i interface{}) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToStringMapStringE casts an empty interface to a map[string]string.
|
||||||
func ToStringMapStringE(i interface{}) (map[string]string, error) {
|
func ToStringMapStringE(i interface{}) (map[string]string, error) {
|
||||||
jww.DEBUG.Println("ToStringMapStringE called on type:", reflect.TypeOf(i))
|
jww.DEBUG.Println("ToStringMapStringE called on type:", reflect.TypeOf(i))
|
||||||
|
|
||||||
var m = map[string]string{}
|
var m = map[string]string{}
|
||||||
|
|
||||||
switch v := i.(type) {
|
switch v := i.(type) {
|
||||||
case map[interface{}]interface{}:
|
case map[string]string:
|
||||||
for k, val := range v {
|
return v, nil
|
||||||
m[ToString(k)] = ToString(val)
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
for k, val := range v {
|
for k, val := range v {
|
||||||
m[ToString(k)] = ToString(val)
|
m[ToString(k)] = ToString(val)
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
case map[string]string:
|
case map[interface{}]string:
|
||||||
return v, nil
|
for k, val := range v {
|
||||||
|
m[ToString(k)] = ToString(val)
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
case map[interface{}]interface{}:
|
||||||
|
for k, val := range v {
|
||||||
|
m[ToString(k)] = ToString(val)
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
default:
|
default:
|
||||||
return m, fmt.Errorf("Unable to Cast %#v to map[string]string", i)
|
return m, fmt.Errorf("Unable to Cast %#v to map[string]string", i)
|
||||||
}
|
}
|
||||||
return m, fmt.Errorf("Unable to Cast %#v to map[string]string", i)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToStringMapStringSliceE casts an empty interface to a map[string][]string.
|
||||||
|
func ToStringMapStringSliceE(i interface{}) (map[string][]string, error) {
|
||||||
|
jww.DEBUG.Println("ToStringMapStringSliceE called on type:", reflect.TypeOf(i))
|
||||||
|
|
||||||
|
var m = map[string][]string{}
|
||||||
|
|
||||||
|
switch v := i.(type) {
|
||||||
|
case map[string][]string:
|
||||||
|
return v, nil
|
||||||
|
case map[string][]interface{}:
|
||||||
|
for k, val := range v {
|
||||||
|
m[ToString(k)] = ToStringSlice(val)
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
case map[string]string:
|
||||||
|
for k, val := range v {
|
||||||
|
m[ToString(k)] = []string{val}
|
||||||
|
}
|
||||||
|
case map[string]interface{}:
|
||||||
|
for k, val := range v {
|
||||||
|
m[ToString(k)] = []string{ToString(val)}
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
case map[interface{}][]string:
|
||||||
|
for k, val := range v {
|
||||||
|
m[ToString(k)] = ToStringSlice(val)
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
case map[interface{}]string:
|
||||||
|
for k, val := range v {
|
||||||
|
m[ToString(k)] = ToStringSlice(val)
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
case map[interface{}][]interface{}:
|
||||||
|
for k, val := range v {
|
||||||
|
m[ToString(k)] = ToStringSlice(val)
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
case map[interface{}]interface{}:
|
||||||
|
for k, val := range v {
|
||||||
|
key, err := ToStringE(k)
|
||||||
|
if err != nil {
|
||||||
|
return m, fmt.Errorf("Unable to Cast %#v to map[string][]string", i)
|
||||||
|
}
|
||||||
|
value, err := ToStringSliceE(val)
|
||||||
|
if err != nil {
|
||||||
|
return m, fmt.Errorf("Unable to Cast %#v to map[string][]string", i)
|
||||||
|
}
|
||||||
|
m[key] = value
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return m, fmt.Errorf("Unable to Cast %#v to map[string][]string", i)
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToStringMapBoolE casts an empty interface to a map[string]bool.
|
||||||
func ToStringMapBoolE(i interface{}) (map[string]bool, error) {
|
func ToStringMapBoolE(i interface{}) (map[string]bool, error) {
|
||||||
jww.DEBUG.Println("ToStringMapBoolE called on type:", reflect.TypeOf(i))
|
jww.DEBUG.Println("ToStringMapBoolE called on type:", reflect.TypeOf(i))
|
||||||
|
|
||||||
@ -250,9 +324,9 @@ func ToStringMapBoolE(i interface{}) (map[string]bool, error) {
|
|||||||
default:
|
default:
|
||||||
return m, fmt.Errorf("Unable to Cast %#v to map[string]bool", i)
|
return m, fmt.Errorf("Unable to Cast %#v to map[string]bool", i)
|
||||||
}
|
}
|
||||||
return m, fmt.Errorf("Unable to Cast %#v to map[string]bool", i)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToStringMapE casts an empty interface to a map[string]interface{}.
|
||||||
func ToStringMapE(i interface{}) (map[string]interface{}, error) {
|
func ToStringMapE(i interface{}) (map[string]interface{}, error) {
|
||||||
jww.DEBUG.Println("ToStringMapE called on type:", reflect.TypeOf(i))
|
jww.DEBUG.Println("ToStringMapE called on type:", reflect.TypeOf(i))
|
||||||
|
|
||||||
@ -269,10 +343,9 @@ func ToStringMapE(i interface{}) (map[string]interface{}, error) {
|
|||||||
default:
|
default:
|
||||||
return m, fmt.Errorf("Unable to Cast %#v to map[string]interface{}", i)
|
return m, fmt.Errorf("Unable to Cast %#v to map[string]interface{}", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
return m, fmt.Errorf("Unable to Cast %#v to map[string]interface{}", i)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToSliceE casts an empty interface to a []interface{}.
|
||||||
func ToSliceE(i interface{}) ([]interface{}, error) {
|
func ToSliceE(i interface{}) ([]interface{}, error) {
|
||||||
jww.DEBUG.Println("ToSliceE called on type:", reflect.TypeOf(i))
|
jww.DEBUG.Println("ToSliceE called on type:", reflect.TypeOf(i))
|
||||||
|
|
||||||
@ -292,10 +365,9 @@ func ToSliceE(i interface{}) ([]interface{}, error) {
|
|||||||
default:
|
default:
|
||||||
return s, fmt.Errorf("Unable to Cast %#v of type %v to []interface{}", i, reflect.TypeOf(i))
|
return s, fmt.Errorf("Unable to Cast %#v of type %v to []interface{}", i, reflect.TypeOf(i))
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, fmt.Errorf("Unable to Cast %#v to []interface{}", i)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToStringSliceE casts an empty interface to a []string.
|
||||||
func ToStringSliceE(i interface{}) ([]string, error) {
|
func ToStringSliceE(i interface{}) ([]string, error) {
|
||||||
jww.DEBUG.Println("ToStringSliceE called on type:", reflect.TypeOf(i))
|
jww.DEBUG.Println("ToStringSliceE called on type:", reflect.TypeOf(i))
|
||||||
|
|
||||||
@ -311,13 +383,18 @@ func ToStringSliceE(i interface{}) ([]string, error) {
|
|||||||
return v, nil
|
return v, nil
|
||||||
case string:
|
case string:
|
||||||
return strings.Fields(v), nil
|
return strings.Fields(v), nil
|
||||||
|
case interface{}:
|
||||||
|
str, err := ToStringE(v)
|
||||||
|
if err != nil {
|
||||||
|
return a, fmt.Errorf("Unable to Cast %#v to []string", i)
|
||||||
|
}
|
||||||
|
return []string{str}, nil
|
||||||
default:
|
default:
|
||||||
return a, fmt.Errorf("Unable to Cast %#v to []string", i)
|
return a, fmt.Errorf("Unable to Cast %#v to []string", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
return a, fmt.Errorf("Unable to Cast %#v to []string", i)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToIntSliceE casts an empty interface to a []int.
|
||||||
func ToIntSliceE(i interface{}) ([]int, error) {
|
func ToIntSliceE(i interface{}) ([]int, error) {
|
||||||
jww.DEBUG.Println("ToIntSliceE called on type:", reflect.TypeOf(i))
|
jww.DEBUG.Println("ToIntSliceE called on type:", reflect.TypeOf(i))
|
||||||
|
|
||||||
@ -346,10 +423,9 @@ func ToIntSliceE(i interface{}) ([]int, error) {
|
|||||||
default:
|
default:
|
||||||
return []int{}, fmt.Errorf("Unable to Cast %#v to []int", i)
|
return []int{}, fmt.Errorf("Unable to Cast %#v to []int", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
return []int{}, fmt.Errorf("Unable to Cast %#v to []int", i)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StringToDate casts an empty interface to a time.Time.
|
||||||
func StringToDate(s string) (time.Time, error) {
|
func StringToDate(s string) (time.Time, error) {
|
||||||
return parseDateWith(s, []string{
|
return parseDateWith(s, []string{
|
||||||
time.RFC3339,
|
time.RFC3339,
|
||||||
@ -365,6 +441,8 @@ func StringToDate(s string) (time.Time, error) {
|
|||||||
"02 Jan 06 15:04 MST",
|
"02 Jan 06 15:04 MST",
|
||||||
"2006-01-02",
|
"2006-01-02",
|
||||||
"02 Jan 2006",
|
"02 Jan 2006",
|
||||||
|
"2006-01-02 15:04:05 -07:00",
|
||||||
|
"2006-01-02 15:04:05 -0700",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,5 +452,5 @@ func parseDateWith(s string, dates []string) (d time.Time, e error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return d, errors.New(fmt.Sprintf("Unable to parse date: %s", s))
|
return d, fmt.Errorf("Unable to parse date: %s", s)
|
||||||
}
|
}
|
||||||
|
3
Godeps/_workspace/src/github.com/spf13/cobra/.mailmap
generated
vendored
Normal file
3
Godeps/_workspace/src/github.com/spf13/cobra/.mailmap
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Steve Francia <steve.francia@gmail.com>
|
||||||
|
Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
|
||||||
|
Fabiano Franz <ffranz@redhat.com> <contact@fabianofranz.com>
|
5
Godeps/_workspace/src/github.com/spf13/cobra/.travis.yml
generated
vendored
5
Godeps/_workspace/src/github.com/spf13/cobra/.travis.yml
generated
vendored
@ -1,8 +1,9 @@
|
|||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.3
|
- 1.3.3
|
||||||
- 1.4.2
|
- 1.4.2
|
||||||
|
- 1.5.1
|
||||||
- tip
|
- tip
|
||||||
script:
|
script:
|
||||||
- go test ./...
|
- go test -v ./...
|
||||||
- go build
|
- go build
|
||||||
|
604
Godeps/_workspace/src/github.com/spf13/cobra/README.md
generated
vendored
604
Godeps/_workspace/src/github.com/spf13/cobra/README.md
generated
vendored
@ -1,95 +1,255 @@
|
|||||||
# Cobra
|

|
||||||
|
|
||||||
A Commander for modern go CLI interactions
|
Cobra is both a library for creating powerful modern CLI applications as well as a program to generate applications and command files.
|
||||||
|
|
||||||
[](https://travis-ci.org/spf13/cobra)
|
Many of the most widely used Go projects are built using Cobra including:
|
||||||
|
|
||||||
## Overview
|
* [Kubernetes](http://kubernetes.io/)
|
||||||
|
* [Hugo](http://gohugo.io)
|
||||||
|
* [rkt](https://github.com/coreos/rkt)
|
||||||
|
* [etcd](https://github.com/coreos/etcd)
|
||||||
|
* [Docker (distribution)](https://github.com/docker/distribution)
|
||||||
|
* [OpenShift](https://www.openshift.com/)
|
||||||
|
* [Delve](https://github.com/derekparker/delve)
|
||||||
|
* [GopherJS](http://www.gopherjs.org/)
|
||||||
|
* [CockroachDB](http://www.cockroachlabs.com/)
|
||||||
|
* [Bleve](http://www.blevesearch.com/)
|
||||||
|
* [ProjectAtomic (enterprise)](http://www.projectatomic.io/)
|
||||||
|
* [Parse (CLI)](https://parse.com/)
|
||||||
|
* [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack)
|
||||||
|
|
||||||
Cobra is a commander providing a simple interface to create powerful modern CLI
|
|
||||||
interfaces similar to git & go tools. In addition to providing an interface, Cobra
|
|
||||||
simultaneously provides a controller to organize your application code.
|
|
||||||
|
|
||||||
Inspired by go, go-Commander, gh and subcommand, Cobra improves on these by
|
[](https://travis-ci.org/spf13/cobra)
|
||||||
providing **fully posix compliant flags** (including short & long versions),
|
[](https://circleci.com/gh/spf13/cobra)
|
||||||
**nesting commands**, and the ability to **define your own help and usage** for any or
|
|
||||||
all commands.
|

|
||||||
|
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
Cobra is a library providing a simple interface to create powerful modern CLI
|
||||||
|
interfaces similar to git & go tools.
|
||||||
|
|
||||||
|
Cobra is also an application that will generate your application scaffolding to rapidly
|
||||||
|
develop a Cobra-based application.
|
||||||
|
|
||||||
|
Cobra provides:
|
||||||
|
* Easy subcommand-based CLIs: `app server`, `app fetch`, etc.
|
||||||
|
* Fully POSIX-compliant flags (including short & long versions)
|
||||||
|
* Nested subcommands
|
||||||
|
* Global, local and cascading flags
|
||||||
|
* Easy generation of applications & commands with `cobra create appname` & `cobra add cmdname`
|
||||||
|
* Intelligent suggestions (`app srver`... did you mean `app server`?)
|
||||||
|
* Automatic help generation for commands and flags
|
||||||
|
* Automatic detailed help for `app help [command]`
|
||||||
|
* Automatic help flag recognition of `-h`, `--help`, etc.
|
||||||
|
* Automatically generated bash autocomplete for your application
|
||||||
|
* Automatically generated man pages for your application
|
||||||
|
* Command aliases so you can change things without breaking them
|
||||||
|
* The flexibilty to define your own help, usage, etc.
|
||||||
|
* Optional tight integration with [viper](http://github.com/spf13/viper) for 12-factor apps
|
||||||
|
|
||||||
Cobra has an exceptionally clean interface and simple design without needless
|
Cobra has an exceptionally clean interface and simple design without needless
|
||||||
constructors or initialization methods.
|
constructors or initialization methods.
|
||||||
|
|
||||||
Applications built with Cobra commands are designed to be as user friendly as
|
Applications built with Cobra commands are designed to be as user-friendly as
|
||||||
possible. Flags can be placed before or after the command (as long as a
|
possible. Flags can be placed before or after the command (as long as a
|
||||||
confusing space isn’t provided). Both short and long flags can be used. A
|
confusing space isn’t provided). Both short and long flags can be used. A
|
||||||
command need not even be fully typed. The shortest unambiguous string will
|
command need not even be fully typed. Help is automatically generated and
|
||||||
suffice. Help is automatically generated and available for the application or
|
available for the application or for a specific command using either the help
|
||||||
for a specific command using either the help command or the --help flag.
|
command or the `--help` flag.
|
||||||
|
|
||||||
## Concepts
|
# Concepts
|
||||||
|
|
||||||
Cobra is built on a structure of commands & flags.
|
Cobra is built on a structure of commands, arguments & flags.
|
||||||
|
|
||||||
**Commands** represent actions and **Flags** are modifiers for those actions.
|
**Commands** represent actions, **Args** are things and **Flags** are modifiers for those actions.
|
||||||
|
|
||||||
In the following example 'server' is a command and 'port' is a flag.
|
The best applications will read like sentences when used. Users will know how
|
||||||
|
to use the application because they will natively understand how to use it.
|
||||||
|
|
||||||
hugo server --port=1313
|
The pattern to follow is
|
||||||
|
`APPNAME VERB NOUN --ADJECTIVE.`
|
||||||
|
or
|
||||||
|
`APPNAME COMMAND ARG --FLAG`
|
||||||
|
|
||||||
### Commands
|
A few good real world examples may better illustrate this point.
|
||||||
|
|
||||||
|
In the following example, 'server' is a command, and 'port' is a flag:
|
||||||
|
|
||||||
|
> hugo server --port=1313
|
||||||
|
|
||||||
|
In this command we are telling Git to clone the url bare.
|
||||||
|
|
||||||
|
> git clone URL --bare
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
Command is the central point of the application. Each interaction that
|
Command is the central point of the application. Each interaction that
|
||||||
the application supports will be contained in a Command. A command can
|
the application supports will be contained in a Command. A command can
|
||||||
have children commands and optionally run an action.
|
have children commands and optionally run an action.
|
||||||
|
|
||||||
In the example above 'server' is the command
|
In the example above, 'server' is the command.
|
||||||
|
|
||||||
A Command has the following structure:
|
A Command has the following structure:
|
||||||
|
|
||||||
|
```go
|
||||||
type Command struct {
|
type Command struct {
|
||||||
Use string // The one-line usage message.
|
Use string // The one-line usage message.
|
||||||
Short string // The short description shown in the 'help' output.
|
Short string // The short description shown in the 'help' output.
|
||||||
Long string // The long message shown in the 'help <this-command>' output.
|
Long string // The long message shown in the 'help <this-command>' output.
|
||||||
Run func(cmd *Command, args []string) // Run runs the command.
|
Run func(cmd *Command, args []string) // Run runs the command.
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Flags
|
## Flags
|
||||||
|
|
||||||
A Flag is a way to modify the behavior of an command. Cobra supports
|
A Flag is a way to modify the behavior of a command. Cobra supports
|
||||||
fully posix compliant flags as well as the go flag package.
|
fully POSIX-compliant flags as well as the Go [flag package](https://golang.org/pkg/flag/).
|
||||||
A Cobra command can define flags that persist through to children commands
|
A Cobra command can define flags that persist through to children commands
|
||||||
and flags that are only available to that command.
|
and flags that are only available to that command.
|
||||||
|
|
||||||
In the example above 'port' is the flag.
|
In the example above, 'port' is the flag.
|
||||||
|
|
||||||
Flag functionality is provided by the [pflag
|
Flag functionality is provided by the [pflag
|
||||||
libary](https://github.com/ogier/pflag), a fork of the flag standard library
|
library](https://github.com/ogier/pflag), a fork of the flag standard library
|
||||||
which maintains the same interface while adding posix compliance.
|
which maintains the same interface while adding POSIX compliance.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Cobra works by creating a set of commands and then organizing them into a tree.
|
Cobra works by creating a set of commands and then organizing them into a tree.
|
||||||
The tree defines the structure of the application.
|
The tree defines the structure of the application.
|
||||||
|
|
||||||
Once each command is defined with it's corresponding flags, then the
|
Once each command is defined with its corresponding flags, then the
|
||||||
tree is assigned to the commander which is finally executed.
|
tree is assigned to the commander which is finally executed.
|
||||||
|
|
||||||
### Installing
|
# Installing
|
||||||
Using Cobra is easy. First use go get to install the latest version
|
Using Cobra is easy. First, use `go get` to install the latest version
|
||||||
of the library.
|
of the library. This command will install the `cobra` generator executible
|
||||||
|
along with the library:
|
||||||
|
|
||||||
$ go get github.com/spf13/cobra
|
> go get -v github.com/spf13/cobra/cobra
|
||||||
|
|
||||||
Next include cobra in your application.
|
Next, include Cobra in your application:
|
||||||
|
|
||||||
|
```go
|
||||||
import "github.com/spf13/cobra"
|
import "github.com/spf13/cobra"
|
||||||
|
```
|
||||||
|
|
||||||
|
# Getting Started
|
||||||
|
|
||||||
|
While you are welcome to provide your own organization, typically a Cobra based
|
||||||
|
application will follow the following organizational structure.
|
||||||
|
|
||||||
|
```
|
||||||
|
▾ appName/
|
||||||
|
▾ cmd/
|
||||||
|
add.go
|
||||||
|
your.go
|
||||||
|
commands.go
|
||||||
|
here.go
|
||||||
|
main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
In a Cobra app, typically the main.go file is very bare. It serves, one purpose, to initialize Cobra.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "{pathToYourApp}/cmd"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := cmd.RootCmd.Execute(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using the Cobra Generator
|
||||||
|
|
||||||
|
Cobra provides its own program that will create your application and add any
|
||||||
|
commands you want. It's the easiest way to incorporate Cobra into your application.
|
||||||
|
|
||||||
|
### cobra init
|
||||||
|
|
||||||
|
The `cobra init [yourApp]` command will create your initial application code
|
||||||
|
for you. It is a very powerful application that will populate your program with
|
||||||
|
the right structure so you can immediately enjoy all the benefits of Cobra. It
|
||||||
|
will also automatically apply the license you specify to your application.
|
||||||
|
|
||||||
|
Cobra init is pretty smart. You can provide it a full path, or simply a path
|
||||||
|
similar to what is expected in the import.
|
||||||
|
|
||||||
|
```
|
||||||
|
cobra init github.com/spf13/newAppName
|
||||||
|
```
|
||||||
|
|
||||||
|
### cobra add
|
||||||
|
|
||||||
|
Once an application is initialized Cobra can create additional commands for you.
|
||||||
|
Let's say you created an app and you wanted the following commands for it:
|
||||||
|
|
||||||
|
* app serve
|
||||||
|
* app config
|
||||||
|
* app config create
|
||||||
|
|
||||||
|
In your project directory (where your main.go file is) you would run the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
cobra add serve
|
||||||
|
cobra add config
|
||||||
|
cobra add create -p 'configCmd'
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you have run these three commands you would have an app structure that would look like:
|
||||||
|
|
||||||
|
```
|
||||||
|
▾ app/
|
||||||
|
▾ cmd/
|
||||||
|
serve.go
|
||||||
|
config.go
|
||||||
|
create.go
|
||||||
|
main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
at this point you can run `go run main.go` and it would run your app. `go run
|
||||||
|
main.go serve`, `go run main.go config`, `go run main.go config create` along
|
||||||
|
with `go run main.go help serve`, etc would all work.
|
||||||
|
|
||||||
|
Obviously you haven't added your own code to these yet, the commands are ready
|
||||||
|
for you to give them their tasks. Have fun.
|
||||||
|
|
||||||
|
### Configuring the cobra generator
|
||||||
|
|
||||||
|
The cobra generator will be easier to use if you provide a simple configuration
|
||||||
|
file which will help you eliminate providing a bunch of repeated information in
|
||||||
|
flags over and over.
|
||||||
|
|
||||||
|
an example ~/.cobra.yaml file:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
author: Steve Francia <spf@spf13.com>
|
||||||
|
license: MIT
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manually implementing Cobra
|
||||||
|
|
||||||
|
To manually implement cobra you need to create a bare main.go file and a RootCmd file.
|
||||||
|
You will optionally provide additional commands as you see fit.
|
||||||
|
|
||||||
### Create the root command
|
### Create the root command
|
||||||
|
|
||||||
The root command represents your binary itself.
|
The root command represents your binary itself.
|
||||||
|
|
||||||
|
|
||||||
|
#### Manually create rootCmd
|
||||||
|
|
||||||
Cobra doesn't require any special constructors. Simply create your commands.
|
Cobra doesn't require any special constructors. Simply create your commands.
|
||||||
|
|
||||||
var HugoCmd = &cobra.Command{
|
Ideally you place this in app/cmd/root.go:
|
||||||
|
|
||||||
|
```go
|
||||||
|
var RootCmd = &cobra.Command{
|
||||||
Use: "hugo",
|
Use: "hugo",
|
||||||
Short: "Hugo is a very fast static site generator",
|
Short: "Hugo is a very fast static site generator",
|
||||||
Long: `A Fast and Flexible Static Site Generator built with
|
Long: `A Fast and Flexible Static Site Generator built with
|
||||||
@ -99,10 +259,67 @@ Cobra doesn't require any special constructors. Simply create your commands.
|
|||||||
// Do Stuff Here
|
// Do Stuff Here
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You will additionally define flags and handle configuration in your init() function.
|
||||||
|
|
||||||
|
for example cmd/root.go:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func init() {
|
||||||
|
cobra.OnInitialize(initConfig)
|
||||||
|
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)")
|
||||||
|
RootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory eg. github.com/spf13/")
|
||||||
|
RootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "Author name for copyright attribution")
|
||||||
|
RootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "Name of license for the project (can provide `licensetext` in config)")
|
||||||
|
RootCmd.PersistentFlags().Bool("viper", true, "Use Viper for configuration")
|
||||||
|
viper.BindPFlag("author", RootCmd.PersistentFlags().Lookup("author"))
|
||||||
|
viper.BindPFlag("projectbase", RootCmd.PersistentFlags().Lookup("projectbase"))
|
||||||
|
viper.BindPFlag("useViper", RootCmd.PersistentFlags().Lookup("viper"))
|
||||||
|
viper.SetDefault("author", "NAME HERE <EMAIL ADDRESS>")
|
||||||
|
viper.SetDefault("license", "apache")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create your main.go
|
||||||
|
|
||||||
|
With the root command you need to have your main function execute it.
|
||||||
|
Execute should be run on the root for clarity, though it can be called on any command.
|
||||||
|
|
||||||
|
In a Cobra app, typically the main.go file is very bare. It serves, one purpose, to initialize Cobra.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "{pathToYourApp}/cmd"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := cmd.RootCmd.Execute(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Create additional commands
|
### Create additional commands
|
||||||
|
|
||||||
Additional commands can be defined.
|
Additional commands can be defined and typically are each given their own file
|
||||||
|
inside of the cmd/ directory.
|
||||||
|
|
||||||
|
If you wanted to create a version command you would create cmd/version.go and
|
||||||
|
populate it with the following:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RootCmd.AddCommand(versionCmd)
|
||||||
|
}
|
||||||
|
|
||||||
var versionCmd = &cobra.Command{
|
var versionCmd = &cobra.Command{
|
||||||
Use: "version",
|
Use: "version",
|
||||||
@ -112,11 +329,35 @@ Additional commands can be defined.
|
|||||||
fmt.Println("Hugo Static Site Generator v0.9 -- HEAD")
|
fmt.Println("Hugo Static Site Generator v0.9 -- HEAD")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Attach command to its parent
|
### Attach command to its parent
|
||||||
In this example we are attaching it to the root, but commands can be attached at any level.
|
|
||||||
|
|
||||||
HugoCmd.AddCommand(versionCmd)
|
|
||||||
|
If you notice in the above example we attach the command to its parent. In
|
||||||
|
this case the parent is the rootCmd. In this example we are attaching it to the
|
||||||
|
root, but commands can be attached at any level.
|
||||||
|
|
||||||
|
```go
|
||||||
|
RootCmd.AddCommand(versionCmd)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Remove a command from its parent
|
||||||
|
|
||||||
|
Removing a command is not a common action in simple programs, but it allows 3rd
|
||||||
|
parties to customize an existing command tree.
|
||||||
|
|
||||||
|
In this example, we remove the existing `VersionCmd` command of an existing
|
||||||
|
root command, and we replace it with our own version:
|
||||||
|
|
||||||
|
```go
|
||||||
|
mainlib.RootCmd.RemoveCommand(mainlib.VersionCmd)
|
||||||
|
mainlib.RootCmd.AddCommand(versionCmd)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Working with Flags
|
||||||
|
|
||||||
|
Flags provide modifiers to control how the action command operates.
|
||||||
|
|
||||||
### Assign flags to a command
|
### Assign flags to a command
|
||||||
|
|
||||||
@ -124,43 +365,35 @@ Since the flags are defined and used in different locations, we need to
|
|||||||
define a variable outside with the correct scope to assign the flag to
|
define a variable outside with the correct scope to assign the flag to
|
||||||
work with.
|
work with.
|
||||||
|
|
||||||
|
```go
|
||||||
var Verbose bool
|
var Verbose bool
|
||||||
var Source string
|
var Source string
|
||||||
|
```
|
||||||
|
|
||||||
There are two different approaches to assign a flag.
|
There are two different approaches to assign a flag.
|
||||||
|
|
||||||
#### Persistent Flags
|
### Persistent Flags
|
||||||
|
|
||||||
A flag can be 'persistent' meaning that this flag will be available to the
|
A flag can be 'persistent' meaning that this flag will be available to the
|
||||||
command it's assigned to as well as every command under that command. For
|
command it's assigned to as well as every command under that command. For
|
||||||
global flags assign a flag as a persistent flag on the root.
|
global flags, assign a flag as a persistent flag on the root.
|
||||||
|
|
||||||
HugoCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
|
```go
|
||||||
|
RootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
|
||||||
|
```
|
||||||
|
|
||||||
#### Local Flags
|
### Local Flags
|
||||||
|
|
||||||
A flag can also be assigned locally which will only apply to that specific command.
|
A flag can also be assigned locally which will only apply to that specific command.
|
||||||
|
|
||||||
HugoCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")
|
```go
|
||||||
|
RootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")
|
||||||
|
```
|
||||||
|
|
||||||
### Remove a command from its parent
|
|
||||||
|
|
||||||
Removing a command is not a common action in simple programs but it allows 3rd parties to customize an existing command tree.
|
|
||||||
|
|
||||||
In this example, we remove the existing `VersionCmd` command of an existing root command, and we replace it by our own version.
|
|
||||||
|
|
||||||
mainlib.RootCmd.RemoveCommand(mainlib.VersionCmd)
|
|
||||||
mainlib.RootCmd.AddCommand(versionCmd)
|
|
||||||
|
|
||||||
### Once all commands and flags are defined, Execute the commands
|
|
||||||
|
|
||||||
Execute should be run on the root for clarity, though it can be called on any command.
|
|
||||||
|
|
||||||
HugoCmd.Execute()
|
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
In the example below we have defined three commands. Two are at the top level
|
In the example below, we have defined three commands. Two are at the top level
|
||||||
and one (cmdTimes) is a child of one of the top commands. In this case the root
|
and one (cmdTimes) is a child of one of the top commands. In this case the root
|
||||||
is not executable meaning that a subcommand is required. This is accomplished
|
is not executable meaning that a subcommand is required. This is accomplished
|
||||||
by not providing a 'Run' for the 'rootCmd'.
|
by not providing a 'Run' for the 'rootCmd'.
|
||||||
@ -169,10 +402,14 @@ We have only defined one flag for a single command.
|
|||||||
|
|
||||||
More documentation about flags is available at https://github.com/spf13/pflag
|
More documentation about flags is available at https://github.com/spf13/pflag
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -220,57 +457,81 @@ More documentation about flags is available at https://github.com/spf13/pflag
|
|||||||
cmdEcho.AddCommand(cmdTimes)
|
cmdEcho.AddCommand(cmdTimes)
|
||||||
rootCmd.Execute()
|
rootCmd.Execute()
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
For a more complete example of a larger application, please checkout [Hugo](http://hugo.spf13.com)
|
For a more complete example of a larger application, please checkout [Hugo](http://gohugo.io/).
|
||||||
|
|
||||||
## The Help Command
|
## The Help Command
|
||||||
|
|
||||||
Cobra automatically adds a help command to your application when you have subcommands.
|
Cobra automatically adds a help command to your application when you have subcommands.
|
||||||
This will be called when a user runs 'app help'. Additionally help will also
|
This will be called when a user runs 'app help'. Additionally, help will also
|
||||||
support all other commands as input. Say for instance you have a command called
|
support all other commands as input. Say, for instance, you have a command called
|
||||||
'create' without any additional configuration cobra will work when 'app help
|
'create' without any additional configuration; Cobra will work when 'app help
|
||||||
create' is called. Every command will automatically have the '--help' flag added.
|
create' is called. Every command will automatically have the '--help' flag added.
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
The following output is automatically generated by cobra. Nothing beyond the
|
The following output is automatically generated by Cobra. Nothing beyond the
|
||||||
command and flag definitions are needed.
|
command and flag definitions are needed.
|
||||||
|
|
||||||
> hugo help
|
> hugo help
|
||||||
|
|
||||||
A Fast and Flexible Static Site Generator built with
|
hugo is the main command, used to build your Hugo site.
|
||||||
love by spf13 and friends in Go.
|
|
||||||
|
|
||||||
Complete documentation is available at http://hugo.spf13.com
|
Hugo is a Fast and Flexible Static Site Generator
|
||||||
|
built with love by spf13 and friends in Go.
|
||||||
|
|
||||||
|
Complete documentation is available at http://gohugo.io/.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
hugo [flags]
|
hugo [flags]
|
||||||
hugo [command]
|
hugo [command]
|
||||||
|
|
||||||
Available Commands:
|
Available Commands:
|
||||||
server :: Hugo runs it's own a webserver to render the files
|
server Hugo runs its own webserver to render the files
|
||||||
version :: Print the version number of Hugo
|
version Print the version number of Hugo
|
||||||
check :: Check content in the source directory
|
config Print the site configuration
|
||||||
benchmark :: Benchmark hugo by building a site a number of times
|
check Check content in the source directory
|
||||||
help [command] :: Help about any command
|
benchmark Benchmark hugo by building a site a number of times.
|
||||||
|
convert Convert your content to different formats
|
||||||
|
new Create new content for your site
|
||||||
|
list Listing out various types of content
|
||||||
|
undraft Undraft changes the content's draft status from 'True' to 'False'
|
||||||
|
genautocomplete Generate shell autocompletion script for Hugo
|
||||||
|
gendoc Generate Markdown documentation for the Hugo CLI.
|
||||||
|
genman Generate man page for Hugo
|
||||||
|
import Import your site from others.
|
||||||
|
|
||||||
Available Flags:
|
Flags:
|
||||||
-b, --base-url="": hostname (and path) to the root eg. http://spf13.com/
|
-b, --baseURL="": hostname (and path) to the root, e.g. http://spf13.com/
|
||||||
-D, --build-drafts=false: include content marked as draft
|
-D, --buildDrafts[=false]: include content marked as draft
|
||||||
|
-F, --buildFuture[=false]: include content with publishdate in the future
|
||||||
|
--cacheDir="": filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/
|
||||||
|
--canonifyURLs[=false]: if true, all relative URLs will be canonicalized using baseURL
|
||||||
--config="": config file (default is path/config.yaml|json|toml)
|
--config="": config file (default is path/config.yaml|json|toml)
|
||||||
-d, --destination="": filesystem path to write files to
|
-d, --destination="": filesystem path to write files to
|
||||||
|
--disableRSS[=false]: Do not build RSS files
|
||||||
|
--disableSitemap[=false]: Do not build Sitemap file
|
||||||
|
--editor="": edit new content with this editor, if provided
|
||||||
|
--ignoreCache[=false]: Ignores the cache directory for reading but still writes to it
|
||||||
|
--log[=false]: Enable Logging
|
||||||
|
--logFile="": Log File path (if set, logging enabled automatically)
|
||||||
|
--noTimes[=false]: Don't sync modification time of files
|
||||||
|
--pluralizeListTitles[=true]: Pluralize titles in lists using inflect
|
||||||
|
--preserveTaxonomyNames[=false]: Preserve taxonomy names as written ("Gérard Depardieu" vs "gerard-depardieu")
|
||||||
-s, --source="": filesystem path to read files relative from
|
-s, --source="": filesystem path to read files relative from
|
||||||
--stepAnalysis=false: display memory and timing of different steps of the program
|
--stepAnalysis[=false]: display memory and timing of different steps of the program
|
||||||
--uglyurls=false: if true, use /filename.html instead of /filename/
|
-t, --theme="": theme to use (located in /themes/THEMENAME/)
|
||||||
-v, --verbose=false: verbose output
|
--uglyURLs[=false]: if true, use /filename.html instead of /filename/
|
||||||
-w, --watch=false: watch filesystem for changes and recreate as needed
|
-v, --verbose[=false]: verbose output
|
||||||
|
--verboseLog[=false]: verbose logging
|
||||||
Use "hugo help [command]" for more information about that command.
|
-w, --watch[=false]: watch filesystem for changes and recreate as needed
|
||||||
|
|
||||||
|
Use "hugo [command] --help" for more information about a command.
|
||||||
|
|
||||||
|
|
||||||
Help is just a command like any other. There is no special logic or behavior
|
Help is just a command like any other. There is no special logic or behavior
|
||||||
around it. In fact you can provide your own if you want.
|
around it. In fact, you can provide your own if you want.
|
||||||
|
|
||||||
### Defining your own help
|
### Defining your own help
|
||||||
|
|
||||||
@ -278,6 +539,7 @@ You can provide your own Help command or you own template for the default comman
|
|||||||
|
|
||||||
The default help command is
|
The default help command is
|
||||||
|
|
||||||
|
```go
|
||||||
func (c *Command) initHelp() {
|
func (c *Command) initHelp() {
|
||||||
if c.helpCommand == nil {
|
if c.helpCommand == nil {
|
||||||
c.helpCommand = &Command{
|
c.helpCommand = &Command{
|
||||||
@ -290,75 +552,104 @@ The default help command is
|
|||||||
}
|
}
|
||||||
c.AddCommand(c.helpCommand)
|
c.AddCommand(c.helpCommand)
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
You can provide your own command, function or template through the following methods.
|
You can provide your own command, function or template through the following methods:
|
||||||
|
|
||||||
|
```go
|
||||||
command.SetHelpCommand(cmd *Command)
|
command.SetHelpCommand(cmd *Command)
|
||||||
|
|
||||||
command.SetHelpFunc(f func(*Command, []string))
|
command.SetHelpFunc(f func(*Command, []string))
|
||||||
|
|
||||||
command.SetHelpTemplate(s string)
|
command.SetHelpTemplate(s string)
|
||||||
|
```
|
||||||
|
|
||||||
The latter two will also apply to any children commands.
|
The latter two will also apply to any children commands.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
When the user provides an invalid flag or invalid command Cobra responds by
|
When the user provides an invalid flag or invalid command, Cobra responds by
|
||||||
showing the user the 'usage'
|
showing the user the 'usage'.
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
You may recognize this from the help above. That's because the default help
|
You may recognize this from the help above. That's because the default help
|
||||||
embeds the usage as part of it's output.
|
embeds the usage as part of its output.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
hugo [flags]
|
hugo [flags]
|
||||||
hugo [command]
|
hugo [command]
|
||||||
|
|
||||||
Available Commands:
|
Available Commands:
|
||||||
server Hugo runs it's own a webserver to render the files
|
server Hugo runs its own webserver to render the files
|
||||||
version Print the version number of Hugo
|
version Print the version number of Hugo
|
||||||
|
config Print the site configuration
|
||||||
check Check content in the source directory
|
check Check content in the source directory
|
||||||
benchmark Benchmark hugo by building a site a number of times
|
benchmark Benchmark hugo by building a site a number of times.
|
||||||
help [command] Help about any command
|
convert Convert your content to different formats
|
||||||
|
new Create new content for your site
|
||||||
|
list Listing out various types of content
|
||||||
|
undraft Undraft changes the content's draft status from 'True' to 'False'
|
||||||
|
genautocomplete Generate shell autocompletion script for Hugo
|
||||||
|
gendoc Generate Markdown documentation for the Hugo CLI.
|
||||||
|
genman Generate man page for Hugo
|
||||||
|
import Import your site from others.
|
||||||
|
|
||||||
Available Flags:
|
Flags:
|
||||||
-b, --base-url="": hostname (and path) to the root eg. http://spf13.com/
|
-b, --baseURL="": hostname (and path) to the root, e.g. http://spf13.com/
|
||||||
-D, --build-drafts=false: include content marked as draft
|
-D, --buildDrafts[=false]: include content marked as draft
|
||||||
|
-F, --buildFuture[=false]: include content with publishdate in the future
|
||||||
|
--cacheDir="": filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/
|
||||||
|
--canonifyURLs[=false]: if true, all relative URLs will be canonicalized using baseURL
|
||||||
--config="": config file (default is path/config.yaml|json|toml)
|
--config="": config file (default is path/config.yaml|json|toml)
|
||||||
-d, --destination="": filesystem path to write files to
|
-d, --destination="": filesystem path to write files to
|
||||||
|
--disableRSS[=false]: Do not build RSS files
|
||||||
|
--disableSitemap[=false]: Do not build Sitemap file
|
||||||
|
--editor="": edit new content with this editor, if provided
|
||||||
|
--ignoreCache[=false]: Ignores the cache directory for reading but still writes to it
|
||||||
|
--log[=false]: Enable Logging
|
||||||
|
--logFile="": Log File path (if set, logging enabled automatically)
|
||||||
|
--noTimes[=false]: Don't sync modification time of files
|
||||||
|
--pluralizeListTitles[=true]: Pluralize titles in lists using inflect
|
||||||
|
--preserveTaxonomyNames[=false]: Preserve taxonomy names as written ("Gérard Depardieu" vs "gerard-depardieu")
|
||||||
-s, --source="": filesystem path to read files relative from
|
-s, --source="": filesystem path to read files relative from
|
||||||
--stepAnalysis=false: display memory and timing of different steps of the program
|
--stepAnalysis[=false]: display memory and timing of different steps of the program
|
||||||
--uglyurls=false: if true, use /filename.html instead of /filename/
|
-t, --theme="": theme to use (located in /themes/THEMENAME/)
|
||||||
-v, --verbose=false: verbose output
|
--uglyURLs[=false]: if true, use /filename.html instead of /filename/
|
||||||
-w, --watch=false: watch filesystem for changes and recreate as needed
|
-v, --verbose[=false]: verbose output
|
||||||
|
--verboseLog[=false]: verbose logging
|
||||||
|
-w, --watch[=false]: watch filesystem for changes and recreate as needed
|
||||||
|
|
||||||
### Defining your own usage
|
### Defining your own usage
|
||||||
You can provide your own usage function or template for cobra to use.
|
You can provide your own usage function or template for Cobra to use.
|
||||||
|
|
||||||
The default usage function is
|
The default usage function is:
|
||||||
|
|
||||||
|
```go
|
||||||
return func(c *Command) error {
|
return func(c *Command) error {
|
||||||
err := tmpl(c.Out(), c.UsageTemplate(), c)
|
err := tmpl(c.Out(), c.UsageTemplate(), c)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Like help the function and template are over ridable through public methods.
|
Like help, the function and template are overridable through public methods:
|
||||||
|
|
||||||
|
```go
|
||||||
command.SetUsageFunc(f func(*Command) error)
|
command.SetUsageFunc(f func(*Command) error)
|
||||||
|
|
||||||
command.SetUsageTemplate(s string)
|
command.SetUsageTemplate(s string)
|
||||||
|
```
|
||||||
|
|
||||||
## PreRun or PostRun Hooks
|
## PreRun or PostRun Hooks
|
||||||
|
|
||||||
It is possible to run functions before or after the main `Run` function of your command. The `PersistentPreRun` and `PreRun` functions will be executed before `Run`. `PersistendPostRun` and `PostRun` will be executed after `Run`. The `Persistent*Run` functions will be inherrited by children if they do not declare their own. These function are run in the following order:
|
It is possible to run functions before or after the main `Run` function of your command. The `PersistentPreRun` and `PreRun` functions will be executed before `Run`. `PersistentPostRun` and `PostRun` will be executed after `Run`. The `Persistent*Run` functions will be inherrited by children if they do not declare their own. These function are run in the following order:
|
||||||
|
|
||||||
- `PersistentPreRun`
|
- `PersistentPreRun`
|
||||||
- `PreRun`
|
- `PreRun`
|
||||||
- `Run`
|
- `Run`
|
||||||
- `PostRun`
|
- `PostRun`
|
||||||
- `PersistenPostRun`
|
- `PersistentPostRun`
|
||||||
|
|
||||||
And example of two commands which use all of these features is below. When the subcommand in executed it will run the root command's `PersistentPreRun` but not the root command's `PersistentPostRun`
|
An example of two commands which use all of these features is below. When the subcommand is executed, it will run the root command's `PersistentPreRun` but not the root command's `PersistentPostRun`:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
@ -418,22 +709,110 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Generating markdown formatted documentation for your command
|
|
||||||
|
|
||||||
Cobra can generate a markdown formatted document based on the subcommands, flags, etc. A simple example of how to do this for your command can be found in [Markdown Docs](md_docs.md)
|
## Alternative Error Handling
|
||||||
|
|
||||||
|
Cobra also has functions where the return signature is an error. This allows for errors to bubble up to the top, providing a way to handle the errors in one location. The current list of functions that return an error is:
|
||||||
|
|
||||||
|
* PersistentPreRunE
|
||||||
|
* PreRunE
|
||||||
|
* RunE
|
||||||
|
* PostRunE
|
||||||
|
* PersistentPostRunE
|
||||||
|
|
||||||
|
**Example Usage using RunE:**
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "hugo",
|
||||||
|
Short: "Hugo is a very fast static site generator",
|
||||||
|
Long: `A Fast and Flexible Static Site Generator built with
|
||||||
|
love by spf13 and friends in Go.
|
||||||
|
Complete documentation is available at http://hugo.spf13.com`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
// Do Stuff Here
|
||||||
|
return errors.New("some random error")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Suggestions when "unknown command" happens
|
||||||
|
|
||||||
|
Cobra will print automatic suggestions when "unknown command" errors happen. This allows Cobra to behave similarly to the `git` command when a typo happens. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ hugo srever
|
||||||
|
Error: unknown command "srever" for "hugo"
|
||||||
|
|
||||||
|
Did you mean this?
|
||||||
|
server
|
||||||
|
|
||||||
|
Run 'hugo --help' for usage.
|
||||||
|
```
|
||||||
|
|
||||||
|
Suggestions are automatic based on every subcommand registered and use an implementation of [Levenshtein distance](http://en.wikipedia.org/wiki/Levenshtein_distance). Every registered command that matches a minimum distance of 2 (ignoring case) will be displayed as a suggestion.
|
||||||
|
|
||||||
|
If you need to disable suggestions or tweak the string distance in your command, use:
|
||||||
|
|
||||||
|
```go
|
||||||
|
command.DisableSuggestions = true
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```go
|
||||||
|
command.SuggestionsMinimumDistance = 1
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also explicitly set names for which a given command will be suggested using the `SuggestFor` attribute. This allows suggestions for strings that are not close in terms of string distance, but makes sense in your set of commands and for some which you don't want aliases. Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ kubectl remove
|
||||||
|
Error: unknown command "remove" for "kubectl"
|
||||||
|
|
||||||
|
Did you mean this?
|
||||||
|
delete
|
||||||
|
|
||||||
|
Run 'kubectl help' for usage.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Generating Markdown-formatted documentation for your command
|
||||||
|
|
||||||
|
Cobra can generate a Markdown-formatted document based on the subcommands, flags, etc. A simple example of how to do this for your command can be found in [Markdown Docs](doc/md_docs.md).
|
||||||
|
|
||||||
|
## Generating man pages for your command
|
||||||
|
|
||||||
|
Cobra can generate a man page based on the subcommands, flags, etc. A simple example of how to do this for your command can be found in [Man Docs](doc/man_docs.md).
|
||||||
|
|
||||||
## Generating bash completions for your command
|
## Generating bash completions for your command
|
||||||
|
|
||||||
Cobra can generate a bash completions file. If you add more information to your command these completions can be amazingly powerful and flexible. Read more about [Bash Completions](bash_completions.md)
|
Cobra can generate a bash-completion file. If you add more information to your command, these completions can be amazingly powerful and flexible. Read more about it in [Bash Completions](bash_completions.md).
|
||||||
|
|
||||||
## Debugging
|
## Debugging
|
||||||
|
|
||||||
Cobra provides a ‘DebugFlags’ method on a command which when called will print
|
Cobra provides a ‘DebugFlags’ method on a command which, when called, will print
|
||||||
out everything Cobra knows about the flags for each command
|
out everything Cobra knows about the flags for each command.
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
|
```go
|
||||||
command.DebugFlags()
|
command.DebugFlags()
|
||||||
|
```
|
||||||
|
|
||||||
## Release Notes
|
## Release Notes
|
||||||
* **0.9.0** June 17, 2014
|
* **0.9.0** June 17, 2014
|
||||||
@ -459,6 +838,12 @@ out everything Cobra knows about the flags for each command
|
|||||||
* **0.1.0** Sept 3, 2013
|
* **0.1.0** Sept 3, 2013
|
||||||
* Implement first draft
|
* Implement first draft
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
Libraries for extending Cobra:
|
||||||
|
|
||||||
|
* [cmdns](https://github.com/gosuri/cmdns): Enables name spacing a command's immediate children. It provides an alternative way to structure subcommands, similar to `heroku apps:create` and `ovrclk clusters:launch`.
|
||||||
|
|
||||||
## ToDo
|
## ToDo
|
||||||
* Launch proper documentation site
|
* Launch proper documentation site
|
||||||
|
|
||||||
@ -474,7 +859,9 @@ out everything Cobra knows about the flags for each command
|
|||||||
|
|
||||||
Names in no particular order:
|
Names in no particular order:
|
||||||
|
|
||||||
* [spf13](https://github.com/spf13)
|
* [spf13](https://github.com/spf13),
|
||||||
|
[eparis](https://github.com/eparis),
|
||||||
|
[bep](https://github.com/bep), and many more!
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
@ -482,4 +869,3 @@ Cobra is released under the Apache 2.0 license. See [LICENSE.txt](https://github
|
|||||||
|
|
||||||
|
|
||||||
[](https://bitdeli.com/free "Bitdeli Badge")
|
[](https://bitdeli.com/free "Bitdeli Badge")
|
||||||
|
|
||||||
|
341
Godeps/_workspace/src/github.com/spf13/cobra/bash_completions.go
generated
vendored
341
Godeps/_workspace/src/github.com/spf13/cobra/bash_completions.go
generated
vendored
@ -1,8 +1,8 @@
|
|||||||
package cobra
|
package cobra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@ -13,19 +13,30 @@ import (
|
|||||||
const (
|
const (
|
||||||
BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extentions"
|
BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extentions"
|
||||||
BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag"
|
BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag"
|
||||||
|
BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir"
|
||||||
)
|
)
|
||||||
|
|
||||||
func preamble(out *bytes.Buffer) {
|
func preamble(out io.Writer, name string) error {
|
||||||
fmt.Fprintf(out, `#!/bin/bash
|
_, err := fmt.Fprintf(out, "# bash completion for %-36s -*- shell-script -*-\n", name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = fmt.Fprintf(out, `
|
||||||
__debug()
|
__debug()
|
||||||
{
|
{
|
||||||
if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then
|
if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then
|
||||||
echo "$*" >> ${BASH_COMP_DEBUG_FILE}
|
echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Homebrew on Macs have version 1.3 of bash-completion which doesn't include
|
||||||
|
# _init_completion. This is a very minimal version of that function.
|
||||||
|
__my_init_completion()
|
||||||
|
{
|
||||||
|
COMPREPLY=()
|
||||||
|
_get_comp_words_by_ref cur prev words cword
|
||||||
|
}
|
||||||
|
|
||||||
__index_of_word()
|
__index_of_word()
|
||||||
{
|
{
|
||||||
local w word=$1
|
local w word=$1
|
||||||
@ -52,7 +63,9 @@ __handle_reply()
|
|||||||
__debug "${FUNCNAME}"
|
__debug "${FUNCNAME}"
|
||||||
case $cur in
|
case $cur in
|
||||||
-*)
|
-*)
|
||||||
|
if [[ $(type -t compopt) = "builtin" ]]; then
|
||||||
compopt -o nospace
|
compopt -o nospace
|
||||||
|
fi
|
||||||
local allflags
|
local allflags
|
||||||
if [ ${#must_have_one_flag[@]} -ne 0 ]; then
|
if [ ${#must_have_one_flag[@]} -ne 0 ]; then
|
||||||
allflags=("${must_have_one_flag[@]}")
|
allflags=("${must_have_one_flag[@]}")
|
||||||
@ -60,7 +73,9 @@ __handle_reply()
|
|||||||
allflags=("${flags[*]} ${two_word_flags[*]}")
|
allflags=("${flags[*]} ${two_word_flags[*]}")
|
||||||
fi
|
fi
|
||||||
COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") )
|
COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") )
|
||||||
|
if [[ $(type -t compopt) = "builtin" ]]; then
|
||||||
[[ $COMPREPLY == *= ]] || compopt +o nospace
|
[[ $COMPREPLY == *= ]] || compopt +o nospace
|
||||||
|
fi
|
||||||
return 0;
|
return 0;
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@ -91,6 +106,21 @@ __handle_reply()
|
|||||||
if [[ ${#COMPREPLY[@]} -eq 0 ]]; then
|
if [[ ${#COMPREPLY[@]} -eq 0 ]]; then
|
||||||
declare -F __custom_func >/dev/null && __custom_func
|
declare -F __custom_func >/dev/null && __custom_func
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
__ltrim_colon_completions "$cur"
|
||||||
|
}
|
||||||
|
|
||||||
|
# The arguments should be in the form "ext1|ext2|extn"
|
||||||
|
__handle_filename_extension_flag()
|
||||||
|
{
|
||||||
|
local ext="$1"
|
||||||
|
_filedir "@(${ext})"
|
||||||
|
}
|
||||||
|
|
||||||
|
__handle_subdirs_in_dir_flag()
|
||||||
|
{
|
||||||
|
local dir="$1"
|
||||||
|
pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
__handle_flag()
|
__handle_flag()
|
||||||
@ -99,8 +129,10 @@ __handle_flag()
|
|||||||
|
|
||||||
# if a command required a flag, and we found it, unset must_have_one_flag()
|
# if a command required a flag, and we found it, unset must_have_one_flag()
|
||||||
local flagname=${words[c]}
|
local flagname=${words[c]}
|
||||||
|
local flagvalue
|
||||||
# if the word contained an =
|
# if the word contained an =
|
||||||
if [[ ${words[c]} == *"="* ]]; then
|
if [[ ${words[c]} == *"="* ]]; then
|
||||||
|
flagvalue=${flagname#*=} # take in as flagvalue after the =
|
||||||
flagname=${flagname%%=*} # strip everything after the =
|
flagname=${flagname%%=*} # strip everything after the =
|
||||||
flagname="${flagname}=" # but put the = back
|
flagname="${flagname}=" # but put the = back
|
||||||
fi
|
fi
|
||||||
@ -109,6 +141,15 @@ __handle_flag()
|
|||||||
must_have_one_flag=()
|
must_have_one_flag=()
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# keep flag value with flagname as flaghash
|
||||||
|
if [ ${flagvalue} ] ; then
|
||||||
|
flaghash[${flagname}]=${flagvalue}
|
||||||
|
elif [ ${words[ $((c+1)) ]} ] ; then
|
||||||
|
flaghash[${flagname}]=${words[ $((c+1)) ]}
|
||||||
|
else
|
||||||
|
flaghash[${flagname}]="true" # pad "true" for bool flag
|
||||||
|
fi
|
||||||
|
|
||||||
# skip the argument to a two word flag
|
# skip the argument to a two word flag
|
||||||
if __contains_word "${words[c]}" "${two_word_flags[@]}"; then
|
if __contains_word "${words[c]}" "${two_word_flags[@]}"; then
|
||||||
c=$((c+1))
|
c=$((c+1))
|
||||||
@ -118,7 +159,6 @@ __handle_flag()
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# skip the flag itself
|
|
||||||
c=$((c+1))
|
c=$((c+1))
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -141,9 +181,9 @@ __handle_command()
|
|||||||
|
|
||||||
local next_command
|
local next_command
|
||||||
if [[ -n ${last_command} ]]; then
|
if [[ -n ${last_command} ]]; then
|
||||||
next_command="_${last_command}_${words[c]}"
|
next_command="_${last_command}_${words[c]//:/__}"
|
||||||
else
|
else
|
||||||
next_command="_${words[c]}"
|
next_command="_${words[c]//:/__}"
|
||||||
fi
|
fi
|
||||||
c=$((c+1))
|
c=$((c+1))
|
||||||
__debug "${FUNCNAME}: looking for ${next_command}"
|
__debug "${FUNCNAME}: looking for ${next_command}"
|
||||||
@ -168,15 +208,24 @@ __handle_word()
|
|||||||
}
|
}
|
||||||
|
|
||||||
`)
|
`)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func postscript(out *bytes.Buffer, name string) {
|
func postscript(w io.Writer, name string) error {
|
||||||
fmt.Fprintf(out, "__start_%s()\n", name)
|
name = strings.Replace(name, ":", "__", -1)
|
||||||
fmt.Fprintf(out, `{
|
_, err := fmt.Fprintf(w, "__start_%s()\n", name)
|
||||||
local cur prev words cword split
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = fmt.Fprintf(w, `{
|
||||||
|
local cur prev words cword
|
||||||
|
declare -A flaghash
|
||||||
|
if declare -F _init_completion >/dev/null 2>&1; then
|
||||||
_init_completion -s || return
|
_init_completion -s || return
|
||||||
|
else
|
||||||
|
__my_init_completion || return
|
||||||
|
fi
|
||||||
|
|
||||||
local completions_func
|
|
||||||
local c=0
|
local c=0
|
||||||
local flags=()
|
local flags=()
|
||||||
local two_word_flags=()
|
local two_word_flags=()
|
||||||
@ -192,35 +241,77 @@ func postscript(out *bytes.Buffer, name string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
`, name)
|
`, name)
|
||||||
fmt.Fprintf(out, "complete -F __start_%s %s\n", name, name)
|
if err != nil {
|
||||||
fmt.Fprintf(out, "# ex: ts=4 sw=4 et filetype=sh\n")
|
return err
|
||||||
|
}
|
||||||
|
_, err = fmt.Fprintf(w, `if [[ $(type -t compopt) = "builtin" ]]; then
|
||||||
|
complete -o default -F __start_%s %s
|
||||||
|
else
|
||||||
|
complete -o default -o nospace -F __start_%s %s
|
||||||
|
fi
|
||||||
|
|
||||||
|
`, name, name, name, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = fmt.Fprintf(w, "# ex: ts=4 sw=4 et filetype=sh\n")
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeCommands(cmd *Command, out *bytes.Buffer) {
|
func writeCommands(cmd *Command, w io.Writer) error {
|
||||||
fmt.Fprintf(out, " commands=()\n")
|
if _, err := fmt.Fprintf(w, " commands=()\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
for _, c := range cmd.Commands() {
|
for _, c := range cmd.Commands() {
|
||||||
if len(c.Deprecated) > 0 {
|
if !c.IsAvailableCommand() || c == cmd.helpCommand {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fmt.Fprintf(out, " commands+=(%q)\n", c.Name())
|
if _, err := fmt.Fprintf(w, " commands+=(%q)\n", c.Name()); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(out, "\n")
|
}
|
||||||
|
_, err := fmt.Fprintf(w, "\n")
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeFlagHandler(name string, annotations map[string][]string, out *bytes.Buffer) {
|
func writeFlagHandler(name string, annotations map[string][]string, w io.Writer) error {
|
||||||
for key, value := range annotations {
|
for key, value := range annotations {
|
||||||
switch key {
|
switch key {
|
||||||
case BashCompFilenameExt:
|
case BashCompFilenameExt:
|
||||||
fmt.Fprintf(out, " flags_with_completion+=(%q)\n", name)
|
_, err := fmt.Fprintf(w, " flags_with_completion+=(%q)\n", name)
|
||||||
|
if err != nil {
|
||||||
ext := strings.Join(value, "|")
|
return err
|
||||||
ext = "_filedir '@(" + ext + ")'"
|
|
||||||
fmt.Fprintf(out, " flags_completion+=(%q)\n", ext)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeShortFlag(flag *pflag.Flag, out *bytes.Buffer) {
|
if len(value) > 0 {
|
||||||
|
ext := "__handle_filename_extension_flag " + strings.Join(value, "|")
|
||||||
|
_, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", ext)
|
||||||
|
} else {
|
||||||
|
ext := "_filedir"
|
||||||
|
_, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", ext)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case BashCompSubdirsInDir:
|
||||||
|
_, err := fmt.Fprintf(w, " flags_with_completion+=(%q)\n", name)
|
||||||
|
|
||||||
|
if len(value) == 1 {
|
||||||
|
ext := "__handle_subdirs_in_dir_flag " + value[0]
|
||||||
|
_, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", ext)
|
||||||
|
} else {
|
||||||
|
ext := "_filedir -d"
|
||||||
|
_, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", ext)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeShortFlag(flag *pflag.Flag, w io.Writer) error {
|
||||||
b := (flag.Value.Type() == "bool")
|
b := (flag.Value.Type() == "bool")
|
||||||
name := flag.Shorthand
|
name := flag.Shorthand
|
||||||
format := " "
|
format := " "
|
||||||
@ -228,11 +319,13 @@ func writeShortFlag(flag *pflag.Flag, out *bytes.Buffer) {
|
|||||||
format += "two_word_"
|
format += "two_word_"
|
||||||
}
|
}
|
||||||
format += "flags+=(\"-%s\")\n"
|
format += "flags+=(\"-%s\")\n"
|
||||||
fmt.Fprintf(out, format, name)
|
if _, err := fmt.Fprintf(w, format, name); err != nil {
|
||||||
writeFlagHandler("-"+name, flag.Annotations, out)
|
return err
|
||||||
|
}
|
||||||
|
return writeFlagHandler("-"+name, flag.Annotations, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeFlag(flag *pflag.Flag, out *bytes.Buffer) {
|
func writeFlag(flag *pflag.Flag, w io.Writer) error {
|
||||||
b := (flag.Value.Type() == "bool")
|
b := (flag.Value.Type() == "bool")
|
||||||
name := flag.Name
|
name := flag.Name
|
||||||
format := " flags+=(\"--%s"
|
format := " flags+=(\"--%s"
|
||||||
@ -240,32 +333,66 @@ func writeFlag(flag *pflag.Flag, out *bytes.Buffer) {
|
|||||||
format += "="
|
format += "="
|
||||||
}
|
}
|
||||||
format += "\")\n"
|
format += "\")\n"
|
||||||
fmt.Fprintf(out, format, name)
|
if _, err := fmt.Fprintf(w, format, name); err != nil {
|
||||||
writeFlagHandler("--"+name, flag.Annotations, out)
|
return err
|
||||||
|
}
|
||||||
|
return writeFlagHandler("--"+name, flag.Annotations, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeFlags(cmd *Command, out *bytes.Buffer) {
|
func writeFlags(cmd *Command, w io.Writer) error {
|
||||||
fmt.Fprintf(out, ` flags=()
|
_, err := fmt.Fprintf(w, ` flags=()
|
||||||
two_word_flags=()
|
two_word_flags=()
|
||||||
flags_with_completion=()
|
flags_with_completion=()
|
||||||
flags_completion=()
|
flags_completion=()
|
||||||
|
|
||||||
`)
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var visitErr error
|
||||||
cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
|
cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
|
||||||
writeFlag(flag, out)
|
if err := writeFlag(flag, w); err != nil {
|
||||||
|
visitErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
if len(flag.Shorthand) > 0 {
|
if len(flag.Shorthand) > 0 {
|
||||||
writeShortFlag(flag, out)
|
if err := writeShortFlag(flag, w); err != nil {
|
||||||
|
visitErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
if visitErr != nil {
|
||||||
fmt.Fprintf(out, "\n")
|
return visitErr
|
||||||
|
}
|
||||||
|
cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
|
||||||
|
if err := writeFlag(flag, w); err != nil {
|
||||||
|
visitErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(flag.Shorthand) > 0 {
|
||||||
|
if err := writeShortFlag(flag, w); err != nil {
|
||||||
|
visitErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if visitErr != nil {
|
||||||
|
return visitErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeRequiredFlag(cmd *Command, out *bytes.Buffer) {
|
_, err = fmt.Fprintf(w, "\n")
|
||||||
fmt.Fprintf(out, " must_have_one_flag=()\n")
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeRequiredFlag(cmd *Command, w io.Writer) error {
|
||||||
|
if _, err := fmt.Fprintf(w, " must_have_one_flag=()\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
flags := cmd.NonInheritedFlags()
|
flags := cmd.NonInheritedFlags()
|
||||||
|
var visitErr error
|
||||||
flags.VisitAll(func(flag *pflag.Flag) {
|
flags.VisitAll(func(flag *pflag.Flag) {
|
||||||
for key, _ := range flag.Annotations {
|
for key := range flag.Annotations {
|
||||||
switch key {
|
switch key {
|
||||||
case BashCompOneRequiredFlag:
|
case BashCompOneRequiredFlag:
|
||||||
format := " must_have_one_flag+=(\"--%s"
|
format := " must_have_one_flag+=(\"--%s"
|
||||||
@ -274,78 +401,126 @@ func writeRequiredFlag(cmd *Command, out *bytes.Buffer) {
|
|||||||
format += "="
|
format += "="
|
||||||
}
|
}
|
||||||
format += "\")\n"
|
format += "\")\n"
|
||||||
fmt.Fprintf(out, format, flag.Name)
|
if _, err := fmt.Fprintf(w, format, flag.Name); err != nil {
|
||||||
|
visitErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if len(flag.Shorthand) > 0 {
|
if len(flag.Shorthand) > 0 {
|
||||||
fmt.Fprintf(out, " must_have_one_flag+=(\"-%s\")\n", flag.Shorthand)
|
if _, err := fmt.Fprintf(w, " must_have_one_flag+=(\"-%s\")\n", flag.Shorthand); err != nil {
|
||||||
|
visitErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
return visitErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeRequiredNoun(cmd *Command, out *bytes.Buffer) {
|
func writeRequiredNoun(cmd *Command, w io.Writer) error {
|
||||||
fmt.Fprintf(out, " must_have_one_noun=()\n")
|
if _, err := fmt.Fprintf(w, " must_have_one_noun=()\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
sort.Sort(sort.StringSlice(cmd.ValidArgs))
|
sort.Sort(sort.StringSlice(cmd.ValidArgs))
|
||||||
for _, value := range cmd.ValidArgs {
|
for _, value := range cmd.ValidArgs {
|
||||||
fmt.Fprintf(out, " must_have_one_noun+=(%q)\n", value)
|
if _, err := fmt.Fprintf(w, " must_have_one_noun+=(%q)\n", value); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func gen(cmd *Command, out *bytes.Buffer) {
|
func gen(cmd *Command, w io.Writer) error {
|
||||||
for _, c := range cmd.Commands() {
|
for _, c := range cmd.Commands() {
|
||||||
if len(c.Deprecated) > 0 {
|
if !c.IsAvailableCommand() || c == cmd.helpCommand {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
gen(c, out)
|
if err := gen(c, w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
commandName := cmd.CommandPath()
|
commandName := cmd.CommandPath()
|
||||||
commandName = strings.Replace(commandName, " ", "_", -1)
|
commandName = strings.Replace(commandName, " ", "_", -1)
|
||||||
fmt.Fprintf(out, "_%s()\n{\n", commandName)
|
commandName = strings.Replace(commandName, ":", "__", -1)
|
||||||
fmt.Fprintf(out, " last_command=%q\n", commandName)
|
if _, err := fmt.Fprintf(w, "_%s()\n{\n", commandName); err != nil {
|
||||||
writeCommands(cmd, out)
|
return err
|
||||||
writeFlags(cmd, out)
|
}
|
||||||
writeRequiredFlag(cmd, out)
|
if _, err := fmt.Fprintf(w, " last_command=%q\n", commandName); err != nil {
|
||||||
writeRequiredNoun(cmd, out)
|
return err
|
||||||
fmt.Fprintf(out, "}\n\n")
|
}
|
||||||
|
if err := writeCommands(cmd, w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := writeFlags(cmd, w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := writeRequiredFlag(cmd, w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := writeRequiredNoun(cmd, w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := fmt.Fprintf(w, "}\n\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cmd *Command) GenBashCompletion(out *bytes.Buffer) {
|
func (cmd *Command) GenBashCompletion(w io.Writer) error {
|
||||||
preamble(out)
|
if err := preamble(w, cmd.Name()); err != nil {
|
||||||
if len(cmd.BashCompletionFunction) > 0 {
|
return err
|
||||||
fmt.Fprintf(out, "%s\n", cmd.BashCompletionFunction)
|
|
||||||
}
|
}
|
||||||
gen(cmd, out)
|
if len(cmd.BashCompletionFunction) > 0 {
|
||||||
postscript(out, cmd.Name())
|
if _, err := fmt.Fprintf(w, "%s\n", cmd.BashCompletionFunction); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := gen(cmd, w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return postscript(w, cmd.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cmd *Command) GenBashCompletionFile(filename string) error {
|
func (cmd *Command) GenBashCompletionFile(filename string) error {
|
||||||
out := new(bytes.Buffer)
|
|
||||||
|
|
||||||
cmd.GenBashCompletion(out)
|
|
||||||
|
|
||||||
outFile, err := os.Create(filename)
|
outFile, err := os.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer outFile.Close()
|
defer outFile.Close()
|
||||||
|
|
||||||
_, err = outFile.Write(out.Bytes())
|
return cmd.GenBashCompletion(outFile)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cmd *Command) MarkFlagRequired(name string) {
|
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag, if it exists.
|
||||||
flag := cmd.Flags().Lookup(name)
|
func (cmd *Command) MarkFlagRequired(name string) error {
|
||||||
if flag == nil {
|
return MarkFlagRequired(cmd.Flags(), name)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if flag.Annotations == nil {
|
|
||||||
flag.Annotations = make(map[string][]string)
|
// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag, if it exists.
|
||||||
|
func (cmd *Command) MarkPersistentFlagRequired(name string) error {
|
||||||
|
return MarkFlagRequired(cmd.PersistentFlags(), name)
|
||||||
}
|
}
|
||||||
annotation := make([]string, 1)
|
|
||||||
annotation[0] = "true"
|
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag in the flag set, if it exists.
|
||||||
flag.Annotations[BashCompOneRequiredFlag] = annotation
|
func MarkFlagRequired(flags *pflag.FlagSet, name string) error {
|
||||||
|
return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists.
|
||||||
|
// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
|
||||||
|
func (cmd *Command) MarkFlagFilename(name string, extensions ...string) error {
|
||||||
|
return MarkFlagFilename(cmd.Flags(), name, extensions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkPersistentFlagFilename adds the BashCompFilenameExt annotation to the named persistent flag, if it exists.
|
||||||
|
// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
|
||||||
|
func (cmd *Command) MarkPersistentFlagFilename(name string, extensions ...string) error {
|
||||||
|
return MarkFlagFilename(cmd.PersistentFlags(), name, extensions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag in the flag set, if it exists.
|
||||||
|
// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
|
||||||
|
func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error {
|
||||||
|
return flags.SetAnnotation(name, BashCompFilenameExt, extensions)
|
||||||
}
|
}
|
||||||
|
17
Godeps/_workspace/src/github.com/spf13/cobra/bash_completions.md
generated
vendored
17
Godeps/_workspace/src/github.com/spf13/cobra/bash_completions.md
generated
vendored
@ -118,20 +118,23 @@ and you'll get something like
|
|||||||
-c --container= -p --pod=
|
-c --container= -p --pod=
|
||||||
```
|
```
|
||||||
|
|
||||||
# Specify valid filename extentions for flags that take a filename
|
# Specify valid filename extensions for flags that take a filename
|
||||||
|
|
||||||
In this example we use --filename= and expect to get a json or yaml file as the argument. To make this easier we annotate the --filename flag with valid filename extensions.
|
In this example we use --filename= and expect to get a json or yaml file as the argument. To make this easier we annotate the --filename flag with valid filename extensions.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
annotations := make([]string, 3)
|
annotations := []string{"json", "yaml", "yml"}
|
||||||
annotations[0] = "json"
|
|
||||||
annotations[1] = "yaml"
|
|
||||||
annotations[2] = "yml"
|
|
||||||
|
|
||||||
annotation := make(map[string][]string)
|
annotation := make(map[string][]string)
|
||||||
annotation[cobra.BashCompFilenameExt] = annotations
|
annotation[cobra.BashCompFilenameExt] = annotations
|
||||||
|
|
||||||
flag := &pflag.Flag{"filename", "f", usage, value, value.String(), false, annotation}
|
flag := &pflag.Flag{
|
||||||
|
Name: "filename",
|
||||||
|
Shorthand: "f",
|
||||||
|
Usage: usage,
|
||||||
|
Value: value,
|
||||||
|
DefValue: value.String(),
|
||||||
|
Annotations: annotation,
|
||||||
|
}
|
||||||
cmd.Flags().AddFlag(flag)
|
cmd.Flags().AddFlag(flag)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
43
Godeps/_workspace/src/github.com/spf13/cobra/bash_completions_test.go
generated
vendored
43
Godeps/_workspace/src/github.com/spf13/cobra/bash_completions_test.go
generated
vendored
@ -34,7 +34,7 @@ COMPREPLY=( "hello" )
|
|||||||
func TestBashCompletions(t *testing.T) {
|
func TestBashCompletions(t *testing.T) {
|
||||||
c := initializeWithRootCmd()
|
c := initializeWithRootCmd()
|
||||||
cmdEcho.AddCommand(cmdTimes)
|
cmdEcho.AddCommand(cmdTimes)
|
||||||
c.AddCommand(cmdEcho, cmdPrint, cmdDeprecated)
|
c.AddCommand(cmdEcho, cmdPrint, cmdDeprecated, cmdColon)
|
||||||
|
|
||||||
// custom completion function
|
// custom completion function
|
||||||
c.BashCompletionFunction = bash_completion_func
|
c.BashCompletionFunction = bash_completion_func
|
||||||
@ -42,23 +42,30 @@ func TestBashCompletions(t *testing.T) {
|
|||||||
// required flag
|
// required flag
|
||||||
c.MarkFlagRequired("introot")
|
c.MarkFlagRequired("introot")
|
||||||
|
|
||||||
// valid nounds
|
// valid nouns
|
||||||
validArgs := []string{"pods", "nodes", "services", "replicationControllers"}
|
validArgs := []string{"pods", "nodes", "services", "replicationControllers"}
|
||||||
c.ValidArgs = validArgs
|
c.ValidArgs = validArgs
|
||||||
|
|
||||||
// filename extentions
|
// filename
|
||||||
annotations := make([]string, 3)
|
|
||||||
annotations[0] = "json"
|
|
||||||
annotations[1] = "yaml"
|
|
||||||
annotations[2] = "yml"
|
|
||||||
|
|
||||||
annotation := make(map[string][]string)
|
|
||||||
annotation[BashCompFilenameExt] = annotations
|
|
||||||
|
|
||||||
var flagval string
|
var flagval string
|
||||||
c.Flags().StringVar(&flagval, "filename", "", "Enter a filename")
|
c.Flags().StringVar(&flagval, "filename", "", "Enter a filename")
|
||||||
flag := c.Flags().Lookup("filename")
|
c.MarkFlagFilename("filename", "json", "yaml", "yml")
|
||||||
flag.Annotations = annotation
|
|
||||||
|
// persistent filename
|
||||||
|
var flagvalPersistent string
|
||||||
|
c.PersistentFlags().StringVar(&flagvalPersistent, "persistent-filename", "", "Enter a filename")
|
||||||
|
c.MarkPersistentFlagFilename("persistent-filename")
|
||||||
|
c.MarkPersistentFlagRequired("persistent-filename")
|
||||||
|
|
||||||
|
// filename extensions
|
||||||
|
var flagvalExt string
|
||||||
|
c.Flags().StringVar(&flagvalExt, "filename-ext", "", "Enter a filename (extension limited)")
|
||||||
|
c.MarkFlagFilename("filename-ext")
|
||||||
|
|
||||||
|
// subdirectories in a given directory
|
||||||
|
var flagvalTheme string
|
||||||
|
c.Flags().StringVar(&flagvalTheme, "theme", "", "theme to use (located in /themes/THEMENAME/)")
|
||||||
|
c.Flags().SetAnnotation("theme", BashCompSubdirsInDir, []string{"themes"})
|
||||||
|
|
||||||
out := new(bytes.Buffer)
|
out := new(bytes.Buffer)
|
||||||
c.GenBashCompletion(out)
|
c.GenBashCompletion(out)
|
||||||
@ -68,15 +75,21 @@ func TestBashCompletions(t *testing.T) {
|
|||||||
check(t, str, "_cobra-test_echo")
|
check(t, str, "_cobra-test_echo")
|
||||||
check(t, str, "_cobra-test_echo_times")
|
check(t, str, "_cobra-test_echo_times")
|
||||||
check(t, str, "_cobra-test_print")
|
check(t, str, "_cobra-test_print")
|
||||||
|
check(t, str, "_cobra-test_cmd__colon")
|
||||||
|
|
||||||
// check for required flags
|
// check for required flags
|
||||||
check(t, str, `must_have_one_flag+=("--introot=")`)
|
check(t, str, `must_have_one_flag+=("--introot=")`)
|
||||||
|
check(t, str, `must_have_one_flag+=("--persistent-filename=")`)
|
||||||
// check for custom completion function
|
// check for custom completion function
|
||||||
check(t, str, `COMPREPLY=( "hello" )`)
|
check(t, str, `COMPREPLY=( "hello" )`)
|
||||||
// check for required nouns
|
// check for required nouns
|
||||||
check(t, str, `must_have_one_noun+=("pods")`)
|
check(t, str, `must_have_one_noun+=("pods")`)
|
||||||
// check for filename extention flags
|
// check for filename extension flags
|
||||||
check(t, str, `flags_completion+=("_filedir '@(json|yaml|yml)'")`)
|
check(t, str, `flags_completion+=("_filedir")`)
|
||||||
|
// check for filename extension flags
|
||||||
|
check(t, str, `flags_completion+=("__handle_filename_extension_flag json|yaml|yml")`)
|
||||||
|
// check for subdirs_in_dir flags
|
||||||
|
check(t, str, `flags_completion+=("__handle_subdirs_in_dir_flag themes")`)
|
||||||
|
|
||||||
checkOmit(t, str, cmdDeprecated.Name())
|
checkOmit(t, str, cmdDeprecated.Name())
|
||||||
}
|
}
|
||||||
|
74
Godeps/_workspace/src/github.com/spf13/cobra/cobra.go
generated
vendored
74
Godeps/_workspace/src/github.com/spf13/cobra/cobra.go
generated
vendored
@ -23,21 +23,36 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var templateFuncs template.FuncMap = template.FuncMap{
|
||||||
|
"trim": strings.TrimSpace,
|
||||||
|
"trimRightSpace": trimRightSpace,
|
||||||
|
"rpad": rpad,
|
||||||
|
"gt": Gt,
|
||||||
|
"eq": Eq,
|
||||||
|
}
|
||||||
|
|
||||||
var initializers []func()
|
var initializers []func()
|
||||||
|
|
||||||
// automatic prefix matching can be a dangerous thing to automatically enable in CLI tools.
|
// automatic prefix matching can be a dangerous thing to automatically enable in CLI tools.
|
||||||
// Set this to true to enable it
|
// Set this to true to enable it
|
||||||
var EnablePrefixMatching bool = false
|
var EnablePrefixMatching bool = false
|
||||||
|
|
||||||
// enables an information splash screen on Windows if the CLI is started from explorer.exe.
|
//AddTemplateFunc adds a template function that's available to Usage and Help
|
||||||
var EnableWindowsMouseTrap bool = true
|
//template generation.
|
||||||
|
func AddTemplateFunc(name string, tmplFunc interface{}) {
|
||||||
|
templateFuncs[name] = tmplFunc
|
||||||
|
}
|
||||||
|
|
||||||
var MousetrapHelpText string = `This is a command line tool
|
//AddTemplateFuncs adds multiple template functions availalble to Usage and
|
||||||
|
//Help template generation.
|
||||||
You need to open cmd.exe and run it from there.
|
func AddTemplateFuncs(tmplFuncs template.FuncMap) {
|
||||||
`
|
for k, v := range tmplFuncs {
|
||||||
|
templateFuncs[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//OnInitialize takes a series of func() arguments and appends them to a slice of func().
|
//OnInitialize takes a series of func() arguments and appends them to a slice of func().
|
||||||
func OnInitialize(y ...func()) {
|
func OnInitialize(y ...func()) {
|
||||||
@ -92,6 +107,10 @@ func Eq(a interface{}, b interface{}) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func trimRightSpace(s string) string {
|
||||||
|
return strings.TrimRightFunc(s, unicode.IsSpace)
|
||||||
|
}
|
||||||
|
|
||||||
//rpad adds padding to the right of a string
|
//rpad adds padding to the right of a string
|
||||||
func rpad(s string, padding int) string {
|
func rpad(s string, padding int) string {
|
||||||
template := fmt.Sprintf("%%-%ds", padding)
|
template := fmt.Sprintf("%%-%ds", padding)
|
||||||
@ -101,12 +120,43 @@ func rpad(s string, padding int) string {
|
|||||||
// tmpl executes the given template text on data, writing the result to w.
|
// tmpl executes the given template text on data, writing the result to w.
|
||||||
func tmpl(w io.Writer, text string, data interface{}) error {
|
func tmpl(w io.Writer, text string, data interface{}) error {
|
||||||
t := template.New("top")
|
t := template.New("top")
|
||||||
t.Funcs(template.FuncMap{
|
t.Funcs(templateFuncs)
|
||||||
"trim": strings.TrimSpace,
|
|
||||||
"rpad": rpad,
|
|
||||||
"gt": Gt,
|
|
||||||
"eq": Eq,
|
|
||||||
})
|
|
||||||
template.Must(t.Parse(text))
|
template.Must(t.Parse(text))
|
||||||
return t.Execute(w, data)
|
return t.Execute(w, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ld compares two strings and returns the levenshtein distance between them
|
||||||
|
func ld(s, t string, ignoreCase bool) int {
|
||||||
|
if ignoreCase {
|
||||||
|
s = strings.ToLower(s)
|
||||||
|
t = strings.ToLower(t)
|
||||||
|
}
|
||||||
|
d := make([][]int, len(s)+1)
|
||||||
|
for i := range d {
|
||||||
|
d[i] = make([]int, len(t)+1)
|
||||||
|
}
|
||||||
|
for i := range d {
|
||||||
|
d[i][0] = i
|
||||||
|
}
|
||||||
|
for j := range d[0] {
|
||||||
|
d[0][j] = j
|
||||||
|
}
|
||||||
|
for j := 1; j <= len(t); j++ {
|
||||||
|
for i := 1; i <= len(s); i++ {
|
||||||
|
if s[i-1] == t[j-1] {
|
||||||
|
d[i][j] = d[i-1][j-1]
|
||||||
|
} else {
|
||||||
|
min := d[i-1][j]
|
||||||
|
if d[i][j-1] < min {
|
||||||
|
min = d[i][j-1]
|
||||||
|
}
|
||||||
|
if d[i-1][j-1] < min {
|
||||||
|
min = d[i-1][j-1]
|
||||||
|
}
|
||||||
|
d[i][j] = min + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return d[len(s)][len(t)]
|
||||||
|
}
|
||||||
|
128
Godeps/_workspace/src/github.com/spf13/cobra/cobra/cmd/add.go
generated
vendored
Normal file
128
Godeps/_workspace/src/github.com/spf13/cobra/cobra/cmd/add.go
generated
vendored
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// Copyright © 2015 Steve Francia <spf@spf13.com>.
|
||||||
|
//
|
||||||
|
// 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 cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/cobra"
|
||||||
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RootCmd.AddCommand(addCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pName string
|
||||||
|
|
||||||
|
// initialize Command
|
||||||
|
var addCmd = &cobra.Command{
|
||||||
|
Use: "add [command name]",
|
||||||
|
Aliases: []string{"command"},
|
||||||
|
Short: "Add a command to a Cobra Application",
|
||||||
|
Long: `Add (cobra add) will create a new command, with a license and
|
||||||
|
the appropriate structure for a Cobra-based CLI application,
|
||||||
|
and register it to its parent (default RootCmd).
|
||||||
|
|
||||||
|
If you want your command to be public, pass in the command name
|
||||||
|
with an initial uppercase letter.
|
||||||
|
|
||||||
|
Example: cobra add server -> resulting in a new cmd/server.go
|
||||||
|
`,
|
||||||
|
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
er("add needs a name for the command")
|
||||||
|
}
|
||||||
|
guessProjectPath()
|
||||||
|
createCmdFile(args[0])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
addCmd.Flags().StringVarP(&pName, "parent", "p", "RootCmd", "name of parent command for this command")
|
||||||
|
}
|
||||||
|
|
||||||
|
func parentName() string {
|
||||||
|
if !strings.HasSuffix(strings.ToLower(pName), "cmd") {
|
||||||
|
return pName + "Cmd"
|
||||||
|
}
|
||||||
|
|
||||||
|
return pName
|
||||||
|
}
|
||||||
|
|
||||||
|
func createCmdFile(cmdName string) {
|
||||||
|
lic := getLicense()
|
||||||
|
|
||||||
|
template := `{{ comment .copyright }}
|
||||||
|
{{ comment .license }}
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// {{.cmdName}}Cmd represents the {{.cmdName}} command
|
||||||
|
var {{ .cmdName }}Cmd = &cobra.Command{
|
||||||
|
Use: "{{ .cmdName }}",
|
||||||
|
Short: "A brief description of your command",
|
||||||
|
Long: ` + "`" + `A longer description that spans multiple lines and likely contains examples
|
||||||
|
and usage of using your command. For example:
|
||||||
|
|
||||||
|
Cobra is a CLI library for Go that empowers applications.
|
||||||
|
This application is a tool to generate the needed files
|
||||||
|
to quickly create a Cobra application.` + "`" + `,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
// TODO: Work your own magic here
|
||||||
|
fmt.Println("{{ .cmdName }} called")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
{{ .parentName }}.AddCommand({{ .cmdName }}Cmd)
|
||||||
|
|
||||||
|
// Here you will define your flags and configuration settings.
|
||||||
|
|
||||||
|
// Cobra supports Persistent Flags which will work for this command
|
||||||
|
// and all subcommands, e.g.:
|
||||||
|
// {{.cmdName}}Cmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||||
|
|
||||||
|
// Cobra supports local flags which will only run when this command
|
||||||
|
// is called directly, e.g.:
|
||||||
|
// {{.cmdName}}Cmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||||
|
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
var data map[string]interface{}
|
||||||
|
data = make(map[string]interface{})
|
||||||
|
|
||||||
|
data["copyright"] = copyrightLine()
|
||||||
|
data["license"] = lic.Header
|
||||||
|
data["appName"] = projectName()
|
||||||
|
data["viper"] = viper.GetBool("useViper")
|
||||||
|
data["parentName"] = parentName()
|
||||||
|
data["cmdName"] = cmdName
|
||||||
|
|
||||||
|
err := writeTemplateToFile(filepath.Join(ProjectPath(), guessCmdDir()), cmdName+".go", template, data)
|
||||||
|
if err != nil {
|
||||||
|
er(err)
|
||||||
|
}
|
||||||
|
fmt.Println(cmdName, "created at", filepath.Join(ProjectPath(), guessCmdDir(), cmdName+".go"))
|
||||||
|
}
|
347
Godeps/_workspace/src/github.com/spf13/cobra/cobra/cmd/helpers.go
generated
vendored
Normal file
347
Godeps/_workspace/src/github.com/spf13/cobra/cobra/cmd/helpers.go
generated
vendored
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
// Copyright © 2015 Steve Francia <spf@spf13.com>.
|
||||||
|
//
|
||||||
|
// 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 cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
// var BaseDir = ""
|
||||||
|
// var AppName = ""
|
||||||
|
// var CommandDir = ""
|
||||||
|
|
||||||
|
var funcMap template.FuncMap
|
||||||
|
var projectPath = ""
|
||||||
|
var inputPath = ""
|
||||||
|
var projectBase = ""
|
||||||
|
|
||||||
|
// for testing only
|
||||||
|
var testWd = ""
|
||||||
|
|
||||||
|
var cmdDirs = []string{"cmd", "cmds", "command", "commands"}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
funcMap = template.FuncMap{
|
||||||
|
"comment": commentifyString,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func er(msg interface{}) {
|
||||||
|
fmt.Println("Error:", msg)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a file or directory exists.
|
||||||
|
func exists(path string) (bool, error) {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
if err == nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProjectPath() string {
|
||||||
|
if projectPath == "" {
|
||||||
|
guessProjectPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
return projectPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapper of the os package so we can test better
|
||||||
|
func getWd() (string, error) {
|
||||||
|
if testWd == "" {
|
||||||
|
return os.Getwd()
|
||||||
|
}
|
||||||
|
return testWd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func guessCmdDir() string {
|
||||||
|
guessProjectPath()
|
||||||
|
if b, _ := isEmpty(projectPath); b {
|
||||||
|
return "cmd"
|
||||||
|
}
|
||||||
|
|
||||||
|
files, _ := filepath.Glob(projectPath + string(os.PathSeparator) + "c*")
|
||||||
|
for _, f := range files {
|
||||||
|
for _, c := range cmdDirs {
|
||||||
|
if f == c {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "cmd"
|
||||||
|
}
|
||||||
|
|
||||||
|
func guessImportPath() string {
|
||||||
|
guessProjectPath()
|
||||||
|
|
||||||
|
if !strings.HasPrefix(projectPath, getSrcPath()) {
|
||||||
|
er("Cobra only supports project within $GOPATH")
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.ToSlash(filepath.Clean(strings.TrimPrefix(projectPath, getSrcPath())))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSrcPath() string {
|
||||||
|
return filepath.Join(os.Getenv("GOPATH"), "src") + string(os.PathSeparator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func projectName() string {
|
||||||
|
return filepath.Base(ProjectPath())
|
||||||
|
}
|
||||||
|
|
||||||
|
func guessProjectPath() {
|
||||||
|
// if no path is provided... assume CWD.
|
||||||
|
if inputPath == "" {
|
||||||
|
x, err := getWd()
|
||||||
|
if err != nil {
|
||||||
|
er(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// inspect CWD
|
||||||
|
base := filepath.Base(x)
|
||||||
|
|
||||||
|
// if we are in the cmd directory.. back up
|
||||||
|
for _, c := range cmdDirs {
|
||||||
|
if base == c {
|
||||||
|
projectPath = filepath.Dir(x)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if projectPath == "" {
|
||||||
|
projectPath = filepath.Clean(x)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
srcPath := getSrcPath()
|
||||||
|
// if provided, inspect for logical locations
|
||||||
|
if strings.ContainsRune(inputPath, os.PathSeparator) {
|
||||||
|
if filepath.IsAbs(inputPath) || filepath.HasPrefix(inputPath, string(os.PathSeparator)) {
|
||||||
|
// if Absolute, use it
|
||||||
|
projectPath = filepath.Clean(inputPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If not absolute but contains slashes,
|
||||||
|
// assuming it means create it from $GOPATH
|
||||||
|
count := strings.Count(inputPath, string(os.PathSeparator))
|
||||||
|
|
||||||
|
switch count {
|
||||||
|
// If only one directory deep, assume "github.com"
|
||||||
|
case 1:
|
||||||
|
projectPath = filepath.Join(srcPath, "github.com", inputPath)
|
||||||
|
return
|
||||||
|
case 2:
|
||||||
|
projectPath = filepath.Join(srcPath, inputPath)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
er("Unknown directory")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// hardest case.. just a word.
|
||||||
|
if projectBase == "" {
|
||||||
|
x, err := getWd()
|
||||||
|
if err == nil {
|
||||||
|
projectPath = filepath.Join(x, inputPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
er(err)
|
||||||
|
} else {
|
||||||
|
projectPath = filepath.Join(srcPath, projectBase, inputPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isEmpty checks if a given path is empty.
|
||||||
|
func isEmpty(path string) (bool, error) {
|
||||||
|
if b, _ := exists(path); !b {
|
||||||
|
return false, fmt.Errorf("%q path does not exist", path)
|
||||||
|
}
|
||||||
|
fi, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if fi.IsDir() {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
// FIX: Resource leak - f.close() should be called here by defer or is missed
|
||||||
|
// if the err != nil branch is taken.
|
||||||
|
defer f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
list, err := f.Readdir(-1)
|
||||||
|
// f.Close() - see bug fix above
|
||||||
|
return len(list) == 0, nil
|
||||||
|
}
|
||||||
|
return fi.Size() == 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isDir checks if a given path is a directory.
|
||||||
|
func isDir(path string) (bool, error) {
|
||||||
|
fi, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return fi.IsDir(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dirExists checks if a path exists and is a directory.
|
||||||
|
func dirExists(path string) (bool, error) {
|
||||||
|
fi, err := os.Stat(path)
|
||||||
|
if err == nil && fi.IsDir() {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeTemplateToFile(path string, file string, template string, data interface{}) error {
|
||||||
|
filename := filepath.Join(path, file)
|
||||||
|
|
||||||
|
r, err := templateToReader(template, data)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = safeWriteToDisk(filename, r)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeStringToFile(path, file, text string) error {
|
||||||
|
filename := filepath.Join(path, file)
|
||||||
|
|
||||||
|
r := strings.NewReader(text)
|
||||||
|
err := safeWriteToDisk(filename, r)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func templateToReader(tpl string, data interface{}) (io.Reader, error) {
|
||||||
|
tmpl := template.New("")
|
||||||
|
tmpl.Funcs(funcMap)
|
||||||
|
tmpl, err := tmpl.Parse(tpl)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
err = tmpl.Execute(buf, data)
|
||||||
|
|
||||||
|
return buf, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same as WriteToDisk but checks to see if file/directory already exists.
|
||||||
|
func safeWriteToDisk(inpath string, r io.Reader) (err error) {
|
||||||
|
dir, _ := filepath.Split(inpath)
|
||||||
|
ospath := filepath.FromSlash(dir)
|
||||||
|
|
||||||
|
if ospath != "" {
|
||||||
|
err = os.MkdirAll(ospath, 0777) // rwx, rw, r
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ex, err := exists(inpath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ex {
|
||||||
|
return fmt.Errorf("%v already exists", inpath)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(inpath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(file, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLicense() License {
|
||||||
|
l := whichLicense()
|
||||||
|
if l != "" {
|
||||||
|
if x, ok := Licenses[l]; ok {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Licenses["apache"]
|
||||||
|
}
|
||||||
|
|
||||||
|
func whichLicense() string {
|
||||||
|
// if explicitly flagged, use that
|
||||||
|
if userLicense != "" {
|
||||||
|
return matchLicense(userLicense)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if already present in the project, use that
|
||||||
|
// TODO: Inspect project for existing license
|
||||||
|
|
||||||
|
// default to viper's setting
|
||||||
|
|
||||||
|
return matchLicense(viper.GetString("license"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyrightLine() string {
|
||||||
|
author := viper.GetString("author")
|
||||||
|
year := time.Now().Format("2006")
|
||||||
|
|
||||||
|
return "Copyright © " + year + " " + author
|
||||||
|
}
|
||||||
|
|
||||||
|
func commentifyString(in string) string {
|
||||||
|
var newlines []string
|
||||||
|
lines := strings.Split(in, "\n")
|
||||||
|
for _, x := range lines {
|
||||||
|
if !strings.HasPrefix(x, "//") {
|
||||||
|
if x != "" {
|
||||||
|
newlines = append(newlines, "// "+x)
|
||||||
|
} else {
|
||||||
|
newlines = append(newlines, "//")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newlines = append(newlines, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(newlines, "\n")
|
||||||
|
}
|
40
Godeps/_workspace/src/github.com/spf13/cobra/cobra/cmd/helpers_test.go
generated
vendored
Normal file
40
Godeps/_workspace/src/github.com/spf13/cobra/cobra/cmd/helpers_test.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = fmt.Println
|
||||||
|
var _ = os.Stderr
|
||||||
|
|
||||||
|
func checkGuess(t *testing.T, wd, input, expected string) {
|
||||||
|
testWd = wd
|
||||||
|
inputPath = input
|
||||||
|
guessProjectPath()
|
||||||
|
|
||||||
|
if projectPath != expected {
|
||||||
|
t.Errorf("Unexpected Project Path. \n Got: %q\nExpected: %q\n", projectPath, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
func reset() {
|
||||||
|
testWd = ""
|
||||||
|
inputPath = ""
|
||||||
|
projectPath = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProjectPath(t *testing.T) {
|
||||||
|
checkGuess(t, "", filepath.Join("github.com", "spf13", "hugo"), filepath.Join(getSrcPath(), "github.com", "spf13", "hugo"))
|
||||||
|
checkGuess(t, "", filepath.Join("spf13", "hugo"), filepath.Join(getSrcPath(), "github.com", "spf13", "hugo"))
|
||||||
|
checkGuess(t, "", filepath.Join("/", "bar", "foo"), filepath.Join("/", "bar", "foo"))
|
||||||
|
checkGuess(t, "/bar/foo", "baz", filepath.Join("/", "bar", "foo", "baz"))
|
||||||
|
checkGuess(t, "/bar/foo/cmd", "", filepath.Join("/", "bar", "foo"))
|
||||||
|
checkGuess(t, "/bar/foo/command", "", filepath.Join("/", "bar", "foo"))
|
||||||
|
checkGuess(t, "/bar/foo/commands", "", filepath.Join("/", "bar", "foo"))
|
||||||
|
checkGuess(t, "github.com/spf13/hugo/../hugo", "", filepath.Join("github.com", "spf13", "hugo"))
|
||||||
|
}
|
226
Godeps/_workspace/src/github.com/spf13/cobra/cobra/cmd/init.go
generated
vendored
Normal file
226
Godeps/_workspace/src/github.com/spf13/cobra/cobra/cmd/init.go
generated
vendored
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
// Copyright © 2015 Steve Francia <spf@spf13.com>.
|
||||||
|
//
|
||||||
|
// 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 cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/cobra"
|
||||||
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RootCmd.AddCommand(initCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize Command
|
||||||
|
var initCmd = &cobra.Command{
|
||||||
|
Use: "init [name]",
|
||||||
|
Aliases: []string{"initialize", "initialise", "create"},
|
||||||
|
Short: "Initialize a Cobra Application",
|
||||||
|
Long: `Initialize (cobra init) will create a new application, with a license
|
||||||
|
and the appropriate structure for a Cobra-based CLI application.
|
||||||
|
|
||||||
|
* If a name is provided, it will be created in the current directory;
|
||||||
|
* If no name is provided, the current directory will be assumed;
|
||||||
|
* If a relative path is provided, it will be created inside $GOPATH
|
||||||
|
(e.g. github.com/spf13/hugo);
|
||||||
|
* If an absolute path is provided, it will be created;
|
||||||
|
* If the directory already exists but is empty, it will be used.
|
||||||
|
|
||||||
|
Init will not use an existing directory with contents.`,
|
||||||
|
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
switch len(args) {
|
||||||
|
case 0:
|
||||||
|
inputPath = ""
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
inputPath = args[0]
|
||||||
|
|
||||||
|
default:
|
||||||
|
er("init doesn't support more than 1 parameter")
|
||||||
|
}
|
||||||
|
guessProjectPath()
|
||||||
|
initializePath(projectPath)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func initializePath(path string) {
|
||||||
|
b, err := exists(path)
|
||||||
|
if err != nil {
|
||||||
|
er(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !b { // If path doesn't yet exist, create it
|
||||||
|
err := os.MkdirAll(path, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
er(err)
|
||||||
|
}
|
||||||
|
} else { // If path exists and is not empty don't use it
|
||||||
|
empty, err := exists(path)
|
||||||
|
if err != nil {
|
||||||
|
er(err)
|
||||||
|
}
|
||||||
|
if !empty {
|
||||||
|
er("Cobra will not create a new project in a non empty directory")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We have a directory and it's empty.. Time to initialize it.
|
||||||
|
|
||||||
|
createLicenseFile()
|
||||||
|
createMainFile()
|
||||||
|
createRootCmdFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
func createLicenseFile() {
|
||||||
|
lic := getLicense()
|
||||||
|
|
||||||
|
template := lic.Text
|
||||||
|
|
||||||
|
var data map[string]interface{}
|
||||||
|
data = make(map[string]interface{})
|
||||||
|
|
||||||
|
// Try to remove the email address, if any
|
||||||
|
data["copyright"] = strings.Split(copyrightLine(), " <")[0]
|
||||||
|
|
||||||
|
err := writeTemplateToFile(ProjectPath(), "LICENSE", template, data)
|
||||||
|
_ = err
|
||||||
|
// if err != nil {
|
||||||
|
// er(err)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMainFile() {
|
||||||
|
lic := getLicense()
|
||||||
|
|
||||||
|
template := `{{ comment .copyright }}
|
||||||
|
{{ comment .license }}
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "{{ .importpath }}"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cmd.Execute()
|
||||||
|
}
|
||||||
|
`
|
||||||
|
var data map[string]interface{}
|
||||||
|
data = make(map[string]interface{})
|
||||||
|
|
||||||
|
data["copyright"] = copyrightLine()
|
||||||
|
data["license"] = lic.Header
|
||||||
|
data["importpath"] = guessImportPath() + "/" + guessCmdDir()
|
||||||
|
|
||||||
|
err := writeTemplateToFile(ProjectPath(), "main.go", template, data)
|
||||||
|
_ = err
|
||||||
|
// if err != nil {
|
||||||
|
// er(err)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
func createRootCmdFile() {
|
||||||
|
lic := getLicense()
|
||||||
|
|
||||||
|
template := `{{ comment .copyright }}
|
||||||
|
{{ comment .license }}
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
{{ if .viper }} "github.com/spf13/viper"
|
||||||
|
{{ end }})
|
||||||
|
{{if .viper}}
|
||||||
|
var cfgFile string
|
||||||
|
{{ end }}
|
||||||
|
// This represents the base command when called without any subcommands
|
||||||
|
var RootCmd = &cobra.Command{
|
||||||
|
Use: "{{ .appName }}",
|
||||||
|
Short: "A brief description of your application",
|
||||||
|
Long: ` + "`" + `A longer description that spans multiple lines and likely contains
|
||||||
|
examples and usage of using your application. For example:
|
||||||
|
|
||||||
|
Cobra is a CLI library for Go that empowers applications.
|
||||||
|
This application is a tool to generate the needed files
|
||||||
|
to quickly create a Cobra application.` + "`" + `,
|
||||||
|
// Uncomment the following line if your bare application
|
||||||
|
// has an action associated with it:
|
||||||
|
// Run: func(cmd *cobra.Command, args []string) { },
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute adds all child commands to the root command sets flags appropriately.
|
||||||
|
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||||
|
func Execute() {
|
||||||
|
if err := RootCmd.Execute(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
{{ if .viper }} cobra.OnInitialize(initConfig)
|
||||||
|
|
||||||
|
{{ end }} // Here you will define your flags and configuration settings.
|
||||||
|
// Cobra supports Persistent Flags, which, if defined here,
|
||||||
|
// will be global for your application.
|
||||||
|
{{ if .viper }}
|
||||||
|
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.{{ .appName }}.yaml)")
|
||||||
|
{{ else }}
|
||||||
|
// RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.{{ .appName }}.yaml)")
|
||||||
|
{{ end }} // Cobra also supports local flags, which will only run
|
||||||
|
// when this action is called directly.
|
||||||
|
RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||||
|
}
|
||||||
|
{{ if .viper }}
|
||||||
|
// initConfig reads in config file and ENV variables if set.
|
||||||
|
func initConfig() {
|
||||||
|
if cfgFile != "" { // enable ability to specify config file via flag
|
||||||
|
viper.SetConfigFile(cfgFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
viper.SetConfigName(".{{ .appName }}") // name of config file (without extension)
|
||||||
|
viper.AddConfigPath("$HOME") // adding home directory as first search path
|
||||||
|
viper.AutomaticEnv() // read in environment variables that match
|
||||||
|
|
||||||
|
// If a config file is found, read it in.
|
||||||
|
if err := viper.ReadInConfig(); err == nil {
|
||||||
|
fmt.Println("Using config file:", viper.ConfigFileUsed())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{{ end }}`
|
||||||
|
|
||||||
|
var data map[string]interface{}
|
||||||
|
data = make(map[string]interface{})
|
||||||
|
|
||||||
|
data["copyright"] = copyrightLine()
|
||||||
|
data["license"] = lic.Header
|
||||||
|
data["appName"] = projectName()
|
||||||
|
data["viper"] = viper.GetBool("useViper")
|
||||||
|
|
||||||
|
err := writeTemplateToFile(ProjectPath()+string(os.PathSeparator)+guessCmdDir(), "root.go", template, data)
|
||||||
|
if err != nil {
|
||||||
|
er(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Your Cobra application is ready at")
|
||||||
|
fmt.Println(ProjectPath())
|
||||||
|
fmt.Println("Give it a try by going there and running `go run main.go`")
|
||||||
|
fmt.Println("Add commands to it by running `cobra add [cmdname]`")
|
||||||
|
}
|
1133
Godeps/_workspace/src/github.com/spf13/cobra/cobra/cmd/licenses.go
generated
vendored
Normal file
1133
Godeps/_workspace/src/github.com/spf13/cobra/cobra/cmd/licenses.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
84
Godeps/_workspace/src/github.com/spf13/cobra/cobra/cmd/root.go
generated
vendored
Normal file
84
Godeps/_workspace/src/github.com/spf13/cobra/cobra/cmd/root.go
generated
vendored
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// Copyright © 2015 Steve Francia <spf@spf13.com>.
|
||||||
|
//
|
||||||
|
// 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 cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/cobra"
|
||||||
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cfgFile string
|
||||||
|
var userLicense string
|
||||||
|
|
||||||
|
// This represents the base command when called without any subcommands
|
||||||
|
var RootCmd = &cobra.Command{
|
||||||
|
Use: "cobra",
|
||||||
|
Short: "A generator for Cobra based Applications",
|
||||||
|
Long: `Cobra is a CLI library for Go that empowers applications.
|
||||||
|
This application is a tool to generate the needed files
|
||||||
|
to quickly create a Cobra application.`,
|
||||||
|
}
|
||||||
|
|
||||||
|
//Execute adds all child commands to the root command sets flags appropriately.
|
||||||
|
func Execute() {
|
||||||
|
if err := RootCmd.Execute(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cobra.OnInitialize(initConfig)
|
||||||
|
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)")
|
||||||
|
RootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory, e.g. github.com/spf13/")
|
||||||
|
RootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "Author name for copyright attribution")
|
||||||
|
RootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "Name of license for the project (can provide `licensetext` in config)")
|
||||||
|
RootCmd.PersistentFlags().Bool("viper", true, "Use Viper for configuration")
|
||||||
|
viper.BindPFlag("author", RootCmd.PersistentFlags().Lookup("author"))
|
||||||
|
viper.BindPFlag("projectbase", RootCmd.PersistentFlags().Lookup("projectbase"))
|
||||||
|
viper.BindPFlag("useViper", RootCmd.PersistentFlags().Lookup("viper"))
|
||||||
|
viper.SetDefault("author", "NAME HERE <EMAIL ADDRESS>")
|
||||||
|
viper.SetDefault("license", "apache")
|
||||||
|
viper.SetDefault("licenseText", `
|
||||||
|
// 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.
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read in config file and ENV variables if set.
|
||||||
|
func initConfig() {
|
||||||
|
if cfgFile != "" { // enable ability to specify config file via flag
|
||||||
|
viper.SetConfigFile(cfgFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
viper.SetConfigName(".cobra") // name of config file (without extension)
|
||||||
|
viper.AddConfigPath("$HOME") // adding home directory as first search path
|
||||||
|
viper.AutomaticEnv() // read in environment variables that match
|
||||||
|
|
||||||
|
// If a config file is found, read it in.
|
||||||
|
if err := viper.ReadInConfig(); err == nil {
|
||||||
|
fmt.Println("Using config file:", viper.ConfigFileUsed())
|
||||||
|
}
|
||||||
|
}
|
20
Godeps/_workspace/src/github.com/spf13/cobra/cobra/main.go
generated
vendored
Normal file
20
Godeps/_workspace/src/github.com/spf13/cobra/cobra/main.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright © 2015 Steve Francia <spf@spf13.com>.
|
||||||
|
//
|
||||||
|
// 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 main
|
||||||
|
|
||||||
|
import "github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/cobra/cobra/cmd"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cmd.Execute()
|
||||||
|
}
|
368
Godeps/_workspace/src/github.com/spf13/cobra/cobra_test.go
generated
vendored
368
Godeps/_workspace/src/github.com/spf13/cobra/cobra_test.go
generated
vendored
@ -4,18 +4,22 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = fmt.Println
|
var _ = fmt.Println
|
||||||
var _ = os.Stderr
|
var _ = os.Stderr
|
||||||
|
|
||||||
var tp, te, tt, t1 []string
|
var tp, te, tt, t1, tr []string
|
||||||
var rootPersPre, echoPre, echoPersPre, timesPersPre []string
|
var rootPersPre, echoPre, echoPersPre, timesPersPre []string
|
||||||
var flagb1, flagb2, flagb3, flagbr, flagbp bool
|
var flagb1, flagb2, flagb3, flagbr, flagbp bool
|
||||||
var flags1, flags2a, flags2b, flags3 string
|
var flags1, flags2a, flags2b, flags3, outs string
|
||||||
var flagi1, flagi2, flagi3, flagir int
|
var flagi1, flagi2, flagi3, flagir int
|
||||||
var globalFlag1 bool
|
var globalFlag1 bool
|
||||||
var flagEcho, rootcalled bool
|
var flagEcho, rootcalled bool
|
||||||
@ -24,6 +28,16 @@ var versionUsed int
|
|||||||
const strtwoParentHelp = "help message for parent flag strtwo"
|
const strtwoParentHelp = "help message for parent flag strtwo"
|
||||||
const strtwoChildHelp = "help message for child flag strtwo"
|
const strtwoChildHelp = "help message for child flag strtwo"
|
||||||
|
|
||||||
|
var cmdHidden = &Command{
|
||||||
|
Use: "hide [secret string to print]",
|
||||||
|
Short: "Print anything to screen (if command is known)",
|
||||||
|
Long: `an absolutely utterly useless command for testing.`,
|
||||||
|
Run: func(cmd *Command, args []string) {
|
||||||
|
outs = "hidden"
|
||||||
|
},
|
||||||
|
Hidden: true,
|
||||||
|
}
|
||||||
|
|
||||||
var cmdPrint = &Command{
|
var cmdPrint = &Command{
|
||||||
Use: "print [string to print]",
|
Use: "print [string to print]",
|
||||||
Short: "Print anything to the screen",
|
Short: "Print anything to the screen",
|
||||||
@ -69,6 +83,7 @@ var cmdDeprecated = &Command{
|
|||||||
|
|
||||||
var cmdTimes = &Command{
|
var cmdTimes = &Command{
|
||||||
Use: "times [# times] [string to echo]",
|
Use: "times [# times] [string to echo]",
|
||||||
|
SuggestFor: []string{"counts"},
|
||||||
Short: "Echo anything to the screen more times",
|
Short: "Echo anything to the screen more times",
|
||||||
Long: `a slightly useless command for testing.`,
|
Long: `a slightly useless command for testing.`,
|
||||||
PersistentPreRun: func(cmd *Command, args []string) {
|
PersistentPreRun: func(cmd *Command, args []string) {
|
||||||
@ -81,7 +96,7 @@ var cmdTimes = &Command{
|
|||||||
|
|
||||||
var cmdRootNoRun = &Command{
|
var cmdRootNoRun = &Command{
|
||||||
Use: "cobra-test",
|
Use: "cobra-test",
|
||||||
Short: "The root can run it's own function",
|
Short: "The root can run its own function",
|
||||||
Long: "The root description for help",
|
Long: "The root description for help",
|
||||||
PersistentPreRun: func(cmd *Command, args []string) {
|
PersistentPreRun: func(cmd *Command, args []string) {
|
||||||
rootPersPre = args
|
rootPersPre = args
|
||||||
@ -96,9 +111,10 @@ var cmdRootSameName = &Command{
|
|||||||
|
|
||||||
var cmdRootWithRun = &Command{
|
var cmdRootWithRun = &Command{
|
||||||
Use: "cobra-test",
|
Use: "cobra-test",
|
||||||
Short: "The root can run it's own function",
|
Short: "The root can run its own function",
|
||||||
Long: "The root description for help",
|
Long: "The root description for help",
|
||||||
Run: func(cmd *Command, args []string) {
|
Run: func(cmd *Command, args []string) {
|
||||||
|
tr = args
|
||||||
rootcalled = true
|
rootcalled = true
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -127,6 +143,12 @@ var cmdVersion2 = &Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cmdColon = &Command{
|
||||||
|
Use: "cmd:colon",
|
||||||
|
Run: func(cmd *Command, args []string) {
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func flagInit() {
|
func flagInit() {
|
||||||
cmdEcho.ResetFlags()
|
cmdEcho.ResetFlags()
|
||||||
cmdPrint.ResetFlags()
|
cmdPrint.ResetFlags()
|
||||||
@ -181,7 +203,7 @@ func initializeWithSameName() *Command {
|
|||||||
|
|
||||||
func initializeWithRootCmd() *Command {
|
func initializeWithRootCmd() *Command {
|
||||||
cmdRootWithRun.ResetCommands()
|
cmdRootWithRun.ResetCommands()
|
||||||
tt, tp, te, rootcalled = nil, nil, nil, false
|
tt, tp, te, tr, rootcalled = nil, nil, nil, nil, false
|
||||||
flagInit()
|
flagInit()
|
||||||
cmdRootWithRun.Flags().BoolVarP(&flagbr, "boolroot", "b", false, "help message for flag boolroot")
|
cmdRootWithRun.Flags().BoolVarP(&flagbr, "boolroot", "b", false, "help message for flag boolroot")
|
||||||
cmdRootWithRun.Flags().IntVarP(&flagir, "introot", "i", 321, "help message for flag introot")
|
cmdRootWithRun.Flags().IntVarP(&flagir, "introot", "i", 321, "help message for flag introot")
|
||||||
@ -201,6 +223,13 @@ func fullSetupTest(input string) resulter {
|
|||||||
return fullTester(c, input)
|
return fullTester(c, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func noRRSetupTestSilenced(input string) resulter {
|
||||||
|
c := initialize()
|
||||||
|
c.SilenceErrors = true
|
||||||
|
c.SilenceUsage = true
|
||||||
|
return fullTester(c, input)
|
||||||
|
}
|
||||||
|
|
||||||
func noRRSetupTest(input string) resulter {
|
func noRRSetupTest(input string) resulter {
|
||||||
c := initialize()
|
c := initialize()
|
||||||
|
|
||||||
@ -225,6 +254,18 @@ func simpleTester(c *Command, input string) resulter {
|
|||||||
return resulter{err, output, c}
|
return resulter{err, output, c}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func simpleTesterC(c *Command, input string) resulter {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
// Testing flag with invalid input
|
||||||
|
c.SetOutput(buf)
|
||||||
|
c.SetArgs(strings.Split(input, " "))
|
||||||
|
|
||||||
|
cmd, err := c.ExecuteC()
|
||||||
|
output := buf.String()
|
||||||
|
|
||||||
|
return resulter{err, output, cmd}
|
||||||
|
}
|
||||||
|
|
||||||
func fullTester(c *Command, input string) resulter {
|
func fullTester(c *Command, input string) resulter {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
// Testing flag with invalid input
|
// Testing flag with invalid input
|
||||||
@ -250,16 +291,24 @@ func logErr(t *testing.T, found, expected string) {
|
|||||||
t.Errorf(out.String())
|
t.Errorf(out.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkStringContains(t *testing.T, found, expected string) {
|
||||||
|
if !strings.Contains(found, expected) {
|
||||||
|
logErr(t, found, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func checkResultContains(t *testing.T, x resulter, check string) {
|
func checkResultContains(t *testing.T, x resulter, check string) {
|
||||||
if !strings.Contains(x.Output, check) {
|
checkStringContains(t, x.Output, check)
|
||||||
logErr(t, x.Output, check)
|
}
|
||||||
|
|
||||||
|
func checkStringOmits(t *testing.T, found, expected string) {
|
||||||
|
if strings.Contains(found, expected) {
|
||||||
|
logErr(t, found, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkResultOmits(t *testing.T, x resulter, check string) {
|
func checkResultOmits(t *testing.T, x resulter, check string) {
|
||||||
if strings.Contains(x.Output, check) {
|
checkStringOmits(t, x.Output, check)
|
||||||
logErr(t, x.Output, check)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkOutputContains(t *testing.T, c *Command, check string) {
|
func checkOutputContains(t *testing.T, c *Command, check string) {
|
||||||
@ -374,9 +423,30 @@ func TestChildSameName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFlagLong(t *testing.T) {
|
func TestGrandChildSameName(t *testing.T) {
|
||||||
noRRSetupTest("echo --intone=13 something here")
|
c := initializeWithSameName()
|
||||||
|
cmdTimes.AddCommand(cmdPrint)
|
||||||
|
c.AddCommand(cmdTimes)
|
||||||
|
c.SetArgs(strings.Split("times print one two", " "))
|
||||||
|
c.Execute()
|
||||||
|
|
||||||
|
if te != nil || tt != nil {
|
||||||
|
t.Error("Wrong command called")
|
||||||
|
}
|
||||||
|
if tp == nil {
|
||||||
|
t.Error("Wrong command called")
|
||||||
|
}
|
||||||
|
if strings.Join(tp, " ") != "one two" {
|
||||||
|
t.Error("Command didn't parse correctly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlagLong(t *testing.T) {
|
||||||
|
noRRSetupTest("echo --intone=13 something -- here")
|
||||||
|
|
||||||
|
if cmdEcho.ArgsLenAtDash() != 1 {
|
||||||
|
t.Errorf("expected argsLenAtDash: %d but got %d", 1, cmdRootNoRun.ArgsLenAtDash())
|
||||||
|
}
|
||||||
if strings.Join(te, " ") != "something here" {
|
if strings.Join(te, " ") != "something here" {
|
||||||
t.Errorf("flags didn't leave proper args remaining..%s given", te)
|
t.Errorf("flags didn't leave proper args remaining..%s given", te)
|
||||||
}
|
}
|
||||||
@ -389,8 +459,11 @@ func TestFlagLong(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFlagShort(t *testing.T) {
|
func TestFlagShort(t *testing.T) {
|
||||||
noRRSetupTest("echo -i13 something here")
|
noRRSetupTest("echo -i13 -- something here")
|
||||||
|
|
||||||
|
if cmdEcho.ArgsLenAtDash() != 0 {
|
||||||
|
t.Errorf("expected argsLenAtDash: %d but got %d", 0, cmdRootNoRun.ArgsLenAtDash())
|
||||||
|
}
|
||||||
if strings.Join(te, " ") != "something here" {
|
if strings.Join(te, " ") != "something here" {
|
||||||
t.Errorf("flags didn't leave proper args remaining..%s given", te)
|
t.Errorf("flags didn't leave proper args remaining..%s given", te)
|
||||||
}
|
}
|
||||||
@ -440,8 +513,8 @@ func TestChildCommandFlags(t *testing.T) {
|
|||||||
t.Errorf("invalid flag should generate error")
|
t.Errorf("invalid flag should generate error")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(r.Output, "unknown shorthand") {
|
if !strings.Contains(r.Error.Error(), "unknown shorthand") {
|
||||||
t.Errorf("Wrong error message displayed, \n %s", r.Output)
|
t.Errorf("Wrong error message displayed, \n %s", r.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
if flagi2 != 99 {
|
if flagi2 != 99 {
|
||||||
@ -458,9 +531,8 @@ func TestChildCommandFlags(t *testing.T) {
|
|||||||
if r.Error == nil {
|
if r.Error == nil {
|
||||||
t.Errorf("invalid flag should generate error")
|
t.Errorf("invalid flag should generate error")
|
||||||
}
|
}
|
||||||
|
if !strings.Contains(r.Error.Error(), "unknown shorthand flag") {
|
||||||
if !strings.Contains(r.Output, "unknown shorthand flag") {
|
t.Errorf("Wrong error message displayed, \n %s", r.Error)
|
||||||
t.Errorf("Wrong error message displayed, \n %s", r.Output)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Testing with persistent flag overwritten by child
|
// Testing with persistent flag overwritten by child
|
||||||
@ -480,9 +552,8 @@ func TestChildCommandFlags(t *testing.T) {
|
|||||||
if r.Error == nil {
|
if r.Error == nil {
|
||||||
t.Errorf("invalid input should generate error")
|
t.Errorf("invalid input should generate error")
|
||||||
}
|
}
|
||||||
|
if !strings.Contains(r.Error.Error(), "invalid argument \"10E\" for i10E") {
|
||||||
if !strings.Contains(r.Output, "invalid argument \"10E\" for -i10E") {
|
t.Errorf("Wrong error message displayed, \n %s", r.Error)
|
||||||
t.Errorf("Wrong error message displayed, \n %s", r.Output)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -494,21 +565,56 @@ func TestTrailingCommandFlags(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidSubCommandFlags(t *testing.T) {
|
func TestInvalidSubcommandFlags(t *testing.T) {
|
||||||
cmd := initializeWithRootCmd()
|
cmd := initializeWithRootCmd()
|
||||||
cmd.AddCommand(cmdTimes)
|
cmd.AddCommand(cmdTimes)
|
||||||
|
|
||||||
result := simpleTester(cmd, "times --inttwo=2 --badflag=bar")
|
result := simpleTester(cmd, "times --inttwo=2 --badflag=bar")
|
||||||
|
// given that we are not checking here result.Error we check for
|
||||||
checkResultContains(t, result, "unknown flag: --badflag")
|
// stock usage message
|
||||||
|
checkResultContains(t, result, "cobra-test times [# times]")
|
||||||
if strings.Contains(result.Output, "unknown flag: --inttwo") {
|
if strings.Contains(result.Error.Error(), "unknown flag: --inttwo") {
|
||||||
t.Errorf("invalid --badflag flag shouldn't fail on 'unknown' --inttwo flag")
|
t.Errorf("invalid --badflag flag shouldn't fail on 'unknown' --inttwo flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSubCommandArgEvaluation(t *testing.T) {
|
func TestSubcommandExecuteC(t *testing.T) {
|
||||||
|
cmd := initializeWithRootCmd()
|
||||||
|
double := &Command{
|
||||||
|
Use: "double message",
|
||||||
|
Run: func(c *Command, args []string) {
|
||||||
|
msg := strings.Join(args, " ")
|
||||||
|
c.Println(msg, msg)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
echo := &Command{
|
||||||
|
Use: "echo message",
|
||||||
|
Run: func(c *Command, args []string) {
|
||||||
|
msg := strings.Join(args, " ")
|
||||||
|
c.Println(msg, msg)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.AddCommand(double, echo)
|
||||||
|
|
||||||
|
result := simpleTesterC(cmd, "double hello world")
|
||||||
|
checkResultContains(t, result, "hello world hello world")
|
||||||
|
|
||||||
|
if result.Command.Name() != "double" {
|
||||||
|
t.Errorf("invalid cmd returned from ExecuteC: should be 'double' but got %s", result.Command.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
result = simpleTesterC(cmd, "echo msg to be echoed")
|
||||||
|
checkResultContains(t, result, "msg to be echoed")
|
||||||
|
|
||||||
|
if result.Command.Name() != "echo" {
|
||||||
|
t.Errorf("invalid cmd returned from ExecuteC: should be 'echo' but got %s", result.Command.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSubcommandArgEvaluation(t *testing.T) {
|
||||||
cmd := initializeWithRootCmd()
|
cmd := initializeWithRootCmd()
|
||||||
|
|
||||||
first := &Command{
|
first := &Command{
|
||||||
@ -537,7 +643,7 @@ func TestSubCommandArgEvaluation(t *testing.T) {
|
|||||||
func TestPersistentFlags(t *testing.T) {
|
func TestPersistentFlags(t *testing.T) {
|
||||||
fullSetupTest("echo -s something -p more here")
|
fullSetupTest("echo -s something -p more here")
|
||||||
|
|
||||||
// persistentFlag should act like normal flag on it's own command
|
// persistentFlag should act like normal flag on its own command
|
||||||
if strings.Join(te, " ") != "more here" {
|
if strings.Join(te, " ") != "more here" {
|
||||||
t.Errorf("flags didn't leave proper args remaining..%s given", te)
|
t.Errorf("flags didn't leave proper args remaining..%s given", te)
|
||||||
}
|
}
|
||||||
@ -548,7 +654,7 @@ func TestPersistentFlags(t *testing.T) {
|
|||||||
t.Errorf("persistent bool flag not parsed correctly. Expected true, had %v", flagbp)
|
t.Errorf("persistent bool flag not parsed correctly. Expected true, had %v", flagbp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// persistentFlag should act like normal flag on it's own command
|
// persistentFlag should act like normal flag on its own command
|
||||||
fullSetupTest("echo times -s again -c -p test here")
|
fullSetupTest("echo times -s again -c -p test here")
|
||||||
|
|
||||||
if strings.Join(tt, " ") != "test here" {
|
if strings.Join(tt, " ") != "test here" {
|
||||||
@ -591,10 +697,38 @@ func TestNonRunChildHelp(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRunnableRootCommand(t *testing.T) {
|
func TestRunnableRootCommand(t *testing.T) {
|
||||||
fullSetupTest("")
|
x := fullSetupTest("")
|
||||||
|
|
||||||
if rootcalled != true {
|
if rootcalled != true {
|
||||||
t.Errorf("Root Function was not called")
|
t.Errorf("Root Function was not called\n out:%v", x.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVisitParents(t *testing.T) {
|
||||||
|
c := &Command{Use: "app"}
|
||||||
|
sub := &Command{Use: "sub"}
|
||||||
|
dsub := &Command{Use: "dsub"}
|
||||||
|
sub.AddCommand(dsub)
|
||||||
|
c.AddCommand(sub)
|
||||||
|
total := 0
|
||||||
|
add := func(x *Command) {
|
||||||
|
total++
|
||||||
|
}
|
||||||
|
sub.VisitParents(add)
|
||||||
|
if total != 1 {
|
||||||
|
t.Errorf("Should have visited 1 parent but visited %d", total)
|
||||||
|
}
|
||||||
|
|
||||||
|
total = 0
|
||||||
|
dsub.VisitParents(add)
|
||||||
|
if total != 2 {
|
||||||
|
t.Errorf("Should have visited 2 parent but visited %d", total)
|
||||||
|
}
|
||||||
|
|
||||||
|
total = 0
|
||||||
|
c.VisitParents(add)
|
||||||
|
if total != 0 {
|
||||||
|
t.Errorf("Should have not visited any parent but visited %d", total)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -609,7 +743,10 @@ func TestRunnableRootCommandNilInput(t *testing.T) {
|
|||||||
c.AddCommand(cmdPrint, cmdEcho)
|
c.AddCommand(cmdPrint, cmdEcho)
|
||||||
c.SetArgs(empty_arg)
|
c.SetArgs(empty_arg)
|
||||||
|
|
||||||
c.Execute()
|
err := c.Execute()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Execute() failed with %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if rootcalled != true {
|
if rootcalled != true {
|
||||||
t.Errorf("Root Function was not called")
|
t.Errorf("Root Function was not called")
|
||||||
@ -746,11 +883,69 @@ func TestRootNoCommandHelp(t *testing.T) {
|
|||||||
|
|
||||||
func TestRootUnknownCommand(t *testing.T) {
|
func TestRootUnknownCommand(t *testing.T) {
|
||||||
r := noRRSetupTest("bogus")
|
r := noRRSetupTest("bogus")
|
||||||
s := "Error: unknown command \"bogus\"\nRun 'cobra-test help' for usage.\n"
|
s := "Error: unknown command \"bogus\" for \"cobra-test\"\nRun 'cobra-test --help' for usage.\n"
|
||||||
|
|
||||||
if r.Output != s {
|
if r.Output != s {
|
||||||
t.Errorf("Unexpected response.\nExpecting to be:\n %q\nGot:\n %q\n", s, r.Output)
|
t.Errorf("Unexpected response.\nExpecting to be:\n %q\nGot:\n %q\n", s, r.Output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r = noRRSetupTest("--strtwo=a bogus")
|
||||||
|
if r.Output != s {
|
||||||
|
t.Errorf("Unexpected response.\nExpecting to be:\n %q\nGot:\n %q\n", s, r.Output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRootUnknownCommandSilenced(t *testing.T) {
|
||||||
|
r := noRRSetupTestSilenced("bogus")
|
||||||
|
s := "Run 'cobra-test --help' for usage.\n"
|
||||||
|
|
||||||
|
if r.Output != "" {
|
||||||
|
t.Errorf("Unexpected response.\nExpecting to be: \n\"\"\n Got:\n %q\n", s, r.Output)
|
||||||
|
}
|
||||||
|
|
||||||
|
r = noRRSetupTestSilenced("--strtwo=a bogus")
|
||||||
|
if r.Output != "" {
|
||||||
|
t.Errorf("Unexpected response.\nExpecting to be:\n\"\"\nGot:\n %q\n", s, r.Output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRootSuggestions(t *testing.T) {
|
||||||
|
outputWithSuggestions := "Error: unknown command \"%s\" for \"cobra-test\"\n\nDid you mean this?\n\t%s\n\nRun 'cobra-test --help' for usage.\n"
|
||||||
|
outputWithoutSuggestions := "Error: unknown command \"%s\" for \"cobra-test\"\nRun 'cobra-test --help' for usage.\n"
|
||||||
|
|
||||||
|
cmd := initializeWithRootCmd()
|
||||||
|
cmd.AddCommand(cmdTimes)
|
||||||
|
|
||||||
|
tests := map[string]string{
|
||||||
|
"time": "times",
|
||||||
|
"tiems": "times",
|
||||||
|
"tims": "times",
|
||||||
|
"timeS": "times",
|
||||||
|
"rimes": "times",
|
||||||
|
"ti": "times",
|
||||||
|
"t": "times",
|
||||||
|
"timely": "times",
|
||||||
|
"ri": "",
|
||||||
|
"timezone": "",
|
||||||
|
"foo": "",
|
||||||
|
"counts": "times",
|
||||||
|
}
|
||||||
|
|
||||||
|
for typo, suggestion := range tests {
|
||||||
|
for _, suggestionsDisabled := range []bool{false, true} {
|
||||||
|
cmd.DisableSuggestions = suggestionsDisabled
|
||||||
|
result := simpleTester(cmd, typo)
|
||||||
|
expected := ""
|
||||||
|
if len(suggestion) == 0 || suggestionsDisabled {
|
||||||
|
expected = fmt.Sprintf(outputWithoutSuggestions, typo)
|
||||||
|
} else {
|
||||||
|
expected = fmt.Sprintf(outputWithSuggestions, typo, suggestion)
|
||||||
|
}
|
||||||
|
if result.Output != expected {
|
||||||
|
t.Errorf("Unexpected response.\nExpecting to be:\n %q\nGot:\n %q\n", expected, result.Output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFlagsBeforeCommand(t *testing.T) {
|
func TestFlagsBeforeCommand(t *testing.T) {
|
||||||
@ -777,8 +972,8 @@ func TestFlagsBeforeCommand(t *testing.T) {
|
|||||||
|
|
||||||
// With parsing error properly reported
|
// With parsing error properly reported
|
||||||
x = fullSetupTest("-i10E echo")
|
x = fullSetupTest("-i10E echo")
|
||||||
if !strings.Contains(x.Output, "invalid argument \"10E\" for -i10E") {
|
if !strings.Contains(x.Error.Error(), "invalid argument \"10E\" for i10E") {
|
||||||
t.Errorf("Wrong error message displayed, \n %s", x.Output)
|
t.Errorf("Wrong error message displayed, \n %s", x.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
//With quotes
|
//With quotes
|
||||||
@ -819,6 +1014,31 @@ func TestRemoveCommand(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommandWithoutSubcommands(t *testing.T) {
|
||||||
|
c := initializeWithRootCmd()
|
||||||
|
|
||||||
|
x := simpleTester(c, "")
|
||||||
|
if x.Error != nil {
|
||||||
|
t.Errorf("Calling command without subcommands should not have error: %v", x.Error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandWithoutSubcommandsWithArg(t *testing.T) {
|
||||||
|
c := initializeWithRootCmd()
|
||||||
|
expectedArgs := []string{"arg"}
|
||||||
|
|
||||||
|
x := simpleTester(c, "arg")
|
||||||
|
if x.Error != nil {
|
||||||
|
t.Errorf("Calling command without subcommands but with arg should not have error: %v", x.Error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(expectedArgs, tr) {
|
||||||
|
t.Errorf("Calling command without subcommands but with arg has wrong args: expected: %v, actual: %v", expectedArgs, tr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestReplaceCommandWithRemove(t *testing.T) {
|
func TestReplaceCommandWithRemove(t *testing.T) {
|
||||||
versionUsed = 0
|
versionUsed = 0
|
||||||
c := initializeWithRootCmd()
|
c := initializeWithRootCmd()
|
||||||
@ -869,3 +1089,81 @@ func TestPreRun(t *testing.T) {
|
|||||||
t.Error("Wrong *Pre functions called!")
|
t.Error("Wrong *Pre functions called!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if cmdEchoSub gets PersistentPreRun from rootCmd even if is added last
|
||||||
|
func TestPeristentPreRunPropagation(t *testing.T) {
|
||||||
|
rootCmd := initialize()
|
||||||
|
|
||||||
|
// First add the cmdEchoSub to cmdPrint
|
||||||
|
cmdPrint.AddCommand(cmdEchoSub)
|
||||||
|
// Now add cmdPrint to rootCmd
|
||||||
|
rootCmd.AddCommand(cmdPrint)
|
||||||
|
|
||||||
|
rootCmd.SetArgs(strings.Split("print echosub lala", " "))
|
||||||
|
rootCmd.Execute()
|
||||||
|
|
||||||
|
if rootPersPre == nil || len(rootPersPre) == 0 || rootPersPre[0] != "lala" {
|
||||||
|
t.Error("RootCmd PersistentPreRun not called but should have been")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGlobalNormFuncPropagation(t *testing.T) {
|
||||||
|
normFunc := func(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||||
|
return pflag.NormalizedName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCmd := initialize()
|
||||||
|
rootCmd.SetGlobalNormalizationFunc(normFunc)
|
||||||
|
if reflect.ValueOf(normFunc) != reflect.ValueOf(rootCmd.GlobalNormalizationFunc()) {
|
||||||
|
t.Error("rootCmd seems to have a wrong normalization function")
|
||||||
|
}
|
||||||
|
|
||||||
|
// First add the cmdEchoSub to cmdPrint
|
||||||
|
cmdPrint.AddCommand(cmdEchoSub)
|
||||||
|
if cmdPrint.GlobalNormalizationFunc() != nil && cmdEchoSub.GlobalNormalizationFunc() != nil {
|
||||||
|
t.Error("cmdPrint and cmdEchoSub should had no normalization functions")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now add cmdPrint to rootCmd
|
||||||
|
rootCmd.AddCommand(cmdPrint)
|
||||||
|
if reflect.ValueOf(cmdPrint.GlobalNormalizationFunc()).Pointer() != reflect.ValueOf(rootCmd.GlobalNormalizationFunc()).Pointer() ||
|
||||||
|
reflect.ValueOf(cmdEchoSub.GlobalNormalizationFunc()).Pointer() != reflect.ValueOf(rootCmd.GlobalNormalizationFunc()).Pointer() {
|
||||||
|
t.Error("cmdPrint and cmdEchoSub should had the normalization function of rootCmd")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlagOnPflagCommandLine(t *testing.T) {
|
||||||
|
flagName := "flagOnCommandLine"
|
||||||
|
pflag.CommandLine.String(flagName, "", "about my flag")
|
||||||
|
r := fullSetupTest("--help")
|
||||||
|
|
||||||
|
checkResultContains(t, r, flagName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddTemplateFunctions(t *testing.T) {
|
||||||
|
AddTemplateFunc("t", func() bool { return true })
|
||||||
|
AddTemplateFuncs(template.FuncMap{
|
||||||
|
"f": func() bool { return false },
|
||||||
|
"h": func() string { return "Hello," },
|
||||||
|
"w": func() string { return "world." }})
|
||||||
|
|
||||||
|
const usage = "Hello, world."
|
||||||
|
|
||||||
|
c := &Command{}
|
||||||
|
c.SetUsageTemplate(`{{if t}}{{h}}{{end}}{{if f}}{{h}}{{end}} {{w}}`)
|
||||||
|
|
||||||
|
if us := c.UsageString(); us != usage {
|
||||||
|
t.Errorf("c.UsageString() != \"%s\", is \"%s\"", usage, us)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsageIsNotPrintedTwice(t *testing.T) {
|
||||||
|
var cmd = &Command{Use: "root"}
|
||||||
|
var sub = &Command{Use: "sub"}
|
||||||
|
cmd.AddCommand(sub)
|
||||||
|
|
||||||
|
r := simpleTester(cmd, "")
|
||||||
|
if strings.Count(r.Output, "Usage:") != 1 {
|
||||||
|
t.Error("Usage output is not printed exactly once")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
486
Godeps/_workspace/src/github.com/spf13/cobra/command.go
generated
vendored
486
Godeps/_workspace/src/github.com/spf13/cobra/command.go
generated
vendored
@ -18,13 +18,12 @@ package cobra
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/inconshreveable/mousetrap"
|
|
||||||
flag "github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/pflag"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
flag "github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Command is just that, a command for your application.
|
// Command is just that, a command for your application.
|
||||||
@ -38,6 +37,8 @@ type Command struct {
|
|||||||
Use string
|
Use string
|
||||||
// An array of aliases that can be used instead of the first word in Use.
|
// An array of aliases that can be used instead of the first word in Use.
|
||||||
Aliases []string
|
Aliases []string
|
||||||
|
// An array of command names for which this command will be suggested - similar to aliases but only suggests.
|
||||||
|
SuggestFor []string
|
||||||
// The short description shown in the 'help' output.
|
// The short description shown in the 'help' output.
|
||||||
Short string
|
Short string
|
||||||
// The long message shown in the 'help <this-command>' output.
|
// The long message shown in the 'help <this-command>' output.
|
||||||
@ -50,12 +51,18 @@ type Command struct {
|
|||||||
BashCompletionFunction string
|
BashCompletionFunction string
|
||||||
// Is this command deprecated and should print this string when used?
|
// Is this command deprecated and should print this string when used?
|
||||||
Deprecated string
|
Deprecated string
|
||||||
|
// Is this command hidden and should NOT show up in the list of available commands?
|
||||||
|
Hidden bool
|
||||||
// Full set of flags
|
// Full set of flags
|
||||||
flags *flag.FlagSet
|
flags *flag.FlagSet
|
||||||
// Set of flags childrens of this command will inherit
|
// Set of flags childrens of this command will inherit
|
||||||
pflags *flag.FlagSet
|
pflags *flag.FlagSet
|
||||||
// Flags that are declared specifically by this command (not inherited).
|
// Flags that are declared specifically by this command (not inherited).
|
||||||
lflags *flag.FlagSet
|
lflags *flag.FlagSet
|
||||||
|
// SilenceErrors is an option to quiet errors down stream
|
||||||
|
SilenceErrors bool
|
||||||
|
// Silence Usage is an option to silence usage when an error occurs.
|
||||||
|
SilenceUsage bool
|
||||||
// The *Run functions are executed in the following order:
|
// The *Run functions are executed in the following order:
|
||||||
// * PersistentPreRun()
|
// * PersistentPreRun()
|
||||||
// * PreRun()
|
// * PreRun()
|
||||||
@ -65,14 +72,26 @@ type Command struct {
|
|||||||
// All functions get the same args, the arguments after the command name
|
// All functions get the same args, the arguments after the command name
|
||||||
// PersistentPreRun: children of this command will inherit and execute
|
// PersistentPreRun: children of this command will inherit and execute
|
||||||
PersistentPreRun func(cmd *Command, args []string)
|
PersistentPreRun func(cmd *Command, args []string)
|
||||||
|
// PersistentPreRunE: PersistentPreRun but returns an error
|
||||||
|
PersistentPreRunE func(cmd *Command, args []string) error
|
||||||
// PreRun: children of this command will not inherit.
|
// PreRun: children of this command will not inherit.
|
||||||
PreRun func(cmd *Command, args []string)
|
PreRun func(cmd *Command, args []string)
|
||||||
|
// PreRunE: PreRun but returns an error
|
||||||
|
PreRunE func(cmd *Command, args []string) error
|
||||||
// Run: Typically the actual work function. Most commands will only implement this
|
// Run: Typically the actual work function. Most commands will only implement this
|
||||||
Run func(cmd *Command, args []string)
|
Run func(cmd *Command, args []string)
|
||||||
|
// RunE: Run but returns an error
|
||||||
|
RunE func(cmd *Command, args []string) error
|
||||||
// PostRun: run after the Run command.
|
// PostRun: run after the Run command.
|
||||||
PostRun func(cmd *Command, args []string)
|
PostRun func(cmd *Command, args []string)
|
||||||
|
// PostRunE: PostRun but returns an error
|
||||||
|
PostRunE func(cmd *Command, args []string) error
|
||||||
// PersistentPostRun: children of this command will inherit and execute after PostRun
|
// PersistentPostRun: children of this command will inherit and execute after PostRun
|
||||||
PersistentPostRun func(cmd *Command, args []string)
|
PersistentPostRun func(cmd *Command, args []string)
|
||||||
|
// PersistentPostRunE: PersistentPostRun but returns an error
|
||||||
|
PersistentPostRunE func(cmd *Command, args []string) error
|
||||||
|
// DisableAutoGenTag remove
|
||||||
|
DisableAutoGenTag bool
|
||||||
// Commands is the list of commands supported by this program.
|
// Commands is the list of commands supported by this program.
|
||||||
commands []*Command
|
commands []*Command
|
||||||
// Parent Command for this command
|
// Parent Command for this command
|
||||||
@ -83,7 +102,6 @@ type Command struct {
|
|||||||
commandsMaxNameLen int
|
commandsMaxNameLen int
|
||||||
|
|
||||||
flagErrorBuf *bytes.Buffer
|
flagErrorBuf *bytes.Buffer
|
||||||
cmdErrorBuf *bytes.Buffer
|
|
||||||
|
|
||||||
args []string // actual args parsed from flags
|
args []string // actual args parsed from flags
|
||||||
output *io.Writer // nil means stderr; use Out() method instead
|
output *io.Writer // nil means stderr; use Out() method instead
|
||||||
@ -92,7 +110,13 @@ type Command struct {
|
|||||||
helpTemplate string // Can be defined by Application
|
helpTemplate string // Can be defined by Application
|
||||||
helpFunc func(*Command, []string) // Help can be defined by application
|
helpFunc func(*Command, []string) // Help can be defined by application
|
||||||
helpCommand *Command // The help command
|
helpCommand *Command // The help command
|
||||||
helpFlagVal bool
|
// The global normalization function that we can use on every pFlag set and children commands
|
||||||
|
globNormFunc func(f *flag.FlagSet, name string) flag.NormalizedName
|
||||||
|
|
||||||
|
// Disable the suggestions based on Levenshtein distance that go along with 'unknown command' messages
|
||||||
|
DisableSuggestions bool
|
||||||
|
// If displaying suggestions, allows to set the minimum levenshtein distance to display, must be > 0
|
||||||
|
SuggestionsMinimumDistance int
|
||||||
}
|
}
|
||||||
|
|
||||||
// os.Args[1:] by default, if desired, can be overridden
|
// os.Args[1:] by default, if desired, can be overridden
|
||||||
@ -151,6 +175,18 @@ func (c *Command) SetHelpTemplate(s string) {
|
|||||||
c.helpTemplate = s
|
c.helpTemplate = s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetGlobalNormalizationFunc sets a normalization function to all flag sets and also to child commands.
|
||||||
|
// The user should not have a cyclic dependency on commands.
|
||||||
|
func (c *Command) SetGlobalNormalizationFunc(n func(f *flag.FlagSet, name string) flag.NormalizedName) {
|
||||||
|
c.Flags().SetNormalizeFunc(n)
|
||||||
|
c.PersistentFlags().SetNormalizeFunc(n)
|
||||||
|
c.globNormFunc = n
|
||||||
|
|
||||||
|
for _, command := range c.commands {
|
||||||
|
command.SetGlobalNormalizationFunc(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Command) UsageFunc() (f func(*Command) error) {
|
func (c *Command) UsageFunc() (f func(*Command) error) {
|
||||||
if c.usageFunc != nil {
|
if c.usageFunc != nil {
|
||||||
return c.usageFunc
|
return c.usageFunc
|
||||||
@ -161,39 +197,31 @@ func (c *Command) UsageFunc() (f func(*Command) error) {
|
|||||||
} else {
|
} else {
|
||||||
return func(c *Command) error {
|
return func(c *Command) error {
|
||||||
err := tmpl(c.Out(), c.UsageTemplate(), c)
|
err := tmpl(c.Out(), c.UsageTemplate(), c)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Print(err)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HelpFunc returns either the function set by SetHelpFunc for this command
|
||||||
|
// or a parent, or it returns a function which calls c.Help()
|
||||||
func (c *Command) HelpFunc() func(*Command, []string) {
|
func (c *Command) HelpFunc() func(*Command, []string) {
|
||||||
if c.helpFunc != nil {
|
cmd := c
|
||||||
return c.helpFunc
|
for cmd != nil {
|
||||||
|
if cmd.helpFunc != nil {
|
||||||
|
return cmd.helpFunc
|
||||||
}
|
}
|
||||||
|
cmd = cmd.parent
|
||||||
if c.HasParent() {
|
|
||||||
return c.parent.HelpFunc()
|
|
||||||
} else {
|
|
||||||
return func(c *Command, args []string) {
|
|
||||||
if len(args) == 0 {
|
|
||||||
// Help called without any topic, calling on root
|
|
||||||
c.Root().Help()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
return func(*Command, []string) {
|
||||||
cmd, _, e := c.Root().Find(args)
|
err := c.Help()
|
||||||
if cmd == nil || e != nil {
|
|
||||||
c.Printf("Unknown help topic %#q.", args)
|
|
||||||
|
|
||||||
c.Root().Usage()
|
|
||||||
} else {
|
|
||||||
err := cmd.Help()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Println(err)
|
c.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var minUsagePadding int = 25
|
var minUsagePadding int = 25
|
||||||
|
|
||||||
@ -234,8 +262,7 @@ func (c *Command) UsageTemplate() string {
|
|||||||
if c.HasParent() {
|
if c.HasParent() {
|
||||||
return c.parent.UsageTemplate()
|
return c.parent.UsageTemplate()
|
||||||
} else {
|
} else {
|
||||||
return `{{ $cmd := . }}
|
return `Usage:{{if .Runnable}}
|
||||||
Usage: {{if .Runnable}}
|
|
||||||
{{.UseLine}}{{if .HasFlags}} [flags]{{end}}{{end}}{{if .HasSubCommands}}
|
{{.UseLine}}{{if .HasFlags}} [flags]{{end}}{{end}}{{if .HasSubCommands}}
|
||||||
{{ .CommandPath}} [command]{{end}}{{if gt .Aliases 0}}
|
{{ .CommandPath}} [command]{{end}}{{if gt .Aliases 0}}
|
||||||
|
|
||||||
@ -244,22 +271,22 @@ Aliases:
|
|||||||
{{end}}{{if .HasExample}}
|
{{end}}{{if .HasExample}}
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
{{ .Example }}
|
{{ .Example }}{{end}}{{ if .HasAvailableSubCommands}}
|
||||||
{{end}}{{ if .HasRunnableSubCommands}}
|
|
||||||
|
|
||||||
Available Commands: {{range .Commands}}{{if and (.Runnable) (not .Deprecated)}}
|
Available Commands:{{range .Commands}}{{if .IsAvailableCommand}}
|
||||||
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}
|
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasLocalFlags}}
|
||||||
{{end}}
|
|
||||||
{{ if .HasLocalFlags}}Flags:
|
Flags:
|
||||||
{{.LocalFlags.FlagUsages}}{{end}}
|
{{.LocalFlags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasInheritedFlags}}
|
||||||
{{ if .HasInheritedFlags}}Global Flags:
|
|
||||||
{{.InheritedFlags.FlagUsages}}{{end}}{{if or (.HasHelpSubCommands) (.HasRunnableSiblings)}}
|
Global Flags:
|
||||||
Additional help topics:
|
{{.InheritedFlags.FlagUsages | trimRightSpace}}{{end}}{{if .HasHelpSubCommands}}
|
||||||
{{if .HasHelpSubCommands}}{{range .Commands}}{{if and (not .Runnable) (not .Deprecated)}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasRunnableSiblings }}{{range .Parent.Commands}}{{if and (not .Runnable) (not .Deprecated)}}{{if not (eq .Name $cmd.Name) }}
|
|
||||||
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{end}}
|
Additional help topics:{{range .Commands}}{{if .IsHelpCommand}}
|
||||||
{{end}}{{ if .HasSubCommands }}
|
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasSubCommands }}
|
||||||
Use "{{.Root.Name}} help [command]" for more information about a command.
|
|
||||||
{{end}}`
|
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
|
||||||
|
`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,9 +298,9 @@ func (c *Command) HelpTemplate() string {
|
|||||||
if c.HasParent() {
|
if c.HasParent() {
|
||||||
return c.parent.HelpTemplate()
|
return c.parent.HelpTemplate()
|
||||||
} else {
|
} else {
|
||||||
return `{{with or .Long .Short }}{{. | trim}}{{end}}
|
return `{{with or .Long .Short }}{{. | trim}}
|
||||||
{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}
|
|
||||||
`
|
{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,31 +387,30 @@ func argsMinusFirstX(args []string, x string) []string {
|
|||||||
|
|
||||||
// find the target command given the args and command tree
|
// find the target command given the args and command tree
|
||||||
// Meant to be run on the highest node. Only searches down.
|
// Meant to be run on the highest node. Only searches down.
|
||||||
func (c *Command) Find(arrs []string) (*Command, []string, error) {
|
func (c *Command) Find(args []string) (*Command, []string, error) {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
return nil, nil, fmt.Errorf("Called find() on a nil Command")
|
return nil, nil, fmt.Errorf("Called find() on a nil Command")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(arrs) == 0 {
|
|
||||||
return c.Root(), arrs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var innerfind func(*Command, []string) (*Command, []string)
|
var innerfind func(*Command, []string) (*Command, []string)
|
||||||
|
|
||||||
innerfind = func(c *Command, args []string) (*Command, []string) {
|
innerfind = func(c *Command, innerArgs []string) (*Command, []string) {
|
||||||
if len(args) > 0 && c.HasSubCommands() {
|
argsWOflags := stripFlags(innerArgs, c)
|
||||||
argsWOflags := stripFlags(args, c)
|
if len(argsWOflags) == 0 {
|
||||||
if len(argsWOflags) > 0 {
|
return c, innerArgs
|
||||||
|
}
|
||||||
|
nextSubCmd := argsWOflags[0]
|
||||||
matches := make([]*Command, 0)
|
matches := make([]*Command, 0)
|
||||||
for _, cmd := range c.commands {
|
for _, cmd := range c.commands {
|
||||||
if cmd.Name() == argsWOflags[0] || cmd.HasAlias(argsWOflags[0]) { // exact name or alias match
|
if cmd.Name() == nextSubCmd || cmd.HasAlias(nextSubCmd) { // exact name or alias match
|
||||||
return innerfind(cmd, argsMinusFirstX(args, argsWOflags[0]))
|
return innerfind(cmd, argsMinusFirstX(innerArgs, nextSubCmd))
|
||||||
} else if EnablePrefixMatching {
|
}
|
||||||
if strings.HasPrefix(cmd.Name(), argsWOflags[0]) { // prefix match
|
if EnablePrefixMatching {
|
||||||
|
if strings.HasPrefix(cmd.Name(), nextSubCmd) { // prefix match
|
||||||
matches = append(matches, cmd)
|
matches = append(matches, cmd)
|
||||||
}
|
}
|
||||||
for _, x := range cmd.Aliases {
|
for _, x := range cmd.Aliases {
|
||||||
if strings.HasPrefix(x, argsWOflags[0]) {
|
if strings.HasPrefix(x, nextSubCmd) {
|
||||||
matches = append(matches, cmd)
|
matches = append(matches, cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -393,38 +419,96 @@ func (c *Command) Find(arrs []string) (*Command, []string, error) {
|
|||||||
|
|
||||||
// only accept a single prefix match - multiple matches would be ambiguous
|
// only accept a single prefix match - multiple matches would be ambiguous
|
||||||
if len(matches) == 1 {
|
if len(matches) == 1 {
|
||||||
return innerfind(matches[0], argsMinusFirstX(args, argsWOflags[0]))
|
return innerfind(matches[0], argsMinusFirstX(innerArgs, argsWOflags[0]))
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, args
|
return c, innerArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
commandFound, a := innerfind(c, arrs)
|
commandFound, a := innerfind(c, args)
|
||||||
|
argsWOflags := stripFlags(a, commandFound)
|
||||||
|
|
||||||
// If we matched on the root, but we asked for a subcommand, return an error
|
// no subcommand, always take args
|
||||||
if commandFound.Name() == c.Name() && len(stripFlags(arrs, c)) > 0 && commandFound.Name() != arrs[0] {
|
if !commandFound.HasSubCommands() {
|
||||||
return nil, a, fmt.Errorf("unknown command %q", a[0])
|
return commandFound, a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// root command with subcommands, do subcommand checking
|
||||||
|
if commandFound == c && len(argsWOflags) > 0 {
|
||||||
|
suggestionsString := ""
|
||||||
|
if !c.DisableSuggestions {
|
||||||
|
if c.SuggestionsMinimumDistance <= 0 {
|
||||||
|
c.SuggestionsMinimumDistance = 2
|
||||||
|
}
|
||||||
|
if suggestions := c.SuggestionsFor(argsWOflags[0]); len(suggestions) > 0 {
|
||||||
|
suggestionsString += "\n\nDid you mean this?\n"
|
||||||
|
for _, s := range suggestions {
|
||||||
|
suggestionsString += fmt.Sprintf("\t%v\n", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return commandFound, a, fmt.Errorf("unknown command %q for %q%s", argsWOflags[0], commandFound.CommandPath(), suggestionsString)
|
||||||
}
|
}
|
||||||
|
|
||||||
return commandFound, a, nil
|
return commandFound, a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Command) SuggestionsFor(typedName string) []string {
|
||||||
|
suggestions := []string{}
|
||||||
|
for _, cmd := range c.commands {
|
||||||
|
if cmd.IsAvailableCommand() {
|
||||||
|
levenshteinDistance := ld(typedName, cmd.Name(), true)
|
||||||
|
suggestByLevenshtein := levenshteinDistance <= c.SuggestionsMinimumDistance
|
||||||
|
suggestByPrefix := strings.HasPrefix(strings.ToLower(cmd.Name()), strings.ToLower(typedName))
|
||||||
|
if suggestByLevenshtein || suggestByPrefix {
|
||||||
|
suggestions = append(suggestions, cmd.Name())
|
||||||
|
}
|
||||||
|
for _, explicitSuggestion := range cmd.SuggestFor {
|
||||||
|
if strings.EqualFold(typedName, explicitSuggestion) {
|
||||||
|
suggestions = append(suggestions, cmd.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return suggestions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) VisitParents(fn func(*Command)) {
|
||||||
|
var traverse func(*Command) *Command
|
||||||
|
|
||||||
|
traverse = func(x *Command) *Command {
|
||||||
|
if x != c {
|
||||||
|
fn(x)
|
||||||
|
}
|
||||||
|
if x.HasParent() {
|
||||||
|
return traverse(x.parent)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
traverse(c)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Command) Root() *Command {
|
func (c *Command) Root() *Command {
|
||||||
var findRoot func(*Command) *Command
|
var findRoot func(*Command) *Command
|
||||||
|
|
||||||
findRoot = func(x *Command) *Command {
|
findRoot = func(x *Command) *Command {
|
||||||
if x.HasParent() {
|
if x.HasParent() {
|
||||||
return findRoot(x.parent)
|
return findRoot(x.parent)
|
||||||
} else {
|
|
||||||
return x
|
|
||||||
}
|
}
|
||||||
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
return findRoot(c)
|
return findRoot(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ArgsLenAtDash will return the length of f.Args at the moment when a -- was
|
||||||
|
// found during arg parsing. This allows your program to know which args were
|
||||||
|
// before the -- and which came after. (Description from
|
||||||
|
// https://godoc.org/github.com/spf13/pflag#FlagSet.ArgsLenAtDash).
|
||||||
|
func (c *Command) ArgsLenAtDash() int {
|
||||||
|
return c.Flags().ArgsLenAtDash()
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Command) execute(a []string) (err error) {
|
func (c *Command) execute(a []string) (err error) {
|
||||||
if c == nil {
|
if c == nil {
|
||||||
return fmt.Errorf("Called Execute() on a nil Command")
|
return fmt.Errorf("Called Execute() on a nil Command")
|
||||||
@ -434,49 +518,73 @@ func (c *Command) execute(a []string) (err error) {
|
|||||||
c.Printf("Command %q is deprecated, %s\n", c.Name(), c.Deprecated)
|
c.Printf("Command %q is deprecated, %s\n", c.Name(), c.Deprecated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initialize help flag as the last point possible to allow for user
|
||||||
|
// overriding
|
||||||
|
c.initHelpFlag()
|
||||||
|
|
||||||
err = c.ParseFlags(a)
|
err = c.ParseFlags(a)
|
||||||
if err == flag.ErrHelp {
|
|
||||||
c.Help()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// We're writing subcommand usage to root command's error buffer to have it displayed to the user
|
|
||||||
r := c.Root()
|
|
||||||
if r.cmdErrorBuf == nil {
|
|
||||||
r.cmdErrorBuf = new(bytes.Buffer)
|
|
||||||
}
|
|
||||||
// for writing the usage to the buffer we need to switch the output temporarily
|
|
||||||
// since Out() returns root output, you also need to revert that on root
|
|
||||||
out := r.Out()
|
|
||||||
r.SetOutput(r.cmdErrorBuf)
|
|
||||||
c.Usage()
|
|
||||||
r.SetOutput(out)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// If help is called, regardless of other flags, we print that.
|
// If help is called, regardless of other flags, return we want help
|
||||||
// Print help also if c.Run is nil.
|
// Also say we need help if the command isn't runnable.
|
||||||
if c.helpFlagVal || !c.Runnable() {
|
helpVal, err := c.Flags().GetBool("help")
|
||||||
c.Help()
|
if err != nil {
|
||||||
return nil
|
// should be impossible to get here as we always declare a help
|
||||||
|
// flag in initHelpFlag()
|
||||||
|
c.Println("\"help\" flag declared as non-bool. Please correct your code")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if helpVal || !c.Runnable() {
|
||||||
|
return flag.ErrHelp
|
||||||
}
|
}
|
||||||
|
|
||||||
c.preRun()
|
c.preRun()
|
||||||
argWoFlags := c.Flags().Args()
|
argWoFlags := c.Flags().Args()
|
||||||
|
|
||||||
if c.PersistentPreRun != nil {
|
for p := c; p != nil; p = p.Parent() {
|
||||||
c.PersistentPreRun(c, argWoFlags)
|
if p.PersistentPreRunE != nil {
|
||||||
|
if err := p.PersistentPreRunE(c, argWoFlags); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
if c.PreRun != nil {
|
break
|
||||||
|
} else if p.PersistentPreRun != nil {
|
||||||
|
p.PersistentPreRun(c, argWoFlags)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.PreRunE != nil {
|
||||||
|
if err := c.PreRunE(c, argWoFlags); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if c.PreRun != nil {
|
||||||
c.PreRun(c, argWoFlags)
|
c.PreRun(c, argWoFlags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.RunE != nil {
|
||||||
|
if err := c.RunE(c, argWoFlags); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
c.Run(c, argWoFlags)
|
c.Run(c, argWoFlags)
|
||||||
|
}
|
||||||
if c.PostRun != nil {
|
if c.PostRunE != nil {
|
||||||
|
if err := c.PostRunE(c, argWoFlags); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if c.PostRun != nil {
|
||||||
c.PostRun(c, argWoFlags)
|
c.PostRun(c, argWoFlags)
|
||||||
}
|
}
|
||||||
if c.PersistentPostRun != nil {
|
for p := c; p != nil; p = p.Parent() {
|
||||||
c.PersistentPostRun(c, argWoFlags)
|
if p.PersistentPostRunE != nil {
|
||||||
|
if err := p.PersistentPostRunE(c, argWoFlags); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
break
|
||||||
|
} else if p.PersistentPostRun != nil {
|
||||||
|
p.PersistentPostRun(c, argWoFlags)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -503,52 +611,80 @@ func (c *Command) errorMsgFromParse() string {
|
|||||||
// Call execute to use the args (os.Args[1:] by default)
|
// Call execute to use the args (os.Args[1:] by default)
|
||||||
// and run through the command tree finding appropriate matches
|
// and run through the command tree finding appropriate matches
|
||||||
// for commands and then corresponding flags.
|
// for commands and then corresponding flags.
|
||||||
func (c *Command) Execute() (err error) {
|
func (c *Command) Execute() error {
|
||||||
|
_, err := c.ExecuteC()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) ExecuteC() (cmd *Command, err error) {
|
||||||
|
|
||||||
// Regardless of what command execute is called on, run on Root only
|
// Regardless of what command execute is called on, run on Root only
|
||||||
if c.HasParent() {
|
if c.HasParent() {
|
||||||
return c.Root().Execute()
|
return c.Root().ExecuteC()
|
||||||
}
|
}
|
||||||
|
|
||||||
if EnableWindowsMouseTrap && runtime.GOOS == "windows" {
|
// windows hook
|
||||||
if mousetrap.StartedByExplorer() {
|
if preExecHookFn != nil {
|
||||||
c.Print(MousetrapHelpText)
|
preExecHookFn(c)
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize help as the last point possible to allow for user
|
// initialize help as the last point possible to allow for user
|
||||||
// overriding
|
// overriding
|
||||||
c.initHelp()
|
c.initHelpCmd()
|
||||||
|
|
||||||
var args []string
|
var args []string
|
||||||
|
|
||||||
if len(c.args) == 0 {
|
// Workaround FAIL with "go test -v" or "cobra.test -test.v", see #155
|
||||||
|
if len(c.args) == 0 && filepath.Base(os.Args[0]) != "cobra.test" {
|
||||||
args = os.Args[1:]
|
args = os.Args[1:]
|
||||||
} else {
|
} else {
|
||||||
args = c.args
|
args = c.args
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd, flags, err := c.Find(args)
|
cmd, flags, err := c.Find(args)
|
||||||
if err == nil {
|
|
||||||
err = cmd.execute(flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == flag.ErrHelp {
|
// If found parse to a subcommand and then failed, talk about the subcommand
|
||||||
c.Help()
|
if cmd != nil {
|
||||||
|
c = cmd
|
||||||
} else {
|
}
|
||||||
|
if !c.SilenceErrors {
|
||||||
c.Println("Error:", err.Error())
|
c.Println("Error:", err.Error())
|
||||||
c.Printf("Run '%v help' for usage.\n", c.Root().Name())
|
c.Printf("Run '%v --help' for usage.\n", c.CommandPath())
|
||||||
|
}
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
err = cmd.execute(flags)
|
||||||
|
if err != nil {
|
||||||
|
// Always show help if requested, even if SilenceErrors is in
|
||||||
|
// effect
|
||||||
|
if err == flag.ErrHelp {
|
||||||
|
cmd.HelpFunc()(cmd, args)
|
||||||
|
return cmd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If root command has SilentErrors flagged,
|
||||||
|
// all subcommands should respect it
|
||||||
|
if !cmd.SilenceErrors && !c.SilenceErrors {
|
||||||
|
c.Println("Error:", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// If root command has SilentUsage flagged,
|
||||||
|
// all subcommands should respect it
|
||||||
|
if !cmd.SilenceUsage && !c.SilenceUsage {
|
||||||
|
c.Println(cmd.UsageString())
|
||||||
|
}
|
||||||
|
return cmd, err
|
||||||
|
}
|
||||||
|
return cmd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) initHelpFlag() {
|
||||||
|
if c.Flags().Lookup("help") == nil {
|
||||||
|
c.Flags().BoolP("help", "h", false, "help for "+c.Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
func (c *Command) initHelpCmd() {
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Command) initHelp() {
|
|
||||||
if c.helpCommand == nil {
|
if c.helpCommand == nil {
|
||||||
if !c.HasSubCommands() {
|
if !c.HasSubCommands() {
|
||||||
return
|
return
|
||||||
@ -559,9 +695,19 @@ func (c *Command) initHelp() {
|
|||||||
Short: "Help about any command",
|
Short: "Help about any command",
|
||||||
Long: `Help provides help for any command in the application.
|
Long: `Help provides help for any command in the application.
|
||||||
Simply type ` + c.Name() + ` help [path to command] for full details.`,
|
Simply type ` + c.Name() + ` help [path to command] for full details.`,
|
||||||
Run: c.HelpFunc(),
|
|
||||||
PersistentPreRun: func(cmd *Command, args []string) {},
|
PersistentPreRun: func(cmd *Command, args []string) {},
|
||||||
PersistentPostRun: func(cmd *Command, args []string) {},
|
PersistentPostRun: func(cmd *Command, args []string) {},
|
||||||
|
|
||||||
|
Run: func(c *Command, args []string) {
|
||||||
|
cmd, _, e := c.Root().Find(args)
|
||||||
|
if cmd == nil || e != nil {
|
||||||
|
c.Printf("Unknown help topic %#q.", args)
|
||||||
|
c.Root().Usage()
|
||||||
|
} else {
|
||||||
|
helpFunc := cmd.HelpFunc()
|
||||||
|
helpFunc(cmd, args)
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.AddCommand(c.helpCommand)
|
c.AddCommand(c.helpCommand)
|
||||||
@ -571,8 +717,6 @@ func (c *Command) initHelp() {
|
|||||||
func (c *Command) ResetCommands() {
|
func (c *Command) ResetCommands() {
|
||||||
c.commands = nil
|
c.commands = nil
|
||||||
c.helpCommand = nil
|
c.helpCommand = nil
|
||||||
c.cmdErrorBuf = new(bytes.Buffer)
|
|
||||||
c.cmdErrorBuf.Reset()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Commands returns a slice of child commands.
|
//Commands returns a slice of child commands.
|
||||||
@ -600,15 +744,11 @@ func (c *Command) AddCommand(cmds ...*Command) {
|
|||||||
if nameLen > c.commandsMaxNameLen {
|
if nameLen > c.commandsMaxNameLen {
|
||||||
c.commandsMaxNameLen = nameLen
|
c.commandsMaxNameLen = nameLen
|
||||||
}
|
}
|
||||||
|
// If global normalization function exists, update all children
|
||||||
|
if c.globNormFunc != nil {
|
||||||
|
x.SetGlobalNormalizationFunc(c.globNormFunc)
|
||||||
|
}
|
||||||
c.commands = append(c.commands, x)
|
c.commands = append(c.commands, x)
|
||||||
|
|
||||||
// Pass on peristent pre/post functions to children
|
|
||||||
if x.PersistentPreRun == nil {
|
|
||||||
x.PersistentPreRun = c.PersistentPreRun
|
|
||||||
}
|
|
||||||
if x.PersistentPostRun == nil {
|
|
||||||
x.PersistentPostRun = c.PersistentPostRun
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -788,7 +928,7 @@ func (c *Command) HasExample() bool {
|
|||||||
|
|
||||||
// Determine if the command is itself runnable
|
// Determine if the command is itself runnable
|
||||||
func (c *Command) Runnable() bool {
|
func (c *Command) Runnable() bool {
|
||||||
return c.Run != nil
|
return c.Run != nil || c.RunE != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine if the command has children commands
|
// Determine if the command has children commands
|
||||||
@ -796,34 +936,75 @@ func (c *Command) HasSubCommands() bool {
|
|||||||
return len(c.commands) > 0
|
return len(c.commands) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Command) HasRunnableSiblings() bool {
|
// IsAvailableCommand determines if a command is available as a non-help command
|
||||||
if !c.HasParent() {
|
// (this includes all non deprecated/hidden commands)
|
||||||
return false
|
func (c *Command) IsAvailableCommand() bool {
|
||||||
}
|
if len(c.Deprecated) != 0 || c.Hidden {
|
||||||
for _, sub := range c.parent.commands {
|
|
||||||
if sub.Runnable() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.HasParent() && c.Parent().helpCommand == c {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Runnable() || c.HasAvailableSubCommands() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsHelpCommand determines if a command is a 'help' command; a help command is
|
||||||
|
// determined by the fact that it is NOT runnable/hidden/deprecated, and has no
|
||||||
|
// sub commands that are runnable/hidden/deprecated
|
||||||
|
func (c *Command) IsHelpCommand() bool {
|
||||||
|
|
||||||
|
// if a command is runnable, deprecated, or hidden it is not a 'help' command
|
||||||
|
if c.Runnable() || len(c.Deprecated) != 0 || c.Hidden {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// if any non-help sub commands are found, the command is not a 'help' command
|
||||||
|
for _, sub := range c.commands {
|
||||||
|
if !sub.IsHelpCommand() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the command either has no sub commands, or no non-help sub commands
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasHelpSubCommands determines if a command has any avilable 'help' sub commands
|
||||||
|
// that need to be shown in the usage/help default template under 'additional help
|
||||||
|
// topics'
|
||||||
func (c *Command) HasHelpSubCommands() bool {
|
func (c *Command) HasHelpSubCommands() bool {
|
||||||
|
|
||||||
|
// return true on the first found available 'help' sub command
|
||||||
for _, sub := range c.commands {
|
for _, sub := range c.commands {
|
||||||
if !sub.Runnable() {
|
if sub.IsHelpCommand() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the command either has no sub commands, or no available 'help' sub commands
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine if the command has runnable children commands
|
// HasAvailableSubCommands determines if a command has available sub commands that
|
||||||
func (c *Command) HasRunnableSubCommands() bool {
|
// need to be shown in the usage/help default template under 'available commands'
|
||||||
|
func (c *Command) HasAvailableSubCommands() bool {
|
||||||
|
|
||||||
|
// return true on the first found available (non deprecated/help/hidden)
|
||||||
|
// sub command
|
||||||
for _, sub := range c.commands {
|
for _, sub := range c.commands {
|
||||||
if sub.Runnable() {
|
if sub.IsAvailableCommand() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the command either has no sub comamnds, or no available (non deprecated/help/hidden)
|
||||||
|
// sub commands
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -832,6 +1013,11 @@ func (c *Command) HasParent() bool {
|
|||||||
return c.parent != nil
|
return c.parent != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GlobalNormalizationFunc returns the global normalization function or nil if doesn't exists
|
||||||
|
func (c *Command) GlobalNormalizationFunc() func(f *flag.FlagSet, name string) flag.NormalizedName {
|
||||||
|
return c.globNormFunc
|
||||||
|
}
|
||||||
|
|
||||||
// Get the complete FlagSet that applies to this command (local and persistent declared here and by all parents)
|
// Get the complete FlagSet that applies to this command (local and persistent declared here and by all parents)
|
||||||
func (c *Command) Flags() *flag.FlagSet {
|
func (c *Command) Flags() *flag.FlagSet {
|
||||||
if c.flags == nil {
|
if c.flags == nil {
|
||||||
@ -840,7 +1026,6 @@ func (c *Command) Flags() *flag.FlagSet {
|
|||||||
c.flagErrorBuf = new(bytes.Buffer)
|
c.flagErrorBuf = new(bytes.Buffer)
|
||||||
}
|
}
|
||||||
c.flags.SetOutput(c.flagErrorBuf)
|
c.flags.SetOutput(c.flagErrorBuf)
|
||||||
c.PersistentFlags().BoolVarP(&c.helpFlagVal, "help", "h", false, "help for "+c.Name())
|
|
||||||
}
|
}
|
||||||
return c.flags
|
return c.flags
|
||||||
}
|
}
|
||||||
@ -853,6 +1038,13 @@ func (c *Command) LocalFlags() *flag.FlagSet {
|
|||||||
c.lflags.VisitAll(func(f *flag.Flag) {
|
c.lflags.VisitAll(func(f *flag.Flag) {
|
||||||
local.AddFlag(f)
|
local.AddFlag(f)
|
||||||
})
|
})
|
||||||
|
if !c.HasParent() {
|
||||||
|
flag.CommandLine.VisitAll(func(f *flag.Flag) {
|
||||||
|
if local.Lookup(f.Name) == nil {
|
||||||
|
local.AddFlag(f)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
return local
|
return local
|
||||||
}
|
}
|
||||||
|
|
||||||
|
5
Godeps/_workspace/src/github.com/spf13/cobra/command_notwin.go
generated
vendored
Normal file
5
Godeps/_workspace/src/github.com/spf13/cobra/command_notwin.go
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
var preExecHookFn func(*Command) = nil
|
24
Godeps/_workspace/src/github.com/spf13/cobra/command_test.go
generated
vendored
24
Godeps/_workspace/src/github.com/spf13/cobra/command_test.go
generated
vendored
@ -5,6 +5,30 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// test to ensure hidden commands run as intended
|
||||||
|
func TestHiddenCommandExecutes(t *testing.T) {
|
||||||
|
|
||||||
|
// ensure that outs does not already equal what the command will be setting it
|
||||||
|
// to, if it did this test would not actually be testing anything...
|
||||||
|
if outs == "hidden" {
|
||||||
|
t.Errorf("outs should NOT EQUAL hidden")
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdHidden.Execute()
|
||||||
|
|
||||||
|
// upon running the command, the value of outs should now be 'hidden'
|
||||||
|
if outs != "hidden" {
|
||||||
|
t.Errorf("Hidden command failed to run!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// test to ensure hidden commands do not show up in usage/help text
|
||||||
|
func TestHiddenCommandIsHidden(t *testing.T) {
|
||||||
|
if cmdHidden.IsAvailableCommand() {
|
||||||
|
t.Errorf("Hidden command found!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestStripFlags(t *testing.T) {
|
func TestStripFlags(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input []string
|
input []string
|
||||||
|
26
Godeps/_workspace/src/github.com/spf13/cobra/command_win.go
generated
vendored
Normal file
26
Godeps/_workspace/src/github.com/spf13/cobra/command_win.go
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package cobra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/inconshreveable/mousetrap"
|
||||||
|
)
|
||||||
|
|
||||||
|
var preExecHookFn = preExecHook
|
||||||
|
|
||||||
|
// enables an information splash screen on Windows if the CLI is started from explorer.exe.
|
||||||
|
var MousetrapHelpText string = `This is a command line tool
|
||||||
|
|
||||||
|
You need to open cmd.exe and run it from there.
|
||||||
|
`
|
||||||
|
|
||||||
|
func preExecHook(c *Command) {
|
||||||
|
if mousetrap.StartedByExplorer() {
|
||||||
|
c.Print(MousetrapHelpText)
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
145
Godeps/_workspace/src/github.com/spf13/cobra/doc/cmd_test.go
generated
vendored
Normal file
145
Godeps/_workspace/src/github.com/spf13/cobra/doc/cmd_test.go
generated
vendored
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
package doc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var flagb1, flagb2, flagb3, flagbr, flagbp bool
|
||||||
|
var flags1, flags2a, flags2b, flags3 string
|
||||||
|
var flagi1, flagi2, flagi3, flagir int
|
||||||
|
|
||||||
|
const strtwoParentHelp = "help message for parent flag strtwo"
|
||||||
|
const strtwoChildHelp = "help message for child flag strtwo"
|
||||||
|
|
||||||
|
var cmdEcho = &cobra.Command{
|
||||||
|
Use: "echo [string to echo]",
|
||||||
|
Aliases: []string{"say"},
|
||||||
|
Short: "Echo anything to the screen",
|
||||||
|
Long: `an utterly useless command for testing.`,
|
||||||
|
Example: "Just run cobra-test echo",
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdEchoSub = &cobra.Command{
|
||||||
|
Use: "echosub [string to print]",
|
||||||
|
Short: "second sub command for echo",
|
||||||
|
Long: `an absolutely utterly useless command for testing gendocs!.`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {},
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdDeprecated = &cobra.Command{
|
||||||
|
Use: "deprecated [can't do anything here]",
|
||||||
|
Short: "A command which is deprecated",
|
||||||
|
Long: `an absolutely utterly useless command for testing deprecation!.`,
|
||||||
|
Deprecated: "Please use echo instead",
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdTimes = &cobra.Command{
|
||||||
|
Use: "times [# times] [string to echo]",
|
||||||
|
SuggestFor: []string{"counts"},
|
||||||
|
Short: "Echo anything to the screen more times",
|
||||||
|
Long: `a slightly useless command for testing.`,
|
||||||
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {},
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {},
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdPrint = &cobra.Command{
|
||||||
|
Use: "print [string to print]",
|
||||||
|
Short: "Print anything to the screen",
|
||||||
|
Long: `an absolutely utterly useless command for testing.`,
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdRootNoRun = &cobra.Command{
|
||||||
|
Use: "cobra-test",
|
||||||
|
Short: "The root can run its own function",
|
||||||
|
Long: "The root description for help",
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdRootSameName = &cobra.Command{
|
||||||
|
Use: "print",
|
||||||
|
Short: "Root with the same name as a subcommand",
|
||||||
|
Long: "The root description for help",
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdRootWithRun = &cobra.Command{
|
||||||
|
Use: "cobra-test",
|
||||||
|
Short: "The root can run its own function",
|
||||||
|
Long: "The root description for help",
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdSubNoRun = &cobra.Command{
|
||||||
|
Use: "subnorun",
|
||||||
|
Short: "A subcommand without a Run function",
|
||||||
|
Long: "A long output about a subcommand without a Run function",
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdVersion1 = &cobra.Command{
|
||||||
|
Use: "version",
|
||||||
|
Short: "Print the version number",
|
||||||
|
Long: `First version of the version command`,
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdVersion2 = &cobra.Command{
|
||||||
|
Use: "version",
|
||||||
|
Short: "Print the version number",
|
||||||
|
Long: `Second version of the version command`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func flagInit() {
|
||||||
|
cmdEcho.ResetFlags()
|
||||||
|
cmdPrint.ResetFlags()
|
||||||
|
cmdTimes.ResetFlags()
|
||||||
|
cmdRootNoRun.ResetFlags()
|
||||||
|
cmdRootSameName.ResetFlags()
|
||||||
|
cmdRootWithRun.ResetFlags()
|
||||||
|
cmdSubNoRun.ResetFlags()
|
||||||
|
cmdRootNoRun.PersistentFlags().StringVarP(&flags2a, "strtwo", "t", "two", strtwoParentHelp)
|
||||||
|
cmdEcho.Flags().IntVarP(&flagi1, "intone", "i", 123, "help message for flag intone")
|
||||||
|
cmdTimes.Flags().IntVarP(&flagi2, "inttwo", "j", 234, "help message for flag inttwo")
|
||||||
|
cmdPrint.Flags().IntVarP(&flagi3, "intthree", "i", 345, "help message for flag intthree")
|
||||||
|
cmdEcho.PersistentFlags().StringVarP(&flags1, "strone", "s", "one", "help message for flag strone")
|
||||||
|
cmdEcho.PersistentFlags().BoolVarP(&flagbp, "persistentbool", "p", false, "help message for flag persistentbool")
|
||||||
|
cmdTimes.PersistentFlags().StringVarP(&flags2b, "strtwo", "t", "2", strtwoChildHelp)
|
||||||
|
cmdPrint.PersistentFlags().StringVarP(&flags3, "strthree", "s", "three", "help message for flag strthree")
|
||||||
|
cmdEcho.Flags().BoolVarP(&flagb1, "boolone", "b", true, "help message for flag boolone")
|
||||||
|
cmdTimes.Flags().BoolVarP(&flagb2, "booltwo", "c", false, "help message for flag booltwo")
|
||||||
|
cmdPrint.Flags().BoolVarP(&flagb3, "boolthree", "b", true, "help message for flag boolthree")
|
||||||
|
cmdVersion1.ResetFlags()
|
||||||
|
cmdVersion2.ResetFlags()
|
||||||
|
}
|
||||||
|
|
||||||
|
func initializeWithRootCmd() *cobra.Command {
|
||||||
|
cmdRootWithRun.ResetCommands()
|
||||||
|
flagInit()
|
||||||
|
cmdRootWithRun.Flags().BoolVarP(&flagbr, "boolroot", "b", false, "help message for flag boolroot")
|
||||||
|
cmdRootWithRun.Flags().IntVarP(&flagir, "introot", "i", 321, "help message for flag introot")
|
||||||
|
return cmdRootWithRun
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkStringContains(t *testing.T, found, expected string) {
|
||||||
|
if !strings.Contains(found, expected) {
|
||||||
|
logErr(t, found, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkStringOmits(t *testing.T, found, expected string) {
|
||||||
|
if strings.Contains(found, expected) {
|
||||||
|
logErr(t, found, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func logErr(t *testing.T, found, expected string) {
|
||||||
|
out := new(bytes.Buffer)
|
||||||
|
|
||||||
|
_, _, line, ok := runtime.Caller(2)
|
||||||
|
if ok {
|
||||||
|
fmt.Fprintf(out, "Line: %d ", line)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(out, "Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
|
||||||
|
t.Errorf(out.String())
|
||||||
|
}
|
217
Godeps/_workspace/src/github.com/spf13/cobra/doc/man_docs.go
generated
vendored
Normal file
217
Godeps/_workspace/src/github.com/spf13/cobra/doc/man_docs.go
generated
vendored
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
// Copyright 2015 Red Hat Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// 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 doc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
mangen "github.com/cpuguy83/go-md2man/md2man"
|
||||||
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/cobra"
|
||||||
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenManTree will generate a man page for this command and all decendants
|
||||||
|
// in the directory given. The header may be nil. This function may not work
|
||||||
|
// correctly if your command names have - in them. If you have `cmd` with two
|
||||||
|
// subcmds, `sub` and `sub-third`. And `sub` has a subcommand called `third`
|
||||||
|
// it is undefined which help output will be in the file `cmd-sub-third.1`.
|
||||||
|
func GenManTree(cmd *cobra.Command, header *GenManHeader, dir string) error {
|
||||||
|
if header == nil {
|
||||||
|
header = &GenManHeader{}
|
||||||
|
}
|
||||||
|
for _, c := range cmd.Commands() {
|
||||||
|
if !c.IsAvailableCommand() || c.IsHelpCommand() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := GenManTree(c, header, dir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
needToResetTitle := header.Title == ""
|
||||||
|
|
||||||
|
filename := cmd.CommandPath()
|
||||||
|
filename = dir + strings.Replace(filename, " ", "-", -1) + ".1"
|
||||||
|
f, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if err := GenMan(cmd, header, f); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if needToResetTitle {
|
||||||
|
header.Title = ""
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenManHeader is a lot like the .TH header at the start of man pages. These
|
||||||
|
// include the title, section, date, source, and manual. We will use the
|
||||||
|
// current time if Date if unset and will use "Auto generated by spf13/cobra"
|
||||||
|
// if the Source is unset.
|
||||||
|
type GenManHeader struct {
|
||||||
|
Title string
|
||||||
|
Section string
|
||||||
|
Date *time.Time
|
||||||
|
date string
|
||||||
|
Source string
|
||||||
|
Manual string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenMan will generate a man page for the given command and write it to
|
||||||
|
// w. The header argument may be nil, however obviously w may not.
|
||||||
|
func GenMan(cmd *cobra.Command, header *GenManHeader, w io.Writer) error {
|
||||||
|
if header == nil {
|
||||||
|
header = &GenManHeader{}
|
||||||
|
}
|
||||||
|
b := genMan(cmd, header)
|
||||||
|
final := mangen.Render(b)
|
||||||
|
_, err := w.Write(final)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func fillHeader(header *GenManHeader, name string) {
|
||||||
|
if header.Title == "" {
|
||||||
|
header.Title = strings.ToUpper(strings.Replace(name, " ", "\\-", -1))
|
||||||
|
}
|
||||||
|
if header.Section == "" {
|
||||||
|
header.Section = "1"
|
||||||
|
}
|
||||||
|
if header.Date == nil {
|
||||||
|
now := time.Now()
|
||||||
|
header.Date = &now
|
||||||
|
}
|
||||||
|
header.date = (*header.Date).Format("Jan 2006")
|
||||||
|
if header.Source == "" {
|
||||||
|
header.Source = "Auto generated by spf13/cobra"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func manPreamble(out io.Writer, header *GenManHeader, name, short, long string) {
|
||||||
|
dashName := strings.Replace(name, " ", "-", -1)
|
||||||
|
fmt.Fprintf(out, `%% %s(%s)%s
|
||||||
|
%% %s
|
||||||
|
%% %s
|
||||||
|
# NAME
|
||||||
|
`, header.Title, header.Section, header.date, header.Source, header.Manual)
|
||||||
|
fmt.Fprintf(out, "%s \\- %s\n\n", dashName, short)
|
||||||
|
fmt.Fprintf(out, "# SYNOPSIS\n")
|
||||||
|
fmt.Fprintf(out, "**%s** [OPTIONS]\n\n", name)
|
||||||
|
fmt.Fprintf(out, "# DESCRIPTION\n")
|
||||||
|
fmt.Fprintf(out, "%s\n\n", long)
|
||||||
|
}
|
||||||
|
|
||||||
|
func manPrintFlags(out io.Writer, flags *pflag.FlagSet) {
|
||||||
|
flags.VisitAll(func(flag *pflag.Flag) {
|
||||||
|
if len(flag.Deprecated) > 0 || flag.Hidden {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
format := ""
|
||||||
|
if len(flag.Shorthand) > 0 {
|
||||||
|
format = "**-%s**, **--%s**"
|
||||||
|
} else {
|
||||||
|
format = "%s**--%s**"
|
||||||
|
}
|
||||||
|
if len(flag.NoOptDefVal) > 0 {
|
||||||
|
format = format + "["
|
||||||
|
}
|
||||||
|
if flag.Value.Type() == "string" {
|
||||||
|
// put quotes on the value
|
||||||
|
format = format + "=%q"
|
||||||
|
} else {
|
||||||
|
format = format + "=%s"
|
||||||
|
}
|
||||||
|
if len(flag.NoOptDefVal) > 0 {
|
||||||
|
format = format + "]"
|
||||||
|
}
|
||||||
|
format = format + "\n\t%s\n\n"
|
||||||
|
fmt.Fprintf(out, format, flag.Shorthand, flag.Name, flag.DefValue, flag.Usage)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func manPrintOptions(out io.Writer, command *cobra.Command) {
|
||||||
|
flags := command.NonInheritedFlags()
|
||||||
|
if flags.HasFlags() {
|
||||||
|
fmt.Fprintf(out, "# OPTIONS\n")
|
||||||
|
manPrintFlags(out, flags)
|
||||||
|
fmt.Fprintf(out, "\n")
|
||||||
|
}
|
||||||
|
flags = command.InheritedFlags()
|
||||||
|
if flags.HasFlags() {
|
||||||
|
fmt.Fprintf(out, "# OPTIONS INHERITED FROM PARENT COMMANDS\n")
|
||||||
|
manPrintFlags(out, flags)
|
||||||
|
fmt.Fprintf(out, "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func genMan(cmd *cobra.Command, header *GenManHeader) []byte {
|
||||||
|
// something like `rootcmd subcmd1 subcmd2`
|
||||||
|
commandName := cmd.CommandPath()
|
||||||
|
// something like `rootcmd-subcmd1-subcmd2`
|
||||||
|
dashCommandName := strings.Replace(commandName, " ", "-", -1)
|
||||||
|
|
||||||
|
fillHeader(header, commandName)
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
short := cmd.Short
|
||||||
|
long := cmd.Long
|
||||||
|
if len(long) == 0 {
|
||||||
|
long = short
|
||||||
|
}
|
||||||
|
|
||||||
|
manPreamble(buf, header, commandName, short, long)
|
||||||
|
manPrintOptions(buf, cmd)
|
||||||
|
if len(cmd.Example) > 0 {
|
||||||
|
fmt.Fprintf(buf, "# EXAMPLE\n")
|
||||||
|
fmt.Fprintf(buf, "```\n%s\n```\n", cmd.Example)
|
||||||
|
}
|
||||||
|
if hasSeeAlso(cmd) {
|
||||||
|
fmt.Fprintf(buf, "# SEE ALSO\n")
|
||||||
|
if cmd.HasParent() {
|
||||||
|
parentPath := cmd.Parent().CommandPath()
|
||||||
|
dashParentPath := strings.Replace(parentPath, " ", "-", -1)
|
||||||
|
fmt.Fprintf(buf, "**%s(%s)**", dashParentPath, header.Section)
|
||||||
|
cmd.VisitParents(func(c *cobra.Command) {
|
||||||
|
if c.DisableAutoGenTag {
|
||||||
|
cmd.DisableAutoGenTag = c.DisableAutoGenTag
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
children := cmd.Commands()
|
||||||
|
sort.Sort(byName(children))
|
||||||
|
for i, c := range children {
|
||||||
|
if !c.IsAvailableCommand() || c.IsHelpCommand() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if cmd.HasParent() || i > 0 {
|
||||||
|
fmt.Fprintf(buf, ", ")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(buf, "**%s-%s(%s)**", dashCommandName, c.Name(), header.Section)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(buf, "\n")
|
||||||
|
}
|
||||||
|
if !cmd.DisableAutoGenTag {
|
||||||
|
fmt.Fprintf(buf, "# HISTORY\n%s Auto generated by spf13/cobra\n", header.Date.Format("2-Jan-2006"))
|
||||||
|
}
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
25
Godeps/_workspace/src/github.com/spf13/cobra/doc/man_docs.md
generated
vendored
Normal file
25
Godeps/_workspace/src/github.com/spf13/cobra/doc/man_docs.md
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Generating Man Pages For Your Own cobra.Command
|
||||||
|
|
||||||
|
Generating man pages from a cobra command is incredibly easy. An example is as follows:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "test",
|
||||||
|
Short: "my test program",
|
||||||
|
}
|
||||||
|
header := &cobra.GenManHeader{
|
||||||
|
Title: "MINE",
|
||||||
|
Section: "3",
|
||||||
|
}
|
||||||
|
cmd.GenManTree(header, "/tmp")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That will get you a man page `/tmp/test.1`
|
97
Godeps/_workspace/src/github.com/spf13/cobra/doc/man_docs_test.go
generated
vendored
Normal file
97
Godeps/_workspace/src/github.com/spf13/cobra/doc/man_docs_test.go
generated
vendored
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package doc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = fmt.Println
|
||||||
|
var _ = os.Stderr
|
||||||
|
|
||||||
|
func translate(in string) string {
|
||||||
|
return strings.Replace(in, "-", "\\-", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenManDoc(t *testing.T) {
|
||||||
|
c := initializeWithRootCmd()
|
||||||
|
// Need two commands to run the command alphabetical sort
|
||||||
|
cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated)
|
||||||
|
c.AddCommand(cmdPrint, cmdEcho)
|
||||||
|
cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
|
||||||
|
|
||||||
|
out := new(bytes.Buffer)
|
||||||
|
|
||||||
|
header := &GenManHeader{
|
||||||
|
Title: "Project",
|
||||||
|
Section: "2",
|
||||||
|
}
|
||||||
|
// We generate on a subcommand so we have both subcommands and parents
|
||||||
|
if err := GenMan(cmdEcho, header, out); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
found := out.String()
|
||||||
|
|
||||||
|
// Make sure parent has - in CommandPath() in SEE ALSO:
|
||||||
|
parentPath := cmdEcho.Parent().CommandPath()
|
||||||
|
dashParentPath := strings.Replace(parentPath, " ", "-", -1)
|
||||||
|
expected := translate(dashParentPath)
|
||||||
|
expected = expected + "(" + header.Section + ")"
|
||||||
|
checkStringContains(t, found, expected)
|
||||||
|
|
||||||
|
// Our description
|
||||||
|
expected = translate(cmdEcho.Name())
|
||||||
|
checkStringContains(t, found, expected)
|
||||||
|
|
||||||
|
// Better have our example
|
||||||
|
expected = translate(cmdEcho.Name())
|
||||||
|
checkStringContains(t, found, expected)
|
||||||
|
|
||||||
|
// A local flag
|
||||||
|
expected = "boolone"
|
||||||
|
checkStringContains(t, found, expected)
|
||||||
|
|
||||||
|
// persistent flag on parent
|
||||||
|
expected = "rootflag"
|
||||||
|
checkStringContains(t, found, expected)
|
||||||
|
|
||||||
|
// We better output info about our parent
|
||||||
|
expected = translate(cmdRootWithRun.Name())
|
||||||
|
checkStringContains(t, found, expected)
|
||||||
|
|
||||||
|
// And about subcommands
|
||||||
|
expected = translate(cmdEchoSub.Name())
|
||||||
|
checkStringContains(t, found, expected)
|
||||||
|
|
||||||
|
unexpected := translate(cmdDeprecated.Name())
|
||||||
|
checkStringOmits(t, found, unexpected)
|
||||||
|
|
||||||
|
// auto generated
|
||||||
|
expected = translate("Auto generated")
|
||||||
|
checkStringContains(t, found, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenManNoGenTag(t *testing.T) {
|
||||||
|
c := initializeWithRootCmd()
|
||||||
|
// Need two commands to run the command alphabetical sort
|
||||||
|
cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated)
|
||||||
|
c.AddCommand(cmdPrint, cmdEcho)
|
||||||
|
cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
|
||||||
|
cmdEcho.DisableAutoGenTag = true
|
||||||
|
out := new(bytes.Buffer)
|
||||||
|
|
||||||
|
header := &GenManHeader{
|
||||||
|
Title: "Project",
|
||||||
|
Section: "2",
|
||||||
|
}
|
||||||
|
// We generate on a subcommand so we have both subcommands and parents
|
||||||
|
if err := GenMan(cmdEcho, header, out); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
found := out.String()
|
||||||
|
|
||||||
|
unexpected := translate("#HISTORY")
|
||||||
|
checkStringOmits(t, found, unexpected)
|
||||||
|
}
|
35
Godeps/_workspace/src/github.com/spf13/cobra/doc/man_examples_test.go
generated
vendored
Normal file
35
Godeps/_workspace/src/github.com/spf13/cobra/doc/man_examples_test.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package doc_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/cobra"
|
||||||
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/cobra/doc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleCommand_GenManTree() {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "test",
|
||||||
|
Short: "my test program",
|
||||||
|
}
|
||||||
|
header := &doc.GenManHeader{
|
||||||
|
Title: "MINE",
|
||||||
|
Section: "3",
|
||||||
|
}
|
||||||
|
doc.GenManTree(cmd, header, "/tmp")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleCommand_GenMan() {
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "test",
|
||||||
|
Short: "my test program",
|
||||||
|
}
|
||||||
|
header := &doc.GenManHeader{
|
||||||
|
Title: "MINE",
|
||||||
|
Section: "3",
|
||||||
|
}
|
||||||
|
out := new(bytes.Buffer)
|
||||||
|
doc.GenMan(cmd, header, out)
|
||||||
|
fmt.Print(out.String())
|
||||||
|
}
|
174
Godeps/_workspace/src/github.com/spf13/cobra/doc/md_docs.go
generated
vendored
Normal file
174
Godeps/_workspace/src/github.com/spf13/cobra/doc/md_docs.go
generated
vendored
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
//Copyright 2015 Red Hat Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// 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 doc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func printOptions(w io.Writer, cmd *cobra.Command, name string) error {
|
||||||
|
flags := cmd.NonInheritedFlags()
|
||||||
|
flags.SetOutput(w)
|
||||||
|
if flags.HasFlags() {
|
||||||
|
if _, err := fmt.Fprintf(w, "### Options\n\n```\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
flags.PrintDefaults()
|
||||||
|
if _, err := fmt.Fprintf(w, "```\n\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parentFlags := cmd.InheritedFlags()
|
||||||
|
parentFlags.SetOutput(w)
|
||||||
|
if parentFlags.HasFlags() {
|
||||||
|
if _, err := fmt.Fprintf(w, "### Options inherited from parent commands\n\n```\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
parentFlags.PrintDefaults()
|
||||||
|
if _, err := fmt.Fprintf(w, "```\n\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenMarkdown(cmd *cobra.Command, w io.Writer) error {
|
||||||
|
return GenMarkdownCustom(cmd, w, func(s string) string { return s })
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, linkHandler func(string) string) error {
|
||||||
|
name := cmd.CommandPath()
|
||||||
|
|
||||||
|
short := cmd.Short
|
||||||
|
long := cmd.Long
|
||||||
|
if len(long) == 0 {
|
||||||
|
long = short
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := fmt.Fprintf(w, "## %s\n\n", name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := fmt.Fprintf(w, "%s\n\n", short); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := fmt.Fprintf(w, "### Synopsis\n\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := fmt.Fprintf(w, "\n%s\n\n", long); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.Runnable() {
|
||||||
|
if _, err := fmt.Fprintf(w, "```\n%s\n```\n\n", cmd.UseLine()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cmd.Example) > 0 {
|
||||||
|
if _, err := fmt.Fprintf(w, "### Examples\n\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := fmt.Fprintf(w, "```\n%s\n```\n\n", cmd.Example); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := printOptions(w, cmd, name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if hasSeeAlso(cmd) {
|
||||||
|
if _, err := fmt.Fprintf(w, "### SEE ALSO\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if cmd.HasParent() {
|
||||||
|
parent := cmd.Parent()
|
||||||
|
pname := parent.CommandPath()
|
||||||
|
link := pname + ".md"
|
||||||
|
link = strings.Replace(link, " ", "_", -1)
|
||||||
|
if _, err := fmt.Fprintf(w, "* [%s](%s)\t - %s\n", pname, linkHandler(link), parent.Short); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd.VisitParents(func(c *cobra.Command) {
|
||||||
|
if c.DisableAutoGenTag {
|
||||||
|
cmd.DisableAutoGenTag = c.DisableAutoGenTag
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
children := cmd.Commands()
|
||||||
|
sort.Sort(byName(children))
|
||||||
|
|
||||||
|
for _, child := range children {
|
||||||
|
if !child.IsAvailableCommand() || child.IsHelpCommand() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cname := name + " " + child.Name()
|
||||||
|
link := cname + ".md"
|
||||||
|
link = strings.Replace(link, " ", "_", -1)
|
||||||
|
if _, err := fmt.Fprintf(w, "* [%s](%s)\t - %s\n", cname, linkHandler(link), child.Short); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := fmt.Fprintf(w, "\n"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !cmd.DisableAutoGenTag {
|
||||||
|
if _, err := fmt.Fprintf(w, "###### Auto generated by spf13/cobra on %s\n", time.Now().Format("2-Jan-2006")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenMarkdownTree(cmd *cobra.Command, dir string) error {
|
||||||
|
identity := func(s string) string { return s }
|
||||||
|
emptyStr := func(s string) string { return "" }
|
||||||
|
return GenMarkdownTreeCustom(cmd, dir, emptyStr, identity)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHandler func(string) string) error {
|
||||||
|
for _, c := range cmd.Commands() {
|
||||||
|
if !c.IsAvailableCommand() || c.IsHelpCommand() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := GenMarkdownTreeCustom(c, dir, filePrepender, linkHandler); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := cmd.CommandPath()
|
||||||
|
filename = dir + strings.Replace(filename, " ", "_", -1) + ".md"
|
||||||
|
f, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if _, err := io.WriteString(f, filePrepender(filename)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := GenMarkdownCustom(cmd, f, linkHandler); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
81
Godeps/_workspace/src/github.com/spf13/cobra/doc/md_docs.md
generated
vendored
Normal file
81
Godeps/_workspace/src/github.com/spf13/cobra/doc/md_docs.md
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# Generating Markdown Docs For Your Own cobra.Command
|
||||||
|
|
||||||
|
## Generate markdown docs for the entire command tree
|
||||||
|
|
||||||
|
This program can actually generate docs for the kubectl command in the kubernetes project
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd"
|
||||||
|
"github.com/spf13/cobra/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
kubectl := cmd.NewFactory(nil).NewKubectlCommand(os.Stdin, ioutil.Discard, ioutil.Discard)
|
||||||
|
doc.GenMarkdownTree(kubectl, "./")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This will generate a whole series of files, one for each command in the tree, in the directory specified (in this case "./")
|
||||||
|
|
||||||
|
## Generate markdown docs for a single command
|
||||||
|
|
||||||
|
You may wish to have more control over the output, or only generate for a single command, instead of the entire command tree. If this is the case you may prefer to `GenMarkdown` instead of `GenMarkdownTree`
|
||||||
|
|
||||||
|
```go
|
||||||
|
out := new(bytes.Buffer)
|
||||||
|
doc.GenMarkdown(cmd, out)
|
||||||
|
```
|
||||||
|
|
||||||
|
This will write the markdown doc for ONLY "cmd" into the out, buffer.
|
||||||
|
|
||||||
|
## Customize the output
|
||||||
|
|
||||||
|
Both `GenMarkdown` and `GenMarkdownTree` have alternate versions with callbacks to get some control of the output:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func GenMarkdownTreeCustom(cmd *Command, dir string, filePrepender, linkHandler func(string) string) error {
|
||||||
|
//...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
func GenMarkdownCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string) string) error {
|
||||||
|
//...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `filePrepender` will prepend the return value given the full filepath to the rendered Markdown file. A common use case is to add front matter to use the generated documentation with [Hugo](http://gohugo.io/):
|
||||||
|
|
||||||
|
```go
|
||||||
|
const fmTemplate = `---
|
||||||
|
date: %s
|
||||||
|
title: "%s"
|
||||||
|
slug: %s
|
||||||
|
url: %s
|
||||||
|
---
|
||||||
|
`
|
||||||
|
|
||||||
|
filePrepender := func(filename string) string {
|
||||||
|
now := time.Now().Format(time.RFC3339)
|
||||||
|
name := filepath.Base(filename)
|
||||||
|
base := strings.TrimSuffix(name, path.Ext(name))
|
||||||
|
url := "/commands/" + strings.ToLower(base) + "/"
|
||||||
|
return fmt.Sprintf(fmTemplate, now, strings.Replace(base, "_", " ", -1), base, url)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `linkHandler` can be used to customize the rendered internal links to the commands, given a filename:
|
||||||
|
|
||||||
|
```go
|
||||||
|
linkHandler := func(name string) string {
|
||||||
|
base := strings.TrimSuffix(name, path.Ext(name))
|
||||||
|
return "/commands/" + strings.ToLower(base) + "/"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package cobra
|
package doc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -21,7 +21,9 @@ func TestGenMdDoc(t *testing.T) {
|
|||||||
out := new(bytes.Buffer)
|
out := new(bytes.Buffer)
|
||||||
|
|
||||||
// We generate on s subcommand so we have both subcommands and parents
|
// We generate on s subcommand so we have both subcommands and parents
|
||||||
GenMarkdown(cmdEcho, out)
|
if err := GenMarkdown(cmdEcho, out); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
found := out.String()
|
found := out.String()
|
||||||
|
|
||||||
// Our description
|
// Our description
|
||||||
@ -65,3 +67,22 @@ func TestGenMdDoc(t *testing.T) {
|
|||||||
t.Errorf("Unexpected response.\nFound: %v\nBut should not have!!\n", unexpected)
|
t.Errorf("Unexpected response.\nFound: %v\nBut should not have!!\n", unexpected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGenMdNoTag(t *testing.T) {
|
||||||
|
c := initializeWithRootCmd()
|
||||||
|
// Need two commands to run the command alphabetical sort
|
||||||
|
cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated)
|
||||||
|
c.AddCommand(cmdPrint, cmdEcho)
|
||||||
|
c.DisableAutoGenTag = true
|
||||||
|
cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
|
||||||
|
out := new(bytes.Buffer)
|
||||||
|
|
||||||
|
if err := GenMarkdown(c, out); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
found := out.String()
|
||||||
|
|
||||||
|
unexpected := "Auto generated"
|
||||||
|
checkStringOmits(t, found, unexpected)
|
||||||
|
|
||||||
|
}
|
38
Godeps/_workspace/src/github.com/spf13/cobra/doc/util.go
generated
vendored
Normal file
38
Godeps/_workspace/src/github.com/spf13/cobra/doc/util.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2015 Red Hat Inc. All rights reserved.
|
||||||
|
//
|
||||||
|
// 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 doc
|
||||||
|
|
||||||
|
import "github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/cobra"
|
||||||
|
|
||||||
|
// Test to see if we have a reason to print See Also information in docs
|
||||||
|
// Basically this is a test for a parent commend or a subcommand which is
|
||||||
|
// both not deprecated and not the autogenerated help command.
|
||||||
|
func hasSeeAlso(cmd *cobra.Command) bool {
|
||||||
|
if cmd.HasParent() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, c := range cmd.Commands() {
|
||||||
|
if !c.IsAvailableCommand() || c.IsHelpCommand() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type byName []*cobra.Command
|
||||||
|
|
||||||
|
func (s byName) Len() int { return len(s) }
|
||||||
|
func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }
|
124
Godeps/_workspace/src/github.com/spf13/cobra/md_docs.go
generated
vendored
124
Godeps/_workspace/src/github.com/spf13/cobra/md_docs.go
generated
vendored
@ -1,124 +0,0 @@
|
|||||||
//Copyright 2015 Red Hat Inc. All rights reserved.
|
|
||||||
//
|
|
||||||
// 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 cobra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func printOptions(out *bytes.Buffer, cmd *Command, name string) {
|
|
||||||
flags := cmd.NonInheritedFlags()
|
|
||||||
flags.SetOutput(out)
|
|
||||||
if flags.HasFlags() {
|
|
||||||
fmt.Fprintf(out, "### Options\n\n```\n")
|
|
||||||
flags.PrintDefaults()
|
|
||||||
fmt.Fprintf(out, "```\n\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
parentFlags := cmd.InheritedFlags()
|
|
||||||
parentFlags.SetOutput(out)
|
|
||||||
if parentFlags.HasFlags() {
|
|
||||||
fmt.Fprintf(out, "### Options inherited from parent commands\n\n```\n")
|
|
||||||
parentFlags.PrintDefaults()
|
|
||||||
fmt.Fprintf(out, "```\n\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type byName []*Command
|
|
||||||
|
|
||||||
func (s byName) Len() int { return len(s) }
|
|
||||||
func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
||||||
func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }
|
|
||||||
|
|
||||||
func GenMarkdown(cmd *Command, out *bytes.Buffer) {
|
|
||||||
name := cmd.CommandPath()
|
|
||||||
|
|
||||||
short := cmd.Short
|
|
||||||
long := cmd.Long
|
|
||||||
if len(long) == 0 {
|
|
||||||
long = short
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(out, "## %s\n\n", name)
|
|
||||||
fmt.Fprintf(out, "%s\n\n", short)
|
|
||||||
fmt.Fprintf(out, "### Synopsis\n\n")
|
|
||||||
fmt.Fprintf(out, "\n%s\n\n", long)
|
|
||||||
|
|
||||||
if cmd.Runnable() {
|
|
||||||
fmt.Fprintf(out, "```\n%s\n```\n\n", cmd.UseLine())
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cmd.Example) > 0 {
|
|
||||||
fmt.Fprintf(out, "### Examples\n\n")
|
|
||||||
fmt.Fprintf(out, "```\n%s\n```\n\n", cmd.Example)
|
|
||||||
}
|
|
||||||
|
|
||||||
printOptions(out, cmd, name)
|
|
||||||
|
|
||||||
if len(cmd.Commands()) > 0 || cmd.HasParent() {
|
|
||||||
fmt.Fprintf(out, "### SEE ALSO\n")
|
|
||||||
if cmd.HasParent() {
|
|
||||||
parent := cmd.Parent()
|
|
||||||
pname := parent.CommandPath()
|
|
||||||
link := pname + ".md"
|
|
||||||
link = strings.Replace(link, " ", "_", -1)
|
|
||||||
fmt.Fprintf(out, "* [%s](%s)\t - %s\n", pname, link, parent.Short)
|
|
||||||
}
|
|
||||||
|
|
||||||
children := cmd.Commands()
|
|
||||||
sort.Sort(byName(children))
|
|
||||||
|
|
||||||
for _, child := range children {
|
|
||||||
if len(child.Deprecated) > 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
cname := name + " " + child.Name()
|
|
||||||
link := cname + ".md"
|
|
||||||
link = strings.Replace(link, " ", "_", -1)
|
|
||||||
fmt.Fprintf(out, "* [%s](%s)\t - %s\n", cname, link, child.Short)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(out, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(out, "###### Auto generated by spf13/cobra at %s\n", time.Now().UTC())
|
|
||||||
}
|
|
||||||
|
|
||||||
func GenMarkdownTree(cmd *Command, dir string) {
|
|
||||||
for _, c := range cmd.Commands() {
|
|
||||||
GenMarkdownTree(c, dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
out := new(bytes.Buffer)
|
|
||||||
|
|
||||||
GenMarkdown(cmd, out)
|
|
||||||
|
|
||||||
filename := cmd.CommandPath()
|
|
||||||
filename = dir + strings.Replace(filename, " ", "_", -1) + ".md"
|
|
||||||
outFile, err := os.Create(filename)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
defer outFile.Close()
|
|
||||||
_, err = outFile.Write(out.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
35
Godeps/_workspace/src/github.com/spf13/cobra/md_docs.md
generated
vendored
35
Godeps/_workspace/src/github.com/spf13/cobra/md_docs.md
generated
vendored
@ -1,35 +0,0 @@
|
|||||||
# Generating Markdown Docs For Your Own cobra.Command
|
|
||||||
|
|
||||||
## Generate markdown docs for the entire command tree
|
|
||||||
|
|
||||||
This program can actually generate docs for the kubectl command in the kubernetes project
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
kubectl := cmd.NewFactory(nil).NewKubectlCommand(os.Stdin, ioutil.Discard, ioutil.Discard)
|
|
||||||
cobra.GenMarkdownTree(kubectl, "./")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This will generate a whole series of files, one for each command in the tree, in the directory specified (in this case "./")
|
|
||||||
|
|
||||||
## Generate markdown docs for a single command
|
|
||||||
|
|
||||||
You may wish to have more control over the output, or only generate for a single command, instead of the entire command tree. If this is the case you may prefer to `GenMarkdown()` instead of `GenMarkdownTree`
|
|
||||||
|
|
||||||
```go
|
|
||||||
out := new(bytes.Buffer)
|
|
||||||
cobra.GenMarkdown(cmd, out)
|
|
||||||
```
|
|
||||||
|
|
||||||
This will write the markdown doc for ONLY "cmd" into the out, buffer.
|
|
18
Godeps/_workspace/src/github.com/spf13/pflag/.travis.yml
generated
vendored
Normal file
18
Godeps/_workspace/src/github.com/spf13/pflag/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
sudo: false
|
||||||
|
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.3
|
||||||
|
- 1.4
|
||||||
|
- 1.5
|
||||||
|
- tip
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get github.com/golang/lint/golint
|
||||||
|
- export PATH=$GOPATH/bin:$PATH
|
||||||
|
- go install ./...
|
||||||
|
|
||||||
|
script:
|
||||||
|
- verify/all.sh -v
|
||||||
|
- go test ./...
|
85
Godeps/_workspace/src/github.com/spf13/pflag/README.md
generated
vendored
85
Godeps/_workspace/src/github.com/spf13/pflag/README.md
generated
vendored
@ -1,3 +1,5 @@
|
|||||||
|
[](https://travis-ci.org/spf13/pflag)
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
pflag is a drop-in replacement for Go's flag package, implementing
|
pflag is a drop-in replacement for Go's flag package, implementing
|
||||||
@ -18,11 +20,11 @@ pflag is available using the standard `go get` command.
|
|||||||
|
|
||||||
Install by running:
|
Install by running:
|
||||||
|
|
||||||
go get github.com/ogier/pflag
|
go get github.com/spf13/pflag
|
||||||
|
|
||||||
Run tests by running:
|
Run tests by running:
|
||||||
|
|
||||||
go test github.com/ogier/pflag
|
go test github.com/spf13/pflag
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@ -31,7 +33,7 @@ pflag under the name "flag" then all code should continue to function
|
|||||||
with no changes.
|
with no changes.
|
||||||
|
|
||||||
``` go
|
``` go
|
||||||
import flag "github.com/ogier/pflag"
|
import flag "github.com/spf13/pflag"
|
||||||
```
|
```
|
||||||
|
|
||||||
There is one exception to this: if you directly instantiate the Flag struct
|
There is one exception to this: if you directly instantiate the Flag struct
|
||||||
@ -82,6 +84,16 @@ fmt.Println("ip has value ", *ip)
|
|||||||
fmt.Println("flagvar has value ", flagvar)
|
fmt.Println("flagvar has value ", flagvar)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
There are helpers function to get values later if you have the FlagSet but
|
||||||
|
it was difficult to keep up with all of the the flag pointers in your code.
|
||||||
|
If you have a pflag.FlagSet with a flag called 'flagname' of type int you
|
||||||
|
can use GetInt() to get the int value. But notice that 'flagname' must exist
|
||||||
|
and it must be an int. GetString("flagname") will fail.
|
||||||
|
|
||||||
|
``` go
|
||||||
|
i, err := flagset.GetInt("flagname")
|
||||||
|
```
|
||||||
|
|
||||||
After parsing, the arguments after the flag are available as the
|
After parsing, the arguments after the flag are available as the
|
||||||
slice flag.Args() or individually as flag.Arg(i).
|
slice flag.Args() or individually as flag.Arg(i).
|
||||||
The arguments are indexed from 0 through flag.NArg()-1.
|
The arguments are indexed from 0 through flag.NArg()-1.
|
||||||
@ -109,29 +121,56 @@ in a command-line interface. The methods of FlagSet are
|
|||||||
analogous to the top-level functions for the command-line
|
analogous to the top-level functions for the command-line
|
||||||
flag set.
|
flag set.
|
||||||
|
|
||||||
|
## Setting no option default values for flags
|
||||||
|
|
||||||
|
After you create a flag it is possible to set the pflag.NoOptDefVal for
|
||||||
|
the given flag. Doing this changes the meaning of the flag slightly. If
|
||||||
|
a flag has a NoOptDefVal and the flag is set on the command line without
|
||||||
|
an option the flag will be set to the NoOptDefVal. For example given:
|
||||||
|
|
||||||
|
``` go
|
||||||
|
var ip = flag.IntP("flagname", "f", 1234, "help message")
|
||||||
|
flag.Lookup("flagname").NoOptDefVal = "4321"
|
||||||
|
```
|
||||||
|
|
||||||
|
Would result in something like
|
||||||
|
|
||||||
|
| Parsed Arguments | Resulting Value |
|
||||||
|
| ------------- | ------------- |
|
||||||
|
| --flagname=1357 | ip=1357 |
|
||||||
|
| --flagname | ip=4321 |
|
||||||
|
| [nothing] | ip=1234 |
|
||||||
|
|
||||||
## Command line flag syntax
|
## Command line flag syntax
|
||||||
|
|
||||||
```
|
```
|
||||||
--flag // boolean flags only
|
--flag // boolean flags, or flags with no option default values
|
||||||
|
--flag x // only on flags without a default value
|
||||||
--flag=x
|
--flag=x
|
||||||
```
|
```
|
||||||
|
|
||||||
Unlike the flag package, a single dash before an option means something
|
Unlike the flag package, a single dash before an option means something
|
||||||
different than a double dash. Single dashes signify a series of shorthand
|
different than a double dash. Single dashes signify a series of shorthand
|
||||||
letters for flags. All but the last shorthand letter must be boolean flags.
|
letters for flags. All but the last shorthand letter must be boolean flags
|
||||||
|
or a flag with a default value
|
||||||
|
|
||||||
```
|
```
|
||||||
// boolean flags
|
// boolean or flags where the 'no option default value' is set
|
||||||
-f
|
-f
|
||||||
|
-f=true
|
||||||
-abc
|
-abc
|
||||||
|
but
|
||||||
|
-b true is INVALID
|
||||||
|
|
||||||
// non-boolean flags
|
// non-boolean and flags without a 'no option default value'
|
||||||
|
-n 1234
|
||||||
|
-n=1234
|
||||||
-n1234
|
-n1234
|
||||||
-Ifile
|
|
||||||
|
|
||||||
// mixed
|
// mixed
|
||||||
-abcs "hello"
|
-abcs "hello"
|
||||||
-abcn1234
|
-absd="hello"
|
||||||
|
-abcs1234
|
||||||
```
|
```
|
||||||
|
|
||||||
Flag parsing stops after the terminator "--". Unlike the flag package,
|
Flag parsing stops after the terminator "--". Unlike the flag package,
|
||||||
@ -177,6 +216,34 @@ func aliasNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
|||||||
myFlagSet.SetNormalizeFunc(aliasNormalizeFunc)
|
myFlagSet.SetNormalizeFunc(aliasNormalizeFunc)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Deprecating a flag or its shorthand
|
||||||
|
It is possible to deprecate a flag, or just its shorthand. Deprecating a flag/shorthand hides it from help text and prints a usage message when the deprecated flag/shorthand is used.
|
||||||
|
|
||||||
|
**Example #1**: You want to deprecate a flag named "badflag" as well as inform the users what flag they should use instead.
|
||||||
|
```go
|
||||||
|
// deprecate a flag by specifying its name and a usage message
|
||||||
|
flags.MarkDeprecated("badflag", "please use --good-flag instead")
|
||||||
|
```
|
||||||
|
This hides "badflag" from help text, and prints `Flag --badflag has been deprecated, please use --good-flag instead` when "badflag" is used.
|
||||||
|
|
||||||
|
**Example #2**: You want to keep a flag name "noshorthandflag" but deprecate its shortname "n".
|
||||||
|
```go
|
||||||
|
// deprecate a flag shorthand by specifying its flag name and a usage message
|
||||||
|
flags.MarkShorthandDeprecated("noshorthandflag", "please use --noshorthandflag only")
|
||||||
|
```
|
||||||
|
This hides the shortname "n" from help text, and prints `Flag shorthand -n has been deprecated, please use --noshorthandflag only` when the shorthand "n" is used.
|
||||||
|
|
||||||
|
Note that usage message is essential here, and it should not be empty.
|
||||||
|
|
||||||
|
## Hidden flags
|
||||||
|
It is possible to mark a flag as hidden, meaning it will still function as normal, however will not show up in usage/help text.
|
||||||
|
|
||||||
|
**Example**: You have a flag named "secretFlag" that you need for internal use only and don't want it showing up in help text, or for its usage text to be available.
|
||||||
|
```go
|
||||||
|
// hide a flag by specifying its name
|
||||||
|
flags.MarkHidden("secretFlag")
|
||||||
|
```
|
||||||
|
|
||||||
## More info
|
## More info
|
||||||
|
|
||||||
You can see the full reference documentation of the pflag package
|
You can see the full reference documentation of the pflag package
|
||||||
|
40
Godeps/_workspace/src/github.com/spf13/pflag/bool.go
generated
vendored
40
Godeps/_workspace/src/github.com/spf13/pflag/bool.go
generated
vendored
@ -34,37 +34,50 @@ func (b *boolValue) String() string { return fmt.Sprintf("%v", *b) }
|
|||||||
|
|
||||||
func (b *boolValue) IsBoolFlag() bool { return true }
|
func (b *boolValue) IsBoolFlag() bool { return true }
|
||||||
|
|
||||||
|
func boolConv(sval string) (interface{}, error) {
|
||||||
|
return strconv.ParseBool(sval)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBool return the bool value of a flag with the given name
|
||||||
|
func (f *FlagSet) GetBool(name string) (bool, error) {
|
||||||
|
val, err := f.getFlagType(name, "bool", boolConv)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return val.(bool), nil
|
||||||
|
}
|
||||||
|
|
||||||
// BoolVar defines a bool flag with specified name, default value, and usage string.
|
// BoolVar defines a bool flag with specified name, default value, and usage string.
|
||||||
// The argument p points to a bool variable in which to store the value of the flag.
|
// The argument p points to a bool variable in which to store the value of the flag.
|
||||||
func (f *FlagSet) BoolVar(p *bool, name string, value bool, usage string) {
|
func (f *FlagSet) BoolVar(p *bool, name string, value bool, usage string) {
|
||||||
f.VarP(newBoolValue(value, p), name, "", usage)
|
f.BoolVarP(p, name, "", value, usage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Like BoolVar, but accepts a shorthand letter that can be used after a single dash.
|
// BoolVarP is like BoolVar, but accepts a shorthand letter that can be used after a single dash.
|
||||||
func (f *FlagSet) BoolVarP(p *bool, name, shorthand string, value bool, usage string) {
|
func (f *FlagSet) BoolVarP(p *bool, name, shorthand string, value bool, usage string) {
|
||||||
f.VarP(newBoolValue(value, p), name, shorthand, usage)
|
flag := f.VarPF(newBoolValue(value, p), name, shorthand, usage)
|
||||||
|
flag.NoOptDefVal = "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
// BoolVar defines a bool flag with specified name, default value, and usage string.
|
// BoolVar defines a bool flag with specified name, default value, and usage string.
|
||||||
// The argument p points to a bool variable in which to store the value of the flag.
|
// The argument p points to a bool variable in which to store the value of the flag.
|
||||||
func BoolVar(p *bool, name string, value bool, usage string) {
|
func BoolVar(p *bool, name string, value bool, usage string) {
|
||||||
CommandLine.VarP(newBoolValue(value, p), name, "", usage)
|
BoolVarP(p, name, "", value, usage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Like BoolVar, but accepts a shorthand letter that can be used after a single dash.
|
// BoolVarP is like BoolVar, but accepts a shorthand letter that can be used after a single dash.
|
||||||
func BoolVarP(p *bool, name, shorthand string, value bool, usage string) {
|
func BoolVarP(p *bool, name, shorthand string, value bool, usage string) {
|
||||||
CommandLine.VarP(newBoolValue(value, p), name, shorthand, usage)
|
flag := CommandLine.VarPF(newBoolValue(value, p), name, shorthand, usage)
|
||||||
|
flag.NoOptDefVal = "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bool defines a bool flag with specified name, default value, and usage string.
|
// Bool defines a bool flag with specified name, default value, and usage string.
|
||||||
// The return value is the address of a bool variable that stores the value of the flag.
|
// The return value is the address of a bool variable that stores the value of the flag.
|
||||||
func (f *FlagSet) Bool(name string, value bool, usage string) *bool {
|
func (f *FlagSet) Bool(name string, value bool, usage string) *bool {
|
||||||
p := new(bool)
|
return f.BoolP(name, "", value, usage)
|
||||||
f.BoolVarP(p, name, "", value, usage)
|
|
||||||
return p
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Like Bool, but accepts a shorthand letter that can be used after a single dash.
|
// BoolP is like Bool, but accepts a shorthand letter that can be used after a single dash.
|
||||||
func (f *FlagSet) BoolP(name, shorthand string, value bool, usage string) *bool {
|
func (f *FlagSet) BoolP(name, shorthand string, value bool, usage string) *bool {
|
||||||
p := new(bool)
|
p := new(bool)
|
||||||
f.BoolVarP(p, name, shorthand, value, usage)
|
f.BoolVarP(p, name, shorthand, value, usage)
|
||||||
@ -74,10 +87,11 @@ func (f *FlagSet) BoolP(name, shorthand string, value bool, usage string) *bool
|
|||||||
// Bool defines a bool flag with specified name, default value, and usage string.
|
// Bool defines a bool flag with specified name, default value, and usage string.
|
||||||
// The return value is the address of a bool variable that stores the value of the flag.
|
// The return value is the address of a bool variable that stores the value of the flag.
|
||||||
func Bool(name string, value bool, usage string) *bool {
|
func Bool(name string, value bool, usage string) *bool {
|
||||||
return CommandLine.BoolP(name, "", value, usage)
|
return BoolP(name, "", value, usage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Like Bool, but accepts a shorthand letter that can be used after a single dash.
|
// BoolP is like Bool, but accepts a shorthand letter that can be used after a single dash.
|
||||||
func BoolP(name, shorthand string, value bool, usage string) *bool {
|
func BoolP(name, shorthand string, value bool, usage string) *bool {
|
||||||
return CommandLine.BoolP(name, shorthand, value, usage)
|
b := CommandLine.BoolP(name, shorthand, value, usage)
|
||||||
|
return b
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user