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(website): add godoc-style documentation to gno packages #526

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
21a510e
add godoc-style documentation to package gno
yassinouider Feb 18, 2023
39fb7f1
Fixes linting issues
yassinouider Feb 18, 2023
83fdc3e
Merge remote-tracking branch 'upstream/master' into improvement/packa…
yassinouider Feb 18, 2023
553565f
Improve code readability with consistent braces and variable grouping
yassinouider Feb 23, 2023
3467f9b
Inline variable and improve code readability
yassinouider Feb 23, 2023
a7ad1fd
Fix error handling in queryFiles function
yassinouider Feb 23, 2023
341e130
Refactor QueryFiles method to use GetFileBodies method on MemPackage
yassinouider Feb 23, 2023
aadbd28
Refactor QueryFiles method to use custom MemFileBodies type for retur…
yassinouider Feb 23, 2023
bd57775
Ingore also _filetest.gno
yassinouider Feb 23, 2023
fbc3c7b
Refactor code, fix bug, and add test
yassinouider Feb 25, 2023
77211c6
Refactors method title and template indentation
yassinouider Feb 25, 2023
1229188
support URL fragments
yassinouider Feb 26, 2023
7839aed
Align type, var and const documentation with godoc guidelines (doc be…
yassinouider Feb 26, 2023
f9b80b2
Merge branch 'master' into improvement/package-documentation
yassinouider Feb 26, 2023
aab5105
Merge remote-tracking branch 'origin/master' into improvement/package…
yassinouider Feb 27, 2023
ee80439
Refactor QueryFiles method
yassinouider Mar 1, 2023
eca656f
Merge remote-tracking branch 'origin/master' into improvement/package…
yassinouider Mar 1, 2023
76f5364
Merge remote-tracking branch 'origin/master' into improvement/package…
yassinouider Mar 1, 2023
d0f3dee
Address code review comments
yassinouider Mar 1, 2023
44b88de
Fix type comment bug and add tests
yassinouider Mar 3, 2023
c10cffd
Merge remote-tracking branch 'origin/master' into improvement/package…
yassinouider Mar 3, 2023
120082c
Use template hjls for package_file
yassinouider Mar 3, 2023
c372d01
Use testify
yassinouider Mar 4, 2023
afdaa82
Merge remote-tracking branch 'origin/master' into improvement/package…
yassinouider Mar 4, 2023
7daa079
use markdown format instead of html
yassinouider Mar 5, 2023
416414d
Merge remote-tracking branch 'origin/master' into improvement/package…
yassinouider Mar 7, 2023
21a27c1
Merge remote-tracking branch 'origin/master' into improvement/package…
yassinouider Mar 8, 2023
5a828ef
Update code for better browser compatibility
yassinouider Mar 8, 2023
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
87 changes: 49 additions & 38 deletions gnoland/website/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ import (
"github.com/gorilla/mux"
"github.com/gotuna/gotuna"

"github.com/gnolang/gno/gnoland/website/pkgs/doc"
"github.com/gnolang/gno/gnoland/website/static" // for static files
"github.com/gnolang/gno/pkgs/sdk/vm" // for error types
// "github.com/gnolang/gno/pkgs/sdk" // for baseapp (info, status)
)

const (
qFileStr = "vm/qfile"
qFileStr = "vm/qfile"
qFilesStr = "vm/qfiles"
)

var flags struct {
Expand Down Expand Up @@ -319,50 +321,59 @@ func handlerPackageFile(app gotuna.App) http.Handler {
vars := mux.Vars(r)
pkgpath := "gno.land/p/" + vars["filepath"]
diruri, filename := std.SplitFilepath(pkgpath)
if filename == "" && diruri == pkgpath {
// redirect to diruri + "/"
http.Redirect(w, r, "/p/"+vars["filepath"]+"/", http.StatusFound)
if filename != "" {
renderPackageFile(app, w, r, diruri, filename)
return
}
renderPackageFile(app, w, r, diruri, filename)
renderPackage(app, w, r, diruri)
})
}

func renderPackage(app gotuna.App, w http.ResponseWriter, r *http.Request, diruri string) {
qpath := qFilesStr
data := []byte(diruri)
res, err := makeRequest(qpath, data)
if err != nil {
writeError(w, err)
return
}

var files map[string]string
if err := json.Unmarshal(res.Data, &files); err != nil {
writeError(w, err)
return
}

pkgdoc, err := doc.New(diruri, files)
if err != nil {
writeError(w, err)
return
}

tmpl := app.NewTemplatingEngine()
tmpl.Set("DirURI", diruri)
tmpl.Set("DirPath", pathOf(diruri))
tmpl.Set("Package", pkgdoc)
tmpl.Render(w, r, "package_dir.html", "funcs.html")
}

func renderPackageFile(app gotuna.App, w http.ResponseWriter, r *http.Request, diruri string, filename string) {
if filename == "" {
// Request is for a folder.
qpath := qFileStr
data := []byte(diruri)
res, err := makeRequest(qpath, data)
if err != nil {
writeError(w, err)
return
}
files := strings.Split(string(res.Data), "\n")
// Render template.
tmpl := app.NewTemplatingEngine()
tmpl.Set("DirURI", diruri)
tmpl.Set("DirPath", pathOf(diruri))
tmpl.Set("Files", files)
tmpl.Render(w, r, "package_dir.html", "funcs.html")
} else {
// Request is for a file.
filepath := diruri + "/" + filename
qpath := qFileStr
data := []byte(filepath)
res, err := makeRequest(qpath, data)
if err != nil {
writeError(w, err)
return
}
// Render template.
tmpl := app.NewTemplatingEngine()
tmpl.Set("DirURI", diruri)
tmpl.Set("DirPath", pathOf(diruri))
tmpl.Set("FileName", filename)
tmpl.Set("FileContents", string(res.Data))
tmpl.Render(w, r, "package_file.html", "funcs.html")
// Request is for a file.
filepath := diruri + "/" + filename
qpath := qFileStr
data := []byte(filepath)
res, err := makeRequest(qpath, data)
thehowl marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
writeError(w, err)
return
}
// Render template.
tmpl := app.NewTemplatingEngine()
tmpl.Set("DirURI", diruri)
tmpl.Set("DirPath", pathOf(diruri))
tmpl.Set("FileName", filename)
tmpl.Set("FileContents", string(res.Data))
tmpl.Render(w, r, "package_file.html", "funcs.html")
}

func makeRequest(qpath string, data []byte) (res *abci.ResponseQuery, err error) {
Expand Down
82 changes: 82 additions & 0 deletions gnoland/website/pkgs/doc/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package doc

import (
"go/ast"
"go/parser"
"go/token"
"sort"
"strings"
)

func New(pkgPath string, files map[string]string) (*Package, error) {
p := Package{
ImportPath: pkgPath,
Filenames: make([]string, 0, len(files)),
}

fset := token.NewFileSet()
gnoFiles := make(map[string]*ast.File)
thehowl marked this conversation as resolved.
Show resolved Hide resolved

for filename, fileContent := range files {
p.Filenames = append(p.Filenames, filename)

f, err := parser.ParseFile(fset, filename, fileContent, parser.ParseComments)
if err != nil {
return nil, err
}

ast.FileExports(f)

if strings.HasSuffix(filename, "_test.gno") {
thehowl marked this conversation as resolved.
Show resolved Hide resolved
continue
}

gnoFiles[filename] = f

if f.Doc != nil {
doc := f.Doc.Text()
if p.Doc != "" {
p.Doc += "\n"
}
p.Doc += doc
thehowl marked this conversation as resolved.
Show resolved Hide resolved
}
}

sort.Strings(p.Filenames)

astPkg, _ := ast.NewPackage(fset, gnoFiles, nil, nil)

p.Name = astPkg.Name

ast.Inspect(astPkg, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.FuncDecl:
fn := extractFunc(x)
p.Funcs = append(p.Funcs, fn)

case *ast.GenDecl:
if x.Tok == token.VAR {
value, _ := extractValue(fset, x)
p.Vars = append(p.Vars, value)
}
if x.Tok == token.CONST {
value, _ := extractValue(fset, x)
p.Consts = append(p.Consts, value)
}
if x.Tok == token.TYPE {
for _, spec := range x.Specs {
if ts, ok := spec.(*ast.TypeSpec); ok {
newType, _ := extractType(fset, ts)
p.Types = append(p.Types, newType)
}
}
}
}

return true
})

p.populateType()

return &p, nil
}
165 changes: 165 additions & 0 deletions gnoland/website/pkgs/doc/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package doc

