Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cmd/gno): add support for gno.mod projects in gno doc #829

Merged
merged 15 commits into from
Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 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,24 @@ 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)
}
var modDirs []string
if rd != "" {
modDirs = append(modDirs, rd)
}
thehowl marked this conversation as resolved.
Show resolved Hide resolved

// 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 | | |
129 changes: 109 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,82 @@ 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 := tryParseGnoMod(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.
// second return parameter is whether gno.mod exists.
func tryParseGnoMod(fname string) (*gnomod.File, error) {
thehowl marked this conversation as resolved.
Show resolved Hide resolved
file, err := os.Stat(fname)
// early exit for errors and non-file fname
if err != nil || file.IsDir() {
if err == nil {
return nil, fmt.Errorf("invalid gno.mod at %q: is a directory", fname)
}
// no need for filename, os errors contain that already
return nil, fmt.Errorf("could not read go.mod file: %w", err)
}
thehowl marked this conversation as resolved.
Show resolved Hide resolved

b, err := os.ReadFile(fname)
if err != nil {
return nil, fmt.Errorf("could not read go.mod file: %w", err)
thehowl marked this conversation as resolved.
Show resolved Hide resolved
}
gm, err := gnomod.Parse(fname, b)
if err == nil {
err = gm.Validate()
}
if err != nil {
return nil, fmt.Errorf("error parsing/validating go.mod file at %q: %w", fname, err)
}
thehowl marked this conversation as resolved.
Show resolved Hide resolved
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 +133,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 +142,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 +190,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 +226,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