You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
123 lines
2.6 KiB
123 lines
2.6 KiB
// Package tdp is td pretty-printing and formatting facilities for types from |
|
// MTProto. |
|
package tdp |
|
|
|
import ( |
|
"encoding/base64" |
|
"fmt" |
|
"reflect" |
|
"strconv" |
|
"strings" |
|
"time" |
|
) |
|
|
|
// options for formatting. |
|
type options struct { |
|
writeTypeID bool |
|
} |
|
|
|
// Option of formatting. |
|
type Option func(o *options) |
|
|
|
// WithTypeID adds type id tp type name. |
|
func WithTypeID(o *options) { |
|
o.writeTypeID = true |
|
} |
|
|
|
const ( |
|
defaultIdent = " " |
|
noIdent = "" |
|
) |
|
|
|
func formatValue(b *strings.Builder, prefix, fieldName string, opt options, v reflect.Value) { |
|
switch v.Kind() { |
|
case reflect.Struct, reflect.Ptr, reflect.Interface: |
|
i, ok := v.Interface().(Object) |
|
if ok { |
|
format(b, prefix+defaultIdent, opt, i) |
|
} else if v.CanAddr() { |
|
formatValue(b, prefix, fieldName, opt, v.Addr()) |
|
} |
|
case reflect.Slice: |
|
if buf, ok := v.Interface().([]byte); ok { |
|
b.WriteString(base64.RawURLEncoding.EncodeToString(buf)) |
|
return |
|
} |
|
|
|
b.WriteRune('\n') |
|
for i := 0; i < v.Len(); i++ { |
|
vi := v.Index(i) |
|
b.WriteString(prefix) |
|
b.WriteString(defaultIdent) |
|
b.WriteString("- ") |
|
formatValue(b, prefix+defaultIdent, fieldName, opt, vi) |
|
b.WriteRune('\n') |
|
} |
|
case reflect.Int: |
|
// Special case for date. |
|
var ( |
|
now = time.Now() |
|
max = now.AddDate(0, 0, 7).Unix() |
|
min = now.AddDate(-2, 0, 0).Unix() |
|
) |
|
i := v.Int() |
|
if i > min && i < max && strings.Contains(fieldName, "date") { |
|
b.WriteString(time.Unix(i, 0).UTC().Format(time.RFC3339)) |
|
} else { |
|
b.WriteString(strconv.FormatInt(i, 10)) |
|
} |
|
default: |
|
b.WriteString(fmt.Sprint(v.Interface())) |
|
} |
|
} |
|
|
|
func format(b *strings.Builder, prefix string, opt options, obj Object) { |
|
if obj == nil { |
|
// No type information is available. it is like Format(nil). |
|
b.WriteString("<nil>") |
|
return |
|
} |
|
|
|
info := obj.TypeInfo() |
|
b.WriteString(info.Name) |
|
if opt.writeTypeID { |
|
b.WriteRune('#') |
|
b.WriteString(strconv.FormatInt(int64(info.ID), 16)) |
|
} |
|
if info.Null { |
|
b.WriteString("(nil)") |
|
return |
|
} |
|
|
|
v := reflect.ValueOf(obj).Elem() |
|
for i, f := range info.Fields { |
|
if i == 0 && f.SchemaName == "flags" { |
|
// Flag field, skipping. |
|
continue |
|
} |
|
if f.Null { |
|
// Optional field not set, skipping. |
|
continue |
|
} |
|
b.WriteRune('\n') |
|
b.WriteString(prefix) |
|
b.WriteString(defaultIdent) |
|
b.WriteString(f.SchemaName) |
|
b.WriteString(": ") |
|
|
|
formatValue(b, prefix, f.SchemaName, opt, v.FieldByName(f.Name)) |
|
} |
|
} |
|
|
|
// Format pretty-prints v into string. |
|
func Format(object Object, opts ...Option) string { |
|
var opt options |
|
for _, o := range opts { |
|
o(&opt) |
|
} |
|
|
|
var b strings.Builder |
|
format(&b, noIdent, opt, object) |
|
|
|
return b.String() |
|
}
|
|
|