feat(cli): add format flag to support PNG output to stdout
This commit is contained in:
parent
1ea36d5d39
commit
b15161f231
4 changed files with 59 additions and 18 deletions
|
|
@ -1,6 +1,7 @@
|
|||
package d2cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
|
|
@ -14,6 +15,24 @@ const SVG exportExtension = ".svg"
|
|||
|
||||
var SUPPORTED_EXTENSIONS = []exportExtension{SVG, PNG, PDF, PPTX, GIF}
|
||||
|
||||
func getOutputFormat(formatFlag *string, outputPath string) (exportExtension, error) {
|
||||
var formatMap = map[string]exportExtension{
|
||||
"png": PNG,
|
||||
"svg": SVG,
|
||||
"pdf": PDF,
|
||||
"pptx": PPTX,
|
||||
"gif": GIF,
|
||||
}
|
||||
|
||||
if *formatFlag != "" {
|
||||
if format, ok := formatMap[*formatFlag]; ok {
|
||||
return format, nil
|
||||
}
|
||||
return "", fmt.Errorf("unsupported format: %s", *formatFlag)
|
||||
}
|
||||
return getExportExtension(outputPath), nil
|
||||
}
|
||||
|
||||
func getExportExtension(outputPath string) exportExtension {
|
||||
ext := filepath.Ext(outputPath)
|
||||
for _, kext := range SUPPORTED_EXTENSIONS {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
func TestOutputFormat(t *testing.T) {
|
||||
type testCase struct {
|
||||
formatFlag string
|
||||
outputPath string
|
||||
extension exportExtension
|
||||
supportsDarkTheme bool
|
||||
|
|
@ -41,6 +42,15 @@ func TestOutputFormat(t *testing.T) {
|
|||
requiresAnimationInterval: false,
|
||||
requiresPngRender: false,
|
||||
},
|
||||
{
|
||||
formatFlag: "png",
|
||||
outputPath: "-",
|
||||
extension: PNG,
|
||||
supportsDarkTheme: false,
|
||||
supportsAnimation: false,
|
||||
requiresAnimationInterval: false,
|
||||
requiresPngRender: true,
|
||||
},
|
||||
{
|
||||
outputPath: "/out.png",
|
||||
extension: PNG,
|
||||
|
|
@ -78,7 +88,8 @@ func TestOutputFormat(t *testing.T) {
|
|||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.outputPath, func(t *testing.T) {
|
||||
extension := getExportExtension(tc.outputPath)
|
||||
extension, err := getOutputFormat(&tc.formatFlag, tc.outputPath)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.extension, extension)
|
||||
assert.Equal(t, tc.supportsAnimation, extension.supportsAnimation())
|
||||
assert.Equal(t, tc.supportsDarkTheme, extension.supportsDarkTheme())
|
||||
|
|
|
|||
|
|
@ -103,6 +103,11 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
formatFlag := ms.Opts.String("", "format", "f", "", "stdout output format (svg, png)")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
browserFlag := ms.Opts.String("BROWSER", "browser", "", "", "browser executable that watch opens. Setting to 0 opens no browser.")
|
||||
centerFlag, err := ms.Opts.Bool("D2_CENTER", "center", "c", false, "center the SVG in the containing viewbox, such as your browser screen")
|
||||
if err != nil {
|
||||
|
|
@ -213,7 +218,12 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
|
|||
if filepath.Ext(outputPath) == ".ppt" {
|
||||
return xmain.UsageErrorf("D2 does not support ppt exports, did you mean \"pptx\"?")
|
||||
}
|
||||
outputFormat := getExportExtension(outputPath)
|
||||
|
||||
outputFormat, err := getOutputFormat(formatFlag, outputPath)
|
||||
if err != nil {
|
||||
return xmain.UsageErrorf("%v", err)
|
||||
}
|
||||
|
||||
if outputPath != "-" {
|
||||
outputPath = ms.AbsPath(outputPath)
|
||||
if *animateIntervalFlag > 0 && !outputFormat.supportsAnimation() {
|
||||
|
|
@ -325,6 +335,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
|
|||
forceAppendix: *forceAppendixFlag,
|
||||
pw: pw,
|
||||
fontFamily: fontFamily,
|
||||
outputFormat: outputFormat,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -355,7 +366,7 @@ func Run(ctx context.Context, ms *xmain.State) (err error) {
|
|||
ctx, cancel := timelib.WithTimeout(ctx, time.Minute*2)
|
||||
defer cancel()
|
||||
|
||||
_, written, err := compile(ctx, ms, plugins, nil, layoutFlag, renderOpts, fontFamily, *animateIntervalFlag, inputPath, outputPath, boardPath, noChildren, *bundleFlag, *forceAppendixFlag, pw.Page)
|
||||
_, written, err := compile(ctx, ms, plugins, nil, layoutFlag, renderOpts, fontFamily, *animateIntervalFlag, inputPath, outputPath, boardPath, noChildren, *bundleFlag, *forceAppendixFlag, pw.Page, outputFormat)
|
||||
if err != nil {
|
||||
if written {
|
||||
return fmt.Errorf("failed to fully compile (partial render written) %s: %w", ms.HumanPath(inputPath), err)
|
||||
|
|
@ -430,7 +441,7 @@ func RouterResolver(ctx context.Context, ms *xmain.State, plugins []d2plugin.Plu
|
|||
}
|
||||
}
|
||||
|
||||
func compile(ctx context.Context, ms *xmain.State, plugins []d2plugin.Plugin, fs fs.FS, layout *string, renderOpts d2svg.RenderOpts, fontFamily *d2fonts.FontFamily, animateInterval int64, inputPath, outputPath string, boardPath []string, noChildren, bundle, forceAppendix bool, page playwright.Page) (_ []byte, written bool, _ error) {
|
||||
func compile(ctx context.Context, ms *xmain.State, plugins []d2plugin.Plugin, fs fs.FS, layout *string, renderOpts d2svg.RenderOpts, fontFamily *d2fonts.FontFamily, animateInterval int64, inputPath, outputPath string, boardPath []string, noChildren, bundle, forceAppendix bool, page playwright.Page, ext exportExtension) (_ []byte, written bool, _ error) {
|
||||
start := time.Now()
|
||||
input, err := ms.ReadPath(inputPath)
|
||||
if err != nil {
|
||||
|
|
@ -522,7 +533,6 @@ func compile(ctx context.Context, ms *xmain.State, plugins []d2plugin.Plugin, fs
|
|||
return nil, false, err
|
||||
}
|
||||
|
||||
ext := getExportExtension(outputPath)
|
||||
switch ext {
|
||||
case GIF:
|
||||
svg, pngs, err := renderPNGsForGIF(ctx, ms, plugin, renderOpts, ruler, page, inputPath, diagram)
|
||||
|
|
@ -598,9 +608,9 @@ func compile(ctx context.Context, ms *xmain.State, plugins []d2plugin.Plugin, fs
|
|||
var boards [][]byte
|
||||
var err error
|
||||
if noChildren {
|
||||
boards, err = renderSingle(ctx, ms, compileDur, plugin, renderOpts, inputPath, outputPath, bundle, forceAppendix, page, ruler, diagram)
|
||||
boards, err = renderSingle(ctx, ms, compileDur, plugin, renderOpts, inputPath, outputPath, bundle, forceAppendix, page, ruler, diagram, ext)
|
||||
} else {
|
||||
boards, err = render(ctx, ms, compileDur, plugin, renderOpts, inputPath, outputPath, bundle, forceAppendix, page, ruler, diagram)
|
||||
boards, err = render(ctx, ms, compileDur, plugin, renderOpts, inputPath, outputPath, bundle, forceAppendix, page, ruler, diagram, ext)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
|
|
@ -739,7 +749,7 @@ func relink(currDiagramPath string, d *d2target.Diagram, linkToOutput map[string
|
|||
return nil
|
||||
}
|
||||
|
||||
func render(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 render(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, ext exportExtension) ([][]byte, error) {
|
||||
if diagram.Name != "" {
|
||||
ext := filepath.Ext(outputPath)
|
||||
outputPath = strings.TrimSuffix(outputPath, ext)
|
||||
|
|
@ -785,21 +795,21 @@ func render(ctx context.Context, ms *xmain.State, compileDur time.Duration, plug
|
|||
|
||||
var boards [][]byte
|
||||
for _, dl := range diagram.Layers {
|
||||
childrenBoards, err := render(ctx, ms, compileDur, plugin, opts, inputPath, layersOutputPath, bundle, forceAppendix, page, ruler, dl)
|
||||
childrenBoards, err := render(ctx, ms, compileDur, plugin, opts, inputPath, layersOutputPath, bundle, forceAppendix, page, ruler, dl, ext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
boards = append(boards, childrenBoards...)
|
||||
}
|
||||
for _, dl := range diagram.Scenarios {
|
||||
childrenBoards, err := render(ctx, ms, compileDur, plugin, opts, inputPath, scenariosOutputPath, bundle, forceAppendix, page, ruler, dl)
|
||||
childrenBoards, err := render(ctx, ms, compileDur, plugin, opts, inputPath, scenariosOutputPath, bundle, forceAppendix, page, ruler, dl, ext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
boards = append(boards, childrenBoards...)
|
||||
}
|
||||
for _, dl := range diagram.Steps {
|
||||
childrenBoards, err := render(ctx, ms, compileDur, plugin, opts, inputPath, stepsOutputPath, bundle, forceAppendix, page, ruler, dl)
|
||||
childrenBoards, err := render(ctx, ms, compileDur, plugin, opts, inputPath, stepsOutputPath, bundle, forceAppendix, page, ruler, dl, ext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -808,7 +818,7 @@ func render(ctx context.Context, ms *xmain.State, compileDur time.Duration, plug
|
|||
|
||||
if !diagram.IsFolderOnly {
|
||||
start := time.Now()
|
||||
out, err := _render(ctx, ms, plugin, opts, inputPath, boardOutputPath, bundle, forceAppendix, page, ruler, diagram)
|
||||
out, err := _render(ctx, ms, plugin, opts, inputPath, boardOutputPath, bundle, forceAppendix, page, ruler, diagram, ext)
|
||||
if err != nil {
|
||||
return boards, err
|
||||
}
|
||||
|
|
@ -822,9 +832,9 @@ func render(ctx context.Context, ms *xmain.State, compileDur time.Duration, plug
|
|||
return boards, nil
|
||||
}
|
||||
|
||||
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, outputFormat exportExtension) ([][]byte, error) {
|
||||
start := time.Now()
|
||||
out, err := _render(ctx, ms, plugin, opts, inputPath, outputPath, bundle, forceAppendix, page, ruler, diagram)
|
||||
out, err := _render(ctx, ms, plugin, opts, inputPath, outputPath, bundle, forceAppendix, page, ruler, diagram, outputFormat)
|
||||
if err != nil {
|
||||
return [][]byte{}, err
|
||||
}
|
||||
|
|
@ -835,8 +845,9 @@ func renderSingle(ctx context.Context, ms *xmain.State, compileDur time.Duration
|
|||
return [][]byte{out}, nil
|
||||
}
|
||||
|
||||
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
|
||||
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, outputFormat exportExtension) ([]byte, error) {
|
||||
toPNG := outputFormat == PNG
|
||||
|
||||
var scale *float64
|
||||
if opts.Scale != nil {
|
||||
scale = opts.Scale
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package d2cli
|
|||
import (
|
||||
"context"
|
||||
"embed"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
|
|
@ -57,6 +56,7 @@ type watcherOpts struct {
|
|||
forceAppendix bool
|
||||
pw png.Playwright
|
||||
fontFamily *d2fonts.FontFamily
|
||||
outputFormat exportExtension
|
||||
}
|
||||
|
||||
type watcher struct {
|
||||
|
|
@ -430,7 +430,7 @@ func (w *watcher) compileLoop(ctx context.Context) error {
|
|||
if w.boardPath != "" {
|
||||
boardPath = strings.Split(w.boardPath, string(os.PathSeparator))
|
||||
}
|
||||
svg, _, err := compile(ctx, w.ms, w.plugins, &fs, w.layout, w.renderOpts, w.fontFamily, w.animateInterval, w.inputPath, w.outputPath, boardPath, false, w.bundle, w.forceAppendix, w.pw.Page)
|
||||
svg, _, err := compile(ctx, w.ms, w.plugins, &fs, w.layout, w.renderOpts, w.fontFamily, w.animateInterval, w.inputPath, w.outputPath, boardPath, false, w.bundle, w.forceAppendix, w.pw.Page, w.outputFormat)
|
||||
w.boardpathMu.Unlock()
|
||||
errs := ""
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Reference in a new issue