Skip to content

Commit

Permalink
feat(tdp,gen): implement basic pretty print
Browse files Browse the repository at this point in the history
  • Loading branch information
ernado committed Mar 2, 2021
1 parent 7990ebf commit 091eeb2
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 12 deletions.
2 changes: 2 additions & 0 deletions internal/gen/_template/header.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strings"

"github.com/gotd/td/bin"
"github.com/gotd/td/tdp"
)

// No-op definition for keeping imports.
Expand All @@ -20,5 +21,6 @@ var _ = fmt.Stringer(nil)
var _ = strings.Builder{}
var _ = errors.Is
var _ = sort.Ints
var _ = tdp.Format

{{ end }}
28 changes: 26 additions & 2 deletions internal/gen/_template/main.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,39 @@ const {{ $s.Name }}TypeID = {{ if not $s.Vector }}0x{{ $s.HexID }}{{- else -}}bi
// TypeID returns type id in TL schema.
//
// See https://core.telegram.org/mtproto/TL-tl#remarks.
func ({{ $s.Receiver }} *{{ $s.Name }}) TypeID() uint32 {
func (*{{ $s.Name }}) TypeID() uint32 {
return {{ $s.Name }}TypeID
}

// TypeName returns name of type in TL schema.
func ({{ $s.Receiver }} *{{ $s.Name }}) TypeName() string {
func (*{{ $s.Name }}) TypeName() string {
return "{{ $s.RawName }}"
}

// TypeInfo returns info about TL type.
func ({{ $s.Receiver }} *{{ $s.Name }}) TypeInfo() tdp.Type {
typ := tdp.Type{
Name: "{{ $s.RawName }}",
ID: {{ $s.Name }}TypeID,
}
if {{ $s.Receiver }} == nil {
typ.Null = true
return typ
}
typ.Fields = []tdp.Field{
{{- range $f := $s.Fields }}
{
Name: "{{ $f.Name }}",
SchemaName: "{{ $f.RawName }}",
{{- if $f.Conditional }}
Null: !{{ $s.Receiver }}.{{ $f.ConditionalField }}.Has({{ $f.ConditionalIndex }}),
{{- end }}
},
{{- end }}
}
return typ
}

// Encode implements bin.Encoder.
func ({{ $s.Receiver }} *{{ $s.Name }}) Encode({{ $s.BufArg }} *bin.Buffer) error {
if {{ $s.Receiver }} == nil {
Expand Down
2 changes: 2 additions & 0 deletions tdp/internal/_testdata/schema.tl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption;
config#330b4067 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int = Config;
3 changes: 3 additions & 0 deletions tdp/internal/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package internal

//go:generate go run github.com/gotd/td/cmd/gotdgen --format=true --clean --package schema --target schema --schema _testdata/schema.tl
82 changes: 77 additions & 5 deletions tdp/tdp.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,99 @@ package tdp

import (
"fmt"
"reflect"
"strconv"
"strings"
)

// options for formatting.
type options struct {
writeTypeID bool
}

// Option of formatting.
type Option interface {
apply(o *options)
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 string, opt options, v reflect.Value) {
switch v.Kind() {
case reflect.Struct, reflect.Ptr:
i, ok := v.Interface().(Object)
if ok {
format(b, prefix+defaultIdent, opt, i)
} else if v.CanAddr() {
formatValue(b, prefix, opt, v.Addr())
}
case reflect.Slice:
b.WriteRune('\n')
b.WriteString(prefix)
for i := 0; i < v.Len(); i++ {
vi := v.Index(i)
b.WriteString(defaultIdent)
b.WriteString("- ")
formatValue(b, prefix+defaultIdent, opt, vi)
}
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, opt, v.FieldByName(f.Name))
}
}

// Format pretty-prints v into string.
func Format(v interface{}, opts ...Option) string {
func Format(object Object, opts ...Option) string {
var opt options
for _, o := range opts {
o.apply(&opt)
o(&opt)
}

var b strings.Builder
b.WriteString(fmt.Sprintf("%T", v))
format(&b, noIdent, opt, object)

return b.String()
}
39 changes: 34 additions & 5 deletions tdp/tdp_test.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,49 @@
package tdp
package tdp_test

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/gotd/td/tdp"
"github.com/gotd/td/tdp/internal/schema"
)

func TestFormat(t *testing.T) {
for _, tt := range []struct {
Input interface{}
Output string
Input tdp.Object
Output string
Options []tdp.Option
}{
{Output: "<nil>"},
{
Output: "<nil>",
},
{
Input: &schema.DcOption{
ID: 10,
IPAddress: "127.0.0.1",
Port: 1010,
},
Options: []tdp.Option{tdp.WithTypeID},
Output: "dcOption#18b7a10d",
},

{
Input: &schema.Config{
DCOptions: []schema.DcOption{
{
ID: 1,
IPAddress: "127.0.0.1",
Port: 1010,
},
},
},
Options: []tdp.Option{tdp.WithTypeID},
},
} {
t.Skip("TODO: Use golden files")
t.Run(tt.Output, func(t *testing.T) {
require.Equal(t, tt.Output, Format(tt.Input))
require.Equal(t, tt.Output, tdp.Format(tt.Input, tt.Options...))
})
}
}
25 changes: 25 additions & 0 deletions tdp/type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package tdp

// Field of TL type, non-recursive.
type Field struct {
Name string
SchemaName string
Null bool
}

// Type info for TL type, non-recursive.
type Type struct {
// Name in TL schema.
Name string
// ID is type id.
ID int
// Fields of type.
Fields []Field
// Null denotes whether value is null.
Null bool
}

// Object of TL schema that can return type info.
type Object interface {
TypeInfo() Type
}

0 comments on commit 091eeb2

Please sign in to comment.