github.com/lbryio/lbcd@v0.22.119/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 }