// Copyright (c) 2015 Conformal Systems LLC. // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package btcjson import ( "fmt" "reflect" "strings" ) // CmdMethod returns the method for the passed command. The provided command // type must be a registered type. All commands provided by this package are // registered by default. func CmdMethod(cmd interface{}) (string, error) { // Look up the cmd type and error out if not registered. rt := reflect.TypeOf(cmd) registerLock.RLock() method, ok := concreteTypeToMethod[rt] registerLock.RUnlock() if !ok { str := fmt.Sprintf("%q is not registered", method) return "", makeError(ErrUnregisteredMethod, str) } return method, nil } // MethodUsageFlags returns the usage flags for the passed command method. The // provided method must be associated with a registered type. All commands // provided by this package are registered by default. func MethodUsageFlags(method string) (UsageFlag, error) { // Look up details about the provided method and error out if not // registered. registerLock.RLock() info, ok := methodToInfo[method] registerLock.RUnlock() if !ok { str := fmt.Sprintf("%q is not registered", method) return 0, makeError(ErrUnregisteredMethod, str) } return info.flags, nil } // subStructUsage returns a string for use in the one-line usage for the given // sub struct. Note that this is specifically for fields which consist of // structs (or an array/slice of structs) as opposed to the top-level command // struct. // // Any fields that include a jsonrpcusage struct tag will use that instead of // being automatically generated. func subStructUsage(structType reflect.Type) string { numFields := structType.NumField() fieldUsages := make([]string, 0, numFields) for i := 0; i < structType.NumField(); i++ { rtf := structType.Field(i) // When the field has a jsonrpcusage struct tag specified use // that instead of automatically generating it. if tag := rtf.Tag.Get("jsonrpcusage"); tag != "" { fieldUsages = append(fieldUsages, tag) continue } // Create the name/value entry for the field while considering // the type of the field. Not all possibile types are covered // here and when one of the types not specifically covered is // encountered, the field name is simply reused for the value. fieldName := strings.ToLower(rtf.Name) fieldValue := fieldName fieldKind := rtf.Type.Kind() switch { case isNumeric(fieldKind): if fieldKind == reflect.Float32 || fieldKind == reflect.Float64 { fieldValue = "n.nnn" } else { fieldValue = "n" } case fieldKind == reflect.String: fieldValue = `"value"` case fieldKind == reflect.Struct: fieldValue = subStructUsage(rtf.Type) case fieldKind == reflect.Array || fieldKind == reflect.Slice: fieldValue = subArrayUsage(rtf.Type, fieldName) } usage := fmt.Sprintf("%q:%s", fieldName, fieldValue) fieldUsages = append(fieldUsages, usage) } return fmt.Sprintf("{%s}", strings.Join(fieldUsages, ",")) } // subArrayUsage returns a string for use in the one-line usage for the given // array or slice. It also contains logic to convert plural field names to // singular so the generated usage string reads better. func subArrayUsage(arrayType reflect.Type, fieldName string) string { // Convert plural field names to singular. Only works for English. singularFieldName := fieldName if strings.HasSuffix(fieldName, "ies") { singularFieldName = strings.TrimSuffix(fieldName, "ies") singularFieldName = singularFieldName + "y" } else if strings.HasSuffix(fieldName, "es") { singularFieldName = strings.TrimSuffix(fieldName, "es") } else if strings.HasSuffix(fieldName, "s") { singularFieldName = strings.TrimSuffix(fieldName, "s") } elemType := arrayType.Elem() switch elemType.Kind() { case reflect.String: return fmt.Sprintf("[%q,...]", singularFieldName) case reflect.Struct: return fmt.Sprintf("[%s,...]", subStructUsage(elemType)) } // Fall back to simply showing the field name in array syntax. return fmt.Sprintf(`[%s,...]`, singularFieldName) } // fieldUsage returns a string for use in the one-line usage for the struct // field of a command. // // Any fields that include a jsonrpcusage struct tag will use that instead of // being automatically generated. func fieldUsage(structField reflect.StructField, defaultVal *reflect.Value) string { // When the field has a jsonrpcusage struct tag specified use that // instead of automatically generating it. if tag := structField.Tag.Get("jsonrpcusage"); tag != "" { return tag } // Indirect the pointer if needed. fieldType := structField.Type if fieldType.Kind() == reflect.Ptr { fieldType = fieldType.Elem() } // When there is a default value, it must also be a pointer due to the // rules enforced by RegisterCmd. if defaultVal != nil { indirect := defaultVal.Elem() defaultVal = &indirect } // Handle certain types uniquely to provide nicer usage. fieldName := strings.ToLower(structField.Name) switch fieldType.Kind() { case reflect.String: if defaultVal != nil { return fmt.Sprintf("%s=%q", fieldName, defaultVal.Interface()) } return fmt.Sprintf("%q", fieldName) case reflect.Array, reflect.Slice: return subArrayUsage(fieldType, fieldName) case reflect.Struct: return subStructUsage(fieldType) } // Simply return the field name when none of the above special cases // apply. if defaultVal != nil { return fmt.Sprintf("%s=%v", fieldName, defaultVal.Interface()) } return fieldName } // methodUsageText returns a one-line usage string for the provided command and // method info. This is the main work horse for the exported MethodUsageText // function. func methodUsageText(rtp reflect.Type, defaults map[int]reflect.Value, method string) string { // Generate the individual usage for each field in the command. Several // simplifying assumptions are made here because the RegisterCmd // function has already rigorously enforced the layout. rt := rtp.Elem() numFields := rt.NumField() reqFieldUsages := make([]string, 0, numFields) optFieldUsages := make([]string, 0, numFields) for i := 0; i < numFields; i++ { rtf := rt.Field(i) var isOptional bool if kind := rtf.Type.Kind(); kind == reflect.Ptr { isOptional = true } var defaultVal *reflect.Value if defVal, ok := defaults[i]; ok { defaultVal = &defVal } // Add human-readable usage to the appropriate slice that is // later used to generate the one-line usage. usage := fieldUsage(rtf, defaultVal) if isOptional { optFieldUsages = append(optFieldUsages, usage) } else { reqFieldUsages = append(reqFieldUsages, usage) } } // Generate and return the one-line usage string. usageStr := method if len(reqFieldUsages) > 0 { usageStr += " " + strings.Join(reqFieldUsages, " ") } if len(optFieldUsages) > 0 { usageStr += fmt.Sprintf(" (%s)", strings.Join(optFieldUsages, " ")) } return usageStr } // MethodUsageText returns a one-line usage string for the provided method. The // provided method must be associated with a registered type. All commands // provided by this package are registered by default. func MethodUsageText(method string) (string, error) { // Look up details about the provided method and error out if not // registered. registerLock.RLock() rtp, ok := methodToConcreteType[method] info := methodToInfo[method] registerLock.RUnlock() if !ok { str := fmt.Sprintf("%q is not registered", method) return "", makeError(ErrUnregisteredMethod, str) } // When the usage for this method has already been generated, simply // return it. if info.usage != "" { return info.usage, nil } // Generate and store the usage string for future calls and return it. usage := methodUsageText(rtp, info.defaults, method) registerLock.Lock() info.usage = usage methodToInfo[method] = info registerLock.Unlock() return usage, nil }