* Copyright 2016-2017 Gary Kramlich <grim@reaperworld.com> * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. "bitbucket.org/rw_grim/convey/color" "bitbucket.org/rw_grim/convey/config" "bitbucket.org/rw_grim/convey/logging" "bitbucket.org/rw_grim/convey/plans" "bitbucket.org/rw_grim/convey/state" node[style="filled,rounded" shape="rect"] node[shape="rect" fillcolor="plum"] {{range .MetaPlans}} {{.Normalized}}_start[label="{{.Name}} start"] {{.Normalized}}_finish[label="{{.Name}} finish"] # add a start and finish node for each plan node[shape="component" fillcolor="palegreen"] {{range .Plans}} {{.Normalized}}_start[label="{{.Name}} start"] {{.Normalized}}_finish[label="{{.Name}} finish"] node[style="filled,rounded" shape="rect" fillcolor="powderblue"] {{range .Tasks}} {{.Normalized}}[label="{{.Name}}"] # add the stages for {{.Name}} edge[color="{{.Color}}"]{{$planNormalized := .Normalized }} {{$planNormalized}}_{{.Normalized}} [label="{{.Name}}" shape="octagon" fillcolor="{{if .Enabled}}lightpink1{{else}}gray{{end}}"{{if .Always}} style="bold,filled,rounded"{{end}}]{{end}} {{.Normalized}}_start{{range .Stages}} -> {{$planNormalized}}_{{.Normalized}}{{if .Concurrent}} -> { {{range .Tasks}} {{.}} {{end}} }{{else}}{{range .Tasks}} -> {{.}}{{end}}{{end}}{{end}} -> {{.Normalized}}_finish {{ range .Stages}}{{$stage := .}}{{range .FallThroughs}}{{$planNormalized}}_{{$stage.Normalized}} -> {{$planNormalized}}_{{.Normalized}} [style="dotted"] edge[color="{{.Color}}" style="dashed,bold"] {{.Normalized}}_start -> {{range .Plans}}{{.Normalized}}_start {{.Normalized}}_finish -> {{end}} {{.Normalized}}_finish var normalizeRegex *regexp.Regexp FallThroughs []GraphvizStage GraphvizMetaPlan struct { func normalize(prefix, str string) string { if normalizeRegex == nil { normalizeRegex = regexp.MustCompile("[^a-zA-Z0-9_]") return prefix + "_" + normalizeRegex.ReplaceAllString(str, "_") func (g *Graphviz) getTasks(cfg *config.Config) []GraphvizTask { tasks := []GraphvizTask{} for name, _ := range cfg.Tasks { Normalized: normalize("task", name), tasks = append(tasks, task) func (g *Graphviz) getMetaPlans(cfg *config.Config) []GraphvizMetaPlan { metaPlans := []GraphvizMetaPlan{} for name, cMetaPlan := range cfg.MetaPlans { metaPlan := GraphvizMetaPlan{ Normalized: normalize("metaplan", name), for _, name := range cMetaPlan.Plans { if plan, found := cfg.Plans[name]; found { metaPlan.Plans = append(metaPlan.Plans, g.plan(name, plan)) metaPlans = append(metaPlans, metaPlan) func (g Graphviz) plan(name string, plan plans.Plan) GraphvizPlan { graphvizPlan := GraphvizPlan{ Normalized: normalize("plan", name), Stages: []GraphvizStage{}, for idx, stage := range plan.Stages { graphvizStage := GraphvizStage{ Normalized: normalize("stage", stage.Name), Concurrent: stage.Concurrent, for _, task := range stage.Tasks { graphvizStage.Tasks = append(graphvizStage.Tasks, normalize("task", task)) graphvizPlan.Stages = append(graphvizPlan.Stages, graphvizStage) // if this always runs, if it's not the first add it to the previous stages' fallthroughs if stage.Always && idx > 0 { for i := 0; i < idx; i++ { graphvizPlan.Stages[i].FallThroughs = append(graphvizPlan.Stages[idx-1].FallThroughs, graphvizStage) func (g *Graphviz) getPlans(cfg *config.Config) []GraphvizPlan { plans := []GraphvizPlan{} for name, plan := range cfg.Plans { plans = append(plans, g.plan(name, plan)) func (g *Graphviz) Run(cfg *config.Config, plans []string, env []string, st *state.State) int { logger := logging.NewAdapter("graphviz") params := map[string]interface{}{ "Tasks": g.getTasks(cfg), "Plans": g.getPlans(cfg), "MetaPlans": g.getMetaPlans(cfg), tmpl, err := template.New("graphviz").Parse(graphvizTemplate) logger.Fatalf("error: %s", err) output := new(bytes.Buffer) err = tmpl.Execute(output, params) logger.Fatalf("error: %s", err)