From 6c8bbe1f6392a43cc3397fdbc0de4be1de599d54 Mon Sep 17 00:00:00 2001 From: Christian Sueiras Date: Tue, 6 Apr 2021 19:15:08 -0400 Subject: [PATCH] Support sourcing from packages --- cmd/reinforcer/cmd/root.go | 8 ++- cmd/reinforcer/cmd/root_test.go | 24 +++++++++ internal/generator/executor/executor.go | 52 ++++++++++++++++---- internal/generator/executor/executor_test.go | 24 +++++++++ 4 files changed, 97 insertions(+), 11 deletions(-) diff --git a/cmd/reinforcer/cmd/root.go b/cmd/reinforcer/cmd/root.go index 5bd09bc..d3caae2 100644 --- a/cmd/reinforcer/cmd/root.go +++ b/cmd/reinforcer/cmd/root.go @@ -93,7 +93,11 @@ such as circuit breaker, retries, timeouts, etc. if err != nil { return err } - if len(sources) == 0 { + sourcePackages, err := flags.GetStringSlice("srcpkg") + if err != nil { + return err + } + if len(sources)+len(sourcePackages) == 0 { goFile := os.Getenv("GOFILE") if goFile == "" { return fmt.Errorf("no source provided") @@ -132,6 +136,7 @@ such as circuit breaker, retries, timeouts, etc. gen, err := exec.Execute(&executor.Parameters{ Sources: sources, + SourcePackages: sourcePackages, Targets: targets, TargetsAll: targetAll, OutPkg: outPkg, @@ -155,6 +160,7 @@ such as circuit breaker, retries, timeouts, etc. flags.BoolP("debug", "d", false, "enables debug logs") flags.BoolP("silent", "q", false, "disables logging. Mutually exclusive with the debug flag.") flags.StringSliceP("src", "s", nil, "source files to scan for the target interface or struct. If unspecified the file pointed by the env variable GOFILE will be used.") + flags.StringSliceP("srcpkg", "k", nil, "source packages to scan for the target interface or struct.") flags.StringSliceP("target", "t", []string{}, "name of target type or regex to match interface or struct names with") flags.BoolP("targetall", "a", false, "codegen for all exported interfaces/structs discovered. This option is mutually exclusive with the target option.") flags.StringP("outputdir", "o", "./reinforced", "directory to write the generated code to") diff --git a/cmd/reinforcer/cmd/root_test.go b/cmd/reinforcer/cmd/root_test.go index dde5205..ce88cf9 100644 --- a/cmd/reinforcer/cmd/root_test.go +++ b/cmd/reinforcer/cmd/root_test.go @@ -17,6 +17,7 @@ func TestRootCommand(t *testing.T) { exec := &mocks.Executor{} exec.On("Execute", &executor.Parameters{ Sources: []string{"/path/to/target.go"}, + SourcePackages: []string{}, Targets: []string{"Client", "SomeOtherClient"}, TargetsAll: false, OutPkg: "reinforced", @@ -32,10 +33,31 @@ func TestRootCommand(t *testing.T) { require.NoError(t, c.Execute()) }) + t.Run("Source packages", func(t *testing.T) { + exec := &mocks.Executor{} + exec.On("Execute", &executor.Parameters{ + Sources: []string{}, + SourcePackages: []string{"github.com/csueiras/somelib"}, + Targets: []string{"Client", "SomeOtherClient"}, + TargetsAll: false, + OutPkg: "reinforced", + IgnoreNoReturnMethods: false, + }).Return(gen, nil) + writ := &mocks.Writer{} + writ.On("Write", "./reinforced", gen).Return(nil) + + b := bytes.NewBufferString("") + c := cmd.NewRootCmd(exec, writ) + c.SetOut(b) + c.SetArgs([]string{"--srcpkg=github.com/csueiras/somelib", "--target=Client", "--target=SomeOtherClient", "--outputdir=./reinforced"}) + require.NoError(t, c.Execute()) + }) + t.Run("Target All", func(t *testing.T) { exec := &mocks.Executor{} exec.On("Execute", &executor.Parameters{ Sources: []string{"/path/to/target.go"}, + SourcePackages: []string{}, Targets: []string{}, TargetsAll: true, OutPkg: "reinforced", @@ -55,6 +77,7 @@ func TestRootCommand(t *testing.T) { exec := &mocks.Executor{} exec.On("Execute", &executor.Parameters{ Sources: []string{"/path/to/target.go"}, + SourcePackages: []string{}, Targets: []string{"Client", "SomeOtherClient"}, TargetsAll: false, OutPkg: "reinforced", @@ -74,6 +97,7 @@ func TestRootCommand(t *testing.T) { exec := &mocks.Executor{} exec.On("Execute", &executor.Parameters{ Sources: []string{"/path/to/target.go"}, + SourcePackages: []string{}, Targets: []string{}, TargetsAll: true, OutPkg: "reinforced", diff --git a/internal/generator/executor/executor.go b/internal/generator/executor/executor.go index e0e0017..d1186c9 100644 --- a/internal/generator/executor/executor.go +++ b/internal/generator/executor/executor.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/csueiras/reinforcer/internal/generator" "github.com/csueiras/reinforcer/internal/loader" + "github.com/pkg/errors" ) // ErrNoTargetableTypesFound indicates that no types that could be targeted for code generation were discovered @@ -19,8 +20,10 @@ type Loader interface { // Parameters are the input parameters for the executor type Parameters struct { - // Sources are the paths to the packages that are eligible for targetting + // Sources are the paths to the packages that are eligible for targeting Sources []string + // SourcePackages are the packages that are eligible for targeting (e.g. github.com/csueiras/somelib) + SourcePackages []string // Targets contains the target types to search for, these are expressions that may contain RegEx Targets []string // TargetsAll enables targeting of every exported interface type @@ -47,6 +50,26 @@ func (e *Executor) Execute(settings *Parameters) (*generator.Generated, error) { var cfg []*generator.FileConfig var err error + + for _, sourcePkg := range settings.SourcePackages { + var match map[string]*loader.Result + if settings.TargetsAll { + match, err = e.loader.LoadAll(sourcePkg, loader.PackageLoadMode) + } else { + match, err = e.loader.LoadMatched(sourcePkg, settings.Targets, loader.PackageLoadMode) + } + if err != nil { + return nil, errors.Wrapf(err, "failed to load from pkg=%s", sourcePkg) + } + + configs, err := createFileConfigs(discoveredTypes, match) + if err != nil { + return nil, err + } + + cfg = append(cfg, configs...) + } + for _, source := range settings.Sources { var match map[string]*loader.Result if settings.TargetsAll { @@ -55,17 +78,13 @@ func (e *Executor) Execute(settings *Parameters) (*generator.Generated, error) { match, err = e.loader.LoadMatched(source, settings.Targets, loader.FileLoadMode) } if err != nil { - return nil, err + return nil, errors.Wrapf(err, "failed to load from file=%s", source) } - - // Check types aren't repeated before adding them to the generator's config - for typName, res := range match { - if _, ok := discoveredTypes[typName]; ok { - return nil, fmt.Errorf("multiple types with same name discovered with name %s", typName) - } - discoveredTypes[typName] = struct{}{} - cfg = append(cfg, generator.NewFileConfig(typName, typName, res.Methods)) + configs, err := createFileConfigs(discoveredTypes, match) + if err != nil { + return nil, err } + cfg = append(cfg, configs...) } if len(cfg) == 0 { @@ -82,3 +101,16 @@ func (e *Executor) Execute(settings *Parameters) (*generator.Generated, error) { } return code, nil } + +func createFileConfigs(discoveredSet map[string]struct{}, match map[string]*loader.Result) ([]*generator.FileConfig, error) { + var cfg []*generator.FileConfig + for typName, res := range match { + // Check types aren't repeated before adding them to the generator's config + if _, ok := discoveredSet[typName]; ok { + return nil, errors.Errorf("multiple types with same name discovered with name %s", typName) + } + discoveredSet[typName] = struct{}{} + cfg = append(cfg, generator.NewFileConfig(typName, typName, res.Methods)) + } + return cfg, nil +} diff --git a/internal/generator/executor/executor_test.go b/internal/generator/executor/executor_test.go index 8832d42..21af151 100644 --- a/internal/generator/executor/executor_test.go +++ b/internal/generator/executor/executor_test.go @@ -35,6 +35,30 @@ func TestExecutor_Execute(t *testing.T) { require.Equal(t, "LockService", got.Files[0].TypeName) }) + t.Run("Loads types from packages", func(t *testing.T) { + l := &mocks.Loader{} + l.On("LoadMatched", "github.com/csueiras/somelib", []string{"MyService"}, loader.PackageLoadMode).Return( + map[string]*loader.Result{ + "LockService": { + Name: "LockService", + Methods: createTestServiceMethods(), + }, + }, nil, + ) + + exec := executor.New(l) + got, err := exec.Execute(&executor.Parameters{ + SourcePackages: []string{"github.com/csueiras/somelib"}, + Targets: []string{"MyService"}, + OutPkg: "testpkg", + IgnoreNoReturnMethods: false, + }) + require.NoError(t, err) + require.NotNil(t, got) + require.Equal(t, 1, len(got.Files)) + require.Equal(t, "LockService", got.Files[0].TypeName) + }) + t.Run("No types found", func(t *testing.T) { l := &mocks.Loader{} l.On("LoadMatched", "./testpkg.go", []string{"MyService"}, loader.FileLoadMode).