From 3f951e4ff7114ec4df091197bf8f42e0aff3c3ff Mon Sep 17 00:00:00 2001 From: Erik Rasmussen Date: Thu, 19 Dec 2024 00:21:16 -0600 Subject: [PATCH] Spit and polish --- pkg/cmd/internal/cwd.go | 23 +++++++ pkg/cmd/internal/print.go | 14 ++++- pkg/cmd/tool.go | 27 +++++++-- pkg/contract.go | 2 +- pkg/tool/crd2pulumi/options.go | 86 +++++++++++++++++--------- pkg/tool/crd2pulumi/options_test.go | 93 +++++++++++++++++------------ pkg/tool/crd2pulumi/tool.go | 47 +++++++++++---- pkg/tool/crd2pulumi/tool_test.go | 5 +- 8 files changed, 208 insertions(+), 89 deletions(-) create mode 100644 pkg/cmd/internal/cwd.go diff --git a/pkg/cmd/internal/cwd.go b/pkg/cmd/internal/cwd.go new file mode 100644 index 00000000..ea04bf29 --- /dev/null +++ b/pkg/cmd/internal/cwd.go @@ -0,0 +1,23 @@ +package internal + +import ( + "os" + "path/filepath" +) + +// Cwd cleans and roots [op] or retrieves the current directory +func Cwd(op string) (cwd string, err error) { + if cwd = op; op == "" { + if cwd, err = os.Getwd(); err != nil { + return + } + } + + if !filepath.IsAbs(cwd) { + if cwd, err = filepath.Abs(cwd); err != nil { + return + } + } + + return +} diff --git a/pkg/cmd/internal/print.go b/pkg/cmd/internal/print.go index 88672324..515877ef 100644 --- a/pkg/cmd/internal/print.go +++ b/pkg/cmd/internal/print.go @@ -8,7 +8,9 @@ import ( ) func PrintFs(fsys afero.Fs) error { - return afero.Walk(fsys, "", + var count int + + err := afero.Walk(fsys, "", func(path string, info fs.FileInfo, err error) error { if err != nil { return err @@ -17,8 +19,18 @@ func PrintFs(fsys afero.Fs) error { return nil } + count++ _, err = fmt.Println(path) return err }, ) + if err != nil { + return err + } + + if count == 0 { + fmt.Println("no output") + } + + return nil } diff --git a/pkg/cmd/tool.go b/pkg/cmd/tool.go index a1c778b3..797abab6 100644 --- a/pkg/cmd/tool.go +++ b/pkg/cmd/tool.go @@ -1,7 +1,7 @@ package cmd import ( - "os" + "fmt" "github.com/charmbracelet/log" "github.com/spf13/afero" @@ -9,6 +9,7 @@ import ( "github.com/unmango/go/fs/ignore" "github.com/unstoppablemango/tdl/internal/util" "github.com/unstoppablemango/tdl/pkg/cmd/internal" + "github.com/unstoppablemango/tdl/pkg/logging" "github.com/unstoppablemango/tdl/pkg/plugin" "github.com/unstoppablemango/tdl/pkg/target" "github.com/unstoppablemango/tdl/pkg/tool" @@ -17,11 +18,14 @@ import ( var DefaultIgnorePatterns = tool.DefaultIgnorePatterns func NewTool() *cobra.Command { - return &cobra.Command{ + var cwd string + + cmd := &cobra.Command{ Use: "tool [NAME]", Short: "Execute a tool", Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { + logging.Init() t, err := target.Parse(args[0]) if err != nil { util.Fail(err) @@ -33,12 +37,11 @@ func NewTool() *cobra.Command { util.Fail(err) } - work, err := os.Getwd() - if err != nil { + if cwd, err = internal.Cwd(cwd); err != nil { util.Fail(err) } - src := afero.NewBasePathFs(afero.NewOsFs(), work) + src := afero.NewBasePathFs(afero.NewOsFs(), cwd) if i, err := internal.OpenGitIgnore(ctx); err != nil { log.Info("not a git repo", "err", err) src = ignore.NewFsFromGitIgnoreLines(src, DefaultIgnorePatterns...) @@ -46,14 +49,26 @@ func NewTool() *cobra.Command { util.Fail(err) } - out, err := tool.Execute(ctx, src) + extraArgs := []string{} + if l := cmd.Flags().ArgsLenAtDash(); l > 0 { + extraArgs = args[l:] + } + + log.Debug("executing", "tool", tool, "cwd", cwd, "args", extraArgs) + out, err := tool.Execute(ctx, src, extraArgs) if err != nil { util.Fail(err) } + fmt.Println("successfully executed") if err = internal.PrintFs(out); err != nil { util.Fail(err) } }, } + + cmd.Flags().StringVarP(&cwd, "cwd", "C", "", "sets the working directory") + cmd.MarkFlagDirname("cwd") + + return cmd } diff --git a/pkg/contract.go b/pkg/contract.go index f32e8ef1..2d2b6da9 100644 --- a/pkg/contract.go +++ b/pkg/contract.go @@ -22,7 +22,7 @@ type Generator interface { type Tool interface { fmt.Stringer - Execute(context.Context, afero.Fs) (afero.Fs, error) + Execute(context.Context, afero.Fs, []string) (afero.Fs, error) } type Plugin interface { diff --git a/pkg/tool/crd2pulumi/options.go b/pkg/tool/crd2pulumi/options.go index cb3acbcf..649f9291 100644 --- a/pkg/tool/crd2pulumi/options.go +++ b/pkg/tool/crd2pulumi/options.go @@ -1,6 +1,10 @@ package crd2pulumi -import "path/filepath" +import ( + "path/filepath" + + "github.com/spf13/pflag" +) type LangOptions struct { Enabled bool @@ -8,33 +12,33 @@ type LangOptions struct { Path string } +func (o LangOptions) IsZero() bool { + return !o.Enabled && o.Name == "" && o.Path == "" +} + type Options struct { - NodeJS *LangOptions - Python *LangOptions - Dotnet *LangOptions - Go *LangOptions - Java *LangOptions + NodeJS LangOptions + Python LangOptions + Dotnet LangOptions + Go LangOptions + Java LangOptions Force bool Version string } -func (t Options) langs() map[string]*LangOptions { - return map[string]*LangOptions{ - "nodejs": t.NodeJS, - "python": t.Python, - "dotnet": t.Dotnet, - "golang": t.Go, - "java": t.Java, +func (o Options) langs() map[string]LangOptions { + return map[string]LangOptions{ + "nodejs": o.NodeJS, + "python": o.Python, + "dotnet": o.Dotnet, + "golang": o.Go, + "java": o.Java, } } -func (t Options) Paths(root string) map[string]string { +func (o Options) Paths(root string) map[string]string { paths := map[string]string{} - for k, v := range t.langs() { - if v == nil { - continue - } - + for k, v := range o.langs() { if v.Path != "" { paths[k] = v.Path } else { @@ -45,13 +49,9 @@ func (t Options) Paths(root string) map[string]string { return paths } -func (t Options) Args(paths map[string]string) []string { +func (o Options) Args(paths map[string]string) []string { args := ArgBuilder{} - for k, v := range t.langs() { - if v == nil { - continue - } - + for k, v := range o.langs() { if v.Enabled { args = args.LangOpt(k) } @@ -63,12 +63,42 @@ func (t Options) Args(paths map[string]string) []string { } } - if t.Version != "" { - args = args.VersionOpt(t.Version) + if o.Version != "" { + args = args.VersionOpt(o.Version) } - if t.Force { + if o.Force { args = args.ForceOpt() } return args } + +func (t *Options) apply(args []string) error { + f := pflag.NewFlagSet("crd2pulumi", pflag.ContinueOnError) + + f.BoolVarP(&t.Dotnet.Enabled, "dotnet", "d", false, "") + f.StringVar(&t.Dotnet.Name, "dotnetName", "", "") + f.StringVar(&t.Dotnet.Path, "dotnetPath", "", "") + + f.BoolVarP(&t.Go.Enabled, "go", "g", false, "") + f.StringVar(&t.Go.Name, "goName", "", "") + f.StringVar(&t.Go.Path, "goPath", "", "") + + f.BoolVarP(&t.NodeJS.Enabled, "nodejs", "n", false, "") + f.StringVar(&t.NodeJS.Name, "nodejsName", "", "") + f.StringVar(&t.NodeJS.Path, "nodejsPath", "", "") + + f.BoolVarP(&t.Python.Enabled, "python", "p", false, "") + f.StringVar(&t.Python.Name, "pythonName", "", "") + f.StringVar(&t.Python.Path, "pythonPath", "", "") + + f.BoolVarP(&t.Force, "force", "f", false, "") + f.StringVarP(&t.Version, "version", "v", "", "") + + return f.Parse(args) +} + +func Parse(args []string) (o Options, err error) { + err = o.apply(args) + return +} diff --git a/pkg/tool/crd2pulumi/options_test.go b/pkg/tool/crd2pulumi/options_test.go index 50b01ca4..753eb8e5 100644 --- a/pkg/tool/crd2pulumi/options_test.go +++ b/pkg/tool/crd2pulumi/options_test.go @@ -19,11 +19,11 @@ var _ = Describe("Options", func() { func(key, value string) { root := "blah" t := crd2pulumi.Options{ - NodeJS: &crd2pulumi.LangOptions{}, - Python: &crd2pulumi.LangOptions{}, - Dotnet: &crd2pulumi.LangOptions{}, - Go: &crd2pulumi.LangOptions{}, - Java: &crd2pulumi.LangOptions{}, + NodeJS: crd2pulumi.LangOptions{}, + Python: crd2pulumi.LangOptions{}, + Dotnet: crd2pulumi.LangOptions{}, + Go: crd2pulumi.LangOptions{}, + Java: crd2pulumi.LangOptions{}, } paths := t.Paths(root) @@ -40,11 +40,11 @@ var _ = Describe("Options", func() { Entry("java", "java", "blah/java"), func(key, value string) { t := crd2pulumi.Options{ - NodeJS: &crd2pulumi.LangOptions{Path: value}, - Python: &crd2pulumi.LangOptions{Path: value}, - Dotnet: &crd2pulumi.LangOptions{Path: value}, - Go: &crd2pulumi.LangOptions{Path: value}, - Java: &crd2pulumi.LangOptions{Path: value}, + NodeJS: crd2pulumi.LangOptions{Path: value}, + Python: crd2pulumi.LangOptions{Path: value}, + Dotnet: crd2pulumi.LangOptions{Path: value}, + Go: crd2pulumi.LangOptions{Path: value}, + Java: crd2pulumi.LangOptions{Path: value}, } paths := t.Paths("doesn't matter") @@ -53,31 +53,13 @@ var _ = Describe("Options", func() { }, ) - It("should exclude nil options", func() { - t := crd2pulumi.Options{ - NodeJS: &crd2pulumi.LangOptions{}, - Python: nil, - Dotnet: nil, - Go: nil, - Java: nil, - } - - paths := t.Paths("doesn't matter") - - Expect(paths).To(HaveKey("nodejs")) - Expect(paths).NotTo(HaveKey("python")) - Expect(paths).NotTo(HaveKey("dotnet")) - Expect(paths).NotTo(HaveKey("golang")) - Expect(paths).NotTo(HaveKey("java")) - }) - It("should include a path when an option is enabled", func() { t := crd2pulumi.Options{ - NodeJS: &crd2pulumi.LangOptions{Enabled: true}, - Python: nil, - Dotnet: nil, - Go: nil, - Java: nil, + NodeJS: crd2pulumi.LangOptions{Enabled: true}, + Python: crd2pulumi.LangOptions{}, + Dotnet: crd2pulumi.LangOptions{}, + Go: crd2pulumi.LangOptions{}, + Java: crd2pulumi.LangOptions{}, } paths := t.Paths("/root") @@ -89,11 +71,11 @@ var _ = Describe("Options", func() { Describe("Args", func() { It("should work", func() { t := crd2pulumi.Options{ - NodeJS: &crd2pulumi.LangOptions{Path: "doesn't matter, the path is take from paths"}, - Python: &crd2pulumi.LangOptions{Name: "peethon"}, - Dotnet: &crd2pulumi.LangOptions{Enabled: true}, - Go: &crd2pulumi.LangOptions{}, - Java: &crd2pulumi.LangOptions{}, + NodeJS: crd2pulumi.LangOptions{Path: "doesn't matter, the path is take from paths"}, + Python: crd2pulumi.LangOptions{Name: "peethon"}, + Dotnet: crd2pulumi.LangOptions{Enabled: true}, + Go: crd2pulumi.LangOptions{}, + Java: crd2pulumi.LangOptions{}, Force: true, Version: "v420", } @@ -114,4 +96,39 @@ var _ = Describe("Options", func() { )) }) }) + + Describe("Parse", func() { + It("should return empty options", func() { + o, err := crd2pulumi.Parse([]string{}) + + Expect(err).NotTo(HaveOccurred()) + Expect(o.Dotnet.Enabled).To(BeFalse()) + Expect(o.Dotnet.Name).To(BeEmpty()) + Expect(o.Dotnet.Path).To(BeEmpty()) + Expect(o.Go.Enabled).To(BeFalse()) + Expect(o.Go.Name).To(BeEmpty()) + Expect(o.Go.Path).To(BeEmpty()) + Expect(o.NodeJS.Enabled).To(BeFalse()) + Expect(o.NodeJS.Name).To(BeEmpty()) + Expect(o.NodeJS.Path).To(BeEmpty()) + Expect(o.Python.Enabled).To(BeFalse()) + Expect(o.Python.Name).To(BeEmpty()) + Expect(o.Python.Path).To(BeEmpty()) + Expect(o.Force).To(BeFalse()) + Expect(o.Version).To(BeEmpty()) + }) + + It("should enable dotnet options", func() { + o, err := crd2pulumi.Parse([]string{ + "--dotnet", + "--dotnetName", "bleh", + "--dotnetPath", "blah", + }) + + Expect(err).NotTo(HaveOccurred()) + Expect(o.Dotnet.Enabled).To(BeTrue()) + Expect(o.Dotnet.Name).To(Equal("bleh")) + Expect(o.Dotnet.Path).To(Equal("blah")) + }) + }) }) diff --git a/pkg/tool/crd2pulumi/tool.go b/pkg/tool/crd2pulumi/tool.go index 99e8ac04..12c55e33 100644 --- a/pkg/tool/crd2pulumi/tool.go +++ b/pkg/tool/crd2pulumi/tool.go @@ -27,15 +27,15 @@ func (t Tool) String() string { return "crd2pulumi" } -func (t Tool) Execute(ctx context.Context, src afero.Fs) (afero.Fs, error) { +func (t Tool) Execute(ctx context.Context, src afero.Fs, args []string) (afero.Fs, error) { base := afero.NewOsFs() - workdir, workfs, err := t.tmpfs(base) + workdir, workfs, err := tmpfs(base) if err != nil { return nil, fmt.Errorf("creating working directory: %w", err) } inputs := []string{} - err = afero.Walk(afero.NewRegexpFs(src, CrdRegex), "", + err = afero.Walk(src, "", func(path string, info fs.FileInfo, err error) error { if err != nil { return err @@ -51,6 +51,7 @@ func (t Tool) Execute(ctx context.Context, src afero.Fs) (afero.Fs, error) { } name := filepath.Base(path) + log.Debugf("copying %s to %s", path, name) if err = afero.WriteReader(workfs, name, s); err != nil { return fmt.Errorf("copying %s: %w", path, err) } @@ -66,13 +67,17 @@ func (t Tool) Execute(ctx context.Context, src afero.Fs) (afero.Fs, error) { return nil, errors.New("no input files") } - outdir, outfs, err := t.tmpfs(base) + outdir, outfs, err := tmpfs(base) if err != nil { return nil, fmt.Errorf("creating output directory: %w", err) } + if err = t.apply(args); err != nil { + return nil, fmt.Errorf("applying extra args: %w", err) + } + paths := t.Paths(outdir) - args := append(t.Args(paths), inputs...) + args = append(t.Args(paths), inputs...) stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{} cmd := exec.CommandContext(ctx, t.path(), args...) @@ -80,7 +85,7 @@ func (t Tool) Execute(ctx context.Context, src afero.Fs) (afero.Fs, error) { cmd.Stderr = stderr cmd.Dir = workdir - log.Debug("executing command") + log.Info("executing command", "cmd", cmd, "work", workdir) if err = cmd.Run(); err != nil { return nil, fmt.Errorf("executing tool: %w: %s", err, stderr) } @@ -92,14 +97,22 @@ func (t Tool) Execute(ctx context.Context, src afero.Fs) (afero.Fs, error) { } return outfs, nil -} -func (t Tool) tmpfs(fs afero.Fs) (string, afero.Fs, error) { - if name, err := afero.TempDir(fs, "", ""); err != nil { - return "", nil, err - } else { - return name, afero.NewBasePathFs(fs, name), nil - } + // WIP: The filter fs has a bug with directories + // return filter.NewFs(outfs, func(s string) bool { + // switch filepath.Ext(s) { + // case ".cs": + // return !t.Dotnet.IsZero() + // case ".go": + // return !t.Go.IsZero() + // case ".ts": + // return !t.NodeJS.IsZero() + // case ".py": + // return !t.Python.IsZero() + // default: + // return false + // } + // }), nil } func (t Tool) path() string { @@ -109,3 +122,11 @@ func (t Tool) path() string { return "crd2pulumi" } } + +func tmpfs(fs afero.Fs) (string, afero.Fs, error) { + if name, err := afero.TempDir(fs, "", ""); err != nil { + return "", nil, err + } else { + return name, afero.NewBasePathFs(fs, name), nil + } +} diff --git a/pkg/tool/crd2pulumi/tool_test.go b/pkg/tool/crd2pulumi/tool_test.go index 3ed668a1..9052b8e7 100644 --- a/pkg/tool/crd2pulumi/tool_test.go +++ b/pkg/tool/crd2pulumi/tool_test.go @@ -23,6 +23,7 @@ var _ = Describe("Tool", func() { Entry(nil, "path/blah.yaml"), Entry(nil, "blah.yml"), Entry(nil, "path/blah.yml"), + Entry(nil, "blah.two-dots.yml"), func(input string) { match := crd2pulumi.CrdRegex.MatchString(input) @@ -44,7 +45,7 @@ var _ = Describe("Tool", func() { t := crd2pulumi.Tool{ Path: toolPath, Options: crd2pulumi.Options{ - NodeJS: &crd2pulumi.LangOptions{ + NodeJS: crd2pulumi.LangOptions{ Enabled: true, }, }, @@ -53,7 +54,7 @@ var _ = Describe("Tool", func() { err := afero.WriteFile(fs, "blah.yaml", crdyaml, os.ModePerm) Expect(err).NotTo(HaveOccurred()) - out, err := t.Execute(ctx, fs) + out, err := t.Execute(ctx, fs, []string{}) Expect(err).NotTo(HaveOccurred()) Expect(afero.IsEmpty(out, "")).To(BeFalseBecause("the tool generated files"))