Skip to content

Commit

Permalink
stat,pkggraph: separate package stats
Browse files Browse the repository at this point in the history
  • Loading branch information
egonelbre committed Dec 28, 2020
1 parent b713cef commit 84bd6c5
Show file tree
Hide file tree
Showing 9 changed files with 450 additions and 1 deletion.
1 change: 1 addition & 0 deletions exec/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"time"

"github.com/google/subcommands"

"github.com/loov/goda/memory"
"github.com/loov/goda/templates"
)
Expand Down
95 changes: 95 additions & 0 deletions pkggraph/graph.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package pkggraph

import (
"sort"

"golang.org/x/tools/go/packages"

"github.com/loov/goda/pkgset"
"github.com/loov/goda/stat"
)

type Graph struct {
Packages map[string]*Node
Sorted []*Node
stat.Stat
}

func (g *Graph) AddNode(n *Node) { g.Packages[n.ID] = n }

type Node struct {
ImportsNodes []*Node
ImportedByNodes []*Node

stat.Stat
Imports stat.Stat
ImportedBy stat.Stat

Errors []error
Graph *Graph

*packages.Package
}

func (n *Node) Pkg() *packages.Package { return n.Package }

func FromSet(pkgs pkgset.Set) *Graph {
return From(map[string]*packages.Package(pkgs))
}

// From creates a new graph from a map of packages.
func From(pkgs map[string]*packages.Package) *Graph {
g := &Graph{Packages: map[string]*Node{}}

for _, p := range pkgs {
n := LoadNode(p)
g.Sorted = append(g.Sorted, n)
g.AddNode(n)
g.Stat.Add(n.Stat)
}
SortNodes(g.Sorted)

// TODO: find ways to improve performance.

cache := allImportsCache(pkgs)

for _, n := range g.Packages {
importsIDs := cache[n.ID]
n.ImportsNodes = make([]*Node, 0, len(importsIDs))
for _, id := range importsIDs {
imported, ok := g.Packages[id]
if !ok {
// we may not want to print info about every package
continue
}

n.ImportsNodes = append(n.ImportsNodes, imported)
imported.ImportedByNodes = append(imported.ImportedByNodes, n)

n.Imports.Add(imported.Stat)
imported.ImportedBy.Add(n.Stat)
}
}

for _, n := range g.Packages {
SortNodes(n.ImportsNodes)
SortNodes(n.ImportedByNodes)
}

return g
}

func LoadNode(p *packages.Package) *Node {
node := &Node{}
node.Package = p

stat, errs := stat.Package(p)
node.Errors = append(node.Errors, errs...)
node.Stat = stat

return node
}

func SortNodes(xs []*Node) {
sort.Slice(xs, func(i, k int) bool { return xs[i].ID < xs[k].ID })
}
55 changes: 55 additions & 0 deletions pkggraph/imports.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package pkggraph

import (
"sort"

"golang.org/x/tools/go/packages"
)

func allImportsCache(pkgs map[string]*packages.Package) map[string][]string {
cache := map[string][]string{}

var fetch func(p *packages.Package) []string
fetch = func(p *packages.Package) []string {
if n, ok := cache[p.ID]; ok {
return n
}

// prevent cycles
cache[p.ID] = []string{}

var xs []string
for _, child := range p.Imports {
xs = includePackageID(xs, child.ID)
for _, pkg := range fetch(child) {
xs = includePackageID(xs, pkg)
}
}
cache[p.ID] = xs

return xs
}

for _, p := range pkgs {
_ = fetch(p)
}

return cache
}

func includePackageID(xs []string, p string) []string {
if !hasPackageID(xs, p) {
xs = append(xs, p)
sort.Strings(xs)
}
return xs
}

func hasPackageID(xs []string, p string) bool {
for _, x := range xs {
if x == p {
return true
}
}
return false
}
51 changes: 51 additions & 0 deletions stat/decl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package stat

import (
"go/ast"
"go/token"
)

// TopDecl stats about top-level declarations.
type TopDecl struct {
Func int64
Type int64
Const int64
Var int64
Other int64
}

func (s *TopDecl) Add(b TopDecl) {
s.Func += b.Func
s.Type += b.Type
s.Const += b.Const
s.Var += b.Var
s.Other += b.Other
}

func (s *TopDecl) Total() int64 {
return s.Func + s.Type + s.Const + s.Var + s.Other
}

func TopDeclFromAst(f *ast.File) TopDecl {
stat := TopDecl{}
for _, decl := range f.Decls {
switch decl := decl.(type) {
case *ast.GenDecl:
switch decl.Tok {
case token.TYPE:
stat.Type++
case token.VAR:
stat.Var++
case token.CONST:
stat.Const++
default:
stat.Other++
}
case *ast.FuncDecl:
stat.Func++
default:
stat.Other++
}
}
return stat
}
73 changes: 73 additions & 0 deletions stat/info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package stat

import (
"errors"
"fmt"
"go/parser"
"go/token"
"io/ioutil"

"golang.org/x/tools/go/packages"
)

type Stat struct {
GoFiles Source
OtherFiles Source

DeclCount TopDecl
TokenCount Tokens
}

func (info *Stat) AllFiles() Source {
var c Source
c.Add(info.GoFiles)
c.Add(info.OtherFiles)
return c
}

func (s *Stat) Add(b Stat) {
s.GoFiles.Add(b.GoFiles)
s.OtherFiles.Add(b.OtherFiles)
s.DeclCount.Add(b.DeclCount)
s.TokenCount.Add(b.TokenCount)
}

func Package(p *packages.Package) (Stat, []error) {
var info Stat
var errs []error

fset := token.NewFileSet()

for _, filename := range p.GoFiles {
src, err := ioutil.ReadFile(filename)
if err != nil {
errs = append(errs, fmt.Errorf("failed to read %q: %w", filename, err))
continue
}

count := SourceFromBytes(src)
info.GoFiles.Add(count)

f, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
if err != nil {
errs = append(errs, fmt.Errorf("failed to parse %q: %w", filename, err))
continue
}

info.DeclCount.Add(TopDeclFromAst(f))
info.TokenCount.Add(TokensFromAst(f))
}

for _, filename := range p.OtherFiles {
count, err := SourceFromPath(filename)
info.OtherFiles.Add(count)
if err != nil {
if !errors.Is(err, ErrEmptyFile) {
errs = append(errs, err)
}
continue
}
}

return info, errs
}
Loading

0 comments on commit 84bd6c5

Please sign in to comment.