pax_global_header00006660000000000000000000000064144204703520014513gustar00rootroot0000000000000052 comment=13909d09ef06398798549d14c96084b7eecd1d1b goda-0.5.7/000077500000000000000000000000001442047035200124365ustar00rootroot00000000000000goda-0.5.7/.gitignore000066400000000000000000000004031442047035200144230ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.prof goda-0.5.7/LICENSE000066400000000000000000000021361442047035200134450ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2018 Egon Elbre Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.goda-0.5.7/README.md000066400000000000000000000064751442047035200137310ustar00rootroot00000000000000# Goda Goda is a Go dependency analysis toolkit. It contains tools to figure out what your program is using. _Note: the exact syntax of the command line arguments has not yet been finalized. So expect some changes to it._ Cool things it can do: ``` # All of the commands should be run in the cloned repository. git clone https://github.com/loov/goda && cd goda # draw a graph of packages in github.com/loov/goda goda graph "github.com/loov/goda/..." | dot -Tsvg -o graph.svg # draw a dependency graph of github.com/loov/goda and dependencies goda graph -cluster -short "github.com/loov/goda:all" | dot -Tsvg -o graph.svg # list direct dependencies of github.com/loov/goda goda list "github.com/loov/goda/...:import" # list dependency graph that reaches flag package, including std goda graph -std "reach(github.com/loov/goda/...:all, flag)" | dot -Tsvg -o graph.svg # list packages shared by github.com/loov/goda/pkgset and github.com/loov/goda/cut goda list "shared(github.com/loov/goda/pkgset:all, github.com/loov/goda/cut:all)"" # list packages that are only imported for tests goda list "github.com/loov/goda/...:+test:all - github.com/loov/goda/...:all" # list packages that are imported with `purego` tag goda list -std "purego=1(github.com/loov/goda/...:all)" # list packages that are imported for windows and not linux goda list "goos=windows(github.com/loov/goda/...:all) - goos=linux(github.com/loov/goda/...:all)" # list how much memory each symbol in the final binary is taking goda weight -h $GOPATH/bin/goda # show the impact of cutting a package goda cut ./...:all # print dependency tree of all sub-packages goda tree ./...:all # print stats while building a go program go build -a --toolexec "goda exec" . # list dependency graph in same format as "go mod graph" goda graph -type edges -f '{{.ID}}{{if .Module}}{{with .Module.Version}}@{{.}}{{end}}{{end}}' ./...:all ``` Maybe you noticed that it's using some weird symbols on the command-line while specifying packages. They allow for more complex scenarios. The basic syntax is that you can specify multiple packages: ``` goda list github.com/loov/goda/... github.com/loov/qloc ``` By default it will select all the specific packages. You can select the package dependencies with `:import` and both of them with `:all`: ``` goda list github.com/loov/goda/...:import:all goda list github.com/loov/goda/...:all ``` You can also do basic arithmetic with these sets. For example, if you wish to ignore all `golang.org/x/tools` dependencies: ``` goda list github.com/loov/goda/...:all - golang.org/x/tools/... ``` To get more help about expressions or formatting: ``` goda help expr goda help format ``` ## Graph example Here's an example output for: ``` git clone https://github.com/loov/goda && cd goda goda graph github.com/loov/goda/... | dot -Tsvg -o graph.svg ``` ![github.com/loov/goda dependency graph](./graph.svg) ## How it differs from `go list` or `go mod` `go list` and `go mod` are tightly integrated with Go and can answer simple queries with compatibility. They also serves as good building blocks for other tools. `goda` is intended for more complicated queries and analysis. Some of the features can be reproduced by format flags and scripts. However, this library aims to make even complicated analysis fast. Also, `goda` can be used together with `go list` and `go mod`. goda-0.5.7/go.mod000066400000000000000000000002721442047035200135450ustar00rootroot00000000000000module github.com/loov/goda go 1.17 require ( github.com/google/subcommands v1.2.0 golang.org/x/mod v0.10.0 golang.org/x/tools v0.8.0 ) require golang.org/x/sys v0.7.0 // indirect goda-0.5.7/go.sum000066400000000000000000000074321442047035200135770ustar00rootroot00000000000000github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= goda-0.5.7/graph.svg000066400000000000000000000626001442047035200142640ustar00rootroot00000000000000 G n_github_com_loov_goda github.com/loov/goda 101 / 3.3KB n_github_com_loov_goda_internal_cut github.com/loov/goda/internal/cut 161 / 4.2KB n_github_com_loov_goda:e->n_github_com_loov_goda_internal_cut n_github_com_loov_goda_internal_exec github.com/loov/goda/internal/exec 126 / 3.3KB n_github_com_loov_goda:e->n_github_com_loov_goda_internal_exec n_github_com_loov_goda_internal_graph github.com/loov/goda/internal/graph 533 / 13.8KB n_github_com_loov_goda:e->n_github_com_loov_goda_internal_graph n_github_com_loov_goda_internal_list github.com/loov/goda/internal/list 62 / 1.7KB n_github_com_loov_goda:e->n_github_com_loov_goda_internal_list n_github_com_loov_goda_internal_pkgset github.com/loov/goda/internal/pkgset 567 / 12.5KB n_github_com_loov_goda:e->n_github_com_loov_goda_internal_pkgset n_github_com_loov_goda_internal_tree github.com/loov/goda/internal/tree 86 / 2.2KB n_github_com_loov_goda:e->n_github_com_loov_goda_internal_tree n_github_com_loov_goda_internal_weight github.com/loov/goda/internal/weight 259 / 6.6KB n_github_com_loov_goda:e->n_github_com_loov_goda_internal_weight n_github_com_loov_goda_internal_pkggraph github.com/loov/goda/internal/pkggraph 196 / 4.2KB n_github_com_loov_goda_internal_cut:e->n_github_com_loov_goda_internal_pkggraph n_github_com_loov_goda_internal_cut:e->n_github_com_loov_goda_internal_pkgset n_github_com_loov_goda_internal_stat github.com/loov/goda/internal/stat 252 / 4.6KB n_github_com_loov_goda_internal_cut:e->n_github_com_loov_goda_internal_stat n_github_com_loov_goda_internal_templates github.com/loov/goda/internal/templates 164 / 3.3KB n_github_com_loov_goda_internal_cut:e->n_github_com_loov_goda_internal_templates n_github_com_loov_goda_internal_memory github.com/loov/goda/internal/memory 29 / 0.7KB n_github_com_loov_goda_internal_exec:e->n_github_com_loov_goda_internal_memory n_github_com_loov_goda_internal_exec:e->n_github_com_loov_goda_internal_templates n_github_com_loov_goda_internal_graph_graphml github.com/loov/goda/internal/graph/graphml 105 / 2.9KB n_github_com_loov_goda_internal_graph:e->n_github_com_loov_goda_internal_graph_graphml n_github_com_loov_goda_internal_graph:e->n_github_com_loov_goda_internal_pkggraph n_github_com_loov_goda_internal_graph:e->n_github_com_loov_goda_internal_pkgset n_github_com_loov_goda_internal_graph:e->n_github_com_loov_goda_internal_templates n_github_com_loov_goda_internal_list:e->n_github_com_loov_goda_internal_pkggraph n_github_com_loov_goda_internal_list:e->n_github_com_loov_goda_internal_pkgset n_github_com_loov_goda_internal_list:e->n_github_com_loov_goda_internal_templates n_github_com_loov_goda_internal_pkggraph:e->n_github_com_loov_goda_internal_stat n_github_com_loov_goda_internal_pkgset_ast github.com/loov/goda/internal/pkgset/ast 258 / 5.7KB n_github_com_loov_goda_internal_pkgset:e->n_github_com_loov_goda_internal_pkgset_ast n_github_com_loov_goda_internal_stat:e->n_github_com_loov_goda_internal_memory n_github_com_loov_goda_internal_templates:e->n_github_com_loov_goda_internal_memory n_github_com_loov_goda_internal_tree:e->n_github_com_loov_goda_internal_pkgset n_github_com_loov_goda_internal_tree:e->n_github_com_loov_goda_internal_templates n_github_com_loov_goda_internal_weight:e->n_github_com_loov_goda_internal_memory goda-0.5.7/internal/000077500000000000000000000000001442047035200142525ustar00rootroot00000000000000goda-0.5.7/internal/cut/000077500000000000000000000000001442047035200150455ustar00rootroot00000000000000goda-0.5.7/internal/cut/cmd.go000066400000000000000000000104721442047035200161430ustar00rootroot00000000000000package cut import ( "context" "flag" "fmt" "io" "os" "sort" "strings" "text/tabwriter" "github.com/google/subcommands" "golang.org/x/tools/go/packages" "github.com/loov/goda/internal/pkggraph" "github.com/loov/goda/internal/pkgset" "github.com/loov/goda/internal/stat" "github.com/loov/goda/internal/templates" ) type Command struct { printStandard bool noAlign bool format string exclude string } func (*Command) Name() string { return "cut" } func (*Command) Synopsis() string { return "Analyse indirect-dependencies." } func (*Command) Usage() string { return `cut : Print information about indirect-dependencies. It shows packages whose removal would remove the most indirect dependencies. See "help expr" for further information about expressions. See "help format" for further information about formatting. ` } func (cmd *Command) SetFlags(f *flag.FlagSet) { f.BoolVar(&cmd.printStandard, "std", false, "print std packages") f.BoolVar(&cmd.noAlign, "noalign", false, "disable aligning tabs") f.StringVar(&cmd.format, "f", "{{.ID}}\tin:{{.InDegree}}\tpkgs:{{.Cut.PackageCount}}\tsize:{{.Cut.AllFiles.Size}}\tloc:{{.Cut.Go.Lines}}", "info formatting") f.StringVar(&cmd.exclude, "exclude", "", "package expr to exclude from output") } func (cmd *Command) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { t, err := templates.Parse(cmd.format) if err != nil { fmt.Fprintf(os.Stderr, "invalid label string: %v\n", err) return subcommands.ExitFailure } if !cmd.printStandard { go pkgset.LoadStd() } result, err := pkgset.Calc(ctx, f.Args()) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) return subcommands.ExitFailure } excluded := pkgset.New() if cmd.exclude != "" { excluded, err = pkgset.Calc(ctx, strings.Fields(cmd.exclude)) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) return subcommands.ExitFailure } } if !cmd.printStandard { result = pkgset.Subtract(result, pkgset.Std()) } graph := pkggraph.From(result) nodes := map[string]*Node{} nodelist := []*Node{} var include func(parent *Node, n *pkggraph.Node) include = func(parent *Node, n *pkggraph.Node) { if n, ok := nodes[n.ID]; ok { parent.Import(n) return } node := &Node{ Node: n, } nodes[n.ID] = node if _, analyse := graph.Packages[n.ID]; analyse { nodelist = append(nodelist, node) } parent.Import(node) for _, child := range n.ImportsNodes { include(node, child) } } for _, n := range graph.Sorted { include(nil, n) } for _, p := range nodes { if !cmd.printStandard && pkgset.IsStd(p.Package) { continue } } for _, node := range nodelist { Reset(nodes) node.Cut = Erase(node) } sort.Slice(nodelist, func(i, k int) bool { if nodelist[i].InDegree() == nodelist[k].InDegree() { return nodelist[i].Cut.PackageCount > nodelist[k].Cut.PackageCount } return nodelist[i].InDegree() < nodelist[k].InDegree() }) var w io.Writer = os.Stdout if !cmd.noAlign { w = tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) } for _, node := range nodelist { if _, exclude := excluded[node.ID]; exclude { continue } err := t.Execute(w, node) fmt.Fprintln(w) if err != nil { fmt.Fprintf(os.Stderr, "template error: %v\n", err) } } if w, ok := w.(interface{ Flush() error }); ok { w.Flush() } return subcommands.ExitSuccess } func Reset(stats map[string]*Node) { for _, stat := range stats { stat.indegree = len(stat.ImportedBy) } } func Erase(stat *Node) stat.Stat { cut := stat.Stat for _, imp := range stat.Imports { imp.indegree-- if imp.indegree == 0 { cut.Add(Erase(imp)) } } return cut } type Node struct { *pkggraph.Node Cut stat.Stat Imports []*Node ImportedBy []*Node indegree int } func (parent *Node) Pkg() *packages.Package { return parent.Package } func (parent *Node) InDegree() int { return len(parent.ImportedBy) } func (parent *Node) OutDegree() int { return len(parent.Imports) } func (parent *Node) Import(child *Node) { if parent == nil { return } if !hasPackage(parent.Imports, child) { child.indegree++ child.ImportedBy = append(child.ImportedBy, parent) parent.Imports = append(parent.Imports, child) } } func hasPackage(xs []*Node, p *Node) bool { for _, x := range xs { if x == p { return true } } return false } goda-0.5.7/internal/exec/000077500000000000000000000000001442047035200151765ustar00rootroot00000000000000goda-0.5.7/internal/exec/cmd.go000066400000000000000000000064541442047035200163010ustar00rootroot00000000000000package exec import ( "context" "flag" "fmt" "os" "os/exec" "path/filepath" "syscall" "time" "github.com/google/subcommands" "github.com/loov/goda/internal/memory" "github.com/loov/goda/internal/templates" ) type Command struct { format string } func (*Command) Name() string { return "exec" } func (*Command) Synopsis() string { return "Run command with extended statistics." } func (*Command) Usage() string { return `calc : Run command with extended statistics. Example: go build -toolexec "goda exec" . ` } func (cmd *Command) SetFlags(f *flag.FlagSet) { f.StringVar(&cmd.format, "f", "{{.Command}} {{.PackageName}} user:{{.UserTime}} system:{{.SystemTime}} in:{{.InputsSize}} out:{{.OutputSize}}", "formatting") } func (cmd *Command) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { if f.NArg() == 0 { return subcommands.ExitSuccess } args := f.Args() t, err := templates.Parse(cmd.format) if err != nil { fmt.Fprintf(os.Stderr, "invalid format string: %v\n", err) return subcommands.ExitFailure } command := exec.CommandContext(ctx, args[0], args[1:]...) command.Stdin, command.Stdout, command.Stderr = os.Stdin, os.Stdout, os.Stderr var info Info startError := command.Start() if startError != nil { fmt.Fprintf(os.Stderr, "failed to start: %v\n", startError) return subcommands.ExitFailure } info.Start = time.Now() exitError := command.Wait() info.Finish = time.Now() if command.ProcessState != nil { info.UserTime = command.ProcessState.UserTime() info.SystemTime = command.ProcessState.SystemTime() } ParseArgs(&info, args) err = t.Execute(os.Stdout, &info) if err != nil { fmt.Fprintf(os.Stderr, "template error: %v\n", err) } fmt.Fprintln(os.Stdout) if exitError != nil { if err, ok := exitError.(*exec.ExitError); ok { if status, ok := err.Sys().(syscall.WaitStatus); ok { return subcommands.ExitStatus(status.ExitStatus()) } } fmt.Fprintf(os.Stderr, "failed to run: %v\n", exitError) return subcommands.ExitFailure } return subcommands.ExitSuccess } type Info struct { Command string PackageName string Args []string Output string OutputSize memory.Bytes Inputs []string InputsSize memory.Bytes Start time.Time Finish time.Time UserTime time.Duration SystemTime time.Duration } func ParseArgs(info *Info, args []string) { cmdname := filepath.Base(args[0]) ext := filepath.Ext(cmdname) info.Command = cmdname[:len(cmdname)-len(ext)] for i := 1; i < len(args); i++ { switch args[i] { case "": case "-I", "-D", "-trimpath": i++ case "-o": i++ if i < len(args) { info.Output = args[i] } case "-p": i++ if i < len(args) { info.PackageName = args[i] } default: // ignore flags if args[i][0] == '-' { continue } ext := filepath.Ext(args[i]) if ext == ".a" || ext == ".o" || ext == ".h" || ext == ".s" || ext == ".c" || ext == ".go" { info.Inputs = append(info.Inputs, args[i]) } } } //TODO: take into account $WORK variable if info.Output != "" { if stat, err := os.Lstat(info.Output); err == nil { info.OutputSize = memory.Bytes(stat.Size()) } } for _, input := range info.Inputs { if stat, err := os.Lstat(input); err == nil { info.InputsSize += memory.Bytes(stat.Size()) } } } goda-0.5.7/internal/graph/000077500000000000000000000000001442047035200153535ustar00rootroot00000000000000goda-0.5.7/internal/graph/cmd.go000066400000000000000000000066061442047035200164550ustar00rootroot00000000000000package graph import ( "context" "flag" "fmt" "os" "strconv" "strings" "github.com/google/subcommands" "github.com/loov/goda/internal/pkggraph" "github.com/loov/goda/internal/pkgset" "github.com/loov/goda/internal/templates" ) type Command struct { printStandard bool docs string outputType string labelFormat string nocolor bool clusters bool shortID bool } func (*Command) Name() string { return "graph" } func (*Command) Synopsis() string { return "Print dependency graph." } func (*Command) Usage() string { return `graph : Print dependency dot graph. Supported output types: dot - GraphViz dot format graphml - GraphML format tgf - Trivial Graph Format edges - format with each edge separately digraph - format with each node and its edges on a single line See "help expr" for further information about expressions. See "help format" for further information about formatting. ` } func (cmd *Command) SetFlags(f *flag.FlagSet) { f.BoolVar(&cmd.printStandard, "std", false, "print std packages") f.BoolVar(&cmd.nocolor, "nocolor", false, "disable coloring") f.StringVar(&cmd.docs, "docs", "https://pkg.go.dev/", "override the docs url to use") f.StringVar(&cmd.outputType, "type", "dot", "output type (dot, graphml, digraph, edges, tgf)") f.StringVar(&cmd.labelFormat, "f", "", "label formatting") f.BoolVar(&cmd.clusters, "cluster", false, "create clusters") f.BoolVar(&cmd.shortID, "short", false, "use short package id-s inside clusters") } func (cmd *Command) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { if cmd.labelFormat == "" { switch cmd.outputType { case "dot": cmd.labelFormat = `{{.ID}}\l{{ .Stat.Go.Lines }} / {{ .Stat.Go.Size }}\l` default: cmd.labelFormat = `{{.ID}}` } } label, err := templates.Parse(cmd.labelFormat) if err != nil { fmt.Fprintf(os.Stderr, "invalid label format: %v\n", err) return subcommands.ExitFailure } var format Format switch strings.ToLower(cmd.outputType) { case "dot": format = &Dot{ out: os.Stdout, err: os.Stderr, docs: cmd.docs, clusters: cmd.clusters, nocolor: cmd.nocolor, shortID: cmd.shortID, label: label, } case "digraph": format = &Digraph{ out: os.Stdout, err: os.Stderr, label: label, } case "tgf": format = &TGF{ out: os.Stdout, err: os.Stderr, label: label, } case "edges": format = &Edges{ out: os.Stdout, err: os.Stderr, label: label, } case "graphml": format = &GraphML{ out: os.Stdout, err: os.Stderr, label: label, } default: fmt.Fprintf(os.Stderr, "unknown output type %q\n", cmd.outputType) return subcommands.ExitFailure } if !cmd.printStandard { go pkgset.LoadStd() } result, err := pkgset.Calc(ctx, f.Args()) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) return subcommands.ExitFailure } if !cmd.printStandard { result = pkgset.Subtract(result, pkgset.Std()) } graph := pkggraph.From(result) if err := format.Write(graph); err != nil { fmt.Fprintf(os.Stderr, "error building graph: %v\n", err) return subcommands.ExitFailure } return subcommands.ExitSuccess } type Format interface { Write(*pkggraph.Graph) error } func pkgID(p *pkggraph.Node) string { // Go quoting rules are similar enough to dot quoting. // At least enough similar to quote a Go import path. return strconv.Quote(p.ID) } goda-0.5.7/internal/graph/color.go000066400000000000000000000020331442047035200170160ustar00rootroot00000000000000package graph import ( "fmt" "math" ) func hslahex(h, s, l, a float64) string { r, g, b, xa := hsla(h, s, l, a) return fmt.Sprintf("\"#%02x%02x%02x%02x\"", sat8(r), sat8(g), sat8(b), sat8(xa)) } func hue(v1, v2, h float64) float64 { if h < 0 { h += 1 } if h > 1 { h -= 1 } if 6*h < 1 { return v1 + (v2-v1)*6*h } else if 2*h < 1 { return v2 } else if 3*h < 2 { return v1 + (v2-v1)*(2.0/3.0-h)*6 } return v1 } func hsla(h, s, l, a float64) (r, g, b, ra float64) { if s == 0 { return l, l, l, a } _, h = math.Modf(h) var v2 float64 if l < 0.5 { v2 = l * (1 + s) } else { v2 = (l + s) - s*l } v1 := 2*l - v2 r = hue(v1, v2, h+1.0/3.0) g = hue(v1, v2, h) b = hue(v1, v2, h-1.0/3.0) ra = a return } // sat8 converts 0..1 float to 0..255 uint8. // sat8 is short for saturate 8, referring to 8 byte saturation arithmetic. // // sat8(x) = 0 if x < 0 // sat8(x) = 255 if x > 1 func sat8(v float64) uint8 { v *= 255.0 if v >= 255 { return 255 } else if v <= 0 { return 0 } return uint8(v) } goda-0.5.7/internal/graph/digraph.go000066400000000000000000000014711442047035200173230ustar00rootroot00000000000000package graph import ( "fmt" "io" "strings" "text/template" "github.com/loov/goda/internal/pkggraph" ) type Digraph struct { out io.Writer err io.Writer label *template.Template } func (ctx *Digraph) Label(p *pkggraph.Node) string { var labelText strings.Builder err := ctx.label.Execute(&labelText, p) if err != nil { fmt.Fprintf(ctx.err, "template error: %v\n", err) } return labelText.String() } func (ctx *Digraph) Write(graph *pkggraph.Graph) error { labelCache := map[*pkggraph.Node]string{} for _, node := range graph.Sorted { labelCache[node] = ctx.Label(node) } for _, node := range graph.Sorted { fmt.Fprintf(ctx.out, "%s", labelCache[node]) for _, imp := range node.ImportsNodes { fmt.Fprintf(ctx.out, " %s", labelCache[imp]) } fmt.Fprintf(ctx.out, "\n") } return nil } goda-0.5.7/internal/graph/dot.go000066400000000000000000000131731442047035200164750ustar00rootroot00000000000000package graph import ( "crypto/sha256" "fmt" "io" "strings" "text/template" "github.com/loov/goda/internal/pkggraph" "github.com/loov/goda/internal/pkgtree" ) type Dot struct { out io.Writer err io.Writer docs string clusters bool nocolor bool shortID bool label *template.Template } func (ctx *Dot) Label(p *pkggraph.Node) string { var labelText strings.Builder err := ctx.label.Execute(&labelText, p) if err != nil { fmt.Fprintf(ctx.err, "template error: %v\n", err) } return labelText.String() } func (ctx *Dot) ModuleLabel(mod *pkgtree.Module) string { lbl := mod.Mod.Path if mod.Mod.Version != "" { lbl += "@" + mod.Mod.Version } if mod.Local { lbl += " (local)" } if rep := mod.Mod.Replace; rep != nil { lbl += " =>\\n" + rep.Path if rep.Version != "" { lbl += "@" + rep.Version } } return lbl } func (ctx *Dot) TreePackageLabel(tp *pkgtree.Package, parentPrinted bool) string { suffix := "" parentPath := tp.Parent.Path() if parentPrinted && tp.Parent != nil && parentPath != "" { suffix = strings.TrimPrefix(tp.Path(), parentPath+"/") } if suffix != "" && ctx.shortID { defer func(previousID string) { tp.GraphNode.ID = previousID }(tp.GraphNode.ID) tp.GraphNode.ID = suffix } var labelText strings.Builder err := ctx.label.Execute(&labelText, tp.GraphNode) if err != nil { fmt.Fprintf(ctx.err, "template error: %v\n", err) } return labelText.String() } func (ctx *Dot) RepoRef(repo *pkgtree.Repo) string { return fmt.Sprintf(`href=%q`, ctx.docs+repo.Path()) } func (ctx *Dot) ModuleRef(mod *pkgtree.Module) string { return fmt.Sprintf(`href=%q`, ctx.docs+mod.Path()+"@"+mod.Mod.Version) } func (ctx *Dot) TreePackageRef(tp *pkgtree.Package) string { return fmt.Sprintf(`href=%q`, ctx.docs+tp.Path()) } func (ctx *Dot) Ref(p *pkggraph.Node) string { return fmt.Sprintf(`href=%q`, ctx.docs+p.ID) } func (ctx *Dot) writeGraphProperties() { if ctx.nocolor { fmt.Fprintf(ctx.out, " node [fontsize=10 shape=rectangle target=\"_graphviz\"];\n") fmt.Fprintf(ctx.out, " edge [tailport=e];\n") } else { fmt.Fprintf(ctx.out, " node [penwidth=2 fontsize=10 shape=rectangle target=\"_graphviz\"];\n") fmt.Fprintf(ctx.out, " edge [tailport=e penwidth=2];\n") } fmt.Fprintf(ctx.out, " compound=true;\n") fmt.Fprintf(ctx.out, " rankdir=LR;\n") fmt.Fprintf(ctx.out, " newrank=true;\n") fmt.Fprintf(ctx.out, " ranksep=\"1.5\";\n") fmt.Fprintf(ctx.out, " quantum=\"0.5\";\n") } func (ctx *Dot) Write(graph *pkggraph.Graph) error { if ctx.clusters { return ctx.WriteClusters(graph) } else { return ctx.WriteRegular(graph) } } func (ctx *Dot) WriteRegular(graph *pkggraph.Graph) error { fmt.Fprintf(ctx.out, "digraph G {\n") ctx.writeGraphProperties() defer fmt.Fprintf(ctx.out, "}\n") for _, n := range graph.Sorted { fmt.Fprintf(ctx.out, " %v [label=\"%v\" %v %v];\n", pkgID(n), ctx.Label(n), ctx.Ref(n), ctx.colorOf(n)) } for _, src := range graph.Sorted { for _, dst := range src.ImportsNodes { fmt.Fprintf(ctx.out, " %v -> %v [%v];\n", pkgID(src), pkgID(dst), ctx.colorOf(dst)) } } return nil } func (ctx *Dot) WriteClusters(graph *pkggraph.Graph) error { root, err := pkgtree.From(graph) if err != nil { return fmt.Errorf("failed to construct cluster tree: %v", err) } lookup := root.LookupTable() isCluster := map[*pkggraph.Node]bool{} fmt.Fprintf(ctx.out, "digraph G {\n") ctx.writeGraphProperties() defer fmt.Fprintf(ctx.out, "}\n") printed := make(map[pkgtree.Node]bool) var visit func(tn pkgtree.Node) visit = func(tn pkgtree.Node) { switch tn := tn.(type) { case *pkgtree.Repo: if tn.SameAsOnlyModule() { break } printed[tn] = true fmt.Fprintf(ctx.out, "subgraph %q {\n", "cluster_"+tn.Path()) fmt.Fprintf(ctx.out, " label=\"%v\"\n", tn.Path()) fmt.Fprintf(ctx.out, " tooltip=\"%v\"\n", tn.Path()) fmt.Fprintf(ctx.out, " %v\n", ctx.RepoRef(tn)) defer fmt.Fprintf(ctx.out, "}\n") case *pkgtree.Module: printed[tn] = true label := ctx.ModuleLabel(tn) fmt.Fprintf(ctx.out, "subgraph %q {\n", "cluster_"+tn.Path()) fmt.Fprintf(ctx.out, " label=\"%v\"\n", label) fmt.Fprintf(ctx.out, " tooltip=\"%v\"\n", label) fmt.Fprintf(ctx.out, " %v\n", ctx.ModuleRef(tn)) defer fmt.Fprintf(ctx.out, "}\n") case *pkgtree.Package: printed[tn] = true gn := tn.GraphNode if tn.Path() == tn.Parent.Path() { isCluster[tn.GraphNode] = true shape := "circle" if tn.OnlyChild() { shape = "point" } fmt.Fprintf(ctx.out, " %v [label=\"\" tooltip=\"%v\" shape=%v %v rank=0];\n", pkgID(gn), tn.Path(), shape, ctx.colorOf(gn)) } else { label := ctx.TreePackageLabel(tn, printed[tn.Parent]) href := ctx.TreePackageRef(tn) fmt.Fprintf(ctx.out, " %v [label=\"%v\" tooltip=\"%v\" %v %v];\n", pkgID(gn), label, tn.Path(), href, ctx.colorOf(gn)) } } tn.VisitChildren(visit) } root.VisitChildren(visit) for _, src := range graph.Sorted { srctree := lookup[src] for _, dst := range src.ImportsNodes { dstID := pkgID(dst) dstTree := lookup[dst] tooltip := src.ID + " -> " + dst.ID if isCluster[dst] && srctree.Parent != dstTree { fmt.Fprintf(ctx.out, " %v -> %v [tooltip=\"%v\" lhead=%q %v];\n", pkgID(src), dstID, tooltip, "cluster_"+dst.ID, ctx.colorOf(dst)) } else { fmt.Fprintf(ctx.out, " %v -> %v [tooltip=\"%v\" %v];\n", pkgID(src), dstID, tooltip, ctx.colorOf(dst)) } } } return nil } func (ctx *Dot) colorOf(p *pkggraph.Node) string { if ctx.nocolor { return "" } hash := sha256.Sum256([]byte(p.PkgPath)) hue := float64(uint(hash[0])<<8|uint(hash[1])) / 0xFFFF return "color=" + hslahex(hue, 0.9, 0.3, 0.7) } goda-0.5.7/internal/graph/edges.go000066400000000000000000000013751442047035200167770ustar00rootroot00000000000000package graph import ( "fmt" "io" "strings" "text/template" "github.com/loov/goda/internal/pkggraph" ) type Edges struct { out io.Writer err io.Writer label *template.Template } func (ctx *Edges) Label(p *pkggraph.Node) string { var labelText strings.Builder err := ctx.label.Execute(&labelText, p) if err != nil { fmt.Fprintf(ctx.err, "template error: %v\n", err) } return labelText.String() } func (ctx *Edges) Write(graph *pkggraph.Graph) error { labelCache := map[*pkggraph.Node]string{} for _, node := range graph.Sorted { labelCache[node] = ctx.Label(node) } for _, node := range graph.Sorted { for _, imp := range node.ImportsNodes { fmt.Fprintf(ctx.out, "%s %s\n", labelCache[node], labelCache[imp]) } } return nil } goda-0.5.7/internal/graph/graphml.go000066400000000000000000000041101442047035200173300ustar00rootroot00000000000000package graph import ( "bytes" "encoding/xml" "fmt" "io" "strings" "text/template" "github.com/loov/goda/internal/graph/graphml" "github.com/loov/goda/internal/pkggraph" ) type GraphML struct { out io.Writer err io.Writer label *template.Template } func (ctx *GraphML) Label(p *pkggraph.Node) string { var labelText strings.Builder err := ctx.label.Execute(&labelText, p) if err != nil { fmt.Fprintf(ctx.err, "template error: %v\n", err) } return labelText.String() } func (ctx *GraphML) Write(graph *pkggraph.Graph) error { file := graphml.NewFile() file.Graphs = append(file.Graphs, ctx.ConvertGraph(graph)) file.Key = []graphml.Key{ {For: "node", ID: "label", AttrName: "label", AttrType: "string"}, {For: "node", ID: "module", AttrName: "module", AttrType: "string"}, {For: "node", ID: "ynodelabel", YFilesType: "nodegraphics"}, } enc := xml.NewEncoder(ctx.out) enc.Indent("", "\t") err := enc.Encode(file) if err != nil { fmt.Fprintf(ctx.err, "failed to output: %v\n", err) } return nil } func (ctx *GraphML) ConvertGraph(graph *pkggraph.Graph) *graphml.Graph { out := &graphml.Graph{} out.EdgeDefault = graphml.Directed for _, node := range graph.Sorted { outnode := graphml.Node{} outnode.ID = node.ID label := ctx.Label(node) outnode.Attrs.AddNonEmpty("label", label) if node.Package != nil { if node.Package.Module != nil { outnode.Attrs.AddNonEmpty("module", node.Package.Module.Path) } } addYedLabelAttr(&outnode.Attrs, "ynodelabel", label) out.Node = append(out.Node, outnode) for _, imp := range node.ImportsNodes { out.Edge = append(out.Edge, graphml.Edge{ Source: node.ID, Target: imp.ID, }) } } return out } func addYedLabelAttr(attrs *graphml.Attrs, key, value string) { if value == "" { return } var buf bytes.Buffer buf.WriteString(``) if err := xml.EscapeText(&buf, []byte(value)); err != nil { // this shouldn't ever happen panic(err) } buf.WriteString(``) *attrs = append(*attrs, graphml.Attr{Key: key, Value: buf.Bytes()}) } goda-0.5.7/internal/graph/graphml/000077500000000000000000000000001442047035200170055ustar00rootroot00000000000000goda-0.5.7/internal/graph/graphml/xml.go000066400000000000000000000056751442047035200201510ustar00rootroot00000000000000package graphml import ( "bytes" "encoding/xml" ) type File struct { XMLName xml.Name `xml:"graphml"` XMLNS string `xml:"xmlns,attr"` XMLNSXSI string `xml:"xmlns:xsi,attr"` XMLNSY string `xml:"xmlns:y,attr"` XSISchemaLocation string `xml:"xsi:schemalocation,attr"` Key []Key `xml:"key"` Graphs []*Graph `xml:"graph"` } func NewFile() *File { file := &File{} file.XMLNS = "http://graphml.graphdrawing.org/xmlns" file.XMLNSXSI = "http://www.w3.org/2001/XMLSchema-instance" file.XMLNSY = "http://www.yworks.com/xml/graphml" file.XSISchemaLocation = "http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd" return file } type Graph struct { // XMLName xml.Name `xml:"graph"` ID string `xml:"id,attr"` EdgeDefault EdgeDefault `xml:"edgedefault,attr"` Node []Node `xml:"node"` Edge []Edge `xml:"edge"` Hyperedge []Hyperedge `xml:"hyperedge"` // TODO: parse info } type Key struct { ID string `xml:"id,attr"` For string `xml:"for,attr"` AttrName string `xml:"attr.name,attr,omitempty"` AttrType string `xml:"attr.type,attr,omitempty"` YFilesType string `xml:"yfiles.type,attr,omitempty"` } type Node struct { // XMLName xml.Name `xml:"node"` ID string `xml:"id,attr"` Port []Port `xml:"port"` Graph []*Graph `xml:"graph"` Attrs Attrs `xml:"data"` // TODO: parse info } type Port struct { // XMLName xml.Name `xml:"port"` Name string `xml:"name,attr"` } type Edge struct { // XMLName xml.Name `xml:"edge"` ID string `xml:"id,attr,omitempty"` Source string `xml:"source,attr"` Target string `xml:"target,attr"` Directed *bool `xml:"directed,attr,omitempty"` SourcePort string `xml:"sourceport,attr,omitempty"` TargetPort string `xml:"targetport,attr,omitempty"` Attrs Attrs `xml:"data"` } type EdgeDefault string const ( Undirected = EdgeDefault("undirected") Directed = EdgeDefault("directed") ) type Attrs []Attr func (attrs *Attrs) AddNonEmpty(key, value string) { if value == "" { return } *attrs = append(*attrs, Attr{Key: key, Value: escapeText(value)}) } type Attr struct { // XMLName xml.Name `xml:"data"` Key string `xml:"key,attr"` Value []byte `xml:",innerxml"` } type Hyperedge struct { // XMLName xml.Name `xml:"hyperedge"` ID string `xml:"id,attr,omitempty"` Endpoint []Endpoint `xml:"endpoint"` } type Endpoint struct { // XMLName xml.Name `xml:"endpoint"` Node string `xml:"node,attr"` Port string `xml:"port,attr,omitempty"` Type EndpointType `xml:"type,attr,omitempty"` } type EndpointType string const ( EndpointIn = EndpointType("in") EndpointOut = EndpointType("out") EndpointUndir = EndpointType("undir") ) func escapeText(s string) []byte { if s == "" { return []byte{} } var buf bytes.Buffer if err := xml.EscapeText(&buf, []byte(s)); err != nil { // this shouldn't ever happen panic(err) } return buf.Bytes() } goda-0.5.7/internal/graph/tgf.go000066400000000000000000000015331442047035200164640ustar00rootroot00000000000000package graph import ( "fmt" "io" "strings" "text/template" "github.com/loov/goda/internal/pkggraph" ) type TGF struct { out io.Writer err io.Writer label *template.Template } func (ctx *TGF) Label(p *pkggraph.Node) string { var labelText strings.Builder err := ctx.label.Execute(&labelText, p) if err != nil { fmt.Fprintf(ctx.err, "template error: %v\n", err) } return labelText.String() } func (ctx *TGF) Write(graph *pkggraph.Graph) error { indexCache := map[*pkggraph.Node]int64{} for i, node := range graph.Sorted { label := ctx.Label(node) indexCache[node] = int64(i + 1) fmt.Fprintf(ctx.out, "%d %s\n", i+1, label) } fmt.Fprintf(ctx.out, "#\n") for _, node := range graph.Sorted { for _, imp := range node.ImportsNodes { fmt.Fprintf(ctx.out, "%d %d\n", indexCache[node], indexCache[imp]) } } return nil } goda-0.5.7/internal/list/000077500000000000000000000000001442047035200152255ustar00rootroot00000000000000goda-0.5.7/internal/list/cmd.go000066400000000000000000000034401442047035200163200ustar00rootroot00000000000000package list import ( "context" "flag" "fmt" "io" "os" "text/tabwriter" "github.com/google/subcommands" "github.com/loov/goda/internal/pkggraph" "github.com/loov/goda/internal/pkgset" "github.com/loov/goda/internal/templates" ) type Command struct { printStandard bool noAlign bool format string } func (*Command) Name() string { return "list" } func (*Command) Synopsis() string { return "List packages" } func (*Command) Usage() string { return `list : List packages using an expression. See "help expr" for further information about expressions. See "help format" for further information about formatting. ` } func (cmd *Command) SetFlags(f *flag.FlagSet) { f.BoolVar(&cmd.printStandard, "std", false, "print std packages") f.BoolVar(&cmd.noAlign, "noalign", false, "disable aligning tabs") f.StringVar(&cmd.format, "f", "{{.ID}}", "formatting") } func (cmd *Command) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { t, err := templates.Parse(cmd.format) if err != nil { fmt.Fprintf(os.Stderr, "invalid format string: %v\n", err) return subcommands.ExitFailure } if !cmd.printStandard { go pkgset.LoadStd() } result, err := pkgset.Calc(ctx, f.Args()) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) return subcommands.ExitFailure } if !cmd.printStandard { result = pkgset.Subtract(result, pkgset.Std()) } graph := pkggraph.From(result) var w io.Writer = os.Stdout if !cmd.noAlign { w = tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) } for _, p := range graph.Sorted { err := t.Execute(w, p) fmt.Fprintln(w) if err != nil { fmt.Fprintf(os.Stderr, "template error: %v\n", err) } } if w, ok := w.(interface{ Flush() error }); ok { w.Flush() } return subcommands.ExitSuccess } goda-0.5.7/internal/memory/000077500000000000000000000000001442047035200155625ustar00rootroot00000000000000goda-0.5.7/internal/memory/size.go000066400000000000000000000013771442047035200170730ustar00rootroot00000000000000package memory import ( "fmt" "strconv" ) type Bytes int64 func (bytes Bytes) String() string { return ToString(int64(bytes)) } // ToString returns a given size in bytes as a human size string. // Examples: ToString(1) ==> "1B"; ToString(1000) ==> "1KB" func ToString(size int64) string { s := float64(size) switch { case s >= (1<<60)*2/3: return fmt.Sprintf("%.1fEB", s/(1<<60)) case s >= (1<<50)*2/3: return fmt.Sprintf("%.1fPB", s/(1<<50)) case s >= (1<<40)*2/3: return fmt.Sprintf("%.1fTB", s/(1<<40)) case s >= (1<<30)*2/3: return fmt.Sprintf("%.1fGB", s/(1<<30)) case s >= (1<<20)*2/3: return fmt.Sprintf("%.1fMB", s/(1<<20)) case s >= (1<<10)*2/3: return fmt.Sprintf("%.1fKB", s/(1<<10)) } return strconv.Itoa(int(size)) + "B" } goda-0.5.7/internal/pkggraph/000077500000000000000000000000001442047035200160555ustar00rootroot00000000000000goda-0.5.7/internal/pkggraph/graph.go000066400000000000000000000073301442047035200175100ustar00rootroot00000000000000package pkggraph import ( "encoding/json" "sort" "golang.org/x/tools/go/packages" "golang.org/x/tools/go/vcs" "github.com/loov/goda/internal/stat" ) type Graph struct { Packages map[string]*Node Sorted []*Node stat.Stat } func (g *Graph) AddNode(n *Node) { g.Packages[n.ID] = n n.Graph = g } type Node struct { *packages.Package Repo *vcs.RepoRoot ImportsNodes []*Node // Stats about the current node. stat.Stat // Stats about upstream nodes. Up stat.Stat // Stats about downstream nodes. Down stat.Stat Errors []error Graph *Graph } func (n *Node) Pkg() *packages.Package { return n.Package } // From creates a new graph from a map of packages. func From(pkgs map[string]*packages.Package) *Graph { g := &Graph{Packages: map[string]*Node{}} // Create the graph nodes. 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) // Populate the graph's Up and Down stats. for _, n := range g.Packages { importsIDs := cache[n.ID] for _, id := range importsIDs { imported, ok := g.Packages[id] if !ok { // we may not want to print info about every package continue } n.Down.Add(imported.Stat) imported.Up.Add(n.Stat) } } // Build node imports from package imports. for _, n := range g.Packages { for id := range n.Package.Imports { direct, ok := g.Packages[id] if !ok { // TODO: // should we include dependencies where Y is hidden? // X -> [Y] -> Z continue } n.ImportsNodes = append(n.ImportsNodes, direct) } } for _, n := range g.Packages { SortNodes(n.ImportsNodes) } return g } func LoadNode(p *packages.Package) *Node { node := &Node{} node.Package = p if repo, err := vcs.RepoRootForImportPath(p.PkgPath, false); err != nil { node.Errors = append(node.Errors, err) node.Repo = &vcs.RepoRoot{ VCS: &vcs.Cmd{}, Repo: p.PkgPath, Root: p.PkgPath, } } else { node.Repo = repo } 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 }) } type flatNode struct { Package struct { ID string Name string `json:",omitempty"` PkgPath string `json:",omitempty"` Errors []packages.Error `json:",omitempty"` GoFiles []string `json:",omitempty"` CompiledGoFiles []string `json:",omitempty"` OtherFiles []string `json:",omitempty"` IgnoredFiles []string `json:",omitempty"` ExportFile string `json:",omitempty"` Imports map[string]string `json:",omitempty"` } ImportsNodes []string `json:",omitempty"` Stat stat.Stat Up stat.Stat Down stat.Stat Errors []error `json:",omitempty"` } func (p *Node) MarshalJSON() ([]byte, error) { flat := flatNode{ Stat: p.Stat, Up: p.Up, Down: p.Down, Errors: p.Errors, } flat.Package.ID = p.Package.ID flat.Package.Name = p.Package.Name flat.Package.PkgPath = p.Package.PkgPath flat.Package.GoFiles = p.Package.GoFiles flat.Package.CompiledGoFiles = p.Package.CompiledGoFiles flat.Package.OtherFiles = p.Package.OtherFiles flat.Package.IgnoredFiles = p.Package.IgnoredFiles flat.Package.ExportFile = p.Package.ExportFile for _, n := range p.ImportsNodes { flat.ImportsNodes = append(flat.ImportsNodes, n.ID) } if len(p.Package.Imports) > 0 { flat.Package.Imports = make(map[string]string, len(p.Imports)) for path, ipkg := range p.Imports { flat.Package.Imports[path] = ipkg.ID } } return json.Marshal(flat) } goda-0.5.7/internal/pkggraph/imports.go000066400000000000000000000016161442047035200201050ustar00rootroot00000000000000package 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 } goda-0.5.7/internal/pkgset/000077500000000000000000000000001442047035200155475ustar00rootroot00000000000000goda-0.5.7/internal/pkgset/ast/000077500000000000000000000000001442047035200163365ustar00rootroot00000000000000goda-0.5.7/internal/pkgset/ast/ast.go000066400000000000000000000071501442047035200174570ustar00rootroot00000000000000package ast import ( "errors" "fmt" "strings" ) type Expr interface { String() string Tree(ident int) string } type Package string type Select struct { Expr Expr Selector string } type Func struct { Name string Args []Expr } func (p Package) String() string { return string(p) } func (s Select) String() string { return s.Expr.String() + ":" + s.Selector } func (f Func) String() string { var args []string for _, arg := range f.Args { args = append(args, arg.String()) } return f.Name + "(" + strings.Join(args, ", ") + ")" } func (p Package) Tree(ident int) string { return strings.Repeat(" ", ident) + string(p) + "\n" } func (s Select) Tree(ident int) string { return strings.Repeat(" ", ident) + "select " + s.Selector + "\n" + s.Expr.Tree(ident+1) } func (f Func) Tree(ident int) string { result := strings.Repeat(" ", ident) + f.Name + "{\n" for _, arg := range f.Args { result += arg.Tree(ident + 1) } return result } func (f Func) IsContext() bool { return strings.IndexByte(f.Name, '=') >= 0 } func Parse(tokens []Token) (Expr, error) { if len(tokens) == 0 { return nil, nil } p, expr, err := parseCombine(0, tokens, false) if err != nil { return expr, err } if p != len(tokens) { panic("failed to parse") } return expr, nil } func parseCombine(p int, tokens []Token, lookingForOperator bool) (int, Expr, error) { var err error if len(tokens) == 0 { return p, nil, nil } var exprs []Expr for p < len(tokens) { tok := tokens[p] var expr Expr switch tok.Kind { case TPackage: p++ expr = Package(tok.Text) case TFunc, TLeftParen: if tok.Kind == TFunc { // position to the left paren p++ } if p >= len(tokens) || tokens[p].Kind != TLeftParen { panic("unexpected func location") } p++ // skip the left paren funcexpr := Func{tok.Text, nil} if tok.Kind == TLeftParen { funcexpr.Name = "" } for { var arg Expr p, arg, err = parseCombine(p, tokens, false) if err != nil { return p, combine(exprs), err } if arg == nil { return p, combine(exprs), errors.New("empty expression") } funcexpr.Args = append(funcexpr.Args, arg) if tokens[p-1].Kind != TComma { break } } if tok.Kind == TLeftParen { if len(funcexpr.Args) != 1 { return p, combine(exprs), errors.New("comma delimited values between parens") } expr = funcexpr.Args[0] } else { expr = funcexpr } case TOp: p++ if lookingForOperator { return p, combine(exprs), nil } op := tok.Text left := combine(exprs) for { var right Expr p, right, err = parseCombine(p, tokens, true) if err != nil { return p, combine(exprs), err } if right == nil { return p, combine(exprs), errors.New("empty expression") } left = Func{op, []Expr{left, right}} // finished parsing if p == len(tokens) && tokens[p-1].Kind != TOp { break } // finished parsing an expression if tokens[p-1].Kind != TOp { return p, left, nil } op = tokens[p-1].Text } return p, left, nil case TSelector: return p, nil, fmt.Errorf("unexpected selector %q %#v", tokens[p].Kind, tokens[p]) case TRightParen, TComma: p++ return p, combine(exprs), nil default: return p, nil, fmt.Errorf("unhandled token %#v", tokens[p]) } for p < len(tokens) && tokens[p].Kind == TSelector { expr = Select{expr, tokens[p].Text} p++ } exprs = append(exprs, expr) } return p, combine(exprs), nil } func combine(exprs []Expr) Expr { if len(exprs) == 0 { return nil } if len(exprs) == 1 { return exprs[0] } return Func{"", exprs} } goda-0.5.7/internal/pkgset/ast/ast_test.go000066400000000000000000000071311442047035200205150ustar00rootroot00000000000000package ast import ( "reflect" "testing" ) func TestParsing(t *testing.T) { tests := []struct { input string clean string tokens []Token }{{ "", "", nil, }, { "golang.org/x/tools/...", "golang.org/x/tools/...", []Token{ {TPackage, "golang.org/x/tools/..."}, }, }, { " github.com/loov/goda golang.org/x/tools/... ", "(github.com/loov/goda, golang.org/x/tools/...)", []Token{ {TPackage, "github.com/loov/goda"}, {TPackage, "golang.org/x/tools/..."}, }, }, { " github.com/loov/goda + golang.org/x/tools/... ", "+(github.com/loov/goda, golang.org/x/tools/...)", []Token{ {TPackage, "github.com/loov/goda"}, {TOp, "+"}, {TPackage, "golang.org/x/tools/..."}, }, }, { "std - (std - unsafe:all)", "-(std, -(std, unsafe:all))", []Token{ {TPackage, "std"}, {TOp, "-"}, {TLeftParen, "("}, {TPackage, "std"}, {TOp, "-"}, {TPackage, "unsafe"}, {TSelector, "all"}, {TRightParen, ")"}, }, }, { " github.com/loov/goda:all - golang.org/x/tools/... ", "-(github.com/loov/goda:all, golang.org/x/tools/...)", []Token{ {TPackage, "github.com/loov/goda"}, {TSelector, "all"}, {TOp, "-"}, {TPackage, "golang.org/x/tools/..."}, }, }, { "Reaches(github.com/loov/goda + github.com/loov/qloc, golang.org/x/tools/...:all)", "Reaches(+(github.com/loov/goda, github.com/loov/qloc), golang.org/x/tools/...:all)", []Token{ {TFunc, "Reaches"}, {TLeftParen, "("}, {TPackage, "github.com/loov/goda"}, {TOp, "+"}, {TPackage, "github.com/loov/qloc"}, {TComma, ","}, {TPackage, "golang.org/x/tools/..."}, {TSelector, "all"}, {TRightParen, ")"}, }, }, { "Reaches(github.com/loov/goda, golang.org/x/tools/...:all):import:all", "Reaches(github.com/loov/goda, golang.org/x/tools/...:all):import:all", []Token{ {TFunc, "Reaches"}, {TLeftParen, "("}, {TPackage, "github.com/loov/goda"}, {TComma, ","}, {TPackage, "golang.org/x/tools/..."}, {TSelector, "all"}, {TRightParen, ")"}, {TSelector, "import"}, {TSelector, "all"}, }, }, { "test=1(github.com/loov/goda)", "test=1(github.com/loov/goda)", []Token{ {TFunc, "test=1"}, {TLeftParen, "("}, {TPackage, "github.com/loov/goda"}, {TRightParen, ")"}, }, }, { "test=1(github.com/loov/goda) - test=0(github.com/loov/goda)", "-(test=1(github.com/loov/goda), test=0(github.com/loov/goda))", []Token{ {TFunc, "test=1"}, {TLeftParen, "("}, {TPackage, "github.com/loov/goda"}, {TRightParen, ")"}, {TOp, "-"}, {TFunc, "test=0"}, {TLeftParen, "("}, {TPackage, "github.com/loov/goda"}, {TRightParen, ")"}, }, }, { "x:-test:+test", "x:-test:+test", []Token{ {TPackage, "x"}, {TSelector, "-test"}, {TSelector, "+test"}, }, }, { "(x + y):+test", "+(x, y):+test", []Token{ {TLeftParen, "("}, {TPackage, "x"}, {TOp, "+"}, {TPackage, "y"}, {TRightParen, ")"}, {TSelector, "+test"}, }, }} for _, test := range tests { tokens, err := Tokenize(test.input) if err != nil { t.Errorf("\nlex %q\n\tgot:%v\n\terr:%v", test.input, tokens, err) continue } if len(tokens) == 0 { tokens = nil } if !reflect.DeepEqual(tokens, test.tokens) { t.Errorf("\nlex %q\n\texp:%v\n\tgot:%v", test.input, test.tokens, tokens) continue } expr, err := Parse(tokens) if err != nil { t.Errorf("\nparse %q\n\terr:%v", test.input, err) continue } if expr == nil { continue } clean := expr.String() if clean != test.clean { t.Errorf("\nparse %q\n\texp:%v\n\tgot:%v", test.input, test.clean, clean) t.Log("\nTREE\n", expr.Tree(0)) continue } } } goda-0.5.7/internal/pkgset/ast/tokens.go000066400000000000000000000046631442047035200202010ustar00rootroot00000000000000package ast import ( "fmt" "strings" ) type Token struct { Kind Kind Text string } type Kind byte const ( TUnknown Kind = '?' TOp Kind = 'o' TComma Kind = ',' TSelector Kind = 's' TFunc Kind = 'f' TLeftParen Kind = '(' TRightParen Kind = ')' TPackage Kind = 'p' ) func (k Kind) String() string { return string(k) } func Tokenize(s string) ([]Token, error) { var tokens []Token emit := func(kind Kind, text string) { tokens = append(tokens, Token{kind, text}) } p := 0 for p < len(s) { // skip whitespace for p < len(s) && s[p] == ' ' { p++ } // finish when everything is parsed if p >= len(s) { break } var ident string p, ident = parseIdent(p, s) if ident != "" { if p < len(s) && s[p] == '(' { emit(TFunc, ident) continue } if strings.Contains(ident, "=") { return tokens, fmt.Errorf("package name %q shouldn't contain '='", ident) } emit(TPackage, ident) continue } switch s[p] { case '(': p++ emit(TLeftParen, "(") case ')': p++ emit(TRightParen, ")") case ':': p++ var selector string p, selector = parseSelector(p, s) if selector == "" { return tokens, fmt.Errorf("expected selector %d", p) } emit(TSelector, selector) case '+', '-': op := string(s[p]) p++ if p < len(s) && s[p] == '(' { emit(TFunc, op) continue } emit(TOp, op) case ',': p++ emit(TComma, ",") default: return tokens, fmt.Errorf("unknown symbol at %d: %s", p, string(s[p])) } } return tokens, nil } func isIdentFirst(p byte) bool { return (p == '.') || ('a' <= p && p <= 'z') || ('A' <= p && p <= 'Z') || ('0' <= p && p <= '9') } func isPrefixOp(p byte) bool { return p == '+' || p == '-' } func isIdent(p byte) bool { return (p == '.') || (p == '@') || (p == '_') || (p == '-') || (p == '/') || ('a' <= p && p <= 'z') || ('A' <= p && p <= 'Z') || ('0' <= p && p <= '9') || (p == '=') // for build tags } func parseIdent(start int, s string) (int, string) { if start >= len(s) { return start, "" } if !isIdentFirst(s[start]) { return start, "" } p := start for p < len(s) && isIdent(s[p]) { p++ } return p, s[start:p] } func parseSelector(start int, s string) (int, string) { if start >= len(s) { return start, "" } if !isIdentFirst(s[start]) && !isPrefixOp(s[start]) { return start, "" } p := start + 1 for p < len(s) && isIdent(s[p]) { p++ } return p, s[start:p] } goda-0.5.7/internal/pkgset/calc.go000066400000000000000000000130521442047035200170010ustar00rootroot00000000000000package pkgset import ( "context" "errors" "fmt" "os" "strings" "github.com/loov/goda/internal/pkgset/ast" ) // Parse converts the expression represented by the expr strings into an AST // representation. func Parse(_ context.Context, expr []string) (ast.Expr, error) { tokens, err := ast.Tokenize(strings.Join(expr, " ")) if err != nil { return nil, fmt.Errorf("failed to tokenize: %v", err) } root, err := ast.Parse(tokens) if err != nil { return nil, fmt.Errorf("failed to parse: %v", err) } return root, nil } // Calc parses expr and computes the set of packages it describes. func Calc(parentContext context.Context, expr []string) (Set, error) { if len(expr) == 0 { expr = []string{"."} } rootExpr, err := Parse(parentContext, expr) if err != nil { return New(), err } var eval func(*Context, ast.Expr) (Set, error) evalArgs := func(ctx *Context, exprs []ast.Expr) ([]Set, error) { args := make([]Set, len(exprs)) var errs []error for i, expr := range exprs { var err error args[i], err = eval(ctx, expr) if err != nil { errs = append(errs, err) } } if len(errs) == 1 { return args, errs[0] } if len(errs) > 1 { return args, fmt.Errorf("%v", errs) } return args, nil } eval = func(ctx *Context, e ast.Expr) (Set, error) { if e == nil { return nil, errors.New("empty expression") } switch e := e.(type) { case ast.Package: roots, err := ctx.Load(string(e)) return NewRoot(roots...), err case ast.Func: if e.IsContext() { subctx := ctx.Clone() key, value := KeyValue(e.Name) subctx.Set(key, value) if len(e.Args) != 1 { return nil, fmt.Errorf("expected 1 argument found %d", len(e.Args)) } return eval(subctx, e.Args[0]) } switch strings.ToLower(e.Name) { case "": args := extractLoadGroup(e) if len(args) > 0 { roots, err := ctx.Load(args...) return NewRoot(roots...), err } // fallback to union implementation fallthrough // binary operators case "+", "add", "or", "-", "subtract", "exclude", "shared", "intersect", "xor": args, err := evalArgs(ctx, e.Args) if len(args) == 0 { return New(), err } var op func(a, b Set) Set switch strings.ToLower(e.Name) { case " ", "+", "add", "or": op = Union case "-", "subtract", "exclude": op = Subtract case "shared", "intersect": op = Intersect case "xor": op = SymmetricDifference default: return nil, fmt.Errorf("unknown op %q", e.Name) } base := args[0] for _, arg := range args[1:] { base = op(base, arg) } return base, nil case "reach": if len(e.Args) != 2 { return nil, fmt.Errorf("reach requires two arguments: %v", e) } args, err := evalArgs(ctx, e.Args) return Reach(args[0], args[1]), err case "incoming": if len(e.Args) != 2 { return nil, fmt.Errorf("incoming requires two arguments: %v", e) } args, err := evalArgs(ctx, e.Args) return Incoming(args[0], args[1]), err case "transitive": if len(e.Args) != 1 { return nil, fmt.Errorf("transitive requires one argument: %v", e) } args, err := evalArgs(ctx, e.Args) return Transitive(args[0]), err default: return nil, fmt.Errorf("unknown func %v: %v", e.Name, e) } case ast.Select: combineOp, selector := "", e.Selector combine := func(source, result Set) Set { return result } switch selector[0] { case '+': combine = Union combineOp, selector = selector[:1], selector[1:] case '-': combine = Subtract combineOp, selector = selector[:1], selector[1:] } switch strings.ToLower(selector) { case "all": set, err := eval(ctx, e.Expr) if err != nil { return nil, err } return combine(set, NewAll(set)), nil case "import", "imp": set, err := eval(ctx, e.Expr) if err != nil { return nil, err } return combine(set, DirectDependencies(set)), nil case "source": set, err := eval(ctx, e.Expr) if err != nil { return nil, err } return combine(set, Sources(set)), nil case "nosource": // Deprecated set, err := eval(ctx, e.Expr) if err != nil { return nil, err } return combine(set, Subtract(set, Sources(set))), nil case "main": set, err := eval(ctx, e.Expr) if err != nil { return nil, err } return combine(set, Main(set)), nil case "test": if pkg, ok := e.Expr.(ast.Package); ok { switch combineOp { case "+": roots, err := ctx.LoadWithTests(string(pkg)) return NewRoot(roots...), err case "-": roots, err := ctx.LoadWithoutTests(string(pkg)) return NewRoot(roots...), err case "": roots, err := ctx.LoadWithTests(string(pkg)) withTests := NewRoot(roots...) return Test(withTests), err default: return nil, fmt.Errorf("unhandled combine op %q", combineOp) } } set, err := eval(ctx, e.Expr) if err != nil { return nil, err } roots, err := ctx.LoadWithTests(set.IDs()...) withTests := NewRoot(roots...) return combine(set, Test(withTests)), err default: return nil, fmt.Errorf("unknown selector %v: %v", e.Selector, e) } default: return nil, fmt.Errorf("unknown token %T", e) } } return eval(&Context{ Context: parentContext, Env: Strings(os.Environ()), }, rootExpr) } func extractLoadGroup(fn ast.Func) []string { var pkgs []string for _, arg := range fn.Args { pkg, ok := arg.(ast.Package) if !ok { return nil } pkgs = append(pkgs, string(pkg)) } return pkgs } goda-0.5.7/internal/pkgset/context.go000066400000000000000000000054421442047035200175670ustar00rootroot00000000000000package pkgset import ( "context" "strings" "golang.org/x/tools/go/packages" ) var envvars = map[string]struct{}{ "GOOS": {}, "GOARCH": {}, "GOENV": {}, "GOFLAGS": {}, "GOROOT": {}, "CGO_ENABLED": {}, } var packageAliases = map[string]string{ "C": "runtime/cgo", } func replaceAliases(patterns ...string) []string { xs := append([]string{}, patterns...) for i, x := range xs { if alias, ok := packageAliases[x]; ok { xs[i] = alias } } return xs } type Context struct { Context context.Context Tags Strings Env Strings } func (ctx Context) Clone() *Context { return &Context{ Context: ctx.Context, Tags: ctx.Tags.Clone(), Env: ctx.Env.Clone(), } } func (ctx Context) Load(patterns ...string) ([]*packages.Package, error) { return packages.Load(ctx.Config(), replaceAliases(patterns...)...) } func (ctx Context) LoadWithTests(patterns ...string) ([]*packages.Package, error) { config := ctx.Config() config.Tests = true return packages.Load(config, replaceAliases(patterns...)...) } func (ctx Context) LoadWithoutTests(patterns ...string) ([]*packages.Package, error) { config := ctx.Config() config.Tests = false return packages.Load(config, replaceAliases(patterns...)...) } func (ctx *Context) Set(key, value string) { if _, ok := envvars[strings.ToUpper(key)]; ok { ctx.Env.Set(strings.ToUpper(key), value) return } ctx.Tags.Set(key, value) } func (ctx Context) Config() *packages.Config { config := &packages.Config{ Context: ctx.Context, Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedModule, Env: ctx.Env, Tests: ctx.Tags.ValueOf("test") == "1", } tags := []string{} for _, tag := range ctx.Tags { key, value := KeyValue(tag) if strings.EqualFold("test", key) { continue } if value == "1" { tags = append(tags, key) } } if len(tags) > 0 { config.BuildFlags = append(config.BuildFlags, "-tags="+strings.Join(tags, ",")) } return config } type Strings []string func (strs *Strings) Set(key, value string) { i := strs.IndexOf(key) if i < 0 { *strs = append(*strs, key+"="+value) return } (*strs)[i] = key + "=" + value } func (strs Strings) ValueOf(key string) string { i := strs.IndexOf(key) if i < 0 { return "" } _, value := KeyValue(strs[i]) return value } func (strs Strings) IndexOf(key string) int { prefix := strings.ToLower(key + "=") for i, x := range strs { x = strings.ToLower(x) if strings.HasPrefix(x, prefix) { return i } } return -1 } func (strs Strings) Clone() Strings { return append(Strings{}, strs...) } // KeyValue parses s into a key and value. func KeyValue(s string) (string, string) { p := strings.LastIndexByte(s, '=') if p < 0 { return s, "" } return s[:p], s[p+1:] } goda-0.5.7/internal/pkgset/set.go000066400000000000000000000131341442047035200166730ustar00rootroot00000000000000package pkgset import ( "sort" "strings" "golang.org/x/tools/go/packages" ) // Set is a p.ID -> *packages.Package type Set map[string]*packages.Package // New makes a set from roots recursively func New(roots ...*packages.Package) Set { set := make(Set) for _, p := range roots { set.IncludeRecursive(p) } return set } // NewRoot makes a set from roots recursively func NewRoot(roots ...*packages.Package) Set { set := make(Set) for _, p := range roots { set[p.ID] = p } return set } // NewAll includes all the dependencies from the graph. func NewAll(src Set) Set { set := make(Set) for _, p := range src { set.IncludeRecursive(p) } return set } // List returns packages in unsorted order. func (set Set) List() []*packages.Package { dst := make([]*packages.Package, 0, len(set)) for _, p := range set { dst = append(dst, p) } return dst } // IDs returns package ID-s in sorted order. func (set Set) IDs() []string { rs := []string{} for id := range set { rs = append(rs, id) } sort.Strings(rs) return rs } // Sorted returns packages in sorted order func (set Set) Sorted() []*packages.Package { var list []*packages.Package for _, pkg := range set { list = append(list, pkg) } sort.Slice(list, func(i, k int) bool { return list[i].ID < list[k].ID }) return list } func (set Set) Walk(fn func(*packages.Package)) { for _, p := range set { fn(p) } } func (set Set) WalkDependencies(fn func(*packages.Package)) { seen := map[string]bool{} var walk func(*packages.Package) walk = func(p *packages.Package) { if seen[p.ID] { return } seen[p.ID] = true fn(p) for _, child := range p.Imports { walk(child) } } for _, p := range set { walk(p) } } // IncludeRecursive adds p recursively func (set Set) IncludeRecursive(p *packages.Package) { if _, added := set[p.ID]; added { return } set[p.ID] = p for _, imp := range p.Imports { set.IncludeRecursive(imp) } } // Clone makes a copy of the set func (set Set) Clone() Set { r := make(Set, len(set)) for pid, p := range set { r[pid] = p // TODO: make a deep clone } return r } // Union includes packages from both sets func Union(a, b Set) Set { if len(a) == 0 { return b.Clone() } r := a.Clone() for pid, p := range b { if _, exists := r[pid]; !exists { r[pid] = p } } return r } // Subtract returns packages that exist in a, but not in b func Subtract(a, b Set) Set { r := a.Clone() for pid := range b { delete(r, pid) } return r } // Intersect returns packages that exist in both func Intersect(a, b Set) Set { r := make(Set, len(a)) for pid := range b { if p, ok := a[pid]; ok { r[pid] = p } } return r } // SymmetricDifference returns packages that are different func SymmetricDifference(a, b Set) Set { r := make(Set, len(a)) for pid, p := range a { if _, ok := b[pid]; !ok { r[pid] = p } } for pid, p := range b { if _, ok := a[pid]; !ok { r[pid] = p } } return r } // Reach returns packages in a that terminate in b func Reach(a, b Set) Set { result := New() reaches := b.Clone() cannotReach := New() var checkReachability func(p *packages.Package) bool checkReachability = func(p *packages.Package) bool { if _, ok := reaches[p.ID]; ok { return true } if _, ok := cannotReach[p.ID]; ok { return false } for _, dep := range p.Imports { if checkReachability(dep) { if _, ina := a[p.ID]; ina { result[p.ID] = p } reaches[p.ID] = p return true } } cannotReach[p.ID] = p return false } for _, p := range a { if _, reaches := b[p.ID]; reaches { result[p.ID] = p continue } checkReachability(p) } return result } // Incoming returns packages from a that directly import packages in b. func Incoming(a, b Set) Set { result := b.Clone() next: for _, x := range a { for _, imp := range x.Imports { if _, ok := b[imp.ID]; ok { result[x.ID] = x continue next } } } return result } // Transitive returns transitive reduction. func Transitive(a Set) Set { result := a.Clone() var includeDeps func(p *packages.Package, r map[string]struct{}) includeDeps = func(p *packages.Package, r map[string]struct{}) { for _, c := range p.Imports { if _, visited := r[c.ID]; visited { continue } r[c.ID] = struct{}{} includeDeps(c, r) } } for _, p := range result { indirectDeps := make(map[string]struct{}) for _, c := range p.Imports { includeDeps(c, indirectDeps) } for dep := range indirectDeps { delete(p.Imports, dep) } } return result } // Sources returns packages that don't have incoming edges func Sources(a Set) Set { incoming := map[string]int{} a.WalkDependencies(func(p *packages.Package) { for _, dep := range p.Imports { incoming[dep.ID]++ } }) result := New() for _, p := range a { if incoming[p.ID] == 0 { result[p.ID] = p } } return result } // DirectDependencies returns packages that are direct dependencies of a, `a` not included. func DirectDependencies(a Set) Set { rs := map[string]*packages.Package{} for _, p := range a { for _, dep := range p.Imports { if _, ok := a[dep.ID]; ok { continue } rs[dep.ID] = dep } } return rs } // Main returns main pacakges. func Main(a Set) Set { rs := Set{} for pid, pkg := range a { if pkg.Name == "main" { rs[pid] = pkg } } return rs } // Test returns test packages from set. func Test(a Set) Set { rs := Set{} for pid, pkg := range a { if IsTestPkg(pkg) { rs[pid] = pkg } } return rs } func IsTestPkg(pkg *packages.Package) bool { return strings.HasSuffix(pkg.ID, ".test") || strings.HasSuffix(pkg.ID, "_test") || strings.HasSuffix(pkg.ID, ".test]") } goda-0.5.7/internal/pkgset/std.go000066400000000000000000000014621442047035200166730ustar00rootroot00000000000000package pkgset import ( "sync" "golang.org/x/tools/go/packages" ) var stdpkgs Set var stdonce sync.Once // LoadStd preloads the std package list. func LoadStd() { stdonce.Do(func() { standard, err := packages.Load(&packages.Config{ Mode: packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedModule, Tests: true, }, "std") if err != nil { panic(err) } stdpkgs = New(standard...) }) } // IsStd returns whether *packages.Package is a std package func IsStd(p *packages.Package) bool { LoadStd() return IsStdName(p.ID) } // IsStdName returns whether id corresponds to a standard package func IsStdName(id string) bool { LoadStd() _, ok := stdpkgs[id] return ok } // Std returns the standard package set func Std() Set { LoadStd() return stdpkgs.Clone() } goda-0.5.7/internal/pkgtree/000077500000000000000000000000001442047035200157135ustar00rootroot00000000000000goda-0.5.7/internal/pkgtree/tree.go000066400000000000000000000116721442047035200172100ustar00rootroot00000000000000package pkgtree import ( "fmt" "os/exec" "path/filepath" "sort" "strings" "github.com/loov/goda/internal/pkggraph" "golang.org/x/mod/module" "golang.org/x/tools/go/packages" "golang.org/x/tools/go/vcs" ) func From(g *pkggraph.Graph) (*Tree, error) { goModCachePath, err := goModCache() if err != nil { return nil, err } t := Tree{ Repos: make(map[string]*Repo), } for _, n := range g.Sorted { repo := t.NodeRepo(n) if n.Module != nil { mod := repo.NodeModule(n, goModCachePath) mod.NodePackage(n) } else { repo.NodePackage(n) } } t.Walk(func(tn Node) { if s, ok := tn.(interface{ Sort() }); ok { s.Sort() } }) return &t, nil } type Node interface { Path() string Package() *Package VisitChildren(func(Node)) } type Tree struct { Repos map[string]*Repo sortedRepos []string } func (t *Tree) Path() string { return "" } func (t *Tree) Package() *Package { return nil } func (t *Tree) LookupTable() map[*pkggraph.Node]*Package { table := map[*pkggraph.Node]*Package{} t.Walk(func(tn Node) { if pkg := tn.Package(); pkg != nil { table[pkg.GraphNode] = pkg } }) return table } func (t *Tree) NodeRepo(n *pkggraph.Node) *Repo { repo, ok := t.Repos[n.Repo.Root] if !ok { repo = &Repo{ Root: n.Repo, Modules: make(map[string]*Module), Pkgs: make(map[string]*Package), } t.Repos[n.Repo.Root] = repo t.sortedRepos = append(t.sortedRepos, n.Repo.Root) } return repo } func (t *Tree) Walk(fn func(Node)) { fn(t) var visit func(Node) visit = func(tn Node) { fn(tn) tn.VisitChildren(visit) } t.VisitChildren(visit) } func (t *Tree) VisitChildren(fn func(Node)) { for _, rp := range t.sortedRepos { fn(t.Repos[rp]) } } func (t *Tree) Sort() { sort.Strings(t.sortedRepos) } type Repo struct { Root *vcs.RepoRoot Modules map[string]*Module sortedMods []string Pkgs map[string]*Package sortedPkgs []string } func (r *Repo) Path() string { return r.Root.Root } func (r *Repo) Package() *Package { return nil } func (r *Repo) SameAsOnlyModule() bool { if len(r.Modules) != 1 { return false } mod := r.Modules[r.sortedMods[0]] prefix, pathMajor, ok := module.SplitPathVersion(mod.Mod.Path) if !ok || r.Path() != prefix { return false } if pathMajor == "" || mod.Local { // Local modules will not have a version, assume it is the same without // checking the major version matches. return true } return module.CheckPathMajor(mod.Mod.Version, pathMajor) == nil } func (r *Repo) NodeModule(n *pkggraph.Node, goModCachePath string) *Module { mod, ok := r.Modules[n.Module.Path] if !ok { mod = &Module{ Parent: r, Mod: n.Module, Pkgs: make(map[string]*Package), } if n.Module.Replace == nil { if rp, err := filepath.Rel(goModCachePath, n.Module.Dir); err == nil { // If the module is in the module cache its path relative to GOMODCACHE // will not start with "..". If it does, then it is outside the // GOMODCACHE and is likely a local copy of the module. mod.Local = strings.HasPrefix(rp, "..") } } r.Modules[n.Module.Path] = mod r.sortedMods = append(r.sortedMods, n.Module.Path) } return mod } func (r *Repo) NodePackage(n *pkggraph.Node) *Package { pkg, ok := r.Pkgs[n.ID] if !ok { pkg = &Package{ Parent: r, GraphNode: n, } r.Pkgs[n.ID] = pkg r.sortedPkgs = append(r.sortedPkgs, n.ID) } return pkg } func (r *Repo) VisitChildren(fn func(Node)) { for _, mp := range r.sortedMods { fn(r.Modules[mp]) } for _, pp := range r.sortedPkgs { fn(r.Pkgs[pp]) } } func (r *Repo) Sort() { sort.Strings(r.sortedMods) sort.Strings(r.sortedPkgs) } type Module struct { Parent *Repo Mod *packages.Module Local bool Pkgs map[string]*Package sortedPkgs []string } func (m *Module) Path() string { return m.Mod.Path } func (m *Module) Package() *Package { return nil } func (m *Module) NodePackage(n *pkggraph.Node) *Package { pkg, ok := m.Pkgs[n.ID] if !ok { pkg = &Package{ Parent: m, GraphNode: n, } m.Pkgs[n.ID] = pkg m.sortedPkgs = append(m.sortedPkgs, n.ID) } return pkg } func (m *Module) VisitChildren(fn func(Node)) { for _, pp := range m.sortedPkgs { fn(m.Pkgs[pp]) } } func (m *Module) Sort() { sort.Strings(m.sortedPkgs) } type Package struct { Parent Node GraphNode *pkggraph.Node } func (p *Package) Path() string { return p.GraphNode.PkgPath } func (p *Package) Package() *Package { return p } func (p *Package) OnlyChild() bool { count := 0 p.Parent.VisitChildren(func(Node) { count++ }) return count == 1 } func (p *Package) VisitChildren(_ func(Node)) {} func goModCache() (string, error) { cmd := exec.Command("go", "env", "GOMODCACHE") out, err := cmd.Output() switch err := err.(type) { case *exec.ExitError: return "", fmt.Errorf("failed to determine GOMODCACHE: %s", err.Stderr) default: return "", fmt.Errorf("failed to determine GOMODCACHE: %s", err) case nil: // just continue } return strings.TrimSpace(string(out)), nil } goda-0.5.7/internal/stat/000077500000000000000000000000001442047035200152255ustar00rootroot00000000000000goda-0.5.7/internal/stat/decl.go000066400000000000000000000014171442047035200164660ustar00rootroot00000000000000package stat import ( "go/ast" "go/token" ) // Decls stats about top-level declarations. type Decls struct { Func int64 Type int64 Const int64 Var int64 Other int64 } func (s *Decls) Add(b Decls) { s.Func += b.Func s.Type += b.Type s.Const += b.Const s.Var += b.Var s.Other += b.Other } func (s *Decls) Total() int64 { return s.Func + s.Type + s.Const + s.Var + s.Other } func DeclsFromAst(f *ast.File) Decls { stat := Decls{} 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 } goda-0.5.7/internal/stat/info.go000066400000000000000000000025341442047035200165130ustar00rootroot00000000000000package stat import ( "errors" "fmt" "go/parser" "go/token" "io/ioutil" "golang.org/x/tools/go/packages" ) type Stat struct { PackageCount int64 Go Source OtherFiles Source Decls Decls Tokens Tokens } func (info *Stat) AllFiles() Source { var c Source c.Add(info.Go) c.Add(info.OtherFiles) return c } func (s *Stat) Add(b Stat) { s.PackageCount += b.PackageCount s.Go.Add(b.Go) s.OtherFiles.Add(b.OtherFiles) s.Decls.Add(b.Decls) s.Tokens.Add(b.Tokens) } func Package(p *packages.Package) (Stat, []error) { var info Stat var errs []error info.PackageCount = 1 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.Go.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.Decls.Add(DeclsFromAst(f)) info.Tokens.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 } goda-0.5.7/internal/stat/source.go000066400000000000000000000037561442047035200170670ustar00rootroot00000000000000package stat import ( "errors" "io" "os" "github.com/loov/goda/internal/memory" ) // Source contains basic analysis of arbitrary source code. type Source struct { // Files count in this stat. Files int // Binary file count. Binary int // Size in bytes of all files. Size memory.Bytes // Count of non-empty lines. Lines int // Count of empty lines. Blank int } func (c *Source) Add(s Source) { c.Files += s.Files c.Binary += s.Binary c.Size += s.Size c.Blank += s.Blank c.Lines += s.Lines } func SourceFromBytes(data []byte) Source { count := Source{Files: 1} if len(data) == 0 { return count } count.Size += memory.Bytes(len(data)) emptyline := true for _, c := range data { switch c { case 0x0: count.Blank = 0 count.Lines = 0 count.Files = 0 count.Binary = 1 return count case '\n': if emptyline { count.Blank++ } else { count.Lines++ } emptyline = true case '\r', ' ', '\t': // ignore default: emptyline = false } } if !emptyline { count.Lines++ } return count } var ErrEmptyFile = errors.New("empty file") func SourceFromPath(path string) (Source, error) { count := Source{ Files: 1, } file, err := os.Open(path) if err != nil { return count, err } defer file.Close() stat, err := file.Stat() if err != nil { return count, err } if stat.Size() <= 0 { return count, ErrEmptyFile } count.Size += memory.Bytes(stat.Size()) buf := make([]byte, 8196) emptyline := true for { n, err := file.Read(buf) if err != nil && err != io.EOF { return count, err } for _, c := range buf[:n] { switch c { case 0x0: count.Blank = 0 count.Lines = 0 count.Files = 0 count.Binary = 1 return count, nil case '\n': if emptyline { count.Blank++ } else { count.Lines++ } emptyline = true case '\r', ' ', '\t': // ignore default: emptyline = false } } if err == io.EOF { break } } if !emptyline { count.Lines++ } return count, nil } goda-0.5.7/internal/stat/token.go000066400000000000000000000010371442047035200166750ustar00rootroot00000000000000package stat import "go/ast" type Tokens struct { Code int64 Comment int64 Basic int64 } func (stat *Tokens) Add(b Tokens) { stat.Code += b.Code stat.Comment += b.Comment stat.Basic += b.Basic } func TokensFromAst(f *ast.File) Tokens { stat := Tokens{} ast.Inspect(f, func(n ast.Node) bool { if n == nil { return true } switch n.(type) { default: stat.Code++ case *ast.BasicLit: stat.Basic++ case *ast.CommentGroup, *ast.Comment: stat.Comment++ return false } return true }) return stat } goda-0.5.7/internal/templates/000077500000000000000000000000001442047035200162505ustar00rootroot00000000000000goda-0.5.7/internal/templates/numeric.go000066400000000000000000000037531442047035200202510ustar00rootroot00000000000000package templates import ( "math" "strconv" "text/template" "github.com/loov/goda/internal/memory" ) func numericFuncs() template.FuncMap { return template.FuncMap{ "add": add, "div": div, "sub": sub, "mul": mul, "float": toFloat64, "int": func(v interface{}) int64 { return int64(toFloat64(v)) }, "round": func(v interface{}) float64 { return math.Round(toFloat64(v)) }, "log": func(v interface{}) float64 { return math.Log(toFloat64(v)) }, "log10": func(v interface{}) float64 { return math.Log10(toFloat64(v)) }, "log2": func(v interface{}) float64 { return math.Log2(toFloat64(v)) }, } } func add(xs ...interface{}) float64 { if len(xs) == 0 { return math.NaN() } total := toFloat64(xs[0]) for _, x := range xs[1:] { total += toFloat64(x) } return total } func div(xs ...interface{}) float64 { if len(xs) == 0 { return math.NaN() } total := toFloat64(xs[0]) for _, x := range xs[1:] { total /= toFloat64(x) } return total } func sub(xs ...interface{}) float64 { if len(xs) == 0 { return math.NaN() } total := toFloat64(xs[0]) for _, x := range xs[1:] { total -= toFloat64(x) } return total } func mul(xs ...interface{}) float64 { if len(xs) == 0 { return math.NaN() } total := toFloat64(xs[0]) for _, x := range xs[1:] { total *= toFloat64(x) } return total } func toFloat64(v interface{}) float64 { switch v := v.(type) { case float64: return v case float32: return float64(v) case int: return float64(v) case int64: return float64(v) case int32: return float64(v) case int16: return float64(v) case int8: return float64(v) case uint: return float64(v) case uint64: return float64(v) case uint32: return float64(v) case uint16: return float64(v) case uint8: return float64(v) case memory.Bytes: return float64(v) case string: if x, err := strconv.ParseFloat(v, 64); err == nil { return x } return math.NaN() case bool: if v { return 1 } return 0 default: return math.NaN() } } goda-0.5.7/internal/templates/string.go000066400000000000000000000024471442047035200201140ustar00rootroot00000000000000package templates import ( "encoding/json" "strings" "text/template" ) func stringFuncs() template.FuncMap { return template.FuncMap{ "rel": rel, "rename": rename, "json": jsonify, } } func rel(prefix, id string) string { if x, ok := replace(id, prefix, "./"); ok { return x } return id } func rename(args ...string) string { if len(args) == 0 { return "" } id, replacements := args[len(args)-1], args[:len(args)-1] for i := 0; i < len(replacements); i += 2 { prefix := replacements[i] replacement := "./" if i+1 < len(replacements) { replacement = replacements[i+1] } if x, ok := replace(id, prefix, replacement); ok { return x } } return id } func replace(id string, prefix, replacement string) (string, bool) { if id == prefix { return replacement, true } prefix = withSlash(prefix) if strings.HasPrefix(id, prefix) { id = strings.TrimPrefix(id, prefix) if replacement != "" { replacement = withSlash(replacement) } return replacement + id, true } return id, false } func withSlash(prefix string) string { if !strings.HasSuffix(prefix, "/") { return prefix + "/" } return prefix } func jsonify(v interface{}) string { data, err := json.Marshal(v) if err != nil { return "unable to marshal: " + err.Error() } return string(data) } goda-0.5.7/internal/templates/templates.go000066400000000000000000000002621442047035200205750ustar00rootroot00000000000000package templates import ( "text/template" ) func Parse(t string) (*template.Template, error) { return template.New("").Funcs(numericFuncs()).Funcs(stringFuncs()).Parse(t) } goda-0.5.7/internal/testdata/000077500000000000000000000000001442047035200160635ustar00rootroot00000000000000goda-0.5.7/internal/testdata/Makefile000066400000000000000000000001001442047035200175120ustar00rootroot00000000000000graph: cd alpha.test; goda graph ./... | dot -Tsvg > graph.svggoda-0.5.7/internal/testdata/README.md000066400000000000000000000002101442047035200173330ustar00rootroot00000000000000# alpha.test This contains a test module with the following dependency structure: ![alpha.test dependency graph](alpha.test/graph.svg)goda-0.5.7/internal/testdata/alpha.test/000077500000000000000000000000001442047035200201265ustar00rootroot00000000000000goda-0.5.7/internal/testdata/alpha.test/cmd/000077500000000000000000000000001442047035200206715ustar00rootroot00000000000000goda-0.5.7/internal/testdata/alpha.test/cmd/client/000077500000000000000000000000001442047035200221475ustar00rootroot00000000000000goda-0.5.7/internal/testdata/alpha.test/cmd/client/main.go000066400000000000000000000001251442047035200234200ustar00rootroot00000000000000package main import ( _ "alpha.test/rpc" _ "alpha.test/service" ) func main() {} goda-0.5.7/internal/testdata/alpha.test/cmd/server/000077500000000000000000000000001442047035200221775ustar00rootroot00000000000000goda-0.5.7/internal/testdata/alpha.test/cmd/server/main.go000066400000000000000000000001321442047035200234460ustar00rootroot00000000000000package main import ( _ "alpha.test/database" _ "alpha.test/service" ) func main() {} goda-0.5.7/internal/testdata/alpha.test/database/000077500000000000000000000000001442047035200216725ustar00rootroot00000000000000goda-0.5.7/internal/testdata/alpha.test/database/database.go000066400000000000000000000000731442047035200237650ustar00rootroot00000000000000package database import ( _ "database/sql" _ "unsafe" ) goda-0.5.7/internal/testdata/alpha.test/database/migration/000077500000000000000000000000001442047035200236635ustar00rootroot00000000000000goda-0.5.7/internal/testdata/alpha.test/database/migration/migration.go000066400000000000000000000001321442047035200261770ustar00rootroot00000000000000package migration import ( _ "alpha.test/database" ) // #include "stdint.h" import "C" goda-0.5.7/internal/testdata/alpha.test/go.mod000066400000000000000000000001341442047035200212320ustar00rootroot00000000000000module alpha.test go 1.16 require github.com/loov/plot v0.0.0-20210121121947-1165ff277fe2 goda-0.5.7/internal/testdata/alpha.test/go.sum000066400000000000000000000003271442047035200212630ustar00rootroot00000000000000github.com/loov/plot v0.0.0-20210121121947-1165ff277fe2 h1:eLXekJ8Ylldc6U75HDCCK1CMNr50VEesRlyG4ucJI4o= github.com/loov/plot v0.0.0-20210121121947-1165ff277fe2/go.mod h1:gSrhfSMoiPGG0CZ9E66kXjaHxFw0fzJhooyicOnz5z4= goda-0.5.7/internal/testdata/alpha.test/graph.svg000066400000000000000000000256771442047035200217710ustar00rootroot00000000000000 G n_alpha_test_cmd_client alpha.test/cmd/client 6 / 85B n_alpha_test_rpc alpha.test/rpc 2 / 47B n_alpha_test_cmd_client:e->n_alpha_test_rpc n_alpha_test_service alpha.test/service 6 / 99B n_alpha_test_cmd_client:e->n_alpha_test_service n_alpha_test_cmd_server alpha.test/cmd/server 6 / 90B n_alpha_test_database alpha.test/database 5 / 59B n_alpha_test_cmd_server:e->n_alpha_test_database n_alpha_test_cmd_server:e->n_alpha_test_service n_alpha_test_database_migration alpha.test/database/migration 6 / 90B n_alpha_test_database_migration:e->n_alpha_test_database n_alpha_test_rpc_rpcconn alpha.test/rpc/rpcconn 2 / 36B n_alpha_test_rpc:e->n_alpha_test_rpc_rpcconn n_alpha_test_service:e->n_alpha_test_database n_alpha_test_service:e->n_alpha_test_rpc n_github_com_loov_plot github.com/loov/plot 1788 / 46.7KB n_alpha_test_service:e->n_github_com_loov_plot n_alpha_test_tool_migrate alpha.test/tool/migrate 2 / 55B n_alpha_test_tool_migrate:e->n_alpha_test_database_migration goda-0.5.7/internal/testdata/alpha.test/rpc/000077500000000000000000000000001442047035200207125ustar00rootroot00000000000000goda-0.5.7/internal/testdata/alpha.test/rpc/rpc.go000066400000000000000000000000571442047035200220270ustar00rootroot00000000000000package rpc import _ "alpha.test/rpc/rpcconn" goda-0.5.7/internal/testdata/alpha.test/rpc/rpcconn/000077500000000000000000000000001442047035200223545ustar00rootroot00000000000000goda-0.5.7/internal/testdata/alpha.test/rpc/rpcconn/conn.go000066400000000000000000000000441442047035200236360ustar00rootroot00000000000000package rpcconn import _ "reflect" goda-0.5.7/internal/testdata/alpha.test/service/000077500000000000000000000000001442047035200215665ustar00rootroot00000000000000goda-0.5.7/internal/testdata/alpha.test/service/service.go000066400000000000000000000001431442047035200235530ustar00rootroot00000000000000package service import ( _ "alpha.test/database" _ "alpha.test/rpc" _ "github.com/loov/plot" ) goda-0.5.7/internal/testdata/alpha.test/tool/000077500000000000000000000000001442047035200211035ustar00rootroot00000000000000goda-0.5.7/internal/testdata/alpha.test/tool/migrate/000077500000000000000000000000001442047035200225335ustar00rootroot00000000000000goda-0.5.7/internal/testdata/alpha.test/tool/migrate/main.go000066400000000000000000000000671442047035200240110ustar00rootroot00000000000000package main import _ "alpha.test/database/migration" goda-0.5.7/internal/testdata/beta.test/000077500000000000000000000000001442047035200177545ustar00rootroot00000000000000goda-0.5.7/internal/testdata/beta.test/a/000077500000000000000000000000001442047035200201745ustar00rootroot00000000000000goda-0.5.7/internal/testdata/beta.test/a/a.go000066400000000000000000000000121442047035200207340ustar00rootroot00000000000000package a goda-0.5.7/internal/testdata/beta.test/a/a_test.go000066400000000000000000000000171442047035200220000ustar00rootroot00000000000000package a_test goda-0.5.7/internal/testdata/beta.test/b/000077500000000000000000000000001442047035200201755ustar00rootroot00000000000000goda-0.5.7/internal/testdata/beta.test/b/b.go000066400000000000000000000000121442047035200207360ustar00rootroot00000000000000package b goda-0.5.7/internal/testdata/beta.test/b/b_test.go000066400000000000000000000000171442047035200220020ustar00rootroot00000000000000package b_test goda-0.5.7/internal/testdata/beta.test/go.mod000066400000000000000000000000321442047035200210550ustar00rootroot00000000000000module beta.test go 1.17 goda-0.5.7/internal/tree/000077500000000000000000000000001442047035200152115ustar00rootroot00000000000000goda-0.5.7/internal/tree/cmd.go000066400000000000000000000044141442047035200163060ustar00rootroot00000000000000package tree import ( "context" "flag" "fmt" "os" "sort" "strings" "github.com/google/subcommands" "golang.org/x/tools/go/packages" "github.com/loov/goda/internal/pkgset" "github.com/loov/goda/internal/templates" ) type Command struct { printStandard bool format string } func (*Command) Name() string { return "tree" } func (*Command) Synopsis() string { return "Print dependency tree." } func (*Command) Usage() string { return `tree : Print dependency tree of packages. ` } func (cmd *Command) SetFlags(f *flag.FlagSet) { f.BoolVar(&cmd.printStandard, "std", false, "print std packages") f.StringVar(&cmd.format, "f", "{{.ID}}", "formatting") } func (cmd *Command) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { t, err := templates.Parse(cmd.format) if err != nil { fmt.Fprintf(os.Stderr, "invalid format string: %v\n", err) return subcommands.ExitFailure } if !cmd.printStandard { go pkgset.LoadStd() } result, err := pkgset.Calc(ctx, f.Args()) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) return subcommands.ExitFailure } if !cmd.printStandard { result = pkgset.Subtract(result, pkgset.Std()) } roots := pkgset.Sources(result) printed := map[string]bool{} var visit func(int, string, *packages.Package, bool) visit = func(ident int, parentID string, p *packages.Package, last bool) { if last { fmt.Fprint(os.Stdout, strings.Repeat(" ", ident), " └ ") } else { fmt.Fprint(os.Stdout, strings.Repeat(" ", ident), " ├ ") } type packageWithImporter struct { ParentID string *packages.Package } err := t.Execute(os.Stdout, packageWithImporter{ ParentID: parentID, Package: p, }) if err != nil { fmt.Fprintf(os.Stderr, "template error: %v\n", err) } if printed[p.ID] || pkgset.IsStd(p) { fmt.Fprintln(os.Stdout, " ~") return } fmt.Fprintln(os.Stdout) printed[p.ID] = true keys := []string{} for id := range p.Imports { if _, ok := result[id]; !ok { continue } keys = append(keys, id) } sort.Strings(keys) for i, id := range keys { dep := p.Imports[id] visit(ident+1, p.ID, dep, i == len(keys)-1) } } for _, root := range roots { visit(0, "\x00", root, false) } return subcommands.ExitSuccess } goda-0.5.7/internal/weight/000077500000000000000000000000001442047035200155415ustar00rootroot00000000000000goda-0.5.7/internal/weight/cmd.go000066400000000000000000000113711442047035200166360ustar00rootroot00000000000000package weight import ( "context" "flag" "fmt" "os" "sort" "strconv" "strings" "github.com/google/subcommands" "github.com/loov/goda/internal/memory" "github.com/loov/goda/internal/weight/nm" ) type Command struct { limit int sort Order cumulative bool humanized bool minimum int } type Order string const ( Default Order = "" BySize Order = "size" ByTotalSize Order = "totalsize" ByName Order = "name" ) func (mode *Order) Set(v string) error { switch Order(strings.ToLower(v)) { case Default: *mode = Default case BySize: *mode = BySize case ByTotalSize: *mode = ByTotalSize case ByName: *mode = ByName default: return fmt.Errorf("unsupported order %q", v) } return nil } func (mode *Order) String() string { return string(*mode) } func (*Command) Name() string { return "weight" } func (*Command) Synopsis() string { return "Analyse binary symbols." } func (*Command) Usage() string { return `weight : Analyse binary symbols. ` } func (cmd *Command) SetFlags(f *flag.FlagSet) { f.IntVar(&cmd.limit, "limit", -1, "limit number of entries to print") f.Var(&cmd.sort, "sort", "sorting mode (size, totalsize, name)") f.BoolVar(&cmd.cumulative, "cum", false, "print cumulative size (deprecated, use -sort)") f.BoolVar(&cmd.humanized, "h", false, "humanized size output") f.IntVar(&cmd.minimum, "minimum", 1024, "minimum size to print") } func (cmd *Command) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { if f.NArg() == 0 { fmt.Fprintf(os.Stderr, "missing binary argument\n") return subcommands.ExitUsageError } syms, err := nm.ParseBinary(f.Arg(0)) if err != nil { fmt.Fprintf(os.Stderr, "loading syms failed: %v\n", err) return subcommands.ExitFailure } root := NewTree("") for _, sym := range syms { root.Insert(sym, "", sym.Path) } trees := []*Tree{root} var recurse func(tree *Tree) recurse = func(tree *Tree) { trees = append(trees, tree.Childs...) for _, child := range tree.Childs { recurse(child) } } recurse(root) if cmd.sort == "" && cmd.cumulative { cmd.sort = ByTotalSize } sorter, ok := sortTreeFunc[cmd.sort] if !ok { fmt.Fprintf(os.Stderr, "invalid sorting mode %q\n", cmd.sort) return subcommands.ExitFailure } root.Sort(sorter, sortSymFunc[cmd.sort]) sorter(trees) if cmd.limit > 0 && cmd.limit > len(trees) { trees = trees[:cmd.limit] } sizeToString := func(v int64) string { return strconv.Itoa(int(v)) } if cmd.humanized { sizeToString = memory.ToString } for _, tree := range trees { if tree.TotalSize < int64(cmd.minimum) { continue } fmt.Fprintf(os.Stdout, "%10s %10s %v [syms %d]\n", sizeToString(tree.TotalSize), sizeToString(tree.Size), tree.Path, len(tree.Syms)) for _, sym := range tree.Syms { if sym.Size < int64(cmd.minimum) { continue } fmt.Fprintf(os.Stdout, "%10s %10s %v %v\n", "", sizeToString(sym.Size), string(sym.Code), sym.Name) } } return subcommands.ExitSuccess } var sortTreeFunc = map[Order]func([]*Tree){ Default: sortBySize, BySize: sortBySize, ByTotalSize: func(trees []*Tree) { sort.Slice(trees, func(i, k int) bool { return trees[i].TotalSize > trees[k].TotalSize }) }, ByName: func(trees []*Tree) { sort.Slice(trees, func(i, k int) bool { return trees[i].Path < trees[k].Path }) }, } var sortSymFunc = map[Order]func([]*nm.Sym){ Default: sortBySymSize, BySize: sortBySymSize, ByTotalSize: sortBySymSize, ByName: func(syms []*nm.Sym) { sort.Slice(syms, func(i, k int) bool { return syms[i].Name < syms[k].Name }) }, } func sortBySize(trees []*Tree) { sort.Slice(trees, func(i, k int) bool { if trees[i].Size == trees[k].Size { return trees[i].TotalSize > trees[k].TotalSize } return trees[i].Size > trees[k].Size }) } func sortBySymSize(syms []*nm.Sym) { sort.Slice(syms, func(i, k int) bool { return syms[i].Size > syms[k].Size }) } type Tree struct { Path string Name string Lookup map[string]*Tree Childs []*Tree TotalSize int64 Size int64 Syms []*nm.Sym } func NewTree(name string) *Tree { return &Tree{ Name: name, Lookup: make(map[string]*Tree), } } func (tree *Tree) Insert(sym *nm.Sym, parent string, suffix []string) { tree.TotalSize += sym.Size if len(suffix) == 0 { tree.Size += sym.Size tree.Syms = append(tree.Syms, sym) return } name := suffix[0] subtree, ok := tree.Lookup[name] if !ok { subtree = NewTree(name) subtree.Path = parent + "/" + name tree.Lookup[subtree.Name] = subtree tree.Childs = append(tree.Childs, subtree) } subtree.Insert(sym, subtree.Path, suffix[1:]) } func (tree *Tree) Sort(sortfn func([]*Tree), sortSyms func([]*nm.Sym)) { sortfn(tree.Childs) for _, child := range tree.Childs { child.Sort(sortfn, sortSyms) } sortSyms(tree.Syms) } goda-0.5.7/internal/weight/nm/000077500000000000000000000000001442047035200161535ustar00rootroot00000000000000goda-0.5.7/internal/weight/nm/measure.go000066400000000000000000000062451442047035200201520ustar00rootroot00000000000000package nm import ( "bufio" "fmt" "os/exec" "strconv" "strings" "unicode" "unicode/utf8" ) type Sym struct { Addr uint64 Size int64 Code rune // nm code (T for text, D for data, and so on) QualifiedName string Info string Path []string Name string } func ParseBinary(binary string) ([]*Sym, error) { command := exec.Command("go", "tool", "nm", "-size", binary) reader, err := command.StdoutPipe() if err != nil { return nil, fmt.Errorf("failed to get stdout: %w", err) } if err := command.Start(); err != nil { return nil, fmt.Errorf("failed to start: %w", err) } var syms []*Sym scanner := bufio.NewScanner(reader) for scanner.Scan() { sym, err := parseLine(scanner.Text()) if err != nil { return nil, fmt.Errorf("failed to parse: %w", err) } if sym.QualifiedName == "" { continue } if len(sym.Path) > 0 && strings.HasPrefix(sym.Path[0], "go.itab.") { continue } if len(sym.Path) > 0 && strings.HasPrefix(sym.Path[0], "type..") { continue } syms = append(syms, sym) } if err := scanner.Err(); err != nil { return nil, fmt.Errorf("scanning failed: %w", err) } return syms, nil } func parseLine(s string) (*Sym, error) { var err error sym := &Sym{} tokens := strings.Fields(s) if len(tokens) <= 2 { return nil, fmt.Errorf("invalid sym text: %q", s) } addrField := "" sizeField := "" typeField := "" nameField := "" infoField := "" isSymType := func(s string) bool { return len(s) == 1 && (unicode.IsLetter(rune(s[0])) || s[0] == '_') } switch { case isSymType(tokens[1]): // in some cases addr is not printed sizeField = tokens[0] typeField = tokens[1] if len(tokens) > 2 { nameField = tokens[2] } if len(tokens) > 3 { infoField = strings.Join(tokens[3:], " ") } case isSymType(tokens[2]): addrField = tokens[0] sizeField = tokens[1] typeField = tokens[2] if len(tokens) > 3 { nameField = tokens[3] } if len(tokens) > 4 { infoField = strings.Join(tokens[4:], " ") } default: return nil, fmt.Errorf("unable to find type in sym: %q", s) } if addrField != "" { sym.Addr, err = strconv.ParseUint(addrField, 16, 64) if err != nil { return nil, fmt.Errorf("invalid addr: %q", addrField) } } if sizeField != "" { sym.Size, err = strconv.ParseInt(sizeField, 10, 64) if err != nil { return nil, fmt.Errorf("invalid size %q: %q", s, sizeField) } // ignore external sym size if sym.Size == 4294967296 { sym.Size = 0 } } if code := strings.TrimSpace(typeField); code != "" { sym.Code, _ = utf8.DecodeRuneInString(code) } sym.QualifiedName = nameField sym.Info = infoField if sym.QualifiedName == "" { return sym, nil } braceOff := strings.IndexByte(sym.QualifiedName, '(') if braceOff < 0 { braceOff = len(sym.QualifiedName) } slashPos := strings.LastIndexByte(sym.QualifiedName[:braceOff], '/') if slashPos < 0 { slashPos = 0 } pointOff := strings.IndexByte(sym.QualifiedName[slashPos:braceOff], '.') if pointOff < 0 { pointOff = 0 } p := slashPos + pointOff if p > 0 { sym.Path = strings.Split(sym.QualifiedName[:p], "/") sym.Name = sym.QualifiedName[p+1:] } else { sym.Name = sym.QualifiedName } return sym, nil } goda-0.5.7/internal/weight/nm/measure_test.go000066400000000000000000000013551442047035200212060ustar00rootroot00000000000000package nm import ( "strings" "testing" ) var validLines = []string{ ` 16781312 U _mmap`, ` 16781312 U _munmap`, ` 16781312 U _notify_is_valid_token`, ` 16781312 U _open`, ` 10d8fe0 8 R $f64.4024000000000000`, ` 10d8fe8 8 R $f64.403a000000000000`, `115d4a0 256 D time.utcLoc`, `1091ca0 192 T type:.eq.[2]interface {}`, `1060160 160 T type:.eq.[2]runtime.Frame`, `1001fa0 256 T type:.eq.[6]internal/cpu.option`, ` 0 0 _ go.go`, } func TestParseLine(t *testing.T) { for _, line := range validLines { _, err := parseLine(line) t.Log(strings.Fields(line)) if err != nil { t.Errorf("%q parsing failed: %v", line, err) } } } goda-0.5.7/internal/weightdiff/000077500000000000000000000000001442047035200163725ustar00rootroot00000000000000goda-0.5.7/internal/weightdiff/cmd.go000066400000000000000000000062571442047035200174760ustar00rootroot00000000000000package weightdiff import ( "context" "flag" "fmt" "os" "sort" "strconv" "text/tabwriter" "github.com/google/subcommands" "github.com/loov/goda/internal/memory" "github.com/loov/goda/internal/weight/nm" ) type Command struct { humanized bool miss bool minimum int64 } func (*Command) Name() string { return "weight-diff" } func (*Command) Synopsis() string { return "Compare binary symbol sizes. (Experimental)" } func (*Command) Usage() string { return `weight-diff : Compare binary sizes and the differences. (Experimental) ` } func (cmd *Command) SetFlags(f *flag.FlagSet) { f.BoolVar(&cmd.humanized, "h", false, "humanized size output") f.BoolVar(&cmd.miss, "miss", false, "include missing entries") f.Int64Var(&cmd.minimum, "minimum", 1024, "minimum size difference to print") } func (cmd *Command) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { if f.NArg() == 0 { fmt.Fprintf(os.Stderr, "missing binary arguments\n") return subcommands.ExitUsageError } binaries := []string{} symnameSet := map[string]struct{}{} symSets := []map[string]*nm.Sym{} for _, binary := range f.Args() { syms, err := nm.ParseBinary(binary) if err != nil { fmt.Fprintf(os.Stderr, "loading syms failed: %v\n", err) return subcommands.ExitFailure } symset := map[string]*nm.Sym{} for _, sym := range syms { symnameSet[sym.QualifiedName] = struct{}{} symset[sym.QualifiedName] = sym } binaries = append(binaries, binary) symSets = append(symSets, symset) } symnames := []string{} for symname := range symnameSet { symnames = append(symnames, symname) } sort.Strings(symnames) type Row struct { QualifiedName string Diff int64 Syms []*nm.Sym } rows := []Row{} for _, symname := range symnames { row := Row{ QualifiedName: symname, } count := 0 min, max := int64(0), int64(0) for _, xs := range symSets { sym := xs[symname] row.Syms = append(row.Syms, sym) if sym != nil { if count == 0 { min, max = sym.Size, sym.Size } else { if sym.Size < min { min = sym.Size } if sym.Size > max { max = sym.Size } } count++ } else { min = 0 } } if count == 1 { row.Diff = max } else { row.Diff = max - min } rows = append(rows, row) } sort.Slice(rows, func(i, k int) bool { a, b := &rows[i], &rows[k] return abs(a.Diff) > abs(b.Diff) }) sizeToString := func(v int64) string { return strconv.Itoa(int(v)) } if cmd.humanized { sizeToString = memory.ToString } w := tabwriter.NewWriter(os.Stdout, 0, 4, 4, ' ', 0) defer func() { _ = w.Flush() }() fmt.Fprintf(w, "name\tdiff") for _, bin := range binaries { fmt.Fprintf(w, "\t%v", bin) } fmt.Fprintf(w, "\n") for _, row := range rows { if abs(row.Diff) < cmd.minimum { continue } fmt.Fprintf(w, "%v\t%v", row.QualifiedName, sizeToString(row.Diff)) for _, sym := range row.Syms { if sym == nil { fmt.Fprintf(w, "\t-") } else { fmt.Fprintf(w, "\t%v", sizeToString(sym.Size)) } } fmt.Fprintf(w, "\n") } return subcommands.ExitSuccess } func abs(v int64) int64 { if v < 0 { return -v } return v } goda-0.5.7/main.go000066400000000000000000000157131442047035200137200ustar00rootroot00000000000000package main import ( "context" "flag" "fmt" "os" "path" "runtime/pprof" "github.com/google/subcommands" "github.com/loov/goda/internal/cut" "github.com/loov/goda/internal/exec" "github.com/loov/goda/internal/graph" "github.com/loov/goda/internal/list" "github.com/loov/goda/internal/pkgset" "github.com/loov/goda/internal/tree" "github.com/loov/goda/internal/weight" "github.com/loov/goda/internal/weightdiff" ) func main() { cpuProfile := flag.String("cpuprofile", "", "profile cpu usage") cmds := subcommands.NewCommander(flag.CommandLine, path.Base(os.Args[0])) cmds.Register(cmds.HelpCommand(), "") cmds.Register(&list.Command{}, "") cmds.Register(&tree.Command{}, "") cmds.Register(&exec.Command{}, "") cmds.Register(&weight.Command{}, "") cmds.Register(&weightdiff.Command{}, "") cmds.Register(&graph.Command{}, "") cmds.Register(&cut.Command{}, "") cmds.Register(&ExprHelp{}, "") cmds.Register(&FormatHelp{}, "") flag.Parse() os.Exit(func() int { ctx := context.Background() if *cpuProfile != "" { f, err := os.Create(*cpuProfile) if err != nil { fmt.Fprintf(os.Stderr, "unable to create file: %v\n", err) return -1 } defer func() { _ = f.Close() }() if err := pprof.StartCPUProfile(f); err != nil { fmt.Fprintf(os.Stderr, "could not start CPU profile: %v\n", err) } defer pprof.StopCPUProfile() } return int(cmds.Execute(ctx)) }()) } type ExprHelp struct{} func (*ExprHelp) Name() string { return "expr" } func (*ExprHelp) Synopsis() string { return "Help about package expressions" } func (*ExprHelp) Usage() string { return `Package expressions allow to specify calculations with dependencies. The examples use X, Y and Z as placeholders for packages, packages paths or package expressions. # Basic operations: There are a few basic operations specified for manipulating sets of packages. X Y Z; X + Y + Z; add(X, Y, Z); or(X, Y, Z) all packages that match X, Y or Z X - Y - Z; subtract(X, Y, Z); exclude(X, Y, Z) packages that match X but not Y or Z shared(X, Y, Z); intersect(X, Y, Z) packages that match all of X, Y and Z xor(X, Y); packages that match one of X or Y but not both # Selectors: Selectors allow selecting parts of the dependency tree. They are applied in left to right order. X:all select X and all of its direct and indirect dependencies X:import, X:imp select direct imports of X X:import:all, X:imp:all select direct and indirect dependencies of X; X not included X:source select packages not imported by any package in X X:-source shorthand for (X - X:source) X:main select packages in X named main X:test select test packages of X # Functions: reach(X, Y); packages from X that can reach a package in Y incoming(X, Y); packages from X that directly import a package in Y, including Y transitive(X); a transitive reduction in package dependencies # Tags and OS: test=1(X); include tests when resolving X goos=linux(X): set goos to "linux" tests when resolving X purego=1(X): add tag "purego" for resolving X # Example expressions: github.com/loov/goda:import all direct dependencies for the "github.com/loov/goda" package shared(github.com/loov/goda/pkgset:all, github.com/loov/goda/templates:all) packages directly or indirectly imported by both "github.com/loov/goda/pkgset" and "github.com/loov/goda/templates" github.com/loov/goda/...:all - golang.org/x/tools/... all of goda's dependencies excluding golang.org/x/tools packages reach(github.com/loov/goda/...:all, golang.org/x/tools/go/packages) packages in github.com/loov/goda/ that use golang.org/x/tools/go/packages ` } func (*ExprHelp) SetFlags(f *flag.FlagSet) {} func (cmd *ExprHelp) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { if f.NArg() == 0 { return subcommands.ExitUsageError } result, err := pkgset.Parse(ctx, f.Args()) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) return subcommands.ExitFailure } fmt.Fprintln(os.Stdout, result.Tree(0)) return subcommands.ExitSuccess } type FormatHelp struct{} func (*FormatHelp) Name() string { return "format" } func (*FormatHelp) Synopsis() string { return "Help about formatting" } func (*FormatHelp) Usage() string { return `Formatting allows to add useful information about packages. Formatting uses -f flag for specifying the output of each package. goda uses https://pkg.go.dev/text/template for templating and it allows for extensive formatting. Each package node in goda has information about the package itself, and its statistics. Additionally there is a summary of downstream and upstream statistics: type Node struct { *Package ImportsNodes []*Node Stat Stat // Stats about the current node. Up Stat // Stats about upstream nodes. Down Stat // Stats about downstream nodes. } type Package struct { ID string // ID is a unique identifier for a package, PkgPath string // PkgPath is the full import path of the package. Module *packages.Module } type Module struct { Path string // module path Version string // module version Main bool // is this the main module? } This is not the full list of information about the node, however, this is the most useful. To see inspect the structures in depth, it's possible to use: goda list -f "{{ printf \"%#v\" .Package }}" . Statistics for package contains the following information: type Stat struct { PackageCount int64 AllFiles Source Go Source OtherFiles Source Decls Decls Tokens Tokens } The source information contains the following information: type Source struct { Files int // Files count in this stat. Binary int // Binary file count. Size memory.Bytes // Size in bytes of all files. Lines int // Count of non-empty lines. Blank int // Count of empty lines. } As an example, to print total size of non-go files in a package: goda list -f "{{.ID}} {{.Stat.OtherFiles.Size}}" ./...:all It's also possible to see information about the ast tokens and declarations, which can be used as an approximation of the final binary size. type Decls struct { Func int64 Type int64 Const int64 Var int64 Other int64 } type Tokens struct { Code int64 Comment int64 Basic int64 } "goda cut" command additionally contains: type Node struct { Cut stat.Stat ... } This contains summary of packages that would be removed when that package would deleted from the project. ` } func (*FormatHelp) SetFlags(f *flag.FlagSet) {} func (cmd *FormatHelp) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { fmt.Println("Run \"goda help format\" to see help about formatting.") return subcommands.ExitUsageError }