Merge pull request #529 from mrjana/integ

Add more integration tests
This commit is contained in:
Madhu Venugopal 2015-09-16 10:32:47 -07:00
commit 0e078d2671
70 changed files with 4400 additions and 10293 deletions

View File

@ -19,11 +19,26 @@
"ImportPath": "github.com/armon/go-metrics",
"Rev": "eb0af217e5e9747e41dd5303755356b62d28e3ec"
},
{
"ImportPath": "github.com/codegangsta/cli",
"Comment": "1.2.0-143-ga65b733",
"Rev": "a65b733b303f0055f8d324d805f393cd3e7a7904"
},
{
"ImportPath": "github.com/coreos/go-etcd/etcd",
"Comment": "v2.0.0-7-g73a8ef7",
"Rev": "73a8ef737e8ea002281a28b4cb92a1de121ad4c6"
},
{
"ImportPath": "github.com/coreos/go-systemd/dbus",
"Comment": "v3",
"Rev": "be94bc700879ae8217780e9d141789a2defa302b"
},
{
"ImportPath": "github.com/coreos/go-systemd/util",
"Comment": "v3",
"Rev": "be94bc700879ae8217780e9d141789a2defa302b"
},
{
"ImportPath": "github.com/docker/docker/pkg/homedir",
"Comment": "v1.4.1-4106-g637023a",
@ -39,6 +54,11 @@
"Comment": "v1.4.1-4106-g637023a",
"Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693"
},
{
"ImportPath": "github.com/docker/docker/pkg/mount",
"Comment": "v1.4.1-4127-gb81f2ee",
"Rev": "b81f2ee5f20d094c13893f565ce716595c539d22"
},
{
"ImportPath": "github.com/docker/docker/pkg/parsers",
"Comment": "v1.4.1-4106-g637023a",
@ -64,11 +84,21 @@
"Comment": "v1.4.1-4106-g637023a",
"Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693"
},
{
"ImportPath": "github.com/docker/docker/pkg/symlink",
"Comment": "v1.4.1-4127-gb81f2ee",
"Rev": "b81f2ee5f20d094c13893f565ce716595c539d22"
},
{
"ImportPath": "github.com/docker/docker/pkg/term",
"Comment": "v1.4.1-4106-g637023a",
"Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693"
},
{
"ImportPath": "github.com/docker/docker/pkg/units",
"Comment": "v1.4.1-4127-gb81f2ee",
"Rev": "b81f2ee5f20d094c13893f565ce716595c539d22"
},
{
"ImportPath": "github.com/docker/libcontainer/user",
"Comment": "v1.4.0-495-g3e66118",
@ -83,6 +113,10 @@
"Comment": "v2-3-g4160802",
"Rev": "41608027bdce7bfa8959d653a00b954591220e67"
},
{
"ImportPath": "github.com/golang/protobuf/proto",
"Rev": "f7137ae6b19afbfd61a94b746fda3b3fe0491874"
},
{
"ImportPath": "github.com/gorilla/context",
"Rev": "215affda49addc4c8ef7e2534915df2c8c35c6cd"
@ -110,75 +144,29 @@
"Rev": "7151adcef72687bf95f451a2e0ba15cb19412bf2"
},
{
"ImportPath": "github.com/samuel/go-zookeeper/zk",
"Rev": "d0e0d8e11f318e000a8cc434616d69e329edc374"
"ImportPath": "github.com/opencontainers/runc/libcontainer",
"Comment": "v0.0.3",
"Rev": "072fa6fdccaba49b11ba91ad4265b1ec1043787e"
},
{
"ImportPath": "github.com/stretchr/objx",
"Rev": "cbeaeb16a013161a98496fad62933b1d21786672"
"ImportPath": "github.com/samuel/go-zookeeper/zk",
"Rev": "d0e0d8e11f318e000a8cc434616d69e329edc374"
},
{
"ImportPath": "github.com/stretchr/testify/assert",
"Rev": "dab07ac62d4905d3e48d17dc549c684ac3b7c15a"
},
{
"ImportPath": "github.com/stretchr/testify/mock",
"Rev": "dab07ac62d4905d3e48d17dc549c684ac3b7c15a"
},
{
"ImportPath": "github.com/vishvananda/netns",
"Rev": "493029407eeb434d0c2d44e02ea072ff2488d322"
"ImportPath": "github.com/syndtr/gocapability/capability",
"Rev": "e55e5833692b49e49a0073ad5baf7803f21bebf4"
},
{
"ImportPath": "github.com/vishvananda/netlink",
"Rev": "4b5dce31de6d42af5bb9811c6d265472199e0fec"
},
{
"ImportPath": "github.com/opencontainers/runc/libcontainer",
"Comment": "v0.0.3",
"Rev": "072fa6fdccaba49b11ba91ad4265b1ec1043787e"
},
{
"ImportPath": "github.com/coreos/go-systemd/dbus",
"Comment": "v3",
"Rev": "be94bc700879ae8217780e9d141789a2defa302b"
},
{
"ImportPath": "github.com/coreos/go-systemd/util",
"Comment": "v3",
"Rev": "be94bc700879ae8217780e9d141789a2defa302b"
},
{
"ImportPath": "github.com/docker/docker/pkg/mount",
"Comment": "v1.4.1-4127-gb81f2ee",
"Rev": "b81f2ee5f20d094c13893f565ce716595c539d22"
},
{
"ImportPath": "github.com/docker/docker/pkg/symlink",
"Comment": "v1.4.1-4127-gb81f2ee",
"Rev": "b81f2ee5f20d094c13893f565ce716595c539d22"
},
{
"ImportPath": "github.com/docker/docker/pkg/term",
"Comment": "v1.4.1-4127-gb81f2ee",
"Rev": "b81f2ee5f20d094c13893f565ce716595c539d22"
},
{
"ImportPath": "github.com/docker/docker/pkg/units",
"Comment": "v1.4.1-4127-gb81f2ee",
"Rev": "b81f2ee5f20d094c13893f565ce716595c539d22"
},
{
"ImportPath": "github.com/golang/protobuf/proto",
"Rev": "f7137ae6b19afbfd61a94b746fda3b3fe0491874"
},
{
"ImportPath": "github.com/opencontainers/specs",
"Rev": "5b31bb2b7771e5074a4eb14eca432da1ca5182d6"
},
{
"ImportPath": "github.com/syndtr/gocapability/capability",
"Rev": "e55e5833692b49e49a0073ad5baf7803f21bebf4"
"ImportPath": "github.com/vishvananda/netns",
"Rev": "493029407eeb434d0c2d44e02ea072ff2488d322"
}
]
}

View File

@ -0,0 +1,13 @@
language: go
sudo: false
go:
- 1.0.3
- 1.1.2
- 1.2.2
- 1.3.3
- 1.4.2
script:
- go vet ./...
- go test -v ./...

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

View File

