Skip to content

Commit ec72178

Browse files
Merge pull request #39 from AlexanderGrooff/sequence-diagram-web
add sequence diagram to web interface
2 parents 4f9f036 + 7043129 commit ec72178

10 files changed

Lines changed: 811 additions & 237 deletions

File tree

cmd/arrow.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ func (g *graph) drawPath(path []gridCoord) (*drawing, [][]drawingCoord, []direct
159159
continue
160160
}
161161
dir := determineDirection(genericCoord(previousCoord), genericCoord(nextCoord))
162-
s := d.drawLine(previousDrawingCoord, nextDrawingCoord, 1, -1)
162+
s := g.drawLine(d, previousDrawingCoord, nextDrawingCoord, 1, -1)
163163
if len(s) == 0 {
164164
// drawLine may return no coords if offsets collapse the line. Use at least one point so arrow and junction logic
165165
// can still infer a direction.
@@ -178,7 +178,7 @@ func (g *graph) drawBoxStart(path []gridCoord, firstLine []drawingCoord) *drawin
178178
dir := determineDirection(genericCoord(path[0]), genericCoord(path[1]))
179179
log.Debugf("Drawing box start at %v with direction %v for line %v", from, dir, path)
180180

181-
if useAscii {
181+
if g.useAscii {
182182
return &d
183183
}
184184

@@ -208,7 +208,7 @@ func (g *graph) drawArrowHead(line []drawingCoord, fallback direction) *drawing
208208
}
209209

210210
var char string
211-
if !useAscii {
211+
if !g.useAscii {
212212
switch dir {
213213
case Up:
214214
char = "▲"
@@ -291,7 +291,7 @@ func (g *graph) drawCorners(path []gridCoord) *drawing {
291291
nextDir := determineDirection(genericCoord(coord), genericCoord(path[idx+1]))
292292

293293
var corner string
294-
if !useAscii {
294+
if !g.useAscii {
295295
switch {
296296
case (prevDir == Right && nextDir == Down) || (prevDir == Up && nextDir == Left):
297297
corner = "┐"

cmd/diagram.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ func (gd *GraphDiagram) Render(config *diagram.Config) (string, error) {
8383
styleType = "cli"
8484
}
8585
gd.properties.styleType = styleType
86+
gd.properties.useAscii = config.UseAscii
8687

8788
return drawMap(gd.properties), nil
8889
}

cmd/draw.go

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type styleClass struct {
3535

3636
func (g *graph) drawNode(n *node) {
3737
log.Debug("Drawing node ", n.name, " at ", *n.drawingCoord)
38-
m := mergeDrawings(g.drawing, *n.drawingCoord, n.drawing)
38+
m := g.mergeDrawings(g.drawing, *n.drawingCoord, n.drawing)
3939
g.drawing = m
4040
}
4141

@@ -55,12 +55,12 @@ func (d *drawing) drawText(start drawingCoord, text string) {
5555
}
5656
}
5757

58-
func (d *drawing) drawLine(from drawingCoord, to drawingCoord, offsetFrom int, offsetTo int) []drawingCoord {
58+
func (g *graph) drawLine(d *drawing, from drawingCoord, to drawingCoord, offsetFrom int, offsetTo int) []drawingCoord {
5959
// Offset determines how far from the actual coord the line should start/stop.
6060
direction := determineDirection(genericCoord(from), genericCoord(to))
6161
drawnCoords := make([]drawingCoord, 0)
6262
log.Debug("Drawing line from ", from, " to ", to, " direction: ", direction, " offsetFrom: ", offsetFrom, " offsetTo: ", offsetTo)
63-
if !useAscii {
63+
if !g.useAscii {
6464
switch direction {
6565
case Up:
6666
for y := from.y - offsetFrom; y >= to.y-offsetTo; y-- {
@@ -155,6 +155,7 @@ func drawMap(properties *graphProperties) string {
155155
g.setStyleClasses(properties)
156156
g.paddingX = properties.paddingX
157157
g.paddingY = properties.paddingY
158+
g.useAscii = properties.useAscii
158159
g.setSubgraphs(properties.subgraphs)
159160
g.createMapping()
160161
d := g.draw()
@@ -182,7 +183,7 @@ func drawBox(n *node, g graph) *drawing {
182183
to := drawingCoord{w, h}
183184
boxDrawing := *(mkDrawing(Max(from.x, to.x), Max(from.y, to.y)))
184185
log.Debug("Drawing box from ", from, " to ", to)
185-
if !useAscii {
186+
if !g.useAscii {
186187
// Draw top border
187188
for x := from.x + 1; x < to.x; x++ {
188189
boxDrawing[x][from.y] = "─" // Horizontal line
@@ -252,7 +253,7 @@ func drawSubgraph(sg *subgraph, g graph) *drawing {
252253

253254
log.Debugf("Drawing subgraph %s from (%d,%d) to (%d,%d)", sg.name, from.x, from.y, to.x, to.y)
254255

255-
if !useAscii {
256+
if !g.useAscii {
256257
// Draw top border
257258
for x := from.x + 1; x < to.x; x++ {
258259
subgraphDrawing[x][from.y] = "─"
@@ -351,7 +352,15 @@ func wrapTextInColor(text, c, styleType string) string {
351352
func (d *drawing) increaseSize(x int, y int) {
352353
currSizeX, currSizeY := getDrawingSize(d)
353354
drawingWithNewSize := mkDrawing(Max(x, currSizeX), Max(y, currSizeY))
354-
*d = *mergeDrawings(drawingWithNewSize, drawingCoord{0, 0}, d)
355+
// For increaseSize, we don't need junction merging, so just copy the drawing
356+
for x := 0; x < len(*drawingWithNewSize); x++ {
357+
for y := 0; y < len((*drawingWithNewSize)[0]); y++ {
358+
if x < len(*d) && y < len((*d)[0]) {
359+
(*drawingWithNewSize)[x][y] = (*d)[x][y]
360+
}
361+
}
362+
}
363+
*d = *drawingWithNewSize
355364
}
356365

357366
func (g *graph) setDrawingSizeToGridConstraints() {
@@ -393,7 +402,7 @@ func mergeJunctions(c1, c2 string) string {
393402
return c1
394403
}
395404

396-
func mergeDrawings(baseDrawing *drawing, mergeCoord drawingCoord, drawings ...*drawing) *drawing {
405+
func (g *graph) mergeDrawings(baseDrawing *drawing, mergeCoord drawingCoord, drawings ...*drawing) *drawing {
397406
// Find the maximum dimensions
398407
maxX, maxY := getDrawingSize(baseDrawing)
399408
for _, d := range drawings {
@@ -421,7 +430,7 @@ func mergeDrawings(baseDrawing *drawing, mergeCoord drawingCoord, drawings ...*d
421430
c := (*d)[x][y]
422431
if c != " " {
423432
currentChar := (*mergedDrawing)[x+mergeCoord.x][y+mergeCoord.y]
424-
if !useAscii && isJunctionChar(c) && isJunctionChar(currentChar) {
433+
if !g.useAscii && isJunctionChar(c) && isJunctionChar(currentChar) {
425434
(*mergedDrawing)[x+mergeCoord.x][y+mergeCoord.y] = mergeJunctions(currentChar, c)
426435
} else {
427436
(*mergedDrawing)[x+mergeCoord.x][y+mergeCoord.y] = c
@@ -515,7 +524,15 @@ func (d drawing) debugDrawingWrapper() *drawing {
515524
debugDrawing[0][y+1] = fmt.Sprintf("%2d", y)
516525
}
517526

518-
return mergeDrawings(&debugDrawing, drawingCoord{1, 1}, &d)
527+
// For debug wrapper, we don't need junction merging, so just copy
528+
for x := 0; x < len(debugDrawing); x++ {
529+
for y := 0; y < len(debugDrawing[0]); y++ {
530+
if x >= 2 && y >= 1 && x-2 < len(d) && y-1 < len(d[0]) {
531+
debugDrawing[x][y] = d[x-2][y-1]
532+
}
533+
}
534+
}
535+
return &debugDrawing
519536
}
520537

521538
func (d drawing) debugCoordWrapper(g graph) *drawing {
@@ -538,5 +555,5 @@ func (d drawing) debugCoordWrapper(g graph) *drawing {
538555
currY += h
539556
}
540557

541-
return mergeDrawings(&debugDrawing, drawingCoord{1, 1}, &d)
558+
return g.mergeDrawings(&debugDrawing, drawingCoord{1, 1}, &d)
542559
}

cmd/graph.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type graph struct {
4343
subgraphs []*subgraph
4444
offsetX int
4545
offsetY int
46+
useAscii bool
4647
}
4748

4849
type subgraph struct {
@@ -550,11 +551,11 @@ func (g *graph) draw() *drawing {
550551
}
551552

552553
// Draw in order
553-
g.drawing = mergeDrawings(g.drawing, drawingCoord{0, 0}, lineDrawings...)
554-
g.drawing = mergeDrawings(g.drawing, drawingCoord{0, 0}, cornerDrawings...)
555-
g.drawing = mergeDrawings(g.drawing, drawingCoord{0, 0}, arrowHeadDrawings...)
556-
g.drawing = mergeDrawings(g.drawing, drawingCoord{0, 0}, boxStartDrawings...)
557-
g.drawing = mergeDrawings(g.drawing, drawingCoord{0, 0}, labelDrawings...)
554+
g.drawing = g.mergeDrawings(g.drawing, drawingCoord{0, 0}, lineDrawings...)
555+
g.drawing = g.mergeDrawings(g.drawing, drawingCoord{0, 0}, cornerDrawings...)
556+
g.drawing = g.mergeDrawings(g.drawing, drawingCoord{0, 0}, arrowHeadDrawings...)
557+
g.drawing = g.mergeDrawings(g.drawing, drawingCoord{0, 0}, boxStartDrawings...)
558+
g.drawing = g.mergeDrawings(g.drawing, drawingCoord{0, 0}, labelDrawings...)
558559

559560
// Draw subgraph labels LAST so they don't get overwritten by arrows
560561
g.drawSubgraphLabels()
@@ -571,7 +572,7 @@ func (g *graph) drawSubgraphs() {
571572
sgDrawing := drawSubgraph(sg, *g)
572573
// Position the drawing at the subgraph's min coordinates
573574
offset := drawingCoord{sg.minX, sg.minY}
574-
g.drawing = mergeDrawings(g.drawing, offset, sgDrawing)
575+
g.drawing = g.mergeDrawings(g.drawing, offset, sgDrawing)
575576
}
576577
}
577578

@@ -583,7 +584,7 @@ func (g *graph) drawSubgraphLabels() {
583584
continue
584585
}
585586
labelDrawing, offset := drawSubgraphLabel(sg, *g)
586-
g.drawing = mergeDrawings(g.drawing, offset, labelDrawing)
587+
g.drawing = g.mergeDrawings(g.drawing, offset, labelDrawing)
587588
}
588589
}
589590

cmd/graph_test.go

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import (
66
"strings"
77
"testing"
88

9+
"github.com/AlexanderGrooff/mermaid-ascii/internal/diagram"
910
"github.com/AlexanderGrooff/mermaid-ascii/internal/diagram/testutil"
1011
log "github.com/sirupsen/logrus"
1112
)
1213

13-
func verifyMap(t *testing.T, testCaseFile string) {
14+
func verifyMap(t *testing.T, testCaseFile string, useAscii bool) {
1415
tc, err := testutil.ReadTestCase(testCaseFile)
1516
if err != nil {
1617
t.Fatalf("Failed to read test case file: %v", err)
@@ -22,6 +23,7 @@ func verifyMap(t *testing.T, testCaseFile string) {
2223
}
2324
properties.paddingX = tc.PaddingX
2425
properties.paddingY = tc.PaddingY
26+
properties.useAscii = useAscii
2527
actualMap := drawMap(properties)
2628
if tc.Expected != actualMap {
2729
expectedWithSpaces := testutil.VisualizeWhitespace(tc.Expected)
@@ -31,7 +33,6 @@ func verifyMap(t *testing.T, testCaseFile string) {
3133
}
3234

3335
func TestASCII(t *testing.T) {
34-
useAscii = true
3536
dir := "testdata/ascii"
3637
files, err := os.ReadDir(dir)
3738
if err != nil {
@@ -41,14 +42,13 @@ func TestASCII(t *testing.T) {
4142
for _, file := range files {
4243
if !file.IsDir() && strings.HasSuffix(file.Name(), ".txt") {
4344
t.Run(file.Name(), func(t *testing.T) {
44-
verifyMap(t, filepath.Join(dir, file.Name()))
45+
verifyMap(t, filepath.Join(dir, file.Name()), true)
4546
})
4647
}
4748
}
4849
}
4950

5051
func TestExtendedChars(t *testing.T) {
51-
useAscii = false
5252
dir := "testdata/extended-chars"
5353
files, err := os.ReadDir(dir)
5454
if err != nil {
@@ -58,10 +58,59 @@ func TestExtendedChars(t *testing.T) {
5858
for _, file := range files {
5959
if !file.IsDir() && strings.HasSuffix(file.Name(), ".txt") {
6060
t.Run(file.Name(), func(t *testing.T) {
61-
verifyMap(t, filepath.Join(dir, file.Name()))
61+
verifyMap(t, filepath.Join(dir, file.Name()), false)
6262
})
6363
}
6464
}
6565
}
6666

67+
// TestGraphUseAsciiConfig tests that RenderDiagram respects config.UseAscii for graphs
68+
func TestGraphUseAsciiConfig(t *testing.T) {
69+
mermaidInput := `graph LR
70+
A --> B`
71+
72+
// Test with UseAscii = true (should produce ASCII output)
73+
asciiConfig := &diagram.Config{
74+
UseAscii: true,
75+
BoxBorderPadding: 1,
76+
PaddingBetweenX: 5,
77+
PaddingBetweenY: 5,
78+
GraphDirection: "LR",
79+
StyleType: "cli",
80+
}
81+
asciiOutput, err := RenderDiagram(mermaidInput, asciiConfig)
82+
if err != nil {
83+
t.Fatalf("Failed to render with ASCII config: %v", err)
84+
}
85+
86+
// Test with UseAscii = false (should produce Unicode output)
87+
unicodeConfig := &diagram.Config{
88+
UseAscii: false,
89+
BoxBorderPadding: 1,
90+
PaddingBetweenX: 5,
91+
PaddingBetweenY: 5,
92+
GraphDirection: "LR",
93+
StyleType: "cli",
94+
}
95+
unicodeOutput, err := RenderDiagram(mermaidInput, unicodeConfig)
96+
if err != nil {
97+
t.Fatalf("Failed to render with Unicode config: %v", err)
98+
}
99+
100+
// Verify outputs are different
101+
if asciiOutput == unicodeOutput {
102+
t.Errorf("ASCII and Unicode outputs should be different, but they were identical:\n%s", asciiOutput)
103+
}
104+
105+
// Verify ASCII output contains ASCII characters (not Unicode box-drawing)
106+
if strings.Contains(asciiOutput, "┌") || strings.Contains(asciiOutput, "─") || strings.Contains(asciiOutput, "│") {
107+
t.Errorf("ASCII output should not contain Unicode box-drawing characters, but found:\n%s", asciiOutput)
108+
}
109+
110+
// Verify Unicode output contains Unicode characters
111+
if !strings.Contains(unicodeOutput, "┌") && !strings.Contains(unicodeOutput, "─") && !strings.Contains(unicodeOutput, "│") {
112+
t.Errorf("Unicode output should contain Unicode box-drawing characters, but found:\n%s", unicodeOutput)
113+
}
114+
}
115+
67116
// Sequence diagram tests moved to sequence_test.go

cmd/parse.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type graphProperties struct {
1818
paddingX int
1919
paddingY int
2020
subgraphs []*textSubgraph
21+
useAscii bool
2122
}
2223

2324
type textNode struct {

cmd/web.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ func setupRouter() *gin.Engine {
7575
r := gin.Default()
7676

7777
r.LoadHTMLGlob("templates/*")
78+
r.Static("/static", "./static")
7879
r.GET("/", func(c *gin.Context) {
7980
c.HTML(http.StatusOK, "index.tmpl", gin.H{
8081
"Version": getGitVersion(),

0 commit comments

Comments
 (0)