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