@ -0,0 +1,316 @@
[![Build Status](https://travis-ci.org/codegangsta/cli.png?branch=master)](https://travis-ci.org/codegangsta/cli)
# cli.go
`cli.go` is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way.
You can view the API docs here:
http://godoc.org/github.com/codegangsta/cli
## Overview
Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app.
**This is where `cli.go` comes into play.** `cli.go` makes command line programming fun, organized, and expressive!
## Installation
Make sure you have a working Go environment (go 1.1+ is *required*). [See the install instructions](http://golang.org/doc/install.html).
To install `cli.go`, simply run:
```
$ go get github.com/codegangsta/cli
```
Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used:
```
export PATH=$PATH:$GOPATH/bin
```
## Getting Started
One of the philosophies behind `cli.go` is that an API should be playful and full of discovery. So a `cli.go` app can be as little as one line of code in `main()`.
``` go
package main
import (
"os"
"github.com/codegangsta/cli"
)
func main() {
cli.NewApp().Run(os.Args)
}
```
This app will run and show help text, but is not very useful. Let's give an action to execute and some help documentation:
``` go
package main
import (
"os"
"github.com/codegangsta/cli"
)
func main() {
app := cli.NewApp()
app.Name = "boom"
app.Usage = "make an explosive entrance"
app.Action = func(c *cli.Context) {
println("boom! I say!")
}
app.Run(os.Args)
}
```
Running this already gives you a ton of functionality, plus support for things like subcommands and flags, which are covered below.
## Example
Being a programmer can be a lonely job. Thankfully by the power of automation that is not the case! Let's create a greeter app to fend off our demons of loneliness!
Start by creating a directory named `greet`, and within it, add a file, `greet.go` with the following code in it:
``` go
package main
import (
"os"
"github.com/codegangsta/cli"
)
func main() {
app := cli.NewApp()
app.Name = "greet"
app.Usage = "fight the loneliness!"
app.Action = func(c *cli.Context) {
println("Hello friend!")
}
app.Run(os.Args)
}
```
Install our command to the `$GOPATH/bin` directory:
```
$ go install
```
Finally run our new command:
```
$ greet
Hello friend!
```
`cli.go` also generates some bitchass help text:
```
$ greet help
NAME:
greet - fight the loneliness!
USAGE:
greet [global options] command [command options] [arguments...]
VERSION:
0.0.0
COMMANDS:
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS
--version Shows version information
```
### Arguments
You can lookup arguments by calling the `Args` function on `cli.Context`.
``` go
...
app.Action = func(c *cli.Context) {
println("Hello", c.Args()[0])
}
...
```
### Flags
Setting and querying flags is simple.
``` go
...
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang",
Value: "english",
Usage: "language for the greeting",
},
}
app.Action = func(c *cli.Context) {
name := "someone"
if len(c.Args()) > 0 {
name = c.Args()[0]
}
if c.String("lang") == "spanish" {
println("Hola", name)
} else {
println("Hello", name)
}
}
...
```
See full list of flags at http://godoc.org/github.com/codegangsta/cli
#### Alternate Names
You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g.
``` go
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
},
}
```
That flag can then be set with `--lang spanish` or `-l spanish`. Note that giving two different forms of the same flag in the same command invocation is an error.
#### Values from the Environment
You can also have the default value set from the environment via `EnvVar`. e.g.
``` go
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
EnvVar: "APP_LANG",
},
}
```
The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default.
``` go
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG",
},
}
```
### Subcommands
Subcommands can be defined for a more git-like command line app.
```go
...
app.Commands = []cli.Command{
{
Name: "add",
Aliases: []string{"a"},
Usage: "add a task to the list",
Action: func(c *cli.Context) {
println("added task: ", c.Args().First())
},
},
{
Name: "complete",
Aliases: []string{"c"},
Usage: "complete a task on the list",
Action: func(c *cli.Context) {
println("completed task: ", c.Args().First())
},
},
{
Name: "template",
Aliases: []string{"r"},
Usage: "options for task templates",
Subcommands: []cli.Command{
{
Name: "add",
Usage: "add a new template",
Action: func(c *cli.Context) {
println("new task template: ", c.Args().First())
},
},
{
Name: "remove",
Usage: "remove an existing template",
Action: func(c *cli.Context) {
println("removed task template: ", c.Args().First())
},
},
},
},
}
...
```
### Bash Completion
You can enable completion commands by setting the `EnableBashCompletion`
flag on the `App` object. By default, this setting will only auto-complete to
show an app's subcommands, but you can write your own completion methods for
the App or its subcommands.
```go
...
var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"}
app := cli.NewApp()
app.EnableBashCompletion = true
app.Commands = []cli.Command{
{
Name: "complete",
Aliases: []string{"c"},
Usage: "complete a task on the list",
Action: func(c *cli.Context) {
println("completed task: ", c.Args().First())
},
BashComplete: func(c *cli.Context) {
// This will complete if no args are passed
if len(c.Args()) > 0 {
return
}
for _, t := range tasks {
fmt.Println(t)
}
},
}
}
...
```
#### To Enable
Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while
setting the `PROG` variable to the name of your program:
`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete`
#### To Distribute
Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename
it to the name of the program you wish to add autocomplete support for (or
automatically install it there if you are distributing a package). Don't forget
to source the file to make it active in the current shell.
```
sudo cp src/bash_autocomplete /etc/bash_completion.d/<myprogram>
source /etc/bash_completion.d/<myprogram>
```
Alternatively, you can just document that users should source the generic
`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set
to the name of their program (as above).
## 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.

View File

@ -0,0 +1,308 @@
package cli
import (
"fmt"
"io"
"io/ioutil"
"os"
"time"
)
// App is the main structure of a cli application. It is recomended that
// an app be created with the cli.NewApp() function
type App struct {
// The name of the program. Defaults to os.Args[0]
Name string
// Description of the program.
Usage string
// Version of the program
Version string
// List of commands to execute
Commands []Command
// List of flags to parse
Flags []Flag
// Boolean to enable bash completion commands
EnableBashCompletion bool
// Boolean to hide built-in help command
HideHelp bool
// Boolean to hide built-in version flag
HideVersion bool
// An action to execute when the bash-completion flag is set
BashComplete func(context *Context)
// An action to execute before any subcommands are run, but after the context is ready
// If a non-nil error is returned, no subcommands are run
Before func(context *Context) error
// An action to execute after any subcommands are run, but after the subcommand has finished
// It is run even if Action() panics
After func(context *Context) error
// The action to execute when no subcommands are specified
Action func(context *Context)
// Execute this function if the proper command cannot be found
CommandNotFound func(context *Context, command string)
// Compilation date
Compiled time.Time
// List of all authors who contributed
Authors []Author
// Copyright of the binary if any
Copyright string
// Name of Author (Note: Use App.Authors, this is deprecated)
Author string
// Email of Author (Note: Use App.Authors, this is deprecated)
Email string
// Writer writer to write output to
Writer io.Writer
}
// Tries to find out when this binary was compiled.
// Returns the current time if it fails to find it.
func compileTime() time.Time {
info, err := os.Stat(os.Args[0])
if err != nil {
return time.Now()
}
return info.ModTime()
}
// Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action.
func NewApp() *App {
return &App{
Name: os.Args[0],
Usage: "A new cli application",
Version: "0.0.0",
BashComplete: DefaultAppComplete,
Action: helpCommand.Action,
Compiled: compileTime(),
Writer: os.Stdout,
}
}
// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
func (a *App) Run(arguments []string) (err error) {
if a.Author != "" || a.Email != "" {
a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email})
}
// append help to commands
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand)
if (HelpFlag != BoolFlag{}) {
a.appendFlag(HelpFlag)
}
}
//append version/help flags
if a.EnableBashCompletion {
a.appendFlag(BashCompletionFlag)
}
if !a.HideVersion {
a.appendFlag(VersionFlag)
}
// parse flags
set := flagSet(a.Name, a.Flags)
set.SetOutput(ioutil.Discard)
err = set.Parse(arguments[1:])
nerr := normalizeFlags(a.Flags, set)
if nerr != nil {
fmt.Fprintln(a.Writer, nerr)
context := NewContext(a, set, nil)
ShowAppHelp(context)
return nerr
}
context := NewContext(a, set, nil)
if err != nil {
fmt.Fprintln(a.Writer, "Incorrect Usage.")
fmt.Fprintln(a.Writer)
ShowAppHelp(context)
return err
}
if checkCompletions(context) {
return nil
}
if checkHelp(context) {
return nil
}
if checkVersion(context) {
return nil
}
if a.After != nil {
defer func() {
afterErr := a.After(context)
if afterErr != nil {
if err != nil {
err = NewMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
}
if a.Before != nil {
err := a.Before(context)
if err != nil {
return err
}
}
args := context.Args()
if args.Present() {
name := args.First()
c := a.Command(name)
if c != nil {
return c.Run(context)
}
}
// Run default Action
a.Action(context)
return nil
}
// Another entry point to the cli app, takes care of passing arguments and error handling
func (a *App) RunAndExitOnError() {
if err := a.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
// Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags
func (a *App) RunAsSubcommand(ctx *Context) (err error) {
// append help to commands
if len(a.Commands) > 0 {
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand)
if (HelpFlag != BoolFlag{}) {
a.appendFlag(HelpFlag)
}
}
}
// append flags
if a.EnableBashCompletion {
a.appendFlag(BashCompletionFlag)
}
// parse flags
set := flagSet(a.Name, a.Flags)
set.SetOutput(ioutil.Discard)
err = set.Parse(ctx.Args().Tail())
nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, ctx)
if nerr != nil {
fmt.Fprintln(a.Writer, nerr)
fmt.Fprintln(a.Writer)
if len(a.Commands) > 0 {
ShowSubcommandHelp(context)
} else {
ShowCommandHelp(ctx, context.Args().First())
}
return nerr
}
if err != nil {
fmt.Fprintln(a.Writer, "Incorrect Usage.")
fmt.Fprintln(a.Writer)
ShowSubcommandHelp(context)
return err
}
if checkCompletions(context) {
return nil
}
if len(a.Commands) > 0 {
if checkSubcommandHelp(context) {
return nil
}
} else {
if checkCommandHelp(ctx, context.Args().First()) {
return nil
}
}
if a.After != nil {
defer func() {
afterErr := a.After(context)
if afterErr != nil {
if err != nil {
err = NewMultiError(err, afterErr)
} else {
err = afterErr
}
}
}()
}
if a.Before != nil {
err := a.Before(context)
if err != nil {
return err
}
}
args := context.Args()
if args.Present() {
name := args.First()
c := a.Command(name)
if c != nil {
return c.Run(context)
}
}
// Run default Action
a.Action(context)
return nil
}
// Returns the named command on App. Returns nil if the command does not exist
func (a *App) Command(name string) *Command {
for _, c := range a.Commands {
if c.HasName(name) {
return &c
}
}
return nil
}
func (a *App) hasFlag(flag Flag) bool {
for _, f := range a.Flags {
if flag == f {
return true
}
}
return false
}
func (a *App) appendFlag(flag Flag) {
if !a.hasFlag(flag) {
a.Flags = append(a.Flags, flag)
}
}
// Author represents someone who has contributed to a cli project.
type Author struct {
Name string // The Authors name
Email string // The Authors email
}
// String makes Author comply to the Stringer interface, to allow an easy print in the templating process
func (a Author) String() string {
e := ""
if a.Email != "" {
e = "<" + a.Email + "> "
}
return fmt.Sprintf("%v %v", a.Name, e)
}

View File

@ -0,0 +1,867 @@
package cli
import (
"bytes"
"flag"
"fmt"
"io"
"os"
"strings"
"testing"
)
func ExampleApp() {
// set args for examples sake
os.Args = []string{"greet", "--name", "Jeremy"}
app := NewApp()
app.Name = "greet"
app.Flags = []Flag{
StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
}
app.Action = func(c *Context) {
fmt.Printf("Hello %v\n", c.String("name"))
}
app.Author = "Harrison"
app.Email = "harrison@lolwut.com"
app.Authors = []Author{Author{Name: "Oliver Allen", Email: "oliver@toyshop.com"}}
app.Run(os.Args)
// Output:
// Hello Jeremy
}
func ExampleAppSubcommand() {
// set args for examples sake
os.Args = []string{"say", "hi", "english", "--name", "Jeremy"}
app := NewApp()
app.Name = "say"
app.Commands = []Command{
{
Name: "hello",
Aliases: []string{"hi"},
Usage: "use it to see a description",
Description: "This is how we describe hello the function",
Subcommands: []Command{
{
Name: "english",
Aliases: []string{"en"},
Usage: "sends a greeting in english",
Description: "greets someone in english",
Flags: []Flag{
StringFlag{
Name: "name",
Value: "Bob",
Usage: "Name of the person to greet",
},
},
Action: func(c *Context) {
fmt.Println("Hello,", c.String("name"))
},
},
},
},
}
app.Run(os.Args)
// Output:
// Hello, Jeremy
}
func ExampleAppHelp() {
// set args for examples sake
os.Args = []string{"greet", "h", "describeit"}
app := NewApp()
app.Name = "greet"
app.Flags = []Flag{
StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
}
app.Commands = []Command{
{
Name: "describeit",
Aliases: []string{"d"},
Usage: "use it to see a description",
Description: "This is how we describe describeit the function",
Action: func(c *Context) {
fmt.Printf("i like to describe things")
},
},
}
app.Run(os.Args)
// Output:
// NAME:
// describeit - use it to see a description
//
// USAGE:
// command describeit [arguments...]
//
// DESCRIPTION:
// This is how we describe describeit the function
}
func ExampleAppBashComplete() {
// set args for examples sake
os.Args = []string{"greet", "--generate-bash-completion"}
app := NewApp()
app.Name = "greet"
app.EnableBashCompletion = true
app.Commands = []Command{
{
Name: "describeit",
Aliases: []string{"d"},
Usage: "use it to see a description",
Description: "This is how we describe describeit the function",
Action: func(c *Context) {
fmt.Printf("i like to describe things")
},
}, {
Name: "next",
Usage: "next example",
Description: "more stuff to see when generating bash completion",
Action: func(c *Context) {
fmt.Printf("the next example")
},
},
}
app.Run(os.Args)
// Output:
// describeit
// d
// next
// help
// h
}
func TestApp_Run(t *testing.T) {
s := ""
app := NewApp()
app.Action = func(c *Context) {
s = s + c.Args().First()
}
err := app.Run([]string{"command", "foo"})
expect(t, err, nil)
err = app.Run([]string{"command", "bar"})
expect(t, err, nil)
expect(t, s, "foobar")
}
var commandAppTests = []struct {
name string
expected bool
}{
{"foobar", true},
{"batbaz", true},
{"b", true},
{"f", true},
{"bat", false},
{"nothing", false},
}
func TestApp_Command(t *testing.T) {
app := NewApp()
fooCommand := Command{Name: "foobar", Aliases: []string{"f"}}
batCommand := Command{Name: "batbaz", Aliases: []string{"b"}}
app.Commands = []Command{
fooCommand,
batCommand,
}
for _, test := range commandAppTests {
expect(t, app.Command(test.name) != nil, test.expected)
}
}
func TestApp_CommandWithArgBeforeFlags(t *testing.T) {
var parsedOption, firstArg string
app := NewApp()
command := Command{
Name: "cmd",
Flags: []Flag{
StringFlag{Name: "option", Value: "", Usage: "some option"},
},
Action: func(c *Context) {
parsedOption = c.String("option")
firstArg = c.Args().First()
},
}
app.Commands = []Command{command}
app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"})
expect(t, parsedOption, "my-option")
expect(t, firstArg, "my-arg")
}
func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
var context *Context
a := NewApp()
a.Commands = []Command{
{
Name: "foo",
Action: func(c *Context) {
context = c
},
Flags: []Flag{
StringFlag{
Name: "lang",
Value: "english",
Usage: "language for the greeting",
},
},
Before: func(_ *Context) error { return nil },
},
}
a.Run([]string{"", "foo", "--lang", "spanish", "abcd"})
expect(t, context.Args().Get(0), "abcd")
expect(t, context.String("lang"), "spanish")
}
func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
var parsedOption string
var args []string
app := NewApp()
command := Command{
Name: "cmd",
Flags: []Flag{
StringFlag{Name: "option", Value: "", Usage: "some option"},
},
Action: func(c *Context) {
parsedOption = c.String("option")
args = c.Args()
},
}
app.Commands = []Command{command}
app.Run([]string{"", "cmd", "my-arg", "--option", "my-option", "--", "--notARealFlag"})
expect(t, parsedOption, "my-option")
expect(t, args[0], "my-arg")
expect(t, args[1], "--")
expect(t, args[2], "--notARealFlag")
}
func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) {
var args []string
app := NewApp()
command := Command{
Name: "cmd",
Action: func(c *Context) {
args = c.Args()
},
}
app.Commands = []Command{command}
app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"})
expect(t, args[0], "my-arg")
expect(t, args[1], "--")
expect(t, args[2], "notAFlagAtAll")
}
func TestApp_Float64Flag(t *testing.T) {
var meters float64
app := NewApp()
app.Flags = []Flag{
Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"},
}
app.Action = func(c *Context) {
meters = c.Float64("height")
}
app.Run([]string{"", "--height", "1.93"})
expect(t, meters, 1.93)
}
func TestApp_ParseSliceFlags(t *testing.T) {
var parsedOption, firstArg string
var parsedIntSlice []int
var parsedStringSlice []string
app := NewApp()
command := Command{
Name: "cmd",
Flags: []Flag{
IntSliceFlag{Name: "p", Value: &IntSlice{}, Usage: "set one or more ip addr"},
StringSliceFlag{Name: "ip", Value: &StringSlice{}, Usage: "set one or more ports to open"},
},
Action: func(c *Context) {
parsedIntSlice = c.IntSlice("p")
parsedStringSlice = c.StringSlice("ip")
parsedOption = c.String("option")
firstArg = c.Args().First()
},
}
app.Commands = []Command{command}
app.Run([]string{"", "cmd", "my-arg", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4"})
IntsEquals := func(a, b []int) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
StrsEquals := func(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
var expectedIntSlice = []int{22, 80}
var expectedStringSlice = []string{"8.8.8.8", "8.8.4.4"}
if !IntsEquals(parsedIntSlice, expectedIntSlice) {
t.Errorf("%v does not match %v", parsedIntSlice, expectedIntSlice)
}
if !StrsEquals(parsedStringSlice, expectedStringSlice) {
t.Errorf("%v does not match %v", parsedStringSlice, expectedStringSlice)
}
}
func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) {
var parsedIntSlice []int
var parsedStringSlice []string
app := NewApp()
command := Command{
Name: "cmd",
Flags: []Flag{
IntSliceFlag{Name: "a", Usage: "set numbers"},
StringSliceFlag{Name: "str", Usage: "set strings"},
},
Action: func(c *Context) {
parsedIntSlice = c.IntSlice("a")
parsedStringSlice = c.StringSlice("str")
},
}
app.Commands = []Command{command}
app.Run([]string{"", "cmd", "my-arg", "-a", "2", "-str", "A"})
var expectedIntSlice = []int{2}
var expectedStringSlice = []string{"A"}
if parsedIntSlice[0] != expectedIntSlice[0] {
t.Errorf("%v does not match %v", parsedIntSlice[0], expectedIntSlice[0])
}
if parsedStringSlice[0] != expectedStringSlice[0] {
t.Errorf("%v does not match %v", parsedIntSlice[0], expectedIntSlice[0])
}
}
func TestApp_DefaultStdout(t *testing.T) {
app := NewApp()
if app.Writer != os.Stdout {
t.Error("Default output writer not set.")
}
}
type mockWriter struct {
written []byte
}
func (fw *mockWriter) Write(p []byte) (n int, err error) {
if fw.written == nil {
fw.written = p
} else {
fw.written = append(fw.written, p...)
}
return len(p), nil
}
func (fw *mockWriter) GetWritten() (b []byte) {
return fw.written
}
func TestApp_SetStdout(t *testing.T) {
w := &mockWriter{}
app := NewApp()
app.Name = "test"
app.Writer = w
err := app.Run([]string{"help"})
if err != nil {
t.Fatalf("Run error: %s", err)
}
if len(w.written) == 0 {
t.Error("App did not write output to desired writer.")
}
}
func TestApp_BeforeFunc(t *testing.T) {
beforeRun, subcommandRun := false, false
beforeError := fmt.Errorf("fail")
var err error
app := NewApp()
app.Before = func(c *Context) error {
beforeRun = true
s := c.String("opt")
if s == "fail" {
return beforeError
}
return nil
}
app.Commands = []Command{
Command{
Name: "sub",
Action: func(c *Context) {
subcommandRun = true
},
},
}
app.Flags = []Flag{
StringFlag{Name: "opt"},
}
// run with the Before() func succeeding
err = app.Run([]string{"command", "--opt", "succeed", "sub"})
if err != nil {
t.Fatalf("Run error: %s", err)
}
if beforeRun == false {
t.Errorf("Before() not executed when expected")
}
if subcommandRun == false {
t.Errorf("Subcommand not executed when expected")
}
// reset
beforeRun, subcommandRun = false, false
// run with the Before() func failing
err = app.Run([]string{"command", "--opt", "fail", "sub"})
// should be the same error produced by the Before func
if err != beforeError {
t.Errorf("Run error expected, but not received")
}
if beforeRun == false {
t.Errorf("Before() not executed when expected")
}
if subcommandRun == true {
t.Errorf("Subcommand executed when NOT expected")
}
}
func TestApp_AfterFunc(t *testing.T) {
afterRun, subcommandRun := false, false
afterError := fmt.Errorf("fail")
var err error
app := NewApp()
app.After = func(c *Context) error {
afterRun = true
s := c.String("opt")
if s == "fail" {
return afterError
}
return nil
}
app.Commands = []Command{
Command{
Name: "sub",
Action: func(c *Context) {
subcommandRun = true
},
},
}
app.Flags = []Flag{
StringFlag{Name: "opt"},
}
// run with the After() func succeeding
err = app.Run([]string{"command", "--opt", "succeed", "sub"})
if err != nil {
t.Fatalf("Run error: %s", err)
}
if afterRun == false {
t.Errorf("After() not executed when expected")
}
if subcommandRun == false {
t.Errorf("Subcommand not executed when expected")
}
// reset
afterRun, subcommandRun = false, false
// run with the Before() func failing
err = app.Run([]string{"command", "--opt", "fail", "sub"})
// should be the same error produced by the Before func
if err != afterError {
t.Errorf("Run error expected, but not received")
}
if afterRun == false {
t.Errorf("After() not executed when expected")
}
if subcommandRun == false {
t.Errorf("Subcommand not executed when expected")
}
}
func TestAppNoHelpFlag(t *testing.T) {
oldFlag := HelpFlag
defer func() {
HelpFlag = oldFlag
}()
HelpFlag = BoolFlag{}
app := NewApp()
err := app.Run([]string{"test", "-h"})
if err != flag.ErrHelp {
t.Errorf("expected error about missing help flag, but got: %s (%T)", err, err)
}
}
func TestAppHelpPrinter(t *testing.T) {
oldPrinter := HelpPrinter
defer func() {
HelpPrinter = oldPrinter
}()
var wasCalled = false
HelpPrinter = func(w io.Writer, template string, data interface{}) {
wasCalled = true
}
app := NewApp()
app.Run([]string{"-h"})
if wasCalled == false {
t.Errorf("Help printer expected to be called, but was not")
}
}
func TestAppVersionPrinter(t *testing.T) {
oldPrinter := VersionPrinter
defer func() {
VersionPrinter = oldPrinter
}()
var wasCalled = false
VersionPrinter = func(c *Context) {
wasCalled = true
}
app := NewApp()
ctx := NewContext(app, nil, nil)
ShowVersion(ctx)
if wasCalled == false {
t.Errorf("Version printer expected to be called, but was not")
}
}
func TestAppCommandNotFound(t *testing.T) {
beforeRun, subcommandRun := false, false
app := NewApp()
app.CommandNotFound = func(c *Context, command string) {
beforeRun = true
}
app.Commands = []Command{
Command{
Name: "bar",
Action: func(c *Context) {
subcommandRun = true
},
},
}
app.Run([]string{"command", "foo"})
expect(t, beforeRun, true)
expect(t, subcommandRun, false)
}
func TestGlobalFlag(t *testing.T) {
var globalFlag string
var globalFlagSet bool
app := NewApp()
app.Flags = []Flag{
StringFlag{Name: "global, g", Usage: "global"},
}
app.Action = func(c *Context) {
globalFlag = c.GlobalString("global")
globalFlagSet = c.GlobalIsSet("global")
}
app.Run([]string{"command", "-g", "foo"})
expect(t, globalFlag, "foo")
expect(t, globalFlagSet, true)
}
func TestGlobalFlagsInSubcommands(t *testing.T) {
subcommandRun := false
parentFlag := false
app := NewApp()
app.Flags = []Flag{
BoolFlag{Name: "debug, d", Usage: "Enable debugging"},
}
app.Commands = []Command{
Command{
Name: "foo",
Flags: []Flag{
BoolFlag{Name: "parent, p", Usage: "Parent flag"},
},
Subcommands: []Command{
{
Name: "bar",
Action: func(c *Context) {
if c.GlobalBool("debug") {
subcommandRun = true
}
if c.GlobalBool("parent") {
parentFlag = true
}
},
},
},
},
}
app.Run([]string{"command", "-d", "foo", "-p", "bar"})
expect(t, subcommandRun, true)
expect(t, parentFlag, true)
}
func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) {
var subcommandHelpTopics = [][]string{
{"command", "foo", "--help"},
{"command", "foo", "-h"},
{"command", "foo", "help"},
}
for _, flagSet := range subcommandHelpTopics {
t.Logf("==> checking with flags %v", flagSet)
app := NewApp()
buf := new(bytes.Buffer)
app.Writer = buf
subCmdBar := Command{
Name: "bar",
Usage: "does bar things",
}
subCmdBaz := Command{
Name: "baz",
Usage: "does baz things",
}
cmd := Command{
Name: "foo",
Description: "descriptive wall of text about how it does foo things",
Subcommands: []Command{subCmdBar, subCmdBaz},
}
app.Commands = []Command{cmd}
err := app.Run(flagSet)
if err != nil {
t.Error(err)
}
output := buf.String()
t.Logf("output: %q\n", buf.Bytes())
if strings.Contains(output, "No help topic for") {
t.Errorf("expect a help topic, got none: \n%q", output)
}
for _, shouldContain := range []string{
cmd.Name, cmd.Description,
subCmdBar.Name, subCmdBar.Usage,
subCmdBaz.Name, subCmdBaz.Usage,
} {
if !strings.Contains(output, shouldContain) {
t.Errorf("want help to contain %q, did not: \n%q", shouldContain, output)
}
}
}
}
func TestApp_Run_SubcommandFullPath(t *testing.T) {
app := NewApp()
buf := new(bytes.Buffer)
app.Writer = buf
subCmd := Command{
Name: "bar",
Usage: "does bar things",
}
cmd := Command{
Name: "foo",
Description: "foo commands",
Subcommands: []Command{subCmd},
}
app.Commands = []Command{cmd}
err := app.Run([]string{"command", "foo", "bar", "--help"})
if err != nil {
t.Error(err)
}
output := buf.String()
if !strings.Contains(output, "foo bar - does bar things") {
t.Errorf("expected full path to subcommand: %s", output)
}
if !strings.Contains(output, "command foo bar [arguments...]") {
t.Errorf("expected full path to subcommand: %s", output)
}
}
func TestApp_Run_Help(t *testing.T) {
var helpArguments = [][]string{{"boom", "--help"}, {"boom", "-h"}, {"boom", "help"}}
for _, args := range helpArguments {
buf := new(bytes.Buffer)
t.Logf("==> checking with arguments %v", args)
app := NewApp()
app.Name = "boom"
app.Usage = "make an explosive entrance"
app.Writer = buf
app.Action = func(c *Context) {
buf.WriteString("boom I say!")
}
err := app.Run(args)
if err != nil {
t.Error(err)
}
output := buf.String()
t.Logf("output: %q\n", buf.Bytes())
if !strings.Contains(output, "boom - make an explosive entrance") {
t.Errorf("want help to contain %q, did not: \n%q", "boom - make an explosive entrance", output)
}
}
}
func TestApp_Run_Version(t *testing.T) {
var versionArguments = [][]string{{"boom", "--version"}, {"boom", "-v"}}
for _, args := range versionArguments {
buf := new(bytes.Buffer)
t.Logf("==> checking with arguments %v", args)
app := NewApp()
app.Name = "boom"
app.Usage = "make an explosive entrance"
app.Version = "0.1.0"
app.Writer = buf
app.Action = func(c *Context) {
buf.WriteString("boom I say!")
}
err := app.Run(args)
if err != nil {
t.Error(err)
}
output := buf.String()
t.Logf("output: %q\n", buf.Bytes())
if !strings.Contains(output, "0.1.0") {
t.Errorf("want version to contain %q, did not: \n%q", "0.1.0", output)
}
}
}
func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
app := NewApp()
app.Action = func(c *Context) {}
app.Before = func(c *Context) error { return fmt.Errorf("before error") }
app.After = func(c *Context) error { return fmt.Errorf("after error") }
err := app.Run([]string{"foo"})
if err == nil {
t.Fatalf("expected to recieve error from Run, got none")
}
if !strings.Contains(err.Error(), "before error") {
t.Errorf("expected text of error from Before method, but got none in \"%v\"", err)
}
if !strings.Contains(err.Error(), "after error") {
t.Errorf("expected text of error from After method, but got none in \"%v\"", err)
}
}
func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) {
app := NewApp()
app.Commands = []Command{
Command{
Name: "bar",
Before: func(c *Context) error { return fmt.Errorf("before error") },
After: func(c *Context) error { return fmt.Errorf("after error") },
},
}
err := app.Run([]string{"foo", "bar"})
if err == nil {
t.Fatalf("expected to recieve error from Run, got none")
}
if !strings.Contains(err.Error(), "before error") {
t.Errorf("expected text of error from Before method, but got none in \"%v\"", err)
}
if !strings.Contains(err.Error(), "after error") {
t.Errorf("expected text of error from After method, but got none in \"%v\"", err)
}
}

View File

@ -0,0 +1,15 @@
#! /bin/bash
: ${PROG:=$(basename ${BASH_SOURCE})}
_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

View File

@ -0,0 +1,5 @@
autoload -U compinit && compinit
autoload -U bashcompinit && bashcompinit
script_dir=$(dirname $0)
source ${script_dir}/bash_autocomplete

View File

@ -0,0 +1,40 @@
// Package cli provides a minimal framework for creating and organizing command line
// Go applications. cli is designed to be easy to understand and write, the most simple
// cli application can be written as follows:
// func main() {
// cli.NewApp().Run(os.Args)
// }
//
// Of course this application does not do much, so let's make this an actual application:
// func main() {
// app := cli.NewApp()
// app.Name = "greet"
// app.Usage = "say a greeting"
// app.Action = func(c *cli.Context) {
// println("Greetings")
// }
//
// app.Run(os.Args)
// }
package cli
import (
"strings"
)
type MultiError struct {
Errors []error
}
func NewMultiError(err ...error) MultiError {
return MultiError{Errors: err}
}
func (m MultiError) Error() string {
errs := make([]string, len(m.Errors))
for i, err := range m.Errors {
errs[i] = err.Error()
}
return strings.Join(errs, "\n")
}

View File

@ -0,0 +1,98 @@
package cli
import (
"os"
)
func Example() {
app := NewApp()
app.Name = "todo"
app.Usage = "task list on the command line"
app.Commands = []Command{
{
Name: "add",
Aliases: []string{"a"},
Usage: "add a task to the list",
Action: func(c *Context) {
println("added task: ", c.Args().First())
},
},
{
Name: "complete",
Aliases: []string{"c"},
Usage: "complete a task on the list",
Action: func(c *Context) {
println("completed task: ", c.Args().First())
},
},
}
app.Run(os.Args)
}
func ExampleSubcommand() {
app := NewApp()
app.Name = "say"
app.Commands = []Command{
{
Name: "hello",
Aliases: []string{"hi"},
Usage: "use it to see a description",
Description: "This is how we describe hello the function",
Subcommands: []Command{
{
Name: "english",
Aliases: []string{"en"},
Usage: "sends a greeting in english",
Description: "greets someone in english",
Flags: []Flag{
StringFlag{
Name: "name",
Value: "Bob",
Usage: "Name of the person to greet",
},
},
Action: func(c *Context) {
println("Hello, ", c.String("name"))
},
}, {
Name: "spanish",
Aliases: []string{"sp"},
Usage: "sends a greeting in spanish",
Flags: []Flag{
StringFlag{
Name: "surname",
Value: "Jones",
Usage: "Surname of the person to greet",
},
},
Action: func(c *Context) {
println("Hola, ", c.String("surname"))
},
}, {
Name: "french",
Aliases: []string{"fr"},
Usage: "sends a greeting in french",
Flags: []Flag{
StringFlag{
Name: "nickname",
Value: "Stevie",
Usage: "Nickname of the person to greet",
},
},
Action: func(c *Context) {
println("Bonjour, ", c.String("nickname"))
},
},
},
}, {
Name: "bye",
Usage: "says goodbye",
Action: func(c *Context) {
println("bye")
},
},
}
app.Run(os.Args)
}

View File

@ -0,0 +1,200 @@
package cli
import (
"fmt"
"io/ioutil"
"strings"
)
// Command is a subcommand for a cli.App.
type Command struct {
// The name of the command
Name string
// short name of the command. Typically one character (deprecated, use `Aliases`)
ShortName string
// A list of aliases for the command
Aliases []string
// A short description of the usage of this command
Usage string
// A longer explanation of how the command works
Description string
// The function to call when checking for bash command completions
BashComplete func(context *Context)
// An action to execute before any sub-subcommands are run, but after the context is ready
// If a non-nil error is returned, no sub-subcommands are run
Before func(context *Context) error
// An action to execute after any subcommands are run, but after the subcommand has finished
// It is run even if Action() panics
After func(context *Context) error
// The function to call when this command is invoked
Action func(context *Context)
// List of child commands
Subcommands []Command
// List of flags to parse
Flags []Flag
// Treat all flags as normal arguments if true
SkipFlagParsing bool
// Boolean to hide built-in help command
HideHelp bool
commandNamePath []string
}
// Returns the full name of the command.
// For subcommands this ensures that parent commands are part of the command path
func (c Command) FullName() string {
if c.commandNamePath == nil {
return c.Name
}
return strings.Join(c.commandNamePath, " ")
}
// Invokes the command given the context, parses ctx.Args() to generate command-specific flags
func (c Command) Run(ctx *Context) error {
if len(c.Subcommands) > 0 || c.Before != nil || c.After != nil {
return c.startApp(ctx)
}
if !c.HideHelp && (HelpFlag != BoolFlag{}) {
// append help to flags
c.Flags = append(
c.Flags,
HelpFlag,
)
}
if ctx.App.EnableBashCompletion {
c.Flags = append(c.Flags, BashCompletionFlag)
}
set := flagSet(c.Name, c.Flags)
set.SetOutput(ioutil.Discard)
firstFlagIndex := -1
terminatorIndex := -1
for index, arg := range ctx.Args() {
if arg == "--" {
terminatorIndex = index
break
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
firstFlagIndex = index
}
}
var err error
if firstFlagIndex > -1 && !c.SkipFlagParsing {
args := ctx.Args()
regularArgs := make([]string, len(args[1:firstFlagIndex]))
copy(regularArgs, args[1:firstFlagIndex])
var flagArgs []string
if terminatorIndex > -1 {
flagArgs = args[firstFlagIndex:terminatorIndex]
regularArgs = append(regularArgs, args[terminatorIndex:]...)
} else {
flagArgs = args[firstFlagIndex:]
}
err = set.Parse(append(flagArgs, regularArgs...))
} else {
err = set.Parse(ctx.Args().Tail())
}
if err != nil {
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.")
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
return err
}
nerr := normalizeFlags(c.Flags, set)
if nerr != nil {
fmt.Fprintln(ctx.App.Writer, nerr)
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
return nerr
}
context := NewContext(ctx.App, set, ctx)
if checkCommandCompletions(context, c.Name) {
return nil
}
if checkCommandHelp(context, c.Name) {
return nil
}
context.Command = c
c.Action(context)
return nil
}
func (c Command) Names() []string {
names := []string{c.Name}
if c.ShortName != "" {
names = append(names, c.ShortName)
}
return append(names, c.Aliases...)
}
// Returns true if Command.Name or Command.ShortName matches given name
func (c Command) HasName(name string) bool {
for _, n := range c.Names() {
if n == name {
return true
}
}
return false
}
func (c Command) startApp(ctx *Context) error {
app := NewApp()
// set the name and usage
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
if c.Description != "" {
app.Usage = c.Description
} else {
app.Usage = c.Usage
}
// set CommandNotFound
app.CommandNotFound = ctx.App.CommandNotFound
// set the flags and commands
app.Commands = c.Subcommands
app.Flags = c.Flags
app.HideHelp = c.HideHelp
app.Version = ctx.App.Version
app.HideVersion = ctx.App.HideVersion
app.Compiled = ctx.App.Compiled
app.Author = ctx.App.Author
app.Email = ctx.App.Email
app.Writer = ctx.App.Writer
// bash completion
app.EnableBashCompletion = ctx.App.EnableBashCompletion
if c.BashComplete != nil {
app.BashComplete = c.BashComplete
}
// set the actions
app.Before = c.Before
app.After = c.After
if c.Action != nil {
app.Action = c.Action
} else {
app.Action = helpSubcommand.Action
}
var newCmds []Command
for _, cc := range app.Commands {
cc.commandNamePath = []string{c.Name, cc.Name}
newCmds = append(newCmds, cc)
}
app.Commands = newCmds
return app.RunAsSubcommand(ctx)
}

View File

@ -0,0 +1,47 @@
package cli
import (
"flag"
"testing"
)
func TestCommandDoNotIgnoreFlags(t *testing.T) {
app := NewApp()
set := flag.NewFlagSet("test", 0)
test := []string{"blah", "blah", "-break"}
set.Parse(test)
c := NewContext(app, set, nil)
command := Command{
Name: "test-cmd",
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(_ *Context) {},
}
err := command.Run(c)
expect(t, err.Error(), "flag provided but not defined: -break")
}
func TestCommandIgnoreFlags(t *testing.T) {
app := NewApp()
set := flag.NewFlagSet("test", 0)
test := []string{"blah", "blah"}
set.Parse(test)
c := NewContext(app, set, nil)
command := Command{
Name: "test-cmd",
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(_ *Context) {},
SkipFlagParsing: true,
}
err := command.Run(c)
expect(t, err, nil)
}

View File

@ -0,0 +1,388 @@
package cli
import (
"errors"
"flag"
"strconv"
"strings"
"time"
)
// Context is a type that is passed through to
// each Handler action in a cli application. Context
// can be used to retrieve context-specific Args and
// parsed command-line options.
type Context struct {
App *App
Command Command
flagSet *flag.FlagSet
setFlags map[string]bool
globalSetFlags map[string]bool
parentContext *Context
}
// Creates a new context. For use in when invoking an App or Command action.
func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
return &Context{App: app, flagSet: set, parentContext: parentCtx}
}
// Looks up the value of a local int flag, returns 0 if no int flag exists
func (c *Context) Int(name string) int {
return lookupInt(name, c.flagSet)
}
// Looks up the value of a local time.Duration flag, returns 0 if no time.Duration flag exists
func (c *Context) Duration(name string) time.Duration {
return lookupDuration(name, c.flagSet)
}
// Looks up the value of a local float64 flag, returns 0 if no float64 flag exists
func (c *Context) Float64(name string) float64 {
return lookupFloat64(name, c.flagSet)
}
// Looks up the value of a local bool flag, returns false if no bool flag exists
func (c *Context) Bool(name string) bool {
return lookupBool(name, c.flagSet)
}
// Looks up the value of a local boolT flag, returns false if no bool flag exists
func (c *Context) BoolT(name string) bool {
return lookupBoolT(name, c.flagSet)
}
// Looks up the value of a local string flag, returns "" if no string flag exists
func (c *Context) String(name string) string {
return lookupString(name, c.flagSet)
}
// Looks up the value of a local string slice flag, returns nil if no string slice flag exists
func (c *Context) StringSlice(name string) []string {
return lookupStringSlice(name, c.flagSet)
}
// Looks up the value of a local int slice flag, returns nil if no int slice flag exists
func (c *Context) IntSlice(name string) []int {
return lookupIntSlice(name, c.flagSet)
}
// Looks up the value of a local generic flag, returns nil if no generic flag exists
func (c *Context) Generic(name string) interface{} {
return lookupGeneric(name, c.flagSet)
}
// Looks up the value of a global int flag, returns 0 if no int flag exists
func (c *Context) GlobalInt(name string) int {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupInt(name, fs)
}
return 0
}
// Looks up the value of a global time.Duration flag, returns 0 if no time.Duration flag exists
func (c *Context) GlobalDuration(name string) time.Duration {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupDuration(name, fs)
}
return 0
}
// Looks up the value of a global bool flag, returns false if no bool flag exists
func (c *Context) GlobalBool(name string) bool {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupBool(name, fs)
}
return false
}
// Looks up the value of a global string flag, returns "" if no string flag exists
func (c *Context) GlobalString(name string) string {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupString(name, fs)
}
return ""
}
// Looks up the value of a global string slice flag, returns nil if no string slice flag exists
func (c *Context) GlobalStringSlice(name string) []string {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupStringSlice(name, fs)
}
return nil
}
// Looks up the value of a global int slice flag, returns nil if no int slice flag exists
func (c *Context) GlobalIntSlice(name string) []int {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupIntSlice(name, fs)
}
return nil
}
// Looks up the value of a global generic flag, returns nil if no generic flag exists
func (c *Context) GlobalGeneric(name string) interface{} {
if fs := lookupGlobalFlagSet(name, c); fs != nil {
return lookupGeneric(name, fs)
}
return nil
}
// Returns the number of flags set
func (c *Context) NumFlags() int {
return c.flagSet.NFlag()
}
// Determines if the flag was actually set
func (c *Context) IsSet(name string) bool {
if c.setFlags == nil {
c.setFlags = make(map[string]bool)
c.flagSet.Visit(func(f *flag.Flag) {
c.setFlags[f.Name] = true
})
}
return c.setFlags[name] == true
}
// Determines if the global flag was actually set
func (c *Context) GlobalIsSet(name string) bool {
if c.globalSetFlags == nil {
c.globalSetFlags = make(map[string]bool)
ctx := c
if ctx.parentContext != nil {
ctx = ctx.parentContext
}
for ; ctx != nil && c.globalSetFlags[name] == false; ctx = ctx.parentContext {
ctx.flagSet.Visit(func(f *flag.Flag) {
c.globalSetFlags[f.Name] = true
})
}
}
return c.globalSetFlags[name]
}
// Returns a slice of flag names used in this context.
func (c *Context) FlagNames() (names []string) {
for _, flag := range c.Command.Flags {
name := strings.Split(flag.getName(), ",")[0]
if name == "help" {
continue
}
names = append(names, name)
}
return
}
// Returns a slice of global flag names used by the app.
func (c *Context) GlobalFlagNames() (names []string) {
for _, flag := range c.App.Flags {
name := strings.Split(flag.getName(), ",")[0]
if name == "help" || name == "version" {
continue
}
names = append(names, name)
}
return
}
// Returns the parent context, if any
func (c *Context) Parent() *Context {
return c.parentContext
}
type Args []string
// Returns the command line arguments associated with the context.
func (c *Context) Args() Args {
args := Args(c.flagSet.Args())
return args
}
// Returns the nth argument, or else a blank string
func (a Args) Get(n int) string {
if len(a) > n {
return a[n]
}
return ""
}
// Returns the first argument, or else a blank string
func (a Args) First() string {
return a.Get(0)
}
// Return the rest of the arguments (not the first one)
// or else an empty string slice
func (a Args) Tail() []string {
if len(a) >= 2 {
return []string(a)[1:]
}
return []string{}
}
// Checks if there are any arguments present
func (a Args) Present() bool {
return len(a) != 0
}
// Swaps arguments at the given indexes
func (a Args) Swap(from, to int) error {
if from >= len(a) || to >= len(a) {
return errors.New("index out of range")
}
a[from], a[to] = a[to], a[from]
return nil
}
func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet {
if ctx.parentContext != nil {
ctx = ctx.parentContext
}
for ; ctx != nil; ctx = ctx.parentContext {
if f := ctx.flagSet.Lookup(name); f != nil {
return ctx.flagSet
}
}
return nil
}
func lookupInt(name string, set *flag.FlagSet) int {
f := set.Lookup(name)
if f != nil {
val, err := strconv.Atoi(f.Value.String())
if err != nil {
return 0
}
return val
}
return 0
}
func lookupDuration(name string, set *flag.FlagSet) time.Duration {
f := set.Lookup(name)
if f != nil {
val, err := time.ParseDuration(f.Value.String())
if err == nil {
return val
}
}
return 0
}
func lookupFloat64(name string, set *flag.FlagSet) float64 {
f := set.Lookup(name)
if f != nil {
val, err := strconv.ParseFloat(f.Value.String(), 64)
if err != nil {
return 0
}
return val
}
return 0
}
func lookupString(name string, set *flag.FlagSet) string {
f := set.Lookup(name)
if f != nil {
return f.Value.String()
}
return ""
}
func lookupStringSlice(name string, set *flag.FlagSet) []string {
f := set.Lookup(name)
if f != nil {
return (f.Value.(*StringSlice)).Value()
}
return nil
}
func lookupIntSlice(name string, set *flag.FlagSet) []int {
f := set.Lookup(name)
if f != nil {
return (f.Value.(*IntSlice)).Value()
}
return nil
}
func lookupGeneric(name string, set *flag.FlagSet) interface{} {
f := set.Lookup(name)
if f != nil {
return f.Value
}
return nil
}
func lookupBool(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
val, err := strconv.ParseBool(f.Value.String())
if err != nil {
return false
}
return val
}
return false
}
func lookupBoolT(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
val, err := strconv.ParseBool(f.Value.String())
if err != nil {
return true
}
return val
}
return false
}
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
switch ff.Value.(type) {
case *StringSlice:
default:
set.Set(name, ff.Value.String())
}
}
func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
visited := make(map[string]bool)
set.Visit(func(f *flag.Flag) {
visited[f.Name] = true
})
for _, f := range flags {
parts := strings.Split(f.getName(), ",")
if len(parts) == 1 {
continue
}
var ff *flag.Flag
for _, name := range parts {
name = strings.Trim(name, " ")
if visited[name] {
if ff != nil {
return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name)
}
ff = set.Lookup(name)
}
}
if ff == nil {
continue
}
for _, name := range parts {
name = strings.Trim(name, " ")
if !visited[name] {
copyFlag(name, ff, set)
}
}
}
return nil
}

View File

@ -0,0 +1,113 @@
package cli
import (
"flag"
"testing"
"time"
)
func TestNewContext(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Int("myflag", 12, "doc")
globalSet := flag.NewFlagSet("test", 0)
globalSet.Int("myflag", 42, "doc")
globalCtx := NewContext(nil, globalSet, nil)
command := Command{Name: "mycommand"}
c := NewContext(nil, set, globalCtx)
c.Command = command
expect(t, c.Int("myflag"), 12)
expect(t, c.GlobalInt("myflag"), 42)
expect(t, c.Command.Name, "mycommand")
}
func TestContext_Int(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Int("myflag", 12, "doc")
c := NewContext(nil, set, nil)
expect(t, c.Int("myflag"), 12)
}
func TestContext_Duration(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Duration("myflag", time.Duration(12*time.Second), "doc")
c := NewContext(nil, set, nil)
expect(t, c.Duration("myflag"), time.Duration(12*time.Second))
}
func TestContext_String(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.String("myflag", "hello world", "doc")
c := NewContext(nil, set, nil)
expect(t, c.String("myflag"), "hello world")
}
func TestContext_Bool(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("myflag", false, "doc")
c := NewContext(nil, set, nil)
expect(t, c.Bool("myflag"), false)
}
func TestContext_BoolT(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("myflag", true, "doc")
c := NewContext(nil, set, nil)
expect(t, c.BoolT("myflag"), true)
}
func TestContext_Args(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("myflag", false, "doc")
c := NewContext(nil, set, nil)
set.Parse([]string{"--myflag", "bat", "baz"})
expect(t, len(c.Args()), 2)
expect(t, c.Bool("myflag"), true)
}
func TestContext_IsSet(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("myflag", false, "doc")
set.String("otherflag", "hello world", "doc")
globalSet := flag.NewFlagSet("test", 0)
globalSet.Bool("myflagGlobal", true, "doc")
globalCtx := NewContext(nil, globalSet, nil)
c := NewContext(nil, set, globalCtx)
set.Parse([]string{"--myflag", "bat", "baz"})
globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"})
expect(t, c.IsSet("myflag"), true)
expect(t, c.IsSet("otherflag"), false)
expect(t, c.IsSet("bogusflag"), false)
expect(t, c.IsSet("myflagGlobal"), false)
}
func TestContext_GlobalIsSet(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("myflag", false, "doc")
set.String("otherflag", "hello world", "doc")
globalSet := flag.NewFlagSet("test", 0)
globalSet.Bool("myflagGlobal", true, "doc")
globalSet.Bool("myflagGlobalUnset", true, "doc")
globalCtx := NewContext(nil, globalSet, nil)
c := NewContext(nil, set, globalCtx)
set.Parse([]string{"--myflag", "bat", "baz"})
globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"})
expect(t, c.GlobalIsSet("myflag"), false)
expect(t, c.GlobalIsSet("otherflag"), false)
expect(t, c.GlobalIsSet("bogusflag"), false)
expect(t, c.GlobalIsSet("myflagGlobal"), true)
expect(t, c.GlobalIsSet("myflagGlobalUnset"), false)
expect(t, c.GlobalIsSet("bogusGlobal"), false)
}
func TestContext_NumFlags(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("myflag", false, "doc")
set.String("otherflag", "hello world", "doc")
globalSet := flag.NewFlagSet("test", 0)
globalSet.Bool("myflagGlobal", true, "doc")
globalCtx := NewContext(nil, globalSet, nil)
c := NewContext(nil, set, globalCtx)
set.Parse([]string{"--myflag", "--otherflag=foo"})
globalSet.Parse([]string{"--myflagGlobal"})
expect(t, c.NumFlags(), 2)
}

View File

@ -0,0 +1,497 @@
package cli
import (
"flag"
"fmt"
"os"
"strconv"
"strings"
"time"
)
// This flag enables bash-completion for all commands and subcommands
var BashCompletionFlag = BoolFlag{
Name: "generate-bash-completion",
}
// This flag prints the version for the application
var VersionFlag = BoolFlag{
Name: "version, v",
Usage: "print the version",
}
// This flag prints the help for all commands and subcommands
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
// unless HideHelp is set to true)
var HelpFlag = BoolFlag{
Name: "help, h",
Usage: "show help",
}
// Flag is a common interface related to parsing flags in cli.
// For more advanced flag parsing techniques, it is recomended that
// this interface be implemented.
type Flag interface {
fmt.Stringer
// Apply Flag settings to the given flag set
Apply(*flag.FlagSet)
getName() string
}
func flagSet(name string, flags []Flag) *flag.FlagSet {
set := flag.NewFlagSet(name, flag.ContinueOnError)
for _, f := range flags {
f.Apply(set)
}
return set
}
func eachName(longName string, fn func(string)) {
parts := strings.Split(longName, ",")
for _, name := range parts {
name = strings.Trim(name, " ")
fn(name)
}
}
// Generic is a generic parseable type identified by a specific flag
type Generic interface {
Set(value string) error
String() string
}
// GenericFlag is the flag type for types implementing Generic
type GenericFlag struct {
Name string
Value Generic
Usage string
EnvVar string
}
// String returns the string representation of the generic flag to display the
// help text to the user (uses the String() method of the generic flag to show
// the value)
func (f GenericFlag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s \"%v\"\t%v", prefixFor(f.Name), f.Name, f.Value, f.Usage))
}
// Apply takes the flagset and calls Set on the generic flag with the value
// provided by the user for parsing by the flag
func (f GenericFlag) Apply(set *flag.FlagSet) {
val := f.Value
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
val.Set(envVal)
break
}
}
}
eachName(f.Name, func(name string) {
set.Var(f.Value, name, f.Usage)
})
}
func (f GenericFlag) getName() string {
return f.Name
}
// StringSlice is an opaque type for []string to satisfy flag.Value
type StringSlice []string
// Set appends the string value to the list of values
func (f *StringSlice) Set(value string) error {
*f = append(*f, value)
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *StringSlice) String() string {
return fmt.Sprintf("%s", *f)
}
// Value returns the slice of strings set by this flag
func (f *StringSlice) Value() []string {
return *f
}
// StringSlice is a string flag that can be specified multiple times on the
// command-line
type StringSliceFlag struct {
Name string
Value *StringSlice
Usage string
EnvVar string
}
// String returns the usage
func (f StringSliceFlag) String() string {
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
pref := prefixFor(firstName)
return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f StringSliceFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
newVal := &StringSlice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
newVal.Set(s)
}
f.Value = newVal
break
}
}
}
eachName(f.Name, func(name string) {
if f.Value == nil {
f.Value = &StringSlice{}
}
set.Var(f.Value, name, f.Usage)
})
}
func (f StringSliceFlag) getName() string {
return f.Name
}
// StringSlice is an opaque type for []int to satisfy flag.Value
type IntSlice []int
// Set parses the value into an integer and appends it to the list of values
func (f *IntSlice) Set(value string) error {
tmp, err := strconv.Atoi(value)
if err != nil {
return err
} else {
*f = append(*f, tmp)
}
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *IntSlice) String() string {
return fmt.Sprintf("%d", *f)
}
// Value returns the slice of ints set by this flag
func (f *IntSlice) Value() []int {
return *f
}
// IntSliceFlag is an int flag that can be specified multiple times on the
// command-line
type IntSliceFlag struct {
Name string
Value *IntSlice
Usage string
EnvVar string
}
// String returns the usage
func (f IntSliceFlag) String() string {
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
pref := prefixFor(firstName)
return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f IntSliceFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
newVal := &IntSlice{}
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
err := newVal.Set(s)
if err != nil {
fmt.Fprintf(os.Stderr, err.Error())
}
}
f.Value = newVal
break
}
}
}
eachName(f.Name, func(name string) {
if f.Value == nil {
f.Value = &IntSlice{}
}
set.Var(f.Value, name, f.Usage)
})
}
func (f IntSliceFlag) getName() string {
return f.Name
}
// BoolFlag is a switch that defaults to false
type BoolFlag struct {
Name string
Usage string
EnvVar string
}
// String returns a readable representation of this value (for usage defaults)
func (f BoolFlag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f BoolFlag) Apply(set *flag.FlagSet) {
val := false
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
envValBool, err := strconv.ParseBool(envVal)
if err == nil {
val = envValBool
}
break
}
}
}
eachName(f.Name, func(name string) {
set.Bool(name, val, f.Usage)
})
}
func (f BoolFlag) getName() string {
return f.Name
}
// BoolTFlag this represents a boolean flag that is true by default, but can
// still be set to false by --some-flag=false
type BoolTFlag struct {
Name string
Usage string
EnvVar string
}
// String returns a readable representation of this value (for usage defaults)
func (f BoolTFlag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f BoolTFlag) Apply(set *flag.FlagSet) {
val := true
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
envValBool, err := strconv.ParseBool(envVal)
if err == nil {
val = envValBool
break
}
}
}
}
eachName(f.Name, func(name string) {
set.Bool(name, val, f.Usage)
})
}
func (f BoolTFlag) getName() string {
return f.Name
}
// StringFlag represents a flag that takes as string value
type StringFlag struct {
Name string
Value string
Usage string
EnvVar string
}
// String returns the usage
func (f StringFlag) String() string {
var fmtString string
fmtString = "%s %v\t%v"
if len(f.Value) > 0 {
fmtString = "%s \"%v\"\t%v"
} else {
fmtString = "%s %v\t%v"
}
return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f StringFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
f.Value = envVal
break
}
}
}
eachName(f.Name, func(name string) {
set.String(name, f.Value, f.Usage)
})
}
func (f StringFlag) getName() string {
return f.Name
}
// IntFlag is a flag that takes an integer
// Errors if the value provided cannot be parsed
type IntFlag struct {
Name string
Value int
Usage string
EnvVar string
}
// String returns the usage
func (f IntFlag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f IntFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err == nil {
f.Value = int(envValInt)
break
}
}
}
}
eachName(f.Name, func(name string) {
set.Int(name, f.Value, f.Usage)
})
}
func (f IntFlag) getName() string {
return f.Name
}
// DurationFlag is a flag that takes a duration specified in Go's duration
// format: https://golang.org/pkg/time/#ParseDuration
type DurationFlag struct {
Name string
Value time.Duration
Usage string
EnvVar string
}
// String returns a readable representation of this value (for usage defaults)
func (f DurationFlag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f DurationFlag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
envValDuration, err := time.ParseDuration(envVal)
if err == nil {
f.Value = envValDuration
break
}
}
}
}
eachName(f.Name, func(name string) {
set.Duration(name, f.Value, f.Usage)
})
}
func (f DurationFlag) getName() string {
return f.Name
}
// Float64Flag is a flag that takes an float value
// Errors if the value provided cannot be parsed
type Float64Flag struct {
Name string
Value float64
Usage string
EnvVar string
}
// String returns the usage
func (f Float64Flag) String() string {
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
}
// Apply populates the flag given the flag set and environment
func (f Float64Flag) Apply(set *flag.FlagSet) {
if f.EnvVar != "" {
for _, envVar := range strings.Split(f.EnvVar, ",") {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
envValFloat, err := strconv.ParseFloat(envVal, 10)
if err == nil {
f.Value = float64(envValFloat)
}
}
}
}
eachName(f.Name, func(name string) {
set.Float64(name, f.Value, f.Usage)
})
}
func (f Float64Flag) getName() string {
return f.Name
}
func prefixFor(name string) (prefix string) {
if len(name) == 1 {
prefix = "-"
} else {
prefix = "--"
}
return
}
func prefixedNames(fullName string) (prefixed string) {
parts := strings.Split(fullName, ",")
for i, name := range parts {
name = strings.Trim(name, " ")
prefixed += prefixFor(name) + name
if i < len(parts)-1 {
prefixed += ", "
}
}
return
}
func withEnvHint(envVar, str string) string {
envText := ""
if envVar != "" {
envText = fmt.Sprintf(" [$%s]", strings.Join(strings.Split(envVar, ","), ", $"))
}
return str + envText
}

View File

@ -0,0 +1,740 @@
package cli
import (
"fmt"
"os"
"reflect"
"strings"
"testing"
)
var boolFlagTests = []struct {
name string
expected string
}{
{"help", "--help\t"},
{"h", "-h\t"},
}
func TestBoolFlagHelpOutput(t *testing.T) {
for _, test := range boolFlagTests {
flag := BoolFlag{Name: test.name}
output := flag.String()
if output != test.expected {
t.Errorf("%s does not match %s", output, test.expected)
}
}
}
var stringFlagTests = []struct {
name string
value string
expected string
}{
{"help", "", "--help \t"},
{"h", "", "-h \t"},
{"h", "", "-h \t"},
{"test", "Something", "--test \"Something\"\t"},
}
func TestStringFlagHelpOutput(t *testing.T) {
for _, test := range stringFlagTests {
flag := StringFlag{Name: test.name, Value: test.value}
output := flag.String()
if output != test.expected {
t.Errorf("%s does not match %s", output, test.expected)
}
}
}
func TestStringFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv()
os.Setenv("APP_FOO", "derp")
for _, test := range stringFlagTests {
flag := StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"}
output := flag.String()
if !strings.HasSuffix(output, " [$APP_FOO]") {
t.Errorf("%s does not end with [$APP_FOO]", output)
}
}
}
var stringSliceFlagTests = []struct {
name string
value *StringSlice
expected string
}{
{"help", func() *StringSlice {
s := &StringSlice{}
s.Set("")
return s
}(), "--help [--help option --help option]\t"},
{"h", func() *StringSlice {
s := &StringSlice{}
s.Set("")
return s
}(), "-h [-h option -h option]\t"},
{"h", func() *StringSlice {
s := &StringSlice{}
s.Set("")
return s
}(), "-h [-h option -h option]\t"},
{"test", func() *StringSlice {
s := &StringSlice{}
s.Set("Something")
return s
}(), "--test [--test option --test option]\t"},
}
func TestStringSliceFlagHelpOutput(t *testing.T) {
for _, test := range stringSliceFlagTests {
flag := StringSliceFlag{Name: test.name, Value: test.value}
output := flag.String()
if output != test.expected {
t.Errorf("%q does not match %q", output, test.expected)
}
}
}
func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv()
os.Setenv("APP_QWWX", "11,4")
for _, test := range stringSliceFlagTests {
flag := StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"}
output := flag.String()
if !strings.HasSuffix(output, " [$APP_QWWX]") {
t.Errorf("%q does not end with [$APP_QWWX]", output)
}
}
}
var intFlagTests = []struct {
name string
expected string
}{
{"help", "--help \"0\"\t"},
{"h", "-h \"0\"\t"},
}
func TestIntFlagHelpOutput(t *testing.T) {
for _, test := range intFlagTests {
flag := IntFlag{Name: test.name}
output := flag.String()
if output != test.expected {
t.Errorf("%s does not match %s", output, test.expected)
}
}
}
func TestIntFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv()
os.Setenv("APP_BAR", "2")
for _, test := range intFlagTests {
flag := IntFlag{Name: test.name, EnvVar: "APP_BAR"}
output := flag.String()
if !strings.HasSuffix(output, " [$APP_BAR]") {
t.Errorf("%s does not end with [$APP_BAR]", output)
}
}
}
var durationFlagTests = []struct {
name string
expected string
}{
{"help", "--help \"0\"\t"},
{"h", "-h \"0\"\t"},
}
func TestDurationFlagHelpOutput(t *testing.T) {
for _, test := range durationFlagTests {
flag := DurationFlag{Name: test.name}
output := flag.String()
if output != test.expected {
t.Errorf("%s does not match %s", output, test.expected)
}
}
}
func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv()
os.Setenv("APP_BAR", "2h3m6s")
for _, test := range durationFlagTests {
flag := DurationFlag{Name: test.name, EnvVar: "APP_BAR"}
output := flag.String()
if !strings.HasSuffix(output, " [$APP_BAR]") {
t.Errorf("%s does not end with [$APP_BAR]", output)
}
}
}
var intSliceFlagTests = []struct {
name string
value *IntSlice
expected string
}{
{"help", &IntSlice{}, "--help [--help option --help option]\t"},
{"h", &IntSlice{}, "-h [-h option -h option]\t"},
{"h", &IntSlice{}, "-h [-h option -h option]\t"},
{"test", func() *IntSlice {
i := &IntSlice{}
i.Set("9")
return i
}(), "--test [--test option --test option]\t"},
}
func TestIntSliceFlagHelpOutput(t *testing.T) {
for _, test := range intSliceFlagTests {
flag := IntSliceFlag{Name: test.name, Value: test.value}
output := flag.String()
if output != test.expected {
t.Errorf("%q does not match %q", output, test.expected)
}
}
}
func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv()
os.Setenv("APP_SMURF", "42,3")
for _, test := range intSliceFlagTests {
flag := IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"}
output := flag.String()
if !strings.HasSuffix(output, " [$APP_SMURF]") {
t.Errorf("%q does not end with [$APP_SMURF]", output)
}
}
}
var float64FlagTests = []struct {
name string
expected string
}{
{"help", "--help \"0\"\t"},
{"h", "-h \"0\"\t"},
}
func TestFloat64FlagHelpOutput(t *testing.T) {
for _, test := range float64FlagTests {
flag := Float64Flag{Name: test.name}
output := flag.String()
if output != test.expected {
t.Errorf("%s does not match %s", output, test.expected)
}
}
}
func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv()
os.Setenv("APP_BAZ", "99.4")
for _, test := range float64FlagTests {
flag := Float64Flag{Name: test.name, EnvVar: "APP_BAZ"}
output := flag.String()
if !strings.HasSuffix(output, " [$APP_BAZ]") {
t.Errorf("%s does not end with [$APP_BAZ]", output)
}
}
}
var genericFlagTests = []struct {
name string
value Generic
expected string
}{
{"test", &Parser{"abc", "def"}, "--test \"abc,def\"\ttest flag"},
{"t", &Parser{"abc", "def"}, "-t \"abc,def\"\ttest flag"},
}
func TestGenericFlagHelpOutput(t *testing.T) {
for _, test := range genericFlagTests {
flag := GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"}
output := flag.String()
if output != test.expected {
t.Errorf("%q does not match %q", output, test.expected)
}
}
}
func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) {
os.Clearenv()
os.Setenv("APP_ZAP", "3")
for _, test := range genericFlagTests {
flag := GenericFlag{Name: test.name, EnvVar: "APP_ZAP"}
output := flag.String()
if !strings.HasSuffix(output, " [$APP_ZAP]") {
t.Errorf("%s does not end with [$APP_ZAP]", output)
}
}
}
func TestParseMultiString(t *testing.T) {
(&App{
Flags: []Flag{
StringFlag{Name: "serve, s"},
},
Action: func(ctx *Context) {
if ctx.String("serve") != "10" {
t.Errorf("main name not set")
}
if ctx.String("s") != "10" {
t.Errorf("short name not set")
}
},
}).Run([]string{"run", "-s", "10"})
}
func TestParseMultiStringFromEnv(t *testing.T) {
os.Clearenv()
os.Setenv("APP_COUNT", "20")
(&App{
Flags: []Flag{
StringFlag{Name: "count, c", EnvVar: "APP_COUNT"},
},
Action: func(ctx *Context) {
if ctx.String("count") != "20" {
t.Errorf("main name not set")
}
if ctx.String("c") != "20" {
t.Errorf("short name not set")
}
},
}).Run([]string{"run"})
}
func TestParseMultiStringFromEnvCascade(t *testing.T) {
os.Clearenv()
os.Setenv("APP_COUNT", "20")
(&App{
Flags: []Flag{
StringFlag{Name: "count, c", EnvVar: "COMPAT_COUNT,APP_COUNT"},
},
Action: func(ctx *Context) {
if ctx.String("count") != "20" {
t.Errorf("main name not set")
}
if ctx.String("c") != "20" {
t.Errorf("short name not set")
}
},
}).Run([]string{"run"})
}
func TestParseMultiStringSlice(t *testing.T) {
(&App{
Flags: []Flag{
StringSliceFlag{Name: "serve, s", Value: &StringSlice{}},
},
Action: func(ctx *Context) {
if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"10", "20"}) {
t.Errorf("main name not set")
}
if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"10", "20"}) {
t.Errorf("short name not set")
}
},
}).Run([]string{"run", "-s", "10", "-s", "20"})
}
func TestParseMultiStringSliceFromEnv(t *testing.T) {
os.Clearenv()
os.Setenv("APP_INTERVALS", "20,30,40")
(&App{
Flags: []Flag{
StringSliceFlag{Name: "intervals, i", Value: &StringSlice{}, EnvVar: "APP_INTERVALS"},
},
Action: func(ctx *Context) {
if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
t.Errorf("main name not set from env")
}
if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) {
t.Errorf("short name not set from env")
}
},
}).Run([]string{"run"})
}
func TestParseMultiStringSliceFromEnvCascade(t *testing.T) {
os.Clearenv()
os.Setenv("APP_INTERVALS", "20,30,40")
(&App{
Flags: []Flag{
StringSliceFlag{Name: "intervals, i", Value: &StringSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"},
},
Action: func(ctx *Context) {
if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
t.Errorf("main name not set from env")
}
if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) {
t.Errorf("short name not set from env")
}
},
}).Run([]string{"run"})
}
func TestParseMultiInt(t *testing.T) {
a := App{
Flags: []Flag{
IntFlag{Name: "serve, s"},
},
Action: func(ctx *Context) {
if ctx.Int("serve") != 10 {
t.Errorf("main name not set")
}
if ctx.Int("s") != 10 {
t.Errorf("short name not set")
}
},
}
a.Run([]string{"run", "-s", "10"})
}
func TestParseMultiIntFromEnv(t *testing.T) {
os.Clearenv()
os.Setenv("APP_TIMEOUT_SECONDS", "10")
a := App{
Flags: []Flag{
IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
},
Action: func(ctx *Context) {
if ctx.Int("timeout") != 10 {
t.Errorf("main name not set")
}
if ctx.Int("t") != 10 {
t.Errorf("short name not set")
}
},
}
a.Run([]string{"run"})
}
func TestParseMultiIntFromEnvCascade(t *testing.T) {
os.Clearenv()
os.Setenv("APP_TIMEOUT_SECONDS", "10")
a := App{
Flags: []Flag{
IntFlag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"},
},
Action: func(ctx *Context) {
if ctx.Int("timeout") != 10 {
t.Errorf("main name not set")
}
if ctx.Int("t") != 10 {
t.Errorf("short name not set")
}
},
}
a.Run([]string{"run"})
}
func TestParseMultiIntSlice(t *testing.T) {
(&App{
Flags: []Flag{
IntSliceFlag{Name: "serve, s", Value: &IntSlice{}},
},
Action: func(ctx *Context) {
if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) {
t.Errorf("main name not set")
}
if !reflect.DeepEqual(ctx.IntSlice("s"), []int{10, 20}) {
t.Errorf("short name not set")
}
},
}).Run([]string{"run", "-s", "10", "-s", "20"})
}
func TestParseMultiIntSliceFromEnv(t *testing.T) {
os.Clearenv()
os.Setenv("APP_INTERVALS", "20,30,40")
(&App{
Flags: []Flag{
IntSliceFlag{Name: "intervals, i", Value: &IntSlice{}, EnvVar: "APP_INTERVALS"},
},
Action: func(ctx *Context) {
if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) {
t.Errorf("main name not set from env")
}
if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) {
t.Errorf("short name not set from env")
}
},
}).Run([]string{"run"})
}
func TestParseMultiIntSliceFromEnvCascade(t *testing.T) {
os.Clearenv()
os.Setenv("APP_INTERVALS", "20,30,40")
(&App{
Flags: []Flag{
IntSliceFlag{Name: "intervals, i", Value: &IntSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"},
},
Action: func(ctx *Context) {
if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) {
t.Errorf("main name not set from env")
}
if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) {
t.Errorf("short name not set from env")
}
},
}).Run([]string{"run"})
}
func TestParseMultiFloat64(t *testing.T) {
a := App{
Flags: []Flag{
Float64Flag{Name: "serve, s"},
},
Action: func(ctx *Context) {
if ctx.Float64("serve") != 10.2 {
t.Errorf("main name not set")
}
if ctx.Float64("s") != 10.2 {
t.Errorf("short name not set")
}
},
}
a.Run([]string{"run", "-s", "10.2"})
}
func TestParseMultiFloat64FromEnv(t *testing.T) {
os.Clearenv()
os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
a := App{
Flags: []Flag{
Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
},
Action: func(ctx *Context) {
if ctx.Float64("timeout") != 15.5 {
t.Errorf("main name not set")
}
if ctx.Float64("t") != 15.5 {
t.Errorf("short name not set")
}
},
}
a.Run([]string{"run"})
}
func TestParseMultiFloat64FromEnvCascade(t *testing.T) {
os.Clearenv()
os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
a := App{
Flags: []Flag{
Float64Flag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"},
},
Action: func(ctx *Context) {
if ctx.Float64("timeout") != 15.5 {
t.Errorf("main name not set")
}
if ctx.Float64("t") != 15.5 {
t.Errorf("short name not set")
}
},
}
a.Run([]string{"run"})
}
func TestParseMultiBool(t *testing.T) {
a := App{
Flags: []Flag{
BoolFlag{Name: "serve, s"},
},
Action: func(ctx *Context) {
if ctx.Bool("serve") != true {
t.Errorf("main name not set")
}
if ctx.Bool("s") != true {
t.Errorf("short name not set")
}
},
}
a.Run([]string{"run", "--serve"})
}
func TestParseMultiBoolFromEnv(t *testing.T) {
os.Clearenv()
os.Setenv("APP_DEBUG", "1")
a := App{
Flags: []Flag{
BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"},
},
Action: func(ctx *Context) {
if ctx.Bool("debug") != true {
t.Errorf("main name not set from env")
}
if ctx.Bool("d") != true {
t.Errorf("short name not set from env")
}
},
}
a.Run([]string{"run"})
}
func TestParseMultiBoolFromEnvCascade(t *testing.T) {
os.Clearenv()
os.Setenv("APP_DEBUG", "1")
a := App{
Flags: []Flag{
BoolFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"},
},
Action: func(ctx *Context) {
if ctx.Bool("debug") != true {
t.Errorf("main name not set from env")
}
if ctx.Bool("d") != true {
t.Errorf("short name not set from env")
}
},
}
a.Run([]string{"run"})
}
func TestParseMultiBoolT(t *testing.T) {
a := App{
Flags: []Flag{
BoolTFlag{Name: "serve, s"},
},
Action: func(ctx *Context) {
if ctx.BoolT("serve") != true {
t.Errorf("main name not set")
}
if ctx.BoolT("s") != true {
t.Errorf("short name not set")
}
},
}
a.Run([]string{"run", "--serve"})
}
func TestParseMultiBoolTFromEnv(t *testing.T) {
os.Clearenv()
os.Setenv("APP_DEBUG", "0")
a := App{
Flags: []Flag{
BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"},
},
Action: func(ctx *Context) {
if ctx.BoolT("debug") != false {
t.Errorf("main name not set from env")
}
if ctx.BoolT("d") != false {
t.Errorf("short name not set from env")
}
},
}
a.Run([]string{"run"})
}
func TestParseMultiBoolTFromEnvCascade(t *testing.T) {
os.Clearenv()
os.Setenv("APP_DEBUG", "0")
a := App{
Flags: []Flag{
BoolTFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"},
},
Action: func(ctx *Context) {
if ctx.BoolT("debug") != false {
t.Errorf("main name not set from env")
}
if ctx.BoolT("d") != false {
t.Errorf("short name not set from env")
}
},
}
a.Run([]string{"run"})
}
type Parser [2]string
func (p *Parser) Set(value string) error {
parts := strings.Split(value, ",")
if len(parts) != 2 {
return fmt.Errorf("invalid format")
}
(*p)[0] = parts[0]
(*p)[1] = parts[1]
return nil
}
func (p *Parser) String() string {
return fmt.Sprintf("%s,%s", p[0], p[1])
}
func TestParseGeneric(t *testing.T) {
a := App{
Flags: []Flag{
GenericFlag{Name: "serve, s", Value: &Parser{}},
},
Action: func(ctx *Context) {
if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) {
t.Errorf("main name not set")
}
if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"10", "20"}) {
t.Errorf("short name not set")
}
},
}
a.Run([]string{"run", "-s", "10,20"})
}
func TestParseGenericFromEnv(t *testing.T) {
os.Clearenv()
os.Setenv("APP_SERVE", "20,30")
a := App{
Flags: []Flag{
GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"},
},
Action: func(ctx *Context) {
if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"20", "30"}) {
t.Errorf("main name not set from env")
}
if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"20", "30"}) {
t.Errorf("short name not set from env")
}
},
}
a.Run([]string{"run"})
}
func TestParseGenericFromEnvCascade(t *testing.T) {
os.Clearenv()
os.Setenv("APP_FOO", "99,2000")
a := App{
Flags: []Flag{
GenericFlag{Name: "foos", Value: &Parser{}, EnvVar: "COMPAT_FOO,APP_FOO"},
},
Action: func(ctx *Context) {
if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) {
t.Errorf("value not set from env")
}
},
}
a.Run([]string{"run"})
}

View File

@ -0,0 +1,238 @@
package cli
import (
"fmt"
"io"
"strings"
"text/tabwriter"
"text/template"
)
// The text template for the Default help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var AppHelpTemplate = `NAME:
{{.Name}} - {{.Usage}}
USAGE:
{{.Name}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} [arguments...]
{{if .Version}}
VERSION:
{{.Version}}
{{end}}{{if len .Authors}}
AUTHOR(S):
{{range .Authors}}{{ . }}{{end}}
{{end}}{{if .Commands}}
COMMANDS:
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
{{end}}{{end}}{{if .Flags}}
GLOBAL OPTIONS:
{{range .Flags}}{{.}}
{{end}}{{end}}{{if .Copyright }}
COPYRIGHT:
{{.Copyright}}
{{end}}
`
// The text template for the command help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var CommandHelpTemplate = `NAME:
{{.FullName}} - {{.Usage}}
USAGE:
command {{.FullName}}{{if .Flags}} [command options]{{end}} [arguments...]{{if .Description}}
DESCRIPTION:
{{.Description}}{{end}}{{if .Flags}}
OPTIONS:
{{range .Flags}}{{.}}
{{end}}{{ end }}
`
// The text template for the subcommand help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var SubcommandHelpTemplate = `NAME:
{{.Name}} - {{.Usage}}
USAGE:
{{.Name}} command{{if .Flags}} [command options]{{end}} [arguments...]
COMMANDS:
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
{{end}}{{if .Flags}}
OPTIONS:
{{range .Flags}}{{.}}
{{end}}{{end}}
`
var helpCommand = Command{
Name: "help",
Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command",
Action: func(c *Context) {
args := c.Args()
if args.Present() {
ShowCommandHelp(c, args.First())
} else {
ShowAppHelp(c)
}
},
}
var helpSubcommand = Command{
Name: "help",
Aliases: []string{"h"},
Usage: "Shows a list of commands or help for one command",
Action: func(c *Context) {
args := c.Args()
if args.Present() {
ShowCommandHelp(c, args.First())
} else {
ShowSubcommandHelp(c)
}
},
}
// Prints help for the App or Command
type helpPrinter func(w io.Writer, templ string, data interface{})
var HelpPrinter helpPrinter = printHelp
// Prints version for the App
var VersionPrinter = printVersion
func ShowAppHelp(c *Context) {
HelpPrinter(c.App.Writer, AppHelpTemplate, c.App)
}
// Prints the list of subcommands as the default app completion method
func DefaultAppComplete(c *Context) {
for _, command := range c.App.Commands {
for _, name := range command.Names() {
fmt.Fprintln(c.App.Writer, name)
}
}
}
// Prints help for the given command
func ShowCommandHelp(ctx *Context, command string) {
// show the subcommand help for a command with subcommands
if command == "" {
HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App)
return
}
for _, c := range ctx.App.Commands {
if c.HasName(command) {
HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c)
return
}
}
if ctx.App.CommandNotFound != nil {
ctx.App.CommandNotFound(ctx, command)
} else {
fmt.Fprintf(ctx.App.Writer, "No help topic for '%v'\n", command)
}
}
// Prints help for the given subcommand
func ShowSubcommandHelp(c *Context) {
ShowCommandHelp(c, c.Command.Name)
}
// Prints the version number of the App
func ShowVersion(c *Context) {
VersionPrinter(c)
}
func printVersion(c *Context) {
fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
}
// Prints the lists of commands within a given context
func ShowCompletions(c *Context) {
a := c.App
if a != nil && a.BashComplete != nil {
a.BashComplete(c)
}
}
// Prints the custom completions for a given command
func ShowCommandCompletions(ctx *Context, command string) {
c := ctx.App.Command(command)
if c != nil && c.BashComplete != nil {
c.BashComplete(ctx)
}
}
func printHelp(out io.Writer, templ string, data interface{}) {
funcMap := template.FuncMap{
"join": strings.Join,
}
w := tabwriter.NewWriter(out, 0, 8, 1, '\t', 0)
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
err := t.Execute(w, data)
if err != nil {
panic(err)
}
w.Flush()
}
func checkVersion(c *Context) bool {
if c.GlobalBool("version") || c.GlobalBool("v") || c.Bool("version") || c.Bool("v") {
ShowVersion(c)
return true
}
return false
}
func checkHelp(c *Context) bool {
if c.GlobalBool("h") || c.GlobalBool("help") || c.Bool("h") || c.Bool("help") {
ShowAppHelp(c)
return true
}
return false
}
func checkCommandHelp(c *Context, name string) bool {
if c.Bool("h") || c.Bool("help") {
ShowCommandHelp(c, name)
return true
}
return false
}
func checkSubcommandHelp(c *Context) bool {
if c.GlobalBool("h") || c.GlobalBool("help") {
ShowSubcommandHelp(c)
return true
}
return false
}
func checkCompletions(c *Context) bool {
if (c.GlobalBool(BashCompletionFlag.Name) || c.Bool(BashCompletionFlag.Name)) && c.App.EnableBashCompletion {
ShowCompletions(c)
return true
}
return false
}
func checkCommandCompletions(c *Context, name string) bool {
if c.Bool(BashCompletionFlag.Name) && c.App.EnableBashCompletion {
ShowCommandCompletions(c, name)
return true
}
return false
}

View File

@ -0,0 +1,36 @@
package cli
import (
"bytes"
"testing"
)
func Test_ShowAppHelp_NoAuthor(t *testing.T) {
output := new(bytes.Buffer)
app := NewApp()
app.Writer = output
c := NewContext(app, nil, nil)
ShowAppHelp(c)
if bytes.Index(output.Bytes(), []byte("AUTHOR(S):")) != -1 {
t.Errorf("expected\n%snot to include %s", output.String(), "AUTHOR(S):")
}
}
func Test_ShowAppHelp_NoVersion(t *testing.T) {
output := new(bytes.Buffer)
app := NewApp()
app.Writer = output
app.Version = ""
c := NewContext(app, nil, nil)
ShowAppHelp(c)
if bytes.Index(output.Bytes(), []byte("VERSION:")) != -1 {
t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:")
}
}

View File

@ -0,0 +1,19 @@
package cli
import (
"reflect"
"testing"
)
/* Test Helpers */
func expect(t *testing.T, a interface{}, b interface{}) {
if a != b {
t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
}
}
func refute(t *testing.T, a interface{}, b interface{}) {
if a == b {
t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
}
}

View File

@ -1,191 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2015 The Linux Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,129 +0,0 @@
# Open Container Specifications
This project is where the [Open Container Initiative](http://www.opencontainers.org/) Specifications are written. This is a work in progress. We should have a first draft by end of July 2015.
Table of Contents
- [Filesystem Bundle](bundle.md)
- [Container Configuration](config.md)
- [Linux Specific Configuration](config-linux.md)
- [Runtime and Lifecycle](runtime.md)
## Use Cases
To provide context for users the following section gives example use cases for each part of the spec.
### Filesystem Bundle & Configuration
- A user can create a root filesystem and configuration, with low-level OS and host specific details, and launch it as a container under an Open Container runtime.
# The 5 principles of Standard Containers
Define a unit of software delivery called a Standard Container. The goal of a Standard Container is to encapsulate a software component and all its dependencies in a format that is self-describing and portable, so that any compliant runtime can run it without extra dependencies, regardless of the underlying machine and the contents of the container.
The specification for Standard Containers is straightforward. It mostly defines 1) a file format, 2) a set of standard operations, and 3) an execution environment.
A great analogy for this is the shipping container. Just like how Standard Containers are a fundamental unit of software delivery, shipping containers are a fundamental unit of physical delivery.
## 1. Standard operations
Just like shipping containers, Standard Containers define a set of STANDARD OPERATIONS. Shipping containers can be lifted, stacked, locked, loaded, unloaded and labelled. Similarly, Standard Containers can be created, started, and stopped using standard container tools (what this spec is about); copied and snapshotted using standard filesystem tools; and downloaded and uploaded using standard network tools.
## 2. Content-agnostic
Just like shipping containers, Standard Containers are CONTENT-AGNOSTIC: all standard operations have the same effect regardless of the contents. A shipping container will be stacked in exactly the same way whether it contains Vietnamese powder coffee or spare Maserati parts. Similarly, Standard Containers are started or uploaded in the same way whether they contain a postgres database, a php application with its dependencies and application server, or Java build artifacts.
## 3. Infrastructure-agnostic
Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be transported to thousands of facilities around the world, and manipulated by a wide variety of equipment. A shipping container can be packed in a factory in Ukraine, transported by truck to the nearest routing center, stacked onto a train, loaded into a German boat by an Australian-built crane, stored in a warehouse at a US facility, etc. Similarly, a standard container can be bundled on my laptop, uploaded to S3, downloaded, run and snapshotted by a build server at Equinix in Virginia, uploaded to 10 staging servers in a home-made Openstack cluster, then sent to 30 production instances across 3 EC2 regions.
## 4. Designed for automation
Because they offer the same standard operations regardless of content and infrastructure, Standard Containers, just like their physical counterparts, are extremely well-suited for automation. In fact, you could say automation is their secret weapon.
Many things that once required time-consuming and error-prone human effort can now be programmed. Before shipping containers, a bag of powder coffee was hauled, dragged, dropped, rolled and stacked by 10 different people in 10 different locations by the time it reached its destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The process was slow, inefficient and cost a fortune - and was entirely different depending on the facility and the type of goods.
Similarly, before Standard Containers, by the time a software component ran in production, it had been individually built, configured, bundled, documented, patched, vendored, templated, tweaked and instrumented by 10 different people on 10 different computers. Builds failed, libraries conflicted, mirrors crashed, post-it notes were lost, logs were misplaced, cluster updates were half-broken. The process was slow, inefficient and cost a fortune - and was entirely different depending on the language and infrastructure provider.
## 5. Industrial-grade delivery
There are 17 million shipping containers in existence, packed with every physical good imaginable. Every single one of them can be loaded onto the same boats, by the same cranes, in the same facilities, and sent anywhere in the World with incredible efficiency. It is embarrassing to think that a 30 ton shipment of coffee can safely travel half-way across the World in *less time* than it takes a software team to deliver its code from one datacenter to another sitting 10 miles away.
With Standard Containers we can put an end to that embarrassment, by making INDUSTRIAL-GRADE DELIVERY of software a reality.
# Contributing
Development happens on github for the spec. Issues are used for bugs and actionable items and longer
discussions can happen on the mailing list. You can subscribe and join the mailing list on
[google groups](https://groups.google.com/a/opencontainers.org/forum/#!forum/dev).
The specification and code is licensed under the Apache 2.0 license found in
the `LICENSE` file of this repository.
## Weekly Call
The contributors and maintainers of the project have a weekly meeting Wednesdays at 10:00 AM PST.
Everyone is welcome to participate in the call. The link to the call will be posted on the mailing
list each week along with set topics for discussion.
Minutes for the call will be posted to the mailing list for those who are unable to join the call.
## Markdown style
To keep consistency throughout the Markdown files in the Open Container spec all files should be formatted one sentence per line.
This fixes two things: it makes diffing easier with git and it resolves fights about line wrapping length.
For example, this paragraph will span three lines in the Markdown source.
### Sign your work
The sign-off is a simple line at the end of the explanation for the
patch, which certifies that you wrote it or otherwise have the right to
pass it on as an open-source patch. The rules are pretty simple: if you
can certify the below (from
[developercertificate.org](http://developercertificate.org/)):
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
then you just add a line to every git commit message:
Signed-off-by: Joe Smith <joe@gmail.com>
using your real name (sorry, no pseudonyms or anonymous contributions.)
You can add the sign off when creating the git commit via `git commit -s`.

View File

@ -1,30 +0,0 @@
# Bundle Container Format
This section defines a format for encoding a container as a *bundle* - a directory organized in a certain way, and containing all the necessary data and metadata for any compliant runtime to perform all standard operations against it. See also [OS X application bundles](http://en.wikipedia.org/wiki/Bundle_%28OS_X%29) for a similar use of the term *bundle*.
The format does not define distribution. In other words, it only specifies how a container must be stored on a local filesystem, for consumption by a runtime. It does not specify how to transfer a container between computers, how to discover containers, or assign names or versions to them. Any distribution method capable of preserving the original layout of a container, as specified here, is considered compliant.
A standard container bundle is made of the following 3 parts:
- A top-level directory holding everything else
- One or more content directories
- A configuration file
# Directory layout
A Standard Container bundle is a directory containing all the content needed to load and run a container. This includes its configuration file (`config.json`) and content directories. The main property of this directory layout is that it can be moved as a unit to another machine and run the same container.
The syntax and semantics for `config.json` are described in [this specification](config.md).
One or more *content directories* may be adjacent to the configuration file. This must include at least the root filesystem (referenced in the configuration file by the *root* field) and may include other related content (signatures, other configs, etc.). The interpretation of these resources is specified in the configuration. The names of the directories may be arbitrary, but users should consider using conventional names as in the example below.
```
/
!
--- config.json
!
--- rootfs
!
--- signatures
```

View File

@ -1,152 +0,0 @@
# Linux-specific configuration
The Linux container specification uses various kernel features like namespaces,
cgroups, capabilities, LSM, and file system jails to fulfill the spec.
Additional information is needed for Linux over the [default spec configuration](config.md)
in order to configure these various kernel features.
## Linux namespaces
A namespace wraps a global system resource in an abstraction that makes it
appear to the processes within the namespace that they have their own isolated
instance of the global resource. Changes to the global resource are visible to
other processes that are members of the namespace, but are invisible to other
processes. For more information, see [the man page](http://man7.org/linux/man-pages/man7/namespaces.7.html)
Namespaces are specified in the spec as an array of entries. Each entry has a
type field with possible values described below and an optional path element.
If a path is specified, that particular file is used to join that type of namespace.
```json
"namespaces": [
{
"type": "pid",
"path": "/proc/1234/ns/pid"
},
{
"type": "net",
"path": "/var/run/netns/neta"
},
{
"type": "mnt",
},
{
"type": "ipc",
},
{
"type": "uts",
},
{
"type": "user",
},
]
```
#### Namespace types
* **pid** processes inside the container will only be able to see other processes inside the same container.
* **network** the container will have it's own network stack.
* **mnt** the container will have an isolated mount table.
* **ipc** processes inside the container will only be able to communicate to other processes inside the same
container via system level IPC.
* **uts** the container will be able to have it's own hostname and domain name.
* **user** the container will be able to remap user and group IDs from the host to local users and groups
within the container.
### Access to devices
Devices is an array specifying the list of devices from the host to make available in the container.
By providing a device name within the list the runtime should look up the same device on the host's `/dev`
and collect information about the device node so that it can be recreated for the container. The runtime
should not only create the device inside the container but ensure that the root user inside
the container has access rights for the device.
```json
"devices": [
"null",
"random",
"full",
"tty",
"zero",
"urandom"
]
```
## Linux control groups
Also known as cgroups, they are used to restrict resource usage for a container and handle
device access. cgroups provide controls to restrict cpu, memory, IO, and network for
the container. For more information, see the [kernel cgroups documentation](https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt)
## Linux capabilities
Capabilities is an array that specifies Linux capabilities that can be provided to the process
inside the container. Valid values are the string after `CAP_` for capabilities defined
in [the man page](http://man7.org/linux/man-pages/man7/capabilities.7.html)
```json
"capabilities": [
"AUDIT_WRITE",
"KILL",
"NET_BIND_SERVICE"
]
```
## Linux sysctl
sysctl allows kernel parameters to be modified at runtime for the container.
For more information, see [the man page](http://man7.org/linux/man-pages/man8/sysctl.8.html)
```json
"sysctl": {
"net.ipv4.ip_forward": "1",
"net.core.somaxconn": "256"
}
```
## Linux rlimits
```json
"rlimits": [
{
"type": "RLIMIT_NPROC",
"soft": 1024,
"hard": 102400
}
]
```
rlimits allow setting resource limits. The type is from the values defined in [the man page](http://man7.org/linux/man-pages/man2/setrlimit.2.html). The kernel enforces the soft limit for a resource while the hard limit acts as a ceiling for that value that could be set by an unprivileged process.
## Linux user namespace mappings
```json
"uidMappings": [
{
"hostID": 1000,
"containerID": 0,
"size": 10
}
],
"gidMappings": [
{
"hostID": 1000,
"containerID": 0,
"size": 10
}
]
```
uid/gid mappings describe the user namespace mappings from the host to the container. *hostID* is the starting uid/gid on the host to be mapped to *containerID* which is the starting uid/gid in the container and *size* refers to the number of ids to be mapped. The Linux kernel has a limit of 5 such mappings that can be specified.
## Rootfs Mount Propagation
rootfsPropagation sets the rootfs's mount propagation. Its value is either slave, private, or shared. [The kernel doc](https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt) has more information about mount propagation.
```json
"rootfsPropagation": "slave",
```
## Security
**TODO:** security profiles

View File

@ -1,150 +0,0 @@
# Configuration file
The containers top-level directory MUST contain a configuration file called `config.json`.
For now the canonical schema is defined in [spec.go](spec.go) and [spec_linux.go](spec_linux.go), but this will be moved to a formal JSON schema over time.
The configuration file contains metadata necessary to implement standard operations against the container.
This includes the process to run, environment variables to inject, sandboxing features to use, etc.
Below is a detailed description of each field defined in the configuration format.
## Manifest version
* **version** (string, required) must be in [SemVer v2.0.0](http://semver.org/spec/v2.0.0.html) format and specifies the version of the OCF specification with which the container bundle complies. The Open Container spec follows semantic versioning and retains forward and backward compatibility within major versions. For example, if an implementation is compliant with version 1.0.1 of the spec, it is compatible with the complete 1.x series.
*Example*
```json
"version": "0.1.0"
```
## Root Configuration
Each container has exactly one *root filesystem*, specified in the *root* object:
* **path** (string, required) Specifies the path to the root filesystem for the container, relative to the path where the manifest is. A directory MUST exist at the relative path declared by the field.
* **readonly** (bool, optional) If true then the root filesystem MUST be read-only inside the container. Defaults to false.
*Example*
```json
"root": {
"path": "rootfs",
"readonly": true
}
```
## Mount Configuration
Additional filesystems can be declared as "mounts", specified in the *mounts* array. The parameters are similar to the ones in Linux mount system call. [http://linux.die.net/man/2/mount](http://linux.die.net/man/2/mount)
* **type** (string, required) Linux, *filesystemtype* argument supported by the kernel are listed in */proc/filesystems* (e.g., "minix", "ext2", "ext3", "jfs", "xfs", "reiserfs", "msdos", "proc", "nfs", "iso9660"). Windows: ntfs
* **source** (string, required) a device name, but can also be a directory name or a dummy. Windows, the volume name that is the target of the mount point. \\?\Volume\{GUID}\ (on Windows source is called target)
* **destination** (string, required) where the source filesystem is mounted relative to the container rootfs.
* **options** (string, optional) in the fstab format [https://wiki.archlinux.org/index.php/Fstab](https://wiki.archlinux.org/index.php/Fstab).
*Example (Linux)*
```json
"mounts": [
{
"type": "proc",
"source": "proc",
"destination": "/proc",
"options": ""
},
{
"type": "tmpfs",
"source": "tmpfs",
"destination": "/dev",
"options": "nosuid,strictatime,mode=755,size=65536k"
},
{
"type": "devpts",
"source": "devpts",
"destination": "/dev/pts",
"options": "nosuid,noexec,newinstance,ptmxmode=0666,mode=0620,gid=5"
},
{
"type": "bind",
"source": "/volumes/testing",
"destination": "/data",
"options": "rbind,rw"
}
]
```
*Example (Windows)*
```json
"mounts": [
{
"type": "ntfs",
"source": "\\\\?\\Volume\\{2eca078d-5cbc-43d3-aff8-7e8511f60d0e}\\",
"destination": "C:\\Users\\crosbymichael\\My Fancy Mount Point\\",
"options": ""
}
]
```
See links for details about [mountvol](http://ss64.com/nt/mountvol.html) and [SetVolumeMountPoint](https://msdn.microsoft.com/en-us/library/windows/desktop/aa365561(v=vs.85).aspx) in Windows.
## Process configuration
* **terminal** (bool, optional) specifies whether you want a terminal attached to that process. Defaults to false.
* **cwd** (string, optional) is the working directory that will be set for the executable.
* **env** (array of strings, optional) contains a list of variables that will be set in the process's environment prior to execution. Elements in the array are specified as Strings in the form "KEY=value". The left hand side must consist solely of letters, digits, and underscores `_` as outlined in [IEEE Std 1003.1-2001](http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html).
* **args** (string, required) executable to launch and any flags as an array. The executable is the first element and must be available at the given path inside of the rootfs. If the executable path is not an absolute path then the search $PATH is interpreted to find the executable.
The user for the process is a platform-specific structure that allows specific control over which user the process runs as.
For Linux-based systems the user structure has the following fields:
* **uid** (int, required) specifies the user id.
* **gid** (int, required) specifies the group id.
* **additionalGids** (array of ints, optional) specifies additional group ids to be added to the process.
*Example (Linux)*
```json
"process": {
"terminal": true,
"user": {
"uid": 1,
"gid": 1,
"additionalGids": []
},
"env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm"
],
"cwd": "",
"args": [
"sh"
]
}
```
## Hostname
* **hostname** (string, optional) as it is accessible to processes running inside.
*Example*
```json
"hostname": "mrsdalloway"
```
## Platform-specific configuration
* **os** (string, required) specifies the operating system family this image must run on. Values for os must be in the list specified by the Go Language document for [`$GOOS`](https://golang.org/doc/install/source#environment).
* **arch** (string, required) specifies the instruction set for which the binaries in the image have been compiled. Values for arch must be in the list specified by the Go Language document for [`$GOARCH`](https://golang.org/doc/install/source#environment).
```json
"platform": {
"os": "linux",
"arch": "amd64"
}
```
Interpretation of the platform section of the JSON file is used to find which platform-specific sections may be available in the document. For example, if `os` is set to `linux`, then a JSON object conforming to the [Linux-specific schema](config-linux.md) SHOULD be found at the key `linux` in the `config.json`.

View File

@ -1,59 +0,0 @@
# Runtime and Lifecycle
## Lifecycle
### Create
Creates the container: file system, namespaces, cgroups, capabilities.
### Start (process)
Runs a process in a container. Can be invoked several times.
### Stop (process)
Not sure we need that from runc cli. Process is killed from the outside.
This event needs to be captured by runc to run onstop event handlers.
## Hooks
Hooks allow one to run code before/after various lifecycle events of the container.
The state of the container is passed to the hooks over stdin, so the hooks could get the information they need to do their work.
Hook paths are absolute and are executed from the host's filesystem.
### Pre-start
The pre-start hooks are called after the container process is spawned, but before the user supplied command is executed.
They are called after the container namespaces are created on Linux, so they provide an opportunity to customize the container.
In Linux, for e.g., the network namespace could be configured in this hook.
If a hook returns a non-zero exit code, then an error including the exit code and the stderr is returned to the caller and the container is torn down.
### Post-stop
The post-stop hooks are called after the container process is stopped. Cleanup or debugging could be performed in such a hook.
If a hook returns a non-zero exit code, then an error is logged and the remaining hooks are executed.
*Example*
```json
"hooks" : {
"prestart": [
{
"path": "/usr/bin/fix-mounts",
"args": ["arg1", "arg2"],
"env": [ "key1=value1"]
},
{
"path": "/usr/bin/setup-network"
}
],
"poststop": [
{
"path": "/usr/sbin/cleanup.sh",
"args": ["-f"]
}
]
}
```
`path` is required for a hook. `args` and `env` are optional.

View File

@ -1,80 +0,0 @@
package specs
// Spec is the base configuration for the container. It specifies platform
// independent configuration.
type Spec struct {
// Version is the version of the specification that is supported.
Version string `json:"version"`
// Platform is the host information for OS and Arch.
Platform Platform `json:"platform"`
// Process is the container's main process.
Process Process `json:"process"`
// Root is the root information for the container's filesystem.
Root Root `json:"root"`
// Hostname is the container's host name.
Hostname string `json:"hostname"`
// Mounts profile configuration for adding mounts to the container's filesystem.
Mounts []Mount `json:"mounts"`
// Hooks are the commands run at various lifecycle events of the container.
Hooks Hooks `json:"hooks"`
}
type Hooks struct {
// Prestart is a list of hooks to be run before the container process is executed.
// On Linux, they are run after the container namespaces are created.
Prestart []Hook `json:"prestart"`
// Poststop is a list of hooks to be run after the container process exits.
Poststop []Hook `json:"poststop"`
}
// Mount specifies a mount for a container.
type Mount struct {
// Type specifies the mount kind.
Type string `json:"type"`
// Source specifies the source path of the mount. In the case of bind mounts on
// linux based systems this would be the file on the host.
Source string `json:"source"`
// Destination is the path where the mount will be placed relative to the container's root.
Destination string `json:"destination"`
// Options are fstab style mount options.
Options string `json:"options"`
}
// Process contains information to start a specific application inside the container.
type Process struct {
// Terminal creates an interactive terminal for the container.
Terminal bool `json:"terminal"`
// User specifies user information for the process.
User User `json:"user"`
// Args specifies the binary and arguments for the application to execute.
Args []string `json:"args"`
// Env populates the process environment for the process.
Env []string `json:"env"`
// Cwd is the current working directory for the process and must be
// relative to the container's root.
Cwd string `json:"cwd"`
}
// Root contains information about the container's root filesystem on the host.
type Root struct {
// Path is the absolute path to the container's root filesystem.
Path string `json:"path"`
// Readonly makes the root filesystem for the container readonly before the process is executed.
Readonly bool `json:"readonly"`
}
// Platform specifies OS and arch information for the host system that the container
// is created for.
type Platform struct {
// OS is the operating system.
OS string `json:"os"`
// Arch is the architecture
Arch string `json:"arch"`
}
// Hook specifies a command that is run at a particular event in the lifecycle of a container.
type Hook struct {
Path string `json:"path"`
Args []string `json:"args"`
Env []string `json:"env"`
}

View File

@ -1,159 +0,0 @@
// +build linux
package specs
// LinuxSpec is the full specification for Linux containers
type LinuxSpec struct {
Spec
// Linux is platform specific configuration for Linux based containers
Linux Linux `json:"linux"`
}
// Linux contains platform specific configuration for Linux based containers
type Linux struct {
// UIDMapping specifies user mappings for supporting user namespaces on Linux
UIDMappings []IDMapping `json:"uidMappings"`
// GIDMapping specifies group mappings for supporting user namespaces on Linux
GIDMappings []IDMapping `json:"gidMappings"`
// Rlimits specifies rlimit options to apply to the container's process
Rlimits []Rlimit `json:"rlimits"`
// Sysctl are a set of key value pairs that are set for the container on start
Sysctl map[string]string `json:"sysctl"`
// Resources contain cgroup information for handling resource constraints
// for the container
Resources Resources `json:"resources"`
// Namespaces contains the namespaces that are created and/or joined by the container
Namespaces []Namespace `json:"namespaces"`
// Capabilities are Linux capabilities that are kept for the container
Capabilities []string `json:"capabilities"`
// Devices are a list of device nodes that are created and enabled for the container
Devices []string `json:"devices"`
// RootfsPropagation is the rootfs mount propagation mode for the container
RootfsPropagation string `json:"rootfsPropagation"`
}
// User specifies Linux specific user and group information for the container's
// main process
type User struct {
// Uid is the user id
UID int32 `json:"uid"`
// Gid is the group id
GID int32 `json:"gid"`
// AdditionalGids are additional group ids set for the container's process
AdditionalGids []int32 `json:"additionalGids"`
}
// Namespace is the configuration for a Linux namespace
type Namespace struct {
// Type is the type of Linux namespace
Type string `json:"type"`
// Path is a path to an existing namespace persisted on disk that can be joined
// and is of the same type
Path string `json:"path"`
}
// IDMapping specifies UID/GID mappings
type IDMapping struct {
// HostID is the UID/GID of the host user or group
HostID int32 `json:"hostID"`
// ContainerID is the UID/GID of the container's user or group
ContainerID int32 `json:"containerID"`
// Size is the length of the range of IDs mapped between the two namespaces
Size int32 `json:"size"`
}
// Rlimit type and restrictions
type Rlimit struct {
// Type of the rlimit to set
Type int `json:"type"`
// Hard is the hard limit for the specified type
Hard uint64 `json:"hard"`
// Soft is the soft limit for the specified type
Soft uint64 `json:"soft"`
}
// HugepageLimit structure corresponds to limiting kernel hugepages
type HugepageLimit struct {
Pagesize string `json:"pageSize"`
Limit int `json:"limit"`
}
// InterfacePriority for network interfaces
type InterfacePriority struct {
// Name is the name of the network interface
Name string `json:"name"`
// Priority for the interface
Priority int64 `json:"priority"`
}
// BlockIO for Linux cgroup 'blockio' resource management
type BlockIO struct {
// Specifies per cgroup weight, range is from 10 to 1000
Weight int64 `json:"blkioWeight"`
// Weight per cgroup per device, can override BlkioWeight
WeightDevice string `json:"blkioWeightDevice"`
// IO read rate limit per cgroup per device, bytes per second
ThrottleReadBpsDevice string `json:"blkioThrottleReadBpsDevice"`
// IO write rate limit per cgroup per divice, bytes per second
ThrottleWriteBpsDevice string `json:"blkioThrottleWriteBpsDevice"`
// IO read rate limit per cgroup per device, IO per second
ThrottleReadIOpsDevice string `json:"blkioThrottleReadIopsDevice"`
// IO write rate limit per cgroup per device, IO per second
ThrottleWriteIOpsDevice string `json:"blkioThrottleWriteIopsDevice"`
}
// Memory for Linux cgroup 'memory' resource management
type Memory struct {
// Memory limit (in bytes)
Limit int64 `json:"limit"`
// Memory reservation or soft_limit (in bytes)
Reservation int64 `json:"reservation"`
// Total memory usage (memory + swap); set `-1' to disable swap
Swap int64 `json:"swap"`
// Kernel memory limit (in bytes)
Kernel int64 `json:"kernel"`
// How aggressive the kernel will swap memory pages. Range from 0 to 100. Set -1 to use system default
Swappiness int64 `json:"swappiness"`
}
// CPU for Linux cgroup 'cpu' resource management
type CPU struct {
// CPU shares (relative weight vs. other cgroups with cpu shares)
Shares int64 `json:"shares"`
// CPU hardcap limit (in usecs). Allowed cpu time in a given period
Quota int64 `json:"quota"`
// CPU period to be used for hardcapping (in usecs). 0 to use system default
Period int64 `json:"period"`
// How many time CPU will use in realtime scheduling (in usecs)
RealtimeRuntime int64 `json:"realtimeRuntime"`
// CPU period to be used for realtime scheduling (in usecs)
RealtimePeriod int64 `json:"realtimePeriod"`
// CPU to use within the cpuset
Cpus string `json:"cpus"`
// MEM to use within the cpuset
Mems string `json:"mems"`
}
// Network identification and priority configuration
type Network struct {
// Set class identifier for container's network packets
ClassID string `json:"classId"`
// Set priority of network traffic for container
Priorities []InterfacePriority `json:"priorities"`
}
// Resources has container runtime resource constraints
type Resources struct {
// DisableOOMKiller disables the OOM killer for out of memory conditions
DisableOOMKiller bool `json:"disableOOMKiller"`
// Memory restriction configuration
Memory Memory `json:"memory"`
// CPU resource restriction configuration
CPU CPU `json:"cpu"`
// BlockIO restriction configuration
BlockIO BlockIO `json:"blockIO"`
// Hugetlb limit (in bytes)
HugepageLimits []HugepageLimit `json:"hugepageLimits"`
// Network restriction configuration
Network Network `json:"network"`
}

View File

@ -1,4 +0,0 @@
package specs
// Version is the specification version that the package types support.
const Version = "pre-draft"

View File

@ -1,22 +0,0 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe

View File

@ -1,23 +0,0 @@
objx - by Mat Ryer and Tyler Bunnell
The MIT License (MIT)
Copyright (c) 2014 Stretchr, Inc.
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.

View File

@ -1,3 +0,0 @@
# objx
* Jump into the [API Documentation](http://godoc.org/github.com/stretchr/objx)

View File

@ -1,179 +0,0 @@
package objx
import (
"fmt"
"regexp"
"strconv"
"strings"
)
// arrayAccesRegexString is the regex used to extract the array number
// from the access path
const arrayAccesRegexString = `^(.+)\[([0-9]+)\]$`
// arrayAccesRegex is the compiled arrayAccesRegexString
var arrayAccesRegex = regexp.MustCompile(arrayAccesRegexString)
// Get gets the value using the specified selector and
// returns it inside a new Obj object.
//
// If it cannot find the value, Get will return a nil
// value inside an instance of Obj.
//
// Get can only operate directly on map[string]interface{} and []interface.
//
// Example
//
// To access the title of the third chapter of the second book, do:
//
// o.Get("books[1].chapters[2].title")
func (m Map) Get(selector string) *Value {
rawObj := access(m, selector, nil, false, false)
return &Value{data: rawObj}
}
// Set sets the value using the specified selector and
// returns the object on which Set was called.
//
// Set can only operate directly on map[string]interface{} and []interface
//
// Example
//
// To set the title of the third chapter of the second book, do:
//
// o.Set("books[1].chapters[2].title","Time to Go")
func (m Map) Set(selector string, value interface{}) Map {
access(m, selector, value, true, false)
return m
}
// access accesses the object using the selector and performs the
// appropriate action.
func access(current, selector, value interface{}, isSet, panics bool) interface{} {
switch selector.(type) {
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
if array, ok := current.([]interface{}); ok {
index := intFromInterface(selector)
if index >= len(array) {
if panics {
panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array)))
}
return nil
}
return array[index]
}
return nil
case string:
selStr := selector.(string)
selSegs := strings.SplitN(selStr, PathSeparator, 2)
thisSel := selSegs[0]
index := -1
var err error
// https://github.com/stretchr/objx/issues/12
if strings.Contains(thisSel, "[") {
arrayMatches := arrayAccesRegex.FindStringSubmatch(thisSel)
if len(arrayMatches) > 0 {
// Get the key into the map
thisSel = arrayMatches[1]
// Get the index into the array at the key
index, err = strconv.Atoi(arrayMatches[2])
if err != nil {
// This should never happen. If it does, something has gone
// seriously wrong. Panic.
panic("objx: Array index is not an integer. Must use array[int].")
}
}
}
if curMap, ok := current.(Map); ok {
current = map[string]interface{}(curMap)
}
// get the object in question
switch current.(type) {
case map[string]interface{}:
curMSI := current.(map[string]interface{})
if len(selSegs) <= 1 && isSet {
curMSI[thisSel] = value
return nil
} else {
current = curMSI[thisSel]
}
default:
current = nil
}
if current == nil && panics {
panic(fmt.Sprintf("objx: '%v' invalid on object.", selector))
}
// do we need to access the item of an array?
if index > -1 {
if array, ok := current.([]interface{}); ok {
if index < len(array) {
current = array[index]
} else {
if panics {
panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array)))
}
current = nil
}
}
}
if len(selSegs) > 1 {
current = access(current, selSegs[1], value, isSet, panics)
}
}
return current
}
// intFromInterface converts an interface object to the largest
// representation of an unsigned integer using a type switch and
// assertions
func intFromInterface(selector interface{}) int {
var value int
switch selector.(type) {
case int:
value = selector.(int)
case int8:
value = int(selector.(int8))
case int16:
value = int(selector.(int16))
case int32:
value = int(selector.(int32))
case int64:
value = int(selector.(int64))
case uint:
value = int(selector.(uint))
case uint8:
value = int(selector.(uint8))
case uint16:
value = int(selector.(uint16))
case uint32:
value = int(selector.(uint32))
case uint64:
value = int(selector.(uint64))
default:
panic("objx: array access argument is not an integer type (this should never happen)")
}
return value
}

