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

Closed
Show file tree
Hide file tree
Changes from all 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
103 changes: 64 additions & 39 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 @@ -309,7 +311,21 @@ func handlerRealmFile(app gotuna.App) http.Handler {
vars := mux.Vars(r)
diruri := "gno.land/r/" + vars["rlmname"]
filename := vars["filename"]
renderPackageFile(app, w, r, diruri, filename)
if filename != "" {
renderPackageFile(app, w, r, diruri, filename)
return
}
res, err := makeRequest(qFileStr, []byte(diruri))
if err != nil {
writeError(w, err)
return
}
files := strings.Split(string(res.Data), "\n")
tmpl := app.NewTemplatingEngine()
tmpl.Set("DirURI", diruri)
tmpl.Set("DirPath", pathOf(diruri))
tmpl.Set("Files", files)
tmpl.Render(w, r, "realm_dir.html", "funcs.html")
})
}

Expand All @@ -318,50 +334,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) {
res, err := makeRequest(qFilesStr, []byte(diruri))
if err != nil {
writeError(w, err)
return
}

var files std.MemFileBodies
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
}

pkgContent, err := pkgdoc.Markdown()
if err != nil {
writeError(w, err)
return
}

tmpl := app.NewTemplatingEngine()
tmpl.Set("DirPath", pathOf(diruri))
tmpl.Set("PkgContent", string(pkgContent))
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.
res, err := makeRequest(qFileStr, []byte(diruri+"/"+filename))
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
189 changes: 189 additions & 0 deletions gnoland/website/pkgs/doc/ast_to_string.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package doc

import (
"fmt"
"go/ast"
"strings"
)

func generateFuncSignature(fn *ast.FuncDecl) string {
if fn == nil {
return ""
}

var b strings.Builder
b.WriteString("func ")

if fn.Recv != nil {
var receiverNames []string
for _, field := range fn.Recv.List {
var fieldName string
if len(field.Names) > 0 {
fieldName = field.Names[0].Name
}
receiverNames = append(receiverNames, fmt.Sprintf("%s %s", fieldName, typeString(field.Type)))
}
if len(receiverNames) > 0 {
b.WriteString(fmt.Sprintf("(%s) ", strings.Join(receiverNames, ", ")))
}
}

fmt.Fprintf(&b, "%s(", fn.Name.Name)

var params []string
if fn.Type.Params != nil {
for _, param := range fn.Type.Params.List {
paramType := typeString(param.Type)
if len(param.Names) == 0 {
params = append(params, paramType)
} else {
paramNames := make([]string, len(param.Names))
for i, name := range param.Names {
paramNames[i] = name.Name
}
params = append(params, fmt.Sprintf("%s %s", strings.Join(paramNames, ", "), paramType))
}
}
}

fmt.Fprintf(&b, "%s)", strings.Join(params, ", "))

results := []string{}
if fn.Type.Results != nil {
hasNamedParams := false
for _, result := range fn.Type.Results.List {
if len(result.Names) == 0 {
results = append(results, typeString(result.Type))
} else {
hasNamedParams = true
var resultNames []string
for _, id := range result.Names {
resultNames = append(resultNames, id.Name)
}
results = append(results, fmt.Sprintf("%s %s", strings.Join(resultNames, ", "), typeString(result.Type)))
}
}

if len(results) > 0 {
b.WriteString(" ")
returnType := strings.Join(results, ", ")

if hasNamedParams || len(results) > 1 {
returnType = fmt.Sprintf("(%s)", returnType)
}

b.WriteString(returnType)
}
}

return b.String()
}

// This code is inspired by the code at https://cs.opensource.google/go/go/+/refs/tags/go1.20.1:src/go/doc/reader.go;drc=40ed3591829f67e7a116180aec543dd15bfcf5f9;bpv=1;bpt=1;l=124
func typeString(expr ast.Expr) string {
if expr == nil {
return ""
}

switch t := expr.(type) {
case *ast.Ident:
return t.Name
case *ast.IndexExpr:
return typeString(t.X)
case *ast.IndexListExpr:
return typeString(t.X)
case *ast.SelectorExpr:
if _, ok := t.X.(*ast.Ident); ok {
return fmt.Sprintf("%s.%s", typeString(t.X), t.Sel.Name)
}
case *ast.ParenExpr:
return typeString(t.X)
case *ast.StarExpr:
return fmt.Sprintf("*%s", typeString(t.X))
case *ast.BasicLit:
return t.Value
case *ast.Ellipsis:
return fmt.Sprintf("...%s", typeString(t.Elt))
case *ast.FuncType:
var params []string
if t.Params != nil {
for _, field := range t.Params.List {
paramType := typeString(field.Type)
if len(field.Names) > 0 {
for _, name := range field.Names {
params = append(params, fmt.Sprintf("%s %s", name.Name, paramType))
}
} else {
params = append(params, paramType)
}
}
}
var results []string
if t.Results != nil {
for _, field := range t.Results.List {
resultType := typeString(field.Type)
if len(field.Names) > 0 {
for _, name := range field.Names {
results = append(results, fmt.Sprintf("%s %s", name.Name, resultType))
}
} else {
results = append(results, resultType)
}
}
}

return strings.TrimSpace(fmt.Sprintf("func(%s) %s", strings.Join(params, ", "), strings.Join(results, ", ")))
case *ast.StructType:
var fields []string
for _, field := range t.Fields.List {
fieldType := typeString(field.Type)
if len(field.Names) > 0 {
for _, name := range field.Names {
fields = append(fields, fmt.Sprintf("%s %s", name.Name, fieldType))
}
} else {
fields = append(fields, fieldType)
}
}
return fmt.Sprintf("struct{%s}", strings.Join(fields, "; "))
case *ast.InterfaceType:
return "interface{}"
case *ast.MapType:
return fmt.Sprintf("map[%s]%s", typeString(t.Key), typeString(t.Value))
case *ast.ChanType:
chanDir := "chan"
if t.Dir == ast.SEND {
chanDir = "chan<-"
} else if t.Dir == ast.RECV {
chanDir = "<-chan"
}
return fmt.Sprintf("%s %s", chanDir, typeString(t.Value))
case *ast.ArrayType:
return fmt.Sprintf("[%s]%s", typeString(t.Len), typeString(t.Elt))
case *ast.SliceExpr:
return fmt.Sprintf("[]%s", typeString(t.X))
}
return ""
}

func isFuncExported(fn *ast.FuncDecl) bool {
if !fn.Name.IsExported() {
return false
}

if fn.Recv == nil {
return true
}

for _, recv := range fn.Recv.List {
if ast.IsExported(removePointer(typeString(recv.Type))) {
return true
}
}

return false
}

func removePointer(name string) string {
return strings.TrimPrefix(name, "*")
}
Loading