* Adding ImportBag type

* Fixing formatting

* Updating default from unknown to the path base.

* Removing unused helper function
This commit is contained in:
Martin Strobel 2018-07-09 17:31:25 -07:00 коммит произвёл GitHub
Родитель 853f6ecd1d
Коммит 0d9fee5e94
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 428 добавлений и 18 удалений

Просмотреть файл

@ -0,0 +1,234 @@
package common
import (
"bytes"
"errors"
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"path"
"sort"
"strings"
)
// PackageSpecifier is a string that represents the name that will be used to refer to
// exported functions and variables from a package.
type PackageSpecifier string
// PackagePath is a string that refers to the location of Go package.
type PackagePath string
// ImportBag captures all of the imports in a Go source file, and attempts
// to ease the process of working with them.
type ImportBag struct {
bySpec map[PackageSpecifier]PackagePath
blankIdent map[PackagePath]struct{}
localIdent map[PackagePath]struct{}
}
// NewImportBag instantiates an empty ImportBag.
func NewImportBag() *ImportBag {
return &ImportBag{
bySpec: make(map[PackageSpecifier]PackagePath),
blankIdent: make(map[PackagePath]struct{}),
localIdent: make(map[PackagePath]struct{}),
}
}
// NewImportBagFromFile reads a Go source file, finds all imports,
// and returns them as an instantiated ImportBag.
func NewImportBagFromFile(filepath string) (*ImportBag, error) {
f, err := parser.ParseFile(token.NewFileSet(), filepath, nil, parser.ImportsOnly)
if err != nil {
return nil, err
}
ib := NewImportBag()
for _, spec := range f.Imports {
pkgPath := PackagePath(strings.Trim(spec.Path.Value, `"`))
if spec.Name == nil {
ib.AddImport(pkgPath)
} else {
ib.AddImportWithSpecifier(pkgPath, PackageSpecifier(spec.Name.Name))
}
}
return ib, nil
}
// AddImport includes a package, and returns the name that was selected to
// be the specified for working with this path. It first attempts to use the
// package name as the specifier. Should that cause a conflict, it determines
// a unique name to be used as the specifier.
//
// If the path provided has already been imported, the existing name for it
// is returned, but err is non-nil.
func (ib *ImportBag) AddImport(pkgPath PackagePath) PackageSpecifier {
spec, err := FindSpecifier(pkgPath)
if err != nil {
spec = PackageSpecifier(path.Base(string(pkgPath)))
}
specLen := len(spec)
suffix := uint(1)
for {
err = ib.AddImportWithSpecifier(pkgPath, spec)
if err == nil {
break
} else if err != ErrDuplicateImport {
panic(err)
}
spec = PackageSpecifier(fmt.Sprintf("%s%d", spec[:specLen], suffix))
suffix++
}
return spec
}
// ErrDuplicateImport is the error that will be returned when two packages are both requested
// to be imported using the same specifier.
var ErrDuplicateImport = errors.New("specifier already in use in ImportBag")
// ErrMultipleLocalImport is the error that will be returned when the same package has been imported
// to the specifer "." more than once.
var ErrMultipleLocalImport = errors.New("package already imported into the local namespace")
// AddImportWithSpecifier will add an import with a given name. If it would lead
// to conflicting package specifiers, it returns an error.
func (ib *ImportBag) AddImportWithSpecifier(pkgPath PackagePath, specifier PackageSpecifier) error {
if specifier == "_" {
ib.blankIdent[pkgPath] = struct{}{}
return nil
}
if specifier == "." {
if _, ok := ib.localIdent[pkgPath]; ok {
return ErrMultipleLocalImport
}
ib.localIdent[pkgPath] = struct{}{}
return nil
}
if impPath, ok := ib.bySpec[specifier]; ok && pkgPath != impPath {
return ErrDuplicateImport
}
ib.bySpec[specifier] = pkgPath
return nil
}
// FindSpecifier finds the specifier assocatied with a particular package.
//
// If the package was not imported, the empty string and false are returned.
//
// If multiple specifiers are assigned to the package, one is returned at
// random.
//
// If the same package is imported with a named specifier, and the blank
// identifier, the name is returned.
func (ib ImportBag) FindSpecifier(pkgPath PackagePath) (PackageSpecifier, bool) {
for k, v := range ib.bySpec {
if v == pkgPath {
return k, true
}
}
if _, ok := ib.blankIdent[pkgPath]; ok {
return "_", true
}
if _, ok := ib.localIdent[pkgPath]; ok {
return ".", true
}
return "", false
}
// List returns each import statement as a slice of strings sorted alphabetically by
// their import paths.
func (ib *ImportBag) List() []string {
specs := ib.ListAsImportSpec()
retval := make([]string, len(specs))
builder := bytes.NewBuffer([]byte{})
for i, s := range specs {
if s.Name != nil {
builder.WriteString(s.Name.Name)
builder.WriteRune(' ')
}
builder.WriteString(s.Path.Value)
retval[i] = builder.String()
builder.Reset()
}
return retval
}
// ListAsImportSpec returns the imports from the ImportBag as a slice of ImportSpecs
// sorted alphabetically by their import paths.
func (ib *ImportBag) ListAsImportSpec() []*ast.ImportSpec {
retval := make([]*ast.ImportSpec, 0, len(ib.bySpec)+len(ib.localIdent)+len(ib.blankIdent))
getLit := func(pkgPath PackagePath) *ast.BasicLit {
return &ast.BasicLit{
Kind: token.STRING,
Value: fmt.Sprintf("%q", string(pkgPath)),
}
}
for k, v := range ib.bySpec {
var name *ast.Ident
if path.Base(string(v)) != string(k) {
name = ast.NewIdent(string(k))
}
retval = append(retval, &ast.ImportSpec{
Name: name,
Path: getLit(v),
})
}
for s := range ib.localIdent {
retval = append(retval, &ast.ImportSpec{
Name: ast.NewIdent("."),
Path: getLit(s),
})
}
for s := range ib.blankIdent {
retval = append(retval, &ast.ImportSpec{
Name: ast.NewIdent("_"),
Path: getLit(s),
})
}
sort.Slice(retval, func(i, j int) bool {
return strings.Compare(retval[i].Path.Value, retval[j].Path.Value) < 0
})
return retval
}
var impFinder = importer.Default()
// FindSpecifier finds the name of a package by loading it in from GOPATH
// or a vendor folder.
func FindSpecifier(pkgPath PackagePath) (PackageSpecifier, error) {
var pkg *types.Package
var err error
if cast, ok := impFinder.(types.ImporterFrom); ok {
pkg, err = cast.ImportFrom(string(pkgPath), "", 0)
} else {
pkg, err = impFinder.Import(string(pkgPath))
}
if err != nil {
return "", err
}
return PackageSpecifier(pkg.Name()), nil
}

Просмотреть файл

@ -0,0 +1,144 @@
package common_test
import (
"testing"
. "github.com/Azure/buffalo-azure/generators/common"
)
func TestImportBag_AddImport_Unique(t *testing.T) {
testCases := [][]PackagePath{
{
"github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid",
},
{
"github.com/Azure/buffalo-azure/generators/common",
"github.com/Azure/buffalo-azure/sdk/eventgrid",
},
{
"fmt",
"github.com/Azure/buffalo-azure/generators/common",
},
}
for _, tc := range testCases {
t.Run("", func(t *testing.T) {
seen := map[PackageSpecifier]struct{}{}
subject := NewImportBag()
for _, pkgPath := range tc {
spec := subject.AddImport(pkgPath)
if _, ok := seen[spec]; ok {
t.Logf("duplicate spec returned")
t.Fail()
}
}
})
}
}
func TestImportBag_List(t *testing.T) {
testCases := []struct {
inputs []PackagePath
expected []string
}{
{
inputs: []PackagePath{
"github.com/Azure/buffalo-azure/generators/common",
"github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid",
},
expected: []string{
`"github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid"`,
`"github.com/Azure/buffalo-azure/generators/common"`,
},
},
{
inputs: []PackagePath{
"github.com/Azure/buffalo-azure/sdk/eventgrid",
"github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid",
},
expected: []string{
`eventgrid1 "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid"`,
`"github.com/Azure/buffalo-azure/sdk/eventgrid"`,
},
},
{
inputs: []PackagePath{
"github.com/Azure/buffalo-azure/sdk/eventgrid",
"github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid",
"github.com/Azure/buffalo-azure/generators/eventgrid",
},
expected: []string{
`eventgrid1 "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid"`,
`eventgrid2 "github.com/Azure/buffalo-azure/generators/eventgrid"`,
`"github.com/Azure/buffalo-azure/sdk/eventgrid"`,
},
},
{
inputs: []PackagePath{
"github.com/Azure/buffalo-azure/sdk/eventgrid",
"github.com/Azure/buffalo-azure/sdk/eventgrid",
},
expected: []string{
`"github.com/Azure/buffalo-azure/sdk/eventgrid"`,
},
},
}
for _, tc := range testCases {
t.Run("", func(t *testing.T) {
subject := NewImportBag()
for _, i := range tc.inputs {
subject.AddImport(i)
}
got := subject.List()
if len(got) != len(tc.expected) {
t.Logf("got: %d imports want: %d imports", len(got), len(tc.expected))
t.Fail()
}
for i, val := range tc.expected {
if got[i] != val {
t.Logf("at %d\n\tgot: %s\n\twant: %s", i, got[i], val)
t.Fail()
}
}
})
}
}
func TestImportBag_NewImportBagFromFile(t *testing.T) {
testCases := []struct {
inputFile string
expected []string
}{
{"./testdata/main.go", []string{`"fmt"`, `_ "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid"`}},
}
for _, tc := range testCases {
t.Run("", func(t *testing.T) {
subject, err := NewImportBagFromFile(tc.inputFile)
if err != nil {
t.Error(err)
return
}
got := subject.List()
for i, imp := range tc.expected {
if imp != got[i] {
t.Logf("at: %d\n\tgot: %s\n\twant: %s", i, got[i], imp)
t.Fail()
}
}
if len(got) != len(tc.expected) {
t.Logf("got: %d imports want: %d imports", len(got), len(tc.expected))
t.Fail()
}
})
}
}

