From 5b4a20a6c67a2695af1e81cbe88b54ba64ccae1d Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Tue, 3 Jan 2023 14:48:23 -0800 Subject: [PATCH] d2plugin: Dedup plugins array Closes #590 --- d2plugin/exec.go | 14 +++++--- d2plugin/plugin.go | 83 +++++++++++++++++++++++----------------------- help.go | 62 ++++++++++++++++++---------------- main.go | 33 +++++++++++------- 4 files changed, 106 insertions(+), 86 deletions(-) diff --git a/d2plugin/exec.go b/d2plugin/exec.go index efbfcd970..b76a802d0 100644 --- a/d2plugin/exec.go +++ b/d2plugin/exec.go @@ -40,9 +40,10 @@ import ( type execPlugin struct { path string opts map[string]string + info *PluginInfo } -func (p execPlugin) Flags(ctx context.Context) (_ []PluginSpecificFlag, err error) { +func (p *execPlugin) Flags(ctx context.Context) (_ []PluginSpecificFlag, err error) { ctx, cancel := context.WithTimeout(ctx, time.Second*10) defer cancel() cmd := exec.CommandContext(ctx, p.path, "flags") @@ -92,7 +93,11 @@ func (p *execPlugin) HydrateOpts(opts []byte) error { return nil } -func (p execPlugin) Info(ctx context.Context) (_ *PluginInfo, err error) { +func (p *execPlugin) Info(ctx context.Context) (_ *PluginInfo, err error) { + if p.info != nil { + return p.info, nil + } + ctx, cancel := context.WithTimeout(ctx, time.Second*10) defer cancel() cmd := exec.CommandContext(ctx, p.path, "info") @@ -114,10 +119,11 @@ func (p execPlugin) Info(ctx context.Context) (_ *PluginInfo, err error) { return nil, fmt.Errorf("failed to unmarshal json: %w", err) } + p.info = &info return &info, nil } -func (p execPlugin) Layout(ctx context.Context, g *d2graph.Graph) error { +func (p *execPlugin) Layout(ctx context.Context, g *d2graph.Graph) error { ctx, cancel := context.WithTimeout(ctx, time.Minute) defer cancel() @@ -152,7 +158,7 @@ func (p execPlugin) Layout(ctx context.Context, g *d2graph.Graph) error { return nil } -func (p execPlugin) PostProcess(ctx context.Context, in []byte) ([]byte, error) { +func (p *execPlugin) PostProcess(ctx context.Context, in []byte) ([]byte, error) { ctx, cancel := context.WithTimeout(ctx, time.Minute) defer cancel() diff --git a/d2plugin/plugin.go b/d2plugin/plugin.go index 25eee8d08..a6eefdf7e 100644 --- a/d2plugin/plugin.go +++ b/d2plugin/plugin.go @@ -80,36 +80,54 @@ type PluginInfo struct { const binaryPrefix = "d2plugin-" -func ListPlugins(ctx context.Context) ([]*PluginInfo, error) { +func ListPlugins(ctx context.Context) ([]Plugin, error) { // 1. Run Info on all bundled plugins in the global plugins array. // - set Type for each bundled plugin to "bundled". // 2. Iterate through directories in $PATH and look for executables within these // directories with the prefix d2plugin-* // 3. Run each plugin binary with the argument info. e.g. d2plugin-dagre info - var infoSlice []*PluginInfo - - for _, p := range plugins { - info, err := p.Info(ctx) - if err != nil { - return nil, err - } - info.Type = "bundled" - infoSlice = append(infoSlice, info) - } + var ps []Plugin + ps = append(ps, plugins...) matches, err := xexec.SearchPath(binaryPrefix) if err != nil { return nil, err } +BINARY_PLUGINS_LOOP: for _, path := range matches { p := &execPlugin{path: path} info, err := p.Info(ctx) if err != nil { return nil, err } - info.Type = "binary" - info.Path = path + for _, p2 := range ps { + info2, err := p2.Info(ctx) + if err != nil { + return nil, err + } + if info.Name == info2.Name { + continue BINARY_PLUGINS_LOOP + } + } + ps = append(ps, p) + } + return ps, nil +} + +func ListPluginInfos(ctx context.Context, ps []Plugin) ([]*PluginInfo, error) { + var infoSlice []*PluginInfo + for _, p := range ps { + info, err := p.Info(ctx) + if err != nil { + return nil, err + } + if ep, ok := p.(*execPlugin); ok { + info.Type = "binary" + info.Path = ep.path + } else { + info.Type = "bundled" + } infoSlice = append(infoSlice, info) } @@ -123,41 +141,22 @@ func ListPlugins(ctx context.Context) ([]*PluginInfo, error) { // **NOTE** When D2 upgrades to go 1.19, remember to ignore exec.ErrDot // 3. If such a binary is found, it builds an execPlugin in exec.go // to get a plugin implementation around the binary and returns it. -func FindPlugin(ctx context.Context, name string) (Plugin, string, error) { - for _, p := range plugins { +func FindPlugin(ctx context.Context, ps []Plugin, name string) (Plugin, error) { + for _, p := range ps { info, err := p.Info(ctx) - if err != nil { - return nil, "", err - } - if info.Name == name { - return p, "", nil - } - } - - path, err := exec.LookPath(binaryPrefix + name) - if err != nil { - return nil, "", err - } - - return &execPlugin{path: path}, path, nil -} - -func ListPluginFlags(ctx context.Context) ([]PluginSpecificFlag, error) { - var out []PluginSpecificFlag - for _, p := range plugins { - flags, err := p.Flags(ctx) if err != nil { return nil, err } - out = append(out, flags...) + if info.Name == name { + return p, nil + } } + return nil, exec.ErrNotFound +} - matches, err := xexec.SearchPath(binaryPrefix) - if err != nil { - return nil, err - } - for _, path := range matches { - p := &execPlugin{path: path} +func ListPluginFlags(ctx context.Context, ps []Plugin) ([]PluginSpecificFlag, error) { + var out []PluginSpecificFlag + for _, p := range ps { flags, err := p.Flags(ctx) if err != nil { return nil, err diff --git a/help.go b/help.go index 27f9bc22c..3efa76b02 100644 --- a/help.go +++ b/help.go @@ -39,23 +39,23 @@ See more docs and the source code at https://oss.terrastruct.com/d2 `, filepath.Base(ms.Name), ms.Opts.Defaults()) } -func layoutCmd(ctx context.Context, ms *xmain.State) error { +func layoutCmd(ctx context.Context, ms *xmain.State, ps []d2plugin.Plugin) error { if len(ms.Opts.Flags.Args()) == 1 { - return shortLayoutHelp(ctx, ms) + return shortLayoutHelp(ctx, ms, ps) } else if len(ms.Opts.Flags.Args()) == 2 { - return longLayoutHelp(ctx, ms) + return longLayoutHelp(ctx, ms, ps) } else { - return pluginSubcommand(ctx, ms) + return pluginSubcommand(ctx, ms, ps) } } -func shortLayoutHelp(ctx context.Context, ms *xmain.State) error { +func shortLayoutHelp(ctx context.Context, ms *xmain.State, ps []d2plugin.Plugin) error { var pluginLines []string - plugins, err := d2plugin.ListPlugins(ctx) + pinfos, err := d2plugin.ListPluginInfos(ctx, ps) if err != nil { return err } - for _, p := range plugins { + for _, p := range pinfos { var l string if p.Type == "bundled" { l = fmt.Sprintf("%s (bundled) - %s", p.Name, p.ShortHelp) @@ -82,40 +82,43 @@ See more docs at https://oss.terrastruct.com/d2 return nil } -func longLayoutHelp(ctx context.Context, ms *xmain.State) error { +func longLayoutHelp(ctx context.Context, ms *xmain.State, ps []d2plugin.Plugin) error { layout := ms.Opts.Flags.Arg(1) - plugin, path, err := d2plugin.FindPlugin(ctx, layout) - if errors.Is(err, exec.ErrNotFound) { - return layoutNotFound(ctx, layout) + plugin, err := d2plugin.FindPlugin(ctx, ps, layout) + if err != nil { + if errors.Is(err, exec.ErrNotFound) { + return layoutNotFound(ctx, ps, layout) + } + return err } - pluginLocation := "bundled" - if path != "" { - pluginLocation = fmt.Sprintf("executable plugin at %s", humanPath(path)) - } - - pluginInfo, err := plugin.Info(ctx) + pinfo, err := plugin.Info(ctx) if err != nil { return err } - if !strings.HasSuffix(pluginInfo.LongHelp, "\n") { - pluginInfo.LongHelp += "\n" + plocation := pinfo.Type + if pinfo.Type == "binary" { + plocation = fmt.Sprintf("executable plugin at %s", humanPath(pinfo.Path)) + } + + if !strings.HasSuffix(pinfo.LongHelp, "\n") { + pinfo.LongHelp += "\n" } fmt.Fprintf(ms.Stdout, `%s (%s): -%s`, pluginInfo.Name, pluginLocation, pluginInfo.LongHelp) +%s`, pinfo.Name, plocation, pinfo.LongHelp) return nil } -func layoutNotFound(ctx context.Context, layout string) error { - var names []string - plugins, err := d2plugin.ListPlugins(ctx) +func layoutNotFound(ctx context.Context, ps []d2plugin.Plugin, layout string) error { + pinfos, err := d2plugin.ListPluginInfos(ctx, ps) if err != nil { return err } - for _, p := range plugins { + var names []string + for _, p := range pinfos { names = append(names, p.Name) } @@ -126,11 +129,14 @@ For more information on setup, please visit https://github.com/terrastruct/d2.`, layout, strings.Join(names, ", ")) } -func pluginSubcommand(ctx context.Context, ms *xmain.State) error { +func pluginSubcommand(ctx context.Context, ms *xmain.State, ps []d2plugin.Plugin) error { layout := ms.Opts.Flags.Arg(1) - plugin, _, err := d2plugin.FindPlugin(ctx, layout) - if errors.Is(err, exec.ErrNotFound) { - return layoutNotFound(ctx, layout) + plugin, err := d2plugin.FindPlugin(ctx, ps, layout) + if err != nil { + if errors.Is(err, exec.ErrNotFound) { + return layoutNotFound(ctx, ps, layout) + } + return err } ms.Opts.Args = ms.Opts.Flags.Args()[2:] diff --git a/main.go b/main.go index 78041b71e..646b8c994 100644 --- a/main.go +++ b/main.go @@ -75,7 +75,11 @@ func run(ctx context.Context, ms *xmain.State) (err error) { return err } - err = populateLayoutOpts(ctx, ms) + ps, err := d2plugin.ListPlugins(ctx) + if err != nil { + return err + } + err = populateLayoutOpts(ctx, ms, ps) if err != nil { return err } @@ -93,7 +97,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) { if len(ms.Opts.Flags.Args()) > 0 { switch ms.Opts.Flags.Arg(0) { case "layout": - return layoutCmd(ctx, ms) + return layoutCmd(ctx, ms, ps) case "fmt": return fmtCmd(ctx, ms) case "version": @@ -142,10 +146,11 @@ func run(ctx context.Context, ms *xmain.State) (err error) { } ms.Log.Debug.Printf("using theme %s (ID: %d)", match.Name, *themeFlag) - plugin, path, err := d2plugin.FindPlugin(ctx, *layoutFlag) - if errors.Is(err, exec.ErrNotFound) { - return layoutNotFound(ctx, *layoutFlag) - } else if err != nil { + plugin, err := d2plugin.FindPlugin(ctx, ps, *layoutFlag) + if err != nil { + if errors.Is(err, exec.ErrNotFound) { + return layoutNotFound(ctx, ps, *layoutFlag) + } return err } @@ -154,11 +159,15 @@ func run(ctx context.Context, ms *xmain.State) (err error) { return err } - pluginLocation := "bundled" - if path != "" { - pluginLocation = fmt.Sprintf("executable plugin at %s", humanPath(path)) + pinfo, err := plugin.Info(ctx) + if err != nil { + return err } - ms.Log.Debug.Printf("using layout plugin %s (%s)", *layoutFlag, pluginLocation) + plocation := pinfo.Type + if pinfo.Type == "binary" { + plocation = fmt.Sprintf("executable plugin at %s", humanPath(pinfo.Path)) + } + ms.Log.Debug.Printf("using layout plugin %s (%s)", *layoutFlag, plocation) var pw png.Playwright if filepath.Ext(outputPath) == ".png" { @@ -299,8 +308,8 @@ func DiscardSlog(ctx context.Context) context.Context { return ctxlog.With(ctx, slog.Make(sloghuman.Sink(io.Discard))) } -func populateLayoutOpts(ctx context.Context, ms *xmain.State) error { - pluginFlags, err := d2plugin.ListPluginFlags(ctx) +func populateLayoutOpts(ctx context.Context, ms *xmain.State, ps []d2plugin.Plugin) error { + pluginFlags, err := d2plugin.ListPluginFlags(ctx, ps) if err != nil { return err }