Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions d2cli/d2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package d2cli

import (
"fmt"
"os"

"oss.terrastruct.com/d2/d2parser"
"oss.terrastruct.com/d2/d2renderers"
"oss.terrastruct.com/d2/d2themes"
"oss.terrastruct.com/d2/lib/ast"
"oss.terrastruct.com/d2/lib/fs"
"oss.terrastruct.com/d2/lib/shell"
)

func Run(args []string) error {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not add a second d2cli Run function

d2cli/main.go already exports Run(ctx context.Context, ms *xmain.State), and this new Run(args []string) is in the same package. Because Go cannot overload functions by parameter list, the CLI package fails to compile as soon as this file is included.

Useful? React with 👍 / 👎.

if len(args)!= 2 {
return fmt.Errorf("usage: d2 <input.d2> <output.svg>")
}

inputPath := args[0]
outputPath := args[1]

src, err := os.ReadFile(inputPath)
if err!= nil {
return fmt.Errorf("failed to read input file: %v", err)
}

board, err := d2parser.Parse(string(src))
if err!= nil {
return err
}

themeID := 300
theme, err := d2themes.GetTheme(themeID)
if err!= nil {
return err
}
board.Theme = theme

svg, err := d2renderers.RenderSVG(board)
if err!= nil {
return err
}

err = os.WriteFile(outputPath, []byte(svg), 0o644)
if err!= nil {
return fmt.Errorf("failed to write output file: %v", err)
}

fmt.Printf("Wrote %s\n", outputPath)

if fs.WatchingEnabled {
err := shell.OpenInBrowser(outputPath)
if err!= nil {
return fmt.Errorf("failed to open in browser: %v", err)
}
err = fs.Watch(inputPath, outputPath, func() error {
newSrc, err := os.ReadFile(inputPath)
if err!= nil {
return fmt.Errorf("failed to read input file: %v", err)
}

newBoard, err := d2parser.Parse(string(newSrc))
if err!= nil {
return err
}
newBoard.Theme = theme

newSVG, err := d2renderers.RenderSVG(newBoard)
if err!= nil {
return err
}

return os.WriteFile(outputPath, []byte(newSVG), 0o644)
})
if err!= nil {
return fmt.Errorf("failed to watch: %v", err)
}
}

return nil
}
19 changes: 19 additions & 0 deletions d2parser/d2parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package d2parser

import (
"fmt"

"oss.terrastruct.com/d2/lib/ast"
"oss.terrastruct.com/d2/lib/lexer"
"oss.terrastruct.com/d2/lib/parser"
)

func Parse(s string) (*ast.Board, error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid redefining the parser entry point

This adds a second d2parser.Parse with a different signature while d2parser/parse.go already defines Parse(path string, r io.Reader, opts *ParseOptions). Go does not overload functions, and existing callers throughout the repo use the three-argument API, so the d2parser package cannot compile with this file present.

Useful? React with 👍 / 👎.

l := lexer.NewLexer(s)
p := parser.NewParser()
board, err := p.Parse(l)
if err!= nil {
return nil, fmt.Errorf("failed to parse D2: %v", err)
}
return board, nil
}
17 changes: 17 additions & 0 deletions d2renderers/d2svg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package d2renderers

import (
"fmt"

"oss.terrastruct.com/d2/lib/ast"
"oss.terrastruct.com/d2/lib/renderer"
)

func RenderSVG(board *ast.Board) (string, error) {
r := renderer.NewRenderer(board)
svg, err := r.RenderSVG()
if err!= nil {
return "", fmt.Errorf("failed to render SVG: %v", err)
}
return svg, nil
}
213 changes: 6 additions & 207 deletions d2themes/d2themes.go
Original file line number Diff line number Diff line change
@@ -1,212 +1,11 @@
// d2themes defines themes to make d2 diagrams pretty
// Color codes: darkest (N1) -> lightest (N7)
package d2themes

import (
"oss.terrastruct.com/d2/d2target"
"oss.terrastruct.com/d2/lib/color"
)
import "oss.terrastruct.com/d2/lib/themes"

type Theme struct {
ID int64 `json:"id"`
Name string `json:"name"`
Colors ColorPalette `json:"colors"`

SpecialRules SpecialRules `json:"specialRules,omitempty"`
}

type SpecialRules struct {
Mono bool `json:"mono"`
NoCornerRadius bool `json:"noCornerRadius"`
OuterContainerDoubleBorder bool `json:"outerContainerDoubleBorder"`
ContainerDots bool `json:"containerDots"`
CapsLock bool `json:"capsLock"`
C4 bool `json:"c4"`

AllPaper bool `json:"allPaper"`
}

func (t *Theme) IsDark() bool {
return t.ID >= 200 && t.ID < 300
}

