Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to define a package that will be imported #52

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
module github.com/vburenin/ifacemaker

go 1.18

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/jessevdk/go-flags v1.4.1-0.20181029123624-5de817a9aa20
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.2.2
golang.org/x/tools v0.1.10
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
)
23 changes: 0 additions & 23 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,39 +1,16 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.1-0.20181029123624-5de817a9aa20 h1:dAOsPLhnBzIyxu0VvmnKjlNcIlgMK+erD6VRHDtweMI=
github.com/jessevdk/go-flags v1.4.1-0.20181029123624-5de817a9aa20/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
18 changes: 16 additions & 2 deletions ifacemaker.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"path/filepath"

"github.com/jessevdk/go-flags"

"github.com/vburenin/ifacemaker/maker"
)

Expand All @@ -17,6 +18,7 @@ type cmdlineArgs struct {
IfaceName string `short:"i" long:"iface" description:"Name of the generated interface" required:"true"`
PkgName string `short:"p" long:"pkg" description:"Package name for the generated interface" required:"true"`
IfaceComment string `short:"y" long:"iface-comment" description:"Comment for the interface, default is '// <iface> ...'"`
ImportModule string `short:"m" long:"import-module" description:"Fully qualified module import for packages with a different target package '// <iface> ...'"`

// jessevdk/go-flags doesn't support default values for boolean flags,
// so we use a string for backwards-compatibility and then convert it to a bool later.
Expand Down Expand Up @@ -58,14 +60,26 @@ func main() {
}
files = append(files, matches...)
}
result, err := maker.Make(files, args.StructType, args.Comment, args.PkgName, args.IfaceName, args.IfaceComment, args.copyDocs, args.CopyTypeDoc)
result, err := maker.Make(maker.MakeOptions{
Files: files,
StructType: args.StructType,
Comment: args.Comment,
PkgName: args.PkgName,
IfaceName: args.IfaceName,
IfaceComment: args.IfaceComment,
CopyDocs: args.copyDocs,
CopyTypeDoc: args.CopyTypeDoc,
ImportModule: args.ImportModule,
})
if err != nil {
log.Fatal(err.Error())
}

if args.Output == "" {
fmt.Println(string(result))
} else {
ioutil.WriteFile(args.Output, result, 0644)
if err := ioutil.WriteFile(args.Output, result, 0644); err != nil {
log.Fatal(err)
}
}
}
37 changes: 37 additions & 0 deletions ifacemaker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,22 @@ type User struct {
Name string
}`

var src4 = `package footest

// Hammer is in the same package but in a different file.
type Smiter struct {
options Options
}
func (s *Smiter) Smite(weapon Hammer) error {
return nil
}
`

var srcFile = os.TempDir() + "/ifacemaker_src.go"
var srcFile2 = os.TempDir() + "/test_impl.go"
var srcFile2_ext = os.TempDir() + "/test_impl_extended.go"
var srcFile3 = os.TempDir() + "/footest/footest.go"
var srcFile4 = os.TempDir() + "/footest/smiter.go"

func TestMain(m *testing.M) {
dirPath := os.TempDir() + "/footest"
Expand All @@ -123,6 +135,7 @@ func TestMain(m *testing.M) {
writeTestSourceFile(src2, srcFile2)
writeTestSourceFile(src2_extend, srcFile2_ext)
writeTestSourceFile(src3, srcFile3)
writeTestSourceFile(src4, srcFile4)

os.Exit(m.Run())
}
Expand Down Expand Up @@ -335,6 +348,30 @@ type TestInterface interface {
assert.Equal(t, expected, out)
}

func TestMainUsingDeclarationInSamePackage(t *testing.T) {
os.Args = []string{"cmd", "-f", srcFile4, "-m", "github.com/test/footest", "-s", "Smiter", "-i", "Smiter", "-p", "another", "-c", "DO NOT EDIT: Auto generated", "-d=false"}
out := captureStdout(func() {
main()
})

expected := `// DO NOT EDIT: Auto generated

package another

