pax_global_header 0000666 0000000 0000000 00000000064 14523706772 0014527 g ustar 00root root 0000000 0000000 52 comment=11b00cee99873c5e95b672117255435f48e6a1dd
golang-github-juju-cmd-3.0.14/ 0000775 0000000 0000000 00000000000 14523706772 0016057 5 ustar 00root root 0000000 0000000 golang-github-juju-cmd-3.0.14/.gitignore 0000664 0000000 0000000 00000000010 14523706772 0020036 0 ustar 00root root 0000000 0000000 /.idea/
golang-github-juju-cmd-3.0.14/LICENSE 0000664 0000000 0000000 00000021501 14523706772 0017063 0 ustar 00root root 0000000 0000000 All files in this repository are licensed as follows. If you contribute
to this repository, it is assumed that you license your contribution
under the same license unless you state otherwise.
All files Copyright (C) 2015 Canonical Ltd. unless otherwise specified in the file.
This software is licensed under the LGPLv3, included below.
As a special exception to the GNU Lesser General Public License version 3
("LGPL3"), the copyright holders of this Library give you permission to
convey to a third party a Combined Work that links statically or dynamically
to this Library without providing any Minimal Corresponding Source or
Minimal Application Code as set out in 4d or providing the installation
information set out in section 4e, provided that you comply with the other
provisions of LGPL3 and provided that you meet, for the Application the
terms and conditions of the license(s) which apply to the Application.
Except as stated in this special exception, the provisions of LGPL3 will
continue to comply in full to this Library. If you modify this Library, you
may apply this exception to your version of this Library, but you are not
obliged to do so. If you do not wish to do so, delete this exception
statement from your version. This exception does not (and cannot) modify any
license terms which apply to the Application, with which you must still
comply.
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
golang-github-juju-cmd-3.0.14/Makefile 0000664 0000000 0000000 00000000261 14523706772 0017516 0 ustar 00root root 0000000 0000000 # Copyright 2014 Canonical Ltd.
# Licensed under the LGPLv3, see LICENSE file for details.
default: check
check:
go test
docs:
godoc2md github.com/juju/cmd/v3 > README.md
golang-github-juju-cmd-3.0.14/README.md 0000664 0000000 0000000 00000044423 14523706772 0017345 0 ustar 00root root 0000000 0000000
# cmd
import "github.com/juju/cmd/v3"
## Variables
``` go
var DefaultFormatters = map[string]Formatter{
"smart": FormatSmart,
"yaml": FormatYaml,
"json": FormatJson,
}
```
DefaultFormatters holds the formatters that can be
specified with the --format flag.
``` go
var ErrNoPath = errors.New("path not set")
```
``` go
var ErrSilent = errors.New("cmd: error out silently")
```
ErrSilent can be returned from Run to signal that Main should exit with
code 1 without producing error output.
``` go
var FormatJson = json.Marshal
```
FormatJson marshals value to a json-formatted []byte.
## func CheckEmpty
``` go
func CheckEmpty(args []string) error
```
CheckEmpty is a utility function that returns an error if args is not empty.
## func FormatSmart
``` go
func FormatSmart(value interface{}) ([]byte, error)
```
FormatSmart marshals value into a []byte according to the following rules:
* string: untouched
* bool: converted to `True` or `False` (to match pyjuju)
* int or float: converted to sensible strings
* []string: joined by `\n`s into a single string
* anything else: delegate to FormatYaml
## func FormatYaml
``` go
func FormatYaml(value interface{}) ([]byte, error)
```
FormatYaml marshals value to a yaml-formatted []byte, unless value is nil.
## func IsErrSilent
``` go
func IsErrSilent(err error) bool
```
IsErrSilent returns whether the error should be logged from cmd.Main.
## func IsRcPassthroughError
``` go
func IsRcPassthroughError(err error) bool
```
IsRcPassthroughError returns whether the error is an RcPassthroughError.
## func Main
``` go
func Main(c Command, ctx *Context, args []string) int
```
Main runs the given Command in the supplied Context with the given
arguments, which should not include the command name. It returns a code
suitable for passing to os.Exit.
## func NewCommandLogWriter
``` go
func NewCommandLogWriter(name string, out, err io.Writer) loggo.Writer
```
NewCommandLogWriter creates a loggo writer for registration
by the callers of a command. This way the logged output can also
be displayed otherwise, e.g. on the screen.
## func NewRcPassthroughError
``` go
func NewRcPassthroughError(code int) error
```
NewRcPassthroughError creates an error that will have the code used at the
return code from the cmd.Main function rather than the default of 1 if
there is an error.
## func ParseAliasFile
``` go
func ParseAliasFile(aliasFilename string) map[string][]string
```
ParseAliasFile will read the specified file and convert
the content to a map of names to the command line arguments
they relate to. The function will always return a valid map, even
if it is empty.
## func ZeroOrOneArgs
``` go
func ZeroOrOneArgs(args []string) (string, error)
```
ZeroOrOneArgs checks to see that there are zero or one args, and returns
the value of the arg if provided, or the empty string if not.
## type AppendStringsValue
``` go
type AppendStringsValue []string
```
AppendStringsValue implements gnuflag.Value for a value that can be set
multiple times, and it appends each value to the slice.
### func NewAppendStringsValue
``` go
func NewAppendStringsValue(target *[]string) *AppendStringsValue
```
NewAppendStringsValue is used to create the type passed into the gnuflag.FlagSet Var function.
f.Var(cmd.NewAppendStringsValue(&someMember), "name", "help")
### func (\*AppendStringsValue) Set
``` go
func (v *AppendStringsValue) Set(s string) error
```
Implements gnuflag.Value Set.
### func (\*AppendStringsValue) String
``` go
func (v *AppendStringsValue) String() string
```
Implements gnuflag.Value String.
## type Command
``` go
type Command interface {
// IsSuperCommand returns true if the command is a super command.
IsSuperCommand() bool
// Info returns information about the Command.
Info() *Info
// SetFlags adds command specific flags to the flag set.
SetFlags(f *gnuflag.FlagSet)
// Init initializes the Command before running.
Init(args []string) error
// Run will execute the Command as directed by the options and positional
// arguments passed to Init.
Run(ctx *Context) error
// AllowInterspersedFlags returns whether the command allows flag
// arguments to be interspersed with non-flag arguments.
AllowInterspersedFlags() bool
}
```
Command is implemented by types that interpret command-line arguments.
## type CommandBase
``` go
type CommandBase struct{}
```
CommandBase provides the default implementation for SetFlags, Init, and Help.
### func (\*CommandBase) AllowInterspersedFlags
``` go
func (c *CommandBase) AllowInterspersedFlags() bool
```
AllowInterspersedFlags returns true by default. Some subcommands
may want to override this.
### func (\*CommandBase) Init
``` go
func (c *CommandBase) Init(args []string) error
```
Init in the simplest case makes sure there are no args.
### func (\*CommandBase) IsSuperCommand
``` go
func (c *CommandBase) IsSuperCommand() bool
```
IsSuperCommand implements Command.IsSuperCommand
### func (\*CommandBase) SetFlags
``` go
func (c *CommandBase) SetFlags(f *gnuflag.FlagSet)
```
SetFlags does nothing in the simplest case.
## type Context
``` go
type Context struct {
Dir string
Env map[string]string
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
// contains filtered or unexported fields
}
```
Context represents the run context of a Command. Command implementations
should interpret file names relative to Dir (see AbsPath below), and print
output and errors to Stdout and Stderr respectively.
### func DefaultContext
``` go
func DefaultContext() (*Context, error)
```
DefaultContext returns a Context suitable for use in non-hosted situations.
### func (\*Context) AbsPath
``` go
func (ctx *Context) AbsPath(path string) string
```
AbsPath returns an absolute representation of path, with relative paths
interpreted as relative to ctx.Dir.
### func (\*Context) GetStderr
``` go
func (ctx *Context) GetStderr() io.Writer
```
GetStderr satisfies environs.BootstrapContext
### func (\*Context) GetStdin
``` go
func (ctx *Context) GetStdin() io.Reader
```
GetStdin satisfies environs.BootstrapContext
### func (\*Context) GetStdout
``` go
func (ctx *Context) GetStdout() io.Writer
```
GetStdout satisfies environs.BootstrapContext
### func (\*Context) Getenv
``` go
func (ctx *Context) Getenv(key string) string
```
Getenv looks up an environment variable in the context. It mirrors
os.Getenv. An empty string is returned if the key is not set.
### func (\*Context) Infof
``` go
func (ctx *Context) Infof(format string, params ...interface{})
```
Infof will write the formatted string to Stderr if quiet is false, but if
quiet is true the message is logged.
### func (\*Context) InterruptNotify
``` go
func (ctx *Context) InterruptNotify(c chan<- os.Signal)
```
InterruptNotify satisfies environs.BootstrapContext
### func (\*Context) Setenv
``` go
func (ctx *Context) Setenv(key, value string) error
```
Setenv sets an environment variable in the context. It mirrors os.Setenv.
### func (\*Context) StopInterruptNotify
``` go
func (ctx *Context) StopInterruptNotify(c chan<- os.Signal)
```
StopInterruptNotify satisfies environs.BootstrapContext
### func (\*Context) Verbosef
``` go
func (ctx *Context) Verbosef(format string, params ...interface{})
```
Verbosef will write the formatted string to Stderr if the verbose is true,
and to the logger if not.
## type DeprecationCheck
``` go
type DeprecationCheck interface {
// Deprecated aliases emit a warning when executed. If the command is
// deprecated, the second return value recommends what to use instead.
Deprecated() (bool, string)
// Obsolete aliases are not actually registered. The purpose of this
// is to allow code to indicate ahead of time some way to determine
// that the command should stop working.
Obsolete() bool
}
```
DeprecationCheck is used to provide callbacks to determine if
a command is deprecated or obsolete.
## type FileVar
``` go
type FileVar struct {
// Path is the path to the file.
Path string
// StdinMarkers are the Path values that should be interpreted as
// stdin. If it is empty then stdin is not supported.
StdinMarkers []string
}
```
FileVar represents a path to a file.
### func (FileVar) IsStdin
``` go
func (f FileVar) IsStdin() bool
```
IsStdin determines whether or not the path represents stdin.
### func (\*FileVar) Open
``` go
func (f *FileVar) Open(ctx *Context) (io.ReadCloser, error)
```
Open opens the file.
### func (\*FileVar) Read
``` go
func (f *FileVar) Read(ctx *Context) ([]byte, error)
```
Read returns the contents of the file.
### func (\*FileVar) Set
``` go
func (f *FileVar) Set(v string) error
```
Set stores the chosen path name in f.Path.
### func (\*FileVar) SetStdin
``` go
func (f *FileVar) SetStdin(markers ...string)
```
SetStdin sets StdinMarkers to the provided strings. If none are
provided then the default of "-" is used.
### func (\*FileVar) String
``` go
func (f *FileVar) String() string
```
String returns the path to the file.
## type Formatter
``` go
type Formatter func(value interface{}) ([]byte, error)
```
Formatter converts an arbitrary object into a []byte.
## type Info
``` go
type Info struct {
// Name is the Command's name.
Name string
// Args describes the command's expected positional arguments.
Args string
// Purpose is a short explanation of the Command's purpose.
Purpose string
// Doc is the long documentation for the Command.
Doc string
// Aliases are other names for the Command.
Aliases []string
}
```
Info holds some of the usage documentation of a Command.
### func (\*Info) Help
``` go
func (i *Info) Help(f *gnuflag.FlagSet) []byte
```
Help renders i's content, along with documentation for any
flags defined in f. It calls f.SetOutput(ioutil.Discard).
## type Log
``` go
type Log struct {
// If DefaultConfig is set, it will be used for the
// default logging configuration.
DefaultConfig string
Path string
Verbose bool
Quiet bool
Debug bool
ShowLog bool
Config string
Factory WriterFactory
}
```
Log supplies the necessary functionality for Commands that wish to set up
logging.
### func (\*Log) AddFlags
``` go
func (l *Log) AddFlags(f *gnuflag.FlagSet)
```
AddFlags adds appropriate flags to f.
### func (\*Log) GetLogWriter
``` go
func (l *Log) GetLogWriter(target io.Writer) loggo.Writer
```
GetLogWriter returns a logging writer for the specified target.
### func (\*Log) Start
``` go
func (log *Log) Start(ctx *Context) error
```
Start starts logging using the given Context.
## type MissingCallback
``` go
type MissingCallback func(ctx *Context, subcommand string, args []string) error
```
MissingCallback defines a function that will be used by the SuperCommand if
the requested subcommand isn't found.
## type Output
``` go
type Output struct {
// contains filtered or unexported fields
}
```
Output is responsible for interpreting output-related command line flags
and writing a value to a file or to stdout as directed.
### func (\*Output) AddFlags
``` go
func (c *Output) AddFlags(f *gnuflag.FlagSet, defaultFormatter string, formatters map[string]Formatter)
```
AddFlags injects the --format and --output command line flags into f.
### func (\*Output) Name
``` go
func (c *Output) Name() string
```
### func (\*Output) Write
``` go
func (c *Output) Write(ctx *Context, value interface{}) (err error)
```
Write formats and outputs the value as directed by the --format and
--output command line flags.
## type RcPassthroughError
``` go
type RcPassthroughError struct {
Code int
}
```
RcPassthroughError indicates that a Juju plugin command exited with a
non-zero exit code. This error is used to exit with the return code.
### func (\*RcPassthroughError) Error
``` go
func (e *RcPassthroughError) Error() string
```
Error implements error.
## type StringsValue
``` go
type StringsValue []string
```
StringsValue implements gnuflag.Value for a comma separated list of
strings. This allows flags to be created where the target is []string, and
the caller is after comma separated values.
### func NewStringsValue
``` go
func NewStringsValue(defaultValue []string, target *[]string) *StringsValue
```
NewStringsValue is used to create the type passed into the gnuflag.FlagSet Var function.
f.Var(cmd.NewStringsValue(defaultValue, &someMember), "name", "help")
### func (\*StringsValue) Set
``` go
func (v *StringsValue) Set(s string) error
```
Implements gnuflag.Value Set.
### func (\*StringsValue) String
``` go
func (v *StringsValue) String() string
```
Implements gnuflag.Value String.
## type SuperCommand
``` go
type SuperCommand struct {
CommandBase
Name string
Purpose string
Doc string
Log *Log
Aliases []string
// contains filtered or unexported fields
}
```
SuperCommand is a Command that selects a subcommand and assumes its
properties; any command line arguments that were not used in selecting
the subcommand are passed down to it, and to Run a SuperCommand is to run
its selected subcommand.
### func NewSuperCommand
``` go
func NewSuperCommand(params SuperCommandParams) *SuperCommand
```
NewSuperCommand creates and initializes a new `SuperCommand`, and returns
the fully initialized structure.
### func (\*SuperCommand) AddHelpTopic
``` go
func (c *SuperCommand) AddHelpTopic(name, short, long string, aliases ...string)
```
AddHelpTopic adds a new help topic with the description being the short
param, and the full text being the long param. The description is shown in
'help topics', and the full text is shown when the command 'help ' is
called.
### func (\*SuperCommand) AddHelpTopicCallback
``` go
func (c *SuperCommand) AddHelpTopicCallback(name, short string, longCallback func() string)
```
AddHelpTopicCallback adds a new help topic with the description being the
short param, and the full text being defined by the callback function.
### func (\*SuperCommand) AllowInterspersedFlags
``` go
func (c *SuperCommand) AllowInterspersedFlags() bool
```
For a SuperCommand, we want to parse the args with
allowIntersperse=false. This will mean that the args may contain other
options that haven't been defined yet, and that only options that relate
to the SuperCommand itself can come prior to the subcommand name.
### func (\*SuperCommand) Info
``` go
func (c *SuperCommand) Info() *Info
```
Info returns a description of the currently selected subcommand, or of the
SuperCommand itself if no subcommand has been specified.
### func (\*SuperCommand) Init
``` go
func (c *SuperCommand) Init(args []string) error
```
Init initializes the command for running.
### func (\*SuperCommand) IsSuperCommand
``` go
func (c *SuperCommand) IsSuperCommand() bool
```
IsSuperCommand implements Command.IsSuperCommand
### func (\*SuperCommand) Register
``` go
func (c *SuperCommand) Register(subcmd Command)
```
Register makes a subcommand available for use on the command line. The
command will be available via its own name, and via any supplied aliases.
### func (\*SuperCommand) RegisterAlias
``` go
func (c *SuperCommand) RegisterAlias(name, forName string, check DeprecationCheck)
```
RegisterAlias makes an existing subcommand available under another name.
If `check` is supplied, and the result of the `Obsolete` call is true,
then the alias is not registered.
### func (\*SuperCommand) RegisterDeprecated
``` go
func (c *SuperCommand) RegisterDeprecated(subcmd Command, check DeprecationCheck)
```
RegisterDeprecated makes a subcommand available for use on the command line if it
is not obsolete. It inserts the command with the specified DeprecationCheck so
that a warning is displayed if the command is deprecated.
### func (\*SuperCommand) RegisterSuperAlias
``` go
func (c *SuperCommand) RegisterSuperAlias(name, super, forName string, check DeprecationCheck)
```
RegisterSuperAlias makes a subcommand of a registered supercommand
available under another name. This is useful when the command structure is
being refactored. If `check` is supplied, and the result of the `Obsolete`
call is true, then the alias is not registered.
### func (\*SuperCommand) Run
``` go
func (c *SuperCommand) Run(ctx *Context) error
```
Run executes the subcommand that was selected in Init.
### func (\*SuperCommand) SetCommonFlags
``` go
func (c *SuperCommand) SetCommonFlags(f *gnuflag.FlagSet)
```
SetCommonFlags creates a new "commonflags" flagset, whose
flags are shared with the argument f; this enables us to
add non-global flags to f, which do not carry into subcommands.
### func (\*SuperCommand) SetFlags
``` go
func (c *SuperCommand) SetFlags(f *gnuflag.FlagSet)
```
SetFlags adds the options that apply to all commands, particularly those
due to logging.
## type SuperCommandParams
``` go
type SuperCommandParams struct {
// UsagePrefix should be set when the SuperCommand is
// actually a subcommand of some other SuperCommand;
// if NotifyRun is called, it name will be prefixed accordingly,
// unless UsagePrefix is identical to Name.
UsagePrefix string
// Notify, if not nil, is called when the SuperCommand
// is about to run a sub-command.
NotifyRun func(cmdName string)
Name string
Purpose string
Doc string
Log *Log
MissingCallback MissingCallback
Aliases []string
Version string
// UserAliasesFilename refers to the location of a file that contains
// name = cmd [args...]
// values, that is used to change default behaviour of commands in order
// to add flags, or provide short cuts to longer commands.
UserAliasesFilename string
}
```
SuperCommandParams provides a way to have default parameter to the
`NewSuperCommand` call.
## type UnrecognizedCommand
``` go
type UnrecognizedCommand struct {
Name string
}
```
### func (\*UnrecognizedCommand) Error
``` go
func (e *UnrecognizedCommand) Error() string
```
## type WriterFactory
``` go
type WriterFactory interface {
NewWriter(target io.Writer) loggo.Writer
}
```
WriterFactory defines the single method to create a new
logging writer for a specified output target.
- - -
Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md) golang-github-juju-cmd-3.0.14/aliasfile.go 0000664 0000000 0000000 00000002614 14523706772 0020342 0 ustar 00root root 0000000 0000000 // Copyright 2015 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmd
import (
"io/ioutil"
"strings"
)
// ParseAliasFile will read the specified file and convert
// the content to a map of names to the command line arguments
// they relate to. The function will always return a valid map, even
// if it is empty.
func ParseAliasFile(aliasFilename string) map[string][]string {
result := map[string][]string{}
if aliasFilename == "" {
return result
}
content, err := ioutil.ReadFile(aliasFilename)
if err != nil {
logger.Tracef("unable to read alias file %q: %s", aliasFilename, err)
return result
}
lines := strings.Split(string(content), "\n")
for i, line := range lines {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
// skip blank lines and comments
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
logger.Warningf("line %d bad in alias file: %s", i+1, line)
continue
}
name, value := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
if name == "" {
logger.Warningf("line %d missing alias name in alias file: %s", i+1, line)
continue
}
if value == "" {
logger.Warningf("line %d missing alias value in alias file: %s", i+1, line)
continue
}
logger.Tracef("setting alias %q=%q", name, value)
result[name] = strings.Fields(value)
}
return result
}
golang-github-juju-cmd-3.0.14/aliasfile_test.go 0000664 0000000 0000000 00000002445 14523706772 0021403 0 ustar 00root root 0000000 0000000 // Copyright 2015 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmd_test
import (
_ "fmt"
"io/ioutil"
"path/filepath"
"github.com/juju/testing"
gc "gopkg.in/check.v1"
"github.com/juju/cmd/v3"
)
type ParseAliasFileSuite struct {
testing.LoggingSuite
}
var _ = gc.Suite(&ParseAliasFileSuite{})
func (*ParseAliasFileSuite) TestMissing(c *gc.C) {
dir := c.MkDir()
filename := filepath.Join(dir, "missing")
aliases := cmd.ParseAliasFile(filename)
c.Assert(aliases, gc.NotNil)
c.Assert(aliases, gc.HasLen, 0)
}
func (*ParseAliasFileSuite) TestParse(c *gc.C) {
dir := c.MkDir()
filename := filepath.Join(dir, "missing")
content := `
# comments skipped, as are the blank lines, such as the line
# at the start of this file
foo = trailing-space
repeat = first
flags = flags --with flag
# if the same alias name is used more than once, last one wins
repeat = second
# badly formated values are logged, but skipped
no equals sign
=
key =
= value
`
err := ioutil.WriteFile(filename, []byte(content), 0644)
c.Assert(err, gc.IsNil)
aliases := cmd.ParseAliasFile(filename)
c.Assert(aliases, gc.DeepEquals, map[string][]string{
"foo": []string{"trailing-space"},
"repeat": []string{"second"},
"flags": []string{"flags", "--with", "flag"},
})
}
golang-github-juju-cmd-3.0.14/args.go 0000664 0000000 0000000 00000003242 14523706772 0017343 0 ustar 00root root 0000000 0000000 // Copyright 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmd
import (
"strings"
"github.com/juju/gnuflag"
)
// StringsValue implements gnuflag.Value for a comma separated list of
// strings. This allows flags to be created where the target is []string, and
// the caller is after comma separated values.
type StringsValue []string
var _ gnuflag.Value = (*StringsValue)(nil)
// NewStringsValue is used to create the type passed into the gnuflag.FlagSet Var function.
// f.Var(cmd.NewStringsValue(defaultValue, &someMember), "name", "help")
func NewStringsValue(defaultValue []string, target *[]string) *StringsValue {
value := (*StringsValue)(target)
*value = defaultValue
return value
}
// Implements gnuflag.Value Set.
func (v *StringsValue) Set(s string) error {
*v = strings.Split(s, ",")
return nil
}
// Implements gnuflag.Value String.
func (v *StringsValue) String() string {
return strings.Join(*v, ",")
}
// AppendStringsValue implements gnuflag.Value for a value that can be set
// multiple times, and it appends each value to the slice.
type AppendStringsValue []string
var _ gnuflag.Value = (*AppendStringsValue)(nil)
// NewAppendStringsValue is used to create the type passed into the gnuflag.FlagSet Var function.
// f.Var(cmd.NewAppendStringsValue(&someMember), "name", "help")
func NewAppendStringsValue(target *[]string) *AppendStringsValue {
return (*AppendStringsValue)(target)
}
// Implements gnuflag.Value Set.
func (v *AppendStringsValue) Set(s string) error {
*v = append(*v, s)
return nil
}
// Implements gnuflag.Value String.
func (v *AppendStringsValue) String() string {
return strings.Join(*v, ",")
}
golang-github-juju-cmd-3.0.14/args_test.go 0000664 0000000 0000000 00000010376 14523706772 0020410 0 ustar 00root root 0000000 0000000 // Copyright 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmd_test
import (
"fmt"
"io/ioutil"
"github.com/juju/gnuflag"
"github.com/juju/testing"
gc "gopkg.in/check.v1"
"github.com/juju/cmd/v3"
)
type ArgsSuite struct {
testing.LoggingSuite
}
var _ = gc.Suite(&ArgsSuite{})
func (*ArgsSuite) TestFlagsUsage(c *gc.C) {
for i, test := range []struct {
message string
defaultValue []string
args []string
expectedValue []string
}{{
message: "nil default and no arg",
}, {
message: "default value and not set by args",
defaultValue: []string{"foo", "bar"},
expectedValue: []string{"foo", "bar"},
}, {
message: "no value set by args",
args: []string{"--value", "foo,bar"},
expectedValue: []string{"foo", "bar"},
}, {
message: "default value and set by args",
defaultValue: []string{"omg"},
args: []string{"--value", "foo,bar"},
expectedValue: []string{"foo", "bar"},
}} {
c.Log(fmt.Sprintf("%v: %s", i, test.message))
f := gnuflag.NewFlagSet("test", gnuflag.ContinueOnError)
f.SetOutput(ioutil.Discard)
var value []string
f.Var(cmd.NewStringsValue(test.defaultValue, &value), "value", "help")
err := f.Parse(false, test.args)
c.Check(err, gc.IsNil)
c.Check(value, gc.DeepEquals, test.expectedValue)
}
}
func (*ArgsSuite) TestNewStringsValue(c *gc.C) {
for i, test := range []struct {
message string
defaultValue []string
}{{
message: "null default",
}, {
message: "empty default",
defaultValue: []string{},
}, {
message: "single value",
defaultValue: []string{"foo"},
}, {
message: "multiple values",
defaultValue: []string{"foo", "bar", "baz"},
}} {
c.Log(fmt.Sprintf("%v: %s", i, test.message))
var underlyingValue []string
_ = cmd.NewStringsValue(test.defaultValue, &underlyingValue)
c.Assert(underlyingValue, gc.DeepEquals, test.defaultValue)
}
}
func (*ArgsSuite) TestSet(c *gc.C) {
for i, test := range []struct {
message string
arg string
expected []string
}{{
message: "empty",
expected: []string{""},
}, {
message: "just whitespace",
arg: " ",
expected: []string{" "},
}, {
message: "whitespace and comma",
arg: " , ",
expected: []string{" ", " "},
}, {
message: "single value",
arg: "foo",
expected: []string{"foo"},
}, {
message: "single value with comma",
arg: "foo,",
expected: []string{"foo", ""},
}, {
message: "single value with whitespace",
arg: " foo ",
expected: []string{" foo "},
}, {
message: "multiple values",
arg: "foo,bar,baz",
expected: []string{"foo", "bar", "baz"},
}, {
message: "multiple values with spaces",
arg: "foo, bar, baz",
expected: []string{"foo", " bar", " baz"},
}} {
c.Log(fmt.Sprintf("%v: %s", i, test.message))
var result []string
value := cmd.NewStringsValue(nil, &result)
error := value.Set(test.arg)
c.Check(error, gc.IsNil)
c.Check(result, gc.DeepEquals, test.expected)
}
}
func (*ArgsSuite) TestString(c *gc.C) {
for i, test := range []struct {
message string
target []string
expected string
}{{
message: "null",
expected: "",
}, {
message: "empty",
target: []string{},
expected: "",
}, {
message: "single value",
target: []string{"foo"},
expected: "foo",
}, {
message: "multiple values",
target: []string{"foo", "bar", "baz"},
expected: "foo,bar,baz",
}} {
c.Log(fmt.Sprintf("%v: %s", i, test.message))
var temp []string
value := cmd.NewStringsValue(test.target, &temp)
c.Assert(value.String(), gc.Equals, test.expected)
}
}
func (*ArgsSuite) TestAppendStringsUsage(c *gc.C) {
for i, test := range []struct {
message string
args []string
expectedValue []string
}{{
message: "no args",
}, {
message: "value set by args",
args: []string{"--value", "foo", "--value=bar"},
expectedValue: []string{"foo", "bar"},
}} {
c.Log(fmt.Sprintf("%v: %s", i, test.message))
f := gnuflag.NewFlagSet("test", gnuflag.ContinueOnError)
f.SetOutput(ioutil.Discard)
var value []string
f.Var(cmd.NewAppendStringsValue(&value), "value", "help")
err := f.Parse(false, test.args)
c.Check(err, gc.IsNil)
c.Check(value, gc.DeepEquals, test.expectedValue)
}
}
golang-github-juju-cmd-3.0.14/cmd.go 0000664 0000000 0000000 00000036250 14523706772 0017157 0 ustar 00root root 0000000 0000000 // Copyright 2012, 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmd
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/signal"
"path/filepath"
"sort"
"strings"
"github.com/juju/ansiterm"
"github.com/juju/gnuflag"
"github.com/juju/loggo"
"github.com/juju/utils/v3"
)
// RcPassthroughError indicates that a Juju plugin command exited with a
// non-zero exit code. This error is used to exit with the return code.
type RcPassthroughError struct {
Code int
}
// Error implements error.
func (e *RcPassthroughError) Error() string {
return fmt.Sprintf("subprocess encountered error code %v", e.Code)
}
// IsRcPassthroughError returns whether the error is an RcPassthroughError.
func IsRcPassthroughError(err error) bool {
_, ok := err.(*RcPassthroughError)
return ok
}
// NewRcPassthroughError creates an error that will have the code used at the
// return code from the cmd.Main function rather than the default of 1 if
// there is an error.
func NewRcPassthroughError(code int) error {
return &RcPassthroughError{code}
}
// ErrSilent can be returned from Run to signal that Main should exit with
// code 1 without producing error output.
var ErrSilent = errors.New("cmd: error out silently")
// IsErrSilent returns whether the error should be logged from cmd.Main.
func IsErrSilent(err error) bool {
if err == ErrSilent {
return true
}
if _, ok := err.(*RcPassthroughError); ok {
return true
}
return false
}
// Command is implemented by types that interpret command-line arguments.
type Command interface {
// IsSuperCommand returns true if the command is a super command.
IsSuperCommand() bool
// Info returns information about the Command.
Info() *Info
// SetFlags adds command specific flags to the flag set.
SetFlags(f *gnuflag.FlagSet)
// Init initializes the Command before running.
Init(args []string) error
// Run will execute the Command as directed by the options and positional
// arguments passed to Init.
Run(ctx *Context) error
// AllowInterspersedFlags returns whether the command allows flag
// arguments to be interspersed with non-flag arguments.
AllowInterspersedFlags() bool
}
// CommandBase provides the default implementation for SetFlags, Init, and Help.
type CommandBase struct{}
// IsSuperCommand implements Command.IsSuperCommand
func (c *CommandBase) IsSuperCommand() bool {
return false
}
// SetFlags does nothing in the simplest case.
func (c *CommandBase) SetFlags(f *gnuflag.FlagSet) {}
// Init in the simplest case makes sure there are no args.
func (c *CommandBase) Init(args []string) error {
return CheckEmpty(args)
}
// AllowInterspersedFlags returns true by default. Some subcommands
// may want to override this.
func (c *CommandBase) AllowInterspersedFlags() bool {
return true
}
// Context represents the run context of a Command. Command implementations
// should interpret file names relative to Dir (see AbsPath below), and print
// output and errors to Stdout and Stderr respectively.
type Context struct {
context.Context
Dir string
Env map[string]string
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
outputFormatUsed bool
quiet bool
verbose bool
serialisable bool
}
// With returns a command context with the specified context.Context.
func (ctx *Context) With(c context.Context) *Context {
newCtx := *ctx
newCtx.Context = c
return &newCtx
}
// Quiet reports whether the command is in "quiet" mode. When
// this is true, informational output should be suppressed (logger
// messages can be used instead).
func (ctx *Context) Quiet() bool {
return ctx.quiet
}
// IsSerial reports whether the command is required to output to a "machine".
// This mode is intended to stop the proliferation of execessive writes to
// stdout and stderr, when the output is intended for machines.
func (ctx *Context) IsSerial() bool {
return ctx.serialisable
}
func (ctx *Context) write(format string, params ...interface{}) {
output := fmt.Sprintf(format, params...)
if !strings.HasSuffix(output, "\n") {
output = output + "\n"
}
fmt.Fprint(ctx.Stderr, output)
}
// Infof will write the formatted string to Stderr if quiet is false, but if
// quiet is true the message is logged.
func (ctx *Context) Infof(format string, params ...interface{}) {
if ctx.quiet {
//Here we use the Loggo.logger method `Logf` as opposed to
//`logger.Infof` to avoid introducing an additional call stack
//level (since `Infof` calls `Logf` internally). This is done so
//that this function can produce more accurate source location
//debug information.
logger.Logf(loggo.INFO, format, params...)
} else {
ctx.write(format, params...)
}
}
// Warningf allows for the logging of messages, at the warning level, from a
// command's context. This is useful for logging errors which do not cause a
// command to fail (e.g. an error message used as a deprecation warning that
// will be upgraded to a real error message at some point in the future.)
func (ctx *Context) Warningf(format string, params ...interface{}) {
// Here we use the Loggo.logger method `Logf` as opposed to
// `logger.Warningf` to avoid introducing an additional call stack level
// (since `Warningf` calls Logf internally). This is done so that this
// function can produce more accurate source location debug information.
logger.Logf(loggo.WARNING, format, params...)
}
// Verbosef will write the formatted string to Stderr if the verbose is true,
// and to the logger if not.
func (ctx *Context) Verbosef(format string, params ...interface{}) {
if ctx.verbose {
ctx.write(format, params...)
} else {
// Here we use the Loggo.logger method `Logf` as opposed to
// `logger.Infof` to avoid introducing an additional call stack
// level (since `Infof` calls `Logf` internally). This is done so
// that this function can produce more accurate source location
// debug information.
logger.Logf(loggo.INFO, format, params...)
}
}
// Errorf allows for the logging of error messages from a command's
// context. This should be used for errors which cause a command to fail.
// Usually these errors are logged by returning them in Run, but that is
// not always sufficent. For instance, if the client has performed multiple
// actions
func (ctx *Context) Errorf(format string, params ...interface{}) {
// Here we use the Loggo.logger method `Logf` as opposed to
// `logger.Errorf` to avoid introducing an additional call stack
// level (since `Errorf` calls `Logf` internally). This is done so
// that this function can produce more accurate source location
// debug information.
logger.Logf(loggo.ERROR, format, params...)
}
// WriteError will output the formatted text to the writer with
// a colored ERROR like the logging would.
//
// DEPRECATED: Use ctx.Errorf instead
func WriteError(writer io.Writer, err error) {
w := ansiterm.NewWriter(writer)
ansiterm.Foreground(ansiterm.BrightRed).Fprintf(w, "ERROR")
fmt.Fprintf(w, " %s\n", err.Error())
}
// Getenv looks up an environment variable in the context. It mirrors
// os.Getenv. An empty string is returned if the key is not set.
func (ctx *Context) Getenv(key string) string {
value, _ := ctx.Env[key]
return value
}
// Setenv sets an environment variable in the context. It mirrors os.Setenv.
func (ctx *Context) Setenv(key, value string) error {
if ctx.Env == nil {
ctx.Env = make(map[string]string)
}
ctx.Env[key] = value
return nil
}
// AbsPath returns an absolute representation of path, with relative paths
// interpreted as relative to ctx.Dir and with "~/" replaced with users
// home dir.
func (ctx *Context) AbsPath(path string) string {
if normalizedPath, err := utils.NormalizePath(path); err == nil {
path = normalizedPath
}
if filepath.IsAbs(path) {
return path
}
return filepath.Join(ctx.Dir, path)
}
// GetStdin satisfies environs.BootstrapContext
func (ctx *Context) GetStdin() io.Reader {
return ctx.Stdin
}
// GetStdout satisfies environs.BootstrapContext
func (ctx *Context) GetStdout() io.Writer {
return ctx.Stdout
}
// GetStderr satisfies environs.BootstrapContext
func (ctx *Context) GetStderr() io.Writer {
return ctx.Stderr
}
// InterruptNotify satisfies environs.BootstrapContext
func (ctx *Context) InterruptNotify(c chan<- os.Signal) {
signal.Notify(c, os.Interrupt)
}
// StopInterruptNotify satisfies environs.BootstrapContext
func (ctx *Context) StopInterruptNotify(c chan<- os.Signal) {
signal.Stop(c)
}
// Info holds some of the usage documentation of a Command.
type Info struct {
// Name is the Command's name.
Name string
// Args describes the command's expected positional arguments.
Args string
// Purpose is a short explanation of the Command's purpose.
Purpose string
// Doc is the long documentation for the Command.
Doc string
// Subcommands stores the name and description of each subcommand.
Subcommands map[string]string
// Examples is a collection of running examples.
Examples string
// SeeAlso is a collection of additional commands to be checked.
SeeAlso []string
// Aliases are other names for the Command.
Aliases []string
// FlagKnownAs allows different projects to customise what their flags are
// known as, e.g. 'flag', 'option', 'item'. All error/log messages
// will use that name when referring to an individual items/flags in this command.
// For example, if this value is 'option', the default message 'value for flag'
// will become 'value for option'.
FlagKnownAs string
// ShowSuperFlags contains the names of the 'super' command flags
// that are desired to be shown in the sub-command help output.
ShowSuperFlags []string
}
// Help renders i's content, along with documentation for any
// flags defined in f.
func (i *Info) Help(f *gnuflag.FlagSet) []byte {
return i.HelpWithSuperFlags(nil, f)
}
// HelpWithSuperFlags renders i's content, along with documentation for any
// flags defined in both command and its super command flag sets.
// Only super command flags defined in i.ShowSuperFlags are displayed, if found.
func (i *Info) HelpWithSuperFlags(superF *gnuflag.FlagSet, f *gnuflag.FlagSet) []byte {
buf := &bytes.Buffer{}
fmt.Fprintf(buf, "Usage: %s", i.Name)
hasOptions := false
f.VisitAll(func(f *gnuflag.Flag) { hasOptions = true })
if hasOptions {
fmt.Fprintf(buf, " [%vs]", f.FlagKnownAs)
}
if i.Args != "" {
fmt.Fprintf(buf, " %s", i.Args)
}
fmt.Fprintf(buf, "\n")
if i.Purpose != "" {
fmt.Fprintf(buf, "\nSummary:\n%s\n", strings.TrimSpace(i.Purpose))
}
hasSuperFlags := false
if superF != nil && len(i.ShowSuperFlags) != 0 {
filteredSuperF := gnuflag.NewFlagSetWithFlagKnownAs("", gnuflag.ContinueOnError, superF.FlagKnownAs)
contains := func(one string) bool {
for _, a := range i.ShowSuperFlags {
if strings.ToLower(one) == strings.ToLower(a) {
return true
}
}
return false
}
superF.VisitAll(func(flag *gnuflag.Flag) {
if contains(flag.Name) {
hasSuperFlags = true
filteredSuperF.Var(flag.Value, flag.Name, flag.Usage)
}
})
if hasSuperFlags {
fmt.Fprintf(buf, "\nGlobal %vs:\n", strings.Title(filteredSuperF.FlagKnownAs))
filteredSuperF.SetOutput(buf)
filteredSuperF.PrintDefaults()
filteredSuperF.SetOutput(ioutil.Discard)
}
}
if hasOptions {
if hasSuperFlags {
fmt.Fprintf(buf, "\nCommand %vs:\n", strings.Title(f.FlagKnownAs))
} else {
fmt.Fprintf(buf, "\n%vs:\n", strings.Title(f.FlagKnownAs))
}
f.SetOutput(buf)
f.PrintDefaults()
}
f.SetOutput(ioutil.Discard)
if i.Doc != "" {
fmt.Fprintf(buf, "\nDetails:\n")
fmt.Fprintf(buf, "%s\n", strings.TrimSpace(i.Doc))
}
if len(i.Aliases) > 0 {
fmt.Fprintf(buf, "\nAliases: %s\n", strings.Join(i.Aliases, ", "))
}
if len(i.Examples) > 0 {
fmt.Fprintf(buf, "\nExamples:\n%s", i.Examples)
}
if len(i.Subcommands) > 0 {
fmt.Fprintf(buf, "\n%s", i.describeCommands())
}
if len(i.SeeAlso) > 0 {
fmt.Fprintf(buf, "\nSee also:\n")
for _, entry := range i.SeeAlso {
fmt.Fprintf(buf, " - %s\n", entry)
}
}
return buf.Bytes()
}
// Default commands should be hidden from the help output.
func isDefaultCommand(cmd string) bool {
switch cmd {
case "documentation", "help", "version":
return true
}
return false
}
func (i *Info) describeCommands() string {
// Sort command names, and work out length of the longest one
cmdNames := make([]string, 0, len(i.Subcommands))
longest := 0
for name := range i.Subcommands {
if isDefaultCommand(name) {
continue
}
if len(name) > longest {
longest = len(name)
}
cmdNames = append(cmdNames, name)
}
sort.Strings(cmdNames)
descr := "Subcommands:\n"
for _, name := range cmdNames {
purpose := i.Subcommands[name]
descr += fmt.Sprintf(" %-*s - %s\n", longest, name, purpose)
}
return descr
}
// Errors from commands can be ErrSilent (don't print an error message),
// ErrHelp (show the help) or some other error related to needed flags
// missing, or needed positional args missing, in which case we should
// print the error and return a non-zero return code.
func handleCommandError(c Command, ctx *Context, err error, f *gnuflag.FlagSet) (rc int, done bool) {
switch err {
case nil:
return 0, false
case gnuflag.ErrHelp:
ctx.Stdout.Write(c.Info().Help(f))
return 0, true
case ErrSilent:
return 2, true
default:
WriteError(ctx.Stderr, err)
return 2, true
}
}
func FlagAlias(c Command, akaDefault string) string {
flagsAKA := c.Info().FlagKnownAs
if flagsAKA == "" {
return akaDefault
}
return flagsAKA
}
// Main runs the given Command in the supplied Context with the given
// arguments, which should not include the command name. It returns a code
// suitable for passing to os.Exit.
func Main(c Command, ctx *Context, args []string) int {
f := gnuflag.NewFlagSetWithFlagKnownAs(c.Info().Name, gnuflag.ContinueOnError, FlagAlias(c, "flag"))
f.SetOutput(ioutil.Discard)
c.SetFlags(f)
if rc, done := handleCommandError(c, ctx, f.Parse(c.AllowInterspersedFlags(), args), f); done {
return rc
}
// Since SuperCommands can also return gnuflag.ErrHelp errors, we need to
// handle both those types of errors as well as "real" errors.
if rc, done := handleCommandError(c, ctx, c.Init(f.Args()), f); done {
return rc
}
if err := c.Run(ctx); err != nil {
if IsRcPassthroughError(err) {
return err.(*RcPassthroughError).Code
}
if err != ErrSilent {
WriteError(ctx.Stderr, err)
}
return 1
}
return 0
}
// DefaultContext returns a Context suitable for use in non-hosted situations.
func DefaultContext() (*Context, error) {
dir, err := os.Getwd()
if err != nil {
return nil, err
}
abs, err := filepath.Abs(dir)
if err != nil {
return nil, err
}
ctx := &Context{
Dir: abs,
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
ctx.Context = context.Background()
return ctx, nil
}
// CheckEmpty is a utility function that returns an error if args is not empty.
func CheckEmpty(args []string) error {
if len(args) != 0 {
return fmt.Errorf("unrecognized args: %q", args)
}
return nil
}
// ZeroOrOneArgs checks to see that there are zero or one args, and returns
// the value of the arg if provided, or the empty string if not.
func ZeroOrOneArgs(args []string) (string, error) {
var result string
if len(args) > 0 {
result, args = args[0], args[1:]
}
if err := CheckEmpty(args); err != nil {
return "", err
}
return result, nil
}
golang-github-juju-cmd-3.0.14/cmd_test.go 0000664 0000000 0000000 00000025402 14523706772 0020213 0 ustar 00root root 0000000 0000000 // Copyright 2012, 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmd_test
import (
"bytes"
"context"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/juju/loggo"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
"github.com/juju/gnuflag"
"github.com/juju/testing"
"github.com/juju/cmd/v3"
"github.com/juju/cmd/v3/cmdtesting"
)
var _ = gc.Suite(&CmdSuite{})
var _ = gc.Suite(&CmdHelpSuite{})
var _ = gc.Suite(&CmdDocumentationSuite{})
type CmdSuite struct {
testing.LoggingCleanupSuite
ctx *cmd.Context
}
func (s *CmdSuite) SetUpTest(c *gc.C) {
s.LoggingCleanupSuite.SetUpTest(c)
s.ctx = cmdtesting.Context(c)
loggo.ReplaceDefaultWriter(cmd.NewWarningWriter(s.ctx.Stderr))
}
func (s *CmdSuite) TestContext(c *gc.C) {
c.Check(s.ctx.Context, jc.DeepEquals, context.Background())
c.Check(s.ctx.AbsPath("/foo/bar"), gc.Equals, "/foo/bar")
c.Check(s.ctx.AbsPath("/foo/../bar"), gc.Equals, "/bar")
c.Check(s.ctx.AbsPath("foo/bar"), gc.Equals, filepath.Join(s.ctx.Dir, "foo/bar"))
homeDir := os.Getenv("HOME")
c.Check(s.ctx.AbsPath("~/foo/bar"), gc.Equals, filepath.Join(homeDir, "foo/bar"))
}
func (s *CmdSuite) TestWith(c *gc.C) {
cancelCtx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := s.ctx.With(cancelCtx)
c.Assert(ctx.Context, jc.DeepEquals, cancelCtx)
}
func (s *CmdSuite) TestContextGetenv(c *gc.C) {
s.ctx.Env = make(map[string]string)
before := s.ctx.Getenv("foo")
s.ctx.Env["foo"] = "bar"
after := s.ctx.Getenv("foo")
c.Check(before, gc.Equals, "")
c.Check(after, gc.Equals, "bar")
}
func (s *CmdSuite) TestContextSetenv(c *gc.C) {
before := s.ctx.Env["foo"]
s.ctx.Setenv("foo", "bar")
after := s.ctx.Env["foo"]
c.Check(before, gc.Equals, "")
c.Check(after, gc.Equals, "bar")
}
func (s *CmdSuite) TestInfo(c *gc.C) {
minimal := &TestCommand{Name: "verb", Minimal: true}
help := minimal.Info().Help(cmdtesting.NewFlagSet())
c.Assert(string(help), gc.Equals, minimalHelp)
full := &TestCommand{Name: "verb"}
f := cmdtesting.NewFlagSet()
var ignored string
f.StringVar(&ignored, "option", "", "option-doc")
help = full.Info().Help(f)
c.Assert(string(help), gc.Equals, fmt.Sprintf(fullHelp, "flag", "Flag"))
optionInfo := full.Info()
optionInfo.Doc = ""
f.FlagKnownAs = "option"
help = optionInfo.Help(f)
c.Assert(string(help), gc.Equals, optionHelp)
}
var initErrorTests = []struct {
c *TestCommand
help string
}{
{&TestCommand{Name: "verb"}, fmt.Sprintf(fullHelp, "flag", strings.Title("flag"))},
{&TestCommand{Name: "verb", Minimal: true}, minimalHelp},
}
func (s *CmdSuite) TestMainInitError(c *gc.C) {
expected := "ERROR flag provided but not defined: --unknown\n"
for _, t := range initErrorTests {
s.SetUpTest(c)
s.assertOptionError(c, t.c, expected)
s.TearDownTest(c)
}
}
func (s *CmdSuite) assertOptionError(c *gc.C, command *TestCommand, expected string) {
result := cmd.Main(command, s.ctx, []string{"--unknown"})
c.Assert(result, gc.Equals, 2)
c.Assert(bufferString(s.ctx.Stdout), gc.Equals, "")
c.Assert(bufferString(s.ctx.Stderr), gc.Equals, expected)
}
func (s *CmdSuite) TestMainFlagsAKA(c *gc.C) {
s.assertOptionError(c,
&TestCommand{Name: "verb", FlagAKA: "option"},
"ERROR option provided but not defined: --unknown\n")
}
func (s *CmdSuite) TestMainRunError(c *gc.C) {
result := cmd.Main(&TestCommand{Name: "verb"}, s.ctx, []string{"--option", "error"})
c.Assert(result, gc.Equals, 1)
c.Assert(bufferString(s.ctx.Stdout), gc.Equals, "")
c.Assert(bufferString(s.ctx.Stderr), gc.Equals, "ERROR BAM!\n")
}
func (s *CmdSuite) TestMainRunSilentError(c *gc.C) {
result := cmd.Main(&TestCommand{Name: "verb"}, s.ctx, []string{"--option", "silent-error"})
c.Assert(result, gc.Equals, 1)
c.Assert(bufferString(s.ctx.Stdout), gc.Equals, "")
c.Assert(bufferString(s.ctx.Stderr), gc.Equals, "")
}
func (s *CmdSuite) TestMainSuccess(c *gc.C) {
result := cmd.Main(&TestCommand{Name: "verb"}, s.ctx, []string{"--option", "success!"})
c.Assert(result, gc.Equals, 0)
c.Assert(bufferString(s.ctx.Stdout), gc.Equals, "success!\n")
c.Assert(bufferString(s.ctx.Stderr), gc.Equals, "")
}
func (s *CmdSuite) TestStdin(c *gc.C) {
const phrase = "Do you, Juju?"
s.ctx.Stdin = bytes.NewBuffer([]byte(phrase))
result := cmd.Main(&TestCommand{Name: "verb"}, s.ctx, []string{"--option", "echo"})
c.Assert(result, gc.Equals, 0)
c.Assert(bufferString(s.ctx.Stdout), gc.Equals, phrase)
c.Assert(bufferString(s.ctx.Stderr), gc.Equals, "")
}
func (s *CmdSuite) TestMainHelp(c *gc.C) {
for _, arg := range []string{"-h", "--help"} {
s.SetUpTest(c)
result := cmd.Main(&TestCommand{Name: "verb"}, s.ctx, []string{arg})
c.Assert(result, gc.Equals, 0)
c.Assert(bufferString(s.ctx.Stdout), gc.Equals, fmt.Sprintf(fullHelp, "flag", "Flag"))
c.Assert(bufferString(s.ctx.Stderr), gc.Equals, "")
s.TearDownTest(c)
}
}
func (s *CmdSuite) TestMainHelpFlagsAKA(c *gc.C) {
for _, arg := range []string{"-h", "--help"} {
s.SetUpTest(c)
result := cmd.Main(&TestCommand{Name: "verb", FlagAKA: "option"}, s.ctx, []string{arg})
c.Assert(result, gc.Equals, 0)
c.Assert(bufferString(s.ctx.Stdout), gc.Equals, fmt.Sprintf(fullHelp, "option", "Option"))
c.Assert(bufferString(s.ctx.Stderr), gc.Equals, "")
s.TearDownTest(c)
}
}
func (s *CmdSuite) TestDefaultContextReturnsErrorInDeletedDirectory(c *gc.C) {
wd, err := os.Getwd()
c.Assert(err, gc.IsNil)
missing := s.ctx.Dir + "/missing"
err = os.Mkdir(missing, 0700)
c.Assert(err, gc.IsNil)
err = os.Chdir(missing)
c.Assert(err, gc.IsNil)
defer os.Chdir(wd)
err = os.Remove(missing)
c.Assert(err, gc.IsNil)
ctx, err := cmd.DefaultContext()
c.Assert(err, gc.ErrorMatches, `getwd: no such file or directory`)
c.Assert(ctx, gc.IsNil)
}
func (s *CmdSuite) TestCheckEmpty(c *gc.C) {
c.Assert(cmd.CheckEmpty(nil), gc.IsNil)
c.Assert(cmd.CheckEmpty([]string{"boo!"}), gc.ErrorMatches, `unrecognized args: \["boo!"\]`)
}
func (s *CmdSuite) TestZeroOrOneArgs(c *gc.C) {
expectValue := func(args []string, expected string) {
arg, err := cmd.ZeroOrOneArgs(args)
c.Assert(arg, gc.Equals, expected)
c.Assert(err, gc.IsNil)
}
expectValue(nil, "")
expectValue([]string{}, "")
expectValue([]string{"foo"}, "foo")
arg, err := cmd.ZeroOrOneArgs([]string{"foo", "bar"})
c.Assert(arg, gc.Equals, "")
c.Assert(err, gc.ErrorMatches, `unrecognized args: \["bar"\]`)
}
func (s *CmdSuite) TestIsErrSilent(c *gc.C) {
c.Assert(cmd.IsErrSilent(cmd.ErrSilent), gc.Equals, true)
c.Assert(cmd.IsErrSilent(cmd.NewRcPassthroughError(99)), gc.Equals, true)
c.Assert(cmd.IsErrSilent(fmt.Errorf("noisy")), gc.Equals, false)
}
func (s *CmdSuite) TestInfoHelp(c *gc.C) {
fs := gnuflag.NewFlagSet("", gnuflag.ContinueOnError)
s.assertFlagSetHelp(c, fs)
}
func (s *CmdSuite) TestInfoHelpFlagsAKA(c *gc.C) {
fs := gnuflag.NewFlagSetWithFlagKnownAs("", gnuflag.ContinueOnError, "item")
s.assertFlagSetHelp(c, fs)
}
func (s *CmdSuite) assertFlagSetHelp(c *gc.C, fs *gnuflag.FlagSet) {
// Test that white space is trimmed consistently from cmd.Info.Purpose
// (Help Summary) and cmd.Info.Doc (Help Details)
option := "option"
fs.StringVar(&option, "option", "", "option-doc")
table := []struct {
summary, details string
}{
{`
verb the juju`,
`
verb-doc`},
{`verb the juju`, `verb-doc`},
{`
verb the juju`,
`
verb-doc`},
{`verb the juju `, `verb-doc
`},
}
want := fmt.Sprintf(fullHelp, fs.FlagKnownAs, strings.Title(fs.FlagKnownAs))
for _, tv := range table {
i := cmd.Info{
Name: "verb",
Args: "",
Purpose: tv.summary,
Doc: tv.details,
}
got := string(i.Help(fs))
c.Check(got, gc.Equals, want)
}
}
type CmdHelpSuite struct {
testing.LoggingCleanupSuite
superfs *gnuflag.FlagSet
commandfs *gnuflag.FlagSet
info cmd.Info
}
func (s *CmdHelpSuite) SetUpTest(c *gc.C) {
s.LoggingCleanupSuite.SetUpTest(c)
addOptions := func(f *gnuflag.FlagSet, options []string) {
for _, a := range options {
option := a
f.StringVar(&option, option, "", "option-doc")
}
}
s.commandfs = gnuflag.NewFlagSet("", gnuflag.ContinueOnError)
addOptions(s.commandfs, []string{"one", "five", "three"})
s.superfs = gnuflag.NewFlagSet("", gnuflag.ContinueOnError)
addOptions(s.superfs, []string{"blackpanther", "captainamerica", "spiderman"})
s.info = cmd.Info{
Name: "verb",
Args: "",
Purpose: "command purpose",
Doc: "command details",
}
}
func (s *CmdHelpSuite) assertHelp(c *gc.C, expected string) {
got := string(s.info.HelpWithSuperFlags(s.superfs, s.commandfs))
c.Check(got, gc.Equals, expected)
}
var noSuperOptions = `
Usage: verb [flags]
Summary:
command purpose
Flags:
--five (= "")
option-doc
--one (= "")
option-doc
--three (= "")
option-doc
Details:
command details
`[1:]
func (s *CmdHelpSuite) TestNoSuperOptionsWanted(c *gc.C) {
got := string(s.info.Help(s.commandfs))
c.Check(got, gc.Equals, noSuperOptions)
s.assertHelp(c, noSuperOptions)
}
func (s *CmdHelpSuite) TestSuperDoesNotHaveDesiredOptions(c *gc.C) {
s.info.ShowSuperFlags = []string{"wanted"}
s.assertHelp(c, noSuperOptions)
}
func (s *CmdHelpSuite) TestSuperHasOneDesiredOption(c *gc.C) {
s.info.ShowSuperFlags = []string{"captainamerica"}
s.assertHelp(c, `
Usage: verb [flags]
Summary:
command purpose
Global Flags:
--captainamerica (= "")
option-doc
Command Flags:
--five (= "")
option-doc
--one (= "")
option-doc
--three (= "")
option-doc
Details:
command details
`[1:])
}
func (s *CmdHelpSuite) TestSuperHasManyDesiredOptions(c *gc.C) {
s.superfs.FlagKnownAs = "option"
s.info.ShowSuperFlags = []string{"spiderman", "blackpanther"}
s.assertHelp(c, `
Usage: verb [flags]
Summary:
command purpose
Global Options:
--blackpanther (= "")
option-doc
--spiderman (= "")
option-doc
Command Flags:
--five (= "")
option-doc
--one (= "")
option-doc
--three (= "")
option-doc
Details:
command details
`[1:])
}
func (s *CmdHelpSuite) TestSuperShowsSubcommands(c *gc.C) {
s.info.Subcommands = map[string]string{
"application": "Wait for an application to reach a specified state.",
"machine": "Wait for a machine to reach a specified state.",
"model": "Wait for a model to reach a specified state.",
"unit": "Wait for a unit to reach a specified state.",
}
s.assertHelp(c, `
Usage: verb [flags]
Summary:
command purpose
Flags:
--five (= "")
option-doc
--one (= "")
option-doc
--three (= "")
option-doc
Details:
command details
Subcommands:
application - Wait for an application to reach a specified state.
machine - Wait for a machine to reach a specified state.
model - Wait for a model to reach a specified state.
unit - Wait for a unit to reach a specified state.
`[1:])
}
type CmdDocumentationSuite struct {
testing.LoggingCleanupSuite
targetCmd cmd.Command
}
golang-github-juju-cmd-3.0.14/cmdtesting/ 0000775 0000000 0000000 00000000000 14523706772 0020220 5 ustar 00root root 0000000 0000000 golang-github-juju-cmd-3.0.14/cmdtesting/cmd.go 0000664 0000000 0000000 00000010030 14523706772 0021304 0 ustar 00root root 0000000 0000000 // Copyright 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmdtesting
import (
"bytes"
"context"
"io/ioutil"
"github.com/juju/gnuflag"
gc "gopkg.in/check.v1"
"github.com/juju/cmd/v3"
)
// NewFlagSet creates a new flag set using the standard options, particularly
// the option to stop the gnuflag methods from writing to StdErr or StdOut.
func NewFlagSet() *gnuflag.FlagSet {
fs := gnuflag.NewFlagSet("", gnuflag.ContinueOnError)
fs.SetOutput(ioutil.Discard)
return fs
}
// InitCommand will create a new flag set, and call the Command's SetFlags and
// Init methods with the appropriate args.
func InitCommand(c cmd.Command, args []string) error {
f := gnuflag.NewFlagSetWithFlagKnownAs(c.Info().Name, gnuflag.ContinueOnError, cmd.FlagAlias(c, "flag"))
f.SetOutput(ioutil.Discard)
c.SetFlags(f)
if err := f.Parse(c.AllowInterspersedFlags(), args); err != nil {
return err
}
return c.Init(f.Args())
}
// Context creates a simple command execution context with the current
// dir set to a newly created directory within the test directory.
func Context(c *gc.C) *cmd.Context {
ctx := &cmd.Context{
Dir: c.MkDir(),
Stdin: &bytes.Buffer{},
Stdout: &bytes.Buffer{},
Stderr: &bytes.Buffer{},
}
ctx.Context = context.TODO()
return ctx
}
// ContextForDir creates a simple command execution context with the current
// dir set to the specified directory.
func ContextForDir(c *gc.C, dir string) *cmd.Context {
ctx := &cmd.Context{
Dir: dir,
Stdin: &bytes.Buffer{},
Stdout: &bytes.Buffer{},
Stderr: &bytes.Buffer{},
}
ctx.Context = context.TODO()
return ctx
}
// Stdout takes a command Context that we assume has been created in this
// package, and gets the content of the Stdout buffer as a string.
func Stdout(ctx *cmd.Context) string {
return ctx.Stdout.(*bytes.Buffer).String()
}
// Stderr takes a command Context that we assume has been created in this
// package, and gets the content of the Stderr buffer as a string.
func Stderr(ctx *cmd.Context) string {
return ctx.Stderr.(*bytes.Buffer).String()
}
// RunCommand runs a command with the specified args. The returned error
// may come from either the parsing of the args, the command initialisation, or
// the actual running of the command. Access to the resulting output streams
// is provided through the returned context instance.
func RunCommand(c *gc.C, com cmd.Command, args ...string) (*cmd.Context, error) {
var context = Context(c)
return runCommand(context, com, args)
}
// RunCommandInDir works like RunCommand, but runs with a context that uses dir.
func RunCommandInDir(c *gc.C, com cmd.Command, args []string, dir string) (*cmd.Context, error) {
var context = ContextForDir(c, dir)
return runCommand(context, com, args)
}
func runCommand(ctx *cmd.Context, com cmd.Command, args []string) (*cmd.Context, error) {
if err := InitCommand(com, args); err != nil {
cmd.WriteError(ctx.Stderr, err)
return ctx, err
}
return ctx, com.Run(ctx)
}
// RunCommandWithContext runs the command asynchronously with
// the specified context and returns a channel which providers
// the command's errors.
func RunCommandWithContext(ctx *cmd.Context, com cmd.Command, args ...string) chan error {
if ctx == nil {
panic("ctx == nil")
}
errc := make(chan error, 1)
go func() {
if err := InitCommand(com, args); err != nil {
errc <- err
return
}
errc <- com.Run(ctx)
}()
return errc
}
// TestInit checks that a command initialises correctly with the given set of
// arguments.
func TestInit(c *gc.C, com cmd.Command, args []string, errPat string) {
err := InitCommand(com, args)
if errPat != "" {
c.Assert(err, gc.ErrorMatches, errPat)
} else {
c.Assert(err, gc.IsNil)
}
}
// HelpText returns a command's formatted help text.
func HelpText(command cmd.Command, name string) string {
buff := &bytes.Buffer{}
info := command.Info()
info.Name = name
f := gnuflag.NewFlagSetWithFlagKnownAs(info.Name, gnuflag.ContinueOnError, cmd.FlagAlias(command, "flag"))
command.SetFlags(f)
buff.Write(info.Help(f))
return buff.String()
}
golang-github-juju-cmd-3.0.14/cmdtesting/package_test.go 0000664 0000000 0000000 00000000332 14523706772 0023177 0 ustar 00root root 0000000 0000000 // Copyright 2017 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmdtesting_test
import (
"testing"
gc "gopkg.in/check.v1"
)
func TestPackage(t *testing.T) {
gc.TestingT(t)
}
golang-github-juju-cmd-3.0.14/cmdtesting/prompt.go 0000664 0000000 0000000 00000013662 14523706772 0022100 0 ustar 00root root 0000000 0000000 // Copyright 2017 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmdtesting
import (
"io"
"strings"
"github.com/juju/errors"
"github.com/juju/loggo"
gc "gopkg.in/check.v1"
)
var logger = loggo.GetLogger("juju.cmd.testing")
// NewSeqPrompter returns a prompter that can be used to check a sequence of
// IO interactions. Expected input from the user is marked with the
// given user input marker (for example a distinctive unicode character
// that will not occur in the rest of the text) and runs to the end of a
// line.
//
// All output text in between user input is treated as regular expressions.
//
// As a special case, if an input marker is followed only by a single input
// marker on that line, the checker will cause io.EOF to be returned for
// that prompt.
//
// The returned SeqPrompter wraps a Prompter and checks that each
// read and write corresponds to the expected action in the sequence.
//
// After all interaction is done, CheckDone or AssertDone should be called to
// check that no more interactions are expected.
//
// Any failures will result in the test failing.
//
// For example given the prompter created with:
//
// checker := NewSeqPrompter(c, "»", `What is your name: »Bob
// And your age: »148
// You're .* old, Bob!
// `)
//
// The following code will pass the checker:
//
// fmt.Fprintf(checker, "What is your name: ")
// buf := make([]byte, 100)
// n, _ := checker.Read(buf)
// name := strings.TrimSpace(string(buf[0:n]))
// fmt.Fprintf(checker, "And your age: ")
// n, _ = checker.Read(buf)
// age, err := strconv.Atoi(strings.TrimSpace(string(buf[0:n])))
// c.Assert(err, gc.IsNil)
// if age > 90 {
// fmt.Fprintf(checker, "You're very old, %s!\n", name)
// }
// checker.CheckDone()
func NewSeqPrompter(c *gc.C, userInputMarker, text string) *SeqPrompter {
p := &SeqPrompter{
c: c,
}
for {
i := strings.Index(text, userInputMarker)
if i == -1 {
p.finalText = text
break
}
prompt := text[0:i]
text = text[i+len(userInputMarker):]
endLine := strings.Index(text, "\n")
if endLine == -1 {
c.Errorf("no newline found after expected input %q", text)
}
reply := text[0 : endLine+1]
if reply[0:len(reply)-1] == userInputMarker {
// EOF line.
reply = ""
}
text = text[endLine+1:]
if prompt == "" && len(p.ios) > 0 {
// Combine multiple contiguous inputs together.
p.ios[len(p.ios)-1].reply += reply
} else {
p.ios = append(p.ios, ioInteraction{
prompt: prompt,
reply: reply,
})
}
}
p.Prompter = NewPrompter(p.prompt)
return p
}
type SeqPrompter struct {
*Prompter
c *gc.C
ios []ioInteraction
finalText string
failed bool
}
type ioInteraction struct {
prompt string
reply string
}
func (p *SeqPrompter) prompt(text string) (string, error) {
if p.failed {
return "", errors.New("prompter failed")
}
if len(p.ios) == 0 {
p.c.Errorf("unexpected prompt %q; expected none", text)
return "", errors.New("unexpected prompt")
}
if !p.c.Check(text, gc.Matches, p.ios[0].prompt) {
p.failed = true
return "", errors.Errorf("unexpected prompt %q; expected %q", text, p.ios[0].prompt)
}
reply := p.ios[0].reply
logger.Infof("prompt %q -> %q", text, reply)
p.ios = p.ios[1:]
return reply, nil
}
// CheckDone asserts that all the expected prompts
// have been printed and all the replies read, and
// reports whether the check succeeded.
func (p *SeqPrompter) CheckDone() bool {
if p.failed {
// No point in doing the details checks if
// a prompt failed earlier - it just makes
// the resulting test failure noisy.
p.c.Errorf("prompter has failed")
return false
}
r := p.c.Check(p.ios, gc.HasLen, 0, gc.Commentf("unused prompts"))
r = p.c.Check(p.HasUnread(), gc.Equals, false, gc.Commentf("some input was not read")) && r
r = p.c.Check(p.Tail(), gc.Matches, p.finalText, gc.Commentf("final text mismatch")) && r
return r
}
// AssertDone is like CheckDone but aborts the test if
// the check fails.
func (p *SeqPrompter) AssertDone() {
if !p.CheckDone() {
p.c.FailNow()
}
}
// NewPrompter returns an io.ReadWriter implementation that calls the
// given function every time Read is called after some text has been
// written or if all the previously returned text has been read. The
// function's argument contains all the text printed since the last
// input. The function should return the text that the user is expected
// to type, or an error to return from Read. If it returns an empty string,
// and no error, it will return io.EOF instead.
func NewPrompter(prompt func(string) (string, error)) *Prompter {
return &Prompter{
prompt: prompt,
}
}
// Prompter is designed to be used in a cmd.Context to
// check interactive request-response sequences
// using stdin and stdout.
type Prompter struct {
prompt func(string) (string, error)
written []byte
allWritten []byte
pending []byte
pendingError error
}
// Tail returns all the text written since the last prompt.
func (p *Prompter) Tail() string {
return string(p.written)
}
// HasUnread reports whether any input
// from the last prompt remains unread.
func (p *Prompter) HasUnread() bool {
return len(p.pending) != 0
}
// Read implements io.Reader.
func (p *Prompter) Read(buf []byte) (int, error) {
if len(p.pending) == 0 && p.pendingError == nil {
s, err := p.prompt(string(p.written))
if s == "" && err == nil {
err = io.EOF
}
p.written = nil
p.pending = []byte(s)
p.pendingError = err
}
if len(p.pending) > 0 {
n := copy(buf, p.pending)
p.pending = p.pending[n:]
return n, nil
}
if err := p.pendingError; err != nil {
p.pendingError = nil
return 0, err
}
panic("unreachable")
}
// String returns all the text that has been written to
// the prompter since it was created.
func (p *Prompter) String() string {
return string(p.allWritten)
}
// Write implements io.Writer.
func (p *Prompter) Write(buf []byte) (int, error) {
p.written = append(p.written, buf...)
p.allWritten = append(p.allWritten, buf...)
return len(buf), nil
}
golang-github-juju-cmd-3.0.14/cmdtesting/prompt_test.go 0000664 0000000 0000000 00000006266 14523706772 0023141 0 ustar 00root root 0000000 0000000 // Copyright 2017 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmdtesting_test
import (
"fmt"
"io"
"strconv"
"strings"
"github.com/juju/errors"
"github.com/juju/testing"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
"github.com/juju/cmd/v3/cmdtesting"
)
type prompterSuite struct {
testing.IsolationSuite
}
var _ = gc.Suite(&prompterSuite{})
func (*prompterSuite) TestPrompter(c *gc.C) {
noPrompt := func(p string) (string, error) {
c.Fatalf("unpexected prompt (text %q)", p)
panic("unreachable")
}
promptFn := noPrompt
p := cmdtesting.NewPrompter(func(p string) (string, error) {
return promptFn(p)
})
promptText := "hello: "
promptReply := "reply\n"
fmt.Fprint(p, promptText)
promptFn = func(p string) (string, error) {
c.Assert(p, gc.Equals, promptText)
return promptReply, nil
}
c.Assert(readStr(c, p, 20), gc.Equals, promptReply)
promptText = "some text\ngoodbye: "
promptReply = "again\n"
fmt.Fprint(p, promptText[0:10])
fmt.Fprint(p, promptText[10:])
c.Assert(readStr(c, p, 3), gc.Equals, promptReply[0:3])
c.Assert(readStr(c, p, 20), gc.Equals, promptReply[3:])
fmt.Fprint(p, "final text\n")
c.Assert(p.Tail(), gc.Equals, "final text\n")
c.Assert(p.HasUnread(), gc.Equals, false)
}
func (*prompterSuite) TestUnreadInput(c *gc.C) {
p := cmdtesting.NewPrompter(func(s string) (string, error) {
return "hello world", nil
})
c.Assert(readStr(c, p, 3), gc.Equals, "hel")
c.Assert(p.HasUnread(), gc.Equals, true)
}
func (*prompterSuite) TestError(c *gc.C) {
expectErr := errors.New("something")
p := cmdtesting.NewPrompter(func(s string) (string, error) {
return "", expectErr
})
buf := make([]byte, 3)
n, err := p.Read(buf)
c.Assert(n, gc.Equals, 0)
c.Assert(err, gc.Equals, expectErr)
}
func (*prompterSuite) TestSeqPrompter(c *gc.C) {
p := cmdtesting.NewSeqPrompter(c, "»", `
hello: »reply
some text
goodbye: »again
final
`[1:])
fmt.Fprint(p, "hello: ")
c.Assert(readStr(c, p, 1), gc.Equals, "r")
c.Assert(readStr(c, p, 20), gc.Equals, "eply\n")
fmt.Fprint(p, "some text\n")
fmt.Fprint(p, "goodbye: ")
c.Assert(readStr(c, p, 20), gc.Equals, "again\n")
fmt.Fprint(p, "final\n")
p.AssertDone()
}
func (*prompterSuite) TestSeqPrompterEOF(c *gc.C) {
p := cmdtesting.NewSeqPrompter(c, "»", `
hello: »»
final
`[1:])
fmt.Fprint(p, "hello: ")
n, err := p.Read(make([]byte, 10))
c.Assert(n, gc.Equals, 0)
c.Assert(err, gc.Equals, io.EOF)
fmt.Fprint(p, "final\n")
p.AssertDone()
}
func (*prompterSuite) TestNewIOChecker(c *gc.C) {
checker := cmdtesting.NewSeqPrompter(c, "»", `What is your name: »Bob
»more
And your age: »148
You're .* old, Bob
more!
`)
fmt.Fprintf(checker, "What is your name: ")
buf := make([]byte, 100)
n, _ := checker.Read(buf)
name := strings.TrimSpace(string(buf[0:n]))
fmt.Fprintf(checker, "And your age: ")
n, _ = checker.Read(buf)
age, err := strconv.Atoi(strings.TrimSpace(string(buf[0:n])))
c.Assert(err, gc.IsNil)
if age > 90 {
fmt.Fprintf(checker, "You're very old, %s!\n", name)
}
checker.CheckDone()
}
func readStr(c *gc.C, r io.Reader, nb int) string {
buf := make([]byte, nb)
n, err := r.Read(buf)
c.Assert(err, jc.ErrorIsNil)
return string(buf[0:n])
}
golang-github-juju-cmd-3.0.14/documentation.go 0000664 0000000 0000000 00000035633 14523706772 0021271 0 ustar 00root root 0000000 0000000 // Copyright 2012-2022 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmd
import (
"bufio"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
"github.com/juju/gnuflag"
)
const (
DocumentationFileName = "documentation.md"
DocumentationIndexFileName = "index.md"
)
var doc string = `
This command generates a markdown formatted document with all the commands, their descriptions, arguments, and examples.
`
var documentationExamples = `
juju documentation
juju documentation --split
juju documentation --split --no-index --out /tmp/docs
To render markdown documentation using a list of existing
commands, you can use a file with the following syntax
command1: id1
command2: id2
commandN: idN
For example:
add-cloud: 1183
add-secret: 1284
remove-cloud: 4344
Then, the urls will be populated using the ids indicated
in the file above.
juju documentation --split --no-index --out /tmp/docs --discourse-ids /tmp/docs/myids
`
type documentationCommand struct {
CommandBase
super *SuperCommand
out string
noIndex bool
split bool
url string
idsPath string
// ids is contains a numeric id of every command
// add-cloud: 1112
// remove-user: 3333
// etc...
ids map[string]string
// reverseAliases maintains a reverse map of the alias and the
// targetting command. This is used to find the ids corresponding
// to a given alias
reverseAliases map[string]string
}
func newDocumentationCommand(s *SuperCommand) *documentationCommand {
return &documentationCommand{super: s}
}
func (c *documentationCommand) Info() *Info {
return &Info{
Name: "documentation",
Args: "--out --no-index --split --url --discourse-ids ",
Purpose: "Generate the documentation for all commands",
Doc: doc,
Examples: documentationExamples,
}
}
// SetFlags adds command specific flags to the flag set.
func (c *documentationCommand) SetFlags(f *gnuflag.FlagSet) {
f.StringVar(&c.out, "out", "", "Documentation output folder if not set the result is displayed using the standard output")
f.BoolVar(&c.noIndex, "no-index", false, "Do not generate the commands index")
f.BoolVar(&c.split, "split", false, "Generate a separate Markdown file for each command")
f.StringVar(&c.url, "url", "", "Documentation host URL")
f.StringVar(&c.idsPath, "discourse-ids", "", "File containing a mapping of commands and their discourse ids")
}
func (c *documentationCommand) Run(ctx *Context) error {
if c.split {
if c.out == "" {
return errors.New("when using --split, you must set the output folder using --out=")
}
return c.dumpSeveralFiles()
}
return c.dumpOneFile(ctx)
}
// dumpOneFile is invoked when the output is contained in a single output
func (c *documentationCommand) dumpOneFile(ctx *Context) error {
var writer io.Writer
if c.out != "" {
_, err := os.Stat(c.out)
if err != nil {
return err
}
target := fmt.Sprintf("%s/%s", c.out, DocumentationFileName)
f, err := os.Create(target)
if err != nil {
return err
}
defer f.Close()
writer = f
} else {
writer = ctx.Stdout
}
return c.dumpEntries(writer)
}
// getSortedListCommands returns an array with the sorted list of
// command names
func (c *documentationCommand) getSortedListCommands() []string {
// sort the commands
sorted := make([]string, len(c.super.subcmds))
i := 0
for k := range c.super.subcmds {
sorted[i] = k
i++
}
sort.Strings(sorted)
return sorted
}
func (c *documentationCommand) computeReverseAliases() {
c.reverseAliases = make(map[string]string)
for name, content := range c.super.subcmds {
for _, alias := range content.command.Info().Aliases {
c.reverseAliases[alias] = name
}
}
}
// dumpSeveralFiles is invoked when every command is dumped into
// a separated entity
func (c *documentationCommand) dumpSeveralFiles() error {
if len(c.super.subcmds) == 0 {
fmt.Printf("No commands found for %s", c.super.Name)
return nil
}
// Attempt to create output directory. This will fail if:
// - we don't have permission to create the dir
// - a file already exists at the given path
err := os.MkdirAll(c.out, os.ModePerm)
if err != nil {
return err
}
if c.idsPath != "" {
// get the list of ids
c.ids, err = c.readFileIds(c.idsPath)
if err != nil {
return err
}
}
// create index if indicated
if !c.noIndex {
target := fmt.Sprintf("%s/%s", c.out, DocumentationIndexFileName)
f, err := os.Create(target)
if err != nil {
return err
}
writer := bufio.NewWriter(f)
_, err = writer.WriteString(c.commandsIndex())
if err != nil {
return err
}
f.Close()
}
return c.writeDocs(c.out, []string{c.super.Name}, true)
}
// writeDocs (recursively) writes docs for all commands in the given folder.
func (c *documentationCommand) writeDocs(folder string, superCommands []string, printDefaultCommands bool) error {
c.computeReverseAliases()
for name, ref := range c.super.subcmds {
if !printDefaultCommands && isDefaultCommand(name) {
continue
}
commandSeq := append(superCommands, name)
sc, isSuperCommand := ref.command.(*SuperCommand)
if !isSuperCommand || (isSuperCommand && !sc.SkipCommandDoc) {
target := fmt.Sprintf("%s.md", strings.Join(commandSeq[1:], "_"))
if err := c.writeDoc(folder, target, ref, commandSeq); err != nil {
return err
}
}
// Handle subcommands
if !isSuperCommand {
continue
}
if err := sc.documentation.writeDocs(folder, commandSeq, false); err != nil {
return err
}
}
return nil
}
func (c *documentationCommand) writeDoc(folder, target string, ref commandReference, commandSeq []string) error {
target = strings.ReplaceAll(target, " ", "_")
target = filepath.Join(folder, target)
f, err := os.Create(target)
if err != nil {
return err
}
defer func() { _ = f.Close() }()
formatted := c.formatCommand(ref, false, commandSeq)
if _, err = fmt.Fprintln(f, formatted); err != nil {
return err
}
_ = f.Sync()
return nil
}
func (c *documentationCommand) readFileIds(path string) (map[string]string, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
reader := bufio.NewScanner(f)
ids := make(map[string]string)
for reader.Scan() {
line := reader.Text()
items := strings.Split(line, ":")
if len(items) != 2 {
return nil, fmt.Errorf("malformed line [%s]", line)
}
command := strings.TrimSpace(items[0])
id := strings.TrimSpace(items[1])
ids[command] = id
}
return ids, nil
}
func (c *documentationCommand) dumpEntries(w io.Writer) error {
if len(c.super.subcmds) == 0 {
fmt.Printf("No commands found for %s", c.super.Name)
return nil
}
if !c.noIndex {
_, err := fmt.Fprintf(w, "%s", c.commandsIndex())
if err != nil {
return err
}
}
return c.writeSections(w, []string{c.super.Name}, true)
}
// writeSections (recursively) writes sections for all commands to the given file.
func (c *documentationCommand) writeSections(w io.Writer, superCommands []string, printDefaultCommands bool) error {
sorted := c.getSortedListCommands()
for _, name := range sorted {
if !printDefaultCommands && isDefaultCommand(name) {
continue
}
ref := c.super.subcmds[name]
commandSeq := append(superCommands, name)
// This is a bit messy, because we want to keep the order of the
// documentation the same.
sc, isSuperCommand := ref.command.(*SuperCommand)
if !isSuperCommand || (isSuperCommand && !sc.SkipCommandDoc) {
if _, err := fmt.Fprintf(w, "%s", c.formatCommand(ref, true, commandSeq)); err != nil {
return err
}
}
// Handle subcommands
if !isSuperCommand {
continue
}
if err := sc.documentation.writeSections(w, commandSeq, false); err != nil {
return err
}
}
return nil
}
func (c *documentationCommand) commandsIndex() string {
index := "# Index\n"
listCommands := c.getSortedListCommands()
for id, name := range listCommands {
if isDefaultCommand(name) {
continue
}
index += fmt.Sprintf("%d. [%s](%s)\n", id, name, c.linkForCommand(name))
// TODO: handle subcommands ??
}
index += "---\n\n"
return index
}
// Return the URL/location for the given command
func (c *documentationCommand) linkForCommand(cmd string) string {
prefix := "#"
if c.ids != nil {
prefix = "/t/"
}
if c.url != "" {
prefix = c.url + "/"
}
target, err := c.getTargetCmd(cmd)
if err != nil {
fmt.Printf("[ERROR] command [%s] has no id, please add it to the list\n", cmd)
return ""
}
return prefix + target
}
// formatCommand returns a string representation of the information contained
// by a command in Markdown format. The title param can be used to set
// whether the command name should be a title or not. This is particularly
// handy when splitting the commands in different files.
func (c *documentationCommand) formatCommand(ref commandReference, title bool, commandSeq []string) string {
formatted := ""
if title {
formatted = "# " + strings.ToUpper(strings.Join(commandSeq[1:], " ")) + "\n"
}
var info *Info
if ref.name == "documentation" {
info = c.Info()
} else {
info = ref.command.Info()
}
// See Also
if len(info.SeeAlso) > 0 {
formatted += "> See also: "
prefix := "#"
if c.ids != nil {
prefix = "/t/"
}
if c.url != "" {
prefix = c.url + "t/"
}
for i, s := range info.SeeAlso {
target, err := c.getTargetCmd(s)
if err != nil {
fmt.Println(err.Error())
}
formatted += fmt.Sprintf("[%s](%s%s)", s, prefix, target)
if i < len(info.SeeAlso)-1 {
formatted += ", "
}
}
formatted += "\n"
}
if ref.alias != "" {
formatted += "**Alias:** " + ref.alias + "\n"
}
if ref.check != nil && ref.check.Obsolete() {
formatted += "*This command is deprecated*\n"
}
formatted += "\n"
// Summary
formatted += "## Summary\n" + info.Purpose + "\n\n"
// Usage
if strings.TrimSpace(info.Args) != "" {
formatted += fmt.Sprintf(`## Usage
`+"```"+`%s [options] %s`+"```"+`
`, strings.Join(commandSeq, " "), info.Args)
}
// Options
formattedFlags := c.formatFlags(ref.command, info)
if len(formattedFlags) > 0 {
formatted += "### Options\n" + formattedFlags + "\n"
}
// Examples
examples := info.Examples
if strings.TrimSpace(examples) != "" {
formatted += "## Examples\n" + examples + "\n\n"
}
// Details
doc := EscapeMarkdown(info.Doc)
if strings.TrimSpace(doc) != "" {
formatted += "## Details\n" + doc + "\n\n"
}
formatted += c.formatSubcommands(info.Subcommands, commandSeq)
formatted += "---\n\n"
return formatted
}
// getTargetCmd is an auxiliary function that returns the target command or
// the corresponding id if available.
func (d *documentationCommand) getTargetCmd(cmd string) (string, error) {
// no ids were set, return the original command
if d.ids == nil {
return cmd, nil
}
target, found := d.ids[cmd]
if found {
return target, nil
} else {
// check if this is an alias
targetCmd, found := d.reverseAliases[cmd]
fmt.Printf("use alias %s -> %s\n", cmd, targetCmd)
if !found {
// if we're working with ids, and we have to mmake the translation,
// we need to have an id per every requested command
return "", fmt.Errorf("requested id for command %s was not found", cmd)
}
return targetCmd, nil
}
}
// formatFlags is an internal formatting solution similar to
// the gnuflag.PrintDefaults. The code is extended here
// to permit additional formatting without modifying the
// gnuflag package.
func (d *documentationCommand) formatFlags(c Command, info *Info) string {
flagsAlias := FlagAlias(c, "")
if flagsAlias == "" {
// For backward compatibility, the default is 'flag'.
flagsAlias = "flag"
}
f := gnuflag.NewFlagSetWithFlagKnownAs(info.Name, gnuflag.ContinueOnError, flagsAlias)
// if we are working with the documentation command,
// we have to set flags on a new instance, otherwise
// we will overwrite the current flag values
if info.Name != "documentation" {
c.SetFlags(f)
} else {
c = newDocumentationCommand(d.super)
c.SetFlags(f)
}
// group together all flags for a given value
flags := make(map[interface{}]flagsByLength)
f.VisitAll(func(f *gnuflag.Flag) {
flags[f.Value] = append(flags[f.Value], f)
})
if len(flags) == 0 {
return ""
}
// sort the output flags by shortest name for each group.
var byName flagsByName
for _, fl := range flags {
sort.Sort(fl)
byName = append(byName, fl)
}
sort.Sort(byName)
formatted := "| Flag | Default | Usage |\n"
formatted += "| --- | --- | --- |\n"
for _, fs := range byName {
theFlags := ""
for i, f := range fs {
if i > 0 {
theFlags += ", "
}
theFlags += fmt.Sprintf("`--%s`", f.Name)
}
formatted += fmt.Sprintf("| %s | %s | %s |\n", theFlags,
EscapeMarkdown(fs[0].DefValue),
strings.ReplaceAll(EscapeMarkdown(fs[0].Usage), "\n", " "),
)
}
return formatted
}
// flagsByLength is a slice of flags implementing sort.Interface,
// sorting primarily by the length of the flag, and secondarily
// alphabetically.
type flagsByLength []*gnuflag.Flag
func (f flagsByLength) Less(i, j int) bool {
s1, s2 := f[i].Name, f[j].Name
if len(s1) != len(s2) {
return len(s1) < len(s2)
}
return s1 < s2
}
func (f flagsByLength) Swap(i, j int) {
f[i], f[j] = f[j], f[i]
}
func (f flagsByLength) Len() int {
return len(f)
}
// flagsByName is a slice of slices of flags implementing sort.Interface,
// alphabetically sorting by the name of the first flag in each slice.
type flagsByName [][]*gnuflag.Flag
func (f flagsByName) Less(i, j int) bool {
return f[i][0].Name < f[j][0].Name
}
func (f flagsByName) Swap(i, j int) {
f[i], f[j] = f[j], f[i]
}
func (f flagsByName) Len() int {
return len(f)
}
// EscapeMarkdown returns a copy of the input string, in which any special
// Markdown characters (e.g. < > |) are escaped.
func EscapeMarkdown(raw string) string {
escapeSeqs := map[rune]string{
'<': "<",
'>': ">",
'&': "&",
'|': "|",
}
var escaped strings.Builder
escaped.Grow(len(raw))
lines := strings.Split(raw, "\n")
for i, line := range lines {
if strings.HasPrefix(line, " ") {
// Literal code block - don't escape anything
escaped.WriteString(line)
} else {
// Keep track of whether we are inside a code span `...`
// If so, don't escape characters
insideCodeSpan := false
for _, c := range line {
if c == '`' {
insideCodeSpan = !insideCodeSpan
}
if !insideCodeSpan {
if escapeSeq, ok := escapeSeqs[c]; ok {
escaped.WriteString(escapeSeq)
continue
}
}
escaped.WriteRune(c)
}
}
if i < len(lines)-1 {
escaped.WriteRune('\n')
}
}
return escaped.String()
}
func (c *documentationCommand) formatSubcommands(subcommands map[string]string, commandSeq []string) string {
var output string
sorted := []string{}
for name := range subcommands {
if isDefaultCommand(name) {
continue
}
sorted = append(sorted, name)
}
sort.Strings(sorted)
if len(sorted) > 0 {
output += "## Subcommands\n"
for _, name := range sorted {
output += fmt.Sprintf("- [%s](%s)\n", name,
c.linkForCommand(strings.Join(append(commandSeq[1:], name), "_")))
}
output += "\n"
}
return output
}
golang-github-juju-cmd-3.0.14/documentation_test.go 0000664 0000000 0000000 00000013060 14523706772 0022316 0 ustar 00root root 0000000 0000000 package cmd_test
import (
"fmt"
"github.com/juju/gnuflag"
gc "gopkg.in/check.v1"
"github.com/juju/cmd/v3"
)
type documentationSuite struct{}
var _ = gc.Suite(&documentationSuite{})
func (s *documentationSuite) TestFormatCommand(c *gc.C) {
tests := []struct {
command cmd.Command
title bool
expected string
}{{
// "smoke test" - just a regular command
command: &docTestCommand{
info: &cmd.Info{
Name: "add-cloud",
Args: " []",
Purpose: "summary for add-cloud...",
Doc: "details for add-cloud...",
Examples: "examples for add-cloud...",
SeeAlso: []string{"clouds", "update-cloud", "remove-cloud", "update-credential"},
Aliases: []string{"cloud-add", "import-cloud"},
},
flags: []string{"force", "format", "output"},
},
title: false,
expected: (`
> See also: [clouds](#clouds), [update-cloud](#update-cloud), [remove-cloud](#remove-cloud), [update-credential](#update-credential)
## Summary
summary for add-cloud...
## Usage
` + "```" + `juju add-cloud [options] []` + "```" + `
### Options
| Flag | Default | Usage |
| --- | --- | --- |
| ` + "`" + `--force` + "`" + ` | default value for "force" flag | description for "force" flag |
| ` + "`" + `--format` + "`" + ` | default value for "format" flag | description for "format" flag |
| ` + "`" + `--output` + "`" + ` | default value for "output" flag | description for "output" flag |
## Examples
examples for add-cloud...
## Details
details for add-cloud...
---
`)[1:],
}, {
// no flags - don't print "Options" table
command: &docTestCommand{
info: &cmd.Info{
Name: "foo",
Args: "",
Purpose: "insert summary here...",
Doc: "insert details here...",
Examples: "insert examples here...",
},
flags: []string{},
},
title: false,
expected: (`
## Summary
insert summary here...
## Usage
` + "```" + `juju foo [options] ` + "```" + `
## Examples
insert examples here...
## Details
insert details here...
---
`)[1:],
}}
for _, t := range tests {
output := cmd.FormatCommand(
t.command,
&cmd.SuperCommand{Name: "juju"},
t.title,
[]string{"juju", t.command.Info().Name},
)
c.Check(output, gc.Equals, t.expected)
}
}
// docTestCommand is a fake implementation of cmd.Command, used for testing
// documentation output.
type docTestCommand struct {
info *cmd.Info
flags []string
}
func (c *docTestCommand) Info() *cmd.Info {
return c.info
}
func (c *docTestCommand) SetFlags(f *gnuflag.FlagSet) {
for _, flag := range c.flags {
f.String(flag,
fmt.Sprintf("default value for %q flag", flag),
fmt.Sprintf("description for %q flag", flag))
}
}
func (c *docTestCommand) IsSuperCommand() bool { return false }
func (c *docTestCommand) Init(args []string) error { return nil }
func (c *docTestCommand) Run(ctx *cmd.Context) error { return nil }
func (c *docTestCommand) AllowInterspersedFlags() bool { return false }
func (*documentationSuite) TestEscapeMarkdown(c *gc.C) {
tests := []struct {
input, output string
}{{
input: `
Juju needs to know how to connect to clouds. A cloud definition
describes a cloud's endpoints and authentication requirements. Each
definition is stored and accessed later as .
If you are accessing a public cloud, running add-cloud is unlikely to be
necessary. Juju already contains definitions for the public cloud
providers it supports.
add-cloud operates in two modes:
juju add-cloud
juju add-cloud
`,
output: `
Juju needs to know how to connect to clouds. A cloud definition
describes a cloud's endpoints and authentication requirements. Each
definition is stored and accessed later as <cloud name>.
If you are accessing a public cloud, running add-cloud is unlikely to be
necessary. Juju already contains definitions for the public cloud
providers it supports.
add-cloud operates in two modes:
juju add-cloud
juju add-cloud
`,
}, {
input: "Specify output format (default|json|tabular|yaml)",
output: "Specify output format (default|json|tabular|yaml)",
}, {
input: "Model to operate in. Accepts [:]|",
output: "Model to operate in. Accepts [<controller name>:]<model name>|<model UUID>",
}, {
input: "The following characters are inside a code span, so they shouldn't be escaped: `< > | &`",
output: "The following characters are inside a code span, so they shouldn't be escaped: `< > | &`",
}, {
input: `
The juju add-credential command operates in two modes.
When called with only the argument, ` + "`" + `juju add-credential` + "`" + ` will
take you through an interactive prompt to add a credential specific to
the cloud provider.
Providing the ` + "`" + `-f ` + "`" + ` option switches to the
non-interactive mode. must be a path to a correctly
formatted YAML-formatted file.
`,
output: `
The juju add-credential command operates in two modes.
When called with only the <cloud name> argument, ` + "`" + `juju add-credential` + "`" + ` will
take you through an interactive prompt to add a credential specific to
the cloud provider.
Providing the ` + "`" + `-f ` + "`" + ` option switches to the
non-interactive mode. <credentials.yaml> must be a path to a correctly
formatted YAML-formatted file.
`,
}}
for _, t := range tests {
c.Check(cmd.EscapeMarkdown(t.input), gc.Equals, t.output)
}
}
golang-github-juju-cmd-3.0.14/export_test.go 0000664 0000000 0000000 00000000740 14523706772 0020767 0 ustar 00root root 0000000 0000000 // Copyright 2017 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmd
func NewVersionCommand(version string, versionDetail interface{}) Command {
return newVersionCommand(version, versionDetail)
}
func FormatCommand(command Command, super *SuperCommand, title bool, commandSeq []string) string {
docCmd := &documentationCommand{super: super}
ref := commandReference{command: command}
return docCmd.formatCommand(ref, title, commandSeq)
}
golang-github-juju-cmd-3.0.14/filevar.go 0000664 0000000 0000000 00000003374 14523706772 0020045 0 ustar 00root root 0000000 0000000 // Copyright 2012, 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmd
import (
"errors"
"io"
"io/ioutil"
"os"
"github.com/juju/utils/v3"
)
// FileVar represents a path to a file.
type FileVar struct {
// Path is the path to the file.
Path string
// StdinMarkers are the Path values that should be interpreted as
// stdin. If it is empty then stdin is not supported.
StdinMarkers []string
}
var ErrNoPath = errors.New("path not set")
// Set stores the chosen path name in f.Path.
func (f *FileVar) Set(v string) error {
f.Path = v
return nil
}
// SetStdin sets StdinMarkers to the provided strings. If none are
// provided then the default of "-" is used.
func (f *FileVar) SetStdin(markers ...string) {
if len(markers) == 0 {
markers = append(markers, "-")
}
f.StdinMarkers = markers
}
// IsStdin determines whether or not the path represents stdin.
func (f FileVar) IsStdin() bool {
for _, marker := range f.StdinMarkers {
if f.Path == marker {
return true
}
}
return false
}
// Open opens the file.
func (f *FileVar) Open(ctx *Context) (io.ReadCloser, error) {
if f.Path == "" {
return nil, ErrNoPath
}
if f.IsStdin() {
return ioutil.NopCloser(ctx.Stdin), nil
}
path, err := utils.NormalizePath(f.Path)
if err != nil {
return nil, err
}
return os.Open(ctx.AbsPath(path))
}
// Read returns the contents of the file.
func (f *FileVar) Read(ctx *Context) ([]byte, error) {
if f.Path == "" {
return nil, ErrNoPath
}
if f.IsStdin() {
return ioutil.ReadAll(ctx.Stdin)
}
path, err := utils.NormalizePath(f.Path)
if err != nil {
return nil, err
}
return ioutil.ReadFile(ctx.AbsPath(path))
}
// String returns the path to the file.
func (f *FileVar) String() string {
return f.Path
}
golang-github-juju-cmd-3.0.14/filevar_test.go 0000664 0000000 0000000 00000011525 14523706772 0021101 0 ustar 00root root 0000000 0000000 // Copyright 2012, 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmd_test
import (
"bytes"
"io"
"io/ioutil"
"os"
"path/filepath"
"github.com/juju/gnuflag"
gitjujutesting "github.com/juju/testing"
jc "github.com/juju/testing/checkers"
"github.com/juju/utils/v3"
gc "gopkg.in/check.v1"
"github.com/juju/cmd/v3"
"github.com/juju/cmd/v3/cmdtesting"
)
type FileVarSuite struct {
gitjujutesting.FakeHomeSuite
ctx *cmd.Context
ValidPath string
InvalidPath string // invalid path refers to a file which is not readable
}
var _ = gc.Suite(&FileVarSuite{})
func (s *FileVarSuite) SetUpTest(c *gc.C) {
s.FakeHomeSuite.SetUpTest(c)
s.ctx = cmdtesting.Context(c)
s.ValidPath = s.ctx.AbsPath("valid.yaml")
s.InvalidPath = s.ctx.AbsPath("invalid.yaml")
f, err := os.Create(s.ValidPath)
c.Assert(err, gc.IsNil)
f.Close()
f, err = os.Create(s.InvalidPath)
c.Assert(err, gc.IsNil)
f.Close()
err = os.Chmod(s.InvalidPath, 0) // make unreadable
c.Assert(err, gc.IsNil)
}
func (s *FileVarSuite) TestSetStdin(c *gc.C) {
var config cmd.FileVar
c.Assert(config.Path, gc.Equals, "")
c.Assert(config.StdinMarkers, jc.DeepEquals, []string{})
config.SetStdin()
c.Assert(config.Path, gc.Equals, "")
c.Assert(config.StdinMarkers, jc.DeepEquals, []string{"-"})
config.SetStdin("<>", "@")
c.Assert(config.Path, gc.Equals, "")
c.Assert(config.StdinMarkers, jc.DeepEquals, []string{"<>", "@"})
}
func (s *FileVarSuite) TestIsStdin(c *gc.C) {
var config cmd.FileVar
c.Check(config.IsStdin(), jc.IsFalse)
config.StdinMarkers = []string{"-"}
c.Check(config.IsStdin(), jc.IsFalse)
config.Path = "spam"
c.Check(config.IsStdin(), jc.IsFalse)
config.Path = "-"
c.Check(config.IsStdin(), jc.IsTrue)
config.StdinMarkers = nil
c.Check(config.IsStdin(), jc.IsFalse)
config.StdinMarkers = []string{"<>", "@"}
c.Check(config.IsStdin(), jc.IsFalse)
config.Path = "<>"
c.Check(config.IsStdin(), jc.IsTrue)
config.Path = "@"
c.Check(config.IsStdin(), jc.IsTrue)
}
func (FileVarSuite) checkOpen(c *gc.C, file io.ReadCloser, expected string) {
defer file.Close()
data, err := ioutil.ReadAll(file)
c.Assert(err, jc.ErrorIsNil)
c.Check(string(data), gc.Equals, expected)
}
func (s *FileVarSuite) TestOpenTilde(c *gc.C) {
path := filepath.Join(utils.Home(), "config.yaml")
err := ioutil.WriteFile(path, []byte("abc"), 0644)
c.Assert(err, gc.IsNil)
var config cmd.FileVar
config.Set("~/config.yaml")
file, err := config.Open(s.ctx)
c.Assert(err, gc.IsNil)
s.checkOpen(c, file, "abc")
}
func (s *FileVarSuite) TestOpenStdin(c *gc.C) {
s.ctx.Stdin = bytes.NewBufferString("abc")
var config cmd.FileVar
config.SetStdin()
config.Set("-")
file, err := config.Open(s.ctx)
c.Assert(err, gc.IsNil)
s.checkOpen(c, file, "abc")
}
func (s *FileVarSuite) TestOpenNotStdin(c *gc.C) {
var config cmd.FileVar
config.Set("-")
_, err := config.Open(s.ctx)
c.Check(err, jc.Satisfies, os.IsNotExist)
}
func (s *FileVarSuite) TestOpenValid(c *gc.C) {
fs, config := fs()
err := fs.Parse(false, []string{"--config", s.ValidPath})
c.Assert(err, gc.IsNil)
c.Assert(config.Path, gc.Equals, s.ValidPath)
_, err = config.Open(s.ctx)
c.Assert(err, gc.IsNil)
}
func (s *FileVarSuite) TestOpenInvalid(c *gc.C) {
fs, config := fs()
err := fs.Parse(false, []string{"--config", s.InvalidPath})
c.Assert(config.Path, gc.Equals, s.InvalidPath)
_, err = config.Open(s.ctx)
c.Assert(err, gc.ErrorMatches, "*permission denied")
}
func (s *FileVarSuite) TestReadTilde(c *gc.C) {
path := filepath.Join(utils.Home(), "config.yaml")
err := ioutil.WriteFile(path, []byte("abc"), 0644)
c.Assert(err, gc.IsNil)
var config cmd.FileVar
config.Set("~/config.yaml")
file, err := config.Read(s.ctx)
c.Assert(err, gc.IsNil)
c.Assert(string(file), gc.Equals, "abc")
}
func (s *FileVarSuite) TestReadStdin(c *gc.C) {
s.ctx.Stdin = bytes.NewBufferString("abc")
var config cmd.FileVar
config.SetStdin()
config.Set("-")
file, err := config.Read(s.ctx)
c.Assert(err, gc.IsNil)
c.Assert(string(file), gc.Equals, "abc")
}
func (s *FileVarSuite) TestReadNotStdin(c *gc.C) {
var config cmd.FileVar
config.Set("-")
_, err := config.Read(s.ctx)
c.Check(err, jc.Satisfies, os.IsNotExist)
}
func (s *FileVarSuite) TestReadValid(c *gc.C) {
fs, config := fs()
err := fs.Parse(false, []string{"--config", s.ValidPath})
c.Assert(err, gc.IsNil)
c.Assert(config.Path, gc.Equals, s.ValidPath)
_, err = config.Read(s.ctx)
c.Assert(err, gc.IsNil)
}
func (s *FileVarSuite) TestReadInvalid(c *gc.C) {
fs, config := fs()
err := fs.Parse(false, []string{"--config", s.InvalidPath})
c.Assert(config.Path, gc.Equals, s.InvalidPath)
_, err = config.Read(s.ctx)
c.Assert(err, gc.ErrorMatches, "*permission denied")
}
func fs() (*gnuflag.FlagSet, *cmd.FileVar) {
var config cmd.FileVar
fs := cmdtesting.NewFlagSet()
fs.Var(&config, "config", "the config")
return fs, &config
}
golang-github-juju-cmd-3.0.14/go.mod 0000664 0000000 0000000 00000002267 14523706772 0017174 0 ustar 00root root 0000000 0000000 module github.com/juju/cmd/v3
go 1.17
require (
github.com/juju/ansiterm v0.0.0-20210706145210-9283cdf370b5
github.com/juju/errors v0.0.0-20220203013757-bd733f3c86b9
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d
github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4
github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494
github.com/juju/utils/v3 v3.0.0-20220203023959-c3fbc78a33b0
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
gopkg.in/yaml.v2 v2.4.0
)
require (
github.com/juju/clock v0.0.0-20220203021603-d9deb868a28a // indirect
github.com/juju/collections v0.0.0-20220203020748-febd7cad8a7a // indirect
github.com/juju/mgo/v2 v2.0.0-20210302023703-70d5d206e208 // indirect
github.com/juju/retry v0.0.0-20180821225755-9058e192b216 // indirect
github.com/juju/version/v2 v2.0.0-20211007103408-2e8da085dc23 // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lunixbochs/vtclean v1.0.0 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
)
golang-github-juju-cmd-3.0.14/go.sum 0000664 0000000 0000000 00000043710 14523706772 0017217 0 ustar 00root root 0000000 0000000 github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/ChrisTrenkamp/goxpath v0.0.0-20210404020558-97928f7e12b6/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/juju/ansiterm v0.0.0-20160907234532-b99631de12cf/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
github.com/juju/ansiterm v0.0.0-20210706145210-9283cdf370b5 h1:Q5klzs6BL5FkassBX65t+KkG0XjYcjxEm+GNcQAsuaw=
github.com/juju/ansiterm v0.0.0-20210706145210-9283cdf370b5/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA=
github.com/juju/clock v0.0.0-20220202072423-1b0f830854c4/go.mod h1:zDZCPSgCJQINeZtQwHx2/cFk4seaBC8Yiqe8V82xiP0=
github.com/juju/clock v0.0.0-20220203021603-d9deb868a28a h1:Az/6CM/P5guGHNy7r6TkOCctv3lDmN3W1uhku7QMupk=
github.com/juju/clock v0.0.0-20220203021603-d9deb868a28a/go.mod h1:GZ/FY8Cqw3KHG6DwRVPUKbSPTAwyrU28xFi5cqZnLsc=
github.com/juju/cmd v0.0.0-20171107070456-e74f39857ca0 h1:kMNSBOBQHgDocCDaItn5Gw/nR6P1RwqB/QyLtINvAMY=
github.com/juju/cmd v0.0.0-20171107070456-e74f39857ca0/go.mod h1:yWJQHl73rdSX4DHVKGqkAip+huBslxRwS8m9CrOLq18=
github.com/juju/cmd/v3 v3.0.0-20220202061353-b1cc80b193b0/go.mod h1:EoGJiEG+vbMwO9l+Es0SDTlaQPjH6nLcnnc4NfZB3cY=
github.com/juju/collections v0.0.0-20200605021417-0d0ec82b7271/go.mod h1:5XgO71dV1JClcOJE+4dzdn4HrI5LiyKd7PlVG6eZYhY=
github.com/juju/collections v0.0.0-20220203020748-febd7cad8a7a h1:d7eZO8OS/ZXxdP0uq3E8CdoA1qNFaecAv90UxrxaY2k=
github.com/juju/collections v0.0.0-20220203020748-febd7cad8a7a/go.mod h1:JWeZdyttIEbkR51z2S13+J+aCuHVe0F6meRy+P0YGDo=
github.com/juju/errors v0.0.0-20150916125642-1b5e39b83d18/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/juju/errors v0.0.0-20200330140219-3fe23663418f/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/juju/errors v0.0.0-20210818161939-5560c4c073ff/go.mod h1:i1eL7XREII6aHpQ2gApI/v6FkVUDEBremNkcBCKYAcY=
github.com/juju/errors v0.0.0-20220203013757-bd733f3c86b9 h1:EJHbsNpQyupmMeWTq7inn+5L/WZ7JfzCVPJ+DP9McCQ=
github.com/juju/errors v0.0.0-20220203013757-bd733f3c86b9/go.mod h1:TRm7EVGA3mQOqSVcBySRY7a9Y1/gyVhh/WTCnc5sD4U=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d h1:c93kUJDtVAXFEhsCh5jSxyOJmFHuzcihnslQiX8Urwo=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/juju/httpprof v0.0.0-20141217160036-14bf14c30767/go.mod h1:+MaLYz4PumRkkyHYeXJ2G5g5cIW0sli2bOfpmbaMV/g=
github.com/juju/loggo v0.0.0-20170605014607-8232ab8918d9/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
github.com/juju/loggo v0.0.0-20200526014432-9ce3a2e09b5e/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4 h1:NO5tuyw++EGLnz56Q8KMyDZRwJwWO8jQnj285J3FOmY=
github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4/go.mod h1:NIXFioti1SmKAlKNuUwbMenNdef59IF52+ZzuOmHYkg=
github.com/juju/mgo/v2 v2.0.0-20210302023703-70d5d206e208 h1:/WiCm+Vpj87e4QWuWwPD/bNE9kDrWCLvPBHOQNcG2+A=
github.com/juju/mgo/v2 v2.0.0-20210302023703-70d5d206e208/go.mod h1:0OChplkvPTZ174D2FYZXg4IB9hbEwyHkD+zT+/eK+Fg=
github.com/juju/mutex v0.0.0-20171110020013-1fe2a4bf0a3a/go.mod h1:Y3oOzHH8CQ0Ppt0oCKJ2JFO81/EsWenH5AEqigLH+yY=
github.com/juju/mutex/v2 v2.0.0-20220128011612-57176ebdcfa3/go.mod h1:TTCG9BJD9rCC4DZFz3jA0QvCqFDHw8Eqz0jstwY7RTQ=
github.com/juju/mutex/v2 v2.0.0-20220203023141-11eeddb42c6c/go.mod h1:jwCfBs/smYDaeZLqeaCi8CB8M+tOes4yf827HoOEoqk=
github.com/juju/retry v0.0.0-20151029024821-62c620325291/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4=
github.com/juju/retry v0.0.0-20180821225755-9058e192b216 h1:/eQL7EJQKFHByJe3DeE8Z36yqManj9UY5zppDoQi4FU=
github.com/juju/retry v0.0.0-20180821225755-9058e192b216/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4=
github.com/juju/testing v0.0.0-20180402130637-44801989f0f7/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
github.com/juju/testing v0.0.0-20180517134105-72703b1e95eb/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
github.com/juju/testing v0.0.0-20210302031854-2c7ee8570c07/go.mod h1:7lxZW0B50+xdGFkvhAb8bwAGt6IU87JB1H9w4t8MNVM=
github.com/juju/testing v0.0.0-20210324180055-18c50b0c2098/go.mod h1:7lxZW0B50+xdGFkvhAb8bwAGt6IU87JB1H9w4t8MNVM=
github.com/juju/testing v0.0.0-20220202055744-1ad0816210a6/go.mod h1:QgWc2UdIPJ8t3rnvv95tFNOsQDfpXYEZDbP281o3b2c=
github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494 h1:XEDzpuZb8Ma7vLja3+5hzUqVTvAqm5Y+ygvnDs5iTMM=
github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494/go.mod h1:rUquetT0ALL48LHZhyRGvjjBH8xZaZ8dFClulKK5wK4=
github.com/juju/utils v0.0.0-20180424094159-2000ea4ff043/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk=
github.com/juju/utils v0.0.0-20200116185830-d40c2fe10647 h1:wQpkHVbIIpz1PCcLYku9KFWsJ7aEMQXHBBmLy3tRBTk=
github.com/juju/utils v0.0.0-20200116185830-d40c2fe10647/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk=
github.com/juju/utils/v2 v2.0.0-20200923005554-4646bfea2ef1/go.mod h1:fdlDtQlzundleLLz/ggoYinEt/LmnrpNKcNTABQATNI=
github.com/juju/utils/v3 v3.0.0-20220130232349-cd7ecef0e94a/go.mod h1:LzwbbEN7buYjySp4nqnti6c6olSqRXUk6RkbSUUP1n8=
github.com/juju/utils/v3 v3.0.0-20220202114721-338bb0530e89/go.mod h1:wf5w+8jyTh2IYnSX0sHnMJo4ZPwwuiBWn+xN3DkQg4k=
github.com/juju/utils/v3 v3.0.0-20220203023959-c3fbc78a33b0 h1:bn+2Adl1yWqYjm3KSFlFqsvfLg2eq+XNL7GGMYApdVw=
github.com/juju/utils/v3 v3.0.0-20220203023959-c3fbc78a33b0/go.mod h1:8csUcj1VRkfjNIRzBFWzLFCMLwLqsRWvkmhfVAUwbC4=
github.com/juju/version v0.0.0-20161031051906-1f41e27e54f2/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U=
github.com/juju/version v0.0.0-20180108022336-b64dbd566305/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U=
github.com/juju/version v0.0.0-20191219164919-81c1be00b9a6 h1:nrqc9b4YKpKV4lPI3GPPFbo5FUuxkWxgZE2Z8O4lgaw=
github.com/juju/version v0.0.0-20191219164919-81c1be00b9a6/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U=
github.com/juju/version/v2 v2.0.0-20211007103408-2e8da085dc23 h1:wtEPbidt1VyHlb8RSztU6ySQj29FLsOQiI9XiJhXDM4=
github.com/juju/version/v2 v2.0.0-20211007103408-2e8da085dc23/go.mod h1:Ljlbryh9sYaUSGXucslAEDf0A2XUSGvDbHJgW8ps6nc=
github.com/julienschmidt/httprouter v1.1.1-0.20151013225520-77a895ad01eb/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lunixbochs/vtclean v0.0.0-20160125035106-4fbf7632a2c6/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/masterzen/azure-sdk-for-go v3.2.0-beta.0.20161014135628-ee4f0065d00c+incompatible/go.mod h1:mf8fjOu33zCqxUjuiU3I8S1lJMyEAlH+0F2+M5xl3hE=
github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc=
github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc=
github.com/masterzen/winrm v0.0.0-20161014151040-7a535cd943fc/go.mod h1:CfZSN7zwz5gJiFhZJz49Uzk7mEBHIceWmbFmYx7Hf7E=
github.com/masterzen/winrm v0.0.0-20211231115050-232efb40349e/go.mod h1:Iju3u6NzoTAvjuhsGCZc+7fReNnr/Bd6DsWj3WTokIU=
github.com/masterzen/xmlpath v0.0.0-20140218185901-13f4951698ad/go.mod h1:A0zPC53iKKKcXYxr4ROjpQRQ5FgJXtelNdSmHHuq/tY=
github.com/mattn/go-colorable v0.0.6/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20180214000028-650f4a345ab4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180406214816-61147c48b25b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v1 v1.0.0-20161222125816-442357a80af5/go.mod h1:u0ALmqvLRxLI95fkdCEWrE6mhWYZW1aMOJHp5YXLHTg=
gopkg.in/httprequest.v1 v1.1.1/go.mod h1:/CkavNL+g3qLOrpFHVrEx4NKepeqR4XTZWNj4sGGjz0=
gopkg.in/mgo.v2 v2.0.0-20160818015218-f2b6f6c918c4/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk=
gopkg.in/yaml.v2 v2.0.0-20170712054546-1be3d31502d6/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM=
launchpad.net/xmlpath v0.0.0-20130614043138-000000000004/go.mod h1:vqyExLOM3qBx7mvYRkoxjSCF945s0mbe7YynlKYXtsA=
golang-github-juju-cmd-3.0.14/help.go 0000664 0000000 0000000 00000015731 14523706772 0017345 0 ustar 00root root 0000000 0000000 // Copyright 2012-2015 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmd
import (
"bytes"
"fmt"
"sort"
"strings"
"github.com/juju/gnuflag"
)
type helpCommand struct {
CommandBase
super *SuperCommand
topic string
topicArgs []string
topics map[string]topic
target *commandReference
targetSuper *SuperCommand
}
func (c *helpCommand) init() {
if c.super.FlagKnownAs == "" {
c.super.FlagKnownAs = "option"
}
flagKey := fmt.Sprintf("global-%vs", c.super.FlagKnownAs)
c.topics = map[string]topic{
"commands": {
short: "Basic help for all commands",
long: func() string { return c.describeCommands() },
},
flagKey: {
short: fmt.Sprintf("%vs common to all commands", strings.Title(c.super.FlagKnownAs)),
long: func() string { return c.globalOptions() },
},
"topics": {
short: "Topic list",
long: func() string { return c.topicList() },
},
}
}
func echo(s string) func() string {
return func() string { return s }
}
func (c *helpCommand) addTopic(name, short string, long func() string, aliases ...string) {
if _, found := c.topics[name]; found {
panic(fmt.Sprintf("help topic already added: %s", name))
}
c.topics[name] = topic{short, long, false}
for _, alias := range aliases {
if _, found := c.topics[alias]; found {
panic(fmt.Sprintf("help topic already added: %s", alias))
}
c.topics[alias] = topic{short, long, true}
}
}
func (c *helpCommand) describeCommands() string {
commands := c.super.describeCommands()
// Sort command names, and work out length of the longest one
cmdNames := make([]string, 0, len(commands))
longest := 0
for name := range commands {
if len(name) > longest {
longest = len(name)
}
cmdNames = append(cmdNames, name)
}
sort.Strings(cmdNames)
var descr string
for _, name := range cmdNames {
if len(descr) > 0 {
descr += "\n"
}
purpose := commands[name]
descr += fmt.Sprintf("%-*s %s", longest, name, purpose)
}
return descr
}
func (c *helpCommand) globalOptions() string {
buf := &bytes.Buffer{}
fmt.Fprintf(buf, `Global %vs
These %vs may be used with any command, and may appear in front of any
command.
`, strings.Title(c.super.FlagKnownAs), c.super.FlagKnownAs)
f := gnuflag.NewFlagSetWithFlagKnownAs("", gnuflag.ContinueOnError, c.super.FlagKnownAs)
c.super.SetCommonFlags(f)
f.SetOutput(buf)
f.PrintDefaults()
return buf.String()
}
func (c *helpCommand) topicList() string {
var topics []string
longest := 0
for name, topic := range c.topics {
if topic.alias {
continue
}
if len(name) > longest {
longest = len(name)
}
topics = append(topics, name)
}
sort.Strings(topics)
for i, name := range topics {
shortHelp := c.topics[name].short
topics[i] = fmt.Sprintf("%-*s %s", longest, name, shortHelp)
}
return fmt.Sprintf("%s", strings.Join(topics, "\n"))
}
func (c *helpCommand) Info() *Info {
return &Info{
Name: "help",
Args: "[topic]",
FlagKnownAs: c.super.FlagKnownAs,
Purpose: helpPurpose,
Doc: `
See also: topics
`,
}
}
func (c *helpCommand) Init(args []string) error {
if c.super.notifyHelp != nil {
c.super.notifyHelp(args)
}
logger.Tracef("helpCommand.Init: %#v", args)
if len(args) == 0 {
// If there is no help topic specified, print basic usage if it is
// there.
if _, ok := c.topics["basics"]; ok {
c.topic = "basics"
}
return nil
}
// Before we start walking down the subcommand list, we want to check
// to see if the first part is there.
if _, ok := c.super.subcmds[args[0]]; !ok {
if c.super.missingCallback == nil && len(args) > 1 {
return fmt.Errorf("extra arguments to command help: %q", args[1:])
}
logger.Tracef("help not found, setting topic")
c.topic, c.topicArgs = args[0], args[1:]
return nil
}
c.targetSuper = c.super
for len(args) > 0 {
c.topic, args = args[0], args[1:]
commandRef, ok := c.targetSuper.subcmds[c.topic]
if !ok {
return fmt.Errorf("subcommand %q not found", c.topic)
}
c.target = &commandRef
// If there are more args and the target isn't a super command
// error out.
logger.Tracef("target name: %s", c.target.name)
if super, ok := c.target.command.(*SuperCommand); ok {
c.targetSuper = super
} else if len(args) > 0 {
return fmt.Errorf("extra arguments to command help: %q", args)
}
}
return nil
}
func (c *helpCommand) getCommandHelp(super *SuperCommand, command Command, alias string) []byte {
info := command.Info()
if command != super {
logger.Tracef("command not super")
// If the alias is to a subcommand of another super command
// the alias string holds the "super sub" name.
if alias == "" {
info.Name = fmt.Sprintf("%s %s", super.Name, info.Name)
} else {
info.Name = fmt.Sprintf("%s %s", super.Name, alias)
}
}
if super.usagePrefix != "" {
logger.Tracef("adding super prefix")
info.Name = fmt.Sprintf("%s %s", super.usagePrefix, info.Name)
}
flagsAKA := FlagAlias(command, "")
if flagsAKA == "" {
flagsAKA = FlagAlias(super, "")
}
if flagsAKA == "" {
flagsAKA = super.FlagKnownAs
}
if flagsAKA == "" {
flagsAKA = FlagAlias(c, "")
}
if flagsAKA == "" {
flagsAKA = FlagAlias(c.super, "")
}
if flagsAKA == "" {
flagsAKA = c.super.FlagKnownAs
}
if flagsAKA == "" {
// For backward compatibility, the default is 'flag'.
flagsAKA = "flag"
}
f := gnuflag.NewFlagSetWithFlagKnownAs(info.Name, gnuflag.ContinueOnError, flagsAKA)
command.SetFlags(f)
superf := gnuflag.NewFlagSetWithFlagKnownAs(super.Info().Name, gnuflag.ContinueOnError, flagsAKA)
super.SetFlags(superf)
return info.HelpWithSuperFlags(superf, f)
}
func (c *helpCommand) Run(ctx *Context) error {
if c.super.showVersion {
v := newVersionCommand(c.super.version, c.super.versionDetail)
v.SetFlags(c.super.flags)
v.Init(nil)
return v.Run(ctx)
}
// If the topic is a registered subcommand, then run the help command with it
if c.target != nil {
ctx.Stdout.Write(c.getCommandHelp(c.targetSuper, c.target.command, c.target.alias))
return nil
}
// If there is no help topic specified, print basic usage.
if c.topic == "" {
// At this point, "help" is selected as the SuperCommand's
// current action, but we want the info to be printed
// as if there was nothing selected.
c.super.action.command = nil
ctx.Stdout.Write(c.getCommandHelp(c.super, c.super, ""))
return nil
}
// Look to see if the topic is a registered topic.
topic, ok := c.topics[c.topic]
if ok {
fmt.Fprintf(ctx.Stdout, "%s\n", strings.TrimSpace(topic.long()))
return nil
}
// If we have a missing callback, call that with --help
if c.super.missingCallback != nil {
helpArgs := []string{"--help"}
if len(c.topicArgs) > 0 {
helpArgs = append(helpArgs, c.topicArgs...)
}
command := &missingCommand{
callback: c.super.missingCallback,
superName: c.super.Name,
name: c.topic,
args: helpArgs,
}
err := command.Run(ctx)
_, isUnrecognized := err.(*UnrecognizedCommand)
if !isUnrecognized {
return err
}
}
return fmt.Errorf("unknown command or topic for %s", c.topic)
}
golang-github-juju-cmd-3.0.14/help_test.go 0000664 0000000 0000000 00000012770 14523706772 0020404 0 ustar 00root root 0000000 0000000 // Copyright 2012-2015 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package cmd_test
import (
"strings"
"github.com/juju/loggo"
gitjujutesting "github.com/juju/testing"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
"github.com/juju/cmd/v3"
"github.com/juju/cmd/v3/cmdtesting"
)
type HelpCommandSuite struct {
gitjujutesting.IsolationSuite
}
var _ = gc.Suite(&HelpCommandSuite{})
func (s *HelpCommandSuite) SetUpTest(c *gc.C) {
s.IsolationSuite.SetUpTest(c)
loggo.GetLogger("juju.cmd").SetLogLevel(loggo.DEBUG)
}
func (s *HelpCommandSuite) assertStdOutMatches(c *gc.C, ctx *cmd.Context, match string) {
stripped := strings.Replace(cmdtesting.Stdout(ctx), "\n", "", -1)
c.Assert(stripped, gc.Matches, match)
}
func (s *HelpCommandSuite) TestHelpOutput(c *gc.C) {
for i, test := range []struct {
message string
args []string
usagePrefix string
helpMatch string
errMatch string
}{
{
message: "no args shows help",
helpMatch: "Usage: jujutest .*",
}, {
message: "usage prefix with help command",
args: []string{"help"},
usagePrefix: "juju",
helpMatch: "Usage: juju jujutest .*",
}, {
message: "usage prefix with help flag",
args: []string{"--help"},
usagePrefix: "juju",
helpMatch: "Usage: juju jujutest .*",
}, {
message: "help arg usage",
args: []string{"blah", "--help"},
helpMatch: "Usage: jujutest blah.*blah-doc.*",
}, {
message: "usage prefix with help command",
args: []string{"help", "blah"},
usagePrefix: "juju",
helpMatch: "Usage: juju jujutest blah .*",
}, {
message: "usage prefix with help flag",
args: []string{"blah", "--help"},
usagePrefix: "juju",
helpMatch: "Usage: juju jujutest blah .*",
}, {
message: "too many args",
args: []string{"help", "blah", "blah"},
errMatch: `extra arguments to command help: \["blah"\]`,
}, {
args: []string{"help", "commands"},
helpMatch: "blah\\s+blah the juju" +
"documentation\\s+Generate the documentation for all commands" +
"help\\s+Show help on a command or other topic.",
},
} {
supername := "jujutest"
super := cmd.NewSuperCommand(cmd.SuperCommandParams{Name: supername, UsagePrefix: test.usagePrefix})
super.Register(&TestCommand{Name: "blah"})
c.Logf("%d: %s, %q", i, test.message, strings.Join(append([]string{supername}, test.args...), " "))
ctx, err := cmdtesting.RunCommand(c, super, test.args...)
if test.errMatch == "" {
c.Assert(err, jc.ErrorIsNil)
s.assertStdOutMatches(c, ctx, test.helpMatch)
} else {
c.Assert(err, gc.ErrorMatches, test.errMatch)
}
}
}
func (s *HelpCommandSuite) TestHelpBasics(c *gc.C) {
super := cmd.NewSuperCommand(cmd.SuperCommandParams{Name: "jujutest"})
super.Register(&TestCommand{Name: "blah"})
super.AddHelpTopic("basics", "short", "long help basics")
ctx, err := cmdtesting.RunCommand(c, super)
c.Assert(err, jc.ErrorIsNil)
s.assertStdOutMatches(c, ctx, "long help basics")
}
func (s *HelpCommandSuite) TestMultipleSuperCommands(c *gc.C) {
level1 := cmd.NewSuperCommand(cmd.SuperCommandParams{Name: "level1"})
level2 := cmd.NewSuperCommand(cmd.SuperCommandParams{Name: "level2", UsagePrefix: "level1"})
level1.Register(level2)
level3 := cmd.NewSuperCommand(cmd.SuperCommandParams{Name: "level3", UsagePrefix: "level1 level2"})
level2.Register(level3)
level3.Register(&TestCommand{Name: "blah"})
ctx, err := cmdtesting.RunCommand(c, level1, "help", "level2", "level3", "blah")
c.Assert(err, jc.ErrorIsNil)
s.assertStdOutMatches(c, ctx, "Usage: level1 level2 level3 blah.*blah-doc.*")
_, err = cmdtesting.RunCommand(c, level1, "help", "level2", "missing", "blah")
c.Assert(err, gc.ErrorMatches, `subcommand "missing" not found`)
}
func (s *HelpCommandSuite) TestAlias(c *gc.C) {
super := cmd.NewSuperCommand(cmd.SuperCommandParams{Name: "super"})
super.Register(&TestCommand{Name: "blah", Aliases: []string{"alias"}})
ctx := cmdtesting.Context(c)
code := cmd.Main(super, ctx, []string{"help", "alias"})
c.Assert(code, gc.Equals, 0)
stripped := strings.Replace(bufferString(ctx.Stdout), "\n", "", -1)
c.Assert(stripped, gc.Matches, "Usage: super blah .*Aliases: alias")
}
func (s *HelpCommandSuite) TestRegisterSuperAliasHelp(c *gc.C) {
jc := cmd.NewSuperCommand(cmd.SuperCommandParams{
Name: "jujutest",
})
sub := cmd.NewSuperCommand(cmd.SuperCommandParams{
Name: "bar",
UsagePrefix: "jujutest",
Purpose: "bar functions",
})
jc.Register(sub)
sub.Register(&simple{name: "foo"})
jc.RegisterSuperAlias("bar-foo", "bar", "foo", nil)
for _, test := range []struct {
args []string
}{
{
args: []string{"bar", "foo", "--help"},
}, {
args: []string{"bar", "help", "foo"},
}, {
args: []string{"help", "bar-foo"},
}, {
args: []string{"bar-foo", "--help"},
},
} {
c.Logf("args: %v", test.args)
ctx := cmdtesting.Context(c)
code := cmd.Main(jc, ctx, test.args)
c.Check(code, gc.Equals, 0)
help := "Usage: jujutest bar foo\n\nSummary:\nto be simple\n"
c.Check(cmdtesting.Stdout(ctx), gc.Equals, help)
}
}
func (s *HelpCommandSuite) TestNotifyHelp(c *gc.C) {
var called [][]string
super := cmd.NewSuperCommand(cmd.SuperCommandParams{
Name: "super",
NotifyHelp: func(args []string) {
called = append(called, args)
},
})
super.Register(&TestCommand{
Name: "blah",
})
ctx := cmdtesting.Context(c)
code := cmd.Main(super, ctx, []string{"help", "blah"})
c.Assert(code, gc.Equals, 0)
c.Assert(called, jc.DeepEquals, [][]string{{"blah"}})
}
golang-github-juju-cmd-3.0.14/logging.go 0000664 0000000 0000000 00000010516 14523706772 0020037 0 ustar 00root root 0000000 0000000 // Copyright 2012, 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmd
import (
"fmt"
"io"
"os"
"github.com/juju/ansiterm"
"github.com/juju/gnuflag"
"github.com/juju/loggo"
"github.com/juju/loggo/loggocolor"
)
// Log supplies the necessary functionality for Commands that wish to set up
// logging.
type Log struct {
// If DefaultConfig is set, it will be used for the
// default logging configuration.
DefaultConfig string
Path string
Verbose bool
Quiet bool
Debug bool
ShowLog bool
Config string
// NewWriter creates a new logging writer for a specified target.
NewWriter func(target io.Writer) loggo.Writer
}
// GetLogWriter returns a logging writer for the specified target.
func (l *Log) GetLogWriter(target io.Writer) loggo.Writer {
if l.NewWriter != nil {
return l.NewWriter(target)
}
return loggocolor.NewWriter(target)
}
// AddFlags adds appropriate flags to f.
func (l *Log) AddFlags(f *gnuflag.FlagSet) {
f.StringVar(&l.Path, "log-file", "", "path to write log to")
f.BoolVar(&l.Verbose, "v", false, "Show more verbose output")
f.BoolVar(&l.Verbose, "verbose", false, "Show more verbose output")
f.BoolVar(&l.Quiet, "q", false, "Show no informational output")
f.BoolVar(&l.Quiet, "quiet", false, "Show no informational output")
f.BoolVar(&l.Debug, "debug", false, "Equivalent to --show-log --logging-config==DEBUG")
f.StringVar(&l.Config, "logging-config", l.DefaultConfig, "Specify log levels for modules")
f.BoolVar(&l.ShowLog, "show-log", false, "If set, write the log file to stderr")
}
// Start starts logging using the given Context.
func (log *Log) Start(ctx *Context) error {
if log.Verbose && log.Quiet {
return fmt.Errorf(`"verbose" and "quiet" flags clash, please use one or the other, not both`)
}
ctx.quiet = log.Quiet
ctx.verbose = log.Verbose
if log.Path != "" {
path := ctx.AbsPath(log.Path)
target, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
if err != nil {
return err
}
writer := log.GetLogWriter(target)
err = loggo.RegisterWriter("logfile", writer)
if err != nil {
return err
}
}
level := loggo.WARNING
if log.ShowLog {
level = loggo.INFO
}
if log.Debug {
log.ShowLog = true
level = loggo.DEBUG
// override quiet or verbose if set, this way all the information goes
// to the log file.
ctx.quiet = true
ctx.verbose = false
}
if log.ShowLog {
// We replace the default writer to use ctx.Stderr rather than os.Stderr.
writer := log.GetLogWriter(ctx.Stderr)
_, err := loggo.ReplaceDefaultWriter(writer)
if err != nil {
return err
}
} else {
_, _ = loggo.RemoveWriter("default")
// Create a simple writer that doesn't show filenames, or timestamps,
// and only shows warning or above.
writer := NewWarningWriter(ctx.Stderr)
err := loggo.RegisterWriter("warning", writer)
if err != nil {
return err
}
}
// Set the level on the root logger.
root := loggo.GetLogger("")
root.SetLogLevel(level)
// Override the logging config with specified logging config.
loggo.ConfigureLoggers(log.Config)
return nil
}
// NewCommandLogWriter creates a loggo writer for registration
// by the callers of a command. This way the logged output can also
// be displayed otherwise, e.g. on the screen.
func NewCommandLogWriter(name string, out, err io.Writer) loggo.Writer {
return &commandLogWriter{name, out, err}
}
// commandLogWriter filters the log messages for name.
type commandLogWriter struct {
name string
out io.Writer
err io.Writer
}
// Write implements loggo's Writer interface.
func (s *commandLogWriter) Write(entry loggo.Entry) {
if entry.Module == s.name {
if entry.Level <= loggo.INFO {
fmt.Fprintf(s.out, "%s\n", entry.Message)
} else {
fmt.Fprintf(s.err, "%s\n", entry.Message)
}
}
}
type warningWriter struct {
writer *ansiterm.Writer
}
// NewWarningWriter will write out colored severity levels if the writer is
// outputting to a terminal.
func NewWarningWriter(writer io.Writer) loggo.Writer {
w := &warningWriter{ansiterm.NewWriter(writer)}
return loggo.NewMinimumLevelWriter(w, loggo.WARNING)
}
// Write implements Writer.
// WARNING The message...
func (w *warningWriter) Write(entry loggo.Entry) {
loggocolor.SeverityColor[entry.Level].Fprintf(w.writer, entry.Level.String())
fmt.Fprintf(w.writer, " %s\n", entry.Message)
}
golang-github-juju-cmd-3.0.14/logging_test.go 0000664 0000000 0000000 00000016270 14523706772 0021101 0 ustar 00root root 0000000 0000000 // Copyright 2012, 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmd_test
import (
"io/ioutil"
"path/filepath"
"github.com/juju/cmd/v3/cmdtesting"
"github.com/juju/loggo"
"github.com/juju/testing"
gc "gopkg.in/check.v1"
"github.com/juju/cmd/v3"
)
var logger = loggo.GetLogger("juju.test")
type LogSuite struct {
testing.LoggingCleanupSuite
}
var _ = gc.Suite(&LogSuite{})
func newLogWithFlags(c *gc.C, defaultConfig string, flags ...string) *cmd.Log {
log := &cmd.Log{
DefaultConfig: defaultConfig,
}
flagSet := cmdtesting.NewFlagSet()
log.AddFlags(flagSet)
err := flagSet.Parse(false, flags)
c.Assert(err, gc.IsNil)
return log
}
func (s *LogSuite) TestNoFlags(c *gc.C) {
log := newLogWithFlags(c, "")
c.Assert(log.Path, gc.Equals, "")
c.Assert(log.Quiet, gc.Equals, false)
c.Assert(log.Verbose, gc.Equals, false)
c.Assert(log.Debug, gc.Equals, false)
c.Assert(log.Config, gc.Equals, "")
}
func (s *LogSuite) TestFlags(c *gc.C) {
log := newLogWithFlags(c, "", "--log-file", "foo", "--verbose", "--debug", "--show-log",
"--logging-config=juju.cmd=INFO;juju.worker.deployer=DEBUG")
c.Assert(log.Path, gc.Equals, "foo")
c.Assert(log.Verbose, gc.Equals, true)
c.Assert(log.Debug, gc.Equals, true)
c.Assert(log.ShowLog, gc.Equals, true)
c.Assert(log.Config, gc.Equals, "juju.cmd=INFO;juju.worker.deployer=DEBUG")
}
func (s *LogSuite) TestLogConfigFromDefault(c *gc.C) {
config := "juju.cmd=INFO;juju.worker.deployer=DEBUG"
log := newLogWithFlags(c, config)
log.DefaultConfig = config
c.Assert(log.Path, gc.Equals, "")
c.Assert(log.Verbose, gc.Equals, false)
c.Assert(log.Debug, gc.Equals, false)
c.Assert(log.Config, gc.Equals, config)
}
func (s *LogSuite) TestDebugSetsLogLevel(c *gc.C) {
l := &cmd.Log{Debug: true}
ctx := cmdtesting.Context(c)
err := l.Start(ctx)
c.Assert(err, gc.IsNil)
c.Assert(loggo.GetLogger("").LogLevel(), gc.Equals, loggo.DEBUG)
c.Assert(cmdtesting.Stderr(ctx), gc.Equals, "")
c.Assert(cmdtesting.Stdout(ctx), gc.Equals, "")
}
func (s *LogSuite) TestShowLogSetsLogLevel(c *gc.C) {
l := &cmd.Log{ShowLog: true}
ctx := cmdtesting.Context(c)
err := l.Start(ctx)
c.Assert(err, gc.IsNil)
c.Assert(loggo.GetLogger("").LogLevel(), gc.Equals, loggo.INFO)
c.Assert(cmdtesting.Stderr(ctx), gc.Equals, "")
c.Assert(cmdtesting.Stdout(ctx), gc.Equals, "")
}
func (s *LogSuite) TestStderr(c *gc.C) {
l := &cmd.Log{ShowLog: true, Config: "=INFO"}
ctx := cmdtesting.Context(c)
err := l.Start(ctx)
c.Assert(err, gc.IsNil)
logger.Infof("hello")
c.Assert(cmdtesting.Stderr(ctx), gc.Matches, `^.* INFO .* hello\n`)
}
func (s *LogSuite) TestRelPathLog(c *gc.C) {
l := &cmd.Log{Path: "foo.log", Config: "=INFO"}
ctx := cmdtesting.Context(c)
err := l.Start(ctx)
c.Assert(err, gc.IsNil)
logger.Infof("hello")
content, err := ioutil.ReadFile(filepath.Join(ctx.Dir, "foo.log"))
c.Assert(err, gc.IsNil)
c.Assert(string(content), gc.Matches, `^.* INFO .* hello\n`)
c.Assert(cmdtesting.Stderr(ctx), gc.Equals, "")
c.Assert(cmdtesting.Stdout(ctx), gc.Equals, "")
}
func (s *LogSuite) TestAbsPathLog(c *gc.C) {
path := filepath.Join(c.MkDir(), "foo.log")
l := &cmd.Log{Path: path, Config: "=INFO"}
ctx := cmdtesting.Context(c)
err := l.Start(ctx)
c.Assert(err, gc.IsNil)
logger.Infof("hello")
c.Assert(cmdtesting.Stderr(ctx), gc.Equals, "")
content, err := ioutil.ReadFile(path)
c.Assert(err, gc.IsNil)
c.Assert(string(content), gc.Matches, `^.* INFO .* hello\n`)
}
func (s *LogSuite) TestLoggingToFileAndStderr(c *gc.C) {
l := &cmd.Log{Path: "foo.log", Config: "=INFO", ShowLog: true}
ctx := cmdtesting.Context(c)
err := l.Start(ctx)
c.Assert(err, gc.IsNil)
logger.Infof("hello")
content, err := ioutil.ReadFile(filepath.Join(ctx.Dir, "foo.log"))
c.Assert(err, gc.IsNil)
c.Assert(string(content), gc.Matches, `^.* INFO .* hello\n`)
c.Assert(cmdtesting.Stderr(ctx), gc.Matches, `^.* INFO .* hello\n`)
c.Assert(cmdtesting.Stdout(ctx), gc.Equals, "")
}
func (s *LogSuite) TestErrorAndWarningLoggingToStderr(c *gc.C) {
// Error and warning go to stderr even with ShowLog=false
l := &cmd.Log{Config: "=INFO", ShowLog: false}
ctx := cmdtesting.Context(c)
err := l.Start(ctx)
c.Assert(err, gc.IsNil)
logger.Warningf("a warning")
logger.Errorf("an error")
logger.Infof("an info")
c.Assert(cmdtesting.Stderr(ctx), gc.Matches, `^.*WARNING a warning\n.*ERROR an error\n.*`)
c.Assert(cmdtesting.Stdout(ctx), gc.Equals, "")
}
func (s *LogSuite) TestQuietAndVerbose(c *gc.C) {
l := &cmd.Log{Verbose: true, Quiet: true}
ctx := cmdtesting.Context(c)
err := l.Start(ctx)
c.Assert(err, gc.ErrorMatches, `"verbose" and "quiet" flags clash, please use one or the other, not both`)
}
func (s *LogSuite) TestOutputDefault(c *gc.C) {
l := &cmd.Log{}
ctx := cmdtesting.Context(c)
err := l.Start(ctx)
c.Assert(err, gc.IsNil)
ctx.Infof("Writing info output")
ctx.Verbosef("Writing verbose output")
c.Assert(cmdtesting.Stderr(ctx), gc.Equals, "Writing info output\n")
}
func (s *LogSuite) TestOutputVerbose(c *gc.C) {
l := &cmd.Log{Verbose: true}
ctx := cmdtesting.Context(c)
err := l.Start(ctx)
c.Assert(err, gc.IsNil)
ctx.Infof("Writing info output")
ctx.Verbosef("Writing verbose output")
c.Assert(cmdtesting.Stderr(ctx), gc.Equals, "Writing info output\nWriting verbose output\n")
}
func (s *LogSuite) TestOutputQuiet(c *gc.C) {
l := &cmd.Log{Quiet: true}
ctx := cmdtesting.Context(c)
err := l.Start(ctx)
c.Assert(err, gc.IsNil)
ctx.Infof("Writing info output")
ctx.Verbosef("Writing verbose output")
c.Assert(cmdtesting.Stderr(ctx), gc.Equals, "")
}
func (s *LogSuite) TestOutputQuietLogs(c *gc.C) {
l := &cmd.Log{Quiet: true, Path: "foo.log", Config: "=INFO"}
ctx := cmdtesting.Context(c)
err := l.Start(ctx)
c.Assert(err, gc.IsNil)
ctx.Infof("Writing info output")
ctx.Verbosef("Writing verbose output")
content, err := ioutil.ReadFile(filepath.Join(ctx.Dir, "foo.log"))
c.Assert(err, gc.IsNil)
c.Assert(cmdtesting.Stderr(ctx), gc.Equals, "")
c.Assert(string(content), gc.Matches, `^.*INFO .* Writing info output\n.*INFO .*Writing verbose output\n.*`)
}
func (s *LogSuite) TestOutputDefaultLogsVerbose(c *gc.C) {
l := &cmd.Log{Path: "foo.log", Config: "=INFO"}
ctx := cmdtesting.Context(c)
err := l.Start(ctx)
c.Assert(err, gc.IsNil)
ctx.Infof("Writing info output")
ctx.Verbosef("Writing verbose output")
content, err := ioutil.ReadFile(filepath.Join(ctx.Dir, "foo.log"))
c.Assert(err, gc.IsNil)
c.Assert(cmdtesting.Stderr(ctx), gc.Equals, "Writing info output\n")
c.Assert(string(content), gc.Matches, `^.*INFO .*Writing verbose output\n.*`)
}
func (s *LogSuite) TestOutputDebugForcesQuiet(c *gc.C) {
l := &cmd.Log{Verbose: true, Debug: true}
ctx := cmdtesting.Context(c)
err := l.Start(ctx)
c.Assert(err, gc.IsNil)
ctx.Infof("Writing info output")
ctx.Verbosef("Writing verbose output")
c.Assert(cmdtesting.Stderr(ctx), gc.Matches, `^.*INFO .* Writing info output\n.*INFO .*Writing verbose output\n.*`)
}
func (s *LogSuite) TestOutputWarning(c *gc.C) {
l := &cmd.Log{Verbose: true, Debug: true}
ctx := cmdtesting.Context(c)
err := l.Start(ctx)
c.Assert(err, gc.IsNil)
ctx.Warningf("Writing warning output")
c.Assert(cmdtesting.Stderr(ctx), gc.Matches, `^.* WARN .* Writing warning output\n.*`)
}
golang-github-juju-cmd-3.0.14/output.go 0000664 0000000 0000000 00000013273 14523706772 0017754 0 ustar 00root root 0000000 0000000 // Copyright 2012, 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmd
import (
"encoding/json"
"fmt"
"io"
"os"
"sort"
"strings"
"github.com/juju/gnuflag"
goyaml "gopkg.in/yaml.v2"
)
// Formatter writes the arbitrary object into the writer.
type Formatter func(writer io.Writer, value interface{}) error
// FormatYaml writes out value as yaml to the writer, unless value is nil.
func FormatYaml(writer io.Writer, value interface{}) error {
if value == nil {
return nil
}
result, err := goyaml.Marshal(value)
if err != nil {
return err
}
for i := len(result) - 1; i > 0; i-- {
if result[i] != '\n' {
break
}
result = result[:i]
}
if len(result) > 0 {
result = append(result, '\n')
_, err = writer.Write(result)
return err
}
return nil
}
// FormatJson writes out value as json.
func FormatJson(writer io.Writer, value interface{}) error {
result, err := json.Marshal(value)
if err != nil {
return err
}
result = append(result, '\n')
_, err = writer.Write(result)
return err
}
// FormatSmart marshals value into a []byte according to the following rules:
// - string: untouched
// - bool: converted to `True` or `False` (to match pyjuju)
// - int or float: converted to sensible strings
// - []string: joined by `\n`s into a single string
// - anything else: delegate to FormatYaml
func FormatSmart(writer io.Writer, value interface{}) error {
if value == nil {
return nil
}
valueStr := ""
switch value := value.(type) {
case string:
valueStr = value
case []string:
valueStr = strings.Join(value, "\n")
case bool:
if value {
valueStr = "True"
} else {
valueStr = "False"
}
default:
return FormatYaml(writer, value)
}
if valueStr == "" {
return nil
}
_, err := writer.Write([]byte(valueStr + "\n"))
return err
}
// TypeFormatter describes a formatting type that can define if a type is
// serialisable.
type TypeFormatter struct {
Formatter Formatter
Serialisable bool
}
type formatters map[string]TypeFormatter
// Formatters returns the underlying formatters without the additional
// information of a TypeFormatter.
func (f formatters) Formatters() map[string]Formatter {
result := make(map[string]Formatter, len(f))
for k, v := range f {
result[k] = v.Formatter
}
return result
}
// DefaultFormatters holds the formatters that can be
// specified with the --format flag.
var DefaultFormatters = formatters{
"smart": TypeFormatter{Formatter: FormatSmart, Serialisable: false},
"yaml": TypeFormatter{Formatter: FormatYaml, Serialisable: true},
"json": TypeFormatter{Formatter: FormatJson, Serialisable: true},
}
// formatterValue implements gnuflag.Value for the --format flag.
type formatterValue struct {
name string
formatters map[string]Formatter
}
// newFormatterValue returns a new formatterValue. The initial Formatter name
// must be present in formatters.
func newFormatterValue(initial string, formatters map[string]Formatter) *formatterValue {
v := &formatterValue{formatters: formatters}
if err := v.Set(initial); err != nil {
panic(err)
}
return v
}
// Set stores the chosen formatter name in v.name.
func (v *formatterValue) Set(value string) error {
if v.formatters[value] == nil {
return fmt.Errorf("unknown format %q", value)
}
v.name = value
return nil
}
// String returns the chosen formatter name.
func (v *formatterValue) String() string {
return v.name
}
// doc returns documentation for the --format flag.
func (v *formatterValue) doc() string {
choices := make([]string, len(v.formatters))
i := 0
for name := range v.formatters {
choices[i] = name
i++
}
sort.Strings(choices)
return "Specify output format (" + strings.Join(choices, "|") + ")"
}
// format runs the chosen formatter on value.
func (v *formatterValue) format(writer io.Writer, value interface{}) error {
return v.formatters[v.name](writer, value)
}
// Output is responsible for interpreting output-related command line flags
// and writing a value to a file or to stdout as directed.
type Output struct {
formatter *formatterValue
outPath string
}
// AddFlags injects the --format and --output command line flags into f.
func (c *Output) AddFlags(f *gnuflag.FlagSet, defaultFormatter string, formatters map[string]Formatter) {
c.formatter = newFormatterValue(defaultFormatter, formatters)
f.Var(c.formatter, "format", c.formatter.doc())
f.StringVar(&c.outPath, "o", "", "Specify an output file")
f.StringVar(&c.outPath, "output", "", "")
}
// Write formats and outputs the value as directed by the --format and
// --output command line flags.
func (c *Output) Write(ctx *Context, value interface{}) (err error) {
formatterName := c.formatter.name
formatter := c.formatter.formatters[formatterName]
if err := c.writeFormatter(ctx, formatter, value); err != nil {
return err
}
return nil
}
// WriteFormatter formats and outputs the value with the given formatter,
// to the output directed by the --output command line flag.
func (c *Output) WriteFormatter(ctx *Context, formatter Formatter, value interface{}) (err error) {
return c.writeFormatter(ctx, formatter, value)
}
func (c *Output) writeFormatter(ctx *Context, formatter Formatter, value interface{}) (err error) {
var target io.Writer
if c.outPath == "" {
target = ctx.Stdout
} else {
path := ctx.AbsPath(c.outPath)
var f *os.File
if f, err = os.Create(path); err != nil {
return
}
defer f.Close()
target = f
}
if err := formatter(target, value); err != nil {
return err
}
// Suppress the handling of errors on stdout when a machine formatter is used.
ctx.outputFormatUsed = true
return nil
}
// Name returns the underlying name of the formatter.
func (c *Output) Name() string {
return c.formatter.name
}
golang-github-juju-cmd-3.0.14/output_test.go 0000664 0000000 0000000 00000012535 14523706772 0021013 0 ustar 00root root 0000000 0000000 // Copyright 2012, 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmd_test
import (
"github.com/juju/gnuflag"
"github.com/juju/loggo"
"github.com/juju/testing"
gc "gopkg.in/check.v1"
"github.com/juju/cmd/v3"
"github.com/juju/cmd/v3/cmdtesting"
)
// OutputCommand is a command that uses the output.go formatters.
type OutputCommand struct {
cmd.CommandBase
out cmd.Output
value interface{}
}
func (c *OutputCommand) Info() *cmd.Info {
return &cmd.Info{
Name: "output",
Args: "",
Purpose: "I like to output",
Doc: "output",
}
}
func (c *OutputCommand) SetFlags(f *gnuflag.FlagSet) {
formatters := make(map[string]cmd.Formatter, len(cmd.DefaultFormatters))
for k, v := range cmd.DefaultFormatters {
formatters[k] = v.Formatter
}
c.out.AddFlags(f, "smart", formatters)
}
func (c *OutputCommand) Init(args []string) error {
return cmd.CheckEmpty(args)
}
func (c *OutputCommand) Run(ctx *cmd.Context) error {
if value, ok := c.value.(overrideFormatter); ok {
return c.out.WriteFormatter(ctx, value.formatter, value.value)
}
return c.out.Write(ctx, c.value)
}
type overrideFormatter struct {
formatter cmd.Formatter
value interface{}
}
// use a struct to control field ordering.
var defaultValue = struct {
Juju int
Puppet bool
}{1, false}
var outputTests = map[string][]struct {
value interface{}
output string
}{
"": {
{nil, ""},
{"", ""},
{1, "1\n"},
{-1, "-1\n"},
{1.1, "1.1\n"},
{10000000, "10000000\n"},
{true, "True\n"},
{false, "False\n"},
{"hello", "hello\n"},
{"\n\n\n", "\n\n\n\n"},
{"foo: bar", "foo: bar\n"},
{[]string{}, ""},
{[]string{"blam", "dink"}, "blam\ndink\n"},
{map[interface{}]interface{}{"foo": "bar"}, "foo: bar\n"},
{overrideFormatter{cmd.FormatSmart, "abc\ndef"}, "abc\ndef\n"},
},
"smart": {
{nil, ""},
{"", ""},
{1, "1\n"},
{-1, "-1\n"},
{1.1, "1.1\n"},
{10000000, "10000000\n"},
{true, "True\n"},
{false, "False\n"},
{"hello", "hello\n"},
{"\n\n\n", "\n\n\n\n"},
{"foo: bar", "foo: bar\n"},
{[]string{}, ""},
{[]string{"blam", "dink"}, "blam\ndink\n"},
{map[interface{}]interface{}{"foo": "bar"}, "foo: bar\n"},
{overrideFormatter{cmd.FormatSmart, "abc\ndef"}, "abc\ndef\n"},
},
"json": {
{nil, "null\n"},
{"", `""` + "\n"},
{1, "1\n"},
{-1, "-1\n"},
{1.1, "1.1\n"},
{10000000, "10000000\n"},
{true, "true\n"},
{false, "false\n"},
{"hello", `"hello"` + "\n"},
{"\n\n\n", `"\n\n\n"` + "\n"},
{"foo: bar", `"foo: bar"` + "\n"},
{[]string{}, `[]` + "\n"},
{[]string{"blam", "dink"}, `["blam","dink"]` + "\n"},
{defaultValue, `{"Juju":1,"Puppet":false}` + "\n"},
{overrideFormatter{cmd.FormatSmart, "abc\ndef"}, "abc\ndef\n"},
{overrideFormatter{cmd.FormatJson, struct{}{}}, "{}\n"},
},
"yaml": {
{nil, ""},
{"", `""` + "\n"},
{1, "1\n"},
{-1, "-1\n"},
{1.1, "1.1\n"},
{10000000, "10000000\n"},
{true, "true\n"},
{false, "false\n"},
{"hello", "hello\n"},
{"\n\n\n", "|2+\n"},
{"foo: bar", "'foo: bar'\n"},
{[]string{}, "[]\n"},
{[]string{"blam", "dink"}, "- blam\n- dink\n"},
{defaultValue, "juju: 1\npuppet: false\n"},
{overrideFormatter{cmd.FormatSmart, "abc\ndef"}, "abc\ndef\n"},
{overrideFormatter{cmd.FormatYaml, struct{}{}}, "{}\n"},
},
}
type OutputSuite struct {
testing.LoggingCleanupSuite
ctx *cmd.Context
}
var _ = gc.Suite(&OutputSuite{})
func (s *OutputSuite) SetUpTest(c *gc.C) {
s.LoggingCleanupSuite.SetUpTest(c)
s.ctx = cmdtesting.Context(c)
loggo.ReplaceDefaultWriter(cmd.NewWarningWriter(s.ctx.Stderr))
}
func (s *OutputSuite) TestOutputFormat(c *gc.C) {
s.testOutputFormat(c, "")
}
func (s *OutputSuite) TestOutputFormatSmart(c *gc.C) {
s.testOutputFormat(c, "smart")
}
func (s *OutputSuite) TestOutputFormatJson(c *gc.C) {
s.testOutputFormat(c, "json")
}
func (s *OutputSuite) TestOutputFormatYaml(c *gc.C) {
s.testOutputFormat(c, "yaml")
}
func (s *OutputSuite) testOutputFormat(c *gc.C, format string) {
tests := outputTests[format]
var args []string
if format != "" {
args = []string{"--format", format}
}
for i, t := range tests {
c.Logf(" test %d", i)
s.SetUpTest(c)
result := cmd.Main(&OutputCommand{value: t.value}, s.ctx, args)
c.Check(result, gc.Equals, 0)
c.Check(bufferString(s.ctx.Stdout), gc.Equals, t.output)
c.Check(bufferString(s.ctx.Stderr), gc.Equals, "")
s.TearDownTest(c)
}
}
func (s *OutputSuite) TestUnknownOutputFormat(c *gc.C) {
result := cmd.Main(&OutputCommand{}, s.ctx, []string{"--format", "cuneiform"})
c.Check(result, gc.Equals, 2)
c.Check(bufferString(s.ctx.Stdout), gc.Equals, "")
c.Check(bufferString(s.ctx.Stderr), gc.Matches, ".*: unknown format \"cuneiform\"\n")
}
// Py juju allowed both --format json and --format=json. This test verifies that juju is
// being built against a version of the gnuflag library (rev 14 or above) that supports
// this argument format.
// LP #1059921
func (s *OutputSuite) TestFormatAlternativeSyntax(c *gc.C) {
result := cmd.Main(&OutputCommand{}, s.ctx, []string{"--format=json"})
c.Assert(result, gc.Equals, 0)
c.Assert(bufferString(s.ctx.Stdout), gc.Equals, "null\n")
}
func (s *OutputSuite) TestFormatters(c *gc.C) {
typeFormatters := cmd.DefaultFormatters
formatters := typeFormatters.Formatters()
c.Assert(len(typeFormatters), gc.Equals, len(formatters))
for k := range typeFormatters {
_, ok := formatters[k]
c.Assert(ok, gc.Equals, true)
}
}
golang-github-juju-cmd-3.0.14/package_test.go 0000664 0000000 0000000 00000000347 14523706772 0021044 0 ustar 00root root 0000000 0000000 // Copyright 2012, 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmd_test
import (
stdtesting "testing"
gc "gopkg.in/check.v1"
)
func TestPackage(t *stdtesting.T) {
gc.TestingT(t)
}
golang-github-juju-cmd-3.0.14/stringmap.go 0000664 0000000 0000000 00000002614 14523706772 0020415 0 ustar 00root root 0000000 0000000 // Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmd
import (
"errors"
"strings"
)
// StringMap is a type that deserializes a CLI string using gnuflag's Value
// semantics. It expects a key=value pair, and supports multiple copies of the
// flag adding more pairs, though the keys must be unique, and both keys and
// values must be non-empty.
type StringMap struct {
Mapping *map[string]string
}
// Set implements gnuflag.Value's Set method.
func (m StringMap) Set(s string) error {
if *m.Mapping == nil {
*m.Mapping = map[string]string{}
}
// make a copy so the following code is less ugly with dereferencing.
mapping := *m.Mapping
// Note that gnuflag will prepend the bad argument to the error message, so
// we don't need to restate it here.
vals := strings.SplitN(s, "=", 2)
if len(vals) != 2 {
return errors.New("expected key=value format")
}
key, value := vals[0], vals[1]
if len(key) == 0 || len(value) == 0 {
return errors.New("key and value must be non-empty")
}
if _, ok := mapping[key]; ok {
return errors.New("duplicate key specified")
}
mapping[key] = value
return nil
}
// String implements gnuflag.Value's String method
func (m StringMap) String() string {
pairs := make([]string, 0, len(*m.Mapping))
for key, value := range *m.Mapping {
pairs = append(pairs, key+"="+value)
}
return strings.Join(pairs, ";")
}
golang-github-juju-cmd-3.0.14/stringmap_test.go 0000664 0000000 0000000 00000003142 14523706772 0021451 0 ustar 00root root 0000000 0000000 // Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package cmd_test
import (
"github.com/juju/cmd/v3"
"github.com/juju/testing"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
)
var _ = gc.Suite(&StringMapSuite{})
type StringMapSuite struct {
testing.IsolationSuite
}
func (StringMapSuite) TestStringMapNilOk(c *gc.C) {
// note that the map may start out nil
var values map[string]string
c.Assert(values, gc.IsNil)
sm := cmd.StringMap{Mapping: &values}
err := sm.Set("foo=foovalue")
c.Assert(err, jc.ErrorIsNil)
err = sm.Set("bar=barvalue")
c.Assert(err, jc.ErrorIsNil)
// now the map is non-nil and filled
c.Assert(values, gc.DeepEquals, map[string]string{
"foo": "foovalue",
"bar": "barvalue",
})
}
func (StringMapSuite) TestStringMapBadVal(c *gc.C) {
sm := cmd.StringMap{Mapping: &map[string]string{}}
err := sm.Set("foo")
c.Assert(err, gc.ErrorMatches, "expected key=value format")
}
func (StringMapSuite) TestStringMapDupVal(c *gc.C) {
sm := cmd.StringMap{Mapping: &map[string]string{}}
err := sm.Set("bar=somevalue")
c.Assert(err, jc.ErrorIsNil)
err = sm.Set("bar=someothervalue")
c.Assert(err, gc.ErrorMatches, "duplicate key specified")
}
func (StringMapSuite) TestStringMapNoValue(c *gc.C) {
sm := cmd.StringMap{Mapping: &map[string]string{}}
err := sm.Set("bar=")
c.Assert(err, gc.ErrorMatches, "key and value must be non-empty")
}
func (StringMapSuite) TestStringMapNoKey(c *gc.C) {
sm := cmd.StringMap{Mapping: &map[string]string{}}
err := sm.Set("=bar")
c.Assert(err, gc.ErrorMatches, "key and value must be non-empty")
}
golang-github-juju-cmd-3.0.14/supercommand.go 0000664 0000000 0000000 00000055553 14523706772 0021120 0 ustar 00root root 0000000 0000000 // Copyright 2012, 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmd
import (
"fmt"
"io/ioutil"
"sort"
"strings"
"github.com/juju/errors"
"github.com/juju/gnuflag"
"github.com/juju/loggo"
)
var logger = loggo.GetLogger("cmd")
type topic struct {
short string
long func() string
// Help aliases are not output when topics are listed, but are used
// to search for the help topic
alias bool
}
// UnrecognizedCommand defines an error that specifies when a command is not
// found.
type UnrecognizedCommand struct {
message string
}
// UnrecognizedCommandf creates a UnrecognizedCommand with additional arguments
// to create a bespoke message for the unrecognized command.
func UnrecognizedCommandf(format string, args ...interface{}) *UnrecognizedCommand {
return &UnrecognizedCommand{
message: fmt.Sprintf(format, args...),
}
}
// DefaultUnrecognizedCommand creates a default message for using the
// UnrecognizedCommand.
func DefaultUnrecognizedCommand(name string) *UnrecognizedCommand {
return UnrecognizedCommandf("unrecognized command: %s", name)
}
func (e *UnrecognizedCommand) Error() string {
return e.message
}
// MissingCallback defines a function that will be used by the SuperCommand if
// the requested subcommand isn't found.
type MissingCallback func(ctx *Context, subcommand string, args []string) error
// SuperCommandParams provides a way to have default parameter to the
// `NewSuperCommand` call.
type SuperCommandParams struct {
// UsagePrefix should be set when the SuperCommand is
// actually a subcommand of some other SuperCommand;
// if NotifyRun is called, it name will be prefixed accordingly,
// unless UsagePrefix is identical to Name.
UsagePrefix string
// Notify, if not nil, is called when the SuperCommand
// is about to run a sub-command.
NotifyRun func(cmdName string)
// NotifyHelp is called just before help is printed, with the
// arguments received by the help command. This can be
// used, for example, to load command information for external
// "plugin" commands, so that their documentation will show up
// in the help output.
NotifyHelp func([]string)
Name string
Purpose string
Doc string
Examples string
// Log holds the Log value associated with the supercommand. If it's nil,
// no logging flags will be configured.
Log *Log
// GlobalFlags specifies a value that can add more global flags to the
// supercommand which will also be available on all subcommands.
GlobalFlags FlagAdder
MissingCallback MissingCallback
Aliases []string
Version string
// VersionDetail is a freeform information that is output when the default version
// subcommand is passed --all. Output is formatted using the user-selected formatter.
// Exported fields should specify yaml and json field tags.
VersionDetail interface{}
// UserAliasesFilename refers to the location of a file that contains
// name = cmd [args...]
// values, that is used to change default behaviour of commands in order
// to add flags, or provide short cuts to longer commands.
UserAliasesFilename string
// FlagKnownAs allows different projects to customise what their flags are
// known as, e.g. 'flag', 'option', 'item'. All error/log messages
// will use that name when referring to an individual items/flags in this command.
// For example, if this value is 'option', the default message 'value for flag'
// will become 'value for option'.
FlagKnownAs string
// SkipCommandDoc is used to skip over the super command documentation.
// This is useful when the super command is used as a wrapper for other
// commands, and the documentation is not relevant to the output of the
// documentation.
// TODO (stickupkid): Remove this. This shouldn't be here, but the
// documentation command is at the wrong abstraction, so we need to
// hack around it.
SkipCommandDoc bool
}
// FlagAdder represents a value that has associated flags.
type FlagAdder interface {
// AddsFlags adds the value's flags to the given flag set.
AddFlags(*gnuflag.FlagSet)
}
// NewSuperCommand creates and initializes a new `SuperCommand`, and returns
// the fully initialized structure.
func NewSuperCommand(params SuperCommandParams) *SuperCommand {
command := &SuperCommand{
Name: params.Name,
Purpose: params.Purpose,
Doc: params.Doc,
Examples: params.Examples,
Log: params.Log,
Aliases: params.Aliases,
globalFlags: params.GlobalFlags,
usagePrefix: params.UsagePrefix,
missingCallback: params.MissingCallback,
version: params.Version,
versionDetail: params.VersionDetail,
notifyRun: params.NotifyRun,
notifyHelp: params.NotifyHelp,
userAliasesFilename: params.UserAliasesFilename,
FlagKnownAs: params.FlagKnownAs,
SkipCommandDoc: params.SkipCommandDoc,
}
command.init()
return command
}
// DeprecationCheck is used to provide callbacks to determine if
// a command is deprecated or obsolete.
type DeprecationCheck interface {
// Deprecated aliases emit a warning when executed. If the command is
// deprecated, the second return value recommends what to use instead.
Deprecated() (bool, string)
// Obsolete aliases are not actually registered. The purpose of this
// is to allow code to indicate ahead of time some way to determine
// that the command should stop working.
Obsolete() bool
}
type commandReference struct {
name string
command Command
alias string
check DeprecationCheck
}
// SuperCommand is a Command that selects a subcommand and assumes its
// properties; any command line arguments that were not used in selecting
// the subcommand are passed down to it, and to Run a SuperCommand is to run
// its selected subcommand.
type SuperCommand struct {
CommandBase
Name string
Purpose string
Doc string
Examples string
Log *Log
Aliases []string
globalFlags FlagAdder
version string
versionDetail interface{}
usagePrefix string
userAliasesFilename string
userAliases map[string][]string
subcmds map[string]commandReference
help *helpCommand
documentation *documentationCommand
commonflags *gnuflag.FlagSet
flags *gnuflag.FlagSet
action commandReference
showHelp bool
showDescription bool
showVersion bool
noAlias bool
missingCallback MissingCallback
notifyRun func(string)
notifyHelp func([]string)
// FlagKnownAs allows different projects to customise what their flags are
// known as, e.g. 'flag', 'option', 'item'. All error/log messages
// will use that name when referring to an individual items/flags in this command.
// For example, if this value is 'option', the default message 'value for flag'
// will become 'value for option'.
FlagKnownAs string
// SkipCommandDoc is used to skip over the super command documentation.
// This is useful when the super command is used as a wrapper for other
// commands, and the documentation is not relevant to the output of the
// documentation.
// TODO (stickupkid): Remove this. This shouldn't be here, but the
// documentation command is at the wrong abstraction, so we need to
// hack around it.
SkipCommandDoc bool
}
// IsSuperCommand implements Command.IsSuperCommand
func (c *SuperCommand) IsSuperCommand() bool {
return true
}
func (c *SuperCommand) init() {
if c.subcmds != nil {
return
}
if c.FlagKnownAs == "" {
// For backward compatibility, the default is 'flag'.
c.FlagKnownAs = "flag"
}
c.help = &helpCommand{
super: c,
}
c.help.init()
c.documentation = &documentationCommand{
super: c,
}
c.subcmds = map[string]commandReference{
"help": {command: c.help},
"documentation": {
command: c.documentation,
name: "documentation",
},
}
if c.version != "" {
c.subcmds["version"] = commandReference{
command: newVersionCommand(c.version, c.versionDetail),
}
}
c.userAliases = ParseAliasFile(c.userAliasesFilename)
}
// AddHelpTopic adds a new help topic with the description being the short
// param, and the full text being the long param. The description is shown in
// 'help topics', and the full text is shown when the command 'help ' is
// called.
func (c *SuperCommand) AddHelpTopic(name, short, long string, aliases ...string) {
c.help.addTopic(name, short, echo(long), aliases...)
}
// AddHelpTopicCallback adds a new help topic with the description being the
// short param, and the full text being defined by the callback function.
func (c *SuperCommand) AddHelpTopicCallback(name, short string, longCallback func() string) {
c.help.addTopic(name, short, longCallback)
}
// Register makes a subcommand available for use on the command line. The
// command will be available via its own name, and via any supplied aliases.
func (c *SuperCommand) Register(subcmd Command) {
info := subcmd.Info()
c.insert(commandReference{name: info.Name, command: subcmd})
for _, name := range info.Aliases {
c.insert(commandReference{name: name, command: subcmd, alias: info.Name})
}
}
// RegisterDeprecated makes a subcommand available for use on the command line if it
// is not obsolete. It inserts the command with the specified DeprecationCheck so
// that a warning is displayed if the command is deprecated.
func (c *SuperCommand) RegisterDeprecated(subcmd Command, check DeprecationCheck) {
if subcmd == nil {
return
}
info := subcmd.Info()
if check != nil && check.Obsolete() {
logger.Infof("%q command not registered as it is obsolete", info.Name)
return
}
c.insert(commandReference{name: info.Name, command: subcmd, check: check})
for _, name := range info.Aliases {
c.insert(commandReference{name: name, command: subcmd, alias: info.Name, check: check})
}
}
// RegisterAlias makes an existing subcommand available under another name.
// If `check` is supplied, and the result of the `Obsolete` call is true,
// then the alias is not registered.
func (c *SuperCommand) RegisterAlias(name, forName string, check DeprecationCheck) {
if check != nil && check.Obsolete() {
logger.Infof("%q alias not registered as it is obsolete", name)
return
}
action, found := c.subcmds[forName]
if !found {
panic(fmt.Sprintf("%q not found when registering alias", forName))
}
c.insert(commandReference{
name: name,
command: action.command,
alias: forName,
check: check,
})
}
// RegisterSuperAlias makes a subcommand of a registered supercommand
// available under another name. This is useful when the command structure is
// being refactored. If `check` is supplied, and the result of the `Obsolete`
// call is true, then the alias is not registered.
func (c *SuperCommand) RegisterSuperAlias(name, super, forName string, check DeprecationCheck) {
if check != nil && check.Obsolete() {
logger.Infof("%q alias not registered as it is obsolete", name)
return
}
action, found := c.subcmds[super]
if !found {
panic(fmt.Sprintf("%q not found when registering alias", super))
}
if !action.command.IsSuperCommand() {
panic(fmt.Sprintf("%q is not a SuperCommand", super))
}
superCmd := action.command.(*SuperCommand)
action, found = superCmd.subcmds[forName]
if !found {
panic(fmt.Sprintf("%q not found as a command in %q", forName, super))
}
c.insert(commandReference{
name: name,
command: action.command,
alias: super + " " + forName,
check: check,
})
}
func (c *SuperCommand) insert(value commandReference) {
if _, found := c.subcmds[value.name]; found {
panic(fmt.Sprintf("command already registered: %q", value.name))
}
c.subcmds[value.name] = value
}
// describeCommands returns a short description of each registered subcommand.
func (c *SuperCommand) describeCommands() map[string]string {
result := make(map[string]string, len(c.subcmds))
for name, action := range c.subcmds {
if deprecated, _ := action.Deprecated(); deprecated {
continue
}
info := action.command.Info()
purpose := info.Purpose
if action.alias != "" {
purpose = "Alias for '" + action.alias + "'."
}
result[name] = purpose
}
return result
}
// Info returns a description of the currently selected subcommand, or of the
// SuperCommand itself if no subcommand has been specified.
func (c *SuperCommand) Info() *Info {
if c.action.command != nil {
info := *c.action.command.Info()
info.Name = fmt.Sprintf("%s %s", c.Name, info.Name)
info.FlagKnownAs = c.FlagKnownAs
return &info
}
return &Info{
Name: c.Name,
Args: " ...",
Purpose: c.Purpose,
Doc: strings.TrimSpace(c.Doc),
Subcommands: c.describeCommands(),
Examples: c.Examples,
Aliases: c.Aliases,
FlagKnownAs: c.FlagKnownAs,
}
}
const helpPurpose = "Show help on a command or other topic."
// SetCommonFlags creates a new "commonflags" flagset, whose
// flags are shared with the argument f; this enables us to
// add non-global flags to f, which do not carry into subcommands.
func (c *SuperCommand) SetCommonFlags(f *gnuflag.FlagSet) {
if c.Log != nil {
c.Log.AddFlags(f)
}
if c.globalFlags != nil {
c.globalFlags.AddFlags(f)
}
f.BoolVar(&c.showHelp, "h", false, helpPurpose)
f.BoolVar(&c.showHelp, "help", false, "")
// In the case where we are providing the basis for a plugin,
// plugins are required to support the --description argument.
// The Purpose attribute will be printed (if defined), allowing
// plugins to provide a sensible line of text for 'juju help plugins'.
f.BoolVar(&c.showDescription, "description", false, "Show short description of plugin, if any")
c.commonflags = gnuflag.NewFlagSetWithFlagKnownAs(c.Info().Name, gnuflag.ContinueOnError, FlagAlias(c, "flag"))
c.commonflags.SetOutput(ioutil.Discard)
f.VisitAll(func(flag *gnuflag.Flag) {
c.commonflags.Var(flag.Value, flag.Name, flag.Usage)
})
}
// SetFlags adds the options that apply to all commands, particularly those
// due to logging.
func (c *SuperCommand) SetFlags(f *gnuflag.FlagSet) {
c.SetCommonFlags(f)
// Only flags set by SetCommonFlags are passed on to subcommands.
// Any flags added below only take effect when no subcommand is
// specified (e.g. command --version).
if c.version != "" {
f.BoolVar(&c.showVersion, "version", false, "show the command's version and exit")
}
if c.userAliasesFilename != "" {
f.BoolVar(&c.noAlias, "no-alias", false, "do not process command aliases when running this command")
}
c.flags = f
}
// For a SuperCommand, we want to parse the args with
// allowIntersperse=false. This will mean that the args may contain other
// options that haven't been defined yet, and that only options that relate
// to the SuperCommand itself can come prior to the subcommand name.
func (c *SuperCommand) AllowInterspersedFlags() bool {
return false
}
// Init initializes the command for running.
func (c *SuperCommand) Init(args []string) error {
if c.showDescription {
return CheckEmpty(args)
}
if len(args) == 0 {
c.action = c.subcmds["help"]
return c.action.command.Init(args)
}
if userAlias, found := c.userAliases[args[0]]; found && !c.noAlias {
logger.Debugf("using alias %q=%q", args[0], strings.Join(userAlias, " "))
args = append(userAlias, args[1:]...)
}
found := false
// Look for the command.
if c.action, found = c.subcmds[args[0]]; !found {
if c.missingCallback != nil {
c.action = commandReference{
command: &missingCommand{
callback: c.missingCallback,
superName: c.Name,
name: args[0],
args: args[1:],
},
}
// Yes return here, no Init called on missing Command.
return nil
}
return fmt.Errorf("unrecognized command: %s %s", c.Name, args[0])
}
args = args[1:]
subcmd := c.action.command
if subcmd.IsSuperCommand() {
f := gnuflag.NewFlagSetWithFlagKnownAs(c.Info().Name, gnuflag.ContinueOnError, FlagAlias(subcmd, "flag"))
f.SetOutput(ioutil.Discard)
subcmd.SetFlags(f)
} else {
subcmd.SetFlags(c.commonflags)
}
if err := c.commonflags.Parse(subcmd.AllowInterspersedFlags(), args); err != nil {
return err
}
args = c.commonflags.Args()
if c.showHelp {
// We want to treat help for the command the same way we would if we went "help foo".
args = []string{c.action.name}
c.action = c.subcmds["help"]
}
return c.action.command.Init(args)
}
// Run executes the subcommand that was selected in Init.
func (c *SuperCommand) Run(ctx *Context) error {
if c.showDescription {
if c.Purpose != "" {
fmt.Fprintf(ctx.Stdout, "%s\n", c.Purpose)
} else {
fmt.Fprintf(ctx.Stdout, "%s: no description available\n", c.Info().Name)
}
return nil
}
if c.action.command == nil {
panic("Run: missing subcommand; Init failed or not called")
}
// Set the serialisable state on the context, by checking the common global
// formatting directive. Set this early enough, so that everyone can take
// appropriate action further down stream.
ctx.serialisable = c.isSerialisableFormatDirective()
if c.Log != nil {
if err := c.Log.Start(ctx); err != nil {
return err
}
}
if c.notifyRun != nil {
name := c.Name
if c.usagePrefix != "" && c.usagePrefix != name {
name = c.usagePrefix + " " + name
}
c.notifyRun(name)
}
if deprecated, replacement := c.action.Deprecated(); deprecated {
ctx.Warningf("%q is deprecated, please use %q", c.action.name, replacement)
}
err := c.action.command.Run(ctx)
if err != nil && !IsErrSilent(err) {
// Handle formatting when displaying errors.
handleErr := c.handleErrorForMachineFormats(ctx)
if handleErr != nil {
// If there is a handle error when attempting to find the machine
// format, we should let the user know. In doing so, we dump the
// original error and return the handle error so that effective
// debugging is possible.
logger.Debugf("error stack: \n%v", errors.ErrorStack(err))
return handleErr
}
WriteError(ctx.Stderr, err)
logger.Debugf("error stack: \n%v", errors.ErrorStack(err))
// Err has been logged above, we can make the err silent so it does not log again in cmd/main
if !IsRcPassthroughError(err) {
err = ErrSilent
}
} else {
logger.Infof("command finished")
}
return err
}
// isSerialisableFormatDirective checks to see if the output format for a given
// super command common flag (global), is intended to be used by a machine or
// not.
// It is expected that when this is set to true, extra actions are performed on
// the output to mitigate addition verbose logging or interactivity.
func (c *SuperCommand) isSerialisableFormatDirective() bool {
formatFlag := c.commonflags.Lookup("format")
if formatFlag == nil {
return false
}
formatName := formatFlag.Value.String()
if typeFormatter, ok := DefaultFormatters[formatName]; ok {
return typeFormatter.Serialisable
}
return false
}
// handleErrorForMachineFormats attempts to handle fatal errors when using
// formatting directives.
// If the formatting directive is what we consider a machine format (yaml or
// json), then we attempt to output nothing for that format. An example of this
// would be; for json, that would be {}.
// No additional writes to stdout or stderr should be performed when a
// successful format lookup is done, otherwise return errors from a unsuccessful
// lookup.
func (c *SuperCommand) handleErrorForMachineFormats(ctx *Context) error {
// If an output format was used on stdout already we can omit correction
// of the machine output.
if !ctx.IsSerial() || ctx.outputFormatUsed {
return nil
}
formatFlag := c.commonflags.Lookup("format")
if formatFlag == nil {
return nil
}
formatName := formatFlag.Value.String()
typeFormatter, ok := DefaultFormatters[formatName]
if !ok {
return errors.Errorf("missing formatter %q", formatName)
}
// Although this code handles errors for machine formats, the actual empty
// type should be written to stdout. This allows consumers of the output to
// correctly handle the resulting empty value.
// If we place it into stderr, it means that you can never add any more
// additional information to stderr, even if it helps the user.
return typeFormatter.Formatter(ctx.Stdout, struct{}{})
}
// FindClosestSubCommand attempts to find a sub command by a given name.
// This is used to help locate potential commands where the name isn't an
// exact match.
// If the resulting fuzzy match algorithm returns a value that is itself too
// far away from the size of the word, we disgard that and say a match isn't
// relavent i.e. "foo" "barsomethingfoo" would not match
func (c *SuperCommand) FindClosestSubCommand(name string) (string, Command, bool) {
// Exit early if there are no subcmds
if len(c.subcmds) == 0 {
return "", nil, false
}
// Attempt to find the closest match of a substring.
type Indexed = struct {
Name string
Value int
}
matches := make([]Indexed, 0, len(c.subcmds))
for cmdName := range c.subcmds {
matches = append(matches, Indexed{
Name: cmdName,
Value: levenshteinDistance(name, cmdName),
})
}
// Find the smallest levenshtein distance. If two values are the same,
// fallback to sorting on the name, which should give predictable results.
sort.Slice(matches, func(i, j int) bool {
if matches[i].Value < matches[j].Value {
return true
}
if matches[i].Value > matches[j].Value {
return false
}
return matches[i].Name < matches[j].Name
})
matchedName := matches[0].Name
matchedValue := matches[0].Value
// If the matched value is less than the length+1 of the string, fail the
// match.
if _, ok := c.subcmds[matchedName]; ok && matchedName != "" && matchedValue < len(matchedName)+1 {
return matchedName, c.subcmds[matchedName].command, true
}
return "", nil, false
}
// levenshteinDistance
// from https://groups.google.com/forum/#!topic/golang-nuts/YyH1f_qCZVc
// (no min, compute lengths once, 2 rows array)
// fastest profiled
func levenshteinDistance(a, b string) int {
la := len(a)
lb := len(b)
d := make([]int, la+1)
var lastdiag, olddiag, temp int
for i := 1; i <= la; i++ {
d[i] = i
}
for i := 1; i <= lb; i++ {
d[0] = i
lastdiag = i - 1
for j := 1; j <= la; j++ {
olddiag = d[j]
min := d[j] + 1
if (d[j-1] + 1) < min {
min = d[j-1] + 1
}
if a[j-1] == b[i-1] {
temp = 0
} else {
temp = 1
}
if (lastdiag + temp) < min {
min = lastdiag + temp
}
d[j] = min
lastdiag = olddiag
}
}
return d[la]
}
type missingCommand struct {
CommandBase
callback MissingCallback
superName string
name string
args []string
}
// Missing commands only need to supply Info for the interface, but this is
// never called.
func (c *missingCommand) Info() *Info {
return nil
}
func (c *missingCommand) Run(ctx *Context) error {
err := c.callback(ctx, c.name, c.args)
_, isUnrecognized := err.(*UnrecognizedCommand)
if !isUnrecognized {
return err
}
return DefaultUnrecognizedCommand(fmt.Sprintf("%s %s", c.superName, c.name))
}
// Deprecated calls into the check interface if one was specified,
// otherwise it says the command isn't deprecated.
func (r commandReference) Deprecated() (bool, string) {
if r.check == nil {
return false, ""
}
return r.check.Deprecated()
}
golang-github-juju-cmd-3.0.14/supercommand_test.go 0000664 0000000 0000000 00000065061 14523706772 0022152 0 ustar 00root root 0000000 0000000 // Copyright 2012, 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package cmd_test
import (
"errors"
"fmt"
"io/ioutil"
"path/filepath"
"strings"
"github.com/juju/gnuflag"
"github.com/juju/loggo"
gitjujutesting "github.com/juju/testing"
gc "gopkg.in/check.v1"
"github.com/juju/cmd/v3"
"github.com/juju/cmd/v3/cmdtesting"
)
func initDefenestrate(args []string) (*cmd.SuperCommand, *TestCommand, error) {
jc := cmd.NewSuperCommand(cmd.SuperCommandParams{Name: "jujutest"})
tc := &TestCommand{Name: "defenestrate"}
jc.Register(tc)
return jc, tc, cmdtesting.InitCommand(jc, args)
}
func initDefenestrateWithAliases(c *gc.C, args []string) (*cmd.SuperCommand, *TestCommand, error) {
dir := c.MkDir()
filename := filepath.Join(dir, "aliases")
err := ioutil.WriteFile(filename, []byte(`
def = defenestrate
be-firm = defenestrate --option firmly
other = missing
`), 0644)
c.Assert(err, gc.IsNil)
jc := cmd.NewSuperCommand(cmd.SuperCommandParams{Name: "jujutest", UserAliasesFilename: filename})
tc := &TestCommand{Name: "defenestrate"}
jc.Register(tc)
return jc, tc, cmdtesting.InitCommand(jc, args)
}
type SuperCommandSuite struct {
gitjujutesting.IsolationSuite
ctx *cmd.Context
}
var _ = gc.Suite(&SuperCommandSuite{})
func baseSubcommandsPlus(newCommands map[string]string) map[string]string {
subcommands := map[string]string{
"documentation": "Generate the documentation for all commands",
"help": "Show help on a command or other topic.",
}
for name, purpose := range newCommands {
subcommands[name] = purpose
}
return subcommands
}
func (s *SuperCommandSuite) SetUpTest(c *gc.C) {
s.IsolationSuite.SetUpTest(c)
s.ctx = cmdtesting.Context(c)
loggo.ReplaceDefaultWriter(cmd.NewWarningWriter(s.ctx.Stderr))
}
func (s *SuperCommandSuite) TestDispatch(c *gc.C) {
jc := cmd.NewSuperCommand(cmd.SuperCommandParams{Name: "jujutest"})
info := jc.Info()
c.Assert(info.Name, gc.Equals, "jujutest")
c.Assert(info.Args, gc.Equals, " ...")
c.Assert(info.Doc, gc.Equals, "")
c.Assert(info.Subcommands, gc.DeepEquals, baseSubcommandsPlus(nil))
jc, _, err := initDefenestrate([]string{"discombobulate"})
c.Assert(err, gc.ErrorMatches, "unrecognized command: jujutest discombobulate")
info = jc.Info()
c.Assert(info.Name, gc.Equals, "jujutest")
c.Assert(info.Args, gc.Equals, " ...")
c.Assert(info.Doc, gc.Equals, "")
c.Assert(info.Subcommands, gc.DeepEquals, baseSubcommandsPlus(map[string]string{
"defenestrate": "defenestrate the juju",
}))
jc, tc, err := initDefenestrate([]string{"defenestrate"})
c.Assert(err, gc.IsNil)
c.Assert(tc.Option, gc.Equals, "")
info = jc.Info()
c.Assert(info.Name, gc.Equals, "jujutest defenestrate")
c.Assert(info.Args, gc.Equals, "")
c.Assert(info.Doc, gc.Equals, "defenestrate-doc")
_, tc, err = initDefenestrate([]string{"defenestrate", "--option", "firmly"})
c.Assert(err, gc.IsNil)
c.Assert(tc.Option, gc.Equals, "firmly")
_, tc, err = initDefenestrate([]string{"defenestrate", "gibberish"})
c.Assert(err, gc.ErrorMatches, `unrecognized args: \["gibberish"\]`)
// --description must be used on it's own.
_, _, err = initDefenestrate([]string{"--description", "defenestrate"})
c.Assert(err, gc.ErrorMatches, `unrecognized args: \["defenestrate"\]`)
// --no-alias is not a valid option if there is no alias file speciifed
_, _, err = initDefenestrate([]string{"--no-alias", "defenestrate"})
c.Assert(err, gc.ErrorMatches, `flag provided but not defined: --no-alias`)
}
func (s *SuperCommandSuite) TestUserAliasDispatch(c *gc.C) {
// Can still use the full name.
jc, tc, err := initDefenestrateWithAliases(c, []string{"defenestrate"})
c.Assert(err, gc.IsNil)
c.Assert(tc.Option, gc.Equals, "")
info := jc.Info()
c.Assert(info.Name, gc.Equals, "jujutest defenestrate")
c.Assert(info.Args, gc.Equals, "")
c.Assert(info.Doc, gc.Equals, "defenestrate-doc")
jc, tc, err = initDefenestrateWithAliases(c, []string{"def"})
c.Assert(err, gc.IsNil)
c.Assert(tc.Option, gc.Equals, "")
info = jc.Info()
c.Assert(info.Name, gc.Equals, "jujutest defenestrate")
jc, tc, err = initDefenestrateWithAliases(c, []string{"be-firm"})
c.Assert(err, gc.IsNil)
c.Assert(tc.Option, gc.Equals, "firmly")
info = jc.Info()
c.Assert(info.Name, gc.Equals, "jujutest defenestrate")
_, _, err = initDefenestrateWithAliases(c, []string{"--no-alias", "def"})
c.Assert(err, gc.ErrorMatches, "unrecognized command: jujutest def")
// Aliases to missing values are converted before lookup.
_, _, err = initDefenestrateWithAliases(c, []string{"other"})
c.Assert(err, gc.ErrorMatches, "unrecognized command: jujutest missing")
}
func (s *SuperCommandSuite) TestRegister(c *gc.C) {
jc := cmd.NewSuperCommand(cmd.SuperCommandParams{Name: "jujutest"})
jc.Register(&TestCommand{Name: "flip"})
jc.Register(&TestCommand{Name: "flap"})
badCall := func() { jc.Register(&TestCommand{Name: "flap"}) }
c.Assert(badCall, gc.PanicMatches, `command already registered: "flap"`)
}
func (s *SuperCommandSuite) TestAliasesRegistered(c *gc.C) {
jc := cmd.NewSuperCommand(cmd.SuperCommandParams{Name: "jujutest"})
jc.Register(&TestCommand{Name: "flip", Aliases: []string{"flap", "flop"}})
info := jc.Info()
c.Assert(info.Subcommands, gc.DeepEquals, baseSubcommandsPlus(map[string]string{
"flap": "Alias for 'flip'.",
"flip": "flip the juju",
"flop": "Alias for 'flip'.",
}))
}
func (s *SuperCommandSuite) TestInfo(c *gc.C) {
jc := cmd.NewSuperCommand(cmd.SuperCommandParams{
Name: "jujutest",
Purpose: "to be purposeful",
Doc: "doc\nblah\ndoc",
})
info := jc.Info()
c.Assert(info.Name, gc.Equals, "jujutest")
c.Assert(info.Purpose, gc.Equals, "to be purposeful")
c.Assert(info.Doc, gc.Matches, jc.Doc)
c.Assert(info.Subcommands, gc.DeepEquals, baseSubcommandsPlus(nil))
subcommands := baseSubcommandsPlus(map[string]string{
"flapbabble": "flapbabble the juju",
"flip": "flip the juju",
})
jc.Register(&TestCommand{Name: "flip"})
jc.Register(&TestCommand{Name: "flapbabble"})
info = jc.Info()
c.Assert(info.Doc, gc.Matches, jc.Doc)
c.Assert(info.Subcommands, gc.DeepEquals, subcommands)
jc.Doc = ""
info = jc.Info()
c.Assert(info.Doc, gc.Equals, "")
c.Assert(info.Subcommands, gc.DeepEquals, subcommands)
}
type testVersionFlagCommand struct {
cmd.CommandBase
version string
}
func (c *testVersionFlagCommand) Info() *cmd.Info {
return &cmd.Info{Name: "test"}
}
func (c *testVersionFlagCommand) SetFlags(f *gnuflag.FlagSet) {
f.StringVar(&c.version, "version", "", "")
}
func (c *testVersionFlagCommand) Run(_ *cmd.Context) error {
return nil
}
func (s *SuperCommandSuite) TestVersionVerb(c *gc.C) {
s.testVersion(c, []string{"version"})
}
func (s *SuperCommandSuite) TestVersionFlag(c *gc.C) {
s.testVersion(c, []string{"--version"})
}
func (s *SuperCommandSuite) testVersion(c *gc.C, params []string) {
jc := cmd.NewSuperCommand(cmd.SuperCommandParams{
Name: "jujutest",
Purpose: "to be purposeful",
Doc: "doc\nblah\ndoc",
Version: "111.222.333",
})
testVersionFlagCommand := &testVersionFlagCommand{}
jc.Register(testVersionFlagCommand)
code := cmd.Main(jc, s.ctx, params)
c.Check(code, gc.Equals, 0)
c.Assert(cmdtesting.Stderr(s.ctx), gc.Equals, "")
c.Assert(cmdtesting.Stdout(s.ctx), gc.Equals, "111.222.333\n")
}
func (s *SuperCommandSuite) TestVersionFlagSpecific(c *gc.C) {
jc := cmd.NewSuperCommand(cmd.SuperCommandParams{
Name: "jujutest",
Purpose: "to be purposeful",
Doc: "doc\nblah\ndoc",
Version: "111.222.333",
})
testVersionFlagCommand := &testVersionFlagCommand{}
jc.Register(testVersionFlagCommand)
// juju test --version should update testVersionFlagCommand.version,
// and there should be no output. The --version flag on the 'test'
// subcommand has a different type to the "juju --version" flag.
code := cmd.Main(jc, s.ctx, []string{"test", "--version=abc.123"})
c.Check(code, gc.Equals, 0)
c.Assert(cmdtesting.Stderr(s.ctx), gc.Equals, "")
c.Assert(cmdtesting.Stdout(s.ctx), gc.Equals, "")
c.Assert(testVersionFlagCommand.version, gc.Equals, "abc.123")
}
func (s *SuperCommandSuite) TestVersionNotProvidedVerb(c *gc.C) {
jc := cmd.NewSuperCommand(cmd.SuperCommandParams{
Name: "jujutest",
Purpose: "to be purposeful",
Doc: "doc\nblah\ndoc",
})
// juju version
code := cmd.Main(jc, s.ctx, []string{"version"})
c.Check(code, gc.Not(gc.Equals), 0)
c.Assert(cmdtesting.Stderr(s.ctx), gc.Equals, "ERROR unrecognized command: jujutest version\n")
}
func (s *SuperCommandSuite) TestVersionNotProvidedFlag(c *gc.C) {
jc := cmd.NewSuperCommand(cmd.SuperCommandParams{
Name: "jujutest",
Purpose: "to be purposeful",
Doc: "doc\nblah\ndoc",
})
// juju --version
code := cmd.Main(jc, s.ctx, []string{"--version"})
c.Check(code, gc.Not(gc.Equals), 0)
c.Assert(cmdtesting.Stderr(s.ctx), gc.Equals, "ERROR flag provided but not defined: --version\n")
}
func (s *SuperCommandSuite) TestVersionNotProvidedOption(c *gc.C) {
jc := cmd.NewSuperCommand(cmd.SuperCommandParams{
Name: "jujutest",
Purpose: "to be purposeful",
Doc: "doc\nblah\ndoc",
})
// juju --version where flags are known as options
jc.FlagKnownAs = "option"
code := cmd.Main(jc, s.ctx, []string{"--version"})
c.Check(code, gc.Not(gc.Equals), 0)
c.Assert(cmdtesting.Stderr(s.ctx), gc.Equals, "ERROR option provided but not defined: --version\n")
}
func (s *SuperCommandSuite) TestLogging(c *gc.C) {
sc := cmd.NewSuperCommand(cmd.SuperCommandParams{
UsagePrefix: "juju",
Name: "command",
Log: &cmd.Log{},
})
sc.Register(&TestCommand{Name: "blah"})
code := cmd.Main(sc, s.ctx, []string{"blah", "--option", "error", "--debug"})
c.Assert(code, gc.Equals, 1)
c.Assert(cmdtesting.Stderr(s.ctx), gc.Matches, `(?m)ERROR BAM!\n.* DEBUG .* error stack: \n.*`)
}
type notifyTest struct {
usagePrefix string
name string
expectName string
}
func (s *SuperCommandSuite) TestNotifyRunJujuJuju(c *gc.C) {
s.testNotifyRun(c, notifyTest{"juju", "juju", "juju"})
}
func (s *SuperCommandSuite) TestNotifyRunSomethingElse(c *gc.C) {
s.testNotifyRun(c, notifyTest{"something", "else", "something else"})
}
func (s *SuperCommandSuite) TestNotifyRunJuju(c *gc.C) {
s.testNotifyRun(c, notifyTest{"", "juju", "juju"})
}
func (s *SuperCommandSuite) TestNotifyRunMyApp(c *gc.C) {
s.testNotifyRun(c, notifyTest{"", "myapp", "myapp"})
}
func (s *SuperCommandSuite) testNotifyRun(c *gc.C, test notifyTest) {
notifyName := ""
sc := cmd.NewSuperCommand(cmd.SuperCommandParams{
UsagePrefix: test.usagePrefix,
Name: test.name,
NotifyRun: func(name string) {
notifyName = name
},
Log: &cmd.Log{},
})
sc.Register(&TestCommand{Name: "blah"})
code := cmd.Main(sc, s.ctx, []string{"blah", "--option", "error"})
c.Assert(cmdtesting.Stderr(s.ctx), gc.Matches, "ERROR BAM!\n")
c.Assert(code, gc.Equals, 1)
c.Assert(notifyName, gc.Equals, test.expectName)
}
func (s *SuperCommandSuite) TestDescription(c *gc.C) {
jc := cmd.NewSuperCommand(cmd.SuperCommandParams{Name: "jujutest", Purpose: "blow up the death star"})
jc.Register(&TestCommand{Name: "blah"})
code := cmd.Main(jc, s.ctx, []string{"blah", "--description"})
c.Assert(code, gc.Equals, 0)
c.Assert(cmdtesting.Stdout(s.ctx), gc.Equals, "blow up the death star\n")
}
func NewSuperWithCallback(callback func(*cmd.Context, string, []string) error) cmd.Command {
return cmd.NewSuperCommand(cmd.SuperCommandParams{
Name: "jujutest",
Log: &cmd.Log{},
MissingCallback: callback,
})
}
func (s *SuperCommandSuite) TestMissingCallback(c *gc.C) {
var calledName string
var calledArgs []string
callback := func(ctx *cmd.Context, subcommand string, args []string) error {
calledName = subcommand
calledArgs = args
return nil
}
code := cmd.Main(
NewSuperWithCallback(callback),
cmdtesting.Context(c),
[]string{"foo", "bar", "baz", "--debug"})
c.Assert(code, gc.Equals, 0)
c.Assert(calledName, gc.Equals, "foo")
c.Assert(calledArgs, gc.DeepEquals, []string{"bar", "baz", "--debug"})
}
func (s *SuperCommandSuite) TestMissingCallbackErrors(c *gc.C) {
callback := func(ctx *cmd.Context, subcommand string, args []string) error {
return fmt.Errorf("command not found %q", subcommand)
}
code := cmd.Main(NewSuperWithCallback(callback), s.ctx, []string{"foo"})
c.Assert(code, gc.Equals, 1)
c.Assert(cmdtesting.Stdout(s.ctx), gc.Equals, "")
c.Assert(cmdtesting.Stderr(s.ctx), gc.Equals, "ERROR command not found \"foo\"\n")
}
func (s *SuperCommandSuite) TestMissingCallbackContextWiredIn(c *gc.C) {
callback := func(ctx *cmd.Context, subcommand string, args []string) error {
fmt.Fprintf(ctx.Stdout, "this is std out")
fmt.Fprintf(ctx.Stderr, "this is std err")
return nil
}
code := cmd.Main(NewSuperWithCallback(callback), s.ctx, []string{"foo", "bar", "baz", "--debug"})
c.Assert(code, gc.Equals, 0)
c.Assert(cmdtesting.Stdout(s.ctx), gc.Equals, "this is std out")
c.Assert(cmdtesting.Stderr(s.ctx), gc.Equals, "this is std err")
}
func (s *SuperCommandSuite) TestSupercommandAliases(c *gc.C) {
jc := cmd.NewSuperCommand(cmd.SuperCommandParams{
Name: "jujutest",
UsagePrefix: "juju",
})
sub := cmd.NewSuperCommand(cmd.SuperCommandParams{
Name: "jubar",
UsagePrefix: "juju jujutest",
Aliases: []string{"jubaz", "jubing"},
})
info := sub.Info()
c.Check(info.Aliases, gc.DeepEquals, []string{"jubaz", "jubing"})
jc.Register(sub)
for _, name := range []string{"jubar", "jubaz", "jubing"} {
c.Logf("testing command name %q", name)
s.SetUpTest(c)
code := cmd.Main(jc, s.ctx, []string{name, "--help"})
c.Assert(code, gc.Equals, 0)
c.Assert(cmdtesting.Stdout(s.ctx), gc.Matches, "(?s).*Usage: juju jujutest jubar.*")
c.Assert(cmdtesting.Stdout(s.ctx), gc.Matches, "(?s).*Aliases: jubaz, jubing.*")
s.TearDownTest(c)
}
}
type simple struct {
cmd.CommandBase
name string
args []string
}
var _ cmd.Command = (*simple)(nil)
func (s *simple) Info() *cmd.Info {
return &cmd.Info{Name: s.name, Purpose: "to be simple"}
}
func (s *simple) Init(args []string) error {
s.args = args
return nil
}
func (s *simple) Run(ctx *cmd.Context) error {
fmt.Fprintf(ctx.Stdout, "%s %s\n", s.name, strings.Join(s.args, ", "))
return nil
}
type deprecate struct {
replacement string
obsolete bool
}
func (d deprecate) Deprecated() (bool, string) {
if d.replacement == "" {
return false, ""
}
return true, d.replacement
}
func (d deprecate) Obsolete() bool {
return d.obsolete
}
func (s *SuperCommandSuite) TestRegisterAlias(c *gc.C) {
jc := cmd.NewSuperCommand(cmd.SuperCommandParams{
Name: "jujutest",
})
jc.Register(&simple{name: "test"})
jc.RegisterAlias("foo", "test", nil)
jc.RegisterAlias("bar", "test", deprecate{replacement: "test"})
jc.RegisterAlias("baz", "test", deprecate{obsolete: true})
c.Assert(
func() { jc.RegisterAlias("omg", "unknown", nil) },
gc.PanicMatches, `"unknown" not found when registering alias`)
info := jc.Info()
// NOTE: deprecated `bar` not shown in commands.
c.Assert(info.Doc, gc.Equals, "")
c.Assert(info.Subcommands, gc.DeepEquals, baseSubcommandsPlus(map[string]string{
"foo": "Alias for 'test'.",
"test": "to be simple",
}))
for _, test := range []struct {
name string
stdout string
stderr string
code int
}{
{
name: "test",
stdout: "test arg\n",
}, {
name: "foo",
stdout: "test arg\n",
}, {
name: "bar",
stdout: "test arg\n",
stderr: "WARNING \"bar\" is deprecated, please use \"test\"\n",
}, {
name: "baz",
stderr: "ERROR unrecognized command: jujutest baz\n",
code: 2,
},
} {
s.SetUpTest(c)
code := cmd.Main(jc, s.ctx, []string{test.name, "arg"})
c.Check(code, gc.Equals, test.code)
c.Check(cmdtesting.Stdout(s.ctx), gc.Equals, test.stdout)
c.Check(cmdtesting.Stderr(s.ctx), gc.Equals, test.stderr)
s.TearDownTest(c)
}
}
func (s *SuperCommandSuite) TestRegisterSuperAlias(c *gc.C) {
jc := cmd.NewSuperCommand(cmd.SuperCommandParams{
Name: "jujutest",
})
jc.Register(&simple{name: "test"})
sub := cmd.NewSuperCommand(cmd.SuperCommandParams{
Name: "bar",
UsagePrefix: "jujutest",
Purpose: "bar functions",
})
jc.Register(sub)
sub.Register(&simple{name: "foo"})
c.Assert(
func() { jc.RegisterSuperAlias("bar-foo", "unknown", "foo", nil) },
gc.PanicMatches, `"unknown" not found when registering alias`)
c.Assert(
func() { jc.RegisterSuperAlias("bar-foo", "test", "foo", nil) },
gc.PanicMatches, `"test" is not a SuperCommand`)
c.Assert(
func() { jc.RegisterSuperAlias("bar-foo", "bar", "unknown", nil) },
gc.PanicMatches, `"unknown" not found as a command in "bar"`)
jc.RegisterSuperAlias("bar-foo", "bar", "foo", nil)
jc.RegisterSuperAlias("bar-dep", "bar", "foo", deprecate{replacement: "bar foo"})
jc.RegisterSuperAlias("bar-ob", "bar", "foo", deprecate{obsolete: true})
info := jc.Info()
// NOTE: deprecated `bar` not shown in commands.
c.Assert(info.Subcommands, gc.DeepEquals, baseSubcommandsPlus(map[string]string{
"bar": "bar functions",
"bar-foo": "Alias for 'bar foo'.",
"test": "to be simple",
}))
for _, test := range []struct {
args []string
stdout string
stderr string
code int
}{
{
args: []string{"bar", "foo", "arg"},
stdout: "foo arg\n",
}, {
args: []string{"bar-foo", "arg"},
stdout: "foo arg\n",
}, {
args: []string{"bar-dep", "arg"},
stdout: "foo arg\n",
stderr: "WARNING \"bar-dep\" is deprecated, please use \"bar foo\"\n",
}, {
args: []string{"bar-ob", "arg"},
stderr: "ERROR unrecognized command: jujutest bar-ob\n",
code: 2,
},
} {
s.SetUpTest(c)
code := cmd.Main(jc, s.ctx, test.args)
c.Check(code, gc.Equals, test.code)
c.Check(cmdtesting.Stdout(s.ctx), gc.Equals, test.stdout)
c.Check(cmdtesting.Stderr(s.ctx), gc.Equals, test.stderr)
s.TearDownTest(c)
}
}
type simpleAlias struct {
simple
}
func (s *simpleAlias) Info() *cmd.Info {
return &cmd.Info{Name: s.name, Purpose: "to be simple with an alias",
Aliases: []string{s.name + "-alias"}}
}
func (s *SuperCommandSuite) TestRegisterDeprecated(c *gc.C) {
jc := cmd.NewSuperCommand(cmd.SuperCommandParams{
Name: "jujutest",
})
// Test that calling with a nil command will not panic
jc.RegisterDeprecated(nil, nil)
jc.RegisterDeprecated(&simpleAlias{simple{name: "test-non-dep"}}, nil)
jc.RegisterDeprecated(&simpleAlias{simple{name: "test-dep"}}, deprecate{replacement: "test-dep-new"})
jc.RegisterDeprecated(&simpleAlias{simple{name: "test-ob"}}, deprecate{obsolete: true})
badCall := func() {
jc.RegisterDeprecated(&simpleAlias{simple{name: "test-dep"}}, deprecate{replacement: "test-dep-new"})
}
c.Assert(badCall, gc.PanicMatches, `command already registered: "test-dep"`)
for _, test := range []struct {
args []string
stdout string
stderr string
code int
}{
{
args: []string{"test-non-dep", "arg"},
stdout: "test-non-dep arg\n",
}, {
args: []string{"test-non-dep-alias", "arg"},
stdout: "test-non-dep arg\n",
}, {
args: []string{"test-dep", "arg"},
stdout: "test-dep arg\n",
stderr: "WARNING \"test-dep\" is deprecated, please use \"test-dep-new\"\n",
}, {
args: []string{"test-dep-alias", "arg"},
stdout: "test-dep arg\n",
stderr: "WARNING \"test-dep-alias\" is deprecated, please use \"test-dep-new\"\n",
}, {
args: []string{"test-ob", "arg"},
stderr: "ERROR unrecognized command: jujutest test-ob\n",
code: 2,
}, {
args: []string{"test-ob-alias", "arg"},
stderr: "ERROR unrecognized command: jujutest test-ob-alias\n",
code: 2,
},
} {
s.SetUpTest(c)
code := cmd.Main(jc, s.ctx, test.args)
c.Check(code, gc.Equals, test.code)
c.Check(cmdtesting.Stderr(s.ctx), gc.Equals, test.stderr)
c.Check(cmdtesting.Stdout(s.ctx), gc.Equals, test.stdout)
s.TearDownTest(c)
}
}
func (s *SuperCommandSuite) TestGlobalFlagsBeforeCommand(c *gc.C) {
flag := ""
sc := cmd.NewSuperCommand(cmd.SuperCommandParams{
UsagePrefix: "juju",
Name: "command",
GlobalFlags: flagAdderFunc(func(fset *gnuflag.FlagSet) {
fset.StringVar(&flag, "testflag", "", "global test flag")
}),
Log: &cmd.Log{},
})
sc.Register(&TestCommand{Name: "blah"})
code := cmd.Main(sc, s.ctx, []string{
"--testflag=something",
"blah",
"--option=testoption",
})
c.Assert(code, gc.Equals, 0)
c.Assert(flag, gc.Equals, "something")
c.Check(cmdtesting.Stdout(s.ctx), gc.Equals, "testoption\n")
}
func (s *SuperCommandSuite) TestGlobalFlagsAfterCommand(c *gc.C) {
flag := ""
sc := cmd.NewSuperCommand(cmd.SuperCommandParams{
UsagePrefix: "juju",
Name: "command",
GlobalFlags: flagAdderFunc(func(fset *gnuflag.FlagSet) {
fset.StringVar(&flag, "testflag", "", "global test flag")
}),
Log: &cmd.Log{},
})
sc.Register(&TestCommand{Name: "blah"})
code := cmd.Main(sc, s.ctx, []string{
"blah",
"--option=testoption",
"--testflag=something",
})
c.Assert(code, gc.Equals, 0)
c.Assert(flag, gc.Equals, "something")
c.Check(cmdtesting.Stdout(s.ctx), gc.Equals, "testoption\n")
}
func (s *SuperCommandSuite) TestSuperSetFlags(c *gc.C) {
sc := cmd.NewSuperCommand(cmd.SuperCommandParams{
UsagePrefix: "juju",
Name: "command",
Log: &cmd.Log{},
FlagKnownAs: "option",
})
s.assertFlagsAlias(c, sc, "option")
}
func (s *SuperCommandSuite) TestSuperSetFlagsDefault(c *gc.C) {
sc := cmd.NewSuperCommand(cmd.SuperCommandParams{
UsagePrefix: "juju",
Name: "command",
Log: &cmd.Log{},
})
s.assertFlagsAlias(c, sc, "flag")
}
func (s *SuperCommandSuite) assertFlagsAlias(c *gc.C, sc *cmd.SuperCommand, expectedAlias string) {
sc.Register(&TestCommand{Name: "blah"})
code := cmd.Main(sc, s.ctx, []string{
"blah",
"--fluffs",
})
c.Assert(code, gc.Equals, 2)
c.Check(s.ctx.IsSerial(), gc.Equals, false)
c.Check(cmdtesting.Stdout(s.ctx), gc.Equals, "")
c.Check(cmdtesting.Stderr(s.ctx), gc.Equals, fmt.Sprintf("ERROR %v provided but not defined: --fluffs\n", expectedAlias))
}
func (s *SuperCommandSuite) TestErrInJson(c *gc.C) {
output := cmd.Output{}
sc := cmd.NewSuperCommand(cmd.SuperCommandParams{
UsagePrefix: "juju",
Name: "command",
Log: &cmd.Log{},
GlobalFlags: flagAdderFunc(func(fset *gnuflag.FlagSet) {
output.AddFlags(fset, "json", map[string]cmd.Formatter{"json": cmd.FormatJson})
}),
})
s.assertFormattingErr(c, sc, "json")
}
func (s *SuperCommandSuite) TestErrInYaml(c *gc.C) {
output := cmd.Output{}
sc := cmd.NewSuperCommand(cmd.SuperCommandParams{
UsagePrefix: "juju",
Name: "command",
Log: &cmd.Log{},
GlobalFlags: flagAdderFunc(func(fset *gnuflag.FlagSet) {
output.AddFlags(fset, "yaml", map[string]cmd.Formatter{"yaml": cmd.FormatYaml})
}),
})
s.assertFormattingErr(c, sc, "yaml")
}
func (s *SuperCommandSuite) TestErrInJsonWithOutput(c *gc.C) {
output := cmd.Output{}
sc := cmd.NewSuperCommand(cmd.SuperCommandParams{
UsagePrefix: "juju",
Name: "command",
Log: &cmd.Log{},
GlobalFlags: flagAdderFunc(func(fset *gnuflag.FlagSet) {
output.AddFlags(fset, "json", map[string]cmd.Formatter{"json": cmd.FormatJson})
}),
})
// This command will throw an error during the run after logging a structured output.
testCmd := &TestCommand{
Name: "blah",
Option: "error",
CustomRun: func(ctx *cmd.Context) error {
output.Write(ctx, struct {
Name string `json:"name"`
}{Name: "test"})
return errors.New("BAM!")
},
}
sc.Register(testCmd)
code := cmd.Main(sc, s.ctx, []string{
"blah",
"--format=json",
"--option=error",
})
c.Assert(code, gc.Equals, 1)
c.Check(s.ctx.IsSerial(), gc.Equals, true)
c.Check(cmdtesting.Stderr(s.ctx), gc.Matches, "ERROR BAM!\n")
c.Check(cmdtesting.Stdout(s.ctx), gc.Equals, "{\"name\":\"test\"}\n")
}
func (s *SuperCommandSuite) assertFormattingErr(c *gc.C, sc *cmd.SuperCommand, format string) {
// This command will throw an error during the run
testCmd := &TestCommand{Name: "blah", Option: "error"}
sc.Register(testCmd)
formatting := fmt.Sprintf("--format=%v", format)
code := cmd.Main(sc, s.ctx, []string{
"blah",
formatting,
"--option=error",
})
c.Assert(code, gc.Equals, 1)
c.Check(s.ctx.IsSerial(), gc.Equals, true)
c.Check(cmdtesting.Stderr(s.ctx), gc.Matches, "ERROR BAM!\n")
c.Check(cmdtesting.Stdout(s.ctx), gc.Equals, "{}\n")
}
type flagAdderFunc func(*gnuflag.FlagSet)
func (f flagAdderFunc) AddFlags(fset *gnuflag.FlagSet) {
f(fset)
}
func (s *SuperCommandSuite) TestFindClosestSubCommand(c *gc.C) {
sc := cmd.NewSuperCommand(cmd.SuperCommandParams{
UsagePrefix: "juju",
Name: "command",
Log: &cmd.Log{},
})
name, _, ok := sc.FindClosestSubCommand("halp")
c.Assert(ok, gc.Equals, true)
c.Assert(name, gc.Equals, "help")
}
func (s *SuperCommandSuite) TestFindClosestSubCommandReturnsExactMatch(c *gc.C) {
sc := cmd.NewSuperCommand(cmd.SuperCommandParams{
UsagePrefix: "juju",
Name: "command",
Log: &cmd.Log{},
})
name, _, ok := sc.FindClosestSubCommand("help")
c.Assert(ok, gc.Equals, true)
c.Assert(name, gc.Equals, "help")
}
func (s *SuperCommandSuite) TestFindClosestSubCommandReturnsNonExactMatch(c *gc.C) {
sc := cmd.NewSuperCommand(cmd.SuperCommandParams{
UsagePrefix: "juju",
Name: "command",
Log: &cmd.Log{},
})
_, _, ok := sc.FindClosestSubCommand("sillycommand")
c.Assert(ok, gc.Equals, false)
}
func (s *SuperCommandSuite) TestFindClosestSubCommandReturnsWithPartialName(c *gc.C) {
sc := cmd.NewSuperCommand(cmd.SuperCommandParams{
UsagePrefix: "juju",
Name: "command",
Log: &cmd.Log{},
})
name, _, ok := sc.FindClosestSubCommand("hel")
c.Assert(ok, gc.Equals, true)
c.Assert(name, gc.Equals, "help")
}
func (s *SuperCommandSuite) TestFindClosestSubCommandReturnsWithLessMisspeltName(c *gc.C) {
sc := cmd.NewSuperCommand(cmd.SuperCommandParams{
UsagePrefix: "juju",
Name: "command",
Log: &cmd.Log{},
})
name, _, ok := sc.FindClosestSubCommand("hlp")
c.Assert(ok, gc.Equals, true)
c.Assert(name, gc.Equals, "help")
}
func (s *SuperCommandSuite) TestFindClosestSubCommandReturnsWithMoreName(c *gc.C) {
sc := cmd.NewSuperCommand(cmd.SuperCommandParams{
UsagePrefix: "juju",
Name: "command",
Log: &cmd.Log{},
})
name, _, ok := sc.FindClosestSubCommand("helper")
c.Assert(ok, gc.Equals, true)
c.Assert(name, gc.Equals, "help")
}
func (s *SuperCommandSuite) TestFindClosestSubCommandReturnsConsistentResults(c *gc.C) {
sc := cmd.NewSuperCommand(cmd.SuperCommandParams{
UsagePrefix: "juju",
Name: "command",
Log: &cmd.Log{},
})
sc.Register(cmd.NewSuperCommand(cmd.SuperCommandParams{
UsagePrefix: "hxlp",
Name: "hxlp",
Log: &cmd.Log{},
}))
sc.Register(cmd.NewSuperCommand(cmd.SuperCommandParams{
UsagePrefix: "hflp",
Name: "hflp",
Log: &cmd.Log{},
}))
name, _, ok := sc.FindClosestSubCommand("helper")
c.Assert(ok, gc.Equals, true)
c.Assert(name, gc.Equals, "help")
}
golang-github-juju-cmd-3.0.14/util_test.go 0000664 0000000 0000000 00000003412 14523706772 0020422 0 ustar 00root root 0000000 0000000 // Copyright 2012, 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmd_test
import (
"bytes"
"errors"
"fmt"
"io"
"github.com/juju/gnuflag"
"github.com/juju/cmd/v3"
)
func bufferString(stream io.Writer) string {
return stream.(*bytes.Buffer).String()
}
// TestCommand is used by several different tests.
type TestCommand struct {
cmd.CommandBase
Name string
Option string
Minimal bool
Aliases []string
FlagAKA string
CustomRun func(*cmd.Context) error
}
func (c *TestCommand) Info() *cmd.Info {
if c.Minimal {
return &cmd.Info{Name: c.Name}
}
i := &cmd.Info{
Name: c.Name,
Args: "",
Purpose: c.Name + " the juju",
Doc: c.Name + "-doc",
Aliases: c.Aliases,
}
if c.FlagAKA != "" {
i.FlagKnownAs = c.FlagAKA
}
return i
}
func (c *TestCommand) SetFlags(f *gnuflag.FlagSet) {
if !c.Minimal {
f.StringVar(&c.Option, "option", "", "option-doc")
}
}
func (c *TestCommand) Init(args []string) error {
return cmd.CheckEmpty(args)
}
func (c *TestCommand) Run(ctx *cmd.Context) error {
if c.CustomRun != nil {
return c.CustomRun(ctx)
}
switch c.Option {
case "error":
return errors.New("BAM!")
case "silent-error":
return cmd.ErrSilent
case "echo":
_, err := io.Copy(ctx.Stdout, ctx.Stdin)
return err
default:
fmt.Fprintln(ctx.Stdout, c.Option)
}
return nil
}
// minimalHelp and fullHelp are the expected help strings for a TestCommand
// with Name "verb", with and without Minimal set.
var minimalHelp = "Usage: verb\n"
var optionHelp = `Usage: verb [options]
Summary:
verb the juju
Options:
--option (= "")
option-doc
`
var fullHelp = `Usage: verb [%vs]
Summary:
verb the juju
%vs:
--option (= "")
option-doc
Details:
verb-doc
`
golang-github-juju-cmd-3.0.14/version.go 0000664 0000000 0000000 00000002126 14523706772 0020074 0 ustar 00root root 0000000 0000000 // Copyright 2012, 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmd
import (
"github.com/juju/gnuflag"
)
// versionCommand is a cmd.Command that prints the current version.
type versionCommand struct {
CommandBase
out Output
version string
versionDetail interface{}
showAll bool
}
func newVersionCommand(version string, versionDetail interface{}) *versionCommand {
return &versionCommand{
version: version,
versionDetail: versionDetail,
}
}
func (v *versionCommand) Info() *Info {
return &Info{
Name: "version",
Purpose: "Print the current version.",
}
}
func (v *versionCommand) SetFlags(f *gnuflag.FlagSet) {
formatters := make(map[string]Formatter, len(DefaultFormatters))
for k, v := range DefaultFormatters {
formatters[k] = v.Formatter
}
v.out.AddFlags(f, "smart", formatters)
f.BoolVar(&v.showAll, "all", false, "Prints all version information")
}
func (v *versionCommand) Run(ctxt *Context) error {
if v.showAll {
return v.out.Write(ctxt, v.versionDetail)
}
return v.out.Write(ctxt, v.version)
}
golang-github-juju-cmd-3.0.14/version_test.go 0000664 0000000 0000000 00000004253 14523706772 0021136 0 ustar 00root root 0000000 0000000 // Copyright 2012, 2013 Canonical Ltd.
// Licensed under the LGPLv3, see LICENSE file for details.
package cmd_test
import (
"fmt"
"github.com/juju/loggo"
"github.com/juju/testing"
gc "gopkg.in/check.v1"
"github.com/juju/cmd/v3"
"github.com/juju/cmd/v3/cmdtesting"
)
type VersionSuite struct {
testing.LoggingSuite
ctx *cmd.Context
}
var _ = gc.Suite(&VersionSuite{})
type versionDetail struct {
Version string `json:"version"`
GitCommitHash string `json:"git-commit-hash"`
GitTreeState string `json:"git-tree-state"`
}
func (s *VersionSuite) SetUpTest(c *gc.C) {
s.LoggingSuite.SetUpTest(c)
s.ctx = cmdtesting.Context(c)
loggo.ReplaceDefaultWriter(cmd.NewWarningWriter(s.ctx.Stderr))
}
func (s *VersionSuite) TestVersion(c *gc.C) {
const version = "999.888.777"
code := cmd.Main(cmd.NewVersionCommand(version, nil), s.ctx, nil)
c.Check(code, gc.Equals, 0)
c.Assert(cmdtesting.Stderr(s.ctx), gc.Equals, "")
c.Assert(cmdtesting.Stdout(s.ctx), gc.Equals, version+"\n")
}
func (s *VersionSuite) TestVersionExtraArgs(c *gc.C) {
code := cmd.Main(cmd.NewVersionCommand("xxx", nil), s.ctx, []string{"foo"})
c.Check(code, gc.Equals, 2)
c.Assert(cmdtesting.Stdout(s.ctx), gc.Equals, "")
c.Assert(cmdtesting.Stderr(s.ctx), gc.Matches, "ERROR unrecognized args.*\n")
}
func (s *VersionSuite) TestVersionJson(c *gc.C) {
const version = "999.888.777"
code := cmd.Main(cmd.NewVersionCommand(version, nil), s.ctx, []string{"--format", "json"})
c.Check(code, gc.Equals, 0)
c.Assert(cmdtesting.Stderr(s.ctx), gc.Equals, "")
c.Assert(cmdtesting.Stdout(s.ctx), gc.Equals, fmt.Sprintf("%q\n", version))
}
func (s *VersionSuite) TestVersionDetailJson(c *gc.C) {
const version = "999.888.777"
detail := versionDetail{
Version: version,
GitCommitHash: "46f1a0bd5592a2f9244ca321b129902a06b53e03",
GitTreeState: "dirty",
}
code := cmd.Main(cmd.NewVersionCommand(version, detail), s.ctx, []string{"--all", "--format", "json"})
c.Check(code, gc.Equals, 0)
c.Assert(cmdtesting.Stderr(s.ctx), gc.Equals, "")
c.Assert(cmdtesting.Stdout(s.ctx), gc.Equals, `
{"version":"999.888.777","git-commit-hash":"46f1a0bd5592a2f9244ca321b129902a06b53e03","git-tree-state":"dirty"}
`[1:])
}