250 lines
7.8 KiB
Go
250 lines
7.8 KiB
Go
|
// Copyright (c) 2015 The btcsuite developers
|
||
|
// Use of this source code is governed by an ISC
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
package btcjson
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"reflect"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// CmdMethod returns the method for the passed command. The provided command
|
||
|
// type must be a registered type. All commands provided by this package are
|
||
|
// registered by default.
|
||
|
func CmdMethod(cmd interface{}) (string, error) {
|
||
|
// Look up the cmd type and error out if not registered.
|
||
|
rt := reflect.TypeOf(cmd)
|
||
|
registerLock.RLock()
|
||
|
method, ok := concreteTypeToMethod[rt]
|
||
|
registerLock.RUnlock()
|
||
|
if !ok {
|
||
|
str := fmt.Sprintf("%q is not registered", method)
|
||
|
return "", makeError(ErrUnregisteredMethod, str)
|
||
|
}
|
||
|
|
||
|
return method, nil
|
||
|
}
|
||
|
|
||
|
// MethodUsageFlags returns the usage flags for the passed command method. The
|
||
|
// provided method must be associated with a registered type. All commands
|
||
|
// provided by this package are registered by default.
|
||
|
func MethodUsageFlags(method string) (UsageFlag, error) {
|
||
|
// Look up details about the provided method and error out if not
|
||
|
// registered.
|
||
|
registerLock.RLock()
|
||
|
info, ok := methodToInfo[method]
|
||
|
registerLock.RUnlock()
|
||
|
if !ok {
|
||
|
str := fmt.Sprintf("%q is not registered", method)
|
||
|
return 0, makeError(ErrUnregisteredMethod, str)
|
||
|
}
|
||
|
|
||
|
return info.flags, nil
|
||
|
}
|
||
|
|
||
|
// subStructUsage returns a string for use in the one-line usage for the given
|
||
|
// sub struct. Note that this is specifically for fields which consist of
|
||
|
// structs (or an array/slice of structs) as opposed to the top-level command
|
||
|
// struct.
|
||
|
//
|
||
|
// Any fields that include a jsonrpcusage struct tag will use that instead of
|
||
|
// being automatically generated.
|
||
|
func subStructUsage(structType reflect.Type) string {
|
||
|
numFields := structType.NumField()
|
||
|
fieldUsages := make([]string, 0, numFields)
|
||
|
for i := 0; i < structType.NumField(); i++ {
|
||
|
rtf := structType.Field(i)
|
||
|
|
||
|
// When the field has a jsonrpcusage struct tag specified use
|
||
|
// that instead of automatically generating it.
|
||
|
if tag := rtf.Tag.Get("jsonrpcusage"); tag != "" {
|
||
|
fieldUsages = append(fieldUsages, tag)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Create the name/value entry for the field while considering
|
||
|
// the type of the field. Not all possible types are covered
|
||
|
// here and when one of the types not specifically covered is
|
||
|
// encountered, the field name is simply reused for the value.
|
||
|
fieldName := strings.ToLower(rtf.Name)
|
||
|
fieldValue := fieldName
|
||
|
fieldKind := rtf.Type.Kind()
|
||
|
switch {
|
||
|
case isNumeric(fieldKind):
|
||
|
if fieldKind == reflect.Float32 || fieldKind == reflect.Float64 {
|
||
|
fieldValue = "n.nnn"
|
||
|
} else {
|
||
|
fieldValue = "n"
|
||
|
}
|
||
|
case fieldKind == reflect.String:
|
||
|
fieldValue = `"value"`
|
||
|
|
||
|
case fieldKind == reflect.Struct:
|
||
|
fieldValue = subStructUsage(rtf.Type)
|
||
|
|
||
|
case fieldKind == reflect.Array || fieldKind == reflect.Slice:
|
||
|
fieldValue = subArrayUsage(rtf.Type, fieldName)
|
||
|
}
|
||
|
|
||
|
usage := fmt.Sprintf("%q:%s", fieldName, fieldValue)
|
||
|
fieldUsages = append(fieldUsages, usage)
|
||
|
}
|
||
|
|
||
|
return fmt.Sprintf("{%s}", strings.Join(fieldUsages, ","))
|
||
|
}
|
||
|
|
||
|
// subArrayUsage returns a string for use in the one-line usage for the given
|
||
|
// array or slice. It also contains logic to convert plural field names to
|
||
|
// singular so the generated usage string reads better.
|
||
|
func subArrayUsage(arrayType reflect.Type, fieldName string) string {
|
||
|
// Convert plural field names to singular. Only works for English.
|
||
|
singularFieldName := fieldName
|
||
|
if strings.HasSuffix(fieldName, "ies") {
|
||
|
singularFieldName = strings.TrimSuffix(fieldName, "ies")
|
||
|
singularFieldName = singularFieldName + "y"
|
||
|
} else if strings.HasSuffix(fieldName, "es") {
|
||
|
singularFieldName = strings.TrimSuffix(fieldName, "es")
|
||
|
} else if strings.HasSuffix(fieldName, "s") {
|
||
|
singularFieldName = strings.TrimSuffix(fieldName, "s")
|
||
|
}
|
||
|
|
||
|
elemType := arrayType.Elem()
|
||
|
switch elemType.Kind() {
|
||
|
case reflect.String:
|
||
|
return fmt.Sprintf("[%q,...]", singularFieldName)
|
||
|
|
||
|
case reflect.Struct:
|
||
|
return fmt.Sprintf("[%s,...]", subStructUsage(elemType))
|
||
|
}
|
||
|
|
||
|
// Fall back to simply showing the field name in array syntax.
|
||
|
return fmt.Sprintf(`[%s,...]`, singularFieldName)
|
||
|
}
|
||
|
|
||
|
// fieldUsage returns a string for use in the one-line usage for the struct
|
||
|
// field of a command.
|
||
|
//
|
||
|
// Any fields that include a jsonrpcusage struct tag will use that instead of
|
||
|
// being automatically generated.
|
||
|
func fieldUsage(structField reflect.StructField, defaultVal *reflect.Value) string {
|
||
|
// When the field has a jsonrpcusage struct tag specified use that
|
||
|
// instead of automatically generating it.
|
||
|
if tag := structField.Tag.Get("jsonrpcusage"); tag != "" {
|
||
|
return tag
|
||
|
}
|
||
|
|
||
|
// Indirect the pointer if needed.
|
||
|
fieldType := structField.Type
|
||
|
if fieldType.Kind() == reflect.Ptr {
|
||
|
fieldType = fieldType.Elem()
|
||
|
}
|
||
|
|
||
|
// When there is a default value, it must also be a pointer due to the
|
||
|
// rules enforced by RegisterCmd.
|
||
|
if defaultVal != nil {
|
||
|
indirect := defaultVal.Elem()
|
||
|
defaultVal = &indirect
|
||
|
}
|
||
|
|
||
|
// Handle certain types uniquely to provide nicer usage.
|
||
|
fieldName := strings.ToLower(structField.Name)
|
||
|
switch fieldType.Kind() {
|
||
|
case reflect.String:
|
||
|
if defaultVal != nil {
|
||
|
return fmt.Sprintf("%s=%q", fieldName,
|
||
|
defaultVal.Interface())
|
||
|
}
|
||
|
|
||
|
return fmt.Sprintf("%q", fieldName)
|
||
|
|
||
|
case reflect.Array, reflect.Slice:
|
||
|
return subArrayUsage(fieldType, fieldName)
|
||
|
|
||
|
case reflect.Struct:
|
||
|
return subStructUsage(fieldType)
|
||
|
}
|
||
|
|
||
|
// Simply return the field name when none of the above special cases
|
||
|
// apply.
|
||
|
if defaultVal != nil {
|
||
|
return fmt.Sprintf("%s=%v", fieldName, defaultVal.Interface())
|
||
|
}
|
||
|
return fieldName
|
||
|
}
|
||
|
|
||
|
// methodUsageText returns a one-line usage string for the provided command and
|
||
|
// method info. This is the main work horse for the exported MethodUsageText
|
||
|
// function.
|
||
|
func methodUsageText(rtp reflect.Type, defaults map[int]reflect.Value, method string) string {
|
||
|
// Generate the individual usage for each field in the command. Several
|
||
|
// simplifying assumptions are made here because the RegisterCmd
|
||
|
// function has already rigorously enforced the layout.
|
||
|
rt := rtp.Elem()
|
||
|
numFields := rt.NumField()
|
||
|
reqFieldUsages := make([]string, 0, numFields)
|
||
|
optFieldUsages := make([]string, 0, numFields)
|
||
|
for i := 0; i < numFields; i++ {
|
||
|
rtf := rt.Field(i)
|
||
|
var isOptional bool
|
||
|
if kind := rtf.Type.Kind(); kind == reflect.Ptr {
|
||
|
isOptional = true
|
||
|
}
|
||
|
|
||
|
var defaultVal *reflect.Value
|
||
|
if defVal, ok := defaults[i]; ok {
|
||
|
defaultVal = &defVal
|
||
|
}
|
||
|
|
||
|
// Add human-readable usage to the appropriate slice that is
|
||
|
// later used to generate the one-line usage.
|
||
|
usage := fieldUsage(rtf, defaultVal)
|
||
|
if isOptional {
|
||
|
optFieldUsages = append(optFieldUsages, usage)
|
||
|
} else {
|
||
|
reqFieldUsages = append(reqFieldUsages, usage)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Generate and return the one-line usage string.
|
||
|
usageStr := method
|
||
|
if len(reqFieldUsages) > 0 {
|
||
|
usageStr += " " + strings.Join(reqFieldUsages, " ")
|
||
|
}
|
||
|
if len(optFieldUsages) > 0 {
|
||
|
usageStr += fmt.Sprintf(" (%s)", strings.Join(optFieldUsages, " "))
|
||
|
}
|
||
|
return usageStr
|
||
|
}
|
||
|
|
||
|
// MethodUsageText returns a one-line usage string for the provided method. The
|
||
|
// provided method must be associated with a registered type. All commands
|
||
|
// provided by this package are registered by default.
|
||
|
func MethodUsageText(method string) (string, error) {
|
||
|
// Look up details about the provided method and error out if not
|
||
|
// registered.
|
||
|
registerLock.RLock()
|
||
|
rtp, ok := methodToConcreteType[method]
|
||
|
info := methodToInfo[method]
|
||
|
registerLock.RUnlock()
|
||
|
if !ok {
|
||
|
str := fmt.Sprintf("%q is not registered", method)
|
||
|
return "", makeError(ErrUnregisteredMethod, str)
|
||
|
}
|
||
|
|
||
|
// When the usage for this method has already been generated, simply
|
||
|
// return it.
|
||
|
if info.usage != "" {
|
||
|
return info.usage, nil
|
||
|
}
|
||
|
|
||
|
// Generate and store the usage string for future calls and return it.
|
||
|
usage := methodUsageText(rtp, info.defaults, method)
|
||
|
registerLock.Lock()
|
||
|
info.usage = usage
|
||
|
methodToInfo[method] = info
|
||
|
registerLock.Unlock()
|
||
|
return usage, nil
|
||
|
}
|