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 }