Change made by: - running "gvt fetch" on each of the packages mentioned in Godeps/Godeps.json - `rm -rf Godeps` - tweaking the build scripts to not mention Godeps - tweaking the build scripts to test `./lib/...`, `./cmd/...` explicitly (to avoid testing vendor) - tweaking the build scripts to not juggle GOPATH for Godeps and instead set GO15VENDOREXPERIMENT. This also results in some updated packages at the same time I bet. Building with Go 1.3 and 1.4 still *works* but won't use our vendored dependencies - the user needs to have the actual packages in their GOPATH then, which they'll get with a normal "go get". Building with Go 1.6+ will get our vendored dependencies by default even when not using our build script, which is nice. By doing this we gain some freedom in that we can pick and choose manually what to include in vendor, as it's not based on just dependency analysis of our own code. This is also a risk as we might pick up dependencies we are unaware of, as the build may work locally with those packages present in GOPATH. On the other hand the build server will detect this as it has no packages in it's GOPATH beyond what is included in the repo. Recommended tool to manage dependencies is github.com/FiloSottile/gvt.
587 lines
13 KiB
Go
587 lines
13 KiB
Go
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
|
|
// is governed by an MIT-style license that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"flag"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/format"
|
|
"go/parser"
|
|
"go/token"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"text/template"
|
|
)
|
|
|
|
type fieldInfo struct {
|
|
Name string
|
|
IsBasic bool // handled by one the native Read/WriteUint64 etc functions
|
|
IsSlice bool // field is a slice of FieldType
|
|
FieldType string // original type of field, i.e. "int"
|
|
Encoder string // the encoder name, i.e. "Uint64" for Read/WriteUint64
|
|
Convert string // what to convert to when encoding, i.e. "uint64"
|
|
Max int // max size for slices and strings
|
|
Submax int // max size for strings inside slices
|
|
}
|
|
|
|
type structInfo struct {
|
|
Name string
|
|
Fields []fieldInfo
|
|
}
|
|
|
|
func (i structInfo) SizeExpr() string {
|
|
var xdrSizes = map[string]int{
|
|
"int8": 4,
|
|
"uint8": 4,
|
|
"int16": 4,
|
|
"uint16": 4,
|
|
"int32": 4,
|
|
"uint32": 4,
|
|
"int64": 8,
|
|
"uint64": 8,
|
|
"int": 8,
|
|
"bool": 4,
|
|
}
|
|
|
|
var terms []string
|
|
nl := ""
|
|
for _, f := range i.Fields {
|
|
if size := xdrSizes[f.FieldType]; size > 0 {
|
|
if f.IsSlice {
|
|
terms = append(terms, nl+"4+len(o."+f.Name+")*"+strconv.Itoa(size))
|
|
} else {
|
|
terms = append(terms, strconv.Itoa(size))
|
|
}
|
|
} else {
|
|
switch f.FieldType {
|
|
case "string", "[]byte":
|
|
if f.IsSlice {
|
|
terms = append(terms, nl+"4+xdr.SizeOfSlice(o."+f.Name+")")
|
|
} else {
|
|
terms = append(terms, nl+"4+len(o."+f.Name+")+xdr.Padding(len(o."+f.Name+"))")
|
|
}
|
|
default:
|
|
if f.IsSlice {
|
|
terms = append(terms, nl+"4+xdr.SizeOfSlice(o."+f.Name+")")
|
|
} else {
|
|
terms = append(terms, nl+"o."+f.Name+".XDRSize()")
|
|
}
|
|
}
|
|
}
|
|
nl = "\n"
|
|
}
|
|
return strings.Join(terms, "+")
|
|
}
|
|
|
|
var headerData = `// ************************************************************
|
|
// This file is automatically generated by genxdr. Do not edit.
|
|
// ************************************************************
|
|
|
|
package {{.Package}}
|
|
|
|
import (
|
|
"github.com/calmh/xdr"
|
|
)
|
|
`
|
|
|
|
var encoderData = `
|
|
func (o {{.Name}}) XDRSize() int {
|
|
return {{.SizeExpr}}
|
|
}//+n
|
|
|
|
func (o {{.Name}}) MarshalXDR() ([]byte, error) {
|
|
buf:= make([]byte, o.XDRSize())
|
|
m := &xdr.Marshaller{Data: buf}
|
|
return buf, o.MarshalXDRInto(m)
|
|
}//+n
|
|
|
|
func (o {{.Name}}) MustMarshalXDR() []byte {
|
|
bs, err := o.MarshalXDR()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return bs
|
|
}//+n
|
|
|
|
func (o {{.Name}}) MarshalXDRInto(m *xdr.Marshaller) error {
|
|
{{range $fi := .Fields}}
|
|
{{if $fi.IsSlice}}
|
|
{{template "marshalSlice" $fi}}
|
|
{{else}}
|
|
{{template "marshalValue" $fi}}
|
|
{{end}}
|
|
{{end}}
|
|
return m.Error
|
|
}//+n
|
|
|
|
{{define "marshalValue"}}
|
|
{{if ne .Convert ""}}
|
|
m.Marshal{{.Encoder}}({{.Convert}}(o.{{.Name}}))
|
|
{{else if .IsBasic}}
|
|
{{if ge .Max 1}}
|
|
if l := len(o.{{.Name}}); l > {{.Max}} {
|
|
return xdr.ElementSizeExceeded("{{.Name}}", l, {{.Max}})
|
|
}
|
|
{{end}}
|
|
m.Marshal{{.Encoder}}(o.{{.Name}})
|
|
{{else}}
|
|
if err := o.{{.Name}}.MarshalXDRInto(m); err != nil {
|
|
return err
|
|
}
|
|
{{end}}
|
|
{{end}}
|
|
|
|
{{define "marshalSlice"}}
|
|
{{if ge .Max 1}}
|
|
if l := len(o.{{.Name}}); l > {{.Max}} {
|
|
return xdr.ElementSizeExceeded("{{.Name}}", l, {{.Max}})
|
|
}
|
|
{{end}}
|
|
|
|
m.MarshalUint32(uint32(len(o.{{.Name}})))
|
|
for i := range o.{{.Name}} {
|
|
{{if ne .Convert ""}}
|
|
m.Marshal{{.Encoder}}({{.Convert}}(o.{{.Name}}[i]))
|
|
{{else if .IsBasic}}
|
|
m.Marshal{{.Encoder}}(o.{{.Name}}[i])
|
|
{{else}}
|
|
if err := o.{{.Name}}[i].MarshalXDRInto(m); err != nil {
|
|
return err
|
|
}
|
|
{{end}}
|
|
}
|
|
{{end}}
|
|
|
|
func (o *{{.Name}}) UnmarshalXDR(bs []byte) error {
|
|
u := &xdr.Unmarshaller{Data: bs}
|
|
return o.UnmarshalXDRFrom(u)
|
|
}
|
|
|
|
func (o *{{.Name}}) UnmarshalXDRFrom(u *xdr.Unmarshaller) error {
|
|
{{range $fi := .Fields}}
|
|
{{if $fi.IsSlice}}
|
|
{{template "unmarshalSlice" $fi}}
|
|
{{else}}
|
|
{{template "unmarshalValue" $fi}}
|
|
{{end}}
|
|
{{end}}
|
|
return u.Error
|
|
}//+n
|
|
|
|
{{define "unmarshalValue"}}
|
|
{{if ne .Convert ""}}
|
|
o.{{.Name}} = {{.FieldType}}(u.Unmarshal{{.Encoder}}())
|
|
{{else if .IsBasic}}
|
|
{{if ge .Max 1}}
|
|
o.{{.Name}} = u.Unmarshal{{.Encoder}}Max({{.Max}})
|
|
{{else}}
|
|
o.{{.Name}} = u.Unmarshal{{.Encoder}}()
|
|
{{end}}
|
|
{{else}}
|
|
(&o.{{.Name}}).UnmarshalXDRFrom(u)
|
|
{{end}}
|
|
{{end}}
|
|
|
|
{{define "unmarshalSlice"}}
|
|
_{{.Name}}Size := int(u.UnmarshalUint32())
|
|
if _{{.Name}}Size < 0 {
|
|
return xdr.ElementSizeExceeded("{{.Name}}", _{{.Name}}Size, {{.Max}})
|
|
} else if _{{.Name}}Size == 0 {
|
|
o.{{.Name}} = nil
|
|
} else {
|
|
{{if ge .Max 1}}
|
|
if _{{.Name}}Size > {{.Max}} {
|
|
return xdr.ElementSizeExceeded("{{.Name}}", _{{.Name}}Size, {{.Max}})
|
|
}
|
|
{{end}}
|
|
if _{{.Name}}Size <= len(o.{{.Name}}) {
|
|
{{if eq .FieldType "string"}}
|
|
for i := _{{.Name}}Size; i < len(o.{{.Name}}); i++ { o.{{.Name}}[i] = "" }
|
|
{{end}}
|
|
{{if eq .FieldType "[]byte"}}
|
|
for i := _{{.Name}}Size; i < len(o.{{.Name}}); i++ { o.{{.Name}}[i] = nil }
|
|
{{end}}
|
|
o.{{.Name}} = o.{{.Name}}[:_{{.Name}}Size]
|
|
} else {
|
|
o.{{.Name}} = make([]{{.FieldType}}, _{{.Name}}Size)
|
|
}
|
|
for i := range o.{{.Name}} {
|
|
{{if ne .Convert ""}}
|
|
o.{{.Name}}[i] = {{.FieldType}}(u.Unmarshal{{.Encoder}}())
|
|
{{else if .IsBasic}}
|
|
{{if ge .Submax 1}}
|
|
o.{{.Name}}[i] = u.Unmarshal{{.Encoder}}Max({{.Submax}})
|
|
{{else}}
|
|
o.{{.Name}}[i] = u.Unmarshal{{.Encoder}}()
|
|
{{end}}
|
|
{{else}}
|
|
(&o.{{.Name}}[i]).UnmarshalXDRFrom(u)
|
|
{{end}}
|
|
}
|
|
}
|
|
{{end}}
|
|
`
|
|
|
|
var (
|
|
encodeTpl = template.Must(template.New("encoder").Parse(encoderData))
|
|
headerTpl = template.Must(template.New("header").Parse(headerData))
|
|
)
|
|
|
|
var emptyTypeTpl = template.Must(template.New("encoder").Parse(`
|
|
func (o {{.Name}}) XDRSize() int {
|
|
return 0
|
|
}
|
|
|
|
func (o {{.Name}}) MarshalXDR() ([]byte, error) {
|
|
return nil, nil
|
|
}//+n
|
|
|
|
func (o {{.Name}}) MustMarshalXDR() []byte {
|
|
return nil
|
|
}//+n
|
|
|
|
func (o {{.Name}}) MarshalXDRInto(m *xdr.Marshaller) error {
|
|
return nil
|
|
}//+n
|
|
|
|
func (o *{{.Name}}) UnmarshalXDR(bs []byte) error {
|
|
return nil
|
|
}//+n
|
|
|
|
func (o *{{.Name}}) UnmarshalXDRFrom(u *xdr.Unmarshaller) error {
|
|
return nil
|
|
}//+n
|
|
`))
|
|
|
|
var maxRe = regexp.MustCompile(`(?:\Wmax:)(\d+)(?:\s*,\s*(\d+))?`)
|
|
|
|
type typeSet struct {
|
|
Type string
|
|
Encoder string
|
|
}
|
|
|
|
var xdrEncoders = map[string]typeSet{
|
|
"int8": typeSet{"uint8", "Uint8"},
|
|
"uint8": typeSet{"", "Uint8"},
|
|
"int16": typeSet{"uint16", "Uint16"},
|
|
"uint16": typeSet{"", "Uint16"},
|
|
"int32": typeSet{"uint32", "Uint32"},
|
|
"uint32": typeSet{"", "Uint32"},
|
|
"int64": typeSet{"uint64", "Uint64"},
|
|
"uint64": typeSet{"", "Uint64"},
|
|
"int": typeSet{"uint64", "Uint64"},
|
|
"string": typeSet{"", "String"},
|
|
"[]byte": typeSet{"", "Bytes"},
|
|
"bool": typeSet{"", "Bool"},
|
|
}
|
|
|
|
func handleStruct(t *ast.StructType) []fieldInfo {
|
|
var fs []fieldInfo
|
|
|
|
for _, sf := range t.Fields.List {
|
|
if len(sf.Names) == 0 {
|
|
// We don't handle anonymous fields
|
|
continue
|
|
}
|
|
|
|
fn := sf.Names[0].Name
|
|
var max1, max2 int
|
|
if sf.Comment != nil {
|
|
c := sf.Comment.List[0].Text
|
|
m := maxRe.FindStringSubmatch(c)
|
|
if len(m) >= 2 {
|
|
max1, _ = strconv.Atoi(m[1])
|
|
}
|
|
if len(m) >= 3 {
|
|
max2, _ = strconv.Atoi(m[2])
|
|
}
|
|
if strings.Contains(c, "noencode") {
|
|
continue
|
|
}
|
|
}
|
|
|
|
var f fieldInfo
|
|
switch ft := sf.Type.(type) {
|
|
case *ast.Ident:
|
|
tn := ft.Name
|
|
if enc, ok := xdrEncoders[tn]; ok {
|
|
f = fieldInfo{
|
|
Name: fn,
|
|
IsBasic: true,
|
|
FieldType: tn,
|
|
Encoder: enc.Encoder,
|
|
Convert: enc.Type,
|
|
Max: max1,
|
|
Submax: max2,
|
|
}
|
|
} else {
|
|
f = fieldInfo{
|
|
Name: fn,
|
|
IsBasic: false,
|
|
FieldType: tn,
|
|
Max: max1,
|
|
Submax: max2,
|
|
}
|
|
}
|
|
|
|
case *ast.ArrayType:
|
|
if ft.Len != nil {
|
|
// We don't handle arrays
|
|
continue
|
|
}
|
|
|
|
tn := ft.Elt.(*ast.Ident).Name
|
|
if enc, ok := xdrEncoders["[]"+tn]; ok {
|
|
f = fieldInfo{
|
|
Name: fn,
|
|
IsBasic: true,
|
|
FieldType: "[]" + tn,
|
|
Encoder: enc.Encoder,
|
|
Convert: enc.Type,
|
|
Max: max1,
|
|
Submax: max2,
|
|
}
|
|
} else if enc, ok := xdrEncoders[tn]; ok {
|
|
f = fieldInfo{
|
|
Name: fn,
|
|
IsBasic: true,
|
|
IsSlice: true,
|
|
FieldType: tn,
|
|
Encoder: enc.Encoder,
|
|
Convert: enc.Type,
|
|
Max: max1,
|
|
Submax: max2,
|
|
}
|
|
} else {
|
|
f = fieldInfo{
|
|
Name: fn,
|
|
IsSlice: true,
|
|
FieldType: tn,
|
|
Max: max1,
|
|
Submax: max2,
|
|
}
|
|
}
|
|
|
|
case *ast.SelectorExpr:
|
|
f = fieldInfo{
|
|
Name: fn,
|
|
FieldType: ft.Sel.Name,
|
|
Max: max1,
|
|
Submax: max2,
|
|
}
|
|
}
|
|
|
|
fs = append(fs, f)
|
|
}
|
|
|
|
return fs
|
|
}
|
|
|
|
func generateCode(output io.Writer, s structInfo) {
|
|
var buf bytes.Buffer
|
|
var err error
|
|
if len(s.Fields) == 0 {
|
|
// This is an empty type. We can create a quite simple codec for it.
|
|
err = emptyTypeTpl.Execute(&buf, s)
|
|
} else {
|
|
// Generate with the default template.
|
|
err = encodeTpl.Execute(&buf, s)
|
|
}
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
bs := regexp.MustCompile(`(\s*\n)+`).ReplaceAll(buf.Bytes(), []byte("\n"))
|
|
bs = bytes.Replace(bs, []byte("//+n"), []byte("\n"), -1)
|
|
output.Write(bs)
|
|
}
|
|
|
|
func uncamelize(s string) string {
|
|
return regexp.MustCompile("[a-z][A-Z]").ReplaceAllStringFunc(s, func(camel string) string {
|
|
return camel[:1] + " " + camel[1:]
|
|
})
|
|
}
|
|
|
|
func generateDiagram(output io.Writer, s structInfo) {
|
|
sn := s.Name
|
|
fs := s.Fields
|
|
|
|
fmt.Fprintln(output, sn+" Structure:")
|
|
|
|
if len(fs) == 0 {
|
|
fmt.Fprintln(output, "(contains no fields)")
|
|
fmt.Fprintln(output)
|
|
fmt.Fprintln(output)
|
|
return
|
|
}
|
|
|
|
fmt.Fprintln(output)
|
|
fmt.Fprintln(output, " 0 1 2 3")
|
|
fmt.Fprintln(output, " 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1")
|
|
line := "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+"
|
|
fmt.Fprintln(output, line)
|
|
|
|
for _, f := range fs {
|
|
tn := f.FieldType
|
|
name := uncamelize(f.Name)
|
|
|
|
suffix := ""
|
|
if f.IsSlice {
|
|
fmt.Fprintf(output, "| %s |\n", center("Number of "+name, 61))
|
|
fmt.Fprintln(output, line)
|
|
suffix = " (n items)"
|
|
fmt.Fprintf(output, "/ %s /\n", center("", 61))
|
|
}
|
|
switch tn {
|
|
case "bool":
|
|
fmt.Fprintf(output, "| %s |V|\n", center(name+" (V=0 or 1)", 59))
|
|
case "int16", "uint16":
|
|
fmt.Fprintf(output, "| %s | %s |\n", center("16 zero bits", 29), center(name, 29))
|
|
case "int8", "uint8":
|
|
fmt.Fprintf(output, "| %s | %s |\n", center("24 zero bits", 45), center(name, 13))
|
|
case "int32", "uint32":
|
|
fmt.Fprintf(output, "| %s |\n", center(name+suffix, 61))
|
|
case "int64", "uint64":
|
|
fmt.Fprintf(output, "| %-61s |\n", "")
|
|
fmt.Fprintf(output, "+ %s +\n", center(name+" (64 bits)", 61))
|
|
fmt.Fprintf(output, "| %-61s |\n", "")
|
|
case "string", "[]byte":
|
|
fmt.Fprintf(output, "/ %61s /\n", "")
|
|
fmt.Fprintf(output, "\\ %s \\\n", center(name+" (length + padded data)", 61))
|
|
fmt.Fprintf(output, "/ %61s /\n", "")
|
|
default:
|
|
if f.IsSlice {
|
|
tn = "Zero or more " + tn + " Structures"
|
|
fmt.Fprintf(output, "\\ %s \\\n", center(tn, 61))
|
|
} else {
|
|
tn = tn + " Structure"
|
|
fmt.Fprintf(output, "/ %s /\n", center("", 61))
|
|
fmt.Fprintf(output, "\\ %s \\\n", center(tn, 61))
|
|
fmt.Fprintf(output, "/ %s /\n", center("", 61))
|
|
}
|
|
}
|
|
|
|
if f.IsSlice {
|
|
fmt.Fprintf(output, "/ %s /\n", center("", 61))
|
|
}
|
|
fmt.Fprintln(output, line)
|
|
}
|
|
fmt.Fprintln(output)
|
|
fmt.Fprintln(output)
|
|
}
|
|
|
|
func generateXdr(output io.Writer, s structInfo) {
|
|
sn := s.Name
|
|
fs := s.Fields
|
|
|
|
fmt.Fprintf(output, "struct %s {\n", sn)
|
|
|
|
for _, f := range fs {
|
|
tn := f.FieldType
|
|
fn := f.Name
|
|
suf := ""
|
|
l := ""
|
|
if f.Max > 0 {
|
|
l = strconv.Itoa(f.Max)
|
|
}
|
|
if f.IsSlice {
|
|
suf = "<" + l + ">"
|
|
}
|
|
|
|
switch tn {
|
|
case "int8", "int16", "int32":
|
|
fmt.Fprintf(output, "\tint %s%s;\n", fn, suf)
|
|
case "uint8", "uint16", "uint32":
|
|
fmt.Fprintf(output, "\tunsigned int %s%s;\n", fn, suf)
|
|
case "int64":
|
|
fmt.Fprintf(output, "\thyper %s%s;\n", fn, suf)
|
|
case "uint64":
|
|
fmt.Fprintf(output, "\tunsigned hyper %s%s;\n", fn, suf)
|
|
case "string":
|
|
fmt.Fprintf(output, "\tstring %s<%s>;\n", fn, l)
|
|
case "[]byte":
|
|
fmt.Fprintf(output, "\topaque %s<%s>;\n", fn, l)
|
|
default:
|
|
fmt.Fprintf(output, "\t%s %s%s;\n", tn, fn, suf)
|
|
}
|
|
}
|
|
fmt.Fprintln(output, "}")
|
|
fmt.Fprintln(output)
|
|
}
|
|
|
|
func center(s string, w int) string {
|
|
w -= len(s)
|
|
l := w / 2
|
|
r := l
|
|
if l+r < w {
|
|
r++
|
|
}
|
|
return strings.Repeat(" ", l) + s + strings.Repeat(" ", r)
|
|
}
|
|
|
|
func inspector(structs *[]structInfo) func(ast.Node) bool {
|
|
return func(n ast.Node) bool {
|
|
switch n := n.(type) {
|
|
case *ast.TypeSpec:
|
|
switch t := n.Type.(type) {
|
|
case *ast.StructType:
|
|
name := n.Name.Name
|
|
fs := handleStruct(t)
|
|
*structs = append(*structs, structInfo{name, fs})
|
|
}
|
|
return false
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
outputFile := flag.String("o", "", "Output file, blank for stdout")
|
|
flag.Parse()
|
|
fname := flag.Arg(0)
|
|
|
|
fset := token.NewFileSet()
|
|
f, err := parser.ParseFile(fset, fname, nil, parser.ParseComments)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
var structs []structInfo
|
|
i := inspector(&structs)
|
|
ast.Inspect(f, i)
|
|
|
|
buf := new(bytes.Buffer)
|
|
headerTpl.Execute(buf, map[string]string{"Package": f.Name.Name})
|
|
for _, s := range structs {
|
|
fmt.Fprintf(buf, "\n/*\n\n")
|
|
generateDiagram(buf, s)
|
|
generateXdr(buf, s)
|
|
fmt.Fprintf(buf, "*/\n")
|
|
generateCode(buf, s)
|
|
}
|
|
|
|
bs, err := format.Source(buf.Bytes())
|
|
if err != nil {
|
|
log.Print(buf.String())
|
|
log.Fatal(err)
|
|
}
|
|
|
|
var output io.Writer = os.Stdout
|
|
if *outputFile != "" {
|
|
fd, err := os.Create(*outputFile)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
output = fd
|
|
}
|
|
output.Write(bs)
|
|
}
|