import (
. "github.com/test/footest"
)

// Smiter ...
type Smiter interface {
Smite(weapon Hammer) error
}

`

assert.Equal(t, expected, out)
}

// not thread safe
func captureStdout(f func()) string {
old := os.Stdout
Expand Down
62 changes: 37 additions & 25 deletions maker/maker.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"io/ioutil"
"log"
"regexp"
"strconv"
"strings"

"golang.org/x/tools/imports"
Expand All @@ -22,8 +23,8 @@ type Method struct {
}

// declaredType identifies the name and package of a type declaration.
type declaredType struct {
Name string
type declaredType struct {
Name string
Package string
}

Expand All @@ -32,7 +33,6 @@ func (dt declaredType) Fullname() string {
return fmt.Sprintf("%s.%s", dt.Package, dt.Name)
}


// Lines return a []string consisting of
// the documentation and code appended
// in chronological order
Expand Down Expand Up @@ -62,7 +62,7 @@ func GetTypeDeclarationName(decl ast.Decl) string {
return ""
}
typeName = typeSpec.Name.Name
break // assuming first value is the good one.
break // assuming first value is the good one.
}

return typeName
Expand Down Expand Up @@ -204,8 +204,6 @@ func MakeInterface(comment, pkgName, ifaceName, ifaceComment string, methods []s
return FormatCode(code)
}



// ParseDeclaredTypes inspect given src code to find type declaractions.
func ParseDeclaredTypes(src []byte) (declaredTypes []declaredType) {
fset := token.NewFileSet()
Expand All @@ -221,7 +219,7 @@ func ParseDeclaredTypes(src []byte) (declaredTypes []declaredType) {
name = GetTypeDeclarationName(d)
if name != "" {
declaredTypes = append(declaredTypes, declaredType{
Name: name,
Name: name,
Package: sourcePackageName,
})
}
Expand All @@ -230,10 +228,6 @@ func ParseDeclaredTypes(src []byte) (declaredTypes []declaredType) {
return
}





// ParseStruct takes in a piece of source code as a
// []byte, the name of the struct it should base the
// interface on and a bool saying whether it should
Expand All @@ -246,7 +240,7 @@ func ParseDeclaredTypes(src []byte) (declaredTypes []declaredType) {
// not, the imports not used will be removed later using the
// 'imports' pkg If anything goes wrong, this method will
// fatally stop the execution
func ParseStruct(src []byte, structName string, copyDocs bool, copyTypeDocs bool, pkgName string, declaredTypes []declaredType) (methods []Method, imports []string, typeDoc string) {
func ParseStruct(src []byte, structName string, copyDocs bool, copyTypeDocs bool, pkgName string, declaredTypes []declaredType, importModule string) (methods []Method, imports []string, typeDoc string) {
fset := token.NewFileSet()
a, err := parser.ParseFile(fset, "", src, parser.ParseComments)
if err != nil {
Expand All @@ -261,6 +255,10 @@ func ParseStruct(src []byte, structName string, copyDocs bool, copyTypeDocs bool
}
}

if importModule != "" {
imports = append(imports, fmt.Sprintf(". %s", strconv.Quote(importModule)))
}

for _, d := range a.Decls {
if a, fd := GetReceiverTypeName(src, d); a == structName {
if !fd.Name.IsExported() {
Expand Down Expand Up @@ -295,19 +293,34 @@ func ParseStruct(src []byte, structName string, copyDocs bool, copyTypeDocs bool
return
}

func Make(files []string, structType, comment, pkgName, ifaceName, ifaceComment string, copyDocs, copyTypeDoc bool) ([]byte, error) {
allMethods := []string{}
allImports := []string{}
allDeclaredTypes := []declaredType{}
// MakeOptions contains options for the Make function.
type MakeOptions struct {
Files []string
StructType string
Comment string
PkgName string
IfaceName string
IfaceComment string
ImportModule string
CopyDocs bool
CopyTypeDoc bool
}

func Make(options MakeOptions) ([]byte, error) {
var (
allMethods []string
allImports []string
allDeclaredTypes []declaredType

mset := make(map[string]struct{})
iset := make(map[string]struct{})
tset := make(map[string]struct{})
mset = make(map[string]struct{})
iset = make(map[string]struct{})
tset = make(map[string]struct{})
)

var typeDoc string

// First pass on all files to find declared types
for _, f := range files {
for _, f := range options.Files {
src, err := ioutil.ReadFile(f)
if err != nil {
return nil, err
Expand All @@ -322,12 +335,12 @@ func Make(files []string, structType, comment, pkgName, ifaceName, ifaceComment
}

// Second pass to build up the interface
for _, f := range files {
for _, f := range options.Files {
src, err := ioutil.ReadFile(f)
if err != nil {
return nil, err
}
methods, imports, parsedTypeDoc := ParseStruct(src, structType, copyDocs, copyTypeDoc, pkgName, allDeclaredTypes)
methods, imports, parsedTypeDoc := ParseStruct(src, options.StructType, options.CopyDocs, options.CopyTypeDoc, options.PkgName, allDeclaredTypes, options.ImportModule)
for _, m := range methods {
if _, ok := mset[m.Code]; !ok {
allMethods = append(allMethods, m.Lines()...)
Expand All @@ -346,14 +359,13 @@ func Make(files []string, structType, comment, pkgName, ifaceName, ifaceComment
}

if typeDoc != "" {
ifaceComment = fmt.Sprintf("%s\n%s", ifaceComment, typeDoc)
options.IfaceComment = fmt.Sprintf("%s\n%s", options.IfaceComment, typeDoc)
}

result, err := MakeInterface(comment, pkgName, ifaceName, ifaceComment, allMethods, allImports)
result, err := MakeInterface(options.Comment, options.PkgName, options.IfaceName, options.IfaceComment, allMethods, allImports)
if err != nil {
return nil, err
}

return result, nil
}

20 changes: 16 additions & 4 deletions maker/maker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ var (
}

type SomeType struct {}`)

)

