diff --git a/README.md b/README.md
index 6301c41..bfa7558 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# gdag
Easily manage 🕸DAG🕷 with Go.
DAG is an acronym for Directed Acyclic Graph.
-Output is in PlantUML format.
+Output is in PlantUML or Mermaid format.
Useful for progressing tasks.
⚠It is incompatible with v0.2.0 and earlier versions⚠
@@ -104,6 +104,117 @@ end note
![image](dag.svg)
+## Mermaidjs
+
+1. `go run main.go`
+
+```go
+package main
+
+import (
+ "fmt"
+
+ g "github.com/ddddddO/gdag"
+)
+
+func main() {
+ var dag *g.Node = g.DAG("ゴール(目的)")
+
+ var design *g.Node = g.Task("設計")
+ reviewDesign := g.Task("レビュー対応")
+
+ developFeature1 := g.Task("feature1開発")
+ developFeature1.Note("noop")
+ reviewDevelopFeature1 := g.Task("レビュー対応")
+
+ developFeature2 := g.Task("feature2開発")
+ developFeature2.Note("noop")
+ reviewDevelopFeature2 := g.Task("レビュー対応")
+
+ prepareInfra := g.Task("インフラ準備")
+ prepareInfra.Note("noop")
+
+ test := g.Task("結合テスト")
+ release := g.Task("リリース")
+ finish := g.Task("finish")
+
+ dag.Con(design).Con(reviewDesign).Con(developFeature1).Con(reviewDevelopFeature1).Con(test)
+ reviewDesign.Con(developFeature2).Con(reviewDevelopFeature2).Con(test)
+ reviewDesign.Con(prepareInfra).Con(test)
+ test.Con(release).Con(finish)
+
+ g.Done(design, reviewDesign, developFeature2, finish)
+
+ mermaid, err := dag.Mermaid()
+ if err != nil {
+ panic(err)
+ }
+ fmt.Println(mermaid)
+}
+
+```
+
+```
+graph TD
+ classDef doneColor fill:#868787
+ 67("ゴール(目的)")
+ 68(["設計"]):::doneColor
+ 69(["レビュー対応"]):::doneColor
+ 70(["feature1開発"])
+ 71(["レビュー対応"])
+ 75(["結合テスト"])
+ 76(["リリース"])
+ 77(["finish"]):::doneColor
+ 72(["feature2開発"]):::doneColor
+ 73(["レビュー対応"])
+ 74(["インフラ準備"])
+
+ 67 --> 68
+ 68 --> 69
+ 69 --> 70
+ 70 --> 71
+ 71 --> 75
+ 75 --> 76
+ 76 --> 77
+ 69 --> 72
+ 72 --> 73
+ 73 --> 75
+ 69 --> 74
+ 74 --> 75
+```
+
+2. rendering
+
+```mermaid
+graph TD
+ classDef doneColor fill:#868787
+ 67("ゴール(目的)")
+ 68(["設計"]):::doneColor
+ 69(["レビュー対応"]):::doneColor
+ 70(["feature1開発"])
+ 71(["レビュー対応"])
+ 75(["結合テスト"])
+ 76(["リリース"])
+ 77(["finish"]):::doneColor
+ 72(["feature2開発"]):::doneColor
+ 73(["レビュー対応"])
+ 74(["インフラ準備"])
+
+ 67 --> 68
+ 68 --> 69
+ 69 --> 70
+ 70 --> 71
+ 71 --> 75
+ 75 --> 76
+ 76 --> 77
+ 69 --> 72
+ 72 --> 73
+ 73 --> 75
+ 69 --> 74
+ 74 --> 75
+```
+
+
## CheckList
1. `go run main.go`
diff --git a/gdag.go b/gdag.go
index 9eda44c..2c4e30a 100644
--- a/gdag.go
+++ b/gdag.go
@@ -12,9 +12,10 @@ type Node struct {
// の予定だったが、自動で生成する。連番。たぶん他の人がumlいじることはないとも思う。
// せめて、連番ではなく、置換しやすいように少し長めのユニークなIDにしたほうがいいかも。テストできそうかも考えて実装した方が良さそう。
// かつ、ソータブルな値が必須(ソートして使っているところがあるため)
- as int
- note string
- color string // done: #DarkGray
+ as int // mermaidjsの識別子としても利用する
+ note string
+ color string // done: #DarkGray
+ colorMermaid string // done: doneColor
upstream []*Node
downstream []*Node
@@ -54,6 +55,7 @@ const (
func Done(nodes ...*Node) {
for _, n := range nodes {
n.color = colorDone
+ n.colorMermaid = colorDoneMermaid
}
}
diff --git a/gdag_mermaid.go b/gdag_mermaid.go
new file mode 100644
index 0000000..36b0aa5
--- /dev/null
+++ b/gdag_mermaid.go
@@ -0,0 +1,92 @@
+package gdag
+
+import (
+ "fmt"
+)
+
+const (
+ colorDoneMermaid = "doneColor"
+)
+
+type mermaidGenerator struct {
+ uniqueC map[int]struct{}
+ uniqueR map[string]struct{}
+}
+
+func newMermaidGenerator() *mermaidGenerator {
+ return &mermaidGenerator{
+ uniqueC: map[int]struct{}{},
+ uniqueR: map[string]struct{}{},
+ }
+}
+
+// Mermaid outputs dag mermaidjs.
+func (start *Node) Mermaid() (string, error) {
+ mg := newMermaidGenerator()
+
+ ret := "graph TD" + "\n"
+ ret += " classDef doneColor fill:#868787" + "\n"
+ ret += mg.generateComponents(start) + "\n"
+ ret += mg.generateRelations(start) + "\n"
+ return ret, nil
+}
+
+func (mg *mermaidGenerator) generateComponents(start *Node) string {
+ return mg.generateComponent(start)
+}
+
+func (mg *mermaidGenerator) generateComponent(n *Node) string {
+ if _, ok := mg.uniqueC[n.as]; ok {
+ return ""
+ }
+ mg.uniqueC[n.as] = struct{}{}
+
+ dst := ""
+ // TODO: mermaidjs用に修正するかどうか。リファクタは必要
+ switch n.nodeType {
+ case rectangle:
+ s := fmt.Sprintf(" %d(\"%s\")", n.as, n.text)
+ if len(n.colorMermaid) != 0 {
+ s += fmt.Sprintf(":::%s", n.colorMermaid)
+ }
+ s += "\n"
+
+ dst += s
+ case usecase:
+ s := fmt.Sprintf(" %d([\"%s\"])", n.as, n.text)
+ if len(n.colorMermaid) != 0 {
+ s += fmt.Sprintf(":::%s", n.colorMermaid)
+ }
+ s += "\n"
+
+ dst += s
+ }
+ if len(n.note) != 0 {
+ // noop
+ }
+
+ for _, d := range n.downstream {
+ dst += mg.generateComponent(d)
+ }
+
+ return dst
+}
+
+func (mg *mermaidGenerator) generateRelations(start *Node) string {
+ return mg.generateRelation(start, "")
+}
+
+func (mg *mermaidGenerator) generateRelation(n *Node, out string) string {
+ r := fmt.Sprintf("%d --> ", n.as)
+ for _, d := range n.downstream {
+ key := fmt.Sprintf("%d-%d", n.as, d.as)
+ if _, ok := mg.uniqueR[key]; ok {
+ continue
+ }
+ mg.uniqueR[key] = struct{}{}
+
+ tmp := fmt.Sprintf(" %s%d\n", r, d.as)
+ out += mg.generateRelation(d, tmp)
+ }
+ return out
+}
diff --git a/gdag_test.go b/gdag_test.go
index 9223c3c..a357ccf 100644
--- a/gdag_test.go
+++ b/gdag_test.go
@@ -417,3 +417,66 @@ func ExampleMultipleCheckLists() {
// - [ ] リリース
// - [x] finish
}
+
+func ExampleMermaid() {
+ var dag *g.Node = g.DAG("ゴール(目的)")
+
+ var design *g.Node = g.Task("設計")
+ reviewDesign := g.Task("レビュー対応")
+
+ developFeature1 := g.Task("feature1開発")
+ developFeature1.Note("noop")
+ reviewDevelopFeature1 := g.Task("レビュー対応")
+
+ developFeature2 := g.Task("feature2開発")
+ developFeature2.Note("noop")
+ reviewDevelopFeature2 := g.Task("レビュー対応")
+
+ prepareInfra := g.Task("インフラ準備")
+ prepareInfra.Note("noop")
+
+ test := g.Task("結合テスト")
+ release := g.Task("リリース")
+ finish := g.Task("finish")
+
+ dag.Con(design).Con(reviewDesign).Con(developFeature1).Con(reviewDevelopFeature1).Con(test)
+ reviewDesign.Con(developFeature2).Con(reviewDevelopFeature2).Con(test)
+ reviewDesign.Con(prepareInfra).Con(test)
+ test.Con(release).Con(finish)
+
+ g.Done(design, reviewDesign, developFeature2, finish)
+
+ mermaid, err := dag.Mermaid()
+ if err != nil {
+ panic(err)
+ }
+ fmt.Println(mermaid)
+ // Output:
+ // graph TD
+ // classDef doneColor fill:#868787
+ // 67("ゴール(目的)")
+ // 68(["設計"]):::doneColor
+ // 69(["レビュー対応"]):::doneColor
+ // 70(["feature1開発"])
+ // 71(["レビュー対応"])
+ // 75(["結合テスト"])
+ // 76(["リリース"])
+ // 77(["finish"]):::doneColor
+ // 72(["feature2開発"]):::doneColor
+ // 73(["レビュー対応"])
+ // 74(["インフラ準備"])
+ //
+ // 67 --> 68
+ // 68 --> 69
+ // 69 --> 70
+ // 70 --> 71
+ // 71 --> 75
+ // 75 --> 76
+ // 76 --> 77
+ // 69 --> 72
+ // 72 --> 73
+ // 73 --> 75
+ // 69 --> 74
+ // 74 --> 75
+
+}