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  `