-
Notifications
You must be signed in to change notification settings - Fork 397
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cmd/gno): initial documentation command for stdlibs and example …
…packages (#610) * feat(gnodev): add preliminary doc command, refactor common flags verbose and root-dir * feat(gnodev): initial documentation command for stdlibs packages * chore: remove accidental log line * chore: linter * feat(commands/doc): add support for examples directory * chore: prefer writing to stdout directly instead of using a buffer * fix(doc): improve arg parsing, more tests * tests: add tests for Document * chore: fix typo * update to new dir structure * fmt * Update gnovm/cmd/gno/doc.go Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com> * typo * Revert "feat(gnodev): add preliminary doc command, refactor common flags verbose and root-dir" This reverts commit 243d24c. * change documentable iface * add doc test * address changes requested from code review * fix typo * code review changes * unexport dirs * some typos, some error logging * better and more consistent error handling * use errors.Is for comparison * empty commit to trigger github workflow --------- Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com>
- Loading branch information
Showing
20 changed files
with
2,217 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"flag" | ||
"path/filepath" | ||
|
||
"github.com/gnolang/gno/gnovm/pkg/doc" | ||
"github.com/gnolang/gno/tm2/pkg/commands" | ||
) | ||
|
||
type docCfg struct { | ||
all bool | ||
src bool | ||
unexported bool | ||
short bool | ||
rootDir string | ||
} | ||
|
||
func newDocCmd(io *commands.IO) *commands.Command { | ||
c := &docCfg{} | ||
return commands.NewCommand( | ||
commands.Metadata{ | ||
Name: "doc", | ||
ShortUsage: "doc [flags] <pkgsym>", | ||
ShortHelp: "get documentation for the specified package or symbol (type, function, method, or variable/constant).", | ||
}, | ||
c, | ||
func(_ context.Context, args []string) error { | ||
return execDoc(c, args, io) | ||
}, | ||
) | ||
} | ||
|
||
func (c *docCfg) RegisterFlags(fs *flag.FlagSet) { | ||
fs.BoolVar( | ||
&c.all, | ||
"all", | ||
false, | ||
"show documentation for all symbols in package", | ||
) | ||
|
||
fs.BoolVar( | ||
&c.src, | ||
"src", | ||
false, | ||
"show source code for symbols", | ||
) | ||
|
||
fs.BoolVar( | ||
&c.unexported, | ||
"u", | ||
false, | ||
"show unexported symbols as well as exported", | ||
) | ||
|
||
fs.BoolVar( | ||
&c.short, | ||
"short", | ||
false, | ||
"show a one line representation for each symbol", | ||
) | ||
|
||
fs.StringVar( | ||
&c.rootDir, | ||
"root-dir", | ||
"", | ||
"clone location of github.com/gnolang/gno (gnodev tries to guess it)", | ||
) | ||
} | ||
|
||
func execDoc(cfg *docCfg, args []string, io *commands.IO) error { | ||
// guess opts.RootDir | ||
if cfg.rootDir == "" { | ||
cfg.rootDir = guessRootDir() | ||
} | ||
dirs := []string{filepath.Join(cfg.rootDir, "gnovm/stdlibs"), filepath.Join(cfg.rootDir, "examples")} | ||
res, err := doc.ResolveDocumentable(dirs, args, cfg.unexported) | ||
if res == nil { | ||
return err | ||
} | ||
if err != nil { | ||
io.Printfln("warning: error parsing some candidate packages:\n%v", err) | ||
} | ||
return res.WriteDocumentation( | ||
io.Out, | ||
&doc.WriteDocumentationOptions{ | ||
ShowAll: cfg.all, | ||
Source: cfg.src, | ||
Unexported: cfg.unexported, | ||
Short: false, | ||
}, | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package main | ||
|
||
import "testing" | ||
|
||
func TestGnoDoc(t *testing.T) { | ||
tc := []testMainCase{ | ||
{ | ||
args: []string{"doc", "io.Writer"}, | ||
stdoutShouldContain: "Writer is the interface that wraps", | ||
}, | ||
{ | ||
args: []string{"doc", "avl"}, | ||
stdoutShouldContain: "func NewNode", | ||
}, | ||
{ | ||
args: []string{"doc", "-u", "avl.Node"}, | ||
stdoutShouldContain: "node *Node", | ||
}, | ||
{ | ||
args: []string{"doc", "dkfdkfkdfjkdfj"}, | ||
errShouldContain: "package not found", | ||
}, | ||
{ | ||
args: []string{"doc", "There.Are.Too.Many.Dots"}, | ||
errShouldContain: "invalid arguments", | ||
}, | ||
} | ||
testMainCaseRun(t, tc) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
// Mostly copied from go source at tip, commit d922c0a. | ||
// | ||
// Copyright 2015 The Go Authors. All rights reserved. | ||
|
||
package doc | ||
|
||
import ( | ||
"log" | ||
"os" | ||
"path/filepath" | ||
"sort" | ||
"strings" | ||
) | ||
|
||
// A bfsDir describes a directory holding code by specifying | ||
// the expected import path and the file system directory. | ||
type bfsDir struct { | ||
importPath string // import path for that dir | ||
dir string // file system directory | ||
} | ||
|
||
// dirs is a structure for scanning the directory tree. | ||
// Its Next method returns the next Go source directory it finds. | ||
// Although it can be used to scan the tree multiple times, it | ||
// only walks the tree once, caching the data it finds. | ||
type bfsDirs struct { | ||
scan chan bfsDir // Directories generated by walk. | ||
hist []bfsDir // History of reported Dirs. | ||
offset int // Counter for Next. | ||
} | ||
|
||
// newDirs begins scanning the given stdlibs directory. | ||
func newDirs(dirs ...string) *bfsDirs { | ||
d := &bfsDirs{ | ||
hist: make([]bfsDir, 0, 256), | ||
scan: make(chan bfsDir), | ||
} | ||
go d.walk(dirs) | ||
return d | ||
} | ||
|
||
// Reset puts the scan back at the beginning. | ||
func (d *bfsDirs) Reset() { | ||
d.offset = 0 | ||
} | ||
|
||
// Next returns the next directory in the scan. The boolean | ||
// is false when the scan is done. | ||
func (d *bfsDirs) Next() (bfsDir, bool) { | ||
if d.offset < len(d.hist) { | ||
dir := d.hist[d.offset] | ||
d.offset++ | ||
return dir, true | ||
} | ||
dir, ok := <-d.scan | ||
if !ok { | ||
return bfsDir{}, false | ||
} | ||
d.hist = append(d.hist, dir) | ||
d.offset++ | ||
return dir, ok | ||
} | ||
|
||
// walk walks the trees in the given roots. | ||
func (d *bfsDirs) walk(roots []string) { | ||
for _, root := range roots { | ||
d.bfsWalkRoot(root) | ||
} | ||
close(d.scan) | ||
} | ||
|
||
// bfsWalkRoot walks a single directory hierarchy in breadth-first lexical order. | ||
// Each Go source directory it finds is delivered on d.scan. | ||
func (d *bfsDirs) bfsWalkRoot(root string) { | ||
root = filepath.Clean(root) | ||
|
||
// this is the queue of directories to examine in this pass. | ||
this := []string{} | ||
// next is the queue of directories to examine in the next pass. | ||
next := []string{root} | ||
|
||
for len(next) > 0 { | ||
this, next = next, this[:0] | ||
for _, dir := range this { | ||
fd, err := os.Open(dir) | ||
if err != nil { | ||
log.Print(err) | ||
continue | ||
} | ||
entries, err := fd.Readdir(0) | ||
fd.Close() | ||
if err != nil { | ||
log.Print(err) | ||
continue | ||
} | ||
hasGnoFiles := false | ||
for _, entry := range entries { | ||
name := entry.Name() | ||
// For plain files, remember if this directory contains any .gno | ||
// source files, but ignore them otherwise. | ||
if !entry.IsDir() { | ||
if !hasGnoFiles && strings.HasSuffix(name, ".gno") { | ||
hasGnoFiles = true | ||
} | ||
continue | ||
} | ||
// Entry is a directory. | ||
|
||
// Ignore same directories ignored by the go tool. | ||
if name[0] == '.' || name[0] == '_' || name == "testdata" { | ||
continue | ||
} | ||
// Remember this (fully qualified) directory for the next pass. | ||
next = append(next, filepath.Join(dir, name)) | ||
} | ||
if hasGnoFiles { | ||
// It's a candidate. | ||
var importPath string | ||
if len(dir) > len(root) { | ||
importPath = filepath.ToSlash(dir[len(root)+1:]) | ||
} | ||
d.scan <- bfsDir{importPath, dir} | ||
} | ||
} | ||
} | ||
} | ||
|
||
// findPackage finds a package iterating over d where the import path has | ||
// name as a suffix (which may be a package name or a fully-qualified path). | ||
// returns a list of possible directories. If a directory's import path matched | ||
// exactly, it will be returned as first. | ||
func (d *bfsDirs) findPackage(name string) []bfsDir { | ||
d.Reset() | ||
candidates := make([]bfsDir, 0, 4) | ||
for dir, ok := d.Next(); ok; dir, ok = d.Next() { | ||
// want either exact matches or suffixes | ||
if dir.importPath == name || strings.HasSuffix(dir.importPath, "/"+name) { | ||
candidates = append(candidates, dir) | ||
} | ||
} | ||
sort.Slice(candidates, func(i, j int) bool { | ||
// prefer exact matches with name | ||
if candidates[i].importPath == name { | ||
return true | ||
} else if candidates[j].importPath == name { | ||
return false | ||
} | ||
return candidates[i].importPath < candidates[j].importPath | ||
}) | ||
return candidates | ||
} | ||
|
||
// findDir determines if the given absdir is present in the Dirs. | ||
// If not, the nil slice is returned. It returns always at most one dir. | ||
func (d *bfsDirs) findDir(absdir string) []bfsDir { | ||
d.Reset() | ||
for dir, ok := d.Next(); ok; dir, ok = d.Next() { | ||
if dir.dir == absdir { | ||
return []bfsDir{dir} | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package doc | ||
|
||
import ( | ||
"path/filepath" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func tNewDirs(t *testing.T) (string, *bfsDirs) { | ||
t.Helper() | ||
p, err := filepath.Abs("./testdata/dirs") | ||
require.NoError(t, err) | ||
return p, newDirs(p) | ||
} | ||
|
||
func TestDirs_findPackage(t *testing.T) { | ||
abs, d := tNewDirs(t) | ||
tt := []struct { | ||
name string | ||
res []bfsDir | ||
}{ | ||
{"rand", []bfsDir{ | ||
{importPath: "rand", dir: filepath.Join(abs, "rand")}, | ||
{importPath: "crypto/rand", dir: filepath.Join(abs, "crypto/rand")}, | ||
{importPath: "math/rand", dir: filepath.Join(abs, "math/rand")}, | ||
}}, | ||
{"crypto/rand", []bfsDir{ | ||
{importPath: "crypto/rand", dir: filepath.Join(abs, "crypto/rand")}, | ||
}}, | ||
{"math", []bfsDir{ | ||
{importPath: "math", dir: filepath.Join(abs, "math")}, | ||
}}, | ||
{"ath", []bfsDir{}}, | ||
{"/math", []bfsDir{}}, | ||
{"", []bfsDir{}}, | ||
} | ||
for _, tc := range tt { | ||
tc := tc | ||
t.Run("name_"+strings.Replace(tc.name, "/", "_", -1), func(t *testing.T) { | ||
res := d.findPackage(tc.name) | ||
assert.Equal(t, tc.res, res, "dirs returned should be the equal") | ||
}) | ||
} | ||
} | ||
|
||
func TestDirs_findDir(t *testing.T) { | ||
abs, d := tNewDirs(t) | ||
tt := []struct { | ||
name string | ||
in string | ||
res []bfsDir | ||
}{ | ||
{"rand", filepath.Join(abs, "rand"), []bfsDir{ | ||
{importPath: "rand", dir: filepath.Join(abs, "rand")}, | ||
}}, | ||
{"crypto/rand", filepath.Join(abs, "crypto/rand"), []bfsDir{ | ||
{importPath: "crypto/rand", dir: filepath.Join(abs, "crypto/rand")}, | ||
}}, | ||
// ignored (dir name testdata), so should not return anything. | ||
{"crypto/testdata/rand", filepath.Join(abs, "crypto/testdata/rand"), nil}, | ||
{"xx", filepath.Join(abs, "xx"), nil}, | ||
{"xx2", "/xx2", nil}, | ||
} | ||
for _, tc := range tt { | ||
tc := tc | ||
t.Run(strings.Replace(tc.name, "/", "_", -1), func(t *testing.T) { | ||
res := d.findDir(tc.in) | ||
assert.Equal(t, tc.res, res, "dirs returned should be the equal") | ||
}) | ||
} | ||
} |
Oops, something went wrong.