1 package d2compiler
2
3 import (
4 "encoding/xml"
5 "fmt"
6 "io"
7 "io/fs"
8 "net/url"
9 "strconv"
10 "strings"
11
12 "oss.terrastruct.com/util-go/go2"
13
14 "oss.terrastruct.com/d2/d2ast"
15 "oss.terrastruct.com/d2/d2format"
16 "oss.terrastruct.com/d2/d2graph"
17 "oss.terrastruct.com/d2/d2ir"
18 "oss.terrastruct.com/d2/d2parser"
19 "oss.terrastruct.com/d2/d2target"
20 "oss.terrastruct.com/d2/lib/color"
21 "oss.terrastruct.com/d2/lib/textmeasure"
22 )
23
24 type CompileOptions struct {
25 UTF16Pos bool
26
27
28 FS fs.FS
29 }
30
31 func Compile(p string, r io.Reader, opts *CompileOptions) (*d2graph.Graph, *d2target.Config, error) {
32 if opts == nil {
33 opts = &CompileOptions{}
34 }
35
36 ast, err := d2parser.Parse(p, r, &d2parser.ParseOptions{
37 UTF16Pos: opts.UTF16Pos,
38 })
39 if err != nil {
40 return nil, nil, err
41 }
42
43 ir, err := d2ir.Compile(ast, &d2ir.CompileOptions{
44 UTF16Pos: opts.UTF16Pos,
45 FS: opts.FS,
46 })
47 if err != nil {
48 return nil, nil, err
49 }
50
51 g, err := compileIR(ast, ir)
52 if err != nil {
53 return nil, nil, err
54 }
55 g.FS = opts.FS
56 g.SortObjectsByAST()
57 g.SortEdgesByAST()
58 config, err := compileConfig(ir)
59 if err != nil {
60 return nil, nil, err
61 }
62 return g, config, nil
63 }
64
65 func compileIR(ast *d2ast.Map, m *d2ir.Map) (*d2graph.Graph, error) {
66 c := &compiler{
67 err: &d2parser.ParseError{},
68 }
69
70 g := d2graph.NewGraph()
71 g.AST = ast
72 c.compileBoard(g, m)
73 if len(c.err.Errors) > 0 {
74 return nil, c.err
75 }
76 c.validateBoardLinks(g)
77 if len(c.err.Errors) > 0 {
78 return nil, c.err
79 }
80 return g, nil
81 }
82
83 func (c *compiler) compileBoard(g *d2graph.Graph, ir *d2ir.Map) *d2graph.Graph {
84 ir = ir.Copy(nil).(*d2ir.Map)
85
86 c.compileMap(g.Root, ir)
87 if len(c.err.Errors) == 0 {
88 c.validateKeys(g.Root, ir)
89 }
90 c.validateLabels(g)
91 c.validateNear(g)
92 c.validateEdges(g)
93
94 c.compileBoardsField(g, ir, "layers")
95 c.compileBoardsField(g, ir, "scenarios")
96 c.compileBoardsField(g, ir, "steps")
97 if d2ir.ParentMap(ir).CopyBase(nil).Equal(ir.CopyBase(nil)) {
98 if len(g.Layers) > 0 || len(g.Scenarios) > 0 || len(g.Steps) > 0 {
99 g.IsFolderOnly = true
100 }
101 }
102 if len(g.Objects) == 0 {
103 g.IsFolderOnly = true
104 }
105 return g
106 }
107
108 func (c *compiler) compileBoardsField(g *d2graph.Graph, ir *d2ir.Map, fieldName string) {
109 layers := ir.GetField(fieldName)
110 if layers.Map() == nil {
111 return
112 }
113 for _, f := range layers.Map().Fields {
114 if f.Map() == nil {
115 continue
116 }
117 if g.GetBoard(f.Name) != nil {
118 c.errorf(f.References[0].AST(), "board name %v already used by another board", f.Name)
119 continue
120 }
121 g2 := d2graph.NewGraph()
122 g2.Parent = g
123 g2.AST = f.Map().AST().(*d2ast.Map)
124 g2.BaseAST = findFieldAST(g.AST, f)
125 c.compileBoard(g2, f.Map())
126 g2.Name = f.Name
127 switch fieldName {
128 case "layers":
129 g.Layers = append(g.Layers, g2)
130 case "scenarios":
131 g.Scenarios = append(g.Scenarios, g2)
132 case "steps":
133 g.Steps = append(g.Steps, g2)
134 }
135 }
136 }
137
138 func findFieldAST(ast *d2ast.Map, f *d2ir.Field) *d2ast.Map {
139 path := []string{}
140 var curr *d2ir.Field = f
141 for {
142 path = append([]string{curr.Name}, path...)
143 boardKind := d2ir.NodeBoardKind(curr)
144 if boardKind == "" {
145 break
146 }
147 curr = d2ir.ParentField(curr)
148 }
149
150 currAST := ast
151 for len(path) > 0 {
152 head := path[0]
153 found := false
154 for _, n := range currAST.Nodes {
155 if n.MapKey == nil {
156 continue
157 }
158 if n.MapKey.Key == nil {
159 continue
160 }
161 if len(n.MapKey.Key.Path) != 1 {
162 continue
163 }
164 head2 := n.MapKey.Key.Path[0].Unbox().ScalarString()
165 if head == head2 {
166 currAST = n.MapKey.Value.Map
167 found = true
168 break
169 }
170 }
171 if !found {
172 return nil
173 }
174 path = path[1:]
175 }
176
177 return currAST
178 }
179
180 type compiler struct {
181 err *d2parser.ParseError
182 }
183
184 func (c *compiler) errorf(n d2ast.Node, f string, v ...interface{}) {
185 err := d2parser.Errorf(n, f, v...).(d2ast.Error)
186 if c.err.ErrorsLookup == nil {
187 c.err.ErrorsLookup = make(map[d2ast.Error]struct{})
188 }
189 if _, ok := c.err.ErrorsLookup[err]; !ok {
190 c.err.Errors = append(c.err.Errors, err)
191 c.err.ErrorsLookup[err] = struct{}{}
192 }
193 }
194
195 func (c *compiler) compileMap(obj *d2graph.Object, m *d2ir.Map) {
196 class := m.GetField("class")
197 if class != nil {
198 var classNames []string
199 if class.Primary() != nil {
200 classNames = append(classNames, class.Primary().String())
201 } else if class.Composite != nil {
202 if arr, ok := class.Composite.(*d2ir.Array); ok {
203 for _, class := range arr.Values {
204 if scalar, ok := class.(*d2ir.Scalar); ok {
205 classNames = append(classNames, scalar.Value.ScalarString())
206 } else {
207 c.errorf(class.LastPrimaryKey(), "invalid value in array")
208 }
209 }
210 }
211 } else {
212 c.errorf(class.LastRef().AST(), "class missing value")
213 }
214
215 for _, className := range classNames {
216 classMap := m.GetClassMap(className)
217 if classMap != nil {
218 c.compileMap(obj, classMap)
219 } else {
220 if strings.Contains(className, ",") {
221 split := strings.Split(className, ",")
222 allFound := true
223 for _, maybeClassName := range split {
224 maybeClassName = strings.TrimSpace(maybeClassName)
225 if m.GetClassMap(maybeClassName) == nil {
226 allFound = false
227 break
228 }
229 }
230 if allFound {
231 c.errorf(class.LastRef().AST(), `class "%s" not found. Did you mean to use ";" to separate array items?`, className)
232 }
233 }
234 }
235 }
236 }
237 shape := m.GetField("shape")
238 if shape != nil {
239 if shape.Composite != nil {
240 c.errorf(shape.LastPrimaryKey(), "reserved field shape does not accept composite")
241 } else {
242 c.compileField(obj, shape)
243 }
244 }
245 for _, f := range m.Fields {
246 if f.Name == "shape" {
247 continue
248 }
249 if _, ok := d2graph.BoardKeywords[f.Name]; ok {
250 continue
251 }
252 c.compileField(obj, f)
253 }
254
255 if !m.IsClass() {
256 switch obj.Shape.Value {
257 case d2target.ShapeClass:
258 c.compileClass(obj)
259 case d2target.ShapeSQLTable:
260 c.compileSQLTable(obj)
261 }
262
263 for _, e := range m.Edges {
264 c.compileEdge(obj, e)
265 }
266 }
267 }
268
269 func (c *compiler) compileField(obj *d2graph.Object, f *d2ir.Field) {
270 keyword := strings.ToLower(f.Name)
271 _, isStyleReserved := d2graph.StyleKeywords[keyword]
272 if isStyleReserved {
273 c.errorf(f.LastRef().AST(), "%v must be style.%v", f.Name, f.Name)
274 return
275 }
276 _, isReserved := d2graph.SimpleReservedKeywords[keyword]
277 if f.Name == "classes" {
278 if f.Map() != nil {
279 if len(f.Map().Edges) > 0 {
280 c.errorf(f.Map().Edges[0].LastRef().AST(), "classes cannot contain an edge")
281 }
282 for _, classesField := range f.Map().Fields {
283 if classesField.Map() == nil {
284 continue
285 }
286 for _, cf := range classesField.Map().Fields {
287 if _, ok := d2graph.ReservedKeywords[cf.Name]; !ok {
288 c.errorf(cf.LastRef().AST(), "%s is an invalid class field, must be reserved keyword", cf.Name)
289 }
290 if cf.Name == "class" {
291 c.errorf(cf.LastRef().AST(), `"class" cannot appear within "classes"`)
292 }
293 }
294 }
295 }
296 return
297 } else if f.Name == "vars" {
298 return
299 } else if f.Name == "source-arrowhead" || f.Name == "target-arrowhead" {
300 c.errorf(f.LastRef().AST(), `%#v can only be used on connections`, f.Name)
301 return
302
303 } else if isReserved {
304 c.compileReserved(&obj.Attributes, f)
305 return
306 } else if f.Name == "style" {
307 if f.Map() == nil || len(f.Map().Fields) == 0 {
308 c.errorf(f.LastRef().AST(), `"style" expected to be set to a map of key-values, or contain an additional keyword like "style.opacity: 0.4"`)
309 return
310 }
311 c.compileStyle(&obj.Attributes, f.Map())
312 if obj.Style.Animated != nil {
313 c.errorf(obj.Style.Animated.MapKey, `key "animated" can only be applied to edges`)
314 }
315 return
316 }
317
318 if obj.Parent != nil {
319 if obj.Parent.Shape.Value == d2target.ShapeSQLTable {
320 c.errorf(f.LastRef().AST(), "sql_table columns cannot have children")
321 return
322 }
323 if obj.Parent.Shape.Value == d2target.ShapeClass {
324 c.errorf(f.LastRef().AST(), "class fields cannot have children")
325 return
326 }
327 }
328
329 obj = obj.EnsureChild(d2graphIDA([]string{f.Name}))
330 if f.Primary() != nil {
331 c.compileLabel(&obj.Attributes, f)
332 }
333 if f.Map() != nil {
334 c.compileMap(obj, f.Map())
335 }
336
337 if obj.Label.MapKey == nil {
338 obj.Label.MapKey = f.LastPrimaryKey()
339 }
340 for _, fr := range f.References {
341 if fr.Primary() {
342 if fr.Context_.Key.Value.Map != nil {
343 obj.Map = fr.Context_.Key.Value.Map
344 }
345 }
346 r := d2graph.Reference{
347 Key: fr.KeyPath,
348 KeyPathIndex: fr.KeyPathIndex(),
349
350 MapKey: fr.Context_.Key,
351 MapKeyEdgeIndex: fr.Context_.EdgeIndex(),
352 Scope: fr.Context_.Scope,
353 ScopeAST: fr.Context_.ScopeAST,
354 }
355 if fr.Context_.ScopeMap != nil && !d2ir.IsVar(fr.Context_.ScopeMap) {
356 scopeObjIDA := d2graphIDA(d2ir.BoardIDA(fr.Context_.ScopeMap))
357 r.ScopeObj = obj.Graph.Root.EnsureChild(scopeObjIDA)
358 }
359 obj.References = append(obj.References, r)
360 }
361 }
362
363 func (c *compiler) compileLabel(attrs *d2graph.Attributes, f d2ir.Node) {
364 scalar := f.Primary().Value
365 switch scalar := scalar.(type) {
366 case *d2ast.BlockString:
367 if strings.TrimSpace(scalar.ScalarString()) == "" {
368 c.errorf(f.LastPrimaryKey(), "block string cannot be empty")
369 }
370 attrs.Language = scalar.Tag
371 fullTag, ok := ShortToFullLanguageAliases[scalar.Tag]
372 if ok {
373 attrs.Language = fullTag
374 }
375 switch attrs.Language {
376 case "latex":
377 attrs.Shape.Value = d2target.ShapeText
378 case "markdown":
379 rendered, err := textmeasure.RenderMarkdown(scalar.ScalarString())
380 if err != nil {
381 c.errorf(f.LastPrimaryKey(), "malformed Markdown")
382 }
383 rendered = "<div>" + rendered + "</div>"
384 var xmlParsed interface{}
385 err = xml.Unmarshal([]byte(rendered), &xmlParsed)
386 if err != nil {
387 switch xmlErr := err.(type) {
388 case *xml.SyntaxError:
389 c.errorf(f.LastPrimaryKey(), "malformed Markdown: %s", xmlErr.Msg)
390 default:
391 c.errorf(f.LastPrimaryKey(), "malformed Markdown: %s", err.Error())
392 }
393 }
394 attrs.Shape.Value = d2target.ShapeText
395 default:
396 attrs.Shape.Value = d2target.ShapeCode
397 }
398 attrs.Label.Value = scalar.ScalarString()
399 default:
400 attrs.Label.Value = scalar.ScalarString()
401 }
402 attrs.Label.MapKey = f.LastPrimaryKey()
403 }
404
405 func (c *compiler) compilePosition(attrs *d2graph.Attributes, f *d2ir.Field) {
406 name := f.Name
407 if f.Map() != nil {
408 for _, f := range f.Map().Fields {
409 if f.Name == "near" {
410 if f.Primary() == nil {
411 c.errorf(f.LastPrimaryKey(), `invalid "near" field`)
412 } else {
413 scalar := f.Primary().Value
414 switch scalar := scalar.(type) {
415 case *d2ast.Null:
416 attrs.LabelPosition = nil
417 default:
418 if _, ok := d2graph.LabelPositions[scalar.ScalarString()]; !ok {
419 c.errorf(f.LastPrimaryKey(), `invalid "near" field`)
420 } else {
421 switch name {
422 case "label":
423 attrs.LabelPosition = &d2graph.Scalar{}
424 attrs.LabelPosition.Value = scalar.ScalarString()
425 attrs.LabelPosition.MapKey = f.LastPrimaryKey()
426 case "icon":
427 attrs.IconPosition = &d2graph.Scalar{}
428 attrs.IconPosition.Value = scalar.ScalarString()
429 attrs.IconPosition.MapKey = f.LastPrimaryKey()
430 }
431 }
432 }
433 }
434 } else {
435 if f.LastPrimaryKey() != nil {
436 c.errorf(f.LastPrimaryKey(), `unexpected field %s`, f.Name)
437 }
438 }
439 }
440 if len(f.Map().Edges) > 0 {
441 c.errorf(f.LastPrimaryKey(), "unexpected edges in map")
442 }
443 }
444 }
445
446 func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) {
447 if f.Primary() == nil {
448 if f.Composite != nil {
449 switch f.Name {
450 case "class":
451 if arr, ok := f.Composite.(*d2ir.Array); ok {
452 for _, class := range arr.Values {
453 if scalar, ok := class.(*d2ir.Scalar); ok {
454 attrs.Classes = append(attrs.Classes, scalar.Value.ScalarString())
455 }
456 }
457 }
458 case "constraint":
459 if arr, ok := f.Composite.(*d2ir.Array); ok {
460 for _, constraint := range arr.Values {
461 if scalar, ok := constraint.(*d2ir.Scalar); ok {
462 switch scalar.Value.(type) {
463 case *d2ast.Null:
464 attrs.Constraint = append(attrs.Constraint, "null")
465 default:
466 attrs.Constraint = append(attrs.Constraint, scalar.Value.ScalarString())
467 }
468 }
469 }
470 }
471 case "label", "icon":
472 c.compilePosition(attrs, f)
473 default:
474 c.errorf(f.LastPrimaryKey(), "reserved field %v does not accept composite", f.Name)
475 }
476 }
477 return
478 }
479 scalar := f.Primary().Value
480 switch f.Name {
481 case "label":
482 c.compileLabel(attrs, f)
483 c.compilePosition(attrs, f)
484 case "shape":
485 in := d2target.IsShape(scalar.ScalarString())
486 _, isArrowhead := d2target.Arrowheads[scalar.ScalarString()]
487 if !in && !isArrowhead {
488 c.errorf(scalar, "unknown shape %q", scalar.ScalarString())
489 return
490 }
491 attrs.Shape.Value = scalar.ScalarString()
492 if attrs.Shape.Value == d2target.ShapeCode {
493
494 attrs.Language = d2target.ShapeText
495 }
496 attrs.Shape.MapKey = f.LastPrimaryKey()
497 case "icon":
498 iconURL, err := url.Parse(scalar.ScalarString())
499 if err != nil {
500 c.errorf(scalar, "bad icon url %#v: %s", scalar.ScalarString(), err)
501 return
502 }
503 attrs.Icon = iconURL
504 c.compilePosition(attrs, f)
505 case "near":
506 nearKey, err := d2parser.ParseKey(scalar.ScalarString())
507 if err != nil {
508 c.errorf(scalar, "bad near key %#v: %s", scalar.ScalarString(), err)
509 return
510 }
511 nearKey.Range = scalar.GetRange()
512 attrs.NearKey = nearKey
513 case "tooltip":
514 attrs.Tooltip = &d2graph.Scalar{}
515 attrs.Tooltip.Value = scalar.ScalarString()
516 attrs.Tooltip.MapKey = f.LastPrimaryKey()
517 case "width":
518 _, err := strconv.Atoi(scalar.ScalarString())
519 if err != nil {
520 c.errorf(scalar, "non-integer width %#v: %s", scalar.ScalarString(), err)
521 return
522 }
523 attrs.WidthAttr = &d2graph.Scalar{}
524 attrs.WidthAttr.Value = scalar.ScalarString()
525 attrs.WidthAttr.MapKey = f.LastPrimaryKey()
526 case "height":
527 _, err := strconv.Atoi(scalar.ScalarString())
528 if err != nil {
529 c.errorf(scalar, "non-integer height %#v: %s", scalar.ScalarString(), err)
530 return
531 }
532 attrs.HeightAttr = &d2graph.Scalar{}
533 attrs.HeightAttr.Value = scalar.ScalarString()
534 attrs.HeightAttr.MapKey = f.LastPrimaryKey()
535 case "top":
536 v, err := strconv.Atoi(scalar.ScalarString())
537 if err != nil {
538 c.errorf(scalar, "non-integer top %#v: %s", scalar.ScalarString(), err)
539 return
540 }
541 if v < 0 {
542 c.errorf(scalar, "top must be a non-negative integer: %#v", scalar.ScalarString())
543 return
544 }
545 attrs.Top = &d2graph.Scalar{}
546 attrs.Top.Value = scalar.ScalarString()
547 attrs.Top.MapKey = f.LastPrimaryKey()
548 case "left":
549 v, err := strconv.Atoi(scalar.ScalarString())
550 if err != nil {
551 c.errorf(scalar, "non-integer left %#v: %s", scalar.ScalarString(), err)
552 return
553 }
554 if v < 0 {
555 c.errorf(scalar, "left must be a non-negative integer: %#v", scalar.ScalarString())
556 return
557 }
558 attrs.Left = &d2graph.Scalar{}
559 attrs.Left.Value = scalar.ScalarString()
560 attrs.Left.MapKey = f.LastPrimaryKey()
561 case "link":
562 attrs.Link = &d2graph.Scalar{}
563 attrs.Link.Value = scalar.ScalarString()
564 attrs.Link.MapKey = f.LastPrimaryKey()
565 case "direction":
566 dirs := []string{"up", "down", "right", "left"}
567 if !go2.Contains(dirs, scalar.ScalarString()) {
568 c.errorf(scalar, `direction must be one of %v, got %q`, strings.Join(dirs, ", "), scalar.ScalarString())
569 return
570 }
571 attrs.Direction.Value = scalar.ScalarString()
572 attrs.Direction.MapKey = f.LastPrimaryKey()
573 case "constraint":
574 if _, ok := scalar.(d2ast.String); !ok {
575 c.errorf(f.LastPrimaryKey(), "constraint value must be a string")
576 return
577 }
578 attrs.Constraint = append(attrs.Constraint, scalar.ScalarString())
579 case "grid-rows":
580 v, err := strconv.Atoi(scalar.ScalarString())
581 if err != nil {
582 c.errorf(scalar, "non-integer grid-rows %#v: %s", scalar.ScalarString(), err)
583 return
584 }
585 if v <= 0 {
586 c.errorf(scalar, "grid-rows must be a positive integer: %#v", scalar.ScalarString())
587 return
588 }
589 attrs.GridRows = &d2graph.Scalar{}
590 attrs.GridRows.Value = scalar.ScalarString()
591 attrs.GridRows.MapKey = f.LastPrimaryKey()
592 case "grid-columns":
593 v, err := strconv.Atoi(scalar.ScalarString())
594 if err != nil {
595 c.errorf(scalar, "non-integer grid-columns %#v: %s", scalar.ScalarString(), err)
596 return
597 }
598 if v <= 0 {
599 c.errorf(scalar, "grid-columns must be a positive integer: %#v", scalar.ScalarString())
600 return
601 }
602 attrs.GridColumns = &d2graph.Scalar{}
603 attrs.GridColumns.Value = scalar.ScalarString()
604 attrs.GridColumns.MapKey = f.LastPrimaryKey()
605 case "grid-gap":
606 v, err := strconv.Atoi(scalar.ScalarString())
607 if err != nil {
608 c.errorf(scalar, "non-integer grid-gap %#v: %s", scalar.ScalarString(), err)
609 return
610 }
611 if v < 0 {
612 c.errorf(scalar, "grid-gap must be a non-negative integer: %#v", scalar.ScalarString())
613 return
614 }
615 attrs.GridGap = &d2graph.Scalar{}
616 attrs.GridGap.Value = scalar.ScalarString()
617 attrs.GridGap.MapKey = f.LastPrimaryKey()
618 case "vertical-gap":
619 v, err := strconv.Atoi(scalar.ScalarString())
620 if err != nil {
621 c.errorf(scalar, "non-integer vertical-gap %#v: %s", scalar.ScalarString(), err)
622 return
623 }
624 if v < 0 {
625 c.errorf(scalar, "vertical-gap must be a non-negative integer: %#v", scalar.ScalarString())
626 return
627 }
628 attrs.VerticalGap = &d2graph.Scalar{}
629 attrs.VerticalGap.Value = scalar.ScalarString()
630 attrs.VerticalGap.MapKey = f.LastPrimaryKey()
631 case "horizontal-gap":
632 v, err := strconv.Atoi(scalar.ScalarString())
633 if err != nil {
634 c.errorf(scalar, "non-integer horizontal-gap %#v: %s", scalar.ScalarString(), err)
635 return
636 }
637 if v < 0 {
638 c.errorf(scalar, "horizontal-gap must be a non-negative integer: %#v", scalar.ScalarString())
639 return
640 }
641 attrs.HorizontalGap = &d2graph.Scalar{}
642 attrs.HorizontalGap.Value = scalar.ScalarString()
643 attrs.HorizontalGap.MapKey = f.LastPrimaryKey()
644 case "class":
645 attrs.Classes = append(attrs.Classes, scalar.ScalarString())
646 case "classes":
647 }
648
649 if attrs.Link != nil && attrs.Tooltip != nil {
650 u, err := url.ParseRequestURI(attrs.Tooltip.Value)
651 if err == nil && u.Host != "" {
652 c.errorf(scalar, "Tooltip cannot be set to URL when link is also set (for security)")
653 }
654 }
655 }
656
657 func (c *compiler) compileStyle(attrs *d2graph.Attributes, m *d2ir.Map) {
658 for _, f := range m.Fields {
659 c.compileStyleField(attrs, f)
660 }
661 }
662
663 func (c *compiler) compileStyleField(attrs *d2graph.Attributes, f *d2ir.Field) {
664 if _, ok := d2graph.StyleKeywords[strings.ToLower(f.Name)]; !ok {
665 c.errorf(f.LastRef().AST(), `invalid style keyword: "%s"`, f.Name)
666 return
667 }
668 if f.Primary() == nil {
669 return
670 }
671 compileStyleFieldInit(attrs, f)
672 scalar := f.Primary().Value
673 err := attrs.Style.Apply(f.Name, scalar.ScalarString())
674 if err != nil {
675 c.errorf(scalar, err.Error())
676 return
677 }
678 }
679
680 func compileStyleFieldInit(attrs *d2graph.Attributes, f *d2ir.Field) {
681 switch f.Name {
682 case "opacity":
683 attrs.Style.Opacity = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
684 case "stroke":
685 attrs.Style.Stroke = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
686 case "fill":
687 attrs.Style.Fill = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
688 case "fill-pattern":
689 attrs.Style.FillPattern = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
690 case "stroke-width":
691 attrs.Style.StrokeWidth = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
692 case "stroke-dash":
693 attrs.Style.StrokeDash = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
694 case "border-radius":
695 attrs.Style.BorderRadius = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
696 case "shadow":
697 attrs.Style.Shadow = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
698 case "3d":
699 attrs.Style.ThreeDee = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
700 case "multiple":
701 attrs.Style.Multiple = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
702 case "font":
703 attrs.Style.Font = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
704 case "font-size":
705 attrs.Style.FontSize = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
706 case "font-color":
707 attrs.Style.FontColor = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
708 case "animated":
709 attrs.Style.Animated = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
710 case "bold":
711 attrs.Style.Bold = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
712 case "italic":
713 attrs.Style.Italic = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
714 case "underline":
715 attrs.Style.Underline = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
716 case "filled":
717 attrs.Style.Filled = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
718 case "width":
719 attrs.WidthAttr = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
720 case "height":
721 attrs.HeightAttr = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
722 case "top":
723 attrs.Top = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
724 case "left":
725 attrs.Left = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
726 case "double-border":
727 attrs.Style.DoubleBorder = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
728 case "text-transform":
729 attrs.Style.TextTransform = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
730 }
731 }
732
733 func (c *compiler) compileEdge(obj *d2graph.Object, e *d2ir.Edge) {
734 edge, err := obj.Connect(d2graphIDA(e.ID.SrcPath), d2graphIDA(e.ID.DstPath), e.ID.SrcArrow, e.ID.DstArrow, "")
735 if err != nil {
736 c.errorf(e.References[0].AST(), err.Error())
737 return
738 }
739
740 if e.Primary() != nil {
741 c.compileLabel(&edge.Attributes, e)
742 }
743 if e.Map() != nil {
744 c.compileEdgeMap(edge, e.Map())
745 }
746
747 edge.Label.MapKey = e.LastPrimaryKey()
748 for _, er := range e.References {
749 r := d2graph.EdgeReference{
750 Edge: er.Context_.Edge,
751 MapKey: er.Context_.Key,
752 MapKeyEdgeIndex: er.Context_.EdgeIndex(),
753 Scope: er.Context_.Scope,
754 ScopeAST: er.Context_.ScopeAST,
755 }
756 if er.Context_.ScopeMap != nil && !d2ir.IsVar(er.Context_.ScopeMap) {
757 scopeObjIDA := d2graphIDA(d2ir.BoardIDA(er.Context_.ScopeMap))
758 r.ScopeObj = edge.Src.Graph.Root.EnsureChild(scopeObjIDA)
759 }
760 edge.References = append(edge.References, r)
761 }
762 }
763
764 func (c *compiler) compileEdgeMap(edge *d2graph.Edge, m *d2ir.Map) {
765 class := m.GetField("class")
766 if class != nil {
767 var classNames []string
768 if class.Primary() != nil {
769 classNames = append(classNames, class.Primary().String())
770 } else if class.Composite != nil {
771 if arr, ok := class.Composite.(*d2ir.Array); ok {
772 for _, class := range arr.Values {
773 if scalar, ok := class.(*d2ir.Scalar); ok {
774 classNames = append(classNames, scalar.Value.ScalarString())
775 } else {
776 c.errorf(class.LastPrimaryKey(), "invalid value in array")
777 }
778 }
779 }
780 } else {
781 c.errorf(class.LastRef().AST(), "class missing value")
782 }
783
784 for _, className := range classNames {
785 classMap := m.GetClassMap(className)
786 if classMap != nil {
787 c.compileEdgeMap(edge, classMap)
788 }
789 }
790 }
791 for _, f := range m.Fields {
792 _, ok := d2graph.ReservedKeywords[f.Name]
793 if !ok {
794 c.errorf(f.References[0].AST(), `edge map keys must be reserved keywords`)
795 continue
796 }
797 c.compileEdgeField(edge, f)
798 }
799 }
800
801 func (c *compiler) compileEdgeField(edge *d2graph.Edge, f *d2ir.Field) {
802 keyword := strings.ToLower(f.Name)
803 _, isStyleReserved := d2graph.StyleKeywords[keyword]
804 if isStyleReserved {
805 c.errorf(f.LastRef().AST(), "%v must be style.%v", f.Name, f.Name)
806 return
807 }
808 _, isReserved := d2graph.SimpleReservedKeywords[keyword]
809 if isReserved {
810 c.compileReserved(&edge.Attributes, f)
811 return
812 } else if f.Name == "style" {
813 if f.Map() == nil {
814 return
815 }
816 c.compileStyle(&edge.Attributes, f.Map())
817 return
818 }
819
820 if f.Name == "source-arrowhead" || f.Name == "target-arrowhead" {
821 c.compileArrowheads(edge, f)
822 }
823 }
824
825 func (c *compiler) compileArrowheads(edge *d2graph.Edge, f *d2ir.Field) {
826 var attrs *d2graph.Attributes
827 if f.Name == "source-arrowhead" {
828 if edge.SrcArrowhead == nil {
829 edge.SrcArrowhead = &d2graph.Attributes{}
830 }
831 attrs = edge.SrcArrowhead
832 } else {
833 if edge.DstArrowhead == nil {
834 edge.DstArrowhead = &d2graph.Attributes{}
835 }
836 attrs = edge.DstArrowhead
837 }
838
839 if f.Primary() != nil {
840 c.compileLabel(attrs, f)
841 }
842
843 if f.Map() != nil {
844 for _, f2 := range f.Map().Fields {
845 keyword := strings.ToLower(f2.Name)
846 _, isReserved := d2graph.SimpleReservedKeywords[keyword]
847 if isReserved {
848 c.compileReserved(attrs, f2)
849 continue
850 } else if f2.Name == "style" {
851 if f2.Map() == nil {
852 continue
853 }
854 c.compileStyle(attrs, f2.Map())
855 continue
856 } else {
857 c.errorf(f2.LastRef().AST(), `source-arrowhead/target-arrowhead map keys must be reserved keywords`)
858 continue
859 }
860 }
861 }
862 }
863
864
865 var ShortToFullLanguageAliases = map[string]string{
866 "md": "markdown",
867 "tex": "latex",
868 "js": "javascript",
869 "go": "golang",
870 "py": "python",
871 "rb": "ruby",
872 "ts": "typescript",
873 }
874 var FullToShortLanguageAliases map[string]string
875
876 func (c *compiler) compileClass(obj *d2graph.Object) {
877 obj.Class = &d2target.Class{}
878 for _, f := range obj.ChildrenArray {
879 visibility := "public"
880 name := f.IDVal
881
882 if name != "" {
883 switch name[0] {
884 case '+':
885 name = name[1:]
886 case '-':
887 visibility = "private"
888 name = name[1:]
889 case '#':
890 visibility = "protected"
891 name = name[1:]
892 }
893 }
894
895 if !strings.Contains(f.IDVal, "(") {
896 typ := f.Label.Value
897 if typ == f.IDVal {
898 typ = ""
899 }
900 obj.Class.Fields = append(obj.Class.Fields, d2target.ClassField{
901 Name: name,
902 Type: typ,
903 Visibility: visibility,
904 })
905 } else {
906
907
908 returnType := f.Label.Value
909 if returnType == f.IDVal {
910 returnType = "void"
911 }
912 obj.Class.Methods = append(obj.Class.Methods, d2target.ClassMethod{
913 Name: name,
914 Return: returnType,
915 Visibility: visibility,
916 })
917 }
918 }
919
920 for _, ch := range obj.ChildrenArray {
921 for i := 0; i < len(obj.Graph.Objects); i++ {
922 if obj.Graph.Objects[i] == ch {
923 obj.Graph.Objects = append(obj.Graph.Objects[:i], obj.Graph.Objects[i+1:]...)
924 i--
925 }
926 }
927 }
928 obj.Children = nil
929 obj.ChildrenArray = nil
930 }
931
932 func (c *compiler) compileSQLTable(obj *d2graph.Object) {
933 obj.SQLTable = &d2target.SQLTable{}
934 for _, col := range obj.ChildrenArray {
935 typ := col.Label.Value
936 if typ == col.IDVal {
937
938
939 typ = ""
940 }
941 d2Col := d2target.SQLColumn{
942 Name: d2target.Text{Label: col.IDVal},
943 Type: d2target.Text{Label: typ},
944 Constraint: col.Constraint,
945 }
946 obj.SQLTable.Columns = append(obj.SQLTable.Columns, d2Col)
947 }
948
949 for _, ch := range obj.ChildrenArray {
950 for i := 0; i < len(obj.Graph.Objects); i++ {
951 if obj.Graph.Objects[i] == ch {
952 obj.Graph.Objects = append(obj.Graph.Objects[:i], obj.Graph.Objects[i+1:]...)
953 i--
954 }
955 }
956 }
957 obj.Children = nil
958 obj.ChildrenArray = nil
959 }
960
961 func (c *compiler) validateKeys(obj *d2graph.Object, m *d2ir.Map) {
962 for _, f := range m.Fields {
963 if _, ok := d2graph.BoardKeywords[f.Name]; ok {
964 continue
965 }
966 c.validateKey(obj, f)
967 }
968 }
969
970 func (c *compiler) validateKey(obj *d2graph.Object, f *d2ir.Field) {
971 keyword := strings.ToLower(f.Name)
972 _, isReserved := d2graph.ReservedKeywords[keyword]
973 if isReserved {
974 switch obj.Shape.Value {
975 case d2target.ShapeCircle, d2target.ShapeSquare:
976 checkEqual := (keyword == "width" && obj.HeightAttr != nil) || (keyword == "height" && obj.WidthAttr != nil)
977 if checkEqual && obj.WidthAttr.Value != obj.HeightAttr.Value {
978 c.errorf(f.LastPrimaryKey(), "width and height must be equal for %s shapes", obj.Shape.Value)
979 }
980 }
981
982 switch f.Name {
983 case "style":
984 if obj.Style.ThreeDee != nil {
985 if !strings.EqualFold(obj.Shape.Value, d2target.ShapeSquare) && !strings.EqualFold(obj.Shape.Value, d2target.ShapeRectangle) && !strings.EqualFold(obj.Shape.Value, d2target.ShapeHexagon) {
986 c.errorf(obj.Style.ThreeDee.MapKey, `key "3d" can only be applied to squares, rectangles, and hexagons`)
987 }
988 }
989 if obj.Style.DoubleBorder != nil {
990 if obj.Shape.Value != "" && obj.Shape.Value != d2target.ShapeSquare && obj.Shape.Value != d2target.ShapeRectangle && obj.Shape.Value != d2target.ShapeCircle && obj.Shape.Value != d2target.ShapeOval {
991 c.errorf(obj.Style.DoubleBorder.MapKey, `key "double-border" can only be applied to squares, rectangles, circles, ovals`)
992 }
993 }
994 case "shape":
995 if obj.Shape.Value == d2target.ShapeImage && obj.Icon == nil {
996 c.errorf(f.LastPrimaryKey(), `image shape must include an "icon" field`)
997 }
998
999 in := d2target.IsShape(obj.Shape.Value)
1000 _, arrowheadIn := d2target.Arrowheads[obj.Shape.Value]
1001 if !in && arrowheadIn {
1002 c.errorf(f.LastPrimaryKey(), fmt.Sprintf(`invalid shape, can only set "%s" for arrowheads`, obj.Shape.Value))
1003 }
1004 case "constraint":
1005 if obj.Shape.Value != d2target.ShapeSQLTable {
1006 c.errorf(f.LastPrimaryKey(), `"constraint" keyword can only be used in "sql_table" shapes`)
1007 }
1008 }
1009 return
1010 }
1011
1012 if obj.Shape.Value == d2target.ShapeImage {
1013 c.errorf(f.LastRef().AST(), "image shapes cannot have children.")
1014 return
1015 }
1016
1017 obj, ok := obj.HasChild([]string{f.Name})
1018 if ok && f.Map() != nil {
1019 c.validateKeys(obj, f.Map())
1020 }
1021 }
1022
1023 func (c *compiler) validateLabels(g *d2graph.Graph) {
1024 for _, obj := range g.Objects {
1025 if obj.Shape.Value != d2target.ShapeText {
1026 continue
1027 }
1028 if obj.Attributes.Language != "" {
1029
1030 continue
1031 }
1032 if strings.TrimSpace(obj.Label.Value) == "" {
1033 c.errorf(obj.Label.MapKey, "shape text must have a non-empty label")
1034 continue
1035 }
1036 }
1037 }
1038
1039 func (c *compiler) validateNear(g *d2graph.Graph) {
1040 for _, obj := range g.Objects {
1041 if obj.NearKey != nil {
1042 nearObj, isKey := g.Root.HasChild(d2graph.Key(obj.NearKey))
1043 _, isConst := d2graph.NearConstants[d2graph.Key(obj.NearKey)[0]]
1044 if isKey {
1045
1046 nearIsAncestor := false
1047 for curr := obj; curr != nil; curr = curr.Parent {
1048 if curr == nearObj {
1049 nearIsAncestor = true
1050 break
1051 }
1052 }
1053 if nearIsAncestor {
1054 c.errorf(obj.NearKey, "near keys cannot be set to an ancestor")
1055 continue
1056 }
1057 nearIsDescendant := false
1058 for curr := nearObj; curr != nil; curr = curr.Parent {
1059 if curr == obj {
1060 nearIsDescendant = true
1061 break
1062 }
1063 }
1064 if nearIsDescendant {
1065 c.errorf(obj.NearKey, "near keys cannot be set to an descendant")
1066 continue
1067 }
1068 if nearObj.OuterSequenceDiagram() != nil {
1069 c.errorf(obj.NearKey, "near keys cannot be set to an object within sequence diagrams")
1070 continue
1071 }
1072 if nearObj.NearKey != nil {
1073 _, nearObjNearIsConst := d2graph.NearConstants[d2graph.Key(nearObj.NearKey)[0]]
1074 if nearObjNearIsConst {
1075 c.errorf(obj.NearKey, "near keys cannot be set to an object with a constant near key")
1076 continue
1077 }
1078 }
1079 } else if isConst {
1080 if obj.Parent != g.Root {
1081 c.errorf(obj.NearKey, "constant near keys can only be set on root level shapes")
1082 continue
1083 }
1084 } else {
1085 c.errorf(obj.NearKey, "near key %#v must be the absolute path to a shape or one of the following constants: %s", d2format.Format(obj.NearKey), strings.Join(d2graph.NearConstantsArray, ", "))
1086 continue
1087 }
1088 }
1089 }
1090
1091 for _, edge := range g.Edges {
1092 if edge.Src.IsConstantNear() && edge.Dst.IsDescendantOf(edge.Src) {
1093 c.errorf(edge.GetAstEdge(), "edge from constant near %#v cannot enter itself", edge.Src.AbsID())
1094 continue
1095 }
1096 if edge.Dst.IsConstantNear() && edge.Src.IsDescendantOf(edge.Dst) {
1097 c.errorf(edge.GetAstEdge(), "edge from constant near %#v cannot enter itself", edge.Dst.AbsID())
1098 continue
1099 }
1100 }
1101
1102 }
1103
1104 func (c *compiler) validateEdges(g *d2graph.Graph) {
1105 for _, edge := range g.Edges {
1106
1107
1108
1109
1110 if edge.Src.IsGridDiagram() && edge.Dst.IsDescendantOf(edge.Src) {
1111 c.errorf(edge.GetAstEdge(), "edge from grid diagram %#v cannot enter itself", edge.Src.AbsID())
1112 continue
1113 }
1114 if edge.Dst.IsGridDiagram() && edge.Src.IsDescendantOf(edge.Dst) {
1115 c.errorf(edge.GetAstEdge(), "edge from grid diagram %#v cannot enter itself", edge.Dst.AbsID())
1116 continue
1117 }
1118 if edge.Src.Parent.IsGridDiagram() && edge.Dst.IsDescendantOf(edge.Src) {
1119 c.errorf(edge.GetAstEdge(), "edge from grid cell %#v cannot enter itself", edge.Src.AbsID())
1120 continue
1121 }
1122 if edge.Dst.Parent.IsGridDiagram() && edge.Src.IsDescendantOf(edge.Dst) {
1123 c.errorf(edge.GetAstEdge(), "edge from grid cell %#v cannot enter itself", edge.Dst.AbsID())
1124 continue
1125 }
1126 if edge.Src.IsSequenceDiagram() && edge.Dst.IsDescendantOf(edge.Src) {
1127 c.errorf(edge.GetAstEdge(), "edge from sequence diagram %#v cannot enter itself", edge.Src.AbsID())
1128 continue
1129 }
1130 if edge.Dst.IsSequenceDiagram() && edge.Src.IsDescendantOf(edge.Dst) {
1131 c.errorf(edge.GetAstEdge(), "edge from sequence diagram %#v cannot enter itself", edge.Dst.AbsID())
1132 continue
1133 }
1134 }
1135 }
1136
1137 func (c *compiler) validateBoardLinks(g *d2graph.Graph) {
1138 for _, obj := range g.Objects {
1139 if obj.Link == nil {
1140 continue
1141 }
1142
1143 linkKey, err := d2parser.ParseKey(obj.Link.Value)
1144 if err != nil {
1145 continue
1146 }
1147
1148 if linkKey.Path[0].Unbox().ScalarString() != "root" {
1149 continue
1150 }
1151
1152 if !hasBoard(g.RootBoard(), linkKey.IDA()) {
1153 c.errorf(obj.Link.MapKey, "linked board not found")
1154 continue
1155 }
1156 }
1157 for _, b := range g.Layers {
1158 c.validateBoardLinks(b)
1159 }
1160 for _, b := range g.Scenarios {
1161 c.validateBoardLinks(b)
1162 }
1163 for _, b := range g.Steps {
1164 c.validateBoardLinks(b)
1165 }
1166 }
1167
1168 func hasBoard(root *d2graph.Graph, ida []string) bool {
1169 if len(ida) == 0 {
1170 return true
1171 }
1172 if ida[0] == "root" {
1173 return hasBoard(root, ida[1:])
1174 }
1175 id := ida[0]
1176 if len(ida) == 1 {
1177 return root.Name == id
1178 }
1179 nextID := ida[1]
1180 switch id {
1181 case "layers":
1182 for _, b := range root.Layers {
1183 if b.Name == nextID {
1184 return hasBoard(b, ida[2:])
1185 }
1186 }
1187 case "scenarios":
1188 for _, b := range root.Scenarios {
1189 if b.Name == nextID {
1190 return hasBoard(b, ida[2:])
1191 }
1192 }
1193 case "steps":
1194 for _, b := range root.Steps {
1195 if b.Name == nextID {
1196 return hasBoard(b, ida[2:])
1197 }
1198 }
1199 }
1200 return false
1201 }
1202
1203 func init() {
1204 FullToShortLanguageAliases = make(map[string]string, len(ShortToFullLanguageAliases))
1205 for k, v := range ShortToFullLanguageAliases {
1206 FullToShortLanguageAliases[v] = k
1207 }
1208 }
1209
1210 func d2graphIDA(irIDA []string) (ida []string) {
1211 for _, el := range irIDA {
1212 n := &d2ast.KeyPath{
1213 Path: []*d2ast.StringBox{d2ast.MakeValueBox(d2ast.RawString(el, true)).StringBox()},
1214 }
1215 ida = append(ida, d2format.Format(n))
1216 }
1217 return ida
1218 }
1219
1220
1221 func (c *compiler) preprocessSeqDiagrams(m *d2ir.Map) {
1222 for _, f := range m.Fields {
1223 if f.Name == "shape" && f.Primary_.Value.ScalarString() == d2target.ShapeSequenceDiagram {
1224 c.preprocessEdgeGroup(m, m)
1225 return
1226 }
1227 if f.Map() != nil {
1228 c.preprocessSeqDiagrams(f.Map())
1229 }
1230 }
1231 }
1232
1233 func (c *compiler) preprocessEdgeGroup(seqDiagram, m *d2ir.Map) {
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244 for _, f := range m.Fields {
1245 if isEdgeGroup(f) {
1246 if f.Map() != nil {
1247 c.preprocessEdgeGroup(seqDiagram, f.Map())
1248 }
1249 } else {
1250 if m == seqDiagram {
1251
1252 continue
1253 }
1254 hoistActor(seqDiagram, f)
1255 }
1256 }
1257
1258
1259 for _, e := range m.Edges {
1260 if isCrossEdgeGroupEdge(m, e) {
1261 c.errorf(e.References[0].AST(), "illegal edge between edge groups")
1262 continue
1263 }
1264
1265 if m == seqDiagram {
1266
1267 continue
1268 }
1269
1270 srcParent := seqDiagram
1271 for i, el := range e.ID.SrcPath {
1272 f := srcParent.GetField(el)
1273 if !isEdgeGroup(f) {
1274 for j := 0; j < i+1; j++ {
1275 e.ID.SrcPath = append([]string{"_"}, e.ID.SrcPath...)
1276 e.ID.DstPath = append([]string{"_"}, e.ID.DstPath...)
1277 }
1278 break
1279 }
1280 srcParent = f.Map()
1281 }
1282 }
1283 }
1284
1285 func hoistActor(seqDiagram *d2ir.Map, f *d2ir.Field) {
1286 f2 := seqDiagram.GetField(f.Name)
1287 if f2 == nil {
1288 seqDiagram.Fields = append(seqDiagram.Fields, f.Copy(seqDiagram).(*d2ir.Field))
1289 } else {
1290 d2ir.OverlayField(f2, f)
1291 d2ir.ParentMap(f).DeleteField(f.Name)
1292 }
1293 }
1294
1295 func isCrossEdgeGroupEdge(m *d2ir.Map, e *d2ir.Edge) bool {
1296 srcParent := m
1297 for _, el := range e.ID.SrcPath {
1298 f := srcParent.GetField(el)
1299 if f == nil {
1300
1301 break
1302 }
1303 if isEdgeGroup(f) {
1304 return true
1305 }
1306 srcParent = f.Map()
1307 }
1308
1309 dstParent := m
1310 for _, el := range e.ID.DstPath {
1311 f := dstParent.GetField(el)
1312 if f == nil {
1313
1314 break
1315 }
1316 if isEdgeGroup(f) {
1317 return true
1318 }
1319 dstParent = f.Map()
1320 }
1321
1322 return false
1323 }
1324
1325 func isEdgeGroup(n d2ir.Node) bool {
1326 return n.Map().EdgeCountRecursive() > 0
1327 }
1328
1329 func parentSeqDiagram(n d2ir.Node) *d2ir.Map {
1330 for {
1331 m := d2ir.ParentMap(n)
1332 if m == nil {
1333 return nil
1334 }
1335 for _, f := range m.Fields {
1336 if f.Name == "shape" && f.Primary_.Value.ScalarString() == d2target.ShapeSequenceDiagram {
1337 return m
1338 }
1339 }
1340 n = m
1341 }
1342 }
1343
1344 func compileConfig(ir *d2ir.Map) (*d2target.Config, error) {
1345 f := ir.GetField("vars", "d2-config")
1346 if f == nil || f.Map() == nil {
1347 return nil, nil
1348 }
1349
1350 configMap := f.Map()
1351
1352 config := &d2target.Config{}
1353
1354 f = configMap.GetField("sketch")
1355 if f != nil {
1356 val, _ := strconv.ParseBool(f.Primary().Value.ScalarString())
1357 config.Sketch = &val
1358 }
1359
1360 f = configMap.GetField("theme-id")
1361 if f != nil {
1362 val, _ := strconv.Atoi(f.Primary().Value.ScalarString())
1363 config.ThemeID = go2.Pointer(int64(val))
1364 }
1365
1366 f = configMap.GetField("dark-theme-id")
1367 if f != nil {
1368 val, _ := strconv.Atoi(f.Primary().Value.ScalarString())
1369 config.DarkThemeID = go2.Pointer(int64(val))
1370 }
1371
1372 f = configMap.GetField("pad")
1373 if f != nil {
1374 val, _ := strconv.Atoi(f.Primary().Value.ScalarString())
1375 config.Pad = go2.Pointer(int64(val))
1376 }
1377
1378 f = configMap.GetField("layout-engine")
1379 if f != nil {
1380 config.LayoutEngine = go2.Pointer(f.Primary().Value.ScalarString())
1381 }
1382
1383 f = configMap.GetField("theme-overrides")
1384 if f != nil {
1385 overrides, err := compileThemeOverrides(f.Map())
1386 if err != nil {
1387 return nil, err
1388 }
1389 config.ThemeOverrides = overrides
1390 }
1391 f = configMap.GetField("dark-theme-overrides")
1392 if f != nil {
1393 overrides, err := compileThemeOverrides(f.Map())
1394 if err != nil {
1395 return nil, err
1396 }
1397 config.DarkThemeOverrides = overrides
1398 }
1399
1400 return config, nil
1401 }
1402
1403 func compileThemeOverrides(m *d2ir.Map) (*d2target.ThemeOverrides, error) {
1404 if m == nil {
1405 return nil, nil
1406 }
1407 themeOverrides := d2target.ThemeOverrides{}
1408
1409 err := &d2parser.ParseError{}
1410 FOR:
1411 for _, f := range m.Fields {
1412 switch strings.ToUpper(f.Name) {
1413 case "N1":
1414 themeOverrides.N1 = go2.Pointer(f.Primary().Value.ScalarString())
1415 case "N2":
1416 themeOverrides.N2 = go2.Pointer(f.Primary().Value.ScalarString())
1417 case "N3":
1418 themeOverrides.N3 = go2.Pointer(f.Primary().Value.ScalarString())
1419 case "N4":
1420 themeOverrides.N4 = go2.Pointer(f.Primary().Value.ScalarString())
1421 case "N5":
1422 themeOverrides.N5 = go2.Pointer(f.Primary().Value.ScalarString())
1423 case "N6":
1424 themeOverrides.N6 = go2.Pointer(f.Primary().Value.ScalarString())
1425 case "N7":
1426 themeOverrides.N7 = go2.Pointer(f.Primary().Value.ScalarString())
1427 case "B1":
1428 themeOverrides.B1 = go2.Pointer(f.Primary().Value.ScalarString())
1429 case "B2":
1430 themeOverrides.B2 = go2.Pointer(f.Primary().Value.ScalarString())
1431 case "B3":
1432 themeOverrides.B3 = go2.Pointer(f.Primary().Value.ScalarString())
1433 case "B4":
1434 themeOverrides.B4 = go2.Pointer(f.Primary().Value.ScalarString())
1435 case "B5":
1436 themeOverrides.B5 = go2.Pointer(f.Primary().Value.ScalarString())
1437 case "B6":
1438 themeOverrides.B6 = go2.Pointer(f.Primary().Value.ScalarString())
1439 case "AA2":
1440 themeOverrides.AA2 = go2.Pointer(f.Primary().Value.ScalarString())
1441 case "AA4":
1442 themeOverrides.AA4 = go2.Pointer(f.Primary().Value.ScalarString())
1443 case "AA5":
1444 themeOverrides.AA5 = go2.Pointer(f.Primary().Value.ScalarString())
1445 case "AB4":
1446 themeOverrides.AB4 = go2.Pointer(f.Primary().Value.ScalarString())
1447 case "AB5":
1448 themeOverrides.AB5 = go2.Pointer(f.Primary().Value.ScalarString())
1449 default:
1450 err.Errors = append(err.Errors, d2parser.Errorf(f.LastPrimaryKey(), fmt.Sprintf(`"%s" is not a valid theme code`, f.Name)).(d2ast.Error))
1451 continue FOR
1452 }
1453 if !go2.Contains(color.NamedColors, strings.ToLower(f.Primary().Value.ScalarString())) && !color.ColorHexRegex.MatchString(f.Primary().Value.ScalarString()) {
1454 err.Errors = append(err.Errors, d2parser.Errorf(f.LastPrimaryKey(), fmt.Sprintf(`expected "%s" to be a valid named color ("orange") or a hex code ("#f0ff3a")`, f.Name)).(d2ast.Error))
1455 }
1456 }
1457
1458 if !err.Empty() {
1459 return nil, err
1460 }
1461
1462 if themeOverrides != (d2target.ThemeOverrides{}) {
1463 return &themeOverrides, nil
1464 }
1465 return nil, nil
1466 }
1467
View as plain text