github.com/BlockABC/godash@v0.0.0-20191112120524-f4aa3a32c566/btcjson/cmdinfo.go (about)

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