diff --git a/README.md b/README.md
index 16ebd21..036d730 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
# What is markdown package
The Package markdown is a simple markdown builder in golang. The markdown package assembles Markdown using method chaining, not uses a template engine like [html/template](https://pkg.go.dev/html/template). The syntax of Markdown follows **GitHub Markdown**.
-The markdown package was initially developed to save test results in [nao1215/spectest](https://github.com/nao1215/spectest). Therefore, the markdown package implements the features required by spectest. For example, the markdown package supports **mermaid sequence diagrams (entity relationship diagram, sequence diagram, pie chart)**, which was a necessary feature in spectest.
+The markdown package was initially developed to save test results in [nao1215/spectest](https://github.com/nao1215/spectest). Therefore, the markdown package implements the features required by spectest. For example, the markdown package supports **mermaid sequence diagrams (entity relationship diagram, sequence diagram, flowchart, pie chart)**, which was a necessary feature in spectest.
Additionally, complex code that increases the complexity of the library, such as generating nested lists, will not be added. I want to keep this library as simple as possible.
@@ -34,6 +34,7 @@ Additionally, complex code that increases the complexity of the library, such as
- [x] Alerts; NOTE, TIP, IMPORTANT, CAUTION, WARNING
- [x] mermaid sequence diagram
- [x] mermaid entity relationship diagram
+- [x] mermaid flowchart
- [x] mermaid pie chart
### Features not in Markdown syntax
@@ -165,6 +166,7 @@ func main() {
if err != nil {
panic(err)
}
+ defer f.Close()
md.NewMarkdown(f).
H1("go generate example").
@@ -366,6 +368,7 @@ func main() {
if err != nil {
panic(err)
}
+ defer f.Close()
teachers := er.NewEntity(
"teachers",
@@ -523,6 +526,86 @@ erDiagram
}
```
+### Flowchart syntax
+
+```go
+package main
+
+import (
+ "io"
+ "os"
+
+ "github.com/nao1215/markdown"
+ "github.com/nao1215/markdown/mermaid/flowchart"
+)
+
+//go:generate go run main.go
+
+func main() {
+ f, err := os.Create("generated.md")
+ if err != nil {
+ panic(err)
+ }
+ defer f.Close()
+
+ fc := flowchart.NewFlowchart(
+ io.Discard,
+ flowchart.WithTitle("mermaid flowchart builder"),
+ flowchart.WithOrientalTopToBottom(),
+ ).
+ NodeWithText("A", "Node A").
+ StadiumNode("B", "Node B").
+ SubroutineNode("C", "Node C").
+ DatabaseNode("D", "Database").
+ LinkWithArrowHead("A", "B").
+ LinkWithArrowHeadAndText("B", "D", "send original data").
+ LinkWithArrowHead("B", "C").
+ DottedLinkWithText("C", "D", "send filtered data").
+ String()
+
+ err = markdown.NewMarkdown(f).
+ H2("Flowchart").
+ CodeBlocks(markdown.SyntaxHighlightMermaid, fc).
+ Build()
+
+ if err != nil {
+ panic(err)
+ }
+}
+```
+
+Plain text output: [markdown is here](./doc/flowchart/generated.md)
+````
+## Flowchart
+```mermaid
+---
+title: mermaid flowchart builder
+---
+flowchart TB
+ A["Node A"]
+ B(["Node B"])
+ C[["Node C"]]
+ D[("Database")]
+ A-->B
+ B-->|"send original data"|D
+ B-->C
+ C-. "send filtered data" .-> D
+```
+````
+
+Mermaid output:
+```mermaid
+flowchart TB
+ A["Node A"]
+ B(["Node B"])
+ C[["Node C"]]
+ D[("Database")]
+ A-->B
+ B-->|"send original data"|D
+ B-->C
+ C-. "send filtered data" .-> D
+```
+
### Pie chart syntax
```go
@@ -543,6 +626,7 @@ func main() {
if err != nil {
panic(err)
}
+ defer f.Close()
chart := piechart.NewPieChart(
io.Discard,
diff --git a/doc/alert/main.go b/doc/alert/main.go
index abfc534..154d410 100644
--- a/doc/alert/main.go
+++ b/doc/alert/main.go
@@ -16,6 +16,7 @@ func main() {
if err != nil {
panic(err)
}
+ defer f.Close()
if err := md.NewMarkdown(f).
H1("Alert example").
diff --git a/doc/badge/main.go b/doc/badge/main.go
index daea9ea..7939449 100644
--- a/doc/badge/main.go
+++ b/doc/badge/main.go
@@ -16,6 +16,7 @@ func main() {
if err != nil {
panic(err)
}
+ defer f.Close()
if err := md.NewMarkdown(f).
H1("badge example").
diff --git a/doc/er/main.go b/doc/er/main.go
index a7ac35b..6619f73 100644
--- a/doc/er/main.go
+++ b/doc/er/main.go
@@ -17,6 +17,7 @@ func main() {
if err != nil {
panic(err)
}
+ defer f.Close()
teachers := er.NewEntity(
"teachers",
diff --git a/doc/flowchart/generated.md b/doc/flowchart/generated.md
new file mode 100644
index 0000000..e4c1ca2
--- /dev/null
+++ b/doc/flowchart/generated.md
@@ -0,0 +1,15 @@
+## Flowchart
+```mermaid
+---
+title: mermaid flowchart builder
+---
+flowchart TB
+ A["Node A"]
+ B(["Node B"])
+ C[["Node C"]]
+ D[("Database")]
+ A-->B
+ B-->|"send original data"|D
+ B-->C
+ C-. "send filtered data" .-> D
+```
\ No newline at end of file
diff --git a/doc/flowchart/main.go b/doc/flowchart/main.go
new file mode 100644
index 0000000..64a3f1e
--- /dev/null
+++ b/doc/flowchart/main.go
@@ -0,0 +1,46 @@
+//go:build linux || darwin
+
+// Package main is generating flowchart.
+package main
+
+import (
+ "io"
+ "os"
+
+ "github.com/nao1215/markdown"
+ "github.com/nao1215/markdown/mermaid/flowchart"
+)
+
+//go:generate go run main.go
+
+func main() {
+ f, err := os.Create("generated.md")
+ if err != nil {
+ panic(err)
+ }
+ defer f.Close()
+
+ fc := flowchart.NewFlowchart(
+ io.Discard,
+ flowchart.WithTitle("mermaid flowchart builder"),
+ flowchart.WithOrientalTopToBottom(),
+ ).
+ NodeWithText("A", "Node A").
+ StadiumNode("B", "Node B").
+ SubroutineNode("C", "Node C").
+ DatabaseNode("D", "Database").
+ LinkWithArrowHead("A", "B").
+ LinkWithArrowHeadAndText("B", "D", "send original data").
+ LinkWithArrowHead("B", "C").
+ DottedLinkWithText("C", "D", "send filtered data").
+ String()
+
+ err = markdown.NewMarkdown(f).
+ H2("Flowchart").
+ CodeBlocks(markdown.SyntaxHighlightMermaid, fc).
+ Build()
+
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/doc/generate/main.go b/doc/generate/main.go
index a1e5d64..57a9b4c 100644
--- a/doc/generate/main.go
+++ b/doc/generate/main.go
@@ -16,6 +16,7 @@ func main() {
if err != nil {
panic(err)
}
+ defer f.Close()
if err := md.NewMarkdown(f).
H1("go generate example").
diff --git a/doc/piechart/main.go b/doc/piechart/main.go
index 6e5ae67..f61cede 100644
--- a/doc/piechart/main.go
+++ b/doc/piechart/main.go
@@ -18,6 +18,7 @@ func main() {
if err != nil {
panic(err)
}
+ defer f.Close()
chart := piechart.NewPieChart(
io.Discard,
diff --git a/doc/sequence/main.go b/doc/sequence/main.go
index 740fb1d..d76ef63 100644
--- a/doc/sequence/main.go
+++ b/doc/sequence/main.go
@@ -18,6 +18,7 @@ func main() {
if err != nil {
panic(err)
}
+ defer f.Close()
diagram := sequence.NewDiagram(io.Discard).
Participant("Sophia").
diff --git a/internal/lf.go b/internal/lf.go
new file mode 100644
index 0000000..935870d
--- /dev/null
+++ b/internal/lf.go
@@ -0,0 +1,12 @@
+// Package internal package is used to store the internal implementation of the mermaid package.
+package internal
+
+import "runtime"
+
+// LineFeed return line feed for current OS.
+func LineFeed() string {
+ if runtime.GOOS == "windows" {
+ return "\r\n"
+ }
+ return "\n"
+}
diff --git a/internal/lf_test.go b/internal/lf_test.go
new file mode 100644
index 0000000..122dea4
--- /dev/null
+++ b/internal/lf_test.go
@@ -0,0 +1,28 @@
+// Package internal package is used to store the internal implementation of the mermaid package.
+package internal
+
+import (
+ "runtime"
+ "testing"
+)
+
+func TestLineFeed(t *testing.T) {
+ t.Parallel()
+
+ t.Run("should return line feed for current OS", func(t *testing.T) {
+ t.Parallel()
+
+ got := LineFeed()
+
+ switch runtime.GOOS {
+ case "windows":
+ if got != "\r\n" {
+ t.Errorf("expected \\r\\n, but got %s", got)
+ }
+ default:
+ if got != "\n" {
+ t.Errorf("expected \\n, but got %s", got)
+ }
+ }
+ })
+}
diff --git a/markdown.go b/markdown.go
index 592438d..76570c2 100644
--- a/markdown.go
+++ b/markdown.go
@@ -4,9 +4,9 @@ package markdown
import (
"fmt"
"io"
- "runtime"
"strings"
+ "github.com/nao1215/markdown/internal"
"github.com/olekukonko/tablewriter"
)
@@ -126,7 +126,7 @@ func NewMarkdown(w io.Writer) *Markdown {
// String returns markdown text.
func (m *Markdown) String() string {
- return strings.Join(m.body, lineFeed())
+ return strings.Join(m.body, internal.LineFeed())
}
// Error returns error.
@@ -238,7 +238,8 @@ func (m *Markdown) H6f(format string, args ...interface{}) *Markdown {
func (m *Markdown) Details(summary, text string) *Markdown {
m.body = append(
m.body,
- fmt.Sprintf("%s
%s%s%s ", summary, lineFeed(), text, lineFeed()))
+ fmt.Sprintf("%s
%s%s%s ",
+ summary, internal.LineFeed(), text, internal.LineFeed()))
return m
}
@@ -288,7 +289,7 @@ func (m *Markdown) CheckBox(set []CheckBoxSet) *Markdown {
// Blockquote is markdown blockquote.
// If you set text "Hello", it will be converted to "> Hello".
func (m *Markdown) Blockquote(text string) *Markdown {
- lines := strings.Split(text, lineFeed())
+ lines := strings.Split(text, internal.LineFeed())
for _, line := range lines {
m.body = append(m.body, fmt.Sprintf("> %s", line))
}
@@ -302,7 +303,7 @@ func (m *Markdown) Blockquote(text string) *Markdown {
// ```".
func (m *Markdown) CodeBlocks(lang SyntaxHighlight, text string) *Markdown {
m.body = append(m.body,
- fmt.Sprintf("```%s%s%s%s```", lang, lineFeed(), text, lineFeed()))
+ fmt.Sprintf("```%s%s%s%s```", lang, internal.LineFeed(), text, internal.LineFeed()))
return m
}
@@ -345,7 +346,7 @@ func (m *Markdown) Table(t TableSet) *Markdown {
buf := &strings.Builder{}
table := tablewriter.NewWriter(buf)
- table.SetNewLine(lineFeed())
+ table.SetNewLine(internal.LineFeed())
table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false})
table.SetCenterSeparator("|")
table.SetHeader(t.Header)
@@ -379,7 +380,7 @@ func (m *Markdown) CustomTable(t TableSet, options TableOptions) *Markdown {
buf := &strings.Builder{}
table := tablewriter.NewWriter(buf)
- table.SetNewLine(lineFeed())
+ table.SetNewLine(internal.LineFeed())
table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false})
table.SetCenterSeparator("|")
table.SetAutoWrapText(options.AutoWrapText)
@@ -401,11 +402,3 @@ func (m *Markdown) LF() *Markdown {
m.body = append(m.body, " ")
return m
}
-
-// lineFeed return line feed for current OS.
-func lineFeed() string {
- if runtime.GOOS == "windows" {
- return "\r\n"
- }
- return "\n"
-}
diff --git a/markdown_test.go b/markdown_test.go
index 77eeda2..7c3fca6 100644
--- a/markdown_test.go
+++ b/markdown_test.go
@@ -8,6 +8,7 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/nao1215/markdown/internal"
)
func TestPlainText(t *testing.T) {
@@ -117,7 +118,7 @@ func TestMarkdownDetailsf(t *testing.T) {
m := NewMarkdown(os.Stdout)
m.Detailsf("Hello", "Good %s", "World")
- want := fmt.Sprintf("Hello
%sGood World%s ", lineFeed(), lineFeed())
+ want := fmt.Sprintf("Hello
%sGood World%s ", internal.LineFeed(), internal.LineFeed())
got := m.body[0]
if diff := cmp.Diff(want, got); diff != "" {
@@ -189,7 +190,7 @@ func TestMarkdownBlockquote(t *testing.T) {
t.Parallel()
m := NewMarkdown(os.Stdout)
- m.Blockquote(fmt.Sprintf("%s%s%s%s%s", "Hello", lineFeed(), "Good", lineFeed(), "World"))
+ m.Blockquote(fmt.Sprintf("%s%s%s%s%s", "Hello", internal.LineFeed(), "Good", internal.LineFeed(), "World"))
want := []string{
"> Hello",
"> Good",
@@ -209,7 +210,7 @@ func TestMarkdownCodeBlocks(t *testing.T) {
m := NewMarkdown(os.Stdout)
m.CodeBlocks(SyntaxHighlightGo, "Hello")
- want := []string{fmt.Sprintf("```go%sHello%s```", lineFeed(), lineFeed())}
+ want := []string{fmt.Sprintf("```go%sHello%s```", internal.LineFeed(), internal.LineFeed())}
got := m.body
if diff := cmp.Diff(want, got); diff != "" {
@@ -279,7 +280,7 @@ func TestMarkdownTable(t *testing.T) {
m.Table(set)
want := []string{
fmt.Sprintf("| NAME | AGE |%s|-------|-----|%s| David | 23 |%s",
- lineFeed(), lineFeed(), lineFeed()),
+ internal.LineFeed(), internal.LineFeed(), internal.LineFeed()),
}
got := m.body
@@ -388,7 +389,7 @@ func TestMarkdownCustomTable(t *testing.T) {
})
want := []string{
fmt.Sprintf("| Name | Age |%s|-------|-----|%s| David | 23 |%s",
- lineFeed(), lineFeed(), lineFeed()),
+ internal.LineFeed(), internal.LineFeed(), internal.LineFeed()),
}
got := m.body
diff --git a/mermaid/er/entity.go b/mermaid/er/entity.go
index ae2b642..1637e81 100644
--- a/mermaid/er/entity.go
+++ b/mermaid/er/entity.go
@@ -3,6 +3,8 @@ package er
import (
"fmt"
"strings"
+
+ "github.com/nao1215/markdown/internal"
)
// Entity is a entity of entity relationship.
@@ -24,9 +26,9 @@ func (e *Entity) string() string {
"%s%s {%s%s%s%s}",
" ", // indent
e.Name,
- lineFeed(),
- strings.Join(attrs, lineFeed()),
- lineFeed(),
+ internal.LineFeed(),
+ strings.Join(attrs, internal.LineFeed()),
+ internal.LineFeed(),
" ", // indent
)
}
diff --git a/mermaid/er/entity_relationship.go b/mermaid/er/entity_relationship.go
index b6976f1..dc1ee91 100644
--- a/mermaid/er/entity_relationship.go
+++ b/mermaid/er/entity_relationship.go
@@ -4,10 +4,11 @@ package er
import (
"fmt"
"io"
- "runtime"
"sort"
"strings"
"sync"
+
+ "github.com/nao1215/markdown/internal"
)
// Diagram is a entity relationship diagram builder.
@@ -42,8 +43,8 @@ func NewDiagram(w io.Writer, opts ...Option) *Diagram {
// String returns the entity relationship diagram body.
func (d *Diagram) String() string {
- s := strings.Join(d.body, lineFeed())
- s += lineFeed()
+ s := strings.Join(d.body, internal.LineFeed())
+ s += internal.LineFeed()
entities := make([]Entity, 0)
d.entities.Range(func(_, value interface{}) bool {
@@ -60,7 +61,7 @@ func (d *Diagram) String() string {
})
for _, e := range entities {
- s += e.string() + lineFeed()
+ s += e.string() + internal.LineFeed()
}
return s
}
@@ -75,11 +76,3 @@ func (d *Diagram) Build() error {
}
return nil
}
-
-// lineFeed return line feed for current OS.
-func lineFeed() string {
- if runtime.GOOS == "windows" {
- return "\r\n"
- }
- return "\n"
-}
diff --git a/mermaid/flowchart/config.go b/mermaid/flowchart/config.go
new file mode 100644
index 0000000..0f19e89
--- /dev/null
+++ b/mermaid/flowchart/config.go
@@ -0,0 +1,68 @@
+package flowchart
+
+const (
+ // noTitle is a constant for no title.
+ noTitle string = ""
+)
+
+// config is a flowchart configuration.
+type config struct {
+ // title is the title of the flowchart.
+ title string
+ // oriental is the oriental of the flowchart.
+ // Default is top to bottom.
+ oriental oriental
+}
+
+// newConfig returns a new Config with default values.
+func newConfig() *config {
+ return &config{
+ oriental: tb,
+ }
+}
+
+// Option sets the options for the Flowchart struct.
+type Option func(*config)
+
+// WithTitle sets the title configuration.
+func WithTitle(title string) Option {
+ return func(c *config) {
+ c.title = title
+ }
+}
+
+// WithOrientalTopToBottom sets the oriental configuration to top to bottom.
+func WithOrientalTopToBottom() Option {
+ return func(c *config) {
+ c.oriental = tb
+ }
+}
+
+// WithOrientalTopDown sets the oriental configuration to top down.
+// Same as top to bottom.
+func WithOrientalTopDown() Option {
+ return func(c *config) {
+ c.oriental = td
+ }
+}
+
+// WithOrientalBottomToTop sets the oriental configuration to bottom to top.
+func WithOrientalBottomToTop() Option {
+ return func(c *config) {
+ c.oriental = bt
+ }
+}
+
+// WithOrientalRightToLeft sets the oriental configuration to right to left.
+func WithOrientalRightToLeft() Option {
+ return func(c *config) {
+ c.oriental = rl
+ }
+}
+
+// WithOrientalLeftToRight sets the oriental configuration to left to right.
+func WithOrientalLeftToRight() Option {
+ return func(c *config) {
+ c.oriental = lr
+ }
+}
diff --git a/mermaid/flowchart/flowchart.go b/mermaid/flowchart/flowchart.go
new file mode 100644
index 0000000..e745b1b
--- /dev/null
+++ b/mermaid/flowchart/flowchart.go
@@ -0,0 +1,61 @@
+// Package flowchart provides a simple way to create flowcharts in mermaid syntax.
+package flowchart
+
+import (
+ "fmt"
+ "io"
+ "strings"
+
+ "github.com/nao1215/markdown/internal"
+)
+
+// Flowchart is a flowchart builder.
+type Flowchart struct {
+ // body is flowchart body.
+ body []string
+ // dest is output destination for flowchart body.
+ dest io.Writer
+ // err manages errors that occur in all parts of the flowchart building.
+ err error
+ // config is the configuration for the flowchart.
+ config *config
+}
+
+// NewFlowchart returns a new Flowchart.
+func NewFlowchart(w io.Writer, opts ...Option) *Flowchart {
+ c := newConfig()
+
+ for _, opt := range opts {
+ opt(c)
+ }
+
+ lines := []string{}
+ if strings.TrimSpace(c.title) != noTitle {
+ lines = append(lines, "---")
+ lines = append(lines, fmt.Sprintf("title: %s", c.title))
+ lines = append(lines, "---")
+ }
+ lines = append(lines, fmt.Sprintf("flowchart %s", c.oriental.string()))
+
+ return &Flowchart{
+ body: lines,
+ dest: w,
+ config: c,
+ }
+}
+
+// String returns the flowchart body.
+func (f *Flowchart) String() string {
+ return strings.Join(f.body, internal.LineFeed())
+}
+
+// Build writes the flowchart body to the output destination.
+func (f *Flowchart) Build() error {
+ if _, err := fmt.Fprint(f.dest, f.String()); err != nil {
+ if f.err != nil {
+ return fmt.Errorf("failed to write: %w: %s", err, f.err.Error()) //nolint:wrapcheck
+ }
+ return fmt.Errorf("failed to write: %w", err)
+ }
+ return nil
+}
diff --git a/mermaid/flowchart/flowchart_test.go b/mermaid/flowchart/flowchart_test.go
new file mode 100644
index 0000000..1483d36
--- /dev/null
+++ b/mermaid/flowchart/flowchart_test.go
@@ -0,0 +1,252 @@
+// Package flowchart provides a simple way to create flowcharts in mermaid syntax.
+package flowchart
+
+import (
+ "bytes"
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+)
+
+func TestFlowchart_Build(t *testing.T) {
+ t.Parallel()
+
+ t.Run("Build a flowchart with title", func(t *testing.T) {
+ t.Parallel()
+
+ b := new(bytes.Buffer)
+
+ f := NewFlowchart(
+ b,
+ WithTitle("mermaid flowchart builder"),
+ WithOrientalTopToBottom(),
+ ).
+ NodeWithText("A", "Node A").
+ StadiumNode("B", "Node B").
+ SubroutineNode("C", "Node C").
+ DatabaseNode("D", "Database").
+ LinkWithArrowHead("A", "B").
+ LinkWithArrowHeadAndText("B", "D", "send original data").
+ LinkWithArrowHead("B", "C").
+ DottedLinkWithText("C", "D", "send filtered data")
+
+ if err := f.Build(); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ want := `---
+title: mermaid flowchart builder
+---
+flowchart TB
+ A["Node A"]
+ B(["Node B"])
+ C[["Node C"]]
+ D[("Database")]
+ A-->B
+ B-->|"send original data"|D
+ B-->C
+ C-. "send filtered data" .-> D`
+
+ got := strings.ReplaceAll(b.String(), "\r\n", "\n")
+
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Errorf("value is mismatch (-want +got):%s", diff)
+ }
+ })
+
+ t.Run("Build a flowchart, top to bottom", func(t *testing.T) {
+ t.Parallel()
+
+ b := new(bytes.Buffer)
+
+ f := NewFlowchart(
+ b,
+ WithOrientalTopToBottom(),
+ ).NodeWithText("A", "Node A")
+
+ if err := f.Build(); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ want := `flowchart TB
+ A["Node A"]`
+ got := strings.ReplaceAll(b.String(), "\r\n", "\n")
+
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Errorf("value is mismatch (-want +got):%s", diff)
+ }
+ })
+
+ t.Run("Build a flowchart, top down", func(t *testing.T) {
+ t.Parallel()
+ b := new(bytes.Buffer)
+
+ f := NewFlowchart(
+ b,
+ WithOrientalTopDown(),
+ ).NodeWithText("A", "Node A")
+
+ if err := f.Build(); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ want := `flowchart TD
+ A["Node A"]`
+ got := strings.ReplaceAll(b.String(), "\r\n", "\n")
+
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Errorf("value is mismatch (-want +got):%s", diff)
+ }
+ })
+
+ t.Run("Build a flowchart, bottom to top", func(t *testing.T) {
+ t.Parallel()
+
+ b := new(bytes.Buffer)
+
+ f := NewFlowchart(
+ b,
+ WithOrientalBottomToTop(),
+ ).NodeWithText("A", "Node A")
+
+ if err := f.Build(); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ want := `flowchart BT
+ A["Node A"]`
+ got := strings.ReplaceAll(b.String(), "\r\n", "\n")
+
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Errorf("value is mismatch (-want +got):%s", diff)
+ }
+ })
+
+ t.Run("Build a flowchart, right to left", func(t *testing.T) {
+ t.Parallel()
+
+ b := new(bytes.Buffer)
+
+ f := NewFlowchart(
+ b,
+ WithOrientalRightToLeft(),
+ ).NodeWithText("A", "Node A")
+
+ if err := f.Build(); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ want := `flowchart RL
+ A["Node A"]`
+ got := strings.ReplaceAll(b.String(), "\r\n", "\n")
+
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Errorf("value is mismatch (-want +got):%s", diff)
+ }
+ })
+
+ t.Run("Build a flowchart, left to right", func(t *testing.T) {
+ t.Parallel()
+
+ b := new(bytes.Buffer)
+
+ f := NewFlowchart(
+ b,
+ WithOrientalLeftToRight(),
+ ).NodeWithText("A", "Node A")
+
+ if err := f.Build(); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ want := `flowchart LR
+ A["Node A"]`
+ got := strings.ReplaceAll(b.String(), "\r\n", "\n")
+
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Errorf("value is mismatch (-want +got):%s", diff)
+ }
+ })
+
+ t.Run("Build a flowchart with all node and link", func(t *testing.T) {
+ t.Parallel()
+
+ b := new(bytes.Buffer)
+
+ f := NewFlowchart(
+ b,
+ WithOrientalTopToBottom(),
+ ).
+ Node("A").
+ NodeWithText("B", "Node B").
+ NodeWithMarkdown("C", "**Node C**").
+ NodeWithNewLines("D", `Node
+D`).RoundEdgesNode("E", "Node E").
+ StadiumNode("F", "Node F").
+ SubroutineNode("G", "Node G").
+ CylindricalNode("H", "Node H").
+ DatabaseNode("I", "Database").
+ CircleNode("J", "Node J").
+ AsymmetricNode("K", "Node K").
+ RhombusNode("L", "Node L").
+ HexagonNode("M", "Node M").
+ ParallelogramNode("N", "Node N").
+ ParallelogramAltNode("O", "Node O").
+ TrapezoidNode("P", "Node P").
+ TrapezoidAltNode("Q", "Node Q").
+ DoubleCircleNode("R", "Node R").
+ LinkWithArrowHead("A", "B").
+ LinkWithArrowHeadAndText("B", "C", "send").
+ OpenLink("C", "D").
+ OpenLinkWithText("D", "E", "send").
+ DottedLink("E", "F").
+ DottedLinkWithText("F", "G", "send").
+ ThickLink("G", "H").
+ ThickLinkWithText("H", "I", "send").
+ InvisibleLink("I", "J")
+
+ if err := f.Build(); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ want := `flowchart TB
+ A
+ B["Node B"]
+ `
+ want += fmt.Sprintf("C[\"`**Node C**`\"]\n") //nolint:gosimple
+ want += fmt.Sprintf("\tD[\"`Node\n") //nolint:gosimple
+ want += "D`\"]"
+ want += `
+ E("Node E")
+ F(["Node F"])
+ G[["Node G"]]
+ H[("Node H")]
+ I[("Database")]
+ J(("Node J"))
+ K>"Node K"]
+ L{"Node L"}
+ M{{"Node M"}}
+ N[/"Node N"/]
+ O[\"Node O"\]
+ P[/"Node P"\]
+ Q[\"Node Q"/]
+ R((("Node R")))
+ A-->B
+ B-->|"send"|C
+ C --- D
+ D---|"send"|E
+ E-.->F
+ F-. "send" .-> G
+ G ==> H
+ H == "send" ==> I
+ I ~~~ J`
+
+ got := strings.ReplaceAll(b.String(), "\r\n", "\n")
+
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Errorf("value is mismatch (-want +got):%s", diff)
+ }
+ })
+}
diff --git a/mermaid/flowchart/link.go b/mermaid/flowchart/link.go
new file mode 100644
index 0000000..337668b
--- /dev/null
+++ b/mermaid/flowchart/link.go
@@ -0,0 +1,57 @@
+package flowchart
+
+import "fmt"
+
+// LinkWithArrowHead adds a link with an arrow head to the flowchart.
+func (f *Flowchart) LinkWithArrowHead(from, to string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s-->%s", from, to))
+ return f
+}
+
+// LinkWithArrowHeadAndText adds a link with an arrow head and text to the flowchart.
+func (f *Flowchart) LinkWithArrowHeadAndText(from, to, text string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s-->|\"%s\"|%s", from, text, to))
+ return f
+}
+
+// OpenLink adds an open link to the flowchart.
+func (f *Flowchart) OpenLink(from, to string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s --- %s", from, to))
+ return f
+}
+
+// OpenLinkWithText adds an open link with text to the flowchart.
+func (f *Flowchart) OpenLinkWithText(from, to, text string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s---|\"%s\"|%s", from, text, to))
+ return f
+}
+
+// DottedLink adds a dotted link to the flowchart.
+func (f *Flowchart) DottedLink(from, to string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s-.->%s", from, to))
+ return f
+}
+
+// DottedLinkWithText adds a dotted link with text to the flowchart.
+func (f *Flowchart) DottedLinkWithText(from, to, text string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s-. \"%s\" .-> %s", from, text, to))
+ return f
+}
+
+// ThickLink adds a thick link to the flowchart.
+func (f *Flowchart) ThickLink(from, to string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s ==> %s", from, to))
+ return f
+}
+
+// ThickLinkWithText adds a thick link with text to the flowchart.
+func (f *Flowchart) ThickLinkWithText(from, to, text string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s == \"%s\" ==> %s", from, text, to))
+ return f
+}
+
+// InvisibleLink adds an invisible link to the flowchart.
+func (f *Flowchart) InvisibleLink(from, to string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s ~~~ %s", from, to))
+ return f
+}
diff --git a/mermaid/flowchart/node.go b/mermaid/flowchart/node.go
new file mode 100644
index 0000000..dc0fc80
--- /dev/null
+++ b/mermaid/flowchart/node.go
@@ -0,0 +1,112 @@
+package flowchart
+
+import "fmt"
+
+// Node adds a node to the flowchart.
+func (f *Flowchart) Node(name string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s", name))
+ return f
+}
+
+// NodeWithText adds a node with text to the flowchart.
+// Unicode characters are supported.
+func (f *Flowchart) NodeWithText(name, text string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s[\"%s\"]", name, text))
+ return f
+}
+
+// NodeWithMarkdown adds a node with markdown text to the flowchart.
+func (f *Flowchart) NodeWithMarkdown(name, markdownText string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s[\"`%s`\"]", name, markdownText))
+ return f
+}
+
+// NodeWithNewLines adds a node with new lines to the flowchart.
+func (f *Flowchart) NodeWithNewLines(name, textWithNewLines string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s[\"`%s`\"]", name, textWithNewLines))
+ return f
+}
+
+// RoundEdgesNode adds a node with round edges to the flowchart.
+func (f *Flowchart) RoundEdgesNode(name, text string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s(\"%s\")", name, text))
+ return f
+}
+
+// StadiumNode adds a node with stadium shape to the flowchart.
+func (f *Flowchart) StadiumNode(name, text string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s([\"%s\"])", name, text))
+ return f
+}
+
+// SubroutineNode adds a node with subroutine shape to the flowchart.
+func (f *Flowchart) SubroutineNode(name, text string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s[[\"%s\"]]", name, text))
+ return f
+}
+
+// CylindricalNode adds a node with cylindrical shape to the flowchart.
+func (f *Flowchart) CylindricalNode(name, text string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s[(\"%s\")]", name, text))
+ return f
+}
+
+// DatabaseNode adds a node with database shape to the flowchart.
+// This method is same as CylindricalShapeNode()
+func (f *Flowchart) DatabaseNode(name, text string) *Flowchart {
+ return f.CylindricalNode(name, text)
+}
+
+// CircleNode adds a node with circle shape to the flowchart.
+func (f *Flowchart) CircleNode(name, text string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s((\"%s\"))", name, text))
+ return f
+}
+
+// AsymmetricNode adds a node with asymmetric shape to the flowchart.
+func (f *Flowchart) AsymmetricNode(name, text string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s>\"%s\"]", name, text))
+ return f
+}
+
+// RhombusNode adds a node with rhombus shape to the flowchart.
+func (f *Flowchart) RhombusNode(name, text string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s{\"%s\"}", name, text))
+ return f
+}
+
+// HexagonNode adds a node with hexagon shape to the flowchart.
+func (f *Flowchart) HexagonNode(name, text string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s{{\"%s\"}}", name, text))
+ return f
+}
+
+// ParallelogramNode adds a node with parallelogram shape to the flowchart.
+func (f *Flowchart) ParallelogramNode(name, text string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s[/\"%s\"/]", name, text))
+ return f
+}
+
+// ParallelogramAltNode adds a node with parallelogram shape to the flowchart.
+func (f *Flowchart) ParallelogramAltNode(name, text string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s[\\\"%s\"\\]", name, text))
+ return f
+}
+
+// TrapezoidNode adds a node with trapezoid shape to the flowchart.
+func (f *Flowchart) TrapezoidNode(name, text string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s[/\"%s\"\\]", name, text))
+ return f
+}
+
+// TrapezoidAltNode adds a node with trapezoid shape to the flowchart.
+func (f *Flowchart) TrapezoidAltNode(name, text string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s[\\\"%s\"/]", name, text))
+ return f
+}
+
+// DoubleCircleNode adds a node with double circle shape to the flowchart.
+func (f *Flowchart) DoubleCircleNode(name, text string) *Flowchart {
+ f.body = append(f.body, fmt.Sprintf(" %s(((\"%s\")))", name, text))
+ return f
+}
diff --git a/mermaid/flowchart/oriental.go b/mermaid/flowchart/oriental.go
new file mode 100644
index 0000000..fb50fa9
--- /dev/null
+++ b/mermaid/flowchart/oriental.go
@@ -0,0 +1,22 @@
+package flowchart
+
+// oriental is a flowchart oriental.
+type oriental string
+
+const (
+ // tb is a top to bottom oriental.
+ tb oriental = "TB"
+ // td is a top down oriental, same as top to Bottom.
+ td oriental = "TD"
+ // bt is a bottom to top oriental.
+ bt oriental = "BT"
+ // rl is a right to left oriental.
+ rl oriental = "RL"
+ // lr is a left to right oriental.
+ lr oriental = "LR"
+)
+
+// string returns the oriental as a string.
+func (o oriental) string() string {
+ return string(o)
+}
diff --git a/mermaid/piechart/pie_chart.go b/mermaid/piechart/pie_chart.go
index 6192c59..457c4c5 100644
--- a/mermaid/piechart/pie_chart.go
+++ b/mermaid/piechart/pie_chart.go
@@ -4,8 +4,9 @@ package piechart
import (
"fmt"
"io"
- "runtime"
"strings"
+
+ "github.com/nao1215/markdown/internal"
)
// PieChart is a pie chart builder.
@@ -57,7 +58,7 @@ func NewPieChart(w io.Writer, opts ...Option) *PieChart {
// String returns the pie chart body.
func (p *PieChart) String() string {
- return strings.Join(p.body, lineFeed())
+ return strings.Join(p.body, internal.LineFeed())
}
// Build writes the pie chart body to the output destination.
@@ -83,11 +84,3 @@ func (p *PieChart) LabelAndFloatValue(label string, value float64) *PieChart {
p.body = append(p.body, fmt.Sprintf(" \"%s\" : %f", label, value))
return p
}
-
-// lineFeed return line feed for current OS.
-func lineFeed() string {
- if runtime.GOOS == "windows" {
- return "\r\n"
- }
- return "\n"
-}
diff --git a/mermaid/sequence/sequence.go b/mermaid/sequence/sequence.go
index 0e659c5..70275c2 100644
--- a/mermaid/sequence/sequence.go
+++ b/mermaid/sequence/sequence.go
@@ -4,8 +4,9 @@ package sequence
import (
"fmt"
"io"
- "runtime"
"strings"
+
+ "github.com/nao1215/markdown/internal"
)
// Diagram is a sequence diagram builder.
@@ -38,7 +39,7 @@ func NewDiagram(w io.Writer, opts ...Option) *Diagram {
// String returns the sequence diagram body.
func (d *Diagram) String() string {
- return strings.Join(d.body, lineFeed())
+ return strings.Join(d.body, internal.LineFeed())
}
// Error returns the error that occurred during the sequence diagram building.
@@ -128,11 +129,3 @@ func (d *Diagram) LF() *Diagram {
d.body = append(d.body, "")
return d
}
-
-// lineFeed return line feed for current OS.
-func lineFeed() string {
- if runtime.GOOS == "windows" {
- return "\r\n"
- }
- return "\n"
-}
diff --git a/mermaid/sequence/sequence_test.go b/mermaid/sequence/sequence_test.go
index 609a54f..561142f 100644
--- a/mermaid/sequence/sequence_test.go
+++ b/mermaid/sequence/sequence_test.go
@@ -8,6 +8,7 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/nao1215/markdown/internal"
)
func TestString(t *testing.T) {
@@ -19,7 +20,7 @@ func TestString(t *testing.T) {
d := NewDiagram(io.Discard)
d.Participant("Alice")
- want := fmt.Sprintf("sequenceDiagram%s participant Alice", lineFeed())
+ want := fmt.Sprintf("sequenceDiagram%s participant Alice", internal.LineFeed())
got := d.String()
if diff := cmp.Diff(want, got); diff != "" {