github.com/df-mc/dragonfly@v0.9.13/server/cmd/command.go (about) 1 package cmd 2 3 import ( 4 "encoding/csv" 5 "fmt" 6 "go/ast" 7 "reflect" 8 "strings" 9 ) 10 11 // Runnable represents a Command that may be run by a Command source. The Command must be a struct type and 12 // its fields represent the parameters of the Command. When the Run method is called, these fields are set 13 // and may be used for behaviour in the Command. Fields unexported or ignored using the `cmd:"-"` struct tag (see 14 // below) have their values copied but retained. 15 // A Runnable may have exported fields only of the following types: 16 // int8, int16, int32, int64, int, uint8, uint16, uint32, uint64, uint, 17 // float32, float64, string, bool, mgl64.Vec3, Varargs, []Target, cmd.SubCommand, Optional[T] (to make a parameter 18 // optional), or a type that implements the cmd.Parameter or cmd.Enum interface. cmd.Enum implementations must be of the 19 // type string. 20 // Fields in the Runnable struct may have `cmd:` struct tag to specify the name and suffix of a parameter as such: 21 // 22 // type T struct { 23 // Param int `cmd:"name,suffix"` 24 // } 25 // 26 // If no name is set, the field name is used. Additionally, the name as specified in the struct tag may be '-' to make 27 // the parser ignore the field. In this case, the field does not have to be of one of the types above. 28 type Runnable interface { 29 // Run runs the Command, using the arguments passed to the Command. The source is passed to the method, 30 // which is the source of the execution of the Command, and the output is passed, to which messages may be 31 // added which get sent to the source. 32 Run(src Source, o *Output) 33 } 34 35 // Allower may be implemented by a type also implementing Runnable to limit the sources that may run the 36 // command. 37 type Allower interface { 38 // Allow checks if the Source passed is allowed to execute the command. True is returned if the Source is 39 // allowed to execute the command. 40 Allow(src Source) bool 41 } 42 43 // Command is a wrapper around a Runnable. It provides additional identity and utility methods for the actual 44 // runnable command so that it may be identified more easily. 45 type Command struct { 46 v []reflect.Value 47 name string 48 description string 49 usage string 50 aliases []string 51 } 52 53 // New returns a new Command using the name and description passed. The Runnable passed must be a 54 // (pointer to a) struct, with its fields representing the parameters of the command. 55 // When the command is run, the Run method of the Runnable will be called, after all fields have their values 56 // from the parsed command set. 57 // If r is not a struct or a pointer to a struct, New panics. 58 func New(name, description string, aliases []string, r ...Runnable) Command { 59 usages := make([]string, len(r)) 60 runnableValues := make([]reflect.Value, len(r)) 61 62 if len(aliases) > 0 { 63 namePresent := false 64 for _, alias := range aliases { 65 if alias == name { 66 namePresent = true 67 } 68 } 69 if !namePresent { 70 aliases = append(aliases, name) 71 } 72 } 73 74 for i, runnable := range r { 75 t := reflect.TypeOf(runnable) 76 if t.Kind() != reflect.Struct && (t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct) { 77 panic(fmt.Sprintf("Runnable r must be struct or pointer to struct, but got %v", t.Kind())) 78 } 79 original := reflect.ValueOf(runnable) 80 if t.Kind() == reflect.Ptr { 81 original = original.Elem() 82 } 83 84 cp := reflect.New(original.Type()).Elem() 85 if err := verifySignature(cp); err != nil { 86 panic(err.Error()) 87 } 88 runnableValues[i], usages[i] = original, parseUsage(name, cp) 89 } 90 91 return Command{name: name, description: description, aliases: aliases, v: runnableValues, usage: strings.Join(usages, "\n")} 92 } 93 94 // Name returns the name of the command. The name is guaranteed to be lowercase and will never have spaces in 95 // it. This name is used to call the command, and is shown in the /help list. 96 func (cmd Command) Name() string { 97 return cmd.name 98 } 99 100 // Description returns the description of the command. The description is shown in the /help list, and 101 // provides information on the functionality of a command. 102 func (cmd Command) Description() string { 103 return cmd.description 104 } 105 106 // Usage returns the usage of the command. The usage will be roughly equal to the one showed by the client 107 // in-game. 108 func (cmd Command) Usage() string { 109 return cmd.usage 110 } 111 112 // Aliases returns a list of aliases for the command. In addition to the name of the command, the command may 113 // be called using one of these aliases. 114 func (cmd Command) Aliases() []string { 115 return cmd.aliases 116 } 117 118 // Execute executes the Command as a source with the args passed. The args are parsed assuming they do not 119 // start with the command name. Execute will attempt to parse and execute one Runnable at a time. If one of 120 // the Runnable was able to parse args correctly, it will be executed and no more Runnables will be attempted 121 // to be run. 122 // If parsing of all Runnables was unsuccessful, a command output with an error message is sent to the Source 123 // passed, and the Run method of the Runnables are not called. 124 // The Source passed must not be nil. The method will panic if a nil Source is passed. 125 func (cmd Command) Execute(args string, source Source) { 126 if source == nil { 127 panic("execute: invalid command source: source must not be nil") 128 } 129 output := &Output{} 130 defer source.SendCommandOutput(output) 131 132 var leastErroneous error 133 leastArgsLeft := len(strings.Split(args, " ")) 134 135 for _, v := range cmd.v { 136 cp := reflect.New(v.Type()) 137 cp.Elem().Set(v) 138 line, err := cmd.executeRunnable(cp, args, source, output) 139 if err == nil { 140 // Command was executed successfully: We won't execute any of the other Runnable values passed, as 141 // we've already found an overload that works. 142 return 143 } 144 if line == nil { 145 // This Runnable was not runnable by the source passed. Only if no error was yet set, we set an 146 // error for the wrong source. 147 if leastErroneous == nil { 148 leastErroneous = err 149 } 150 continue 151 } 152 if line.Len() <= leastArgsLeft { 153 // If the line had less (or equal) arguments left than the previous lowest, we update the error, 154 // so that we can return an error that applies for the most successful Runnable. 155 leastErroneous = err 156 leastArgsLeft = line.Len() 157 } 158 } 159 // No working Runnable found for the arguments passed. We add the most applicable error to the output and 160 // stop there. 161 output.Error(leastErroneous) 162 } 163 164 // ParamInfo holds the information of a parameter in a Runnable. Information of a parameter may be obtained 165 // by calling Command.Params(). 166 type ParamInfo struct { 167 Name string 168 Value any 169 Optional bool 170 Suffix string 171 } 172 173 // Params returns a list of all parameters of the runnables. No assumptions should be done on the values that 174 // they hold: Only the types are guaranteed to be consistent. 175 func (cmd Command) Params(src Source) [][]ParamInfo { 176 params := make([][]ParamInfo, 0, len(cmd.v)) 177 for _, runnable := range cmd.v { 178 elem := reflect.New(runnable.Type()).Elem() 179 elem.Set(runnable) 180 181 if allower, ok := runnable.Interface().(Allower); ok && !allower.Allow(src) { 182 // This source cannot execute this runnable. 183 continue 184 } 185 186 var fields []ParamInfo 187 for _, t := range exportedFields(elem) { 188 field := elem.FieldByName(t.Name) 189 fields = append(fields, ParamInfo{ 190 Name: name(t), 191 Value: unwrap(field).Interface(), 192 Optional: optional(field), 193 Suffix: suffix(t), 194 }) 195 } 196 params = append(params, fields) 197 } 198 return params 199 } 200 201 // Runnables returns a map of all Runnable implementations of the Command that a Source can execute. 202 func (cmd Command) Runnables(src Source) map[int]Runnable { 203 m := make(map[int]Runnable, len(cmd.v)) 204 for i, runnable := range cmd.v { 205 v := runnable.Interface().(Runnable) 206 if allower, ok := v.(Allower); !ok || allower.Allow(src) { 207 m[i] = v 208 } 209 } 210 return m 211 } 212 213 // String returns the usage of the command. The usage will be roughly equal to the one showed by the client 214 // in-game. 215 func (cmd Command) String() string { 216 return cmd.usage 217 } 218 219 // executeRunnable executes a Runnable v, by parsing the args passed using the source and output obtained. If 220 // parsing was not successful or the Runnable could not be run by this source, an error is returned, and the 221 // leftover command line. 222 func (cmd Command) executeRunnable(v reflect.Value, args string, source Source, output *Output) (*Line, error) { 223 if a, ok := v.Interface().(Allower); ok && !a.Allow(source) { 224 //lint:ignore ST1005 Error string is capitalised because it is shown to the player. 225 //goland:noinspection GoErrorStringFormat 226 return nil, fmt.Errorf("You cannot execute this command.") 227 } 228 229 var argFrags []string 230 if args != "" { 231 r := csv.NewReader(strings.NewReader(args)) 232 r.Comma = ' ' 233 r.LazyQuotes = true 234 record, err := r.Read() 235 if err != nil { 236 return nil, fmt.Errorf("error parsing command string: %w", err) 237 } 238 argFrags = record 239 } 240 parser := parser{} 241 arguments := &Line{args: argFrags, src: source} 242 243 // We iterate over all the fields of the struct: Each of the fields will have an argument parsed to 244 // produce its value. 245 signature := v.Elem() 246 for _, t := range exportedFields(signature) { 247 field := signature.FieldByName(t.Name) 248 parser.currentField = t.Name 249 opt := optional(field) 250 251 val := field 252 if opt { 253 val = reflect.New(field.Field(0).Type()).Elem() 254 } 255 256 err, success := parser.parseArgument(arguments, val, opt, name(t), source) 257 if err != nil { 258 // Parsing was not successful, we return immediately as we don't need to call the Runnable. 259 return arguments, err 260 } 261 if success && opt { 262 field.Set(reflect.ValueOf(field.Interface().(optionalT).with(val.Interface()))) 263 } 264 } 265 if arguments.Len() != 0 { 266 return arguments, fmt.Errorf("unexpected '%v'", strings.Join(arguments.args, " ")) 267 } 268 269 v.Interface().(Runnable).Run(source, output) 270 return arguments, nil 271 } 272 273 // parseUsage parses the usage of a command found in value v using the name passed. It accounts for optional 274 // parameters and converts types to a more friendly representation. 275 func parseUsage(commandName string, command reflect.Value) string { 276 parts := make([]string, 0, command.NumField()+1) 277 parts = append(parts, "/"+commandName) 278 279 for _, t := range exportedFields(command) { 280 field := command.FieldByName(t.Name) 281 282 typeName := typeNameOf(field.Interface(), name(t)) 283 if _, ok := field.Interface().(optionalT); ok { 284 typeName = typeNameOf(reflect.New(field.Field(0).Type()).Elem().Interface(), name(t)) 285 } 286 if _, ok := field.Interface().(SubCommand); ok { 287 parts = append(parts, typeName) 288 continue 289 } 290 if optional(field) { 291 parts = append(parts, "["+name(t)+": "+typeName+"]"+suffix(t)) 292 continue 293 } 294 parts = append(parts, "<"+name(t)+": "+typeName+">"+suffix(t)) 295 } 296 return strings.Join(parts, " ") 297 } 298 299 // verifySignature verifies the passed struct pointer value signature to ensure it is a valid command, 300 // checking things such as the validity of the optional struct tags. 301 // If not valid, an error is returned. 302 func verifySignature(command reflect.Value) error { 303 optionalField := false 304 for _, t := range exportedFields(command) { 305 field := command.FieldByName(t.Name) 306 307 // If the field is not optional, while the last field WAS optional, we return an error, as this is 308 // not parsable in an expected way. 309 opt := optional(field) 310 if !opt && optionalField { 311 return fmt.Errorf("command must only have optional parameters at the end") 312 } 313 val := field 314 if opt { 315 val = reflect.New(field.Field(0).Type()).Elem() 316 } 317 if _, ok := val.Interface().(Enum); ok && val.Kind() != reflect.String { 318 return fmt.Errorf("parameters implementing Enum must be of the type string") 319 } 320 optionalField = opt 321 } 322 return nil 323 } 324 325 // exportedFields returns all exported struct fields of the reflect.Value passed. It returns the fields as returned by 326 // reflect.VisibleFields, but filters out unexported fields, anonymous fields and fields that have a name value in the 327 // 'cmd' tag of '-'. 328 func exportedFields(command reflect.Value) []reflect.StructField { 329 visible := reflect.VisibleFields(command.Type()) 330 fields := make([]reflect.StructField, 0, len(visible)) 331 332 for _, t := range visible { 333 if !ast.IsExported(t.Name) || name(t) == "-" || t.Anonymous { 334 continue 335 } 336 field := command.FieldByName(t.Name) 337 if !field.CanSet() { 338 continue 339 } 340 fields = append(fields, t) 341 } 342 return fields 343 }