github.com/wangyougui/gf/v2@v2.6.5/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/wangyougui/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/wangyougui/gf/v2" 21 "github.com/wangyougui/gf/v2/errors/gcode" 22 "github.com/wangyougui/gf/v2/errors/gerror" 23 "github.com/wangyougui/gf/v2/net/gtrace" 24 "github.com/wangyougui/gf/v2/os/gcfg" 25 "github.com/wangyougui/gf/v2/os/genv" 26 "github.com/wangyougui/gf/v2/os/glog" 27 "github.com/wangyougui/gf/v2/text/gstr" 28 "github.com/wangyougui/gf/v2/util/gconv" 29 "github.com/wangyougui/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) 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(ctx context.Context, args []string) (lastCmd, foundCmd *Command, newCtx context.Context) { 219 if len(args) == 0 { 220 return c, nil, ctx 221 } 222 for _, cmd := range c.commands { 223 // Recursively searching the command. 224 if cmd.Name == args[0] { 225 leftArgs := args[1:] 226 // If this command needs argument, 227 // it then gives all its left arguments to it. 228 if cmd.hasArgumentFromIndex() { 229 ctx = context.WithValue(ctx, CtxKeyArguments, leftArgs) 230 return c, cmd, ctx 231 } 232 // Recursively searching. 233 if len(leftArgs) == 0 { 234 return c, cmd, ctx 235 } 236 return cmd.searchCommand(ctx, leftArgs) 237 } 238 } 239 return c, nil, ctx 240 } 241 242 func (c *Command) hasArgumentFromIndex() bool { 243 for _, arg := range c.Arguments { 244 if arg.IsArg { 245 return true 246 } 247 } 248 return false 249 } 250 251 func (c *Command) hasArgumentFromOption() bool { 252 for _, arg := range c.Arguments { 253 if !arg.IsArg { 254 return true 255 } 256 } 257 return false 258 }