Commit 6e2c3ef4 authored by Russ Cox's avatar Russ Cox

go: implement doc, fmt, fix, list, vet

This CL is concerned with the basic Package structure
and applies it to the (trivial) implementations of the
doc, fmt, fix, list, and vet commands.

The command as a whole is still very much a work in progress.
In particular, work making the error messages look nice
is deferred to a future CL.

R=golang-dev, adg, dsymonds, r
CC=golang-dev
https://golang.org/cl/5482048
parent 969b71d9
...@@ -14,6 +14,7 @@ GOFILES=\ ...@@ -14,6 +14,7 @@ GOFILES=\
help.go\ help.go\
list.go\ list.go\
main.go\ main.go\
pkg.go\
test.go\ test.go\
version.go\ version.go\
vet.go\ vet.go\
......
...@@ -31,3 +31,5 @@ Additional help topics: ...@@ -31,3 +31,5 @@ Additional help topics:
Use "go help [topic]" for more information about that topic. Use "go help [topic]" for more information about that topic.
*/ */
package documentation package documentation
// NOTE: cmdDoc is in fmt.go.
...@@ -21,7 +21,10 @@ See also: go fmt, go vet. ...@@ -21,7 +21,10 @@ See also: go fmt, go vet.
} }
func runFix(cmd *Command, args []string) { func runFix(cmd *Command, args []string) {
args = importPaths(args) for _, pkg := range packages(args) {
_ = args // Use pkg.gofiles instead of pkg.Dir so that
panic("fix not implemented") // the command only applies to this package,
// not to packages in subdirectories.
run(append([]string{"gofix"}, pkg.gofiles...)...)
}
} }
...@@ -7,21 +7,48 @@ package main ...@@ -7,21 +7,48 @@ package main
var cmdFmt = &Command{ var cmdFmt = &Command{
Run: runFmt, Run: runFmt,
UsageLine: "fmt [importpath...]", UsageLine: "fmt [importpath...]",
Short: "run gofmt -w on packages", Short: "run gofmt on package sources",
Long: ` Long: `
Fmt runs the command 'gofmt -w' on the packages named by the import paths. Fmt runs the command 'gofmt -l -w' on the packages named
by the import paths. It prints the names of the files that are modified.
For more about gofmt, see 'godoc gofmt'. For more about gofmt, see 'godoc gofmt'.
For more about import paths, see 'go help importpath'. For more about import paths, see 'go help importpath'.
To run gofmt with specific options, run gofmt itself. To run gofmt with specific options, run gofmt itself.
See also: go fix, go vet. See also: go doc, go fix, go vet.
`, `,
} }
func runFmt(cmd *Command, args []string) { func runFmt(cmd *Command, args []string) {
args = importPaths(args) for _, pkg := range packages(args) {
_ = args // Use pkg.gofiles instead of pkg.Dir so that
panic("fmt not implemented") // the command only applies to this package,
// not to packages in subdirectories.
run(append([]string{"gofmt", "-l", "-w"}, pkg.gofiles...)...)
}
}
var cmdDoc = &Command{
Run: runDoc,
UsageLine: "doc [importpath...]",
Short: "run godoc on package sources",
Long: `
Doc runs the godoc command on the packages named by the
import paths.
For more about godoc, see 'godoc godoc'.
For more about import paths, see 'go help importpath'.
To run gofmt with specific options, run gofmt itself.
See also: go fix, go fmt, go vet.
`,
}
func runDoc(cmd *Command, args []string) {
for _, pkg := range packages(args) {
run("godoc", pkg.Dir)
}
} }
...@@ -4,8 +4,13 @@ ...@@ -4,8 +4,13 @@
package main package main
import (
"encoding/json"
"os"
"text/template"
)
var cmdList = &Command{ var cmdList = &Command{
Run: runList,
UsageLine: "list [-f format] [-json] [importpath...]", UsageLine: "list [-f format] [-json] [importpath...]",
Short: "list packages", Short: "list packages",
Long: ` Long: `
...@@ -19,31 +24,59 @@ The default output shows the package name and file system location: ...@@ -19,31 +24,59 @@ The default output shows the package name and file system location:
The -f flag specifies an alternate format for the list, The -f flag specifies an alternate format for the list,
using the syntax of package template. The default output using the syntax of package template. The default output
is equivalent to -f '{{.Name}} {{.Dir}}' The struct is equivalent to -f '{{.Name}} {{.Dir}}'. The struct
being passed to the template is: being passed to the template is:
type Package struct { type Package struct {
Name string // package name Name string // package name
Doc string // package documentation string Doc string // package documentation string
GoFiles []string // names of Go source files in package ImportPath string // import path of package in dir
ImportPath string // import path denoting package Dir string // directory containing package sources
Imports []string // import paths used by this package Version string // version of installed package (TODO)
Deps []string // all (recursively) imported dependencies
Dir string // directory containing package sources // Source files
Version string // version of installed package GoFiles []string // .go source files (excluding CgoFiles)
CFiles []string // .c source files
SFiles []string // .s source files
CgoFiles []string // .go sources files that import "C"
// Dependency information
Imports []string // import paths used by this package
Deps []string // all (recursively) imported dependencies
} }
The -json flag causes the package data to be printed in JSON format. The -json flag causes the package data to be printed in JSON format
instead of using the template format.
For more about import paths, see 'go help importpath'. For more about import paths, see 'go help importpath'.
`, `,
} }
func init() {
cmdList.Run = runList // break init cycle
}
var listFmt = cmdList.Flag.String("f", "{{.Name}} {{.Dir}}", "") var listFmt = cmdList.Flag.String("f", "{{.Name}} {{.Dir}}", "")
var listJson = cmdList.Flag.Bool("json", false, "") var listJson = cmdList.Flag.Bool("json", false, "")
func runList(cmd *Command, args []string) { func runList(cmd *Command, args []string) {
args = importPaths(args) var do func(*Package)
_ = args if *listJson {
panic("list not implemented") enc := json.NewEncoder(os.Stdout)
do = func(p *Package) { enc.Encode(p) }
} else {
tmpl, err := template.New("main").Parse(*listFmt + "\n")
if err != nil {
fatalf("%s", err)
}
do = func(p *Package) {
if err := tmpl.Execute(os.Stdout, p); err != nil {
fatalf("%s", err)
}
}
}
for _, pkg := range packages(args) {
do(pkg)
}
} }
...@@ -8,7 +8,9 @@ import ( ...@@ -8,7 +8,9 @@ import (
"flag" "flag"
"fmt" "fmt"
"io" "io"
"log"
"os" "os"
"os/exec"
"strings" "strings"
"text/template" "text/template"
) )
...@@ -55,6 +57,7 @@ func (c *Command) Usage() { ...@@ -55,6 +57,7 @@ func (c *Command) Usage() {
var commands = []*Command{ var commands = []*Command{
cmdBuild, cmdBuild,
cmdClean, cmdClean,
cmdDoc,
cmdFix, cmdFix,
cmdFmt, cmdFmt,
cmdGet, cmdGet,
...@@ -69,9 +72,12 @@ var commands = []*Command{ ...@@ -69,9 +72,12 @@ var commands = []*Command{
helpRemote, helpRemote,
} }
var exitStatus = 0
func main() { func main() {
flag.Usage = usage flag.Usage = usage
flag.Parse() flag.Parse()
log.SetFlags(0)
args := flag.Args() args := flag.Args()
if len(args) < 1 { if len(args) < 1 {
...@@ -89,6 +95,7 @@ func main() { ...@@ -89,6 +95,7 @@ func main() {
cmd.Flag.Parse(args[1:]) cmd.Flag.Parse(args[1:])
args = cmd.Flag.Args() args = cmd.Flag.Args()
cmd.Run(cmd, args) cmd.Run(cmd, args)
os.Exit(exitStatus)
return return
} }
} }
...@@ -172,3 +179,28 @@ func importPaths(args []string) []string { ...@@ -172,3 +179,28 @@ func importPaths(args []string) []string {
} }
return args return args
} }
func fatalf(format string, args ...interface{}) {
log.Printf(format, args...)
os.Exit(1)
}
func errorf(format string, args ...interface{}) {
log.Printf(format, args...)
exitStatus = 1
}
func exitIfErrors() {
if exitStatus != 0 {
os.Exit(exitStatus)
}
}
func run(cmdline ...string) {
cmd := exec.Command(cmdline[0], cmdline[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
errorf("%v", err)
}
}
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"go/build"
"go/doc"
"path/filepath"
"sort"
"strings"
)
// A Package describes a single package found in a directory.
type Package struct {
// Note: These fields are part of the go command's public API.
// See list.go. It is okay to add fields, but not to change or
// remove existing ones. Keep in sync with list.go
Name string // package name
Doc string // package documentation string
ImportPath string // import path of package in dir
Dir string // directory containing package sources
Version string // version of installed package (TODO)
Standard bool // is this package part of the standard Go library?
// Source files
GoFiles []string // .go source files (excluding CgoFiles)
CFiles []string // .c source files
SFiles []string // .s source files
CgoFiles []string // .go sources files that import "C"
// Dependency information
Imports []string // import paths used by this package
Deps []string // all (recursively) imported dependencies
// Unexported fields are not part of the public API.
t *build.Tree
info *build.DirInfo
imports []*Package
gofiles []string // GoFiles+CgoFiles
}
// packageCache is a lookup cache for loadPackage,
// so that if we look up a package multiple times
// we return the same pointer each time.
var packageCache = map[string]*Package{}
// loadPackage scans directory named by arg,
// which is either an import path or a file system path
// (if the latter, must be rooted or begin with . or ..),
// and returns a *Package describing the package
// found in that directory.
func loadPackage(arg string) (*Package, error) {
// Check package cache.
if p := packageCache[arg]; p != nil {
// We use p.imports==nil to detect a package that
// is in the midst of its own loadPackage call
// (all the recursion below happens before p.imports gets set).
if p.imports == nil {
return nil, fmt.Errorf("import loop at %s", arg)
}
return p, nil
}
// Find basic information about package path.
t, importPath, err := build.FindTree(arg)
if err != nil {
return nil, err
}
dir := filepath.Join(t.SrcDir(), importPath)
// Maybe we know the package by its directory.
if p := packageCache[dir]; p != nil {
if p.imports == nil {
return nil, fmt.Errorf("import loop at %s", arg)
}
return p, nil
}
// Read the files in the directory to learn the structure
// of the package.
info, err := build.ScanDir(dir)
if err != nil {
return nil, err
}
p := &Package{
Name: info.Package,
Doc: doc.CommentText(info.PackageComment),
ImportPath: importPath,
Dir: dir,
Imports: info.Imports,
GoFiles: info.GoFiles,
CFiles: info.CFiles,
SFiles: info.SFiles,
CgoFiles: info.CgoFiles,
Standard: t.Goroot && !strings.Contains(importPath, "."),
}
// Build list of full paths to all Go files in the package,
// for use by commands like go fmt.
for _, f := range info.GoFiles {
p.gofiles = append(p.gofiles, filepath.Join(dir, f))
}
for _, f := range info.CgoFiles {
p.gofiles = append(p.gofiles, filepath.Join(dir, f))
}
sort.Strings(p.gofiles)
// Record package under both import path and full directory name.
packageCache[dir] = p
packageCache[importPath] = p
// Build list of imported packages and full dependency list.
imports := make([]*Package, 0, len(p.Imports))
deps := make(map[string]bool)
for _, path := range p.Imports {
deps[path] = true
if path == "C" {
continue
}
p1, err := loadPackage(path)
if err != nil {
delete(packageCache, arg)
delete(packageCache, importPath)
// Add extra error detail to show full import chain.
// Always useful, but especially useful in import loops.
return nil, fmt.Errorf("%s: import %s\n\t%v", arg, path, err)
}
imports = append(imports, p1)
for _, dep := range p1.Deps {
deps[dep] = true
}
}
p.imports = imports
p.Deps = make([]string, 0, len(deps))
for dep := range deps {
p.Deps = append(p.Deps, dep)
}
sort.Strings(p.Deps)
return p, nil
}
// packages returns the packages named by the
// command line arguments 'args'.
func packages(args []string) []*Package {
args = importPaths(args)
var pkgs []*Package
for _, arg := range args {
pkg, err := loadPackage(arg)
if err != nil {
errorf("%s", err)
continue
}
pkgs = append(pkgs, pkg)
}
return pkgs
}
...@@ -21,7 +21,10 @@ See also: go fmt, go fix. ...@@ -21,7 +21,10 @@ See also: go fmt, go fix.
} }
func runVet(cmd *Command, args []string) { func runVet(cmd *Command, args []string) {
args = importPaths(args) for _, pkg := range packages(args) {
_ = args // Use pkg.gofiles instead of pkg.Dir so that
panic("vet not implemented") // the command only applies to this package,
// not to packages in subdirectories.
run(append([]string{"govet"}, pkg.gofiles...)...)
}
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment