diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eccaa1f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +graphpkg diff --git a/README.md b/README.md index 36e97ca..758544c 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,53 @@ # Installation - go get github.com/davecheney/graphpkg +```sh +# davecheney's original +go get github.com/davecheney/graphpkg + +# or jbenet's fork +go get github.com/davecheney/graphpkg +``` # Usage To graph the dependencies of the net package: - x-www-browser $(graphpkg net) +``` +x-www-browser $(graphpkg net) +``` + + +``` +> graphpkg +usage: graphpkg [flags] + + -browser=false: open a browser with the output + -format="dot-svg": format: {dot, dot-*, d3json} + -match=".*": filter packages +``` # Filtering graphpkg can also filter out packages that do not match the supplied regex, this may improve the readability of some graphs by excluding the std library: - x-www-browser $(graphpkg -match 'launchpad.net' launchpad.net/goamz/s3) + graphpkg -match 'launchpad.net' launchpad.net/goamz/s3 + + +# Examples + +``` +> graphpkg -format=dot-svg -browset +opening in your browser... + +> graphpkg -format=dot runtime +digraph { + N0 [label="unsafe",shape=box]; + N1 [label="runtime",shape=box]; + N1 -> N0 [weight=1]; +} + +> graphpkg -format=d3json runtime +[{"name":"runtime","size":1000,"imports":["unsafe"]} +,{"name":"unsafe","size":1000} +] +``` diff --git a/encoding.go b/encoding.go new file mode 100644 index 0000000..7996702 --- /dev/null +++ b/encoding.go @@ -0,0 +1,111 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "os" + "os/exec" +) + +func allKeys(pkgs *map[string][]string) []string { + keys := make(map[string]bool) + for k, v := range *pkgs { + keys[k] = true + for _, v := range v { + keys[v] = true + } + } + v := make([]string, 0, len(keys)) + for k, _ := range keys { + v = append(v, k) + } + return v +} + +func keys(pkgs *map[string][]string) map[string]int { + m := make(map[string]int) + for i, k := range allKeys(pkgs) { + m[k] = i + } + return m +} + +// writeDotRaw outputs the import graph as dot +func writeDotRaw(w io.Writer, pkgs *map[string][]string) error { + fmt.Fprint(w, "digraph {\n") + keys := keys(pkgs) + for p, i := range keys { + fmt.Fprintf(w, "\tN%d [label=%q,shape=box];\n", i, p) + } + for k, v := range *pkgs { + for _, p := range v { + fmt.Fprintf(w, "\tN%d -> N%d [weight=1];\n", keys[k], keys[p]) + } + } + fmt.Fprintf(w, "}\n") + return nil +} + +// writeDotOutput uses graphviz's dot util to convert dot fmt into something else +func writeDotOutput(out io.Writer, fmt string, pkgs *map[string][]string) error { + cmd := exec.Command("dot", "-T"+fmt) + cmd.Stdout = out // write dot's output straight to ours. + cmd.Stderr = os.Stderr + + in, err := cmd.StdinPipe() + if err != nil { + return err + } + + if err := cmd.Start(); err != nil { + return err + } + + // write dot fmt into dot's input + if err := writeDotRaw(in, pkgs); err != nil { + return err + } + in.Close() + return cmd.Wait() +} + +type d3pkg struct { + Name string `json:"name,omitempty"` + Size int `json:"size,omitempty"` + Imports []string `json:"imports,omitempty"` +} + +// writeD3JSON outputs the import graph as mbostock's json imports thing +func writeD3JSON(w io.Writer, pkgs *map[string][]string) error { + d3pkgs := pkgsToD3Pkgs(pkgs) + enc := json.NewEncoder(w) + + w.Write([]byte("[")) + first := true + for _, p := range *d3pkgs { + if first { + first = false + } else { + w.Write([]byte(",")) + } + + if err := enc.Encode(p); err != nil { + return err + } + } + w.Write([]byte("]")) + return nil +} + +func pkgsToD3Pkgs(pkgs *map[string][]string) *map[string]d3pkg { + d3pkgs := make(map[string]d3pkg) + for p, imports := range *pkgs { + d3pkgs[p] = d3pkg{ + Name: p, + Size: 1000, // change this once we know wtf it is. + Imports: imports, + } + } + return &d3pkgs +} diff --git a/main.go b/main.go index 7c2aa48..e719536 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,5 @@ // Graphpkg produces an svg graph of the dependency tree of a package -// +// // Requires // - dot (graphviz) // @@ -12,18 +12,21 @@ import ( "flag" "fmt" "go/build" + "io" "log" "os" - "os/exec" "regexp" + "strings" "github.com/pkg/browser" ) var ( - pkgs = make(map[string][]string) - matchvar = flag.String("match", ".*", "filter packages") - pkgmatch *regexp.Regexp + pkgs = make(map[string][]string) + matchvar = flag.String("match", ".*", "filter packages") + browservar = flag.Bool("browser", false, "open a browser with the output") + formatvar = flag.String("format", "dot-svg", "format: {dot, dot-*, d3json}") + pkgmatch *regexp.Regexp ) func findImport(p string) { @@ -59,30 +62,11 @@ func filter(s []string) []string { return r } -func allKeys() []string { - keys := make(map[string]bool) - for k, v := range pkgs { - keys[k] = true - for _, v := range v { - keys[v] = true - } - } - v := make([]string, 0, len(keys)) - for k, _ := range keys { - v = append(v, k) - } - return v -} - -func keys() map[string]int { - m := make(map[string]int) - for i, k := range allKeys() { - m[k] = i - } - return m -} - func init() { + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "usage: %s [flags] \n\n", os.Args[0]) + flag.PrintDefaults() + } flag.Parse() pkgmatch = regexp.MustCompile(*matchvar) } @@ -94,30 +78,49 @@ func check(err error) { } func main() { - for _, pkg := range flag.Args() { + args := flag.Args() + if len(args) == 0 { + flag.Usage() + os.Exit(1) + } + + for _, pkg := range args { findImport(pkg) } - cmd := exec.Command("dot", "-Tsvg") - in, err := cmd.StdinPipe() + + r, w, err := os.Pipe() check(err) - out, err := cmd.StdoutPipe() - cmd.Stderr = os.Stderr - check(cmd.Start()) - fmt.Fprintf(in, "digraph {\n") - keys := keys() - for p, i := range keys { - fmt.Fprintf(in, "\tN%d [label=%q,shape=box];\n", i, p) - } - for k, v := range pkgs { - for _, p := range v { - fmt.Fprintf(in, "\tN%d -> N%d [weight=1];\n", keys[k], keys[p]) - } - } - fmt.Fprintf(in, "}\n") - in.Close() + // run the transform + go xform(w) + output(r) +} - go browser.OpenReader(out) +func xform(w io.WriteCloser) { + var err error + switch { + case *formatvar == "d3json": + err = writeD3JSON(w, &pkgs) + case *formatvar == "dot": + err = writeDotRaw(w, &pkgs) + case strings.HasPrefix(*formatvar, "dot-"): + f := (*formatvar)[4:] + err = writeDotOutput(w, f, &pkgs) + default: + err = fmt.Errorf("error: unknown format %s", *formatvar) + } + w.Close() + check(err) +} - check(cmd.Wait()) +func output(r io.Reader) { + var err error + switch { + case *browservar: + fmt.Println("opening in your browser...") + err = browser.OpenReader(r) + default: + _, err = io.Copy(os.Stdout, r) + } + check(err) }