github.com/emcfarlane/larking@v0.0.0-20220605172417-1704b45ee6c3/builder/graph.go (about) 1 package builder 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "net" 8 "net/http" 9 "os" 10 "os/exec" 11 "strings" 12 13 "github.com/emcfarlane/larking/starlib" 14 "github.com/emcfarlane/larking/starlib/starlarkrule" 15 "github.com/emcfarlane/larking/starlib/starlarkthread" 16 "go.starlark.net/starlark" 17 ) 18 19 // TODO: build dot-graph 20 // https://graphviz.org/Gallery/directed/bazel.html 21 // https://graphviz.org/Gallery/directed/go-package.html 22 23 // https://github.com/google/pprof/blob/83db2b799d1f74c40857232cb5eb4c60379fe6c2/internal/driver/webui.go#L332 24 func dotToSvg(dot []byte) ([]byte, error) { 25 cmd := exec.Command("dot", "-Tsvg") 26 out := &bytes.Buffer{} 27 cmd.Stdin, cmd.Stdout, cmd.Stderr = bytes.NewBuffer(dot), out, os.Stderr 28 if err := cmd.Run(); err != nil { 29 return nil, err 30 } 31 32 // Fix dot bug related to unquoted ampersands. 33 svg := bytes.ReplaceAll(out.Bytes(), []byte("&;"), []byte("&;")) 34 35 // Cleanup for embedding by dropping stuff before the <svg> start. 36 if pos := bytes.Index(svg, []byte("<svg")); pos >= 0 { 37 svg = svg[pos:] 38 } 39 return svg, nil 40 } 41 42 func generateDot(a *starlarkrule.Action) ([]byte, error) { 43 var ( 44 buf bytes.Buffer 45 tabs = 0 46 ) 47 p := func(ss ...string) { 48 for i := 0; i < tabs; i++ { 49 buf.WriteRune('\t') 50 } 51 for _, s := range ss { 52 buf.WriteString(s) 53 } 54 buf.WriteRune('\n') 55 } 56 q := `"` 57 58 p(`digraph laze {`) 59 tabs += 1 60 p(`fontname="Helvetica,Arial,sans-serif"`) 61 p(`node [fontname="Helvetica,Arial,sans-serif"]`) 62 p(`edge [fontname="Helvetica,Arial,sans-serif"]`) 63 p(`node [shape=box];`) 64 //p(`rankdir="LR"`) 65 66 deps := []*starlarkrule.Action{a} 67 for n := len(deps); n > 0; n = len(deps) { 68 a, deps = deps[n-1], deps[:n-1] // pop 69 70 p(q, a.Key(), q) 71 72 deps = append(deps, a.Deps...) 73 for _, at := range a.Deps { 74 p(q, at.Key(), q, " -> ", q, a.Key(), q) 75 } 76 } 77 78 tabs -= 1 79 p(`}`) 80 81 return buf.Bytes(), nil 82 } 83 84 var isLocalhost = map[string]bool{ 85 "localhost": true, 86 "127.0.0.1": true, 87 "[::1]": true, 88 "::1": true, 89 } 90 91 func Serve(l net.Listener) error { //addr string) error { 92 host, _, err := net.SplitHostPort(l.Addr().String()) 93 if err != nil { 94 return err 95 } 96 97 dir := "" 98 label, err := starlarkrule.ParseRelativeLabel("file://./?metadata=skip", dir) 99 if err != nil { 100 return err 101 } 102 103 b, err := starlarkrule.NewBuilder(label) 104 if err != nil { 105 return err 106 } 107 108 globals := starlib.NewGlobals() 109 loader := starlib.NewLoader(globals) 110 111 resources := starlarkthread.ResourceStore{} // resources 112 defer resources.Close() 113 114 isLocal := isLocalhost[host] 115 handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 116 if isLocal { 117 // Only allow local clients 118 host, _, err := net.SplitHostPort(r.RemoteAddr) 119 if err != nil || !isLocalhost[host] { 120 http.Error(w, "permission denied", http.StatusForbidden) 121 return 122 } 123 } 124 125 name := strings.TrimPrefix(r.URL.Path, "/graph/") 126 l, err := label.Parse(name) 127 if err != nil { 128 http.Error(w, err.Error(), http.StatusBadRequest) 129 return 130 } 131 132 ctx := r.Context() 133 thread := &starlark.Thread{ 134 Name: l.String(), 135 Load: loader.Load, 136 } 137 starlarkthread.SetResourceStore(thread, &resources) 138 starlarkthread.SetContext(thread, ctx) 139 140 action, err := b.Build(thread, label) 141 if err != nil { 142 http.Error(w, err.Error(), http.StatusBadRequest) 143 return 144 } 145 146 dot, err := generateDot(action) 147 if err != nil { 148 http.Error(w, err.Error(), http.StatusInternalServerError) 149 return 150 } 151 fmt.Println("------ dot -------") 152 fmt.Println(string(dot)) 153 fmt.Println("------ dot -------") 154 155 svg, err := dotToSvg(dot) 156 if err != nil { 157 http.Error(w, err.Error(), http.StatusInternalServerError) 158 return 159 } 160 161 var buf bytes.Buffer 162 163 buf.WriteString(`<html> 164 <head> 165 <title>Laze</title> 166 </head> 167 <body>`) 168 buf.Write(svg) 169 buf.WriteString(` </body> 170 </html>`) 171 172 w.Header().Set("Content-Type", "text/html") 173 if _, err := io.Copy(w, &buf); err != nil { 174 http.Error(w, err.Error(), http.StatusInternalServerError) 175 return 176 } 177 178 }) 179 180 mux := http.NewServeMux() 181 //mux.Handle("/", handler) 182 mux.Handle("/graph/", handler) 183 s := &http.Server{Handler: mux} 184 return s.Serve(l) 185 }