package proto
import (
"bytes"
"encoding"
"fmt"
"io"
"math"
"sort"
"strings"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/encoding/protowire"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
)
const wrapTextMarshalV2 = false
type TextMarshaler struct {
Compact bool
ExpandAny bool
}
func (tm *TextMarshaler ) Marshal (w io .Writer , m Message ) error {
b , err := tm .marshal (m )
if len (b ) > 0 {
if _ , err := w .Write (b ); err != nil {
return err
}
}
return err
}
func (tm *TextMarshaler ) Text (m Message ) string {
b , _ := tm .marshal (m )
return string (b )
}
func (tm *TextMarshaler ) marshal (m Message ) ([]byte , error ) {
mr := MessageReflect (m )
if mr == nil || !mr .IsValid () {
return []byte ("<nil>" ), nil
}
if wrapTextMarshalV2 {
if m , ok := m .(encoding .TextMarshaler ); ok {
return m .MarshalText ()
}
opts := prototext .MarshalOptions {
AllowPartial : true ,
EmitUnknown : true ,
}
if !tm .Compact {
opts .Indent = " "
}
if !tm .ExpandAny {
opts .Resolver = (*protoregistry .Types )(nil )
}
return opts .Marshal (mr .Interface ())
} else {
w := &textWriter {
compact : tm .Compact ,
expandAny : tm .ExpandAny ,
complete : true ,
}
if m , ok := m .(encoding .TextMarshaler ); ok {
b , err := m .MarshalText ()
if err != nil {
return nil , err
}
w .Write (b )
return w .buf , nil
}
err := w .writeMessage (mr )
return w .buf , err
}
}
var (
defaultTextMarshaler = TextMarshaler {}
compactTextMarshaler = TextMarshaler {Compact : true }
)
func MarshalText (w io .Writer , m Message ) error { return defaultTextMarshaler .Marshal (w , m ) }
func MarshalTextString (m Message ) string { return defaultTextMarshaler .Text (m ) }
func CompactText (w io .Writer , m Message ) error { return compactTextMarshaler .Marshal (w , m ) }
func CompactTextString (m Message ) string { return compactTextMarshaler .Text (m ) }
var (
newline = []byte ("\n" )
endBraceNewline = []byte ("}\n" )
posInf = []byte ("inf" )
negInf = []byte ("-inf" )
nan = []byte ("nan" )
)
type textWriter struct {
compact bool
expandAny bool
complete bool
indent int
buf []byte
}
func (w *textWriter ) Write (p []byte ) (n int , _ error ) {
newlines := bytes .Count (p , newline )
if newlines == 0 {
if !w .compact && w .complete {
w .writeIndent ()
}
w .buf = append (w .buf , p ...)
w .complete = false
return len (p ), nil
}
frags := bytes .SplitN (p , newline , newlines +1 )
if w .compact {
for i , frag := range frags {
if i > 0 {
w .buf = append (w .buf , ' ' )
n ++
}
w .buf = append (w .buf , frag ...)
n += len (frag )
}
return n , nil
}
for i , frag := range frags {
if w .complete {
w .writeIndent ()
}
w .buf = append (w .buf , frag ...)
n += len (frag )
if i +1 < len (frags ) {
w .buf = append (w .buf , '\n' )
n ++
}
}
w .complete = len (frags [len (frags )-1 ]) == 0
return n , nil
}
func (w *textWriter ) WriteByte (c byte ) error {
if w .compact && c == '\n' {
c = ' '
}
if !w .compact && w .complete {
w .writeIndent ()
}
w .buf = append (w .buf , c )
w .complete = c == '\n'
return nil
}
func (w *textWriter ) writeName (fd protoreflect .FieldDescriptor ) {
if !w .compact && w .complete {
w .writeIndent ()
}
w .complete = false
if fd .Kind () != protoreflect .GroupKind {
w .buf = append (w .buf , fd .Name ()...)
w .WriteByte (':' )
} else {
w .buf = append (w .buf , fd .Message ().Name ()...)
}
if !w .compact {
w .WriteByte (' ' )
}
}
func requiresQuotes(u string ) bool {
for _ , ch := range u {
switch {
case ch == '.' || ch == '/' || ch == '_' :
continue
case '0' <= ch && ch <= '9' :
continue
case 'A' <= ch && ch <= 'Z' :
continue
case 'a' <= ch && ch <= 'z' :
continue
default :
return true
}
}
return false
}
func (w *textWriter ) writeProto3Any (m protoreflect .Message ) (bool , error ) {
md := m .Descriptor ()
fdURL := md .Fields ().ByName ("type_url" )
fdVal := md .Fields ().ByName ("value" )
url := m .Get (fdURL ).String ()
mt , err := protoregistry .GlobalTypes .FindMessageByURL (url )
if err != nil {
return false , nil
}
b := m .Get (fdVal ).Bytes ()
m2 := mt .New ()
if err := proto .Unmarshal (b , m2 .Interface ()); err != nil {
return false , nil
}
w .Write ([]byte ("[" ))
if requiresQuotes (url ) {
w .writeQuotedString (url )
} else {
w .Write ([]byte (url ))
}
if w .compact {
w .Write ([]byte ("]:<" ))
} else {
w .Write ([]byte ("]: <\n" ))
w .indent ++
}
if err := w .writeMessage (m2 ); err != nil {
return true , err
}
if w .compact {
w .Write ([]byte ("> " ))
} else {
w .indent --
w .Write ([]byte (">\n" ))
}
return true , nil
}
func (w *textWriter ) writeMessage (m protoreflect .Message ) error {
md := m .Descriptor ()
if w .expandAny && md .FullName () == "google.protobuf.Any" {
if canExpand , err := w .writeProto3Any (m ); canExpand {
return err
}
}
fds := md .Fields ()
for i := 0 ; i < fds .Len (); {
fd := fds .Get (i )
if od := fd .ContainingOneof (); od != nil {
fd = m .WhichOneof (od )
i += od .Fields ().Len ()
} else {
i ++
}
if fd == nil || !m .Has (fd ) {
continue
}
switch {
case fd .IsList ():
lv := m .Get (fd ).List ()
for j := 0 ; j < lv .Len (); j ++ {
w .writeName (fd )
v := lv .Get (j )
if err := w .writeSingularValue (v , fd ); err != nil {
return err
}
w .WriteByte ('\n' )
}
case fd .IsMap ():
kfd := fd .MapKey ()
vfd := fd .MapValue ()
mv := m .Get (fd ).Map ()
type entry struct { key , val protoreflect .Value }
var entries []entry
mv .Range (func (k protoreflect .MapKey , v protoreflect .Value ) bool {
entries = append (entries , entry {k .Value (), v })
return true
})
sort .Slice (entries , func (i , j int ) bool {
switch kfd .Kind () {
case protoreflect .BoolKind :
return !entries [i ].key .Bool () && entries [j ].key .Bool ()
case protoreflect .Int32Kind , protoreflect .Sint32Kind , protoreflect .Sfixed32Kind , protoreflect .Int64Kind , protoreflect .Sint64Kind , protoreflect .Sfixed64Kind :
return entries [i ].key .Int () < entries [j ].key .Int ()
case protoreflect .Uint32Kind , protoreflect .Fixed32Kind , protoreflect .Uint64Kind , protoreflect .Fixed64Kind :
return entries [i ].key .Uint () < entries [j ].key .Uint ()
case protoreflect .StringKind :
return entries [i ].key .String () < entries [j ].key .String ()
default :
panic ("invalid kind" )
}
})
for _ , entry := range entries {
w .writeName (fd )
w .WriteByte ('<' )
if !w .compact {
w .WriteByte ('\n' )
}
w .indent ++
w .writeName (kfd )
if err := w .writeSingularValue (entry .key , kfd ); err != nil {
return err
}
w .WriteByte ('\n' )
w .writeName (vfd )
if err := w .writeSingularValue (entry .val , vfd ); err != nil {
return err
}
w .WriteByte ('\n' )
w .indent --
w .WriteByte ('>' )
w .WriteByte ('\n' )
}
default :
w .writeName (fd )
if err := w .writeSingularValue (m .Get (fd ), fd ); err != nil {
return err
}
w .WriteByte ('\n' )
}
}
if b := m .GetUnknown (); len (b ) > 0 {
w .writeUnknownFields (b )
}
return w .writeExtensions (m )
}
func (w *textWriter ) writeSingularValue (v protoreflect .Value , fd protoreflect .FieldDescriptor ) error {
switch fd .Kind () {
case protoreflect .FloatKind , protoreflect .DoubleKind :
switch vf := v .Float (); {
case math .IsInf (vf , +1 ):
w .Write (posInf )
case math .IsInf (vf , -1 ):
w .Write (negInf )
case math .IsNaN (vf ):
w .Write (nan )
default :
fmt .Fprint (w , v .Interface ())
}
case protoreflect .StringKind :
w .writeQuotedString (string (v .String ()))
case protoreflect .BytesKind :
w .writeQuotedString (string (v .Bytes ()))
case protoreflect .MessageKind , protoreflect .GroupKind :
var bra , ket byte = '<' , '>'
if fd .Kind () == protoreflect .GroupKind {
bra , ket = '{' , '}'
}
w .WriteByte (bra )
if !w .compact {
w .WriteByte ('\n' )
}
w .indent ++
m := v .Message ()
if m2 , ok := m .Interface ().(encoding .TextMarshaler ); ok {
b , err := m2 .MarshalText ()
if err != nil {
return err
}
w .Write (b )
} else {
w .writeMessage (m )
}
w .indent --
w .WriteByte (ket )
case protoreflect .EnumKind :
if ev := fd .Enum ().Values ().ByNumber (v .Enum ()); ev != nil {
fmt .Fprint (w , ev .Name ())
} else {
fmt .Fprint (w , v .Enum ())
}
default :
fmt .Fprint (w , v .Interface ())
}
return nil
}
func (w *textWriter ) writeQuotedString (s string ) {
w .WriteByte ('"' )
for i := 0 ; i < len (s ); i ++ {
switch c := s [i ]; c {
case '\n' :
w .buf = append (w .buf , `\n` ...)
case '\r' :
w .buf = append (w .buf , `\r` ...)
case '\t' :
w .buf = append (w .buf , `\t` ...)
case '"' :
w .buf = append (w .buf , `\"` ...)
case '\\' :
w .buf = append (w .buf , `\\` ...)
default :
if isPrint := c >= 0x20 && c < 0x7f ; isPrint {
w .buf = append (w .buf , c )
} else {
w .buf = append (w .buf , fmt .Sprintf (`\%03o` , c )...)
}
}
}
w .WriteByte ('"' )
}
func (w *textWriter ) writeUnknownFields (b []byte ) {
if !w .compact {
fmt .Fprintf (w , "/* %d unknown bytes */\n" , len (b ))
}
for len (b ) > 0 {
num , wtyp , n := protowire .ConsumeTag (b )
if n < 0 {
return
}
b = b [n :]
if wtyp == protowire .EndGroupType {
w .indent --
w .Write (endBraceNewline )
continue
}
fmt .Fprint (w , num )
if wtyp != protowire .StartGroupType {
w .WriteByte (':' )
}
if !w .compact || wtyp == protowire .StartGroupType {
w .WriteByte (' ' )
}
switch wtyp {
case protowire .VarintType :
v , n := protowire .ConsumeVarint (b )
if n < 0 {
return
}
b = b [n :]
fmt .Fprint (w , v )
case protowire .Fixed32Type :
v , n := protowire .ConsumeFixed32 (b )
if n < 0 {
return
}
b = b [n :]
fmt .Fprint (w , v )
case protowire .Fixed64Type :
v , n := protowire .ConsumeFixed64 (b )
if n < 0 {
return
}
b = b [n :]
fmt .Fprint (w , v )
case protowire .BytesType :
v , n := protowire .ConsumeBytes (b )
if n < 0 {
return
}
b = b [n :]
fmt .Fprintf (w , "%q" , v )
case protowire .StartGroupType :
w .WriteByte ('{' )
w .indent ++
default :
fmt .Fprintf (w , "/* unknown wire type %d */" , wtyp )
}
w .WriteByte ('\n' )
}
}
func (w *textWriter ) writeExtensions (m protoreflect .Message ) error {
md := m .Descriptor ()
if md .ExtensionRanges ().Len () == 0 {
return nil
}
type ext struct {
desc protoreflect .FieldDescriptor
val protoreflect .Value
}
var exts []ext
m .Range (func (fd protoreflect .FieldDescriptor , v protoreflect .Value ) bool {
if fd .IsExtension () {
exts = append (exts , ext {fd , v })
}
return true
})
sort .Slice (exts , func (i , j int ) bool {
return exts [i ].desc .Number () < exts [j ].desc .Number ()
})
for _ , ext := range exts {
name := string (ext .desc .FullName ())
if isMessageSet (ext .desc .ContainingMessage ()) {
name = strings .TrimSuffix (name , ".message_set_extension" )
}
if !ext .desc .IsList () {
if err := w .writeSingularExtension (name , ext .val , ext .desc ); err != nil {
return err
}
} else {
lv := ext .val .List ()
for i := 0 ; i < lv .Len (); i ++ {
if err := w .writeSingularExtension (name , lv .Get (i ), ext .desc ); err != nil {
return err
}
}
}
}
return nil
}
func (w *textWriter ) writeSingularExtension (name string , v protoreflect .Value , fd protoreflect .FieldDescriptor ) error {
fmt .Fprintf (w , "[%s]:" , name )
if !w .compact {
w .WriteByte (' ' )
}
if err := w .writeSingularValue (v , fd ); err != nil {
return err
}
w .WriteByte ('\n' )
return nil
}
func (w *textWriter ) writeIndent () {
if !w .complete {
return
}
for i := 0 ; i < w .indent *2 ; i ++ {
w .buf = append (w .buf , ' ' )
}
w .complete = false
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .