mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-12-23 18:52:11 +00:00
414 lines
12 KiB
Go
414 lines
12 KiB
Go
// Copyright 2018 The Go 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 descfmt provides functionality to format descriptors.
|
||
package descfmt
|
||
|
||
import (
|
||
"fmt"
|
||
"io"
|
||
"reflect"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"google.golang.org/protobuf/internal/detrand"
|
||
"google.golang.org/protobuf/internal/pragma"
|
||
"google.golang.org/protobuf/reflect/protoreflect"
|
||
)
|
||
|
||
type list interface {
|
||
Len() int
|
||
pragma.DoNotImplement
|
||
}
|
||
|
||
func FormatList(s fmt.State, r rune, vs list) {
|
||
io.WriteString(s, formatListOpt(vs, true, r == 'v' && (s.Flag('+') || s.Flag('#'))))
|
||
}
|
||
func formatListOpt(vs list, isRoot, allowMulti bool) string {
|
||
start, end := "[", "]"
|
||
if isRoot {
|
||
var name string
|
||
switch vs.(type) {
|
||
case protoreflect.Names:
|
||
name = "Names"
|
||
case protoreflect.FieldNumbers:
|
||
name = "FieldNumbers"
|
||
case protoreflect.FieldRanges:
|
||
name = "FieldRanges"
|
||
case protoreflect.EnumRanges:
|
||
name = "EnumRanges"
|
||
case protoreflect.FileImports:
|
||
name = "FileImports"
|
||
case protoreflect.Descriptor:
|
||
name = reflect.ValueOf(vs).MethodByName("Get").Type().Out(0).Name() + "s"
|
||
default:
|
||
name = reflect.ValueOf(vs).Elem().Type().Name()
|
||
}
|
||
start, end = name+"{", "}"
|
||
}
|
||
|
||
var ss []string
|
||
switch vs := vs.(type) {
|
||
case protoreflect.Names:
|
||
for i := 0; i < vs.Len(); i++ {
|
||
ss = append(ss, fmt.Sprint(vs.Get(i)))
|
||
}
|
||
return start + joinStrings(ss, false) + end
|
||
case protoreflect.FieldNumbers:
|
||
for i := 0; i < vs.Len(); i++ {
|
||
ss = append(ss, fmt.Sprint(vs.Get(i)))
|
||
}
|
||
return start + joinStrings(ss, false) + end
|
||
case protoreflect.FieldRanges:
|
||
for i := 0; i < vs.Len(); i++ {
|
||
r := vs.Get(i)
|
||
if r[0]+1 == r[1] {
|
||
ss = append(ss, fmt.Sprintf("%d", r[0]))
|
||
} else {
|
||
ss = append(ss, fmt.Sprintf("%d:%d", r[0], r[1])) // enum ranges are end exclusive
|
||
}
|
||
}
|
||
return start + joinStrings(ss, false) + end
|
||
case protoreflect.EnumRanges:
|
||
for i := 0; i < vs.Len(); i++ {
|
||
r := vs.Get(i)
|
||
if r[0] == r[1] {
|
||
ss = append(ss, fmt.Sprintf("%d", r[0]))
|
||
} else {
|
||
ss = append(ss, fmt.Sprintf("%d:%d", r[0], int64(r[1])+1)) // enum ranges are end inclusive
|
||
}
|
||
}
|
||
return start + joinStrings(ss, false) + end
|
||
case protoreflect.FileImports:
|
||
for i := 0; i < vs.Len(); i++ {
|
||
var rs records
|
||
rv := reflect.ValueOf(vs.Get(i))
|
||
rs.Append(rv, []methodAndName{
|
||
{rv.MethodByName("Path"), "Path"},
|
||
{rv.MethodByName("Package"), "Package"},
|
||
{rv.MethodByName("IsPublic"), "IsPublic"},
|
||
{rv.MethodByName("IsWeak"), "IsWeak"},
|
||
}...)
|
||
ss = append(ss, "{"+rs.Join()+"}")
|
||
}
|
||
return start + joinStrings(ss, allowMulti) + end
|
||
default:
|
||
_, isEnumValue := vs.(protoreflect.EnumValueDescriptors)
|
||
for i := 0; i < vs.Len(); i++ {
|
||
m := reflect.ValueOf(vs).MethodByName("Get")
|
||
v := m.Call([]reflect.Value{reflect.ValueOf(i)})[0].Interface()
|
||
ss = append(ss, formatDescOpt(v.(protoreflect.Descriptor), false, allowMulti && !isEnumValue, nil))
|
||
}
|
||
return start + joinStrings(ss, allowMulti && isEnumValue) + end
|
||
}
|
||
}
|
||
|
||
type methodAndName struct {
|
||
method reflect.Value
|
||
name string
|
||
}
|
||
|
||
func FormatDesc(s fmt.State, r rune, t protoreflect.Descriptor) {
|
||
io.WriteString(s, formatDescOpt(t, true, r == 'v' && (s.Flag('+') || s.Flag('#')), nil))
|
||
}
|
||
|
||
func InternalFormatDescOptForTesting(t protoreflect.Descriptor, isRoot, allowMulti bool, record func(string)) string {
|
||
return formatDescOpt(t, isRoot, allowMulti, record)
|
||
}
|
||
|
||
func formatDescOpt(t protoreflect.Descriptor, isRoot, allowMulti bool, record func(string)) string {
|
||
rv := reflect.ValueOf(t)
|
||
rt := rv.MethodByName("ProtoType").Type().In(0)
|
||
|
||
start, end := "{", "}"
|
||
if isRoot {
|
||
start = rt.Name() + "{"
|
||
}
|
||
|
||
_, isFile := t.(protoreflect.FileDescriptor)
|
||
rs := records{
|
||
allowMulti: allowMulti,
|
||
record: record,
|
||
}
|
||
if t.IsPlaceholder() {
|
||
if isFile {
|
||
rs.Append(rv, []methodAndName{
|
||
{rv.MethodByName("Path"), "Path"},
|
||
{rv.MethodByName("Package"), "Package"},
|
||
{rv.MethodByName("IsPlaceholder"), "IsPlaceholder"},
|
||
}...)
|
||
} else {
|
||
rs.Append(rv, []methodAndName{
|
||
{rv.MethodByName("FullName"), "FullName"},
|
||
{rv.MethodByName("IsPlaceholder"), "IsPlaceholder"},
|
||
}...)
|
||
}
|
||
} else {
|
||
switch {
|
||
case isFile:
|
||
rs.Append(rv, methodAndName{rv.MethodByName("Syntax"), "Syntax"})
|
||
case isRoot:
|
||
rs.Append(rv, []methodAndName{
|
||
{rv.MethodByName("Syntax"), "Syntax"},
|
||
{rv.MethodByName("FullName"), "FullName"},
|
||
}...)
|
||
default:
|
||
rs.Append(rv, methodAndName{rv.MethodByName("Name"), "Name"})
|
||
}
|
||
switch t := t.(type) {
|
||
case protoreflect.FieldDescriptor:
|
||
accessors := []methodAndName{
|
||
{rv.MethodByName("Number"), "Number"},
|
||
{rv.MethodByName("Cardinality"), "Cardinality"},
|
||
{rv.MethodByName("Kind"), "Kind"},
|
||
{rv.MethodByName("HasJSONName"), "HasJSONName"},
|
||
{rv.MethodByName("JSONName"), "JSONName"},
|
||
{rv.MethodByName("HasPresence"), "HasPresence"},
|
||
{rv.MethodByName("IsExtension"), "IsExtension"},
|
||
{rv.MethodByName("IsPacked"), "IsPacked"},
|
||
{rv.MethodByName("IsWeak"), "IsWeak"},
|
||
{rv.MethodByName("IsList"), "IsList"},
|
||
{rv.MethodByName("IsMap"), "IsMap"},
|
||
{rv.MethodByName("MapKey"), "MapKey"},
|
||
{rv.MethodByName("MapValue"), "MapValue"},
|
||
{rv.MethodByName("HasDefault"), "HasDefault"},
|
||
{rv.MethodByName("Default"), "Default"},
|
||
{rv.MethodByName("ContainingOneof"), "ContainingOneof"},
|
||
{rv.MethodByName("ContainingMessage"), "ContainingMessage"},
|
||
{rv.MethodByName("Message"), "Message"},
|
||
{rv.MethodByName("Enum"), "Enum"},
|
||
}
|
||
for _, s := range accessors {
|
||
switch s.name {
|
||
case "MapKey":
|
||
if k := t.MapKey(); k != nil {
|
||
rs.recs = append(rs.recs, [2]string{"MapKey", k.Kind().String()})
|
||
}
|
||
case "MapValue":
|
||
if v := t.MapValue(); v != nil {
|
||
switch v.Kind() {
|
||
case protoreflect.EnumKind:
|
||
rs.AppendRecs("MapValue", [2]string{"MapValue", string(v.Enum().FullName())})
|
||
case protoreflect.MessageKind, protoreflect.GroupKind:
|
||
rs.AppendRecs("MapValue", [2]string{"MapValue", string(v.Message().FullName())})
|
||
default:
|
||
rs.AppendRecs("MapValue", [2]string{"MapValue", v.Kind().String()})
|
||
}
|
||
}
|
||
case "ContainingOneof":
|
||
if od := t.ContainingOneof(); od != nil {
|
||
rs.AppendRecs("ContainingOneof", [2]string{"Oneof", string(od.Name())})
|
||
}
|
||
case "ContainingMessage":
|
||
if t.IsExtension() {
|
||
rs.AppendRecs("ContainingMessage", [2]string{"Extendee", string(t.ContainingMessage().FullName())})
|
||
}
|
||
case "Message":
|
||
if !t.IsMap() {
|
||
rs.Append(rv, s)
|
||
}
|
||
default:
|
||
rs.Append(rv, s)
|
||
}
|
||
}
|
||
case protoreflect.OneofDescriptor:
|
||
var ss []string
|
||
fs := t.Fields()
|
||
for i := 0; i < fs.Len(); i++ {
|
||
ss = append(ss, string(fs.Get(i).Name()))
|
||
}
|
||
if len(ss) > 0 {
|
||
rs.AppendRecs("Fields", [2]string{"Fields", "[" + joinStrings(ss, false) + "]"})
|
||
}
|
||
|
||
case protoreflect.FileDescriptor:
|
||
rs.Append(rv, []methodAndName{
|
||
{rv.MethodByName("Path"), "Path"},
|
||
{rv.MethodByName("Package"), "Package"},
|
||
{rv.MethodByName("Imports"), "Imports"},
|
||
{rv.MethodByName("Messages"), "Messages"},
|
||
{rv.MethodByName("Enums"), "Enums"},
|
||
{rv.MethodByName("Extensions"), "Extensions"},
|
||
{rv.MethodByName("Services"), "Services"},
|
||
}...)
|
||
|
||
case protoreflect.MessageDescriptor:
|
||
rs.Append(rv, []methodAndName{
|
||
{rv.MethodByName("IsMapEntry"), "IsMapEntry"},
|
||
{rv.MethodByName("Fields"), "Fields"},
|
||
{rv.MethodByName("Oneofs"), "Oneofs"},
|
||
{rv.MethodByName("ReservedNames"), "ReservedNames"},
|
||
{rv.MethodByName("ReservedRanges"), "ReservedRanges"},
|
||
{rv.MethodByName("RequiredNumbers"), "RequiredNumbers"},
|
||
{rv.MethodByName("ExtensionRanges"), "ExtensionRanges"},
|
||
{rv.MethodByName("Messages"), "Messages"},
|
||
{rv.MethodByName("Enums"), "Enums"},
|
||
{rv.MethodByName("Extensions"), "Extensions"},
|
||
}...)
|
||
|
||
case protoreflect.EnumDescriptor:
|
||
rs.Append(rv, []methodAndName{
|
||
{rv.MethodByName("Values"), "Values"},
|
||
{rv.MethodByName("ReservedNames"), "ReservedNames"},
|
||
{rv.MethodByName("ReservedRanges"), "ReservedRanges"},
|
||
}...)
|
||
|
||
case protoreflect.EnumValueDescriptor:
|
||
rs.Append(rv, []methodAndName{
|
||
{rv.MethodByName("Number"), "Number"},
|
||
}...)
|
||
|
||
case protoreflect.ServiceDescriptor:
|
||
rs.Append(rv, []methodAndName{
|
||
{rv.MethodByName("Methods"), "Methods"},
|
||
}...)
|
||
|
||
case protoreflect.MethodDescriptor:
|
||
rs.Append(rv, []methodAndName{
|
||
{rv.MethodByName("Input"), "Input"},
|
||
{rv.MethodByName("Output"), "Output"},
|
||
{rv.MethodByName("IsStreamingClient"), "IsStreamingClient"},
|
||
{rv.MethodByName("IsStreamingServer"), "IsStreamingServer"},
|
||
}...)
|
||
}
|
||
if m := rv.MethodByName("GoType"); m.IsValid() {
|
||
rs.Append(rv, methodAndName{m, "GoType"})
|
||
}
|
||
}
|
||
return start + rs.Join() + end
|
||
}
|
||
|
||
type records struct {
|
||
recs [][2]string
|
||
allowMulti bool
|
||
|
||
// record is a function that will be called for every Append() or
|
||
// AppendRecs() call, to be used for testing with the
|
||
// InternalFormatDescOptForTesting function.
|
||
record func(string)
|
||
}
|
||
|
||
func (rs *records) AppendRecs(fieldName string, newRecs [2]string) {
|
||
if rs.record != nil {
|
||
rs.record(fieldName)
|
||
}
|
||
rs.recs = append(rs.recs, newRecs)
|
||
}
|
||
|
||
func (rs *records) Append(v reflect.Value, accessors ...methodAndName) {
|
||
for _, a := range accessors {
|
||
if rs.record != nil {
|
||
rs.record(a.name)
|
||
}
|
||
var rv reflect.Value
|
||
if a.method.IsValid() {
|
||
rv = a.method.Call(nil)[0]
|
||
}
|
||
if v.Kind() == reflect.Struct && !rv.IsValid() {
|
||
rv = v.FieldByName(a.name)
|
||
}
|
||
if !rv.IsValid() {
|
||
panic(fmt.Sprintf("unknown accessor: %v.%s", v.Type(), a.name))
|
||
}
|
||
if _, ok := rv.Interface().(protoreflect.Value); ok {
|
||
rv = rv.MethodByName("Interface").Call(nil)[0]
|
||
if !rv.IsNil() {
|
||
rv = rv.Elem()
|
||
}
|
||
}
|
||
|
||
// Ignore zero values.
|
||
var isZero bool
|
||
switch rv.Kind() {
|
||
case reflect.Interface, reflect.Slice:
|
||
isZero = rv.IsNil()
|
||
case reflect.Bool:
|
||
isZero = rv.Bool() == false
|
||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||
isZero = rv.Int() == 0
|
||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||
isZero = rv.Uint() == 0
|
||
case reflect.String:
|
||
isZero = rv.String() == ""
|
||
}
|
||
if n, ok := rv.Interface().(list); ok {
|
||
isZero = n.Len() == 0
|
||
}
|
||
if isZero {
|
||
continue
|
||
}
|
||
|
||
// Format the value.
|
||
var s string
|
||
v := rv.Interface()
|
||
switch v := v.(type) {
|
||
case list:
|
||
s = formatListOpt(v, false, rs.allowMulti)
|
||
case protoreflect.FieldDescriptor, protoreflect.OneofDescriptor, protoreflect.EnumValueDescriptor, protoreflect.MethodDescriptor:
|
||
s = string(v.(protoreflect.Descriptor).Name())
|
||
case protoreflect.Descriptor:
|
||
s = string(v.FullName())
|
||
case string:
|
||
s = strconv.Quote(v)
|
||
case []byte:
|
||
s = fmt.Sprintf("%q", v)
|
||
default:
|
||
s = fmt.Sprint(v)
|
||
}
|
||
rs.recs = append(rs.recs, [2]string{a.name, s})
|
||
}
|
||
}
|
||
|
||
func (rs *records) Join() string {
|
||
var ss []string
|
||
|
||
// In single line mode, simply join all records with commas.
|
||
if !rs.allowMulti {
|
||
for _, r := range rs.recs {
|
||
ss = append(ss, r[0]+formatColon(0)+r[1])
|
||
}
|
||
return joinStrings(ss, false)
|
||
}
|
||
|
||
// In allowMulti line mode, align single line records for more readable output.
|
||
var maxLen int
|
||
flush := func(i int) {
|
||
for _, r := range rs.recs[len(ss):i] {
|
||
ss = append(ss, r[0]+formatColon(maxLen-len(r[0]))+r[1])
|
||
}
|
||
maxLen = 0
|
||
}
|
||
for i, r := range rs.recs {
|
||
if isMulti := strings.Contains(r[1], "\n"); isMulti {
|
||
flush(i)
|
||
ss = append(ss, r[0]+formatColon(0)+strings.Join(strings.Split(r[1], "\n"), "\n\t"))
|
||
} else if maxLen < len(r[0]) {
|
||
maxLen = len(r[0])
|
||
}
|
||
}
|
||
flush(len(rs.recs))
|
||
return joinStrings(ss, true)
|
||
}
|
||
|
||
func formatColon(padding int) string {
|
||
// Deliberately introduce instability into the debug output to
|
||
// discourage users from performing string comparisons.
|
||
// This provides us flexibility to change the output in the future.
|
||
if detrand.Bool() {
|
||
return ":" + strings.Repeat(" ", 1+padding) // use non-breaking spaces (U+00a0)
|
||
} else {
|
||
return ":" + strings.Repeat(" ", 1+padding) // use regular spaces (U+0020)
|
||
}
|
||
}
|
||
|
||
func joinStrings(ss []string, isMulti bool) string {
|
||
if len(ss) == 0 {
|
||
return ""
|
||
}
|
||
if isMulti {
|
||
return "\n\t" + strings.Join(ss, "\n\t") + "\n"
|
||
}
|
||
return strings.Join(ss, ", ")
|
||
}
|