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 + +}