parent
296b6407e9
commit
5b4a20a6c6
4 changed files with 106 additions and 86 deletions
|
|
@ -40,9 +40,10 @@ import (
|
||||||
type execPlugin struct {
|
type execPlugin struct {
|
||||||
path string
|
path string
|
||||||
opts map[string]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)
|
ctx, cancel := context.WithTimeout(ctx, time.Second*10)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
cmd := exec.CommandContext(ctx, p.path, "flags")
|
cmd := exec.CommandContext(ctx, p.path, "flags")
|
||||||
|
|
@ -92,7 +93,11 @@ func (p *execPlugin) HydrateOpts(opts []byte) error {
|
||||||
return nil
|
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)
|
ctx, cancel := context.WithTimeout(ctx, time.Second*10)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
cmd := exec.CommandContext(ctx, p.path, "info")
|
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)
|
return nil, fmt.Errorf("failed to unmarshal json: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.info = &info
|
||||||
return &info, nil
|
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)
|
ctx, cancel := context.WithTimeout(ctx, time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
|
@ -152,7 +158,7 @@ func (p execPlugin) Layout(ctx context.Context, g *d2graph.Graph) error {
|
||||||
return nil
|
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)
|
ctx, cancel := context.WithTimeout(ctx, time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,36 +80,54 @@ type PluginInfo struct {
|
||||||
|
|
||||||
const binaryPrefix = "d2plugin-"
|
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.
|
// 1. Run Info on all bundled plugins in the global plugins array.
|
||||||
// - set Type for each bundled plugin to "bundled".
|
// - set Type for each bundled plugin to "bundled".
|
||||||
// 2. Iterate through directories in $PATH and look for executables within these
|
// 2. Iterate through directories in $PATH and look for executables within these
|
||||||
// directories with the prefix d2plugin-*
|
// directories with the prefix d2plugin-*
|
||||||
// 3. Run each plugin binary with the argument info. e.g. d2plugin-dagre info
|
// 3. Run each plugin binary with the argument info. e.g. d2plugin-dagre info
|
||||||
|
|
||||||
var infoSlice []*PluginInfo
|
var ps []Plugin
|
||||||
|
ps = append(ps, plugins...)
|
||||||
for _, p := range plugins {
|
|
||||||
info, err := p.Info(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
info.Type = "bundled"
|
|
||||||
infoSlice = append(infoSlice, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
matches, err := xexec.SearchPath(binaryPrefix)
|
matches, err := xexec.SearchPath(binaryPrefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
BINARY_PLUGINS_LOOP:
|
||||||
for _, path := range matches {
|
for _, path := range matches {
|
||||||
p := &execPlugin{path: path}
|
p := &execPlugin{path: path}
|
||||||
info, err := p.Info(ctx)
|
info, err := p.Info(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
info.Type = "binary"
|
for _, p2 := range ps {
|
||||||
info.Path = path
|
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)
|
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
|
// **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
|
// 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.
|
// to get a plugin implementation around the binary and returns it.
|
||||||
func FindPlugin(ctx context.Context, name string) (Plugin, string, error) {
|
func FindPlugin(ctx context.Context, ps []Plugin, name string) (Plugin, error) {
|
||||||
for _, p := range plugins {
|
for _, p := range ps {
|
||||||
info, err := p.Info(ctx)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
out = append(out, flags...)
|
if info.Name == name {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return nil, exec.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
matches, err := xexec.SearchPath(binaryPrefix)
|
func ListPluginFlags(ctx context.Context, ps []Plugin) ([]PluginSpecificFlag, error) {
|
||||||
if err != nil {
|
var out []PluginSpecificFlag
|
||||||
return nil, err
|
for _, p := range ps {
|
||||||
}
|
|
||||||
for _, path := range matches {
|
|
||||||
p := &execPlugin{path: path}
|
|
||||||
flags, err := p.Flags(ctx)
|
flags, err := p.Flags(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
62
help.go
62
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())
|
`, 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 {
|
if len(ms.Opts.Flags.Args()) == 1 {
|
||||||
return shortLayoutHelp(ctx, ms)
|
return shortLayoutHelp(ctx, ms, ps)
|
||||||
} else if len(ms.Opts.Flags.Args()) == 2 {
|
} else if len(ms.Opts.Flags.Args()) == 2 {
|
||||||
return longLayoutHelp(ctx, ms)
|
return longLayoutHelp(ctx, ms, ps)
|
||||||
} else {
|
} 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
|
var pluginLines []string
|
||||||
plugins, err := d2plugin.ListPlugins(ctx)
|
pinfos, err := d2plugin.ListPluginInfos(ctx, ps)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, p := range plugins {
|
for _, p := range pinfos {
|
||||||
var l string
|
var l string
|
||||||
if p.Type == "bundled" {
|
if p.Type == "bundled" {
|
||||||
l = fmt.Sprintf("%s (bundled) - %s", p.Name, p.ShortHelp)
|
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
|
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)
|
layout := ms.Opts.Flags.Arg(1)
|
||||||
plugin, path, err := d2plugin.FindPlugin(ctx, layout)
|
plugin, err := d2plugin.FindPlugin(ctx, ps, layout)
|
||||||
if errors.Is(err, exec.ErrNotFound) {
|
if err != nil {
|
||||||
return layoutNotFound(ctx, layout)
|
if errors.Is(err, exec.ErrNotFound) {
|
||||||
|
return layoutNotFound(ctx, ps, layout)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginLocation := "bundled"
|
pinfo, err := plugin.Info(ctx)
|
||||||
if path != "" {
|
|
||||||
pluginLocation = fmt.Sprintf("executable plugin at %s", humanPath(path))
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginInfo, err := plugin.Info(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasSuffix(pluginInfo.LongHelp, "\n") {
|
plocation := pinfo.Type
|
||||||
pluginInfo.LongHelp += "\n"
|
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):
|
fmt.Fprintf(ms.Stdout, `%s (%s):
|
||||||
|
|
||||||
%s`, pluginInfo.Name, pluginLocation, pluginInfo.LongHelp)
|
%s`, pinfo.Name, plocation, pinfo.LongHelp)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func layoutNotFound(ctx context.Context, layout string) error {
|
func layoutNotFound(ctx context.Context, ps []d2plugin.Plugin, layout string) error {
|
||||||
var names []string
|
pinfos, err := d2plugin.ListPluginInfos(ctx, ps)
|
||||||
plugins, err := d2plugin.ListPlugins(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, p := range plugins {
|
var names []string
|
||||||
|
for _, p := range pinfos {
|
||||||
names = append(names, p.Name)
|
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, ", "))
|
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)
|
layout := ms.Opts.Flags.Arg(1)
|
||||||
plugin, _, err := d2plugin.FindPlugin(ctx, layout)
|
plugin, err := d2plugin.FindPlugin(ctx, ps, layout)
|
||||||
if errors.Is(err, exec.ErrNotFound) {
|
if err != nil {
|
||||||
return layoutNotFound(ctx, layout)
|
if errors.Is(err, exec.ErrNotFound) {
|
||||||
|
return layoutNotFound(ctx, ps, layout)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ms.Opts.Args = ms.Opts.Flags.Args()[2:]
|
ms.Opts.Args = ms.Opts.Flags.Args()[2:]
|
||||||
|
|
|
||||||
33
main.go
33
main.go
|
|
@ -75,7 +75,11 @@ func run(ctx context.Context, ms *xmain.State) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = populateLayoutOpts(ctx, ms)
|
ps, err := d2plugin.ListPlugins(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = populateLayoutOpts(ctx, ms, ps)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -93,7 +97,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) {
|
||||||
if len(ms.Opts.Flags.Args()) > 0 {
|
if len(ms.Opts.Flags.Args()) > 0 {
|
||||||
switch ms.Opts.Flags.Arg(0) {
|
switch ms.Opts.Flags.Arg(0) {
|
||||||
case "layout":
|
case "layout":
|
||||||
return layoutCmd(ctx, ms)
|
return layoutCmd(ctx, ms, ps)
|
||||||
case "fmt":
|
case "fmt":
|
||||||
return fmtCmd(ctx, ms)
|
return fmtCmd(ctx, ms)
|
||||||
case "version":
|
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)
|
ms.Log.Debug.Printf("using theme %s (ID: %d)", match.Name, *themeFlag)
|
||||||
|
|
||||||
plugin, path, err := d2plugin.FindPlugin(ctx, *layoutFlag)
|
plugin, err := d2plugin.FindPlugin(ctx, ps, *layoutFlag)
|
||||||
if errors.Is(err, exec.ErrNotFound) {
|
if err != nil {
|
||||||
return layoutNotFound(ctx, *layoutFlag)
|
if errors.Is(err, exec.ErrNotFound) {
|
||||||
} else if err != nil {
|
return layoutNotFound(ctx, ps, *layoutFlag)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -154,11 +159,15 @@ func run(ctx context.Context, ms *xmain.State) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginLocation := "bundled"
|
pinfo, err := plugin.Info(ctx)
|
||||||
if path != "" {
|
if err != nil {
|
||||||
pluginLocation = fmt.Sprintf("executable plugin at %s", humanPath(path))
|
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
|
var pw png.Playwright
|
||||||
if filepath.Ext(outputPath) == ".png" {
|
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)))
|
return ctxlog.With(ctx, slog.Make(sloghuman.Sink(io.Discard)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func populateLayoutOpts(ctx context.Context, ms *xmain.State) error {
|
func populateLayoutOpts(ctx context.Context, ms *xmain.State, ps []d2plugin.Plugin) error {
|
||||||
pluginFlags, err := d2plugin.ListPluginFlags(ctx)
|
pluginFlags, err := d2plugin.ListPluginFlags(ctx, ps)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue