OSDN Git Service

new repo
[bytom/vapor.git] / vendor / github.com / btcsuite / btcd / btcjson / help.go
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         "bytes"
9         "fmt"
10         "reflect"
11         "strings"
12         "text/tabwriter"
13 )
14
15 // baseHelpDescs house the various help labels, types, and example values used
16 // when generating help.  The per-command synopsis, field descriptions,
17 // conditions, and result descriptions are to be provided by the caller.
18 var baseHelpDescs = map[string]string{
19         // Misc help labels and output.
20         "help-arguments":      "Arguments",
21         "help-arguments-none": "None",
22         "help-result":         "Result",
23         "help-result-nothing": "Nothing",
24         "help-default":        "default",
25         "help-optional":       "optional",
26         "help-required":       "required",
27
28         // JSON types.
29         "json-type-numeric": "numeric",
30         "json-type-string":  "string",
31         "json-type-bool":    "boolean",
32         "json-type-array":   "array of ",
33         "json-type-object":  "object",
34         "json-type-value":   "value",
35
36         // JSON examples.
37         "json-example-string":   "value",
38         "json-example-bool":     "true|false",
39         "json-example-map-data": "data",
40         "json-example-unknown":  "unknown",
41 }
42
43 // descLookupFunc is a function which is used to lookup a description given
44 // a key.
45 type descLookupFunc func(string) string
46
47 // reflectTypeToJSONType returns a string that represents the JSON type
48 // associated with the provided Go type.
49 func reflectTypeToJSONType(xT descLookupFunc, rt reflect.Type) string {
50         kind := rt.Kind()
51         if isNumeric(kind) {
52                 return xT("json-type-numeric")
53         }
54
55         switch kind {
56         case reflect.String:
57                 return xT("json-type-string")
58
59         case reflect.Bool:
60                 return xT("json-type-bool")
61
62         case reflect.Array, reflect.Slice:
63                 return xT("json-type-array") + reflectTypeToJSONType(xT,
64                         rt.Elem())
65
66         case reflect.Struct:
67                 return xT("json-type-object")
68
69         case reflect.Map:
70                 return xT("json-type-object")
71         }
72
73         return xT("json-type-value")
74 }
75
76 // resultStructHelp returns a slice of strings containing the result help output
77 // for a struct.  Each line makes use of tabs to separate the relevant pieces so
78 // a tabwriter can be used later to line everything up.  The descriptions are
79 // pulled from the active help descriptions map based on the lowercase version
80 // of the provided reflect type and json name (or the lowercase version of the
81 // field name if no json tag was specified).
82 func resultStructHelp(xT descLookupFunc, rt reflect.Type, indentLevel int) []string {
83         indent := strings.Repeat(" ", indentLevel)
84         typeName := strings.ToLower(rt.Name())
85
86         // Generate the help for each of the fields in the result struct.
87         numField := rt.NumField()
88         results := make([]string, 0, numField)
89         for i := 0; i < numField; i++ {
90                 rtf := rt.Field(i)
91
92                 // The field name to display is the json name when it's
93                 // available, otherwise use the lowercase field name.
94                 var fieldName string
95                 if tag := rtf.Tag.Get("json"); tag != "" {
96                         fieldName = strings.Split(tag, ",")[0]
97                 } else {
98                         fieldName = strings.ToLower(rtf.Name)
99                 }
100
101                 // Deference pointer if needed.
102                 rtfType := rtf.Type
103                 if rtfType.Kind() == reflect.Ptr {
104                         rtfType = rtf.Type.Elem()
105                 }
106
107                 // Generate the JSON example for the result type of this struct
108                 // field.  When it is a complex type, examine the type and
109                 // adjust the opening bracket and brace combination accordingly.
110                 fieldType := reflectTypeToJSONType(xT, rtfType)
111                 fieldDescKey := typeName + "-" + fieldName
112                 fieldExamples, isComplex := reflectTypeToJSONExample(xT,
113                         rtfType, indentLevel, fieldDescKey)
114                 if isComplex {
115                         var brace string
116                         kind := rtfType.Kind()
117                         if kind == reflect.Array || kind == reflect.Slice {
118                                 brace = "[{"
119                         } else {
120                                 brace = "{"
121                         }
122                         result := fmt.Sprintf("%s\"%s\": %s\t(%s)\t%s", indent,
123                                 fieldName, brace, fieldType, xT(fieldDescKey))
124                         results = append(results, result)
125                         results = append(results, fieldExamples...)
126                 } else {
127                         result := fmt.Sprintf("%s\"%s\": %s,\t(%s)\t%s", indent,
128                                 fieldName, fieldExamples[0], fieldType,
129                                 xT(fieldDescKey))
130                         results = append(results, result)
131                 }
132         }
133
134         return results
135 }
136
137 // reflectTypeToJSONExample generates example usage in the format used by the
138 // help output.  It handles arrays, slices and structs recursively.  The output
139 // is returned as a slice of lines so the final help can be nicely aligned via
140 // a tab writer.  A bool is also returned which specifies whether or not the
141 // type results in a complex JSON object since they need to be handled
142 // differently.
143 func reflectTypeToJSONExample(xT descLookupFunc, rt reflect.Type, indentLevel int, fieldDescKey string) ([]string, bool) {
144         // Indirect pointer if needed.
145         if rt.Kind() == reflect.Ptr {
146                 rt = rt.Elem()
147         }
148         kind := rt.Kind()
149         if isNumeric(kind) {
150                 if kind == reflect.Float32 || kind == reflect.Float64 {
151                         return []string{"n.nnn"}, false
152                 }
153
154                 return []string{"n"}, false
155         }
156
157         switch kind {
158         case reflect.String:
159                 return []string{`"` + xT("json-example-string") + `"`}, false
160
161         case reflect.Bool:
162                 return []string{xT("json-example-bool")}, false
163
164         case reflect.Struct:
165                 indent := strings.Repeat(" ", indentLevel)
166                 results := resultStructHelp(xT, rt, indentLevel+1)
167
168                 // An opening brace is needed for the first indent level.  For
169                 // all others, it will be included as a part of the previous
170                 // field.
171                 if indentLevel == 0 {
172                         newResults := make([]string, len(results)+1)
173                         newResults[0] = "{"
174                         copy(newResults[1:], results)
175                         results = newResults
176                 }
177
178                 // The closing brace has a comma after it except for the first
179                 // indent level.  The final tabs are necessary so the tab writer
180                 // lines things up properly.
181                 closingBrace := indent + "}"
182                 if indentLevel > 0 {
183                         closingBrace += ","
184                 }
185                 results = append(results, closingBrace+"\t\t")
186                 return results, true
187
188         case reflect.Array, reflect.Slice:
189                 results, isComplex := reflectTypeToJSONExample(xT, rt.Elem(),
190                         indentLevel, fieldDescKey)
191
192                 // When the result is complex, it is because this is an array of
193                 // objects.
194                 if isComplex {
195                         // When this is at indent level zero, there is no
196                         // previous field to house the opening array bracket, so
197                         // replace the opening object brace with the array
198                         // syntax.  Also, replace the final closing object brace
199                         // with the variadiac array closing syntax.
200                         indent := strings.Repeat(" ", indentLevel)
201                         if indentLevel == 0 {
202                                 results[0] = indent + "[{"
203                                 results[len(results)-1] = indent + "},...]"
204                                 return results, true
205                         }
206
207                         // At this point, the indent level is greater than 0, so
208                         // the opening array bracket and object brace are
209                         // already a part of the previous field.  However, the
210                         // closing entry is a simple object brace, so replace it
211                         // with the variadiac array closing syntax.  The final
212                         // tabs are necessary so the tab writer lines things up
213                         // properly.
214                         results[len(results)-1] = indent + "},...],\t\t"
215                         return results, true
216                 }
217
218                 // It's an array of primitives, so return the formatted text
219                 // accordingly.
220                 return []string{fmt.Sprintf("[%s,...]", results[0])}, false
221
222         case reflect.Map:
223                 indent := strings.Repeat(" ", indentLevel)
224                 results := make([]string, 0, 3)
225
226                 // An opening brace is needed for the first indent level.  For
227                 // all others, it will be included as a part of the previous
228                 // field.
229                 if indentLevel == 0 {
230                         results = append(results, indent+"{")
231                 }
232
233                 // Maps are a bit special in that they need to have the key,
234                 // value, and description of the object entry specifically
235                 // called out.
236                 innerIndent := strings.Repeat(" ", indentLevel+1)
237                 result := fmt.Sprintf("%s%q: %s, (%s) %s", innerIndent,
238                         xT(fieldDescKey+"--key"), xT(fieldDescKey+"--value"),
239                         reflectTypeToJSONType(xT, rt), xT(fieldDescKey+"--desc"))
240                 results = append(results, result)
241                 results = append(results, innerIndent+"...")
242
243                 results = append(results, indent+"}")
244                 return results, true
245         }
246
247         return []string{xT("json-example-unknown")}, false
248 }
249
250 // resultTypeHelp generates and returns formatted help for the provided result
251 // type.
252 func resultTypeHelp(xT descLookupFunc, rt reflect.Type, fieldDescKey string) string {
253         // Generate the JSON example for the result type.
254         results, isComplex := reflectTypeToJSONExample(xT, rt, 0, fieldDescKey)
255
256         // When this is a primitive type, add the associated JSON type and
257         // result description into the final string, format it accordingly,
258         // and return it.
259         if !isComplex {
260                 return fmt.Sprintf("%s (%s) %s", results[0],
261                         reflectTypeToJSONType(xT, rt), xT(fieldDescKey))
262         }
263
264         // At this point, this is a complex type that already has the JSON types
265         // and descriptions in the results.  Thus, use a tab writer to nicely
266         // align the help text.
267         var formatted bytes.Buffer
268         w := new(tabwriter.Writer)
269         w.Init(&formatted, 0, 4, 1, ' ', 0)
270         for i, text := range results {
271                 if i == len(results)-1 {
272                         fmt.Fprintf(w, text)
273                 } else {
274                         fmt.Fprintln(w, text)
275                 }
276         }
277         w.Flush()
278         return formatted.String()
279 }
280
281 // argTypeHelp returns the type of provided command argument as a string in the
282 // format used by the help output.  In particular, it includes the JSON type
283 // (boolean, numeric, string, array, object) along with optional and the default
284 // value if applicable.
285 func argTypeHelp(xT descLookupFunc, structField reflect.StructField, defaultVal *reflect.Value) string {
286         // Indirect the pointer if needed and track if it's an optional field.
287         fieldType := structField.Type
288         var isOptional bool
289         if fieldType.Kind() == reflect.Ptr {
290                 fieldType = fieldType.Elem()
291                 isOptional = true
292         }
293
294         // When there is a default value, it must also be a pointer due to the
295         // rules enforced by RegisterCmd.
296         if defaultVal != nil {
297                 indirect := defaultVal.Elem()
298                 defaultVal = &indirect
299         }
300
301         // Convert the field type to a JSON type.
302         details := make([]string, 0, 3)
303         details = append(details, reflectTypeToJSONType(xT, fieldType))
304
305         // Add optional and default value to the details if needed.
306         if isOptional {
307                 details = append(details, xT("help-optional"))
308
309                 // Add the default value if there is one.  This is only checked
310                 // when the field is optional since a non-optional field can't
311                 // have a default value.
312                 if defaultVal != nil {
313                         val := defaultVal.Interface()
314                         if defaultVal.Kind() == reflect.String {
315                                 val = fmt.Sprintf(`"%s"`, val)
316                         }
317                         str := fmt.Sprintf("%s=%v", xT("help-default"), val)
318                         details = append(details, str)
319                 }
320         } else {
321                 details = append(details, xT("help-required"))
322         }
323
324         return strings.Join(details, ", ")
325 }
326
327 // argHelp generates and returns formatted help for the provided command.
328 func argHelp(xT descLookupFunc, rtp reflect.Type, defaults map[int]reflect.Value, method string) string {
329         // Return now if the command has no arguments.
330         rt := rtp.Elem()
331         numFields := rt.NumField()
332         if numFields == 0 {
333                 return ""
334         }
335
336         // Generate the help for each argument in the command.  Several
337         // simplifying assumptions are made here because the RegisterCmd
338         // function has already rigorously enforced the layout.
339         args := make([]string, 0, numFields)
340         for i := 0; i < numFields; i++ {
341                 rtf := rt.Field(i)
342                 var defaultVal *reflect.Value
343                 if defVal, ok := defaults[i]; ok {
344                         defaultVal = &defVal
345                 }
346
347                 fieldName := strings.ToLower(rtf.Name)
348                 helpText := fmt.Sprintf("%d.\t%s\t(%s)\t%s", i+1, fieldName,
349                         argTypeHelp(xT, rtf, defaultVal),
350                         xT(method+"-"+fieldName))
351                 args = append(args, helpText)
352
353                 // For types which require a JSON object, or an array of JSON
354                 // objects, generate the full syntax for the argument.
355                 fieldType := rtf.Type
356                 if fieldType.Kind() == reflect.Ptr {
357                         fieldType = fieldType.Elem()
358                 }
359                 kind := fieldType.Kind()
360                 switch kind {
361                 case reflect.Struct:
362                         fieldDescKey := fmt.Sprintf("%s-%s", method, fieldName)
363                         resultText := resultTypeHelp(xT, fieldType, fieldDescKey)
364                         args = append(args, resultText)
365
366                 case reflect.Map:
367                         fieldDescKey := fmt.Sprintf("%s-%s", method, fieldName)
368                         resultText := resultTypeHelp(xT, fieldType, fieldDescKey)
369                         args = append(args, resultText)
370
371                 case reflect.Array, reflect.Slice:
372                         fieldDescKey := fmt.Sprintf("%s-%s", method, fieldName)
373                         if rtf.Type.Elem().Kind() == reflect.Struct {
374                                 resultText := resultTypeHelp(xT, fieldType,
375                                         fieldDescKey)
376                                 args = append(args, resultText)
377                         }
378                 }
379         }
380
381         // Add argument names, types, and descriptions if there are any.  Use a
382         // tab writer to nicely align the help text.
383         var formatted bytes.Buffer
384         w := new(tabwriter.Writer)
385         w.Init(&formatted, 0, 4, 1, ' ', 0)
386         for _, text := range args {
387                 fmt.Fprintln(w, text)
388         }
389         w.Flush()
390         return formatted.String()
391 }
392
393 // methodHelp generates and returns the help output for the provided command
394 // and method info.  This is the main work horse for the exported MethodHelp
395 // function.
396 func methodHelp(xT descLookupFunc, rtp reflect.Type, defaults map[int]reflect.Value, method string, resultTypes []interface{}) string {
397         // Start off with the method usage and help synopsis.
398         help := fmt.Sprintf("%s\n\n%s\n", methodUsageText(rtp, defaults, method),
399                 xT(method+"--synopsis"))
400
401         // Generate the help for each argument in the command.
402         if argText := argHelp(xT, rtp, defaults, method); argText != "" {
403                 help += fmt.Sprintf("\n%s:\n%s", xT("help-arguments"),
404                         argText)
405         } else {
406                 help += fmt.Sprintf("\n%s:\n%s\n", xT("help-arguments"),
407                         xT("help-arguments-none"))
408         }
409
410         // Generate the help text for each result type.
411         resultTexts := make([]string, 0, len(resultTypes))
412         for i := range resultTypes {
413                 rtp := reflect.TypeOf(resultTypes[i])
414                 fieldDescKey := fmt.Sprintf("%s--result%d", method, i)
415                 if resultTypes[i] == nil {
416                         resultText := xT("help-result-nothing")
417                         resultTexts = append(resultTexts, resultText)
418                         continue
419                 }
420
421                 resultText := resultTypeHelp(xT, rtp.Elem(), fieldDescKey)
422                 resultTexts = append(resultTexts, resultText)
423         }
424
425         // Add result types and descriptions.  When there is more than one
426         // result type, also add the condition which triggers it.
427         if len(resultTexts) > 1 {
428                 for i, resultText := range resultTexts {
429                         condKey := fmt.Sprintf("%s--condition%d", method, i)
430                         help += fmt.Sprintf("\n%s (%s):\n%s\n",
431                                 xT("help-result"), xT(condKey), resultText)
432                 }
433         } else if len(resultTexts) > 0 {
434                 help += fmt.Sprintf("\n%s:\n%s\n", xT("help-result"),
435                         resultTexts[0])
436         } else {
437                 help += fmt.Sprintf("\n%s:\n%s\n", xT("help-result"),
438                         xT("help-result-nothing"))
439         }
440         return help
441 }
442
443 // isValidResultType returns whether the passed reflect kind is one of the
444 // acceptable types for results.
445 func isValidResultType(kind reflect.Kind) bool {
446         if isNumeric(kind) {
447                 return true
448         }
449
450         switch kind {
451         case reflect.String, reflect.Struct, reflect.Array, reflect.Slice,
452                 reflect.Bool, reflect.Map:
453
454                 return true
455         }
456
457         return false
458 }
459
460 // GenerateHelp generates and returns help output for the provided method and
461 // result types given a map to provide the appropriate keys for the method
462 // synopsis, field descriptions, conditions, and result descriptions.  The
463 // method must be associated with a registered type.  All commands provided by
464 // this package are registered by default.
465 //
466 // The resultTypes must be pointer-to-types which represent the specific types
467 // of values the command returns.  For example, if the command only returns a
468 // boolean value, there should only be a single entry of (*bool)(nil).  Note
469 // that each type must be a single pointer to the type.  Therefore, it is
470 // recommended to simply pass a nil pointer cast to the appropriate type as
471 // previously shown.
472 //
473 // The provided descriptions map must contain all of the keys or an error will
474 // be returned which includes the missing key, or the final missing key when
475 // there is more than one key missing.  The generated help in the case of such
476 // an error will use the key in place of the description.
477 //
478 // The following outlines the required keys:
479 //   "<method>--synopsis"             Synopsis for the command
480 //   "<method>-<lowerfieldname>"      Description for each command argument
481 //   "<typename>-<lowerfieldname>"    Description for each object field
482 //   "<method>--condition<#>"         Description for each result condition
483 //   "<method>--result<#>"            Description for each primitive result num
484 //
485 // Notice that the "special" keys synopsis, condition<#>, and result<#> are
486 // preceded by a double dash to ensure they don't conflict with field names.
487 //
488 // The condition keys are only required when there is more than on result type,
489 // and the result key for a given result type is only required if it's not an
490 // object.
491 //
492 // For example, consider the 'help' command itself.  There are two possible
493 // returns depending on the provided parameters.  So, the help would be
494 // generated by calling the function as follows:
495 //   GenerateHelp("help", descs, (*string)(nil), (*string)(nil)).
496 //
497 // The following keys would then be required in the provided descriptions map:
498 //
499 //   "help--synopsis":   "Returns a list of all commands or help for ...."
500 //   "help-command":     "The command to retrieve help for",
501 //   "help--condition0": "no command provided"
502 //   "help--condition1": "command specified"
503 //   "help--result0":    "List of commands"
504 //   "help--result1":    "Help for specified command"
505 func GenerateHelp(method string, descs map[string]string, resultTypes ...interface{}) (string, error) {
506         // Look up details about the provided method and error out if not
507         // registered.
508         registerLock.RLock()
509         rtp, ok := methodToConcreteType[method]
510         info := methodToInfo[method]
511         registerLock.RUnlock()
512         if !ok {
513                 str := fmt.Sprintf("%q is not registered", method)
514                 return "", makeError(ErrUnregisteredMethod, str)
515         }
516
517         // Validate each result type is a pointer to a supported type (or nil).
518         for i, resultType := range resultTypes {
519                 if resultType == nil {
520                         continue
521                 }
522
523                 rtp := reflect.TypeOf(resultType)
524                 if rtp.Kind() != reflect.Ptr {
525                         str := fmt.Sprintf("result #%d (%v) is not a pointer",
526                                 i, rtp.Kind())
527                         return "", makeError(ErrInvalidType, str)
528                 }
529
530                 elemKind := rtp.Elem().Kind()
531                 if !isValidResultType(elemKind) {
532                         str := fmt.Sprintf("result #%d (%v) is not an allowed "+
533                                 "type", i, elemKind)
534                         return "", makeError(ErrInvalidType, str)
535                 }
536         }
537
538         // Create a closure for the description lookup function which falls back
539         // to the base help descriptions map for unrecognized keys and tracks
540         // and missing keys.
541         var missingKey string
542         xT := func(key string) string {
543                 if desc, ok := descs[key]; ok {
544                         return desc
545                 }
546                 if desc, ok := baseHelpDescs[key]; ok {
547                         return desc
548                 }
549
550                 missingKey = key
551                 return key
552         }
553
554         // Generate and return the help for the method.
555         help := methodHelp(xT, rtp, info.defaults, method, resultTypes)
556         if missingKey != "" {
557                 return help, makeError(ErrMissingDescription, missingKey)
558         }
559         return help, nil
560 }