d2plugin: Dedup plugins array

Closes #590
This commit is contained in:
Anmol Sethi 2023-01-03 14:48:23 -08:00
parent 296b6407e9
commit 5b4a20a6c6
No known key found for this signature in database
GPG key ID: 25BC68888A99A8BA
4 changed files with 106 additions and 86 deletions

View file

@ -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()

View file

@ -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

62
help.go
View file

@ -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:]

33
main.go
View file

@ -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
}