github.com/alkemics/goflow@v0.2.1/gfutil/gfgo/generate.go (about) 1 package gfgo 2 3 import ( 4 "fmt" 5 "io" 6 "sort" 7 "strings" 8 "text/template" 9 10 "github.com/alkemics/goflow" 11 ) 12 13 // The nodeSorter sorts the node in alphabetical order, to make the graph generation 14 // reproducible. 15 type nodeSorter struct { 16 goflow.GraphRenderer 17 } 18 19 func (pp nodeSorter) Nodes() []goflow.NodeRenderer { 20 nodes := pp.GraphRenderer.Nodes() 21 sorted := make([]goflow.NodeRenderer, len(nodes)) 22 copy(sorted, nodes) 23 24 sort.SliceStable(sorted, func(i, j int) bool { 25 return sorted[i].ID() <= sorted[j].ID() 26 }) 27 28 return sorted 29 } 30 31 // Generate writes the graph g in w. 32 // 33 // It does not format the code, for example in the case of imports 34 // added twice. Use Writer to do so. 35 func Generate(w io.Writer, g goflow.GraphRenderer) error { 36 tmpl, err := template. 37 New("template"). 38 Funcs(template.FuncMap{ 39 "DepsNameAndTypes": func(fields []goflow.Field) string { 40 s := make([]string, len(fields)) 41 for i, f := range fields { 42 s[i] = fmt.Sprintf("%s %s", f.Name, f.Type) 43 } 44 return strings.Join(s, "\n") 45 }, 46 "NameAndTypes": func(fields []goflow.Field) string { 47 s := make([]string, len(fields)) 48 for i, f := range fields { 49 s[i] = fmt.Sprintf("%s %s", f.Name, f.Type) 50 } 51 return strings.Join(s, ", ") 52 }, 53 "Names": func(fields []goflow.Field) string { 54 s := make([]string, len(fields)) 55 for i, f := range fields { 56 s[i] = f.Name 57 } 58 return strings.Join(s, ", ") 59 }, 60 }). 61 Parse(tmplStr) 62 if err != nil { 63 return err 64 } 65 66 return tmpl.Execute(w, nodeSorter{g}) 67 } 68 69 // tmplStr defines the template used to generate a graph. It is a go constant 70 // and not a file to be easier to ship when extending the generator. 71 const tmplStr = ` 72 // Code generated by goflow DO NOT EDIT. 73 74 // +build !codeanalysis 75 76 package {{ .Pkg }} 77 78 import ( 79 {{ range .Imports -}} 80 {{ .Pkg }} "{{ .Dir }}" 81 {{ end }} 82 ) 83 84 /* 85 {{ .Doc }} 86 */ 87 type {{ .Name }} struct { 88 {{- DepsNameAndTypes .Dependencies -}} 89 } 90 91 func New{{ .Name }}({{ NameAndTypes .Dependencies }}) {{ .Name }} { 92 return {{ .Name }}{ 93 {{ range .Dependencies -}} 94 {{ .Name }}: {{ .Name }}, 95 {{- end }} 96 } 97 } 98 99 func new{{ .Name }}(id string, {{ NameAndTypes .Dependencies }}) {{ .Name }} { 100 return {{ .Name }}{ 101 {{ range .Dependencies -}} 102 {{ .Name }}: {{ .Name }}, 103 {{- end }} 104 } 105 } 106 107 /* 108 {{ .Doc }} 109 */ 110 func (g *{{ .Name }}) Run({{ NameAndTypes .Inputs }}) ({{ NameAndTypes .Outputs }}) { 111 {{ range .Nodes }} 112 {{ $id := .ID }} 113 // {{ $id }} outputs 114 {{ range .Outputs -}} 115 var {{ .Name }} {{ .Type }} 116 {{ end }} 117 {{ end }} 118 119 igniteNodeID := "ignite" 120 doneNodeID := "done" 121 122 done := make(chan string) 123 defer close(done) 124 125 steps := map[string]struct { 126 deps map[string]struct{} 127 run func() 128 alreadyDone bool 129 }{ 130 {{ range .Nodes }} 131 "{{ .ID }}": { 132 deps: map[string]struct{}{ 133 {{ range .Previous -}} 134 "{{ . }}": {}, 135 {{ end -}} 136 igniteNodeID: {}, 137 }, 138 run: func() { 139 {{ .Run (.Inputs) (.Outputs) }} 140 done <- "{{ .ID }}" 141 }, 142 alreadyDone: false, 143 }, 144 {{- end }} 145 igniteNodeID: { 146 deps: map[string]struct{}{}, 147 run: func() { 148 done <- igniteNodeID 149 }, 150 alreadyDone: false, 151 }, 152 doneNodeID: { 153 deps: map[string]struct{}{ 154 {{ range .Nodes -}} 155 "{{ .ID }}": {}, 156 {{ end -}} 157 }, 158 run: func() { 159 done <- doneNodeID 160 }, 161 alreadyDone: false, 162 }, 163 } 164 165 // Ignite 166 ignite := steps[igniteNodeID] 167 ignite.alreadyDone = true 168 steps[igniteNodeID] = ignite 169 go steps[igniteNodeID].run() 170 171 // Resolve the graph 172 for resolved := range done { 173 if resolved == doneNodeID { 174 // If all the graph was resolved, get out of the loop 175 break 176 } 177 178 for name, step := range steps { 179 delete(step.deps, resolved) 180 if len(step.deps) == 0 && !step.alreadyDone { 181 step.alreadyDone = true 182 steps[name] = step 183 go step.run() 184 } 185 } 186 } 187 188 return {{ Names .Outputs }} 189 } 190 `