func TestLines(t *testing.T) {
Expand All @@ -113,7 +112,7 @@ func TestParseDeclaredTypes(t *testing.T) {
Name: "Person",
Package: "main",
},
declaredTypes[0])
declaredTypes[0])
assert.Equal(t, declaredType{
Name: "SomeType",
Package: "main",
Expand All @@ -122,7 +121,7 @@ func TestParseDeclaredTypes(t *testing.T) {
}

func TestParseStruct(t *testing.T) {
methods, imports, typeDoc := ParseStruct(src, "Person", true, true, "", nil)
methods, imports, typeDoc := ParseStruct(src, "Person", true, true, "", nil, "")

assert.Equal(t, "Name() (string)", methods[0].Code)

Expand All @@ -133,6 +132,19 @@ func TestParseStruct(t *testing.T) {
assert.Equal(t, "Person ...", typeDoc)
}

func TestParseStructWithImportModule(t *testing.T) {
methods, imports, typeDoc := ParseStruct(src, "Person", true, true, "", nil, "github.com/test/test")

assert.Equal(t, "Name() (string)", methods[0].Code)

imp, module := imports[0], imports[1]
trimmedImp := strings.TrimSpace(imp)

assert.Equal(t, `notmain "fmt"`, trimmedImp)
assert.Equal(t, `. "github.com/test/test"`, module)
assert.Equal(t, "Person ...", typeDoc)
}

func TestGetReceiverTypeName(t *testing.T) {
fset := token.NewFileSet()
a, err := parser.ParseFile(fset, "", src, parser.ParseComments)
Expand Down Expand Up @@ -203,7 +215,7 @@ func TestFormatFieldList(t *testing.T) {
}

func TestNoCopyTypeDocs(t *testing.T) {
_, _, typeDoc := ParseStruct(src, "Person", true, false, "", nil)
_, _, typeDoc := ParseStruct(src, "Person", true, false, "", nil, "")
assert.Equal(t, "", typeDoc)
}

Expand Down