import (
"strings"
)

type Package struct {
ImportPath string
Name string
Doc string
Filenames []string
Funcs []*Func
Methods []*Func
Vars []*Value
Consts []*Value
Types []*Type
Examples []*Example
}

func (p *Package) populateType() {
p.populateTypeWithMethods()
p.populateTypeWithFuncs()
p.populateTypeWithValue()
thehowl marked this conversation as resolved.
Show resolved Hide resolved
}

func (p *Package) populateTypeWithMethods() {
for _, t := range p.Types {
matchedFuncs := make([]*Func, 0)
remainingFuncs := make([]*Func, 0)
thehowl marked this conversation as resolved.
Show resolved Hide resolved
for _, fn := range p.Funcs {
if fn.Recv == nil {
remainingFuncs = append(remainingFuncs, fn)
continue
}
for _, n := range fn.Recv {
if n == t.Name || "*"+n == t.Name {
matchedFuncs = append(matchedFuncs, fn)
} else {
remainingFuncs = append(remainingFuncs, fn)
}
}
}
t.Methods = matchedFuncs
p.Funcs = remainingFuncs
}
}

func (p *Package) populateTypeWithFuncs() {
for _, t := range p.Types {
var matchedFuncs []*Func
var remainingFuncs []*Func
thehowl marked this conversation as resolved.
Show resolved Hide resolved

for _, fn := range p.Funcs {
foundMatch := false

for _, r := range fn.Returns {
if r.Type == t.Name || strings.HasPrefix(r.Type, "*") && r.Type[1:] == t.Name {
thehowl marked this conversation as resolved.
Show resolved Hide resolved
matchedFuncs = append(matchedFuncs, fn)
foundMatch = true
break
}
}

if !foundMatch {
remainingFuncs = append(remainingFuncs, fn)
}
}

t.Funcs = matchedFuncs
p.Funcs = remainingFuncs
}
}

