blob: 75573c1def360f261db315d77c93d7b152212a09 [file]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package genparams
import (
"bytes"
"fmt"
"math"
"reflect"
"sort"
"strconv"
"strings"
"text/template"
"time"
"chromiumos/tast/errors"
)
// funcs is a map of functions available to templates.
var funcs = map[string]interface{}{
"fmt": format,
}
// Template is a utility function to render a Go template into a string in a
// single call.
//
// Go's standard template engine is used to render a template string. See
// https://godoc.org/text/template for the template syntax.
//
// This function also installs a few helper function that can be called inside
// templates:
//
// fmt(v interface{}) string - Formats v in a Go syntax. Supported types are
// boolean, integer, float, string, slice of supported types, map of
// supported types.
func Template(t TestingT, text string, data interface{}) string {
t.Helper()
tmpl, err := template.New("").Funcs(funcs).Parse(text)
if err != nil {
t.Fatalf("Failed to parse template: %v", err)
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, data); err != nil {
t.Fatalf("Failed to render template: %v", err)
}
return buf.String()
}
func format(v interface{}) (string, error) {
return formatRec(v, true)
}
func formatRec(v interface{}, needType bool) (text string, err error) {
if v == nil {
return "nil", nil
}
val := reflect.ValueOf(v)
typ := val.Type()
if duration, ok := v.(time.Duration); ok {
return formatDuration(duration)
}
if defined(typ) {
return "", errors.Errorf("unsupported type %T", v)
}
if needType {
switch typ.Kind() {
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32:
defer func() { text = fmt.Sprintf("%T(%s)", v, text) }()
case reflect.Slice, reflect.Map:
defer func() { text = fmt.Sprintf("%T%s", v, text) }()
}
}
switch typ.Kind() {
case reflect.Bool:
return strconv.FormatBool(val.Bool()), nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(val.Int(), 10), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.FormatUint(val.Uint(), 10), nil
case reflect.Float32, reflect.Float64:
return strconv.FormatFloat(val.Float(), 'g', -1, 64), nil
case reflect.String:
return strconv.Quote(val.String()), nil
case reflect.Slice:
var elems []string
for i := 0; i < val.Len(); i++ {
s, err := formatRec(val.Index(i).Interface(), false)
if err != nil {
return "", err
}
elems = append(elems, s)
}
return fmt.Sprintf("{%s}", strings.Join(elems, ", ")), nil
case reflect.Map:
keys, err := sortKeys(val.MapKeys())
if err != nil {
return "", err
}
var elems []string
for _, key := range keys {
sk, err := formatRec(key.Interface(), true)
if err != nil {
return "", err
}
sv, err := formatRec(val.MapIndex(key).Interface(), false)
if err != nil {
return "", err
}
elems = append(elems, fmt.Sprintf("%s: %s", sk, sv))
}
return fmt.Sprintf("{%s}", strings.Join(elems, ", ")), nil
default:
return "", errors.Errorf("unsupported type %T", v)
}
}
func sortKeys(keys []reflect.Value) ([]reflect.Value, error) {
if len(keys) == 0 {
return nil, nil
}
switch keys[0].Type().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
sort.Slice(keys, func(i, j int) bool {
return keys[i].Int() < keys[j].Int()
})
return keys, nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
sort.Slice(keys, func(i, j int) bool {
return keys[i].Uint() < keys[j].Uint()
})
return keys, nil
case reflect.Float32, reflect.Float64:
sort.Slice(keys, func(i, j int) bool {
return keys[i].Float() < keys[j].Float()
})
return keys, nil
case reflect.String:
sort.Slice(keys, func(i, j int) bool {
return keys[i].String() < keys[j].String()
})
return keys, nil
default:
return nil, errors.Errorf("unsupported type %T for map keys", keys[0].Interface())
}
}
// defined returns true if t is a defined type.
// https://golang.org/ref/spec#Type_definitions
func defined(t reflect.Type) bool {
// PkgPath is empty for predeclared types and non-defined types.
// Name is empty for non-defined types.
return t.PkgPath() != "" && t.Name() != ""
}
func formatDuration(t time.Duration) (string, error) {
if hours := t.Hours(); hours == math.Floor(hours) {
return strconv.FormatInt(int64(hours), 10) + " * time.Hour", nil
} else if minutes := t.Minutes(); minutes == math.Floor(minutes) {
return strconv.FormatInt(int64(minutes), 10) + " * time.Minute", nil
} else if seconds := t.Seconds(); seconds == math.Floor(seconds) {
return strconv.FormatInt(int64(seconds), 10) + " * time.Second", nil
} else {
return "", errors.Errorf("time.Duration %v is too precise", t)
}
}