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.
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)
20 method, ok := concreteTypeToMethod[rt]
21 registerLock.RUnlock()
23 str := fmt.Sprintf("%q is not registered", method)
24 return "", makeError(ErrUnregisteredMethod, str)
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
37 info, ok := methodToInfo[method]
38 registerLock.RUnlock()
40 str := fmt.Sprintf("%q is not registered", method)
41 return 0, makeError(ErrUnregisteredMethod, str)
44 return info.flags, nil
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
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)
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)
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()
75 case isNumeric(fieldKind):
76 if fieldKind == reflect.Float32 || fieldKind == reflect.Float64 {
81 case fieldKind == reflect.String:
82 fieldValue = `"value"`
84 case fieldKind == reflect.Struct:
85 fieldValue = subStructUsage(rtf.Type)
87 case fieldKind == reflect.Array || fieldKind == reflect.Slice:
88 fieldValue = subArrayUsage(rtf.Type, fieldName)
91 usage := fmt.Sprintf("%q:%s", fieldName, fieldValue)
92 fieldUsages = append(fieldUsages, usage)
95 return fmt.Sprintf("{%s}", strings.Join(fieldUsages, ","))
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")
113 elemType := arrayType.Elem()
114 switch elemType.Kind() {
116 return fmt.Sprintf("[%q,...]", singularFieldName)
119 return fmt.Sprintf("[%s,...]", subStructUsage(elemType))
122 // Fall back to simply showing the field name in array syntax.
123 return fmt.Sprintf(`[%s,...]`, singularFieldName)
126 // fieldUsage returns a string for use in the one-line usage for the struct
127 // field of a command.
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 != "" {
138 // Indirect the pointer if needed.
139 fieldType := structField.Type
140 if fieldType.Kind() == reflect.Ptr {
141 fieldType = fieldType.Elem()
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
151 // Handle certain types uniquely to provide nicer usage.
152 fieldName := strings.ToLower(structField.Name)
153 switch fieldType.Kind() {
155 if defaultVal != nil {
156 return fmt.Sprintf("%s=%q", fieldName,
157 defaultVal.Interface())
160 return fmt.Sprintf("%q", fieldName)
162 case reflect.Array, reflect.Slice:
163 return subArrayUsage(fieldType, fieldName)
166 return subStructUsage(fieldType)
169 // Simply return the field name when none of the above special cases
171 if defaultVal != nil {
172 return fmt.Sprintf("%s=%v", fieldName, defaultVal.Interface())
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
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.
185 numFields := rt.NumField()
186 reqFieldUsages := make([]string, 0, numFields)
187 optFieldUsages := make([]string, 0, numFields)
188 for i := 0; i < numFields; i++ {
191 if kind := rtf.Type.Kind(); kind == reflect.Ptr {
195 var defaultVal *reflect.Value
196 if defVal, ok := defaults[i]; ok {
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)
204 optFieldUsages = append(optFieldUsages, usage)
206 reqFieldUsages = append(reqFieldUsages, usage)
210 // Generate and return the one-line usage string.
212 if len(reqFieldUsages) > 0 {
213 usageStr += " " + strings.Join(reqFieldUsages, " ")
215 if len(optFieldUsages) > 0 {
216 usageStr += fmt.Sprintf(" (%s)", strings.Join(optFieldUsages, " "))
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
228 rtp, ok := methodToConcreteType[method]
229 info := methodToInfo[method]
230 registerLock.RUnlock()
232 str := fmt.Sprintf("%q is not registered", method)
233 return "", makeError(ErrUnregisteredMethod, str)
236 // When the usage for this method has already been generated, simply
238 if info.usage != "" {
239 return info.usage, nil
242 // Generate and store the usage string for future calls and return it.
243 usage := methodUsageText(rtp, info.defaults, method)
246 methodToInfo[method] = info
247 registerLock.Unlock()