github.com/gogf/gf/v2@v2.7.4/os/gcmd/gcmd_command_run.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/gogf/gf.
     6  //
     7  
     8  package gcmd
     9  
    10  import (
    11  	"bytes"
    12  	"context"
    13  	"fmt"
    14  	"os"
    15  
    16  	"go.opentelemetry.io/otel"
    17  	"go.opentelemetry.io/otel/propagation"
    18  	"go.opentelemetry.io/otel/trace"
    19  
    20  	"github.com/gogf/gf/v2"
    21  	"github.com/gogf/gf/v2/errors/gcode"
    22  	"github.com/gogf/gf/v2/errors/gerror"
    23  	"github.com/gogf/gf/v2/net/gtrace"
    24  	"github.com/gogf/gf/v2/os/gcfg"
    25  	"github.com/gogf/gf/v2/os/genv"
    26  	"github.com/gogf/gf/v2/os/glog"
    27  	"github.com/gogf/gf/v2/text/gstr"
    28  	"github.com/gogf/gf/v2/util/gconv"
    29  	"github.com/gogf/gf/v2/util/gutil"
    30  )
    31  
    32  // Run calls custom function in os.Args that bound to this command.
    33  // It exits this process with exit code 1 if any error occurs.
    34  func (c *Command) Run(ctx context.Context) {
    35  	_ = c.RunWithValue(ctx)
    36  }
    37  
    38  // RunWithValue calls custom function in os.Args that bound to this command with value output.
    39  // It exits this process with exit code 1 if any error occurs.
    40  func (c *Command) RunWithValue(ctx context.Context) (value interface{}) {
    41  	value, err := c.RunWithValueError(ctx)
    42  	if err != nil {
    43  		var (
    44  			code   = gerror.Code(err)
    45  			detail = code.Detail()
    46  			buffer = bytes.NewBuffer(nil)
    47  		)
    48  		if code.Code() == gcode.CodeNotFound.Code() {
    49  			buffer.WriteString(fmt.Sprintf("ERROR: %s\n", gstr.Trim(err.Error())))
    50  			if lastCmd, ok := detail.(*Command); ok {
    51  				lastCmd.PrintTo(buffer)
    52  			} else {
    53  				c.PrintTo(buffer)
    54  			}
    55  		} else {
    56  			buffer.WriteString(fmt.Sprintf("%+v\n", err))
    57  		}
    58  		if gtrace.GetTraceID(ctx) == "" {
    59  			fmt.Println(buffer.String())
    60  			os.Exit(1)
    61  		}
    62  		glog.Stack(false).Fatal(ctx, buffer.String())
    63  	}
    64  	return value
    65  }
    66  
    67  // RunWithError calls custom function in os.Args that bound to this command with error output.
    68  func (c *Command) RunWithError(ctx context.Context) (err error) {
    69  	_, err = c.RunWithValueError(ctx)
    70  	return
    71  }
    72  
    73  // RunWithValueError calls custom function in os.Args that bound to this command with value and error output.
    74  func (c *Command) RunWithValueError(ctx context.Context) (value interface{}, err error) {
    75  	return c.RunWithSpecificArgs(ctx, os.Args)
    76  }
    77  
    78  // RunWithSpecificArgs calls custom function in specific args that bound to this command with value and error output.
    79  func (c *Command) RunWithSpecificArgs(ctx context.Context, args []string) (value interface{}, err error) {
    80  	if len(args) == 0 {
    81  		return nil, gerror.NewCode(gcode.CodeInvalidParameter, "args can not be empty!")
    82  	}
    83  	parser, err := ParseArgs(args, nil)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	parsedArgs := parser.GetArgAll()
    88  	if len(parsedArgs) == 1 {
    89  		return c.doRun(ctx, args, parser)
    90  	}
    91  
    92  	// Exclude the root binary name.
    93  	parsedArgs = parsedArgs[1:]
    94  
    95  	// Find the matched command and run it.
    96  	lastCmd, foundCmd, newCtx := c.searchCommand(ctx, parsedArgs, 0)
    97  	if foundCmd != nil {
    98  		return foundCmd.doRun(newCtx, args, parser)
    99  	}
   100  
   101  	// Print error and help command if no command found.
   102  	err = gerror.NewCodef(
   103  		gcode.WithCode(gcode.CodeNotFound, lastCmd),
   104  		`command "%s" not found for command "%s", command line: %s`,
   105  		gstr.Join(parsedArgs, " "),
   106  		c.Name,
   107  		gstr.Join(args, " "),
   108  	)
   109  	return
   110  }
   111  
   112  func (c *Command) doRun(ctx context.Context, args []string, parser *Parser) (value interface{}, err error) {
   113  	defer func() {
   114  		if exception := recover(); exception != nil {
   115  			if v, ok := exception.(error); ok && gerror.HasStack(v) {
   116  				err = v
   117  			} else {
   118  				err = gerror.NewCodef(gcode.CodeInternalPanic, "exception recovered: %+v", exception)
   119  			}
   120  		}
   121  	}()
   122  
   123  	ctx = context.WithValue(ctx, CtxKeyCommand, c)
   124  	// Check built-in help command.
   125  	if parser.GetOpt(helpOptionName) != nil || parser.GetOpt(helpOptionNameShort) != nil {
   126  		if c.HelpFunc != nil {
   127  			return nil, c.HelpFunc(ctx, parser)
   128  		}
   129  		return nil, c.defaultHelpFunc(ctx, parser)
   130  	}
   131  	// OpenTelemetry for command.
   132  	var (
   133  		span trace.Span
   134  		tr   = otel.GetTracerProvider().Tracer(
   135  			tracingInstrumentName,
   136  			trace.WithInstrumentationVersion(gf.VERSION),
   137  		)
   138  	)
   139  	ctx, span = tr.Start(
   140  		otel.GetTextMapPropagator().Extract(
   141  			ctx,
   142  			propagation.MapCarrier(genv.Map()),
   143  		),
   144  		gstr.Join(os.Args, " "),
   145  		trace.WithSpanKind(trace.SpanKindServer),
   146  	)
   147  	defer span.End()
   148  	span.SetAttributes(gtrace.CommonLabels()...)
   149  	// Reparse the original arguments for current command configuration.
   150  	parser, err = c.reParse(ctx, args, parser)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  	// Registered command function calling.
   155  	if c.Func != nil {
   156  		return nil, c.Func(ctx, parser)
   157  	}
   158  	if c.FuncWithValue != nil {
   159  		return c.FuncWithValue(ctx, parser)
   160  	}
   161  	// If no function defined in current command, it then prints help.
   162  	if c.HelpFunc != nil {
   163  		return nil, c.HelpFunc(ctx, parser)
   164  	}
   165  	return nil, c.defaultHelpFunc(ctx, parser)
   166  }
   167  
   168  // reParse parses the original arguments using option configuration of current command.
   169  func (c *Command) reParse(ctx context.Context, args []string, parser *Parser) (*Parser, error) {
   170  	if len(c.Arguments) == 0 {
   171  		return parser, nil
   172  	}
   173  	var (
   174  		optionKey        string
   175  		supportedOptions = make(map[string]bool)
   176  	)
   177  	for _, arg := range c.Arguments {
   178  		if arg.IsArg {
   179  			continue
   180  		}
   181  		if arg.Short != "" {
   182  			optionKey = fmt.Sprintf(`%s,%s`, arg.Name, arg.Short)
   183  		} else {
   184  			optionKey = arg.Name
   185  		}
   186  		supportedOptions[optionKey] = !arg.Orphan
   187  	}
   188  	parser, err := ParseArgs(args, supportedOptions, ParserOption{
   189  		CaseSensitive: c.CaseSensitive,
   190  		Strict:        c.Strict,
   191  	})
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  	// Retrieve option values from config component if it has "config" tag.
   196  	if c.Config != "" && gcfg.Instance().Available(ctx) {
   197  		value, err := gcfg.Instance().Get(ctx, c.Config)
   198  		if err != nil {
   199  			return nil, err
   200  		}
   201  		configMap := value.Map()
   202  		for optionName := range parser.supportedOptions {
   203  			// The command line has the high priority.
   204  			if parser.GetOpt(optionName) != nil {
   205  				continue
   206  			}
   207  			// Merge the config value into parser.
   208  			foundKey, foundValue := gutil.MapPossibleItemByKey(configMap, optionName)
   209  			if foundKey != "" {
   210  				parser.parsedOptions[optionName] = gconv.String(foundValue)
   211  			}
   212  		}
   213  	}
   214  	return parser, nil
   215  }
   216  
   217  // searchCommand recursively searches the command according given arguments.
   218  func (c *Command) searchCommand(
   219  	ctx context.Context, args []string, fromArgIndex int,
   220  ) (lastCmd, foundCmd *Command, newCtx context.Context) {
   221  	if len(args) == 0 {
   222  		return c, nil, ctx
   223  	}
   224  	for _, cmd := range c.commands {
   225  		// Recursively searching the command.
   226  		// String comparison case-sensitive.
   227  		if cmd.Name == args[0] {
   228  			leftArgs := args[1:]
   229  			// If this command needs argument,
   230  			// it then gives all its left arguments to it using arg index marks.
   231  			//
   232  			// Note that the args here (using default args parsing) could be different with the args
   233  			// that are parsed in command.
   234  			if cmd.hasArgumentFromIndex() || len(leftArgs) == 0 {
   235  				ctx = context.WithValue(ctx, CtxKeyArgumentsIndex, fromArgIndex+1)
   236  				return c, cmd, ctx
   237  			}
   238  			return cmd.searchCommand(ctx, leftArgs, fromArgIndex+1)
   239  		}
   240  	}
   241  	return c, nil, ctx
   242  }
   243  
   244  func (c *Command) hasArgumentFromIndex() bool {
   245  	for _, arg := range c.Arguments {
   246  		if arg.IsArg {
   247  			return true
   248  		}
   249  	}
   250  	return false
   251  }
   252  
   253  func (c *Command) hasArgumentFromOption() bool {
   254  	for _, arg := range c.Arguments {
   255  		if !arg.IsArg {
   256  			return true
   257  		}
   258  	}
   259  	return false
   260  }