From 6b839b7d7154fafe1bbfa825999e624f2419f1ef Mon Sep 17 00:00:00 2001 From: Juan Batiz-Benet Date: Sat, 20 Dec 2014 15:35:00 -0800 Subject: [PATCH 1/7] added usage ouput --- main.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 7c2aa48..a811da9 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) // @@ -94,7 +94,13 @@ func check(err error) { } func main() { - for _, pkg := range flag.Args() { + args := flag.Args() + if len(args) == 0 { + fmt.Fprintf(os.Stderr, "usage: %s [flags] \n", os.Args[0]) + os.Exit(1) + } + + for _, pkg := range args { findImport(pkg) } cmd := exec.Command("dot", "-Tsvg") From 360d2ccc92747bb750524ea54e47ecb82607fbd7 Mon Sep 17 00:00:00 2001 From: Juan Batiz-Benet Date: Sat, 20 Dec 2014 15:35:09 -0800 Subject: [PATCH 2/7] added browser flag. output to stdout by default. Also attempt to catch + output `browser.OpenFile`'s error. See #3 --- main.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index a811da9..9757f3c 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,7 @@ import ( "flag" "fmt" "go/build" + "io" "log" "os" "os/exec" @@ -21,9 +22,10 @@ import ( ) 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") + pkgmatch *regexp.Regexp ) func findImport(p string) { @@ -123,7 +125,15 @@ func main() { fmt.Fprintf(in, "}\n") in.Close() - go browser.OpenReader(out) + if *browservar { + fmt.Println("opening in your browser...") + go func() { + err := browser.OpenReader(out) + check(err) + }() + } else { + io.Copy(os.Stdout, out) + } check(cmd.Wait()) } From 5b778f474c03fc15a2b3c7bc7c980d3def295e60 Mon Sep 17 00:00:00 2001 From: Juan Batiz-Benet Date: Sat, 20 Dec 2014 17:08:45 -0800 Subject: [PATCH 3/7] ignore graphpkg --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eccaa1f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +graphpkg From bbdcabb30b5dd1abefec5ea3b20706810bb32132 Mon Sep 17 00:00:00 2001 From: Juan Batiz-Benet Date: Sat, 20 Dec 2014 17:09:59 -0800 Subject: [PATCH 4/7] support multiple formats: dot, svg, d3json --- encoding.go | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 85 +++++++++++++++++------------------------ 2 files changed, 142 insertions(+), 50 deletions(-) create mode 100644 encoding.go diff --git a/encoding.go b/encoding.go new file mode 100644 index 0000000..521db46 --- /dev/null +++ b/encoding.go @@ -0,0 +1,107 @@ +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() +} + +// writeSVG uses graphviz's dot util to convert dot fmt into svg +func writeSVG(out io.Writer, pkgs *map[string][]string) error { + return writeDotOutput(out, "svg", pkgs) +} + +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) + + for _, p := range *d3pkgs { + if err := enc.Encode(p); err != nil { + return err + } + } + 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 9757f3c..d433376 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,6 @@ import ( "io" "log" "os" - "os/exec" "regexp" "github.com/pkg/browser" @@ -25,6 +24,7 @@ var ( 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", "svg", "format: {svg, dot, d3json}") pkgmatch *regexp.Regexp ) @@ -61,30 +61,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) } @@ -98,42 +79,46 @@ func check(err error) { func main() { args := flag.Args() if len(args) == 0 { - fmt.Fprintf(os.Stderr, "usage: %s [flags] \n", os.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]) - } + // run the transform + go xform(w) + output(r) +} + +func xform(w io.WriteCloser) { + var err error + switch *formatvar { + case "d3json": + err = writeD3JSON(w, &pkgs) + case "svg": + err = writeSVG(w, &pkgs) + case "dot": + err = writeDotRaw(w, &pkgs) + default: + err = fmt.Errorf("error: unknown format %s", *formatvar) } - fmt.Fprintf(in, "}\n") - in.Close() + w.Close() + check(err) +} - if *browservar { +func output(r io.Reader) { + var err error + switch { + case *browservar: fmt.Println("opening in your browser...") - go func() { - err := browser.OpenReader(out) - check(err) - }() - } else { - io.Copy(os.Stdout, out) + err = browser.OpenReader(r) + default: + _, err = io.Copy(os.Stdout, r) } - - check(cmd.Wait()) + check(err) } From e8cea4c4e70061bb13f7c7620b2a5659baa09987 Mon Sep 17 00:00:00 2001 From: Juan Batiz-Benet Date: Sat, 20 Dec 2014 17:13:48 -0800 Subject: [PATCH 5/7] support format "dot-" using `dot -T` --- encoding.go | 5 ----- main.go | 14 ++++++++------ 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/encoding.go b/encoding.go index 521db46..065e647 100644 --- a/encoding.go +++ b/encoding.go @@ -70,11 +70,6 @@ func writeDotOutput(out io.Writer, fmt string, pkgs *map[string][]string) error return cmd.Wait() } -// writeSVG uses graphviz's dot util to convert dot fmt into svg -func writeSVG(out io.Writer, pkgs *map[string][]string) error { - return writeDotOutput(out, "svg", pkgs) -} - type d3pkg struct { Name string `json:"name,omitempty"` Size int `json:"size,omitempty"` diff --git a/main.go b/main.go index d433376..e719536 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( "log" "os" "regexp" + "strings" "github.com/pkg/browser" ) @@ -24,7 +25,7 @@ var ( 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", "svg", "format: {svg, dot, d3json}") + formatvar = flag.String("format", "dot-svg", "format: {dot, dot-*, d3json}") pkgmatch *regexp.Regexp ) @@ -97,13 +98,14 @@ func main() { func xform(w io.WriteCloser) { var err error - switch *formatvar { - case "d3json": + switch { + case *formatvar == "d3json": err = writeD3JSON(w, &pkgs) - case "svg": - err = writeSVG(w, &pkgs) - case "dot": + 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) } From 8929cce23773918c0c1f86ae9f9df2414daab292 Mon Sep 17 00:00:00 2001 From: Juan Batiz-Benet Date: Sat, 20 Dec 2014 17:58:00 -0800 Subject: [PATCH 6/7] silly javascript arrays --- encoding.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/encoding.go b/encoding.go index 065e647..7996702 100644 --- a/encoding.go +++ b/encoding.go @@ -81,11 +81,20 @@ 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 } From 22d8851dbf1f7f2e3c42e2864846e776a4c193a9 Mon Sep 17 00:00:00 2001 From: Juan Batiz-Benet Date: Sat, 20 Dec 2014 18:03:34 -0800 Subject: [PATCH 7/7] updated readme --- README.md | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) 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} +] +```