gf/os/gcmd/gcmd_command_run.go
2021-12-11 01:11:40 +08:00

176 lines
4.4 KiB
Go

// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
//
package gcmd
import (
"context"
"fmt"
"os"
"github.com/gogf/gf/v2/os/gcfg"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gutil"
)
// Run calls custom function that bound to this command.
func (c *Command) Run(ctx context.Context) error {
_, err := c.RunWithValue(ctx)
return err
}
// RunWithValue calls custom function that bound to this command with value output.
func (c *Command) RunWithValue(ctx context.Context) (value interface{}, err error) {
// Parse command arguments and options using default algorithm.
parser, err := Parse(nil)
if err != nil {
return nil, err
}
args := parser.GetArgAll()
if len(args) == 1 {
return c.doRun(ctx, parser)
}
// Exclude the root binary name.
args = args[1:]
// Find the matched command and run it.
if subCommand, newCtx := c.searchCommand(ctx, args); subCommand != nil {
return subCommand.doRun(newCtx, parser)
}
// Print error and help command if no command found.
fmt.Printf(
"ERROR: command \"%s\" not found for arguments \"%s\"\n",
gstr.Join(args, " "),
gstr.Join(os.Args, " "),
)
c.Print()
return nil, nil
}
func (c *Command) doRun(ctx context.Context, parser *Parser) (value interface{}, err error) {
ctx = context.WithValue(ctx, CtxKeyCommand, c)
// Check built-in help command.
if parser.ContainsOpt(helpOptionName) || parser.ContainsOpt(helpOptionNameShort) {
if c.HelpFunc != nil {
return nil, c.HelpFunc(ctx, parser)
}
return nil, c.defaultHelpFunc(ctx, parser)
}
// Reparse the arguments for current command configuration.
parser, err = c.reParse(ctx, parser)
if err != nil {
return nil, err
}
// Registered command function calling.
if c.Func != nil {
return nil, c.Func(ctx, parser)
}
if c.FuncWithValue != nil {
return c.FuncWithValue(ctx, parser)
}
// If no function defined in current command, it then prints help.
if c.HelpFunc != nil {
return nil, c.HelpFunc(ctx, parser)
}
return nil, c.defaultHelpFunc(ctx, parser)
}
// reParse re-parses the arguments using option configuration of current command.
func (c *Command) reParse(ctx context.Context, parser *Parser) (*Parser, error) {
if len(c.Arguments) == 0 {
return parser, nil
}
var (
optionKey string
supportedOptions = make(map[string]bool)
)
for _, arg := range c.Arguments {
if arg.IsArg {
continue
}
if arg.Short != "" {
optionKey = fmt.Sprintf(`%s,%s`, arg.Name, arg.Short)
} else {
optionKey = arg.Name
}
supportedOptions[optionKey] = !arg.Orphan
}
parser, err := Parse(supportedOptions, c.Strict)
if err != nil {
return nil, err
}
// Retrieve option values from config component if it has "config" tag.
if c.Config != "" && gcfg.Instance().Available(ctx) {
value, err := gcfg.Instance().Get(ctx, c.Config)
if err != nil {
return nil, err
}
configMap := value.Map()
for optionName, _ := range parser.passedOptions {
// The command line has the high priority.
if parser.ContainsOpt(optionName) {
continue
}
// Merge the config value into parser.
foundKey, foundValue := gutil.MapPossibleItemByKey(configMap, optionName)
if foundKey != "" {
parser.parsedOptions[optionName] = gconv.String(foundValue)
}
}
}
return parser, nil
}
// searchCommand recursively searches the command according given arguments.
func (c *Command) searchCommand(ctx context.Context, args []string) (*Command, context.Context) {
if len(args) == 0 {
return nil, ctx
}
for _, cmd := range c.commands {
// Recursively searching the command.
if cmd.Name == args[0] {
leftArgs := args[1:]
// If this command needs argument,
// it then gives all its left arguments to it.
if cmd.hasArgumentFromIndex() {
ctx = context.WithValue(ctx, CtxKeyArguments, leftArgs)
return cmd, ctx
}
// Recursively searching.
if len(leftArgs) == 0 {
return cmd, ctx
}
return cmd.searchCommand(ctx, leftArgs)
}
}
return nil, ctx
}
func (c *Command) hasArgumentFromIndex() bool {
for _, arg := range c.Arguments {
if arg.IsArg {
return true
}
}
return false
}
func (c *Command) hasArgumentFromOption() bool {
for _, arg := range c.Arguments {
if !arg.IsArg {
return true
}
}
return false
}