cli: resolve local icon paths relative to d2 file

This commit is contained in:
Alexander Wang 2024-04-25 21:24:57 -06:00
parent 939a684819
commit 15b4717323
No known key found for this signature in database
GPG key ID: BE3937D0D52D8927
3 changed files with 41 additions and 32 deletions

View file

@ -1,3 +1,7 @@
Hotfix for 0.6.4 breaking plugins, along with 2 other compiler bugfixes. #### Features 🚀
#### Improvements 🧹
#### Bugfixes ⛑️ #### Bugfixes ⛑️
- Local relative icons are relative to the d2 file instead of CLI invoke path [#1924](https://github.com/terrastruct/d2/pull/1924)

View file

@ -529,7 +529,7 @@ func compile(ctx context.Context, ms *xmain.State, plugins []d2plugin.Plugin, fs
ext := getExportExtension(outputPath) ext := getExportExtension(outputPath)
switch ext { switch ext {
case GIF: case GIF:
svg, pngs, err := renderPNGsForGIF(ctx, ms, plugin, renderOpts, ruler, page, diagram) svg, pngs, err := renderPNGsForGIF(ctx, ms, plugin, renderOpts, ruler, page, inputPath, diagram)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
@ -553,7 +553,7 @@ func compile(ctx context.Context, ms *xmain.State, plugins []d2plugin.Plugin, fs
path := []pdf.BoardTitle{ path := []pdf.BoardTitle{
{Name: diagram.Root.Label, BoardID: "root"}, {Name: diagram.Root.Label, BoardID: "root"},
} }
pdf, err := renderPDF(ctx, ms, plugin, renderOpts, outputPath, page, ruler, diagram, nil, path, pageMap, diagram.Root.Label != "") pdf, err := renderPDF(ctx, ms, plugin, renderOpts, inputPath, outputPath, page, ruler, diagram, nil, path, pageMap, diagram.Root.Label != "")
if err != nil { if err != nil {
return pdf, false, err return pdf, false, err
} }
@ -574,7 +574,7 @@ func compile(ctx context.Context, ms *xmain.State, plugins []d2plugin.Plugin, fs
path := []pptx.BoardTitle{ path := []pptx.BoardTitle{
{Name: "root", BoardID: "root", LinkToSlide: boardIdToIndex["root"] + 1}, {Name: "root", BoardID: "root", LinkToSlide: boardIdToIndex["root"] + 1},
} }
svg, err := renderPPTX(ctx, ms, p, plugin, renderOpts, ruler, outputPath, page, diagram, path, boardIdToIndex) svg, err := renderPPTX(ctx, ms, p, plugin, renderOpts, ruler, inputPath, outputPath, page, diagram, path, boardIdToIndex)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
@ -808,7 +808,7 @@ func render(ctx context.Context, ms *xmain.State, compileDur time.Duration, plug
if !diagram.IsFolderOnly { if !diagram.IsFolderOnly {
start := time.Now() start := time.Now()
out, err := _render(ctx, ms, plugin, opts, boardOutputPath, bundle, forceAppendix, page, ruler, diagram) out, err := _render(ctx, ms, plugin, opts, inputPath, boardOutputPath, bundle, forceAppendix, page, ruler, diagram)
if err != nil { if err != nil {
return boards, err return boards, err
} }
@ -824,7 +824,7 @@ func render(ctx context.Context, ms *xmain.State, compileDur time.Duration, plug
func renderSingle(ctx context.Context, ms *xmain.State, compileDur time.Duration, plugin d2plugin.Plugin, opts d2svg.RenderOpts, inputPath, outputPath string, bundle, forceAppendix bool, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram) ([][]byte, error) { func renderSingle(ctx context.Context, ms *xmain.State, compileDur time.Duration, plugin d2plugin.Plugin, opts d2svg.RenderOpts, inputPath, outputPath string, bundle, forceAppendix bool, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram) ([][]byte, error) {
start := time.Now() start := time.Now()
out, err := _render(ctx, ms, plugin, opts, outputPath, bundle, forceAppendix, page, ruler, diagram) out, err := _render(ctx, ms, plugin, opts, inputPath, outputPath, bundle, forceAppendix, page, ruler, diagram)
if err != nil { if err != nil {
return [][]byte{}, err return [][]byte{}, err
} }
@ -835,7 +835,7 @@ func renderSingle(ctx context.Context, ms *xmain.State, compileDur time.Duration
return [][]byte{out}, nil return [][]byte{out}, nil
} }
func _render(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts d2svg.RenderOpts, outputPath string, bundle, forceAppendix bool, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram) ([]byte, error) { func _render(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts d2svg.RenderOpts, inputPath, outputPath string, bundle, forceAppendix bool, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram) ([]byte, error) {
toPNG := getExportExtension(outputPath) == PNG toPNG := getExportExtension(outputPath) == PNG
var scale *float64 var scale *float64
if opts.Scale != nil { if opts.Scale != nil {
@ -865,7 +865,7 @@ func _render(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts
cacheImages := ms.Env.Getenv("IMG_CACHE") == "1" cacheImages := ms.Env.Getenv("IMG_CACHE") == "1"
l := simplelog.FromCmdLog(ms.Log) l := simplelog.FromCmdLog(ms.Log)
svg, bundleErr := imgbundler.BundleLocal(ctx, l, svg, cacheImages) svg, bundleErr := imgbundler.BundleLocal(ctx, l, inputPath, svg, cacheImages)
if bundle { if bundle {
var bundleErr2 error var bundleErr2 error
svg, bundleErr2 = imgbundler.BundleRemote(ctx, l, svg, cacheImages) svg, bundleErr2 = imgbundler.BundleRemote(ctx, l, svg, cacheImages)
@ -915,7 +915,7 @@ func _render(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts
return svg, nil return svg, nil
} }
func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts d2svg.RenderOpts, outputPath string, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram, doc *pdf.GoFPDF, boardPath []pdf.BoardTitle, pageMap map[string]int, includeNav bool) (svg []byte, err error) { func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts d2svg.RenderOpts, inputPath, outputPath string, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram, doc *pdf.GoFPDF, boardPath []pdf.BoardTitle, pageMap map[string]int, includeNav bool) (svg []byte, err error) {
var isRoot bool var isRoot bool
if doc == nil { if doc == nil {
doc = pdf.Init() doc = pdf.Init()
@ -953,7 +953,7 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opt
cacheImages := ms.Env.Getenv("IMG_CACHE") == "1" cacheImages := ms.Env.Getenv("IMG_CACHE") == "1"
l := simplelog.FromCmdLog(ms.Log) l := simplelog.FromCmdLog(ms.Log)
svg, bundleErr := imgbundler.BundleLocal(ctx, l, svg, cacheImages) svg, bundleErr := imgbundler.BundleLocal(ctx, l, inputPath, svg, cacheImages)
svg, bundleErr2 := imgbundler.BundleRemote(ctx, l, svg, cacheImages) svg, bundleErr2 := imgbundler.BundleRemote(ctx, l, svg, cacheImages)
bundleErr = multierr.Combine(bundleErr, bundleErr2) bundleErr = multierr.Combine(bundleErr, bundleErr2)
if bundleErr != nil { if bundleErr != nil {
@ -986,7 +986,7 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opt
Name: dl.Root.Label, Name: dl.Root.Label,
BoardID: strings.Join([]string{boardPath[len(boardPath)-1].BoardID, LAYERS, dl.Name}, "."), BoardID: strings.Join([]string{boardPath[len(boardPath)-1].BoardID, LAYERS, dl.Name}, "."),
}) })
_, err := renderPDF(ctx, ms, plugin, opts, "", page, ruler, dl, doc, path, pageMap, includeNav) _, err := renderPDF(ctx, ms, plugin, opts, inputPath, "", page, ruler, dl, doc, path, pageMap, includeNav)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -996,7 +996,7 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opt
Name: dl.Root.Label, Name: dl.Root.Label,
BoardID: strings.Join([]string{boardPath[len(boardPath)-1].BoardID, SCENARIOS, dl.Name}, "."), BoardID: strings.Join([]string{boardPath[len(boardPath)-1].BoardID, SCENARIOS, dl.Name}, "."),
}) })
_, err := renderPDF(ctx, ms, plugin, opts, "", page, ruler, dl, doc, path, pageMap, includeNav) _, err := renderPDF(ctx, ms, plugin, opts, inputPath, "", page, ruler, dl, doc, path, pageMap, includeNav)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1006,7 +1006,7 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opt
Name: dl.Root.Label, Name: dl.Root.Label,
BoardID: strings.Join([]string{boardPath[len(boardPath)-1].BoardID, STEPS, dl.Name}, "."), BoardID: strings.Join([]string{boardPath[len(boardPath)-1].BoardID, STEPS, dl.Name}, "."),
}) })
_, err := renderPDF(ctx, ms, plugin, opts, "", page, ruler, dl, doc, path, pageMap, includeNav) _, err := renderPDF(ctx, ms, plugin, opts, inputPath, "", page, ruler, dl, doc, path, pageMap, includeNav)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1022,7 +1022,7 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opt
return svg, nil return svg, nil
} }
func renderPPTX(ctx context.Context, ms *xmain.State, presentation *pptx.Presentation, plugin d2plugin.Plugin, opts d2svg.RenderOpts, ruler *textmeasure.Ruler, outputPath string, page playwright.Page, diagram *d2target.Diagram, boardPath []pptx.BoardTitle, boardIDToIndex map[string]int) ([]byte, error) { func renderPPTX(ctx context.Context, ms *xmain.State, presentation *pptx.Presentation, plugin d2plugin.Plugin, opts d2svg.RenderOpts, ruler *textmeasure.Ruler, inputPath, outputPath string, page playwright.Page, diagram *d2target.Diagram, boardPath []pptx.BoardTitle, boardIDToIndex map[string]int) ([]byte, error) {
var svg []byte var svg []byte
if !diagram.IsFolderOnly { if !diagram.IsFolderOnly {
// gofpdf will print the png img with a slight filter // gofpdf will print the png img with a slight filter
@ -1055,7 +1055,7 @@ func renderPPTX(ctx context.Context, ms *xmain.State, presentation *pptx.Present
cacheImages := ms.Env.Getenv("IMG_CACHE") == "1" cacheImages := ms.Env.Getenv("IMG_CACHE") == "1"
l := simplelog.FromCmdLog(ms.Log) l := simplelog.FromCmdLog(ms.Log)
svg, bundleErr := imgbundler.BundleLocal(ctx, l, svg, cacheImages) svg, bundleErr := imgbundler.BundleLocal(ctx, l, inputPath, svg, cacheImages)
svg, bundleErr2 := imgbundler.BundleRemote(ctx, l, svg, cacheImages) svg, bundleErr2 := imgbundler.BundleRemote(ctx, l, svg, cacheImages)
bundleErr = multierr.Combine(bundleErr, bundleErr2) bundleErr = multierr.Combine(bundleErr, bundleErr2)
if bundleErr != nil { if bundleErr != nil {
@ -1120,7 +1120,7 @@ func renderPPTX(ctx context.Context, ms *xmain.State, presentation *pptx.Present
BoardID: boardID, BoardID: boardID,
LinkToSlide: boardIDToIndex[boardID] + 1, LinkToSlide: boardIDToIndex[boardID] + 1,
}) })
_, err := renderPPTX(ctx, ms, presentation, plugin, opts, ruler, "", page, dl, path, boardIDToIndex) _, err := renderPPTX(ctx, ms, presentation, plugin, opts, ruler, inputPath, "", page, dl, path, boardIDToIndex)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1132,7 +1132,7 @@ func renderPPTX(ctx context.Context, ms *xmain.State, presentation *pptx.Present
BoardID: boardID, BoardID: boardID,
LinkToSlide: boardIDToIndex[boardID] + 1, LinkToSlide: boardIDToIndex[boardID] + 1,
}) })
_, err := renderPPTX(ctx, ms, presentation, plugin, opts, ruler, "", page, dl, path, boardIDToIndex) _, err := renderPPTX(ctx, ms, presentation, plugin, opts, ruler, inputPath, "", page, dl, path, boardIDToIndex)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1144,7 +1144,7 @@ func renderPPTX(ctx context.Context, ms *xmain.State, presentation *pptx.Present
BoardID: boardID, BoardID: boardID,
LinkToSlide: boardIDToIndex[boardID] + 1, LinkToSlide: boardIDToIndex[boardID] + 1,
}) })
_, err := renderPPTX(ctx, ms, presentation, plugin, opts, ruler, "", page, dl, path, boardIDToIndex) _, err := renderPPTX(ctx, ms, presentation, plugin, opts, ruler, inputPath, "", page, dl, path, boardIDToIndex)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1276,7 +1276,7 @@ func buildBoardIDToIndex(diagram *d2target.Diagram, dictionary map[string]int, p
return dictionary return dictionary
} }
func renderPNGsForGIF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts d2svg.RenderOpts, ruler *textmeasure.Ruler, page playwright.Page, diagram *d2target.Diagram) (svg []byte, pngs [][]byte, err error) { func renderPNGsForGIF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, opts d2svg.RenderOpts, ruler *textmeasure.Ruler, page playwright.Page, inputPath string, diagram *d2target.Diagram) (svg []byte, pngs [][]byte, err error) {
if !diagram.IsFolderOnly { if !diagram.IsFolderOnly {
var scale *float64 var scale *float64
@ -1302,7 +1302,7 @@ func renderPNGsForGIF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plug
cacheImages := ms.Env.Getenv("IMG_CACHE") == "1" cacheImages := ms.Env.Getenv("IMG_CACHE") == "1"
l := simplelog.FromCmdLog(ms.Log) l := simplelog.FromCmdLog(ms.Log)
svg, bundleErr := imgbundler.BundleLocal(ctx, l, svg, cacheImages) svg, bundleErr := imgbundler.BundleLocal(ctx, l, inputPath, svg, cacheImages)
svg, bundleErr2 := imgbundler.BundleRemote(ctx, l, svg, cacheImages) svg, bundleErr2 := imgbundler.BundleRemote(ctx, l, svg, cacheImages)
bundleErr = multierr.Combine(bundleErr, bundleErr2) bundleErr = multierr.Combine(bundleErr, bundleErr2)
if bundleErr != nil { if bundleErr != nil {
@ -1319,21 +1319,21 @@ func renderPNGsForGIF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plug
} }
for _, dl := range diagram.Layers { for _, dl := range diagram.Layers {
_, layerPNGs, err := renderPNGsForGIF(ctx, ms, plugin, opts, ruler, page, dl) _, layerPNGs, err := renderPNGsForGIF(ctx, ms, plugin, opts, ruler, page, inputPath, dl)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
pngs = append(pngs, layerPNGs...) pngs = append(pngs, layerPNGs...)
} }
for _, dl := range diagram.Scenarios { for _, dl := range diagram.Scenarios {
_, scenarioPNGs, err := renderPNGsForGIF(ctx, ms, plugin, opts, ruler, page, dl) _, scenarioPNGs, err := renderPNGsForGIF(ctx, ms, plugin, opts, ruler, page, inputPath, dl)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
pngs = append(pngs, scenarioPNGs...) pngs = append(pngs, scenarioPNGs...)
} }
for _, dl := range diagram.Steps { for _, dl := range diagram.Steps {
_, stepsPNGs, err := renderPNGsForGIF(ctx, ms, plugin, opts, ruler, page, dl) _, stepsPNGs, err := renderPNGsForGIF(ctx, ms, plugin, opts, ruler, page, inputPath, dl)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

View file

@ -12,6 +12,7 @@ import (
"net/url" "net/url"
"os" "os"
"path" "path"
"path/filepath"
"regexp" "regexp"
"strings" "strings"
"sync" "sync"
@ -29,12 +30,12 @@ const maxImageSize int64 = 1 << 25 // 33_554_432
var imageRegex = regexp.MustCompile(`<image href="([^"]+)"`) var imageRegex = regexp.MustCompile(`<image href="([^"]+)"`)
func BundleLocal(ctx context.Context, l simplelog.Logger, in []byte, cacheImages bool) ([]byte, error) { func BundleLocal(ctx context.Context, l simplelog.Logger, inputPath string, in []byte, cacheImages bool) ([]byte, error) {
return bundle(ctx, l, in, false, cacheImages) return bundle(ctx, l, inputPath, in, false, cacheImages)
} }
func BundleRemote(ctx context.Context, l simplelog.Logger, in []byte, cacheImages bool) ([]byte, error) { func BundleRemote(ctx context.Context, l simplelog.Logger, in []byte, cacheImages bool) ([]byte, error) {
return bundle(ctx, l, in, true, cacheImages) return bundle(ctx, l, "", in, true, cacheImages)
} }
type repl struct { type repl struct {
@ -42,7 +43,7 @@ type repl struct {
to []byte to []byte
} }
func bundle(ctx context.Context, l simplelog.Logger, svg []byte, isRemote, cacheImages bool) (_ []byte, err error) { func bundle(ctx context.Context, l simplelog.Logger, inputPath string, svg []byte, isRemote, cacheImages bool) (_ []byte, err error) {
if isRemote { if isRemote {
defer xdefer.Errorf(&err, "failed to bundle remote images") defer xdefer.Errorf(&err, "failed to bundle remote images")
} else { } else {
@ -54,7 +55,7 @@ func bundle(ctx context.Context, l simplelog.Logger, svg []byte, isRemote, cache
ctx, cancel := context.WithTimeout(ctx, time.Minute*5) ctx, cancel := context.WithTimeout(ctx, time.Minute*5)
defer cancel() defer cancel()
return runWorkers(ctx, l, svg, imgs, isRemote, cacheImages) return runWorkers(ctx, l, inputPath, svg, imgs, isRemote, cacheImages)
} }
// filterImageElements finds all unique image elements in imgs that are // filterImageElements finds all unique image elements in imgs that are
@ -84,7 +85,7 @@ func filterImageElements(imgs [][][]byte, isRemote bool) [][][]byte {
return imgs2 return imgs2
} }
func runWorkers(ctx context.Context, l simplelog.Logger, svg []byte, imgs [][][]byte, isRemote, cacheImages bool) (_ []byte, err error) { func runWorkers(ctx context.Context, l simplelog.Logger, inputPath string, svg []byte, imgs [][][]byte, isRemote, cacheImages bool) (_ []byte, err error) {
var wg sync.WaitGroup var wg sync.WaitGroup
replc := make(chan repl) replc := make(chan repl)
@ -111,7 +112,7 @@ func runWorkers(ctx context.Context, l simplelog.Logger, svg []byte, imgs [][][]
<-sema <-sema
}() }()
bundledImage, err := worker(ctx, l, img[1], isRemote, cacheImages) bundledImage, err := worker(ctx, l, inputPath, img[1], isRemote, cacheImages)
if err != nil { if err != nil {
l.Error(fmt.Sprintf("failed to bundle %s: %v", img[1], err)) l.Error(fmt.Sprintf("failed to bundle %s: %v", img[1], err))
errhrefsMu.Lock() errhrefsMu.Lock()
@ -150,7 +151,7 @@ func runWorkers(ctx context.Context, l simplelog.Logger, svg []byte, imgs [][][]
} }
} }
func worker(ctx context.Context, l simplelog.Logger, href []byte, isRemote, cacheImages bool) ([]byte, error) { func worker(ctx context.Context, l simplelog.Logger, inputPath string, href []byte, isRemote, cacheImages bool) ([]byte, error) {
if cacheImages { if cacheImages {
if hit, ok := imgCache.Load(string(href)); ok { if hit, ok := imgCache.Load(string(href)); ok {
return hit.([]byte), nil return hit.([]byte), nil
@ -164,7 +165,11 @@ func worker(ctx context.Context, l simplelog.Logger, href []byte, isRemote, cach
buf, mimeType, err = httpGet(ctx, html.UnescapeString(string(href))) buf, mimeType, err = httpGet(ctx, html.UnescapeString(string(href)))
} else { } else {
l.Debug(fmt.Sprintf("reading %s from disk", string(href))) l.Debug(fmt.Sprintf("reading %s from disk", string(href)))
buf, err = os.ReadFile(html.UnescapeString(string(href))) path := html.UnescapeString(string(href))
if inputPath != "-" && !filepath.IsAbs(path) {
path = filepath.Join(filepath.Dir(inputPath), path)
}
buf, err = os.ReadFile(path)
} }
if err != nil { if err != nil {
return nil, err return nil, err