mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 12:15:52 +00:00
First iteration of go2idl.
* Add support for pointers, map keys, more builtins, Alias types * Support for packages & imports * Add helper functions to types * change generation interface * fix naming/ordering * SnippetWriter for templates * Naming systems * Use the standard packages for import resolution * Documentation
This commit is contained in:
parent
a5a917603c
commit
5ac9b16ade
56
cmd/libs/go2idl/generator/default_generator.go
Normal file
56
cmd/libs/go2idl/generator/default_generator.go
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package generator
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"k8s.io/kubernetes/cmd/libs/go2idl/namer"
|
||||
"k8s.io/kubernetes/cmd/libs/go2idl/types"
|
||||
)
|
||||
|
||||
// DefaultGen implements a do-nothing Generator.
|
||||
//
|
||||
// It can be used to implement static content files.
|
||||
type DefaultGen struct {
|
||||
// OptionalName, if present, will be used for the generator's name, and
|
||||
// the filename (with ".go" appended).
|
||||
OptionalName string
|
||||
|
||||
// OptionalBody, if present, will be used as the return from the "Init"
|
||||
// method. This causes it to be static content for the entire file if
|
||||
// no other generator touches the file.
|
||||
OptionalBody []byte
|
||||
}
|
||||
|
||||
func (d DefaultGen) Name() string { return d.OptionalName }
|
||||
func (d DefaultGen) Filter(*Context, *types.Type) bool { return true }
|
||||
func (d DefaultGen) Namers(*Context) namer.NameSystems { return nil }
|
||||
func (d DefaultGen) Imports(*Context) []string { return []string{} }
|
||||
func (d DefaultGen) PackageVars(*Context) []string { return []string{} }
|
||||
func (d DefaultGen) PackageConsts(*Context) []string { return []string{} }
|
||||
func (d DefaultGen) GenerateType(*Context, *types.Type, io.Writer) error { return nil }
|
||||
func (d DefaultGen) Filename() string { return d.OptionalName + ".go" }
|
||||
|
||||
func (d DefaultGen) Init(c *Context, w io.Writer) error {
|
||||
_, err := w.Write(d.OptionalBody)
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
_ = Generator(DefaultGen{})
|
||||
)
|
72
cmd/libs/go2idl/generator/default_package.go
Normal file
72
cmd/libs/go2idl/generator/default_package.go
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package generator
|
||||
|
||||
import (
|
||||
"k8s.io/kubernetes/cmd/libs/go2idl/types"
|
||||
)
|
||||
|
||||
// DefaultPackage contains a default implentation of Package.
|
||||
type DefaultPackage struct {
|
||||
// Short name of package, used in the "package xxxx" line.
|
||||
PackageName string
|
||||
// Import path of the package, and the location on disk of the package.
|
||||
PackagePath string
|
||||
|
||||
// Emitted at the top of every file.
|
||||
HeaderText []byte
|
||||
|
||||
// Emitted only for a "doc.go" file; appended to the HeaderText for
|
||||
// that file.
|
||||
PackageDocumentation []byte
|
||||
|
||||
// If non-nil, will be called on "Generators"; otherwise, the static
|
||||
// list will be used. So you should set only one of these two fields.
|
||||
GeneratorFunc func(*Context) []Generator
|
||||
GeneratorList []Generator
|
||||
|
||||
// Optional; filters the types exposed to the generators.
|
||||
FilterFunc func(*Context, *types.Type) bool
|
||||
}
|
||||
|
||||
func (d *DefaultPackage) Name() string { return d.PackageName }
|
||||
func (d *DefaultPackage) Path() string { return d.PackagePath }
|
||||
|
||||
func (d *DefaultPackage) Filter(c *Context, t *types.Type) bool {
|
||||
if d.FilterFunc != nil {
|
||||
return d.FilterFunc(c, t)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *DefaultPackage) Generators(c *Context) []Generator {
|
||||
if d.GeneratorFunc != nil {
|
||||
return d.GeneratorFunc(c)
|
||||
}
|
||||
return d.GeneratorList
|
||||
}
|
||||
|
||||
func (d *DefaultPackage) Header(filename string) []byte {
|
||||
if filename == "doc.go" {
|
||||
return append(d.HeaderText, d.PackageDocumentation...)
|
||||
}
|
||||
return d.HeaderText
|
||||
}
|
||||
|
||||
var (
|
||||
_ = Package(&DefaultPackage{})
|
||||
)
|
31
cmd/libs/go2idl/generator/doc.go
Normal file
31
cmd/libs/go2idl/generator/doc.go
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package generator defines an interface for code generators to implement.
|
||||
//
|
||||
// To use this package, you'll implement the "Package" and "Generator"
|
||||
// interfaces; you'll call NewContext to load up the types you want to work
|
||||
// with, and then you'll call one or more of the Execute methods. See the
|
||||
// interface definitions for explanations. All output will have gofmt called on
|
||||
// it automatically, so you do not need to worry about generating correct
|
||||
// indentation.
|
||||
//
|
||||
// This package also exposes SnippetWriter. SnippetWriter reduces to a minimum
|
||||
// the boilerplate involved in setting up a template from go's text/template
|
||||
// package. Additionally, all naming systems in the Context will be added as
|
||||
// functions to the parsed template, so that they can be called directly from
|
||||
// your templates!
|
||||
package generator
|
50
cmd/libs/go2idl/generator/error_tracker.go
Normal file
50
cmd/libs/go2idl/generator/error_tracker.go
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package generator
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// ErrorTracker tracks errors to the underlying writer, so that you can ignore
|
||||
// them until you're ready to return.
|
||||
type ErrorTracker struct {
|
||||
io.Writer
|
||||
err error
|
||||
}
|
||||
|
||||
// NewErrorTracker makes a new error tracker; note that it implements io.Writer.
|
||||
func NewErrorTracker(w io.Writer) *ErrorTracker {
|
||||
return &ErrorTracker{Writer: w}
|
||||
}
|
||||
|
||||
// Write intercepts calls to Write.
|
||||
func (et *ErrorTracker) Write(p []byte) (n int, err error) {
|
||||
if et.err != nil {
|
||||
return 0, et.err
|
||||
}
|
||||
n, err = et.Writer.Write(p)
|
||||
if err != nil {
|
||||
et.err = err
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Error returns nil if no error has occurred, otherwise it returns the error.
|
||||
func (et *ErrorTracker) Error() error {
|
||||
return et.err
|
||||
}
|
226
cmd/libs/go2idl/generator/execute.go
Normal file
226
cmd/libs/go2idl/generator/execute.go
Normal file
@ -0,0 +1,226 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package generator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubernetes/cmd/libs/go2idl/namer"
|
||||
"k8s.io/kubernetes/cmd/libs/go2idl/types"
|
||||
)
|
||||
|
||||
// ExecutePackages runs the generators for every package in 'packages'. 'outDir'
|
||||
// is the base directory in which to place all the generated packages; it
|
||||
// should be a physical path on disk, not an import path. e.g.:
|
||||
// /path/to/home/path/to/gopath/src/
|
||||
// Each package has its import path already, this will be appended to 'outDir'.
|
||||
func (c *Context) ExecutePackages(outDir string, packages Packages) error {
|
||||
for _, p := range packages {
|
||||
if err := c.ExecutePackage(outDir, p); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type file struct {
|
||||
name string
|
||||
packageName string
|
||||
header []byte
|
||||
imports map[string]struct{}
|
||||
vars bytes.Buffer
|
||||
consts bytes.Buffer
|
||||
body bytes.Buffer
|
||||
}
|
||||
|
||||
func (f *file) assembleToFile(pathname string) error {
|
||||
log.Printf("Assembling file %q", pathname)
|
||||
destFile, err := os.Create(pathname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
b := &bytes.Buffer{}
|
||||
et := NewErrorTracker(b)
|
||||
f.assemble(et)
|
||||
if et.Error() != nil {
|
||||
return et.Error()
|
||||
}
|
||||
if formatted, err := format.Source(b.Bytes()); err != nil {
|
||||
log.Printf("Warning: unable to run gofmt on %q (%v).", pathname, err)
|
||||
_, err = destFile.Write(b.Bytes())
|
||||
return err
|
||||
} else {
|
||||
_, err = destFile.Write(formatted)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (f *file) assemble(w io.Writer) {
|
||||
w.Write(f.header)
|
||||
fmt.Fprintf(w, "package %v\n\n", f.packageName)
|
||||
|
||||
if len(f.imports) > 0 {
|
||||
fmt.Fprint(w, "import (\n")
|
||||
for i := range f.imports {
|
||||
if strings.Contains(i, "\"") {
|
||||
// they included quotes, or are using the
|
||||
// `name "path/to/pkg"` format.
|
||||
fmt.Fprintf(w, "\t%s\n", i)
|
||||
} else {
|
||||
fmt.Fprintf(w, "\t%q\n", i)
|
||||
}
|
||||
}
|
||||
fmt.Fprint(w, ")\n\n")
|
||||
}
|
||||
|
||||
if f.vars.Len() > 0 {
|
||||
fmt.Fprint(w, "var (\n")
|
||||
w.Write(f.vars.Bytes())
|
||||
fmt.Fprint(w, ")\n\n")
|
||||
}
|
||||
|
||||
if f.consts.Len() > 0 {
|
||||
fmt.Fprint(w, "const (\n")
|
||||
w.Write(f.consts.Bytes())
|
||||
fmt.Fprint(w, ")\n\n")
|
||||
}
|
||||
|
||||
w.Write(f.body.Bytes())
|
||||
}
|
||||
|
||||
// format should be one line only, and not end with \n.
|
||||
func addIndentHeaderComment(b *bytes.Buffer, format string, args ...interface{}) {
|
||||
if b.Len() > 0 {
|
||||
fmt.Fprintf(b, "\n\t// "+format+"\n", args...)
|
||||
} else {
|
||||
fmt.Fprintf(b, "\t// "+format+"\n", args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) filteredBy(f func(*Context, *types.Type) bool) *Context {
|
||||
c2 := *c
|
||||
c2.Order = []*types.Type{}
|
||||
for _, t := range c.Order {
|
||||
if f(c, t) {
|
||||
c2.Order = append(c2.Order, t)
|
||||
}
|
||||
}
|
||||
return &c2
|
||||
}
|
||||
|
||||
// make a new context; inheret c.Namers, but add on 'namers'. In case of a name
|
||||
// collision, the namer in 'namers' wins.
|
||||
func (c *Context) addNameSystems(namers namer.NameSystems) *Context {
|
||||
if namers == nil {
|
||||
return c
|
||||
}
|
||||
c2 := *c
|
||||
// Copy the existing name systems so we don't corrupt a parent context
|
||||
c2.Namers = namer.NameSystems{}
|
||||
for k, v := range c.Namers {
|
||||
c2.Namers[k] = v
|
||||
}
|
||||
|
||||
for name, namer := range namers {
|
||||
c2.Namers[name] = namer
|
||||
}
|
||||
return &c2
|
||||
}
|
||||
|
||||
// ExecutePackage executes a single package. 'outDir' is the base directory in
|
||||
// which to place the package; it should be a physical path on disk, not an
|
||||
// import path. e.g.: '/path/to/home/path/to/gopath/src/' The package knows its
|
||||
// import path already, this will be appended to 'outDir'.
|
||||
func (c *Context) ExecutePackage(outDir string, p Package) error {
|
||||
path := filepath.Join(outDir, p.Path())
|
||||
log.Printf("Executing package %v into %v", p.Name(), path)
|
||||
// Filter out any types the *package* doesn't care about.
|
||||
packageContext := c.filteredBy(p.Filter)
|
||||
os.MkdirAll(path, 0755)
|
||||
files := map[string]*file{}
|
||||
for _, g := range p.Generators(packageContext) {
|
||||
// Filter out types the *generator* doesn't care about.
|
||||
genContext := packageContext.filteredBy(g.Filter)
|
||||
// Now add any extra name systems defined by this generator
|
||||
genContext = genContext.addNameSystems(g.Namers(genContext))
|
||||
|
||||
f := files[g.Filename()]
|
||||
if f == nil {
|
||||
// This is the first generator to reference this file, so start it.
|
||||
f = &file{
|
||||
name: g.Filename(),
|
||||
packageName: p.Name(),
|
||||
header: p.Header(g.Filename()),
|
||||
imports: map[string]struct{}{},
|
||||
}
|
||||
files[f.name] = f
|
||||
}
|
||||
if vars := g.PackageVars(genContext); len(vars) > 0 {
|
||||
addIndentHeaderComment(&f.vars, "Package-wide variables from generator %q.", g.Name())
|
||||
for _, v := range vars {
|
||||
if _, err := fmt.Fprintf(&f.vars, "\t%s\n", v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if consts := g.PackageVars(genContext); len(consts) > 0 {
|
||||
addIndentHeaderComment(&f.consts, "Package-wide consts from generator %q.", g.Name())
|
||||
for _, v := range consts {
|
||||
if _, err := fmt.Fprintf(&f.consts, "\t%s\n", v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := genContext.executeBody(&f.body, g); err != nil {
|
||||
return err
|
||||
}
|
||||
if imports := g.Imports(genContext); len(imports) > 0 {
|
||||
for _, i := range imports {
|
||||
f.imports[i] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
if err := f.assembleToFile(filepath.Join(path, f.name)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Context) executeBody(w io.Writer, generator Generator) error {
|
||||
et := NewErrorTracker(w)
|
||||
if err := generator.Init(c, et); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, t := range c.Order {
|
||||
if err := generator.GenerateType(c, t, et); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return et.Error()
|
||||
}
|
160
cmd/libs/go2idl/generator/generator.go
Normal file
160
cmd/libs/go2idl/generator/generator.go
Normal file
@ -0,0 +1,160 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package generator
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"k8s.io/kubernetes/cmd/libs/go2idl/namer"
|
||||
"k8s.io/kubernetes/cmd/libs/go2idl/parser"
|
||||
"k8s.io/kubernetes/cmd/libs/go2idl/types"
|
||||
)
|
||||
|
||||
// Package contains the contract for generating a package.
|
||||
type Package interface {
|
||||
// Name returns the package short name.
|
||||
Name() string
|
||||
// Path returns the package import path.
|
||||
Path() string
|
||||
|
||||
// Filter should return true if this package cares about this type.
|
||||
// Otherwise, this type will be ommitted from the type ordering for
|
||||
// this package.
|
||||
Filter(*Context, *types.Type) bool
|
||||
|
||||
// Header should return a header for the file, including comment markers.
|
||||
// Useful for copyright notices and doc strings. Include an
|
||||
// autogeneration notice! Do not include the "package x" line.
|
||||
Header(filename string) []byte
|
||||
|
||||
// Generators returns the list of generators for this package. It is
|
||||
// allowed for more than one generator to write to the same file.
|
||||
// A Context is passed in case the list of generators depends on the
|
||||
// input types.
|
||||
Generators(*Context) []Generator
|
||||
}
|
||||
|
||||
// Packages is a list of packages to generate.
|
||||
type Packages []Package
|
||||
|
||||
// Generator is the contract for anything that wants to do auto-generation.
|
||||
// It's expected that the io.Writers passed to the below functions will be
|
||||
// ErrorTrackers; this allows implementations to not check for io errors,
|
||||
// making more readable code.
|
||||
//
|
||||
// The call order for the functions that take a Context is:
|
||||
// 1. Filter() // Subsequent calls see only types that pass this.
|
||||
// 2. Namers() // Subsequent calls see the namers provided by this.
|
||||
// 3. PackageVars()
|
||||
// 4. PackageConsts()
|
||||
// 5. Init()
|
||||
// 6. GenerateType() // Called N times, once per type in the context's Order.
|
||||
// 7. Imports()
|
||||
//
|
||||
// You may have multiple generators for the same file.
|
||||
type Generator interface {
|
||||
// The name of this generator. Will be included in generated comments.
|
||||
Name() string
|
||||
|
||||
// Filter should return true if this generator cares about this type.
|
||||
// (otherwise, GenerateType will not be called.)
|
||||
//
|
||||
// Filter is called before any of the generator's other functions;
|
||||
// subsequent calls will get a context with only the types that passed
|
||||
// this filter.
|
||||
Filter(*Context, *types.Type) bool
|
||||
|
||||
// If this generator needs special namers, return them here. These will
|
||||
// override the original namers in the context if there is a collision.
|
||||
// You may return nil if you don't need special names. These names will
|
||||
// be available in the context passed to the rest of the generator's
|
||||
// functions.
|
||||
//
|
||||
// A use case for this is to return a namer that tracks imports.
|
||||
Namers(*Context) namer.NameSystems
|
||||
|
||||
// Init should write an init function, and any other content that's not
|
||||
// generated per-type. (It's not intended for generator specific
|
||||
// initialization! Do that when your Package constructs the
|
||||
// Generators.)
|
||||
Init(*Context, io.Writer) error
|
||||
|
||||
// PackageVars should emit an array of variable lines. They will be
|
||||
// placed in a var ( ... ) block. There's no need to include a leading
|
||||
// \t or trailing \n.
|
||||
PackageVars(*Context) []string
|
||||
|
||||
// PackageConsts should emit an array of constant lines. They will be
|
||||
// placed in a const ( ... ) block. There's no need to include a leading
|
||||
// \t or trailing \n.
|
||||
PackageConsts(*Context) []string
|
||||
|
||||
// GenerateType should emit the code for a particular type.
|
||||
GenerateType(*Context, *types.Type, io.Writer) error
|
||||
|
||||
// Imports should return a list of necessary imports. They will be
|
||||
// formatted correctly. You do not need to include quotation marks,
|
||||
// return only the package name; alternatively, you can also return
|
||||
// imports in the format `name "path/to/pkg"`. Imports will be called
|
||||
// after Init, PackageVars, PackageConsts, and GenerateType, to allow
|
||||
// you to keep track of what imports you actually need.
|
||||
Imports(*Context) []string
|
||||
|
||||
// Preferred file name of this generator, not including a path. It is
|
||||
// allowed for multiple generators to use the same filename, but it's
|
||||
// up to you to make sure they don't have colliding import names.
|
||||
// TODO: provide per-file import tracking, removing the requirement
|
||||
// that generators coordinate..
|
||||
Filename() string
|
||||
}
|
||||
|
||||
// Context is global context for individual generators to consume.
|
||||
type Context struct {
|
||||
// A map from the naming system to the names for that system. E.g., you
|
||||
// might have public names and several private naming systems.
|
||||
Namers namer.NameSystems
|
||||
|
||||
// All the types, in case you want to look up something.
|
||||
Universe types.Universe
|
||||
|
||||
// The canonical ordering of the types (will be filtered by both the
|
||||
// Package's and Generator's Filter methods).
|
||||
Order []*types.Type
|
||||
}
|
||||
|
||||
// NewContext generates a context from the given builder, naming systems, and
|
||||
// the naming system you wish to construct the canonical ordering from.
|
||||
func NewContext(b *parser.Builder, nameSystems namer.NameSystems, canonicalOrderName string) (*Context, error) {
|
||||
u, err := b.FindTypes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := &Context{
|
||||
Namers: namer.NameSystems{},
|
||||
Universe: u,
|
||||
}
|
||||
|
||||
for name, systemNamer := range nameSystems {
|
||||
c.Namers[name] = systemNamer
|
||||
if name == canonicalOrderName {
|
||||
orderer := namer.Orderer{systemNamer}
|
||||
c.Order = orderer.Order(u)
|
||||
}
|
||||
}
|
||||
return c, nil
|
||||
}
|
97
cmd/libs/go2idl/generator/import_tracker.go
Normal file
97
cmd/libs/go2idl/generator/import_tracker.go
Normal file
@ -0,0 +1,97 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package generator
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubernetes/cmd/libs/go2idl/types"
|
||||
)
|
||||
|
||||
// ImportTracker may be passed to a namer.RawNamer, to track the imports needed
|
||||
// for the types it names.
|
||||
//
|
||||
// TODO: pay attention to the package name (instead of renaming every package).
|
||||
// TODO: Figure out the best way to make names for packages that collide.
|
||||
type ImportTracker struct {
|
||||
pathToName map[string]string
|
||||
// forbidden names are in here. (e.g. "go" is a directory in which
|
||||
// there is code, but "go" is not a legal name for a package, so we put
|
||||
// it here to prevent us from naming any package "go")
|
||||
nameToPath map[string]string
|
||||
}
|
||||
|
||||
func NewImportTracker(types ...*types.Type) *ImportTracker {
|
||||
tracker := &ImportTracker{
|
||||
pathToName: map[string]string{},
|
||||
nameToPath: map[string]string{
|
||||
"go": "",
|
||||
// Add other forbidden keywords that also happen to be
|
||||
// package names here.
|
||||
},
|
||||
}
|
||||
tracker.AddTypes(types...)
|
||||
return tracker
|
||||
}
|
||||
|
||||
func (tracker *ImportTracker) AddTypes(types ...*types.Type) {
|
||||
for _, t := range types {
|
||||
tracker.AddType(t)
|
||||
}
|
||||
}
|
||||
func (tracker *ImportTracker) AddType(t *types.Type) {
|
||||
path := t.Name.Package
|
||||
if path == "" {
|
||||
return
|
||||
}
|
||||
if _, ok := tracker.pathToName[path]; ok {
|
||||
return
|
||||
}
|
||||
dirs := strings.Split(path, string(filepath.Separator))
|
||||
for n := len(dirs) - 1; n >= 0; n-- {
|
||||
// TODO: bikeshed about whether it's more readable to have an
|
||||
// _, something else, or nothing between directory names.
|
||||
name := strings.Join(dirs[n:], "_")
|
||||
// These characters commonly appear in import paths for go
|
||||
// packages, but aren't legal go names. So we'll sanitize.
|
||||
name = strings.Replace(name, ".", "_", -1)
|
||||
name = strings.Replace(name, "-", "_", -1)
|
||||
if _, found := tracker.nameToPath[name]; found {
|
||||
// This name collides with some other package
|
||||
continue
|
||||
}
|
||||
tracker.nameToPath[name] = path
|
||||
tracker.pathToName[path] = name
|
||||
return
|
||||
}
|
||||
panic("can't find import for " + path)
|
||||
}
|
||||
|
||||
func (tracker *ImportTracker) ImportLines() []string {
|
||||
out := []string{}
|
||||
for path, name := range tracker.pathToName {
|
||||
out = append(out, name+" \""+path+"\"")
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// LocalNameOf returns the name you would use to refer to the package at the
|
||||
// specified path within the body of a file.
|
||||
func (tracker *ImportTracker) LocalNameOf(path string) string {
|
||||
return tracker.pathToName[path]
|
||||
}
|
122
cmd/libs/go2idl/generator/snippet_writer.go
Normal file
122
cmd/libs/go2idl/generator/snippet_writer.go
Normal file
@ -0,0 +1,122 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package generator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// SnippetWriter is an attempt to make the template library usable.
|
||||
// Methods are chainable, and you don't have to check Error() until you're all
|
||||
// done.
|
||||
type SnippetWriter struct {
|
||||
w io.Writer
|
||||
context *Context
|
||||
// Left & right delimiters. text/template defaults to "{{" and "}}"
|
||||
// which is totally unusable for go code based templates.
|
||||
left, right string
|
||||
funcMap template.FuncMap
|
||||
err error
|
||||
}
|
||||
|
||||
// w is the destination; left and right are the delimiters; @ and $ are both
|
||||
// reasonable choices.
|
||||
//
|
||||
// c is used to make a function for every naming system, to which you can pass
|
||||
// a type and get the corresponding name.
|
||||
func NewSnippetWriter(w io.Writer, c *Context, left, right string) *SnippetWriter {
|
||||
sw := &SnippetWriter{
|
||||
w: w,
|
||||
context: c,
|
||||
left: left,
|
||||
right: right,
|
||||
funcMap: template.FuncMap{},
|
||||
}
|
||||
for name, namer := range c.Namers {
|
||||
sw.funcMap[name] = namer.Name
|
||||
}
|
||||
return sw
|
||||
}
|
||||
|
||||
// Do parses format and runs args through it. You can have arbitrary logic in
|
||||
// the format (see the text/template documentation), but consider running many
|
||||
// short templaces, with ordinary go logic in between--this may be more
|
||||
// readable. Do is chainable. Any error causes every other call to do to be
|
||||
// ignored, and the error will be returned by Error(). So you can check it just
|
||||
// once, at the end of your function.
|
||||
//
|
||||
// 'args' can be quite literally anything; read the text/template documentation
|
||||
// for details. Maps and structs work particularly nicely. Conveniently, the
|
||||
// types package is designed to have structs that are easily referencable from
|
||||
// the template language.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// sw := generator.NewSnippetWriter(outBuffer, context, "$", "$")
|
||||
// sw.Do(`The public type name is: $.type|public$`, map[string]interface{}{"type": t})
|
||||
// return sw.Error()
|
||||
//
|
||||
// Where:
|
||||
// * "$" starts a template directive
|
||||
// * "." references the entire thing passed as args
|
||||
// * "type" therefore sees a map and looks up the key "type"
|
||||
// * "|" means "pass the thing on the left to the thing on the right"
|
||||
// * "public" is the name of a naming system, so the SnippetWriter has given
|
||||
// the template a function called "public" that takes a *types.Type and
|
||||
// returns the naming system's name. E.g., if the type is "string" this might
|
||||
// return "String".
|
||||
// * the second "$" ends the template directive.
|
||||
//
|
||||
// The map is actually not necessary. The below does the same thing:
|
||||
//
|
||||
// sw.Do(`The public type name is: $.|public$`, t)
|
||||
//
|
||||
// You may or may not find it more readable to use the map with a descriptive
|
||||
// key, but if you want to pass more than one arg, the map or a custom struct
|
||||
// becomes a requirement. You can do arbitrary logic inside these templates,
|
||||
// but you should consider doing the logic in go and stitching them together
|
||||
// for the sake of your readers.
|
||||
func (s *SnippetWriter) Do(format string, args interface{}) *SnippetWriter {
|
||||
if s.err != nil {
|
||||
return s
|
||||
}
|
||||
// Name the template by source file:line so it can be found when
|
||||
// there's an error.
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
tmpl, err := template.
|
||||
New(fmt.Sprintf("%s:%d", file, line)).
|
||||
Delims(s.left, s.right).
|
||||
Funcs(s.funcMap).
|
||||
Parse(format)
|
||||
if err != nil {
|
||||
s.err = err
|
||||
return s
|
||||
}
|
||||
err = tmpl.Execute(s.w, args)
|
||||
if err != nil {
|
||||
s.err = err
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Error returns any encountered error.
|
||||
func (s *SnippetWriter) Error() error {
|
||||
return s.err
|
||||
}
|
88
cmd/libs/go2idl/generator/snippet_writer_test.go
Normal file
88
cmd/libs/go2idl/generator/snippet_writer_test.go
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package generator_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/cmd/libs/go2idl/generator"
|
||||
"k8s.io/kubernetes/cmd/libs/go2idl/namer"
|
||||
"k8s.io/kubernetes/cmd/libs/go2idl/parser"
|
||||
)
|
||||
|
||||
func construct(t *testing.T, files map[string]string) *generator.Context {
|
||||
b := parser.New()
|
||||
for name, src := range files {
|
||||
if err := b.AddFile(name, []byte(src)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
c, err := generator.NewContext(b, namer.NameSystems{
|
||||
"public": namer.NewPublicNamer(0),
|
||||
"private": namer.NewPrivateNamer(0),
|
||||
}, "public")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func TestSnippetWriter(t *testing.T) {
|
||||
var structTest = map[string]string{
|
||||
"base/foo/proto/foo.go": `
|
||||
package foo
|
||||
|
||||
// Blah is a test.
|
||||
// A test, I tell you.
|
||||
type Blah struct {
|
||||
// A is the first field.
|
||||
A int64 ` + "`" + `json:"a"` + "`" + `
|
||||
|
||||
// B is the second field.
|
||||
// Multiline comments work.
|
||||
B string ` + "`" + `json:"b"` + "`" + `
|
||||
}
|
||||
`,
|
||||
}
|
||||
|
||||
c := construct(t, structTest)
|
||||
b := &bytes.Buffer{}
|
||||
err := generator.NewSnippetWriter(b, c, "$", "$").
|
||||
Do("$.|public$$.|private$", c.Order[0]).
|
||||
Error()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %v", err)
|
||||
}
|
||||
if e, a := "Blahblah", b.String(); e != a {
|
||||
t.Errorf("Expected %q, got %q", e, a)
|
||||
}
|
||||
|
||||
err = generator.NewSnippetWriter(b, c, "$", "$").
|
||||
Do("$.|public", c.Order[0]).
|
||||
Error()
|
||||
if err == nil {
|
||||
t.Errorf("expected error on invalid template")
|
||||
} else {
|
||||
// Dear reader, I apologize for making the worst change
|
||||
// detection test in the history of ever.
|
||||
if e, a := "snippet_writer_test.go:78", err.Error(); !strings.Contains(a, e) {
|
||||
t.Errorf("Expected %q but didn't find it in %q", e, a)
|
||||
}
|
||||
}
|
||||
}
|
31
cmd/libs/go2idl/namer/doc.go
Normal file
31
cmd/libs/go2idl/namer/doc.go
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package namer has support for making different type naming systems.
|
||||
//
|
||||
// This is because sometimes you want to refer to the literal type, sometimes
|
||||
// you want to make a name for the thing you're generating, and you want to
|
||||
// make the name based on the type. For example, if you have `type foo string`,
|
||||
// you want to be able to generate something like `func FooPrinter(f *foo) {
|
||||
// Print(string(*f)) }`; that is, you want to refer to a public name, a literal
|
||||
// name, and the underlying literal name.
|
||||
//
|
||||
// This package supports the idea of a "Namer" and a set of "NameSystems" to
|
||||
// support these use cases.
|
||||
//
|
||||
// Additionally, a "RawNamer" can optionally keep track of what needs to be
|
||||
// imported.
|
||||
package namer
|
347
cmd/libs/go2idl/namer/namer.go
Normal file
347
cmd/libs/go2idl/namer/namer.go
Normal file
@ -0,0 +1,347 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package namer
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubernetes/cmd/libs/go2idl/types"
|
||||
)
|
||||
|
||||
// NewPublicNamer is a helper function that returns a namer that makes
|
||||
// CamelCase names. See the NameStrategy struct for an explanation of the
|
||||
// arguments to this constructor.
|
||||
func NewPublicNamer(prependPackageNames int, ignoreWords ...string) *NameStrategy {
|
||||
n := &NameStrategy{
|
||||
Join: Joiner(IC, IC),
|
||||
IgnoreWords: map[string]bool{},
|
||||
PrependPackageNames: prependPackageNames,
|
||||
}
|
||||
for _, w := range ignoreWords {
|
||||
n.IgnoreWords[w] = true
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// NewPrivateNamer is a helper function that returns a namer that makes
|
||||
// camelCase names. See the NameStrategy struct for an explanation of the
|
||||
// arguments to this constructor.
|
||||
func NewPrivateNamer(prependPackageNames int, ignoreWords ...string) *NameStrategy {
|
||||
n := &NameStrategy{
|
||||
Join: Joiner(IL, IC),
|
||||
IgnoreWords: map[string]bool{},
|
||||
PrependPackageNames: prependPackageNames,
|
||||
}
|
||||
for _, w := range ignoreWords {
|
||||
n.IgnoreWords[w] = true
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// NewRawNamer will return a Namer that makes a name by which you would
|
||||
// directly refer to a type, optionally keeping track of the import paths
|
||||
// necessary to reference the names it provides. Tracker may be nil.
|
||||
//
|
||||
// For example, if the type is map[string]int, a raw namer will literally
|
||||
// return "map[string]int".
|
||||
//
|
||||
// Or if the type, in package foo, is "type Bar struct { ... }", then the raw
|
||||
// namer will return "foo.Bar" as the name of the type, and if 'tracker' was
|
||||
// not nil, will record that package foo needs to be imported.
|
||||
func NewRawNamer(tracker ImportTracker) *rawNamer {
|
||||
return &rawNamer{tracker: tracker}
|
||||
}
|
||||
|
||||
// Names is a map from Type to name, as defined by some Namer.
|
||||
type Names map[*types.Type]string
|
||||
|
||||
// Namer takes a type, and assigns a name.
|
||||
//
|
||||
// The purpose of this complexity is so that you can assign coherent
|
||||
// side-by-side systems of names for the types. For example, you might want a
|
||||
// public interface, a private implementation struct, and also to reference
|
||||
// literally the type name.
|
||||
//
|
||||
// Note that it is safe to call your own Name() function recursively to find
|
||||
// the names of keys, elements, etc. This is because anonymous types can't have
|
||||
// cycles in their names, and named types don't require the sort of recursion
|
||||
// that would be problematic.
|
||||
type Namer interface {
|
||||
Name(*types.Type) string
|
||||
}
|
||||
|
||||
// NameSystems is a map of a system name to a namer for that system.
|
||||
type NameSystems map[string]Namer
|
||||
|
||||
// NameStrategy is a general Namer. The easiest way to use it is to copy the
|
||||
// Public/PrivateNamer variables, and modify the members you wish to change.
|
||||
//
|
||||
// The Name method produces a name for the given type, of the forms:
|
||||
// Anonymous types: <Prefix><Type description><Suffix>
|
||||
// Named types: <Prefix><Optional Prepended Package name(s)><Original name><Suffix>
|
||||
//
|
||||
// In all cases, every part of the name is run through the capitalization
|
||||
// functions.
|
||||
//
|
||||
// The IgnoreWords map can be set if you have directory names that are
|
||||
// semantically meaningless for naming purposes, e.g. "proto".
|
||||
//
|
||||
// Prefix and Suffix can be used to disambiguate parallel systems of type
|
||||
// names. For example, if you want to generate an interface and an
|
||||
// implementation, you might want to suffix one with "Interface" and the other
|
||||
// with "Implementation". Another common use-- if you want to generate private
|
||||
// types, and one of your source types could be "string", you can't use the
|
||||
// default lowercase private namer. You'll have to add a suffix or prefix.
|
||||
type NameStrategy struct {
|
||||
Prefix, Suffix string
|
||||
Join func(pre string, parts []string, post string) string
|
||||
|
||||
// Add non-meaningful package directory names here (e.g. "proto") and
|
||||
// they will be ignored.
|
||||
IgnoreWords map[string]bool
|
||||
|
||||
// If > 0, prepend exactly that many package directory names (or as
|
||||
// many as there are). Package names listed in "IgnoreWords" will be
|
||||
// ignored.
|
||||
//
|
||||
// For example, if Ignore words lists "proto" and type Foo is in
|
||||
// pkg/server/frobbing/proto, then a value of 1 will give a type name
|
||||
// of FrobbingFoo, 2 gives ServerFrobbingFoo, etc.
|
||||
PrependPackageNames int
|
||||
|
||||
// A cache of names thus far assigned by this namer.
|
||||
Names
|
||||
}
|
||||
|
||||
// IC ensures the first character is uppercase.
|
||||
func IC(in string) string {
|
||||
if in == "" {
|
||||
return in
|
||||
}
|
||||
return strings.ToUpper(in[:1]) + in[1:]
|
||||
}
|
||||
|
||||
// IL ensures the first character is lowercase.
|
||||
func IL(in string) string {
|
||||
if in == "" {
|
||||
return in
|
||||
}
|
||||
return strings.ToLower(in[:1]) + in[1:]
|
||||
}
|
||||
|
||||
// Joiner lets you specify functions that preprocess the various components of
|
||||
// a name before joining them. You can construct e.g. camelCase or CamelCase or
|
||||
// any other way of joining words. (See the IC and IL convenience functions.)
|
||||
func Joiner(first, others func(string) string) func(pre string, in []string, post string) string {
|
||||
return func(pre string, in []string, post string) string {
|
||||
tmp := []string{others(pre)}
|
||||
for i := range in {
|
||||
tmp = append(tmp, others(in[i]))
|
||||
}
|
||||
tmp = append(tmp, others(post))
|
||||
return first(strings.Join(tmp, ""))
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *NameStrategy) removePrefixAndSuffix(s string) string {
|
||||
// The join function may have changed capitalization.
|
||||
lowerIn := strings.ToLower(s)
|
||||
lowerP := strings.ToLower(ns.Prefix)
|
||||
lowerS := strings.ToLower(ns.Suffix)
|
||||
b, e := 0, len(s)
|
||||
if strings.HasPrefix(lowerIn, lowerP) {
|
||||
b = len(ns.Prefix)
|
||||
}
|
||||
if strings.HasSuffix(lowerIn, lowerS) {
|
||||
e -= len(ns.Suffix)
|
||||
}
|
||||
return s[b:e]
|
||||
}
|
||||
|
||||
var (
|
||||
importPathNameSanitizer = strings.NewReplacer("-", "_", ".", "")
|
||||
)
|
||||
|
||||
// filters out unwanted directory names and sanitizes remaining names.
|
||||
func (ns *NameStrategy) filterDirs(path string) []string {
|
||||
allDirs := strings.Split(path, string(filepath.Separator))
|
||||
dirs := make([]string, 0, len(allDirs))
|
||||
for _, p := range allDirs {
|
||||
if ns.IgnoreWords == nil || !ns.IgnoreWords[p] {
|
||||
dirs = append(dirs, importPathNameSanitizer.Replace(p))
|
||||
}
|
||||
}
|
||||
return dirs
|
||||
}
|
||||
|
||||
// See the comment on NameStrategy.
|
||||
func (ns *NameStrategy) Name(t *types.Type) string {
|
||||
if ns.Names == nil {
|
||||
ns.Names = Names{}
|
||||
}
|
||||
if s, ok := ns.Names[t]; ok {
|
||||
return s
|
||||
}
|
||||
|
||||
if t.Name.Package != "" {
|
||||
dirs := append(ns.filterDirs(t.Name.Package), t.Name.Name)
|
||||
i := ns.PrependPackageNames + 1
|
||||
dn := len(dirs)
|
||||
if i > dn {
|
||||
i = dn
|
||||
}
|
||||
name := ns.Join(ns.Prefix, dirs[dn-i:], ns.Suffix)
|
||||
ns.Names[t] = name
|
||||
return name
|
||||
}
|
||||
|
||||
// Only anonymous types remain.
|
||||
var name string
|
||||
switch t.Kind {
|
||||
case types.Builtin:
|
||||
name = ns.Join(ns.Prefix, []string{t.Name.Name}, ns.Suffix)
|
||||
case types.Map:
|
||||
name = ns.Join(ns.Prefix, []string{
|
||||
"Map",
|
||||
ns.removePrefixAndSuffix(ns.Name(t.Key)),
|
||||
"To",
|
||||
ns.removePrefixAndSuffix(ns.Name(t.Elem)),
|
||||
}, ns.Suffix)
|
||||
case types.Slice:
|
||||
name = ns.Join(ns.Prefix, []string{
|
||||
"Slice",
|
||||
ns.removePrefixAndSuffix(ns.Name(t.Elem)),
|
||||
}, ns.Suffix)
|
||||
case types.Pointer:
|
||||
name = ns.Join(ns.Prefix, []string{
|
||||
"Pointer",
|
||||
ns.removePrefixAndSuffix(ns.Name(t.Elem)),
|
||||
}, ns.Suffix)
|
||||
case types.Struct:
|
||||
names := []string{"Struct"}
|
||||
for _, m := range t.Members {
|
||||
names = append(names, ns.removePrefixAndSuffix(ns.Name(m.Type)))
|
||||
}
|
||||
name = ns.Join(ns.Prefix, names, ns.Suffix)
|
||||
// TODO: add types.Chan
|
||||
case types.Interface:
|
||||
// TODO: add to name test
|
||||
names := []string{"Interface"}
|
||||
for _, m := range t.Methods {
|
||||
// TODO: include function signature
|
||||
names = append(names, m.Name.Name)
|
||||
}
|
||||
name = ns.Join(ns.Prefix, names, ns.Suffix)
|
||||
case types.Func:
|
||||
// TODO: add to name test
|
||||
parts := []string{"Func"}
|
||||
for _, pt := range t.Signature.Parameters {
|
||||
parts = append(parts, ns.removePrefixAndSuffix(ns.Name(pt)))
|
||||
}
|
||||
parts = append(parts, "Returns")
|
||||
for _, rt := range t.Signature.Results {
|
||||
parts = append(parts, ns.removePrefixAndSuffix(ns.Name(rt)))
|
||||
}
|
||||
name = ns.Join(ns.Prefix, parts, ns.Suffix)
|
||||
default:
|
||||
name = "unnameable_" + string(t.Kind)
|
||||
}
|
||||
ns.Names[t] = name
|
||||
return name
|
||||
}
|
||||
|
||||
// ImportTracker allows a raw namer to keep track of the packages needed for
|
||||
// import. You can implement yourself or use the one in the generation package.
|
||||
type ImportTracker interface {
|
||||
AddType(*types.Type)
|
||||
LocalNameOf(packagePath string) string
|
||||
}
|
||||
|
||||
type rawNamer struct {
|
||||
tracker ImportTracker
|
||||
Names
|
||||
}
|
||||
|
||||
// Name makes a name the way you'd write it to literally refer to type t,
|
||||
// making ordinary assumptions about how you've imported t's package (or using
|
||||
// r.tracker to specifically track the package imports).
|
||||
func (r *rawNamer) Name(t *types.Type) string {
|
||||
if r.Names == nil {
|
||||
r.Names = Names{}
|
||||
}
|
||||
if name, ok := r.Names[t]; ok {
|
||||
return name
|
||||
}
|
||||
if t.Name.Package != "" {
|
||||
var name string
|
||||
if r.tracker != nil {
|
||||
r.tracker.AddType(t)
|
||||
name = r.tracker.LocalNameOf(t.Name.Package) + "." + t.Name.Name
|
||||
} else {
|
||||
name = filepath.Base(t.Name.Package) + "." + t.Name.Name
|
||||
}
|
||||
r.Names[t] = name
|
||||
return name
|
||||
}
|
||||
var name string
|
||||
switch t.Kind {
|
||||
case types.Builtin:
|
||||
name = t.Name.Name
|
||||
case types.Map:
|
||||
name = "map[" + r.Name(t.Key) + "]" + r.Name(t.Elem)
|
||||
case types.Slice:
|
||||
name = "[]" + r.Name(t.Elem)
|
||||
case types.Pointer:
|
||||
name = "*" + r.Name(t.Elem)
|
||||
case types.Struct:
|
||||
elems := []string{}
|
||||
for _, m := range t.Members {
|
||||
elems = append(elems, m.Name+" "+r.Name(m.Type))
|
||||
}
|
||||
name = "struct{" + strings.Join(elems, "; ") + "}"
|
||||
// TODO: add types.Chan
|
||||
case types.Interface:
|
||||
// TODO: add to name test
|
||||
elems := []string{}
|
||||
for _, m := range t.Methods {
|
||||
// TODO: include function signature
|
||||
elems = append(elems, m.Name.Name)
|
||||
}
|
||||
name = "interface{" + strings.Join(elems, "; ") + "}"
|
||||
case types.Func:
|
||||
// TODO: add to name test
|
||||
params := []string{}
|
||||
for _, pt := range t.Signature.Parameters {
|
||||
params = append(params, r.Name(pt))
|
||||
}
|
||||
results := []string{}
|
||||
for _, rt := range t.Signature.Results {
|
||||
results = append(results, r.Name(rt))
|
||||
}
|
||||
name = "func(" + strings.Join(params, ",") + ")"
|
||||
if len(results) == 1 {
|
||||
name += " " + results[0]
|
||||
} else if len(results) > 1 {
|
||||
name += " (" + strings.Join(results, ",") + ")"
|
||||
}
|
||||
default:
|
||||
name = "unnameable_" + string(t.Kind)
|
||||
}
|
||||
r.Names[t] = name
|
||||
return name
|
||||
}
|
84
cmd/libs/go2idl/namer/namer_test.go
Normal file
84
cmd/libs/go2idl/namer/namer_test.go
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package namer
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/cmd/libs/go2idl/types"
|
||||
)
|
||||
|
||||
func TestNameStrategy(t *testing.T) {
|
||||
u := types.Universe{}
|
||||
|
||||
// Add some types.
|
||||
base := u.Get(types.Name{"foo/bar", "Baz"})
|
||||
base.Kind = types.Struct
|
||||
|
||||
tmp := u.Get(types.Name{"", "[]bar.Baz"})
|
||||
tmp.Kind = types.Slice
|
||||
tmp.Elem = base
|
||||
|
||||
tmp = u.Get(types.Name{"", "map[string]bar.Baz"})
|
||||
tmp.Kind = types.Map
|
||||
tmp.Key = types.String
|
||||
tmp.Elem = base
|
||||
|
||||
tmp = u.Get(types.Name{"foo/other", "Baz"})
|
||||
tmp.Kind = types.Struct
|
||||
tmp.Members = []types.Member{{
|
||||
Embedded: true,
|
||||
Type: base,
|
||||
}}
|
||||
|
||||
u.Get(types.Name{"", "string"})
|
||||
|
||||
o := Orderer{NewPublicNamer(0)}
|
||||
order := o.Order(u)
|
||||
orderedNames := make([]string, len(order))
|
||||
for i, t := range order {
|
||||
orderedNames[i] = o.Name(t)
|
||||
}
|
||||
expect := []string{"Baz", "Baz", "MapStringToBaz", "SliceBaz", "String"}
|
||||
if e, a := expect, orderedNames; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Wanted %#v, got %#v", e, a)
|
||||
}
|
||||
|
||||
o = Orderer{NewRawNamer(nil)}
|
||||
order = o.Order(u)
|
||||
orderedNames = make([]string, len(order))
|
||||
for i, t := range order {
|
||||
orderedNames[i] = o.Name(t)
|
||||
}
|
||||
|
||||
expect = []string{"[]bar.Baz", "bar.Baz", "map[string]bar.Baz", "other.Baz", "string"}
|
||||
if e, a := expect, orderedNames; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Wanted %#v, got %#v", e, a)
|
||||
}
|
||||
|
||||
o = Orderer{NewPublicNamer(1)}
|
||||
order = o.Order(u)
|
||||
orderedNames = make([]string, len(order))
|
||||
for i, t := range order {
|
||||
orderedNames[i] = o.Name(t)
|
||||
}
|
||||
expect = []string{"BarBaz", "MapStringToBarBaz", "OtherBaz", "SliceBarBaz", "String"}
|
||||
if e, a := expect, orderedNames; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Wanted %#v, got %#v", e, a)
|
||||
}
|
||||
}
|
52
cmd/libs/go2idl/namer/order.go
Normal file
52
cmd/libs/go2idl/namer/order.go
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package namer
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"k8s.io/kubernetes/cmd/libs/go2idl/types"
|
||||
)
|
||||
|
||||
// Orderer produces an ordering of types given a Namer.
|
||||
type Orderer struct {
|
||||
Namer
|
||||
}
|
||||
|
||||
// Order assigns a name to every type, and returns a list sorted by those
|
||||
// names.
|
||||
func (o *Orderer) Order(u types.Universe) []*types.Type {
|
||||
list := tList{
|
||||
namer: o.Namer,
|
||||
}
|
||||
for _, p := range u {
|
||||
for _, t := range p.Types {
|
||||
list.types = append(list.types, t)
|
||||
}
|
||||
}
|
||||
sort.Sort(list)
|
||||
return list.types
|
||||
}
|
||||
|
||||
type tList struct {
|
||||
namer Namer
|
||||
types []*types.Type
|
||||
}
|
||||
|
||||
func (t tList) Len() int { return len(t.types) }
|
||||
func (t tList) Less(i, j int) bool { return t.namer.Name(t.types[i]) < t.namer.Name(t.types[j]) }
|
||||
func (t tList) Swap(i, j int) { t.types[i], t.types[j] = t.types[j], t.types[i] }
|
19
cmd/libs/go2idl/parser/doc.go
Normal file
19
cmd/libs/go2idl/parser/doc.go
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package parser provides code to parse go files, type-check them, extract the
|
||||
// types.
|
||||
package parser
|
497
cmd/libs/go2idl/parser/parse.go
Normal file
497
cmd/libs/go2idl/parser/parse.go
Normal file
@ -0,0 +1,497 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
tc "go/types"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubernetes/cmd/libs/go2idl/types"
|
||||
)
|
||||
|
||||
// Builder lets you add all the go files in all the packages that you care
|
||||
// about, then constructs the type source data.
|
||||
type Builder struct {
|
||||
context *build.Context
|
||||
buildInfo map[string]*build.Package
|
||||
|
||||
fset *token.FileSet
|
||||
// map of package id to list of parsed files
|
||||
parsed map[string][]*ast.File
|
||||
|
||||
// Set by makePackages, used by importer() and friends.
|
||||
pkgs map[string]*tc.Package
|
||||
|
||||
// Map of package path to whether the user requested it or it was from
|
||||
// an import.
|
||||
userRequested map[string]bool
|
||||
|
||||
// All comments from everywhere in every parsed file.
|
||||
endLineToCommentGroup map[fileLine]*ast.CommentGroup
|
||||
|
||||
// map of package to list of packages it imports.
|
||||
importGraph map[string]map[string]struct{}
|
||||
}
|
||||
|
||||
// key type for finding comments.
|
||||
type fileLine struct {
|
||||
file string
|
||||
line int
|
||||
}
|
||||
|
||||
// New constructs a new builder.
|
||||
func New() *Builder {
|
||||
c := build.Default
|
||||
if c.GOROOT == "" {
|
||||
if p, err := exec.Command("which", "go").CombinedOutput(); err == nil {
|
||||
// The returned string will have some/path/bin/go, so remove the last two elements.
|
||||
c.GOROOT = filepath.Dir(filepath.Dir(strings.Trim(string(p), "\n")))
|
||||
} else {
|
||||
fmt.Printf("Warning: $GOROOT not set, and unable to run `which go` to find it: %v\n", err)
|
||||
}
|
||||
}
|
||||
return &Builder{
|
||||
context: &c,
|
||||
buildInfo: map[string]*build.Package{},
|
||||
fset: token.NewFileSet(),
|
||||
parsed: map[string][]*ast.File{},
|
||||
userRequested: map[string]bool{},
|
||||
endLineToCommentGroup: map[fileLine]*ast.CommentGroup{},
|
||||
importGraph: map[string]map[string]struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
// Get package information from the go/build package. Automatically excludes
|
||||
// e.g. test files and files for other platforms-- there is quite a bit of
|
||||
// logic of that nature in the build package.
|
||||
func (b *Builder) buildPackage(pkgPath string) (*build.Package, error) {
|
||||
// First, find it, so we know what path to use.
|
||||
pkg, err := b.context.Import(pkgPath, ".", build.FindOnly)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to *find* %q: %v", pkgPath, err)
|
||||
}
|
||||
|
||||
pkgPath = pkg.ImportPath
|
||||
|
||||
if pkg, ok := b.buildInfo[pkgPath]; ok {
|
||||
return pkg, nil
|
||||
}
|
||||
pkg, err = b.context.Import(pkgPath, ".", build.ImportComment)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to import %q: %v", pkgPath, err)
|
||||
}
|
||||
b.buildInfo[pkgPath] = pkg
|
||||
|
||||
if b.importGraph[pkgPath] == nil {
|
||||
b.importGraph[pkgPath] = map[string]struct{}{}
|
||||
}
|
||||
for _, p := range pkg.Imports {
|
||||
b.importGraph[pkgPath][p] = struct{}{}
|
||||
}
|
||||
return pkg, nil
|
||||
}
|
||||
|
||||
// AddFile adds a file to the set. The name must be of the form canonical/pkg/path/file.go.
|
||||
func (b *Builder) AddFile(name string, src []byte) error {
|
||||
return b.addFile(name, src, true)
|
||||
}
|
||||
|
||||
// addFile adds a file to the set. The name must be of the form
|
||||
// canonical/pkg/path/file.go. A flag indicates whether this file was
|
||||
// user-requested or just from following the import graph.
|
||||
func (b *Builder) addFile(name string, src []byte, userRequested bool) error {
|
||||
p, err := parser.ParseFile(b.fset, name, src, parser.DeclarationErrors|parser.ParseComments)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pkg := filepath.Dir(name)
|
||||
b.parsed[pkg] = append(b.parsed[pkg], p)
|
||||
b.userRequested[pkg] = userRequested
|
||||
for _, c := range p.Comments {
|
||||
position := b.fset.Position(c.End())
|
||||
b.endLineToCommentGroup[fileLine{position.Filename, position.Line}] = c
|
||||
}
|
||||
|
||||
// We have to get the packages from this specific file, in case the
|
||||
// user added individual files instead of entire directories.
|
||||
if b.importGraph[pkg] == nil {
|
||||
b.importGraph[pkg] = map[string]struct{}{}
|
||||
}
|
||||
for _, im := range p.Imports {
|
||||
importedPath := strings.Trim(im.Path.Value, `"`)
|
||||
b.importGraph[pkg][importedPath] = struct{}{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddDir adds an entire directory, scanning it for go files. 'dir' should have
|
||||
// a single go package in it. GOPATH, GOROOT, and the location of your go
|
||||
// binary (`which go`) will all be searched if dir doesn't literally resolve.
|
||||
func (b *Builder) AddDir(dir string) error {
|
||||
return b.addDir(dir, true)
|
||||
}
|
||||
|
||||
// The implementation of AddDir. A flag indicates whether this directory was
|
||||
// user-requested or just from following the import graph.
|
||||
func (b *Builder) addDir(dir string, userRequested bool) error {
|
||||
pkg, err := b.buildPackage(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dir = pkg.Dir
|
||||
// Check in case this package was added (maybe dir was not canonical)
|
||||
if _, alreadyAdded := b.parsed[dir]; alreadyAdded {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, n := range pkg.GoFiles {
|
||||
if !strings.HasSuffix(n, ".go") {
|
||||
continue
|
||||
}
|
||||
absPath := filepath.Join(pkg.Dir, n)
|
||||
pkgPath := filepath.Join(pkg.ImportPath, n)
|
||||
data, err := ioutil.ReadFile(absPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("while loading %q: %v", absPath, err)
|
||||
}
|
||||
err = b.addFile(pkgPath, data, userRequested)
|
||||
if err != nil {
|
||||
return fmt.Errorf("while parsing %q: %v", pkgPath, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// importer is a function that will be called by the type check package when it
|
||||
// needs to import a go package. 'path' is the import path. go1.5 changes the
|
||||
// interface, and importAdapter below implements the new interface in terms of
|
||||
// the old one.
|
||||
func (b *Builder) importer(imports map[string]*tc.Package, path string) (*tc.Package, error) {
|
||||
if pkg, ok := imports[path]; ok {
|
||||
return pkg, nil
|
||||
}
|
||||
ignoreError := false
|
||||
if _, ours := b.parsed[path]; !ours {
|
||||
// Ignore errors in paths that we're importing solely because
|
||||
// they're referenced by other packages.
|
||||
ignoreError = true
|
||||
// fmt.Printf("trying to import %q\n", path)
|
||||
if err := b.addDir(path, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
pkg, err := b.typeCheckPackage(path)
|
||||
if err != nil {
|
||||
if ignoreError && pkg != nil {
|
||||
fmt.Printf("type checking encountered some errors in %q, but ignoring.\n", path)
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
imports[path] = pkg
|
||||
return pkg, nil
|
||||
}
|
||||
|
||||
type importAdapter struct {
|
||||
b *Builder
|
||||
}
|
||||
|
||||
func (a importAdapter) Import(path string) (*tc.Package, error) {
|
||||
return a.b.importer(a.b.pkgs, path)
|
||||
}
|
||||
|
||||
// typeCheckPackage will attempt to return the package even if there are some
|
||||
// errors, so you may check whether the package is nil or not even if you get
|
||||
// an error.
|
||||
func (b *Builder) typeCheckPackage(id string) (*tc.Package, error) {
|
||||
if pkg, ok := b.pkgs[id]; ok {
|
||||
if pkg != nil {
|
||||
return pkg, nil
|
||||
}
|
||||
// We store a nil right before starting work on a package. So
|
||||
// if we get here and it's present and nil, that means there's
|
||||
// another invocation of this function on the call stack
|
||||
// already processing this package.
|
||||
return nil, fmt.Errorf("circular dependency for %q", id)
|
||||
}
|
||||
files, ok := b.parsed[id]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("No files for pkg %q: %#v", id, b.parsed)
|
||||
}
|
||||
b.pkgs[id] = nil
|
||||
c := tc.Config{
|
||||
IgnoreFuncBodies: true,
|
||||
// Note that importAdater can call b.import which calls this
|
||||
// method. So there can't be cycles in the import graph.
|
||||
Importer: importAdapter{b},
|
||||
Error: func(err error) {
|
||||
fmt.Printf("type checker error: %v\n", err)
|
||||
},
|
||||
}
|
||||
pkg, err := c.Check(id, b.fset, files, nil)
|
||||
b.pkgs[id] = pkg // record the result whether or not there was an error
|
||||
return pkg, err
|
||||
}
|
||||
|
||||
func (b *Builder) makePackages() error {
|
||||
b.pkgs = map[string]*tc.Package{}
|
||||
for id := range b.parsed {
|
||||
// We have to check here even though we made a new one above,
|
||||
// because typeCheckPackage follows the import graph, which may
|
||||
// cause a package to be filled before we get to it in this
|
||||
// loop.
|
||||
if _, done := b.pkgs[id]; done {
|
||||
continue
|
||||
}
|
||||
if _, err := b.typeCheckPackage(id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindTypes finalizes the package imports, and searches through all the
|
||||
// packages for types.
|
||||
func (b *Builder) FindTypes() (types.Universe, error) {
|
||||
if err := b.makePackages(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u := types.Universe{}
|
||||
|
||||
for pkgName, pkg := range b.pkgs {
|
||||
if !b.userRequested[pkgName] {
|
||||
// Since walkType is recursive, all types that the
|
||||
// packages they asked for depend on will be included.
|
||||
// But we don't need to include all types in all
|
||||
// *packages* they depend on.
|
||||
continue
|
||||
}
|
||||
s := pkg.Scope()
|
||||
for _, n := range s.Names() {
|
||||
obj := s.Lookup(n)
|
||||
tn, ok := obj.(*tc.TypeName)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
t := b.walkType(u, nil, tn.Type())
|
||||
t.CommentLines = b.priorCommentLines(obj.Pos())
|
||||
}
|
||||
for p := range b.importGraph[pkgName] {
|
||||
u.AddImports(pkgName, p)
|
||||
}
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// if there's a comment on the line before pos, return its text, otherwise "".
|
||||
func (b *Builder) priorCommentLines(pos token.Pos) string {
|
||||
position := b.fset.Position(pos)
|
||||
key := fileLine{position.Filename, position.Line - 1}
|
||||
if c, ok := b.endLineToCommentGroup[key]; ok {
|
||||
return c.Text()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func tcNameToName(in string) types.Name {
|
||||
// Detect anonymous type names. (These may have '.' characters because
|
||||
// embedded types may have packages, so we detect them specially.)
|
||||
if strings.HasPrefix(in, "struct{") ||
|
||||
strings.HasPrefix(in, "*") ||
|
||||
strings.HasPrefix(in, "map[") ||
|
||||
strings.HasPrefix(in, "[") {
|
||||
return types.Name{Name: in}
|
||||
}
|
||||
|
||||
// Otherwise, if there are '.' characters present, the name has a
|
||||
// package path in front.
|
||||
nameParts := strings.Split(in, ".")
|
||||
name := types.Name{Name: in}
|
||||
if n := len(nameParts); n >= 2 {
|
||||
// The final "." is the name of the type--previous ones must
|
||||
// have been in the package path.
|
||||
name.Package, name.Name = strings.Join(nameParts[:n-1], "."), nameParts[n-1]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func (b *Builder) convertSignature(u types.Universe, t *tc.Signature) *types.Signature {
|
||||
signature := &types.Signature{}
|
||||
for i := 0; i < t.Params().Len(); i++ {
|
||||
signature.Parameters = append(signature.Parameters, b.walkType(u, nil, t.Params().At(i).Type()))
|
||||
}
|
||||
for i := 0; i < t.Results().Len(); i++ {
|
||||
signature.Results = append(signature.Results, b.walkType(u, nil, t.Results().At(i).Type()))
|
||||
}
|
||||
if r := t.Recv(); r != nil {
|
||||
signature.Receiver = b.walkType(u, nil, r.Type())
|
||||
}
|
||||
signature.Variadic = t.Variadic()
|
||||
return signature
|
||||
}
|
||||
|
||||
// walkType adds the type, and any necessary child types.
|
||||
func (b *Builder) walkType(u types.Universe, useName *types.Name, in tc.Type) *types.Type {
|
||||
// Most of the cases are underlying types of the named type.
|
||||
name := tcNameToName(in.String())
|
||||
if useName != nil {
|
||||
name = *useName
|
||||
}
|
||||
|
||||
switch t := in.(type) {
|
||||
case *tc.Struct:
|
||||
out := u.Get(name)
|
||||
if out.Kind != types.Unknown {
|
||||
return out
|
||||
}
|
||||
out.Kind = types.Struct
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
f := t.Field(i)
|
||||
m := types.Member{
|
||||
Name: f.Name(),
|
||||
Embedded: f.Anonymous(),
|
||||
Tags: t.Tag(i),
|
||||
Type: b.walkType(u, nil, f.Type()),
|
||||
CommentLines: b.priorCommentLines(f.Pos()),
|
||||
}
|
||||
out.Members = append(out.Members, m)
|
||||
}
|
||||
return out
|
||||
case *tc.Map:
|
||||
out := u.Get(name)
|
||||
if out.Kind != types.Unknown {
|
||||
return out
|
||||
}
|
||||
out.Kind = types.Map
|
||||
out.Elem = b.walkType(u, nil, t.Elem())
|
||||
out.Key = b.walkType(u, nil, t.Key())
|
||||
return out
|
||||
case *tc.Pointer:
|
||||
out := u.Get(name)
|
||||
if out.Kind != types.Unknown {
|
||||
return out
|
||||
}
|
||||
out.Kind = types.Pointer
|
||||
out.Elem = b.walkType(u, nil, t.Elem())
|
||||
return out
|
||||
case *tc.Slice:
|
||||
out := u.Get(name)
|
||||
if out.Kind != types.Unknown {
|
||||
return out
|
||||
}
|
||||
out.Kind = types.Slice
|
||||
out.Elem = b.walkType(u, nil, t.Elem())
|
||||
return out
|
||||
case *tc.Array:
|
||||
out := u.Get(name)
|
||||
if out.Kind != types.Unknown {
|
||||
return out
|
||||
}
|
||||
out.Kind = types.Array
|
||||
out.Elem = b.walkType(u, nil, t.Elem())
|
||||
// TODO: need to store array length, otherwise raw type name
|
||||
// cannot be properly written.
|
||||
return out
|
||||
case *tc.Chan:
|
||||
out := u.Get(name)
|
||||
if out.Kind != types.Unknown {
|
||||
return out
|
||||
}
|
||||
out.Kind = types.Chan
|
||||
out.Elem = b.walkType(u, nil, t.Elem())
|
||||
// TODO: need to store direction, otherwise raw type name
|
||||
// cannot be properly written.
|
||||
return out
|
||||
case *tc.Basic:
|
||||
out := u.Get(types.Name{
|
||||
Package: "",
|
||||
Name: t.Name(),
|
||||
})
|
||||
if out.Kind != types.Unknown {
|
||||
return out
|
||||
}
|
||||
out.Kind = types.Unsupported
|
||||
return out
|
||||
case *tc.Signature:
|
||||
out := u.Get(name)
|
||||
if out.Kind != types.Unknown {
|
||||
return out
|
||||
}
|
||||
out.Kind = types.Func
|
||||
out.Signature = b.convertSignature(u, t)
|
||||
return out
|
||||
case *tc.Interface:
|
||||
out := u.Get(name)
|
||||
if out.Kind != types.Unknown {
|
||||
return out
|
||||
}
|
||||
out.Kind = types.Interface
|
||||
t.Complete()
|
||||
for i := 0; i < t.NumMethods(); i++ {
|
||||
out.Methods = append(out.Methods, b.walkType(u, nil, t.Method(i).Type()))
|
||||
}
|
||||
return out
|
||||
case *tc.Named:
|
||||
switch t.Underlying().(type) {
|
||||
case *tc.Named, *tc.Basic:
|
||||
name := tcNameToName(t.String())
|
||||
out := u.Get(name)
|
||||
if out.Kind != types.Unknown {
|
||||
return out
|
||||
}
|
||||
out.Kind = types.Alias
|
||||
out.Underlying = b.walkType(u, nil, t.Underlying())
|
||||
return out
|
||||
default:
|
||||
// tc package makes everything "named" with an
|
||||
// underlying anonymous type--we remove that annoying
|
||||
// "feature" for users. This flattens those types
|
||||
// together.
|
||||
name := tcNameToName(t.String())
|
||||
if out := u.Get(name); out.Kind != types.Unknown {
|
||||
return out // short circuit if we've already made this.
|
||||
}
|
||||
out := b.walkType(u, &name, t.Underlying())
|
||||
if len(out.Methods) == 0 {
|
||||
// If the underlying type didn't already add
|
||||
// methods, add them. (Interface types will
|
||||
// have already added methods.)
|
||||
for i := 0; i < t.NumMethods(); i++ {
|
||||
out.Methods = append(out.Methods, b.walkType(u, nil, t.Method(i).Type()))
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
default:
|
||||
out := u.Get(name)
|
||||
if out.Kind != types.Unknown {
|
||||
return out
|
||||
}
|
||||
out.Kind = types.Unsupported
|
||||
fmt.Printf("Making unsupported type entry %q for: %#v\n", out, t)
|
||||
return out
|
||||
}
|
||||
}
|
343
cmd/libs/go2idl/parser/parse_test.go
Normal file
343
cmd/libs/go2idl/parser/parse_test.go
Normal file
@ -0,0 +1,343 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package parser_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
"k8s.io/kubernetes/cmd/libs/go2idl/namer"
|
||||
"k8s.io/kubernetes/cmd/libs/go2idl/parser"
|
||||
"k8s.io/kubernetes/cmd/libs/go2idl/types"
|
||||
)
|
||||
|
||||
func construct(t *testing.T, files map[string]string, testNamer namer.Namer) (*parser.Builder, types.Universe, []*types.Type) {
|
||||
b := parser.New()
|
||||
for name, src := range files {
|
||||
if err := b.AddFile(name, []byte(src)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
u, err := b.FindTypes()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
orderer := namer.Orderer{testNamer}
|
||||
o := orderer.Order(u)
|
||||
return b, u, o
|
||||
}
|
||||
|
||||
func TestBuilder(t *testing.T) {
|
||||
var testFiles = map[string]string{
|
||||
"base/foo/proto/foo.go": `
|
||||
package foo
|
||||
|
||||
import (
|
||||
"base/common/proto"
|
||||
)
|
||||
|
||||
type Blah struct {
|
||||
common.Object
|
||||
Count int64
|
||||
Frobbers map[string]*Frobber
|
||||
Baz []Object
|
||||
Nickname *string
|
||||
NumberIsAFavorite map[int]bool
|
||||
}
|
||||
|
||||
type Frobber struct {
|
||||
Name string
|
||||
Amount int64
|
||||
}
|
||||
|
||||
type Object struct {
|
||||
common.Object
|
||||
}
|
||||
|
||||
`,
|
||||
"base/common/proto/common.go": `
|
||||
package common
|
||||
|
||||
type Object struct {
|
||||
ID int64
|
||||
}
|
||||
`,
|
||||
}
|
||||
|
||||
var tmplText = `
|
||||
package o
|
||||
{{define "Struct"}}type {{Name .}} interface { {{range $m := .Members}}{{$n := Name $m.Type}}
|
||||
{{if $m.Embedded}}{{$n}}{{else}}{{$m.Name}}() {{$n}}{{if $m.Type.Elem}}{{else}}
|
||||
Set{{$m.Name}}({{$n}}){{end}}{{end}}{{end}}
|
||||
}
|
||||
|
||||
{{end}}
|
||||
{{range $t := .}}{{if eq $t.Kind "Struct"}}{{template "Struct" $t}}{{end}}{{end}}`
|
||||
|
||||
var expect = `
|
||||
package o
|
||||
|
||||
type CommonObject interface {
|
||||
ID() Int64
|
||||
SetID(Int64)
|
||||
}
|
||||
|
||||
type FooBlah interface {
|
||||
CommonObject
|
||||
Count() Int64
|
||||
SetCount(Int64)
|
||||
Frobbers() MapStringToPointerFooFrobber
|
||||
Baz() SliceFooObject
|
||||
Nickname() PointerString
|
||||
NumberIsAFavorite() MapIntToBool
|
||||
}
|
||||
|
||||
type FooFrobber interface {
|
||||
Name() String
|
||||
SetName(String)
|
||||
Amount() Int64
|
||||
SetAmount(Int64)
|
||||
}
|
||||
|
||||
type FooObject interface {
|
||||
CommonObject
|
||||
}
|
||||
|
||||
`
|
||||
testNamer := namer.NewPublicNamer(1, "proto")
|
||||
_, u, o := construct(t, testFiles, testNamer)
|
||||
t.Logf("\n%v\n\n", o)
|
||||
tmpl := template.Must(
|
||||
template.New("").
|
||||
Funcs(
|
||||
map[string]interface{}{
|
||||
"Name": testNamer.Name,
|
||||
}).
|
||||
Parse(tmplText),
|
||||
)
|
||||
buf := &bytes.Buffer{}
|
||||
tmpl.Execute(buf, o)
|
||||
if e, a := expect, buf.String(); e != a {
|
||||
t.Errorf("Wanted, got:\n%v\n-----\n%v\n", e, a)
|
||||
}
|
||||
|
||||
if p := u.Package("base/foo/proto"); !p.HasImport("base/common/proto") {
|
||||
t.Errorf("Unexpected lack of import line: %#s", p.Imports)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStructParse(t *testing.T) {
|
||||
var structTest = map[string]string{
|
||||
"base/foo/proto/foo.go": `
|
||||
package foo
|
||||
|
||||
// Blah is a test.
|
||||
// A test, I tell you.
|
||||
type Blah struct {
|
||||
// A is the first field.
|
||||
A int64 ` + "`" + `json:"a"` + "`" + `
|
||||
|
||||
// B is the second field.
|
||||
// Multiline comments work.
|
||||
B string ` + "`" + `json:"b"` + "`" + `
|
||||
}
|
||||
`,
|
||||
}
|
||||
|
||||
_, u, o := construct(t, structTest, namer.NewPublicNamer(0))
|
||||
t.Logf("%#v", o)
|
||||
blahT := u.Get(types.Name{"base/foo/proto", "Blah"})
|
||||
if blahT == nil {
|
||||
t.Fatal("type not found")
|
||||
}
|
||||
if e, a := types.Struct, blahT.Kind; e != a {
|
||||
t.Errorf("struct kind wrong, wanted %v, got %v", e, a)
|
||||
}
|
||||
if e, a := "Blah is a test.\nA test, I tell you.\n", blahT.CommentLines; e != a {
|
||||
t.Errorf("struct comment wrong, wanted %v, got %v", e, a)
|
||||
}
|
||||
m := types.Member{
|
||||
Name: "B",
|
||||
Embedded: false,
|
||||
CommentLines: "B is the second field.\nMultiline comments work.\n",
|
||||
Tags: `json:"b"`,
|
||||
Type: types.String,
|
||||
}
|
||||
if e, a := m, blahT.Members[1]; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("wanted, got:\n%#v\n%#v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeKindParse(t *testing.T) {
|
||||
var testFiles = map[string]string{
|
||||
"a/foo.go": "package a\ntype Test string\n",
|
||||
"b/foo.go": "package b\ntype Test map[int]string\n",
|
||||
"c/foo.go": "package c\ntype Test []string\n",
|
||||
"d/foo.go": "package d\ntype Test struct{a int; b struct{a int}; c map[int]string; d *string}\n",
|
||||
"e/foo.go": "package e\ntype Test *string\n",
|
||||
"f/foo.go": `
|
||||
package f
|
||||
import (
|
||||
"a"
|
||||
"b"
|
||||
)
|
||||
type Test []a.Test
|
||||
type Test2 *a.Test
|
||||
type Test3 map[a.Test]b.Test
|
||||
type Test4 struct {
|
||||
a struct {a a.Test; b b.Test}
|
||||
b map[a.Test]b.Test
|
||||
c *a.Test
|
||||
d []a.Test
|
||||
e []string
|
||||
}
|
||||
`,
|
||||
"g/foo.go": `
|
||||
package g
|
||||
type Test func(a, b string) (c, d string)
|
||||
func (t Test) Method(a, b string) (c, d string) { return t(a, b) }
|
||||
type Interface interface{Method(a, b string) (c, d string)}
|
||||
`,
|
||||
}
|
||||
|
||||
// Check that the right types are found, and the namers give the expected names.
|
||||
|
||||
assertions := []struct {
|
||||
Package, Name string
|
||||
k types.Kind
|
||||
names []string
|
||||
}{
|
||||
{
|
||||
Package: "a", Name: "Test", k: types.Alias,
|
||||
names: []string{"Test", "ATest", "test", "aTest", "a.Test"},
|
||||
},
|
||||
{
|
||||
Package: "b", Name: "Test", k: types.Map,
|
||||
names: []string{"Test", "BTest", "test", "bTest", "b.Test"},
|
||||
},
|
||||
{
|
||||
Package: "c", Name: "Test", k: types.Slice,
|
||||
names: []string{"Test", "CTest", "test", "cTest", "c.Test"},
|
||||
},
|
||||
{
|
||||
Package: "d", Name: "Test", k: types.Struct,
|
||||
names: []string{"Test", "DTest", "test", "dTest", "d.Test"},
|
||||
},
|
||||
{
|
||||
Package: "e", Name: "Test", k: types.Pointer,
|
||||
names: []string{"Test", "ETest", "test", "eTest", "e.Test"},
|
||||
},
|
||||
{
|
||||
Package: "f", Name: "Test", k: types.Slice,
|
||||
names: []string{"Test", "FTest", "test", "fTest", "f.Test"},
|
||||
},
|
||||
{
|
||||
Package: "g", Name: "Test", k: types.Func,
|
||||
names: []string{"Test", "GTest", "test", "gTest", "g.Test"},
|
||||
},
|
||||
{
|
||||
Package: "g", Name: "Interface", k: types.Interface,
|
||||
names: []string{"Interface", "GInterface", "interface", "gInterface", "g.Interface"},
|
||||
},
|
||||
{
|
||||
Package: "", Name: "string", k: types.Builtin,
|
||||
names: []string{"String", "String", "string", "string", "string"},
|
||||
},
|
||||
{
|
||||
Package: "", Name: "int", k: types.Builtin,
|
||||
names: []string{"Int", "Int", "int", "int", "int"},
|
||||
},
|
||||
{
|
||||
Package: "", Name: "struct{a int}", k: types.Struct,
|
||||
names: []string{"StructInt", "StructInt", "structInt", "structInt", "struct{a int}"},
|
||||
},
|
||||
{
|
||||
Package: "", Name: "struct{a a.Test; b b.Test}", k: types.Struct,
|
||||
names: []string{"StructTestTest", "StructATestBTest", "structTestTest", "structATestBTest", "struct{a a.Test; b b.Test}"},
|
||||
},
|
||||
{
|
||||
Package: "", Name: "map[int]string", k: types.Map,
|
||||
names: []string{"MapIntToString", "MapIntToString", "mapIntToString", "mapIntToString", "map[int]string"},
|
||||
},
|
||||
{
|
||||
Package: "", Name: "map[a.Test]b.Test", k: types.Map,
|
||||
names: []string{"MapTestToTest", "MapATestToBTest", "mapTestToTest", "mapATestToBTest", "map[a.Test]b.Test"},
|
||||
},
|
||||
{
|
||||
Package: "", Name: "[]string", k: types.Slice,
|
||||
names: []string{"SliceString", "SliceString", "sliceString", "sliceString", "[]string"},
|
||||
},
|
||||
{
|
||||
Package: "", Name: "[]a.Test", k: types.Slice,
|
||||
names: []string{"SliceTest", "SliceATest", "sliceTest", "sliceATest", "[]a.Test"},
|
||||
},
|
||||
{
|
||||
Package: "", Name: "*string", k: types.Pointer,
|
||||
names: []string{"PointerString", "PointerString", "pointerString", "pointerString", "*string"},
|
||||
},
|
||||
{
|
||||
Package: "", Name: "*a.Test", k: types.Pointer,
|
||||
names: []string{"PointerTest", "PointerATest", "pointerTest", "pointerATest", "*a.Test"},
|
||||
},
|
||||
}
|
||||
|
||||
namers := []namer.Namer{
|
||||
namer.NewPublicNamer(0),
|
||||
namer.NewPublicNamer(1),
|
||||
namer.NewPrivateNamer(0),
|
||||
namer.NewPrivateNamer(1),
|
||||
namer.NewRawNamer(nil),
|
||||
}
|
||||
|
||||
for nameIndex, namer := range namers {
|
||||
_, u, _ := construct(t, testFiles, namer)
|
||||
t.Logf("Found types:\n")
|
||||
for pkgName, pkg := range u {
|
||||
for typeName, cur := range pkg.Types {
|
||||
t.Logf("%q-%q: %s %s", pkgName, typeName, cur.Name, cur.Kind)
|
||||
}
|
||||
}
|
||||
t.Logf("\n\n")
|
||||
|
||||
for _, item := range assertions {
|
||||
n := types.Name{Package: item.Package, Name: item.Name}
|
||||
thisType := u.Get(n)
|
||||
if thisType == nil {
|
||||
t.Errorf("type %s not found", n)
|
||||
continue
|
||||
}
|
||||
if e, a := item.k, thisType.Kind; e != a {
|
||||
t.Errorf("%v-%s: type kind wrong, wanted %v, got %v (%#v)", nameIndex, n, e, a, thisType)
|
||||
}
|
||||
if e, a := item.names[nameIndex], namer.Name(thisType); e != a {
|
||||
t.Errorf("%v-%s: Expected %q, got %q", nameIndex, n, e, a)
|
||||
}
|
||||
}
|
||||
|
||||
// Also do some one-off checks
|
||||
gtest := u.Get(types.Name{"g", "Test"})
|
||||
if e, a := 1, len(gtest.Methods); e != a {
|
||||
t.Errorf("expected %v but found %v methods: %#v", e, a, gtest)
|
||||
}
|
||||
iface := u.Get(types.Name{"g", "Interface"})
|
||||
if e, a := 1, len(iface.Methods); e != a {
|
||||
t.Errorf("expected %v but found %v methods: %#v", e, a, iface)
|
||||
}
|
||||
}
|
||||
}
|
64
cmd/libs/go2idl/types/comments.go
Normal file
64
cmd/libs/go2idl/types/comments.go
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package types contains go type information, packaged in a way that makes
|
||||
// auto-generation convenient, whether by template or straight go functions.
|
||||
package types
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ExtractCommentTags parses comments for lines of the form:
|
||||
//
|
||||
// 'marker'+"key1=value1,key2=value2".
|
||||
//
|
||||
// Values are optional; 'true' is the default. If a key is set multiple times,
|
||||
// the last one wins.
|
||||
//
|
||||
// Example: if you pass "+" for 'marker', and the following two lines are in
|
||||
// the comments:
|
||||
// +foo=value1,bar
|
||||
// +foo=value2,baz="frobber"
|
||||
// Then this function will return:
|
||||
// map[string]string{"foo":"value2", "bar": "true", "baz": "frobber"}
|
||||
//
|
||||
// TODO: Basically we need to define a standard way of giving instructions to
|
||||
// autogenerators in the comments of a type. This is a first iteration of that.
|
||||
// TODO: allow multiple values per key?
|
||||
func ExtractCommentTags(marker, allLines string) map[string]string {
|
||||
lines := strings.Split(allLines, "\n")
|
||||
out := map[string]string{}
|
||||
for _, line := range lines {
|
||||
line = strings.Trim(line, " ")
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(line, marker) {
|
||||
continue
|
||||
}
|
||||
pairs := strings.Split(line[len(marker):], ",")
|
||||
for _, p := range pairs {
|
||||
kv := strings.Split(p, "=")
|
||||
if len(kv) == 2 {
|
||||
out[kv[0]] = kv[1]
|
||||
} else if len(kv) == 1 {
|
||||
out[kv[0]] = "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
35
cmd/libs/go2idl/types/comments_test.go
Normal file
35
cmd/libs/go2idl/types/comments_test.go
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExtractCommentTags(t *testing.T) {
|
||||
commentLines := `
|
||||
Human comment that is ignored.
|
||||
+foo=value1,bar
|
||||
+foo=value2,baz=frobber
|
||||
`
|
||||
a := ExtractCommentTags("+", commentLines)
|
||||
e := map[string]string{"foo": "value2", "bar": "true", "baz": "frobber"}
|
||||
if !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Wanted %#v, got %#v", e, a)
|
||||
}
|
||||
}
|
19
cmd/libs/go2idl/types/doc.go
Normal file
19
cmd/libs/go2idl/types/doc.go
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package types contains go type information, packaged in a way that makes
|
||||
// auto-generation convenient, whether by template or straight go functions.
|
||||
package types
|
57
cmd/libs/go2idl/types/flatten.go
Normal file
57
cmd/libs/go2idl/types/flatten.go
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package types
|
||||
|
||||
// FlattenMembers recursively takes any embedded members and puts them in the
|
||||
// top level, correctly hiding them if the top level hides them. There must not
|
||||
// be a cycle-- that implies infinite members.
|
||||
//
|
||||
// This is useful for e.g. computing all the valid keys in a json struct,
|
||||
// properly considering any configuration of embedded structs.
|
||||
func FlattenMembers(m []Member) []Member {
|
||||
embedded := []Member{}
|
||||
normal := []Member{}
|
||||
type nameInfo struct {
|
||||
top bool
|
||||
i int
|
||||
}
|
||||
names := map[string]nameInfo{}
|
||||
for i := range m {
|
||||
if m[i].Embedded && m[i].Type.Kind == Struct {
|
||||
embedded = append(embedded, m[i])
|
||||
} else {
|
||||
normal = append(normal, m[i])
|
||||
names[m[i].Name] = nameInfo{true, len(normal) - 1}
|
||||
}
|
||||
}
|
||||
for i := range embedded {
|
||||
for _, e := range FlattenMembers(embedded[i].Type.Members) {
|
||||
if info, found := names[e.Name]; found {
|
||||
if info.top {
|
||||
continue
|
||||
}
|
||||
if n := normal[info.i]; n.Name == e.Name && n.Type == e.Type {
|
||||
continue
|
||||
}
|
||||
panic("conflicting members")
|
||||
}
|
||||
normal = append(normal, e)
|
||||
names[e.Name] = nameInfo{false, len(normal) - 1}
|
||||
}
|
||||
}
|
||||
return normal
|
||||
}
|
68
cmd/libs/go2idl/types/flatten_test.go
Normal file
68
cmd/libs/go2idl/types/flatten_test.go
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFlatten(t *testing.T) {
|
||||
mapType := &Type{
|
||||
Name: Name{"", "map[string]string"},
|
||||
Kind: Map,
|
||||
Key: String,
|
||||
Elem: String,
|
||||
}
|
||||
m := []Member{
|
||||
{
|
||||
Name: "Baz",
|
||||
Embedded: true,
|
||||
Type: &Type{
|
||||
Name: Name{"pkg", "Baz"},
|
||||
Kind: Struct,
|
||||
Members: []Member{
|
||||
{Name: "Foo", Type: String},
|
||||
{
|
||||
Name: "Qux",
|
||||
Embedded: true,
|
||||
Type: &Type{
|
||||
Name: Name{"pkg", "Qux"},
|
||||
Kind: Struct,
|
||||
Members: []Member{{Name: "Zot", Type: String}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{Name: "Bar", Type: String},
|
||||
{
|
||||
Name: "NotSureIfLegal",
|
||||
Embedded: true,
|
||||
Type: mapType,
|
||||
},
|
||||
}
|
||||
e := []Member{
|
||||
{Name: "Bar", Type: String},
|
||||
{Name: "NotSureIfLegal", Type: mapType, Embedded: true},
|
||||
{Name: "Foo", Type: String},
|
||||
{Name: "Zot", Type: String},
|
||||
}
|
||||
if a := FlattenMembers(m); !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected \n%#v\n, got \n%#v\n", e, a)
|
||||
}
|
||||
}
|
307
cmd/libs/go2idl/types/types.go
Normal file
307
cmd/libs/go2idl/types/types.go
Normal file
@ -0,0 +1,307 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package types
|
||||
|
||||
// A type name may have a package qualifier.
|
||||
type Name struct {
|
||||
// Empty if embedded or builtin. This is the package path.
|
||||
Package string
|
||||
// The type name.
|
||||
Name string
|
||||
}
|
||||
|
||||
// String returns the name formatted as a string.
|
||||
func (n Name) String() string {
|
||||
if n.Package == "" {
|
||||
return n.Name
|
||||
}
|
||||
return n.Package + "." + n.Name
|
||||
}
|
||||
|
||||
// The possible classes of types.
|
||||
type Kind string
|
||||
|
||||
const (
|
||||
// Builtin is a primitive, like bool, string, int.
|
||||
Builtin Kind = "Builtin"
|
||||
Struct Kind = "Struct"
|
||||
Map Kind = "Map"
|
||||
Slice Kind = "Slice"
|
||||
Pointer Kind = "Pointer"
|
||||
|
||||
// Alias is an alias of another type, e.g. in:
|
||||
// type Foo string
|
||||
// type Bar Foo
|
||||
// Bar is an alias of Foo.
|
||||
//
|
||||
// In the real go type system, Foo is a "Named" string; but to simplify
|
||||
// generation, this type system will just say that Foo *is* a builtin.
|
||||
// We then need "Alias" as a way for us to say that Bar *is* a Foo.
|
||||
Alias Kind = "Alias"
|
||||
|
||||
// Interface is any type that could have differing types at run time.
|
||||
Interface Kind = "Interface"
|
||||
|
||||
// The remaining types are included for completeness, but are not well
|
||||
// supported.
|
||||
Array Kind = "Array" // Array is just like slice, but has a fixed length.
|
||||
Chan Kind = "Chan"
|
||||
Func Kind = "Func"
|
||||
Unknown Kind = ""
|
||||
Unsupported Kind = "Unsupported"
|
||||
)
|
||||
|
||||
// Package holds package-level information.
|
||||
// Fields are public, as everything in this package, to enable consumption by
|
||||
// templates (for example). But it is strongly encouraged for code to build by
|
||||
// using the provided functions.
|
||||
type Package struct {
|
||||
// Canonical name of this package-- its path.
|
||||
Path string
|
||||
|
||||
// Short name of this package; the name that appears in the
|
||||
// 'package x' line.
|
||||
Name string
|
||||
|
||||
// Types within this package, indexed by their name (*not* including
|
||||
// package name).
|
||||
Types map[string]*Type
|
||||
|
||||
// Packages imported by this package, indexed by (canonicalized)
|
||||
// package path.
|
||||
Imports map[string]*Package
|
||||
}
|
||||
|
||||
// Has returns true if the given name references a type known to this package.
|
||||
func (p *Package) Has(name string) bool {
|
||||
_, has := p.Types[name]
|
||||
return has
|
||||
}
|
||||
|
||||
// Get (or add) the given type
|
||||
func (p *Package) Get(typeName string) *Type {
|
||||
if t, ok := p.Types[typeName]; ok {
|
||||
return t
|
||||
}
|
||||
if p.Path == "" {
|
||||
// Import the standard builtin types!
|
||||
if t, ok := builtins.Types[typeName]; ok {
|
||||
p.Types[typeName] = t
|
||||
return t
|
||||
}
|
||||
}
|
||||
t := &Type{Name: Name{p.Path, typeName}}
|
||||
p.Types[typeName] = t
|
||||
return t
|
||||
}
|
||||
|
||||
// HasImport returns true if p imports packageName. Package names include the
|
||||
// package directory.
|
||||
func (p *Package) HasImport(packageName string) bool {
|
||||
_, has := p.Imports[packageName]
|
||||
return has
|
||||
}
|
||||
|
||||
// Universe is a map of all packages. The key is the package name, but you
|
||||
// should use Get() or Package() instead of direct access.
|
||||
type Universe map[string]*Package
|
||||
|
||||
// Get returns the canonical type for the given fully-qualified name. Builtin
|
||||
// types will always be found, even if they haven't been explicitly added to
|
||||
// the map. If a non-existing type is requested, u will create (a marker for)
|
||||
// it.
|
||||
func (u Universe) Get(n Name) *Type {
|
||||
return u.Package(n.Package).Get(n.Name)
|
||||
}
|
||||
|
||||
// AddImports registers import lines for packageName. May be called multiple times.
|
||||
// You are responsible for canonicalizing all package paths.
|
||||
func (u Universe) AddImports(packagePath string, importPaths ...string) {
|
||||
p := u.Package(packagePath)
|
||||
for _, i := range importPaths {
|
||||
p.Imports[i] = u.Package(i)
|
||||
}
|
||||
}
|
||||
|
||||
// Get (create if needed) the package.
|
||||
func (u Universe) Package(packagePath string) *Package {
|
||||
if p, ok := u[packagePath]; ok {
|
||||
return p
|
||||
}
|
||||
p := &Package{
|
||||
Path: packagePath,
|
||||
Types: map[string]*Type{},
|
||||
Imports: map[string]*Package{},
|
||||
}
|
||||
u[packagePath] = p
|
||||
return p
|
||||
}
|
||||
|
||||
// Type represents a subset of possible go types.
|
||||
type Type struct {
|
||||
// There are two general categories of types, those explicitly named
|
||||
// and those anonymous. Named ones will have a non-empty package in the
|
||||
// name field.
|
||||
Name Name
|
||||
|
||||
// The general kind of this type.
|
||||
Kind Kind
|
||||
|
||||
// If there are comment lines immediately before the type definition,
|
||||
// they will be recorded here.
|
||||
CommentLines string
|
||||
|
||||
// If Kind == Struct
|
||||
Members []Member
|
||||
|
||||
// If Kind == Map, Slice, Pointer, or Chan
|
||||
Elem *Type
|
||||
|
||||
// If Kind == Map, this is the map's key type.
|
||||
Key *Type
|
||||
|
||||
// If Kind == Alias, this is the underlying type.
|
||||
Underlying *Type
|
||||
|
||||
// If Kind == Interface, this is the list of all required functions.
|
||||
// Otherwise, if this is a named type, this is the list of methods that
|
||||
// type has. (All elements will have Kind=="Func")
|
||||
Methods []*Type
|
||||
|
||||
// If Kind == func, this is the signature of the function.
|
||||
Signature *Signature
|
||||
|
||||
// TODO: Add:
|
||||
// * channel direction
|
||||
// * array length
|
||||
}
|
||||
|
||||
// String returns the name of the type.
|
||||
func (t *Type) String() string {
|
||||
return t.Name.String()
|
||||
}
|
||||
|
||||
// A single struct member
|
||||
type Member struct {
|
||||
// The name of the member.
|
||||
Name string
|
||||
|
||||
// If the member is embedded (anonymous) this will be true, and the
|
||||
// Name will be the type name.
|
||||
Embedded bool
|
||||
|
||||
// If there are comment lines immediately before the member in the type
|
||||
// definition, they will be recorded here.
|
||||
CommentLines string
|
||||
|
||||
// If there are tags along with this member, they will be saved here.
|
||||
Tags string
|
||||
|
||||
// The type of this member.
|
||||
Type *Type
|
||||
}
|
||||
|
||||
// String returns the name and type of the member.
|
||||
func (m Member) String() string {
|
||||
return m.Name + " " + m.Type.String()
|
||||
}
|
||||
|
||||
// Signature is a function's signature.
|
||||
type Signature struct {
|
||||
// TODO: store the parameter names, not just types.
|
||||
|
||||
// If a method of some type, this is the type it's a member of.
|
||||
Receiver *Type
|
||||
Parameters []*Type
|
||||
Results []*Type
|
||||
|
||||
// True if the last in parameter is of the form ...T.
|
||||
Variadic bool
|
||||
|
||||
// If there are comment lines immediately before this
|
||||
// signature/method/function declaration, they will be recorded here.
|
||||
CommentLines string
|
||||
}
|
||||
|
||||
// Built in types.
|
||||
var (
|
||||
String = &Type{
|
||||
Name: Name{Name: "string"},
|
||||
Kind: Builtin,
|
||||
}
|
||||
Int64 = &Type{
|
||||
Name: Name{Name: "int64"},
|
||||
Kind: Builtin,
|
||||
}
|
||||
Int32 = &Type{
|
||||
Name: Name{Name: "int32"},
|
||||
Kind: Builtin,
|
||||
}
|
||||
Int16 = &Type{
|
||||
Name: Name{Name: "int16"},
|
||||
Kind: Builtin,
|
||||
}
|
||||
Int = &Type{
|
||||
Name: Name{Name: "int"},
|
||||
Kind: Builtin,
|
||||
}
|
||||
Uint64 = &Type{
|
||||
Name: Name{Name: "uint64"},
|
||||
Kind: Builtin,
|
||||
}
|
||||
Uint32 = &Type{
|
||||
Name: Name{Name: "uint32"},
|
||||
Kind: Builtin,
|
||||
}
|
||||
Uint16 = &Type{
|
||||
Name: Name{Name: "uint16"},
|
||||
Kind: Builtin,
|
||||
}
|
||||
Uint = &Type{
|
||||
Name: Name{Name: "uint"},
|
||||
Kind: Builtin,
|
||||
}
|
||||
Bool = &Type{
|
||||
Name: Name{Name: "bool"},
|
||||
Kind: Builtin,
|
||||
}
|
||||
Byte = &Type{
|
||||
Name: Name{Name: "byte"},
|
||||
Kind: Builtin,
|
||||
}
|
||||
|
||||
builtins = &Package{
|
||||
Types: map[string]*Type{
|
||||
"bool": Bool,
|
||||
"string": String,
|
||||
"int": Int,
|
||||
"int64": Int64,
|
||||
"int32": Int32,
|
||||
"int16": Int16,
|
||||
"int8": Byte,
|
||||
"uint": Uint,
|
||||
"uint64": Uint64,
|
||||
"uint32": Uint32,
|
||||
"uint16": Uint16,
|
||||
"uint8": Byte,
|
||||
"byte": Byte,
|
||||
},
|
||||
Imports: map[string]*Package{},
|
||||
Path: "",
|
||||
Name: "",
|
||||
}
|
||||
)
|
47
cmd/libs/go2idl/types/types_test.go
Normal file
47
cmd/libs/go2idl/types/types_test.go
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetBuiltin(t *testing.T) {
|
||||
u := Universe{}
|
||||
if builtinPkg := u.Package(""); builtinPkg.Has("string") {
|
||||
t.Errorf("Expected builtin package to not have builtins until they're asked for explicitly. %#v", builtinPkg)
|
||||
}
|
||||
s := u.Get(Name{"", "string"})
|
||||
if s != String {
|
||||
t.Errorf("Expected canonical string type.")
|
||||
}
|
||||
if builtinPkg := u.Package(""); !builtinPkg.Has("string") {
|
||||
t.Errorf("Expected builtin package to exist and have builtins by default. %#v", builtinPkg)
|
||||
}
|
||||
if builtinPkg := u.Package(""); len(builtinPkg.Types) != 1 {
|
||||
t.Errorf("Expected builtin package to not have builtins until they're asked for explicitly. %#v", builtinPkg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMarker(t *testing.T) {
|
||||
u := Universe{}
|
||||
n := Name{"path/to/package", "Foo"}
|
||||
f := u.Get(n)
|
||||
if f == nil || f.Name != n {
|
||||
t.Errorf("Expected marker type.")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user