mirror of https://github.com/tailscale/tailscale/
cmd/viewer: add codegen tool for Views
Signed-off-by: Maisem Ali <maisem@tailscale.com>bradfitz/dot
parent
e409e59a54
commit
c4e9739251
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) 2022 Tailscale Inc & 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 tests serves a list of tests for tailscale.com/cmd/viewer.
|
||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"inet.af/netaddr"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate go run tailscale.com/cmd/viewer --type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices
|
||||||
|
|
||||||
|
type StructWithoutPtrs struct {
|
||||||
|
Int int
|
||||||
|
Pfx netaddr.IPPrefix
|
||||||
|
}
|
||||||
|
|
||||||
|
type Map struct {
|
||||||
|
M map[string]int
|
||||||
|
}
|
||||||
|
|
||||||
|
type StructWithPtrs struct {
|
||||||
|
Value *StructWithoutPtrs
|
||||||
|
Int *int
|
||||||
|
|
||||||
|
NoCloneValue *StructWithoutPtrs `codegen:"noclone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *StructWithPtrs) String() string { return fmt.Sprintf("%v", v.Int) }
|
||||||
|
|
||||||
|
func (v *StructWithPtrs) Equal(v2 *StructWithPtrs) bool {
|
||||||
|
return v.Value == v2.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
type StructWithSlices struct {
|
||||||
|
Values []StructWithoutPtrs
|
||||||
|
ValuePointers []*StructWithoutPtrs
|
||||||
|
StructPointers []*StructWithPtrs
|
||||||
|
Structs []StructWithPtrs
|
||||||
|
Ints []*int
|
||||||
|
|
||||||
|
Slice []string
|
||||||
|
Prefixes []netaddr.IPPrefix
|
||||||
|
Data []byte
|
||||||
|
}
|
@ -0,0 +1,120 @@
|
|||||||
|
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Code generated by tailscale.com/cmd/cloner; DO NOT EDIT.
|
||||||
|
|
||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"inet.af/netaddr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Clone makes a deep copy of StructWithPtrs.
|
||||||
|
// The result aliases no memory with the original.
|
||||||
|
func (src *StructWithPtrs) Clone() *StructWithPtrs {
|
||||||
|
if src == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
dst := new(StructWithPtrs)
|
||||||
|
*dst = *src
|
||||||
|
if dst.Value != nil {
|
||||||
|
dst.Value = new(StructWithoutPtrs)
|
||||||
|
*dst.Value = *src.Value
|
||||||
|
}
|
||||||
|
if dst.Int != nil {
|
||||||
|
dst.Int = new(int)
|
||||||
|
*dst.Int = *src.Int
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||||
|
var _StructWithPtrsCloneNeedsRegeneration = StructWithPtrs(struct {
|
||||||
|
Value *StructWithoutPtrs
|
||||||
|
Int *int
|
||||||
|
NoCloneValue *StructWithoutPtrs
|
||||||
|
}{})
|
||||||
|
|
||||||
|
// Clone makes a deep copy of StructWithoutPtrs.
|
||||||
|
// The result aliases no memory with the original.
|
||||||
|
func (src *StructWithoutPtrs) Clone() *StructWithoutPtrs {
|
||||||
|
if src == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
dst := new(StructWithoutPtrs)
|
||||||
|
*dst = *src
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||||
|
var _StructWithoutPtrsCloneNeedsRegeneration = StructWithoutPtrs(struct {
|
||||||
|
Int int
|
||||||
|
Pfx netaddr.IPPrefix
|
||||||
|
}{})
|
||||||
|
|
||||||
|
// Clone makes a deep copy of Map.
|
||||||
|
// The result aliases no memory with the original.
|
||||||
|
func (src *Map) Clone() *Map {
|
||||||
|
if src == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
dst := new(Map)
|
||||||
|
*dst = *src
|
||||||
|
if dst.M != nil {
|
||||||
|
dst.M = map[string]int{}
|
||||||
|
for k, v := range src.M {
|
||||||
|
dst.M[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||||
|
var _MapCloneNeedsRegeneration = Map(struct {
|
||||||
|
M map[string]int
|
||||||
|
}{})
|
||||||
|
|
||||||
|
// Clone makes a deep copy of StructWithSlices.
|
||||||
|
// The result aliases no memory with the original.
|
||||||
|
func (src *StructWithSlices) Clone() *StructWithSlices {
|
||||||
|
if src == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
dst := new(StructWithSlices)
|
||||||
|
*dst = *src
|
||||||
|
dst.Values = append(src.Values[:0:0], src.Values...)
|
||||||
|
dst.ValuePointers = make([]*StructWithoutPtrs, len(src.ValuePointers))
|
||||||
|
for i := range dst.ValuePointers {
|
||||||
|
dst.ValuePointers[i] = src.ValuePointers[i].Clone()
|
||||||
|
}
|
||||||
|
dst.StructPointers = make([]*StructWithPtrs, len(src.StructPointers))
|
||||||
|
for i := range dst.StructPointers {
|
||||||
|
dst.StructPointers[i] = src.StructPointers[i].Clone()
|
||||||
|
}
|
||||||
|
dst.Structs = make([]StructWithPtrs, len(src.Structs))
|
||||||
|
for i := range dst.Structs {
|
||||||
|
dst.Structs[i] = *src.Structs[i].Clone()
|
||||||
|
}
|
||||||
|
dst.Ints = make([]*int, len(src.Ints))
|
||||||
|
for i := range dst.Ints {
|
||||||
|
x := *src.Ints[i]
|
||||||
|
dst.Ints[i] = &x
|
||||||
|
}
|
||||||
|
dst.Slice = append(src.Slice[:0:0], src.Slice...)
|
||||||
|
dst.Prefixes = append(src.Prefixes[:0:0], src.Prefixes...)
|
||||||
|
dst.Data = append(src.Data[:0:0], src.Data...)
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||||
|
var _StructWithSlicesCloneNeedsRegeneration = StructWithSlices(struct {
|
||||||
|
Values []StructWithoutPtrs
|
||||||
|
ValuePointers []*StructWithoutPtrs
|
||||||
|
StructPointers []*StructWithPtrs
|
||||||
|
Structs []StructWithPtrs
|
||||||
|
Ints []*int
|
||||||
|
Slice []string
|
||||||
|
Prefixes []netaddr.IPPrefix
|
||||||
|
Data []byte
|
||||||
|
}{})
|
@ -0,0 +1,268 @@
|
|||||||
|
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Code generated by tailscale/cmd/viewer; DO NOT EDIT.
|
||||||
|
|
||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"go4.org/mem"
|
||||||
|
"inet.af/netaddr"
|
||||||
|
"tailscale.com/types/views"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate go run tailscale.com/cmd/cloner -clonefunc=false -type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices
|
||||||
|
|
||||||
|
// View returns a readonly view of StructWithPtrs.
|
||||||
|
func (p *StructWithPtrs) View() StructWithPtrsView {
|
||||||
|
return StructWithPtrsView{ж: p}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructWithPtrsView provides a read-only view over StructWithPtrs.
|
||||||
|
//
|
||||||
|
// Its methods should only be called if `Valid()` returns true.
|
||||||
|
type StructWithPtrsView struct {
|
||||||
|
// ж is the underlying mutable value, named with a hard-to-type
|
||||||
|
// character that looks pointy like a pointer.
|
||||||
|
// It is named distinctively to make you think of how dangerous it is to escape
|
||||||
|
// to callers. You must not let callers be able to mutate it.
|
||||||
|
ж *StructWithPtrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid reports whether underlying value is non-nil.
|
||||||
|
func (v StructWithPtrsView) Valid() bool { return v.ж != nil }
|
||||||
|
|
||||||
|
// AsStruct returns a clone of the underlying value which aliases no memory with
|
||||||
|
// the original.
|
||||||
|
func (v StructWithPtrsView) AsStruct() *StructWithPtrs {
|
||||||
|
if v.ж == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v.ж.Clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v StructWithPtrsView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
|
||||||
|
|
||||||
|
func (v *StructWithPtrsView) UnmarshalJSON(b []byte) error {
|
||||||
|
if v.ж != nil {
|
||||||
|
return errors.New("already initialized")
|
||||||
|
}
|
||||||
|
if len(b) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var x StructWithPtrs
|
||||||
|
if err := json.Unmarshal(b, &x); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.ж = &x
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v StructWithPtrsView) Value() *StructWithoutPtrs {
|
||||||
|
if v.ж.Value == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
x := *v.ж.Value
|
||||||
|
return &x
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v StructWithPtrsView) Int() *int {
|
||||||
|
if v.ж.Int == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
x := *v.ж.Int
|
||||||
|
return &x
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v StructWithPtrsView) NoCloneValue() *StructWithoutPtrs { return v.ж.NoCloneValue }
|
||||||
|
func (v StructWithPtrsView) String() string { return v.ж.String() }
|
||||||
|
func (v StructWithPtrsView) Equal(v2 StructWithPtrsView) bool { return v.ж.Equal(v2.ж) }
|
||||||
|
|
||||||
|
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||||
|
var _StructWithPtrsViewNeedsRegeneration = StructWithPtrs(struct {
|
||||||
|
Value *StructWithoutPtrs
|
||||||
|
Int *int
|
||||||
|
NoCloneValue *StructWithoutPtrs
|
||||||
|
}{})
|
||||||
|
|
||||||
|
// View returns a readonly view of StructWithoutPtrs.
|
||||||
|
func (p *StructWithoutPtrs) View() StructWithoutPtrsView {
|
||||||
|
return StructWithoutPtrsView{ж: p}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructWithoutPtrsView provides a read-only view over StructWithoutPtrs.
|
||||||
|
//
|
||||||
|
// Its methods should only be called if `Valid()` returns true.
|
||||||
|
type StructWithoutPtrsView struct {
|
||||||
|
// ж is the underlying mutable value, named with a hard-to-type
|
||||||
|
// character that looks pointy like a pointer.
|
||||||
|
// It is named distinctively to make you think of how dangerous it is to escape
|
||||||
|
// to callers. You must not let callers be able to mutate it.
|
||||||
|
ж *StructWithoutPtrs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid reports whether underlying value is non-nil.
|
||||||
|
func (v StructWithoutPtrsView) Valid() bool { return v.ж != nil }
|
||||||
|
|
||||||
|
// AsStruct returns a clone of the underlying value which aliases no memory with
|
||||||
|
// the original.
|
||||||
|
func (v StructWithoutPtrsView) AsStruct() *StructWithoutPtrs {
|
||||||
|
if v.ж == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v.ж.Clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v StructWithoutPtrsView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
|
||||||
|
|
||||||
|
func (v *StructWithoutPtrsView) UnmarshalJSON(b []byte) error {
|
||||||
|
if v.ж != nil {
|
||||||
|
return errors.New("already initialized")
|
||||||
|
}
|
||||||
|
if len(b) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var x StructWithoutPtrs
|
||||||
|
if err := json.Unmarshal(b, &x); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.ж = &x
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v StructWithoutPtrsView) Int() int { return v.ж.Int }
|
||||||
|
func (v StructWithoutPtrsView) Pfx() netaddr.IPPrefix { return v.ж.Pfx }
|
||||||
|
|
||||||
|
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||||
|
var _StructWithoutPtrsViewNeedsRegeneration = StructWithoutPtrs(struct {
|
||||||
|
Int int
|
||||||
|
Pfx netaddr.IPPrefix
|
||||||
|
}{})
|
||||||
|
|
||||||
|
// View returns a readonly view of Map.
|
||||||
|
func (p *Map) View() MapView {
|
||||||
|
return MapView{ж: p}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapView provides a read-only view over Map.
|
||||||
|
//
|
||||||
|
// Its methods should only be called if `Valid()` returns true.
|
||||||
|
type MapView struct {
|
||||||
|
// ж is the underlying mutable value, named with a hard-to-type
|
||||||
|
// character that looks pointy like a pointer.
|
||||||
|
// It is named distinctively to make you think of how dangerous it is to escape
|
||||||
|
// to callers. You must not let callers be able to mutate it.
|
||||||
|
ж *Map
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid reports whether underlying value is non-nil.
|
||||||
|
func (v MapView) Valid() bool { return v.ж != nil }
|
||||||
|
|
||||||
|
// AsStruct returns a clone of the underlying value which aliases no memory with
|
||||||
|
// the original.
|
||||||
|
func (v MapView) AsStruct() *Map {
|
||||||
|
if v.ж == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v.ж.Clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v MapView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
|
||||||
|
|
||||||
|
func (v *MapView) UnmarshalJSON(b []byte) error {
|
||||||
|
if v.ж != nil {
|
||||||
|
return errors.New("already initialized")
|
||||||
|
}
|
||||||
|
if len(b) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var x Map
|
||||||
|
if err := json.Unmarshal(b, &x); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.ж = &x
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||||
|
var _MapViewNeedsRegeneration = Map(struct {
|
||||||
|
M map[string]int
|
||||||
|
}{})
|
||||||
|
|
||||||
|
// View returns a readonly view of StructWithSlices.
|
||||||
|
func (p *StructWithSlices) View() StructWithSlicesView {
|
||||||
|
return StructWithSlicesView{ж: p}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructWithSlicesView provides a read-only view over StructWithSlices.
|
||||||
|
//
|
||||||
|
// Its methods should only be called if `Valid()` returns true.
|
||||||
|
type StructWithSlicesView struct {
|
||||||
|
// ж is the underlying mutable value, named with a hard-to-type
|
||||||
|
// character that looks pointy like a pointer.
|
||||||
|
// It is named distinctively to make you think of how dangerous it is to escape
|
||||||
|
// to callers. You must not let callers be able to mutate it.
|
||||||
|
ж *StructWithSlices
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid reports whether underlying value is non-nil.
|
||||||
|
func (v StructWithSlicesView) Valid() bool { return v.ж != nil }
|
||||||
|
|
||||||
|
// AsStruct returns a clone of the underlying value which aliases no memory with
|
||||||
|
// the original.
|
||||||
|
func (v StructWithSlicesView) AsStruct() *StructWithSlices {
|
||||||
|
if v.ж == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v.ж.Clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v StructWithSlicesView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
|
||||||
|
|
||||||
|
func (v *StructWithSlicesView) UnmarshalJSON(b []byte) error {
|
||||||
|
if v.ж != nil {
|
||||||
|
return errors.New("already initialized")
|
||||||
|
}
|
||||||
|
if len(b) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var x StructWithSlices
|
||||||
|
if err := json.Unmarshal(b, &x); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.ж = &x
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v StructWithSlicesView) Values() views.Slice[StructWithoutPtrs] {
|
||||||
|
return views.SliceOf(v.ж.Values)
|
||||||
|
}
|
||||||
|
func (v StructWithSlicesView) ValuePointers() views.SliceView[*StructWithoutPtrs, StructWithoutPtrsView] {
|
||||||
|
return views.SliceOfViews[*StructWithoutPtrs, StructWithoutPtrsView](v.ж.ValuePointers)
|
||||||
|
}
|
||||||
|
func (v StructWithSlicesView) StructPointers() views.SliceView[*StructWithPtrs, StructWithPtrsView] {
|
||||||
|
return views.SliceOfViews[*StructWithPtrs, StructWithPtrsView](v.ж.StructPointers)
|
||||||
|
}
|
||||||
|
func (v StructWithSlicesView) Structs() StructWithPtrs { panic("unsupported") }
|
||||||
|
func (v StructWithSlicesView) Ints() *int { panic("unsupported") }
|
||||||
|
func (v StructWithSlicesView) Slice() views.Slice[string] { return views.SliceOf(v.ж.Slice) }
|
||||||
|
func (v StructWithSlicesView) Prefixes() views.IPPrefixSlice {
|
||||||
|
return views.IPPrefixSliceOf(v.ж.Prefixes)
|
||||||
|
}
|
||||||
|
func (v StructWithSlicesView) Data() mem.RO { return mem.B(v.ж.Data) }
|
||||||
|
|
||||||
|
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||||
|
var _StructWithSlicesViewNeedsRegeneration = StructWithSlices(struct {
|
||||||
|
Values []StructWithoutPtrs
|
||||||
|
ValuePointers []*StructWithoutPtrs
|
||||||
|
StructPointers []*StructWithPtrs
|
||||||
|
Structs []StructWithPtrs
|
||||||
|
Ints []*int
|
||||||
|
Slice []string
|
||||||
|
Prefixes []netaddr.IPPrefix
|
||||||
|
Data []byte
|
||||||
|
}{})
|
@ -0,0 +1,316 @@
|
|||||||
|
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Viewer is a tool to automate the creation of "view" wrapper types that
|
||||||
|
// provide read-only accessor methods to underlying fields.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"go/types"
|
||||||
|
"html/template"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"tailscale.com/util/codegen"
|
||||||
|
)
|
||||||
|
|
||||||
|
const viewTemplateStr = `{{define "common"}}
|
||||||
|
// View returns a readonly view of {{.StructName}}.
|
||||||
|
func (p *{{.StructName}}) View() {{.ViewName}} {
|
||||||
|
return {{.ViewName}}{ж: p}
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.ViewName}} provides a read-only view over {{.StructName}}.
|
||||||
|
//
|
||||||
|
// Its methods should only be called if ` + "`Valid()`" + ` returns true.
|
||||||
|
type {{.ViewName}} struct {
|
||||||
|
// ж is the underlying mutable value, named with a hard-to-type
|
||||||
|
// character that looks pointy like a pointer.
|
||||||
|
// It is named distinctively to make you think of how dangerous it is to escape
|
||||||
|
// to callers. You must not let callers be able to mutate it.
|
||||||
|
ж *{{.StructName}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid reports whether underlying value is non-nil.
|
||||||
|
func (v {{.ViewName}}) Valid() bool { return v.ж != nil }
|
||||||
|
|
||||||
|
// AsStruct returns a clone of the underlying value which aliases no memory with
|
||||||
|
// the original.
|
||||||
|
func (v {{.ViewName}}) AsStruct() *{{.StructName}}{
|
||||||
|
if v.ж == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v.ж.Clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v {{.ViewName}}) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
|
||||||
|
|
||||||
|
func (v *{{.ViewName}}) UnmarshalJSON(b []byte) error {
|
||||||
|
if v.ж != nil {
|
||||||
|
return errors.New("already initialized")
|
||||||
|
}
|
||||||
|
if len(b) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var x {{.StructName}}
|
||||||
|
if err := json.Unmarshal(b, &x); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.ж=&x
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
{{end}}
|
||||||
|
{{define "valueField"}}func (v {{.ViewName}}) {{.FieldName}}() {{.FieldType}} { return v.ж.{{.FieldName}} }
|
||||||
|
{{end}}
|
||||||
|
{{define "byteSliceField"}}func (v {{.ViewName}}) {{.FieldName}}() mem.RO { return mem.B(v.ж.{{.FieldName}}) }
|
||||||
|
{{end}}
|
||||||
|
{{define "ipPrefixSliceField"}}func (v {{.ViewName}}) {{.FieldName}}() views.IPPrefixSlice { return views.IPPrefixSliceOf(v.ж.{{.FieldName}}) }
|
||||||
|
{{end}}
|
||||||
|
{{define "sliceField"}}func (v {{.ViewName}}) {{.FieldName}}() views.Slice[{{.FieldType}}] { return views.SliceOf(v.ж.{{.FieldName}}) }
|
||||||
|
{{end}}
|
||||||
|
{{define "viewSliceField"}}func (v {{.ViewName}}) {{.FieldName}}() views.SliceView[{{.FieldType}},{{.FieldViewName}}] { return views.SliceOfViews[{{.FieldType}},{{.FieldViewName}}](v.ж.{{.FieldName}}) }
|
||||||
|
{{end}}
|
||||||
|
{{define "viewField"}}func (v {{.ViewName}}) {{.FieldName}}() {{.FieldType}}View { return v.ж.{{.FieldName}}.View() }
|
||||||
|
{{end}}
|
||||||
|
{{define "valuePointerField"}}func (v {{.ViewName}}) {{.FieldName}}() {{.FieldType}} {
|
||||||
|
if v.ж.{{.FieldName}} == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
x := *v.ж.{{.FieldName}}
|
||||||
|
return &x
|
||||||
|
}
|
||||||
|
|
||||||
|
{{end}}
|
||||||
|
{{define "mapField"}}
|
||||||
|
// Unsupported, panics.
|
||||||
|
func(v {{.ViewName}}) {{.FieldName}}() {{.FieldType}} {panic("unsupported")}
|
||||||
|
{{end}}
|
||||||
|
{{define "unsupportedField"}}func(v {{.ViewName}}) {{.FieldName}}() {{.FieldType}} {panic("unsupported")}
|
||||||
|
{{end}}
|
||||||
|
{{define "stringFunc"}}func(v {{.ViewName}}) String() string { return v.ж.String() }
|
||||||
|
{{end}}
|
||||||
|
{{define "equalFunc"}}func(v {{.ViewName}}) Equal(v2 {{.ViewName}}) bool { return v.ж.Equal(v2.ж) }
|
||||||
|
{{end}}
|
||||||
|
`
|
||||||
|
|
||||||
|
var viewTemplate *template.Template
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
viewTemplate = template.Must(template.New("view").Parse(viewTemplateStr))
|
||||||
|
}
|
||||||
|
|
||||||
|
func requiresCloning(t types.Type) (shallow, deep bool, base types.Type) {
|
||||||
|
switch v := t.(type) {
|
||||||
|
case *types.Pointer:
|
||||||
|
_, deep, base = requiresCloning(v.Elem())
|
||||||
|
return true, deep, base
|
||||||
|
case *types.Slice:
|
||||||
|
_, deep, base = requiresCloning(v.Elem())
|
||||||
|
return true, deep, base
|
||||||
|
}
|
||||||
|
p := codegen.ContainsPointers(t)
|
||||||
|
return p, p, t
|
||||||
|
}
|
||||||
|
|
||||||
|
func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, thisPkg *types.Package) {
|
||||||
|
t, ok := typ.Underlying().(*types.Struct)
|
||||||
|
if !ok || codegen.IsViewType(t) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
it.Import("encoding/json")
|
||||||
|
it.Import("errors")
|
||||||
|
|
||||||
|
args := struct {
|
||||||
|
StructName string
|
||||||
|
ViewName string
|
||||||
|
FieldName string
|
||||||
|
FieldType string
|
||||||
|
FieldViewName string
|
||||||
|
}{
|
||||||
|
StructName: typ.Obj().Name(),
|
||||||
|
ViewName: typ.Obj().Name() + "View",
|
||||||
|
}
|
||||||
|
|
||||||
|
writeTemplate := func(name string) {
|
||||||
|
if err := viewTemplate.ExecuteTemplate(buf, name, args); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeTemplate("common")
|
||||||
|
for i := 0; i < t.NumFields(); i++ {
|
||||||
|
f := t.Field(i)
|
||||||
|
fname := f.Name()
|
||||||
|
if !f.Exported() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
args.FieldName = fname
|
||||||
|
fieldType := f.Type()
|
||||||
|
if codegen.IsInvalid(fieldType) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !codegen.ContainsPointers(fieldType) || codegen.IsViewType(fieldType) || codegen.HasNoClone(t.Tag(i)) {
|
||||||
|
args.FieldType = it.QualifiedName(fieldType)
|
||||||
|
writeTemplate("valueField")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch underlying := fieldType.Underlying().(type) {
|
||||||
|
case *types.Slice:
|
||||||
|
slice := underlying
|
||||||
|
elem := slice.Elem()
|
||||||
|
args.FieldType = it.QualifiedName(elem)
|
||||||
|
switch elem.String() {
|
||||||
|
case "byte":
|
||||||
|
it.Import("go4.org/mem")
|
||||||
|
writeTemplate("byteSliceField")
|
||||||
|
case "inet.af/netaddr.IPPrefix":
|
||||||
|
it.Import("tailscale.com/types/views")
|
||||||
|
writeTemplate("ipPrefixSliceField")
|
||||||
|
default:
|
||||||
|
it.Import("tailscale.com/types/views")
|
||||||
|
shallow, deep, base := requiresCloning(elem)
|
||||||
|
if deep {
|
||||||
|
if _, isPtr := elem.(*types.Pointer); isPtr {
|
||||||
|
args.FieldViewName = it.QualifiedName(base) + "View"
|
||||||
|
writeTemplate("viewSliceField")
|
||||||
|
} else {
|
||||||
|
writeTemplate("unsupportedField")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else if shallow {
|
||||||
|
if _, isBasic := base.(*types.Basic); isBasic {
|
||||||
|
writeTemplate("unsupportedField")
|
||||||
|
} else {
|
||||||
|
args.FieldViewName = it.QualifiedName(base) + "View"
|
||||||
|
writeTemplate("viewSliceField")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
writeTemplate("sliceField")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
case *types.Struct:
|
||||||
|
strucT := underlying
|
||||||
|
args.FieldType = it.QualifiedName(fieldType)
|
||||||
|
if codegen.ContainsPointers(strucT) {
|
||||||
|
writeTemplate("viewField")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
writeTemplate("valueField")
|
||||||
|
continue
|
||||||
|
case *types.Map:
|
||||||
|
// TODO(maisem): support this.
|
||||||
|
// args.FieldType = importedName(ft)
|
||||||
|
// writeTemplate("mapField")
|
||||||
|
continue
|
||||||
|
case *types.Pointer:
|
||||||
|
ptr := underlying
|
||||||
|
_, deep, base := requiresCloning(ptr)
|
||||||
|
if deep {
|
||||||
|
args.FieldType = it.QualifiedName(base)
|
||||||
|
writeTemplate("viewField")
|
||||||
|
} else {
|
||||||
|
args.FieldType = it.QualifiedName(ptr)
|
||||||
|
writeTemplate("valuePointerField")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
writeTemplate("unsupportedField")
|
||||||
|
}
|
||||||
|
for i := 0; i < typ.NumMethods(); i++ {
|
||||||
|
f := typ.Method(i)
|
||||||
|
if !f.Exported() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sig, ok := f.Type().(*types.Signature)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch f.Name() {
|
||||||
|
case "Clone", "View":
|
||||||
|
continue // "AsStruct"
|
||||||
|
case "String":
|
||||||
|
writeTemplate("stringFunc")
|
||||||
|
continue
|
||||||
|
case "Equal":
|
||||||
|
if sig.Results().Len() == 1 && sig.Results().At(0).Type().String() == "bool" {
|
||||||
|
writeTemplate("equalFunc")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(buf, "\n")
|
||||||
|
buf.Write(codegen.AssertStructUnchanged(t, args.StructName, "View", it))
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
flagTypes = flag.String("type", "", "comma-separated list of types; required")
|
||||||
|
flagBuildTags = flag.String("tags", "", "compiler build tags to apply")
|
||||||
|
flagCloneFunc = flag.Bool("clonefunc", false, "add a top-level Clone func")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.SetFlags(0)
|
||||||
|
log.SetPrefix("viewer: ")
|
||||||
|
flag.Parse()
|
||||||
|
if len(*flagTypes) == 0 {
|
||||||
|
flag.Usage()
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
typeNames := strings.Split(*flagTypes, ",")
|
||||||
|
|
||||||
|
var flagArgs []string
|
||||||
|
flagArgs = append(flagArgs, fmt.Sprintf("-clonefunc=%v", *flagCloneFunc))
|
||||||
|
if *flagTypes != "" {
|
||||||
|
flagArgs = append(flagArgs, "-type="+*flagTypes)
|
||||||
|
}
|
||||||
|
if *flagBuildTags != "" {
|
||||||
|
flagArgs = append(flagArgs, "-tags="+*flagBuildTags)
|
||||||
|
}
|
||||||
|
pkg, namedTypes, err := codegen.LoadTypes(*flagBuildTags, ".")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
it := codegen.NewImportTracker(pkg.Types)
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
fmt.Fprintf(buf, `//go:generate go run tailscale.com/cmd/cloner %s`, strings.Join(flagArgs, " "))
|
||||||
|
fmt.Fprintln(buf)
|
||||||
|
runCloner := false
|
||||||
|
for _, typeName := range typeNames {
|
||||||
|
typ, ok := namedTypes[typeName]
|
||||||
|
if !ok {
|
||||||
|
log.Fatalf("could not find type %s", typeName)
|
||||||
|
}
|
||||||
|
var hasClone bool
|
||||||
|
for i, n := 0, typ.NumMethods(); i < n; i++ {
|
||||||
|
if typ.Method(i).Name() == "Clone" {
|
||||||
|
hasClone = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasClone {
|
||||||
|
runCloner = true
|
||||||
|
}
|
||||||
|
genView(buf, it, typ, pkg.Types)
|
||||||
|
}
|
||||||
|
out := pkg.Name + "_view.go"
|
||||||
|
if err := codegen.WritePackageFile("tailscale/cmd/viewer", pkg, out, it, buf); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if runCloner {
|
||||||
|
// When a new pacakge is added or when existing generated files have
|
||||||
|
// been deleted, we might run into a case where tailscale.com/cmd/cloner
|
||||||
|
// has not run yet. We detect this by verifying that all the structs we
|
||||||
|
// interacted with have had Clone method already generated. If they
|
||||||
|
// haven't we ask the caller to rerun generation again so that those get
|
||||||
|
// generated.
|
||||||
|
log.Printf("%v requires regeneration. Please run go generate again", pkg.Name+"_clone.go")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue