github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/cli/app.go (about)

     1  package cli
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"sort"
    10  	"time"
    11  )
    12  
    13  // App is the main structure of a cli application. It is recommended that
    14  // an app be created with the cli.NewApp() function
    15  type App struct {
    16  	// The name of the program. Defaults to path.Base(os.Args[0])
    17  	Name string
    18  	// Full name of command for help, defaults to Name
    19  	HelpName string
    20  	// Description of the program.
    21  	Usage string
    22  	// Text to override the USAGE section of help
    23  	UsageText string
    24  	// Description of the program argument format.
    25  	ArgsUsage string
    26  	// Version of the program
    27  	Version string
    28  	// List of commands to execute
    29  	Commands []Command
    30  	// List of flags to parse
    31  	Flags []Flag
    32  	// Boolean to enable bash completion commands
    33  	EnableBashCompletion bool
    34  	// Boolean to hide built-in help command
    35  	HideHelp bool
    36  	// Boolean to hide built-in version flag and the VERSION section of help
    37  	HideVersion bool
    38  	// Populate on app startup, only gettable throught method Categories()
    39  	categories CommandCategories
    40  	// An action to execute when the bash-completion flag is set
    41  	BashComplete func(context *Context)
    42  	// An action to execute before any subcommands are run, but after the context is ready
    43  	// If a non-nil error is returned, no subcommands are run
    44  	Before func(context *Context) error
    45  	// An action to execute after any subcommands are run, but after the subcommand has finished
    46  	// It is run even if Action() panics
    47  	After func(context *Context) error
    48  	// The action to execute when no subcommands are specified
    49  	Action func(context *Context)
    50  	// Execute this function if the proper command cannot be found
    51  	CommandNotFound func(context *Context, command string)
    52  	// Execute this function, if an usage error occurs. This is useful for displaying customized usage error messages.
    53  	// This function is able to replace the original error messages.
    54  	// If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted.
    55  	OnUsageError func(context *Context, err error, isSubcommand bool) error
    56  	// Compilation date
    57  	Compiled time.Time
    58  	// List of all authors who contributed
    59  	Authors []Author
    60  	// Copyright of the binary if any
    61  	Copyright string
    62  	// Name of Author (Note: Use App.Authors, this is deprecated)
    63  	Author string
    64  	// Email of Author (Note: Use App.Authors, this is deprecated)
    65  	Email string
    66  	// Writer writer to write output to
    67  	Writer io.Writer
    68  }
    69  
    70  // Tries to find out when this binary was compiled.
    71  // Returns the current time if it fails to find it.
    72  func compileTime() time.Time {
    73  	info, err := os.Stat(os.Args[0])
    74  	if err != nil {
    75  		return time.Now()
    76  	}
    77  	return info.ModTime()
    78  }
    79  
    80  // Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action.
    81  func NewApp() *App {
    82  	return &App{
    83  		Name:         filepath.Base(os.Args[0]),
    84  		HelpName:     filepath.Base(os.Args[0]),
    85  		Usage:        "A new cli application",
    86  		UsageText:    "",
    87  		Version:      "0.0.0",
    88  		BashComplete: DefaultAppComplete,
    89  		Action:       helpCommand.Action,
    90  		Compiled:     compileTime(),
    91  		Writer:       os.Stdout,
    92  	}
    93  }
    94  
    95  // Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
    96  func (a *App) Run(arguments []string) (err error) {
    97  	if a.Author != "" || a.Email != "" {
    98  		a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email})
    99  	}
   100  
   101  	newCmds := []Command{}
   102  	for _, c := range a.Commands {
   103  		if c.HelpName == "" {
   104  			c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
   105  		}
   106  		newCmds = append(newCmds, c)
   107  	}
   108  	a.Commands = newCmds
   109  
   110  	a.categories = CommandCategories{}
   111  	for _, command := range a.Commands {
   112  		a.categories = a.categories.AddCommand(command.Category, command)
   113  	}
   114  	sort.Sort(a.categories)
   115  
   116  	// append help to commands
   117  	if a.Command(helpCommand.Name) == nil && !a.HideHelp {
   118  		a.Commands = append(a.Commands, helpCommand)
   119  		if (HelpFlag != BoolFlag{}) {
   120  			a.appendFlag(HelpFlag)
   121  		}
   122  	}
   123  
   124  	//append version/help flags
   125  	if a.EnableBashCompletion {
   126  		a.appendFlag(BashCompletionFlag)
   127  	}
   128  
   129  	if !a.HideVersion {
   130  		a.appendFlag(VersionFlag)
   131  	}
   132  
   133  	// parse flags
   134  	set := flagSet(a.Name, a.Flags)
   135  	set.SetOutput(ioutil.Discard)
   136  	err = set.Parse(arguments[1:])
   137  	nerr := normalizeFlags(a.Flags, set)
   138  	context := NewContext(a, set, nil)
   139  	if nerr != nil {
   140  		fmt.Fprintln(a.Writer, nerr)
   141  		ShowAppHelp(context)
   142  		return nerr
   143  	}
   144  
   145  	if checkCompletions(context) {
   146  		return nil
   147  	}
   148  
   149  	if err != nil {
   150  		if a.OnUsageError != nil {
   151  			err := a.OnUsageError(context, err, false)
   152  			return err
   153  		} else {
   154  			fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.")
   155  			ShowAppHelp(context)
   156  			return err
   157  		}
   158  	}
   159  
   160  	if !a.HideHelp && checkHelp(context) {
   161  		ShowAppHelp(context)
   162  		return nil
   163  	}
   164  
   165  	if !a.HideVersion && checkVersion(context) {
   166  		ShowVersion(context)
   167  		return nil
   168  	}
   169  
   170  	if a.After != nil {
   171  		defer func() {
   172  			if afterErr := a.After(context); afterErr != nil {
   173  				if err != nil {
   174  					err = NewMultiError(err, afterErr)
   175  				} else {
   176  					err = afterErr
   177  				}
   178  			}
   179  		}()
   180  	}
   181  
   182  	if a.Before != nil {
   183  		err = a.Before(context)
   184  		if err != nil {
   185  			fmt.Fprintf(a.Writer, "%v\n\n", err)
   186  			ShowAppHelp(context)
   187  			return err
   188  		}
   189  	}
   190  
   191  	args := context.Args()
   192  	if args.Present() {
   193  		name := args.First()
   194  		c := a.Command(name)
   195  		if c != nil {
   196  			return c.Run(context)
   197  		}
   198  	}
   199  
   200  	// Run default Action
   201  	a.Action(context)
   202  	return nil
   203  }
   204  
   205  // Another entry point to the cli app, takes care of passing arguments and error handling
   206  func (a *App) RunAndExitOnError() {
   207  	if err := a.Run(os.Args); err != nil {
   208  		fmt.Fprintln(os.Stderr, err)
   209  		os.Exit(1)
   210  	}
   211  }
   212  
   213  // Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags
   214  func (a *App) RunAsSubcommand(ctx *Context) (err error) {
   215  	// append help to commands
   216  	if len(a.Commands) > 0 {
   217  		if a.Command(helpCommand.Name) == nil && !a.HideHelp {
   218  			a.Commands = append(a.Commands, helpCommand)
   219  			if (HelpFlag != BoolFlag{}) {
   220  				a.appendFlag(HelpFlag)
   221  			}
   222  		}
   223  	}
   224  
   225  	newCmds := []Command{}
   226  	for _, c := range a.Commands {
   227  		if c.HelpName == "" {
   228  			c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
   229  		}
   230  		newCmds = append(newCmds, c)
   231  	}
   232  	a.Commands = newCmds
   233  
   234  	// append flags
   235  	if a.EnableBashCompletion {
   236  		a.appendFlag(BashCompletionFlag)
   237  	}
   238  
   239  	// parse flags
   240  	set := flagSet(a.Name, a.Flags)
   241  	set.SetOutput(ioutil.Discard)
   242  	err = set.Parse(ctx.Args().Tail())
   243  	nerr := normalizeFlags(a.Flags, set)
   244  	context := NewContext(a, set, ctx)
   245  
   246  	if nerr != nil {
   247  		fmt.Fprintln(a.Writer, nerr)
   248  		fmt.Fprintln(a.Writer)
   249  		if len(a.Commands) > 0 {
   250  			ShowSubcommandHelp(context)
   251  		} else {
   252  			ShowCommandHelp(ctx, context.Args().First())
   253  		}
   254  		return nerr
   255  	}
   256  
   257  	if checkCompletions(context) {
   258  		return nil
   259  	}
   260  
   261  	if err != nil {
   262  		if a.OnUsageError != nil {
   263  			err = a.OnUsageError(context, err, true)
   264  			return err
   265  		} else {
   266  			fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.")
   267  			ShowSubcommandHelp(context)
   268  			return err
   269  		}
   270  	}
   271  
   272  	if len(a.Commands) > 0 {
   273  		if checkSubcommandHelp(context) {
   274  			return nil
   275  		}
   276  	} else {
   277  		if checkCommandHelp(ctx, context.Args().First()) {
   278  			return nil
   279  		}
   280  	}
   281  
   282  	if a.After != nil {
   283  		defer func() {
   284  			afterErr := a.After(context)
   285  			if afterErr != nil {
   286  				if err != nil {
   287  					err = NewMultiError(err, afterErr)
   288  				} else {
   289  					err = afterErr
   290  				}
   291  			}
   292  		}()
   293  	}
   294  
   295  	if a.Before != nil {
   296  		err := a.Before(context)
   297  		if err != nil {
   298  			return err
   299  		}
   300  	}
   301  
   302  	args := context.Args()
   303  	if args.Present() {
   304  		name := args.First()
   305  		c := a.Command(name)
   306  		if c != nil {
   307  			return c.Run(context)
   308  		}
   309  	}
   310  
   311  	// Run default Action
   312  	a.Action(context)
   313  
   314  	return nil
   315  }
   316  
   317  // Returns the named command on App. Returns nil if the command does not exist
   318  func (a *App) Command(name string) *Command {
   319  	for _, c := range a.Commands {
   320  		if c.HasName(name) {
   321  			return &c
   322  		}
   323  	}
   324  
   325  	return nil
   326  }
   327  
   328  // Returnes the array containing all the categories with the commands they contain
   329  func (a *App) Categories() CommandCategories {
   330  	return a.categories
   331  }
   332  
   333  func (a *App) hasFlag(flag Flag) bool {
   334  	for _, f := range a.Flags {
   335  		if flag == f {
   336  			return true
   337  		}
   338  	}
   339  
   340  	return false
   341  }
   342  
   343  func (a *App) appendFlag(flag Flag) {
   344  	if !a.hasFlag(flag) {
   345  		a.Flags = append(a.Flags, flag)
   346  	}
   347  }
   348  
   349  // Author represents someone who has contributed to a cli project.
   350  type Author struct {
   351  	Name  string // The Authors name
   352  	Email string // The Authors email
   353  }
   354  
   355  // String makes Author comply to the Stringer interface, to allow an easy print in the templating process
   356  func (a Author) String() string {
   357  	e := ""
   358  	if a.Email != "" {
   359  		e = "<" + a.Email + "> "
   360  	}
   361  
   362  	return fmt.Sprintf("%v %v", a.Name, e)
   363  }