View File

@ -1,145 +0,0 @@
package objx
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestAccessorsAccessGetSingleField(t *testing.T) {
current := map[string]interface{}{"name": "Tyler"}
assert.Equal(t, "Tyler", access(current, "name", nil, false, true))
}
func TestAccessorsAccessGetDeep(t *testing.T) {
current := map[string]interface{}{"name": map[string]interface{}{"first": "Tyler", "last": "Bunnell"}}
assert.Equal(t, "Tyler", access(current, "name.first", nil, false, true))
assert.Equal(t, "Bunnell", access(current, "name.last", nil, false, true))
}
func TestAccessorsAccessGetDeepDeep(t *testing.T) {
current := map[string]interface{}{"one": map[string]interface{}{"two": map[string]interface{}{"three": map[string]interface{}{"four": 4}}}}
assert.Equal(t, 4, access(current, "one.two.three.four", nil, false, true))
}
func TestAccessorsAccessGetInsideArray(t *testing.T) {
current := map[string]interface{}{"names": []interface{}{map[string]interface{}{"first": "Tyler", "last": "Bunnell"}, map[string]interface{}{"first": "Capitol", "last": "Bollocks"}}}
assert.Equal(t, "Tyler", access(current, "names[0].first", nil, false, true))
assert.Equal(t, "Bunnell", access(current, "names[0].last", nil, false, true))
assert.Equal(t, "Capitol", access(current, "names[1].first", nil, false, true))
assert.Equal(t, "Bollocks", access(current, "names[1].last", nil, false, true))
assert.Panics(t, func() {
access(current, "names[2]", nil, false, true)
})
assert.Nil(t, access(current, "names[2]", nil, false, false))
}
func TestAccessorsAccessGetFromArrayWithInt(t *testing.T) {
current := []interface{}{map[string]interface{}{"first": "Tyler", "last": "Bunnell"}, map[string]interface{}{"first": "Capitol", "last": "Bollocks"}}
one := access(current, 0, nil, false, false)
two := access(current, 1, nil, false, false)
three := access(current, 2, nil, false, false)
assert.Equal(t, "Tyler", one.(map[string]interface{})["first"])
assert.Equal(t, "Capitol", two.(map[string]interface{})["first"])
assert.Nil(t, three)
}
func TestAccessorsGet(t *testing.T) {
current := New(map[string]interface{}{"name": "Tyler"})
assert.Equal(t, "Tyler", current.Get("name").data)
}
func TestAccessorsAccessSetSingleField(t *testing.T) {
current := map[string]interface{}{"name": "Tyler"}
access(current, "name", "Mat", true, false)
assert.Equal(t, current["name"], "Mat")
access(current, "age", 29, true, true)
assert.Equal(t, current["age"], 29)
}
func TestAccessorsAccessSetSingleFieldNotExisting(t *testing.T) {
current := map[string]interface{}{}
access(current, "name", "Mat", true, false)
assert.Equal(t, current["name"], "Mat")
}
func TestAccessorsAccessSetDeep(t *testing.T) {
current := map[string]interface{}{"name": map[string]interface{}{"first": "Tyler", "last": "Bunnell"}}
access(current, "name.first", "Mat", true, true)
access(current, "name.last", "Ryer", true, true)
assert.Equal(t, "Mat", access(current, "name.first", nil, false, true))
assert.Equal(t, "Ryer", access(current, "name.last", nil, false, true))
}
func TestAccessorsAccessSetDeepDeep(t *testing.T) {
current := map[string]interface{}{"one": map[string]interface{}{"two": map[string]interface{}{"three": map[string]interface{}{"four": 4}}}}
access(current, "one.two.three.four", 5, true, true)
assert.Equal(t, 5, access(current, "one.two.three.four", nil, false, true))
}
func TestAccessorsAccessSetArray(t *testing.T) {
current := map[string]interface{}{"names": []interface{}{"Tyler"}}
access(current, "names[0]", "Mat", true, true)
assert.Equal(t, "Mat", access(current, "names[0]", nil, false, true))
}
func TestAccessorsAccessSetInsideArray(t *testing.T) {
current := map[string]interface{}{"names": []interface{}{map[string]interface{}{"first": "Tyler", "last": "Bunnell"}, map[string]interface{}{"first": "Capitol", "last": "Bollocks"}}}
access(current, "names[0].first", "Mat", true, true)
access(current, "names[0].last", "Ryer", true, true)
access(current, "names[1].first", "Captain", true, true)
access(current, "names[1].last", "Underpants", true, true)
assert.Equal(t, "Mat", access(current, "names[0].first", nil, false, true))
assert.Equal(t, "Ryer", access(current, "names[0].last", nil, false, true))
assert.Equal(t, "Captain", access(current, "names[1].first", nil, false, true))
assert.Equal(t, "Underpants", access(current, "names[1].last", nil, false, true))
}
func TestAccessorsAccessSetFromArrayWithInt(t *testing.T) {
current := []interface{}{map[string]interface{}{"first": "Tyler", "last": "Bunnell"}, map[string]interface{}{"first": "Capitol", "last": "Bollocks"}}
one := access(current, 0, nil, false, false)
two := access(current, 1, nil, false, false)
three := access(current, 2, nil, false, false)
assert.Equal(t, "Tyler", one.(map[string]interface{})["first"])
assert.Equal(t, "Capitol", two.(map[string]interface{})["first"])
assert.Nil(t, three)
}
func TestAccessorsSet(t *testing.T) {
current := New(map[string]interface{}{"name": "Tyler"})
current.Set("name", "Mat")
assert.Equal(t, "Mat", current.Get("name").data)
}

View File

@ -1,14 +0,0 @@
case []{1}:
a := object.([]{1})
if isSet {
a[index] = value.({1})
} else {
if index >= len(a) {
if panics {
panic(fmt.Sprintf("objx: Index %d is out of range because the []{1} only contains %d items.", index, len(a)))
}
return nil
} else {
return a[index]
}
}

View File

@ -1,86 +0,0 @@
<html>
<head>
<title>
Codegen
</title>
<style>
body {
width: 800px;
margin: auto;
}
textarea {
width: 100%;
min-height: 100px;
font-family: Courier;
}
</style>
</head>
<body>
<h2>
Template
</h2>
<p>
Use <code>{x}</code> as a placeholder for each argument.
</p>
<textarea id="template"></textarea>
<h2>
Arguments (comma separated)
</h2>
<p>
One block per line
</p>
<textarea id="args"></textarea>
<h2>
Output
</h2>
<input id="go" type="button" value="Generate code" />
<textarea id="output"></textarea>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
$(function(){
$("#go").click(function(){
var output = ""
var template = $("#template").val()
var args = $("#args").val()
// collect the args
var argLines = args.split("\n")
for (var line in argLines) {
var argLine = argLines[line];
var thisTemp = template
// get individual args
var args = argLine.split(",")
for (var argI in args) {
var argText = args[argI];
var argPlaceholder = "{" + argI + "}";
while (thisTemp.indexOf(argPlaceholder) > -1) {
thisTemp = thisTemp.replace(argPlaceholder, argText);
}
}
output += thisTemp
}
$("#output").val(output);
});
});
</script>
</body>
</html>

