aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/cli/internal/graphvisualizer/graphvisualizer.go
diff options
context:
space:
mode:
Diffstat (limited to 'cli/internal/graphvisualizer/graphvisualizer.go')
-rw-r--r--cli/internal/graphvisualizer/graphvisualizer.go205
1 files changed, 0 insertions, 205 deletions
diff --git a/cli/internal/graphvisualizer/graphvisualizer.go b/cli/internal/graphvisualizer/graphvisualizer.go
deleted file mode 100644
index 4e134b2..0000000
--- a/cli/internal/graphvisualizer/graphvisualizer.go
+++ /dev/null
@@ -1,205 +0,0 @@
-package graphvisualizer
-
-import (
- "fmt"
- "io"
- "math/rand"
- "os/exec"
- "path/filepath"
- "sort"
- "strings"
-
- "github.com/fatih/color"
- "github.com/mitchellh/cli"
- "github.com/pyr-sh/dag"
- "github.com/vercel/turbo/cli/internal/turbopath"
- "github.com/vercel/turbo/cli/internal/ui"
- "github.com/vercel/turbo/cli/internal/util"
- "github.com/vercel/turbo/cli/internal/util/browser"
-)
-
-// GraphVisualizer requirements
-type GraphVisualizer struct {
- repoRoot turbopath.AbsoluteSystemPath
- ui cli.Ui
- TaskGraph *dag.AcyclicGraph
-}
-
-// hasGraphViz checks for the presence of https://graphviz.org/
-func hasGraphViz() bool {
- err := exec.Command("dot", "-V").Run()
- return err == nil
-}
-
-func getRandChar() string {
- i := rand.Intn(25) + 65
- return string(rune(i))
-}
-
-func getRandID() string {
- return getRandChar() + getRandChar() + getRandChar() + getRandChar()
-}
-
-// New creates an instance of ColorCache with helpers for adding colors to task outputs
-func New(repoRoot turbopath.AbsoluteSystemPath, ui cli.Ui, TaskGraph *dag.AcyclicGraph) *GraphVisualizer {
- return &GraphVisualizer{
- repoRoot: repoRoot,
- ui: ui,
- TaskGraph: TaskGraph,
- }
-}
-
-// Converts the TaskGraph dag into a string
-func (g *GraphVisualizer) generateDotString() string {
- return string(g.TaskGraph.Dot(&dag.DotOpts{
- Verbose: true,
- DrawCycles: true,
- }))
-}
-
-// Outputs a warning when a file was requested, but graphviz is not available
-func (g *GraphVisualizer) graphVizWarnUI() {
- g.ui.Warn(color.New(color.FgYellow, color.Bold, color.ReverseVideo).Sprint(" WARNING ") + color.YellowString(" `turbo` uses Graphviz to generate an image of your\ngraph, but Graphviz isn't installed on this machine.\n\nYou can download Graphviz from https://graphviz.org/download.\n\nIn the meantime, you can use this string output with an\nonline Dot graph viewer."))
-}
-
-// RenderDotGraph renders a dot graph string for the current TaskGraph
-func (g *GraphVisualizer) RenderDotGraph() {
- g.ui.Output("")
- g.ui.Output(g.generateDotString())
-}
-
-type nameCache map[string]string
-
-func (nc nameCache) getName(in string) string {
- if existing, ok := nc[in]; ok {
- return existing
- }
- newName := getRandID()
- nc[in] = newName
- return newName
-}
-
-type sortableEdge dag.Edge
-type sortableEdges []sortableEdge
-
-// methods mostly copied from marshalEdges in the dag library
-func (e sortableEdges) Less(i, j int) bool {
- iSrc := dag.VertexName(e[i].Source())
- jSrc := dag.VertexName(e[j].Source())
- if iSrc < jSrc {
- return true
- } else if iSrc > jSrc {
- return false
- }
- return dag.VertexName(e[i].Target()) < dag.VertexName(e[j].Target())
-}
-func (e sortableEdges) Len() int { return len(e) }
-func (e sortableEdges) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
-
-func (g *GraphVisualizer) generateMermaid(out io.StringWriter) error {
- if _, err := out.WriteString("graph TD\n"); err != nil {
- return err
- }
- cache := make(nameCache)
- // cast edges to our custom type so we can sort them
- // this allows us to generate the same graph every time
- var edges sortableEdges
- for _, edge := range g.TaskGraph.Edges() {
- edges = append(edges, sortableEdge(edge))
- }
- sort.Sort(edges)
- for _, edge := range edges {
- left := dag.VertexName(edge.Source())
- right := dag.VertexName(edge.Target())
- leftName := cache.getName(left)
- rightName := cache.getName(right)
- if _, err := out.WriteString(fmt.Sprintf("\t%v(\"%v\") --> %v(\"%v\")\n", leftName, left, rightName, right)); err != nil {
- return err
- }
- }
- return nil
-}
-
-// GenerateGraphFile saves a visualization of the TaskGraph to a file (or renders a DotGraph as a fallback))
-func (g *GraphVisualizer) GenerateGraphFile(outputName string) error {
- outputFilename := g.repoRoot.UntypedJoin(outputName)
- ext := outputFilename.Ext()
- // use .jpg as default extension if none is provided
- if ext == "" {
- ext = ".jpg"
- outputFilename = g.repoRoot.UntypedJoin(outputName + ext)
- }
- if ext == ".mermaid" {
- f, err := outputFilename.Create()
- if err != nil {
- return fmt.Errorf("error creating file: %w", err)
- }
- defer util.CloseAndIgnoreError(f)
- if err := g.generateMermaid(f); err != nil {
- return err
- }
- g.ui.Output(fmt.Sprintf("✔ Generated task graph in %s", ui.Bold(outputFilename.ToString())))
- return nil
- }
- graphString := g.generateDotString()
- if ext == ".html" {
- f, err := outputFilename.Create()
- if err != nil {
- return fmt.Errorf("error creating file: %w", err)
- }
- defer f.Close() //nolint errcheck
- _, writeErr1 := f.WriteString(`<!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <title>Graph</title>
- </head>
- <body>
- <script src="https://cdn.jsdelivr.net/npm/viz.js@2.1.2-pre.1/viz.js"></script>
- <script src="https://cdn.jsdelivr.net/npm/viz.js@2.1.2-pre.1/full.render.js"></script>
- <script>`)
- if writeErr1 != nil {
- return fmt.Errorf("error writing graph contents: %w", writeErr1)
- }
-
- _, writeErr2 := f.WriteString("const s = `" + graphString + "`.replace(/\\_\\_\\_ROOT\\_\\_\\_/g, \"Root\").replace(/\\[root\\]/g, \"\");new Viz().renderSVGElement(s).then(el => document.body.appendChild(el)).catch(e => console.error(e));")
- if writeErr2 != nil {
- return fmt.Errorf("error creating file: %w", writeErr2)
- }
-
- _, writeErr3 := f.WriteString(`
- </script>
- </body>
- </html>`)
- if writeErr3 != nil {
- return fmt.Errorf("error creating file: %w", writeErr3)
- }
-
- g.ui.Output("")
- g.ui.Output(fmt.Sprintf("✔ Generated task graph in %s", ui.Bold(outputFilename.ToString())))
- if ui.IsTTY {
- if err := browser.OpenBrowser(outputFilename.ToString()); err != nil {
- g.ui.Warn(color.New(color.FgYellow, color.Bold, color.ReverseVideo).Sprintf("failed to open browser. Please navigate to file://%v", filepath.ToSlash(outputFilename.ToString())))
- }
- }
- return nil
- }
- hasDot := hasGraphViz()
- if hasDot {
- dotArgs := []string{"-T" + ext[1:], "-o", outputFilename.ToString()}
- cmd := exec.Command("dot", dotArgs...)
- cmd.Stdin = strings.NewReader(graphString)
- if err := cmd.Run(); err != nil {
- return fmt.Errorf("could not generate task graphfile %v: %w", outputFilename, err)
- }
- g.ui.Output("")
- g.ui.Output(fmt.Sprintf("✔ Generated task graph in %s", ui.Bold(outputFilename.ToString())))
-
- } else {
- g.ui.Output("")
- // User requested a file, but we're falling back to console here so warn about installing graphViz correctly
- g.graphVizWarnUI()
- g.RenderDotGraph()
- }
- return nil
-}