11
generators/common/testdata/main.go поставляемый Normal file
Просмотреть файл

@ -0,0 +1,11 @@
package main
import (
"fmt"
_ "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid"
)
func main() {
fmt.Println("Hello, world!!!")
}

Просмотреть файл

@ -11,6 +11,8 @@ import (
"github.com/gobuffalo/buffalo/meta"
"github.com/gobuffalo/makr"
"github.com/markbates/inflect"
"github.com/Azure/buffalo-azure/generators/common"
)
//go:generate go run ./builder/builder.go -o ./static_templates.go ./templates
@ -26,15 +28,24 @@ func (eg *Generator) Run(app meta.App, name string, types map[string]reflect.Typ
type TypeMapping struct {
Identifier string
inflect.Name
PackageSpecifier string
PkgPath string
PkgSpec common.PackageSpecifier
}
flatTypes := make([]TypeMapping, 0, len(types))
ib := common.NewImportBag()
ib.AddImport("encoding/json")
ib.AddImport("errors")
ib.AddImport("net/http")
ib.AddImportWithSpecifier("github.com/Azure/buffalo-azure/sdk/eventgrid", "eg")
ib.AddImport("github.com/gobuffalo/buffalo")
for i, n := range types {
flatTypes = append(flatTypes, TypeMapping{
Identifier: i,
PackageSpecifier: path.Base(n.PkgPath()),
Name: inflect.Name(n.Name()),
Identifier: i,
PkgPath: n.PkgPath(),
PkgSpec: ib.AddImport(common.PackagePath(n.PkgPath())),
Name: inflect.Name(n.Name()),
})
}
@ -60,6 +71,7 @@ func (eg *Generator) Run(app meta.App, name string, types map[string]reflect.Typ
d := make(makr.Data)
d["name"] = iName
d["types"] = flatTypes
d["imports"] = ib.List()
return g.Run(app.Root, d)
}

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -1,22 +1,18 @@
package actions
import (
"encoding/json"
"errors"
"net/http"
"github.com/Azure/buffalo-azure/sdk/eventgrid"
"github.com/gobuffalo/buffalo"
{{ range $i := .imports }} {{$i}}
{{ end }}
)
// My{{$.name.Camel}}Subscriber gathers responds to all Requests sent to a particular endpoint.
type {{$.name.Camel}}Subscriber struct {
eventgrid.Subscriber
eg.Subscriber
}
// New{{$.name.Camel}}Subscriber instantiates {{$.name.Camel}}Subscriber for use in a `buffalo.App`.
func New{{$.name.Camel}}Subscriber(parent eventgrid.Subscriber) (created *{{$.name.Camel}}Subscriber) {
dispatcher := eventgrid.NewTypeDispatchSubscriber(parent)
func New{{$.name.Camel}}Subscriber(parent eg.Subscriber) (created *{{$.name.Camel}}Subscriber) {
dispatcher := eg.NewTypeDispatchSubscriber(parent)
created = &{{$.name.Camel}}Subscriber{
Subscriber: dispatcher,
@ -25,15 +21,15 @@ func New{{$.name.Camel}}Subscriber(parent eventgrid.Subscriber) (created *{{$.na
{{ range $t := .types}}
dispatcher.Bind("{{$t.Identifier}}", created.Receive{{$t.Name.Camel}})
{{end}}
dispatcher.Bind(eventgrid.EventTypeWildcard, created.ReceiveDefault)
dispatcher.Bind(eg.EventTypeWildcard, created.ReceiveDefault)
return
}
{{ range $t := .types }}
// Receive{{$t.Name.Camel}} will respond to an `eventgrid.Event` carrying a serialized `{{$t.Name.Camel}}` as its payload.
func (s *{{$.name.Camel}}Subscriber) Receive{{$t.Name.Camel}}(c buffalo.Context, e eventgrid.Event) error {
var payload {{$t.PackageSpecifier}}.{{$t.Name.Camel}}
func (s *{{$.name.Camel}}Subscriber) Receive{{$t.Name.Camel}}(c buffalo.Context, e eg.Event) error {
var payload {{$t.PkgSpec}}.{{$t.Name.Camel}}
if err := json.Unmarshal(e.Data, &payload); err != nil {
return c.Error(http.StatusBadRequest, errors.New("unable to unmarshal request data"))
}
@ -44,6 +40,6 @@ func (s *{{$.name.Camel}}Subscriber) Receive{{$t.Name.Camel}}(c buffalo.Context,
{{end}}
// ReceiveDefault will respond to an `eventgrid.Event` carrying any EventType as its payload.
func (s *{{$.name.Camel}}Subscriber) ReceiveDefault(c buffalo.Context, e eventgrid.Event) error {
func (s *{{$.name.Camel}}Subscriber) ReceiveDefault(c buffalo.Context, e eg.Event) error {
return c.Error(http.StatusInternalServerError, errors.New("not implemented"))
}

13
generators/eventgrid/testdata/main.go поставляемый Normal file
Просмотреть файл

@ -0,0 +1,13 @@
package main
import (
"fmt"
egdp "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid"
"github.com/Azure/buffalo-azure/sdk/eventgrid"
)
func main() {
fmt.Printf("%#v\n", eventgrid.TypeDispatchSubscriber{})
fmt.Printf("%#v\n", egdp.StorageBlobCreatedEventData{})
}