1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/gookit-gcli

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
cmd.go 19 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Inhere Отправлено 06.01.2022 05:21 ee4068c
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843
package gcli
import (
"errors"
"flag"
"fmt"
"os"
"strings"
"text/template"
"github.com/gookit/color"
"github.com/gookit/gcli/v3/helper"
"github.com/gookit/goutil/structs"
"github.com/gookit/goutil/strutil"
)
// Runner /Executor interface
type Runner interface {
// Config(c *Command)
Run(c *Command, args []string) error
}
// RunnerFunc definition
type RunnerFunc func(c *Command, args []string) error
// Run implement the Runner interface
func (f RunnerFunc) Run(c *Command, args []string) error {
return f(c, args)
}
const maxFunc = 64
// HandlersChain middleware handlers chain definition
type HandlersChain []RunnerFunc
// Last returns the last handler in the chain. ie. the last handler is the main own.
func (c HandlersChain) Last() RunnerFunc {
length := len(c)
if length > 0 {
return c[length-1]
}
return nil
}
// Command a CLI command structure
type Command struct {
// core is internal use
core
// cmdLine is internal use
// *cmdLine
// HelpVars
// // Hooks can allow setting some hooks func on running.
// Hooks // allowed hooks: "init", "before", "after", "error"
commandBase
// --- provide option and argument parse and binding.
// Flags options for the command
Flags
// Arguments for the command
Arguments
// Name is the full command name.
Name string
// Desc is the command description message.
Desc string
// Aliases is the command name's alias names
Aliases []string
// Category for the command
Category string
// Config func, will call on `initialize`.
// - you can config options and other init works
Config func(c *Command)
// Hidden the command on render help
Hidden bool
// --- for middleware ---
// run error
runErr error
// middleware index number
middleIdx int8
// middleware functions
middles HandlersChain
// errorHandler // loop find parent.errorHandler
// path names of the command. 'parent current'
pathNames []string
// Parent parent command
parent *Command
// Subs sub commands of the Command
// NOTICE: if command has been initialized, adding through this field is invalid
Subs []*Command
// module is the name for grouped commands
// subName is the name for grouped commands
// eg: "sys:info" -> module: "sys", subName: "info"
// module, subName string
// Examples some usage example display
Examples string
// Func is the command handler func. Func Runner
Func RunnerFunc
// Help is the long help message text
Help string
// HelpRender custom render cmd help message
HelpRender func(c *Command)
// command is inject to the App
app *App
// mark is disabled. if true will skip register to cli-app.
disabled bool
// command is standalone running.
standalone bool
// global option binding on standalone.
goptBounded bool
}
// NewCommand create a new command instance.
// Usage:
// cmd := NewCommand("my-cmd", "description")
// // OR with an config func
// cmd := NewCommand("my-cmd", "description", func(c *Command) { ... })
// app.Add(cmd) // OR cmd.AttachTo(app)
func NewCommand(name, desc string, fn ...func(c *Command)) *Command {
c := &Command{
Name: name,
Desc: desc,
// Flags: *NewFlags(name, desc),
}
// has config func
if len(fn) > 0 {
c.Config = fn[0]
}
// set name
c.Arguments.SetName(name)
return c
}
// Init command. only use for tests
func (c *Command) Init() {
c.initialize()
}
// SetFunc Settings command handler func
func (c *Command) SetFunc(fn RunnerFunc) {
c.Func = fn
}
// WithFunc Settings command handler func
func (c *Command) WithFunc(fn RunnerFunc) *Command {
c.Func = fn
return c
}
// WithHidden Settings command is hidden
func (c *Command) WithHidden() *Command {
c.Hidden = true
return c
}
// AttachTo attach the command to CLI application
func (c *Command) AttachTo(app *App) {
app.AddCommand(c)
}
// Disable set cmd is disabled
func (c *Command) Disable() {
c.disabled = true
}
// Visible return cmd is visible
func (c *Command) Visible() bool {
return c.Hidden == false
}
// IsDisabled get cmd is disabled
func (c *Command) IsDisabled() bool {
return c.disabled
}
// Runnable reports whether the command can be run; otherwise
// it is a documentation pseudo-command such as import path.
func (c *Command) Runnable() bool {
return c.Func != nil
}
// Add one or multi sub-command(s). alias of the AddSubs
func (c *Command) Add(sub *Command, more ...*Command) {
c.AddSubs(sub, more...)
}
// AddSubs add one or multi sub-command(s)
func (c *Command) AddSubs(sub *Command, more ...*Command) {
c.AddCommand(sub)
if len(more) > 0 {
for _, cmd := range more {
c.AddCommand(cmd)
}
}
}
// AddCommand add a sub command
func (c *Command) AddCommand(sub *Command) {
// init command
sub.app = c.app
sub.parent = c
// inherit standalone value
sub.standalone = c.standalone
// inherit global flags from application
sub.core.gFlags = c.gFlags
// initialize command
c.initialize()
// extend path names from parent
sub.pathNames = c.pathNames[0:]
// do add
c.commandBase.addCommand(c.Name, sub)
}
// Match sub command by input names
func (c *Command) Match(names []string) *Command {
// ensure is initialized
c.initialize()
ln := len(names)
if ln == 0 { // return self.
return c
}
return c.commandBase.Match(names)
}
// MatchByPath command by path. eg. "top:sub"
func (c *Command) MatchByPath(path string) *Command {
return c.Match(splitPath2names(path))
}
// initialize works for the command
func (c *Command) initialize() {
if c.initialized {
return
}
// check command name
cName := c.goodName()
Debugf("initialize the command '%s'", cName)
c.initialized = true
c.pathNames = append(c.pathNames, cName)
// init core
c.initCore(cName)
// init commandBase
c.initCommandBase()
// load common subs
if len(c.Subs) > 0 {
for _, sub := range c.Subs {
c.AddCommand(sub)
}
}
// init for cmd Arguments
c.Arguments.SetName(cName)
c.Arguments.SetValidateNum(gOpts.strictMode)
// init for cmd Flags
c.Flags.SetOptions(newDefaultFlagOption())
c.Flags.InitFlagSet(cName)
// c.Flags.SetOption(cName)
// c.Flags.FSet().SetOutput(c.Flags.out)
// c.Flags.FSet().Usage = func() { // call on exists "-h" "--help"
// Logf(VerbDebug, "render help on exists '-h|--help' or has unknown flag")
// c.ShowHelp()
// }
// format description
if len(c.Desc) > 0 {
c.Desc = strutil.UpperFirst(c.Desc)
// contains help var "{$cmd}". replace on here is for 'app help'
if strings.Contains(c.Desc, "{$cmd}") {
c.Desc = strings.Replace(c.Desc, "{$cmd}", c.Name, -1)
}
}
// call Config func
if c.Config != nil {
c.Config(c)
}
c.Fire(EvtCmdInit, nil)
}
// init core
func (c *Command) initCore(cName string) {
Logf(VerbCrazy, "init command c.core for the command: %s", cName)
c.core.cmdLine = CLI
if c.core.Hooks == nil {
c.core.Hooks = &Hooks{}
}
binWithPath := c.binName + " " + c.Path()
c.AddVars(c.innerHelpVars())
c.AddVars(map[string]string{
"cmd": cName,
// binName with command name
"binWithCmd": binWithPath,
// binName with command path
"binWithPath": binWithPath,
// binFile with command
"fullCmd": c.binFile + " " + cName,
})
}
func (c *Command) initCommandBase() {
Logf(VerbCrazy, "init command c.commandBase for the command: %s", c.Name)
c.commandBase.cmdNames = make(map[string]int)
c.commandBase.commands = make(map[string]*Command)
// set an default value.
c.commandBase.nameMaxWidth = 12
// c.commandBase.cmdAliases = make(maputil.Aliases)
c.commandBase.cmdAliases = structs.NewAliases(aliasNameCheck)
}
// Next TODO processing, run all middleware handlers
func (c *Command) Next() {
c.middleIdx++
s := int8(len(c.middles))
for ; c.middleIdx < s; c.middleIdx++ {
err := c.middles[c.middleIdx](c, c.RawArgs())
// will abort on error
if err != nil {
c.runErr = err
return
}
}
}
/*************************************************************
* standalone running
*************************************************************/
var errCallRunOnApp = errors.New("c.Run() method can only be called in standalone mode")
var errCallRunOnSub = errors.New("c.Run() cannot allow call at subcommand")
// MustRun Alone the current command, will panic on error
//
// Usage:
// // run with os.Args
// cmd.MustRun(nil)
// cmd.MustRun(os.Args[1:])
// // custom args
// cmd.MustRun([]string{"-a", ...})
func (c *Command) MustRun(args []string) {
if err := c.Run(args); err != nil {
color.Error.Println("ERROR:", err.Error())
panic(err)
}
}
// Run standalone running the command
//
// Usage:
// // run with os.Args
// cmd.Run(nil)
// cmd.Run(os.Args[1:])
// // custom args
// cmd.Run([]string{"-a", ...})
func (c *Command) Run(args []string) (err error) {
if c.app != nil {
return errCallRunOnApp
}
if c.parent != nil {
return errCallRunOnSub
}
// mark is standalone
c.standalone = true
// if not set input args
if args == nil {
args = os.Args[1:]
}
// init the command
c.initialize()
// add default error handler.
if !c.HasHook(EvtCmdRunError) {
c.On(EvtCmdRunError, defaultErrHandler)
}
// binding global options
if !c.goptBounded {
c.goptBounded = true
Debugf("global options will binding to c.Flags on standalone mode")
// bindingCommonGOpts(&c.Flags)
gOpts.bindingFlags(&c.Flags)
}
// dispatch and parse flags and execute command
return c.innerDispatch(args)
}
/*************************************************************
* command run
*************************************************************/
// dispatch execute the command
func (c *Command) innerDispatch(args []string) (err error) {
// parse command flags
args, err = c.parseOptions(args)
if err != nil {
// ignore flag.ErrHelp error
if err == flag.ErrHelp {
c.ShowHelp()
return nil
}
Debugf("cmd: %s - command options parse error", c.Name)
color.Error.Tips("option error - %s", err.Error())
return nil
}
// remaining args
if c.standalone {
if gOpts.showHelp {
c.ShowHelp()
return
}
c.Fire(EvtGOptionsParsed, args)
}
c.Fire(EvtCmdOptParsed, args)
Debugf("cmd: %s - remaining args on options parsed: %v", c.Name, args)
// find sub command
if len(args) > 0 {
name := args[0]
// ensure is not an option
if name[0] != '-' {
name = c.ResolveAlias(name)
// is valid sub command
if sub, has := c.Command(name); has {
// loop find sub...command and run it.
return sub.innerDispatch(args[1:])
}
// no arguments, name is not founded subcommand
if !c.HasArguments() {
// fire events
stop := c.Fire(EvtCmdSubNotFound, name)
if stop == true {
return
}
if stop = c.Fire(EvtCmdNotFound, name); stop == false {
return
}
color.Error.Tips("subcommand '%s' - not found on the command", name)
}
} else {
name = "" // reset var
}
}
// not set command func and has sub commands.
if c.Func == nil && len(c.commands) > 0 {
// color.Cyanln()
Logf(VerbWarn, "cmd: %s - c.Func is empty, but has subcommands, render cmd list", c.Name)
c.ShowHelp()
return err
}
// do execute current command
return c.doExecute(args)
}
// execute the current command
func (c *Command) innerExecute(args []string, igrErrHelp bool) (err error) {
// parse flags
args, err = c.parseOptions(args)
if err != nil {
// whether ignore flag.ErrHelp error
if igrErrHelp && err == flag.ErrHelp {
err = nil
}
return
}
// do execute command
return c.doExecute(args)
}
// do parse option flags, remaining is cmd args
func (c *Command) parseOptions(args []string) (ss []string, err error) {
// strict format options
if gOpts.strictMode && len(args) > 0 {
args = strictFormatArgs(args)
}
// fix and compatible
// args = moveArgumentsToEnd(args)
// Debugf("cmd: %s - option flags on after format: %v", c.Name, args)
// NOTICE: disable output internal error message on parse flags
// c.FSet().SetOutput(ioutil.Discard)
Debugf("cmd: %s - will parse options by args: %v", c.Name, args)
// parse options, don't contains command name.
if err = c.Parse(args); err != nil {
Logf(VerbCrazy, "'%s' - parse options err: <red>%s</>", c.Name, err.Error())
return
}
// remaining args
return c.Flags.RawArgs(), nil
}
// prepare: before execute the command
func (c *Command) prepare(_ []string) (status int, err error) {
return
}
// do execute the command
func (c *Command) doExecute(args []string) (err error) {
// collect and binding named argument
Debugf("cmd: %s - collect and binding named argument", c.Name)
if err := c.ParseArgs(args); err != nil {
c.Fire(EvtCmdRunError, err)
Logf(VerbCrazy, "binding command '%s' arguments err: <red>%s</>", c.Name, err.Error())
return err
}
c.Fire(EvtCmdRunBefore, args)
// do call command handler func
if c.Func == nil {
Logf(VerbWarn, "the command '%s' no handler func to running", c.Name)
} else {
// err := c.Func.Run(c, args)
err = c.Func(c, args)
}
if err != nil {
c.Fire(EvtCmdRunError, err)
} else {
c.Fire(EvtCmdRunAfter, nil)
}
return
}
/*************************************************************
* parent and subs
*************************************************************/
// Root get root command
func (c *Command) Root() *Command {
if c.parent != nil {
return c.parent.Root()
}
return c
}
// IsRoot command
func (c *Command) IsRoot() bool {
return c.parent == nil
}
// Parent get parent
func (c *Command) Parent() *Command {
return c.parent
}
// SetParent set parent
func (c *Command) SetParent(parent *Command) {
c.parent = parent
}
// Module name of the grouped command
func (c *Command) ParentName() string {
if c.parent != nil {
return c.parent.Name
}
return ""
}
// Sub get sub command by name. eg "sub"
func (c *Command) Sub(name string) *Command {
return c.GetCommand(name)
}
// SubCommand get sub command by name. eg "sub"
func (c *Command) SubCommand(name string) *Command {
return c.GetCommand(name)
}
// IsSubCommand name check. alias of the HasCommand()
func (c *Command) IsSubCommand(name string) bool {
return c.IsCommand(name)
}
// find sub command by name
// func (c *Command) findSub(name string) *Command {
// if index, ok := c.subName2index[name]; ok {
// return c.Subs[index]
// }
//
// return nil
// }
/*************************************************************
* command help
*************************************************************/
// CmdHelpTemplate help template for a command
var CmdHelpTemplate = `{{.Desc}}
{{if .Cmd.NotStandalone}}
<comment>Name:</> {{.Cmd.Name}}{{if .Cmd.Aliases}} (alias: <info>{{.Cmd.AliasesString}}</>){{end}}{{end}}
<comment>Usage:</>
{$binName} [global options] {{if .Cmd.NotStandalone}}<cyan>{{.Cmd.Path}}</> {{end}}[--options ...] [arguments ...]{{ if .Subs }}
{$binName} [global options] {{if .Cmd.NotStandalone}}<cyan>{{.Cmd.Path}}</> {{end}}<cyan>SUBCOMMAND</> [--options ...] [arguments ...]{{end}}
{{if .GOpts}}
<comment>Global Options:</>
{{.GOpts}}{{end}}{{if .Options}}
<comment>Options:</>
{{.Options}}{{end}}{{if .Cmd.Args}}
<comment>Arguments:</>{{range $a := .Cmd.Args}}
<info>{{$a.HelpName | printf "%-12s"}}</>{{$a.Desc | ucFirst}}{{if $a.Required}}<red>*</>{{end}}{{end}}
{{end}}{{ if .Subs }}
<comment>Sub Commands:</>{{range $n,$c := .Subs}}
<info>{{$c.Name | paddingName }}</> {{$c.HelpDesc}}{{if $c.Aliases}} (alias: <green>{{ join $c.Aliases ","}}</>){{end}}{{end}}
{{end}}{{if .Cmd.Examples}}
<comment>Examples:</>
{{.Cmd.Examples}}{{end}}{{if .Cmd.Help}}
<comment>Help:</>
{{.Cmd.Help}}{{end}}`
// ShowHelp show command help info
func (c *Command) ShowHelp() {
Debugf("render the command '%s' help information", c.Name)
// custom help render func
if c.HelpRender != nil {
c.HelpRender(c)
return
}
// clear space and empty new line
if c.Examples != "" {
c.Examples = strings.Trim(c.Examples, "\n") + "\n"
}
// clear space and empty new line
if c.Help != "" {
c.Help = strings.Join([]string{strings.TrimSpace(c.Help), "\n"}, "")
}
vars := map[string]interface{}{
"Cmd": c,
"Subs": c.commands,
// global options
// - on standalone, will not init c.core.gFlags
"GOpts": nil,
// parse options to string
"Options": c.Flags.String(),
// always upper first char
"Desc": c.HelpDesc(),
}
if c.NotStandalone() {
vars["GOpts"] = c.GFlags().String()
}
// render help message
str := helper.RenderText(CmdHelpTemplate, vars, template.FuncMap{
"paddingName": func(n string) string {
return strutil.PadRight(n, " ", c.nameMaxWidth)
},
})
// parse gcli help vars then print help
// fmt.Printf("%#v\n", s)
color.Print(c.ReplaceVars(str))
}
/*************************************************************
* helper methods
*************************************************************/
// GFlags get global flags
func (c *Command) GFlags() *Flags {
// 如果先注册S子命令到一个命令A中,再将A注册到应用App。此时,S.gFlags 就是空的。
// If you first register the S subcommand to a command A, then register A to the application App.
// At this time, S.gFlags is empty.
if c.gFlags == nil {
if c.parent == nil {
return nil
}
// inherit from parent command.
c.core.gFlags = c.parent.GFlags()
}
return c.gFlags
}
// IsStandalone running
func (c *Command) IsStandalone() bool {
return c.standalone
}
// NotStandalone running
func (c *Command) NotStandalone() bool {
return !c.standalone
}
// ID get command ID name.
func (c *Command) goodName() string {
name := strings.Trim(strings.TrimSpace(c.Name), ": ")
if name == "" {
panicf("the command name can not be empty")
}
if !goodCmdName.MatchString(name) {
panicf("the command name '%s' is invalid, must match: %s", name, regGoodCmdName)
}
// update name
c.Name = name
return name
}
// Fire event handler by name
func (c *Command) Fire(event string, data interface{}) (stop bool) {
Debugf("cmd: %s - trigger the event: <mga>%s</>", c.Name, event)
return c.core.Fire(event, c, data)
}
// On add hook handler for a hook event
func (c *Command) On(name string, handler HookFunc) {
Debugf("cmd: %s - register hook: <cyan>%s</>", c.Name, name)
c.Hooks.On(name, handler)
}
// Copy a new command for current
func (c *Command) Copy() *Command {
nc := *c
// reset some fields
nc.Func = nil
nc.Hooks.ClearHooks() // TODO bug, will clear c.Hooks
// nc.Flags = flag.FlagSet{}
return &nc
}
// App returns the CLI application
func (c *Command) App() *App {
return c.app
}
// ID get command ID string
func (c *Command) ID() string {
return strings.Join(c.pathNames, CommandSep)
}
// Path get command full path
func (c *Command) Path() string {
return strings.Join(c.pathNames, " ")
}
// PathNames get command path names
func (c *Command) PathNames() []string {
return c.pathNames
}
// Errorf format message and add error to the command
func (c *Command) Errorf(format string, v ...interface{}) error {
return fmt.Errorf(format, v...)
}
// NewErr format message and add error to the command
func (c *Command) NewErr(msg string) error {
return errors.New(msg)
}
// NewErrf format message and add error to the command
func (c *Command) NewErrf(format string, v ...interface{}) error {
return fmt.Errorf(format, v...)
}
// AliasesString returns aliases string
func (c *Command) AliasesString(sep ...string) string {
s := ","
if len(sep) == 1 {
s = sep[0]
}
return strings.Join(c.Aliases, s)
}
// HelpDesc format desc string for render help
func (c *Command) HelpDesc() (desc string) {
if len(c.Desc) == 0 {
return
}
// dump.P(desc)
desc = strutil.UpperFirst(c.Desc)
// contains help var "{$cmd}". replace on here is for 'app help'
if strings.Contains(desc, "{$cmd}") {
desc = strings.Replace(desc, "{$cmd}", color.WrapTag(c.Name, "mga"), -1)
}
return wrapColor2string(desc)
}
// Logf print log message
// func (c *Command) Logf(level uint, format string, v ...interface{}) {
// Logf(level, format, v...)
// }

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/gookit-gcli.git
git@api.gitlife.ru:oschina-mirror/gookit-gcli.git
oschina-mirror
gookit-gcli
gookit-gcli
master