commit
6ae3ede27f
11 changed files with 92 additions and 28 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
env:
|
||||
GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }}
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: d2chaos
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
.Nm d2
|
||||
.Op Fl -watch Ar false
|
||||
.Op Fl -theme Em 0
|
||||
.Op Fl -salt Ar string
|
||||
.Ar file.d2
|
||||
.Op Ar file.svg | file.png
|
||||
.Nm d2
|
||||
|
|
@ -128,6 +129,9 @@ The maximum number of seconds that D2 runs for before timing out and exiting. Wh
|
|||
.It Fl -check Ar false
|
||||
Check that the specified files are formatted correctly
|
||||
.Ns .
|
||||
.It Fl -salt Ar string
|
||||
Add a salt value to ensure the output uses unique IDs. This is useful when generating multiple identical diagrams to be included in the same HTML doc, so that duplicate id's do not cause invalid HTML. The salt value is a string that will be appended to IDs in the output.
|
||||
.Ns .
|
||||
.It Fl h , -help
|
||||
Print usage information and exit
|
||||
.Ns .
|
||||
|
|
|
|||
|
|
@ -134,6 +134,8 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
saltFlag := ms.Opts.String("", "salt", "", "", "Add a salt value to ensure the output uses unique IDs. This is useful when generating multiple identical diagrams to be included in the same HTML doc, so that duplicate IDs do not cause invalid HTML. The salt value is a string that will be appended to IDs in the output.")
|
||||
|
||||
plugins, err := d2plugin.ListPlugins(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -324,6 +326,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
|
|||
DarkThemeID: darkThemeFlag,
|
||||
Scale: scale,
|
||||
NoXMLTag: noXMLTagFlag,
|
||||
Salt: saltFlag,
|
||||
}
|
||||
|
||||
if *watchFlag {
|
||||
|
|
@ -517,7 +520,7 @@ func compile(ctx context.Context, ms *xmain.State, plugins []d2plugin.Plugin, fs
|
|||
plugin, _ := d2plugin.FindPlugin(ctx, plugins, *opts.Layout)
|
||||
|
||||
if animateInterval > 0 {
|
||||
masterID, err := diagram.HashID()
|
||||
masterID, err := diagram.HashID(renderOpts.Salt)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
|
@ -865,7 +868,7 @@ func _render(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts
|
|||
} else if toPNG {
|
||||
scale = go2.Pointer(1.)
|
||||
}
|
||||
svg, err := d2svg.Render(diagram, &d2svg.RenderOpts{
|
||||
renderOpts := &d2svg.RenderOpts{
|
||||
Pad: opts.Pad,
|
||||
Sketch: opts.Sketch,
|
||||
Center: opts.Center,
|
||||
|
|
@ -875,8 +878,10 @@ func _render(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts
|
|||
ThemeOverrides: opts.ThemeOverrides,
|
||||
DarkThemeOverrides: opts.DarkThemeOverrides,
|
||||
NoXMLTag: opts.NoXMLTag,
|
||||
Salt: opts.Salt,
|
||||
Scale: scale,
|
||||
})
|
||||
}
|
||||
svg, err := d2svg.Render(diagram, renderOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -897,12 +902,12 @@ func _render(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts
|
|||
bundleErr = multierr.Combine(bundleErr, bundleErr2)
|
||||
}
|
||||
if forceAppendix && !toPNG {
|
||||
svg = appendix.Append(diagram, ruler, svg)
|
||||
svg = appendix.Append(diagram, renderOpts, ruler, svg)
|
||||
}
|
||||
|
||||
out := svg
|
||||
if toPNG {
|
||||
svg := appendix.Append(diagram, ruler, svg)
|
||||
svg := appendix.Append(diagram, renderOpts, ruler, svg)
|
||||
|
||||
if !bundle {
|
||||
var bundleErr2 error
|
||||
|
|
@ -960,7 +965,7 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opt
|
|||
scale = go2.Pointer(1.)
|
||||
}
|
||||
|
||||
svg, err = d2svg.Render(diagram, &d2svg.RenderOpts{
|
||||
renderOpts := &d2svg.RenderOpts{
|
||||
Pad: opts.Pad,
|
||||
Sketch: opts.Sketch,
|
||||
Center: opts.Center,
|
||||
|
|
@ -969,7 +974,8 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opt
|
|||
DarkThemeID: opts.DarkThemeID,
|
||||
ThemeOverrides: opts.ThemeOverrides,
|
||||
DarkThemeOverrides: opts.DarkThemeOverrides,
|
||||
})
|
||||
}
|
||||
svg, err = d2svg.Render(diagram, renderOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -987,7 +993,7 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opt
|
|||
if bundleErr != nil {
|
||||
return svg, bundleErr
|
||||
}
|
||||
svg = appendix.Append(diagram, ruler, svg)
|
||||
svg = appendix.Append(diagram, renderOpts, ruler, svg)
|
||||
|
||||
pngImg, err := ConvertSVG(ms, page, svg)
|
||||
if err != nil {
|
||||
|
|
@ -1066,7 +1072,7 @@ func renderPPTX(ctx context.Context, ms *xmain.State, presentation *pptx.Present
|
|||
|
||||
var err error
|
||||
|
||||
svg, err = d2svg.Render(diagram, &d2svg.RenderOpts{
|
||||
renderOpts := &d2svg.RenderOpts{
|
||||
Pad: opts.Pad,
|
||||
Sketch: opts.Sketch,
|
||||
Center: opts.Center,
|
||||
|
|
@ -1075,7 +1081,8 @@ func renderPPTX(ctx context.Context, ms *xmain.State, presentation *pptx.Present
|
|||
DarkThemeID: opts.DarkThemeID,
|
||||
ThemeOverrides: opts.ThemeOverrides,
|
||||
DarkThemeOverrides: opts.DarkThemeOverrides,
|
||||
})
|
||||
}
|
||||
svg, err = d2svg.Render(diagram, renderOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1094,7 +1101,7 @@ func renderPPTX(ctx context.Context, ms *xmain.State, presentation *pptx.Present
|
|||
return nil, bundleErr
|
||||
}
|
||||
|
||||
svg = appendix.Append(diagram, ruler, svg)
|
||||
svg = appendix.Append(diagram, renderOpts, ruler, svg)
|
||||
|
||||
pngImg, err := ConvertSVG(ms, page, svg)
|
||||
if err != nil {
|
||||
|
|
@ -1312,7 +1319,7 @@ func renderPNGsForGIF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plug
|
|||
} else {
|
||||
scale = go2.Pointer(1.)
|
||||
}
|
||||
svg, err = d2svg.Render(diagram, &d2svg.RenderOpts{
|
||||
renderOpts := &d2svg.RenderOpts{
|
||||
Pad: opts.Pad,
|
||||
Sketch: opts.Sketch,
|
||||
Center: opts.Center,
|
||||
|
|
@ -1321,7 +1328,8 @@ func renderPNGsForGIF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plug
|
|||
DarkThemeID: opts.DarkThemeID,
|
||||
ThemeOverrides: opts.ThemeOverrides,
|
||||
DarkThemeOverrides: opts.DarkThemeOverrides,
|
||||
})
|
||||
}
|
||||
svg, err = d2svg.Render(diagram, renderOpts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
@ -1340,7 +1348,7 @@ func renderPNGsForGIF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plug
|
|||
return nil, nil, bundleErr
|
||||
}
|
||||
|
||||
svg = appendix.Append(diagram, ruler, svg)
|
||||
svg = appendix.Append(diagram, renderOpts, ruler, svg)
|
||||
|
||||
pngImg, err := ConvertSVG(ms, page, svg)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -303,10 +303,10 @@ a -> b
|
|||
db, err := compile(ctx, bString)
|
||||
assert.JSON(t, nil, err)
|
||||
|
||||
hashA, err := da.HashID()
|
||||
hashA, err := da.HashID(nil)
|
||||
assert.JSON(t, nil, err)
|
||||
|
||||
hashB, err := db.HashID()
|
||||
hashB, err := db.HashID(nil)
|
||||
assert.JSON(t, nil, err)
|
||||
|
||||
assert.NotEqual(t, hashA, hashB)
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ func Wrap(rootDiagram *d2target.Diagram, svgs [][]byte, renderOpts d2svg.RenderO
|
|||
svgsStr += string(svg) + " "
|
||||
}
|
||||
|
||||
diagramHash, err := rootDiagram.HashID()
|
||||
diagramHash, err := rootDiagram.HashID(renderOpts.Salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ func FindViewboxSlice(svg []byte) []string {
|
|||
return strings.Split(viewboxRaw, " ")
|
||||
}
|
||||
|
||||
func Append(diagram *d2target.Diagram, ruler *textmeasure.Ruler, in []byte) []byte {
|
||||
func Append(diagram *d2target.Diagram, renderOpts *d2svg.RenderOpts, ruler *textmeasure.Ruler, in []byte) []byte {
|
||||
svg := string(in)
|
||||
|
||||
appendix, w, h := generateAppendix(diagram, ruler, svg)
|
||||
|
|
@ -177,7 +177,11 @@ func Append(diagram *d2target.Diagram, ruler *textmeasure.Ruler, in []byte) []by
|
|||
return renderOrder[i].shape.Level < renderOrder[j].shape.Level
|
||||
})
|
||||
|
||||
diagramHash, err := diagram.HashID()
|
||||
var salt *string
|
||||
if renderOpts != nil {
|
||||
salt = renderOpts.Salt
|
||||
}
|
||||
diagramHash, err := diagram.HashID(salt)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package appendix_test
|
|||
import (
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"io/ioutil"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
|
@ -172,11 +171,11 @@ func run(t *testing.T, tc testCase) {
|
|||
|
||||
svgBytes, err := d2svg.Render(diagram, renderOpts)
|
||||
assert.Success(t, err)
|
||||
svgBytes = appendix.Append(diagram, ruler, svgBytes)
|
||||
svgBytes = appendix.Append(diagram, nil, ruler, svgBytes)
|
||||
|
||||
err = os.MkdirAll(dataPath, 0755)
|
||||
assert.Success(t, err)
|
||||
err = ioutil.WriteFile(pathGotSVG, svgBytes, 0600)
|
||||
err = os.WriteFile(pathGotSVG, svgBytes, 0600)
|
||||
assert.Success(t, err)
|
||||
defer os.Remove(pathGotSVG)
|
||||
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ type RenderOpts struct {
|
|||
// Currently, that's when multi-boards are collapsed
|
||||
MasterID string
|
||||
NoXMLTag *bool
|
||||
Salt *string
|
||||
}
|
||||
|
||||
func dimensions(diagram *d2target.Diagram, pad int) (left, top, width, height int) {
|
||||
|
|
@ -1908,7 +1909,7 @@ func Render(diagram *d2target.Diagram, opts *RenderOpts) ([]byte, error) {
|
|||
}
|
||||
|
||||
// Apply hash on IDs for targeting, to be specific for this diagram
|
||||
diagramHash, err := diagram.HashID()
|
||||
diagramHash, err := diagram.HashID(opts.Salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -223,13 +223,16 @@ func (diagram Diagram) HasShape(condition func(Shape) bool) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (diagram Diagram) HashID() (string, error) {
|
||||
func (diagram Diagram) HashID(salt *string) (string, error) {
|
||||
bytes, err := diagram.Bytes()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
h := fnv.New32a()
|
||||
h.Write(bytes)
|
||||
if salt != nil {
|
||||
h.Write([]byte(*salt))
|
||||
}
|
||||
// CSS names can't start with numbers, so prepend a little something
|
||||
return fmt.Sprintf("d2-%d", h.Sum32()), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ func TestConfigHash(t *testing.T) {
|
|||
}
|
||||
ctx := log.WithDefault(context.Background())
|
||||
diagram, _, _ := d2lib.Compile(ctx, "x -> y", compileOpts, renderOpts)
|
||||
hash1, err = diagram.HashID()
|
||||
hash1, err = diagram.HashID(nil)
|
||||
assert.Success(t, err)
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +57,52 @@ func TestConfigHash(t *testing.T) {
|
|||
}
|
||||
ctx := log.WithDefault(context.Background())
|
||||
diagram, _, _ := d2lib.Compile(ctx, "x -> y", compileOpts, renderOpts)
|
||||
hash2, err = diagram.HashID()
|
||||
hash2, err = diagram.HashID(nil)
|
||||
assert.Success(t, err)
|
||||
}
|
||||
|
||||
assert.NotEqual(t, hash1, hash2)
|
||||
}
|
||||
|
||||
func TestHashSalt(t *testing.T) {
|
||||
var hash1, hash2 string
|
||||
var err error
|
||||
|
||||
{
|
||||
ruler, _ := textmeasure.NewRuler()
|
||||
layoutResolver := func(engine string) (d2graph.LayoutGraph, error) {
|
||||
return d2dagrelayout.DefaultLayout, nil
|
||||
}
|
||||
renderOpts := &d2svg.RenderOpts{
|
||||
Pad: go2.Pointer(int64(5)),
|
||||
ThemeID: &d2themescatalog.GrapeSoda.ID,
|
||||
}
|
||||
compileOpts := &d2lib.CompileOptions{
|
||||
LayoutResolver: layoutResolver,
|
||||
Ruler: ruler,
|
||||
}
|
||||
ctx := log.WithDefault(context.Background())
|
||||
diagram, _, _ := d2lib.Compile(ctx, "x -> y", compileOpts, renderOpts)
|
||||
hash1, err = diagram.HashID(nil)
|
||||
assert.Success(t, err)
|
||||
}
|
||||
|
||||
{
|
||||
ruler, _ := textmeasure.NewRuler()
|
||||
layoutResolver := func(engine string) (d2graph.LayoutGraph, error) {
|
||||
return d2dagrelayout.DefaultLayout, nil
|
||||
}
|
||||
renderOpts := &d2svg.RenderOpts{
|
||||
Pad: go2.Pointer(int64(5)),
|
||||
ThemeID: &d2themescatalog.GrapeSoda.ID,
|
||||
}
|
||||
compileOpts := &d2lib.CompileOptions{
|
||||
LayoutResolver: layoutResolver,
|
||||
Ruler: ruler,
|
||||
}
|
||||
ctx := log.WithDefault(context.Background())
|
||||
diagram, _, _ := d2lib.Compile(ctx, "x -> y", compileOpts, renderOpts)
|
||||
hash2, err = diagram.HashID(go2.Pointer("asdf"))
|
||||
assert.Success(t, err)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ func run(t *testing.T, tc testCase) {
|
|||
pathGotSVG := filepath.Join(dataPath, "sketch.got.svg")
|
||||
|
||||
if len(diagram.Layers) > 0 || len(diagram.Scenarios) > 0 || len(diagram.Steps) > 0 {
|
||||
masterID, err := diagram.HashID()
|
||||
masterID, err := diagram.HashID(nil)
|
||||
assert.Success(t, err)
|
||||
renderOpts.MasterID = masterID
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue