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  }