Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
graphpkg
44 changes: 41 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -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] <package name>

-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}
]
```
111 changes: 111 additions & 0 deletions encoding.go
Original file line number Diff line number Diff line change
@@ -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
}
99 changes: 51 additions & 48 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Graphpkg produces an svg graph of the dependency tree of a package
//
//
// Requires
// - dot (graphviz)
//
Expand All @@ -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) {
Expand Down Expand Up @@ -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] <package name>\n\n", os.Args[0])
flag.PrintDefaults()
}
flag.Parse()
pkgmatch = regexp.MustCompile(*matchvar)
}
Expand All @@ -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)
}