Skip to content

Commit

Permalink
feat(cmd/gno): add support for gno.mod projects in gno doc (#829)
Browse files Browse the repository at this point in the history
* feat(cmd/gno): add support for gno.mod projects in gno doc

* fix tests

* remove bfsRootDir

* update compat doc

* add tests

* harioms pr merged, remove duplicate code

* fix: tests for wrong arguments passed to newDirs

* xx

* fix dirsempty

* Apply suggestions from code review

Co-authored-by: Hariom Verma <hariom18599@gmail.com>

* s/tryParse/parse/g

---------

Co-authored-by: Hariom Verma <hariom18599@gmail.com>
  • Loading branch information
thehowl and harry-hov authored Jun 19, 2023
1 parent 24770d5 commit f51ce54
Show file tree
Hide file tree
Showing 20 changed files with 319 additions and 83 deletions.
19 changes: 18 additions & 1 deletion gnovm/cmd/gno/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ package main

import (
"context"
"errors"
"flag"
"fmt"
"os"
"path/filepath"

"github.com/gnolang/gno/gnovm/pkg/doc"
"github.com/gnolang/gno/gnovm/pkg/gnomod"
"github.com/gnolang/gno/tm2/pkg/commands"
)

Expand Down Expand Up @@ -74,8 +78,21 @@ func execDoc(cfg *docCfg, args []string, io *commands.IO) error {
if cfg.rootDir == "" {
cfg.rootDir = guessRootDir()
}

wd, err := os.Getwd()
if err != nil {
return fmt.Errorf("could not determine working directory: %w", err)
}

rd, err := gnomod.FindRootDir(wd)
if err != nil && !errors.Is(err, gnomod.ErrGnoModNotFound) {
return fmt.Errorf("error determining root gno.mod file: %w", err)
}
modDirs := []string{rd}

// select dirs from which to gather directories
dirs := []string{filepath.Join(cfg.rootDir, "gnovm/stdlibs"), filepath.Join(cfg.rootDir, "examples")}
res, err := doc.ResolveDocumentable(dirs, args, cfg.unexported)
res, err := doc.ResolveDocumentable(dirs, modDirs, args, cfg.unexported)
if res == nil {
return err
}
Expand Down
48 changes: 24 additions & 24 deletions gnovm/docs/go-gno-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,27 +345,27 @@ Additional native types:

## Tooling (`gno` binary)

| go command | gno command | comment |
|-------------------|------------------|-----------------------------------------------|
| go bug | | see https://github.com/gnolang/gno/issues/733 |
| go build | gno build | same intention, limited compatibility |
| go clean | gno clean | same intention, limited compatibility |
| go doc | | see https://github.com/gnolang/gno/pull/610 |
| go env | | |
| go fix | | |
| go fmt | | |
| go generate | | |
| go get | | |
| go help | | |
| go install | | |
| go list | | |
| go mod | | |
| + go mod download | gno mod download | same behavior |
| | gno precompile | |
| go work | | |
| | gno repl | |
| go run | gno run | |
| go test | gno test | limited compatibility |
| go tool | | |
| go version | | |
| go vet | | |
| go command | gno command | comment |
|-------------------|------------------|-----------------------------------------------------------------------|
| go bug | | see https://github.com/gnolang/gno/issues/733 |
| go build | gno build | same intention, limited compatibility |
| go clean | gno clean | same intention, limited compatibility |
| go doc | gno doc | limited compatibility; see https://github.com/gnolang/gno/issues/522 |
| go env | | |
| go fix | | |
| go fmt | | |
| go generate | | |
| go get | | |
| go help | | |
| go install | | |
| go list | | |
| go mod | | |
| + go mod download | gno mod download | same behavior |
| | gno precompile | |
| go work | | |
| | gno repl | |
| go run | gno run | |
| go test | gno test | limited compatibility |
| go tool | | |
| go version | | |
| go vet | | |
127 changes: 107 additions & 20 deletions gnovm/pkg/doc/dirs.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
package doc

import (
"fmt"
"log"
"os"
"path"
"path/filepath"
"sort"
"strings"

"github.com/gnolang/gno/gnovm/pkg/gnomod"
)

// A bfsDir describes a directory holding code by specifying
Expand All @@ -30,15 +34,80 @@ type bfsDirs struct {
}

// newDirs begins scanning the given stdlibs directory.
func newDirs(dirs ...string) *bfsDirs {
// dirs are "gopath-like" directories, such as @/gnovm/stdlibs and @/examples.
// modDirs are user directories, expected to have gno.mod files
func newDirs(dirs []string, modDirs []string) *bfsDirs {
d := &bfsDirs{
hist: make([]bfsDir, 0, 256),
scan: make(chan bfsDir),
}
go d.walk(dirs)

roots := make([]bfsDir, 0, len(dirs)+len(modDirs))
for _, dir := range dirs {
roots = append(roots, bfsDir{
dir: dir,
importPath: "",
})
}

for _, mdir := range modDirs {
gm, err := parseGnoMod(filepath.Join(mdir, "gno.mod"))
if err != nil {
log.Printf("%v", err)
continue
}
roots = append(roots, bfsDir{
dir: mdir,
importPath: gm.Module.Mod.Path,
})
roots = append(roots, getGnoModDirs(gm)...)
}

go d.walk(roots)
return d
}

// tries to parse gno mod file given the filename, using Parse and Validate from
// the gnomod package
func parseGnoMod(fname string) (*gnomod.File, error) {
file, err := os.Stat(fname)
if err != nil {
return nil, fmt.Errorf("could not read gno.mod file: %w", err)
}
if file.IsDir() {
return nil, fmt.Errorf("invalid gno.mod at %q: is a directory", fname)
}

b, err := os.ReadFile(fname)
if err != nil {
return nil, fmt.Errorf("could not read gno.mod file: %w", err)
}
gm, err := gnomod.Parse(fname, b)
if err != nil {
return nil, fmt.Errorf("error parsing gno.mod file at %q: %w", fname, err)
}
if err := gm.Validate(); err != nil {
return nil, fmt.Errorf("error validating gno.mod file at %q: %w", fname, err)
}
return gm, nil
}

func getGnoModDirs(gm *gnomod.File) []bfsDir {
// cmd/go makes use of the go list command, we don't have that here.

dirs := make([]bfsDir, 0, len(gm.Require))
for _, r := range gm.Require {
mv := gm.Resolve(r)
path := gnomod.PackageDir("", mv)
dirs = append(dirs, bfsDir{
importPath: mv.Path,
dir: path,
})
}

return dirs
}

// Reset puts the scan back at the beginning.
func (d *bfsDirs) Reset() {
d.offset = 0
Expand All @@ -62,7 +131,7 @@ func (d *bfsDirs) Next() (bfsDir, bool) {
}

// walk walks the trees in the given roots.
func (d *bfsDirs) walk(roots []string) {
func (d *bfsDirs) walk(roots []bfsDir) {
for _, root := range roots {
d.bfsWalkRoot(root)
}
Expand All @@ -71,28 +140,36 @@ func (d *bfsDirs) walk(roots []string) {

// 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)
func (d *bfsDirs) bfsWalkRoot(root bfsDir) {
root.dir = filepath.Clean(root.dir)

// this is the queue of directories to examine in this pass.
this := []string{}
this := []bfsDir{}
// next is the queue of directories to examine in the next pass.
next := []string{root}
next := []bfsDir{root}

for len(next) > 0 {
this, next = next, this[:0]
for _, dir := range this {
fd, err := os.Open(dir)
fd, err := os.Open(dir.dir)
if err != nil {
log.Print(err)
continue
}
entries, err := fd.Readdir(0)

// read dir entries.
entries, err := fd.ReadDir(0)
fd.Close()
if err != nil {
log.Print(err)
continue
}

// stop at module boundaries
if dir.dir != root.dir && containsGnoMod(entries) {
continue
}

hasGnoFiles := false
for _, entry := range entries {
name := entry.Name()
Expand All @@ -111,20 +188,28 @@ func (d *bfsDirs) bfsWalkRoot(root string) {
continue
}
// Remember this (fully qualified) directory for the next pass.
next = append(next, filepath.Join(dir, name))
next = append(next, bfsDir{
dir: filepath.Join(dir.dir, name),
importPath: path.Join(dir.importPath, 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}
d.scan <- dir
}
}
}
}

func containsGnoMod(entries []os.DirEntry) bool {
for _, entry := range entries {
if entry.Name() == "gno.mod" && !entry.IsDir() {
return true
}
}
return false
}

// 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
Expand All @@ -139,12 +224,14 @@ func (d *bfsDirs) findPackage(name string) []bfsDir {
}
}
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
// prefer shorter paths -- if we have an exact match it will be of the
// shortest possible pkg path.
ci := strings.Count(candidates[i].importPath, "/")
cj := strings.Count(candidates[j].importPath, "/")
if ci != cj {
return ci < cj
}
// use alphabetical ordering otherwise.
return candidates[i].importPath < candidates[j].importPath
})
return candidates
Expand Down
Loading

0 comments on commit f51ce54

Please sign in to comment.