func (p *Package) populateTypeWithValue() {
for _, t := range p.Types {
var matchedVars []*Value
var matchedConsts []*Value

var remainingVars []*Value
thehowl marked this conversation as resolved.
Show resolved Hide resolved
for _, v := range p.Vars {
var matched bool
for _, item := range v.Items {
if item.Type == t.Name {
matchedVars = append(matchedVars, v)
matched = true
break
}
}
if !matched {
remainingVars = append(remainingVars, v)
}
}
p.Vars = remainingVars

var remainingConsts []*Value
for _, c := range p.Consts {
var matched bool
for _, item := range c.Items {
if item.Type == t.Name {
matchedConsts = append(matchedConsts, c)
matched = true
break
}
}
if !matched {
remainingConsts = append(remainingConsts, c)
}
}
p.Consts = remainingConsts

t.Vars = matchedVars
t.Consts = matchedConsts
}
}

type Example struct {
Name string
Doc string
Code string
Output []string
Play bool
}

type Value struct {
Doc string
Names []string
Items []*ValueItem
Signature string
}

type ValueItem struct {
Doc string
Type string
Name string
Value string
}

type FuncParam struct {
Type string
Names []string
}

type FuncReturn struct {
Type string
Names []string
}

type Func struct {
Doc string
Name string
Params []*FuncParam
Returns []*FuncReturn
Recv []string
Signature string
}

type Type struct {
Doc string
Name string
Definition string
Consts []*Value
Vars []*Value
Funcs []*Func
Methods []*Func
}
Loading