From 590caa243c63e2ac9e65174efa93e7c8566aca4f Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Thu, 29 Dec 2022 21:09:53 -0800 Subject: [PATCH 01/16] works --- d2layouts/d2dagrelayout/layout.go | 15 +++++++++++-- d2layouts/d2elklayout/layout.go | 14 ++++++++++++- d2lib/c.go | 7 ------- d2lib/d2.go | 11 +++++----- d2plugin/exec.go | 14 +++++++++++++ d2plugin/plugin.go | 2 ++ d2plugin/plugin_dagre.go | 21 ++++++++++++++++--- d2plugin/plugin_elk.go | 19 +++++++++++++++-- main.go | 35 +++++++++++++++++++++++++++++++ 9 files changed, 118 insertions(+), 20 deletions(-) delete mode 100644 d2lib/c.go diff --git a/d2layouts/d2dagrelayout/layout.go b/d2layouts/d2dagrelayout/layout.go index 68d8ec0f6..18709c1bf 100644 --- a/d2layouts/d2dagrelayout/layout.go +++ b/d2layouts/d2dagrelayout/layout.go @@ -30,6 +30,14 @@ var setupJS string //go:embed dagre.js var dagreJS string +type Opts struct { + NodeSep int +} + +var DefaultOpts = Opts{ + NodeSep: 60, +} + type DagreNode struct { ID string `json:"id"` X float64 `json:"x"` @@ -51,7 +59,10 @@ type dagreGraphAttrs struct { rankdir string } -func Layout(ctx context.Context, g *d2graph.Graph) (err error) { +func Layout(ctx context.Context, g *d2graph.Graph, opts *Opts) (err error) { + if opts == nil { + opts = &DefaultOpts + } defer xdefer.Errorf(&err, "failed to dagre layout") debugJS := false @@ -65,7 +76,7 @@ func Layout(ctx context.Context, g *d2graph.Graph) (err error) { rootAttrs := dagreGraphAttrs{ edgesep: 40, - nodesep: 60, + nodesep: opts.NodeSep, } isHorizontal := false switch g.Root.Attributes.Direction.Value { diff --git a/d2layouts/d2elklayout/layout.go b/d2layouts/d2elklayout/layout.go index 6dd37f828..610a74572 100644 --- a/d2layouts/d2elklayout/layout.go +++ b/d2layouts/d2elklayout/layout.go @@ -77,6 +77,15 @@ type ELKGraph struct { Edges []*ELKEdge `json:"edges,omitempty"` } +var DefaultOpts = ELKLayoutOptions{ + Algorithm: "layered", + HierarchyHandling: "INCLUDE_CHILDREN", + NodeSpacing: 100.0, + EdgeNodeSpacing: 50.0, + SelfLoopSpacing: 50.0, + ConsiderModelOrder: "NODES_AND_EDGES", +} + type ELKLayoutOptions struct { Algorithm string `json:"elk.algorithm,omitempty"` HierarchyHandling string `json:"elk.hierarchyHandling,omitempty"` @@ -90,7 +99,10 @@ type ELKLayoutOptions struct { ForceNodeModelOrder bool `json:"elk.layered.crossingMinimization.forceNodeModelOrder,omitempty"` } -func Layout(ctx context.Context, g *d2graph.Graph) (err error) { +func Layout(ctx context.Context, g *d2graph.Graph, opts *ELKLayoutOptions) (err error) { + if opts == nil { + opts = &DefaultOpts + } defer xdefer.Errorf(&err, "failed to ELK layout") vm := goja.New() diff --git a/d2lib/c.go b/d2lib/c.go deleted file mode 100644 index c62ef79ae..000000000 --- a/d2lib/c.go +++ /dev/null @@ -1,7 +0,0 @@ -package d2lib - -import "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" - -func init() { - dagreLayout = d2dagrelayout.Layout -} diff --git a/d2lib/d2.go b/d2lib/d2.go index 936124b9f..bfe9713a9 100644 --- a/d2lib/d2.go +++ b/d2lib/d2.go @@ -9,6 +9,7 @@ import ( "oss.terrastruct.com/d2/d2compiler" "oss.terrastruct.com/d2/d2exporter" "oss.terrastruct.com/d2/d2graph" + "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" "oss.terrastruct.com/d2/d2layouts/d2near" "oss.terrastruct.com/d2/d2layouts/d2sequence" "oss.terrastruct.com/d2/d2renderers/d2fonts" @@ -74,12 +75,12 @@ func Compile(ctx context.Context, input string, opts *CompileOptions) (*d2target func getLayout(opts *CompileOptions) (func(context.Context, *d2graph.Graph) error, error) { if opts.Layout != nil { return opts.Layout, nil - } else if os.Getenv("D2_LAYOUT") == "dagre" && dagreLayout != nil { - return dagreLayout, nil + } else if os.Getenv("D2_LAYOUT") == "dagre" { + defaultLayout := func(ctx context.Context, g *d2graph.Graph) error { + return d2dagrelayout.Layout(ctx, g, nil) + } + return defaultLayout, nil } else { return nil, errors.New("no available layout") } } - -// See c.go -var dagreLayout func(context.Context, *d2graph.Graph) error diff --git a/d2plugin/exec.go b/d2plugin/exec.go index b4929dcc2..b15ac7a5f 100644 --- a/d2plugin/exec.go +++ b/d2plugin/exec.go @@ -10,6 +10,7 @@ import ( "time" "oss.terrastruct.com/util-go/xdefer" + "oss.terrastruct.com/util-go/xmain" "oss.terrastruct.com/d2/d2graph" ) @@ -37,6 +38,19 @@ import ( // the error to stderr. type execPlugin struct { path string + opts map[string]string +} + +func (p execPlugin) HydrateOpts(ctx context.Context, opts interface{}) error { + if opts != nil { + execOpts, ok := opts.(map[string]string) + if !ok { + return xmain.UsageErrorf("non-exec layout options given for exec") + } + + p.opts = execOpts + } + return nil } func (p execPlugin) Info(ctx context.Context) (_ *PluginInfo, err error) { diff --git a/d2plugin/plugin.go b/d2plugin/plugin.go index 56798c88f..2e538f4c4 100644 --- a/d2plugin/plugin.go +++ b/d2plugin/plugin.go @@ -23,6 +23,8 @@ type Plugin interface { // Info returns the current info information of the plugin. Info(context.Context) (*PluginInfo, error) + HydrateOpts(context.Context, interface{}) error + // Layout runs the plugin's autolayout algorithm on the input graph // and returns a new graph with the computed placements. Layout(context.Context, *d2graph.Graph) error diff --git a/d2plugin/plugin_dagre.go b/d2plugin/plugin_dagre.go index 228011ac0..b4d75688b 100644 --- a/d2plugin/plugin_dagre.go +++ b/d2plugin/plugin_dagre.go @@ -7,15 +7,30 @@ import ( "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" + "oss.terrastruct.com/util-go/xmain" ) var DagrePlugin = dagrePlugin{} func init() { - plugins = append(plugins, DagrePlugin) + plugins = append(plugins, &DagrePlugin) } -type dagrePlugin struct{} +type dagrePlugin struct { + opts *d2dagrelayout.Opts +} + +func (p *dagrePlugin) HydrateOpts(ctx context.Context, opts interface{}) error { + if opts != nil { + dagreOpts, ok := opts.(d2dagrelayout.Opts) + if !ok { + return xmain.UsageErrorf("non-dagre layout options given for dagre") + } + + p.opts = &dagreOpts + } + return nil +} func (p dagrePlugin) Info(context.Context) (*PluginInfo, error) { return &PluginInfo{ @@ -33,7 +48,7 @@ note: dagre is the primary layout algorithm for text to diagram generator Mermai } func (p dagrePlugin) Layout(ctx context.Context, g *d2graph.Graph) error { - return d2dagrelayout.Layout(ctx, g) + return d2dagrelayout.Layout(ctx, g, p.opts) } func (p dagrePlugin) PostProcess(ctx context.Context, in []byte) ([]byte, error) { diff --git a/d2plugin/plugin_elk.go b/d2plugin/plugin_elk.go index 6a8567f5e..5bdc018e1 100644 --- a/d2plugin/plugin_elk.go +++ b/d2plugin/plugin_elk.go @@ -7,6 +7,7 @@ import ( "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2layouts/d2elklayout" + "oss.terrastruct.com/util-go/xmain" ) var ELKPlugin = elkPlugin{} @@ -15,7 +16,21 @@ func init() { plugins = append(plugins, ELKPlugin) } -type elkPlugin struct{} +type elkPlugin struct { + opts *d2elklayout.ELKLayoutOptions +} + +func (p elkPlugin) HydrateOpts(ctx context.Context, opts interface{}) error { + if opts != nil { + elkOpts, ok := opts.(d2elklayout.ELKLayoutOptions) + if !ok { + return xmain.UsageErrorf("non-dagre layout options given for dagre") + } + + p.opts = &elkOpts + } + return nil +} func (p elkPlugin) Info(context.Context) (*PluginInfo, error) { return &PluginInfo{ @@ -28,7 +43,7 @@ See https://github.com/kieler/elkjs for more.`, } func (p elkPlugin) Layout(ctx context.Context, g *d2graph.Graph) error { - return d2elklayout.Layout(ctx, g) + return d2elklayout.Layout(ctx, g, p.opts) } func (p elkPlugin) PostProcess(ctx context.Context, in []byte) ([]byte, error) { diff --git a/main.go b/main.go index 0377ac538..83ccef354 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ import ( "oss.terrastruct.com/util-go/go2" "oss.terrastruct.com/util-go/xmain" + "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" "oss.terrastruct.com/d2/d2lib" "oss.terrastruct.com/d2/d2plugin" "oss.terrastruct.com/d2/d2renderers/d2fonts" @@ -75,6 +76,11 @@ func run(ctx context.Context, ms *xmain.State) (err error) { return err } + err = populateLayoutOpts(ms) + if err != nil { + return err + } + err = ms.Opts.Flags.Parse(ms.Opts.Args) if !errors.Is(err, pflag.ErrHelp) && err != nil { return xmain.UsageErrorf("failed to parse flags: %v", err) @@ -144,6 +150,16 @@ func run(ctx context.Context, ms *xmain.State) (err error) { return err } + layoutOpts, err := parseLayoutOpts(ms, *layoutFlag) + if err != nil { + return err + } + + err = plugin.HydrateOpts(ctx, layoutOpts) + if err != nil { + return err + } + pluginLocation := "bundled" if path != "" { pluginLocation = fmt.Sprintf("executable plugin at %s", humanPath(path)) @@ -288,3 +304,22 @@ func renameExt(fp string, newExt string) string { func DiscardSlog(ctx context.Context) context.Context { return ctxlog.With(ctx, slog.Make(sloghuman.Sink(io.Discard))) } + +func populateLayoutOpts(ms *xmain.State) error { + _, err := ms.Opts.Int64("", "dagre-nodesep", "", int64(d2dagrelayout.DefaultOpts.NodeSep), "number of pixels that separate nodes horizontally in the layout.") + if err != nil { + return err + } + return nil +} + +func parseLayoutOpts(ms *xmain.State, layout string) (interface{}, error) { + if layout == "dagre" { + nodesep, _ := ms.Opts.Flags.GetInt64("dagre-nodesep") + return d2dagrelayout.Opts{ + NodeSep: int(nodesep), + }, nil + } + + return nil, fmt.Errorf("unexpected error, layout not found for parsing opts") +} From eccec7afddd51dc2f547b4ae3490a9dcab6865e0 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Thu, 29 Dec 2022 22:43:01 -0800 Subject: [PATCH 02/16] elk --- d2layouts/d2dagrelayout/layout.go | 4 ++- d2layouts/d2elklayout/layout.go | 49 ++++++++++++++++--------------- d2plugin/plugin_elk.go | 14 ++++----- main.go | 41 ++++++++++++++++++++++++-- 4 files changed, 75 insertions(+), 33 deletions(-) diff --git a/d2layouts/d2dagrelayout/layout.go b/d2layouts/d2dagrelayout/layout.go index 18709c1bf..ee86e9599 100644 --- a/d2layouts/d2dagrelayout/layout.go +++ b/d2layouts/d2dagrelayout/layout.go @@ -32,10 +32,12 @@ var dagreJS string type Opts struct { NodeSep int + EdgeSep int } var DefaultOpts = Opts{ NodeSep: 60, + EdgeSep: 40, } type DagreNode struct { @@ -75,7 +77,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *Opts) (err error) { } rootAttrs := dagreGraphAttrs{ - edgesep: 40, + edgesep: opts.EdgeSep, nodesep: opts.NodeSep, } isHorizontal := false diff --git a/d2layouts/d2elklayout/layout.go b/d2layouts/d2elklayout/layout.go index 610a74572..f46e19004 100644 --- a/d2layouts/d2elklayout/layout.go +++ b/d2layouts/d2elklayout/layout.go @@ -77,29 +77,33 @@ type ELKGraph struct { Edges []*ELKEdge `json:"edges,omitempty"` } -var DefaultOpts = ELKLayoutOptions{ - Algorithm: "layered", - HierarchyHandling: "INCLUDE_CHILDREN", - NodeSpacing: 100.0, - EdgeNodeSpacing: 50.0, - SelfLoopSpacing: 50.0, - ConsiderModelOrder: "NODES_AND_EDGES", +var DefaultOpts = ConfigurableOpts{ + Algorithm: "layered", + NodeSpacing: 100.0, + Padding: "[top=75,left=75,bottom=75,right=75]", + EdgeNodeSpacing: 50.0, + SelfLoopSpacing: 50.0, +} + +type ConfigurableOpts struct { + Algorithm string `json:"elk.algorithm,omitempty"` + NodeSpacing int `json:"spacing.nodeNodeBetweenLayers,omitempty"` + Padding string `json:"elk.padding,omitempty"` + EdgeNodeSpacing int `json:"spacing.edgeNodeBetweenLayers,omitempty"` + SelfLoopSpacing int `json:"elk.spacing.nodeSelfLoop"` } type ELKLayoutOptions struct { - Algorithm string `json:"elk.algorithm,omitempty"` - HierarchyHandling string `json:"elk.hierarchyHandling,omitempty"` - NodeSpacing float64 `json:"spacing.nodeNodeBetweenLayers,omitempty"` - Padding string `json:"elk.padding,omitempty"` - EdgeNodeSpacing float64 `json:"spacing.edgeNodeBetweenLayers,omitempty"` - Direction string `json:"elk.direction"` - SelfLoopSpacing float64 `json:"elk.spacing.nodeSelfLoop"` - InlineEdgeLabels bool `json:"elk.edgeLabels.inline,omitempty"` - ConsiderModelOrder string `json:"elk.layered.considerModelOrder.strategy,omitempty"` - ForceNodeModelOrder bool `json:"elk.layered.crossingMinimization.forceNodeModelOrder,omitempty"` + Direction string `json:"elk.direction"` + HierarchyHandling string `json:"elk.hierarchyHandling,omitempty"` + InlineEdgeLabels bool `json:"elk.edgeLabels.inline,omitempty"` + ForceNodeModelOrder bool `json:"elk.layered.crossingMinimization.forceNodeModelOrder,omitempty"` + ConsiderModelOrder string `json:"elk.layered.considerModelOrder.strategy,omitempty"` + + ConfigurableOpts } -func Layout(ctx context.Context, g *d2graph.Graph, opts *ELKLayoutOptions) (err error) { +func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err error) { if opts == nil { opts = &DefaultOpts } @@ -122,12 +126,9 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ELKLayoutOptions) (err elkGraph := &ELKGraph{ ID: "root", LayoutOptions: &ELKLayoutOptions{ - Algorithm: "layered", HierarchyHandling: "INCLUDE_CHILDREN", - NodeSpacing: 100.0, - EdgeNodeSpacing: 50.0, - SelfLoopSpacing: 50.0, ConsiderModelOrder: "NODES_AND_EDGES", + ConfigurableOpts: *opts, }, } switch g.Root.Attributes.Direction.Value { @@ -171,8 +172,10 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ELKLayoutOptions) (err if len(obj.ChildrenArray) > 0 { n.LayoutOptions = &ELKLayoutOptions{ - Padding: "[top=75,left=75,bottom=75,right=75]", ForceNodeModelOrder: true, + ConfigurableOpts: ConfigurableOpts{ + Padding: opts.Padding, + }, } } diff --git a/d2plugin/plugin_elk.go b/d2plugin/plugin_elk.go index 5bdc018e1..da0ee3a5e 100644 --- a/d2plugin/plugin_elk.go +++ b/d2plugin/plugin_elk.go @@ -13,16 +13,16 @@ import ( var ELKPlugin = elkPlugin{} func init() { - plugins = append(plugins, ELKPlugin) + plugins = append(plugins, &ELKPlugin) } type elkPlugin struct { - opts *d2elklayout.ELKLayoutOptions + opts *d2elklayout.ConfigurableOpts } -func (p elkPlugin) HydrateOpts(ctx context.Context, opts interface{}) error { +func (p *elkPlugin) HydrateOpts(ctx context.Context, opts interface{}) error { if opts != nil { - elkOpts, ok := opts.(d2elklayout.ELKLayoutOptions) + elkOpts, ok := opts.(d2elklayout.ConfigurableOpts) if !ok { return xmain.UsageErrorf("non-dagre layout options given for dagre") } @@ -32,7 +32,7 @@ func (p elkPlugin) HydrateOpts(ctx context.Context, opts interface{}) error { return nil } -func (p elkPlugin) Info(context.Context) (*PluginInfo, error) { +func (p *elkPlugin) Info(context.Context) (*PluginInfo, error) { return &PluginInfo{ Name: "elk", ShortHelp: "Eclipse Layout Kernel (ELK) with the Layered algorithm.", @@ -42,10 +42,10 @@ See https://github.com/kieler/elkjs for more.`, }, nil } -func (p elkPlugin) Layout(ctx context.Context, g *d2graph.Graph) error { +func (p *elkPlugin) Layout(ctx context.Context, g *d2graph.Graph) error { return d2elklayout.Layout(ctx, g, p.opts) } -func (p elkPlugin) PostProcess(ctx context.Context, in []byte) ([]byte, error) { +func (p *elkPlugin) PostProcess(ctx context.Context, in []byte) ([]byte, error) { return in, nil } diff --git a/main.go b/main.go index 83ccef354..28ef74ea2 100644 --- a/main.go +++ b/main.go @@ -18,6 +18,7 @@ import ( "oss.terrastruct.com/util-go/xmain" "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" + "oss.terrastruct.com/d2/d2layouts/d2elklayout" "oss.terrastruct.com/d2/d2lib" "oss.terrastruct.com/d2/d2plugin" "oss.terrastruct.com/d2/d2renderers/d2fonts" @@ -306,18 +307,54 @@ func DiscardSlog(ctx context.Context) context.Context { } func populateLayoutOpts(ms *xmain.State) error { - _, err := ms.Opts.Int64("", "dagre-nodesep", "", int64(d2dagrelayout.DefaultOpts.NodeSep), "number of pixels that separate nodes horizontally in the layout.") + _, err := ms.Opts.Int64("", "dagre-nodesep", "", int64(d2dagrelayout.DefaultOpts.NodeSep), "number of pixels that separate nodes horizontally.") if err != nil { return err } + _, err = ms.Opts.Int64("", "dagre-edgesep", "", int64(d2dagrelayout.DefaultOpts.EdgeSep), "number of pixels that separate edges horizontally.") + if err != nil { + return err + } + + ms.Opts.String("", "elk-algorithm", "", d2elklayout.DefaultOpts.Algorithm, "number of pixels that separate nodes horizontally.") + _, err = ms.Opts.Int64("", "elk-nodeNodeBetweenLayers", "", int64(d2elklayout.DefaultOpts.NodeSpacing), "number of pixels that separate edges horizontally.") + if err != nil { + return err + } + ms.Opts.String("", "elk-padding", "", d2elklayout.DefaultOpts.Padding, "number of pixels that separate nodes horizontally.") + _, err = ms.Opts.Int64("", "elk-edgeNodeBetweenLayers", "", int64(d2elklayout.DefaultOpts.EdgeNodeSpacing), "number of pixels that separate edges horizontally.") + if err != nil { + return err + } + _, err = ms.Opts.Int64("", "elk-nodeSelfLoop", "", int64(d2elklayout.DefaultOpts.SelfLoopSpacing), "number of pixels that separate edges horizontally.") + if err != nil { + return err + } + return nil } func parseLayoutOpts(ms *xmain.State, layout string) (interface{}, error) { - if layout == "dagre" { + switch layout { + case "dagre": nodesep, _ := ms.Opts.Flags.GetInt64("dagre-nodesep") + edgesep, _ := ms.Opts.Flags.GetInt64("dagre-edgesep") return d2dagrelayout.Opts{ NodeSep: int(nodesep), + EdgeSep: int(edgesep), + }, nil + case "elk": + algorithm, _ := ms.Opts.Flags.GetString("elk-algorithm") + nodeSpacing, _ := ms.Opts.Flags.GetInt64("elk-nodeNodeBetweenLayers") + padding, _ := ms.Opts.Flags.GetString("elk-padding") + edgeNodeSpacing, _ := ms.Opts.Flags.GetInt64("elk-edgeNodeSpacing") + selfLoopSpacing, _ := ms.Opts.Flags.GetInt64("elk-nodeSelfLoop") + return d2elklayout.ConfigurableOpts{ + Algorithm: algorithm, + NodeSpacing: int(nodeSpacing), + Padding: padding, + EdgeNodeSpacing: int(edgeNodeSpacing), + SelfLoopSpacing: int(selfLoopSpacing), }, nil } From 35ccc05d3965f1637f245384dbbc4c65a7fcc9cb Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Fri, 30 Dec 2022 00:09:28 -0800 Subject: [PATCH 03/16] refactor --- d2plugin/exec.go | 12 +++-- d2plugin/plugin.go | 21 +++++++- d2plugin/plugin_dagre.go | 27 ++++++++-- d2plugin/plugin_elk.go | 41 ++++++++++++--- main.go | 107 ++++++++++++++++++++------------------- 5 files changed, 143 insertions(+), 65 deletions(-) diff --git a/d2plugin/exec.go b/d2plugin/exec.go index b15ac7a5f..0a348c0c9 100644 --- a/d2plugin/exec.go +++ b/d2plugin/exec.go @@ -41,10 +41,16 @@ type execPlugin struct { opts map[string]string } -func (p execPlugin) HydrateOpts(ctx context.Context, opts interface{}) error { +func (p execPlugin) Flags() []PluginSpecificFlag { + // TODO + return nil +} + +func (p *execPlugin) HydrateOpts(opts []byte) error { if opts != nil { - execOpts, ok := opts.(map[string]string) - if !ok { + var execOpts map[string]string + err := json.Unmarshal(opts, &execOpts) + if err != nil { return xmain.UsageErrorf("non-exec layout options given for exec") } diff --git a/d2plugin/plugin.go b/d2plugin/plugin.go index 2e538f4c4..b4524cdad 100644 --- a/d2plugin/plugin.go +++ b/d2plugin/plugin.go @@ -19,11 +19,22 @@ import ( // See plugin_* files for the plugins available for bundling. var plugins []Plugin +type PluginSpecificFlag struct { + Name string + Type string + Default interface{} + Usage string + // Must match the tag in the opt + Tag string +} + type Plugin interface { // Info returns the current info information of the plugin. Info(context.Context) (*PluginInfo, error) - HydrateOpts(context.Context, interface{}) error + Flags() []PluginSpecificFlag + + HydrateOpts([]byte) error // Layout runs the plugin's autolayout algorithm on the input graph // and returns a new graph with the computed placements. @@ -111,3 +122,11 @@ func FindPlugin(ctx context.Context, name string) (Plugin, string, error) { return &execPlugin{path: path}, path, nil } + +func ListPluginFlags() []PluginSpecificFlag { + var out []PluginSpecificFlag + for _, p := range plugins { + out = append(out, p.Flags()...) + } + return out +} diff --git a/d2plugin/plugin_dagre.go b/d2plugin/plugin_dagre.go index b4d75688b..e78629481 100644 --- a/d2plugin/plugin_dagre.go +++ b/d2plugin/plugin_dagre.go @@ -4,6 +4,7 @@ package d2plugin import ( "context" + "encoding/json" "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" @@ -20,10 +21,30 @@ type dagrePlugin struct { opts *d2dagrelayout.Opts } -func (p *dagrePlugin) HydrateOpts(ctx context.Context, opts interface{}) error { +func (p dagrePlugin) Flags() []PluginSpecificFlag { + return []PluginSpecificFlag{ + { + Name: "dagre-nodesep", + Type: "int64", + Default: int64(d2dagrelayout.DefaultOpts.NodeSep), + Usage: "number of pixels that separate nodes horizontally.", + Tag: "nodesep", + }, + { + Name: "dagre-edgesep", + Type: "int64", + Default: int64(d2dagrelayout.DefaultOpts.EdgeSep), + Usage: "number of pixels that separate edges horizontally.", + Tag: "edgesep", + }, + } +} + +func (p *dagrePlugin) HydrateOpts(opts []byte) error { if opts != nil { - dagreOpts, ok := opts.(d2dagrelayout.Opts) - if !ok { + var dagreOpts d2dagrelayout.Opts + err := json.Unmarshal(opts, &dagreOpts) + if err != nil { return xmain.UsageErrorf("non-dagre layout options given for dagre") } diff --git a/d2plugin/plugin_elk.go b/d2plugin/plugin_elk.go index da0ee3a5e..b07db9c37 100644 --- a/d2plugin/plugin_elk.go +++ b/d2plugin/plugin_elk.go @@ -4,6 +4,7 @@ package d2plugin import ( "context" + "encoding/json" "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2layouts/d2elklayout" @@ -20,10 +21,38 @@ type elkPlugin struct { opts *d2elklayout.ConfigurableOpts } -func (p *elkPlugin) HydrateOpts(ctx context.Context, opts interface{}) error { +func (p elkPlugin) Flags() []PluginSpecificFlag { + // ms.Opts.String("", "elk-algorithm", "", d2elklayout.DefaultOpts.Algorithm, "number of pixels that separate nodes horizontally.") + // _, err = ms.Opts.Int64("", "elk-nodeNodeBetweenLayers", "", int64(d2elklayout.DefaultOpts.NodeSpacing), "number of pixels that separate edges horizontally.") + // if err != nil { + // return err + // } + // ms.Opts.String("", "elk-padding", "", d2elklayout.DefaultOpts.Padding, "number of pixels that separate nodes horizontally.") + // _, err = ms.Opts.Int64("", "elk-edgeNodeBetweenLayers", "", int64(d2elklayout.DefaultOpts.EdgeNodeSpacing), "number of pixels that separate edges horizontally.") + // if err != nil { + // return err + // } + // _, err = ms.Opts.Int64("", "elk-nodeSelfLoop", "", int64(d2elklayout.DefaultOpts.SelfLoopSpacing), "number of pixels that separate edges horizontally.") + // if err != nil { + // return err + // } + return []PluginSpecificFlag{ + { + Name: "elk-algorithm", + Type: "string", + Default: d2elklayout.DefaultOpts.Algorithm, + Usage: "number of pixels that separate nodes horizontally.", + Tag: "elk.algorithm", + }, + } +} + +func (p *elkPlugin) HydrateOpts(opts []byte) error { if opts != nil { - elkOpts, ok := opts.(d2elklayout.ConfigurableOpts) - if !ok { + var elkOpts d2elklayout.ConfigurableOpts + err := json.Unmarshal(opts, &elkOpts) + if err != nil { + // TODO not right return xmain.UsageErrorf("non-dagre layout options given for dagre") } @@ -32,7 +61,7 @@ func (p *elkPlugin) HydrateOpts(ctx context.Context, opts interface{}) error { return nil } -func (p *elkPlugin) Info(context.Context) (*PluginInfo, error) { +func (p elkPlugin) Info(context.Context) (*PluginInfo, error) { return &PluginInfo{ Name: "elk", ShortHelp: "Eclipse Layout Kernel (ELK) with the Layered algorithm.", @@ -42,10 +71,10 @@ See https://github.com/kieler/elkjs for more.`, }, nil } -func (p *elkPlugin) Layout(ctx context.Context, g *d2graph.Graph) error { +func (p elkPlugin) Layout(ctx context.Context, g *d2graph.Graph) error { return d2elklayout.Layout(ctx, g, p.opts) } -func (p *elkPlugin) PostProcess(ctx context.Context, in []byte) ([]byte, error) { +func (p elkPlugin) PostProcess(ctx context.Context, in []byte) ([]byte, error) { return in, nil } diff --git a/main.go b/main.go index 28ef74ea2..d536bdaed 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "encoding/json" "errors" "fmt" "io" @@ -17,8 +18,6 @@ import ( "oss.terrastruct.com/util-go/go2" "oss.terrastruct.com/util-go/xmain" - "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" - "oss.terrastruct.com/d2/d2layouts/d2elklayout" "oss.terrastruct.com/d2/d2lib" "oss.terrastruct.com/d2/d2plugin" "oss.terrastruct.com/d2/d2renderers/d2fonts" @@ -151,12 +150,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) { return err } - layoutOpts, err := parseLayoutOpts(ms, *layoutFlag) - if err != nil { - return err - } - - err = plugin.HydrateOpts(ctx, layoutOpts) + err = parseLayoutOpts(ms, plugin) if err != nil { return err } @@ -307,56 +301,65 @@ func DiscardSlog(ctx context.Context) context.Context { } func populateLayoutOpts(ms *xmain.State) error { - _, err := ms.Opts.Int64("", "dagre-nodesep", "", int64(d2dagrelayout.DefaultOpts.NodeSep), "number of pixels that separate nodes horizontally.") - if err != nil { - return err - } - _, err = ms.Opts.Int64("", "dagre-edgesep", "", int64(d2dagrelayout.DefaultOpts.EdgeSep), "number of pixels that separate edges horizontally.") - if err != nil { - return err - } + pluginFlags := d2plugin.ListPluginFlags() - ms.Opts.String("", "elk-algorithm", "", d2elklayout.DefaultOpts.Algorithm, "number of pixels that separate nodes horizontally.") - _, err = ms.Opts.Int64("", "elk-nodeNodeBetweenLayers", "", int64(d2elklayout.DefaultOpts.NodeSpacing), "number of pixels that separate edges horizontally.") - if err != nil { - return err - } - ms.Opts.String("", "elk-padding", "", d2elklayout.DefaultOpts.Padding, "number of pixels that separate nodes horizontally.") - _, err = ms.Opts.Int64("", "elk-edgeNodeBetweenLayers", "", int64(d2elklayout.DefaultOpts.EdgeNodeSpacing), "number of pixels that separate edges horizontally.") - if err != nil { - return err - } - _, err = ms.Opts.Int64("", "elk-nodeSelfLoop", "", int64(d2elklayout.DefaultOpts.SelfLoopSpacing), "number of pixels that separate edges horizontally.") - if err != nil { - return err + for _, f := range pluginFlags { + switch f.Type { + case "string": + ms.Opts.String("", f.Name, "", f.Default.(string), f.Usage) + case "int64": + ms.Opts.Int64("", f.Name, "", f.Default.(int64), f.Usage) + } } return nil } -func parseLayoutOpts(ms *xmain.State, layout string) (interface{}, error) { - switch layout { - case "dagre": - nodesep, _ := ms.Opts.Flags.GetInt64("dagre-nodesep") - edgesep, _ := ms.Opts.Flags.GetInt64("dagre-edgesep") - return d2dagrelayout.Opts{ - NodeSep: int(nodesep), - EdgeSep: int(edgesep), - }, nil - case "elk": - algorithm, _ := ms.Opts.Flags.GetString("elk-algorithm") - nodeSpacing, _ := ms.Opts.Flags.GetInt64("elk-nodeNodeBetweenLayers") - padding, _ := ms.Opts.Flags.GetString("elk-padding") - edgeNodeSpacing, _ := ms.Opts.Flags.GetInt64("elk-edgeNodeSpacing") - selfLoopSpacing, _ := ms.Opts.Flags.GetInt64("elk-nodeSelfLoop") - return d2elklayout.ConfigurableOpts{ - Algorithm: algorithm, - NodeSpacing: int(nodeSpacing), - Padding: padding, - EdgeNodeSpacing: int(edgeNodeSpacing), - SelfLoopSpacing: int(selfLoopSpacing), - }, nil +func parseLayoutOpts(ms *xmain.State, plugin d2plugin.Plugin) error { + opts := make(map[string]interface{}) + for _, f := range plugin.Flags() { + switch f.Type { + case "string": + val, _ := ms.Opts.Flags.GetString(f.Name) + opts[f.Tag] = val + case "int64": + val, _ := ms.Opts.Flags.GetInt64(f.Name) + opts[f.Tag] = val + } } - return nil, fmt.Errorf("unexpected error, layout not found for parsing opts") + b, err := json.Marshal(opts) + if err != nil { + return err + } + + err = plugin.HydrateOpts(b) + return err + + // switch layout { + // case "dagre": + // nodesep, _ := ms.Opts.Flags.GetInt64("dagre-nodesep") + // edgesep, _ := ms.Opts.Flags.GetInt64("dagre-edgesep") + // return d2dagrelayout.Opts{ + // NodeSep: int(nodesep), + // EdgeSep: int(edgesep), + // }, nil + // case "elk": + // algorithm, _ := ms.Opts.Flags.GetString("elk-algorithm") + // nodeSpacing, _ := ms.Opts.Flags.GetInt64("elk-nodeNodeBetweenLayers") + // padding, _ := ms.Opts.Flags.GetString("elk-padding") + // edgeNodeSpacing, _ := ms.Opts.Flags.GetInt64("elk-edgeNodeSpacing") + // selfLoopSpacing, _ := ms.Opts.Flags.GetInt64("elk-nodeSelfLoop") + // return d2elklayout.ConfigurableOpts{ + // Algorithm: algorithm, + // NodeSpacing: int(nodeSpacing), + // Padding: padding, + // EdgeNodeSpacing: int(edgeNodeSpacing), + // SelfLoopSpacing: int(selfLoopSpacing), + // }, nil + // default: + // + // } + // + // return nil, fmt.Errorf("unexpected error, layout not found for parsing opts") } From fc5f58323c6a4e669869a827aec9af8100792feb Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Fri, 30 Dec 2022 11:33:32 -0800 Subject: [PATCH 04/16] add ctx, implement exec --- d2plugin/exec.go | 26 +++++++++++++++++++++++--- d2plugin/plugin.go | 12 ++++++++---- d2plugin/plugin_dagre.go | 4 ++-- d2plugin/plugin_elk.go | 4 ++-- main.go | 19 +++++++++++++------ 5 files changed, 48 insertions(+), 17 deletions(-) diff --git a/d2plugin/exec.go b/d2plugin/exec.go index 0a348c0c9..0a186c908 100644 --- a/d2plugin/exec.go +++ b/d2plugin/exec.go @@ -41,9 +41,29 @@ type execPlugin struct { opts map[string]string } -func (p execPlugin) Flags() []PluginSpecificFlag { - // TODO - return nil +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") + defer xdefer.Errorf(&err, "failed to run %v", cmd.Args) + + stdout, err := cmd.Output() + if err != nil { + ee := &exec.ExitError{} + if errors.As(err, &ee) && len(ee.Stderr) > 0 { + return nil, fmt.Errorf("%v\nstderr:\n%s", ee, ee.Stderr) + } + return nil, err + } + + var flags []PluginSpecificFlag + + err = json.Unmarshal(stdout, &flags) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal json: %w", err) + } + + return flags, nil } func (p *execPlugin) HydrateOpts(opts []byte) error { diff --git a/d2plugin/plugin.go b/d2plugin/plugin.go index b4524cdad..0b7288a75 100644 --- a/d2plugin/plugin.go +++ b/d2plugin/plugin.go @@ -32,7 +32,7 @@ type Plugin interface { // Info returns the current info information of the plugin. Info(context.Context) (*PluginInfo, error) - Flags() []PluginSpecificFlag + Flags(context.Context) ([]PluginSpecificFlag, error) HydrateOpts([]byte) error @@ -123,10 +123,14 @@ func FindPlugin(ctx context.Context, name string) (Plugin, string, error) { return &execPlugin{path: path}, path, nil } -func ListPluginFlags() []PluginSpecificFlag { +func ListPluginFlags(ctx context.Context) ([]PluginSpecificFlag, error) { var out []PluginSpecificFlag for _, p := range plugins { - out = append(out, p.Flags()...) + flags, err := p.Flags(ctx) + if err != nil { + return nil, err + } + out = append(out, flags...) } - return out + return out, nil } diff --git a/d2plugin/plugin_dagre.go b/d2plugin/plugin_dagre.go index e78629481..2e23e0db6 100644 --- a/d2plugin/plugin_dagre.go +++ b/d2plugin/plugin_dagre.go @@ -21,7 +21,7 @@ type dagrePlugin struct { opts *d2dagrelayout.Opts } -func (p dagrePlugin) Flags() []PluginSpecificFlag { +func (p dagrePlugin) Flags(context.Context) ([]PluginSpecificFlag, error) { return []PluginSpecificFlag{ { Name: "dagre-nodesep", @@ -37,7 +37,7 @@ func (p dagrePlugin) Flags() []PluginSpecificFlag { Usage: "number of pixels that separate edges horizontally.", Tag: "edgesep", }, - } + }, nil } func (p *dagrePlugin) HydrateOpts(opts []byte) error { diff --git a/d2plugin/plugin_elk.go b/d2plugin/plugin_elk.go index b07db9c37..c3ab55544 100644 --- a/d2plugin/plugin_elk.go +++ b/d2plugin/plugin_elk.go @@ -21,7 +21,7 @@ type elkPlugin struct { opts *d2elklayout.ConfigurableOpts } -func (p elkPlugin) Flags() []PluginSpecificFlag { +func (p elkPlugin) Flags(context.Context) ([]PluginSpecificFlag, error) { // ms.Opts.String("", "elk-algorithm", "", d2elklayout.DefaultOpts.Algorithm, "number of pixels that separate nodes horizontally.") // _, err = ms.Opts.Int64("", "elk-nodeNodeBetweenLayers", "", int64(d2elklayout.DefaultOpts.NodeSpacing), "number of pixels that separate edges horizontally.") // if err != nil { @@ -44,7 +44,7 @@ func (p elkPlugin) Flags() []PluginSpecificFlag { Usage: "number of pixels that separate nodes horizontally.", Tag: "elk.algorithm", }, - } + }, nil } func (p *elkPlugin) HydrateOpts(opts []byte) error { diff --git a/main.go b/main.go index d536bdaed..727761ed5 100644 --- a/main.go +++ b/main.go @@ -76,7 +76,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) { return err } - err = populateLayoutOpts(ms) + err = populateLayoutOpts(ctx, ms) if err != nil { return err } @@ -150,7 +150,7 @@ func run(ctx context.Context, ms *xmain.State) (err error) { return err } - err = parseLayoutOpts(ms, plugin) + err = parseLayoutOpts(ctx, ms, plugin) if err != nil { return err } @@ -300,8 +300,11 @@ func DiscardSlog(ctx context.Context) context.Context { return ctxlog.With(ctx, slog.Make(sloghuman.Sink(io.Discard))) } -func populateLayoutOpts(ms *xmain.State) error { - pluginFlags := d2plugin.ListPluginFlags() +func populateLayoutOpts(ctx context.Context, ms *xmain.State) error { + pluginFlags, err := d2plugin.ListPluginFlags(ctx) + if err != nil { + return err + } for _, f := range pluginFlags { switch f.Type { @@ -315,9 +318,13 @@ func populateLayoutOpts(ms *xmain.State) error { return nil } -func parseLayoutOpts(ms *xmain.State, plugin d2plugin.Plugin) error { +func parseLayoutOpts(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin) error { opts := make(map[string]interface{}) - for _, f := range plugin.Flags() { + flags, err := plugin.Flags(ctx) + if err != nil { + return err + } + for _, f := range flags { switch f.Type { case "string": val, _ := ms.Opts.Flags.GetString(f.Name) From e6f9779e49783cb6597099ad28e33f33187aaa60 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Fri, 30 Dec 2022 11:33:56 -0800 Subject: [PATCH 05/16] clean --- main.go | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/main.go b/main.go index 727761ed5..bbfdff7b1 100644 --- a/main.go +++ b/main.go @@ -342,31 +342,4 @@ func parseLayoutOpts(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugi err = plugin.HydrateOpts(b) return err - - // switch layout { - // case "dagre": - // nodesep, _ := ms.Opts.Flags.GetInt64("dagre-nodesep") - // edgesep, _ := ms.Opts.Flags.GetInt64("dagre-edgesep") - // return d2dagrelayout.Opts{ - // NodeSep: int(nodesep), - // EdgeSep: int(edgesep), - // }, nil - // case "elk": - // algorithm, _ := ms.Opts.Flags.GetString("elk-algorithm") - // nodeSpacing, _ := ms.Opts.Flags.GetInt64("elk-nodeNodeBetweenLayers") - // padding, _ := ms.Opts.Flags.GetString("elk-padding") - // edgeNodeSpacing, _ := ms.Opts.Flags.GetInt64("elk-edgeNodeSpacing") - // selfLoopSpacing, _ := ms.Opts.Flags.GetInt64("elk-nodeSelfLoop") - // return d2elklayout.ConfigurableOpts{ - // Algorithm: algorithm, - // NodeSpacing: int(nodeSpacing), - // Padding: padding, - // EdgeNodeSpacing: int(edgeNodeSpacing), - // SelfLoopSpacing: int(selfLoopSpacing), - // }, nil - // default: - // - // } - // - // return nil, fmt.Errorf("unexpected error, layout not found for parsing opts") } From 85d102204f2e2635bf25ba51b01f4b2eda451e03 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Fri, 30 Dec 2022 12:06:40 -0800 Subject: [PATCH 06/16] add args to exec --- d2plugin/exec.go | 6 +++++- d2plugin/serve.go | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/d2plugin/exec.go b/d2plugin/exec.go index 0a186c908..cce95b5db 100644 --- a/d2plugin/exec.go +++ b/d2plugin/exec.go @@ -113,7 +113,11 @@ func (p execPlugin) Layout(ctx context.Context, g *d2graph.Graph) error { return err } - cmd := exec.CommandContext(ctx, p.path, "layout") + args := []string{"layout"} + for k, v := range p.opts { + args = append(args, k, v) + } + cmd := exec.CommandContext(ctx, p.path, args...) buffer := bytes.Buffer{} buffer.Write(graphBytes) diff --git a/d2plugin/serve.go b/d2plugin/serve.go index b13680746..cf857b3ee 100644 --- a/d2plugin/serve.go +++ b/d2plugin/serve.go @@ -38,6 +38,8 @@ func Serve(p Plugin) xmain.RunFunc { switch subcmd { case "info": return info(ctx, p, ms) + case "flags": + return flags(ctx, p, ms) case "layout": return layout(ctx, p, ms) case "postprocess": @@ -64,6 +66,22 @@ func info(ctx context.Context, p Plugin, ms *xmain.State) error { return nil } +func flags(ctx context.Context, p Plugin, ms *xmain.State) error { + flags, err := p.Flags(ctx) + if err != nil { + return err + } + b, err := json.Marshal(flags) + if err != nil { + return err + } + _, err = ms.Stdout.Write(b) + if err != nil { + return err + } + return nil +} + func layout(ctx context.Context, p Plugin, ms *xmain.State) error { in, err := io.ReadAll(ms.Stdin) if err != nil { From 4b99bc30a70633e1c1706c62d0f1587edad5eb52 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Fri, 30 Dec 2022 12:25:33 -0800 Subject: [PATCH 07/16] elk/dagre name consistency --- d2layouts/d2dagrelayout/layout.go | 26 +++++++------- d2layouts/d2elklayout/layout.go | 60 +++++++++++++++---------------- 2 files changed, 44 insertions(+), 42 deletions(-) diff --git a/d2layouts/d2dagrelayout/layout.go b/d2layouts/d2dagrelayout/layout.go index ee86e9599..6d2a5f91b 100644 --- a/d2layouts/d2dagrelayout/layout.go +++ b/d2layouts/d2dagrelayout/layout.go @@ -30,12 +30,12 @@ var setupJS string //go:embed dagre.js var dagreJS string -type Opts struct { +type ConfigurableOpts struct { NodeSep int EdgeSep int } -var DefaultOpts = Opts{ +var DefaultOpts = ConfigurableOpts{ NodeSep: 60, EdgeSep: 40, } @@ -52,16 +52,16 @@ type DagreEdge struct { Points []*geo.Point `json:"points"` } -type dagreGraphAttrs struct { +type dagreOpts struct { // for a top to bottom graph: ranksep is y spacing, nodesep is x spacing, edgesep is x spacing ranksep int - edgesep int - nodesep int // graph direction: tb (top to bottom)| bt | lr | rl rankdir string + + ConfigurableOpts } -func Layout(ctx context.Context, g *d2graph.Graph, opts *Opts) (err error) { +func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err error) { if opts == nil { opts = &DefaultOpts } @@ -76,9 +76,11 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *Opts) (err error) { return err } - rootAttrs := dagreGraphAttrs{ - edgesep: opts.EdgeSep, - nodesep: opts.NodeSep, + rootAttrs := dagreOpts{ + ConfigurableOpts: ConfigurableOpts{ + EdgeSep: opts.EdgeSep, + NodeSep: opts.NodeSep, + }, } isHorizontal := false switch g.Root.Attributes.Direction.Value { @@ -279,7 +281,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *Opts) (err error) { return nil } -func setGraphAttrs(attrs dagreGraphAttrs) string { +func setGraphAttrs(attrs dagreOpts) string { return fmt.Sprintf(`g.setGraph({ ranksep: %d, edgesep: %d, @@ -288,8 +290,8 @@ func setGraphAttrs(attrs dagreGraphAttrs) string { }); `, attrs.ranksep, - attrs.edgesep, - attrs.nodesep, + attrs.ConfigurableOpts.EdgeSep, + attrs.ConfigurableOpts.NodeSep, attrs.rankdir, ) } diff --git a/d2layouts/d2elklayout/layout.go b/d2layouts/d2elklayout/layout.go index f46e19004..384d877e5 100644 --- a/d2layouts/d2elklayout/layout.go +++ b/d2layouts/d2elklayout/layout.go @@ -31,23 +31,23 @@ var elkJS string var setupJS string type ELKNode struct { - ID string `json:"id"` - X float64 `json:"x"` - Y float64 `json:"y"` - Width float64 `json:"width"` - Height float64 `json:"height"` - Children []*ELKNode `json:"children,omitempty"` - Labels []*ELKLabel `json:"labels,omitempty"` - LayoutOptions *ELKLayoutOptions `json:"layoutOptions,omitempty"` + ID string `json:"id"` + X float64 `json:"x"` + Y float64 `json:"y"` + Width float64 `json:"width"` + Height float64 `json:"height"` + Children []*ELKNode `json:"children,omitempty"` + Labels []*ELKLabel `json:"labels,omitempty"` + LayoutOptions *elkOpts `json:"layoutOptions,omitempty"` } type ELKLabel struct { - Text string `json:"text"` - X float64 `json:"x"` - Y float64 `json:"y"` - Width float64 `json:"width"` - Height float64 `json:"height"` - LayoutOptions *ELKLayoutOptions `json:"layoutOptions,omitempty"` + Text string `json:"text"` + X float64 `json:"x"` + Y float64 `json:"y"` + Width float64 `json:"width"` + Height float64 `json:"height"` + LayoutOptions *elkOpts `json:"layoutOptions,omitempty"` } type ELKPoint struct { @@ -71,18 +71,10 @@ type ELKEdge struct { } type ELKGraph struct { - ID string `json:"id"` - LayoutOptions *ELKLayoutOptions `json:"layoutOptions"` - Children []*ELKNode `json:"children,omitempty"` - Edges []*ELKEdge `json:"edges,omitempty"` -} - -var DefaultOpts = ConfigurableOpts{ - Algorithm: "layered", - NodeSpacing: 100.0, - Padding: "[top=75,left=75,bottom=75,right=75]", - EdgeNodeSpacing: 50.0, - SelfLoopSpacing: 50.0, + ID string `json:"id"` + LayoutOptions *elkOpts `json:"layoutOptions"` + Children []*ELKNode `json:"children,omitempty"` + Edges []*ELKEdge `json:"edges,omitempty"` } type ConfigurableOpts struct { @@ -93,7 +85,15 @@ type ConfigurableOpts struct { SelfLoopSpacing int `json:"elk.spacing.nodeSelfLoop"` } -type ELKLayoutOptions struct { +var DefaultOpts = ConfigurableOpts{ + Algorithm: "layered", + NodeSpacing: 100.0, + Padding: "[top=75,left=75,bottom=75,right=75]", + EdgeNodeSpacing: 50.0, + SelfLoopSpacing: 50.0, +} + +type elkOpts struct { Direction string `json:"elk.direction"` HierarchyHandling string `json:"elk.hierarchyHandling,omitempty"` InlineEdgeLabels bool `json:"elk.edgeLabels.inline,omitempty"` @@ -125,7 +125,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err elkGraph := &ELKGraph{ ID: "root", - LayoutOptions: &ELKLayoutOptions{ + LayoutOptions: &elkOpts{ HierarchyHandling: "INCLUDE_CHILDREN", ConsiderModelOrder: "NODES_AND_EDGES", ConfigurableOpts: *opts, @@ -171,7 +171,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err } if len(obj.ChildrenArray) > 0 { - n.LayoutOptions = &ELKLayoutOptions{ + n.LayoutOptions = &elkOpts{ ForceNodeModelOrder: true, ConfigurableOpts: ConfigurableOpts{ Padding: opts.Padding, @@ -206,7 +206,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err Text: edge.Attributes.Label.Value, Width: float64(edge.LabelDimensions.Width), Height: float64(edge.LabelDimensions.Height), - LayoutOptions: &ELKLayoutOptions{ + LayoutOptions: &elkOpts{ InlineEdgeLabels: true, }, }) From 761d399d3abc83a4406b191194836fdc4254a66d Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Fri, 30 Dec 2022 12:34:25 -0800 Subject: [PATCH 08/16] elk options --- d2plugin/plugin_elk.go | 44 ++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/d2plugin/plugin_elk.go b/d2plugin/plugin_elk.go index c3ab55544..3667c7154 100644 --- a/d2plugin/plugin_elk.go +++ b/d2plugin/plugin_elk.go @@ -22,28 +22,42 @@ type elkPlugin struct { } func (p elkPlugin) Flags(context.Context) ([]PluginSpecificFlag, error) { - // ms.Opts.String("", "elk-algorithm", "", d2elklayout.DefaultOpts.Algorithm, "number of pixels that separate nodes horizontally.") - // _, err = ms.Opts.Int64("", "elk-nodeNodeBetweenLayers", "", int64(d2elklayout.DefaultOpts.NodeSpacing), "number of pixels that separate edges horizontally.") - // if err != nil { - // return err - // } - // ms.Opts.String("", "elk-padding", "", d2elklayout.DefaultOpts.Padding, "number of pixels that separate nodes horizontally.") - // _, err = ms.Opts.Int64("", "elk-edgeNodeBetweenLayers", "", int64(d2elklayout.DefaultOpts.EdgeNodeSpacing), "number of pixels that separate edges horizontally.") - // if err != nil { - // return err - // } - // _, err = ms.Opts.Int64("", "elk-nodeSelfLoop", "", int64(d2elklayout.DefaultOpts.SelfLoopSpacing), "number of pixels that separate edges horizontally.") - // if err != nil { - // return err - // } return []PluginSpecificFlag{ { Name: "elk-algorithm", Type: "string", Default: d2elklayout.DefaultOpts.Algorithm, - Usage: "number of pixels that separate nodes horizontally.", + Usage: "layout algorithm. https://www.eclipse.org/elk/reference/options/org-eclipse-elk-algorithm.html", Tag: "elk.algorithm", }, + { + Name: "elk-nodeNodeBetweenLayers", + Type: "int64", + Default: d2elklayout.DefaultOpts.NodeSpacing, + Usage: "the spacing to be preserved between any pair of nodes of two adjacent layers. https://www.eclipse.org/elk/reference/options/org-eclipse-elk-layered-spacing-nodeNodeBetweenLayers.html", + Tag: "spacing.nodeNodeBetweenLayers", + }, + { + Name: "elk-padding", + Type: "string", + Default: d2elklayout.DefaultOpts.Padding, + Usage: "the padding to be left to a parent element’s border when placing child elements. https://www.eclipse.org/elk/reference/options/org-eclipse-elk-padding.html", + Tag: "elk.padding", + }, + { + Name: "elk-edgeNodeBetweenLayers", + Type: "int64", + Default: d2elklayout.DefaultOpts.EdgeNodeSpacing, + Usage: "the spacing to be preserved between nodes and edges that are routed next to the node’s layer. https://www.eclipse.org/elk/reference/options/org-eclipse-elk-layered-spacing-edgeNodeBetweenLayers.html", + Tag: "spacing.edgeNodeBetweenLayers", + }, + { + Name: "elk-nodeSelfLoop", + Type: "int64", + Default: d2elklayout.DefaultOpts.SelfLoopSpacing, + Usage: "spacing to be preserved between a node and its self loops. https://www.eclipse.org/elk/reference/options/org-eclipse-elk-spacing-nodeSelfLoop.html", + Tag: "elk.spacing.nodeSelfLoop", + }, }, nil } From 3f540809ac9412a658fef4390265872302d7ee41 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Fri, 30 Dec 2022 12:40:52 -0800 Subject: [PATCH 09/16] save --- d2plugin/plugin_dagre.go | 4 ++-- d2plugin/plugin_elk.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/d2plugin/plugin_dagre.go b/d2plugin/plugin_dagre.go index 2e23e0db6..cfdca3b77 100644 --- a/d2plugin/plugin_dagre.go +++ b/d2plugin/plugin_dagre.go @@ -18,7 +18,7 @@ func init() { } type dagrePlugin struct { - opts *d2dagrelayout.Opts + opts *d2dagrelayout.ConfigurableOpts } func (p dagrePlugin) Flags(context.Context) ([]PluginSpecificFlag, error) { @@ -42,7 +42,7 @@ func (p dagrePlugin) Flags(context.Context) ([]PluginSpecificFlag, error) { func (p *dagrePlugin) HydrateOpts(opts []byte) error { if opts != nil { - var dagreOpts d2dagrelayout.Opts + var dagreOpts d2dagrelayout.ConfigurableOpts err := json.Unmarshal(opts, &dagreOpts) if err != nil { return xmain.UsageErrorf("non-dagre layout options given for dagre") diff --git a/d2plugin/plugin_elk.go b/d2plugin/plugin_elk.go index 3667c7154..2d4619bf8 100644 --- a/d2plugin/plugin_elk.go +++ b/d2plugin/plugin_elk.go @@ -33,7 +33,7 @@ func (p elkPlugin) Flags(context.Context) ([]PluginSpecificFlag, error) { { Name: "elk-nodeNodeBetweenLayers", Type: "int64", - Default: d2elklayout.DefaultOpts.NodeSpacing, + Default: int64(d2elklayout.DefaultOpts.NodeSpacing), Usage: "the spacing to be preserved between any pair of nodes of two adjacent layers. https://www.eclipse.org/elk/reference/options/org-eclipse-elk-layered-spacing-nodeNodeBetweenLayers.html", Tag: "spacing.nodeNodeBetweenLayers", }, @@ -47,14 +47,14 @@ func (p elkPlugin) Flags(context.Context) ([]PluginSpecificFlag, error) { { Name: "elk-edgeNodeBetweenLayers", Type: "int64", - Default: d2elklayout.DefaultOpts.EdgeNodeSpacing, + Default: int64(d2elklayout.DefaultOpts.EdgeNodeSpacing), Usage: "the spacing to be preserved between nodes and edges that are routed next to the node’s layer. https://www.eclipse.org/elk/reference/options/org-eclipse-elk-layered-spacing-edgeNodeBetweenLayers.html", Tag: "spacing.edgeNodeBetweenLayers", }, { Name: "elk-nodeSelfLoop", Type: "int64", - Default: d2elklayout.DefaultOpts.SelfLoopSpacing, + Default: int64(d2elklayout.DefaultOpts.SelfLoopSpacing), Usage: "spacing to be preserved between a node and its self loops. https://www.eclipse.org/elk/reference/options/org-eclipse-elk-spacing-nodeSelfLoop.html", Tag: "elk.spacing.nodeSelfLoop", }, From 85e87c8c2c42311eb963baaf28b1f9fa61a86571 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Fri, 30 Dec 2022 13:19:48 -0800 Subject: [PATCH 10/16] add help --- d2plugin/plugin.go | 10 ++++++++++ d2plugin/plugin_dagre.go | 24 +++++++++++++++++------- d2plugin/plugin_elk.go | 31 +++++++++++++++++++++++-------- main.go | 9 +++------ 4 files changed, 53 insertions(+), 21 deletions(-) diff --git a/d2plugin/plugin.go b/d2plugin/plugin.go index 0b7288a75..765d9f60e 100644 --- a/d2plugin/plugin.go +++ b/d2plugin/plugin.go @@ -10,6 +10,7 @@ import ( "os/exec" "oss.terrastruct.com/util-go/xexec" + "oss.terrastruct.com/util-go/xmain" "oss.terrastruct.com/d2/d2graph" ) @@ -28,6 +29,15 @@ type PluginSpecificFlag struct { Tag string } +func (f *PluginSpecificFlag) AddToOpts(opts *xmain.Opts) { + switch f.Type { + case "string": + opts.String("", f.Name, "", f.Default.(string), f.Usage) + case "int64": + opts.Int64("", f.Name, "", f.Default.(int64), f.Usage) + } +} + type Plugin interface { // Info returns the current info information of the plugin. Info(context.Context) (*PluginInfo, error) diff --git a/d2plugin/plugin_dagre.go b/d2plugin/plugin_dagre.go index cfdca3b77..c3f16a8f1 100644 --- a/d2plugin/plugin_dagre.go +++ b/d2plugin/plugin_dagre.go @@ -5,6 +5,7 @@ package d2plugin import ( "context" "encoding/json" + "fmt" "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" @@ -53,18 +54,27 @@ func (p *dagrePlugin) HydrateOpts(opts []byte) error { return nil } -func (p dagrePlugin) Info(context.Context) (*PluginInfo, error) { +func (p dagrePlugin) Info(ctx context.Context) (*PluginInfo, error) { + opts := xmain.NewOpts(nil, nil, nil) + flags, err := p.Flags(ctx) + if err != nil { + return nil, err + } + for _, f := range flags { + f.AddToOpts(opts) + } + return &PluginInfo{ Name: "dagre", ShortHelp: "The directed graph layout library Dagre", - LongHelp: `dagre is a directed graph layout library for JavaScript. + LongHelp: fmt.Sprintf(`dagre is a directed graph layout library for JavaScript. See https://github.com/dagrejs/dagre -The implementation of this plugin is at: https://github.com/terrastruct/d2/tree/master/d2plugin/d2dagrelayout -note: dagre is the primary layout algorithm for text to diagram generator Mermaid.js. - See https://github.com/mermaid-js/mermaid - We have a useful comparison at https://text-to-diagram.com/?example=basic&a=d2&b=mermaid -`, +Flags correspond to ones found at https://github.com/dagrejs/dagre/wiki. See dagre's reference for more on each. + +Flags: +%s +`, opts.Defaults()), }, nil } diff --git a/d2plugin/plugin_elk.go b/d2plugin/plugin_elk.go index 2d4619bf8..c5033d4e3 100644 --- a/d2plugin/plugin_elk.go +++ b/d2plugin/plugin_elk.go @@ -5,6 +5,7 @@ package d2plugin import ( "context" "encoding/json" + "fmt" "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2layouts/d2elklayout" @@ -27,35 +28,35 @@ func (p elkPlugin) Flags(context.Context) ([]PluginSpecificFlag, error) { Name: "elk-algorithm", Type: "string", Default: d2elklayout.DefaultOpts.Algorithm, - Usage: "layout algorithm. https://www.eclipse.org/elk/reference/options/org-eclipse-elk-algorithm.html", + Usage: "layout algorithm", Tag: "elk.algorithm", }, { Name: "elk-nodeNodeBetweenLayers", Type: "int64", Default: int64(d2elklayout.DefaultOpts.NodeSpacing), - Usage: "the spacing to be preserved between any pair of nodes of two adjacent layers. https://www.eclipse.org/elk/reference/options/org-eclipse-elk-layered-spacing-nodeNodeBetweenLayers.html", + Usage: "the spacing to be preserved between any pair of nodes of two adjacent layers", Tag: "spacing.nodeNodeBetweenLayers", }, { Name: "elk-padding", Type: "string", Default: d2elklayout.DefaultOpts.Padding, - Usage: "the padding to be left to a parent element’s border when placing child elements. https://www.eclipse.org/elk/reference/options/org-eclipse-elk-padding.html", + Usage: "the padding to be left to a parent element’s border when placing child elements", Tag: "elk.padding", }, { Name: "elk-edgeNodeBetweenLayers", Type: "int64", Default: int64(d2elklayout.DefaultOpts.EdgeNodeSpacing), - Usage: "the spacing to be preserved between nodes and edges that are routed next to the node’s layer. https://www.eclipse.org/elk/reference/options/org-eclipse-elk-layered-spacing-edgeNodeBetweenLayers.html", + Usage: "the spacing to be preserved between nodes and edges that are routed next to the node’s layer", Tag: "spacing.edgeNodeBetweenLayers", }, { Name: "elk-nodeSelfLoop", Type: "int64", Default: int64(d2elklayout.DefaultOpts.SelfLoopSpacing), - Usage: "spacing to be preserved between a node and its self loops. https://www.eclipse.org/elk/reference/options/org-eclipse-elk-spacing-nodeSelfLoop.html", + Usage: "spacing to be preserved between a node and its self loops", Tag: "elk.spacing.nodeSelfLoop", }, }, nil @@ -75,13 +76,27 @@ func (p *elkPlugin) HydrateOpts(opts []byte) error { return nil } -func (p elkPlugin) Info(context.Context) (*PluginInfo, error) { +func (p elkPlugin) Info(ctx context.Context) (*PluginInfo, error) { + opts := xmain.NewOpts(nil, nil, nil) + flags, err := p.Flags(ctx) + if err != nil { + return nil, err + } + for _, f := range flags { + f.AddToOpts(opts) + } return &PluginInfo{ Name: "elk", ShortHelp: "Eclipse Layout Kernel (ELK) with the Layered algorithm.", - LongHelp: `ELK is a layout engine offered by Eclipse. + LongHelp: fmt.Sprintf(`ELK is a layout engine offered by Eclipse. Originally written in Java, it has been ported to Javascript and cross-compiled into D2. -See https://github.com/kieler/elkjs for more.`, +See https://github.com/kieler/elkjs for more. + +Flags correspond to ones found in https://www.eclipse.org/elk/reference.html. See ELK's reference for more on each. + +Flags: +%s +`, opts.Defaults()), }, nil } diff --git a/main.go b/main.go index bbfdff7b1..d9c5655b4 100644 --- a/main.go +++ b/main.go @@ -307,12 +307,9 @@ func populateLayoutOpts(ctx context.Context, ms *xmain.State) error { } for _, f := range pluginFlags { - switch f.Type { - case "string": - ms.Opts.String("", f.Name, "", f.Default.(string), f.Usage) - case "int64": - ms.Opts.Int64("", f.Name, "", f.Default.(int64), f.Usage) - } + f.AddToOpts(ms.Opts) + // Don't pollute the main d2 flagset with these. It'll be a lot + ms.Opts.Flags.MarkHidden(f.Name) } return nil From 09ca0c5b03d5fc029a33841a922fcb8ad9c5b909 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Fri, 30 Dec 2022 13:22:45 -0800 Subject: [PATCH 11/16] modify --- ci/release/template/man/d2.1 | 2 +- help.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ci/release/template/man/d2.1 b/ci/release/template/man/d2.1 index a484ebe8f..b1bcb709c 100644 --- a/ci/release/template/man/d2.1 +++ b/ci/release/template/man/d2.1 @@ -82,7 +82,7 @@ Print version information and exit. .It Ar layout Lists available layout engine options with short help. .It Ar layout Op Ar name -Display long help for a particular layout engine. +Display long help for a particular layout engine, including its configuration options. .It Ar fmt Ar file.d2 Format .Ar file.d2 diff --git a/help.go b/help.go index bd6b455d0..27f9bc22c 100644 --- a/help.go +++ b/help.go @@ -32,7 +32,7 @@ Flags: Subcommands: %[1]s layout - Lists available layout engine options with short help - %[1]s layout [name] - Display long help for a particular layout engine + %[1]s layout [name] - Display long help for a particular layout engine, including its configuration options %[1]s fmt file.d2 - Format file.d2 See more docs and the source code at https://oss.terrastruct.com/d2 @@ -75,7 +75,7 @@ Example: D2_LAYOUT=dagre d2 in.d2 out.svg Subcommands: - %s layout [layout name] - Display long help for a particular layout engine + %s layout [layout name] - Display long help for a particular layout engine, including its configuration options See more docs at https://oss.terrastruct.com/d2 `, strings.Join(pluginLines, "\n"), ms.Name) From 1831af5308da8bf53b93e782b79f8d19a7f4fd6d Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Fri, 30 Dec 2022 13:26:01 -0800 Subject: [PATCH 12/16] add tags to dagre --- d2layouts/d2dagrelayout/layout.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/d2layouts/d2dagrelayout/layout.go b/d2layouts/d2dagrelayout/layout.go index 6d2a5f91b..a6cb68e84 100644 --- a/d2layouts/d2dagrelayout/layout.go +++ b/d2layouts/d2dagrelayout/layout.go @@ -31,8 +31,8 @@ var setupJS string var dagreJS string type ConfigurableOpts struct { - NodeSep int - EdgeSep int + NodeSep int `json:"nodesep"` + EdgeSep int `json:"edgesep"` } var DefaultOpts = ConfigurableOpts{ From 97078e53aee53d8d0cc9bf4bc49be929c4d19d25 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Fri, 30 Dec 2022 13:29:16 -0800 Subject: [PATCH 13/16] ok --- d2plugin/plugin_elk.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/d2plugin/plugin_elk.go b/d2plugin/plugin_elk.go index c5033d4e3..9ef2c5847 100644 --- a/d2plugin/plugin_elk.go +++ b/d2plugin/plugin_elk.go @@ -92,7 +92,7 @@ func (p elkPlugin) Info(ctx context.Context) (*PluginInfo, error) { Originally written in Java, it has been ported to Javascript and cross-compiled into D2. See https://github.com/kieler/elkjs for more. -Flags correspond to ones found in https://www.eclipse.org/elk/reference.html. See ELK's reference for more on each. +Flags correspond to ones found at https://www.eclipse.org/elk/reference.html. See ELK's reference for more on each. Flags: %s From 115d6e41477e029463eebf341f0e3ea9c717e134 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Fri, 30 Dec 2022 13:36:49 -0800 Subject: [PATCH 14/16] default layouts --- cmd/d2plugin-dagre/main.go | 2 +- d2chaos/d2chaos_test.go | 2 +- d2exporter/export_test.go | 2 +- d2layouts/d2dagrelayout/layout.go | 4 ++++ d2layouts/d2elklayout/layout.go | 4 ++++ d2renderers/d2sketch/sketch_test.go | 2 +- d2renderers/d2svg/appendix/appendix_test.go | 2 +- docs/examples/lib/1-d2lib/d2lib.go | 6 +++++- docs/examples/lib/2-d2oracle/d2oracle.go | 2 +- docs/examples/lib/3-lowlevel/lowlevel.go | 2 +- e2etests/e2e_test.go | 4 ++-- 11 files changed, 22 insertions(+), 10 deletions(-) diff --git a/cmd/d2plugin-dagre/main.go b/cmd/d2plugin-dagre/main.go index 5dbed2235..96130300e 100644 --- a/cmd/d2plugin-dagre/main.go +++ b/cmd/d2plugin-dagre/main.go @@ -9,5 +9,5 @@ import ( ) func main() { - xmain.Main(d2plugin.Serve(d2plugin.DagrePlugin)) + xmain.Main(d2plugin.Serve(&d2plugin.DagrePlugin)) } diff --git a/d2chaos/d2chaos_test.go b/d2chaos/d2chaos_test.go index d5ac6a17d..8b73e4e82 100644 --- a/d2chaos/d2chaos_test.go +++ b/d2chaos/d2chaos_test.go @@ -123,7 +123,7 @@ func test(t *testing.T, textPath, text string) { err = g.SetDimensions(nil, ruler, nil) assert.Nil(t, err) - err = d2dagrelayout.Layout(ctx, g) + err = d2dagrelayout.DefaultLayout(ctx, g) if err != nil { t.Fatal(err) } diff --git a/d2exporter/export_test.go b/d2exporter/export_test.go index ca813d2b1..613090be5 100644 --- a/d2exporter/export_test.go +++ b/d2exporter/export_test.go @@ -239,7 +239,7 @@ func run(t *testing.T, tc testCase) { err = g.SetDimensions(nil, ruler, nil) assert.JSON(t, nil, err) - err = d2sequence.Layout(ctx, g, d2dagrelayout.Layout) + err = d2sequence.Layout(ctx, g, d2dagrelayout.DefaultLayout) if err != nil { t.Fatal(err) } diff --git a/d2layouts/d2dagrelayout/layout.go b/d2layouts/d2dagrelayout/layout.go index a6cb68e84..9969d2080 100644 --- a/d2layouts/d2dagrelayout/layout.go +++ b/d2layouts/d2dagrelayout/layout.go @@ -61,6 +61,10 @@ type dagreOpts struct { ConfigurableOpts } +func DefaultLayout(ctx context.Context, g *d2graph.Graph) (err error) { + return Layout(ctx, g, nil) +} + func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err error) { if opts == nil { opts = &DefaultOpts diff --git a/d2layouts/d2elklayout/layout.go b/d2layouts/d2elklayout/layout.go index 384d877e5..253310c61 100644 --- a/d2layouts/d2elklayout/layout.go +++ b/d2layouts/d2elklayout/layout.go @@ -103,6 +103,10 @@ type elkOpts struct { ConfigurableOpts } +func DefaultLayout(ctx context.Context, g *d2graph.Graph) (err error) { + return Layout(ctx, g, nil) +} + func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err error) { if opts == nil { opts = &DefaultOpts diff --git a/d2renderers/d2sketch/sketch_test.go b/d2renderers/d2sketch/sketch_test.go index e033bedfe..c2b8dadea 100644 --- a/d2renderers/d2sketch/sketch_test.go +++ b/d2renderers/d2sketch/sketch_test.go @@ -314,7 +314,7 @@ func run(t *testing.T, tc testCase) { diagram, _, err := d2lib.Compile(ctx, tc.script, &d2lib.CompileOptions{ Ruler: ruler, ThemeID: 0, - Layout: d2dagrelayout.Layout, + Layout: d2dagrelayout.DefaultLayout, FontFamily: go2.Pointer(d2fonts.HandDrawn), }) if !tassert.Nil(t, err) { diff --git a/d2renderers/d2svg/appendix/appendix_test.go b/d2renderers/d2svg/appendix/appendix_test.go index a4105c38e..74a6f327f 100644 --- a/d2renderers/d2svg/appendix/appendix_test.go +++ b/d2renderers/d2svg/appendix/appendix_test.go @@ -121,7 +121,7 @@ func run(t *testing.T, tc testCase) { diagram, _, err := d2lib.Compile(ctx, tc.script, &d2lib.CompileOptions{ Ruler: ruler, ThemeID: 0, - Layout: d2dagrelayout.Layout, + Layout: d2dagrelayout.DefaultLayout, }) if !tassert.Nil(t, err) { return diff --git a/docs/examples/lib/1-d2lib/d2lib.go b/docs/examples/lib/1-d2lib/d2lib.go index 3b0114c88..af2b98674 100644 --- a/docs/examples/lib/1-d2lib/d2lib.go +++ b/docs/examples/lib/1-d2lib/d2lib.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "path/filepath" + "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" "oss.terrastruct.com/d2/d2lib" "oss.terrastruct.com/d2/d2renderers/d2svg" @@ -15,8 +16,11 @@ import ( // Remember to add if err != nil checks in production. func main() { ruler, _ := textmeasure.NewRuler() + defaultLayout := func(ctx context.Context, g *d2graph.Graph) error { + return d2dagrelayout.Layout(ctx, g, nil) + } diagram, _, _ := d2lib.Compile(context.Background(), "x -> y", &d2lib.CompileOptions{ - Layout: d2dagrelayout.Layout, + Layout: defaultLayout, Ruler: ruler, ThemeID: d2themescatalog.GrapeSoda.ID, }) diff --git a/docs/examples/lib/2-d2oracle/d2oracle.go b/docs/examples/lib/2-d2oracle/d2oracle.go index 1f265a05e..617943e15 100644 --- a/docs/examples/lib/2-d2oracle/d2oracle.go +++ b/docs/examples/lib/2-d2oracle/d2oracle.go @@ -17,7 +17,7 @@ func main() { // From one.go ruler, _ := textmeasure.NewRuler() _, graph, _ := d2lib.Compile(context.Background(), "x -> y", &d2lib.CompileOptions{ - Layout: d2dagrelayout.Layout, + Layout: d2dagrelayout.DefaultLayout, Ruler: ruler, ThemeID: d2themescatalog.GrapeSoda.ID, }) diff --git a/docs/examples/lib/3-lowlevel/lowlevel.go b/docs/examples/lib/3-lowlevel/lowlevel.go index 1ad6443b0..1912137f4 100644 --- a/docs/examples/lib/3-lowlevel/lowlevel.go +++ b/docs/examples/lib/3-lowlevel/lowlevel.go @@ -19,7 +19,7 @@ func main() { graph, _ := d2compiler.Compile("", strings.NewReader("x -> y"), nil) ruler, _ := textmeasure.NewRuler() _ = graph.SetDimensions(nil, ruler, nil) - _ = d2dagrelayout.Layout(context.Background(), graph) + _ = d2dagrelayout.Layout(context.Background(), graph, nil) diagram, _ := d2exporter.Export(context.Background(), graph, d2themescatalog.NeutralDefault.ID, nil) out, _ := d2svg.Render(diagram, &d2svg.RenderOpts{ Pad: d2svg.DEFAULT_PADDING, diff --git a/e2etests/e2e_test.go b/e2etests/e2e_test.go index 994770426..dcca4bba2 100644 --- a/e2etests/e2e_test.go +++ b/e2etests/e2e_test.go @@ -130,9 +130,9 @@ func run(t *testing.T, tc testCase) { for _, layoutName := range layoutsTested { var layout func(context.Context, *d2graph.Graph) error if layoutName == "dagre" { - layout = d2dagrelayout.Layout + layout = d2dagrelayout.DefaultLayout } else if layoutName == "elk" { - layout = d2elklayout.Layout + layout = d2elklayout.DefaultLayout } diagram, _, err := d2lib.Compile(ctx, tc.script, &d2lib.CompileOptions{ Ruler: ruler, From 4e1ac0876e092f45e65cf8594c121f17fc004328 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Fri, 30 Dec 2022 13:39:21 -0800 Subject: [PATCH 15/16] remove a todo --- d2plugin/plugin_elk.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/d2plugin/plugin_elk.go b/d2plugin/plugin_elk.go index 9ef2c5847..631e953ba 100644 --- a/d2plugin/plugin_elk.go +++ b/d2plugin/plugin_elk.go @@ -67,8 +67,7 @@ func (p *elkPlugin) HydrateOpts(opts []byte) error { var elkOpts d2elklayout.ConfigurableOpts err := json.Unmarshal(opts, &elkOpts) if err != nil { - // TODO not right - return xmain.UsageErrorf("non-dagre layout options given for dagre") + return xmain.UsageErrorf("non-ELK layout options given for ELK") } p.opts = &elkOpts From 26be278934fa6109eafa2ef8616f2fe40ed81286 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Fri, 30 Dec 2022 14:28:52 -0800 Subject: [PATCH 16/16] fix elk --- d2layouts/d2elklayout/layout.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/d2layouts/d2elklayout/layout.go b/d2layouts/d2elklayout/layout.go index 253310c61..89ad6e50a 100644 --- a/d2layouts/d2elklayout/layout.go +++ b/d2layouts/d2elklayout/layout.go @@ -132,7 +132,12 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err LayoutOptions: &elkOpts{ HierarchyHandling: "INCLUDE_CHILDREN", ConsiderModelOrder: "NODES_AND_EDGES", - ConfigurableOpts: *opts, + ConfigurableOpts: ConfigurableOpts{ + Algorithm: opts.Algorithm, + NodeSpacing: opts.NodeSpacing, + EdgeNodeSpacing: opts.EdgeNodeSpacing, + SelfLoopSpacing: opts.SelfLoopSpacing, + }, }, } switch g.Root.Attributes.Direction.Value {