forked from TrueCloudLab/distribution
Merge branch 'master' of https://github.com/xiekeyang/distribution
This commit is contained in:
commit
0da3dcf038
54 changed files with 4006 additions and 225 deletions
3
.mailmap
3
.mailmap
|
@ -1,4 +1,5 @@
|
||||||
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@users.noreply.github.com>
|
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@users.noreply.github.com>
|
||||||
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@gmail.com>
|
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@gmail.com>
|
||||||
Olivier Gambier <olivier@docker.com> Olivier Gambier <dmp42@users.noreply.github.com>
|
Olivier Gambier <olivier@docker.com> Olivier Gambier <dmp42@users.noreply.github.com>
|
||||||
Brian Bland <brian.bland@docker.com> Brian Bland <r4nd0m1n4t0r@gmail.com>
|
Brian Bland <brian.bland@docker.com> Brian Bland <r4nd0m1n4t0r@gmail.com>
|
||||||
|
Josh Hawn <josh.hawn@docker.com> Josh Hawn <jlhawn@berkeley.edu>
|
||||||
|
|
17
Godeps/Godeps.json
generated
17
Godeps/Godeps.json
generated
|
@ -45,19 +45,24 @@
|
||||||
"ImportPath": "github.com/bugsnag/panicwrap",
|
"ImportPath": "github.com/bugsnag/panicwrap",
|
||||||
"Rev": "e5f9854865b9778a45169fc249e99e338d4d6f27"
|
"Rev": "e5f9854865b9778a45169fc249e99e338d4d6f27"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/codegangsta/cli",
|
||||||
|
"Comment": "1.2.0-66-g6086d79",
|
||||||
|
"Rev": "6086d7927ec35315964d9fea46df6c04e6d697c1"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/docker/docker/pkg/tarsum",
|
"ImportPath": "github.com/docker/docker/pkg/tarsum",
|
||||||
"Comment": "v1.4.1-330-g3fbf723",
|
"Comment": "v1.4.1-863-g165ea5c",
|
||||||
"Rev": "3fbf723e81fa2696daa95847ccdcacddba6484da"
|
"Rev": "165ea5c158cff3fc40d476ffe233a5ccc03e7d61"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar",
|
"ImportPath": "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar",
|
||||||
"Comment": "v1.4.1-330-g3fbf723",
|
"Comment": "v1.4.1-863-g165ea5c",
|
||||||
"Rev": "3fbf723e81fa2696daa95847ccdcacddba6484da"
|
"Rev": "165ea5c158cff3fc40d476ffe233a5ccc03e7d61"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/docker/libtrust",
|
"ImportPath": "github.com/docker/libtrust",
|
||||||
"Rev": "c54fbb67c1f1e68d7d6f8d2ad7c9360404616a41"
|
"Rev": "fa567046d9b14f6aa788882a950d69651d230b21"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/gorilla/context",
|
"ImportPath": "github.com/gorilla/context",
|
||||||
|
@ -94,7 +99,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "gopkg.in/yaml.v2",
|
"ImportPath": "gopkg.in/yaml.v2",
|
||||||
"Rev": "71e7ede9d48a2e096f6d5d0516c763513a471bd1"
|
"Rev": "bef53efd0c76e49e6de55ead051f886bea7e9420"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
30
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/json.go
generated
vendored
Normal file
30
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/json.go
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
func (u UUID) MarshalJSON() ([]byte, error) {
|
||||||
|
if len(u) == 0 {
|
||||||
|
return []byte(`""`), nil
|
||||||
|
}
|
||||||
|
return []byte(`"` + u.String() + `"`), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UUID) UnmarshalJSON(data []byte) error {
|
||||||
|
if len(data) == 0 || string(data) == `""` {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(data) < 2 || data[0] != '"' || data[len(data)-1] != '"' {
|
||||||
|
return errors.New("invalid UUID format")
|
||||||
|
}
|
||||||
|
data = data[1 : len(data)-1]
|
||||||
|
uu := Parse(string(data))
|
||||||
|
if uu == nil {
|
||||||
|
return errors.New("invalid UUID format")
|
||||||
|
}
|
||||||
|
*u = uu
|
||||||
|
return nil
|
||||||
|
}
|
32
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/json_test.go
generated
vendored
Normal file
32
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/json_test.go
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testUUID = Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
|
||||||
|
|
||||||
|
func TestJSON(t *testing.T) {
|
||||||
|
type S struct {
|
||||||
|
ID1 UUID
|
||||||
|
ID2 UUID
|
||||||
|
}
|
||||||
|
s1 := S{ID1: testUUID}
|
||||||
|
data, err := json.Marshal(&s1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var s2 S
|
||||||
|
if err := json.Unmarshal(data, &s2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(&s1, &s2) {
|
||||||
|
t.Errorf("got %#v, want %#v", s2, s1)
|
||||||
|
}
|
||||||
|
}
|
66
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/seq_test.go
generated
vendored
Normal file
66
Godeps/_workspace/src/code.google.com/p/go-uuid/uuid/seq_test.go
generated
vendored
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
// Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This test is only run when --regressions is passed on the go test line.
|
||||||
|
var regressions = flag.Bool("regressions", false, "run uuid regression tests")
|
||||||
|
|
||||||
|
// TestClockSeqRace tests for a particular race condition of returning two
|
||||||
|
// identical Version1 UUIDs. The duration of 1 minute was chosen as the race
|
||||||
|
// condition, before being fixed, nearly always occured in under 30 seconds.
|
||||||
|
func TestClockSeqRace(t *testing.T) {
|
||||||
|
if !*regressions {
|
||||||
|
t.Skip("skipping regression tests")
|
||||||
|
}
|
||||||
|
duration := time.Minute
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
defer close(done)
|
||||||
|
|
||||||
|
ch := make(chan UUID, 10000)
|
||||||
|
ncpu := runtime.NumCPU()
|
||||||
|
switch ncpu {
|
||||||
|
case 0, 1:
|
||||||
|
// We can't run the test effectively.
|
||||||
|
t.Skip("skipping race test, only one CPU detected")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
runtime.GOMAXPROCS(ncpu)
|
||||||
|
}
|
||||||
|
for i := 0; i < ncpu; i++ {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
case ch <- NewUUID():
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
uuids := make(map[string]bool)
|
||||||
|
cnt := 0
|
||||||
|
start := time.Now()
|
||||||
|
for u := range ch {
|
||||||
|
s := u.String()
|
||||||
|
if uuids[s] {
|
||||||
|
t.Errorf("duplicate uuid after %d in %v: %s", cnt, time.Since(start), s)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uuids[s] = true
|
||||||
|
if time.Since(start) > duration {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cnt++
|
||||||
|
}
|
||||||
|
}
|
8
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_openbsd.go
generated
vendored
Normal file
8
Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_openbsd.go
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const ioctlReadTermios = syscall.TIOCGETA
|
||||||
|
|
||||||
|
type Termios syscall.Termios
|
33
Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter_test.go
generated
vendored
Normal file
33
Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter_test.go
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestQuoting(t *testing.T) {
|
||||||
|
tf := &TextFormatter{DisableColors: true}
|
||||||
|
|
||||||
|
checkQuoting := func(q bool, value interface{}) {
|
||||||
|
b, _ := tf.Format(WithField("test", value))
|
||||||
|
idx := bytes.Index(b, ([]byte)("test="))
|
||||||
|
cont := bytes.Contains(b[idx+5:], []byte{'"'})
|
||||||
|
if cont != q {
|
||||||
|
if q {
|
||||||
|
t.Errorf("quoting expected for: %#v", value)
|
||||||
|
} else {
|
||||||
|
t.Errorf("quoting not expected for: %#v", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkQuoting(false, "abcd")
|
||||||
|
checkQuoting(false, "v1.0")
|
||||||
|
checkQuoting(true, "/foobar")
|
||||||
|
checkQuoting(true, "x y")
|
||||||
|
checkQuoting(true, "x,y")
|
||||||
|
checkQuoting(false, errors.New("invalid"))
|
||||||
|
checkQuoting(true, errors.New("invalid argument"))
|
||||||
|
}
|
31
Godeps/_workspace/src/github.com/Sirupsen/logrus/writer.go
generated
vendored
Normal file
31
Godeps/_workspace/src/github.com/Sirupsen/logrus/writer.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (logger *Logger) Writer() (*io.PipeWriter) {
|
||||||
|
reader, writer := io.Pipe()
|
||||||
|
|
||||||
|
go logger.writerScanner(reader)
|
||||||
|
runtime.SetFinalizer(writer, writerFinalizer)
|
||||||
|
|
||||||
|
return writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) writerScanner(reader *io.PipeReader) {
|
||||||
|
scanner := bufio.NewScanner(reader)
|
||||||
|
for scanner.Scan() {
|
||||||
|
logger.Print(scanner.Text())
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
logger.Errorf("Error while reading from Writer: %s", err)
|
||||||
|
}
|
||||||
|
reader.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func writerFinalizer(writer *io.PipeWriter) {
|
||||||
|
writer.Close()
|
||||||
|
}
|
6
Godeps/_workspace/src/github.com/codegangsta/cli/.travis.yml
generated
vendored
Normal file
6
Godeps/_workspace/src/github.com/codegangsta/cli/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
language: go
|
||||||
|
go: 1.1
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go vet ./...
|
||||||
|
- go test -v ./...
|
21
Godeps/_workspace/src/github.com/codegangsta/cli/LICENSE
generated
vendored
Normal file
21
Godeps/_workspace/src/github.com/codegangsta/cli/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
Copyright (C) 2013 Jeremy Saenz
|
||||||
|
All Rights Reserved.
|
||||||
|
|
||||||
|
MIT LICENSE
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
298
Godeps/_workspace/src/github.com/codegangsta/cli/README.md
generated
vendored
Normal file
298
Godeps/_workspace/src/github.com/codegangsta/cli/README.md
generated
vendored
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
[![Build Status](https://travis-ci.org/codegangsta/cli.png?branch=master)](https://travis-ci.org/codegangsta/cli)
|
||||||
|
|
||||||
|
# cli.go
|
||||||
|
cli.go is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way.
|
||||||
|
|
||||||
|
You can view the API docs here:
|
||||||
|
http://godoc.org/github.com/codegangsta/cli
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app.
|
||||||
|
|
||||||
|
**This is where cli.go comes into play.** cli.go makes command line programming fun, organized, and expressive!
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
Make sure you have a working Go environment (go 1.1 is *required*). [See the install instructions](http://golang.org/doc/install.html).
|
||||||
|
|
||||||
|
To install `cli.go`, simply run:
|
||||||
|
```
|
||||||
|
$ go get github.com/codegangsta/cli
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used:
|
||||||
|
```
|
||||||
|
export PATH=$PATH:$GOPATH/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
One of the philosophies behind cli.go is that an API should be playful and full of discovery. So a cli.go app can be as little as one line of code in `main()`.
|
||||||
|
|
||||||
|
``` go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cli.NewApp().Run(os.Args)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This app will run and show help text, but is not very useful. Let's give an action to execute and some help documentation:
|
||||||
|
|
||||||
|
``` go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "boom"
|
||||||
|
app.Usage = "make an explosive entrance"
|
||||||
|
app.Action = func(c *cli.Context) {
|
||||||
|
println("boom! I say!")
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run(os.Args)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Running this already gives you a ton of functionality, plus support for things like subcommands and flags, which are covered below.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
Being a programmer can be a lonely job. Thankfully by the power of automation that is not the case! Let's create a greeter app to fend off our demons of loneliness!
|
||||||
|
|
||||||
|
Start by creating a directory named `greet`, and within it, add a file, `greet.go` with the following code in it:
|
||||||
|
|
||||||
|
``` go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "greet"
|
||||||
|
app.Usage = "fight the loneliness!"
|
||||||
|
app.Action = func(c *cli.Context) {
|
||||||
|
println("Hello friend!")
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run(os.Args)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Install our command to the `$GOPATH/bin` directory:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go install
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally run our new command:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ greet
|
||||||
|
Hello friend!
|
||||||
|
```
|
||||||
|
|
||||||
|
cli.go also generates some bitchass help text:
|
||||||
|
```
|
||||||
|
$ greet help
|
||||||
|
NAME:
|
||||||
|
greet - fight the loneliness!
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
greet [global options] command [command options] [arguments...]
|
||||||
|
|
||||||
|
VERSION:
|
||||||
|
0.0.0
|
||||||
|
|
||||||
|
COMMANDS:
|
||||||
|
help, h Shows a list of commands or help for one command
|
||||||
|
|
||||||
|
GLOBAL OPTIONS
|
||||||
|
--version Shows version information
|
||||||
|
```
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
You can lookup arguments by calling the `Args` function on `cli.Context`.
|
||||||
|
|
||||||
|
``` go
|
||||||
|
...
|
||||||
|
app.Action = func(c *cli.Context) {
|
||||||
|
println("Hello", c.Args()[0])
|
||||||
|
}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flags
|
||||||
|
Setting and querying flags is simple.
|
||||||
|
``` go
|
||||||
|
...
|
||||||
|
app.Flags = []cli.Flag {
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "lang",
|
||||||
|
Value: "english",
|
||||||
|
Usage: "language for the greeting",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Action = func(c *cli.Context) {
|
||||||
|
name := "someone"
|
||||||
|
if len(c.Args()) > 0 {
|
||||||
|
name = c.Args()[0]
|
||||||
|
}
|
||||||
|
if c.String("lang") == "spanish" {
|
||||||
|
println("Hola", name)
|
||||||
|
} else {
|
||||||
|
println("Hello", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Alternate Names
|
||||||
|
|
||||||
|
You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g.
|
||||||
|
|
||||||
|
``` go
|
||||||
|
app.Flags = []cli.Flag {
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "lang, l",
|
||||||
|
Value: "english",
|
||||||
|
Usage: "language for the greeting",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That flag can then be set with `--lang spanish` or `-l spanish`. Note that giving two different forms of the same flag in the same command invocation is an error.
|
||||||
|
|
||||||
|
#### Values from the Environment
|
||||||
|
|
||||||
|
You can also have the default value set from the environment via `EnvVar`. e.g.
|
||||||
|
|
||||||
|
``` go
|
||||||
|
app.Flags = []cli.Flag {
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "lang, l",
|
||||||
|
Value: "english",
|
||||||
|
Usage: "language for the greeting",
|
||||||
|
EnvVar: "APP_LANG",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default.
|
||||||
|
|
||||||
|
``` go
|
||||||
|
app.Flags = []cli.Flag {
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "lang, l",
|
||||||
|
Value: "english",
|
||||||
|
Usage: "language for the greeting",
|
||||||
|
EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Subcommands
|
||||||
|
|
||||||
|
Subcommands can be defined for a more git-like command line app.
|
||||||
|
```go
|
||||||
|
...
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "add",
|
||||||
|
ShortName: "a",
|
||||||
|
Usage: "add a task to the list",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
println("added task: ", c.Args().First())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "complete",
|
||||||
|
ShortName: "c",
|
||||||
|
Usage: "complete a task on the list",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
println("completed task: ", c.Args().First())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "template",
|
||||||
|
ShortName: "r",
|
||||||
|
Usage: "options for task templates",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "add",
|
||||||
|
Usage: "add a new template",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
println("new task template: ", c.Args().First())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "remove",
|
||||||
|
Usage: "remove an existing template",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
println("removed task template: ", c.Args().First())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bash Completion
|
||||||
|
|
||||||
|
You can enable completion commands by setting the `EnableBashCompletion`
|
||||||
|
flag on the `App` object. By default, this setting will only auto-complete to
|
||||||
|
show an app's subcommands, but you can write your own completion methods for
|
||||||
|
the App or its subcommands.
|
||||||
|
```go
|
||||||
|
...
|
||||||
|
var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"}
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.EnableBashCompletion = true
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "complete",
|
||||||
|
ShortName: "c",
|
||||||
|
Usage: "complete a task on the list",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
println("completed task: ", c.Args().First())
|
||||||
|
},
|
||||||
|
BashComplete: func(c *cli.Context) {
|
||||||
|
// This will complete if no args are passed
|
||||||
|
if len(c.Args()) > 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, t := range tasks {
|
||||||
|
fmt.Println(t)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
#### To Enable
|
||||||
|
|
||||||
|
Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while
|
||||||
|
setting the `PROG` variable to the name of your program:
|
||||||
|
|
||||||
|
`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete`
|
||||||
|
|
||||||
|
|
||||||
|
## Contribution Guidelines
|
||||||
|
Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch.
|
||||||
|
|
||||||
|
If you have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together.
|
||||||
|
|
||||||
|
If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out.
|
275
Godeps/_workspace/src/github.com/codegangsta/cli/app.go
generated
vendored
Normal file
275
Godeps/_workspace/src/github.com/codegangsta/cli/app.go
generated
vendored
Normal file
|
@ -0,0 +1,275 @@
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"text/tabwriter"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// App is the main structure of a cli application. It is recomended that
|
||||||
|
// and app be created with the cli.NewApp() function
|
||||||
|
type App struct {
|
||||||
|
// The name of the program. Defaults to os.Args[0]
|
||||||
|
Name string
|
||||||
|
// Description of the program.
|
||||||
|
Usage string
|
||||||
|
// Version of the program
|
||||||
|
Version string
|
||||||
|
// List of commands to execute
|
||||||
|
Commands []Command
|
||||||
|
// List of flags to parse
|
||||||
|
Flags []Flag
|
||||||
|
// Boolean to enable bash completion commands
|
||||||
|
EnableBashCompletion bool
|
||||||
|
// Boolean to hide built-in help command
|
||||||
|
HideHelp bool
|
||||||
|
// Boolean to hide built-in version flag
|
||||||
|
HideVersion bool
|
||||||
|
// An action to execute when the bash-completion flag is set
|
||||||
|
BashComplete func(context *Context)
|
||||||
|
// An action to execute before any subcommands are run, but after the context is ready
|
||||||
|
// If a non-nil error is returned, no subcommands are run
|
||||||
|
Before func(context *Context) error
|
||||||
|
// The action to execute when no subcommands are specified
|
||||||
|
Action func(context *Context)
|
||||||
|
// Execute this function if the proper command cannot be found
|
||||||
|
CommandNotFound func(context *Context, command string)
|
||||||
|
// Compilation date
|
||||||
|
Compiled time.Time
|
||||||
|
// Author
|
||||||
|
Author string
|
||||||
|
// Author e-mail
|
||||||
|
Email string
|
||||||
|
// Writer writer to write output to
|
||||||
|
Writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tries to find out when this binary was compiled.
|
||||||
|
// Returns the current time if it fails to find it.
|
||||||
|
func compileTime() time.Time {
|
||||||
|
info, err := os.Stat(os.Args[0])
|
||||||
|
if err != nil {
|
||||||
|
return time.Now()
|
||||||
|
}
|
||||||
|
return info.ModTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action.
|
||||||
|
func NewApp() *App {
|
||||||
|
return &App{
|
||||||
|
Name: os.Args[0],
|
||||||
|
Usage: "A new cli application",
|
||||||
|
Version: "0.0.0",
|
||||||
|
BashComplete: DefaultAppComplete,
|
||||||
|
Action: helpCommand.Action,
|
||||||
|
Compiled: compileTime(),
|
||||||
|
Author: "Author",
|
||||||
|
Email: "unknown@email",
|
||||||
|
Writer: os.Stdout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
|
||||||
|
func (a *App) Run(arguments []string) error {
|
||||||
|
if HelpPrinter == nil {
|
||||||
|
defer func() {
|
||||||
|
HelpPrinter = nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
HelpPrinter = func(templ string, data interface{}) {
|
||||||
|
w := tabwriter.NewWriter(a.Writer, 0, 8, 1, '\t', 0)
|
||||||
|
t := template.Must(template.New("help").Parse(templ))
|
||||||
|
err := t.Execute(w, data)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
w.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// append help to commands
|
||||||
|
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
||||||
|
a.Commands = append(a.Commands, helpCommand)
|
||||||
|
if (HelpFlag != BoolFlag{}) {
|
||||||
|
a.appendFlag(HelpFlag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//append version/help flags
|
||||||
|
if a.EnableBashCompletion {
|
||||||
|
a.appendFlag(BashCompletionFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !a.HideVersion {
|
||||||
|
a.appendFlag(VersionFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse flags
|
||||||
|
set := flagSet(a.Name, a.Flags)
|
||||||
|
set.SetOutput(ioutil.Discard)
|
||||||
|
err := set.Parse(arguments[1:])
|
||||||
|
nerr := normalizeFlags(a.Flags, set)
|
||||||
|
if nerr != nil {
|
||||||
|
fmt.Fprintln(a.Writer, nerr)
|
||||||
|
context := NewContext(a, set, set)
|
||||||
|
ShowAppHelp(context)
|
||||||
|
fmt.Fprintln(a.Writer)
|
||||||
|
return nerr
|
||||||
|
}
|
||||||
|
context := NewContext(a, set, set)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(a.Writer, "Incorrect Usage.\n\n")
|
||||||
|
ShowAppHelp(context)
|
||||||
|
fmt.Fprintln(a.Writer)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkCompletions(context) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkHelp(context) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkVersion(context) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.Before != nil {
|
||||||
|
err := a.Before(context)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
args := context.Args()
|
||||||
|
if args.Present() {
|
||||||
|
name := args.First()
|
||||||
|
c := a.Command(name)
|
||||||
|
if c != nil {
|
||||||
|
return c.Run(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run default Action
|
||||||
|
a.Action(context)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Another entry point to the cli app, takes care of passing arguments and error handling
|
||||||
|
func (a *App) RunAndExitOnError() {
|
||||||
|
if err := a.Run(os.Args); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags
|
||||||
|
func (a *App) RunAsSubcommand(ctx *Context) error {
|
||||||
|
// append help to commands
|
||||||
|
if len(a.Commands) > 0 {
|
||||||
|
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
||||||
|
a.Commands = append(a.Commands, helpCommand)
|
||||||
|
if (HelpFlag != BoolFlag{}) {
|
||||||
|
a.appendFlag(HelpFlag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// append flags
|
||||||
|
if a.EnableBashCompletion {
|
||||||
|
a.appendFlag(BashCompletionFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse flags
|
||||||
|
set := flagSet(a.Name, a.Flags)
|
||||||
|
set.SetOutput(ioutil.Discard)
|
||||||
|
err := set.Parse(ctx.Args().Tail())
|
||||||
|
nerr := normalizeFlags(a.Flags, set)
|
||||||
|
context := NewContext(a, set, ctx.globalSet)
|
||||||
|
|
||||||
|
if nerr != nil {
|
||||||
|
fmt.Fprintln(a.Writer, nerr)
|
||||||
|
if len(a.Commands) > 0 {
|
||||||
|
ShowSubcommandHelp(context)
|
||||||
|
} else {
|
||||||
|
ShowCommandHelp(ctx, context.Args().First())
|
||||||
|
}
|
||||||
|
fmt.Fprintln(a.Writer)
|
||||||
|
return nerr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(a.Writer, "Incorrect Usage.\n\n")
|
||||||
|
ShowSubcommandHelp(context)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkCompletions(context) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(a.Commands) > 0 {
|
||||||
|
if checkSubcommandHelp(context) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if checkCommandHelp(ctx, context.Args().First()) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.Before != nil {
|
||||||
|
err := a.Before(context)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
args := context.Args()
|
||||||
|
if args.Present() {
|
||||||
|
name := args.First()
|
||||||
|
c := a.Command(name)
|
||||||
|
if c != nil {
|
||||||
|
return c.Run(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run default Action
|
||||||
|
a.Action(context)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the named command on App. Returns nil if the command does not exist
|
||||||
|
func (a *App) Command(name string) *Command {
|
||||||
|
for _, c := range a.Commands {
|
||||||
|
if c.HasName(name) {
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) hasFlag(flag Flag) bool {
|
||||||
|
for _, f := range a.Flags {
|
||||||
|
if flag == f {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) appendFlag(flag Flag) {
|
||||||
|
if !a.hasFlag(flag) {
|
||||||
|
a.Flags = append(a.Flags, flag)
|
||||||
|
}
|
||||||
|
}
|
554
Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go
generated
vendored
Normal file
554
Godeps/_workspace/src/github.com/codegangsta/cli/app_test.go
generated
vendored
Normal file
|
@ -0,0 +1,554 @@
|
||||||
|
package cli_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleApp() {
|
||||||
|
// set args for examples sake
|
||||||
|
os.Args = []string{"greet", "--name", "Jeremy"}
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "greet"
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
|
cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
|
||||||
|
}
|
||||||
|
app.Action = func(c *cli.Context) {
|
||||||
|
fmt.Printf("Hello %v\n", c.String("name"))
|
||||||
|
}
|
||||||
|
app.Run(os.Args)
|
||||||
|
// Output:
|
||||||
|
// Hello Jeremy
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleAppSubcommand() {
|
||||||
|
// set args for examples sake
|
||||||
|
os.Args = []string{"say", "hi", "english", "--name", "Jeremy"}
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "say"
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "hello",
|
||||||
|
ShortName: "hi",
|
||||||
|
Usage: "use it to see a description",
|
||||||
|
Description: "This is how we describe hello the function",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "english",
|
||||||
|
ShortName: "en",
|
||||||
|
Usage: "sends a greeting in english",
|
||||||
|
Description: "greets someone in english",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Value: "Bob",
|
||||||
|
Usage: "Name of the person to greet",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
fmt.Println("Hello,", c.String("name"))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run(os.Args)
|
||||||
|
// Output:
|
||||||
|
// Hello, Jeremy
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleAppHelp() {
|
||||||
|
// set args for examples sake
|
||||||
|
os.Args = []string{"greet", "h", "describeit"}
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "greet"
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
|
cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
|
||||||
|
}
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "describeit",
|
||||||
|
ShortName: "d",
|
||||||
|
Usage: "use it to see a description",
|
||||||
|
Description: "This is how we describe describeit the function",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
fmt.Printf("i like to describe things")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Run(os.Args)
|
||||||
|
// Output:
|
||||||
|
// NAME:
|
||||||
|
// describeit - use it to see a description
|
||||||
|
//
|
||||||
|
// USAGE:
|
||||||
|
// command describeit [arguments...]
|
||||||
|
//
|
||||||
|
// DESCRIPTION:
|
||||||
|
// This is how we describe describeit the function
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleAppBashComplete() {
|
||||||
|
// set args for examples sake
|
||||||
|
os.Args = []string{"greet", "--generate-bash-completion"}
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "greet"
|
||||||
|
app.EnableBashCompletion = true
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "describeit",
|
||||||
|
ShortName: "d",
|
||||||
|
Usage: "use it to see a description",
|
||||||
|
Description: "This is how we describe describeit the function",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
fmt.Printf("i like to describe things")
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Name: "next",
|
||||||
|
Usage: "next example",
|
||||||
|
Description: "more stuff to see when generating bash completion",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
fmt.Printf("the next example")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run(os.Args)
|
||||||
|
// Output:
|
||||||
|
// describeit
|
||||||
|
// d
|
||||||
|
// next
|
||||||
|
// help
|
||||||
|
// h
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_Run(t *testing.T) {
|
||||||
|
s := ""
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Action = func(c *cli.Context) {
|
||||||
|
s = s + c.Args().First()
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.Run([]string{"command", "foo"})
|
||||||
|
expect(t, err, nil)
|
||||||
|
err = app.Run([]string{"command", "bar"})
|
||||||
|
expect(t, err, nil)
|
||||||
|
expect(t, s, "foobar")
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandAppTests = []struct {
|
||||||
|
name string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{"foobar", true},
|
||||||
|
{"batbaz", true},
|
||||||
|
{"b", true},
|
||||||
|
{"f", true},
|
||||||
|
{"bat", false},
|
||||||
|
{"nothing", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_Command(t *testing.T) {
|
||||||
|
app := cli.NewApp()
|
||||||
|
fooCommand := cli.Command{Name: "foobar", ShortName: "f"}
|
||||||
|
batCommand := cli.Command{Name: "batbaz", ShortName: "b"}
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
fooCommand,
|
||||||
|
batCommand,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range commandAppTests {
|
||||||
|
expect(t, app.Command(test.name) != nil, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_CommandWithArgBeforeFlags(t *testing.T) {
|
||||||
|
var parsedOption, firstArg string
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
command := cli.Command{
|
||||||
|
Name: "cmd",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{Name: "option", Value: "", Usage: "some option"},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
parsedOption = c.String("option")
|
||||||
|
firstArg = c.Args().First()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Commands = []cli.Command{command}
|
||||||
|
|
||||||
|
app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"})
|
||||||
|
|
||||||
|
expect(t, parsedOption, "my-option")
|
||||||
|
expect(t, firstArg, "my-arg")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
|
||||||
|
var context *cli.Context
|
||||||
|
|
||||||
|
a := cli.NewApp()
|
||||||
|
a.Commands = []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "foo",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
context = c
|
||||||
|
},
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "lang",
|
||||||
|
Value: "english",
|
||||||
|
Usage: "language for the greeting",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Before: func(_ *cli.Context) error { return nil },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"", "foo", "--lang", "spanish", "abcd"})
|
||||||
|
|
||||||
|
expect(t, context.Args().Get(0), "abcd")
|
||||||
|
expect(t, context.String("lang"), "spanish")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
|
||||||
|
var parsedOption string
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
command := cli.Command{
|
||||||
|
Name: "cmd",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{Name: "option", Value: "", Usage: "some option"},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
parsedOption = c.String("option")
|
||||||
|
args = c.Args()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Commands = []cli.Command{command}
|
||||||
|
|
||||||
|
app.Run([]string{"", "cmd", "my-arg", "--option", "my-option", "--", "--notARealFlag"})
|
||||||
|
|
||||||
|
expect(t, parsedOption, "my-option")
|
||||||
|
expect(t, args[0], "my-arg")
|
||||||
|
expect(t, args[1], "--")
|
||||||
|
expect(t, args[2], "--notARealFlag")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) {
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
command := cli.Command{
|
||||||
|
Name: "cmd",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
args = c.Args()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Commands = []cli.Command{command}
|
||||||
|
|
||||||
|
app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"})
|
||||||
|
|
||||||
|
expect(t, args[0], "my-arg")
|
||||||
|
expect(t, args[1], "--")
|
||||||
|
expect(t, args[2], "notAFlagAtAll")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_Float64Flag(t *testing.T) {
|
||||||
|
var meters float64
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
|
cli.Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"},
|
||||||
|
}
|
||||||
|
app.Action = func(c *cli.Context) {
|
||||||
|
meters = c.Float64("height")
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run([]string{"", "--height", "1.93"})
|
||||||
|
expect(t, meters, 1.93)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_ParseSliceFlags(t *testing.T) {
|
||||||
|
var parsedOption, firstArg string
|
||||||
|
var parsedIntSlice []int
|
||||||
|
var parsedStringSlice []string
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
command := cli.Command{
|
||||||
|
Name: "cmd",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.IntSliceFlag{Name: "p", Value: &cli.IntSlice{}, Usage: "set one or more ip addr"},
|
||||||
|
cli.StringSliceFlag{Name: "ip", Value: &cli.StringSlice{}, Usage: "set one or more ports to open"},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
parsedIntSlice = c.IntSlice("p")
|
||||||
|
parsedStringSlice = c.StringSlice("ip")
|
||||||
|
parsedOption = c.String("option")
|
||||||
|
firstArg = c.Args().First()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Commands = []cli.Command{command}
|
||||||
|
|
||||||
|
app.Run([]string{"", "cmd", "my-arg", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4"})
|
||||||
|
|
||||||
|
IntsEquals := func(a, b []int) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, v := range a {
|
||||||
|
if v != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
StrsEquals := func(a, b []string) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, v := range a {
|
||||||
|
if v != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
var expectedIntSlice = []int{22, 80}
|
||||||
|
var expectedStringSlice = []string{"8.8.8.8", "8.8.4.4"}
|
||||||
|
|
||||||
|
if !IntsEquals(parsedIntSlice, expectedIntSlice) {
|
||||||
|
t.Errorf("%v does not match %v", parsedIntSlice, expectedIntSlice)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !StrsEquals(parsedStringSlice, expectedStringSlice) {
|
||||||
|
t.Errorf("%v does not match %v", parsedStringSlice, expectedStringSlice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_DefaultStdout(t *testing.T) {
|
||||||
|
app := cli.NewApp()
|
||||||
|
|
||||||
|
if app.Writer != os.Stdout {
|
||||||
|
t.Error("Default output writer not set.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockWriter struct {
|
||||||
|
written []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fw *mockWriter) Write(p []byte) (n int, err error) {
|
||||||
|
if fw.written == nil {
|
||||||
|
fw.written = p
|
||||||
|
} else {
|
||||||
|
fw.written = append(fw.written, p...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fw *mockWriter) GetWritten() (b []byte) {
|
||||||
|
return fw.written
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_SetStdout(t *testing.T) {
|
||||||
|
w := &mockWriter{}
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "test"
|
||||||
|
app.Writer = w
|
||||||
|
|
||||||
|
err := app.Run([]string{"help"})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Run error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(w.written) == 0 {
|
||||||
|
t.Error("App did not write output to desired writer.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_BeforeFunc(t *testing.T) {
|
||||||
|
beforeRun, subcommandRun := false, false
|
||||||
|
beforeError := fmt.Errorf("fail")
|
||||||
|
var err error
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
|
||||||
|
app.Before = func(c *cli.Context) error {
|
||||||
|
beforeRun = true
|
||||||
|
s := c.String("opt")
|
||||||
|
if s == "fail" {
|
||||||
|
return beforeError
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
cli.Command{
|
||||||
|
Name: "sub",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
subcommandRun = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
|
cli.StringFlag{Name: "opt"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// run with the Before() func succeeding
|
||||||
|
err = app.Run([]string{"command", "--opt", "succeed", "sub"})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Run error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if beforeRun == false {
|
||||||
|
t.Errorf("Before() not executed when expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
if subcommandRun == false {
|
||||||
|
t.Errorf("Subcommand not executed when expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset
|
||||||
|
beforeRun, subcommandRun = false, false
|
||||||
|
|
||||||
|
// run with the Before() func failing
|
||||||
|
err = app.Run([]string{"command", "--opt", "fail", "sub"})
|
||||||
|
|
||||||
|
// should be the same error produced by the Before func
|
||||||
|
if err != beforeError {
|
||||||
|
t.Errorf("Run error expected, but not received")
|
||||||
|
}
|
||||||
|
|
||||||
|
if beforeRun == false {
|
||||||
|
t.Errorf("Before() not executed when expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
if subcommandRun == true {
|
||||||
|
t.Errorf("Subcommand executed when NOT expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppNoHelpFlag(t *testing.T) {
|
||||||
|
oldFlag := cli.HelpFlag
|
||||||
|
defer func() {
|
||||||
|
cli.HelpFlag = oldFlag
|
||||||
|
}()
|
||||||
|
|
||||||
|
cli.HelpFlag = cli.BoolFlag{}
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
err := app.Run([]string{"test", "-h"})
|
||||||
|
|
||||||
|
if err != flag.ErrHelp {
|
||||||
|
t.Errorf("expected error about missing help flag, but got: %s (%T)", err, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppHelpPrinter(t *testing.T) {
|
||||||
|
oldPrinter := cli.HelpPrinter
|
||||||
|
defer func() {
|
||||||
|
cli.HelpPrinter = oldPrinter
|
||||||
|
}()
|
||||||
|
|
||||||
|
var wasCalled = false
|
||||||
|
cli.HelpPrinter = func(template string, data interface{}) {
|
||||||
|
wasCalled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Run([]string{"-h"})
|
||||||
|
|
||||||
|
if wasCalled == false {
|
||||||
|
t.Errorf("Help printer expected to be called, but was not")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppVersionPrinter(t *testing.T) {
|
||||||
|
oldPrinter := cli.VersionPrinter
|
||||||
|
defer func() {
|
||||||
|
cli.VersionPrinter = oldPrinter
|
||||||
|
}()
|
||||||
|
|
||||||
|
var wasCalled = false
|
||||||
|
cli.VersionPrinter = func(c *cli.Context) {
|
||||||
|
wasCalled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
ctx := cli.NewContext(app, nil, nil)
|
||||||
|
cli.ShowVersion(ctx)
|
||||||
|
|
||||||
|
if wasCalled == false {
|
||||||
|
t.Errorf("Version printer expected to be called, but was not")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppCommandNotFound(t *testing.T) {
|
||||||
|
beforeRun, subcommandRun := false, false
|
||||||
|
app := cli.NewApp()
|
||||||
|
|
||||||
|
app.CommandNotFound = func(c *cli.Context, command string) {
|
||||||
|
beforeRun = true
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
cli.Command{
|
||||||
|
Name: "bar",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
subcommandRun = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run([]string{"command", "foo"})
|
||||||
|
|
||||||
|
expect(t, beforeRun, true)
|
||||||
|
expect(t, subcommandRun, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGlobalFlagsInSubcommands(t *testing.T) {
|
||||||
|
subcommandRun := false
|
||||||
|
app := cli.NewApp()
|
||||||
|
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
|
cli.BoolFlag{Name: "debug, d", Usage: "Enable debugging"},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
cli.Command{
|
||||||
|
Name: "foo",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "bar",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
if c.GlobalBool("debug") {
|
||||||
|
subcommandRun = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run([]string{"command", "-d", "foo", "bar"})
|
||||||
|
|
||||||
|
expect(t, subcommandRun, true)
|
||||||
|
}
|
13
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete
generated
vendored
Normal file
13
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
_cli_bash_autocomplete() {
|
||||||
|
local cur prev opts base
|
||||||
|
COMPREPLY=()
|
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
|
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
|
||||||
|
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
complete -F _cli_bash_autocomplete $PROG
|
5
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/zsh_autocomplete
generated
vendored
Normal file
5
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/zsh_autocomplete
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
autoload -U compinit && compinit
|
||||||
|
autoload -U bashcompinit && bashcompinit
|
||||||
|
|
||||||
|
script_dir=$(dirname $0)
|
||||||
|
source ${script_dir}/bash_autocomplete
|
19
Godeps/_workspace/src/github.com/codegangsta/cli/cli.go
generated
vendored
Normal file
19
Godeps/_workspace/src/github.com/codegangsta/cli/cli.go
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// Package cli provides a minimal framework for creating and organizing command line
|
||||||
|
// Go applications. cli is designed to be easy to understand and write, the most simple
|
||||||
|
// cli application can be written as follows:
|
||||||
|
// func main() {
|
||||||
|
// cli.NewApp().Run(os.Args)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Of course this application does not do much, so let's make this an actual application:
|
||||||
|
// func main() {
|
||||||
|
// app := cli.NewApp()
|
||||||
|
// app.Name = "greet"
|
||||||
|
// app.Usage = "say a greeting"
|
||||||
|
// app.Action = func(c *cli.Context) {
|
||||||
|
// println("Greetings")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// app.Run(os.Args)
|
||||||
|
// }
|
||||||
|
package cli
|
100
Godeps/_workspace/src/github.com/codegangsta/cli/cli_test.go
generated
vendored
Normal file
100
Godeps/_workspace/src/github.com/codegangsta/cli/cli_test.go
generated
vendored
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
package cli_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example() {
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "todo"
|
||||||
|
app.Usage = "task list on the command line"
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "add",
|
||||||
|
ShortName: "a",
|
||||||
|
Usage: "add a task to the list",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
println("added task: ", c.Args().First())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "complete",
|
||||||
|
ShortName: "c",
|
||||||
|
Usage: "complete a task on the list",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
println("completed task: ", c.Args().First())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run(os.Args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleSubcommand() {
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "say"
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "hello",
|
||||||
|
ShortName: "hi",
|
||||||
|
Usage: "use it to see a description",
|
||||||
|
Description: "This is how we describe hello the function",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "english",
|
||||||
|
ShortName: "en",
|
||||||
|
Usage: "sends a greeting in english",
|
||||||
|
Description: "greets someone in english",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Value: "Bob",
|
||||||
|
Usage: "Name of the person to greet",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
println("Hello, ", c.String("name"))
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Name: "spanish",
|
||||||
|
ShortName: "sp",
|
||||||
|
Usage: "sends a greeting in spanish",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "surname",
|
||||||
|
Value: "Jones",
|
||||||
|
Usage: "Surname of the person to greet",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
println("Hola, ", c.String("surname"))
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Name: "french",
|
||||||
|
ShortName: "fr",
|
||||||
|
Usage: "sends a greeting in french",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "nickname",
|
||||||
|
Value: "Stevie",
|
||||||
|
Usage: "Nickname of the person to greet",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
println("Bonjour, ", c.String("nickname"))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Name: "bye",
|
||||||
|
Usage: "says goodbye",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
println("bye")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run(os.Args)
|
||||||
|
}
|
156
Godeps/_workspace/src/github.com/codegangsta/cli/command.go
generated
vendored
Normal file
156
Godeps/_workspace/src/github.com/codegangsta/cli/command.go
generated
vendored
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Command is a subcommand for a cli.App.
|
||||||
|
type Command struct {
|
||||||
|
// The name of the command
|
||||||
|
Name string
|
||||||
|
// short name of the command. Typically one character
|
||||||
|
ShortName string
|
||||||
|
// A short description of the usage of this command
|
||||||
|
Usage string
|
||||||
|
// A longer explanation of how the command works
|
||||||
|
Description string
|
||||||
|
// The function to call when checking for bash command completions
|
||||||
|
BashComplete func(context *Context)
|
||||||
|
// An action to execute before any sub-subcommands are run, but after the context is ready
|
||||||
|
// If a non-nil error is returned, no sub-subcommands are run
|
||||||
|
Before func(context *Context) error
|
||||||
|
// The function to call when this command is invoked
|
||||||
|
Action func(context *Context)
|
||||||
|
// List of child commands
|
||||||
|
Subcommands []Command
|
||||||
|
// List of flags to parse
|
||||||
|
Flags []Flag
|
||||||
|
// Treat all flags as normal arguments if true
|
||||||
|
SkipFlagParsing bool
|
||||||
|
// Boolean to hide built-in help command
|
||||||
|
HideHelp bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invokes the command given the context, parses ctx.Args() to generate command-specific flags
|
||||||
|
func (c Command) Run(ctx *Context) error {
|
||||||
|
|
||||||
|
if len(c.Subcommands) > 0 || c.Before != nil {
|
||||||
|
return c.startApp(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.HideHelp && (HelpFlag != BoolFlag{}) {
|
||||||
|
// append help to flags
|
||||||
|
c.Flags = append(
|
||||||
|
c.Flags,
|
||||||
|
HelpFlag,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.App.EnableBashCompletion {
|
||||||
|
c.Flags = append(c.Flags, BashCompletionFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
set := flagSet(c.Name, c.Flags)
|
||||||
|
set.SetOutput(ioutil.Discard)
|
||||||
|
|
||||||
|
firstFlagIndex := -1
|
||||||
|
terminatorIndex := -1
|
||||||
|
for index, arg := range ctx.Args() {
|
||||||
|
if arg == "--" {
|
||||||
|
terminatorIndex = index
|
||||||
|
break
|
||||||
|
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
|
||||||
|
firstFlagIndex = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if firstFlagIndex > -1 && !c.SkipFlagParsing {
|
||||||
|
args := ctx.Args()
|
||||||
|
regularArgs := make([]string, len(args[1:firstFlagIndex]))
|
||||||
|
copy(regularArgs, args[1:firstFlagIndex])
|
||||||
|
|
||||||
|
var flagArgs []string
|
||||||
|
if terminatorIndex > -1 {
|
||||||
|
flagArgs = args[firstFlagIndex:terminatorIndex]
|
||||||
|
regularArgs = append(regularArgs, args[terminatorIndex:]...)
|
||||||
|
} else {
|
||||||
|
flagArgs = args[firstFlagIndex:]
|
||||||
|
}
|
||||||
|
|
||||||
|
err = set.Parse(append(flagArgs, regularArgs...))
|
||||||
|
} else {
|
||||||
|
err = set.Parse(ctx.Args().Tail())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprint(ctx.App.Writer, "Incorrect Usage.\n\n")
|
||||||
|
ShowCommandHelp(ctx, c.Name)
|
||||||
|
fmt.Fprintln(ctx.App.Writer)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nerr := normalizeFlags(c.Flags, set)
|
||||||
|
if nerr != nil {
|
||||||
|
fmt.Fprintln(ctx.App.Writer, nerr)
|
||||||
|
fmt.Fprintln(ctx.App.Writer)
|
||||||
|
ShowCommandHelp(ctx, c.Name)
|
||||||
|
fmt.Fprintln(ctx.App.Writer)
|
||||||
|
return nerr
|
||||||
|
}
|
||||||
|
context := NewContext(ctx.App, set, ctx.globalSet)
|
||||||
|
|
||||||
|
if checkCommandCompletions(context, c.Name) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkCommandHelp(context, c.Name) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
context.Command = c
|
||||||
|
c.Action(context)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if Command.Name or Command.ShortName matches given name
|
||||||
|
func (c Command) HasName(name string) bool {
|
||||||
|
return c.Name == name || c.ShortName == name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Command) startApp(ctx *Context) error {
|
||||||
|
app := NewApp()
|
||||||
|
|
||||||
|
// set the name and usage
|
||||||
|
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
|
||||||
|
if c.Description != "" {
|
||||||
|
app.Usage = c.Description
|
||||||
|
} else {
|
||||||
|
app.Usage = c.Usage
|
||||||
|
}
|
||||||
|
|
||||||
|
// set CommandNotFound
|
||||||
|
app.CommandNotFound = ctx.App.CommandNotFound
|
||||||
|
|
||||||
|
// set the flags and commands
|
||||||
|
app.Commands = c.Subcommands
|
||||||
|
app.Flags = c.Flags
|
||||||
|
app.HideHelp = c.HideHelp
|
||||||
|
|
||||||
|
// bash completion
|
||||||
|
app.EnableBashCompletion = ctx.App.EnableBashCompletion
|
||||||
|
if c.BashComplete != nil {
|
||||||
|
app.BashComplete = c.BashComplete
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the actions
|
||||||
|
app.Before = c.Before
|
||||||
|
if c.Action != nil {
|
||||||
|
app.Action = c.Action
|
||||||
|
} else {
|
||||||
|
app.Action = helpSubcommand.Action
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.RunAsSubcommand(ctx)
|
||||||
|
}
|
49
Godeps/_workspace/src/github.com/codegangsta/cli/command_test.go
generated
vendored
Normal file
49
Godeps/_workspace/src/github.com/codegangsta/cli/command_test.go
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package cli_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCommandDoNotIgnoreFlags(t *testing.T) {
|
||||||
|
app := cli.NewApp()
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
test := []string{"blah", "blah", "-break"}
|
||||||
|
set.Parse(test)
|
||||||
|
|
||||||
|
c := cli.NewContext(app, set, set)
|
||||||
|
|
||||||
|
command := cli.Command{
|
||||||
|
Name: "test-cmd",
|
||||||
|
ShortName: "tc",
|
||||||
|
Usage: "this is for testing",
|
||||||
|
Description: "testing",
|
||||||
|
Action: func(_ *cli.Context) {},
|
||||||
|
}
|
||||||
|
err := command.Run(c)
|
||||||
|
|
||||||
|
expect(t, err.Error(), "flag provided but not defined: -break")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandIgnoreFlags(t *testing.T) {
|
||||||
|
app := cli.NewApp()
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
test := []string{"blah", "blah"}
|
||||||
|
set.Parse(test)
|
||||||
|
|
||||||
|
c := cli.NewContext(app, set, set)
|
||||||
|
|
||||||
|
command := cli.Command{
|
||||||
|
Name: "test-cmd",
|
||||||
|
ShortName: "tc",
|
||||||
|
Usage: "this is for testing",
|
||||||
|
Description: "testing",
|
||||||
|
Action: func(_ *cli.Context) {},
|
||||||
|
SkipFlagParsing: true,
|
||||||
|
}
|
||||||
|
err := command.Run(c)
|
||||||
|
|
||||||
|
expect(t, err, nil)
|
||||||
|
}
|
339
Godeps/_workspace/src/github.com/codegangsta/cli/context.go
generated
vendored
Normal file
339
Godeps/_workspace/src/github.com/codegangsta/cli/context.go
generated
vendored
Normal file
|
@ -0,0 +1,339 @@
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Context is a type that is passed through to
|
||||||
|
// each Handler action in a cli application. Context
|
||||||
|
// can be used to retrieve context-specific Args and
|
||||||
|
// parsed command-line options.
|
||||||
|
type Context struct {
|
||||||
|
App *App
|
||||||
|
Command Command
|
||||||
|
flagSet *flag.FlagSet
|
||||||
|
globalSet *flag.FlagSet
|
||||||
|
setFlags map[string]bool
|
||||||
|
globalSetFlags map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new context. For use in when invoking an App or Command action.
|
||||||
|
func NewContext(app *App, set *flag.FlagSet, globalSet *flag.FlagSet) *Context {
|
||||||
|
return &Context{App: app, flagSet: set, globalSet: globalSet}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up the value of a local int flag, returns 0 if no int flag exists
|
||||||
|
func (c *Context) Int(name string) int {
|
||||||
|
return lookupInt(name, c.flagSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up the value of a local time.Duration flag, returns 0 if no time.Duration flag exists
|
||||||
|
func (c *Context) Duration(name string) time.Duration {
|
||||||
|
return lookupDuration(name, c.flagSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up the value of a local float64 flag, returns 0 if no float64 flag exists
|
||||||
|
func (c *Context) Float64(name string) float64 {
|
||||||
|
return lookupFloat64(name, c.flagSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up the value of a local bool flag, returns false if no bool flag exists
|
||||||
|
func (c *Context) Bool(name string) bool {
|
||||||
|
return lookupBool(name, c.flagSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up the value of a local boolT flag, returns false if no bool flag exists
|
||||||
|
func (c *Context) BoolT(name string) bool {
|
||||||
|
return lookupBoolT(name, c.flagSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up the value of a local string flag, returns "" if no string flag exists
|
||||||
|
func (c *Context) String(name string) string {
|
||||||
|
return lookupString(name, c.flagSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up the value of a local string slice flag, returns nil if no string slice flag exists
|
||||||
|
func (c *Context) StringSlice(name string) []string {
|
||||||
|
return lookupStringSlice(name, c.flagSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up the value of a local int slice flag, returns nil if no int slice flag exists
|
||||||
|
func (c *Context) IntSlice(name string) []int {
|
||||||
|
return lookupIntSlice(name, c.flagSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up the value of a local generic flag, returns nil if no generic flag exists
|
||||||
|
func (c *Context) Generic(name string) interface{} {
|
||||||
|
return lookupGeneric(name, c.flagSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up the value of a global int flag, returns 0 if no int flag exists
|
||||||
|
func (c *Context) GlobalInt(name string) int {
|
||||||
|
return lookupInt(name, c.globalSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up the value of a global time.Duration flag, returns 0 if no time.Duration flag exists
|
||||||
|
func (c *Context) GlobalDuration(name string) time.Duration {
|
||||||
|
return lookupDuration(name, c.globalSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up the value of a global bool flag, returns false if no bool flag exists
|
||||||
|
func (c *Context) GlobalBool(name string) bool {
|
||||||
|
return lookupBool(name, c.globalSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up the value of a global string flag, returns "" if no string flag exists
|
||||||
|
func (c *Context) GlobalString(name string) string {
|
||||||
|
return lookupString(name, c.globalSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up the value of a global string slice flag, returns nil if no string slice flag exists
|
||||||
|
func (c *Context) GlobalStringSlice(name string) []string {
|
||||||
|
return lookupStringSlice(name, c.globalSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up the value of a global int slice flag, returns nil if no int slice flag exists
|
||||||
|
func (c *Context) GlobalIntSlice(name string) []int {
|
||||||
|
return lookupIntSlice(name, c.globalSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up the value of a global generic flag, returns nil if no generic flag exists
|
||||||
|
func (c *Context) GlobalGeneric(name string) interface{} {
|
||||||
|
return lookupGeneric(name, c.globalSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determines if the flag was actually set
|
||||||
|
func (c *Context) IsSet(name string) bool {
|
||||||
|
if c.setFlags == nil {
|
||||||
|
c.setFlags = make(map[string]bool)
|
||||||
|
c.flagSet.Visit(func(f *flag.Flag) {
|
||||||
|
c.setFlags[f.Name] = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return c.setFlags[name] == true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determines if the global flag was actually set
|
||||||
|
func (c *Context) GlobalIsSet(name string) bool {
|
||||||
|
if c.globalSetFlags == nil {
|
||||||
|
c.globalSetFlags = make(map[string]bool)
|
||||||
|
c.globalSet.Visit(func(f *flag.Flag) {
|
||||||
|
c.globalSetFlags[f.Name] = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return c.globalSetFlags[name] == true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a slice of flag names used in this context.
|
||||||
|
func (c *Context) FlagNames() (names []string) {
|
||||||
|
for _, flag := range c.Command.Flags {
|
||||||
|
name := strings.Split(flag.getName(), ",")[0]
|
||||||
|
if name == "help" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a slice of global flag names used by the app.
|
||||||
|
func (c *Context) GlobalFlagNames() (names []string) {
|
||||||
|
for _, flag := range c.App.Flags {
|
||||||
|
name := strings.Split(flag.getName(), ",")[0]
|
||||||
|
if name == "help" || name == "version" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Args []string
|
||||||
|
|
||||||
|
// Returns the command line arguments associated with the context.
|
||||||
|
func (c *Context) Args() Args {
|
||||||
|
args := Args(c.flagSet.Args())
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the nth argument, or else a blank string
|
||||||
|
func (a Args) Get(n int) string {
|
||||||
|
if len(a) > n {
|
||||||
|
return a[n]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the first argument, or else a blank string
|
||||||
|
func (a Args) First() string {
|
||||||
|
return a.Get(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the rest of the arguments (not the first one)
|
||||||
|
// or else an empty string slice
|
||||||
|
func (a Args) Tail() []string {
|
||||||
|
if len(a) >= 2 {
|
||||||
|
return []string(a)[1:]
|
||||||
|
}
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if there are any arguments present
|
||||||
|
func (a Args) Present() bool {
|
||||||
|
return len(a) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swaps arguments at the given indexes
|
||||||
|
func (a Args) Swap(from, to int) error {
|
||||||
|
if from >= len(a) || to >= len(a) {
|
||||||
|
return errors.New("index out of range")
|
||||||
|
}
|
||||||
|
a[from], a[to] = a[to], a[from]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupInt(name string, set *flag.FlagSet) int {
|
||||||
|
f := set.Lookup(name)
|
||||||
|
if f != nil {
|
||||||
|
val, err := strconv.Atoi(f.Value.String())
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupDuration(name string, set *flag.FlagSet) time.Duration {
|
||||||
|
f := set.Lookup(name)
|
||||||
|
if f != nil {
|
||||||
|
val, err := time.ParseDuration(f.Value.String())
|
||||||
|
if err == nil {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupFloat64(name string, set *flag.FlagSet) float64 {
|
||||||
|
f := set.Lookup(name)
|
||||||
|
if f != nil {
|
||||||
|
val, err := strconv.ParseFloat(f.Value.String(), 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupString(name string, set *flag.FlagSet) string {
|
||||||
|
f := set.Lookup(name)
|
||||||
|
if f != nil {
|
||||||
|
return f.Value.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupStringSlice(name string, set *flag.FlagSet) []string {
|
||||||
|
f := set.Lookup(name)
|
||||||
|
if f != nil {
|
||||||
|
return (f.Value.(*StringSlice)).Value()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupIntSlice(name string, set *flag.FlagSet) []int {
|
||||||
|
f := set.Lookup(name)
|
||||||
|
if f != nil {
|
||||||
|
return (f.Value.(*IntSlice)).Value()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupGeneric(name string, set *flag.FlagSet) interface{} {
|
||||||
|
f := set.Lookup(name)
|
||||||
|
if f != nil {
|
||||||
|
return f.Value
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupBool(name string, set *flag.FlagSet) bool {
|
||||||
|
f := set.Lookup(name)
|
||||||
|
if f != nil {
|
||||||
|
val, err := strconv.ParseBool(f.Value.String())
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupBoolT(name string, set *flag.FlagSet) bool {
|
||||||
|
f := set.Lookup(name)
|
||||||
|
if f != nil {
|
||||||
|
val, err := strconv.ParseBool(f.Value.String())
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
|
||||||
|
switch ff.Value.(type) {
|
||||||
|
case *StringSlice:
|
||||||
|
default:
|
||||||
|
set.Set(name, ff.Value.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
|
||||||
|
visited := make(map[string]bool)
|
||||||
|
set.Visit(func(f *flag.Flag) {
|
||||||
|
visited[f.Name] = true
|
||||||
|
})
|
||||||
|
for _, f := range flags {
|
||||||
|
parts := strings.Split(f.getName(), ",")
|
||||||
|
if len(parts) == 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var ff *flag.Flag
|
||||||
|
for _, name := range parts {
|
||||||
|
name = strings.Trim(name, " ")
|
||||||
|
if visited[name] {
|
||||||
|
if ff != nil {
|
||||||
|
return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name)
|
||||||
|
}
|
||||||
|
ff = set.Lookup(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ff == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, name := range parts {
|
||||||
|
name = strings.Trim(name, " ")
|
||||||
|
if !visited[name] {
|
||||||
|
copyFlag(name, ff, set)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
99
Godeps/_workspace/src/github.com/codegangsta/cli/context_test.go
generated
vendored
Normal file
99
Godeps/_workspace/src/github.com/codegangsta/cli/context_test.go
generated
vendored
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
package cli_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewContext(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Int("myflag", 12, "doc")
|
||||||
|
globalSet := flag.NewFlagSet("test", 0)
|
||||||
|
globalSet.Int("myflag", 42, "doc")
|
||||||
|
command := cli.Command{Name: "mycommand"}
|
||||||
|
c := cli.NewContext(nil, set, globalSet)
|
||||||
|
c.Command = command
|
||||||
|
expect(t, c.Int("myflag"), 12)
|
||||||
|
expect(t, c.GlobalInt("myflag"), 42)
|
||||||
|
expect(t, c.Command.Name, "mycommand")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_Int(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Int("myflag", 12, "doc")
|
||||||
|
c := cli.NewContext(nil, set, set)
|
||||||
|
expect(t, c.Int("myflag"), 12)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_Duration(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Duration("myflag", time.Duration(12*time.Second), "doc")
|
||||||
|
c := cli.NewContext(nil, set, set)
|
||||||
|
expect(t, c.Duration("myflag"), time.Duration(12*time.Second))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_String(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.String("myflag", "hello world", "doc")
|
||||||
|
c := cli.NewContext(nil, set, set)
|
||||||
|
expect(t, c.String("myflag"), "hello world")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_Bool(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Bool("myflag", false, "doc")
|
||||||
|
c := cli.NewContext(nil, set, set)
|
||||||
|
expect(t, c.Bool("myflag"), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_BoolT(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Bool("myflag", true, "doc")
|
||||||
|
c := cli.NewContext(nil, set, set)
|
||||||
|
expect(t, c.BoolT("myflag"), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_Args(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Bool("myflag", false, "doc")
|
||||||
|
c := cli.NewContext(nil, set, set)
|
||||||
|
set.Parse([]string{"--myflag", "bat", "baz"})
|
||||||
|
expect(t, len(c.Args()), 2)
|
||||||
|
expect(t, c.Bool("myflag"), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_IsSet(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Bool("myflag", false, "doc")
|
||||||
|
set.String("otherflag", "hello world", "doc")
|
||||||
|
globalSet := flag.NewFlagSet("test", 0)
|
||||||
|
globalSet.Bool("myflagGlobal", true, "doc")
|
||||||
|
c := cli.NewContext(nil, set, globalSet)
|
||||||
|
set.Parse([]string{"--myflag", "bat", "baz"})
|
||||||
|
globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"})
|
||||||
|
expect(t, c.IsSet("myflag"), true)
|
||||||
|
expect(t, c.IsSet("otherflag"), false)
|
||||||
|
expect(t, c.IsSet("bogusflag"), false)
|
||||||
|
expect(t, c.IsSet("myflagGlobal"), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_GlobalIsSet(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Bool("myflag", false, "doc")
|
||||||
|
set.String("otherflag", "hello world", "doc")
|
||||||
|
globalSet := flag.NewFlagSet("test", 0)
|
||||||
|
globalSet.Bool("myflagGlobal", true, "doc")
|
||||||
|
globalSet.Bool("myflagGlobalUnset", true, "doc")
|
||||||
|
c := cli.NewContext(nil, set, globalSet)
|
||||||
|
set.Parse([]string{"--myflag", "bat", "baz"})
|
||||||
|
globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"})
|
||||||
|
expect(t, c.GlobalIsSet("myflag"), false)
|
||||||
|
expect(t, c.GlobalIsSet("otherflag"), false)
|
||||||
|
expect(t, c.GlobalIsSet("bogusflag"), false)
|
||||||
|
expect(t, c.GlobalIsSet("myflagGlobal"), true)
|
||||||
|
expect(t, c.GlobalIsSet("myflagGlobalUnset"), false)
|
||||||
|
expect(t, c.GlobalIsSet("bogusGlobal"), false)
|
||||||
|
}
|
454
Godeps/_workspace/src/github.com/codegangsta/cli/flag.go
generated
vendored
Normal file
454
Godeps/_workspace/src/github.com/codegangsta/cli/flag.go
generated
vendored
Normal file
|
@ -0,0 +1,454 @@
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This flag enables bash-completion for all commands and subcommands
|
||||||
|
var BashCompletionFlag = BoolFlag{
|
||||||
|
Name: "generate-bash-completion",
|
||||||
|
}
|
||||||
|
|
||||||
|
// This flag prints the version for the application
|
||||||
|
var VersionFlag = BoolFlag{
|
||||||
|
Name: "version, v",
|
||||||
|
Usage: "print the version",
|
||||||
|
}
|
||||||
|
|
||||||
|
// This flag prints the help for all commands and subcommands
|
||||||
|
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
|
||||||
|
// unless HideHelp is set to true)
|
||||||
|
var HelpFlag = BoolFlag{
|
||||||
|
Name: "help, h",
|
||||||
|
Usage: "show help",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flag is a common interface related to parsing flags in cli.
|
||||||
|
// For more advanced flag parsing techniques, it is recomended that
|
||||||
|
// this interface be implemented.
|
||||||
|
type Flag interface {
|
||||||
|
fmt.Stringer
|
||||||
|
// Apply Flag settings to the given flag set
|
||||||
|
Apply(*flag.FlagSet)
|
||||||
|
getName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func flagSet(name string, flags []Flag) *flag.FlagSet {
|
||||||
|
set := flag.NewFlagSet(name, flag.ContinueOnError)
|
||||||
|
|
||||||
|
for _, f := range flags {
|
||||||
|
f.Apply(set)
|
||||||
|
}
|
||||||
|
return set
|
||||||
|
}
|
||||||
|
|
||||||
|
func eachName(longName string, fn func(string)) {
|
||||||
|
parts := strings.Split(longName, ",")
|
||||||
|
for _, name := range parts {
|
||||||
|
name = strings.Trim(name, " ")
|
||||||
|
fn(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic is a generic parseable type identified by a specific flag
|
||||||
|
type Generic interface {
|
||||||
|
Set(value string) error
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenericFlag is the flag type for types implementing Generic
|
||||||
|
type GenericFlag struct {
|
||||||
|
Name string
|
||||||
|
Value Generic
|
||||||
|
Usage string
|
||||||
|
EnvVar string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of the generic flag to display the
|
||||||
|
// help text to the user (uses the String() method of the generic flag to show
|
||||||
|
// the value)
|
||||||
|
func (f GenericFlag) String() string {
|
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s \"%v\"\t%v", prefixFor(f.Name), f.Name, f.Value, f.Usage))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply takes the flagset and calls Set on the generic flag with the value
|
||||||
|
// provided by the user for parsing by the flag
|
||||||
|
func (f GenericFlag) Apply(set *flag.FlagSet) {
|
||||||
|
val := f.Value
|
||||||
|
if f.EnvVar != "" {
|
||||||
|
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||||
|
envVar = strings.TrimSpace(envVar)
|
||||||
|
if envVal := os.Getenv(envVar); envVal != "" {
|
||||||
|
val.Set(envVal)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eachName(f.Name, func(name string) {
|
||||||
|
set.Var(f.Value, name, f.Usage)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f GenericFlag) getName() string {
|
||||||
|
return f.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
type StringSlice []string
|
||||||
|
|
||||||
|
func (f *StringSlice) Set(value string) error {
|
||||||
|
*f = append(*f, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *StringSlice) String() string {
|
||||||
|
return fmt.Sprintf("%s", *f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *StringSlice) Value() []string {
|
||||||
|
return *f
|
||||||
|
}
|
||||||
|
|
||||||
|
type StringSliceFlag struct {
|
||||||
|
Name string
|
||||||
|
Value *StringSlice
|
||||||
|
Usage string
|
||||||
|
EnvVar string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f StringSliceFlag) String() string {
|
||||||
|
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
|
||||||
|
pref := prefixFor(firstName)
|
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f StringSliceFlag) Apply(set *flag.FlagSet) {
|
||||||
|
if f.EnvVar != "" {
|
||||||
|
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||||
|
envVar = strings.TrimSpace(envVar)
|
||||||
|
if envVal := os.Getenv(envVar); envVal != "" {
|
||||||
|
newVal := &StringSlice{}
|
||||||
|
for _, s := range strings.Split(envVal, ",") {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
newVal.Set(s)
|
||||||
|
}
|
||||||
|
f.Value = newVal
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eachName(f.Name, func(name string) {
|
||||||
|
set.Var(f.Value, name, f.Usage)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f StringSliceFlag) getName() string {
|
||||||
|
return f.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntSlice []int
|
||||||
|
|
||||||
|
func (f *IntSlice) Set(value string) error {
|
||||||
|
|
||||||
|
tmp, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
*f = append(*f, tmp)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *IntSlice) String() string {
|
||||||
|
return fmt.Sprintf("%d", *f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *IntSlice) Value() []int {
|
||||||
|
return *f
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntSliceFlag struct {
|
||||||
|
Name string
|
||||||
|
Value *IntSlice
|
||||||
|
Usage string
|
||||||
|
EnvVar string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f IntSliceFlag) String() string {
|
||||||
|
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
|
||||||
|
pref := prefixFor(firstName)
|
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f IntSliceFlag) Apply(set *flag.FlagSet) {
|
||||||
|
if f.EnvVar != "" {
|
||||||
|
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||||
|
envVar = strings.TrimSpace(envVar)
|
||||||
|
if envVal := os.Getenv(envVar); envVal != "" {
|
||||||
|
newVal := &IntSlice{}
|
||||||
|
for _, s := range strings.Split(envVal, ",") {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
err := newVal.Set(s)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.Value = newVal
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eachName(f.Name, func(name string) {
|
||||||
|
set.Var(f.Value, name, f.Usage)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f IntSliceFlag) getName() string {
|
||||||
|
return f.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
type BoolFlag struct {
|
||||||
|
Name string
|
||||||
|
Usage string
|
||||||
|
EnvVar string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f BoolFlag) String() string {
|
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f BoolFlag) Apply(set *flag.FlagSet) {
|
||||||
|
val := false
|
||||||
|
if f.EnvVar != "" {
|
||||||
|
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||||
|
envVar = strings.TrimSpace(envVar)
|
||||||
|
if envVal := os.Getenv(envVar); envVal != "" {
|
||||||
|
envValBool, err := strconv.ParseBool(envVal)
|
||||||
|
if err == nil {
|
||||||
|
val = envValBool
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eachName(f.Name, func(name string) {
|
||||||
|
set.Bool(name, val, f.Usage)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f BoolFlag) getName() string {
|
||||||
|
return f.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
type BoolTFlag struct {
|
||||||
|
Name string
|
||||||
|
Usage string
|
||||||
|
EnvVar string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f BoolTFlag) String() string {
|
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f BoolTFlag) Apply(set *flag.FlagSet) {
|
||||||
|
val := true
|
||||||
|
if f.EnvVar != "" {
|
||||||
|
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||||
|
envVar = strings.TrimSpace(envVar)
|
||||||
|
if envVal := os.Getenv(envVar); envVal != "" {
|
||||||
|
envValBool, err := strconv.ParseBool(envVal)
|
||||||
|
if err == nil {
|
||||||
|
val = envValBool
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eachName(f.Name, func(name string) {
|
||||||
|
set.Bool(name, val, f.Usage)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f BoolTFlag) getName() string {
|
||||||
|
return f.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
type StringFlag struct {
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
Usage string
|
||||||
|
EnvVar string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f StringFlag) String() string {
|
||||||
|
var fmtString string
|
||||||
|
fmtString = "%s %v\t%v"
|
||||||
|
|
||||||
|
if len(f.Value) > 0 {
|
||||||
|
fmtString = "%s \"%v\"\t%v"
|
||||||
|
} else {
|
||||||
|
fmtString = "%s %v\t%v"
|
||||||
|
}
|
||||||
|
|
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f StringFlag) Apply(set *flag.FlagSet) {
|
||||||
|
if f.EnvVar != "" {
|
||||||
|
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||||
|
envVar = strings.TrimSpace(envVar)
|
||||||
|
if envVal := os.Getenv(envVar); envVal != "" {
|
||||||
|
f.Value = envVal
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eachName(f.Name, func(name string) {
|
||||||
|
set.String(name, f.Value, f.Usage)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f StringFlag) getName() string {
|
||||||
|
return f.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntFlag struct {
|
||||||
|
Name string
|
||||||
|
Value int
|
||||||
|
Usage string
|
||||||
|
EnvVar string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f IntFlag) String() string {
|
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f IntFlag) Apply(set *flag.FlagSet) {
|
||||||
|
if f.EnvVar != "" {
|
||||||
|
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||||
|
envVar = strings.TrimSpace(envVar)
|
||||||
|
if envVal := os.Getenv(envVar); envVal != "" {
|
||||||
|
envValInt, err := strconv.ParseInt(envVal, 0, 64)
|
||||||
|
if err == nil {
|
||||||
|
f.Value = int(envValInt)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eachName(f.Name, func(name string) {
|
||||||
|
set.Int(name, f.Value, f.Usage)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f IntFlag) getName() string {
|
||||||
|
return f.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
type DurationFlag struct {
|
||||||
|
Name string
|
||||||
|
Value time.Duration
|
||||||
|
Usage string
|
||||||
|
EnvVar string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f DurationFlag) String() string {
|
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f DurationFlag) Apply(set *flag.FlagSet) {
|
||||||
|
if f.EnvVar != "" {
|
||||||
|
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||||
|
envVar = strings.TrimSpace(envVar)
|
||||||
|
if envVal := os.Getenv(envVar); envVal != "" {
|
||||||
|
envValDuration, err := time.ParseDuration(envVal)
|
||||||
|
if err == nil {
|
||||||
|
f.Value = envValDuration
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eachName(f.Name, func(name string) {
|
||||||
|
set.Duration(name, f.Value, f.Usage)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f DurationFlag) getName() string {
|
||||||
|
return f.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
type Float64Flag struct {
|
||||||
|
Name string
|
||||||
|
Value float64
|
||||||
|
Usage string
|
||||||
|
EnvVar string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Float64Flag) String() string {
|
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Float64Flag) Apply(set *flag.FlagSet) {
|
||||||
|
if f.EnvVar != "" {
|
||||||
|
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||||
|
envVar = strings.TrimSpace(envVar)
|
||||||
|
if envVal := os.Getenv(envVar); envVal != "" {
|
||||||
|
envValFloat, err := strconv.ParseFloat(envVal, 10)
|
||||||
|
if err == nil {
|
||||||
|
f.Value = float64(envValFloat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eachName(f.Name, func(name string) {
|
||||||
|
set.Float64(name, f.Value, f.Usage)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Float64Flag) getName() string {
|
||||||
|
return f.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func prefixFor(name string) (prefix string) {
|
||||||
|
if len(name) == 1 {
|
||||||
|
prefix = "-"
|
||||||
|
} else {
|
||||||
|
prefix = "--"
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func prefixedNames(fullName string) (prefixed string) {
|
||||||
|
parts := strings.Split(fullName, ",")
|
||||||
|
for i, name := range parts {
|
||||||
|
name = strings.Trim(name, " ")
|
||||||
|
prefixed += prefixFor(name) + name
|
||||||
|
if i < len(parts)-1 {
|
||||||
|
prefixed += ", "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func withEnvHint(envVar, str string) string {
|
||||||
|
envText := ""
|
||||||
|
if envVar != "" {
|
||||||
|
envText = fmt.Sprintf(" [$%s]", strings.Join(strings.Split(envVar, ","), ", $"))
|
||||||
|
}
|
||||||
|
return str + envText
|
||||||
|
}
|
742
Godeps/_workspace/src/github.com/codegangsta/cli/flag_test.go
generated
vendored
Normal file
742
Godeps/_workspace/src/github.com/codegangsta/cli/flag_test.go
generated
vendored
Normal file
|
@ -0,0 +1,742 @@
|
||||||
|
package cli_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var boolFlagTests = []struct {
|
||||||
|
name string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"help", "--help\t"},
|
||||||
|
{"h", "-h\t"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolFlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
|
for _, test := range boolFlagTests {
|
||||||
|
flag := cli.BoolFlag{Name: test.name}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if output != test.expected {
|
||||||
|
t.Errorf("%s does not match %s", output, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringFlagTests = []struct {
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"help", "", "--help \t"},
|
||||||
|
{"h", "", "-h \t"},
|
||||||
|
{"h", "", "-h \t"},
|
||||||
|
{"test", "Something", "--test \"Something\"\t"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringFlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
|
for _, test := range stringFlagTests {
|
||||||
|
flag := cli.StringFlag{Name: test.name, Value: test.value}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if output != test.expected {
|
||||||
|
t.Errorf("%s does not match %s", output, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_FOO", "derp")
|
||||||
|
for _, test := range stringFlagTests {
|
||||||
|
flag := cli.StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if !strings.HasSuffix(output, " [$APP_FOO]") {
|
||||||
|
t.Errorf("%s does not end with [$APP_FOO]", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringSliceFlagTests = []struct {
|
||||||
|
name string
|
||||||
|
value *cli.StringSlice
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"help", func() *cli.StringSlice {
|
||||||
|
s := &cli.StringSlice{}
|
||||||
|
s.Set("")
|
||||||
|
return s
|
||||||
|
}(), "--help [--help option --help option]\t"},
|
||||||
|
{"h", func() *cli.StringSlice {
|
||||||
|
s := &cli.StringSlice{}
|
||||||
|
s.Set("")
|
||||||
|
return s
|
||||||
|
}(), "-h [-h option -h option]\t"},
|
||||||
|
{"h", func() *cli.StringSlice {
|
||||||
|
s := &cli.StringSlice{}
|
||||||
|
s.Set("")
|
||||||
|
return s
|
||||||
|
}(), "-h [-h option -h option]\t"},
|
||||||
|
{"test", func() *cli.StringSlice {
|
||||||
|
s := &cli.StringSlice{}
|
||||||
|
s.Set("Something")
|
||||||
|
return s
|
||||||
|
}(), "--test [--test option --test option]\t"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringSliceFlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
|
for _, test := range stringSliceFlagTests {
|
||||||
|
flag := cli.StringSliceFlag{Name: test.name, Value: test.value}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if output != test.expected {
|
||||||
|
t.Errorf("%q does not match %q", output, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_QWWX", "11,4")
|
||||||
|
for _, test := range stringSliceFlagTests {
|
||||||
|
flag := cli.StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if !strings.HasSuffix(output, " [$APP_QWWX]") {
|
||||||
|
t.Errorf("%q does not end with [$APP_QWWX]", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var intFlagTests = []struct {
|
||||||
|
name string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"help", "--help \"0\"\t"},
|
||||||
|
{"h", "-h \"0\"\t"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntFlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
|
for _, test := range intFlagTests {
|
||||||
|
flag := cli.IntFlag{Name: test.name}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if output != test.expected {
|
||||||
|
t.Errorf("%s does not match %s", output, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_BAR", "2")
|
||||||
|
for _, test := range intFlagTests {
|
||||||
|
flag := cli.IntFlag{Name: test.name, EnvVar: "APP_BAR"}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if !strings.HasSuffix(output, " [$APP_BAR]") {
|
||||||
|
t.Errorf("%s does not end with [$APP_BAR]", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var durationFlagTests = []struct {
|
||||||
|
name string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"help", "--help \"0\"\t"},
|
||||||
|
{"h", "-h \"0\"\t"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDurationFlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
|
for _, test := range durationFlagTests {
|
||||||
|
flag := cli.DurationFlag{Name: test.name}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if output != test.expected {
|
||||||
|
t.Errorf("%s does not match %s", output, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_BAR", "2h3m6s")
|
||||||
|
for _, test := range durationFlagTests {
|
||||||
|
flag := cli.DurationFlag{Name: test.name, EnvVar: "APP_BAR"}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if !strings.HasSuffix(output, " [$APP_BAR]") {
|
||||||
|
t.Errorf("%s does not end with [$APP_BAR]", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var intSliceFlagTests = []struct {
|
||||||
|
name string
|
||||||
|
value *cli.IntSlice
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"help", &cli.IntSlice{}, "--help [--help option --help option]\t"},
|
||||||
|
{"h", &cli.IntSlice{}, "-h [-h option -h option]\t"},
|
||||||
|
{"h", &cli.IntSlice{}, "-h [-h option -h option]\t"},
|
||||||
|
{"test", func() *cli.IntSlice {
|
||||||
|
i := &cli.IntSlice{}
|
||||||
|
i.Set("9")
|
||||||
|
return i
|
||||||
|
}(), "--test [--test option --test option]\t"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntSliceFlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
|
for _, test := range intSliceFlagTests {
|
||||||
|
flag := cli.IntSliceFlag{Name: test.name, Value: test.value}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if output != test.expected {
|
||||||
|
t.Errorf("%q does not match %q", output, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_SMURF", "42,3")
|
||||||
|
for _, test := range intSliceFlagTests {
|
||||||
|
flag := cli.IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if !strings.HasSuffix(output, " [$APP_SMURF]") {
|
||||||
|
t.Errorf("%q does not end with [$APP_SMURF]", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var float64FlagTests = []struct {
|
||||||
|
name string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"help", "--help \"0\"\t"},
|
||||||
|
{"h", "-h \"0\"\t"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloat64FlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
|
for _, test := range float64FlagTests {
|
||||||
|
flag := cli.Float64Flag{Name: test.name}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if output != test.expected {
|
||||||
|
t.Errorf("%s does not match %s", output, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_BAZ", "99.4")
|
||||||
|
for _, test := range float64FlagTests {
|
||||||
|
flag := cli.Float64Flag{Name: test.name, EnvVar: "APP_BAZ"}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if !strings.HasSuffix(output, " [$APP_BAZ]") {
|
||||||
|
t.Errorf("%s does not end with [$APP_BAZ]", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var genericFlagTests = []struct {
|
||||||
|
name string
|
||||||
|
value cli.Generic
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"test", &Parser{"abc", "def"}, "--test \"abc,def\"\ttest flag"},
|
||||||
|
{"t", &Parser{"abc", "def"}, "-t \"abc,def\"\ttest flag"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenericFlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
|
for _, test := range genericFlagTests {
|
||||||
|
flag := cli.GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if output != test.expected {
|
||||||
|
t.Errorf("%q does not match %q", output, test.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_ZAP", "3")
|
||||||
|
for _, test := range genericFlagTests {
|
||||||
|
flag := cli.GenericFlag{Name: test.name, EnvVar: "APP_ZAP"}
|
||||||
|
output := flag.String()
|
||||||
|
|
||||||
|
if !strings.HasSuffix(output, " [$APP_ZAP]") {
|
||||||
|
t.Errorf("%s does not end with [$APP_ZAP]", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiString(t *testing.T) {
|
||||||
|
(&cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{Name: "serve, s"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if ctx.String("serve") != "10" {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.String("s") != "10" {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).Run([]string{"run", "-s", "10"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiStringFromEnv(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_COUNT", "20")
|
||||||
|
(&cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{Name: "count, c", EnvVar: "APP_COUNT"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if ctx.String("count") != "20" {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.String("c") != "20" {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiStringFromEnvCascade(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_COUNT", "20")
|
||||||
|
(&cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{Name: "count, c", EnvVar: "COMPAT_COUNT,APP_COUNT"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if ctx.String("count") != "20" {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.String("c") != "20" {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiStringSlice(t *testing.T) {
|
||||||
|
(&cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringSliceFlag{Name: "serve, s", Value: &cli.StringSlice{}},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"10", "20"}) {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"10", "20"}) {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).Run([]string{"run", "-s", "10", "-s", "20"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiStringSliceFromEnv(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_INTERVALS", "20,30,40")
|
||||||
|
|
||||||
|
(&cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringSliceFlag{Name: "intervals, i", Value: &cli.StringSlice{}, EnvVar: "APP_INTERVALS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
|
||||||
|
t.Errorf("main name not set from env")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) {
|
||||||
|
t.Errorf("short name not set from env")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiStringSliceFromEnvCascade(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_INTERVALS", "20,30,40")
|
||||||
|
|
||||||
|
(&cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringSliceFlag{Name: "intervals, i", Value: &cli.StringSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
|
||||||
|
t.Errorf("main name not set from env")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) {
|
||||||
|
t.Errorf("short name not set from env")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiInt(t *testing.T) {
|
||||||
|
a := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.IntFlag{Name: "serve, s"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if ctx.Int("serve") != 10 {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.Int("s") != 10 {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run", "-s", "10"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiIntFromEnv(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_TIMEOUT_SECONDS", "10")
|
||||||
|
a := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if ctx.Int("timeout") != 10 {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.Int("t") != 10 {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiIntFromEnvCascade(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_TIMEOUT_SECONDS", "10")
|
||||||
|
a := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.IntFlag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if ctx.Int("timeout") != 10 {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.Int("t") != 10 {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiIntSlice(t *testing.T) {
|
||||||
|
(&cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.IntSliceFlag{Name: "serve, s", Value: &cli.IntSlice{}},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ctx.IntSlice("s"), []int{10, 20}) {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).Run([]string{"run", "-s", "10", "-s", "20"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiIntSliceFromEnv(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_INTERVALS", "20,30,40")
|
||||||
|
|
||||||
|
(&cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.IntSliceFlag{Name: "intervals, i", Value: &cli.IntSlice{}, EnvVar: "APP_INTERVALS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) {
|
||||||
|
t.Errorf("main name not set from env")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) {
|
||||||
|
t.Errorf("short name not set from env")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiIntSliceFromEnvCascade(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_INTERVALS", "20,30,40")
|
||||||
|
|
||||||
|
(&cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.IntSliceFlag{Name: "intervals, i", Value: &cli.IntSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) {
|
||||||
|
t.Errorf("main name not set from env")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) {
|
||||||
|
t.Errorf("short name not set from env")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiFloat64(t *testing.T) {
|
||||||
|
a := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.Float64Flag{Name: "serve, s"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if ctx.Float64("serve") != 10.2 {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.Float64("s") != 10.2 {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run", "-s", "10.2"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiFloat64FromEnv(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
|
||||||
|
a := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if ctx.Float64("timeout") != 15.5 {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.Float64("t") != 15.5 {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiFloat64FromEnvCascade(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
|
||||||
|
a := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.Float64Flag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if ctx.Float64("timeout") != 15.5 {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.Float64("t") != 15.5 {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiBool(t *testing.T) {
|
||||||
|
a := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.BoolFlag{Name: "serve, s"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if ctx.Bool("serve") != true {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.Bool("s") != true {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run", "--serve"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiBoolFromEnv(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_DEBUG", "1")
|
||||||
|
a := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if ctx.Bool("debug") != true {
|
||||||
|
t.Errorf("main name not set from env")
|
||||||
|
}
|
||||||
|
if ctx.Bool("d") != true {
|
||||||
|
t.Errorf("short name not set from env")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiBoolFromEnvCascade(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_DEBUG", "1")
|
||||||
|
a := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.BoolFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if ctx.Bool("debug") != true {
|
||||||
|
t.Errorf("main name not set from env")
|
||||||
|
}
|
||||||
|
if ctx.Bool("d") != true {
|
||||||
|
t.Errorf("short name not set from env")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiBoolT(t *testing.T) {
|
||||||
|
a := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.BoolTFlag{Name: "serve, s"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if ctx.BoolT("serve") != true {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if ctx.BoolT("s") != true {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run", "--serve"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiBoolTFromEnv(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_DEBUG", "0")
|
||||||
|
a := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if ctx.BoolT("debug") != false {
|
||||||
|
t.Errorf("main name not set from env")
|
||||||
|
}
|
||||||
|
if ctx.BoolT("d") != false {
|
||||||
|
t.Errorf("short name not set from env")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseMultiBoolTFromEnvCascade(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_DEBUG", "0")
|
||||||
|
a := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.BoolTFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if ctx.BoolT("debug") != false {
|
||||||
|
t.Errorf("main name not set from env")
|
||||||
|
}
|
||||||
|
if ctx.BoolT("d") != false {
|
||||||
|
t.Errorf("short name not set from env")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Parser [2]string
|
||||||
|
|
||||||
|
func (p *Parser) Set(value string) error {
|
||||||
|
parts := strings.Split(value, ",")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return fmt.Errorf("invalid format")
|
||||||
|
}
|
||||||
|
|
||||||
|
(*p)[0] = parts[0]
|
||||||
|
(*p)[1] = parts[1]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) String() string {
|
||||||
|
return fmt.Sprintf("%s,%s", p[0], p[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseGeneric(t *testing.T) {
|
||||||
|
a := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.GenericFlag{Name: "serve, s", Value: &Parser{}},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) {
|
||||||
|
t.Errorf("main name not set")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"10", "20"}) {
|
||||||
|
t.Errorf("short name not set")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run", "-s", "10,20"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseGenericFromEnv(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_SERVE", "20,30")
|
||||||
|
a := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"20", "30"}) {
|
||||||
|
t.Errorf("main name not set from env")
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"20", "30"}) {
|
||||||
|
t.Errorf("short name not set from env")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseGenericFromEnvCascade(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("APP_FOO", "99,2000")
|
||||||
|
a := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.GenericFlag{Name: "foos", Value: &Parser{}, EnvVar: "COMPAT_FOO,APP_FOO"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) {
|
||||||
|
t.Errorf("value not set from env")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run"})
|
||||||
|
}
|
211
Godeps/_workspace/src/github.com/codegangsta/cli/help.go
generated
vendored
Normal file
211
Godeps/_workspace/src/github.com/codegangsta/cli/help.go
generated
vendored
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// The text template for the Default help topic.
|
||||||
|
// cli.go uses text/template to render templates. You can
|
||||||
|
// render custom help text by setting this variable.
|
||||||
|
var AppHelpTemplate = `NAME:
|
||||||
|
{{.Name}} - {{.Usage}}
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
{{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...]
|
||||||
|
|
||||||
|
VERSION:
|
||||||
|
{{.Version}}{{if or .Author .Email}}
|
||||||
|
|
||||||
|
AUTHOR:{{if .Author}}
|
||||||
|
{{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}}
|
||||||
|
{{.Email}}{{end}}{{end}}
|
||||||
|
|
||||||
|
COMMANDS:
|
||||||
|
{{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
|
||||||
|
{{end}}{{if .Flags}}
|
||||||
|
GLOBAL OPTIONS:
|
||||||
|
{{range .Flags}}{{.}}
|
||||||
|
{{end}}{{end}}
|
||||||
|
`
|
||||||
|
|
||||||
|
// The text template for the command help topic.
|
||||||
|
// cli.go uses text/template to render templates. You can
|
||||||
|
// render custom help text by setting this variable.
|
||||||
|
var CommandHelpTemplate = `NAME:
|
||||||
|
{{.Name}} - {{.Usage}}
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
command {{.Name}}{{if .Flags}} [command options]{{end}} [arguments...]{{if .Description}}
|
||||||
|
|
||||||
|
DESCRIPTION:
|
||||||
|
{{.Description}}{{end}}{{if .Flags}}
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
{{range .Flags}}{{.}}
|
||||||
|
{{end}}{{ end }}
|
||||||
|
`
|
||||||
|
|
||||||
|
// The text template for the subcommand help topic.
|
||||||
|
// cli.go uses text/template to render templates. You can
|
||||||
|
// render custom help text by setting this variable.
|
||||||
|
var SubcommandHelpTemplate = `NAME:
|
||||||
|
{{.Name}} - {{.Usage}}
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
{{.Name}} command{{if .Flags}} [command options]{{end}} [arguments...]
|
||||||
|
|
||||||
|
COMMANDS:
|
||||||
|
{{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
|
||||||
|
{{end}}{{if .Flags}}
|
||||||
|
OPTIONS:
|
||||||
|
{{range .Flags}}{{.}}
|
||||||
|
{{end}}{{end}}
|
||||||
|
`
|
||||||
|
|
||||||
|
var helpCommand = Command{
|
||||||
|
Name: "help",
|
||||||
|
ShortName: "h",
|
||||||
|
Usage: "Shows a list of commands or help for one command",
|
||||||
|
Action: func(c *Context) {
|
||||||
|
args := c.Args()
|
||||||
|
if args.Present() {
|
||||||
|
ShowCommandHelp(c, args.First())
|
||||||
|
} else {
|
||||||
|
ShowAppHelp(c)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var helpSubcommand = Command{
|
||||||
|
Name: "help",
|
||||||
|
ShortName: "h",
|
||||||
|
Usage: "Shows a list of commands or help for one command",
|
||||||
|
Action: func(c *Context) {
|
||||||
|
args := c.Args()
|
||||||
|
if args.Present() {
|
||||||
|
ShowCommandHelp(c, args.First())
|
||||||
|
} else {
|
||||||
|
ShowSubcommandHelp(c)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prints help for the App
|
||||||
|
type helpPrinter func(templ string, data interface{})
|
||||||
|
|
||||||
|
var HelpPrinter helpPrinter = nil
|
||||||
|
|
||||||
|
// Prints version for the App
|
||||||
|
var VersionPrinter = printVersion
|
||||||
|
|
||||||
|
func ShowAppHelp(c *Context) {
|
||||||
|
HelpPrinter(AppHelpTemplate, c.App)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prints the list of subcommands as the default app completion method
|
||||||
|
func DefaultAppComplete(c *Context) {
|
||||||
|
for _, command := range c.App.Commands {
|
||||||
|
fmt.Fprintln(c.App.Writer, command.Name)
|
||||||
|
if command.ShortName != "" {
|
||||||
|
fmt.Fprintln(c.App.Writer, command.ShortName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prints help for the given command
|
||||||
|
func ShowCommandHelp(c *Context, command string) {
|
||||||
|
for _, c := range c.App.Commands {
|
||||||
|
if c.HasName(command) {
|
||||||
|
HelpPrinter(CommandHelpTemplate, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.App.CommandNotFound != nil {
|
||||||
|
c.App.CommandNotFound(c, command)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(c.App.Writer, "No help topic for '%v'\n", command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prints help for the given subcommand
|
||||||
|
func ShowSubcommandHelp(c *Context) {
|
||||||
|
ShowCommandHelp(c, c.Command.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prints the version number of the App
|
||||||
|
func ShowVersion(c *Context) {
|
||||||
|
VersionPrinter(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printVersion(c *Context) {
|
||||||
|
fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prints the lists of commands within a given context
|
||||||
|
func ShowCompletions(c *Context) {
|
||||||
|
a := c.App
|
||||||
|
if a != nil && a.BashComplete != nil {
|
||||||
|
a.BashComplete(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prints the custom completions for a given command
|
||||||
|
func ShowCommandCompletions(ctx *Context, command string) {
|
||||||
|
c := ctx.App.Command(command)
|
||||||
|
if c != nil && c.BashComplete != nil {
|
||||||
|
c.BashComplete(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkVersion(c *Context) bool {
|
||||||
|
if c.GlobalBool("version") {
|
||||||
|
ShowVersion(c)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkHelp(c *Context) bool {
|
||||||
|
if c.GlobalBool("h") || c.GlobalBool("help") {
|
||||||
|
ShowAppHelp(c)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkCommandHelp(c *Context, name string) bool {
|
||||||
|
if c.Bool("h") || c.Bool("help") {
|
||||||
|
ShowCommandHelp(c, name)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkSubcommandHelp(c *Context) bool {
|
||||||
|
if c.GlobalBool("h") || c.GlobalBool("help") {
|
||||||
|
ShowSubcommandHelp(c)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkCompletions(c *Context) bool {
|
||||||
|
if (c.GlobalBool(BashCompletionFlag.Name) || c.Bool(BashCompletionFlag.Name)) && c.App.EnableBashCompletion {
|
||||||
|
ShowCompletions(c)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkCommandCompletions(c *Context, name string) bool {
|
||||||
|
if c.Bool(BashCompletionFlag.Name) && c.App.EnableBashCompletion {
|
||||||
|
ShowCommandCompletions(c, name)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
19
Godeps/_workspace/src/github.com/codegangsta/cli/helpers_test.go
generated
vendored
Normal file
19
Godeps/_workspace/src/github.com/codegangsta/cli/helpers_test.go
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package cli_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Test Helpers */
|
||||||
|
func expect(t *testing.T, a interface{}, b interface{}) {
|
||||||
|
if a != b {
|
||||||
|
t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func refute(t *testing.T, a interface{}, b interface{}) {
|
||||||
|
if a == b {
|
||||||
|
t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
|
||||||
|
}
|
||||||
|
}
|
40
Godeps/_workspace/src/github.com/docker/docker/pkg/tarsum/tarsum.go
generated
vendored
40
Godeps/_workspace/src/github.com/docker/docker/pkg/tarsum/tarsum.go
generated
vendored
|
@ -3,8 +3,11 @@ package tarsum
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
"crypto"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -39,6 +42,30 @@ func NewTarSumHash(r io.Reader, dc bool, v Version, tHash THash) (TarSum, error)
|
||||||
return ts, err
|
return ts, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a new TarSum using the provided TarSum version+hash label.
|
||||||
|
func NewTarSumForLabel(r io.Reader, disableCompression bool, label string) (TarSum, error) {
|
||||||
|
parts := strings.SplitN(label, "+", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return nil, errors.New("tarsum label string should be of the form: {tarsum_version}+{hash_name}")
|
||||||
|
}
|
||||||
|
|
||||||
|
versionName, hashName := parts[0], parts[1]
|
||||||
|
|
||||||
|
version, ok := tarSumVersionsByName[versionName]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unknown TarSum version name: %q", versionName)
|
||||||
|
}
|
||||||
|
|
||||||
|
hashConfig, ok := standardHashConfigs[hashName]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unknown TarSum hash name: %q", hashName)
|
||||||
|
}
|
||||||
|
|
||||||
|
tHash := NewTHash(hashConfig.name, hashConfig.hash.New)
|
||||||
|
|
||||||
|
return NewTarSumHash(r, disableCompression, version, tHash)
|
||||||
|
}
|
||||||
|
|
||||||
// TarSum is the generic interface for calculating fixed time
|
// TarSum is the generic interface for calculating fixed time
|
||||||
// checksums of a tar archive
|
// checksums of a tar archive
|
||||||
type TarSum interface {
|
type TarSum interface {
|
||||||
|
@ -89,6 +116,19 @@ func NewTHash(name string, h func() hash.Hash) THash {
|
||||||
return simpleTHash{n: name, h: h}
|
return simpleTHash{n: name, h: h}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type tHashConfig struct {
|
||||||
|
name string
|
||||||
|
hash crypto.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// NOTE: DO NOT include MD5 or SHA1, which are considered insecure.
|
||||||
|
standardHashConfigs = map[string]tHashConfig{
|
||||||
|
"sha256": {name: "sha256", hash: crypto.SHA256},
|
||||||
|
"sha512": {name: "sha512", hash: crypto.SHA512},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// TarSum default is "sha256"
|
// TarSum default is "sha256"
|
||||||
var DefaultTHash = NewTHash("sha256", sha256.New)
|
var DefaultTHash = NewTHash("sha256", sha256.New)
|
||||||
|
|
||||||
|
|
29
Godeps/_workspace/src/github.com/docker/docker/pkg/tarsum/versioning.go
generated
vendored
29
Godeps/_workspace/src/github.com/docker/docker/pkg/tarsum/versioning.go
generated
vendored
|
@ -22,6 +22,18 @@ const (
|
||||||
VersionDev
|
VersionDev
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// VersionLabelForChecksum returns the label for the given tarsum
|
||||||
|
// checksum, i.e., everything before the first `+` character in
|
||||||
|
// the string or an empty string if no label separator is found.
|
||||||
|
func VersionLabelForChecksum(checksum string) string {
|
||||||
|
// Checksums are in the form: {versionLabel}+{hashID}:{hex}
|
||||||
|
sepIndex := strings.Index(checksum, "+")
|
||||||
|
if sepIndex < 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return checksum[:sepIndex]
|
||||||
|
}
|
||||||
|
|
||||||
// Get a list of all known tarsum Version
|
// Get a list of all known tarsum Version
|
||||||
func GetVersions() []Version {
|
func GetVersions() []Version {
|
||||||
v := []Version{}
|
v := []Version{}
|
||||||
|
@ -31,11 +43,18 @@ func GetVersions() []Version {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
var tarSumVersions = map[Version]string{
|
var (
|
||||||
Version0: "tarsum",
|
tarSumVersions = map[Version]string{
|
||||||
Version1: "tarsum.v1",
|
Version0: "tarsum",
|
||||||
VersionDev: "tarsum.dev",
|
Version1: "tarsum.v1",
|
||||||
}
|
VersionDev: "tarsum.dev",
|
||||||
|
}
|
||||||
|
tarSumVersionsByName = map[string]Version{
|
||||||
|
"tarsum": Version0,
|
||||||
|
"tarsum.v1": Version1,
|
||||||
|
"tarsum.dev": VersionDev,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func (tsv Version) String() string {
|
func (tsv Version) String() string {
|
||||||
return tarSumVersions[tsv]
|
return tarSumVersions[tsv]
|
||||||
|
|
6
Godeps/_workspace/src/gopkg.in/yaml.v2/decode_test.go
generated
vendored
6
Godeps/_workspace/src/gopkg.in/yaml.v2/decode_test.go
generated
vendored
|
@ -540,6 +540,12 @@ var unmarshalTests = []struct {
|
||||||
"a: 1.2.3.4\n",
|
"a: 1.2.3.4\n",
|
||||||
map[string]net.IP{"a": net.IPv4(1, 2, 3, 4)},
|
map[string]net.IP{"a": net.IPv4(1, 2, 3, 4)},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Encode empty lists as zero-length slices.
|
||||||
|
{
|
||||||
|
"a: []",
|
||||||
|
&struct{ A []int }{[]int{}},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
type M map[interface{}]interface{}
|
type M map[interface{}]interface{}
|
||||||
|
|
3
Godeps/_workspace/src/gopkg.in/yaml.v2/encode.go
generated
vendored
3
Godeps/_workspace/src/gopkg.in/yaml.v2/encode.go
generated
vendored
|
@ -74,8 +74,7 @@ func (e *encoder) marshal(tag string, in reflect.Value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
in = reflect.ValueOf(v)
|
in = reflect.ValueOf(v)
|
||||||
}
|
} else if m, ok := iface.(encoding.TextMarshaler); ok {
|
||||||
if m, ok := iface.(encoding.TextMarshaler); ok {
|
|
||||||
text, err := m.MarshalText()
|
text, err := m.MarshalText()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fail(err)
|
fail(err)
|
||||||
|
|
4
Godeps/_workspace/src/gopkg.in/yaml.v2/encode_test.go
generated
vendored
4
Godeps/_workspace/src/gopkg.in/yaml.v2/encode_test.go
generated
vendored
|
@ -356,6 +356,10 @@ type marshalerType struct {
|
||||||
value interface{}
|
value interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o marshalerType) MarshalText() ([]byte, error) {
|
||||||
|
panic("MarshalText called on type with MarshalYAML")
|
||||||
|
}
|
||||||
|
|
||||||
func (o marshalerType) MarshalYAML() (interface{}, error) {
|
func (o marshalerType) MarshalYAML() (interface{}, error) {
|
||||||
return o.value, nil
|
return o.value, nil
|
||||||
}
|
}
|
||||||
|
|
34
Makefile
34
Makefile
|
@ -16,34 +16,48 @@ version/version.go:
|
||||||
./version/version.sh > $@
|
./version/version.sh > $@
|
||||||
|
|
||||||
${PREFIX}/bin/registry: version/version.go $(shell find . -type f -name '*.go')
|
${PREFIX}/bin/registry: version/version.go $(shell find . -type f -name '*.go')
|
||||||
go build -o $@ ${GO_LDFLAGS} ./cmd/registry
|
@echo "+ $@"
|
||||||
|
@go build -o $@ ${GO_LDFLAGS} ./cmd/registry
|
||||||
|
|
||||||
${PREFIX}/bin/registry-api-descriptor-template: version/version.go $(shell find . -type f -name '*.go')
|
${PREFIX}/bin/registry-api-descriptor-template: version/version.go $(shell find . -type f -name '*.go')
|
||||||
go build -o $@ ${GO_LDFLAGS} ./cmd/registry-api-descriptor-template
|
@echo "+ $@"
|
||||||
|
@go build -o $@ ${GO_LDFLAGS} ./cmd/registry-api-descriptor-template
|
||||||
|
|
||||||
|
${PREFIX}/bin/dist: version/version.go $(shell find . -type f -name '*.go')
|
||||||
|
@echo "+ $@"
|
||||||
|
@go build -o $@ ${GO_LDFLAGS} ./cmd/dist
|
||||||
|
|
||||||
doc/spec/api.md: doc/spec/api.md.tmpl ${PREFIX}/bin/registry-api-descriptor-template
|
doc/spec/api.md: doc/spec/api.md.tmpl ${PREFIX}/bin/registry-api-descriptor-template
|
||||||
./bin/registry-api-descriptor-template $< > $@
|
./bin/registry-api-descriptor-template $< > $@
|
||||||
|
|
||||||
vet:
|
vet:
|
||||||
go vet ./...
|
@echo "+ $@"
|
||||||
|
@go vet ./...
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
test -z "$$(gofmt -s -l . | grep -v Godeps/_workspace/src/ | tee /dev/stderr)" || \
|
@echo "+ $@"
|
||||||
|
@test -z "$$(gofmt -s -l . | grep -v Godeps/_workspace/src/ | tee /dev/stderr)" || \
|
||||||
echo "+ please format Go code with 'gofmt -s'"
|
echo "+ please format Go code with 'gofmt -s'"
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
test -z "$$(golint ./... | grep -v Godeps/_workspace/src/ | tee /dev/stderr)"
|
@echo "+ $@"
|
||||||
|
@test -z "$$(golint ./... | grep -v Godeps/_workspace/src/ | tee /dev/stderr)"
|
||||||
|
|
||||||
build:
|
build:
|
||||||
go build ${GO_LDFLAGS} ./...
|
@echo "+ $@"
|
||||||
|
@go build -v ${GO_LDFLAGS} ./...
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test -test.short ./...
|
@echo "+ $@"
|
||||||
|
@go test -test.short ./...
|
||||||
|
|
||||||
test-full:
|
test-full:
|
||||||
go test ./...
|
@echo "+ $@"
|
||||||
|
@go test ./...
|
||||||
|
|
||||||
binaries: ${PREFIX}/bin/registry ${PREFIX}/bin/registry-api-descriptor-template
|
binaries: ${PREFIX}/bin/registry ${PREFIX}/bin/registry-api-descriptor-template ${PREFIX}/bin/dist
|
||||||
|
@echo "+ $@"
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf "${PREFIX}/bin/registry"
|
@echo "+ $@"
|
||||||
|
@rm -rf "${PREFIX}/bin/registry" "${PREFIX}/bin/registry-api-descriptor-template"
|
||||||
|
|
|
@ -123,7 +123,7 @@ func (d Digest) Validate() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch s[:i] {
|
switch s[:i] {
|
||||||
case "md5", "sha1", "sha256":
|
case "sha256", "sha384", "sha512":
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
return ErrDigestUnsupported
|
return ErrDigestUnsupported
|
||||||
|
|
|
@ -34,9 +34,9 @@ func TestParseDigest(t *testing.T) {
|
||||||
hex: "e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b",
|
hex: "e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "md5:d41d8cd98f00b204e9800998ecf8427e",
|
input: "sha384:d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",
|
||||||
algorithm: "md5",
|
algorithm: "sha384",
|
||||||
hex: "d41d8cd98f00b204e9800998ecf8427e",
|
hex: "d3fc7881460b7e22e3d172954463dddd7866d17597e7248453c48b3e9d26d9596bf9c4a9cf8072c9d5bad76e19af801d",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// empty hex
|
// empty hex
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
package digest
|
package digest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
|
||||||
"crypto/sha1"
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -32,7 +31,7 @@ type Verifier interface {
|
||||||
func NewDigestVerifier(d Digest) Verifier {
|
func NewDigestVerifier(d Digest) Verifier {
|
||||||
alg := d.Algorithm()
|
alg := d.Algorithm()
|
||||||
switch alg {
|
switch alg {
|
||||||
case "md5", "sha1", "sha256":
|
case "sha256", "sha384", "sha512":
|
||||||
return hashVerifier{
|
return hashVerifier{
|
||||||
hash: newHash(alg),
|
hash: newHash(alg),
|
||||||
digest: d,
|
digest: d,
|
||||||
|
@ -97,10 +96,10 @@ func newHash(name string) hash.Hash {
|
||||||
switch name {
|
switch name {
|
||||||
case "sha256":
|
case "sha256":
|
||||||
return sha256.New()
|
return sha256.New()
|
||||||
case "sha1":
|
case "sha384":
|
||||||
return sha1.New()
|
return sha512.New384()
|
||||||
case "md5":
|
case "sha512":
|
||||||
return md5.New()
|
return sha512.New()
|
||||||
default:
|
default:
|
||||||
panic("unsupport algorithm: " + name)
|
panic("unsupport algorithm: " + name)
|
||||||
}
|
}
|
||||||
|
|
109
errors.go
Normal file
109
errors.go
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
package distribution
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/digest"
|
||||||
|
"github.com/docker/distribution/manifest"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrLayerExists returned when layer already exists
|
||||||
|
ErrLayerExists = fmt.Errorf("layer exists")
|
||||||
|
|
||||||
|
// ErrLayerTarSumVersionUnsupported when tarsum is unsupported version.
|
||||||
|
ErrLayerTarSumVersionUnsupported = fmt.Errorf("unsupported tarsum version")
|
||||||
|
|
||||||
|
// ErrLayerUploadUnknown returned when upload is not found.
|
||||||
|
ErrLayerUploadUnknown = fmt.Errorf("layer upload unknown")
|
||||||
|
|
||||||
|
// ErrLayerClosed returned when an operation is attempted on a closed
|
||||||
|
// Layer or LayerUpload.
|
||||||
|
ErrLayerClosed = fmt.Errorf("layer closed")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrRepositoryUnknown is returned if the named repository is not known by
|
||||||
|
// the registry.
|
||||||
|
type ErrRepositoryUnknown struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrRepositoryUnknown) Error() string {
|
||||||
|
return fmt.Sprintf("unknown respository name=%s", err.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrRepositoryNameInvalid should be used to denote an invalid repository
|
||||||
|
// name. Reason may set, indicating the cause of invalidity.
|
||||||
|
type ErrRepositoryNameInvalid struct {
|
||||||
|
Name string
|
||||||
|
Reason error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrRepositoryNameInvalid) Error() string {
|
||||||
|
return fmt.Sprintf("repository name %q invalid: %v", err.Name, err.Reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrManifestUnknown is returned if the manifest is not known by the
|
||||||
|
// registry.
|
||||||
|
type ErrManifestUnknown struct {
|
||||||
|
Name string
|
||||||
|
Tag string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrManifestUnknown) Error() string {
|
||||||
|
return fmt.Sprintf("unknown manifest name=%s tag=%s", err.Name, err.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrUnknownManifestRevision is returned when a manifest cannot be found by
|
||||||
|
// revision within a repository.
|
||||||
|
type ErrUnknownManifestRevision struct {
|
||||||
|
Name string
|
||||||
|
Revision digest.Digest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrUnknownManifestRevision) Error() string {
|
||||||
|
return fmt.Sprintf("unknown manifest name=%s revision=%s", err.Name, err.Revision)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrManifestUnverified is returned when the registry is unable to verify
|
||||||
|
// the manifest.
|
||||||
|
type ErrManifestUnverified struct{}
|
||||||
|
|
||||||
|
func (ErrManifestUnverified) Error() string {
|
||||||
|
return fmt.Sprintf("unverified manifest")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrManifestVerification provides a type to collect errors encountered
|
||||||
|
// during manifest verification. Currently, it accepts errors of all types,
|
||||||
|
// but it may be narrowed to those involving manifest verification.
|
||||||
|
type ErrManifestVerification []error
|
||||||
|
|
||||||
|
func (errs ErrManifestVerification) Error() string {
|
||||||
|
var parts []string
|
||||||
|
for _, err := range errs {
|
||||||
|
parts = append(parts, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("errors verifying manifest: %v", strings.Join(parts, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrUnknownLayer returned when layer cannot be found.
|
||||||
|
type ErrUnknownLayer struct {
|
||||||
|
FSLayer manifest.FSLayer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrUnknownLayer) Error() string {
|
||||||
|
return fmt.Sprintf("unknown layer %v", err.FSLayer.BlobSum)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrLayerInvalidDigest returned when tarsum check fails.
|
||||||
|
type ErrLayerInvalidDigest struct {
|
||||||
|
Digest digest.Digest
|
||||||
|
Reason error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrLayerInvalidDigest) Error() string {
|
||||||
|
return fmt.Sprintf("invalid digest for referenced layer: %v, %v",
|
||||||
|
err.Digest, err.Reason)
|
||||||
|
}
|
84
layer.go
84
layer.go
|
@ -1,84 +0,0 @@
|
||||||
package distribution
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/distribution/digest"
|
|
||||||
"github.com/docker/distribution/manifest"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Layer provides a readable and seekable layer object. Typically,
|
|
||||||
// implementations are *not* goroutine safe.
|
|
||||||
type Layer interface {
|
|
||||||
// http.ServeContent requires an efficient implementation of
|
|
||||||
// ReadSeeker.Seek(0, os.SEEK_END).
|
|
||||||
io.ReadSeeker
|
|
||||||
io.Closer
|
|
||||||
|
|
||||||
// Digest returns the unique digest of the blob, which is the tarsum for
|
|
||||||
// layers.
|
|
||||||
Digest() digest.Digest
|
|
||||||
|
|
||||||
// CreatedAt returns the time this layer was created.
|
|
||||||
CreatedAt() time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// LayerUpload provides a handle for working with in-progress uploads.
|
|
||||||
// Instances can be obtained from the LayerService.Upload and
|
|
||||||
// LayerService.Resume.
|
|
||||||
type LayerUpload interface {
|
|
||||||
io.WriteSeeker
|
|
||||||
io.ReaderFrom
|
|
||||||
io.Closer
|
|
||||||
|
|
||||||
// UUID returns the identifier for this upload.
|
|
||||||
UUID() string
|
|
||||||
|
|
||||||
// StartedAt returns the time this layer upload was started.
|
|
||||||
StartedAt() time.Time
|
|
||||||
|
|
||||||
// Finish marks the upload as completed, returning a valid handle to the
|
|
||||||
// uploaded layer. The digest is validated against the contents of the
|
|
||||||
// uploaded layer.
|
|
||||||
Finish(digest digest.Digest) (Layer, error)
|
|
||||||
|
|
||||||
// Cancel the layer upload process.
|
|
||||||
Cancel() error
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrLayerExists returned when layer already exists
|
|
||||||
ErrLayerExists = fmt.Errorf("layer exists")
|
|
||||||
|
|
||||||
// ErrLayerTarSumVersionUnsupported when tarsum is unsupported version.
|
|
||||||
ErrLayerTarSumVersionUnsupported = fmt.Errorf("unsupported tarsum version")
|
|
||||||
|
|
||||||
// ErrLayerUploadUnknown returned when upload is not found.
|
|
||||||
ErrLayerUploadUnknown = fmt.Errorf("layer upload unknown")
|
|
||||||
|
|
||||||
// ErrLayerClosed returned when an operation is attempted on a closed
|
|
||||||
// Layer or LayerUpload.
|
|
||||||
ErrLayerClosed = fmt.Errorf("layer closed")
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrUnknownLayer returned when layer cannot be found.
|
|
||||||
type ErrUnknownLayer struct {
|
|
||||||
FSLayer manifest.FSLayer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrUnknownLayer) Error() string {
|
|
||||||
return fmt.Sprintf("unknown layer %v", err.FSLayer.BlobSum)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrLayerInvalidDigest returned when tarsum check fails.
|
|
||||||
type ErrLayerInvalidDigest struct {
|
|
||||||
Digest digest.Digest
|
|
||||||
Reason error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrLayerInvalidDigest) Error() string {
|
|
||||||
return fmt.Sprintf("invalid digest for referenced layer: %v, %v",
|
|
||||||
err.Digest, err.Reason)
|
|
||||||
}
|
|
|
@ -7,6 +7,16 @@ import (
|
||||||
"github.com/docker/libtrust"
|
"github.com/docker/libtrust"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO(stevvooe): When we rev the manifest format, the contents of this
|
||||||
|
// package should me moved to manifest/v1.
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ManifestMediaType specifies the mediaType for the current version. Note
|
||||||
|
// that for schema version 1, the the media is optionally
|
||||||
|
// "application/json".
|
||||||
|
ManifestMediaType = "application/vnd.docker.distribution.manifest.v1+json"
|
||||||
|
)
|
||||||
|
|
||||||
// Versioned provides a struct with just the manifest schemaVersion. Incoming
|
// Versioned provides a struct with just the manifest schemaVersion. Incoming
|
||||||
// content with unknown schema version can be decoded against this struct to
|
// content with unknown schema version can be decoded against this struct to
|
||||||
// check the version.
|
// check the version.
|
||||||
|
|
|
@ -21,7 +21,11 @@ func TestListener(t *testing.T) {
|
||||||
ops: make(map[string]int),
|
ops: make(map[string]int),
|
||||||
}
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
repository := Listen(registry.Repository(ctx, "foo/bar"), tl)
|
repository, err := registry.Repository(ctx, "foo/bar")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error getting repo: %v", err)
|
||||||
|
}
|
||||||
|
repository = Listen(repository, tl)
|
||||||
|
|
||||||
// Now take the registry through a number of operations
|
// Now take the registry through a number of operations
|
||||||
checkExerciseRepository(t, repository)
|
checkExerciseRepository(t, repository)
|
||||||
|
|
46
registry.go
46
registry.go
|
@ -1,19 +1,20 @@
|
||||||
package distribution
|
package distribution
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO(stevvooe): These types need to be moved out of the storage package.
|
|
||||||
|
|
||||||
// Registry represents a collection of repositories, addressable by name.
|
// Registry represents a collection of repositories, addressable by name.
|
||||||
type Registry interface {
|
type Registry interface {
|
||||||
// Repository should return a reference to the named repository. The
|
// Repository should return a reference to the named repository. The
|
||||||
// registry may or may not have the repository but should always return a
|
// registry may or may not have the repository but should always return a
|
||||||
// reference.
|
// reference.
|
||||||
Repository(ctx context.Context, name string) Repository
|
Repository(ctx context.Context, name string) (Repository, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repository is a named collection of manifests and layers.
|
// Repository is a named collection of manifests and layers.
|
||||||
|
@ -82,3 +83,42 @@ type LayerService interface {
|
||||||
// before proceeding.
|
// before proceeding.
|
||||||
Resume(uuid string) (LayerUpload, error)
|
Resume(uuid string) (LayerUpload, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Layer provides a readable and seekable layer object. Typically,
|
||||||
|
// implementations are *not* goroutine safe.
|
||||||
|
type Layer interface {
|
||||||
|
// http.ServeContent requires an efficient implementation of
|
||||||
|
// ReadSeeker.Seek(0, os.SEEK_END).
|
||||||
|
io.ReadSeeker
|
||||||
|
io.Closer
|
||||||
|
|
||||||
|
// Digest returns the unique digest of the blob, which is the tarsum for
|
||||||
|
// layers.
|
||||||
|
Digest() digest.Digest
|
||||||
|
|
||||||
|
// CreatedAt returns the time this layer was created.
|
||||||
|
CreatedAt() time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// LayerUpload provides a handle for working with in-progress uploads.
|
||||||
|
// Instances can be obtained from the LayerService.Upload and
|
||||||
|
// LayerService.Resume.
|
||||||
|
type LayerUpload interface {
|
||||||
|
io.WriteSeeker
|
||||||
|
io.ReaderFrom
|
||||||
|
io.Closer
|
||||||
|
|
||||||
|
// UUID returns the identifier for this upload.
|
||||||
|
UUID() string
|
||||||
|
|
||||||
|
// StartedAt returns the time this layer upload was started.
|
||||||
|
StartedAt() time.Time
|
||||||
|
|
||||||
|
// Finish marks the upload as completed, returning a valid handle to the
|
||||||
|
// uploaded layer. The digest is validated against the contents of the
|
||||||
|
// uploaded layer.
|
||||||
|
Finish(digest digest.Digest) (Layer, error)
|
||||||
|
|
||||||
|
// Cancel the layer upload process.
|
||||||
|
Cancel() error
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO(stevvooe): Move these definitions back to an exported package. While
|
||||||
|
// they are used with v2 definitions, their relevance expands beyond.
|
||||||
|
// "distribution/names" is a candidate package.
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// RepositoryNameComponentMinLength is the minimum number of characters in a
|
// RepositoryNameComponentMinLength is the minimum number of characters in a
|
||||||
// single repository name slash-delimited component
|
// single repository name slash-delimited component
|
||||||
|
@ -37,10 +41,6 @@ var RepositoryNameComponentRegexp = regexp.MustCompile(`[a-z0-9]+(?:[._-][a-z0-9
|
||||||
// RepositoryNameComponentRegexp which must completely match the content
|
// RepositoryNameComponentRegexp which must completely match the content
|
||||||
var RepositoryNameComponentAnchoredRegexp = regexp.MustCompile(`^` + RepositoryNameComponentRegexp.String() + `$`)
|
var RepositoryNameComponentAnchoredRegexp = regexp.MustCompile(`^` + RepositoryNameComponentRegexp.String() + `$`)
|
||||||
|
|
||||||
// TODO(stevvooe): RepositoryName needs to be limited to some fixed length.
|
|
||||||
// Looking path prefixes and s3 limitation of 1024, this should likely be
|
|
||||||
// around 512 bytes. 256 bytes might be more manageable.
|
|
||||||
|
|
||||||
// RepositoryNameRegexp builds on RepositoryNameComponentRegexp to allow 1 to
|
// RepositoryNameRegexp builds on RepositoryNameComponentRegexp to allow 1 to
|
||||||
// 5 path components, separated by a forward slash.
|
// 5 path components, separated by a forward slash.
|
||||||
var RepositoryNameRegexp = regexp.MustCompile(`(?:` + RepositoryNameComponentRegexp.String() + `/){0,4}` + RepositoryNameComponentRegexp.String())
|
var RepositoryNameRegexp = regexp.MustCompile(`(?:` + RepositoryNameComponentRegexp.String() + `/){0,4}` + RepositoryNameComponentRegexp.String())
|
||||||
|
|
|
@ -146,7 +146,7 @@ type tokenAccessOptions struct {
|
||||||
func checkOptions(options map[string]interface{}) (tokenAccessOptions, error) {
|
func checkOptions(options map[string]interface{}) (tokenAccessOptions, error) {
|
||||||
var opts tokenAccessOptions
|
var opts tokenAccessOptions
|
||||||
|
|
||||||
keys := []string{"realm", "issuer", "service", "rootCertBundle"}
|
keys := []string{"realm", "issuer", "service", "rootcertbundle"}
|
||||||
vals := make([]string, 0, len(keys))
|
vals := make([]string, 0, len(keys))
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
val, ok := options[key].(string)
|
val, ok := options[key].(string)
|
||||||
|
|
|
@ -261,7 +261,7 @@ func TestAccessController(t *testing.T) {
|
||||||
"realm": realm,
|
"realm": realm,
|
||||||
"issuer": issuer,
|
"issuer": issuer,
|
||||||
"service": service,
|
"service": service,
|
||||||
"rootCertBundle": rootCertBundleFilename,
|
"rootcertbundle": rootCertBundleFilename,
|
||||||
}
|
}
|
||||||
|
|
||||||
accessController, err := newAccessController(options)
|
accessController, err := newAccessController(options)
|
||||||
|
|
|
@ -227,10 +227,30 @@ func (app *App) dispatcher(dispatch dispatchFunc) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// decorate the authorized repository with an event bridge.
|
if app.nameRequired(r) {
|
||||||
context.Repository = notifications.Listen(
|
repository, err := app.registry.Repository(context, getName(context))
|
||||||
app.registry.Repository(context, getName(context)),
|
|
||||||
app.eventBridge(context, r))
|
if err != nil {
|
||||||
|
ctxu.GetLogger(context).Errorf("error resolving repository: %v", err)
|
||||||
|
|
||||||
|
switch err := err.(type) {
|
||||||
|
case distribution.ErrRepositoryUnknown:
|
||||||
|
context.Errors.Push(v2.ErrorCodeNameUnknown, err)
|
||||||
|
case distribution.ErrRepositoryNameInvalid:
|
||||||
|
context.Errors.Push(v2.ErrorCodeNameInvalid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
serveJSON(w, context.Errors)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// assign and decorate the authorized repository with an event bridge.
|
||||||
|
context.Repository = notifications.Listen(
|
||||||
|
repository,
|
||||||
|
app.eventBridge(context, r))
|
||||||
|
}
|
||||||
|
|
||||||
handler := dispatch(context, r)
|
handler := dispatch(context, r)
|
||||||
|
|
||||||
ssrw := &singleStatusResponseWriter{ResponseWriter: w}
|
ssrw := &singleStatusResponseWriter{ResponseWriter: w}
|
||||||
|
@ -318,9 +338,7 @@ func (app *App) authorized(w http.ResponseWriter, r *http.Request, context *Cont
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Only allow the name not to be set on the base route.
|
// Only allow the name not to be set on the base route.
|
||||||
route := mux.CurrentRoute(r)
|
if app.nameRequired(r) {
|
||||||
|
|
||||||
if route == nil || route.GetName() != v2.RouteNameBase {
|
|
||||||
// For this to be properly secured, repo must always be set for a
|
// For this to be properly secured, repo must always be set for a
|
||||||
// resource that may make a modification. The only condition under
|
// resource that may make a modification. The only condition under
|
||||||
// which name is not set and we still allow access is when the
|
// which name is not set and we still allow access is when the
|
||||||
|
@ -378,6 +396,12 @@ func (app *App) eventBridge(ctx *Context, r *http.Request) notifications.Listene
|
||||||
return notifications.NewBridge(ctx.urlBuilder, app.events.source, actor, request, app.events.sink)
|
return notifications.NewBridge(ctx.urlBuilder, app.events.source, actor, request, app.events.sink)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nameRequired returns true if the route requires a name.
|
||||||
|
func (app *App) nameRequired(r *http.Request) bool {
|
||||||
|
route := mux.CurrentRoute(r)
|
||||||
|
return route == nil || route.GetName() != v2.RouteNameBase
|
||||||
|
}
|
||||||
|
|
||||||
// apiBase implements a simple yes-man for doing overall checks against the
|
// apiBase implements a simple yes-man for doing overall checks against the
|
||||||
// api. This can support auth roundtrips to support docker login.
|
// api. This can support auth roundtrips to support docker login.
|
||||||
func apiBase(w http.ResponseWriter, r *http.Request) {
|
func apiBase(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
"github.com/docker/distribution/registry/api/v2"
|
"github.com/docker/distribution/registry/api/v2"
|
||||||
"github.com/docker/distribution/registry/storage"
|
|
||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -70,12 +69,12 @@ func (imh *imageManifestHandler) PutImageManifest(w http.ResponseWriter, r *http
|
||||||
// TODO(stevvooe): These error handling switches really need to be
|
// TODO(stevvooe): These error handling switches really need to be
|
||||||
// handled by an app global mapper.
|
// handled by an app global mapper.
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case storage.ErrManifestVerification:
|
case distribution.ErrManifestVerification:
|
||||||
for _, verificationError := range err {
|
for _, verificationError := range err {
|
||||||
switch verificationError := verificationError.(type) {
|
switch verificationError := verificationError.(type) {
|
||||||
case distribution.ErrUnknownLayer:
|
case distribution.ErrUnknownLayer:
|
||||||
imh.Errors.Push(v2.ErrorCodeBlobUnknown, verificationError.FSLayer)
|
imh.Errors.Push(v2.ErrorCodeBlobUnknown, verificationError.FSLayer)
|
||||||
case storage.ErrManifestUnverified:
|
case distribution.ErrManifestUnverified:
|
||||||
imh.Errors.Push(v2.ErrorCodeManifestUnverified)
|
imh.Errors.Push(v2.ErrorCodeManifestUnverified)
|
||||||
default:
|
default:
|
||||||
if verificationError == digest.ErrDigestInvalidFormat {
|
if verificationError == digest.ErrDigestInvalidFormat {
|
||||||
|
@ -104,7 +103,7 @@ func (imh *imageManifestHandler) DeleteImageManifest(w http.ResponseWriter, r *h
|
||||||
manifests := imh.Repository.Manifests()
|
manifests := imh.Repository.Manifests()
|
||||||
if err := manifests.Delete(imh.Tag); err != nil {
|
if err := manifests.Delete(imh.Tag); err != nil {
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case storage.ErrUnknownManifest:
|
case distribution.ErrManifestUnknown:
|
||||||
imh.Errors.Push(v2.ErrorCodeManifestUnknown, err)
|
imh.Errors.Push(v2.ErrorCodeManifestUnknown, err)
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -174,7 +174,7 @@ func (luh *layerUploadHandler) PutLayerUploadComplete(w http.ResponseWriter, r *
|
||||||
|
|
||||||
if dgstStr == "" {
|
if dgstStr == "" {
|
||||||
// no digest? return error, but allow retry.
|
// no digest? return error, but allow retry.
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
luh.Errors.Push(v2.ErrorCodeDigestInvalid, "digest missing")
|
luh.Errors.Push(v2.ErrorCodeDigestInvalid, "digest missing")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/registry/api/v2"
|
"github.com/docker/distribution/registry/api/v2"
|
||||||
"github.com/docker/distribution/registry/storage"
|
|
||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ func (th *tagsHandler) GetTags(w http.ResponseWriter, r *http.Request) {
|
||||||
tags, err := manifests.Tags()
|
tags, err := manifests.Tags()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case storage.ErrUnknownRepository:
|
case distribution.ErrRepositoryUnknown:
|
||||||
w.WriteHeader(404)
|
w.WriteHeader(404)
|
||||||
th.Errors.Push(v2.ErrorCodeNameUnknown, map[string]string{"name": th.Repository.Name()})
|
th.Errors.Push(v2.ErrorCodeNameUnknown, map[string]string{"name": th.Repository.Name()})
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -587,6 +587,15 @@ func (d *driver) List(path string) ([]string, error) {
|
||||||
if path != "/" && path[len(path)-1] != '/' {
|
if path != "/" && path[len(path)-1] != '/' {
|
||||||
path = path + "/"
|
path = path + "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is to cover for the cases when the rootDirectory of the driver is either "" or "/".
|
||||||
|
// In those cases, there is no root prefix to replace and we must actually add a "/" to all
|
||||||
|
// results in order to keep them as valid paths as recognized by storagedriver.PathRegexp
|
||||||
|
prefix := ""
|
||||||
|
if d.s3Path("") == "" {
|
||||||
|
prefix = "/"
|
||||||
|
}
|
||||||
|
|
||||||
listResponse, err := d.Bucket.List(d.s3Path(path), "/", "", listMax)
|
listResponse, err := d.Bucket.List(d.s3Path(path), "/", "", listMax)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -597,11 +606,11 @@ func (d *driver) List(path string) ([]string, error) {
|
||||||
|
|
||||||
for {
|
for {
|
||||||
for _, key := range listResponse.Contents {
|
for _, key := range listResponse.Contents {
|
||||||
files = append(files, strings.Replace(key.Key, d.s3Path(""), "", 1))
|
files = append(files, strings.Replace(key.Key, d.s3Path(""), prefix, 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, commonPrefix := range listResponse.CommonPrefixes {
|
for _, commonPrefix := range listResponse.CommonPrefixes {
|
||||||
directories = append(directories, strings.Replace(commonPrefix[0:len(commonPrefix)-1], d.s3Path(""), "", 1))
|
directories = append(directories, strings.Replace(commonPrefix[0:len(commonPrefix)-1], d.s3Path(""), prefix, 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
if listResponse.IsTruncated {
|
if listResponse.IsTruncated {
|
||||||
|
|
|
@ -16,6 +16,8 @@ import (
|
||||||
// Hook up gocheck into the "go test" runner.
|
// Hook up gocheck into the "go test" runner.
|
||||||
func Test(t *testing.T) { check.TestingT(t) }
|
func Test(t *testing.T) { check.TestingT(t) }
|
||||||
|
|
||||||
|
type S3DriverConstructor func(rootDirectory string) (*Driver, error)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
accessKey := os.Getenv("AWS_ACCESS_KEY")
|
accessKey := os.Getenv("AWS_ACCESS_KEY")
|
||||||
secretKey := os.Getenv("AWS_SECRET_KEY")
|
secretKey := os.Getenv("AWS_SECRET_KEY")
|
||||||
|
@ -30,7 +32,7 @@ func init() {
|
||||||
}
|
}
|
||||||
defer os.Remove(root)
|
defer os.Remove(root)
|
||||||
|
|
||||||
s3DriverConstructor := func(region aws.Region) (storagedriver.StorageDriver, error) {
|
s3DriverConstructor := func(rootDirectory string) (*Driver, error) {
|
||||||
encryptBool := false
|
encryptBool := false
|
||||||
if encrypt != "" {
|
if encrypt != "" {
|
||||||
encryptBool, err = strconv.ParseBool(encrypt)
|
encryptBool, err = strconv.ParseBool(encrypt)
|
||||||
|
@ -47,7 +49,7 @@ func init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
v4AuthBool := true
|
v4AuthBool := false
|
||||||
if v4auth != "" {
|
if v4auth != "" {
|
||||||
v4AuthBool, err = strconv.ParseBool(v4auth)
|
v4AuthBool, err = strconv.ParseBool(v4auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -59,12 +61,12 @@ func init() {
|
||||||
accessKey,
|
accessKey,
|
||||||
secretKey,
|
secretKey,
|
||||||
bucket,
|
bucket,
|
||||||
region,
|
aws.GetRegion(region),
|
||||||
encryptBool,
|
encryptBool,
|
||||||
secureBool,
|
secureBool,
|
||||||
v4AuthBool,
|
v4AuthBool,
|
||||||
minChunkSize,
|
minChunkSize,
|
||||||
root,
|
rootDirectory,
|
||||||
}
|
}
|
||||||
|
|
||||||
return New(parameters)
|
return New(parameters)
|
||||||
|
@ -78,14 +80,18 @@ func init() {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// for _, region := range aws.Regions {
|
driverConstructor := func() (storagedriver.StorageDriver, error) {
|
||||||
// if region == aws.USGovWest {
|
return s3DriverConstructor(root)
|
||||||
// continue
|
}
|
||||||
// }
|
|
||||||
|
testsuites.RegisterInProcessSuite(driverConstructor, skipCheck)
|
||||||
|
|
||||||
|
// s3Constructor := func() (*Driver, error) {
|
||||||
|
// return s3DriverConstructor(aws.GetRegion(region))
|
||||||
|
// }
|
||||||
|
|
||||||
|
RegisterS3DriverSuite(s3DriverConstructor, skipCheck)
|
||||||
|
|
||||||
testsuites.RegisterInProcessSuite(func() (storagedriver.StorageDriver, error) {
|
|
||||||
return s3DriverConstructor(aws.GetRegion(region))
|
|
||||||
}, skipCheck)
|
|
||||||
// testsuites.RegisterIPCSuite(driverName, map[string]string{
|
// testsuites.RegisterIPCSuite(driverName, map[string]string{
|
||||||
// "accesskey": accessKey,
|
// "accesskey": accessKey,
|
||||||
// "secretkey": secretKey,
|
// "secretkey": secretKey,
|
||||||
|
@ -95,3 +101,50 @@ func init() {
|
||||||
// }, skipCheck)
|
// }, skipCheck)
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RegisterS3DriverSuite(s3DriverConstructor S3DriverConstructor, skipCheck testsuites.SkipCheck) {
|
||||||
|
check.Suite(&S3DriverSuite{
|
||||||
|
Constructor: s3DriverConstructor,
|
||||||
|
SkipCheck: skipCheck,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type S3DriverSuite struct {
|
||||||
|
Constructor S3DriverConstructor
|
||||||
|
testsuites.SkipCheck
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *S3DriverSuite) SetUpSuite(c *check.C) {
|
||||||
|
if reason := suite.SkipCheck(); reason != "" {
|
||||||
|
c.Skip(reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *S3DriverSuite) TestEmptyRootList(c *check.C) {
|
||||||
|
validRoot, err := ioutil.TempDir("", "driver-")
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
defer os.Remove(validRoot)
|
||||||
|
|
||||||
|
rootedDriver, err := suite.Constructor(validRoot)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
emptyRootDriver, err := suite.Constructor("")
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
slashRootDriver, err := suite.Constructor("/")
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
|
filename := "/test"
|
||||||
|
contents := []byte("contents")
|
||||||
|
err = rootedDriver.PutContent(filename, contents)
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
defer rootedDriver.Delete(filename)
|
||||||
|
|
||||||
|
keys, err := emptyRootDriver.List("/")
|
||||||
|
for _, path := range keys {
|
||||||
|
c.Assert(storagedriver.PathRegexp.MatchString(path), check.Equals, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
keys, err = slashRootDriver.List("/")
|
||||||
|
for _, path := range keys {
|
||||||
|
c.Assert(storagedriver.PathRegexp.MatchString(path), check.Equals, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -36,7 +36,11 @@ func TestSimpleLayerUpload(t *testing.T) {
|
||||||
imageName := "foo/bar"
|
imageName := "foo/bar"
|
||||||
driver := inmemory.New()
|
driver := inmemory.New()
|
||||||
registry := NewRegistryWithDriver(driver)
|
registry := NewRegistryWithDriver(driver)
|
||||||
ls := registry.Repository(ctx, imageName).Layers()
|
repository, err := registry.Repository(ctx, imageName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error getting repo: %v", err)
|
||||||
|
}
|
||||||
|
ls := repository.Layers()
|
||||||
|
|
||||||
h := sha256.New()
|
h := sha256.New()
|
||||||
rd := io.TeeReader(randomDataReader, h)
|
rd := io.TeeReader(randomDataReader, h)
|
||||||
|
@ -140,7 +144,11 @@ func TestSimpleLayerRead(t *testing.T) {
|
||||||
imageName := "foo/bar"
|
imageName := "foo/bar"
|
||||||
driver := inmemory.New()
|
driver := inmemory.New()
|
||||||
registry := NewRegistryWithDriver(driver)
|
registry := NewRegistryWithDriver(driver)
|
||||||
ls := registry.Repository(ctx, imageName).Layers()
|
repository, err := registry.Repository(ctx, imageName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error getting repo: %v", err)
|
||||||
|
}
|
||||||
|
ls := repository.Layers()
|
||||||
|
|
||||||
randomLayerReader, tarSumStr, err := testutil.CreateRandomTarFile()
|
randomLayerReader, tarSumStr, err := testutil.CreateRandomTarFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -245,7 +253,11 @@ func TestLayerUploadZeroLength(t *testing.T) {
|
||||||
imageName := "foo/bar"
|
imageName := "foo/bar"
|
||||||
driver := inmemory.New()
|
driver := inmemory.New()
|
||||||
registry := NewRegistryWithDriver(driver)
|
registry := NewRegistryWithDriver(driver)
|
||||||
ls := registry.Repository(ctx, imageName).Layers()
|
repository, err := registry.Repository(ctx, imageName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error getting repo: %v", err)
|
||||||
|
}
|
||||||
|
ls := repository.Layers()
|
||||||
|
|
||||||
upload, err := ls.Upload()
|
upload, err := ls.Upload()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -2,69 +2,13 @@ package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
ctxu "github.com/docker/distribution/context"
|
ctxu "github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/digest"
|
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
"github.com/docker/libtrust"
|
"github.com/docker/libtrust"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrUnknownRepository is returned if the named repository is not known by
|
|
||||||
// the registry.
|
|
||||||
type ErrUnknownRepository struct {
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrUnknownRepository) Error() string {
|
|
||||||
return fmt.Sprintf("unknown respository name=%s", err.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrUnknownManifest is returned if the manifest is not known by the
|
|
||||||
// registry.
|
|
||||||
type ErrUnknownManifest struct {
|
|
||||||
Name string
|
|
||||||
Tag string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrUnknownManifest) Error() string {
|
|
||||||
return fmt.Sprintf("unknown manifest name=%s tag=%s", err.Name, err.Tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrUnknownManifestRevision is returned when a manifest cannot be found by
|
|
||||||
// revision within a repository.
|
|
||||||
type ErrUnknownManifestRevision struct {
|
|
||||||
Name string
|
|
||||||
Revision digest.Digest
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrUnknownManifestRevision) Error() string {
|
|
||||||
return fmt.Sprintf("unknown manifest name=%s revision=%s", err.Name, err.Revision)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrManifestUnverified is returned when the registry is unable to verify
|
|
||||||
// the manifest.
|
|
||||||
type ErrManifestUnverified struct{}
|
|
||||||
|
|
||||||
func (ErrManifestUnverified) Error() string {
|
|
||||||
return fmt.Sprintf("unverified manifest")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrManifestVerification provides a type to collect errors encountered
|
|
||||||
// during manifest verification. Currently, it accepts errors of all types,
|
|
||||||
// but it may be narrowed to those involving manifest verification.
|
|
||||||
type ErrManifestVerification []error
|
|
||||||
|
|
||||||
func (errs ErrManifestVerification) Error() string {
|
|
||||||
var parts []string
|
|
||||||
for _, err := range errs {
|
|
||||||
parts = append(parts, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("errors verifying manifest: %v", strings.Join(parts, ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
type manifestStore struct {
|
type manifestStore struct {
|
||||||
repository *repository
|
repository *repository
|
||||||
|
|
||||||
|
@ -147,7 +91,7 @@ func (ms *manifestStore) Delete(tag string) error {
|
||||||
// registry only tries to store valid content, leaving trust policies of that
|
// registry only tries to store valid content, leaving trust policies of that
|
||||||
// content up to consumers.
|
// content up to consumers.
|
||||||
func (ms *manifestStore) verifyManifest(tag string, mnfst *manifest.SignedManifest) error {
|
func (ms *manifestStore) verifyManifest(tag string, mnfst *manifest.SignedManifest) error {
|
||||||
var errs ErrManifestVerification
|
var errs distribution.ErrManifestVerification
|
||||||
if mnfst.Name != ms.repository.Name() {
|
if mnfst.Name != ms.repository.Name() {
|
||||||
// TODO(stevvooe): This needs to be an exported error
|
// TODO(stevvooe): This needs to be an exported error
|
||||||
errs = append(errs, fmt.Errorf("repository name does not match manifest name"))
|
errs = append(errs, fmt.Errorf("repository name does not match manifest name"))
|
||||||
|
@ -161,10 +105,10 @@ func (ms *manifestStore) verifyManifest(tag string, mnfst *manifest.SignedManife
|
||||||
if _, err := manifest.Verify(mnfst); err != nil {
|
if _, err := manifest.Verify(mnfst); err != nil {
|
||||||
switch err {
|
switch err {
|
||||||
case libtrust.ErrMissingSignatureKey, libtrust.ErrInvalidJSONContent, libtrust.ErrMissingSignatureKey:
|
case libtrust.ErrMissingSignatureKey, libtrust.ErrInvalidJSONContent, libtrust.ErrMissingSignatureKey:
|
||||||
errs = append(errs, ErrManifestUnverified{})
|
errs = append(errs, distribution.ErrManifestUnverified{})
|
||||||
default:
|
default:
|
||||||
if err.Error() == "invalid signature" { // TODO(stevvooe): This should be exported by libtrust
|
if err.Error() == "invalid signature" { // TODO(stevvooe): This should be exported by libtrust
|
||||||
errs = append(errs, ErrManifestUnverified{})
|
errs = append(errs, distribution.ErrManifestUnverified{})
|
||||||
} else {
|
} else {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
"github.com/docker/distribution/registry/storage/driver/inmemory"
|
"github.com/docker/distribution/registry/storage/driver/inmemory"
|
||||||
|
@ -20,7 +21,10 @@ func TestManifestStorage(t *testing.T) {
|
||||||
tag := "thetag"
|
tag := "thetag"
|
||||||
driver := inmemory.New()
|
driver := inmemory.New()
|
||||||
registry := NewRegistryWithDriver(driver)
|
registry := NewRegistryWithDriver(driver)
|
||||||
repo := registry.Repository(ctx, name)
|
repo, err := registry.Repository(ctx, name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error getting repo: %v", err)
|
||||||
|
}
|
||||||
ms := repo.Manifests()
|
ms := repo.Manifests()
|
||||||
|
|
||||||
exists, err := ms.Exists(tag)
|
exists, err := ms.Exists(tag)
|
||||||
|
@ -34,7 +38,7 @@ func TestManifestStorage(t *testing.T) {
|
||||||
|
|
||||||
if _, err := ms.Get(tag); true {
|
if _, err := ms.Get(tag); true {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case ErrUnknownManifest:
|
case distribution.ErrManifestUnknown:
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
t.Fatalf("expected manifest unknown error: %#v", err)
|
t.Fatalf("expected manifest unknown error: %#v", err)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
|
"github.com/docker/distribution/registry/api/v2"
|
||||||
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
@ -36,12 +37,19 @@ func NewRegistryWithDriver(driver storagedriver.StorageDriver) distribution.Regi
|
||||||
// Repository returns an instance of the repository tied to the registry.
|
// Repository returns an instance of the repository tied to the registry.
|
||||||
// Instances should not be shared between goroutines but are cheap to
|
// Instances should not be shared between goroutines but are cheap to
|
||||||
// allocate. In general, they should be request scoped.
|
// allocate. In general, they should be request scoped.
|
||||||
func (reg *registry) Repository(ctx context.Context, name string) distribution.Repository {
|
func (reg *registry) Repository(ctx context.Context, name string) (distribution.Repository, error) {
|
||||||
|
if err := v2.ValidateRespositoryName(name); err != nil {
|
||||||
|
return nil, distribution.ErrRepositoryNameInvalid{
|
||||||
|
Name: name,
|
||||||
|
Reason: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &repository{
|
return &repository{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
registry: reg,
|
registry: reg,
|
||||||
name: name,
|
name: name,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// repository provides name-scoped access to various services.
|
// repository provides name-scoped access to various services.
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
"github.com/docker/libtrust"
|
"github.com/docker/libtrust"
|
||||||
|
@ -40,7 +41,7 @@ func (rs *revisionStore) get(revision digest.Digest) (*manifest.SignedManifest,
|
||||||
if exists, err := rs.exists(revision); err != nil {
|
if exists, err := rs.exists(revision); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if !exists {
|
} else if !exists {
|
||||||
return nil, ErrUnknownManifestRevision{
|
return nil, distribution.ErrUnknownManifestRevision{
|
||||||
Name: rs.Name(),
|
Name: rs.Name(),
|
||||||
Revision: revision,
|
Revision: revision,
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package storage
|
||||||
import (
|
import (
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
storagedriver "github.com/docker/distribution/registry/storage/driver"
|
||||||
)
|
)
|
||||||
|
@ -26,7 +27,7 @@ func (ts *tagStore) tags() ([]string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case storagedriver.PathNotFoundError:
|
case storagedriver.PathNotFoundError:
|
||||||
return nil, ErrUnknownRepository{Name: ts.name}
|
return nil, distribution.ErrRepositoryUnknown{Name: ts.name}
|
||||||
default:
|
default:
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -104,7 +105,7 @@ func (ts *tagStore) resolve(tag string) (digest.Digest, error) {
|
||||||
if exists, err := exists(ts.driver, currentPath); err != nil {
|
if exists, err := exists(ts.driver, currentPath); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
} else if !exists {
|
} else if !exists {
|
||||||
return "", ErrUnknownManifest{Name: ts.Name(), Tag: tag}
|
return "", distribution.ErrManifestUnknown{Name: ts.Name(), Tag: tag}
|
||||||
}
|
}
|
||||||
|
|
||||||
revision, err := ts.blobStore.readlink(currentPath)
|
revision, err := ts.blobStore.readlink(currentPath)
|
||||||
|
|
Loading…
Reference in a new issue