Update server dependencies

This commit is contained in:
Ken-Håvard Lieng 2016-01-15 19:48:03 +01:00
parent 6865bf2832
commit f1c3326af1
152 changed files with 16097 additions and 4318 deletions

20
Godeps/Godeps.json generated
View File

@ -29,8 +29,8 @@
},
{
"ImportPath": "github.com/boltdb/bolt",
"Comment": "v1.0-43-gcf33c9e",
"Rev": "cf33c9e0ca0a23509b8bb8edfc63e4776bb1a330"
"Comment": "v1.1.0-61-g6465994",
"Rev": "6465994716bf6400605746e79224cf1e7ed68725"
},
{
"ImportPath": "github.com/coreos/go-etcd/etcd",
@ -52,7 +52,7 @@
},
{
"ImportPath": "github.com/gorilla/websocket",
"Rev": "ecff5aabe41f13b4cdf897e3c0c9bbccbe552a29"
"Rev": "3986be78bf859e01f01af631ad76da5b269d270c"
},
{
"ImportPath": "github.com/inconshreveable/mousetrap",
@ -82,7 +82,7 @@
},
{
"ImportPath": "github.com/mitchellh/go-homedir",
"Rev": "1f6da4a72e57d4e7edd4a7295a585e0a3999a2d4"
"Rev": "d682a8f0cf139663a984ff12528da460ca963de9"
},
{
"ImportPath": "github.com/mitchellh/mapstructure",
@ -94,11 +94,11 @@
},
{
"ImportPath": "github.com/spf13/cast",
"Rev": "4d07383ffe94b5e5a6fa3af9211374a4507a0184"
"Rev": "ee7b3e0353166ab1f3a605294ac8cd2b77953778"
},
{
"ImportPath": "github.com/spf13/cobra",
"Rev": "3ee9552eebbb5db27cb81abcae66c6f1430cad29"
"Rev": "9c9300901990faada0c5fb3b5730f452585c7c2b"
},
{
"ImportPath": "github.com/spf13/jwalterweatherman",
@ -106,11 +106,11 @@
},
{
"ImportPath": "github.com/spf13/pflag",
"Rev": "32bfad653e29e893e4ed3812fdc0294a05126c08"
"Rev": "7f60f83a2c81bc3c3c0d5297f61ddfa68da9d3b7"
},
{
"ImportPath": "github.com/spf13/viper",
"Rev": "d62d4bb4c68a773c3b5f4e72844913a2d5de0de0"
"Rev": "a212099cbe6fbe8d07476bfda8d2d39b6ff8f325"
},
{
"ImportPath": "github.com/square/go-jose",
@ -140,8 +140,8 @@
},
{
"ImportPath": "github.com/xenolf/lego/acme",
"Comment": "v0.1.1-34-g12b5de7",
"Rev": "12b5de7e8cb451949aabad64cce93e4b846e2aa7"
"Comment": "v0.2.0-6-gdb3a956",
"Rev": "db3a956d52bf23cc5201fe98bc9c9787d3b32c2d"
},
{
"ImportPath": "github.com/xordataexchange/crypt/backend",

View File

@ -1,3 +1,4 @@
*.prof
*.test
*.swp
/bin/

View File

@ -1,54 +1,18 @@
TEST=.
BENCH=.
COVERPROFILE=/tmp/c.out
BRANCH=`git rev-parse --abbrev-ref HEAD`
COMMIT=`git rev-parse --short HEAD`
GOLDFLAGS="-X main.branch $(BRANCH) -X main.commit $(COMMIT)"
default: build
bench:
go test -v -test.run=NOTHINCONTAINSTHIS -test.bench=$(BENCH)
# 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
race:
@go test -v -race -test.run="TestSimulate_(100op|1000op)"
# go get github.com/kisielk/errcheck
errcheck:
@echo "=== errcheck ==="
@errcheck github.com/boltdb/bolt
@errcheck -ignorepkg=bytes -ignore=os:Remove github.com/boltdb/bolt
fmt:
@go fmt ./...
test:
@go test -v -cover .
@go test -v ./cmd/bolt
get:
@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
.PHONY: fmt test

View File

@ -1,8 +1,8 @@
Bolt [![Build Status](https://drone.io/github.com/boltdb/bolt/status.png)](https://drone.io/github.com/boltdb/bolt/latest) [![Coverage Status](https://coveralls.io/repos/boltdb/bolt/badge.png?branch=master)](https://coveralls.io/r/boltdb/bolt?branch=master) [![GoDoc](https://godoc.org/github.com/boltdb/bolt?status.png)](https://godoc.org/github.com/boltdb/bolt) ![Version](http://img.shields.io/badge/version-1.0-green.png)
Bolt [![Build Status](https://drone.io/github.com/boltdb/bolt/status.png)](https://drone.io/github.com/boltdb/bolt/latest) [![Coverage Status](https://coveralls.io/repos/boltdb/bolt/badge.svg?branch=master)](https://coveralls.io/r/boltdb/bolt?branch=master) [![GoDoc](https://godoc.org/github.com/boltdb/bolt?status.svg)](https://godoc.org/github.com/boltdb/bolt) ![Version](https://img.shields.io/badge/version-1.0-green.svg)
====
Bolt is a pure Go key/value store inspired by [Howard Chu's][hyc_symas] and
the [LMDB project][lmdb]. The goal of the project is to provide a simple,
Bolt is a pure Go key/value store inspired by [Howard Chu's][hyc_symas]
[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
server such as Postgres or MySQL.
@ -13,7 +13,6 @@ and setting values. That's it.
[hyc_symas]: https://twitter.com/hyc_symas
[lmdb]: http://symas.com/mdb/
## Project Status
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
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
@ -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 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
@ -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
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
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.
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
transaction.
You can use the `Tx.Begin()` function directly but **please** be sure to close
the transaction.
```go
// 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
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
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.
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
@ -225,7 +348,9 @@ iteration over these keys extremely fast. To iterate over keys we'll use a
```go
db.View(func(tx *bolt.Tx) error {
// Assume bucket exists and has keys
b := tx.Bucket([]byte("MyBucket"))
c := b.Cursor()
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.
```
When you have iterated to the end of the cursor then `Next()` will return `nil`.
You must seek to a position using `First()`, `Last()`, or `Seek()` before
calling `Next()` or `Prev()`. If you do not seek to a position then these
functions will return `nil`.
Each of those functions has a return signature of `(key []byte, value []byte)`.
When you have iterated to the end of the cursor then `Next()` will return a
`nil` key. You must seek to a position using `First()`, `Last()`, or `Seek()`
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
@ -261,6 +391,7 @@ To iterate over a key prefix, you can combine `Seek()` and `bytes.HasPrefix()`:
```go
db.View(func(tx *bolt.Tx) error {
// Assume bucket exists and has keys
c := tx.Bucket([]byte("MyBucket")).Cursor()
prefix := []byte("1234")
@ -280,7 +411,7 @@ date range like this:
```go
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()
// Our time range spans the 90's decade.
@ -304,7 +435,9 @@ all the keys in a bucket:
```go
db.View(func(tx *bolt.Tx) error {
// Assume bucket exists and has keys
b := tx.Bucket([]byte("MyBucket"))
b.ForEach(func(k, v []byte) error {
fmt.Printf("key=%s, value=%s\n", k, v)
return nil
@ -328,22 +461,26 @@ func (*Bucket) DeleteBucket(key []byte) error
### 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
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
to prevent page cache trashing.
your other database reads and writes.
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
do database backups:
```go
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-Disposition", `attachment; filename="my.db"`)
w.Header().Set("Content-Length", strconv.Itoa(int(tx.Size())))
return tx.Copy(w)
_, err := tx.WriteTo(w)
return err
})
if err != nil {
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.
### 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
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).
* [Bolt -- an embedded key/value database for Go](https://www.progville.com/go/bolt-embedded-db-golang/) by Progville
## 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
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
have trade offs.
have trade-offs.
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
@ -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:
* 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
[transaction coalescer](https://github.com/boltdb/coalescer) in front of Bolt
to mitigate this issue.
also fast but random writes can be slow. You can use `DB.Batch()` or add a
write-ahead log to help mitigate this issue.
* 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.
@ -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
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
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
@ -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:
* [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.
* [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.
* [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.
* [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.
* [ipxed](https://github.com/kelseyhightower/ipxed) - Web interface and api for ipxed.
* [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.
* [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.
* [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.
* [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.

View 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

View File

@ -4,8 +4,6 @@ import (
"syscall"
)
var odirect = syscall.O_DIRECT
// fdatasync flushes written data to a file descriptor.
func fdatasync(db *DB) error {
return syscall.Fdatasync(int(db.file.Fd()))

View File

@ -11,8 +11,6 @@ const (
msInvalidate // invalidate cached data
)
var odirect int
func msync(db *DB) error {
_, _, errno := syscall.Syscall(syscall.SYS_MSYNC, uintptr(unsafe.Pointer(db.data)), uintptr(db.datasz), msInvalidate)
if errno != 0 {

View 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

View 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

View File

@ -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()
}
}

View File

@ -1,4 +1,4 @@
// +build !windows,!plan9
// +build !windows,!plan9,!solaris
package bolt
@ -11,7 +11,7 @@ import (
)
// 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
for {
// 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 {
return ErrTimeout
}
flag := syscall.LOCK_SH
if exclusive {
flag = syscall.LOCK_EX
}
// 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 {
return nil
} else if err != syscall.EWOULDBLOCK {
@ -42,21 +46,17 @@ func funlock(f *os.File) error {
// mmap memory maps a DB's data file.
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.
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 {
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.
db.dataref = b
db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0]))
@ -78,3 +78,12 @@ func munmap(db *DB) error {
db.datasz = 0
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
}

View 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
}

View File

@ -8,7 +8,37 @@ import (
"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.
func fdatasync(db *DB) error {
@ -16,21 +46,47 @@ func fdatasync(db *DB) error {
}
// flock acquires an advisory lock on a file descriptor.
func flock(f *os.File, _ time.Duration) error {
return nil
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
} 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.
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.
// Based on: https://github.com/edsrzf/mmap-go
func mmap(db *DB, sz int) error {
// Truncate the database to the size of the mmap.
if err := db.file.Truncate(int64(sz)); err != nil {
return fmt.Errorf("truncate: %s", err)
if !db.readOnly {
// Truncate the database to the size of the mmap.
if err := db.file.Truncate(int64(sz)); err != nil {
return fmt.Errorf("truncate: %s", err)
}
}
// Open a file mapping handle.

View File

@ -2,8 +2,6 @@
package bolt
var odirect int
// fdatasync flushes written data to a file descriptor.
func fdatasync(db *DB) error {
return db.file.Sync()

View File

@ -11,7 +11,7 @@ const (
MaxKeySize = 32768
// MaxValueSize is the maximum length of a value, in bytes.
MaxValueSize = 4294967295
MaxValueSize = (1 << 31) - 2
)
const (
@ -99,6 +99,7 @@ func (b *Bucket) Cursor() *Cursor {
// Bucket retrieves a nested bucket by name.
// 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 {
if b.buckets != 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.
// 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) {
if b.tx.db == nil {
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.
// 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) {
child, err := b.CreateBucket(key)
if err == ErrBucketExists {
@ -252,6 +255,7 @@ func (b *Bucket) DeleteBucket(key []byte) error {
// 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.
// The returned value is only valid for the life of the transaction.
func (b *Bucket) Get(key []byte) []byte {
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.
// 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.
func (b *Bucket) Put(key []byte, value []byte) error {
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.
// 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 {
if b.tx.db == nil {
return ErrTxClosed

File diff suppressed because it is too large Load Diff

View File

@ -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()
}

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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
})
}

View File

@ -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
}
}

View File

@ -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)
})
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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)
})
}

File diff suppressed because it is too large Load Diff

View File

@ -1,69 +1,185 @@
package main_test
import (
"fmt"
"bytes"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"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"
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/boltdb/bolt/cmd/bolt"
)
// open creates and opens a Bolt database in the temp directory.
func open(fn func(*bolt.DB, string)) {
path := tempfile()
defer os.RemoveAll(path)
// Ensure the "info" command can print information about a database.
func TestInfoCommand_Run(t *testing.T) {
db := MustOpen(0666, nil)
db.DB.Close()
defer db.Close()
db, err := bolt.Open(path, 0600, nil)
if err != nil {
panic("db open error: " + err.Error())
// Run the info command.
m := NewMain()
if err := m.Run("info", db.Path); err != nil {
t.Fatal(err)
}
fn(db, path)
}
// run executes a command against the CLI and returns the output.
func run(args ...string) string {
args = append([]string{"bolt"}, args...)
NewApp().Run(args)
return strings.TrimSpace(LogBuffer())
// 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())
}
}
// tempfile returns a temporary file path.
func tempfile() 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 {
return err
}
for i := 0; i < 10; i++ {
if err := b.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i))); err != nil {
return err
}
}
// Create "bar" bucket.
b, err = tx.CreateBucket([]byte("bar"))
if err != nil {
return err
}
for i := 0; i < 100; i++ {
if err := b.Put([]byte(strconv.Itoa(i)), []byte(strconv.Itoa(i))); err != nil {
return err
}
}
// Create "baz" bucket.
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.Close()
os.Remove(f.Name())
return f.Name()
}
// 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) {
db, err := bolt.Open(f.Name(), mode, options)
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()
panic(err.Error())
}
return &DB{DB: db, Path: f.Name()}
}
// 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()
}
// DB is a test wrapper for bolt.DB.
type DB struct {
*bolt.DB
Path string
}
// Close closes and removes the database.
func (db *DB) Close() error {
defer os.Remove(db.Path)
return db.DB.Close()
}

View File

@ -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
})
}

View File

@ -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
}
}

View File

@ -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)
})
}

View File

@ -10,6 +10,8 @@ import (
// Cursors see nested buckets with value == nil.
// 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
// and return unexpected keys and/or values. You must reposition your cursor
// 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.
// 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) {
_assert(c.bucket.tx.db != nil, "tx closed")
c.stack = c.stack[:0]
p, n := c.bucket.pageNode(c.bucket.root)
c.stack = append(c.stack, elemRef{page: p, node: n, index: 0})
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()
if (flags & uint32(bucketLeafFlag)) != 0 {
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.
// 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) {
_assert(c.bucket.tx.db != nil, "tx closed")
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.
// 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) {
_assert(c.bucket.tx.db != nil, "tx closed")
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.
// 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) {
_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.
// If the key does not exist then the next key is used. If no keys
// 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) {
k, v, flags := c.seek(seek)
@ -202,28 +216,37 @@ func (c *Cursor) last() {
// 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.
func (c *Cursor) next() (key []byte, value []byte, flags uint32) {
// 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.
var i int
for i = len(c.stack) - 1; i >= 0; i-- {
elem := &c.stack[i]
if elem.index < elem.count()-1 {
elem.index++
break
for {
// 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.
var i int
for i = len(c.stack) - 1; i >= 0; i-- {
elem := &c.stack[i]
if elem.index < elem.count()-1 {
elem.index++
break
}
}
}
// If we've hit the root page then stop and return. This will leave the
// cursor on the last element of the last page.
if i == -1 {
return nil, nil, 0
}
// If we've hit the root page then stop and return. This will leave the
// cursor on the last element of the last page.
if i == -1 {
return nil, nil, 0
}
// Otherwise start from where we left off in the stack and find the
// first element of the first leaf page.
c.stack = c.stack[:i+1]
c.first()
return c.keyValue()
// Otherwise start from where we left off in the stack and find the
// first element of the first leaf page.
c.stack = c.stack[:i+1]
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()
}
}
// search recursively performs a binary search against a given page/node until it finds a given key.

View File

@ -4,7 +4,9 @@ import (
"bytes"
"encoding/binary"
"fmt"
"log"
"os"
"reflect"
"sort"
"testing"
"testing/quick"
@ -14,100 +16,149 @@ import (
// Ensure that a cursor can return a reference to the bucket that created it.
func TestCursor_Bucket(t *testing.T) {
db := NewTestDB()
defer db.Close()
db.Update(func(tx *bolt.Tx) error {
b, _ := tx.CreateBucket([]byte("widgets"))
c := b.Cursor()
equals(t, b, c.Bucket())
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 cb := b.Cursor().Bucket(); !reflect.DeepEqual(cb, b) {
t.Fatal("cursor bucket mismatch")
}
return nil
})
}); err != nil {
t.Fatal(err)
}
}
// Ensure that a Tx cursor can seek to the appropriate keys.
func TestCursor_Seek(t *testing.T) {
db := NewTestDB()
defer db.Close()
db.Update(func(tx *bolt.Tx) error {
db := MustOpenDB()
defer db.MustClose()
if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets"))
ok(t, err)
ok(t, b.Put([]byte("foo"), []byte("0001")))
ok(t, b.Put([]byte("bar"), []byte("0002")))
ok(t, b.Put([]byte("baz"), []byte("0003")))
_, err = b.CreateBucket([]byte("bkt"))
ok(t, err)
if err != nil {
t.Fatal(err)
}
if err := b.Put([]byte("foo"), []byte("0001")); err != nil {
t.Fatal(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
})
db.View(func(tx *bolt.Tx) error {
}); err != nil {
t.Fatal(err)
}
if err := db.View(func(tx *bolt.Tx) error {
c := tx.Bucket([]byte("widgets")).Cursor()
// Exact match should go to the key.
k, v := c.Seek([]byte("bar"))
equals(t, []byte("bar"), k)
equals(t, []byte("0002"), v)
if k, v := c.Seek([]byte("bar")); !bytes.Equal(k, []byte("bar")) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, []byte("0002")) {
t.Fatalf("unexpected value: %v", v)
}
// Inexact match should go to the next key.
k, v = c.Seek([]byte("bas"))
equals(t, []byte("baz"), k)
equals(t, []byte("0003"), v)
if k, v := c.Seek([]byte("bas")); !bytes.Equal(k, []byte("baz")) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, []byte("0003")) {
t.Fatalf("unexpected value: %v", v)
}
// Low key should go to the first key.
k, v = c.Seek([]byte(""))
equals(t, []byte("bar"), k)
equals(t, []byte("0002"), v)
if k, v := c.Seek([]byte("")); !bytes.Equal(k, []byte("bar")) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, []byte("0002")) {
t.Fatalf("unexpected value: %v", v)
}
// High key should return no key.
k, v = c.Seek([]byte("zzz"))
assert(t, k == nil, "")
assert(t, v == nil, "")
if k, v := c.Seek([]byte("zzz")); k != nil {
t.Fatalf("expected nil key: %v", k)
} else if v != nil {
t.Fatalf("expected nil value: %v", v)
}
// Buckets should return their key but no value.
k, v = c.Seek([]byte("bkt"))
equals(t, []byte("bkt"), k)
assert(t, v == nil, "")
if k, v := c.Seek([]byte("bkt")); !bytes.Equal(k, []byte("bkt")) {
t.Fatalf("unexpected key: %v", k)
} else if v != nil {
t.Fatalf("expected nil value: %v", v)
}
return nil
})
}); err != nil {
t.Fatal(err)
}
}
func TestCursor_Delete(t *testing.T) {
db := NewTestDB()
defer db.Close()
db := MustOpenDB()
defer db.MustClose()
var count = 1000
const count = 1000
// Insert every other key between 0 and $count.
db.Update(func(tx *bolt.Tx) error {
b, _ := tx.CreateBucket([]byte("widgets"))
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 < count; i += 1 {
k := make([]byte, 8)
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
})
}); 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()
bound := make([]byte, 8)
binary.BigEndian.PutUint64(bound, uint64(count/2))
for key, _ := c.First(); bytes.Compare(key, bound) < 0; key, _ = c.Next() {
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 {
b := tx.Bucket([]byte("widgets"))
equals(t, b.Stats().KeyN, count/2+1)
c.Seek([]byte("sub"))
if err := c.Delete(); err != bolt.ErrIncompatibleValue {
t.Fatalf("unexpected error: %s", err)
}
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
@ -116,25 +167,33 @@ func TestCursor_Delete(t *testing.T) {
//
// Related: https://github.com/boltdb/bolt/pull/187
func TestCursor_Seek_Large(t *testing.T) {
db := NewTestDB()
defer db.Close()
db := MustOpenDB()
defer db.MustClose()
var count = 10000
// Insert every other key between 0 and $count.
db.Update(func(tx *bolt.Tx) error {
b, _ := tx.CreateBucket([]byte("widgets"))
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 < count; i += 100 {
for j := i; j < i+100; j += 2 {
k := make([]byte, 8)
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
})
}); 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()
for i := 0; i < count; i++ {
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
// it should return nil.
if i == count-1 {
assert(t, k == nil, "")
if k != nil {
t.Fatal("expected nil key")
}
continue
}
// Otherwise we should seek to the exact key or the next key.
num := binary.BigEndian.Uint64(k)
if i%2 == 0 {
equals(t, uint64(i), num)
if num != uint64(i) {
t.Fatalf("unexpected num: %d", num)
}
} else {
equals(t, uint64(i+1), num)
if num != uint64(i+1) {
t.Fatalf("unexpected num: %d", num)
}
}
}
return nil
})
}); err != nil {
t.Fatal(err)
}
}
// Ensure that a cursor can iterate over an empty bucket without error.
func TestCursor_EmptyBucket(t *testing.T) {
db := NewTestDB()
defer db.Close()
db.Update(func(tx *bolt.Tx) error {
db := MustOpenDB()
defer db.MustClose()
if err := db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucket([]byte("widgets"))
return err
})
db.View(func(tx *bolt.Tx) error {
}); err != nil {
t.Fatal(err)
}
if err := db.View(func(tx *bolt.Tx) error {
c := tx.Bucket([]byte("widgets")).Cursor()
k, v := c.First()
assert(t, k == nil, "")
assert(t, v == nil, "")
if k != nil {
t.Fatalf("unexpected key: %v", k)
} else if v != nil {
t.Fatalf("unexpected value: %v", v)
}
return nil
})
}); err != nil {
t.Fatal(err)
}
}
// Ensure that a Tx cursor can reverse iterate over an empty bucket without error.
func TestCursor_EmptyBucketReverse(t *testing.T) {
db := NewTestDB()
defer db.Close()
db := MustOpenDB()
defer db.MustClose()
db.Update(func(tx *bolt.Tx) error {
if err := db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucket([]byte("widgets"))
return err
})
db.View(func(tx *bolt.Tx) error {
}); err != nil {
t.Fatal(err)
}
if err := db.View(func(tx *bolt.Tx) error {
c := tx.Bucket([]byte("widgets")).Cursor()
k, v := c.Last()
assert(t, k == nil, "")
assert(t, v == nil, "")
if k != nil {
t.Fatalf("unexpected key: %v", k)
} else if v != nil {
t.Fatalf("unexpected value: %v", v)
}
return nil
})
}); err != nil {
t.Fatal(err)
}
}
// Ensure that a Tx cursor can iterate over a single root with a couple elements.
func TestCursor_Iterate_Leaf(t *testing.T) {
db := NewTestDB()
defer db.Close()
db := MustOpenDB()
defer db.MustClose()
db.Update(func(tx *bolt.Tx) error {
tx.CreateBucket([]byte("widgets"))
tx.Bucket([]byte("widgets")).Put([]byte("baz"), []byte{})
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte{0})
tx.Bucket([]byte("widgets")).Put([]byte("bar"), []byte{1})
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("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
})
tx, _ := db.Begin(false)
}); err != nil {
t.Fatal(err)
}
tx, err := db.Begin(false)
if err != nil {
t.Fatal(err)
}
defer func() { _ = tx.Rollback() }()
c := tx.Bucket([]byte("widgets")).Cursor()
k, v := c.First()
equals(t, string(k), "bar")
equals(t, v, []byte{1})
if !bytes.Equal(k, []byte("bar")) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, []byte{1}) {
t.Fatalf("unexpected value: %v", v)
}
k, v = c.Next()
equals(t, string(k), "baz")
equals(t, v, []byte{})
if !bytes.Equal(k, []byte("baz")) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, []byte{}) {
t.Fatalf("unexpected value: %v", v)
}
k, v = c.Next()
equals(t, string(k), "foo")
equals(t, v, []byte{0})
if !bytes.Equal(k, []byte("foo")) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, []byte{0}) {
t.Fatalf("unexpected value: %v", v)
}
k, v = c.Next()
assert(t, k == nil, "")
assert(t, v == nil, "")
if k != nil {
t.Fatalf("expected nil key: %v", k)
} else if v != nil {
t.Fatalf("expected nil value: %v", v)
}
k, v = c.Next()
assert(t, k == nil, "")
assert(t, v == nil, "")
if k != 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.
func TestCursor_LeafRootReverse(t *testing.T) {
db := NewTestDB()
defer db.Close()
db := MustOpenDB()
defer db.MustClose()
db.Update(func(tx *bolt.Tx) error {
tx.CreateBucket([]byte("widgets"))
tx.Bucket([]byte("widgets")).Put([]byte("baz"), []byte{})
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte{0})
tx.Bucket([]byte("widgets")).Put([]byte("bar"), []byte{1})
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("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
})
tx, _ := db.Begin(false)
}); err != nil {
t.Fatal(err)
}
tx, err := db.Begin(false)
if err != nil {
t.Fatal(err)
}
c := tx.Bucket([]byte("widgets")).Cursor()
k, v := c.Last()
equals(t, string(k), "foo")
equals(t, v, []byte{0})
if k, v := c.Last(); !bytes.Equal(k, []byte("foo")) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, []byte{0}) {
t.Fatalf("unexpected value: %v", v)
}
k, v = c.Prev()
equals(t, string(k), "baz")
equals(t, v, []byte{})
if k, v := c.Prev(); !bytes.Equal(k, []byte("baz")) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, []byte{}) {
t.Fatalf("unexpected value: %v", v)
}
k, v = c.Prev()
equals(t, string(k), "bar")
equals(t, v, []byte{1})
if k, v := c.Prev(); !bytes.Equal(k, []byte("bar")) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, []byte{1}) {
t.Fatalf("unexpected value: %v", v)
}
k, v = c.Prev()
assert(t, k == nil, "")
assert(t, v == nil, "")
if k, v := c.Prev(); k != nil {
t.Fatalf("expected nil key: %v", k)
} else if v != nil {
t.Fatalf("expected nil value: %v", v)
}
k, v = c.Prev()
assert(t, k == nil, "")
assert(t, v == nil, "")
if k, v := c.Prev(); k != 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 restart from the beginning.
func TestCursor_Restart(t *testing.T) {
db := NewTestDB()
defer db.Close()
db := MustOpenDB()
defer db.MustClose()
db.Update(func(tx *bolt.Tx) error {
tx.CreateBucket([]byte("widgets"))
tx.Bucket([]byte("widgets")).Put([]byte("bar"), []byte{})
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte{})
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("bar"), []byte{}); err != nil {
t.Fatal(err)
}
if err := b.Put([]byte("foo"), []byte{}); err != nil {
t.Fatal(err)
}
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()
k, _ := c.First()
equals(t, string(k), "bar")
if k, _ := c.First(); !bytes.Equal(k, []byte("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()
equals(t, string(k), "foo")
if k, _ := c.First(); !bytes.Equal(k, []byte("bar")) {
t.Fatalf("unexpected key: %v", k)
}
if k, _ := c.Next(); !bytes.Equal(k, []byte("foo")) {
t.Fatalf("unexpected key: %v", k)
}
k, _ = c.First()
equals(t, string(k), "bar")
if err := tx.Rollback(); err != nil {
t.Fatal(err)
}
}
k, _ = c.Next()
equals(t, string(k), "foo")
// Ensure that a cursor can skip over empty pages that have been deleted.
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.
func TestCursor_QuickCheck(t *testing.T) {
f := func(items testdata) bool {
db := NewTestDB()
defer db.Close()
db := MustOpenDB()
defer db.MustClose()
// Bulk insert all values.
tx, _ := db.Begin(true)
tx.CreateBucket([]byte("widgets"))
b := tx.Bucket([]byte("widgets"))
for _, item := range items {
ok(t, b.Put(item.Key, item.Value))
tx, err := db.Begin(true)
if err != nil {
t.Fatal(err)
}
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.Sort(items)
// Iterate over all items and check consistency.
var index = 0
tx, _ = db.Begin(false)
tx, err = db.Begin(false)
if err != nil {
t.Fatal(err)
}
c := tx.Bucket([]byte("widgets")).Cursor()
for k, v := c.First(); k != nil && index < len(items); k, v = c.Next() {
equals(t, k, items[index].Key)
equals(t, v, items[index].Value)
if !bytes.Equal(k, items[index].Key) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, items[index].Value) {
t.Fatalf("unexpected value: %v", v)
}
index++
}
equals(t, len(items), index)
tx.Rollback()
if len(items) != index {
t.Fatalf("unexpected item count: %v, expected %v", len(items), index)
}
if err := tx.Rollback(); err != nil {
t.Fatal(err)
}
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.
func TestCursor_QuickCheck_Reverse(t *testing.T) {
f := func(items testdata) bool {
db := NewTestDB()
defer db.Close()
db := MustOpenDB()
defer db.MustClose()
// Bulk insert all values.
tx, _ := db.Begin(true)
tx.CreateBucket([]byte("widgets"))
b := tx.Bucket([]byte("widgets"))
for _, item := range items {
ok(t, b.Put(item.Key, item.Value))
tx, err := db.Begin(true)
if err != nil {
t.Fatal(err)
}
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.Sort(revtestdata(items))
// Iterate over all items and check consistency.
var index = 0
tx, _ = db.Begin(false)
tx, err = db.Begin(false)
if err != nil {
t.Fatal(err)
}
c := tx.Bucket([]byte("widgets")).Cursor()
for k, v := c.Last(); k != nil && index < len(items); k, v = c.Prev() {
equals(t, k, items[index].Key)
equals(t, v, items[index].Value)
if !bytes.Equal(k, items[index].Key) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, items[index].Value) {
t.Fatalf("unexpected value: %v", v)
}
index++
}
equals(t, len(items), index)
tx.Rollback()
if len(items) != index {
t.Fatalf("unexpected item count: %v, expected %v", len(items), index)
}
if err := tx.Rollback(); err != nil {
t.Fatal(err)
}
return true
}
@ -379,76 +624,114 @@ func TestCursor_QuickCheck_Reverse(t *testing.T) {
// Ensure that a Tx cursor can iterate over subbuckets.
func TestCursor_QuickCheck_BucketsOnly(t *testing.T) {
db := NewTestDB()
defer db.Close()
db := MustOpenDB()
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"))
ok(t, err)
_, err = b.CreateBucket([]byte("foo"))
ok(t, err)
_, err = b.CreateBucket([]byte("bar"))
ok(t, err)
_, err = b.CreateBucket([]byte("baz"))
ok(t, err)
if err != nil {
t.Fatal(err)
}
if _, err := b.CreateBucket([]byte("foo")); err != nil {
t.Fatal(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
})
db.View(func(tx *bolt.Tx) error {
}); err != nil {
t.Fatal(err)
}
if err := db.View(func(tx *bolt.Tx) error {
var names []string
c := tx.Bucket([]byte("widgets")).Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
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
})
}); err != nil {
t.Fatal(err)
}
}
// Ensure that a Tx cursor can reverse iterate over subbuckets.
func TestCursor_QuickCheck_BucketsOnly_Reverse(t *testing.T) {
db := NewTestDB()
defer db.Close()
db := MustOpenDB()
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"))
ok(t, err)
_, err = b.CreateBucket([]byte("foo"))
ok(t, err)
_, err = b.CreateBucket([]byte("bar"))
ok(t, err)
_, err = b.CreateBucket([]byte("baz"))
ok(t, err)
if err != nil {
t.Fatal(err)
}
if _, err := b.CreateBucket([]byte("foo")); err != nil {
t.Fatal(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
})
db.View(func(tx *bolt.Tx) error {
}); err != nil {
t.Fatal(err)
}
if err := db.View(func(tx *bolt.Tx) error {
var names []string
c := tx.Bucket([]byte("widgets")).Cursor()
for k, v := c.Last(); k != nil; k, v = c.Prev() {
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
})
}); err != nil {
t.Fatal(err)
}
}
func ExampleCursor() {
// 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 db.Close()
// 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.
tx.CreateBucket([]byte("animals"))
b, err := tx.CreateBucket([]byte("animals"))
if err != nil {
return err
}
// Insert data into a bucket.
b := tx.Bucket([]byte("animals"))
b.Put([]byte("dog"), []byte("fun"))
b.Put([]byte("cat"), []byte("lame"))
b.Put([]byte("liger"), []byte("awesome"))
if err := b.Put([]byte("dog"), []byte("fun")); err != nil {
log.Fatal(err)
}
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.
c := b.Cursor()
@ -463,7 +746,13 @@ func ExampleCursor() {
}
return nil
})
}); err != nil {
log.Fatal(err)
}
if err := db.Close(); err != nil {
log.Fatal(err)
}
// Output:
// A cat is lame.
@ -473,20 +762,30 @@ func ExampleCursor() {
func ExampleCursor_reverse() {
// 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 db.Close()
// 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.
tx.CreateBucket([]byte("animals"))
b, err := tx.CreateBucket([]byte("animals"))
if err != nil {
return err
}
// Insert data into a bucket.
b := tx.Bucket([]byte("animals"))
b.Put([]byte("dog"), []byte("fun"))
b.Put([]byte("cat"), []byte("lame"))
b.Put([]byte("liger"), []byte("awesome"))
if err := b.Put([]byte("dog"), []byte("fun")); err != nil {
log.Fatal(err)
}
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.
c := b.Cursor()
@ -502,7 +801,14 @@ func ExampleCursor_reverse() {
}
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:
// A liger is awesome.

View File

@ -1,8 +1,10 @@
package bolt
import (
"errors"
"fmt"
"hash/fnv"
"log"
"os"
"runtime"
"runtime/debug"
@ -24,9 +26,16 @@ const magic uint32 = 0xED0CDAED
// IgnoreNoSync specifies whether the NoSync field of a DB is ignored when
// 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
// must be synchronzied using the msync(2) syscall.
// must be synchronized using the msync(2) syscall.
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.
// 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.
@ -49,11 +58,45 @@ type DB struct {
// THIS IS UNSAFE. PLEASE USE WITH CAUTION.
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
file *os.File
dataref []byte
dataref []byte // mmap'ed readonly, write throws SEGV
data *[maxMapSize]byte
datasz int
filesz int // current on disk file size
meta0 *meta
meta1 *meta
pageSize int
@ -63,6 +106,9 @@ type DB struct {
freelist *freelist
stats Stats
batchMu sync.Mutex
batch *batch
rwlock sync.Mutex // Allows only one writer at a time.
metalock sync.Mutex // Protects meta page access.
mmaplock sync.RWMutex // Protects mmap access during remapping.
@ -71,6 +117,10 @@ type DB struct {
ops struct {
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.
@ -98,20 +148,36 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
if options == nil {
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.
db.path = path
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()
return nil, err
}
// Lock file so that other processes using Bolt cannot use the database
// at the same time. This would cause corruption since the two processes
// would write meta pages and free pages separately.
if err := flock(db.file, options.Timeout); err != nil {
// Lock file so that other processes using Bolt in read-write mode cannot
// use the database at the same time. This would cause corruption since
// the two processes would write meta pages and free pages separately.
// 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()
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.
if info, err := db.file.Stat(); err != nil {
return nil, fmt.Errorf("stat error: %s", err)
return nil, err
} else if info.Size() == 0 {
// Initialize new files with meta pages.
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 {
m := db.pageInBuffer(buf[:], 0).meta()
if err := m.validate(); err != nil {
return nil, fmt.Errorf("meta0 error: %s", err)
return nil, err
}
db.pageSize = int(m.pageSize)
}
}
// Memory map the data file.
if err := db.mmap(0); err != nil {
if err := db.mmap(options.InitialMmapSize); err != nil {
_ = db.close()
return nil, err
}
@ -197,10 +263,10 @@ func (db *DB) mmap(minsz int) error {
// Validate the meta pages.
if err := db.meta0.validate(); err != nil {
return fmt.Errorf("meta0 error: %s", err)
return err
}
if err := db.meta1.validate(); err != nil {
return fmt.Errorf("meta1 error: %s", err)
return err
}
return nil
@ -215,11 +281,11 @@ func (db *DB) munmap() error {
}
// 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.
func (db *DB) mmapSize(size int) (int, error) {
// Double the size from 1MB until 1GB.
for i := uint(20); i <= 30; i++ {
// Double the size from 32KB until 1GB.
for i := uint(15); i <= 30; i++ {
if size <= 1<<i {
return 1 << i, nil
}
@ -300,8 +366,15 @@ func (db *DB) init() error {
// Close releases all database resources.
// All transactions must be closed before closing the database.
func (db *DB) Close() error {
db.rwlock.Lock()
defer db.rwlock.Unlock()
db.metalock.Lock()
defer db.metalock.Unlock()
db.mmaplock.RLock()
defer db.mmaplock.RUnlock()
return db.close()
}
@ -321,8 +394,13 @@ func (db *DB) close() error {
// Close file handles.
if db.file != nil {
// Unlock the file.
_ = funlock(db.file)
// No need to unlock read-only file.
if !db.readOnly {
// Unlock the file.
if err := funlock(db.file); err != nil {
log.Printf("bolt.Close(): funlock error: %s", err)
}
}
// Close the file descriptor.
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
// 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
// else the database will not reclaim old pages.
func (db *DB) Begin(writable bool) (*Tx, error) {
@ -388,6 +475,11 @@ func (db *DB) beginTx() (*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.
// This enforces only one writer transaction at a time.
db.rwlock.Lock()
@ -518,6 +610,142 @@ func (db *DB) View(fn func(*Tx) error) error {
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.
// This is only updated when a transaction closes.
func (db *DB) Stats() Stats {
@ -578,18 +806,73 @@ func (db *DB) allocate(count int) (*page, error) {
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.
type Options struct {
// 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
// available on Darwin and Linux.
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().
// No timeout is used which will cause Bolt to wait indefinitely for a lock.
var DefaultOptions = &Options{
Timeout: 0,
Timeout: 0,
NoGrowSync: false,
}
// Stats represents statistics about the database.

File diff suppressed because it is too large Load Diff

View File

@ -36,6 +36,10 @@ var (
// ErrTxClosed is returned when committing or rolling back a transaction
// that has already been committed or rolled back.
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.

View File

@ -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.
func (f *freelist) all() []pgid {
ids := make([]pgid, len(f.ids))
copy(ids, f.ids)
m := make(pgids, 0)
for _, list := range f.pending {
ids = append(ids, list...)
m = append(m, list...)
}
sort.Sort(pgids(ids))
return ids
sort.Sort(m)
return pgids(f.ids).merge(m)
}
// 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.
func (f *freelist) release(txid txid) {
m := make(pgids, 0)
for tid, ids := range f.pending {
if tid <= txid {
// Move transaction's pending pages to the available freelist.
// 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)
}
}
sort.Sort(pgids(f.ids))
sort.Sort(m)
f.ids = pgids(f.ids).merge(m)
}
// rollback removes the pages from a given pending tx.

View File

@ -1,7 +1,9 @@
package bolt
import (
"math/rand"
"reflect"
"sort"
"testing"
"unsafe"
)
@ -115,7 +117,9 @@ func TestFreelist_write(t *testing.T) {
f.pending[100] = []pgid{28, 11}
f.pending[101] = []pgid{3}
p := (*page)(unsafe.Pointer(&buf[0]))
f.write(p)
if err := f.write(p); err != nil {
t.Fatal(err)
}
// Read the page back out.
f2 := newFreelist()
@ -127,3 +131,28 @@ func TestFreelist_write(t *testing.T) {
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
}

View File

@ -221,11 +221,20 @@ func (n *node) write(p *page) {
_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.
copy(b[0:], item.key)
b = b[len(item.key):]
b = b[klen:]
copy(b[0:], item.value)
b = b[len(item.value):]
b = b[vlen:]
}
// DEBUG ONLY: n.dump()

View File

@ -3,6 +3,7 @@ package bolt
import (
"fmt"
"os"
"sort"
"unsafe"
)
@ -96,7 +97,7 @@ type branchPageElement struct {
// key returns a byte slice of the node key.
func (n *branchPageElement) key() []byte {
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.
@ -110,13 +111,13 @@ type leafPageElement struct {
// key returns a byte slice of the node key.
func (n *leafPageElement) key() []byte {
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.
func (n *leafPageElement) value() []byte {
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.
@ -132,3 +133,40 @@ type pgids []pgid
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) 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
}

View File

@ -1,7 +1,10 @@
package bolt
import (
"reflect"
"sort"
"testing"
"testing/quick"
)
// 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) {
(&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)
}
}

View File

@ -10,7 +10,7 @@ import (
"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_100op_1p(t *testing.T) { testSimulate(t, 100, 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)
versions[1] = NewQuickDB()
db := NewTestDB()
defer db.Close()
db := MustOpenDB()
defer db.MustClose()
var mutex sync.Mutex
@ -89,10 +89,12 @@ func testSimulate(t *testing.T, threadCount, parallelism int) {
versions[tx.ID()] = qdb
mutex.Unlock()
ok(t, tx.Commit())
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
}()
} else {
defer tx.Rollback()
defer func() { _ = tx.Rollback() }()
}
// Ignore operation if we don't have data yet.

View File

@ -29,6 +29,14 @@ type Tx struct {
pages map[pgid]*page
stats TxStats
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.
@ -87,18 +95,21 @@ func (tx *Tx) Stats() TxStats {
// Bucket retrieves a bucket by name.
// 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 {
return tx.root.Bucket(name)
}
// 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.
// The bucket instance is only valid for the lifetime of the transaction.
func (tx *Tx) CreateBucket(name []byte) (*Bucket, error) {
return tx.root.CreateBucket(name)
}
// 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.
// The bucket instance is only valid for the lifetime of the transaction.
func (tx *Tx) CreateBucketIfNotExists(name []byte) (*Bucket, error) {
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.
// 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 {
_assert(!tx.managed, "managed tx commit not allowed")
if tx.db == nil {
@ -156,6 +168,8 @@ func (tx *Tx) Commit() error {
// Free the old root bucket.
tx.meta.root.root = tx.root.root
opgid := tx.meta.pgid
// 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).
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
// 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.
startTime = time.Now()
if err := tx.write(); err != nil {
@ -203,7 +225,8 @@ func (tx *Tx) Commit() error {
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 {
_assert(!tx.managed, "managed tx rollback not allowed")
if tx.db == nil {
@ -234,7 +257,8 @@ func (tx *Tx) close() {
var freelistPendingN = tx.db.freelist.pending_count()
var freelistAlloc = tx.db.freelist.size()
// Remove writer lock.
// Remove transaction ref & writer lock.
tx.db.rwtx = nil
tx.db.rwlock.Unlock()
// Merge statistics.
@ -248,41 +272,47 @@ func (tx *Tx) close() {
} else {
tx.db.removeTx(tx)
}
// Clear all references.
tx.db = nil
tx.meta = nil
tx.root = Bucket{tx: tx}
tx.pages = nil
}
// Copy writes the entire database to a writer.
// A reader transaction is maintained during the copy so it is safe to continue
// using the database while a copy is in progress.
// Copy will write exactly tx.Size() bytes into the writer.
// This function exists for backwards compatibility. Use WriteTo() instead.
func (tx *Tx) Copy(w io.Writer) error {
var f *os.File
var err error
_, err := tx.WriteTo(w)
return err
}
// 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
}
// 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.
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()
if err != nil {
_ = f.Close()
return fmt.Errorf("meta copy: %s", err)
return n, fmt.Errorf("meta copy: %s", err)
}
// Copy data pages.
if _, err := io.CopyN(w, f, tx.Size()-int64(tx.db.pageSize*2)); err != nil {
_ = f.Close()
return err
wn, err := io.CopyN(w, f, tx.Size()-int64(tx.db.pageSize*2))
n += wn
if err != nil {
return n, err
}
return f.Close()
return n, f.Close()
}
// 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.
for _, p := range pages {
size := (int(p.overflow) + 1) * tx.db.pageSize
buf := (*[maxAllocSize]byte)(unsafe.Pointer(p))[:size]
offset := int64(p.id) * int64(tx.db.pageSize)
if _, err := tx.db.ops.writeAt(buf, offset); err != nil {
return err
}
// Update statistics.
tx.stats.Write++
// 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 {
return err
}
// Update statistics.
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 err := fdatasync(tx.db); err != nil {
return err
@ -461,7 +515,7 @@ func (tx *Tx) writeMeta() error {
}
// 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 {
// Check the dirty pages first.
if tx.pages != nil {

View File

@ -1,8 +1,10 @@
package bolt_test
import (
"bytes"
"errors"
"fmt"
"log"
"os"
"testing"
@ -10,299 +12,519 @@ import (
)
// Ensure that committing a closed transaction returns an error.
func TestTx_Commit_Closed(t *testing.T) {
db := NewTestDB()
defer db.Close()
tx, _ := db.Begin(true)
tx.CreateBucket([]byte("foo"))
ok(t, tx.Commit())
equals(t, tx.Commit(), bolt.ErrTxClosed)
func TestTx_Commit_ErrTxClosed(t *testing.T) {
db := MustOpenDB()
defer db.MustClose()
tx, err := db.Begin(true)
if err != nil {
t.Fatal(err)
}
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.
func TestTx_Rollback_Closed(t *testing.T) {
db := NewTestDB()
defer db.Close()
tx, _ := db.Begin(true)
ok(t, tx.Rollback())
equals(t, tx.Rollback(), bolt.ErrTxClosed)
func TestTx_Rollback_ErrTxClosed(t *testing.T) {
db := MustOpenDB()
defer db.MustClose()
tx, err := db.Begin(true)
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.
func TestTx_Commit_ReadOnly(t *testing.T) {
db := NewTestDB()
defer db.Close()
tx, _ := db.Begin(false)
equals(t, tx.Commit(), bolt.ErrTxNotWritable)
func TestTx_Commit_ErrTxNotWritable(t *testing.T) {
db := MustOpenDB()
defer db.MustClose()
tx, err := db.Begin(false)
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.
func TestTx_Cursor(t *testing.T) {
db := NewTestDB()
defer db.Close()
db.Update(func(tx *bolt.Tx) error {
tx.CreateBucket([]byte("widgets"))
tx.CreateBucket([]byte("woojits"))
db := MustOpenDB()
defer db.MustClose()
if err := db.Update(func(tx *bolt.Tx) error {
if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
t.Fatal(err)
}
if _, err := tx.CreateBucket([]byte("woojits")); err != nil {
t.Fatal(err)
}
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()
equals(t, "widgets", string(k))
assert(t, v == nil, "")
if k, v := c.Next(); !bytes.Equal(k, []byte("woojits")) {
t.Fatalf("unexpected key: %v", k)
} else if v != nil {
t.Fatalf("unexpected value: %v", v)
}
k, v = c.Next()
equals(t, "woojits", string(k))
assert(t, v == nil, "")
k, v = c.Next()
assert(t, k == nil, "")
assert(t, v == nil, "")
if k, v := c.Next(); k != nil {
t.Fatalf("unexpected key: %v", k)
} else if v != nil {
t.Fatalf("unexpected value: %v", k)
}
return nil
})
}); err != nil {
t.Fatal(err)
}
}
// Ensure that creating a bucket with a read-only transaction returns an error.
func TestTx_CreateBucket_ReadOnly(t *testing.T) {
db := NewTestDB()
defer db.Close()
db.View(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("foo"))
assert(t, b == nil, "")
equals(t, bolt.ErrTxNotWritable, err)
func TestTx_CreateBucket_ErrTxNotWritable(t *testing.T) {
db := MustOpenDB()
defer db.MustClose()
if err := db.View(func(tx *bolt.Tx) error {
_, err := tx.CreateBucket([]byte("foo"))
if err != bolt.ErrTxNotWritable {
t.Fatalf("unexpected error: %s", err)
}
return nil
})
}); err != nil {
t.Fatal(err)
}
}
// Ensure that creating a bucket on a closed transaction returns an error.
func TestTx_CreateBucket_Closed(t *testing.T) {
db := NewTestDB()
defer db.Close()
tx, _ := db.Begin(true)
tx.Commit()
b, err := tx.CreateBucket([]byte("foo"))
assert(t, b == nil, "")
equals(t, bolt.ErrTxClosed, err)
func TestTx_CreateBucket_ErrTxClosed(t *testing.T) {
db := MustOpenDB()
defer db.MustClose()
tx, err := db.Begin(true)
if err != nil {
t.Fatal(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.
func TestTx_Bucket(t *testing.T) {
db := NewTestDB()
defer db.Close()
db.Update(func(tx *bolt.Tx) error {
tx.CreateBucket([]byte("widgets"))
b := tx.Bucket([]byte("widgets"))
assert(t, b != nil, "")
db := MustOpenDB()
defer db.MustClose()
if err := db.Update(func(tx *bolt.Tx) error {
if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
t.Fatal(err)
}
if tx.Bucket([]byte("widgets")) == nil {
t.Fatal("expected bucket")
}
return nil
})
}); err != nil {
t.Fatal(err)
}
}
// Ensure that a Tx retrieving a non-existent key returns nil.
func TestTx_Get_Missing(t *testing.T) {
db := NewTestDB()
defer db.Close()
db.Update(func(tx *bolt.Tx) error {
tx.CreateBucket([]byte("widgets"))
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
value := tx.Bucket([]byte("widgets")).Get([]byte("no_such_key"))
assert(t, value == nil, "")
func TestTx_Get_NotFound(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 b.Get([]byte("no_such_key")) != nil {
t.Fatal("expected nil value")
}
return nil
})
}); err != nil {
t.Fatal(err)
}
}
// Ensure that a bucket can be created and retrieved.
func TestTx_CreateBucket(t *testing.T) {
db := NewTestDB()
defer db.Close()
db := MustOpenDB()
defer db.MustClose()
// 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"))
assert(t, b != nil, "")
ok(t, err)
if err != nil {
t.Fatal(err)
} else if b == nil {
t.Fatal("expected bucket")
}
return nil
})
}); err != nil {
t.Fatal(err)
}
// Read the bucket through a separate transaction.
db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("widgets"))
assert(t, b != nil, "")
if err := db.View(func(tx *bolt.Tx) error {
if tx.Bucket([]byte("widgets")) == nil {
t.Fatal("expected bucket")
}
return nil
})
}); err != nil {
t.Fatal(err)
}
}
// Ensure that a bucket can be created if it doesn't already exist.
func TestTx_CreateBucketIfNotExists(t *testing.T) {
db := NewTestDB()
defer db.Close()
db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte("widgets"))
assert(t, b != nil, "")
ok(t, err)
db := MustOpenDB()
defer db.MustClose()
if err := db.Update(func(tx *bolt.Tx) error {
// Create bucket.
if b, err := tx.CreateBucketIfNotExists([]byte("widgets")); err != nil {
t.Fatal(err)
} else if b == nil {
t.Fatal("expected bucket")
}
b, err = tx.CreateBucketIfNotExists([]byte("widgets"))
assert(t, b != nil, "")
ok(t, err)
// Create bucket again.
if b, err := tx.CreateBucketIfNotExists([]byte("widgets")); err != nil {
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
})
}); err != nil {
t.Fatal(err)
}
// Read the bucket through a separate transaction.
db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("widgets"))
assert(t, b != nil, "")
if err := db.View(func(tx *bolt.Tx) error {
if tx.Bucket([]byte("widgets")) == nil {
t.Fatal("expected bucket")
}
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.
func TestTx_CreateBucket_Exists(t *testing.T) {
db := NewTestDB()
defer db.Close()
func TestTx_CreateBucket_ErrBucketExists(t *testing.T) {
db := MustOpenDB()
defer db.MustClose()
// Create a bucket.
db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets"))
assert(t, b != nil, "")
ok(t, err)
if err := db.Update(func(tx *bolt.Tx) error {
if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
t.Fatal(err)
}
return nil
})
}); err != nil {
t.Fatal(err)
}
// Create the same bucket again.
db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets"))
assert(t, b == nil, "")
equals(t, bolt.ErrBucketExists, err)
if err := db.Update(func(tx *bolt.Tx) error {
if _, err := tx.CreateBucket([]byte("widgets")); err != bolt.ErrBucketExists {
t.Fatalf("unexpected error: %s", err)
}
return nil
})
}); err != nil {
t.Fatal(err)
}
}
// Ensure that a bucket is created with a non-blank name.
func TestTx_CreateBucket_NameRequired(t *testing.T) {
db := NewTestDB()
defer db.Close()
db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket(nil)
assert(t, b == nil, "")
equals(t, bolt.ErrBucketNameRequired, err)
func TestTx_CreateBucket_ErrBucketNameRequired(t *testing.T) {
db := MustOpenDB()
defer db.MustClose()
if err := db.Update(func(tx *bolt.Tx) error {
if _, err := tx.CreateBucket(nil); err != bolt.ErrBucketNameRequired {
t.Fatalf("unexpected error: %s", err)
}
return nil
})
}); err != nil {
t.Fatal(err)
}
}
// Ensure that a bucket can be deleted.
func TestTx_DeleteBucket(t *testing.T) {
db := NewTestDB()
defer db.Close()
db := MustOpenDB()
defer db.MustClose()
// Create a bucket and add a value.
db.Update(func(tx *bolt.Tx) error {
tx.CreateBucket([]byte("widgets"))
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
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)
}
return nil
})
}); err != nil {
t.Fatal(err)
}
// Delete the bucket and make sure we can't get the value.
db.Update(func(tx *bolt.Tx) error {
ok(t, tx.DeleteBucket([]byte("widgets")))
assert(t, tx.Bucket([]byte("widgets")) == nil, "")
if err := db.Update(func(tx *bolt.Tx) error {
if err := tx.DeleteBucket([]byte("widgets")); err != nil {
t.Fatal(err)
}
if tx.Bucket([]byte("widgets")) != nil {
t.Fatal("unexpected bucket")
}
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.
b, err := tx.CreateBucket([]byte("widgets"))
assert(t, b != nil, "")
ok(t, err)
assert(t, tx.Bucket([]byte("widgets")).Get([]byte("foo")) == nil, "")
if err != nil {
t.Fatal(err)
}
if v := b.Get([]byte("foo")); v != nil {
t.Fatalf("unexpected phantom value: %v", v)
}
return nil
})
}); err != nil {
t.Fatal(err)
}
}
// Ensure that deleting a bucket on a closed transaction returns an error.
func TestTx_DeleteBucket_Closed(t *testing.T) {
db := NewTestDB()
defer db.Close()
tx, _ := db.Begin(true)
tx.Commit()
equals(t, tx.DeleteBucket([]byte("foo")), bolt.ErrTxClosed)
func TestTx_DeleteBucket_ErrTxClosed(t *testing.T) {
db := MustOpenDB()
defer db.MustClose()
tx, err := db.Begin(true)
if err != nil {
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.
func TestTx_DeleteBucket_ReadOnly(t *testing.T) {
db := NewTestDB()
defer db.Close()
db.View(func(tx *bolt.Tx) error {
equals(t, tx.DeleteBucket([]byte("foo")), bolt.ErrTxNotWritable)
db := MustOpenDB()
defer db.MustClose()
if err := db.View(func(tx *bolt.Tx) error {
if err := tx.DeleteBucket([]byte("foo")); err != bolt.ErrTxNotWritable {
t.Fatalf("unexpected error: %s", err)
}
return nil
})
}); err != nil {
t.Fatal(err)
}
}
// Ensure that nothing happens when deleting a bucket that doesn't exist.
func TestTx_DeleteBucket_NotFound(t *testing.T) {
db := NewTestDB()
defer db.Close()
db.Update(func(tx *bolt.Tx) error {
equals(t, bolt.ErrBucketNotFound, tx.DeleteBucket([]byte("widgets")))
db := MustOpenDB()
defer db.MustClose()
if err := db.Update(func(tx *bolt.Tx) error {
if err := tx.DeleteBucket([]byte("widgets")); err != bolt.ErrBucketNotFound {
t.Fatalf("unexpected error: %s", err)
}
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.
func TestTx_OnCommit(t *testing.T) {
db := MustOpenDB()
defer db.MustClose()
var x int
db := NewTestDB()
defer db.Close()
db.Update(func(tx *bolt.Tx) error {
if err := db.Update(func(tx *bolt.Tx) error {
tx.OnCommit(func() { x += 1 })
tx.OnCommit(func() { x += 2 })
_, err := tx.CreateBucket([]byte("widgets"))
return err
})
equals(t, 3, x)
if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
t.Fatal(err)
}
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.
func TestTx_OnCommit_Rollback(t *testing.T) {
db := MustOpenDB()
defer db.MustClose()
var x int
db := NewTestDB()
defer db.Close()
db.Update(func(tx *bolt.Tx) error {
if err := db.Update(func(tx *bolt.Tx) error {
tx.OnCommit(func() { x += 1 })
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")
})
equals(t, 0, x)
}); err == nil || err.Error() != "rollback this commit" {
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.
func TestTx_CopyFile(t *testing.T) {
db := NewTestDB()
defer db.Close()
var dest = tempfile()
db.Update(func(tx *bolt.Tx) error {
tx.CreateBucket([]byte("widgets"))
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
tx.Bucket([]byte("widgets")).Put([]byte("baz"), []byte("bat"))
db := MustOpenDB()
defer db.MustClose()
path := tempfile()
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 := b.Put([]byte("baz"), []byte("bat")); err != nil {
t.Fatal(err)
}
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)
ok(t, err)
defer db2.Close()
db2, err := bolt.Open(path, 0600, nil)
if err != nil {
t.Fatal(err)
}
db2.View(func(tx *bolt.Tx) error {
equals(t, []byte("bar"), tx.Bucket([]byte("widgets")).Get([]byte("foo")))
equals(t, []byte("bat"), tx.Bucket([]byte("widgets")).Get([]byte("baz")))
if err := db2.View(func(tx *bolt.Tx) error {
if v := tx.Bucket([]byte("widgets")).Get([]byte("foo")); !bytes.Equal(v, []byte("bar")) {
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
})
}); err != nil {
t.Fatal(err)
}
if err := db2.Close(); err != nil {
t.Fatal(err)
}
}
type failWriterError struct{}
@ -328,63 +550,107 @@ func (f *failWriter) Write(p []byte) (n int, err error) {
// Ensure that Copy handles write errors right.
func TestTx_CopyFile_Error_Meta(t *testing.T) {
db := NewTestDB()
defer db.Close()
db.Update(func(tx *bolt.Tx) error {
tx.CreateBucket([]byte("widgets"))
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
tx.Bucket([]byte("widgets")).Put([]byte("baz"), []byte("bat"))
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 := b.Put([]byte("baz"), []byte("bat")); err != nil {
t.Fatal(err)
}
return nil
})
}); err != nil {
t.Fatal(err)
}
err := db.View(func(tx *bolt.Tx) error { return tx.Copy(&failWriter{}) })
equals(t, err.Error(), "meta copy: error injected for tests")
if err := db.View(func(tx *bolt.Tx) error {
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.
func TestTx_CopyFile_Error_Normal(t *testing.T) {
db := NewTestDB()
defer db.Close()
db.Update(func(tx *bolt.Tx) error {
tx.CreateBucket([]byte("widgets"))
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
tx.Bucket([]byte("widgets")).Put([]byte("baz"), []byte("bat"))
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 := b.Put([]byte("baz"), []byte("bat")); err != nil {
t.Fatal(err)
}
return nil
})
}); err != nil {
t.Fatal(err)
}
err := db.View(func(tx *bolt.Tx) error { return tx.Copy(&failWriter{3 * db.Info().PageSize}) })
equals(t, err.Error(), "error injected for tests")
if err := db.View(func(tx *bolt.Tx) error {
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() {
// 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 db.Close()
// Create a bucket.
db.Update(func(tx *bolt.Tx) error {
if err := db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucket([]byte("widgets"))
return err
})
}); err != nil {
log.Fatal(err)
}
// 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"))
})
}); err != nil {
log.Fatal(err)
}
// 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.Put([]byte("foo"), []byte("baz"))
tx.Rollback()
if err := b.Put([]byte("foo"), []byte("baz")); err != nil {
log.Fatal(err)
}
if err := tx.Rollback(); err != nil {
log.Fatal(err)
}
// 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"))
fmt.Printf("The value for 'foo' is still: %s\n", value)
return nil
})
}); err != nil {
log.Fatal(err)
}
// Close database to release file lock.
if err := db.Close(); err != nil {
log.Fatal(err)
}
// Output:
// The value for 'foo' is still: bar
@ -392,32 +658,58 @@ func ExampleTx_Rollback() {
func ExampleTx_CopyFile() {
// 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 db.Close()
// Create a bucket and a key.
db.Update(func(tx *bolt.Tx) error {
tx.CreateBucket([]byte("widgets"))
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets"))
if err != nil {
return err
}
if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
return err
}
return nil
})
}); err != nil {
log.Fatal(err)
}
// Copy the database to another file.
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)
// Open the cloned database.
db2, _ := bolt.Open(toFile, 0666, nil)
defer db2.Close()
db2, err := bolt.Open(toFile, 0666, nil)
if err != nil {
log.Fatal(err)
}
// 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"))
fmt.Printf("The value for 'foo' in the clone is: %s\n", value)
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:
// The value for 'foo' in the clone is: bar

View File

@ -7,6 +7,8 @@ Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
* [API Reference](http://godoc.org/github.com/gorilla/websocket)
* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat)
* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command)
* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo)
* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch)
### Status

View File

@ -5,8 +5,12 @@
package websocket
import (
"bufio"
"bytes"
"crypto/tls"
"errors"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
@ -27,50 +31,17 @@ var ErrBadHandshake = errors.New("websocket: bad handshake")
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
// non-nil *http.Response so that callers can handle redirects, authentication,
// etc.
//
// Deprecated: Use Dialer instead.
func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) {
challengeKey, err := generateChallengeKey()
if err != nil {
return nil, nil, err
d := Dialer{
ReadBufferSize: readBufSize,
WriteBufferSize: writeBufSize,
NetDial: func(net, addr string) (net.Conn, error) {
return netConn, nil
},
}
acceptKey := computeAcceptKey(challengeKey)
c = newConn(netConn, false, readBufSize, writeBufSize)
p := c.writeBuf[:0]
p = append(p, "GET "...)
p = append(p, u.RequestURI()...)
p = append(p, " HTTP/1.1\r\nHost: "...)
p = append(p, u.Host...)
// "Upgrade" is capitalized for servers that do not use case insensitive
// comparisons on header tokens.
p = append(p, "\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: "...)
p = append(p, challengeKey...)
p = append(p, "\r\n"...)
for k, vs := range requestHeader {
for _, v := range vs {
p = append(p, k...)
p = append(p, ": "...)
p = append(p, v...)
p = append(p, "\r\n"...)
}
}
p = append(p, "\r\n"...)
if _, err := netConn.Write(p); err != nil {
return nil, nil, err
}
resp, err := http.ReadResponse(c.br, &http.Request{Method: "GET", URL: u})
if err != nil {
return nil, nil, err
}
if resp.StatusCode != 101 ||
!strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") ||
!strings.EqualFold(resp.Header.Get("Connection"), "upgrade") ||
resp.Header.Get("Sec-Websocket-Accept") != acceptKey {
return nil, resp, ErrBadHandshake
}
c.subprotocol = resp.Header.Get("Sec-Websocket-Protocol")
return c, resp, nil
return d.Dial(u.String(), requestHeader)
}
// A Dialer contains options for connecting to WebSocket server.
@ -79,6 +50,12 @@ type Dialer struct {
// NetDial is nil, net.Dial is used.
NetDial func(network, addr string) (net.Conn, error)
// Proxy specifies a function to return a proxy for a given
// Request. If the function returns a non-nil error, the
// request is aborted with the provided error.
// If Proxy is nil or returns a nil *URL, no proxy is used.
Proxy func(*http.Request) (*url.URL, error)
// TLSClientConfig specifies the TLS configuration to use with tls.Client.
// If nil, the default configuration is used.
TLSClientConfig *tls.Config
@ -96,17 +73,15 @@ type Dialer struct {
var errMalformedURL = errors.New("malformed ws or wss URL")
// parseURL parses the URL. The url.Parse function is not used here because
// url.Parse mangles the path.
// parseURL parses the URL.
//
// This function is a replacement for the standard library url.Parse function.
// In Go 1.4 and earlier, url.Parse loses information from the path.
func parseURL(s string) (*url.URL, error) {
// From the RFC:
//
// ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
// wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
//
// We don't use the net/url parser here because the dialer interface does
// not provide a way for applications to work around percent deocding in
// the net/url parser.
var u url.URL
switch {
@ -120,11 +95,24 @@ func parseURL(s string) (*url.URL, error) {
return nil, errMalformedURL
}
u.Host = s
u.Opaque = "/"
if i := strings.Index(s, "?"); i >= 0 {
u.RawQuery = s[i+1:]
s = s[:i]
}
if i := strings.Index(s, "/"); i >= 0 {
u.Host = 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
@ -136,9 +124,12 @@ func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) {
if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") {
hostNoPort = hostNoPort[:i]
} else {
if u.Scheme == "wss" {
switch u.Scheme {
case "wss":
hostPort += ":443"
} else {
case "https":
hostPort += ":443"
default:
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.
var DefaultDialer *Dialer
var DefaultDialer = &Dialer{
Proxy: http.ProxyFromEnvironment,
}
// Dial creates a new client connection. Use requestHeader to specify the
// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie).
@ -155,17 +148,94 @@ var DefaultDialer *Dialer
//
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
// 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) {
if d == nil {
d = &Dialer{
Proxy: http.ProxyFromEnvironment,
}
}
challengeKey, err := generateChallengeKey()
if err != nil {
return nil, nil, err
}
u, err := parseURL(urlStr)
if err != nil {
return nil, nil, err
}
switch u.Scheme {
case "ws":
u.Scheme = "http"
case "wss":
u.Scheme = "https"
default:
return nil, nil, errMalformedURL
}
if u.User != nil {
// User name and password are not allowed in websocket URIs.
return nil, nil, errMalformedURL
}
req := &http.Request{
Method: "GET",
URL: u,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: make(http.Header),
Host: u.Host,
}
// Set the request headers using the capitalization for names and values in
// RFC examples. Although the capitalization shouldn't matter, there are
// servers that depend on it. The Header.Set method is not used because the
// method canonicalizes the header names.
req.Header["Upgrade"] = []string{"websocket"}
req.Header["Connection"] = []string{"Upgrade"}
req.Header["Sec-WebSocket-Key"] = []string{challengeKey}
req.Header["Sec-WebSocket-Version"] = []string{"13"}
if len(d.Subprotocols) > 0 {
req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")}
}
for k, vs := range requestHeader {
switch {
case k == "Host":
if len(vs) > 0 {
req.Host = vs[0]
}
case k == "Upgrade" ||
k == "Connection" ||
k == "Sec-Websocket-Key" ||
k == "Sec-Websocket-Version" ||
(k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0):
return nil, nil, errors.New("websocket: duplicate header not allowed: " + k)
default:
req.Header[k] = vs
}
}
hostPort, hostNoPort := hostPortNoPort(u)
if d == nil {
d = &Dialer{}
var proxyURL *url.URL
// Check wether the proxy method has been configured
if d.Proxy != nil {
proxyURL, err = d.Proxy(req)
}
if err != nil {
return nil, nil, err
}
var targetHostPort string
if proxyURL != nil {
targetHostPort, _ = hostPortNoPort(proxyURL)
} else {
targetHostPort = hostPort
}
var deadline time.Time
@ -179,7 +249,7 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
netDial = netDialer.Dial
}
netConn, err := netDial("tcp", hostPort)
netConn, err := netDial("tcp", targetHostPort)
if err != nil {
return nil, nil, err
}
@ -194,7 +264,31 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Re
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
if cfg == nil {
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 {
h := http.Header{}
for k, v := range requestHeader {
h[k] = v
}
h.Set("Sec-Websocket-Protocol", strings.Join(d.Subprotocols, ", "))
requestHeader = h
conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize)
if err := req.Write(netConn); err != nil {
return nil, nil, err
}
conn, resp, err := NewClient(netConn, u, requestHeader, d.ReadBufferSize, d.WriteBufferSize)
resp, err := http.ReadResponse(conn.br, req)
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 = nil // to avoid close in defer.

View File

@ -8,11 +8,13 @@ import (
"crypto/tls"
"crypto/x509"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"strings"
"testing"
"time"
)
@ -34,29 +36,42 @@ var cstDialer = Dialer{
type cstHandler struct{ *testing.T }
type Server struct {
type cstServer struct {
*httptest.Server
URL string
}
func newServer(t *testing.T) *Server {
var s Server
const (
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.URL = "ws" + s.Server.URL[len("http"):]
s.Server.URL += cstRequestURI
s.URL = makeWsProto(s.Server.URL)
return &s
}
func newTLSServer(t *testing.T) *Server {
var s Server
func newTLSServer(t *testing.T) *cstServer {
var s cstServer
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
}
func (t cstHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
t.Logf("method %s not allowed", r.Method)
http.Error(w, "method not allowed", 405)
if r.URL.Path != cstPath {
t.Logf("path=%v, want %v", r.URL.Path, cstPath)
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
}
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) {
const message = "Hello World!"
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) {
s := newServer(t)
defer s.Close()
@ -148,7 +206,7 @@ func TestDialTLS(t *testing.T) {
d := cstDialer
d.NetDial = func(network, addr string) (net.Conn, error) { return net.Dial(network, u.Host) }
d.TLSClientConfig = &tls.Config{RootCAs: certs}
ws, _, err := d.Dial("wss://example.com/", nil)
ws, _, err := d.Dial("wss://example.com"+cstRequestURI, nil)
if err != nil {
t.Fatalf("Dial: %v", err)
}
@ -157,6 +215,7 @@ func TestDialTLS(t *testing.T) {
}
func xTestDialTLSBadCert(t *testing.T) {
// This test is deactivated because of noisy logging from the net/http package.
s := newTLSServer(t)
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) {
s := newServer(t)
defer s.Close()
@ -247,3 +345,66 @@ func TestHandshake(t *testing.T) {
}
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)
}

View File

@ -11,15 +11,19 @@ import (
)
var parseURLTests = []struct {
s string
u *url.URL
s string
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:7777/", &url.URL{Scheme: "ws", Host: "example.com:7777", Opaque: "/"}},
{"wss://example.com/", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/"}},
{"wss://example.com/a/b", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/a/b"}},
{"ss://example.com/a/b", nil},
{"ws://example.com/", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}, "/"},
{"ws://example.com", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}, "/"},
{"ws://example.com:7777/", &url.URL{Scheme: "ws", Host: "example.com:7777", Opaque: "/"}, "/"},
{"wss://example.com/", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/"}, "/"},
{"wss://example.com/a/b", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/a/b"}, "/a/b"},
{"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) {
@ -29,14 +33,19 @@ func TestParseURL(t *testing.T) {
t.Errorf("parseURL(%q) returned error %v", tt.s, err)
continue
}
if tt.u == nil && err == nil {
t.Errorf("parseURL(%q) did not return error", tt.s)
if tt.u == nil {
if err == nil {
t.Errorf("parseURL(%q) did not return error", tt.s)
}
continue
}
if !reflect.DeepEqual(u, tt.u) {
t.Errorf("parseURL(%q) returned %v, want %v", tt.s, u, tt.u)
t.Errorf("parseURL(%q) = %v, want %v", tt.s, u, tt.u)
continue
}
if u.RequestURI() != tt.rui {
t.Errorf("parseURL(%q).RequestURI() = %v, want %v", tt.s, u.RequestURI(), tt.rui)
}
}
}

View File

@ -88,19 +88,23 @@ func (e *netError) Error() string { return e.msg }
func (e *netError) Temporary() bool { return e.temporary }
func (e *netError) Timeout() bool { return e.timeout }
// closeError represents close frame.
type closeError struct {
code int
text string
// CloseError represents close frame.
type CloseError struct {
// Code is defined in RFC 6455, section 11.7.
Code int
// Text is the optional text payload.
Text string
}
func (e *closeError) Error() string {
return "websocket: close " + strconv.Itoa(e.code) + " " + e.text
func (e *CloseError) Error() string {
return "websocket: close " + strconv.Itoa(e.Code) + " " + e.Text
}
var (
errWriteTimeout = &netError{msg: "websocket: write timeout", timeout: true}
errUnexpectedEOF = &closeError{code: CloseAbnormalClosure, text: io.ErrUnexpectedEOF.Error()}
errWriteTimeout = &netError{msg: "websocket: write timeout", timeout: true, temporary: true}
errUnexpectedEOF = &CloseError{Code: CloseAbnormalClosure, Text: io.ErrUnexpectedEOF.Error()}
errBadWriteOpCode = errors.New("websocket: bad write message type")
errWriteClosed = errors.New("websocket: write closed")
errInvalidControlFrame = errors.New("websocket: invalid control frame")
@ -296,7 +300,7 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er
if n != 0 && n != len(buf) {
c.conn.Close()
}
return err
return hideTempErr(err)
}
// 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))
closeText = string(payload[2:])
}
switch closeCode {
case CloseNormalClosure, CloseGoingAway:
return noFrame, io.EOF
default:
return noFrame, &closeError{code: closeCode, text: closeText}
}
return noFrame, &CloseError{Code: closeCode, Text: closeText}
}
return frameType, nil
@ -790,20 +789,27 @@ func (c *Conn) SetReadLimit(limit int64) {
}
// SetPingHandler sets the handler for ping messages received from the peer.
// The default ping handler sends a pong to the peer.
func (c *Conn) SetPingHandler(h func(string) error) {
// The appData argument to h is the PING frame application data. The default
// ping handler sends a pong to the peer.
func (c *Conn) SetPingHandler(h func(appData string) error) {
if h == nil {
h = func(message string) error {
c.WriteControl(PongMessage, []byte(message), time.Now().Add(writeWait))
return nil
err := c.WriteControl(PongMessage, []byte(message), time.Now().Add(writeWait))
if err == ErrCloseSent {
return nil
} else if e, ok := err.(net.Error); ok && e.Temporary() {
return nil
}
return err
}
}
c.handlePing = h
}
// SetPongHandler sets the handler for pong messages received from the peer.
// The default pong handler does nothing.
func (c *Conn) SetPongHandler(h func(string) error) {
// The appData argument to h is the PONG frame application data. The default
// pong handler does nothing.
func (c *Conn) SetPongHandler(h func(appData string) error) {
if h == nil {
h = func(string) error { return nil }
}

View File

@ -5,11 +5,13 @@
package websocket
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"net"
"reflect"
"testing"
"testing/iotest"
"time"
@ -146,13 +148,15 @@ func TestControl(t *testing.T) {
func TestCloseBeforeFinalFrame(t *testing.T) {
const bufSize = 512
expectedErr := &CloseError{Code: CloseNormalClosure, Text: "hello"}
var b1, b2 bytes.Buffer
wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, bufSize)
rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024)
w, _ := wc.NextWriter(BinaryMessage)
w.Write(make([]byte, bufSize+bufSize/2))
wc.WriteControl(CloseMessage, FormatCloseMessage(CloseNormalClosure, ""), time.Now().Add(10*time.Second))
wc.WriteControl(CloseMessage, FormatCloseMessage(expectedErr.Code, expectedErr.Text), time.Now().Add(10*time.Second))
w.Close()
op, r, err := rc.NextReader()
@ -160,12 +164,12 @@ func TestCloseBeforeFinalFrame(t *testing.T) {
t.Fatalf("NextReader() returned %d, %v", op, err)
}
_, err = io.Copy(ioutil.Discard, r)
if err != errUnexpectedEOF {
t.Fatalf("io.Copy() returned %v, want %v", err, errUnexpectedEOF)
if !reflect.DeepEqual(err, expectedErr) {
t.Fatalf("io.Copy() returned %v, want %v", err, expectedErr)
}
_, _, err = rc.NextReader()
if err != io.EOF {
t.Fatalf("NextReader() returned %v, want %v", err, io.EOF)
if !reflect.DeepEqual(err, expectedErr) {
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.")
}
}
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))
}
}

View File

@ -24,7 +24,7 @@
// ... 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
// messages using these methods:
//
@ -97,10 +97,13 @@
//
// Concurrency
//
// Connections do not support concurrent calls to the write methods
// (NextWriter, SetWriteDeadline, WriteMessage) or concurrent calls to the read
// methods methods (NextReader, SetReadDeadline, ReadMessage). Connections do
// support a concurrent reader and writer.
// Connections support one concurrent reader and one concurrent writer.
//
// Applications are responsible for ensuring that no more than one goroutine
// calls the write methods (NextWriter, SetWriteDeadline, WriteMessage,
// WriteJSON) concurrently and that no more than one goroutine calls the read
// methods (NextReader, SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler,
// SetPingHandler) concurrently.
//
// The Close and WriteControl methods can be called concurrently with all other
// methods.

View File

@ -17,3 +17,4 @@ using the following commands.
$ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/chat`
$ go run *.go
To use the chat example, open http://localhost:8080/ in your browser.

View File

@ -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) {
if r.Method != "GET" {
http.Error(w, "Method not allowed", 405)
return
}
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)

View 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

View 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>

View 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))
}

View 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.

View 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
}
}
}

View 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>
`))

View File

@ -48,9 +48,7 @@ func (c *Conn) ReadJSON(v interface{}) error {
}
err = json.NewDecoder(r).Decode(v)
if err == io.EOF {
// Decode returns io.EOF when the message is empty or all whitespace.
// Convert to io.ErrUnexpectedEOF so that application can distinguish
// between an error reading the JSON value and the connection closing.
// One value is expected in the message.
err = io.ErrUnexpectedEOF
}
return err

View File

@ -38,7 +38,7 @@ func TestJSON(t *testing.T) {
}
}
func TestPartialJsonRead(t *testing.T) {
func TestPartialJSONRead(t *testing.T) {
var buf bytes.Buffer
c := fakeNetConn{&buf, &buf}
wc := newConn(c, true, 1024, 1024)
@ -87,7 +87,7 @@ func TestPartialJsonRead(t *testing.T) {
}
err = rc.ReadJSON(&v)
if err != io.EOF {
if _, ok := err.(*CloseError); !ok {
t.Error("final", err)
}
}

View File

@ -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
// application negotiated subprotocol (Sec-Websocket-Protocol).
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) {
if r.Method != "GET" {
return u.returnError(w, r, http.StatusMethodNotAllowed, "websocket: method not GET")
}
if values := r.Header["Sec-Websocket-Version"]; len(values) == 0 || values[0] != "13" {
return u.returnError(w, r, http.StatusBadRequest, "websocket: version != 13")
}

View File

@ -7,20 +7,49 @@ import (
"os/exec"
"path/filepath"
"runtime"
"strconv"
"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.
//
// This uses an OS-specific method for discovering the home directory.
// An error is returned if a home directory cannot be detected.
func Dir() (string, error) {
if runtime.GOOS == "windows" {
return dirWindows()
if !DisableCache {
cacheLock.RLock()
cached := homedirCache
cacheLock.RUnlock()
if cached != "" {
return cached, nil
}
}
// Unix-like system, so just assume Unix
return dirUnix()
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
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
@ -53,9 +82,28 @@ func dirUnix() (string, error) {
return home, nil
}
// If that fails, try the shell
// If that fails, try getent
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
if err := cmd.Run(); err != nil {
return "", err

View File

@ -17,6 +17,18 @@ func patchEnv(key, value string) func() {
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) {
u, err := user.Current()
if err != nil {
@ -86,6 +98,8 @@ func TestExpand(t *testing.T) {
}
}
DisableCache = true
defer func() { DisableCache = false }()
defer patchEnv("HOME", "/custom/path/")()
expected := "/custom/path/foo/bar"
actual, err := Expand("~/foo/bar")

View File

@ -42,6 +42,11 @@ func ToStringMapString(i interface{}) map[string]string {
return v
}
func ToStringMapStringSlice(i interface{}) map[string][]string {
v, _ := ToStringMapStringSliceE(i)
return v
}
func ToStringMapBool(i interface{}) map[string]bool {
v, _ := ToStringMapBoolE(i)
return v

View File

@ -6,9 +6,9 @@
package cast
import (
"testing"
"html/template"
"testing"
"time"
"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([]byte("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(nil), "")
assert.Equal(t, ToString(true), "true")
assert.Equal(t, ToString(false), "false")
}
type foo struct {
@ -73,8 +76,43 @@ func TestErrorToString(t *testing.T) {
func TestMaps(t *testing.T) {
var taxonomies = map[interface{}]interface{}{"tag": "tags", "group": "groups"}
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, 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) {
@ -115,3 +153,12 @@ func TestIndirectPointers(t *testing.T) {
assert.Equal(t, ToInt(y), 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)
}

View File

@ -6,7 +6,6 @@
package cast
import (
"errors"
"fmt"
"html/template"
"reflect"
@ -17,6 +16,7 @@ import (
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) {
i = indirect(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) {
i = indirect(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) {
case time.Duration:
return s, nil
case int64:
d = time.Duration(s)
return
case float64:
d = time.Duration(s)
return
case string:
d, err = time.ParseDuration(s)
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) {
i = indirect(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) {
i = indirect(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)
if err == 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:
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) {
i = indirect(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)
if err == 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:
return int(s), nil
case bool:
if bool(s) {
return 1, nil
} else {
return 0, nil
}
return 0, nil
case nil:
return 0, nil
default:
@ -179,6 +186,7 @@ func indirectToStringerOrError(a interface{}) interface{} {
return v.Interface()
}
// ToStringE casts an empty interface to a string.
func ToStringE(i interface{}) (string, error) {
i = indirectToStringerOrError(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) {
case string:
return s, nil
case bool:
return strconv.FormatBool(s), nil
case float64:
return strconv.FormatFloat(i.(float64), 'f', -1, 64), nil
case int:
@ -194,6 +204,8 @@ func ToStringE(i interface{}) (string, error) {
return string(s), nil
case template.HTML:
return string(s), nil
case template.URL:
return string(s), nil
case nil:
return "", nil
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) {
jww.DEBUG.Println("ToStringMapStringE called on type:", reflect.TypeOf(i))
var m = map[string]string{}
switch v := i.(type) {
case map[interface{}]interface{}:
for k, val := range v {
m[ToString(k)] = ToString(val)
}
return m, nil
case map[string]string:
return v, nil
case map[string]interface{}:
for k, val := range v {
m[ToString(k)] = ToString(val)
}
return m, nil
case map[string]string:
return v, nil
case map[interface{}]string:
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:
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) {
jww.DEBUG.Println("ToStringMapBoolE called on type:", reflect.TypeOf(i))
@ -250,9 +324,9 @@ func ToStringMapBoolE(i interface{}) (map[string]bool, error) {
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)
}
// ToStringMapE casts an empty interface to a map[string]interface{}.
func ToStringMapE(i interface{}) (map[string]interface{}, error) {
jww.DEBUG.Println("ToStringMapE called on type:", reflect.TypeOf(i))
@ -269,10 +343,9 @@ func ToStringMapE(i interface{}) (map[string]interface{}, error) {
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)
}
// ToSliceE casts an empty interface to a []interface{}.
func ToSliceE(i interface{}) ([]interface{}, error) {
jww.DEBUG.Println("ToSliceE called on type:", reflect.TypeOf(i))
@ -292,10 +365,9 @@ func ToSliceE(i interface{}) ([]interface{}, error) {
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 to []interface{}", i)
}
// ToStringSliceE casts an empty interface to a []string.
func ToStringSliceE(i interface{}) ([]string, error) {
jww.DEBUG.Println("ToStringSliceE called on type:", reflect.TypeOf(i))
@ -311,13 +383,18 @@ func ToStringSliceE(i interface{}) ([]string, error) {
return v, nil
case string:
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:
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) {
jww.DEBUG.Println("ToIntSliceE called on type:", reflect.TypeOf(i))
@ -346,10 +423,9 @@ func ToIntSliceE(i interface{}) ([]int, error) {
default:
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) {
return parseDateWith(s, []string{
time.RFC3339,
@ -365,6 +441,8 @@ func StringToDate(s string) (time.Time, error) {
"02 Jan 06 15:04 MST",
"2006-01-02",
"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 d, errors.New(fmt.Sprintf("Unable to parse date: %s", s))
return d, fmt.Errorf("Unable to parse date: %s", s)
}

View 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>

View File

@ -1,8 +1,9 @@
language: go
go:
- 1.3
- 1.3.3
- 1.4.2
- 1.5.1
- tip
script:
- go test ./...
- go test -v ./...
- go build

View File

@ -1,122 +1,363 @@
# Cobra
![cobra logo](https://cloud.githubusercontent.com/assets/173412/10886352/ad566232-814f-11e5-9cd0-aa101788c117.png)
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.
[![Build Status](https://travis-ci.org/spf13/cobra.svg)](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
providing **fully posix compliant flags** (including short & long versions),
**nesting commands**, and the ability to **define your own help and usage** for any or
all commands.
[![Build Status](https://travis-ci.org/spf13/cobra.svg "Travis CI status")](https://travis-ci.org/spf13/cobra)
[![CircleCI status](https://circleci.com/gh/spf13/cobra.png?circle-token=:circle-token "CircleCI status")](https://circleci.com/gh/spf13/cobra)
![cobra](https://cloud.githubusercontent.com/assets/173412/10911369/84832a8e-8212-11e5-9f82-cc96660a4794.gif)
# 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
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
confusing space isnt provided). Both short and long flags can be used. A
command need not even be fully typed. The shortest unambiguous string will
suffice. Help is automatically generated and available for the application or
for a specific command using either the help command or the --help flag.
command need not even be fully typed. Help is automatically generated and
available for the application or for a specific command using either the help
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
the application supports will be contained in a Command. A command can
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:
type Command struct {
Use string // The one-line usage message.
Short string // The short description shown in the 'help' output.
Long string // The long message shown in the 'help <this-command>' output.
Run func(cmd *Command, args []string) // Run runs the command.
}
```go
type Command struct {
Use string // The one-line usage message.
Short string // The short description shown in the 'help' output.
Long string // The long message shown in the 'help <this-command>' output.
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
fully posix compliant flags as well as the go flag package.
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](https://golang.org/pkg/flag/).
A Cobra command can define flags that persist through to children commands
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
libary](https://github.com/ogier/pflag), a fork of the flag standard library
which maintains the same interface while adding posix compliance.
library](https://github.com/ogier/pflag), a fork of the flag standard library
which maintains the same interface while adding POSIX compliance.
## Usage
Cobra works by creating a set of commands and then organizing them into a tree.
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.
### Installing
Using Cobra is easy. First use go get to install the latest version
of the library.
# Installing
Using Cobra is easy. First, use `go get` to install the latest version
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:
import "github.com/spf13/cobra"
```go
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
The root command represents your binary itself.
#### Manually create rootCmd
Cobra doesn't require any special constructors. Simply create your commands.
var HugoCmd = &cobra.Command{
Use: "hugo",
Short: "Hugo is a very fast static site generator",
Long: `A Fast and Flexible Static Site Generator built with
Ideally you place this in app/cmd/root.go:
```go
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`,
Run: func(cmd *cobra.Command, args []string) {
// Do Stuff Here
},
}
Run: func(cmd *cobra.Command, args []string) {
// 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
Additional commands can be defined.
Additional commands can be defined and typically are each given their own file
inside of the cmd/ directory.
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of Hugo",
Long: `All software has versions. This is Hugo's`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Hugo Static Site Generator v0.9 -- HEAD")
},
}
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{
Use: "version",
Short: "Print the version number of Hugo",
Long: `All software has versions. This is Hugo's`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Hugo Static Site Generator v0.9 -- HEAD")
},
}
```
### 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
@ -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
work with.
var Verbose bool
var Source string
```go
var Verbose bool
var Source string
```
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
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.
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
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
is not executable meaning that a subcommand is required. This is accomplished
by not providing a 'Run' for the 'rootCmd'.
@ -169,196 +402,254 @@ We have only defined one flag for a single command.
More documentation about flags is available at https://github.com/spf13/pflag
import(
"github.com/spf13/cobra"
"fmt"
"strings"
)
```go
package main
func main() {
import (
"fmt"
"strings"
var echoTimes int
"github.com/spf13/cobra"
)
var cmdPrint = &cobra.Command{
Use: "print [string to print]",
Short: "Print anything to the screen",
Long: `print is for printing anything back to the screen.
func main() {
var echoTimes int
var cmdPrint = &cobra.Command{
Use: "print [string to print]",
Short: "Print anything to the screen",
Long: `print is for printing anything back to the screen.
For many years people have printed back to the screen.
`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Print: " + strings.Join(args, " "))
},
}
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Print: " + strings.Join(args, " "))
},
}
var cmdEcho = &cobra.Command{
Use: "echo [string to echo]",
Short: "Echo anything to the screen",
Long: `echo is for echoing anything back.
var cmdEcho = &cobra.Command{
Use: "echo [string to echo]",
Short: "Echo anything to the screen",
Long: `echo is for echoing anything back.
Echo works a lot like print, except it has a child command.
`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Print: " + strings.Join(args, " "))
},
}
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Print: " + strings.Join(args, " "))
},
}
var cmdTimes = &cobra.Command{
Use: "times [# times] [string to echo]",
Short: "Echo anything to the screen more times",
Long: `echo things multiple times back to the user by providing
var cmdTimes = &cobra.Command{
Use: "times [# times] [string to echo]",
Short: "Echo anything to the screen more times",
Long: `echo things multiple times back to the user by providing
a count and a string.`,
Run: func(cmd *cobra.Command, args []string) {
for i:=0; i < echoTimes; i++ {
fmt.Println("Echo: " + strings.Join(args, " "))
}
},
}
Run: func(cmd *cobra.Command, args []string) {
for i := 0; i < echoTimes; i++ {
fmt.Println("Echo: " + strings.Join(args, " "))
}
},
}
cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input")
cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input")
var rootCmd = &cobra.Command{Use: "app"}
rootCmd.AddCommand(cmdPrint, cmdEcho)
cmdEcho.AddCommand(cmdTimes)
rootCmd.Execute()
}
var rootCmd = &cobra.Command{Use: "app"}
rootCmd.AddCommand(cmdPrint, cmdEcho)
cmdEcho.AddCommand(cmdTimes)
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
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
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
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
'create' without any additional configuration; Cobra will work when 'app help
create' is called. Every command will automatically have the '--help' flag added.
### 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.
> hugo help
A Fast and Flexible Static Site Generator built with
love by spf13 and friends in Go.
hugo is the main command, used to build your Hugo site.
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:
hugo [flags]
hugo [command]
Available Commands:
server :: Hugo runs it's own a webserver to render the files
version :: Print the version number of Hugo
check :: Check content in the source directory
benchmark :: Benchmark hugo by building a site a number of times
help [command] :: Help about any command
server Hugo runs its own webserver to render the files
version Print the version number of Hugo
config Print the site configuration
check Check content in the source directory
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:
-b, --base-url="": hostname (and path) to the root eg. http://spf13.com/
-D, --build-drafts=false: include content marked as draft
Flags:
-b, --baseURL="": hostname (and path) to the root, e.g. http://spf13.com/
-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)
-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
--stepAnalysis=false: display memory and timing of different steps of the program
--uglyurls=false: if true, use /filename.html instead of /filename/
-v, --verbose=false: verbose output
-w, --watch=false: watch filesystem for changes and recreate as needed
Use "hugo help [command]" for more information about that command.
--stepAnalysis[=false]: display memory and timing of different steps of the program
-t, --theme="": theme to use (located in /themes/THEMENAME/)
--uglyURLs[=false]: if true, use /filename.html instead of /filename/
-v, --verbose[=false]: verbose output
--verboseLog[=false]: verbose logging
-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
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
You can provide your own Help command or you own template for the default command to use.
The default help command is
The default help command is
func (c *Command) initHelp() {
if c.helpCommand == nil {
c.helpCommand = &Command{
Use: "help [command]",
Short: "Help about any command",
Long: `Help provides help for any command in the application.
```go
func (c *Command) initHelp() {
if c.helpCommand == nil {
c.helpCommand = &Command{
Use: "help [command]",
Short: "Help about any command",
Long: `Help provides help for any command in the application.
Simply type ` + c.Name() + ` help [path to command] for full details.`,
Run: c.HelpFunc(),
}
}
c.AddCommand(c.helpCommand)
}
Run: c.HelpFunc(),
}
}
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:
command.SetHelpCommand(cmd *Command)
```go
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.
## Usage
When the user provides an invalid flag or invalid command Cobra responds by
showing the user the 'usage'
When the user provides an invalid flag or invalid command, Cobra responds by
showing the user the 'usage'.
### Example
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:
hugo [flags]
hugo [command]
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
config Print the site configuration
check Check content in the source directory
benchmark Benchmark hugo by building a site a number of times
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:
-b, --base-url="": hostname (and path) to the root eg. http://spf13.com/
-D, --build-drafts=false: include content marked as draft
Flags:
-b, --baseURL="": hostname (and path) to the root, e.g. http://spf13.com/
-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)
-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
--stepAnalysis=false: display memory and timing of different steps of the program
--uglyurls=false: if true, use /filename.html instead of /filename/
-v, --verbose=false: verbose output
-w, --watch=false: watch filesystem for changes and recreate as needed
--stepAnalysis[=false]: display memory and timing of different steps of the program
-t, --theme="": theme to use (located in /themes/THEMENAME/)
--uglyURLs[=false]: if true, use /filename.html instead of /filename/
-v, --verbose[=false]: verbose output
--verboseLog[=false]: verbose logging
-w, --watch[=false]: watch filesystem for changes and recreate as needed
### 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:
return func(c *Command) error {
err := tmpl(c.Out(), c.UsageTemplate(), c)
return err
}
```go
return func(c *Command) error {
err := tmpl(c.Out(), c.UsageTemplate(), c)
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:
command.SetUsageFunc(f func(*Command) error)
```go
command.SetUsageFunc(f func(*Command) error)
command.SetUsageTemplate(s string)
command.SetUsageTemplate(s string)
```
## 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`
- `PreRun`
- `Run`
- `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
package main
@ -393,7 +684,7 @@ func main() {
var subCmd = &cobra.Command{
Use: "sub [no options!]",
Short: "My sub command",
Short: "My subcommand",
PreRun: func(cmd *cobra.Command, args []string) {
fmt.Printf("Inside subCmd PreRun with args: %v\n", args)
},
@ -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
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
Cobra provides a DebugFlags method on a command which when called will print
out everything Cobra knows about the flags for each command
Cobra provides a DebugFlags method on a command which, when called, will print
out everything Cobra knows about the flags for each command.
### Example
command.DebugFlags()
```go
command.DebugFlags()
```
## Release Notes
* **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
* 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
* Launch proper documentation site
@ -474,7 +859,9 @@ out everything Cobra knows about the flags for each command
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
@ -482,4 +869,3 @@ Cobra is released under the Apache 2.0 license. See [LICENSE.txt](https://github
[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/spf13/cobra/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

View File

@ -1,8 +1,8 @@
package cobra
import (
"bytes"
"fmt"
"io"
"os"
"sort"
"strings"
@ -13,19 +13,30 @@ import (
const (
BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extentions"
BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag"
BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir"
)
func preamble(out *bytes.Buffer) {
fmt.Fprintf(out, `#!/bin/bash
func preamble(out io.Writer, name string) error {
_, err := fmt.Fprintf(out, "# bash completion for %-36s -*- shell-script -*-\n", name)
if err != nil {
return err
}
_, err = fmt.Fprintf(out, `
__debug()
{
if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then
echo "$*" >> ${BASH_COMP_DEBUG_FILE}
echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
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()
{
local w word=$1
@ -52,7 +63,9 @@ __handle_reply()
__debug "${FUNCNAME}"
case $cur in
-*)
compopt -o nospace
if [[ $(type -t compopt) = "builtin" ]]; then
compopt -o nospace
fi
local allflags
if [ ${#must_have_one_flag[@]} -ne 0 ]; then
allflags=("${must_have_one_flag[@]}")
@ -60,7 +73,9 @@ __handle_reply()
allflags=("${flags[*]} ${two_word_flags[*]}")
fi
COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") )
[[ $COMPREPLY == *= ]] || compopt +o nospace
if [[ $(type -t compopt) = "builtin" ]]; then
[[ $COMPREPLY == *= ]] || compopt +o nospace
fi
return 0;
;;
esac
@ -91,6 +106,21 @@ __handle_reply()
if [[ ${#COMPREPLY[@]} -eq 0 ]]; then
declare -F __custom_func >/dev/null && __custom_func
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()
@ -99,8 +129,10 @@ __handle_flag()
# if a command required a flag, and we found it, unset must_have_one_flag()
local flagname=${words[c]}
local flagvalue
# if the word contained an =
if [[ ${words[c]} == *"="* ]]; then
flagvalue=${flagname#*=} # take in as flagvalue after the =
flagname=${flagname%%=*} # strip everything after the =
flagname="${flagname}=" # but put the = back
fi
@ -109,6 +141,15 @@ __handle_flag()
must_have_one_flag=()
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
if __contains_word "${words[c]}" "${two_word_flags[@]}"; then
c=$((c+1))
@ -118,7 +159,6 @@ __handle_flag()
fi
fi
# skip the flag itself
c=$((c+1))
}
@ -141,9 +181,9 @@ __handle_command()
local next_command
if [[ -n ${last_command} ]]; then
next_command="_${last_command}_${words[c]}"
next_command="_${last_command}_${words[c]//:/__}"
else
next_command="_${words[c]}"
next_command="_${words[c]//:/__}"
fi
c=$((c+1))
__debug "${FUNCNAME}: looking for ${next_command}"
@ -154,11 +194,11 @@ __handle_word()
{
if [[ $c -ge $cword ]]; then
__handle_reply
return
return
fi
__debug "${FUNCNAME}: c is $c words[c] is ${words[c]}"
if [[ "${words[c]}" == -* ]]; then
__handle_flag
__handle_flag
elif __contains_word "${words[c]}" "${commands[@]}"; then
__handle_command
else
@ -168,15 +208,24 @@ __handle_word()
}
`)
return err
}
func postscript(out *bytes.Buffer, name string) {
fmt.Fprintf(out, "__start_%s()\n", name)
fmt.Fprintf(out, `{
local cur prev words cword split
_init_completion -s || return
func postscript(w io.Writer, name string) error {
name = strings.Replace(name, ":", "__", -1)
_, err := fmt.Fprintf(w, "__start_%s()\n", name)
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
else
__my_init_completion || return
fi
local completions_func
local c=0
local flags=()
local two_word_flags=()
@ -192,35 +241,77 @@ func postscript(out *bytes.Buffer, name string) {
}
`, name)
fmt.Fprintf(out, "complete -F __start_%s %s\n", name, name)
fmt.Fprintf(out, "# ex: ts=4 sw=4 et filetype=sh\n")
if err != nil {
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) {
fmt.Fprintf(out, " commands=()\n")
func writeCommands(cmd *Command, w io.Writer) error {
if _, err := fmt.Fprintf(w, " commands=()\n"); err != nil {
return err
}
for _, c := range cmd.Commands() {
if len(c.Deprecated) > 0 {
if !c.IsAvailableCommand() || c == cmd.helpCommand {
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 {
switch key {
case BashCompFilenameExt:
fmt.Fprintf(out, " flags_with_completion+=(%q)\n", name)
_, err := fmt.Fprintf(w, " flags_with_completion+=(%q)\n", name)
if err != nil {
return err
}
ext := strings.Join(value, "|")
ext = "_filedir '@(" + ext + ")'"
fmt.Fprintf(out, " flags_completion+=(%q)\n", ext)
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, out *bytes.Buffer) {
func writeShortFlag(flag *pflag.Flag, w io.Writer) error {
b := (flag.Value.Type() == "bool")
name := flag.Shorthand
format := " "
@ -228,11 +319,13 @@ func writeShortFlag(flag *pflag.Flag, out *bytes.Buffer) {
format += "two_word_"
}
format += "flags+=(\"-%s\")\n"
fmt.Fprintf(out, format, name)
writeFlagHandler("-"+name, flag.Annotations, out)
if _, err := fmt.Fprintf(w, format, name); err != nil {
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")
name := flag.Name
format := " flags+=(\"--%s"
@ -240,32 +333,66 @@ func writeFlag(flag *pflag.Flag, out *bytes.Buffer) {
format += "="
}
format += "\")\n"
fmt.Fprintf(out, format, name)
writeFlagHandler("--"+name, flag.Annotations, out)
if _, err := fmt.Fprintf(w, format, name); err != nil {
return err
}
return writeFlagHandler("--"+name, flag.Annotations, w)
}
func writeFlags(cmd *Command, out *bytes.Buffer) {
fmt.Fprintf(out, ` flags=()
func writeFlags(cmd *Command, w io.Writer) error {
_, err := fmt.Fprintf(w, ` flags=()
two_word_flags=()
flags_with_completion=()
flags_completion=()
`)
if err != nil {
return err
}
var visitErr error
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 {
writeShortFlag(flag, out)
if err := writeShortFlag(flag, w); err != nil {
visitErr = err
return
}
}
})
if visitErr != nil {
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
}
fmt.Fprintf(out, "\n")
_, err = fmt.Fprintf(w, "\n")
return err
}
func writeRequiredFlag(cmd *Command, out *bytes.Buffer) {
fmt.Fprintf(out, " must_have_one_flag=()\n")
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()
var visitErr error
flags.VisitAll(func(flag *pflag.Flag) {
for key, _ := range flag.Annotations {
for key := range flag.Annotations {
switch key {
case BashCompOneRequiredFlag:
format := " must_have_one_flag+=(\"--%s"
@ -274,78 +401,126 @@ func writeRequiredFlag(cmd *Command, out *bytes.Buffer) {
format += "="
}
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 {
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) {
fmt.Fprintf(out, " must_have_one_noun=()\n")
func writeRequiredNoun(cmd *Command, w io.Writer) error {
if _, err := fmt.Fprintf(w, " must_have_one_noun=()\n"); err != nil {
return err
}
sort.Sort(sort.StringSlice(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() {
if len(c.Deprecated) > 0 {
if !c.IsAvailableCommand() || c == cmd.helpCommand {
continue
}
gen(c, out)
if err := gen(c, w); err != nil {
return err
}
}
commandName := cmd.CommandPath()
commandName = strings.Replace(commandName, " ", "_", -1)
fmt.Fprintf(out, "_%s()\n{\n", commandName)
fmt.Fprintf(out, " last_command=%q\n", commandName)
writeCommands(cmd, out)
writeFlags(cmd, out)
writeRequiredFlag(cmd, out)
writeRequiredNoun(cmd, out)
fmt.Fprintf(out, "}\n\n")
commandName = strings.Replace(commandName, ":", "__", -1)
if _, err := fmt.Fprintf(w, "_%s()\n{\n", commandName); err != nil {
return err
}
if _, err := fmt.Fprintf(w, " last_command=%q\n", commandName); err != nil {
return err
}
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) {
preamble(out)
if len(cmd.BashCompletionFunction) > 0 {
fmt.Fprintf(out, "%s\n", cmd.BashCompletionFunction)
func (cmd *Command) GenBashCompletion(w io.Writer) error {
if err := preamble(w, cmd.Name()); err != nil {
return err
}
gen(cmd, out)
postscript(out, cmd.Name())
if len(cmd.BashCompletionFunction) > 0 {
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 {
out := new(bytes.Buffer)
cmd.GenBashCompletion(out)
outFile, err := os.Create(filename)
if err != nil {
return err
}
defer outFile.Close()
_, err = outFile.Write(out.Bytes())
if err != nil {
return err
}
return nil
return cmd.GenBashCompletion(outFile)
}
func (cmd *Command) MarkFlagRequired(name string) {
flag := cmd.Flags().Lookup(name)
if flag == nil {
return
}
if flag.Annotations == nil {
flag.Annotations = make(map[string][]string)
}
annotation := make([]string, 1)
annotation[0] = "true"
flag.Annotations[BashCompOneRequiredFlag] = annotation
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag, if it exists.
func (cmd *Command) MarkFlagRequired(name string) error {
return MarkFlagRequired(cmd.Flags(), name)
}
// 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)
}
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag in the flag set, if it exists.
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)
}

View File

@ -118,20 +118,23 @@ and you'll get something like
-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.
```go
annotations := make([]string, 3)
annotations[0] = "json"
annotations[1] = "yaml"
annotations[2] = "yml"
annotations := []string{"json", "yaml", "yml"}
annotation := make(map[string][]string)
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)
```

View File

@ -34,7 +34,7 @@ COMPREPLY=( "hello" )
func TestBashCompletions(t *testing.T) {
c := initializeWithRootCmd()
cmdEcho.AddCommand(cmdTimes)
c.AddCommand(cmdEcho, cmdPrint, cmdDeprecated)
c.AddCommand(cmdEcho, cmdPrint, cmdDeprecated, cmdColon)
// custom completion function
c.BashCompletionFunction = bash_completion_func
@ -42,23 +42,30 @@ func TestBashCompletions(t *testing.T) {
// required flag
c.MarkFlagRequired("introot")
// valid nounds
// valid nouns
validArgs := []string{"pods", "nodes", "services", "replicationControllers"}
c.ValidArgs = validArgs
// filename extentions
annotations := make([]string, 3)
annotations[0] = "json"
annotations[1] = "yaml"
annotations[2] = "yml"
annotation := make(map[string][]string)
annotation[BashCompFilenameExt] = annotations
// filename
var flagval string
c.Flags().StringVar(&flagval, "filename", "", "Enter a filename")
flag := c.Flags().Lookup("filename")
flag.Annotations = annotation
c.MarkFlagFilename("filename", "json", "yaml", "yml")
// 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)
c.GenBashCompletion(out)
@ -68,15 +75,21 @@ func TestBashCompletions(t *testing.T) {
check(t, str, "_cobra-test_echo")
check(t, str, "_cobra-test_echo_times")
check(t, str, "_cobra-test_print")
check(t, str, "_cobra-test_cmd__colon")
// check for required flags
check(t, str, `must_have_one_flag+=("--introot=")`)
check(t, str, `must_have_one_flag+=("--persistent-filename=")`)
// check for custom completion function
check(t, str, `COMPREPLY=( "hello" )`)
// check for required nouns
check(t, str, `must_have_one_noun+=("pods")`)
// check for filename extention flags
check(t, str, `flags_completion+=("_filedir '@(json|yaml|yml)'")`)
// check for filename extension flags
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())
}

View File

@ -23,21 +23,36 @@ import (
"strconv"
"strings"
"text/template"
"unicode"
)
var templateFuncs template.FuncMap = template.FuncMap{
"trim": strings.TrimSpace,
"trimRightSpace": trimRightSpace,
"rpad": rpad,
"gt": Gt,
"eq": Eq,
}
var initializers []func()
// automatic prefix matching can be a dangerous thing to automatically enable in CLI tools.
// Set this to true to enable it
var EnablePrefixMatching bool = false
// enables an information splash screen on Windows if the CLI is started from explorer.exe.
var EnableWindowsMouseTrap bool = true
//AddTemplateFunc adds a template function that's available to Usage and Help
//template generation.
func AddTemplateFunc(name string, tmplFunc interface{}) {
templateFuncs[name] = tmplFunc
}
var MousetrapHelpText string = `This is a command line tool
You need to open cmd.exe and run it from there.
`
//AddTemplateFuncs adds multiple template functions availalble to Usage and
//Help template generation.
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().
func OnInitialize(y ...func()) {
@ -92,6 +107,10 @@ func Eq(a interface{}, b interface{}) bool {
return false
}
func trimRightSpace(s string) string {
return strings.TrimRightFunc(s, unicode.IsSpace)
}
//rpad adds padding to the right of a string
func rpad(s string, padding int) string {
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.
func tmpl(w io.Writer, text string, data interface{}) error {
t := template.New("top")
t.Funcs(template.FuncMap{
"trim": strings.TrimSpace,
"rpad": rpad,
"gt": Gt,
"eq": Eq,
})
t.Funcs(templateFuncs)
template.Must(t.Parse(text))
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)]
}

View 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"))
}

View 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")
}

View 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"))
}

View 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]`")
}

File diff suppressed because it is too large Load Diff

View 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())
}
}

View 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()
}

View File

@ -4,18 +4,22 @@ import (
"bytes"
"fmt"
"os"
"reflect"
"runtime"
"strings"
"testing"
"text/template"
"github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/pflag"
)
var _ = fmt.Println
var _ = os.Stderr
var tp, te, tt, t1 []string
var tp, te, tt, t1, tr []string
var rootPersPre, echoPre, echoPersPre, timesPersPre []string
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 globalFlag1 bool
var flagEcho, rootcalled bool
@ -24,6 +28,16 @@ var versionUsed int
const strtwoParentHelp = "help message for parent 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{
Use: "print [string to print]",
Short: "Print anything to the screen",
@ -68,9 +82,10 @@ var cmdDeprecated = &Command{
}
var cmdTimes = &Command{
Use: "times [# times] [string to echo]",
Short: "Echo anything to the screen more times",
Long: `a slightly useless command for testing.`,
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 *Command, args []string) {
timesPersPre = args
},
@ -81,7 +96,7 @@ var cmdTimes = &Command{
var cmdRootNoRun = &Command{
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",
PersistentPreRun: func(cmd *Command, args []string) {
rootPersPre = args
@ -96,9 +111,10 @@ var cmdRootSameName = &Command{
var cmdRootWithRun = &Command{
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",
Run: func(cmd *Command, args []string) {
tr = args
rootcalled = true
},
}
@ -127,6 +143,12 @@ var cmdVersion2 = &Command{
},
}
var cmdColon = &Command{
Use: "cmd:colon",
Run: func(cmd *Command, args []string) {
},
}
func flagInit() {
cmdEcho.ResetFlags()
cmdPrint.ResetFlags()
@ -181,7 +203,7 @@ func initializeWithSameName() *Command {
func initializeWithRootCmd() *Command {
cmdRootWithRun.ResetCommands()
tt, tp, te, rootcalled = nil, nil, nil, false
tt, tp, te, tr, rootcalled = nil, nil, nil, nil, false
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")
@ -201,6 +223,13 @@ func fullSetupTest(input string) resulter {
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 {
c := initialize()
@ -225,6 +254,18 @@ func simpleTester(c *Command, input string) resulter {
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 {
buf := new(bytes.Buffer)
// Testing flag with invalid input
@ -250,16 +291,24 @@ func logErr(t *testing.T, found, expected 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) {
if !strings.Contains(x.Output, check) {
logErr(t, x.Output, check)
checkStringContains(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) {
if strings.Contains(x.Output, check) {
logErr(t, x.Output, check)
}
checkStringOmits(t, x.Output, check)
}
func checkOutputContains(t *testing.T, c *Command, check string) {
@ -374,9 +423,30 @@ func TestChildSameName(t *testing.T) {
}
}
func TestFlagLong(t *testing.T) {
noRRSetupTest("echo --intone=13 something here")
func TestGrandChildSameName(t *testing.T) {
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" {
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) {
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" {
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")
}
if !strings.Contains(r.Output, "unknown shorthand") {
t.Errorf("Wrong error message displayed, \n %s", r.Output)
if !strings.Contains(r.Error.Error(), "unknown shorthand") {
t.Errorf("Wrong error message displayed, \n %s", r.Error)
}
if flagi2 != 99 {
@ -458,9 +531,8 @@ func TestChildCommandFlags(t *testing.T) {
if r.Error == nil {
t.Errorf("invalid flag should generate error")
}
if !strings.Contains(r.Output, "unknown shorthand flag") {
t.Errorf("Wrong error message displayed, \n %s", r.Output)
if !strings.Contains(r.Error.Error(), "unknown shorthand flag") {
t.Errorf("Wrong error message displayed, \n %s", r.Error)
}
// Testing with persistent flag overwritten by child
@ -480,9 +552,8 @@ func TestChildCommandFlags(t *testing.T) {
if r.Error == nil {
t.Errorf("invalid input should generate error")
}
if !strings.Contains(r.Output, "invalid argument \"10E\" for -i10E") {
t.Errorf("Wrong error message displayed, \n %s", r.Output)
if !strings.Contains(r.Error.Error(), "invalid argument \"10E\" for i10E") {
t.Errorf("Wrong error message displayed, \n %s", r.Error)
}
}
@ -494,21 +565,56 @@ func TestTrailingCommandFlags(t *testing.T) {
}
}
func TestInvalidSubCommandFlags(t *testing.T) {
func TestInvalidSubcommandFlags(t *testing.T) {
cmd := initializeWithRootCmd()
cmd.AddCommand(cmdTimes)
result := simpleTester(cmd, "times --inttwo=2 --badflag=bar")
checkResultContains(t, result, "unknown flag: --badflag")
if strings.Contains(result.Output, "unknown flag: --inttwo") {
// given that we are not checking here result.Error we check for
// stock usage message
checkResultContains(t, result, "cobra-test times [# times]")
if strings.Contains(result.Error.Error(), "unknown flag: --inttwo") {
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()
first := &Command{
@ -537,7 +643,7 @@ func TestSubCommandArgEvaluation(t *testing.T) {
func TestPersistentFlags(t *testing.T) {
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" {
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)
}
// 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")
if strings.Join(tt, " ") != "test here" {
@ -591,10 +697,38 @@ func TestNonRunChildHelp(t *testing.T) {
}
func TestRunnableRootCommand(t *testing.T) {
fullSetupTest("")
x := fullSetupTest("")
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.SetArgs(empty_arg)
c.Execute()
err := c.Execute()
if err != nil {
t.Errorf("Execute() failed with %v", err)
}
if rootcalled != true {
t.Errorf("Root Function was not called")
@ -746,11 +883,69 @@ func TestRootNoCommandHelp(t *testing.T) {
func TestRootUnknownCommand(t *testing.T) {
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 {
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) {
@ -777,8 +972,8 @@ func TestFlagsBeforeCommand(t *testing.T) {
// With parsing error properly reported
x = fullSetupTest("-i10E echo")
if !strings.Contains(x.Output, "invalid argument \"10E\" for -i10E") {
t.Errorf("Wrong error message displayed, \n %s", x.Output)
if !strings.Contains(x.Error.Error(), "invalid argument \"10E\" for i10E") {
t.Errorf("Wrong error message displayed, \n %s", x.Error)
}
//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) {
versionUsed = 0
c := initializeWithRootCmd()
@ -869,3 +1089,81 @@ func TestPreRun(t *testing.T) {
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")
}
}

View File

@ -18,13 +18,12 @@ package cobra
import (
"bytes"
"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"
"os"
"runtime"
"path/filepath"
"strings"
"time"
flag "github.com/khlieng/dispatch/Godeps/_workspace/src/github.com/spf13/pflag"
)
// Command is just that, a command for your application.
@ -38,6 +37,8 @@ type Command struct {
Use string
// An array of aliases that can be used instead of the first word in Use.
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.
Short string
// The long message shown in the 'help <this-command>' output.
@ -50,12 +51,18 @@ type Command struct {
BashCompletionFunction string
// Is this command deprecated and should print this string when used?
Deprecated string
// Is this command hidden and should NOT show up in the list of available commands?
Hidden bool
// Full set of flags
flags *flag.FlagSet
// Set of flags childrens of this command will inherit
pflags *flag.FlagSet
// Flags that are declared specifically by this command (not inherited).
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:
// * PersistentPreRun()
// * PreRun()
@ -65,14 +72,26 @@ type Command struct {
// All functions get the same args, the arguments after the command name
// PersistentPreRun: children of this command will inherit and execute
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 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 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 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 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 []*Command
// Parent Command for this command
@ -83,7 +102,6 @@ type Command struct {
commandsMaxNameLen int
flagErrorBuf *bytes.Buffer
cmdErrorBuf *bytes.Buffer
args []string // actual args parsed from flags
output *io.Writer // nil means stderr; use Out() method instead
@ -92,7 +110,13 @@ type Command struct {
helpTemplate string // Can be defined by Application
helpFunc func(*Command, []string) // Help can be defined by application
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
@ -151,6 +175,18 @@ func (c *Command) SetHelpTemplate(s string) {
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) {
if c.usageFunc != nil {
return c.usageFunc
@ -161,36 +197,28 @@ func (c *Command) UsageFunc() (f func(*Command) error) {
} else {
return func(c *Command) error {
err := tmpl(c.Out(), c.UsageTemplate(), c)
if err != nil {
fmt.Print(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) {
if c.helpFunc != nil {
return c.helpFunc
cmd := c
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
}
cmd, _, e := c.Root().Find(args)
if cmd == nil || e != nil {
c.Printf("Unknown help topic %#q.", args)
c.Root().Usage()
} else {
err := cmd.Help()
if err != nil {
c.Println(err)
}
}
return func(*Command, []string) {
err := c.Help()
if err != nil {
c.Println(err)
}
}
}
@ -234,8 +262,7 @@ func (c *Command) UsageTemplate() string {
if c.HasParent() {
return c.parent.UsageTemplate()
} else {
return `{{ $cmd := . }}
Usage: {{if .Runnable}}
return `Usage:{{if .Runnable}}
{{.UseLine}}{{if .HasFlags}} [flags]{{end}}{{end}}{{if .HasSubCommands}}
{{ .CommandPath}} [command]{{end}}{{if gt .Aliases 0}}
@ -244,22 +271,22 @@ Aliases:
{{end}}{{if .HasExample}}
Examples:
{{ .Example }}
{{end}}{{ if .HasRunnableSubCommands}}
{{ .Example }}{{end}}{{ if .HasAvailableSubCommands}}
Available Commands: {{range .Commands}}{{if and (.Runnable) (not .Deprecated)}}
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}
{{end}}
{{ if .HasLocalFlags}}Flags:
{{.LocalFlags.FlagUsages}}{{end}}
{{ if .HasInheritedFlags}}Global Flags:
{{.InheritedFlags.FlagUsages}}{{end}}{{if or (.HasHelpSubCommands) (.HasRunnableSiblings)}}
Additional help topics:
{{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}}
{{end}}{{ if .HasSubCommands }}
Use "{{.Root.Name}} help [command]" for more information about a command.
{{end}}`
Available Commands:{{range .Commands}}{{if .IsAvailableCommand}}
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasLocalFlags}}
Flags:
{{.LocalFlags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasInheritedFlags}}
Global Flags:
{{.InheritedFlags.FlagUsages | trimRightSpace}}{{end}}{{if .HasHelpSubCommands}}
Additional help topics:{{range .Commands}}{{if .IsHelpCommand}}
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasSubCommands }}
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
`
}
}
@ -271,9 +298,9 @@ func (c *Command) HelpTemplate() string {
if c.HasParent() {
return c.parent.HelpTemplate()
} else {
return `{{with or .Long .Short }}{{. | trim}}{{end}}
{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}
`
return `{{with or .Long .Short }}{{. | trim}}
{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
}
}
@ -360,71 +387,128 @@ func argsMinusFirstX(args []string, x string) []string {
// find the target command given the args and command tree
// 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 {
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)
innerfind = func(c *Command, args []string) (*Command, []string) {
if len(args) > 0 && c.HasSubCommands() {
argsWOflags := stripFlags(args, c)
if len(argsWOflags) > 0 {
matches := make([]*Command, 0)
for _, cmd := range c.commands {
if cmd.Name() == argsWOflags[0] || cmd.HasAlias(argsWOflags[0]) { // exact name or alias match
return innerfind(cmd, argsMinusFirstX(args, argsWOflags[0]))
} else if EnablePrefixMatching {
if strings.HasPrefix(cmd.Name(), argsWOflags[0]) { // prefix match
matches = append(matches, cmd)
}
for _, x := range cmd.Aliases {
if strings.HasPrefix(x, argsWOflags[0]) {
matches = append(matches, cmd)
}
}
}
innerfind = func(c *Command, innerArgs []string) (*Command, []string) {
argsWOflags := stripFlags(innerArgs, c)
if len(argsWOflags) == 0 {
return c, innerArgs
}
nextSubCmd := argsWOflags[0]
matches := make([]*Command, 0)
for _, cmd := range c.commands {
if cmd.Name() == nextSubCmd || cmd.HasAlias(nextSubCmd) { // exact name or alias match
return innerfind(cmd, argsMinusFirstX(innerArgs, nextSubCmd))
}
if EnablePrefixMatching {
if strings.HasPrefix(cmd.Name(), nextSubCmd) { // prefix match
matches = append(matches, cmd)
}
// only accept a single prefix match - multiple matches would be ambiguous
if len(matches) == 1 {
return innerfind(matches[0], argsMinusFirstX(args, argsWOflags[0]))
for _, x := range cmd.Aliases {
if strings.HasPrefix(x, nextSubCmd) {
matches = append(matches, cmd)
}
}
}
}
return c, args
// only accept a single prefix match - multiple matches would be ambiguous
if len(matches) == 1 {
return innerfind(matches[0], argsMinusFirstX(innerArgs, argsWOflags[0]))
}
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
if commandFound.Name() == c.Name() && len(stripFlags(arrs, c)) > 0 && commandFound.Name() != arrs[0] {
return nil, a, fmt.Errorf("unknown command %q", a[0])
// no subcommand, always take args
if !commandFound.HasSubCommands() {
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
}
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 {
var findRoot func(*Command) *Command
findRoot = func(x *Command) *Command {
if x.HasParent() {
return findRoot(x.parent)
} else {
return x
}
return x
}
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) {
if c == nil {
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)
}
// initialize help flag as the last point possible to allow for user
// overriding
c.initHelpFlag()
err = c.ParseFlags(a)
if err == flag.ErrHelp {
c.Help()
return 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
}
// If help is called, regardless of other flags, we print that.
// Print help also if c.Run is nil.
if c.helpFlagVal || !c.Runnable() {
c.Help()
return nil
// If help is called, regardless of other flags, return we want help
// Also say we need help if the command isn't runnable.
helpVal, err := c.Flags().GetBool("help")
if err != 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()
argWoFlags := c.Flags().Args()
if c.PersistentPreRun != nil {
c.PersistentPreRun(c, argWoFlags)
for p := c; p != nil; p = p.Parent() {
if p.PersistentPreRunE != nil {
if err := p.PersistentPreRunE(c, argWoFlags); err != nil {
return err
}
break
} else if p.PersistentPreRun != nil {
p.PersistentPreRun(c, argWoFlags)
break
}
}
if c.PreRun != nil {
if c.PreRunE != nil {
if err := c.PreRunE(c, argWoFlags); err != nil {
return err
}
} else if c.PreRun != nil {
c.PreRun(c, argWoFlags)
}
c.Run(c, argWoFlags)
if c.PostRun != nil {
if c.RunE != nil {
if err := c.RunE(c, argWoFlags); err != nil {
return err
}
} else {
c.Run(c, argWoFlags)
}
if c.PostRunE != nil {
if err := c.PostRunE(c, argWoFlags); err != nil {
return err
}
} else if c.PostRun != nil {
c.PostRun(c, argWoFlags)
}
if c.PersistentPostRun != nil {
c.PersistentPostRun(c, argWoFlags)
for p := c; p != nil; p = p.Parent() {
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
@ -503,52 +611,80 @@ func (c *Command) errorMsgFromParse() string {
// Call execute to use the args (os.Args[1:] by default)
// and run through the command tree finding appropriate matches
// 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
if c.HasParent() {
return c.Root().Execute()
return c.Root().ExecuteC()
}
if EnableWindowsMouseTrap && runtime.GOOS == "windows" {
if mousetrap.StartedByExplorer() {
c.Print(MousetrapHelpText)
time.Sleep(5 * time.Second)
os.Exit(1)
}
// windows hook
if preExecHookFn != nil {
preExecHookFn(c)
}
// initialize help as the last point possible to allow for user
// overriding
c.initHelp()
c.initHelpCmd()
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:]
} else {
args = c.args
}
cmd, flags, err := c.Find(args)
if err == nil {
err = cmd.execute(flags)
}
if err != nil {
if err == flag.ErrHelp {
c.Help()
} else {
c.Println("Error:", err.Error())
c.Printf("Run '%v help' for usage.\n", c.Root().Name())
// If found parse to a subcommand and then failed, talk about the subcommand
if cmd != nil {
c = cmd
}
if !c.SilenceErrors {
c.Println("Error:", err.Error())
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
}
return
// 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) initHelp() {
func (c *Command) initHelpFlag() {
if c.Flags().Lookup("help") == nil {
c.Flags().BoolP("help", "h", false, "help for "+c.Name())
}
}
func (c *Command) initHelpCmd() {
if c.helpCommand == nil {
if !c.HasSubCommands() {
return
@ -559,9 +695,19 @@ func (c *Command) initHelp() {
Short: "Help about any command",
Long: `Help provides help for any command in the application.
Simply type ` + c.Name() + ` help [path to command] for full details.`,
Run: c.HelpFunc(),
PersistentPreRun: 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)
@ -571,8 +717,6 @@ func (c *Command) initHelp() {
func (c *Command) ResetCommands() {
c.commands = nil
c.helpCommand = nil
c.cmdErrorBuf = new(bytes.Buffer)
c.cmdErrorBuf.Reset()
}
//Commands returns a slice of child commands.
@ -600,15 +744,11 @@ func (c *Command) AddCommand(cmds ...*Command) {
if nameLen > c.commandsMaxNameLen {
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)
// 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
func (c *Command) Runnable() bool {
return c.Run != nil
return c.Run != nil || c.RunE != nil
}
// Determine if the command has children commands
@ -796,34 +936,75 @@ func (c *Command) HasSubCommands() bool {
return len(c.commands) > 0
}
func (c *Command) HasRunnableSiblings() bool {
if !c.HasParent() {
// IsAvailableCommand determines if a command is available as a non-help command
// (this includes all non deprecated/hidden commands)
func (c *Command) IsAvailableCommand() bool {
if len(c.Deprecated) != 0 || c.Hidden {
return false
}
for _, sub := range c.parent.commands {
if sub.Runnable() {
return true
}
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 {
// return true on the first found available 'help' sub command
for _, sub := range c.commands {
if !sub.Runnable() {
if sub.IsHelpCommand() {
return true
}
}
// the command either has no sub commands, or no available 'help' sub commands
return false
}
// Determine if the command has runnable children commands
func (c *Command) HasRunnableSubCommands() bool {
// HasAvailableSubCommands determines if a command has available sub commands that
// 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 {
if sub.Runnable() {
if sub.IsAvailableCommand() {
return true
}
}
// the command either has no sub comamnds, or no available (non deprecated/help/hidden)
// sub commands
return false
}
@ -832,6 +1013,11 @@ func (c *Command) HasParent() bool {
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)
func (c *Command) Flags() *flag.FlagSet {
if c.flags == nil {
@ -840,7 +1026,6 @@ func (c *Command) Flags() *flag.FlagSet {
c.flagErrorBuf = new(bytes.Buffer)
}
c.flags.SetOutput(c.flagErrorBuf)
c.PersistentFlags().BoolVarP(&c.helpFlagVal, "help", "h", false, "help for "+c.Name())
}
return c.flags
}
@ -853,6 +1038,13 @@ func (c *Command) LocalFlags() *flag.FlagSet {
c.lflags.VisitAll(func(f *flag.Flag) {
local.AddFlag(f)
})
if !c.HasParent() {
flag.CommandLine.VisitAll(func(f *flag.Flag) {
if local.Lookup(f.Name) == nil {
local.AddFlag(f)
}
})
}
return local
}

View File

@ -0,0 +1,5 @@
// +build !windows
package cobra
var preExecHookFn func(*Command) = nil

View File

@ -5,6 +5,30 @@ import (
"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) {
tests := []struct {
input []string

View 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)
}
}

View 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())
}

View 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()
}

View 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`

View 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)
}

View 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())
}

View 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
}

View 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) + "/"
}
```

View File

@ -1,4 +1,4 @@
package cobra
package doc
import (
"bytes"
@ -21,7 +21,9 @@ func TestGenMdDoc(t *testing.T) {
out := new(bytes.Buffer)
// 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()
// Our description
@ -65,3 +67,22 @@ func TestGenMdDoc(t *testing.T) {
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)
}

View 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() }

View File

@ -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)
}
}

View File

@ -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.

View 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 ./...

View File

@ -1,3 +1,5 @@
[![Build Status](https://travis-ci.org/spf13/pflag.svg?branch=master)](https://travis-ci.org/spf13/pflag)
## Description
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:
go get github.com/ogier/pflag
go get github.com/spf13/pflag
Run tests by running:
go test github.com/ogier/pflag
go test github.com/spf13/pflag
## Usage
@ -31,7 +33,7 @@ pflag under the name "flag" then all code should continue to function
with no changes.
``` 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
@ -82,6 +84,16 @@ fmt.Println("ip has value ", *ip)
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
slice flag.Args() or individually as flag.Arg(i).
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
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
```
--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
```
Unlike the flag package, a single dash before an option means something
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=true
-abc
but
-b true is INVALID
// non-boolean flags
// non-boolean and flags without a 'no option default value'
-n 1234
-Ifile
-n=1234
-n1234
// mixed
-abcs "hello"
-abcn1234
-absd="hello"
-abcs1234
```
Flag parsing stops after the terminator "--". Unlike the flag package,
@ -149,7 +188,7 @@ It is possible to set a custom flag name 'normalization function.' It allows fla
**Example #1**: You want -, _, and . in flags to compare the same. aka --my-flag == --my_flag == --my.flag
```go
``` go
func wordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
from := []string{"-", "_"}
to := "."
@ -164,7 +203,7 @@ myFlagSet.SetNormalizeFunc(wordSepNormalizeFunc)
**Example #2**: You want to alias two flags. aka --old-flag-name == --new-flag-name
```go
``` go
func aliasNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
switch name {
case "old-flag-name":
@ -177,6 +216,34 @@ func aliasNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
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
You can see the full reference documentation of the pflag package

View File

@ -34,37 +34,50 @@ func (b *boolValue) String() string { return fmt.Sprintf("%v", *b) }
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.
// 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) {
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) {
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.
// 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) {
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) {
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.
// 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 {
p := new(bool)
f.BoolVarP(p, name, "", value, usage)
return p
return f.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 (f *FlagSet) BoolP(name, shorthand string, value bool, usage string) *bool {
p := new(bool)
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.
// 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 {
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 {
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