github.com/btcsuite/btcd@v0.24.0/btcjson/cmdinfo.go (about)

     1  // Copyright (c) 2015 The btcsuite developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package btcjson
     6  
     7  import (
     8  	"fmt"
     9  	"reflect"
    10  	"strings"
    11  )
    12  
    13  // CmdMethod returns the method for the passed command.  The provided command
    14  // type must be a registered type.  All commands provided by this package are
    15  // registered by default.
    16  func CmdMethod(cmd interface{}) (string, error) {
    17  	// Look up the cmd type and error out if not registered.
    18  	rt := reflect.TypeOf(cmd)
    19  	registerLock.RLock()
    20  	method, ok := concreteTypeToMethod[rt]
    21  	registerLock.RUnlock()
    22  	if !ok {
    23  		str := fmt.Sprintf("%q is not registered", method)
    24  		return "", makeError(ErrUnregisteredMethod, str)
    25  	}
    26  
    27  	return method, nil
    28  }
    29  
    30  // MethodUsageFlags returns the usage flags for the passed command method.  The
    31  // provided method must be associated with a registered type.  All commands
    32  // provided by this package are registered by default.
    33  func MethodUsageFlags(method string) (UsageFlag, error) {
    34  	// Look up details about the provided method and error out if not
    35  	// registered.
    36  	registerLock.RLock()
    37  	info, ok := methodToInfo[method]
    38  	registerLock.RUnlock()
    39  	if !ok {
    40  		str := fmt.Sprintf("%q is not registered", method)
    41  		return 0, makeError(ErrUnregisteredMethod, str)
    42  	}
    43  
    44  	return info.flags, nil
    45  }
    46  
    47  // subStructUsage returns a string for use in the one-line usage for the given
    48  // sub struct.  Note that this is specifically for fields which consist of
    49  // structs (or an array/slice of structs) as opposed to the top-level command
    50  // struct.
    51  //
    52  // Any fields that include a jsonrpcusage struct tag will use that instead of
    53  // being automatically generated.
    54  func subStructUsage(structType reflect.Type) string {
    55  	numFields := structType.NumField()
    56  	fieldUsages := make([]string, 0, numFields)
    57  	for i := 0; i < structType.NumField(); i++ {
    58  		rtf := structType.Field(i)
    59  
    60  		// When the field has a jsonrpcusage struct tag specified use
    61  		// that instead of automatically generating it.
    62  		if tag := rtf.Tag.Get("jsonrpcusage"); tag != "" {
    63  			fieldUsages = append(fieldUsages, tag)
    64  			continue
    65  		}
    66  
    67  		// Create the name/value entry for the field while considering
    68  		// the type of the field.  Not all possible types are covered
    69  		// here and when one of the types not specifically covered is
    70  		// encountered, the field name is simply reused for the value.
    71  		fieldName := strings.ToLower(rtf.Name)
    72  		fieldValue := fieldName
    73  		fieldKind := rtf.Type.Kind()
    74  		switch {
    75  		case isNumeric(fieldKind):
    76  			if fieldKind == reflect.Float32 || fieldKind == reflect.Float64 {
    77  				fieldValue = "n.nnn"
    78  			} else {
    79  				fieldValue = "n"
    80  			}
    81  		case fieldKind == reflect.String:
    82  			fieldValue = `"value"`
    83  
    84  		case fieldKind == reflect.Struct:
    85  			fieldValue = subStructUsage(rtf.Type)
    86  
    87  		case fieldKind == reflect.Array || fieldKind == reflect.Slice:
    88  			fieldValue = subArrayUsage(rtf.Type, fieldName)
    89  		}
    90  
    91  		usage := fmt.Sprintf("%q:%s", fieldName, fieldValue)
    92  		fieldUsages = append(fieldUsages, usage)
    93  	}
    94  
    95  	return fmt.Sprintf("{%s}", strings.Join(fieldUsages, ","))
    96  }
    97  
    98  // subArrayUsage returns a string for use in the one-line usage for the given
    99  // array or slice.  It also contains logic to convert plural field names to
   100  // singular so the generated usage string reads better.
   101  func subArrayUsage(arrayType reflect.Type, fieldName string) string {
   102  	// Convert plural field names to singular.  Only works for English.
   103  	singularFieldName := fieldName
   104  	if strings.HasSuffix(fieldName, "ies") {
   105  		singularFieldName = strings.TrimSuffix(fieldName, "ies")
   106  		singularFieldName = singularFieldName + "y"
   107  	} else if strings.HasSuffix(fieldName, "es") {
   108  		singularFieldName = strings.TrimSuffix(fieldName, "es")
   109  	} else if strings.HasSuffix(fieldName, "s") {
   110  		singularFieldName = strings.TrimSuffix(fieldName, "s")
   111  	}
   112  
   113  	elemType := arrayType.Elem()
   114  	switch elemType.Kind() {
   115  	case reflect.String:
   116  		return fmt.Sprintf("[%q,...]", singularFieldName)
   117  
   118  	case reflect.Struct:
   119  		return fmt.Sprintf("[%s,...]", subStructUsage(elemType))
   120  	}
   121  
   122  	// Fall back to simply showing the field name in array syntax.
   123  	return fmt.Sprintf(`[%s,...]`, singularFieldName)
   124  }
   125  
   126  // fieldUsage returns a string for use in the one-line usage for the struct
   127  // field of a command.
   128  //
   129  // Any fields that include a jsonrpcusage struct tag will use that instead of
   130  // being automatically generated.
   131  func fieldUsage(structField reflect.StructField, defaultVal *reflect.Value) string {
   132  	// When the field has a jsonrpcusage struct tag specified use that
   133  	// instead of automatically generating it.
   134  	if tag := structField.Tag.Get("jsonrpcusage"); tag != "" {
   135  		return tag
   136  	}
   137  
   138  	// Indirect the pointer if needed.
   139  	fieldType := structField.Type
   140  	if fieldType.Kind() == reflect.Ptr {
   141  		fieldType = fieldType.Elem()
   142  	}
   143  
   144  	// When there is a default value, it must also be a pointer due to the
   145  	// rules enforced by RegisterCmd.
   146  	if defaultVal != nil {
   147  		indirect := defaultVal.Elem()
   148  		defaultVal = &indirect
   149  	}
   150  
   151  	// Handle certain types uniquely to provide nicer usage.
   152  	fieldName := strings.ToLower(structField.Name)
   153  	switch fieldType.Kind() {
   154  	case reflect.String:
   155  		if defaultVal != nil {
   156  			return fmt.Sprintf("%s=%q", fieldName,
   157  				defaultVal.Interface())
   158  		}
   159  
   160  		return fmt.Sprintf("%q", fieldName)
   161  
   162  	case reflect.Array, reflect.Slice:
   163  		return subArrayUsage(fieldType, fieldName)
   164  
   165  	case reflect.Struct:
   166  		return subStructUsage(fieldType)
   167  	}
   168  
   169  	// Simply return the field name when none of the above special cases
   170  	// apply.
   171  	if defaultVal != nil {
   172  		return fmt.Sprintf("%s=%v", fieldName, defaultVal.Interface())
   173  	}
   174  	return fieldName
   175  }
   176  
   177  // methodUsageText returns a one-line usage string for the provided command and
   178  // method info.  This is the main work horse for the exported MethodUsageText
   179  // function.
   180  func methodUsageText(rtp reflect.Type, defaults map[int]reflect.Value, method string) string {
   181  	// Generate the individual usage for each field in the command.  Several
   182  	// simplifying assumptions are made here because the RegisterCmd
   183  	// function has already rigorously enforced the layout.
   184  	rt := rtp.Elem()
   185  	numFields := rt.NumField()
   186  	reqFieldUsages := make([]string, 0, numFields)
   187  	optFieldUsages := make([]string, 0, numFields)
   188  	for i := 0; i < numFields; i++ {
   189  		rtf := rt.Field(i)
   190  		var isOptional bool
   191  		if kind := rtf.Type.Kind(); kind == reflect.Ptr {
   192  			isOptional = true
   193  		}
   194  
   195  		var defaultVal *reflect.Value
   196  		if defVal, ok := defaults[i]; ok {
   197  			defaultVal = &defVal
   198  		}
   199  
   200  		// Add human-readable usage to the appropriate slice that is
   201  		// later used to generate the one-line usage.
   202  		usage := fieldUsage(rtf, defaultVal)
   203  		if isOptional {
   204  			optFieldUsages = append(optFieldUsages, usage)
   205  		} else {
   206  			reqFieldUsages = append(reqFieldUsages, usage)
   207  		}
   208  	}
   209  
   210  	// Generate and return the one-line usage string.
   211  	usageStr := method
   212  	if len(reqFieldUsages) > 0 {
   213  		usageStr += " " + strings.Join(reqFieldUsages, " ")
   214  	}
   215  	if len(optFieldUsages) > 0 {
   216  		usageStr += fmt.Sprintf(" (%s)", strings.Join(optFieldUsages, " "))
   217  	}
   218  	return usageStr
   219  }
   220  
   221  // MethodUsageText returns a one-line usage string for the provided method.  The
   222  // provided method must be associated with a registered type.  All commands
   223  // provided by this package are registered by default.
   224  func MethodUsageText(method string) (string, error) {
   225  	// Look up details about the provided method and error out if not
   226  	// registered.
   227  	registerLock.RLock()
   228  	rtp, ok := methodToConcreteType[method]
   229  	info := methodToInfo[method]
   230  	registerLock.RUnlock()
   231  	if !ok {
   232  		str := fmt.Sprintf("%q is not registered", method)
   233  		return "", makeError(ErrUnregisteredMethod, str)
   234  	}
   235  
   236  	// When the usage for this method has already been generated, simply
   237  	// return it.
   238  	if info.usage != "" {
   239  		return info.usage, nil
   240  	}
   241  
   242  	// Generate and store the usage string for future calls and return it.
   243  	usage := methodUsageText(rtp, info.defaults, method)
   244  	registerLock.Lock()
   245  	info.usage = usage
   246  	methodToInfo[method] = info
   247  	registerLock.Unlock()
   248  	return usage, nil
   249  }