View File

@ -1,286 +0,0 @@
/*
{4} ({1} and []{1})
--------------------------------------------------
*/
// {4} gets the value as a {1}, returns the optionalDefault
// value or a system default object if the value is the wrong type.
func (v *Value) {4}(optionalDefault ...{1}) {1} {
if s, ok := v.data.({1}); ok {
return s
}
if len(optionalDefault) == 1 {
return optionalDefault[0]
}
return {3}
}
// Must{4} gets the value as a {1}.
//
// Panics if the object is not a {1}.
func (v *Value) Must{4}() {1} {
return v.data.({1})
}
// {4}Slice gets the value as a []{1}, returns the optionalDefault
// value or nil if the value is not a []{1}.
func (v *Value) {4}Slice(optionalDefault ...[]{1}) []{1} {
if s, ok := v.data.([]{1}); ok {
return s
}
if len(optionalDefault) == 1 {
return optionalDefault[0]
}
return nil
}
// Must{4}Slice gets the value as a []{1}.
//
// Panics if the object is not a []{1}.
func (v *Value) Must{4}Slice() []{1} {
return v.data.([]{1})
}
// Is{4} gets whether the object contained is a {1} or not.
func (v *Value) Is{4}() bool {
_, ok := v.data.({1})
return ok
}
// Is{4}Slice gets whether the object contained is a []{1} or not.
func (v *Value) Is{4}Slice() bool {
_, ok := v.data.([]{1})
return ok
}
// Each{4} calls the specified callback for each object
// in the []{1}.
//
// Panics if the object is the wrong type.
func (v *Value) Each{4}(callback func(int, {1}) bool) *Value {
for index, val := range v.Must{4}Slice() {
carryon := callback(index, val)
if carryon == false {
break
}
}
return v
}
// Where{4} uses the specified decider function to select items
// from the []{1}. The object contained in the result will contain
// only the selected items.
func (v *Value) Where{4}(decider func(int, {1}) bool) *Value {
var selected []{1}
v.Each{4}(func(index int, val {1}) bool {
shouldSelect := decider(index, val)
if shouldSelect == false {
selected = append(selected, val)
}
return true
})
return &Value{data:selected}
}
// Group{4} uses the specified grouper function to group the items
// keyed by the return of the grouper. The object contained in the
// result will contain a map[string][]{1}.
func (v *Value) Group{4}(grouper func(int, {1}) string) *Value {
groups := make(map[string][]{1})
v.Each{4}(func(index int, val {1}) bool {
group := grouper(index, val)
if _, ok := groups[group]; !ok {
groups[group] = make([]{1}, 0)
}
groups[group] = append(groups[group], val)
return true
})
return &Value{data:groups}
}
// Replace{4} uses the specified function to replace each {1}s
// by iterating each item. The data in the returned result will be a
// []{1} containing the replaced items.
func (v *Value) Replace{4}(replacer func(int, {1}) {1}) *Value {
arr := v.Must{4}Slice()
replaced := make([]{1}, len(arr))
v.Each{4}(func(index int, val {1}) bool {
replaced[index] = replacer(index, val)
return true
})
return &Value{data:replaced}
}
// Collect{4} uses the specified collector function to collect a value
// for each of the {1}s in the slice. The data returned will be a
// []interface{}.
func (v *Value) Collect{4}(collector func(int, {1}) interface{}) *Value {
arr := v.Must{4}Slice()
collected := make([]interface{}, len(arr))
v.Each{4}(func(index int, val {1}) bool {
collected[index] = collector(index, val)
return true
})
return &Value{data:collected}
}
// ************************************************************
// TESTS
// ************************************************************
func Test{4}(t *testing.T) {
val := {1}( {2} )
m := map[string]interface{}{"value": val, "nothing": nil}
assert.Equal(t, val, New(m).Get("value").{4}())
assert.Equal(t, val, New(m).Get("value").Must{4}())
assert.Equal(t, {1}({3}), New(m).Get("nothing").{4}())
assert.Equal(t, val, New(m).Get("nothing").{4}({2}))
assert.Panics(t, func() {
New(m).Get("age").Must{4}()
})
}
func Test{4}Slice(t *testing.T) {
val := {1}( {2} )
m := map[string]interface{}{"value": []{1}{ val }, "nothing": nil}
assert.Equal(t, val, New(m).Get("value").{4}Slice()[0])
assert.Equal(t, val, New(m).Get("value").Must{4}Slice()[0])
assert.Equal(t, []{1}(nil), New(m).Get("nothing").{4}Slice())
assert.Equal(t, val, New(m).Get("nothing").{4}Slice( []{1}{ {1}({2}) } )[0])
assert.Panics(t, func() {
New(m).Get("nothing").Must{4}Slice()
})
}
func TestIs{4}(t *testing.T) {
var v *Value
v = &Value{data: {1}({2})}
assert.True(t, v.Is{4}())
v = &Value{data: []{1}{ {1}({2}) }}
assert.True(t, v.Is{4}Slice())
}
func TestEach{4}(t *testing.T) {
v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }}
count := 0
replacedVals := make([]{1}, 0)
assert.Equal(t, v, v.Each{4}(func(i int, val {1}) bool {
count++
replacedVals = append(replacedVals, val)
// abort early
if i == 2 {
return false
}
return true
}))
assert.Equal(t, count, 3)
assert.Equal(t, replacedVals[0], v.Must{4}Slice()[0])
assert.Equal(t, replacedVals[1], v.Must{4}Slice()[1])
assert.Equal(t, replacedVals[2], v.Must{4}Slice()[2])
}
func TestWhere{4}(t *testing.T) {
v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }}
selected := v.Where{4}(func(i int, val {1}) bool {
return i%2==0
}).Must{4}Slice()
assert.Equal(t, 3, len(selected))
}
func TestGroup{4}(t *testing.T) {
v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }}
grouped := v.Group{4}(func(i int, val {1}) string {
return fmt.Sprintf("%v", i%2==0)
}).data.(map[string][]{1})
assert.Equal(t, 2, len(grouped))
assert.Equal(t, 3, len(grouped["true"]))
assert.Equal(t, 3, len(grouped["false"]))
}
func TestReplace{4}(t *testing.T) {
v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }}
rawArr := v.Must{4}Slice()
replaced := v.Replace{4}(func(index int, val {1}) {1} {
if index < len(rawArr)-1 {
return rawArr[index+1]
}
return rawArr[0]
})
replacedArr := replaced.Must{4}Slice()
if assert.Equal(t, 6, len(replacedArr)) {
assert.Equal(t, replacedArr[0], rawArr[1])
assert.Equal(t, replacedArr[1], rawArr[2])
assert.Equal(t, replacedArr[2], rawArr[3])
assert.Equal(t, replacedArr[3], rawArr[4])
assert.Equal(t, replacedArr[4], rawArr[5])
assert.Equal(t, replacedArr[5], rawArr[0])
}
}
func TestCollect{4}(t *testing.T) {
v := &Value{data: []{1}{ {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}), {1}({2}) }}
collected := v.Collect{4}(func(index int, val {1}) interface{} {
return index
})
collectedArr := collected.MustInterSlice()
if assert.Equal(t, 6, len(collectedArr)) {
assert.Equal(t, collectedArr[0], 0)
assert.Equal(t, collectedArr[1], 1)
assert.Equal(t, collectedArr[2], 2)
assert.Equal(t, collectedArr[3], 3)
assert.Equal(t, collectedArr[4], 4)
assert.Equal(t, collectedArr[5], 5)
}
}

View File

@ -1,20 +0,0 @@
Interface,interface{},"something",nil,Inter
Map,map[string]interface{},map[string]interface{}{"name":"Tyler"},nil,MSI
ObjxMap,(Map),New(1),New(nil),ObjxMap
Bool,bool,true,false,Bool
String,string,"hello","",Str
Int,int,1,0,Int
Int8,int8,1,0,Int8
Int16,int16,1,0,Int16
Int32,int32,1,0,Int32
Int64,int64,1,0,Int64
Uint,uint,1,0,Uint
Uint8,uint8,1,0,Uint8
Uint16,uint16,1,0,Uint16
Uint32,uint32,1,0,Uint32
Uint64,uint64,1,0,Uint64
Uintptr,uintptr,1,0,Uintptr
Float32,float32,1,0,Float32
Float64,float64,1,0,Float64
Complex64,complex64,1,0,Complex64
Complex128,complex128,1,0,Complex128

View File

@ -1,13 +0,0 @@
package objx
const (
// PathSeparator is the character used to separate the elements
// of the keypath.
//
// For example, `location.address.city`
PathSeparator string = "."
// SignatureSeparator is the character that is used to
// separate the Base64 string from the security signature.
SignatureSeparator = "_"
)

View File

@ -1,117 +0,0 @@
package objx
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/url"
)
// JSON converts the contained object to a JSON string
// representation
func (m Map) JSON() (string, error) {
result, err := json.Marshal(m)
if err != nil {
err = errors.New("objx: JSON encode failed with: " + err.Error())
}
return string(result), err
}
// MustJSON converts the contained object to a JSON string
// representation and panics if there is an error
func (m Map) MustJSON() string {
result, err := m.JSON()
if err != nil {
panic(err.Error())
}
return result
}
// Base64 converts the contained object to a Base64 string
// representation of the JSON string representation
func (m Map) Base64() (string, error) {
var buf bytes.Buffer
jsonData, err := m.JSON()
if err != nil {
return "", err
}
encoder := base64.NewEncoder(base64.StdEncoding, &buf)
encoder.Write([]byte(jsonData))
encoder.Close()
return buf.String(), nil
}
// MustBase64 converts the contained object to a Base64 string
// representation of the JSON string representation and panics
// if there is an error
func (m Map) MustBase64() string {
result, err := m.Base64()
if err != nil {
panic(err.Error())
}
return result
}
// SignedBase64 converts the contained object to a Base64 string
// representation of the JSON string representation and signs it
// using the provided key.
func (m Map) SignedBase64(key string) (string, error) {
base64, err := m.Base64()
if err != nil {
return "", err
}
sig := HashWithKey(base64, key)
return base64 + SignatureSeparator + sig, nil
}
// MustSignedBase64 converts the contained object to a Base64 string
// representation of the JSON string representation and signs it
// using the provided key and panics if there is an error
func (m Map) MustSignedBase64(key string) string {
result, err := m.SignedBase64(key)
if err != nil {
panic(err.Error())
}
return result
}
/*
URL Query
------------------------------------------------
*/
// URLValues creates a url.Values object from an Obj. This
// function requires that the wrapped object be a map[string]interface{}
func (m Map) URLValues() url.Values {
vals := make(url.Values)
for k, v := range m {
//TODO: can this be done without sprintf?
vals.Set(k, fmt.Sprintf("%v", v))
}
return vals
}
// URLQuery gets an encoded URL query representing the given
// Obj. This function requires that the wrapped object be a
// map[string]interface{}
func (m Map) URLQuery() (string, error) {
return m.URLValues().Encode(), nil
}

View File

@ -1,94 +0,0 @@
package objx
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestConversionJSON(t *testing.T) {
jsonString := `{"name":"Mat"}`
o := MustFromJSON(jsonString)
result, err := o.JSON()
if assert.NoError(t, err) {
assert.Equal(t, jsonString, result)
}
assert.Equal(t, jsonString, o.MustJSON())
}
func TestConversionJSONWithError(t *testing.T) {
o := MSI()
o["test"] = func() {}
assert.Panics(t, func() {
o.MustJSON()
})
_, err := o.JSON()
assert.Error(t, err)
}
func TestConversionBase64(t *testing.T) {
o := New(map[string]interface{}{"name": "Mat"})
result, err := o.Base64()
if assert.NoError(t, err) {
assert.Equal(t, "eyJuYW1lIjoiTWF0In0=", result)
}
assert.Equal(t, "eyJuYW1lIjoiTWF0In0=", o.MustBase64())
}
func TestConversionBase64WithError(t *testing.T) {
o := MSI()
o["test"] = func() {}
assert.Panics(t, func() {
o.MustBase64()
})
_, err := o.Base64()
assert.Error(t, err)
}
func TestConversionSignedBase64(t *testing.T) {
o := New(map[string]interface{}{"name": "Mat"})
result, err := o.SignedBase64("key")
if assert.NoError(t, err) {
assert.Equal(t, "eyJuYW1lIjoiTWF0In0=_67ee82916f90b2c0d68c903266e8998c9ef0c3d6", result)
}
assert.Equal(t, "eyJuYW1lIjoiTWF0In0=_67ee82916f90b2c0d68c903266e8998c9ef0c3d6", o.MustSignedBase64("key"))
}
func TestConversionSignedBase64WithError(t *testing.T) {
o := MSI()
o["test"] = func() {}
assert.Panics(t, func() {
o.MustSignedBase64("key")
})
_, err := o.SignedBase64("key")
assert.Error(t, err)
}

View File

@ -1,72 +0,0 @@
// objx - Go package for dealing with maps, slices, JSON and other data.
//
// Overview
//
// Objx provides the `objx.Map` type, which is a `map[string]interface{}` that exposes
// a powerful `Get` method (among others) that allows you to easily and quickly get
// access to data within the map, without having to worry too much about type assertions,
// missing data, default values etc.
//
// Pattern
//
// Objx uses a preditable pattern to make access data from within `map[string]interface{}'s
// easy.
//
// Call one of the `objx.` functions to create your `objx.Map` to get going:
//
// m, err := objx.FromJSON(json)
//
// NOTE: Any methods or functions with the `Must` prefix will panic if something goes wrong,
// the rest will be optimistic and try to figure things out without panicking.
//
// Use `Get` to access the value you're interested in. You can use dot and array
// notation too:
//
// m.Get("places[0].latlng")
//
// Once you have saught the `Value` you're interested in, you can use the `Is*` methods
// to determine its type.
//
// if m.Get("code").IsStr() { /* ... */ }
//
// Or you can just assume the type, and use one of the strong type methods to
// extract the real value:
//
// m.Get("code").Int()
//
// If there's no value there (or if it's the wrong type) then a default value
// will be returned, or you can be explicit about the default value.
//
// Get("code").Int(-1)
//
// If you're dealing with a slice of data as a value, Objx provides many useful
// methods for iterating, manipulating and selecting that data. You can find out more
// by exploring the index below.
//
// Reading data
//
// A simple example of how to use Objx:
//
// // use MustFromJSON to make an objx.Map from some JSON
// m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`)
//
// // get the details
// name := m.Get("name").Str()
// age := m.Get("age").Int()
//
// // get their nickname (or use their name if they
// // don't have one)
// nickname := m.Get("nickname").Str(name)
//
// Ranging
//
// Since `objx.Map` is a `map[string]interface{}` you can treat it as such. For
// example, to `range` the data, do what you would expect:
//
// m := objx.MustFromJSON(json)
// for key, value := range m {
//
// /* ... do your magic ... */
//
// }
package objx

View File

