From 88444f80ff295eff798c4d5252fe96f09c913a96 Mon Sep 17 00:00:00 2001 From: Erik Rasmussen Date: Fri, 29 Nov 2024 21:09:58 -0600 Subject: [PATCH] Everything except the dash input (#490) * WIP refactoring arg parsing and inputs # Conflicts: # pkg/contract.go # pkg/testing/os.go # Conflicts: # internal/os.go # Conflicts: # pkg/cmd/gen.go # pkg/input/fs.go # pkg/input/reader.go * WIP getting it all rejiggered # Conflicts: # pkg/cmd/internal/fs.go # pkg/cmd/internal/parse.go # pkg/cmd/internal/parse_test.go # pkg/cmd/internal/reader.go # pkg/contract.go # Conflicts: # pkg/contract.go # pkg/plugin/aggregate.go # pkg/plugin/aggregate_test.go # pkg/plugin/github/release.go # pkg/plugin/path.go # pkg/testing/plugin.go # pkg/testing/target.go # Conflicts: # pkg/cmd/internal/fs_test.go # Conflicts: # pkg/cmd/gen.go * WIP fiddling with mocks # Conflicts: # pkg/cmd/internal/fs.go # pkg/cmd/internal/parse.go # pkg/cmd/internal/parse_test.go # pkg/cmd/internal/reader.go # pkg/testing/os.go # Conflicts: # pkg/cmd/internal/fs_test.go * Input parsing tests # Conflicts: # pkg/cmd/internal/fs_test.go * dfskjdl # Conflicts: # pkg/cmd/gen.go * Meh # Conflicts: # pkg/contract.go # pkg/plugin/aggregate.go # pkg/plugin/github/release.go # pkg/plugin/path_test.go # Conflicts: # pkg/cmd/gen.go * Added panic for unimplemented functions in MockGenerator The MockGenerator now panics if the StringFunc or ExecuteFunc are not implemented. This change ensures that these methods are properly set before being called, preventing silent failures. The default implementation of ExecuteFunc in NewMockGenerator has been removed as it is no longer needed with this new check. * Added context to generator function calls The generator function in the plugin package now accepts a context as its first argument. This change has been reflected across all usages of this function, including tests. Additionally, minor adjustments were made to import statements and method parameters for consistency and readability. * Updated error messages and plugin search logic The changes include an update to the error message in e2e tests for better clarity. A new function 'Find' has been added to lookup plugins based on a predicate, improving the plugin search mechanism. The string representation of 'fromPath' has been simplified by removing unnecessary prefix. In typescript.go, the generator now uses the new 'Find' function for searching suitable plugins, enhancing code readability and efficiency. Corresponding test cases have also been updated to reflect these changes. * Updated TypeScript generator logic The TypeScript generator logic has been simplified. The 'Choose' function, which was previously used to select a generator from an available list, has been removed. This change simplifies the code and reduces potential error scenarios. Additionally, the declaration of the 'TypeScript' variable has been moved up for better visibility in the code structure. --- cmd/ux/e2e_test.go | 9 ++++++ pkg/cmd/gen.go | 1 + pkg/contract.go | 2 -- pkg/gen/cli/generator.go | 18 +++++------ pkg/plugin/aggregate.go | 20 ------------ pkg/plugin/aggregate_test.go | 10 +++--- pkg/plugin/github/release.go | 10 +----- pkg/plugin/lookup.go | 13 ++++++++ pkg/plugin/path.go | 2 +- pkg/plugin/path_test.go | 2 +- pkg/target/choose.go | 4 +-- pkg/target/typescript.go | 61 +++++++---------------------------- pkg/target/typescript_test.go | 31 ++++++++---------- pkg/testing/generator.go | 45 +++++++++++++------------- pkg/testing/plugin.go | 18 +++-------- pkg/testing/target.go | 39 ++++------------------ 16 files changed, 99 insertions(+), 186 deletions(-) diff --git a/cmd/ux/e2e_test.go b/cmd/ux/e2e_test.go index 349f5fef..2a3b131f 100644 --- a/cmd/ux/e2e_test.go +++ b/cmd/ux/e2e_test.go @@ -28,6 +28,15 @@ var _ = Describe("End to end", func() { }) Describe("gen", func() { + It("should error when input is not provided", func(ctx context.Context) { + cmd := UxCommand(ctx, "gen", "ts") + + out, err := cmd.CombinedOutput() + + Expect(err).To(HaveOccurred()) + Expect(string(out)).To(Equal("no input specified\n")) + }) + It("should read spec from yaml file", FlakeAttempts(5), func(ctx context.Context) { input := filepath.Join(tsSuitePath(), "interface", "source.yml") output, err := os.ReadFile(filepath.Join(tsSuitePath(), "interface", "target.ts")) diff --git a/pkg/cmd/gen.go b/pkg/cmd/gen.go index f69e823a..c537f9c8 100644 --- a/pkg/cmd/gen.go +++ b/pkg/cmd/gen.go @@ -22,6 +22,7 @@ func NewGen() *cobra.Command { Short: "Run code generation for TARGET", Args: cobra.RangeArgs(1, 3), Run: func(cmd *cobra.Command, args []string) { + // log.SetLevel(log.DebugLevel) ctx := cmd.Context() config, err := run.ParseArgs(args) if err != nil { diff --git a/pkg/contract.go b/pkg/contract.go index 29705748..61d1b7b8 100644 --- a/pkg/contract.go +++ b/pkg/contract.go @@ -30,13 +30,11 @@ type Generator interface { type Plugin interface { fmt.Stringer - SinkGenerator(Target) (SinkGenerator, error) Generator(context.Context, Target) (Generator, error) } type Target interface { fmt.Stringer - Choose([]SinkGenerator) (SinkGenerator, error) Generator(iter.Seq[Plugin]) (Generator, error) } diff --git a/pkg/gen/cli/generator.go b/pkg/gen/cli/generator.go index 3ba70ee5..db23c9a8 100644 --- a/pkg/gen/cli/generator.go +++ b/pkg/gen/cli/generator.go @@ -15,17 +15,17 @@ import ( tdlv1alpha1 "github.com/unstoppablemango/tdl/pkg/unmango/dev/tdl/v1alpha1" ) -type cli struct { +type generator struct { name string args []string enc tdl.MediaType stdout bool } -type Option func(*cli) +type Option func(*generator) // Execute implements tdl.Generator. -func (c cli) Execute(ctx context.Context, spec *tdlv1alpha1.Spec) (afero.Fs, error) { +func (c generator) Execute(ctx context.Context, spec *tdlv1alpha1.Spec) (afero.Fs, error) { log.Debug("creating temp directory") tmp, err := os.MkdirTemp("", "") if err != nil { @@ -63,12 +63,12 @@ func (c cli) Execute(ctx context.Context, spec *tdlv1alpha1.Spec) (afero.Fs, err } // String implements fmt.Stringer -func (c cli) String() string { +func (c generator) String() string { return c.name } func New(name string, options ...Option) tdl.Generator { - gen := cli{ + gen := generator{ name: name, enc: mediatype.ApplicationProtobuf, } @@ -78,23 +78,23 @@ func New(name string, options ...Option) tdl.Generator { } func WithArgs(args ...string) Option { - return func(c *cli) { + return func(c *generator) { c.args = args } } func WithEncoding(media tdl.MediaType) Option { - return func(c *cli) { + return func(c *generator) { c.enc = media } } -func ExpectStdout(cli *cli) { +func ExpectStdout(cli *generator) { cli.stdout = true } func WithExpectStdout(stdout bool) Option { - return func(c *cli) { + return func(c *generator) { c.stdout = stdout } } diff --git a/pkg/plugin/aggregate.go b/pkg/plugin/aggregate.go index 2a4832bf..173f811f 100644 --- a/pkg/plugin/aggregate.go +++ b/pkg/plugin/aggregate.go @@ -14,26 +14,6 @@ const UnwrapDepth = 3 type Aggregate []tdl.Plugin -// SinkGenerator implements tdl.Plugin. -func (a Aggregate) SinkGenerator(t tdl.Target) (tdl.SinkGenerator, error) { - if len(a) == 0 { - return nil, errors.New("empty aggregate plugin") - } - - errs := []error{} - for _, p := range a.sorted() { - g, err := p.SinkGenerator(t) - if err == nil { - return g, nil - } - - log.Error(err, "generator", g) - errs = append(errs, err) - } - - return nil, errors.Join(errs...) -} - // Generator implements tdl.Plugin. func (a Aggregate) Generator(ctx context.Context, t tdl.Target) (tdl.Generator, error) { if len(a) == 0 { diff --git a/pkg/plugin/aggregate_test.go b/pkg/plugin/aggregate_test.go index 3520afad..73c52ded 100644 --- a/pkg/plugin/aggregate_test.go +++ b/pkg/plugin/aggregate_test.go @@ -1,6 +1,8 @@ package plugin_test import ( + "context" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -18,15 +20,15 @@ var _ = Describe("Aggregate", func() { Expect(result).To(ConsistOf(p)) }) - It("should pick the given generator", func() { - g := testing.NewMockGenerator() + It("should pick the given generator", func(ctx context.Context) { + g := &testing.MockGenerator{} p := (&testing.MockPlugin{}). - WithGenerator(func(tdl.Target) (tdl.SinkGenerator, error) { + WithGenerator(func(tdl.Target) (tdl.Generator, error) { return g, nil }) agg := plugin.NewAggregate(p) - result, err := agg.SinkGenerator(&testing.MockTarget{}) + result, err := agg.Generator(ctx, &testing.MockTarget{}) Expect(err).NotTo(HaveOccurred()) Expect(result).To(BeIdenticalTo(g)) diff --git a/pkg/plugin/github/release.go b/pkg/plugin/github/release.go index 196d5c19..b4d66379 100644 --- a/pkg/plugin/github/release.go +++ b/pkg/plugin/github/release.go @@ -12,7 +12,6 @@ import ( "github.com/unmango/go/iter" "github.com/unmango/go/option" tdl "github.com/unstoppablemango/tdl/pkg" - "github.com/unstoppablemango/tdl/pkg/gen" "github.com/unstoppablemango/tdl/pkg/plugin/cache" "github.com/unstoppablemango/tdl/pkg/progress" ) @@ -32,14 +31,7 @@ type release struct { type Option func(*release) -// SinkGenerator implements tdl.Plugin. -func (g *release) SinkGenerator(target tdl.Target) (tdl.SinkGenerator, error) { - return target.Choose([]tdl.SinkGenerator{ - gen.NewCli("uml2ts"), - }) -} - -// SinkGenerator implements tdl.Plugin. +// Generator implements tdl.Plugin. func (g *release) Generator(ctx context.Context, target tdl.Target) (tdl.Generator, error) { return target.Generator(iter.Singleton[tdl.Plugin](g)) } diff --git a/pkg/plugin/lookup.go b/pkg/plugin/lookup.go index a3314947..c475001c 100644 --- a/pkg/plugin/lookup.go +++ b/pkg/plugin/lookup.go @@ -4,6 +4,7 @@ import ( "context" "errors" + "github.com/unmango/go/iter" tdl "github.com/unstoppablemango/tdl/pkg" "github.com/unstoppablemango/tdl/pkg/plugin/cache" ) @@ -25,6 +26,18 @@ func FirstAvailable(target tdl.Target) (tdl.Plugin, error) { return nil, errors.New("no plugins available") } +func Find(plugins iter.Seq[tdl.Plugin], pred func(tdl.Plugin) bool) (tdl.Plugin, bool) { + for plugin := range plugins { + for _, nested := range Unwrap(plugin) { + if pred(nested) { + return nested, true + } + } + } + + return nil, false +} + func tryCache(p tdl.Plugin) error { c := cache.XdgBinHome r, ok := p.(cache.Cachable) diff --git a/pkg/plugin/path.go b/pkg/plugin/path.go index 0e47aac9..af1bae72 100644 --- a/pkg/plugin/path.go +++ b/pkg/plugin/path.go @@ -40,7 +40,7 @@ func (f fromPath) Generator(context.Context, tdl.Target) (tdl.Generator, error) // String implements tdl.Plugin. func (f fromPath) String() string { - return fmt.Sprintf("PATH: %s", f.name) + return f.name } func (f fromPath) Order() int { diff --git a/pkg/plugin/path_test.go b/pkg/plugin/path_test.go index 931af7e3..a3b0f0f7 100644 --- a/pkg/plugin/path_test.go +++ b/pkg/plugin/path_test.go @@ -21,7 +21,7 @@ var _ = Describe("Path", func() { It("should stringify", func() { p := plugin.FromPath("path-thing") - Expect(p.String()).To(Equal("PATH: path-thing")) + Expect(p.String()).To(Equal("path-thing")) }) Describe("Generator", func() { diff --git a/pkg/target/choose.go b/pkg/target/choose.go index 609b7108..deac5b79 100644 --- a/pkg/target/choose.go +++ b/pkg/target/choose.go @@ -7,7 +7,7 @@ import ( ) type RejectionErr struct { - Generator tdl.SinkGenerator + Generator tdl.Generator Reason string } @@ -15,6 +15,6 @@ func (e RejectionErr) Error() string { return fmt.Sprintf("rejected %s: %s", e.Generator, e.Reason) } -func Reject(generator tdl.SinkGenerator, reason string) error { +func Reject(generator tdl.Generator, reason string) error { return &RejectionErr{generator, reason} } diff --git a/pkg/target/typescript.go b/pkg/target/typescript.go index 080afe0a..2e703641 100644 --- a/pkg/target/typescript.go +++ b/pkg/target/typescript.go @@ -3,70 +3,31 @@ package target import ( "context" "errors" - "path/filepath" + "github.com/charmbracelet/log" "github.com/unmango/go/iter" - "github.com/unmango/go/slices" tdl "github.com/unstoppablemango/tdl/pkg" - "github.com/unstoppablemango/tdl/pkg/gen" "github.com/unstoppablemango/tdl/pkg/plugin" ) type typescript string -// Generator implements tdl.Target. -func (t typescript) Generator(available iter.Seq[tdl.Plugin]) (tdl.Generator, error) { - for p := range available { - if p.String() == "uml2ts" { - return p.Generator(context.TODO(), t) - } - } - - return nil, errors.New("no suitable plugin") -} - var TypeScript typescript = "TypeScript" -// Choose implements tdl.Target. -func (t typescript) Choose(available []tdl.SinkGenerator) (tdl.SinkGenerator, error) { - if len(available) == 0 { - return nil, errors.New("no generators to choose from") - } - - errs := []error{} - for _, g := range available { - if err := supported(g); err != nil { - errs = append(errs, err) - } else { - return g, nil - } - } - - return nil, errors.Join(errs...) -} - -// Plugins implements tdl.Target. -func (t typescript) Plugins() iter.Seq[tdl.Plugin] { - return slices.Values([]tdl.Plugin{ - plugin.Uml2Ts, +// Generator implements tdl.Target. +func (t typescript) Generator(available iter.Seq[tdl.Plugin]) (tdl.Generator, error) { + plugin, ok := plugin.Find(available, func(p tdl.Plugin) bool { + log.Debugf("considering %s", p) + return p.String() == "uml2ts" }) + if !ok { + return nil, errors.New("no suitable plugin") + } else { + return plugin.Generator(context.TODO(), t) + } } // String implements tdl.Target. func (t typescript) String() string { return string(t) } - -func supported(g tdl.SinkGenerator) error { - cli, ok := g.(*gen.Cli) - if !ok { - return Reject(g, "not a CLI") - } - - name := filepath.Base(cli.String()) - if name != "uml2ts" { - return Reject(g, "only uml2ts is supported") - } - - return nil -} diff --git a/pkg/target/typescript_test.go b/pkg/target/typescript_test.go index 35449642..5dcd11be 100644 --- a/pkg/target/typescript_test.go +++ b/pkg/target/typescript_test.go @@ -4,7 +4,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/unmango/go/slices" + "github.com/unmango/go/iter" tdl "github.com/unstoppablemango/tdl/pkg" "github.com/unstoppablemango/tdl/pkg/plugin" "github.com/unstoppablemango/tdl/pkg/target" @@ -12,31 +12,26 @@ import ( ) var _ = Describe("Typescript", func() { - It("should list the uml2ts plugin", func() { - expected := plugin.NewAggregate(plugin.Uml2Ts) - - plugins := target.TypeScript.Plugins() - - Expect(slices.Collect(plugins)).To(ConsistOf(expected)) - }) - - Describe("Choose", func() { + Describe("Generator", func() { It("should choose uml2ts", func() { - expected, err := plugin.Uml2Ts.SinkGenerator(target.TypeScript) - Expect(err).NotTo(HaveOccurred()) - - chosen, err := target.TypeScript.Choose([]tdl.SinkGenerator{expected}) + chosen, err := target.TypeScript.Generator( + iter.Singleton(plugin.Uml2Ts), + ) Expect(err).NotTo(HaveOccurred()) - Expect(chosen).To(BeIdenticalTo(expected)) + Expect(chosen).NotTo(BeNil()) // TODO }) It("should ignore unsupported generators", func() { - g := testing.NewMockGenerator() + g := (&testing.MockPlugin{}).WithString(func() string { + return "test" + }) - _, err := target.TypeScript.Choose([]tdl.SinkGenerator{g}) + _, err := target.TypeScript.Generator( + iter.Singleton[tdl.Plugin](g), + ) - Expect(err).To(MatchError(ContainSubstring("not a CLI"))) + Expect(err).To(MatchError("no suitable plugin")) }) }) }) diff --git a/pkg/testing/generator.go b/pkg/testing/generator.go index 934690c2..bff504a8 100644 --- a/pkg/testing/generator.go +++ b/pkg/testing/generator.go @@ -3,30 +3,39 @@ package testing import ( "context" + "github.com/spf13/afero" tdl "github.com/unstoppablemango/tdl/pkg" tdlv1alpha1 "github.com/unstoppablemango/tdl/pkg/unmango/dev/tdl/v1alpha1" ) type MockGenerator struct { - ExecuteFunc func(*tdlv1alpha1.Spec, tdl.Sink) error + ExecuteFunc func(context.Context, *tdlv1alpha1.Spec) (afero.Fs, error) + StringFunc func() string } -type MockGeneratorStringer struct { - *MockGenerator - StringFunc func() string +// String implements tdl.Generator. +func (m *MockGenerator) String() string { + if m.StringFunc == nil { + panic("unimplemented") + } + + return m.StringFunc() } // Execute implements tdl.Generator. func (m *MockGenerator) Execute( ctx context.Context, spec *tdlv1alpha1.Spec, - sink tdl.Sink, -) error { - return m.ExecuteFunc(spec, sink) +) (afero.Fs, error) { + if m.ExecuteFunc == nil { + panic("unimplemented") + } + + return m.ExecuteFunc(ctx, spec) } func (m *MockGenerator) WithExecute( - fn func(*tdlv1alpha1.Spec, tdl.Sink) error, + fn func(context.Context, *tdlv1alpha1.Spec) (afero.Fs, error), ) *MockGenerator { m.ExecuteFunc = fn return m @@ -34,25 +43,15 @@ func (m *MockGenerator) WithExecute( func (m *MockGenerator) WithString( fn func() string, -) *MockGeneratorStringer { - return &MockGeneratorStringer{ - MockGenerator: m, - StringFunc: fn, - } +) *MockGenerator { + m.StringFunc = fn + return m } -func (m *MockGenerator) WithName(name string) *MockGeneratorStringer { +func (m *MockGenerator) WithName(name string) *MockGenerator { return m.WithString(func() string { return name }) } -var _ tdl.SinkGenerator = &MockGenerator{} - -func NewMockGenerator() *MockGenerator { - return &MockGenerator{ - ExecuteFunc: func(*tdlv1alpha1.Spec, tdl.Sink) error { - panic("unimplemented") - }, - } -} +var _ tdl.Generator = &MockGenerator{} diff --git a/pkg/testing/plugin.go b/pkg/testing/plugin.go index 640116e9..680392a1 100644 --- a/pkg/testing/plugin.go +++ b/pkg/testing/plugin.go @@ -7,9 +7,8 @@ import ( ) type MockPlugin struct { - GeneratorFunc func(tdl.Target) (tdl.Generator, error) - SinkGeneratorFunc func(tdl.Target) (tdl.SinkGenerator, error) - StringFunc func() string + GeneratorFunc func(tdl.Target) (tdl.Generator, error) + StringFunc func() string } // Generator implements tdl.Plugin. @@ -21,24 +20,15 @@ func (m *MockPlugin) Generator(ctx context.Context, t tdl.Target) (tdl.Generator return m.GeneratorFunc(t) } -// SinkGenerator implements tdl.Plugin. -func (m *MockPlugin) SinkGenerator(t tdl.Target) (tdl.SinkGenerator, error) { - if m.SinkGeneratorFunc == nil { - panic("unimplemented") - } - - return m.SinkGeneratorFunc(t) -} - // String implements tdl.Plugin. func (m *MockPlugin) String() string { return m.StringFunc() } func (m *MockPlugin) WithGenerator( - fn func(t tdl.Target) (tdl.SinkGenerator, error), + fn func(t tdl.Target) (tdl.Generator, error), ) *MockPlugin { - m.SinkGeneratorFunc = fn + m.GeneratorFunc = fn return m } diff --git a/pkg/testing/target.go b/pkg/testing/target.go index 3682c24d..2bfa9203 100644 --- a/pkg/testing/target.go +++ b/pkg/testing/target.go @@ -6,9 +6,7 @@ import ( ) type MockTarget struct { - ChooseFunc func([]tdl.SinkGenerator) (tdl.SinkGenerator, error) GeneratorFunc func(iter.Seq[tdl.Plugin]) (tdl.Generator, error) - PluginsFunc func() iter.Seq[tdl.Plugin] StringFunc func() string } @@ -21,24 +19,6 @@ func (m *MockTarget) Generator(available iter.Seq[tdl.Plugin]) (tdl.Generator, e return m.GeneratorFunc(available) } -// Choose implements tdl.Target. -func (m *MockTarget) Choose(available []tdl.SinkGenerator) (tdl.SinkGenerator, error) { - if m.ChooseFunc == nil { - panic("unimplemented") - } - - return m.ChooseFunc(available) -} - -// Plugins implements tdl.Target. -func (m *MockTarget) Plugins() iter.Seq[tdl.Plugin] { - if m.PluginsFunc == nil { - panic("unimplemented") - } - - return m.PluginsFunc() -} - // String implements tdl.Target. func (m *MockTarget) String() string { if m.StringFunc == nil { @@ -48,24 +28,17 @@ func (m *MockTarget) String() string { return m.StringFunc() } -func (m *MockTarget) WithChoose( - fn func([]tdl.SinkGenerator) (tdl.SinkGenerator, error), -) *MockTarget { - m.ChooseFunc = fn - return m -} - -func (m *MockTarget) WithPlugins( - fn func() iter.Seq[tdl.Plugin], +func (m *MockTarget) WithString( + fn func() string, ) *MockTarget { - m.PluginsFunc = fn + m.StringFunc = fn return m } -func (m *MockTarget) WithString( - fn func() string, +func (m *MockTarget) WithGenerator( + fn func(iter.Seq[tdl.Plugin]) (tdl.Generator, error), ) *MockTarget { - m.StringFunc = fn + m.GeneratorFunc = fn return m }