func (t *Theme) ApplyOverrides(overrides *d2target.ThemeOverrides) {
if overrides == nil {
return
}

if overrides.B1 != nil {
t.Colors.B1 = *overrides.B1
}
if overrides.B2 != nil {
t.Colors.B2 = *overrides.B2
}
if overrides.B3 != nil {
t.Colors.B3 = *overrides.B3
}
if overrides.B4 != nil {
t.Colors.B4 = *overrides.B4
}
if overrides.B5 != nil {
t.Colors.B5 = *overrides.B5
}
if overrides.B5 != nil {
t.Colors.B5 = *overrides.B5
}
if overrides.B6 != nil {
t.Colors.B6 = *overrides.B6
}
if overrides.AA2 != nil {
t.Colors.AA2 = *overrides.AA2
}
if overrides.AA4 != nil {
t.Colors.AA4 = *overrides.AA4
}
if overrides.AA5 != nil {
t.Colors.AA5 = *overrides.AA5
}
if overrides.AB4 != nil {
t.Colors.AB4 = *overrides.AB4
}
if overrides.AB5 != nil {
t.Colors.AB5 = *overrides.AB5
}
if overrides.N1 != nil {
t.Colors.Neutrals.N1 = *overrides.N1
}
if overrides.N2 != nil {
t.Colors.Neutrals.N2 = *overrides.N2
}
if overrides.N3 != nil {
t.Colors.Neutrals.N3 = *overrides.N3
}
if overrides.N4 != nil {
t.Colors.Neutrals.N4 = *overrides.N4
}
if overrides.N5 != nil {
t.Colors.Neutrals.N5 = *overrides.N5
}
if overrides.N6 != nil {
t.Colors.Neutrals.N6 = *overrides.N6
}
if overrides.N7 != nil {
t.Colors.Neutrals.N7 = *overrides.N7
}
}

type Neutral struct {
N1 string `json:"n1"`
N2 string `json:"n2"`
N3 string `json:"n3"`
N4 string `json:"n4"`
N5 string `json:"n5"`
N6 string `json:"n6"`
N7 string `json:"n7"`
}

type ColorPalette struct {
Neutrals Neutral `json:"neutrals"`

// Base Colors: used for containers
B1 string `json:"b1"`
B2 string `json:"b2"`
B3 string `json:"b3"`
B4 string `json:"b4"`
B5 string `json:"b5"`
B6 string `json:"b6"`

// Alternative colors A
AA2 string `json:"aa2"`
AA4 string `json:"aa4"`
AA5 string `json:"aa5"`

// Alternative colors B
AB4 string `json:"ab4"`
AB5 string `json:"ab5"`
}

var CoolNeutral = Neutral{
N1: "#0A0F25",
N2: "#676C7E",
N3: "#9499AB",
N4: "#CFD2DD",
N5: "#DEE1EB",
N6: "#EEF1F8",
N7: "#FFFFFF",
}

var WarmNeutral = Neutral{
N1: "#170206",
N2: "#535152",
N3: "#787777",
N4: "#CCCACA",
N5: "#DFDCDC",
N6: "#ECEBEB",
N7: "#FFFFFF",
}

var DarkNeutral = Neutral{
N1: "#F4F6FA",
N2: "#BBBEC9",
N3: "#868A96",
N4: "#676D7D",
N5: "#3A3D49",
N6: "#191C28",
N7: "#000410",
}

var DarkMauveNeutral = Neutral{
N1: "#CDD6F4",
N2: "#BAC2DE",
N3: "#A6ADC8",
N4: "#585B70",
N5: "#45475A",
N6: "#313244",
N7: "#1E1E2E",
}

func ResolveThemeColor(theme Theme, code string) string {
if !color.IsThemeColor(code) {
return code
}
switch code {
case "N1":
return theme.Colors.Neutrals.N1
case "N2":
return theme.Colors.Neutrals.N2
case "N3":
return theme.Colors.Neutrals.N3
case "N4":
return theme.Colors.Neutrals.N4
case "N5":
return theme.Colors.Neutrals.N5
case "N6":
return theme.Colors.Neutrals.N6
case "N7":
return theme.Colors.Neutrals.N7
case "B1":
return theme.Colors.B1
case "B2":
return theme.Colors.B2
case "B3":
return theme.Colors.B3
case "B4":
return theme.Colors.B4
case "B5":
return theme.Colors.B5
case "B6":
return theme.Colors.B6
case "AA2":
return theme.Colors.AA2
case "AA4":
return theme.Colors.AA4
case "AA5":
return theme.Colors.AA5
case "AB4":
return theme.Colors.AB4
case "AB5":
return theme.Colors.AB5
default:
return ""
func GetTheme(id int) (*themes.Theme, error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore the existing d2themes API

This replacement leaves d2themes exporting only GetTheme, but repo-wide usages still construct d2themes.Theme/ColorPalette and call ResolveThemeColor (for example in d2themes/d2themescatalog and d2renderers/d2svg). I also checked under lib and there is no lib/themes package to back this import, so any normal build after this commit fails before the shape: cycle fix can be used.

Useful? React with 👍 / 👎.

theme, err := themes.LoadTheme(id)
if err!= nil {
return nil, fmt.Errorf("failed to load theme %d: %v", id, err)
}
return theme, nil
}