@ -1,98 +0,0 @@
package objx
import (
"github.com/stretchr/testify/assert"
"testing"
)
var fixtures = []struct {
// name is the name of the fixture (used for reporting
// failures)
name string
// data is the JSON data to be worked on
data string
// get is the argument(s) to pass to Get
get interface{}
// output is the expected output
output interface{}
}{
{
name: "Simple get",
data: `{"name": "Mat"}`,
get: "name",
output: "Mat",
},
{
name: "Get with dot notation",
data: `{"address": {"city": "Boulder"}}`,
get: "address.city",
output: "Boulder",
},
{
name: "Deep get with dot notation",
data: `{"one": {"two": {"three": {"four": "hello"}}}}`,
get: "one.two.three.four",
output: "hello",
},
{
name: "Get missing with dot notation",
data: `{"one": {"two": {"three": {"four": "hello"}}}}`,
get: "one.ten",
output: nil,
},
{
name: "Get with array notation",
data: `{"tags": ["one", "two", "three"]}`,
get: "tags[1]",
output: "two",
},
{
name: "Get with array and dot notation",
data: `{"types": { "tags": ["one", "two", "three"]}}`,
get: "types.tags[1]",
output: "two",
},
{
name: "Get with array and dot notation - field after array",
data: `{"tags": [{"name":"one"}, {"name":"two"}, {"name":"three"}]}`,
get: "tags[1].name",
output: "two",
},
{
name: "Complex get with array and dot notation",
data: `{"tags": [{"list": [{"one":"pizza"}]}]}`,
get: "tags[0].list[0].one",
output: "pizza",
},
{
name: "Get field from within string should be nil",
data: `{"name":"Tyler"}`,
get: "name.something",
output: nil,
},
{
name: "Get field from within string (using array accessor) should be nil",
data: `{"numbers":["one", "two", "three"]}`,
get: "numbers[0].nope",
output: nil,
},
}
func TestFixtures(t *testing.T) {
for _, fixture := range fixtures {
m := MustFromJSON(fixture.data)
// get the value
t.Logf("Running get fixture: \"%s\" (%v)", fixture.name, fixture)
value := m.Get(fixture.get.(string))
// make sure it matches
assert.Equal(t, fixture.output, value.data,
"Get fixture \"%s\" failed: %v", fixture.name, fixture,
)
}
}

View File

@ -1,222 +0,0 @@
package objx
import (
"encoding/base64"
"encoding/json"
"errors"
"io/ioutil"
"net/url"
"strings"
)
// MSIConvertable is an interface that defines methods for converting your
// custom types to a map[string]interface{} representation.
type MSIConvertable interface {
// MSI gets a map[string]interface{} (msi) representing the
// object.
MSI() map[string]interface{}
}
// Map provides extended functionality for working with
// untyped data, in particular map[string]interface (msi).
type Map map[string]interface{}
// Value returns the internal value instance
func (m Map) Value() *Value {
return &Value{data: m}
}
// Nil represents a nil Map.
var Nil Map = New(nil)
// New creates a new Map containing the map[string]interface{} in the data argument.
// If the data argument is not a map[string]interface, New attempts to call the
// MSI() method on the MSIConvertable interface to create one.
func New(data interface{}) Map {
if _, ok := data.(map[string]interface{}); !ok {
if converter, ok := data.(MSIConvertable); ok {
data = converter.MSI()
} else {
return nil
}
}
return Map(data.(map[string]interface{}))
}
// MSI creates a map[string]interface{} and puts it inside a new Map.
//
// The arguments follow a key, value pattern.
//
// Panics
//
// Panics if any key arugment is non-string or if there are an odd number of arguments.
//
// Example
//
// To easily create Maps:
//
// m := objx.MSI("name", "Mat", "age", 29, "subobj", objx.MSI("active", true))
//
// // creates an Map equivalent to
// m := objx.New(map[string]interface{}{"name": "Mat", "age": 29, "subobj": map[string]interface{}{"active": true}})
func MSI(keyAndValuePairs ...interface{}) Map {
newMap := make(map[string]interface{})
keyAndValuePairsLen := len(keyAndValuePairs)
if keyAndValuePairsLen%2 != 0 {
panic("objx: MSI must have an even number of arguments following the 'key, value' pattern.")
}
for i := 0; i < keyAndValuePairsLen; i = i + 2 {
key := keyAndValuePairs[i]
value := keyAndValuePairs[i+1]
// make sure the key is a string
keyString, keyStringOK := key.(string)
if !keyStringOK {
panic("objx: MSI must follow 'string, interface{}' pattern. " + keyString + " is not a valid key.")
}
newMap[keyString] = value
}
return New(newMap)
}
// ****** Conversion Constructors
// MustFromJSON creates a new Map containing the data specified in the
// jsonString.
//
// Panics if the JSON is invalid.
func MustFromJSON(jsonString string) Map {
o, err := FromJSON(jsonString)
if err != nil {
panic("objx: MustFromJSON failed with error: " + err.Error())
}
return o
}
// FromJSON creates a new Map containing the data specified in the
// jsonString.
//
// Returns an error if the JSON is invalid.
func FromJSON(jsonString string) (Map, error) {
var data interface{}
err := json.Unmarshal([]byte(jsonString), &data)
if err != nil {
return Nil, err
}
return New(data), nil
}
// FromBase64 creates a new Obj containing the data specified
// in the Base64 string.
//
// The string is an encoded JSON string returned by Base64
func FromBase64(base64String string) (Map, error) {
decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64String))
decoded, err := ioutil.ReadAll(decoder)
if err != nil {
return nil, err
}
return FromJSON(string(decoded))
}
// MustFromBase64 creates a new Obj containing the data specified
// in the Base64 string and panics if there is an error.
//
// The string is an encoded JSON string returned by Base64
func MustFromBase64(base64String string) Map {
result, err := FromBase64(base64String)
if err != nil {
panic("objx: MustFromBase64 failed with error: " + err.Error())
}
return result
}
// FromSignedBase64 creates a new Obj containing the data specified
// in the Base64 string.
//
// The string is an encoded JSON string returned by SignedBase64
func FromSignedBase64(base64String, key string) (Map, error) {
parts := strings.Split(base64String, SignatureSeparator)
if len(parts) != 2 {
return nil, errors.New("objx: Signed base64 string is malformed.")
}
sig := HashWithKey(parts[0], key)
if parts[1] != sig {
return nil, errors.New("objx: Signature for base64 data does not match.")
}
return FromBase64(parts[0])
}
// MustFromSignedBase64 creates a new Obj containing the data specified
// in the Base64 string and panics if there is an error.
//
// The string is an encoded JSON string returned by Base64
func MustFromSignedBase64(base64String, key string) Map {
result, err := FromSignedBase64(base64String, key)
if err != nil {
panic("objx: MustFromSignedBase64 failed with error: " + err.Error())
}
return result
}
// FromURLQuery generates a new Obj by parsing the specified
// query.
//
// For queries with multiple values, the first value is selected.
func FromURLQuery(query string) (Map, error) {
vals, err := url.ParseQuery(query)
if err != nil {
return nil, err
}
m := make(map[string]interface{})
for k, vals := range vals {
m[k] = vals[0]
}
return New(m), nil
}
// MustFromURLQuery generates a new Obj by parsing the specified
// query.
//
// For queries with multiple values, the first value is selected.
//
// Panics if it encounters an error
func MustFromURLQuery(query string) Map {
o, err := FromURLQuery(query)
if err != nil {
panic("objx: MustFromURLQuery failed with error: " + err.Error())
}
return o
}

View File

@ -1,10 +0,0 @@
package objx
var TestMap map[string]interface{} = map[string]interface{}{
"name": "Tyler",
"address": map[string]interface{}{
"city": "Salt Lake City",
"state": "UT",
},
"numbers": []interface{}{"one", "two", "three", "four", "five"},
}

View File

@ -1,147 +0,0 @@
package objx
import (
"github.com/stretchr/testify/assert"
"testing"
)
type Convertable struct {
name string
}
func (c *Convertable) MSI() map[string]interface{} {
return map[string]interface{}{"name": c.name}
}
type Unconvertable struct {
name string
}
func TestMapCreation(t *testing.T) {
o := New(nil)
assert.Nil(t, o)
o = New("Tyler")
assert.Nil(t, o)
unconvertable := &Unconvertable{name: "Tyler"}
o = New(unconvertable)
assert.Nil(t, o)
convertable := &Convertable{name: "Tyler"}
o = New(convertable)
if assert.NotNil(t, convertable) {
assert.Equal(t, "Tyler", o["name"], "Tyler")
}
o = MSI()
if assert.NotNil(t, o) {
assert.NotNil(t, o)
}
o = MSI("name", "Tyler")
if assert.NotNil(t, o) {
if assert.NotNil(t, o) {
assert.Equal(t, o["name"], "Tyler")
}
}
}
func TestMapMustFromJSONWithError(t *testing.T) {
_, err := FromJSON(`"name":"Mat"}`)
assert.Error(t, err)
}
func TestMapFromJSON(t *testing.T) {
o := MustFromJSON(`{"name":"Mat"}`)
if assert.NotNil(t, o) {
if assert.NotNil(t, o) {
assert.Equal(t, "Mat", o["name"])
}
}
}
func TestMapFromJSONWithError(t *testing.T) {
var m Map
assert.Panics(t, func() {
m = MustFromJSON(`"name":"Mat"}`)
})
assert.Nil(t, m)
}
func TestMapFromBase64String(t *testing.T) {
base64String := "eyJuYW1lIjoiTWF0In0="
o, err := FromBase64(base64String)
if assert.NoError(t, err) {
assert.Equal(t, o.Get("name").Str(), "Mat")
}
assert.Equal(t, MustFromBase64(base64String).Get("name").Str(), "Mat")
}
func TestMapFromBase64StringWithError(t *testing.T) {
base64String := "eyJuYW1lIjoiTWFasd0In0="
_, err := FromBase64(base64String)
assert.Error(t, err)
assert.Panics(t, func() {
MustFromBase64(base64String)
})
}
func TestMapFromSignedBase64String(t *testing.T) {
base64String := "eyJuYW1lIjoiTWF0In0=_67ee82916f90b2c0d68c903266e8998c9ef0c3d6"
o, err := FromSignedBase64(base64String, "key")
if assert.NoError(t, err) {
assert.Equal(t, o.Get("name").Str(), "Mat")
}
assert.Equal(t, MustFromSignedBase64(base64String, "key").Get("name").Str(), "Mat")
}
func TestMapFromSignedBase64StringWithError(t *testing.T) {
base64String := "eyJuYW1lasdIjoiTWF0In0=_67ee82916f90b2c0d68c903266e8998c9ef0c3d6"
_, err := FromSignedBase64(base64String, "key")
assert.Error(t, err)
assert.Panics(t, func() {
MustFromSignedBase64(base64String, "key")
})
}
func TestMapFromURLQuery(t *testing.T) {
m, err := FromURLQuery("name=tyler&state=UT")
if assert.NoError(t, err) && assert.NotNil(t, m) {
assert.Equal(t, "tyler", m.Get("name").Str())
assert.Equal(t, "UT", m.Get("state").Str())
}
}

View File

@ -1,81 +0,0 @@
package objx
// Exclude returns a new Map with the keys in the specified []string
// excluded.
func (d Map) Exclude(exclude []string) Map {
excluded := make(Map)
for k, v := range d {
var shouldInclude bool = true
for _, toExclude := range exclude {
if k == toExclude {
shouldInclude = false
break
}
}
if shouldInclude {
excluded[k] = v
}
}
return excluded
}
// Copy creates a shallow copy of the Obj.
func (m Map) Copy() Map {
copied := make(map[string]interface{})
for k, v := range m {
copied[k] = v
}
return New(copied)
}
// Merge blends the specified map with a copy of this map and returns the result.
//
// Keys that appear in both will be selected from the specified map.
// This method requires that the wrapped object be a map[string]interface{}
func (m Map) Merge(merge Map) Map {
return m.Copy().MergeHere(merge)
}
// Merge blends the specified map with this map and returns the current map.
//
// Keys that appear in both will be selected from the specified map. The original map
// will be modified. This method requires that
// the wrapped object be a map[string]interface{}
func (m Map) MergeHere(merge Map) Map {
for k, v := range merge {
m[k] = v
}
return m
}
// Transform builds a new Obj giving the transformer a chance
// to change the keys and values as it goes. This method requires that
// the wrapped object be a map[string]interface{}
func (m Map) Transform(transformer func(key string, value interface{}) (string, interface{})) Map {
newMap := make(map[string]interface{})
for k, v := range m {
modifiedKey, modifiedVal := transformer(k, v)
newMap[modifiedKey] = modifiedVal
}
return New(newMap)
}
// TransformKeys builds a new map using the specified key mapping.
//
// Unspecified keys will be unaltered.
// This method requires that the wrapped object be a map[string]interface{}
func (m Map) TransformKeys(mapping map[string]string) Map {
return m.Transform(func(key string, value interface{}) (string, interface{}) {
if newKey, ok := mapping[key]; ok {
return newKey, value
}
return key, value
})
}

View File

@ -1,77 +0,0 @@
package objx
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestExclude(t *testing.T) {
d := make(Map)
d["name"] = "Mat"
d["age"] = 29
d["secret"] = "ABC"
excluded := d.Exclude([]string{"secret"})
assert.Equal(t, d["name"], excluded["name"])
assert.Equal(t, d["age"], excluded["age"])
assert.False(t, excluded.Has("secret"), "secret should be excluded")
}
func TestCopy(t *testing.T) {
d1 := make(map[string]interface{})
d1["name"] = "Tyler"
d1["location"] = "UT"
d1Obj := New(d1)
d2Obj := d1Obj.Copy()
d2Obj["name"] = "Mat"
assert.Equal(t, d1Obj.Get("name").Str(), "Tyler")
assert.Equal(t, d2Obj.Get("name").Str(), "Mat")
}
func TestMerge(t *testing.T) {
d := make(map[string]interface{})
d["name"] = "Mat"
d1 := make(map[string]interface{})
d1["name"] = "Tyler"
d1["location"] = "UT"
dObj := New(d)
d1Obj := New(d1)
merged := dObj.Merge(d1Obj)
assert.Equal(t, merged.Get("name").Str(), d1Obj.Get("name").Str())
assert.Equal(t, merged.Get("location").Str(), d1Obj.Get("location").Str())
assert.Empty(t, dObj.Get("location").Str())
}
func TestMergeHere(t *testing.T) {
d := make(map[string]interface{})
d["name"] = "Mat"
d1 := make(map[string]interface{})
d1["name"] = "Tyler"
d1["location"] = "UT"
dObj := New(d)
d1Obj := New(d1)
merged := dObj.MergeHere(d1Obj)
assert.Equal(t, dObj, merged, "With MergeHere, it should return the first modified map")
assert.Equal(t, merged.Get("name").Str(), d1Obj.Get("name").Str())
assert.Equal(t, merged.Get("location").Str(), d1Obj.Get("location").Str())
assert.Equal(t, merged.Get("location").Str(), dObj.Get("location").Str())
}

View File

@ -1,14 +0,0 @@
package objx
import (
"crypto/sha1"
"encoding/hex"
)
// HashWithKey hashes the specified string using the security
// key.
func HashWithKey(data, key string) string {
hash := sha1.New()
hash.Write([]byte(data + ":" + key))
return hex.EncodeToString(hash.Sum(nil))
}

View File

@ -1,12 +0,0 @@
package objx
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestHashWithKey(t *testing.T) {
assert.Equal(t, "0ce84d8d01f2c7b6e0882b784429c54d280ea2d9", HashWithKey("abc", "def"))
}

View File

@ -1,41 +0,0 @@
package objx
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestSimpleExample(t *testing.T) {
// build a map from a JSON object
o := MustFromJSON(`{"name":"Mat","foods":["indian","chinese"], "location":{"county":"hobbiton","city":"the shire"}}`)
// Map can be used as a straight map[string]interface{}
assert.Equal(t, o["name"], "Mat")
// Get an Value object
v := o.Get("name")
assert.Equal(t, v, &Value{data: "Mat"})
// Test the contained value
assert.False(t, v.IsInt())
assert.False(t, v.IsBool())
assert.True(t, v.IsStr())
// Get the contained value
assert.Equal(t, v.Str(), "Mat")
// Get a default value if the contained value is not of the expected type or does not exist
assert.Equal(t, 1, v.Int(1))
// Get a value by using array notation
assert.Equal(t, "indian", o.Get("foods[0]").Data())
// Set a value by using array notation
o.Set("foods[0]", "italian")
assert.Equal(t, "italian", o.Get("foods[0]").Str())
// Get a value by using dot notation
assert.Equal(t, "hobbiton", o.Get("location.county").Str())
}

View File

@ -1,17 +0,0 @@
package objx
// Has gets whether there is something at the specified selector
// or not.
//
// If m is nil, Has will always return false.
func (m Map) Has(selector string) bool {
if m == nil {
return false
}
return !m.Get(selector).IsNil()
}
// IsNil gets whether the data is nil or not.
func (v *Value) IsNil() bool {
return v == nil || v.data == nil
}

View File

@ -1,24 +0,0 @@
package objx
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestHas(t *testing.T) {
m := New(TestMap)
assert.True(t, m.Has("name"))
assert.True(t, m.Has("address.state"))
assert.True(t, m.Has("numbers[4]"))
assert.False(t, m.Has("address.state.nope"))
assert.False(t, m.Has("address.nope"))
assert.False(t, m.Has("nope"))
assert.False(t, m.Has("numbers[5]"))
m = nil
assert.False(t, m.Has("nothing"))
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +0,0 @@
package objx
// Value provides methods for extracting interface{} data in various
// types.
type Value struct {
// data contains the raw data being managed by this Value
data interface{}
}
// Data returns the raw data contained by this Value
func (v *Value) Data() interface{} {
return v.data
}

View File

@ -1 +0,0 @@
package objx

View File

@ -1,43 +0,0 @@
// Provides a system by which it is possible to mock your objects and verify calls are happening as expected.
//
// Example Usage
//
// The mock package provides an object, Mock, that tracks activity on another object. It is usually
// embedded into a test object as shown below:
//
// type MyTestObject struct {
// // add a Mock object instance
// mock.Mock
//
// // other fields go here as normal
// }
//
// When implementing the methods of an interface, you wire your functions up
// to call the Mock.Called(args...) method, and return the appropriate values.
//
// For example, to mock a method that saves the name and age of a person and returns
// the year of their birth or an error, you might write this:
//
// func (o *MyTestObject) SavePersonDetails(firstname, lastname string, age int) (int, error) {
// args := o.Called(firstname, lastname, age)
// return args.Int(0), args.Error(1)
// }
//
// The Int, Error and Bool methods are examples of strongly typed getters that take the argument
// index position. Given this argument list:
//
// (12, true, "Something")
//
// You could read them out strongly typed like this:
//
// args.Int(0)
// args.Bool(1)
// args.String(2)
//
// For objects of your own type, use the generic Arguments.Get(index) method and make a type assertion:
//
// return args.Get(0).(*MyObject), args.Get(1).(*AnotherObjectOfMine)
//
// This may cause a panic if the object you are getting is nil (the type assertion will fail), in those
// cases you should check for nil first.
package mock

View File

@ -1,566 +0,0 @@
package mock
import (
"fmt"
"github.com/stretchr/objx"
"github.com/stretchr/testify/assert"
"reflect"
"runtime"
"strings"
"sync"
"time"
)
// TestingT is an interface wrapper around *testing.T
type TestingT interface {
Logf(format string, args ...interface{})
Errorf(format string, args ...interface{})
}
/*
Call
*/
// Call represents a method call and is used for setting expectations,
// as well as recording activity.
type Call struct {
// The name of the method that was or will be called.
Method string
// Holds the arguments of the method.
Arguments Arguments
// Holds the arguments that should be returned when
// this method is called.
ReturnArguments Arguments
// The number of times to return the return arguments when setting
// expectations. 0 means to always return the value.
Repeatability int
// Holds a channel that will be used to block the Return until it either
// recieves a message or is closed. nil means it returns immediately.
WaitFor <-chan time.Time
// Holds a handler used to manipulate arguments content that are passed by
// reference. It's useful when mocking methods such as unmarshalers or
// decoders.
Run func(Arguments)
}
// Mock is the workhorse used to track activity on another object.
// For an example of its usage, refer to the "Example Usage" section at the top of this document.
type Mock struct {
// The method name that is currently
// being referred to by the On method.
onMethodName string
// An array of the arguments that are
// currently being referred to by the On method.
onMethodArguments Arguments
// Represents the calls that are expected of
// an object.
ExpectedCalls []Call
// Holds the calls that were made to this mocked object.
Calls []Call
// TestData holds any data that might be useful for testing. Testify ignores
// this data completely allowing you to do whatever you like with it.
testData objx.Map
mutex sync.Mutex
}
// TestData holds any data that might be useful for testing. Testify ignores
// this data completely allowing you to do whatever you like with it.
func (m *Mock) TestData() objx.Map {
if m.testData == nil {
m.testData = make(objx.Map)
}
return m.testData
}
/*
Setting expectations
*/
// On starts a description of an expectation of the specified method
// being called.
//
// Mock.On("MyMethod", arg1, arg2)
func (m *Mock) On(methodName string, arguments ...interface{}) *Mock {
m.onMethodName = methodName
m.onMethodArguments = arguments
for _, arg := range arguments {
if v := reflect.ValueOf(arg); v.Kind() == reflect.Func {
panic(fmt.Sprintf("cannot use Func in expectations. Use mock.AnythingOfType(\"%T\")", arg))
}
}
return m
}
// Return finishes a description of an expectation of the method (and arguments)
// specified in the most recent On method call.
//
// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2)
func (m *Mock) Return(returnArguments ...interface{}) *Mock {
m.ExpectedCalls = append(m.ExpectedCalls, Call{m.onMethodName, m.onMethodArguments, returnArguments, 0, nil, nil})
return m
}
// Once indicates that that the mock should only return the value once.
//
// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Once()
func (m *Mock) Once() {
m.ExpectedCalls[len(m.ExpectedCalls)-1].Repeatability = 1
}
// Twice indicates that that the mock should only return the value twice.
//
// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Twice()
func (m *Mock) Twice() {
m.ExpectedCalls[len(m.ExpectedCalls)-1].Repeatability = 2
}
// Times indicates that that the mock should only return the indicated number
// of times.
//
// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Times(5)
func (m *Mock) Times(i int) {
m.ExpectedCalls[len(m.ExpectedCalls)-1].Repeatability = i
}
// WaitUntil sets the channel that will block the mock's return until its closed
// or a message is received.
//
// Mock.On("MyMethod", arg1, arg2).WaitUntil(time.After(time.Second))
func (m *Mock) WaitUntil(w <-chan time.Time) *Mock {
m.ExpectedCalls[len(m.ExpectedCalls)-1].WaitFor = w
return m
}
// After sets how long to block until the call returns
//
// Mock.On("MyMethod", arg1, arg2).After(time.Second)
func (m *Mock) After(d time.Duration) *Mock {
return m.WaitUntil(time.After(d))
}
// Run sets a handler to be called before returning. It can be used when
// mocking a method such as unmarshalers that takes a pointer to a struct and
// sets properties in such struct
//
// Mock.On("Unmarshal", AnythingOfType("*map[string]interface{}").Return().Run(function(args Arguments) {
// arg := args.Get(0).(*map[string]interface{})
// arg["foo"] = "bar"
// })
func (m *Mock) Run(fn func(Arguments)) *Mock {
m.ExpectedCalls[len(m.ExpectedCalls)-1].Run = fn
return m
}
/*
Recording and responding to activity
*/
func (m *Mock) findExpectedCall(method string, arguments ...interface{}) (int, *Call) {
for i, call := range m.ExpectedCalls {
if call.Method == method && call.Repeatability > -1 {
_, diffCount := call.Arguments.Diff(arguments)
if diffCount == 0 {
return i, &call
}
}
}
return -1, nil
}
func (m *Mock) findClosestCall(method string, arguments ...interface{}) (bool, *Call) {
diffCount := 0
var closestCall *Call = nil
for _, call := range m.ExpectedCalls {
if call.Method == method {
_, tempDiffCount := call.Arguments.Diff(arguments)
if tempDiffCount < diffCount || diffCount == 0 {
diffCount = tempDiffCount
closestCall = &call
}
}
}
if closestCall == nil {
return false, nil
}
return true, closestCall
}
func callString(method string, arguments Arguments, includeArgumentValues bool) string {
var argValsString string = ""
if includeArgumentValues {
var argVals []string
for argIndex, arg := range arguments {
argVals = append(argVals, fmt.Sprintf("%d: %v", argIndex, arg))
}
argValsString = fmt.Sprintf("\n\t\t%s", strings.Join(argVals, "\n\t\t"))
}
return fmt.Sprintf("%s(%s)%s", method, arguments.String(), argValsString)
}
// Called tells the mock object that a method has been called, and gets an array
// of arguments to return. Panics if the call is unexpected (i.e. not preceeded by
// appropriate .On .Return() calls)
// If Call.WaitFor is set, blocks until the channel is closed or receives a message.
func (m *Mock) Called(arguments ...interface{}) Arguments {
defer m.mutex.Unlock()
m.mutex.Lock()
// get the calling function's name
pc, _, _, ok := runtime.Caller(1)
if !ok {
panic("Couldn't get the caller information")
}
functionPath := runtime.FuncForPC(pc).Name()
parts := strings.Split(functionPath, ".")
functionName := parts[len(parts)-1]
found, call := m.findExpectedCall(functionName, arguments...)
switch {
case found < 0:
// we have to fail here - because we don't know what to do
// as the return arguments. This is because:
//
// a) this is a totally unexpected call to this method,
// b) the arguments are not what was expected, or
// c) the developer has forgotten to add an accompanying On...Return pair.
closestFound, closestCall := m.findClosestCall(functionName, arguments...)
if closestFound {
panic(fmt.Sprintf("\n\nmock: Unexpected Method Call\n-----------------------------\n\n%s\n\nThe closest call I have is: \n\n%s\n", callString(functionName, arguments, true), callString(functionName, closestCall.Arguments, true)))
} else {
panic(fmt.Sprintf("\nassert: mock: I don't know what to return because the method call was unexpected.\n\tEither do Mock.On(\"%s\").Return(...) first, or remove the %s() call.\n\tThis method was unexpected:\n\t\t%s\n\tat: %s", functionName, functionName, callString(functionName, arguments, true), assert.CallerInfo()))
}
case call.Repeatability == 1:
call.Repeatability = -1
m.ExpectedCalls[found] = *call
case call.Repeatability > 1:
call.Repeatability -= 1
m.ExpectedCalls[found] = *call
}
// add the call
m.Calls = append(m.Calls, Call{functionName, arguments, make([]interface{}, 0), 0, nil, nil})
// block if specified
if call.WaitFor != nil {
<-call.WaitFor
}
if call.Run != nil {
call.Run(arguments)
}
return call.ReturnArguments
}
/*
Assertions
*/
// AssertExpectationsForObjects asserts that everything specified with On and Return
// of the specified objects was in fact called as expected.
//
// Calls may have occurred in any order.
func AssertExpectationsForObjects(t TestingT, testObjects ...interface{}) bool {
var success bool = true
for _, obj := range testObjects {
mockObj := obj.(Mock)
success = success && mockObj.AssertExpectations(t)
}
return success
}
// AssertExpectations asserts that everything specified with On and Return was
// in fact called as expected. Calls may have occurred in any order.
func (m *Mock) AssertExpectations(t TestingT) bool {
var somethingMissing bool = false
var failedExpectations int = 0
// iterate through each expectation
for _, expectedCall := range m.ExpectedCalls {
switch {
case !m.methodWasCalled(expectedCall.Method, expectedCall.Arguments):
somethingMissing = true
failedExpectations++
t.Logf("\u274C\t%s(%s)", expectedCall.Method, expectedCall.Arguments.String())
case expectedCall.Repeatability > 0:
somethingMissing = true
failedExpectations++
default:
t.Logf("\u2705\t%s(%s)", expectedCall.Method, expectedCall.Arguments.String())
}
}
if somethingMissing {
t.Errorf("FAIL: %d out of %d expectation(s) were met.\n\tThe code you are testing needs to make %d more call(s).\n\tat: %s", len(m.ExpectedCalls)-failedExpectations, len(m.ExpectedCalls), failedExpectations, assert.CallerInfo())
}
return !somethingMissing
}
// AssertNumberOfCalls asserts that the method was called expectedCalls times.
func (m *Mock) AssertNumberOfCalls(t TestingT, methodName string, expectedCalls int) bool {
var actualCalls int = 0
for _, call := range m.Calls {
if call.Method == methodName {
actualCalls++
}
}
return assert.Equal(t, actualCalls, expectedCalls, fmt.Sprintf("Expected number of calls (%d) does not match the actual number of calls (%d).", expectedCalls, actualCalls))
}
// AssertCalled asserts that the method was called.
func (m *Mock) AssertCalled(t TestingT, methodName string, arguments ...interface{}) bool {
if !assert.True(t, m.methodWasCalled(methodName, arguments), fmt.Sprintf("The \"%s\" method should have been called with %d argument(s), but was not.", methodName, len(arguments))) {
t.Logf("%v", m.ExpectedCalls)
return false
}
return true
}
// AssertNotCalled asserts that the method was not called.
func (m *Mock) AssertNotCalled(t TestingT, methodName string, arguments ...interface{}) bool {
if !assert.False(t, m.methodWasCalled(methodName, arguments), fmt.Sprintf("The \"%s\" method was called with %d argument(s), but should NOT have been.", methodName, len(arguments))) {
t.Logf("%v", m.ExpectedCalls)
return false
}
return true
}
func (m *Mock) methodWasCalled(methodName string, expected []interface{}) bool {
for _, call := range m.Calls {
if call.Method == methodName {
_, differences := Arguments(expected).Diff(call.Arguments)
if differences == 0 {
// found the expected call
return true
}
}
}
// we didn't find the expected call
return false
}
/*
Arguments
*/
// Arguments holds an array of method arguments or return values.
type Arguments []interface{}
const (
// The "any" argument. Used in Diff and Assert when
// the argument being tested shouldn't be taken into consideration.
Anything string = "mock.Anything"
)
// AnythingOfTypeArgument is a string that contains the type of an argument
// for use when type checking. Used in Diff and Assert.
type AnythingOfTypeArgument string
// AnythingOfType returns an AnythingOfTypeArgument object containing the
// name of the type to check for. Used in Diff and Assert.
//
// For example:
// Assert(t, AnythingOfType("string"), AnythingOfType("int"))
func AnythingOfType(t string) AnythingOfTypeArgument {
return AnythingOfTypeArgument(t)
}
// Get Returns the argument at the specified index.
func (args Arguments) Get(index int) interface{} {
if index+1 > len(args) {
panic(fmt.Sprintf("assert: arguments: Cannot call Get(%d) because there are %d argument(s).", index, len(args)))
}
return args[index]
}
// Is gets whether the objects match the arguments specified.
func (args Arguments) Is(objects ...interface{}) bool {
for i, obj := range args {
if obj != objects[i] {
return false
}
}
return true
}
// Diff gets a string describing the differences between the arguments
// and the specified objects.
//
// Returns the diff string and number of differences found.
func (args Arguments) Diff(objects []interface{}) (string, int) {
var output string = "\n"
var differences int
var maxArgCount int = len(args)
if len(objects) > maxArgCount {
maxArgCount = len(objects)
}
for i := 0; i < maxArgCount; i++ {
var actual, expected interface{}
if len(objects) <= i {
actual = "(Missing)"
} else {
actual = objects[i]
}
if len(args) <= i {
expected = "(Missing)"
} else {
expected = args[i]
}
if reflect.TypeOf(expected) == reflect.TypeOf((*AnythingOfTypeArgument)(nil)).Elem() {
// type checking
if reflect.TypeOf(actual).Name() != string(expected.(AnythingOfTypeArgument)) && reflect.TypeOf(actual).String() != string(expected.(AnythingOfTypeArgument)) {
// not match
differences++
output = fmt.Sprintf("%s\t%d: \u274C type %s != type %s - %s\n", output, i, expected, reflect.TypeOf(actual).Name(), actual)
}
} else {
// normal checking
if assert.ObjectsAreEqual(expected, Anything) || assert.ObjectsAreEqual(actual, Anything) || assert.ObjectsAreEqual(actual, expected) {
// match
output = fmt.Sprintf("%s\t%d: \u2705 %s == %s\n", output, i, actual, expected)
} else {
// not match
differences++
output = fmt.Sprintf("%s\t%d: \u274C %s != %s\n", output, i, actual, expected)
}
}
}
if differences == 0 {
return "No differences.", differences
}
return output, differences
}
// Assert compares the arguments with the specified objects and fails if
// they do not exactly match.
func (args Arguments) Assert(t TestingT, objects ...interface{}) bool {
// get the differences
diff, diffCount := args.Diff(objects)
if diffCount == 0 {
return true
}
// there are differences... report them...
t.Logf(diff)
t.Errorf("%sArguments do not match.", assert.CallerInfo())
return false
}
// String gets the argument at the specified index. Panics if there is no argument, or
// if the argument is of the wrong type.
//
// If no index is provided, String() returns a complete string representation
// of the arguments.
func (args Arguments) String(indexOrNil ...int) string {
if len(indexOrNil) == 0 {
// normal String() method - return a string representation of the args
var argsStr []string
for _, arg := range args {
argsStr = append(argsStr, fmt.Sprintf("%s", reflect.TypeOf(arg)))
}
return strings.Join(argsStr, ",")
} else if len(indexOrNil) == 1 {
// Index has been specified - get the argument at that index
var index int = indexOrNil[0]
var s string
var ok bool
if s, ok = args.Get(index).(string); !ok {
panic(fmt.Sprintf("assert: arguments: String(%d) failed because object wasn't correct type: %s", index, args.Get(index)))
}
return s
}
panic(fmt.Sprintf("assert: arguments: Wrong number of arguments passed to String. Must be 0 or 1, not %d", len(indexOrNil)))
}
// Int gets the argument at the specified index. Panics if there is no argument, or
// if the argument is of the wrong type.
func (args Arguments) Int(index int) int {
var s int
var ok bool
if s, ok = args.Get(index).(int); !ok {
panic(fmt.Sprintf("assert: arguments: Int(%d) failed because object wasn't correct type: %v", index, args.Get(index)))
}
return s
}
// Error gets the argument at the specified index. Panics if there is no argument, or
// if the argument is of the wrong type.
func (args Arguments) Error(index int) error {
obj := args.Get(index)
var s error
var ok bool
if obj == nil {
return nil
}
if s, ok = obj.(error); !ok {
panic(fmt.Sprintf("assert: arguments: Error(%d) failed because object wasn't correct type: %v", index, args.Get(index)))
}
return s
}
// Bool gets the argument at the specified index. Panics if there is no argument, or
// if the argument is of the wrong type.
func (args Arguments) Bool(index int) bool {
var s bool
var ok bool
if s, ok = args.Get(index).(bool); !ok {
panic(fmt.Sprintf("assert: arguments: Bool(%d) failed because object wasn't correct type: %v", index, args.Get(index)))
}
return s
}

View File

@ -1,843 +0,0 @@
package mock
import (
"errors"
"github.com/stretchr/testify/assert"
"testing"
"time"
)
/*
Test objects
*/
// ExampleInterface represents an example interface.
type ExampleInterface interface {
TheExampleMethod(a, b, c int) (int, error)
}
// TestExampleImplementation is a test implementation of ExampleInterface
type TestExampleImplementation struct {
Mock
}
func (i *TestExampleImplementation) TheExampleMethod(a, b, c int) (int, error) {
args := i.Called(a, b, c)
return args.Int(0), errors.New("Whoops")
}
func (i *TestExampleImplementation) TheExampleMethod2(yesorno bool) {
i.Called(yesorno)
}
type ExampleType struct {
ran bool
}
func (i *TestExampleImplementation) TheExampleMethod3(et *ExampleType) error {
args := i.Called(et)
return args.Error(0)
}
func (i *TestExampleImplementation) TheExampleMethodFunc(fn func(string) error) error {
args := i.Called(fn)
return args.Error(0)
}
type ExampleFuncType func(string) error
func (i *TestExampleImplementation) TheExampleMethodFuncType(fn ExampleFuncType) error {
args := i.Called(fn)
return args.Error(0)
}
/*
Mock
*/
func Test_Mock_TestData(t *testing.T) {
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
if assert.NotNil(t, mockedService.TestData()) {
mockedService.TestData().Set("something", 123)
assert.Equal(t, 123, mockedService.TestData().Get("something").Data())
}
}
func Test_Mock_On(t *testing.T) {
// make a test impl object
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
assert.Equal(t, mockedService.On("TheExampleMethod"), &mockedService.Mock)
assert.Equal(t, "TheExampleMethod", mockedService.onMethodName)
}
func Test_Mock_On_WithArgs(t *testing.T) {
// make a test impl object
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
assert.Equal(t, mockedService.On("TheExampleMethod", 1, 2, 3), &mockedService.Mock)
assert.Equal(t, "TheExampleMethod", mockedService.onMethodName)
assert.Equal(t, 1, mockedService.onMethodArguments[0])
assert.Equal(t, 2, mockedService.onMethodArguments[1])
assert.Equal(t, 3, mockedService.onMethodArguments[2])
}
func Test_Mock_On_WithFuncArg(t *testing.T) {
// make a test impl object
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
assert.Equal(t, mockedService.On("TheExampleMethodFunc", AnythingOfType("func(string) error")).Return(nil), &mockedService.Mock)
assert.Equal(t, "TheExampleMethodFunc", mockedService.onMethodName)
assert.Equal(t, AnythingOfType("func(string) error"), mockedService.onMethodArguments[0])
fn := func(string) error { return nil }
mockedService.TheExampleMethodFunc(fn)
}
func Test_Mock_On_WithFuncPanics(t *testing.T) {
// make a test impl object
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
assert.Panics(t, func() {
mockedService.On("TheExampleMethodFunc", func(string) error { return nil })
})
}
func Test_Mock_On_WithFuncTypeArg(t *testing.T) {
// make a test impl object
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
assert.Equal(t, mockedService.On("TheExampleMethodFuncType", AnythingOfType("mock.ExampleFuncType")).Return(nil), &mockedService.Mock)
assert.Equal(t, "TheExampleMethodFuncType", mockedService.onMethodName)
assert.Equal(t, AnythingOfType("mock.ExampleFuncType"), mockedService.onMethodArguments[0])
fn := func(string) error { return nil }
mockedService.TheExampleMethodFuncType(fn)
}
func Test_Mock_Return(t *testing.T) {
// make a test impl object
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
assert.Equal(t, mockedService.On("TheExampleMethod", "A", "B", true).Return(1, "two", true), &mockedService.Mock)
// ensure the call was created
if assert.Equal(t, 1, len(mockedService.ExpectedCalls)) {
call := mockedService.ExpectedCalls[0]
assert.Equal(t, "TheExampleMethod", call.Method)
assert.Equal(t, "A", call.Arguments[0])
assert.Equal(t, "B", call.Arguments[1])
assert.Equal(t, true, call.Arguments[2])
assert.Equal(t, 1, call.ReturnArguments[0])
assert.Equal(t, "two", call.ReturnArguments[1])
assert.Equal(t, true, call.ReturnArguments[2])
assert.Equal(t, 0, call.Repeatability)
assert.Nil(t, call.WaitFor)
}
}
func Test_Mock_Return_WaitUntil(t *testing.T) {
// make a test impl object
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
ch := time.After(time.Second)
assert.Equal(t, mockedService.Mock.On("TheExampleMethod", "A", "B", true).Return(1, "two", true).WaitUntil(ch), &mockedService.Mock)
// ensure the call was created
if assert.Equal(t, 1, len(mockedService.Mock.ExpectedCalls)) {
call := mockedService.Mock.ExpectedCalls[0]
assert.Equal(t, "TheExampleMethod", call.Method)
assert.Equal(t, "A", call.Arguments[0])
assert.Equal(t, "B", call.Arguments[1])
assert.Equal(t, true, call.Arguments[2])
assert.Equal(t, 1, call.ReturnArguments[0])
assert.Equal(t, "two", call.ReturnArguments[1])
assert.Equal(t, true, call.ReturnArguments[2])
assert.Equal(t, 0, call.Repeatability)
assert.Equal(t, ch, call.WaitFor)
}
}
func Test_Mock_Return_After(t *testing.T) {
// make a test impl object
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
assert.Equal(t, mockedService.Mock.On("TheExampleMethod", "A", "B", true).Return(1, "two", true).After(time.Second), &mockedService.Mock)
// ensure the call was created
if assert.Equal(t, 1, len(mockedService.Mock.ExpectedCalls)) {
call := mockedService.Mock.ExpectedCalls[0]
assert.Equal(t, "TheExampleMethod", call.Method)
assert.Equal(t, "A", call.Arguments[0])
assert.Equal(t, "B", call.Arguments[1])
assert.Equal(t, true, call.Arguments[2])
assert.Equal(t, 1, call.ReturnArguments[0])
assert.Equal(t, "two", call.ReturnArguments[1])
assert.Equal(t, true, call.ReturnArguments[2])
assert.Equal(t, 0, call.Repeatability)
assert.NotEqual(t, nil, call.WaitFor)
}
}
func Test_Mock_Return_Run(t *testing.T) {
// make a test impl object
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
assert.Equal(t, mockedService.Mock.On("TheExampleMethod3", AnythingOfType("*mock.ExampleType")).Return(nil).Run(func(args Arguments) {
arg := args.Get(0).(*ExampleType)
arg.ran = true
}), &mockedService.Mock)
// ensure the call was created
if assert.Equal(t, 1, len(mockedService.Mock.ExpectedCalls)) {
call := mockedService.Mock.ExpectedCalls[0]
assert.Equal(t, "TheExampleMethod3", call.Method)
assert.Equal(t, AnythingOfType("*mock.ExampleType"), call.Arguments[0])
assert.Equal(t, nil, call.ReturnArguments[0])
assert.Equal(t, 0, call.Repeatability)
assert.NotEqual(t, nil, call.WaitFor)
assert.NotNil(t, call.Run)
}
et := ExampleType{}
assert.Equal(t, false, et.ran)
mockedService.TheExampleMethod3(&et)
assert.Equal(t, true, et.ran)
}
func Test_Mock_Return_Once(t *testing.T) {
// make a test impl object
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
mockedService.On("TheExampleMethod", "A", "B", true).Return(1, "two", true).Once()
// ensure the call was created
if assert.Equal(t, 1, len(mockedService.ExpectedCalls)) {
call := mockedService.ExpectedCalls[0]
assert.Equal(t, "TheExampleMethod", call.Method)
assert.Equal(t, "A", call.Arguments[0])
assert.Equal(t, "B", call.Arguments[1])
assert.Equal(t, true, call.Arguments[2])
assert.Equal(t, 1, call.ReturnArguments[0])
assert.Equal(t, "two", call.ReturnArguments[1])
assert.Equal(t, true, call.ReturnArguments[2])
assert.Equal(t, 1, call.Repeatability)
assert.Nil(t, call.WaitFor)
}
}
func Test_Mock_Return_Twice(t *testing.T) {
// make a test impl object
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
mockedService.On("TheExampleMethod", "A", "B", true).Return(1, "two", true).Twice()
// ensure the call was created
if assert.Equal(t, 1, len(mockedService.ExpectedCalls)) {
call := mockedService.ExpectedCalls[0]
assert.Equal(t, "TheExampleMethod", call.Method)
assert.Equal(t, "A", call.Arguments[0])
assert.Equal(t, "B", call.Arguments[1])
assert.Equal(t, true, call.Arguments[2])
assert.Equal(t, 1, call.ReturnArguments[0])
assert.Equal(t, "two", call.ReturnArguments[1])
assert.Equal(t, true, call.ReturnArguments[2])
assert.Equal(t, 2, call.Repeatability)
assert.Nil(t, call.WaitFor)
}
}
func Test_Mock_Return_Times(t *testing.T) {
// make a test impl object
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
mockedService.On("TheExampleMethod", "A", "B", true).Return(1, "two", true).Times(5)
// ensure the call was created
if assert.Equal(t, 1, len(mockedService.ExpectedCalls)) {
call := mockedService.ExpectedCalls[0]
assert.Equal(t, "TheExampleMethod", call.Method)
assert.Equal(t, "A", call.Arguments[0])
assert.Equal(t, "B", call.Arguments[1])
assert.Equal(t, true, call.Arguments[2])
assert.Equal(t, 1, call.ReturnArguments[0])
assert.Equal(t, "two", call.ReturnArguments[1])
assert.Equal(t, true, call.ReturnArguments[2])
assert.Equal(t, 5, call.Repeatability)
assert.Nil(t, call.WaitFor)
}
}
func Test_Mock_Return_Nothing(t *testing.T) {
// make a test impl object
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
assert.Equal(t, mockedService.On("TheExampleMethod", "A", "B", true).Return(), &mockedService.Mock)
// ensure the call was created
if assert.Equal(t, 1, len(mockedService.ExpectedCalls)) {
call := mockedService.ExpectedCalls[0]
assert.Equal(t, "TheExampleMethod", call.Method)
assert.Equal(t, "A", call.Arguments[0])
assert.Equal(t, "B", call.Arguments[1])
assert.Equal(t, true, call.Arguments[2])
assert.Equal(t, 0, len(call.ReturnArguments))
}
}
func Test_Mock_findExpectedCall(t *testing.T) {
m := new(Mock)
m.On("One", 1).Return("one")
m.On("Two", 2).Return("two")
m.On("Two", 3).Return("three")
f, c := m.findExpectedCall("Two", 3)
if assert.Equal(t, 2, f) {
if assert.NotNil(t, c) {
assert.Equal(t, "Two", c.Method)
assert.Equal(t, 3, c.Arguments[0])
assert.Equal(t, "three", c.ReturnArguments[0])
}
}
}
func Test_Mock_findExpectedCall_For_Unknown_Method(t *testing.T) {
m := new(Mock)
m.On("One", 1).Return("one")
m.On("Two", 2).Return("two")
m.On("Two", 3).Return("three")
f, _ := m.findExpectedCall("Two")
assert.Equal(t, -1, f)
}
func Test_Mock_findExpectedCall_Respects_Repeatability(t *testing.T) {
m := new(Mock)
m.On("One", 1).Return("one")
m.On("Two", 2).Return("two").Once()
m.On("Two", 3).Return("three").Twice()
m.On("Two", 3).Return("three").Times(8)
f, c := m.findExpectedCall("Two", 3)
if assert.Equal(t, 2, f) {
if assert.NotNil(t, c) {
assert.Equal(t, "Two", c.Method)
assert.Equal(t, 3, c.Arguments[0])
assert.Equal(t, "three", c.ReturnArguments[0])
}
}
}
func Test_callString(t *testing.T) {
assert.Equal(t, `Method(int,bool,string)`, callString("Method", []interface{}{1, true, "something"}, false))
}
func Test_Mock_Called(t *testing.T) {
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
mockedService.On("Test_Mock_Called", 1, 2, 3).Return(5, "6", true)
returnArguments := mockedService.Called(1, 2, 3)
if assert.Equal(t, 1, len(mockedService.Calls)) {
assert.Equal(t, "Test_Mock_Called", mockedService.Calls[0].Method)
assert.Equal(t, 1, mockedService.Calls[0].Arguments[0])
assert.Equal(t, 2, mockedService.Calls[0].Arguments[1])
assert.Equal(t, 3, mockedService.Calls[0].Arguments[2])
}
if assert.Equal(t, 3, len(returnArguments)) {
assert.Equal(t, 5, returnArguments[0])
assert.Equal(t, "6", returnArguments[1])
assert.Equal(t, true, returnArguments[2])
}
}
func asyncCall(m *Mock, ch chan Arguments) {
ch <- m.Called(1, 2, 3)
}
func Test_Mock_Called_blocks(t *testing.T) {
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
mockedService.Mock.On("asyncCall", 1, 2, 3).Return(5, "6", true).After(2 * time.Millisecond)
ch := make(chan Arguments)
go asyncCall(&mockedService.Mock, ch)
select {
case <-ch:
t.Fatal("should have waited")
case <-time.After(1 * time.Millisecond):
}
returnArguments := <-ch
if assert.Equal(t, 1, len(mockedService.Mock.Calls)) {
assert.Equal(t, "asyncCall", mockedService.Mock.Calls[0].Method)
assert.Equal(t, 1, mockedService.Mock.Calls[0].Arguments[0])
assert.Equal(t, 2, mockedService.Mock.Calls[0].Arguments[1])
assert.Equal(t, 3, mockedService.Mock.Calls[0].Arguments[2])
}
if assert.Equal(t, 3, len(returnArguments)) {
assert.Equal(t, 5, returnArguments[0])
assert.Equal(t, "6", returnArguments[1])
assert.Equal(t, true, returnArguments[2])
}
}
func Test_Mock_Called_For_Bounded_Repeatability(t *testing.T) {
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
mockedService.On("Test_Mock_Called_For_Bounded_Repeatability", 1, 2, 3).Return(5, "6", true).Once()
mockedService.On("Test_Mock_Called_For_Bounded_Repeatability", 1, 2, 3).Return(-1, "hi", false)
returnArguments1 := mockedService.Called(1, 2, 3)
returnArguments2 := mockedService.Called(1, 2, 3)
if assert.Equal(t, 2, len(mockedService.Calls)) {
assert.Equal(t, "Test_Mock_Called_For_Bounded_Repeatability", mockedService.Calls[0].Method)
assert.Equal(t, 1, mockedService.Calls[0].Arguments[0])
assert.Equal(t, 2, mockedService.Calls[0].Arguments[1])
assert.Equal(t, 3, mockedService.Calls[0].Arguments[2])
assert.Equal(t, "Test_Mock_Called_For_Bounded_Repeatability", mockedService.Calls[1].Method)
assert.Equal(t, 1, mockedService.Calls[1].Arguments[0])
assert.Equal(t, 2, mockedService.Calls[1].Arguments[1])
assert.Equal(t, 3, mockedService.Calls[1].Arguments[2])
}
if assert.Equal(t, 3, len(returnArguments1)) {
assert.Equal(t, 5, returnArguments1[0])
assert.Equal(t, "6", returnArguments1[1])
assert.Equal(t, true, returnArguments1[2])
}
if assert.Equal(t, 3, len(returnArguments2)) {
assert.Equal(t, -1, returnArguments2[0])
assert.Equal(t, "hi", returnArguments2[1])
assert.Equal(t, false, returnArguments2[2])
}
}
func Test_Mock_Called_For_SetTime_Expectation(t *testing.T) {
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
mockedService.On("TheExampleMethod", 1, 2, 3).Return(5, "6", true).Times(4)
mockedService.TheExampleMethod(1, 2, 3)
mockedService.TheExampleMethod(1, 2, 3)
mockedService.TheExampleMethod(1, 2, 3)
mockedService.TheExampleMethod(1, 2, 3)
assert.Panics(t, func() {
mockedService.TheExampleMethod(1, 2, 3)
})
}
func Test_Mock_Called_Unexpected(t *testing.T) {
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
// make sure it panics if no expectation was made
assert.Panics(t, func() {
mockedService.Called(1, 2, 3)
}, "Calling unexpected method should panic")
}
func Test_AssertExpectationsForObjects_Helper(t *testing.T) {
var mockedService1 *TestExampleImplementation = new(TestExampleImplementation)
var mockedService2 *TestExampleImplementation = new(TestExampleImplementation)
var mockedService3 *TestExampleImplementation = new(TestExampleImplementation)
mockedService1.On("Test_AssertExpectationsForObjects_Helper", 1).Return()
mockedService2.On("Test_AssertExpectationsForObjects_Helper", 2).Return()
mockedService3.On("Test_AssertExpectationsForObjects_Helper", 3).Return()
mockedService1.Called(1)
mockedService2.Called(2)
mockedService3.Called(3)
assert.True(t, AssertExpectationsForObjects(t, mockedService1.Mock, mockedService2.Mock, mockedService3.Mock))
}
func Test_AssertExpectationsForObjects_Helper_Failed(t *testing.T) {
var mockedService1 *TestExampleImplementation = new(TestExampleImplementation)
var mockedService2 *TestExampleImplementation = new(TestExampleImplementation)
var mockedService3 *TestExampleImplementation = new(TestExampleImplementation)
mockedService1.On("Test_AssertExpectationsForObjects_Helper_Failed", 1).Return()
mockedService2.On("Test_AssertExpectationsForObjects_Helper_Failed", 2).Return()
mockedService3.On("Test_AssertExpectationsForObjects_Helper_Failed", 3).Return()
mockedService1.Called(1)
mockedService3.Called(3)
tt := new(testing.T)
assert.False(t, AssertExpectationsForObjects(tt, mockedService1.Mock, mockedService2.Mock, mockedService3.Mock))
}
func Test_Mock_AssertExpectations(t *testing.T) {
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
mockedService.On("Test_Mock_AssertExpectations", 1, 2, 3).Return(5, 6, 7)
tt := new(testing.T)
assert.False(t, mockedService.AssertExpectations(tt))
// make the call now
mockedService.Called(1, 2, 3)
// now assert expectations
assert.True(t, mockedService.AssertExpectations(tt))
}
func Test_Mock_AssertExpectationsCustomType(t *testing.T) {
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
mockedService.On("TheExampleMethod3", AnythingOfType("*mock.ExampleType")).Return(nil).Once()
tt := new(testing.T)
assert.False(t, mockedService.AssertExpectations(tt))
// make the call now
mockedService.TheExampleMethod3(&ExampleType{})
// now assert expectations
assert.True(t, mockedService.AssertExpectations(tt))
}
func Test_Mock_AssertExpectations_With_Repeatability(t *testing.T) {
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
mockedService.On("Test_Mock_AssertExpectations_With_Repeatability", 1, 2, 3).Return(5, 6, 7).Twice()
tt := new(testing.T)
assert.False(t, mockedService.AssertExpectations(tt))
// make the call now
mockedService.Called(1, 2, 3)
assert.False(t, mockedService.AssertExpectations(tt))
mockedService.Called(1, 2, 3)
// now assert expectations
assert.True(t, mockedService.AssertExpectations(tt))
}
func Test_Mock_TwoCallsWithDifferentArguments(t *testing.T) {
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
mockedService.On("Test_Mock_TwoCallsWithDifferentArguments", 1, 2, 3).Return(5, 6, 7)
mockedService.On("Test_Mock_TwoCallsWithDifferentArguments", 4, 5, 6).Return(5, 6, 7)
args1 := mockedService.Called(1, 2, 3)
assert.Equal(t, 5, args1.Int(0))
assert.Equal(t, 6, args1.Int(1))
assert.Equal(t, 7, args1.Int(2))
args2 := mockedService.Called(4, 5, 6)
assert.Equal(t, 5, args2.Int(0))
assert.Equal(t, 6, args2.Int(1))
assert.Equal(t, 7, args2.Int(2))
}
func Test_Mock_AssertNumberOfCalls(t *testing.T) {
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
mockedService.On("Test_Mock_AssertNumberOfCalls", 1, 2, 3).Return(5, 6, 7)
mockedService.Called(1, 2, 3)
assert.True(t, mockedService.AssertNumberOfCalls(t, "Test_Mock_AssertNumberOfCalls", 1))
mockedService.Called(1, 2, 3)
assert.True(t, mockedService.AssertNumberOfCalls(t, "Test_Mock_AssertNumberOfCalls", 2))
}
func Test_Mock_AssertCalled(t *testing.T) {
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
mockedService.On("Test_Mock_AssertCalled", 1, 2, 3).Return(5, 6, 7)
mockedService.Called(1, 2, 3)
assert.True(t, mockedService.AssertCalled(t, "Test_Mock_AssertCalled", 1, 2, 3))
}
func Test_Mock_AssertCalled_WithAnythingOfTypeArgument(t *testing.T) {
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
mockedService.On("Test_Mock_AssertCalled_WithAnythingOfTypeArgument", Anything, Anything, Anything).Return()
mockedService.Called(1, "two", []uint8("three"))
assert.True(t, mockedService.AssertCalled(t, "Test_Mock_AssertCalled_WithAnythingOfTypeArgument", AnythingOfType("int"), AnythingOfType("string"), AnythingOfType("[]uint8")))
}
func Test_Mock_AssertCalled_WithArguments(t *testing.T) {
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
mockedService.On("Test_Mock_AssertCalled_WithArguments", 1, 2, 3).Return(5, 6, 7)
mockedService.Called(1, 2, 3)
tt := new(testing.T)
assert.True(t, mockedService.AssertCalled(tt, "Test_Mock_AssertCalled_WithArguments", 1, 2, 3))
assert.False(t, mockedService.AssertCalled(tt, "Test_Mock_AssertCalled_WithArguments", 2, 3, 4))
}
func Test_Mock_AssertCalled_WithArguments_With_Repeatability(t *testing.T) {
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
mockedService.On("Test_Mock_AssertCalled_WithArguments_With_Repeatability", 1, 2, 3).Return(5, 6, 7).Once()
mockedService.On("Test_Mock_AssertCalled_WithArguments_With_Repeatability", 2, 3, 4).Return(5, 6, 7).Once()
mockedService.Called(1, 2, 3)
mockedService.Called(2, 3, 4)
tt := new(testing.T)
assert.True(t, mockedService.AssertCalled(tt, "Test_Mock_AssertCalled_WithArguments_With_Repeatability", 1, 2, 3))
assert.True(t, mockedService.AssertCalled(tt, "Test_Mock_AssertCalled_WithArguments_With_Repeatability", 2, 3, 4))
assert.False(t, mockedService.AssertCalled(tt, "Test_Mock_AssertCalled_WithArguments_With_Repeatability", 3, 4, 5))
}
func Test_Mock_AssertNotCalled(t *testing.T) {
var mockedService *TestExampleImplementation = new(TestExampleImplementation)
mockedService.On("Test_Mock_AssertNotCalled", 1, 2, 3).Return(5, 6, 7)
mockedService.Called(1, 2, 3)
assert.True(t, mockedService.AssertNotCalled(t, "Test_Mock_NotCalled"))
}
/*
Arguments helper methods
*/
func Test_Arguments_Get(t *testing.T) {
var args Arguments = []interface{}{"string", 123, true}
assert.Equal(t, "string", args.Get(0).(string))
assert.Equal(t, 123, args.Get(1).(int))
assert.Equal(t, true, args.Get(2).(bool))
}
func Test_Arguments_Is(t *testing.T) {
var args Arguments = []interface{}{"string", 123, true}
assert.True(t, args.Is("string", 123, true))
assert.False(t, args.Is("wrong", 456, false))
}
func Test_Arguments_Diff(t *testing.T) {
var args Arguments = []interface{}{"Hello World", 123, true}
var diff string
var count int
diff, count = args.Diff([]interface{}{"Hello World", 456, "false"})
assert.Equal(t, 2, count)
assert.Contains(t, diff, `%!s(int=456) != %!s(int=123)`)
assert.Contains(t, diff, `false != %!s(bool=true)`)
}
func Test_Arguments_Diff_DifferentNumberOfArgs(t *testing.T) {
var args Arguments = []interface{}{"string", 123, true}
var diff string
var count int
diff, count = args.Diff([]interface{}{"string", 456, "false", "extra"})
assert.Equal(t, 3, count)
assert.Contains(t, diff, `extra != (Missing)`)
}
func Test_Arguments_Diff_WithAnythingArgument(t *testing.T) {
var args Arguments = []interface{}{"string", 123, true}
var count int
_, count = args.Diff([]interface{}{"string", Anything, true})
assert.Equal(t, 0, count)
}
func Test_Arguments_Diff_WithAnythingArgument_InActualToo(t *testing.T) {
var args Arguments = []interface{}{"string", Anything, true}
var count int
_, count = args.Diff([]interface{}{"string", 123, true})
assert.Equal(t, 0, count)
}
func Test_Arguments_Diff_WithAnythingOfTypeArgument(t *testing.T) {
var args Arguments = []interface{}{"string", AnythingOfType("int"), true}
var count int
_, count = args.Diff([]interface{}{"string", 123, true})
assert.Equal(t, 0, count)
}
func Test_Arguments_Diff_WithAnythingOfTypeArgument_Failing(t *testing.T) {
var args Arguments = []interface{}{"string", AnythingOfType("string"), true}
var count int
var diff string
diff, count = args.Diff([]interface{}{"string", 123, true})
assert.Equal(t, 1, count)
assert.Contains(t, diff, `string != type int - %!s(int=123)`)
}
func Test_Arguments_Assert(t *testing.T) {
var args Arguments = []interface{}{"string", 123, true}
assert.True(t, args.Assert(t, "string", 123, true))
}
func Test_Arguments_String_Representation(t *testing.T) {
var args Arguments = []interface{}{"string", 123, true}
assert.Equal(t, `string,int,bool`, args.String())
}
func Test_Arguments_String(t *testing.T) {
var args Arguments = []interface{}{"string", 123, true}
assert.Equal(t, "string", args.String(0))
}
func Test_Arguments_Error(t *testing.T) {
var err error = errors.New("An Error")
var args Arguments = []interface{}{"string", 123, true, err}
assert.Equal(t, err, args.Error(3))
}
func Test_Arguments_Error_Nil(t *testing.T) {
var args Arguments = []interface{}{"string", 123, true, nil}
assert.Equal(t, nil, args.Error(3))
}
func Test_Arguments_Int(t *testing.T) {
var args Arguments = []interface{}{"string", 123, true}
assert.Equal(t, 123, args.Int(1))
}
func Test_Arguments_Bool(t *testing.T) {
var args Arguments = []interface{}{"string", 123, true}
assert.Equal(t, true, args.Bool(2))
}

View File

@ -31,7 +31,7 @@ build: ${build_image}.created
@echo "Done building code"
build-local:
@$(shell which godep) go build -tags libnetwork_discovery ./...
@$(shell which godep) go build ./...
@$(shell which godep) go build -o ./cmd/dnet/dnet ./cmd/dnet
check: ${build_image}.created

View File

@ -47,8 +47,8 @@ func setupMockHTTPCallback() {
srvList = append(srvList, ep)
mockServiceListJSON, _ = json.Marshal(srvList)
var sbxList []sandboxResource
sb := sandboxResource{ID: mockSandboxID, ContainerID: mockContainerID}
var sbxList []SandboxResource
sb := SandboxResource{ID: mockSandboxID, ContainerID: mockContainerID}
mockSbJSON, _ = json.Marshal(sb)
sbxList = append(sbxList, sb)
mockSbListJSON, _ = json.Marshal(sbxList)

View File

@ -120,7 +120,7 @@ func lookupSandboxID(cli *NetworkCli, containerID string) (string, error) {
return "", err
}
var sandboxList []sandboxResource
var sandboxList []SandboxResource
err = json.Unmarshal(obj, &sandboxList)
if err != nil {
return "", err
@ -268,7 +268,7 @@ func getBackendID(cli *NetworkCli, servID string) (string, error) {
)
if obj, _, err = readBody(cli.call("GET", "/services/"+servID+"/backend", nil, nil)); err == nil {
var sr sandboxResource
var sr SandboxResource
if err := json.NewDecoder(bytes.NewReader(obj)).Decode(&sr); err == nil {
bk = sr.ContainerID
} else {

View File

@ -21,8 +21,8 @@ type serviceResource struct {
Network string `json:"network"`
}
// sandboxResource is the body of "get service backend" response message
type sandboxResource struct {
// SandboxResource is the body of "get service backend" response message
type SandboxResource struct {
ID string `json:"id"`
Key string `json:"key"`
ContainerID string `json:"container_id"`
@ -52,7 +52,8 @@ type serviceAttach struct {
SandboxID string `json:"sandbox_id"`
}
type sandboxCreate struct {
// SandboxCreate is the body of the "post /sandboxes" http request message
type SandboxCreate struct {
ContainerID string `json:"container_id"`
HostName string `json:"host_name"`
DomainName string `json:"domain_name"`

146
libnetwork/cmd/dnet/cmd.go Normal file
View File

@ -0,0 +1,146 @@
package main
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"github.com/codegangsta/cli"
"github.com/docker/docker/pkg/term"
"github.com/docker/libnetwork/client"
)
var (
containerCreateCommand = cli.Command{
Name: "create",
Usage: "Create a container",
Action: runContainerCreate,
}
containerRmCommand = cli.Command{
Name: "rm",
Usage: "Remove a container",
Action: runContainerRm,
}
containerCommands = []cli.Command{
containerCreateCommand,
containerRmCommand,
}
dnetCommands = []cli.Command{
createDockerCommand("network"),
createDockerCommand("service"),
{
Name: "container",
Usage: "Container management commands",
Subcommands: containerCommands,
},
}
)
func runContainerCreate(c *cli.Context) {
if len(c.Args()) == 0 {
fmt.Printf("Please provide container id argument\n")
os.Exit(1)
}
sc := client.SandboxCreate{ContainerID: c.Args()[0]}
obj, _, err := readBody(epConn.httpCall("POST", "/sandboxes", sc, nil))
if err != nil {
fmt.Printf("POST failed during create container: %v\n", err)
os.Exit(1)
}
var replyID string
err = json.Unmarshal(obj, &replyID)
if err != nil {
fmt.Printf("Unmarshall of response failed during create container: %v\n", err)
os.Exit(1)
}
fmt.Printf("%s\n", replyID)
}
func runContainerRm(c *cli.Context) {
var sbList []*client.SandboxResource
if len(c.Args()) == 0 {
fmt.Printf("Please provide container id argument\n")
os.Exit(1)
}
obj, _, err := readBody(epConn.httpCall("GET", "/sandboxes?partial-container-id="+c.Args()[0], nil, nil))
if err != nil {
fmt.Printf("GET failed during container id lookup: %v\n", err)
os.Exit(1)
}
err = json.Unmarshal(obj, &sbList)
if err != nil {
fmt.Printf("Unmarshall of container id lookup response failed: %v", err)
os.Exit(1)
}
if len(sbList) == 0 {
fmt.Printf("No sandbox for container %s found\n", c.Args()[0])
os.Exit(1)
}
_, _, err = readBody(epConn.httpCall("DELETE", "/sandboxes/"+sbList[0].ID, nil, nil))
if err != nil {
fmt.Printf("DELETE of sandbox id %s failed: %v", sbList[0].ID, err)
os.Exit(1)
}
}
func runDockerCommand(c *cli.Context, cmd string) {
_, stdout, stderr := term.StdStreams()
oldcli := client.NewNetworkCli(stdout, stderr, epConn.httpCall)
var args []string
args = append(args, cmd)
if c.Bool("h") {
args = append(args, "--help")
} else {
args = append(args, c.Args()...)
}
if err := oldcli.Cmd("dnet", args...); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func createDockerCommand(cmd string) cli.Command {
return cli.Command{
Name: cmd,
Usage: fmt.Sprintf("%s management commands", cmd),
SkipFlagParsing: true,
Action: func(c *cli.Context) {
runDockerCommand(c, cmd)
},
Subcommands: []cli.Command{
{
Name: "h, -help",
Usage: fmt.Sprintf("%s help", cmd),
},
},
}
}
func readBody(stream io.ReadCloser, hdr http.Header, statusCode int, err error) ([]byte, int, error) {
if stream != nil {
defer stream.Close()
}
if err != nil {
return nil, statusCode, err
}
body, err := ioutil.ReadAll(stream)
if err != nil {
return nil, -1, err
}
return body, statusCode, nil
}

View File

@ -10,7 +10,7 @@ import (
"os"
"strings"
flag "github.com/docker/docker/pkg/mflag"
"github.com/codegangsta/cli"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/reexec"
@ -18,7 +18,6 @@ import (
"github.com/docker/docker/pkg/term"
"github.com/docker/libnetwork"
"github.com/docker/libnetwork/api"
"github.com/docker/libnetwork/client"
"github.com/docker/libnetwork/config"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/options"
@ -36,6 +35,8 @@ const (
defaultCfgFile = "/etc/default/libnetwork.toml"
)
var epConn *dnetConnection
func main() {
if reexec.Init() {
return
@ -44,7 +45,7 @@ func main() {
_, stdout, stderr := term.StdStreams()
logrus.SetOutput(stderr)
err := dnetCommand(stdout, stderr)
err := dnetApp(stdout, stderr)
if err != nil {
os.Exit(1)
}
@ -89,61 +90,16 @@ func processConfig(cfg *config.Config) []config.Option {
return options
}
func dnetCommand(stdout, stderr io.Writer) error {
flag.Parse()
func dnetApp(stdout, stderr io.Writer) error {
app := cli.NewApp()
if *flHelp {
flag.Usage()
return nil
}
app.Name = "dnet"
app.Usage = "A self-sufficient runtime for container networking."
app.Flags = dnetFlags
app.Before = processFlags
app.Commands = dnetCommands
if *flLogLevel != "" {
lvl, err := logrus.ParseLevel(*flLogLevel)
if err != nil {
fmt.Fprintf(stderr, "Unable to parse logging level: %s\n", *flLogLevel)
return err
}
logrus.SetLevel(lvl)
} else {
logrus.SetLevel(logrus.InfoLevel)
}
if *flDebug {
logrus.SetLevel(logrus.DebugLevel)
}
if *flHost == "" {
defaultHost := os.Getenv("DNET_HOST")
if defaultHost == "" {
// TODO : Add UDS support
defaultHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort)
}
*flHost = defaultHost
}
dc, err := newDnetConnection(*flHost)
if err != nil {
if *flDaemon {
logrus.Error(err)
} else {
fmt.Fprint(stderr, err)
}
return err
}
if *flDaemon {
err := dc.dnetDaemon()
if err != nil {
logrus.Errorf("dnet Daemon exited with an error : %v", err)
}
return err
}
cli := client.NewNetworkCli(stdout, stderr, dc.httpCall)
if err := cli.Cmd("dnet", flag.Args()...); err != nil {
fmt.Fprintln(stderr, err)
return err
}
app.Run(os.Args)
return nil
}
@ -177,8 +133,8 @@ type dnetConnection struct {
addr string
}
func (d *dnetConnection) dnetDaemon() error {
cfg, err := parseConfig(*flCfgFile)
func (d *dnetConnection) dnetDaemon(cfgFile string) error {
cfg, err := parseConfig(cfgFile)
var cOptions []config.Option
if err == nil {
cOptions = processConfig(cfg)

View File

@ -1,132 +0,0 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"testing"
"time"
"github.com/docker/libnetwork/testutils"
)
const dnetCommandName = "dnet"
var origStdOut = os.Stdout
func TestDnetDaemonCustom(t *testing.T) {
if !testutils.IsRunningInContainer() {
t.Skip("This test must run inside a container ")
}
customPort := 4567
doneChan := make(chan bool)
go func() {
args := []string{dnetCommandName, "-d", fmt.Sprintf("-H=:%d", customPort)}
executeDnetCommand(t, args, true)
doneChan <- true
}()
select {
case <-doneChan:
t.Fatal("dnet Daemon is not supposed to exit")
case <-time.After(3 * time.Second):
args := []string{dnetCommandName, "-d=false", fmt.Sprintf("-H=:%d", customPort), "-D", "network", "ls"}
executeDnetCommand(t, args, true)
}
}
func TestDnetDaemonInvalidCustom(t *testing.T) {
if !testutils.IsRunningInContainer() {
t.Skip("This test must run inside a container ")
}
customPort := 4668
doneChan := make(chan bool)
go func() {
args := []string{dnetCommandName, "-d=true", fmt.Sprintf("-H=:%d", customPort)}
executeDnetCommand(t, args, true)
doneChan <- true
}()
select {
case <-doneChan:
t.Fatal("dnet Daemon is not supposed to exit")
case <-time.After(3 * time.Second):
args := []string{dnetCommandName, "-d=false", "-H=:6669", "-D", "network", "ls"}
executeDnetCommand(t, args, false)
}
}
func TestDnetDaemonInvalidParams(t *testing.T) {
if !testutils.IsRunningInContainer() {
t.Skip("This test must run inside a container ")
}
args := []string{dnetCommandName, "-d=false", "-H=tcp:/127.0.0.1:8080"}
executeDnetCommand(t, args, false)
args = []string{dnetCommandName, "-d=false", "-H=unix://var/run/dnet.sock"}
executeDnetCommand(t, args, false)
args = []string{dnetCommandName, "-d=false", "-H=", "-l=invalid"}
executeDnetCommand(t, args, false)
args = []string{dnetCommandName, "-d=false", "-H=", "-l=error", "invalid"}
executeDnetCommand(t, args, false)
}
func TestDnetDefaultsWithFlags(t *testing.T) {
if !testutils.IsRunningInContainer() {
t.Skip("This test must run inside a container ")
}
doneChan := make(chan bool)
go func() {
args := []string{dnetCommandName, "-d=true", "-H=", "-l=error"}
executeDnetCommand(t, args, true)
doneChan <- true
}()
select {
case <-doneChan:
t.Fatal("dnet Daemon is not supposed to exit")
case <-time.After(3 * time.Second):
args := []string{dnetCommandName, "-d=false", "network", "create", "-d=null", "test"}
executeDnetCommand(t, args, true)
args = []string{dnetCommandName, "-d=false", "-D", "network", "ls"}
executeDnetCommand(t, args, true)
}
}
func TestDnetMain(t *testing.T) {
if !testutils.IsRunningInContainer() {
t.Skip("This test must run inside a container ")
}
customPort := 4568
doneChan := make(chan bool)
go func() {
args := []string{dnetCommandName, "-d=true", "-h=false", fmt.Sprintf("-H=:%d", customPort)}
os.Args = args
main()
doneChan <- true
}()
select {
case <-doneChan:
t.Fatal("dnet Daemon is not supposed to exit")
case <-time.After(2 * time.Second):
}
}
func executeDnetCommand(t *testing.T, args []string, shouldSucced bool) {
_, w, _ := os.Pipe()
os.Stdout = w
os.Args = args
err := dnetCommand(ioutil.Discard, ioutil.Discard)
if shouldSucced && err != nil {
os.Stdout = origStdOut
t.Fatalf("cli [%v] must succeed, but failed with an error : %v", args, err)
} else if !shouldSucced && err == nil {
os.Stdout = origStdOut
t.Fatalf("cli [%v] must fail, but succeeded with an error : %v", args, err)
}
os.Stdout = origStdOut
}

View File

@ -4,48 +4,84 @@ import (
"fmt"
"os"
flag "github.com/docker/docker/pkg/mflag"
"github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
)
type command struct {
name string
description string
}
type byName []command
var (
flDaemon = flag.Bool([]string{"d", "-daemon"}, false, "Enable daemon mode")
flHost = flag.String([]string{"H", "-host"}, "", "Daemon socket to connect to")
flLogLevel = flag.String([]string{"l", "-log-level"}, "info", "Set the logging level")
flDebug = flag.Bool([]string{"D", "-debug"}, false, "Enable debug mode")
flCfgFile = flag.String([]string{"c", "-cfg-file"}, "/etc/default/libnetwork.toml", "Configuration file")
flHelp = flag.Bool([]string{"h", "-help"}, false, "Print usage")
dnetCommands = []command{
{"network", "Network management commands"},
{"service", "Service management commands"},
dnetFlags = []cli.Flag{
cli.BoolFlag{
Name: "d, -daemon",
Usage: "Enable daemon mode",
},
cli.StringFlag{
Name: "H, -host",
Value: "",
Usage: "Daemon socket to connect to",
},
cli.StringFlag{
Name: "l, -log-level",
Value: "info",
Usage: "Set the logging level",
},
cli.BoolFlag{
Name: "D, -debug",
Usage: "Enable debug mode",
},
cli.StringFlag{
Name: "c, -cfg-file",
Value: "/etc/default/libnetwork.toml",
Usage: "Configuration file",
},
}
)
func init() {
flag.Usage = func() {
fmt.Fprint(os.Stdout, "Usage: dnet [OPTIONS] COMMAND [arg...]\n\nA self-sufficient runtime for container networking.\n\nOptions:\n")
func processFlags(c *cli.Context) error {
var err error
flag.CommandLine.SetOutput(os.Stdout)
flag.PrintDefaults()
help := "\nCommands:\n"
for _, cmd := range dnetCommands {
help += fmt.Sprintf(" %-10.10s%s\n", cmd.name, cmd.description)
if c.String("l") != "" {
lvl, err := logrus.ParseLevel(c.String("l"))
if err != nil {
fmt.Printf("Unable to parse logging level: %s\n", c.String("l"))
os.Exit(1)
}
help += "\nRun 'dnet COMMAND --help' for more information on a command."
fmt.Fprintf(os.Stdout, "%s\n", help)
logrus.SetLevel(lvl)
} else {
logrus.SetLevel(logrus.InfoLevel)
}
}
func printUsage() {
fmt.Println("Usage: dnet <OPTIONS> COMMAND [arg...]")
if c.Bool("D") {
logrus.SetLevel(logrus.DebugLevel)
}
hostFlag := c.String("H")
if hostFlag == "" {
defaultHost := os.Getenv("DNET_HOST")
if defaultHost == "" {
// TODO : Add UDS support
defaultHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort)
}
hostFlag = defaultHost
}
epConn, err = newDnetConnection(hostFlag)
if err != nil {
if c.Bool("d") {
logrus.Error(err)
} else {
fmt.Println(err)
}
os.Exit(1)
}
if c.Bool("d") {
err = epConn.dnetDaemon(c.String("c"))
if err != nil {
logrus.Errorf("dnet Daemon exited with an error : %v", err)
os.Exit(1)
}
os.Exit(1)
}
return nil
}

View File

@ -0,0 +1,31 @@
#!/usr/bin/env bats
load helpers
@test "Test dnet custom port" {
start_dnet 1 a none null 4567
dnet_cmd 4567 network ls
stop_dnet 1 a
}
@test "Test dnet invalid custom port" {
start_dnet 1 b none null 4567
run dnet_cmd 4568 network ls
echo ${output}
[ "$status" -ne 0 ]
stop_dnet 1 b
}
@test "Test dnet invalid params" {
start_dnet 1 c none null
run dnet_cmd 8080 network ls
echo ${output}
[ "$status" -ne 0 ]
run ./cmd/dnet/dnet -H=unix://var/run/dnet.sock network ls
echo ${output}
[ "$status" -ne 0 ]
run ./cmd/dnet/dnet -H= -l=invalid network ls
echo ${output}
[ "$status" -ne 0 ]
stop_dnet 1 c
}

View File

@ -1,10 +1,21 @@
function inst_id2port() {
echo $((41000+${1}-1))
}
function start_consul() {
stop_consul
docker run -d --name=pr_consul -p 8500:8500 -p 8300-8302:8300-8302/tcp -p 8300-8302:8300-8302/udp -h consul progrium/consul -server -bootstrap
docker run -d \
--name=pr_consul \
-p 8500:8500 \
-p 8300-8302:8300-8302/tcp \
-p 8300-8302:8300-8302/udp \
-h consul \
progrium/consul -server -bootstrap
sleep 2
}
function stop_consul() {
echo "consul started"
docker stop pr_consul || true
# You cannot destroy a container in Circle CI. So do not attempt destroy in circleci
if [ -z "$CIRCLECI" ]; then
@ -13,11 +24,19 @@ function stop_consul() {
}
function start_dnet() {
stop_dnet $1
name="dnet-$1"
hport=$((41000+${1}-1))
stop_dnet $1 $2
name="dnet-$1-$2"
if [ -z "$5" ]
then
hport=$((41000+${1}-1))
cport=2385
hopt=""
else
hport=$5
cport=$5
hopt="-H tcp://0.0.0.0:${cport}"
fi
bridge_ip=$(docker inspect --format '{{.NetworkSettings.Gateway}}' pr_consul)
mkdir -p /tmp/dnet/${name}
tomlfile="/tmp/dnet/${name}/libnetwork.toml"
cat > ${tomlfile} <<EOF
@ -25,21 +44,42 @@ title = "LibNetwork Configuration file"
[daemon]
debug = false
defaultnetwork = "${2}"
defaultdriver = "${3}"
defaultnetwork = "${3}"
defaultdriver = "${4}"
labels = ["com.docker.network.driver.overlay.bind_interface=eth0"]
[datastore]
embedded = false
EOF
if [ "${4}" == "overlay" ]
then
bridge_ip=$(docker inspect --format '{{.NetworkSettings.Gateway}}' pr_consul)
cat >> ${tomlfile} <<EOF
[datastore.client]
provider = "consul"
Address = "${bridge_ip}:8500"
EOF
docker run -d --name=${name} --privileged -p ${hport}:2385 -v $(pwd)/:/go/src/github.com/docker/libnetwork -v /tmp:/tmp -w /go/src/github.com/docker/libnetwork golang:1.4 ./cmd/dnet/dnet -dD -c ${tomlfile}
fi
docker run \
-d \
--name=${name} \
--privileged \
-p ${hport}:${cport} \
-v $(pwd)/:/go/src/github.com/docker/libnetwork \
-v /tmp:/tmp \
-w /go/src/github.com/docker/libnetwork \
golang:1.4 ./cmd/dnet/dnet -d -D ${hopt} -c ${tomlfile}
sleep 2
}
function skip_for_circleci() {
if [ -n "$CIRCLECI" ]; then
skip
fi
}
function stop_dnet() {
name="dnet-$1"
name="dnet-$1-$2"
rm -rf /tmp/dnet/${name} || true
docker stop ${name} || true
# You cannot destroy a container in Circle CI. So do not attempt destroy in circleci
@ -50,7 +90,7 @@ function stop_dnet() {
}
function dnet_cmd() {
hport=$((41000+${1}-1))
hport=$1
shift
./cmd/dnet/dnet -H 127.0.0.1:${hport} $*
./cmd/dnet/dnet -H tcp://127.0.0.1:${hport} $*
}

View File

@ -2,28 +2,24 @@
load helpers
export BATS_TEST_CNT=0
function setup() {
if [ "${BATS_TEST_CNT}" -eq 0 ]; then
if [ "${BATS_TEST_NUMBER}" -eq 1 ]; then
start_consul
start_dnet 1 multihost overlay
export BATS_TEST_CNT=$((${BATS_TEST_CNT}+1))
start_dnet 1 simple multihost overlay
fi
}
function teardown() {
export BATS_TEST_CNT=$((${BATS_TEST_CNT}-1))
if [ "${BATS_TEST_CNT}" -eq 0 ]; then
stop_dnet 1
if [ "${BATS_TEST_NUMBER}" -eq 6 ]; then
stop_dnet 1 simple
stop_consul
fi
}
@test "Test default network" {
echo $(docker ps)
run dnet_cmd 1 network ls
run dnet_cmd $(inst_id2port 1) network ls
[ "$status" -eq 0 ]
echo ${output}
echo ${lines[1]}
name=$(echo ${lines[1]} | cut -d" " -f2)
@ -32,3 +28,72 @@ function teardown() {
[ "$name" = "multihost" ]
[ "$driver" = "overlay" ]
}
@test "Test network create" {
echo $(docker ps)
run dnet_cmd $(inst_id2port 1) network create -d overlay mh1
[ "$status" -eq 0 ]
line=$(dnet_cmd $(inst_id2port 1) network ls | grep mh1)
echo ${line}
name=$(echo ${line} | cut -d" " -f2)
driver=$(echo ${line} | cut -d" " -f3)
echo ${name} ${driver}
[ "$name" = "mh1" ]
[ "$driver" = "overlay" ]
dnet_cmd $(inst_id2port 1) network rm mh1
}
@test "Test network delete with id" {
echo $(docker ps)
run dnet_cmd $(inst_id2port 1) network create -d overlay mh1
[ "$status" -eq 0 ]
echo ${output}
dnet_cmd $(inst_id2port 1) network rm ${output}
}
@test "Test service create" {
echo $(docker ps)
run dnet_cmd $(inst_id2port 1) service publish svc1.multihost
[ "$status" -eq 0 ]
echo ${output}
run dnet_cmd $(inst_id2port 1) service ls
[ "$status" -eq 0 ]
echo ${output}
echo ${lines[1]}
svc=$(echo ${lines[1]} | cut -d" " -f2)
network=$(echo ${lines[1]} | cut -d" " -f3)
echo ${svc} ${network}
[ "$network" = "multihost" ]
[ "$svc" = "svc1" ]
dnet_cmd $(inst_id2port 1) service unpublish svc1.multihost
}
@test "Test service delete with id" {
echo $(docker ps)
run dnet_cmd $(inst_id2port 1) service publish svc1.multihost
[ "$status" -eq 0 ]
echo ${output}
run dnet_cmd $(inst_id2port 1) service ls
[ "$status" -eq 0 ]
echo ${output}
echo ${lines[1]}
id=$(echo ${lines[1]} | cut -d" " -f1)
dnet_cmd $(inst_id2port 1) service unpublish ${id}
}
@test "Test service attach" {
skip_for_circleci
echo $(docker ps)
dnet_cmd $(inst_id2port 1) service publish svc1.multihost
dnet_cmd $(inst_id2port 1) container create container_1
dnet_cmd $(inst_id2port 1) service attach container_1 svc1.multihost
run dnet_cmd $(inst_id2port 1) service ls
[ "$status" -eq 0 ]
echo ${output}
echo ${lines[1]}
container=$(echo ${lines[1]} | cut -d" " -f4)
[ "$container" = "container_1" ]
dnet_cmd $(inst_id2port 1) service detach container_1 svc1.multihost
dnet_cmd $(inst_id2port 1) container rm container_1
dnet_cmd $(inst_id2port 1) service unpublish svc1.multihost
}