From 21a510e5f3109f02ef4b663115c96e014951130e Mon Sep 17 00:00:00 2001 From: Yassin Date: Sat, 18 Feb 2023 18:12:01 +0100 Subject: [PATCH 01/19] add godoc-style documentation to package gno --- gnoland/website/main.go | 93 ++++++----- gnoland/website/pkgs/doc/doc.go | 83 ++++++++++ gnoland/website/pkgs/doc/tplfunc.go | 23 +++ gnoland/website/pkgs/doc/types.go | 165 ++++++++++++++++++++ gnoland/website/pkgs/doc/utils.go | 203 +++++++++++++++++++++++++ gnoland/website/static/css/app.css | 58 +++++++ gnoland/website/views/package_dir.html | 129 +++++++++++++++- pkgs/sdk/vm/handler.go | 20 +++ pkgs/sdk/vm/keeper.go | 13 ++ 9 files changed, 743 insertions(+), 44 deletions(-) create mode 100644 gnoland/website/pkgs/doc/doc.go create mode 100644 gnoland/website/pkgs/doc/tplfunc.go create mode 100644 gnoland/website/pkgs/doc/types.go create mode 100644 gnoland/website/pkgs/doc/utils.go diff --git a/gnoland/website/main.go b/gnoland/website/main.go index b997c5a9c80..8481a8263cd 100644 --- a/gnoland/website/main.go +++ b/gnoland/website/main.go @@ -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 { @@ -69,6 +71,12 @@ func main() { // StaticPrefix: "static/", } + app.ViewHelpers = []gotuna.ViewHelperFunc{ + func(w http.ResponseWriter, r *http.Request) (string, interface{}) { + return "comment", doc.CommentTplFunc + }, + } + app.Router.Handle("/", handlerHome(app)) app.Router.Handle("/about", handlerAbout(app)) app.Router.Handle("/game-of-realms", handlerGor(app)) @@ -319,50 +327,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) + 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) { diff --git a/gnoland/website/pkgs/doc/doc.go b/gnoland/website/pkgs/doc/doc.go new file mode 100644 index 00000000000..f14f4b7900e --- /dev/null +++ b/gnoland/website/pkgs/doc/doc.go @@ -0,0 +1,83 @@ +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) + + 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") { + continue + } + + gnoFiles[filename] = f + + if f.Doc != nil { + doc := f.Doc.Text() + if p.Doc != "" { + p.Doc += "\n" + } + p.Doc += doc + } + } + + 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 +} diff --git a/gnoland/website/pkgs/doc/tplfunc.go b/gnoland/website/pkgs/doc/tplfunc.go new file mode 100644 index 00000000000..f7e2ea51dc6 --- /dev/null +++ b/gnoland/website/pkgs/doc/tplfunc.go @@ -0,0 +1,23 @@ +package doc + +import ( + "go/doc/comment" + "html/template" + "regexp" +) + +var TemplateFuncs = template.FuncMap{ + "comment": CommentTplFunc, +} + +func CommentTplFunc(s string) template.HTML { + var p comment.Parser + doc := p.Parse(s) + var pr comment.Printer + pr.DocLinkBaseURL = "/p/demo" + + re := regexp.MustCompile(`(?s)
(.*?)
`) + output := re.ReplaceAllString(string(pr.HTML(doc)), "
$1
") + + return template.HTML(output) +} diff --git a/gnoland/website/pkgs/doc/types.go b/gnoland/website/pkgs/doc/types.go new file mode 100644 index 00000000000..de353d6eaa2 --- /dev/null +++ b/gnoland/website/pkgs/doc/types.go @@ -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() +} + +func (p *Package) populateTypeWithMethods() { + for _, t := range p.Types { + matchedFuncs := make([]*Func, 0) + remainingFuncs := make([]*Func, 0) + 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 + + 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 { + 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 + 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 +} diff --git a/gnoland/website/pkgs/doc/utils.go b/gnoland/website/pkgs/doc/utils.go new file mode 100644 index 00000000000..41a2dde4a19 --- /dev/null +++ b/gnoland/website/pkgs/doc/utils.go @@ -0,0 +1,203 @@ +package doc + +import ( + "bytes" + "fmt" + "go/ast" + "go/format" + "go/token" + "go/types" + "strings" +) + +func generateFuncSignature(fn *ast.FuncDecl) string { + if fn == nil { + return "" + } + var receiver string + 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 + } + receiverType := typeString(field.Type) + receiverNames = append(receiverNames, fmt.Sprintf("%s %s", fieldName, receiverType)) + } + if len(receiverNames) > 0 { + receiver = fmt.Sprintf("(%s) ", strings.Join(receiverNames, ", ")) + } + } + params := []string{} + if fn.Type.Params != nil { + for _, param := range fn.Type.Params.List { + var paramType string + if len(param.Names) == 0 { + // unnamed parameter + paramType = typeString(param.Type) + } else { + // named parameter(s) + var paramNames []string + for _, id := range param.Names { + paramNames = append(paramNames, id.Name) + } + paramType = fmt.Sprintf("%s %s", strings.Join(paramNames, ", "), typeString(param.Type)) + } + params = append(params, paramType) + } + } + results := []string{} + if fn.Type.Results != nil { + for _, result := range fn.Type.Results.List { + var resultType string + if len(result.Names) == 0 { + // unnamed result parameter + resultType = typeString(result.Type) + } else { + // named result parameter(s) + var resultNames []string + for _, id := range result.Names { + resultNames = append(resultNames, id.Name) + } + resultType = fmt.Sprintf("%s %s", strings.Join(resultNames, ", "), typeString(result.Type)) + } + results = append(results, resultType) + } + } + var returnType string + if len(results) == 1 { + returnType = results[0] + } else { + returnType = fmt.Sprintf("(%s)", strings.Join(results, ", ")) + } + + return fmt.Sprintf("func %s%s(%s) %s", receiver, fn.Name.Name, strings.Join(params, ", "), returnType) +} + +func extractFunc(x *ast.FuncDecl) *Func { + fn := Func{ + Doc: x.Doc.Text(), + Name: x.Name.String(), + Signature: generateFuncSignature(x), + } + if x.Recv != nil { + for _, rcv := range x.Recv.List { + if ident, ok := rcv.Type.(*ast.Ident); ok { + fn.Recv = append(fn.Recv, ident.Name) + } + if star, ok := rcv.Type.(*ast.StarExpr); ok { + fn.Recv = append(fn.Recv, types.ExprString(star.X)) + } + } + } + + for _, field := range x.Type.Params.List { + paramNames := []string{} + for _, name := range field.Names { + paramNames = append(paramNames, name.Name) + } + paramType := typeString(field.Type) + param := &FuncParam{ + Type: paramType, + Names: paramNames, + } + fn.Params = append(fn.Params, param) + } + + if x.Type.Results != nil { + for _, field := range x.Type.Results.List { + returnNames := []string{} + for _, name := range field.Names { + if name != nil { + continue + } + returnNames = append(returnNames, name.Name) + } + returnType := typeString(field.Type) + ret := &FuncReturn{ + Type: returnType, + Names: returnNames, + } + fn.Returns = append(fn.Returns, ret) + } + } + + return &fn +} + +func extractValue(fset *token.FileSet, x *ast.GenDecl) (*Value, error) { + value := Value{ + Doc: x.Doc.Text(), + } + x.Doc = nil + var buf bytes.Buffer + if err := format.Node(&buf, fset, x); err != nil { + return nil, err + } + value.Signature = buf.String() + for _, spec := range x.Specs { + if valueSpec, ok := spec.(*ast.ValueSpec); ok { + for i, name := range valueSpec.Names { + valueItem := ValueItem{ + Type: typeString(valueSpec.Type), + Name: name.String(), + } + if len(valueSpec.Values) > i { + if lit, ok := valueSpec.Values[i].(*ast.BasicLit); ok { + valueItem.Value = lit.Value + } else if ident, ok := valueSpec.Values[i].(*ast.Ident); ok { + valueItem.Value = ident.Name + } + } + if valueSpec.Doc != nil { + valueItem.Doc = valueSpec.Doc.Text() + } + value.Items = append(value.Items, &valueItem) + } + } + } + return &value, nil +} + +func extractType(fset *token.FileSet, x *ast.TypeSpec) (*Type, error) { + newType := Type{} + newType.Name = x.Name.String() + newType.Doc = x.Doc.Text() + + x.Doc = nil + var buf bytes.Buffer + if err := format.Node(&buf, fset, x); err != nil { + return nil, err + } + + newType.Definition = buf.String() + return &newType, nil +} + +// 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.Ellipsis: + return fmt.Sprintf("...%s", typeString(t.Elt)) + } + return "" +} diff --git a/gnoland/website/static/css/app.css b/gnoland/website/static/css/app.css index 332861317b4..7f535ef8828 100644 --- a/gnoland/website/static/css/app.css +++ b/gnoland/website/static/css/app.css @@ -194,3 +194,61 @@ pre { #source { display: none; } + +#pkg_doc { + padding: 0 22px; +} + +#pkg_doc #pkg_doc__header__overview { + padding: 10px 0; +} + +#pkg_doc__body > .pkg_doc__section { + padding: 10px 0; +} + +#pkg_doc__body > .pkg_doc__section > .pkg_doc__section-heading { + padding-top: 5px; + padding-bottom: 5px; +} + +.pkg_doc__variable { + padding: 10px 0; +} + +.pkg_doc__constant { + padding: 10px 0; +} + +.pkg_doc__comment { + padding: 10px 10px; +} + +.pkg_doc__section .pkg_doc__comment { + padding: 5px 15px; + font-size: 0.9rem; +} + +.pkg_doc__section .pkg_doc__function { + padding: 10px 0; +} + +.pkg_doc__type { + padding: 20px 0; +} + +.pkg_doc__type-title { + font-weight: bold; +} + +.pkg_doc__type > .pkg_doc__function { + padding: 15px 10px; +} + +.pkg_doc__type > .pkg_doc__variable { + padding: 15px 10px; +} + +.pkg_doc__type > .pkg_doc__constant { + padding: 15px 10px; +} diff --git a/gnoland/website/views/package_dir.html b/gnoland/website/views/package_dir.html index 7b4d62c00cc..a3c42eeb5a7 100644 --- a/gnoland/website/views/package_dir.html +++ b/gnoland/website/views/package_dir.html @@ -4,6 +4,7 @@ Gno.land {{ template "html_head" }} +
@@ -15,22 +16,138 @@ {{ template "header_buttons" }}
-
- {{ template "dir_contents" . }} +
+
+

Package {{ .Data.Package.Name}}

+

import "{{ .Data.Package.ImportPath }}"

+
+

Overview

+ {{ if .Data.Package.Doc }} +
+ {{ .Data.Package.Doc | comment }} +
+ {{ else }} +

This section is empty.

+ {{ end }} +
+
+ +
+
+

Constants

+ {{ range .Data.Package.Consts }} +
+
{{ .Signature }}
+ {{ if .Doc }} +
{{ .Doc }}
+ {{end}} +
+ {{ else }} +

This section is empty.

+ {{ end }} +
+ +
+

Variables

+ {{ range .Data.Package.Vars }} +
+
{{ .Signature }}
+ {{ if .Doc }} +
{{ .Doc }}
+ {{end}} +
+ {{ else }} +

This section is empty.

+ {{ end }} +
+ +
+

Functions

+
+ {{ range .Data.Package.Funcs }} +
+

func {{ .Name }}

+
{{.Signature }}
+ {{ if .Doc }} +
{{ .Doc | comment }}
+ {{end}} +
+ {{ else }} +

This section is empty.

+ {{ end }} +
+
+ +
+

Types

+ {{ range .Data.Package.Types }} +
+

type {{ .Name }}

+
{{ .Definition }}
+ {{ if .Doc }} +
{{ .Doc | comment }}
+ {{end}} {{ range .Vars }} +
+
{{ .Signature }}
+ {{ if .Doc }} +
{{ .Doc | comment }}
+ {{end}} +
+ {{ end}} {{ range .Consts }} +
+
{{ .Signature }}
+ {{ if .Doc }} +
{{ .Doc | comment }}
+ {{end}} +
+ {{ end}} {{ range .Funcs }} +
+

func {{ .Name }}

+
{{ .Signature }}
+ {{ if .Doc }} +
{{ .Doc | comment }}
+ {{end}} +
+ {{ end }} {{ range .Methods }} +
+

func {{ .Name }}

+
{{ .Signature }}
+ {{ if .Doc }} +
{{ .Doc | comment }}
+ {{end}} +
+ + {{ end }} +
+ {{ else }} +

This section is empty.

+ {{ end }} +
+ +
+

Source Files

+
{{ template "dir_contents" . }}
+
+
{{ template "footer" }}
{{ template "js" }} + + -{{- end -}} - -{{ define "dir_contents" }} +{{- end -}} {{ define "dir_contents" }}
{{ $dirPath := .Data.DirPath }}
{{ end }} {{ range .Methods }} @@ -113,7 +115,7 @@

Types

func {{ .Name }}

{{ .Signature }}
{{ if .Doc }} -
{{ .Doc | comment }}
+
{{ .Doc }}
{{end}} diff --git a/pkgs/sdk/vm/handler.go b/pkgs/sdk/vm/handler.go index d03e1671aa6..4a275f1f528 100644 --- a/pkgs/sdk/vm/handler.go +++ b/pkgs/sdk/vm/handler.go @@ -209,7 +209,7 @@ func (vh vmHandler) queryFiles(ctx sdk.Context, req abci.RequestQuery) (res abci sdk.ABCIResponseQueryFromError(err) return } - res.Data = []byte(b) + res.Data = b return } From 553565f6ec4992da413b8513c16b118901516c18 Mon Sep 17 00:00:00 2001 From: Yassin Date: Thu, 23 Feb 2023 19:51:44 +0100 Subject: [PATCH 03/19] Improve code readability with consistent braces and variable grouping --- gnoland/website/pkgs/doc/types.go | 20 ++++++++++++-------- gnoland/website/static/css/app.css | 15 ++++----------- gnoland/website/views/package_dir.html | 22 +++++++++++----------- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/gnoland/website/pkgs/doc/types.go b/gnoland/website/pkgs/doc/types.go index de353d6eaa2..b0dc3e9c728 100644 --- a/gnoland/website/pkgs/doc/types.go +++ b/gnoland/website/pkgs/doc/types.go @@ -47,8 +47,10 @@ func (p *Package) populateTypeWithMethods() { func (p *Package) populateTypeWithFuncs() { for _, t := range p.Types { - var matchedFuncs []*Func - var remainingFuncs []*Func + var ( + matchedFuncs []*Func + remainingFuncs []*Func + ) for _, fn := range p.Funcs { foundMatch := false @@ -73,10 +75,13 @@ func (p *Package) populateTypeWithFuncs() { func (p *Package) populateTypeWithValue() { for _, t := range p.Types { - var matchedVars []*Value - var matchedConsts []*Value + var ( + matchedVars []*Value + matchedConsts []*Value + remainingVars []*Value + remainingConsts []*Value + ) - var remainingVars []*Value for _, v := range p.Vars { var matched bool for _, item := range v.Items { @@ -90,9 +95,7 @@ func (p *Package) populateTypeWithValue() { remainingVars = append(remainingVars, v) } } - p.Vars = remainingVars - var remainingConsts []*Value for _, c := range p.Consts { var matched bool for _, item := range c.Items { @@ -106,10 +109,11 @@ func (p *Package) populateTypeWithValue() { remainingConsts = append(remainingConsts, c) } } - p.Consts = remainingConsts t.Vars = matchedVars + p.Vars = remainingVars t.Consts = matchedConsts + p.Consts = remainingConsts } } diff --git a/gnoland/website/static/css/app.css b/gnoland/website/static/css/app.css index 35b9054fb21..83310299692 100644 --- a/gnoland/website/static/css/app.css +++ b/gnoland/website/static/css/app.css @@ -229,8 +229,7 @@ pre { } .pkg_doc__section .pkg_doc__comment { - padding: 5px 15px; - font-size: 0.9rem; + padding: 1em 0; } .pkg_doc__section .pkg_doc__function { @@ -245,14 +244,8 @@ pre { font-weight: bold; } -.pkg_doc__type > .pkg_doc__function { - padding: 15px 10px; -} - +.pkg_doc__type > .pkg_doc__function, +.pkg_doc__type > .pkg_doc__constant, .pkg_doc__type > .pkg_doc__variable { - padding: 15px 10px; -} - -.pkg_doc__type > .pkg_doc__constant { - padding: 15px 10px; + padding: 5px 0 5px 20px; } diff --git a/gnoland/website/views/package_dir.html b/gnoland/website/views/package_dir.html index d266cd31db1..994d4b673fd 100644 --- a/gnoland/website/views/package_dir.html +++ b/gnoland/website/views/package_dir.html @@ -18,7 +18,7 @@
-

Package {{ .Data.Package.Name}}

+

Package {{ .Data.Package.Name }}

import "{{ .Data.Package.ImportPath }}"

Overview

@@ -40,7 +40,7 @@

Constants

{{ .Signature }}
{{ if .Doc }}
{{ .Doc }}
- {{end}} + {{ end }}
{{ else }}

This section is empty.

@@ -54,7 +54,7 @@

Variables

{{ .Signature }}
{{ if .Doc }}
{{ .Doc }}
- {{end}} + {{ end }}
{{ else }}

This section is empty.

@@ -72,7 +72,7 @@

Functions

{{ .Doc  }}
- {{end}} + {{ end }}
{{ else }}

This section is empty.

@@ -88,27 +88,27 @@

Types

{{ .Definition }}
{{ if .Doc }}
{{ .Doc }}
- {{end}} {{ range .Vars }} + {{ end }} {{ range .Vars }}
{{ .Signature }}
{{ if .Doc }}
{{ .Doc }}
- {{end}} + {{ end }}
- {{ end}} {{ range .Consts }} + {{ end }} {{ range .Consts }}
{{ .Signature }}
{{ if .Doc }}
{{ .Doc }}
- {{end}} + {{ end }}
- {{ end}} {{ range .Funcs }} + {{ end }} {{ range .Funcs }}

func {{ .Name }}

{{ .Signature }}
{{ if .Doc }}
{{ .Doc }}
- {{end}} + {{ end }}
{{ end }} {{ range .Methods }}
@@ -116,7 +116,7 @@

Types

{{ .Signature }}
{{ if .Doc }}
{{ .Doc }}
- {{end}} + {{ end }}
{{ end }} From 3467f9b685d19e3089371eb94296b2963f68da3f Mon Sep 17 00:00:00 2001 From: Yassin Date: Thu, 23 Feb 2023 20:07:25 +0100 Subject: [PATCH 04/19] Inline variable and improve code readability --- gnoland/website/main.go | 9 ++------- pkgs/sdk/vm/keeper.go | 3 +-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/gnoland/website/main.go b/gnoland/website/main.go index ce37326b754..3bca2e97af3 100644 --- a/gnoland/website/main.go +++ b/gnoland/website/main.go @@ -330,9 +330,7 @@ func handlerPackageFile(app gotuna.App) http.Handler { } func renderPackage(app gotuna.App, w http.ResponseWriter, r *http.Request, diruri string) { - qpath := qFilesStr - data := []byte(diruri) - res, err := makeRequest(qpath, data) + res, err := makeRequest(qFilesStr, []byte(diruri)) if err != nil { writeError(w, err) return @@ -359,10 +357,7 @@ func renderPackage(app gotuna.App, w http.ResponseWriter, r *http.Request, dirur func renderPackageFile(app gotuna.App, w http.ResponseWriter, r *http.Request, diruri string, filename string) { // Request is for a file. - filepath := diruri + "/" + filename - qpath := qFileStr - data := []byte(filepath) - res, err := makeRequest(qpath, data) + res, err := makeRequest(qFileStr, []byte(diruri+"/"+filename)) if err != nil { writeError(w, err) return diff --git a/pkgs/sdk/vm/keeper.go b/pkgs/sdk/vm/keeper.go index a3e0c78c2f2..bbf3065d65b 100644 --- a/pkgs/sdk/vm/keeper.go +++ b/pkgs/sdk/vm/keeper.go @@ -466,9 +466,8 @@ func (vm *VMKeeper) QueryFile(ctx sdk.Context, filepath string) (res string, err func (vm *VMKeeper) QueryFiles(ctx sdk.Context, path string) (res map[string]string, err error) { store := vm.getGnoStore(ctx) dirpath, _ := std.SplitFilepath(path) - memPkg := store.GetMemPackage(dirpath) files := map[string]string{} - if memPkg != nil { + if memPkg := store.GetMemPackage(dirpath); memPkg != nil { for _, memfile := range memPkg.Files { files[memfile.Name] = memfile.Body } From a7ad1fd10896b10cf32aa3fbd26c2c9ce4117236 Mon Sep 17 00:00:00 2001 From: Yassin Date: Thu, 23 Feb 2023 20:09:35 +0100 Subject: [PATCH 05/19] Fix error handling in queryFiles function --- pkgs/sdk/vm/handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/sdk/vm/handler.go b/pkgs/sdk/vm/handler.go index 4a275f1f528..b7501e1c51e 100644 --- a/pkgs/sdk/vm/handler.go +++ b/pkgs/sdk/vm/handler.go @@ -206,7 +206,7 @@ func (vh vmHandler) queryFiles(ctx sdk.Context, req abci.RequestQuery) (res abci } b, err := json.Marshal(&result) if err != nil { - sdk.ABCIResponseQueryFromError(err) + res = sdk.ABCIResponseQueryFromError(err) return } res.Data = b From 341e130d1a8104454c039e0ce2fa548b51988caa Mon Sep 17 00:00:00 2001 From: Yassin Date: Thu, 23 Feb 2023 20:21:59 +0100 Subject: [PATCH 06/19] Refactor QueryFiles method to use GetFileBodies method on MemPackage --- pkgs/sdk/vm/keeper.go | 13 ++++++------- pkgs/std/memfile.go | 8 ++++++++ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/pkgs/sdk/vm/keeper.go b/pkgs/sdk/vm/keeper.go index bbf3065d65b..b815c96ad26 100644 --- a/pkgs/sdk/vm/keeper.go +++ b/pkgs/sdk/vm/keeper.go @@ -464,13 +464,12 @@ func (vm *VMKeeper) QueryFile(ctx sdk.Context, filepath string) (res string, err } func (vm *VMKeeper) QueryFiles(ctx sdk.Context, path string) (res map[string]string, err error) { - store := vm.getGnoStore(ctx) dirpath, _ := std.SplitFilepath(path) - files := map[string]string{} - if memPkg := store.GetMemPackage(dirpath); memPkg != nil { - for _, memfile := range memPkg.Files { - files[memfile.Name] = memfile.Body - } + memPkg := vm.getGnoStore(ctx).GetMemPackage(dirpath) + + if memPkg != nil { + return memPkg.GetFileBodies(), nil } - return files, nil + + return map[string]string{}, nil } diff --git a/pkgs/std/memfile.go b/pkgs/std/memfile.go index b0d7a41b7ef..a1cb50c6c59 100644 --- a/pkgs/std/memfile.go +++ b/pkgs/std/memfile.go @@ -30,6 +30,14 @@ func (mempkg *MemPackage) GetFile(name string) *MemFile { return nil } +func (mempkg *MemPackage) GetFileBodies() map[string]string { + files := map[string]string{} + for _, memfile := range mempkg.Files { + files[memfile.Name] = memfile.Body + } + return files +} + const ( reDomainPart = `gno\.land` rePathPart = `[a-z][a-z0-9_]*` From aadbd28b1c8ceda5bcd11b34d608ec54b8f06bba Mon Sep 17 00:00:00 2001 From: Yassin Date: Thu, 23 Feb 2023 20:40:46 +0100 Subject: [PATCH 07/19] Refactor QueryFiles method to use custom MemFileBodies type for returning file map and update renderPackage method to reflect the change --- gnoland/website/main.go | 2 +- pkgs/sdk/vm/keeper.go | 4 ++-- pkgs/std/memfile.go | 12 +++++++----- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/gnoland/website/main.go b/gnoland/website/main.go index 3bca2e97af3..d54550c5c9c 100644 --- a/gnoland/website/main.go +++ b/gnoland/website/main.go @@ -336,7 +336,7 @@ func renderPackage(app gotuna.App, w http.ResponseWriter, r *http.Request, dirur return } - var files map[string]string + var files std.MemFileBodies if err := json.Unmarshal(res.Data, &files); err != nil { writeError(w, err) return diff --git a/pkgs/sdk/vm/keeper.go b/pkgs/sdk/vm/keeper.go index b815c96ad26..b41a0ccf174 100644 --- a/pkgs/sdk/vm/keeper.go +++ b/pkgs/sdk/vm/keeper.go @@ -463,7 +463,7 @@ func (vm *VMKeeper) QueryFile(ctx sdk.Context, filepath string) (res string, err } } -func (vm *VMKeeper) QueryFiles(ctx sdk.Context, path string) (res map[string]string, err error) { +func (vm *VMKeeper) QueryFiles(ctx sdk.Context, path string) (res std.MemFileBodies, err error) { dirpath, _ := std.SplitFilepath(path) memPkg := vm.getGnoStore(ctx).GetMemPackage(dirpath) @@ -471,5 +471,5 @@ func (vm *VMKeeper) QueryFiles(ctx sdk.Context, path string) (res map[string]str return memPkg.GetFileBodies(), nil } - return map[string]string{}, nil + return std.MemFileBodies{}, nil } diff --git a/pkgs/std/memfile.go b/pkgs/std/memfile.go index a1cb50c6c59..464e75a5110 100644 --- a/pkgs/std/memfile.go +++ b/pkgs/std/memfile.go @@ -13,6 +13,8 @@ type MemFile struct { Body string } +type MemFileBodies map[string]string + // NOTE: in the future, a MemPackage may represent // updates/additional-files for an existing package. type MemPackage struct { @@ -30,12 +32,12 @@ func (mempkg *MemPackage) GetFile(name string) *MemFile { return nil } -func (mempkg *MemPackage) GetFileBodies() map[string]string { - files := map[string]string{} - for _, memfile := range mempkg.Files { - files[memfile.Name] = memfile.Body +func (mempkg *MemPackage) GetFileBodies() MemFileBodies { + fileBodies := MemFileBodies{} + for _, memFile := range mempkg.Files { + fileBodies[memFile.Name] = memFile.Body } - return files + return fileBodies } const ( From bd577756e9906d5e8ec71beb4464688ff17cd7bd Mon Sep 17 00:00:00 2001 From: Yassin Date: Thu, 23 Feb 2023 20:46:06 +0100 Subject: [PATCH 08/19] Ingore also _filetest.gno --- gnoland/website/pkgs/doc/doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnoland/website/pkgs/doc/doc.go b/gnoland/website/pkgs/doc/doc.go index 7615c141fda..9c99c31f9bf 100644 --- a/gnoland/website/pkgs/doc/doc.go +++ b/gnoland/website/pkgs/doc/doc.go @@ -27,7 +27,7 @@ func New(pkgPath string, files map[string]string) (*Package, error) { ast.FileExports(f) - if strings.HasSuffix(filename, "_test.gno") { + if strings.HasSuffix(filename, "_test.gno") || strings.HasSuffix(filename, "_filetest.gno") { continue } From fbc3c7be667ad8ffa09718387a801a872cc3d187 Mon Sep 17 00:00:00 2001 From: Yassin Date: Sat, 25 Feb 2023 18:29:49 +0100 Subject: [PATCH 09/19] Refactor code, fix bug, and add test --- gnoland/website/pkgs/doc/ast_to_string.go | 170 ++++++++++++ .../website/pkgs/doc/ast_to_string_test.go | 243 +++++++++++++++++ gnoland/website/pkgs/doc/doc.go | 70 +++-- gnoland/website/pkgs/doc/doc_test.go | 125 +++++++++ gnoland/website/pkgs/doc/types.go | 254 ++++++++++++------ gnoland/website/pkgs/doc/utils.go | 203 -------------- 6 files changed, 745 insertions(+), 320 deletions(-) create mode 100644 gnoland/website/pkgs/doc/ast_to_string.go create mode 100644 gnoland/website/pkgs/doc/ast_to_string_test.go create mode 100644 gnoland/website/pkgs/doc/doc_test.go delete mode 100644 gnoland/website/pkgs/doc/utils.go diff --git a/gnoland/website/pkgs/doc/ast_to_string.go b/gnoland/website/pkgs/doc/ast_to_string.go new file mode 100644 index 00000000000..2689590ac0d --- /dev/null +++ b/gnoland/website/pkgs/doc/ast_to_string.go @@ -0,0 +1,170 @@ +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, ", "))) + } + } + + b.WriteString(fmt.Sprintf("%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)) + } + } + } + + b.WriteString(fmt.Sprintf("%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, ", ") + + hasMultipleResultsOrNamedParams := hasNamedParams || len(results) > 1 + if hasMultipleResultsOrNamedParams { + 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: + var chanDir string + if t.Dir == ast.SEND { + chanDir = "chan<-" + } else if t.Dir == ast.RECV { + chanDir = "<-chan" + } else { + 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 "" +} diff --git a/gnoland/website/pkgs/doc/ast_to_string_test.go b/gnoland/website/pkgs/doc/ast_to_string_test.go new file mode 100644 index 00000000000..78488710619 --- /dev/null +++ b/gnoland/website/pkgs/doc/ast_to_string_test.go @@ -0,0 +1,243 @@ +package doc + +import ( + "go/ast" + "testing" +) + +func TestGenerateFuncSignature(t *testing.T) { + t.Parallel() + + testcases := []struct { + name string + fn *ast.FuncDecl + want string + }{ + { + name: "NoParametersNoResults", + fn: &ast.FuncDecl{Name: ast.NewIdent("testFunc"), Type: &ast.FuncType{}}, + want: "func testFunc()", + }, + { + name: "ParametersNoResults", + fn: &ast.FuncDecl{ + Name: ast.NewIdent("testFunc"), + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{ast.NewIdent("param1")}, + Type: ast.NewIdent("string"), + }, + { + Names: []*ast.Ident{ast.NewIdent("param2")}, + Type: ast.NewIdent("int"), + }, + }, + }, + }, + }, + want: "func testFunc(param1 string, param2 int)", + }, + { + name: "NoParametersResults", + fn: &ast.FuncDecl{ + Name: ast.NewIdent("testFunc"), + Type: &ast.FuncType{ + Results: &ast.FieldList{ + List: []*ast.Field{ + { + Type: ast.NewIdent("string"), + }, + { + Type: ast.NewIdent("error"), + }, + }, + }, + }, + }, + want: "func testFunc() (string, error)", + }, + { + name: "OneNamedResult", + fn: &ast.FuncDecl{ + Name: &ast.Ident{ + Name: "testFunc", + }, + Type: &ast.FuncType{ + Results: &ast.FieldList{ + List: []*ast.Field{ + { + Type: ast.NewIdent("string"), + Names: []*ast.Ident{ + { + Name: "result", + }, + }, + }, + }, + }, + }, + }, + want: "func testFunc() (result string)", + }, + { + name: "TwoNamedResults", + fn: &ast.FuncDecl{ + Name: &ast.Ident{ + Name: "testFunc", + }, + Type: &ast.FuncType{ + Results: &ast.FieldList{ + List: []*ast.Field{ + { + Type: ast.NewIdent("string"), + Names: []*ast.Ident{ + { + Name: "result1", + }, + }, + }, + { + Type: ast.NewIdent("int"), + Names: []*ast.Ident{ + { + Name: "result2", + }, + }, + }, + }, + }, + }, + }, + want: "func testFunc() (result1 string, result2 int)", + }, + { + name: "FunctionParameter", + fn: &ast.FuncDecl{ + Name: &ast.Ident{Name: "testFunc"}, + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + { + Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + { + Type: &ast.Ident{Name: "MyType"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + want: "func testFunc(func(MyType))", + }, + { + name: "InterfaceResult", + fn: &ast.FuncDecl{ + Name: &ast.Ident{ + Name: "testFunc", + }, + Type: &ast.FuncType{ + Results: &ast.FieldList{ + List: []*ast.Field{ + { + Type: &ast.InterfaceType{}, + }, + }, + }, + }, + }, + want: "func testFunc() interface{}", + }, + } + + for _, c := range testcases { + got := generateFuncSignature(c.fn) + if got != c.want { + t.Errorf("%s : got %q, want %q", c.name, got, c.want) + } + } +} + +func TestTypeString(t *testing.T) { + t.Parallel() + + testcases := []struct { + expr ast.Expr + want string + }{ + {&ast.Ident{Name: "int"}, "int"}, + {&ast.Ident{Name: "string"}, "string"}, + {&ast.StarExpr{X: &ast.Ident{Name: "int"}}, "*int"}, + {&ast.ArrayType{Elt: &ast.Ident{Name: "string"}}, "[]string"}, + {&ast.ArrayType{Len: &ast.BasicLit{Value: "5"}, Elt: &ast.Ident{Name: "int"}}, "[5]int"}, + {&ast.MapType{Key: &ast.Ident{Name: "string"}, Value: &ast.Ident{Name: "int"}}, "map[string]int"}, + {&ast.ChanType{Value: &ast.Ident{Name: "string"}}, "chan string"}, + {&ast.ChanType{Dir: ast.SEND, Value: &ast.Ident{Name: "int"}}, "chan<- int"}, + {&ast.ChanType{Dir: ast.RECV, Value: &ast.Ident{Name: "float64"}}, "<-chan float64"}, + {&ast.StructType{ + Fields: &ast.FieldList{ + List: []*ast.Field{ + {Names: []*ast.Ident{{Name: "a"}}, Type: &ast.Ident{Name: "int"}}, + {Names: []*ast.Ident{{Name: "b"}}, Type: &ast.Ident{Name: "string"}}, + {Type: &ast.Ident{Name: "bool"}}, + }, + }, + }, "struct{a int; b string; bool}"}, + {&ast.FuncType{}, "func()"}, + {&ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + {Names: []*ast.Ident{{Name: "x"}}, Type: &ast.Ident{Name: "int"}}, + {Names: []*ast.Ident{{Name: "y"}}, Type: &ast.Ident{Name: "string"}}, + }, + }, + Results: &ast.FieldList{ + List: []*ast.Field{ + {Type: &ast.Ident{Name: "float64"}}, + }, + }, + }, "func(x int, y string) float64"}, + {&ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + {Names: []*ast.Ident{{Name: "f"}}, Type: &ast.FuncType{ + Params: &ast.FieldList{ + List: []*ast.Field{ + {Type: &ast.Ident{Name: "int"}}, + {Type: &ast.Ident{Name: "float64"}}, + }, + }, + Results: &ast.FieldList{ + List: []*ast.Field{}, + }, + }}, + }, + }, + Results: &ast.FieldList{}, + }, "func(f func(int, float64))"}, + {&ast.FuncType{ + Results: &ast.FieldList{ + List: []*ast.Field{ + { + Type: &ast.InterfaceType{}, + }, + }, + }, + }, "func() interface{}"}, + {&ast.SliceExpr{X: &ast.Ident{Name: "string"}}, "[]string"}, + {&ast.SliceExpr{X: &ast.Ident{Name: "int"}}, "[]int"}, + } + + for _, c := range testcases { + got := typeString(c.expr) + if got != c.want { + t.Errorf("got %q, want %q", got, c.want) + } + } +} diff --git a/gnoland/website/pkgs/doc/doc.go b/gnoland/website/pkgs/doc/doc.go index 9c99c31f9bf..5f7001968d0 100644 --- a/gnoland/website/pkgs/doc/doc.go +++ b/gnoland/website/pkgs/doc/doc.go @@ -33,6 +33,10 @@ func New(pkgPath string, files map[string]string) (*Package, error) { gnoFiles[filename] = f + if p.Name == "" { + p.Name = f.Name.Name + } + if f.Doc != nil { doc := f.Doc.Text() if p.Doc != "" { @@ -42,41 +46,51 @@ func New(pkgPath string, files map[string]string) (*Package, error) { } } - 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) + for _, f := range gnoFiles { + for _, decl := range f.Decls { + switch x := decl.(type) { + case *ast.FuncDecl: + if x.Name.IsExported() { + fn := extractFunc(x) + p.Funcs = append(p.Funcs, fn) + } + case *ast.GenDecl: + if x.Tok == token.TYPE { + for _, spec := range x.Specs { + if ident, ok := spec.(*ast.TypeSpec); ok { + if ident.Name.IsExported() { + newType, _ := extractType(fset, ident) + p.Types = append(p.Types, newType) + } + } } } + 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) + } } } + } - return true + for _, t := range p.Types { + t.Funcs, t.Methods = p.filterTypeFuncs(t.Name) + t.Vars, t.Consts = p.filterTypeValues(t.Name) + } + + sort.Slice(p.Types, func(i, j int) bool { + return p.Types[i].Name < p.Types[j].Name + }) + + sort.Slice(p.Funcs, func(i, j int) bool { + return p.Funcs[i].Name < p.Funcs[j].Name }) - p.populateType() + sort.Strings(p.Filenames) return &p, nil } diff --git a/gnoland/website/pkgs/doc/doc_test.go b/gnoland/website/pkgs/doc/doc_test.go new file mode 100644 index 00000000000..6467aa4f387 --- /dev/null +++ b/gnoland/website/pkgs/doc/doc_test.go @@ -0,0 +1,125 @@ +package doc + +import ( + "testing" +) + +func TestNew(t *testing.T) { + t.Parallel() + + files := map[string]string{ + "example.gno": ` +// Package example is an example package. +package example + +// A private variable. +var private string = "I'm private" + +// A public variable. +var Public string = "I'm public" + +// A public grouped variable. +var ( + Grouped1 string = "I'm Grouped1" + Grouped2 string = "I'm Grouped2" +) + +// A private constant. +const privateConst string = "I'm a private constant" + +// A public constant. +const PublicConst string = "I'm a public constant" + +// A private grouped constant. +const ( + groupedConst1 string = "I'm grouped const 1" + groupedConst2 string = "I'm grouped const 2" +) + +// A public type. +type MyType struct { + Name string // Name is a public field + age int // age is a private field +} + +// A method with a pointer. +func (mt *MyType) PointerMethod() {} + +// A method without a pointer. +func (mt MyType) NonPointerMethod() {} + +// A function that returns MyType. +func NewMyType(name string) *MyType { + return &MyType{Name: name} +} + +// A function that takes a MyType as a parameter. +func UseMyType(mt *MyType) {} + +// A private type. +type myPrivateType struct {} + +// A public interface. +type MyInterface interface { + MyMethod() string +} + +// A function that takes various types as parameters. +func ComplexFunction(s string, i int, f float64, b bool, a []string, fn func(), mt *MyType, iface MyInterface, mt2 MyType, fn2 func(string, int) (int, string)) {} + +// A function that returns multiple values. +func MultipleReturnValues() (string, int) { + return "gno", 42 +} + +// A function with named parameters and named return values. +func NamedParameters(firstParam int, secondParam string) (firstReturn string, secondReturn int) { + return "gno", 42 +} + +// A function with unnamed parameters and unnamed return values. +func UnnamedParameters(int, string) (string, int) { + return "gno", 42 +} +`, + } + pkgPath := "gno.land/p/demo/example" + pkg, err := New(pkgPath, files) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if pkg.ImportPath != pkgPath { + t.Errorf("package import path: got %q, want %q", pkg.ImportPath, pkgPath) + } + + pkgName := "example" + if pkg.Name != pkgName { + t.Errorf("package name: got %q, want %q", pkg.Name, pkgName) + } + + pkgDoc := "Package example is an example package.\n" + if pkg.Doc != pkgDoc { + t.Errorf("package doc: got %q, want %q", pkg.Doc, pkgDoc) + } + + if len(pkg.Filenames) != 1 { + t.Errorf("package filenames: got %d, want 1 file", len(pkg.Filenames)) + } + + if len(pkg.Consts) != 1 { + t.Errorf("package consts: got %d, want 1 const", len(pkg.Consts)) + } + + if len(pkg.Vars) != 2 { + t.Errorf("package vars: got %d, want 2 vars", len(pkg.Vars)) + } + + if len(pkg.Funcs) != 5 { + t.Errorf("package funcs: got %d, want 5 functions", len(pkg.Funcs)) + } + + if len(pkg.Types) != 2 { + t.Errorf("package types: got %d, want 2 types", len(pkg.Types)) + } +} diff --git a/gnoland/website/pkgs/doc/types.go b/gnoland/website/pkgs/doc/types.go index b0dc3e9c728..67827df7621 100644 --- a/gnoland/website/pkgs/doc/types.go +++ b/gnoland/website/pkgs/doc/types.go @@ -1,7 +1,11 @@ package doc import ( - "strings" + "bytes" + "go/ast" + "go/format" + "go/token" + "sort" ) type Package struct { @@ -14,115 +18,88 @@ type Package struct { Vars []*Value Consts []*Value Types []*Type - Examples []*Example } -func (p *Package) populateType() { - p.populateTypeWithMethods() - p.populateTypeWithFuncs() - p.populateTypeWithValue() -} - -func (p *Package) populateTypeWithMethods() { - for _, t := range p.Types { - matchedFuncs := make([]*Func, 0) - remainingFuncs := make([]*Func, 0) - 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 - remainingFuncs []*Func - ) - - for _, fn := range p.Funcs { - foundMatch := false +func (p *Package) filterTypeFuncs(typeName string) (funcs []*Func, methods []*Func) { + var remainingFuncs []*Func + for _, fn := range p.Funcs { + if fn.Recv == nil { + matched := false for _, r := range fn.Returns { - if r.Type == t.Name || strings.HasPrefix(r.Type, "*") && r.Type[1:] == t.Name { - matchedFuncs = append(matchedFuncs, fn) - foundMatch = true + if r.Type == typeName || r.Type == "*"+typeName { + funcs = append(funcs, fn) + matched = true break } } - if !foundMatch { + if !matched { + remainingFuncs = append(remainingFuncs, fn) + } + continue + } + for _, n := range fn.Recv { + if n == typeName || n == "*"+typeName { + methods = append(methods, fn) + break + } else { remainingFuncs = append(remainingFuncs, fn) } } - - t.Funcs = matchedFuncs - p.Funcs = remainingFuncs } + + sort.Slice(funcs, func(i, j int) bool { + return funcs[i].Name < funcs[j].Name + }) + + sort.Slice(methods, func(i, j int) bool { + return methods[i].Name < methods[j].Name + }) + + p.Funcs = remainingFuncs + + return } -func (p *Package) populateTypeWithValue() { - for _, t := range p.Types { - var ( - matchedVars []*Value - matchedConsts []*Value - remainingVars []*Value - remainingConsts []*Value - ) - - 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) +func (p *Package) filterTypeValues(typeName string) (vars []*Value, consts []*Value) { + var ( + remainingVars []*Value + remainingConsts []*Value + ) + + for _, v := range p.Vars { + var matched bool + for _, item := range v.Items { + if item.Type == typeName || item.Type == "*"+typeName { + vars = append(vars, v) + matched = true + break } } + if !matched { + remainingVars = append(remainingVars, v) + } + } - 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) + for _, c := range p.Consts { + var matched bool + for _, item := range c.Items { + if item.Type == typeName { + consts = append(consts, c) + matched = true + break } } - - t.Vars = matchedVars - p.Vars = remainingVars - t.Consts = matchedConsts - p.Consts = remainingConsts + if !matched { + remainingConsts = append(remainingConsts, c) + } } -} -type Example struct { - Name string - Doc string - Code string - Output []string - Play bool + p.Vars = remainingVars + p.Consts = remainingConsts + + return } type Value struct { @@ -167,3 +144,102 @@ type Type struct { Funcs []*Func Methods []*Func } + +func extractFunc(x *ast.FuncDecl) *Func { + fn := Func{ + Doc: x.Doc.Text(), + Name: x.Name.String(), + Signature: generateFuncSignature(x), + } + if x.Recv != nil { + for _, rcv := range x.Recv.List { + fn.Recv = append(fn.Recv, typeString(rcv.Type)) + } + } + + for _, field := range x.Type.Params.List { + paramNames := []string{} + for _, name := range field.Names { + paramNames = append(paramNames, name.Name) + } + paramType := typeString(field.Type) + param := &FuncParam{ + Type: paramType, + Names: paramNames, + } + fn.Params = append(fn.Params, param) + } + + if x.Type.Results != nil { + for _, field := range x.Type.Results.List { + returnNames := []string{} + for _, name := range field.Names { + if name != nil { + continue + } + returnNames = append(returnNames, name.Name) + } + returnType := typeString(field.Type) + ret := &FuncReturn{ + Type: returnType, + Names: returnNames, + } + fn.Returns = append(fn.Returns, ret) + } + } + + return &fn +} + +func extractValue(fset *token.FileSet, x *ast.GenDecl) (*Value, error) { + value := Value{ + Doc: x.Doc.Text(), + } + x.Doc = nil + var buf bytes.Buffer + if err := format.Node(&buf, fset, x); err != nil { + return nil, err + } + value.Signature = buf.String() + for _, spec := range x.Specs { + if valueSpec, ok := spec.(*ast.ValueSpec); ok { + for i, name := range valueSpec.Names { + if !name.IsExported() { + continue + } + valueItem := ValueItem{ + Type: typeString(valueSpec.Type), + Name: name.String(), + } + if len(valueSpec.Values) > i { + if lit, ok := valueSpec.Values[i].(*ast.BasicLit); ok { + valueItem.Value = lit.Value + } else if ident, ok := valueSpec.Values[i].(*ast.Ident); ok { + valueItem.Value = ident.Name + } + } + if valueSpec.Doc != nil { + valueItem.Doc = valueSpec.Doc.Text() + } + value.Items = append(value.Items, &valueItem) + } + } + } + return &value, nil +} + +func extractType(fset *token.FileSet, x *ast.TypeSpec) (*Type, error) { + newType := Type{} + newType.Name = x.Name.String() + newType.Doc = x.Doc.Text() + + x.Doc = nil + var buf bytes.Buffer + buf.WriteString("type ") + if err := format.Node(&buf, fset, x); err != nil { + return nil, err + } + + newType.Definition = buf.String() + return &newType, nil +} diff --git a/gnoland/website/pkgs/doc/utils.go b/gnoland/website/pkgs/doc/utils.go deleted file mode 100644 index 41a2dde4a19..00000000000 --- a/gnoland/website/pkgs/doc/utils.go +++ /dev/null @@ -1,203 +0,0 @@ -package doc - -import ( - "bytes" - "fmt" - "go/ast" - "go/format" - "go/token" - "go/types" - "strings" -) - -func generateFuncSignature(fn *ast.FuncDecl) string { - if fn == nil { - return "" - } - var receiver string - 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 - } - receiverType := typeString(field.Type) - receiverNames = append(receiverNames, fmt.Sprintf("%s %s", fieldName, receiverType)) - } - if len(receiverNames) > 0 { - receiver = fmt.Sprintf("(%s) ", strings.Join(receiverNames, ", ")) - } - } - params := []string{} - if fn.Type.Params != nil { - for _, param := range fn.Type.Params.List { - var paramType string - if len(param.Names) == 0 { - // unnamed parameter - paramType = typeString(param.Type) - } else { - // named parameter(s) - var paramNames []string - for _, id := range param.Names { - paramNames = append(paramNames, id.Name) - } - paramType = fmt.Sprintf("%s %s", strings.Join(paramNames, ", "), typeString(param.Type)) - } - params = append(params, paramType) - } - } - results := []string{} - if fn.Type.Results != nil { - for _, result := range fn.Type.Results.List { - var resultType string - if len(result.Names) == 0 { - // unnamed result parameter - resultType = typeString(result.Type) - } else { - // named result parameter(s) - var resultNames []string - for _, id := range result.Names { - resultNames = append(resultNames, id.Name) - } - resultType = fmt.Sprintf("%s %s", strings.Join(resultNames, ", "), typeString(result.Type)) - } - results = append(results, resultType) - } - } - var returnType string - if len(results) == 1 { - returnType = results[0] - } else { - returnType = fmt.Sprintf("(%s)", strings.Join(results, ", ")) - } - - return fmt.Sprintf("func %s%s(%s) %s", receiver, fn.Name.Name, strings.Join(params, ", "), returnType) -} - -func extractFunc(x *ast.FuncDecl) *Func { - fn := Func{ - Doc: x.Doc.Text(), - Name: x.Name.String(), - Signature: generateFuncSignature(x), - } - if x.Recv != nil { - for _, rcv := range x.Recv.List { - if ident, ok := rcv.Type.(*ast.Ident); ok { - fn.Recv = append(fn.Recv, ident.Name) - } - if star, ok := rcv.Type.(*ast.StarExpr); ok { - fn.Recv = append(fn.Recv, types.ExprString(star.X)) - } - } - } - - for _, field := range x.Type.Params.List { - paramNames := []string{} - for _, name := range field.Names { - paramNames = append(paramNames, name.Name) - } - paramType := typeString(field.Type) - param := &FuncParam{ - Type: paramType, - Names: paramNames, - } - fn.Params = append(fn.Params, param) - } - - if x.Type.Results != nil { - for _, field := range x.Type.Results.List { - returnNames := []string{} - for _, name := range field.Names { - if name != nil { - continue - } - returnNames = append(returnNames, name.Name) - } - returnType := typeString(field.Type) - ret := &FuncReturn{ - Type: returnType, - Names: returnNames, - } - fn.Returns = append(fn.Returns, ret) - } - } - - return &fn -} - -func extractValue(fset *token.FileSet, x *ast.GenDecl) (*Value, error) { - value := Value{ - Doc: x.Doc.Text(), - } - x.Doc = nil - var buf bytes.Buffer - if err := format.Node(&buf, fset, x); err != nil { - return nil, err - } - value.Signature = buf.String() - for _, spec := range x.Specs { - if valueSpec, ok := spec.(*ast.ValueSpec); ok { - for i, name := range valueSpec.Names { - valueItem := ValueItem{ - Type: typeString(valueSpec.Type), - Name: name.String(), - } - if len(valueSpec.Values) > i { - if lit, ok := valueSpec.Values[i].(*ast.BasicLit); ok { - valueItem.Value = lit.Value - } else if ident, ok := valueSpec.Values[i].(*ast.Ident); ok { - valueItem.Value = ident.Name - } - } - if valueSpec.Doc != nil { - valueItem.Doc = valueSpec.Doc.Text() - } - value.Items = append(value.Items, &valueItem) - } - } - } - return &value, nil -} - -func extractType(fset *token.FileSet, x *ast.TypeSpec) (*Type, error) { - newType := Type{} - newType.Name = x.Name.String() - newType.Doc = x.Doc.Text() - - x.Doc = nil - var buf bytes.Buffer - if err := format.Node(&buf, fset, x); err != nil { - return nil, err - } - - newType.Definition = buf.String() - return &newType, nil -} - -// 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.Ellipsis: - return fmt.Sprintf("...%s", typeString(t.Elt)) - } - return "" -} From 77211c69b080f29a2e61acc18433e0b9f9d0e959 Mon Sep 17 00:00:00 2001 From: Yassin Date: Sat, 25 Feb 2023 18:41:28 +0100 Subject: [PATCH 10/19] Refactors method title and template indentation --- gnoland/website/pkgs/doc/types.go | 15 +++++++++++++++ gnoland/website/static/css/app.css | 2 +- gnoland/website/views/package_dir.html | 6 +++--- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/gnoland/website/pkgs/doc/types.go b/gnoland/website/pkgs/doc/types.go index 67827df7621..63d69671c5d 100644 --- a/gnoland/website/pkgs/doc/types.go +++ b/gnoland/website/pkgs/doc/types.go @@ -2,10 +2,12 @@ package doc import ( "bytes" + "fmt" "go/ast" "go/format" "go/token" "sort" + "strings" ) type Package struct { @@ -135,6 +137,19 @@ type Func struct { Signature string } +func (f Func) String() string { + var b strings.Builder + b.WriteString("func ") + + if len(f.Recv) > 0 { + b.WriteString(fmt.Sprintf("(%s) ", strings.Join(f.Recv, ", "))) + } + + b.WriteString(f.Name) + + return b.String() +} + type Type struct { Doc string Name string diff --git a/gnoland/website/static/css/app.css b/gnoland/website/static/css/app.css index 83310299692..1891501f359 100644 --- a/gnoland/website/static/css/app.css +++ b/gnoland/website/static/css/app.css @@ -247,5 +247,5 @@ pre { .pkg_doc__type > .pkg_doc__function, .pkg_doc__type > .pkg_doc__constant, .pkg_doc__type > .pkg_doc__variable { - padding: 5px 0 5px 20px; + padding: 5px 0 5px; } diff --git a/gnoland/website/views/package_dir.html b/gnoland/website/views/package_dir.html index 994d4b673fd..532be050d77 100644 --- a/gnoland/website/views/package_dir.html +++ b/gnoland/website/views/package_dir.html @@ -66,7 +66,7 @@

Functions

{{ range .Data.Package.Funcs }}
-

func {{ .Name }}

+

{{ . }}

{{ .Signature }}
{{ if .Doc }}
@@ -104,7 +104,7 @@

Types

{{ end }} {{ range .Funcs }}
-

func {{ .Name }}

+

{{ . }}

{{ .Signature }}
{{ if .Doc }}
{{ .Doc }}
@@ -112,7 +112,7 @@

Types

{{ end }} {{ range .Methods }}
-

func {{ .Name }}

+

{{ . }}

{{ .Signature }}
{{ if .Doc }}
{{ .Doc }}
From 122918885ba1198806b7ade1474fd480149b572d Mon Sep 17 00:00:00 2001 From: Yassin Date: Sun, 26 Feb 2023 09:30:32 +0100 Subject: [PATCH 11/19] support URL fragments --- gnoland/website/pkgs/doc/types.go | 21 +++++++-- gnoland/website/views/package_dir.html | 59 +++++++++++++++++++------- 2 files changed, 61 insertions(+), 19 deletions(-) diff --git a/gnoland/website/pkgs/doc/types.go b/gnoland/website/pkgs/doc/types.go index 63d69671c5d..6f5058c52c8 100644 --- a/gnoland/website/pkgs/doc/types.go +++ b/gnoland/website/pkgs/doc/types.go @@ -105,6 +105,7 @@ func (p *Package) filterTypeValues(typeName string) (vars []*Value, consts []*Va } type Value struct { + ID string Doc string Names []string Items []*ValueItem @@ -112,6 +113,7 @@ type Value struct { } type ValueItem struct { + ID string Doc string Type string Name string @@ -129,6 +131,7 @@ type FuncReturn struct { } type Func struct { + ID string Doc string Name string Params []*FuncParam @@ -151,6 +154,7 @@ func (f Func) String() string { } type Type struct { + ID string Doc string Name string Definition string @@ -162,6 +166,7 @@ type Type struct { func extractFunc(x *ast.FuncDecl) *Func { fn := Func{ + ID: x.Name.String(), Doc: x.Doc.Text(), Name: x.Name.String(), Signature: generateFuncSignature(x), @@ -172,6 +177,10 @@ func extractFunc(x *ast.FuncDecl) *Func { } } + if len(fn.Recv) > 0 { + fn.ID = fmt.Sprintf("%s.%s", fn.Recv[0], fn.Name) + } + for _, field := range x.Type.Params.List { paramNames := []string{} for _, name := range field.Names { @@ -222,10 +231,14 @@ func extractValue(fset *token.FileSet, x *ast.GenDecl) (*Value, error) { if !name.IsExported() { continue } + value.ID += name.String() + value.Names = append(value.Names, name.Name) valueItem := ValueItem{ + ID: name.String(), Type: typeString(valueSpec.Type), Name: name.String(), } + if len(valueSpec.Values) > i { if lit, ok := valueSpec.Values[i].(*ast.BasicLit); ok { valueItem.Value = lit.Value @@ -244,9 +257,11 @@ func extractValue(fset *token.FileSet, x *ast.GenDecl) (*Value, error) { } func extractType(fset *token.FileSet, x *ast.TypeSpec) (*Type, error) { - newType := Type{} - newType.Name = x.Name.String() - newType.Doc = x.Doc.Text() + newType := Type{ + ID: x.Name.String(), + Name: x.Name.String(), + Doc: x.Doc.Text(), + } x.Doc = nil var buf bytes.Buffer diff --git a/gnoland/website/views/package_dir.html b/gnoland/website/views/package_dir.html index 532be050d77..97f866977cd 100644 --- a/gnoland/website/views/package_dir.html +++ b/gnoland/website/views/package_dir.html @@ -19,9 +19,11 @@

Package {{ .Data.Package.Name }}

-

import "{{ .Data.Package.ImportPath }}"

+

import "{{ .Data.Package.ImportPath }}"

-

Overview

+

+ Overview +

{{ if .Data.Package.Doc }}
{{ .Data.Package.Doc }}
@@ -34,10 +36,14 @@

Overview

-

Constants

+

+ Constants +

{{ range .Data.Package.Consts }}
-
{{ .Signature }}
+ +
{{ .Signature }}
+
{{ if .Doc }}
{{ .Doc }}
{{ end }} @@ -48,10 +54,14 @@

Constants

-

Variables

+

+ Variables +

{{ range .Data.Package.Vars }}
-
{{ .Signature }}
+ +
{{ .Signature }}
+
{{ if .Doc }}
{{ .Doc }}
{{ end }} @@ -62,12 +72,16 @@

Variables

-

Functions

+

+ Functions +

{{ range .Data.Package.Funcs }}

{{ . }}

-
{{ .Signature }}
+ +
{{ .Signature }}
+
{{ if .Doc }}
{{ .Doc  }}
@@ -81,30 +95,40 @@

Functions

-

Types

+

+ Types +

{{ range .Data.Package.Types }}
-

type {{ .Name }}

+ +

type {{ .Name }}

+
{{ .Definition }}
{{ if .Doc }}
{{ .Doc }}
{{ end }} {{ range .Vars }}
-
{{ .Signature }}
+ +
{{ .Signature }}
+
{{ if .Doc }}
{{ .Doc }}
{{ end }}
{{ end }} {{ range .Consts }}
-
{{ .Signature }}
+ +
{{ .Signature }}
+
{{ if .Doc }}
{{ .Doc }}
{{ end }}
{{ end }} {{ range .Funcs }}
-

{{ . }}

+ +

{{ . }}

+
{{ .Signature }}
{{ if .Doc }}
{{ .Doc }}
@@ -112,13 +136,14 @@

Types

{{ end }} {{ range .Methods }}
-

{{ . }}

+ +

{{ . }}

+
{{ .Signature }}
{{ if .Doc }}
{{ .Doc }}
{{ end }}
- {{ end }}
{{ else }} @@ -127,7 +152,9 @@

Types

-

Source Files

+

+ +

{{ template "dir_contents" . }}
From 7839aed34404a56185089566dc4bbf8c8a4441cd Mon Sep 17 00:00:00 2001 From: Yassin Date: Sun, 26 Feb 2023 09:46:47 +0100 Subject: [PATCH 12/19] Align type, var and const documentation with godoc guidelines (doc before) --- gnoland/website/views/package_dir.html | 29 +++++++++++++------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/gnoland/website/views/package_dir.html b/gnoland/website/views/package_dir.html index 97f866977cd..612b6f674cc 100644 --- a/gnoland/website/views/package_dir.html +++ b/gnoland/website/views/package_dir.html @@ -41,12 +41,12 @@

{{ range .Data.Package.Consts }}
- -
{{ .Signature }}
-
{{ if .Doc }}
{{ .Doc }}
{{ end }} + +
{{ .Signature }}
+
{{ else }}

This section is empty.

@@ -59,12 +59,12 @@

{{ range .Data.Package.Vars }}
- -
{{ .Signature }}
-
{{ if .Doc }}
{{ .Doc }}
{{ end }} + +
{{ .Signature }}
+
{{ else }}

This section is empty.

@@ -103,26 +103,27 @@

type {{ .Name }}

-
{{ .Definition }}
{{ if .Doc }}
{{ .Doc }}
- {{ end }} {{ range .Vars }} + {{ end }} +
{{ .Definition }}
+ {{ range .Vars }}
- -
{{ .Signature }}
-
{{ if .Doc }}
{{ .Doc }}
{{ end }} -
- {{ end }} {{ range .Consts }} - + {{ end }} {{ range .Consts }} +
{{ if .Doc }}
{{ .Doc }}
{{ end }} + +
{{ .Signature }}
+
{{ end }} {{ range .Funcs }}
From ee8043977a03aa35f4f6bc5be199114b0e099042 Mon Sep 17 00:00:00 2001 From: Yassin Date: Wed, 1 Mar 2023 10:42:14 +0100 Subject: [PATCH 13/19] Refactor QueryFiles method --- pkgs/sdk/vm/keeper.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkgs/sdk/vm/keeper.go b/pkgs/sdk/vm/keeper.go index b41a0ccf174..072f3a04611 100644 --- a/pkgs/sdk/vm/keeper.go +++ b/pkgs/sdk/vm/keeper.go @@ -465,9 +465,8 @@ func (vm *VMKeeper) QueryFile(ctx sdk.Context, filepath string) (res string, err func (vm *VMKeeper) QueryFiles(ctx sdk.Context, path string) (res std.MemFileBodies, err error) { dirpath, _ := std.SplitFilepath(path) - memPkg := vm.getGnoStore(ctx).GetMemPackage(dirpath) - - if memPkg != nil { + store := vm.getGnoStore(ctx) + if memPkg := store.GetMemPackage(dirpath); memPkg != nil { return memPkg.GetFileBodies(), nil } From d0f3deee855a68164f8b5d50a892d83d77f62ec1 Mon Sep 17 00:00:00 2001 From: Yassin Date: Wed, 1 Mar 2023 23:18:16 +0100 Subject: [PATCH 14/19] Address code review comments --- gnoland/website/pkgs/doc/ast_to_string.go | 11 ++++----- .../website/pkgs/doc/ast_to_string_test.go | 2 -- gnoland/website/pkgs/doc/doc.go | 19 +++++++-------- gnoland/website/pkgs/doc/doc_test.go | 2 -- gnoland/website/pkgs/doc/types.go | 3 +-- gnoland/website/views/funcs.html | 10 ++++++++ gnoland/website/views/package_dir.html | 24 ++++++++----------- 7 files changed, 33 insertions(+), 38 deletions(-) diff --git a/gnoland/website/pkgs/doc/ast_to_string.go b/gnoland/website/pkgs/doc/ast_to_string.go index 2689590ac0d..43c78ede260 100644 --- a/gnoland/website/pkgs/doc/ast_to_string.go +++ b/gnoland/website/pkgs/doc/ast_to_string.go @@ -28,7 +28,7 @@ func generateFuncSignature(fn *ast.FuncDecl) string { } } - b.WriteString(fmt.Sprintf("%s(", fn.Name.Name)) + fmt.Fprintf(&b, "%s(", fn.Name.Name) var params []string if fn.Type.Params != nil { @@ -46,7 +46,7 @@ func generateFuncSignature(fn *ast.FuncDecl) string { } } - b.WriteString(fmt.Sprintf("%s)", strings.Join(params, ", "))) + fmt.Fprintf(&b, "%s)", strings.Join(params, ", ")) results := []string{} if fn.Type.Results != nil { @@ -68,8 +68,7 @@ func generateFuncSignature(fn *ast.FuncDecl) string { b.WriteString(" ") returnType := strings.Join(results, ", ") - hasMultipleResultsOrNamedParams := hasNamedParams || len(results) > 1 - if hasMultipleResultsOrNamedParams { + if hasNamedParams || len(results) > 1 { returnType = fmt.Sprintf("(%s)", returnType) } @@ -152,13 +151,11 @@ func typeString(expr ast.Expr) string { case *ast.MapType: return fmt.Sprintf("map[%s]%s", typeString(t.Key), typeString(t.Value)) case *ast.ChanType: - var chanDir string + chanDir := "chan" if t.Dir == ast.SEND { chanDir = "chan<-" } else if t.Dir == ast.RECV { chanDir = "<-chan" - } else { - chanDir = "chan" } return fmt.Sprintf("%s %s", chanDir, typeString(t.Value)) case *ast.ArrayType: diff --git a/gnoland/website/pkgs/doc/ast_to_string_test.go b/gnoland/website/pkgs/doc/ast_to_string_test.go index 78488710619..bbbb0bcf71a 100644 --- a/gnoland/website/pkgs/doc/ast_to_string_test.go +++ b/gnoland/website/pkgs/doc/ast_to_string_test.go @@ -6,8 +6,6 @@ import ( ) func TestGenerateFuncSignature(t *testing.T) { - t.Parallel() - testcases := []struct { name string fn *ast.FuncDecl diff --git a/gnoland/website/pkgs/doc/doc.go b/gnoland/website/pkgs/doc/doc.go index 5f7001968d0..0bf2850d165 100644 --- a/gnoland/website/pkgs/doc/doc.go +++ b/gnoland/website/pkgs/doc/doc.go @@ -15,7 +15,7 @@ func New(pkgPath string, files map[string]string) (*Package, error) { } fset := token.NewFileSet() - gnoFiles := make(map[string]*ast.File) + gnoFiles := make(map[string]*ast.File, len(files)) for filename, fileContent := range files { p.Filenames = append(p.Filenames, filename) @@ -55,21 +55,18 @@ func New(pkgPath string, files map[string]string) (*Package, error) { p.Funcs = append(p.Funcs, fn) } case *ast.GenDecl: - if x.Tok == token.TYPE { + switch x.Tok { + case token.TYPE: for _, spec := range x.Specs { - if ident, ok := spec.(*ast.TypeSpec); ok { - if ident.Name.IsExported() { - newType, _ := extractType(fset, ident) - p.Types = append(p.Types, newType) - } + if ident, ok := spec.(*ast.TypeSpec); ok && ident.Name.IsExported() { + newType, _ := extractType(fset, ident) + p.Types = append(p.Types, newType) } } - } - if x.Tok == token.VAR { + case token.VAR: value, _ := extractValue(fset, x) p.Vars = append(p.Vars, value) - } - if x.Tok == token.CONST { + case token.CONST: value, _ := extractValue(fset, x) p.Consts = append(p.Consts, value) } diff --git a/gnoland/website/pkgs/doc/doc_test.go b/gnoland/website/pkgs/doc/doc_test.go index 6467aa4f387..b9797a1cc30 100644 --- a/gnoland/website/pkgs/doc/doc_test.go +++ b/gnoland/website/pkgs/doc/doc_test.go @@ -5,8 +5,6 @@ import ( ) func TestNew(t *testing.T) { - t.Parallel() - files := map[string]string{ "example.gno": ` // Package example is an example package. diff --git a/gnoland/website/pkgs/doc/types.go b/gnoland/website/pkgs/doc/types.go index 6f5058c52c8..ff205763160 100644 --- a/gnoland/website/pkgs/doc/types.go +++ b/gnoland/website/pkgs/doc/types.go @@ -142,10 +142,9 @@ type Func struct { func (f Func) String() string { var b strings.Builder - b.WriteString("func ") if len(f.Recv) > 0 { - b.WriteString(fmt.Sprintf("(%s) ", strings.Join(f.Recv, ", "))) + fmt.Fprintf(&b, "(%s) ", strings.Join(f.Recv, ", ")) } b.WriteString(f.Name) diff --git a/gnoland/website/views/funcs.html b/gnoland/website/views/funcs.html index 716470dd49d..7a998b50847 100644 --- a/gnoland/website/views/funcs.html +++ b/gnoland/website/views/funcs.html @@ -36,6 +36,16 @@ {{ end }} +{{ define "hljs" }} + + +{{ end }} + {{ define "subscribe" }}
diff --git a/gnoland/website/views/package_dir.html b/gnoland/website/views/package_dir.html index 612b6f674cc..d6f3574147b 100644 --- a/gnoland/website/views/package_dir.html +++ b/gnoland/website/views/package_dir.html @@ -78,10 +78,12 @@

{{ range .Data.Package.Funcs }}
-

{{ . }}

- -
{{ .Signature }}
-
+

+ + func {{ . }} + +

+
{{ .Signature }}
{{ if .Doc }}
{{ .Doc  }}
@@ -128,7 +130,7 @@

{{ end }} {{ range .Funcs }}
-

{{ . }}

+

func {{ . }}

{{ .Signature }}
{{ if .Doc }} @@ -138,7 +140,7 @@

{{ end }} {{ range .Methods }}
-

{{ . }}

+

func {{ . }}

{{ .Signature }}
{{ if .Doc }} @@ -154,7 +156,7 @@

- + Source Files

{{ template "dir_contents" . }}
@@ -164,13 +166,7 @@

{{ template "footer" }}

{{ template "js" }} - - + {{ template "hljs" }} {{- end -}} {{ define "dir_contents" }} From 44b88de61a121dddbc79609b18eb29daaecc29f9 Mon Sep 17 00:00:00 2001 From: Yassin Date: Fri, 3 Mar 2023 16:50:06 +0100 Subject: [PATCH 15/19] Fix type comment bug and add tests --- gnoland/website/pkgs/doc/ast_to_string.go | 22 +++++ .../website/pkgs/doc/ast_to_string_test.go | 89 ++++++++++++++++++- gnoland/website/pkgs/doc/doc.go | 5 +- gnoland/website/pkgs/doc/doc_test.go | 55 ++++++++++++ gnoland/website/pkgs/doc/types.go | 6 +- 5 files changed, 170 insertions(+), 7 deletions(-) diff --git a/gnoland/website/pkgs/doc/ast_to_string.go b/gnoland/website/pkgs/doc/ast_to_string.go index 43c78ede260..c7c6a1bf7c4 100644 --- a/gnoland/website/pkgs/doc/ast_to_string.go +++ b/gnoland/website/pkgs/doc/ast_to_string.go @@ -165,3 +165,25 @@ func typeString(expr ast.Expr) string { } 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, "*") +} diff --git a/gnoland/website/pkgs/doc/ast_to_string_test.go b/gnoland/website/pkgs/doc/ast_to_string_test.go index bbbb0bcf71a..8d0b812a3ca 100644 --- a/gnoland/website/pkgs/doc/ast_to_string_test.go +++ b/gnoland/website/pkgs/doc/ast_to_string_test.go @@ -157,14 +157,12 @@ func TestGenerateFuncSignature(t *testing.T) { for _, c := range testcases { got := generateFuncSignature(c.fn) if got != c.want { - t.Errorf("%s : got %q, want %q", c.name, got, c.want) + t.Errorf("%s: got %q, want %q", c.name, got, c.want) } } } func TestTypeString(t *testing.T) { - t.Parallel() - testcases := []struct { expr ast.Expr want string @@ -239,3 +237,88 @@ func TestTypeString(t *testing.T) { } } } + +func TestRemovePointer(t *testing.T) { + testcases := []struct { + name string + want string + }{ + {"MyType", "MyType"}, + {"*MyType", "MyType"}, + } + + for _, c := range testcases { + got := removePointer(c.name) + if got != c.want { + t.Errorf("got %s, want %s", got, c.want) + } + } +} + +func TestIsFuncExported(t *testing.T) { + testcases := []struct { + name string + fn *ast.FuncDecl + want bool + }{ + { + name: "exported function without receiver", + fn: &ast.FuncDecl{ + Name: ast.NewIdent("ExportedFunc"), + }, + want: true, + }, + { + name: "unexported function without receiver", + fn: &ast.FuncDecl{ + Name: ast.NewIdent("unexportedFunc"), + }, + want: false, + }, + { + name: "exported method with exported pointer receiver", + fn: &ast.FuncDecl{ + Name: ast.NewIdent("ExportedMethod"), + Recv: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{ast.NewIdent("p")}, + Type: &ast.StarExpr{ + X: &ast.Ident{ + Name: "MyType", + }, + }, + }, + }, + }, + }, + want: true, + }, + { + name: "exported method with unexported pointer receiver", + fn: &ast.FuncDecl{ + Name: ast.NewIdent("ExportedMethod"), + Recv: &ast.FieldList{ + List: []*ast.Field{ + { + Names: []*ast.Ident{ast.NewIdent("p")}, + Type: &ast.StarExpr{ + X: &ast.Ident{ + Name: "myType", + }, + }, + }, + }, + }, + }, + want: false, + }, + } + + for _, c := range testcases { + got := isFuncExported(c.fn) + if got != c.want { + t.Errorf("%s: got %v, want %v", c.name, got, c.want) + } + } +} diff --git a/gnoland/website/pkgs/doc/doc.go b/gnoland/website/pkgs/doc/doc.go index 0bf2850d165..ed549e5803c 100644 --- a/gnoland/website/pkgs/doc/doc.go +++ b/gnoland/website/pkgs/doc/doc.go @@ -50,7 +50,7 @@ func New(pkgPath string, files map[string]string) (*Package, error) { for _, decl := range f.Decls { switch x := decl.(type) { case *ast.FuncDecl: - if x.Name.IsExported() { + if isFuncExported(x) { fn := extractFunc(x) p.Funcs = append(p.Funcs, fn) } @@ -60,6 +60,9 @@ func New(pkgPath string, files map[string]string) (*Package, error) { for _, spec := range x.Specs { if ident, ok := spec.(*ast.TypeSpec); ok && ident.Name.IsExported() { newType, _ := extractType(fset, ident) + if x.Doc != nil { + newType.Doc = x.Doc.Text() + newType.Doc + } p.Types = append(p.Types, newType) } } diff --git a/gnoland/website/pkgs/doc/doc_test.go b/gnoland/website/pkgs/doc/doc_test.go index b9797a1cc30..cc66781603e 100644 --- a/gnoland/website/pkgs/doc/doc_test.go +++ b/gnoland/website/pkgs/doc/doc_test.go @@ -56,6 +56,9 @@ func UseMyType(mt *MyType) {} // A private type. type myPrivateType struct {} + +// A public method with a private type. +func (mPT *myPrivateType) PublicMethod() {} // A public interface. type MyInterface interface { @@ -119,5 +122,57 @@ func UnnamedParameters(int, string) (string, int) { if len(pkg.Types) != 2 { t.Errorf("package types: got %d, want 2 types", len(pkg.Types)) + } else { + myInterfaceType := pkg.Types[0] + if want := "MyInterface"; myInterfaceType.Name != want { + t.Errorf("package type name: got %q, want %q", myInterfaceType.Name, want) + } + if want := "MyInterface"; myInterfaceType.ID != want { + t.Errorf("package type id: got %q, want %q", myInterfaceType.Name, want) + } + if want := "A public interface.\n"; myInterfaceType.Doc != want { + t.Errorf("package type doc: got %q, want %q", myInterfaceType.Doc, want) + } + if want := 0; len(myInterfaceType.Funcs) != want { + t.Errorf("package type funcs: got %d, want %d", len(myInterfaceType.Funcs), want) + } + if want := 0; len(myInterfaceType.Methods) != want { + t.Errorf("package type methods: got %d, want %d", len(myInterfaceType.Methods), want) + } + if want := 0; len(myInterfaceType.Vars) != want { + t.Errorf("package type vars: got %d, want %d", len(myInterfaceType.Vars), want) + } + if want := 0; len(myInterfaceType.Consts) != want { + t.Errorf("package type vars: got %d, want %d", len(myInterfaceType.Consts), want) + } + if want := "type MyInterface interface {\n\tMyMethod() string\n}"; myInterfaceType.Definition != want { + t.Errorf("package type definition: got %q, want %q", myInterfaceType.Definition, want) + } + + myTypeType := pkg.Types[1] + if want := "MyType"; myTypeType.Name != want { + t.Errorf("package type name: got %q, want %q", myTypeType.Name, want) + } + if want := "MyType"; myTypeType.ID != want { + t.Errorf("package type id: got %q, want %q", myTypeType.Name, want) + } + if want := "A public type.\n"; myTypeType.Doc != want { + t.Errorf("package type doc: got %q, want %q", myTypeType.Doc, want) + } + if want := 2; len(myTypeType.Methods) != want { + t.Errorf("package type methods: got %d, want %d", len(myTypeType.Methods), want) + } + if want := 1; len(myTypeType.Funcs) != want { + t.Errorf("package type funcs: got %d, want %d", len(myTypeType.Funcs), want) + } + if want := 0; len(myTypeType.Vars) != want { + t.Errorf("package type vars: got %d, want %d", len(myTypeType.Vars), want) + } + if want := 0; len(myTypeType.Consts) != want { + t.Errorf("package type consts: got %d, want %d", len(myTypeType.Consts), want) + } + if want := "type MyType struct {\n\tName string // Name is a public field\n\t// contains filtered or unexported fields\n}"; myTypeType.Definition != want { + t.Errorf("package type definition: got %q, want %q", myTypeType.Definition, want) + } } } diff --git a/gnoland/website/pkgs/doc/types.go b/gnoland/website/pkgs/doc/types.go index ff205763160..8b02bec75dc 100644 --- a/gnoland/website/pkgs/doc/types.go +++ b/gnoland/website/pkgs/doc/types.go @@ -29,7 +29,7 @@ func (p *Package) filterTypeFuncs(typeName string) (funcs []*Func, methods []*Fu if fn.Recv == nil { matched := false for _, r := range fn.Returns { - if r.Type == typeName || r.Type == "*"+typeName { + if removePointer(r.Type) == typeName { funcs = append(funcs, fn) matched = true break @@ -42,7 +42,7 @@ func (p *Package) filterTypeFuncs(typeName string) (funcs []*Func, methods []*Fu continue } for _, n := range fn.Recv { - if n == typeName || n == "*"+typeName { + if removePointer(n) == typeName { methods = append(methods, fn) break } else { @@ -73,7 +73,7 @@ func (p *Package) filterTypeValues(typeName string) (vars []*Value, consts []*Va for _, v := range p.Vars { var matched bool for _, item := range v.Items { - if item.Type == typeName || item.Type == "*"+typeName { + if removePointer(item.Type) == typeName { vars = append(vars, v) matched = true break From 120082c0e1254fc7f3f8b85aa2cb0901fdd440a1 Mon Sep 17 00:00:00 2001 From: Yassin Date: Fri, 3 Mar 2023 17:37:03 +0100 Subject: [PATCH 16/19] Use template hjls for package_file --- gnoland/website/views/package_file.html | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/gnoland/website/views/package_file.html b/gnoland/website/views/package_file.html index d30a98ddbac..0f0b92f74aa 100644 --- a/gnoland/website/views/package_file.html +++ b/gnoland/website/views/package_file.html @@ -22,13 +22,7 @@ {{ template "footer" }}

{{ template "js" }} - - + {{ template "hljs" }} {{- end -}} From c372d013d47bfc9bd5ea463b2d98dd70fa7f7a49 Mon Sep 17 00:00:00 2001 From: Yassin Date: Sat, 4 Mar 2023 14:55:25 +0100 Subject: [PATCH 17/19] Use testify --- .../website/pkgs/doc/ast_to_string_test.go | 22 +-- gnoland/website/pkgs/doc/doc_test.go | 132 +++++++----------- 2 files changed, 57 insertions(+), 97 deletions(-) diff --git a/gnoland/website/pkgs/doc/ast_to_string_test.go b/gnoland/website/pkgs/doc/ast_to_string_test.go index 8d0b812a3ca..fb5f1456ef3 100644 --- a/gnoland/website/pkgs/doc/ast_to_string_test.go +++ b/gnoland/website/pkgs/doc/ast_to_string_test.go @@ -3,6 +3,8 @@ package doc import ( "go/ast" "testing" + + "github.com/stretchr/testify/assert" ) func TestGenerateFuncSignature(t *testing.T) { @@ -155,10 +157,7 @@ func TestGenerateFuncSignature(t *testing.T) { } for _, c := range testcases { - got := generateFuncSignature(c.fn) - if got != c.want { - t.Errorf("%s: got %q, want %q", c.name, got, c.want) - } + assert.Equal(t, c.want, generateFuncSignature(c.fn), c.name) } } @@ -231,10 +230,7 @@ func TestTypeString(t *testing.T) { } for _, c := range testcases { - got := typeString(c.expr) - if got != c.want { - t.Errorf("got %q, want %q", got, c.want) - } + assert.Equal(t, c.want, typeString(c.expr)) } } @@ -248,10 +244,7 @@ func TestRemovePointer(t *testing.T) { } for _, c := range testcases { - got := removePointer(c.name) - if got != c.want { - t.Errorf("got %s, want %s", got, c.want) - } + assert.Equal(t, c.want, removePointer(c.name)) } } @@ -316,9 +309,6 @@ func TestIsFuncExported(t *testing.T) { } for _, c := range testcases { - got := isFuncExported(c.fn) - if got != c.want { - t.Errorf("%s: got %v, want %v", c.name, got, c.want) - } + assert.Equal(t, c.want, isFuncExported(c.fn), c.name) } } diff --git a/gnoland/website/pkgs/doc/doc_test.go b/gnoland/website/pkgs/doc/doc_test.go index cc66781603e..21fb5312635 100644 --- a/gnoland/website/pkgs/doc/doc_test.go +++ b/gnoland/website/pkgs/doc/doc_test.go @@ -2,9 +2,14 @@ package doc import ( "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNew(t *testing.T) { + assert, require := assert.New(t), require.New(t) + files := map[string]string{ "example.gno": ` // Package example is an example package. @@ -86,93 +91,58 @@ func UnnamedParameters(int, string) (string, int) { } pkgPath := "gno.land/p/demo/example" pkg, err := New(pkgPath, files) - if err != nil { - t.Errorf("unexpected error: %v", err) - } + require.NoError(err) + require.NotNil(pkg) - if pkg.ImportPath != pkgPath { - t.Errorf("package import path: got %q, want %q", pkg.ImportPath, pkgPath) - } - - pkgName := "example" - if pkg.Name != pkgName { - t.Errorf("package name: got %q, want %q", pkg.Name, pkgName) - } + assert.Equal(pkgPath, pkg.ImportPath) + assert.Equal("example", pkg.Name) + assert.Equal("Package example is an example package.\n", pkg.Doc) - pkgDoc := "Package example is an example package.\n" - if pkg.Doc != pkgDoc { - t.Errorf("package doc: got %q, want %q", pkg.Doc, pkgDoc) - } + assert.Len(pkg.Filenames, 1) + assert.Len(pkg.Consts, 1) + assert.Len(pkg.Vars, 2) + assert.Len(pkg.Funcs, 5) + require.Len(pkg.Types, 2) - if len(pkg.Filenames) != 1 { - t.Errorf("package filenames: got %d, want 1 file", len(pkg.Filenames)) - } + myInterfaceType := pkg.Types[0] + assert.Equal("MyInterface", myInterfaceType.Name) + assert.Equal("MyInterface", myInterfaceType.ID) + assert.Equal("A public interface.\n", myInterfaceType.Doc) + assert.Len(myInterfaceType.Vars, 0) + assert.Len(myInterfaceType.Consts, 0) + assert.Len(myInterfaceType.Funcs, 0) + assert.Len(myInterfaceType.Methods, 0) + assert.Equal("type MyInterface interface {\n\tMyMethod() string\n}", myInterfaceType.Definition) - if len(pkg.Consts) != 1 { - t.Errorf("package consts: got %d, want 1 const", len(pkg.Consts)) - } + myTypeType := pkg.Types[1] + assert.Equal("MyType", myTypeType.Name) + assert.Equal("MyType", myTypeType.ID) + assert.Equal("A public type.\n", myTypeType.Doc) + assert.Len(myTypeType.Vars, 0) + assert.Len(myTypeType.Consts, 0) - if len(pkg.Vars) != 2 { - t.Errorf("package vars: got %d, want 2 vars", len(pkg.Vars)) - } + require.Len(myTypeType.Funcs, 1) + assert.Equal("NewMyType", myTypeType.Funcs[0].Name) + assert.Equal("NewMyType", myTypeType.Funcs[0].ID) + assert.Equal("A function that returns MyType.\n", myTypeType.Funcs[0].Doc) + assert.Len(myTypeType.Funcs[0].Params, 1) + assert.Len(myTypeType.Funcs[0].Returns, 1) + assert.Len(myTypeType.Funcs[0].Recv, 0) - if len(pkg.Funcs) != 5 { - t.Errorf("package funcs: got %d, want 5 functions", len(pkg.Funcs)) - } + require.Len(myTypeType.Methods, 2) + assert.Equal("NonPointerMethod", myTypeType.Methods[0].Name) + assert.Equal("MyType.NonPointerMethod", myTypeType.Methods[0].ID) + assert.Equal("A method without a pointer.\n", myTypeType.Methods[0].Doc) + assert.Len(myTypeType.Methods[0].Params, 0) + assert.Len(myTypeType.Methods[0].Returns, 0) + assert.Len(myTypeType.Methods[0].Recv, 1) - if len(pkg.Types) != 2 { - t.Errorf("package types: got %d, want 2 types", len(pkg.Types)) - } else { - myInterfaceType := pkg.Types[0] - if want := "MyInterface"; myInterfaceType.Name != want { - t.Errorf("package type name: got %q, want %q", myInterfaceType.Name, want) - } - if want := "MyInterface"; myInterfaceType.ID != want { - t.Errorf("package type id: got %q, want %q", myInterfaceType.Name, want) - } - if want := "A public interface.\n"; myInterfaceType.Doc != want { - t.Errorf("package type doc: got %q, want %q", myInterfaceType.Doc, want) - } - if want := 0; len(myInterfaceType.Funcs) != want { - t.Errorf("package type funcs: got %d, want %d", len(myInterfaceType.Funcs), want) - } - if want := 0; len(myInterfaceType.Methods) != want { - t.Errorf("package type methods: got %d, want %d", len(myInterfaceType.Methods), want) - } - if want := 0; len(myInterfaceType.Vars) != want { - t.Errorf("package type vars: got %d, want %d", len(myInterfaceType.Vars), want) - } - if want := 0; len(myInterfaceType.Consts) != want { - t.Errorf("package type vars: got %d, want %d", len(myInterfaceType.Consts), want) - } - if want := "type MyInterface interface {\n\tMyMethod() string\n}"; myInterfaceType.Definition != want { - t.Errorf("package type definition: got %q, want %q", myInterfaceType.Definition, want) - } + assert.Equal("PointerMethod", myTypeType.Methods[1].Name) + assert.Equal("*MyType.PointerMethod", myTypeType.Methods[1].ID) + assert.Equal("A method with a pointer.\n", myTypeType.Methods[1].Doc) + assert.Len(myTypeType.Methods[1].Params, 0) + assert.Len(myTypeType.Methods[1].Returns, 0) + assert.Len(myTypeType.Methods[1].Recv, 1) - myTypeType := pkg.Types[1] - if want := "MyType"; myTypeType.Name != want { - t.Errorf("package type name: got %q, want %q", myTypeType.Name, want) - } - if want := "MyType"; myTypeType.ID != want { - t.Errorf("package type id: got %q, want %q", myTypeType.Name, want) - } - if want := "A public type.\n"; myTypeType.Doc != want { - t.Errorf("package type doc: got %q, want %q", myTypeType.Doc, want) - } - if want := 2; len(myTypeType.Methods) != want { - t.Errorf("package type methods: got %d, want %d", len(myTypeType.Methods), want) - } - if want := 1; len(myTypeType.Funcs) != want { - t.Errorf("package type funcs: got %d, want %d", len(myTypeType.Funcs), want) - } - if want := 0; len(myTypeType.Vars) != want { - t.Errorf("package type vars: got %d, want %d", len(myTypeType.Vars), want) - } - if want := 0; len(myTypeType.Consts) != want { - t.Errorf("package type consts: got %d, want %d", len(myTypeType.Consts), want) - } - if want := "type MyType struct {\n\tName string // Name is a public field\n\t// contains filtered or unexported fields\n}"; myTypeType.Definition != want { - t.Errorf("package type definition: got %q, want %q", myTypeType.Definition, want) - } - } + assert.Equal("type MyType struct {\n\tName string // Name is a public field\n\t// contains filtered or unexported fields\n}", myTypeType.Definition) } From 7daa079f6ade5c06839ef279518053d359b1ecd4 Mon Sep 17 00:00:00 2001 From: Yassin Date: Sun, 5 Mar 2023 23:39:43 +0100 Subject: [PATCH 18/19] use markdown format instead of html --- gnoland/website/main.go | 25 +++- gnoland/website/pkgs/doc/doc.go | 9 +- gnoland/website/pkgs/doc/template.go | 115 +++++++++++++++++ gnoland/website/pkgs/doc/types.go | 15 +++ gnoland/website/static/css/app.css | 55 ++------ gnoland/website/views/funcs.html | 10 +- gnoland/website/views/package_dir.html | 163 +----------------------- gnoland/website/views/package_file.html | 4 +- gnoland/website/views/realm_dir.html | 40 ++++++ 9 files changed, 222 insertions(+), 214 deletions(-) create mode 100644 gnoland/website/pkgs/doc/template.go create mode 100644 gnoland/website/views/realm_dir.html diff --git a/gnoland/website/main.go b/gnoland/website/main.go index d54550c5c9c..30de09bf708 100644 --- a/gnoland/website/main.go +++ b/gnoland/website/main.go @@ -312,7 +312,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") }) } @@ -348,10 +362,15 @@ func renderPackage(app gotuna.App, w http.ResponseWriter, r *http.Request, dirur return } + pkgContent, err := pkgdoc.Markdown() + if err != nil { + writeError(w, err) + return + } + tmpl := app.NewTemplatingEngine() - tmpl.Set("DirURI", diruri) tmpl.Set("DirPath", pathOf(diruri)) - tmpl.Set("Package", pkgdoc) + tmpl.Set("PkgContent", string(pkgContent)) tmpl.Render(w, r, "package_dir.html", "funcs.html") } diff --git a/gnoland/website/pkgs/doc/doc.go b/gnoland/website/pkgs/doc/doc.go index ed549e5803c..bb0d8ace807 100644 --- a/gnoland/website/pkgs/doc/doc.go +++ b/gnoland/website/pkgs/doc/doc.go @@ -11,6 +11,7 @@ import ( func New(pkgPath string, files map[string]string) (*Package, error) { p := Package{ ImportPath: pkgPath, + Path: strings.TrimPrefix(pkgPath, "gno.land"), Filenames: make([]string, 0, len(files)), } @@ -20,6 +21,10 @@ func New(pkgPath string, files map[string]string) (*Package, error) { for filename, fileContent := range files { p.Filenames = append(p.Filenames, filename) + if !strings.HasSuffix(filename, ".gno") || strings.HasSuffix(filename, "_test.gno") || strings.HasSuffix(filename, "_filetest.gno") { + continue + } + f, err := parser.ParseFile(fset, filename, fileContent, parser.ParseComments) if err != nil { return nil, err @@ -27,10 +32,6 @@ func New(pkgPath string, files map[string]string) (*Package, error) { ast.FileExports(f) - if strings.HasSuffix(filename, "_test.gno") || strings.HasSuffix(filename, "_filetest.gno") { - continue - } - gnoFiles[filename] = f if p.Name == "" { diff --git a/gnoland/website/pkgs/doc/template.go b/gnoland/website/pkgs/doc/template.go new file mode 100644 index 00000000000..40cff3f7797 --- /dev/null +++ b/gnoland/website/pkgs/doc/template.go @@ -0,0 +1,115 @@ +package doc + +import ( + "fmt" + "go/doc/comment" +) + +func commentFmt(c string) string { + var p comment.Parser + pr := &comment.Printer{ + DocLinkBaseURL: "/p", + } + return string(pr.Markdown(p.Parse(c))) +} + +func codeFmt(code string) string { + return fmt.Sprintf("%s\n%s\n%s", "```go", code, "```") +} + +var TemplateMarkdown = ` +# Package {{ .Name }} + +import "{{ .ImportPath }}" + +## Overview + +{{ if .Doc }} +{{ comment .Doc }} +{{ else }} +This section is empty. +{{ end }} + +## Constants + +{{ range .Consts }} +{{ if .Doc }} +{{ comment .Doc }} +{{ end }} +{{ code .Signature }} +{{ else }} +This section is empty. +{{ end }} + +## Variables + +{{ range .Vars }} +{{ if .Doc }} +{{ comment .Doc }} +{{ end }} +{{ code .Signature }} +{{ else }} +This section is empty. +{{ end }} + +## Functions + +{{ range .Funcs }} +### func {{ . }} +{{ code .Signature }} +{{ if .Doc }} +{{ comment .Doc }} +{{ end }} +{{ else }} +This section is empty. +{{ end }} + +## Types + +{{ range .Types }} +### type {{ .Name }} +{{ if .Doc }} +{{ comment .Doc }} +{{ end }} +{{ code .Definition }} + +{{ range .Vars }} + +{{ if .Doc }} +{{ comment .Doc }} +{{ end }} +{{ code .Signature }} +{{ end }} + +{{ range .Consts }} + +{{ if .Doc }} +{{ comment .Doc }} +{{ end }} +{{ code .Signature }} +{{ end }} + +{{ range .Funcs }} +### func {{ . }} +{{ code .Signature }} +{{ if .Doc }} +{{ .Doc }} +{{ end }} +{{ end }} + +{{ range .Methods }} +### func {{ . }} +{{ code .Signature }} +{{ if .Doc }} +{{ comment .Doc }} +{{ end }} +{{ end }} +{{ else }} +This section is empty. +{{ end }} + +## Source Files +{{ range .Filenames }} +- [{{ . }}]({{ $.Path }}/{{ . }}) +{{ end }} +` diff --git a/gnoland/website/pkgs/doc/types.go b/gnoland/website/pkgs/doc/types.go index 8b02bec75dc..2d9ea4b3b76 100644 --- a/gnoland/website/pkgs/doc/types.go +++ b/gnoland/website/pkgs/doc/types.go @@ -8,10 +8,12 @@ import ( "go/token" "sort" "strings" + "text/template" ) type Package struct { ImportPath string + Path string Name string Doc string Filenames []string @@ -22,6 +24,19 @@ type Package struct { Types []*Type } +func (p *Package) Markdown() ([]byte, error) { + var buf bytes.Buffer + + if err := template.Must(template.New("").Funcs(template.FuncMap{ + "code": codeFmt, + "comment": commentFmt, + }).Parse(TemplateMarkdown)).Execute(&buf, p); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + func (p *Package) filterTypeFuncs(typeName string) (funcs []*Func, methods []*Func) { var remainingFuncs []*Func diff --git a/gnoland/website/static/css/app.css b/gnoland/website/static/css/app.css index f2b0127dc13..6d3039db5f7 100644 --- a/gnoland/website/static/css/app.css +++ b/gnoland/website/static/css/app.css @@ -362,53 +362,22 @@ code.hljs { padding: 0 22px; } -#pkg_doc #pkg_doc__header__overview { - padding: 10px 0; +#pkg_doc h1, +#pkg_doc h2 { + border-bottom: 1px solid #ccc; + margin-bottom: 20px; + padding-bottom: 10px; } -#pkg_doc__body > .pkg_doc__section { - padding: 10px 0; +#pkg_doc h2 { + margin-top: 60px; } -#pkg_doc__body > .pkg_doc__section > .pkg_doc__section-heading { - padding-top: 5px; - padding-bottom: 5px; +#pkg_doc h3 { + margin-top: 40px; } -.pkg_doc__variable { - padding: 10px 0; -} - -.pkg_doc__constant { - padding: 10px 0; -} - -.pkg_doc__comment { - padding: 10px 10px; -} - -.pkg_doc__comment pre { - background-color: transparent; -} - -.pkg_doc__section .pkg_doc__comment { - padding: 1em 0; -} - -.pkg_doc__section .pkg_doc__function { - padding: 10px 0; -} - -.pkg_doc__type { - padding: 20px 0; -} - -.pkg_doc__type-title { - font-weight: bold; -} - -.pkg_doc__type > .pkg_doc__function, -.pkg_doc__type > .pkg_doc__constant, -.pkg_doc__type > .pkg_doc__variable { - padding: 5px 0 5px; +#pkg_doc pre { + margin-top: 10px; + margin-bottom: 10px; } \ No newline at end of file diff --git a/gnoland/website/views/funcs.html b/gnoland/website/views/funcs.html index b6b8f728a27..97619730ae5 100644 --- a/gnoland/website/views/funcs.html +++ b/gnoland/website/views/funcs.html @@ -115,15 +115,19 @@ {{ end }} diff --git a/gnoland/website/views/package_dir.html b/gnoland/website/views/package_dir.html index 823a5290775..a9402a20acd 100644 --- a/gnoland/website/views/package_dir.html +++ b/gnoland/website/views/package_dir.html @@ -5,7 +5,7 @@ Gno.land {{ template "html_head" }} - +
-
-
-

Package {{ .Data.Package.Name }}

-

import "{{ .Data.Package.ImportPath }}"

-
-

- Overview -

- {{ if .Data.Package.Doc }} -
-
{{ .Data.Package.Doc }}
-
- {{ else }} -

This section is empty.

- {{ end }} -
-
- -
-
-

- Constants -

- {{ range .Data.Package.Consts }} -
- {{ if .Doc }} -
{{ .Doc }}
- {{ end }} - -
{{ .Signature }}
-
-
- {{ else }} -

This section is empty.

- {{ end }} -
- -
-

- Variables -

- {{ range .Data.Package.Vars }} -
- {{ if .Doc }} -
{{ .Doc }}
- {{ end }} - -
{{ .Signature }}
-
-
- {{ else }} -

This section is empty.

- {{ end }} -
- -
-

- Functions -

-
- {{ range .Data.Package.Funcs }} -
-

- - func {{ . }} - -

-
{{ .Signature }}
- {{ if .Doc }} -
-
{{ .Doc  }}
-
- {{ end }} -
- {{ else }} -

This section is empty.

- {{ end }} -
-
- -
-

- Types -

- {{ range .Data.Package.Types }} -
- -

type {{ .Name }}

-
- {{ if .Doc }} -
{{ .Doc }}
- {{ end }} -
{{ .Definition }}
- {{ range .Vars }} -
- {{ if .Doc }} -
{{ .Doc }}
- {{ end }} - -
{{ .Signature }}
-
-
- {{ end }} {{ range .Consts }} -
- {{ if .Doc }} -
{{ .Doc }}
- {{ end }} - -
{{ .Signature }}
-
-
- {{ end }} {{ range .Funcs }} -
- -

func {{ . }}

-
-
{{ .Signature }}
- {{ if .Doc }} -
{{ .Doc }}
- {{ end }} -
- {{ end }} {{ range .Methods }} -
- -

func {{ . }}

-
-
{{ .Signature }}
- {{ if .Doc }} -
{{ .Doc }}
- {{ end }} -
- {{ end }} -
- {{ else }} -

This section is empty.

- {{ end }} -
- -
-

- Source Files -

-
{{ template "dir_contents" . }}
-
-
+
{{ .Data.PkgContent }}
{{ template "footer" }}
- {{ template "js" }} {{ template "hljs" }} + {{ template "js" }} -{{- end -}} {{ define "dir_contents" }} -
- {{ $dirPath := .Data.DirPath }} -
    - {{ range .Data.Package.Filenames }} -
  • - {{ . }} -
  • - {{ end }} -
-
-{{ end }} +{{- end -}} \ No newline at end of file diff --git a/gnoland/website/views/package_file.html b/gnoland/website/views/package_file.html index 0f0b92f74aa..f67db8ceb5b 100644 --- a/gnoland/website/views/package_file.html +++ b/gnoland/website/views/package_file.html @@ -21,8 +21,8 @@ {{ template "footer" }}

- {{ template "js" }} {{ template "hljs" }} + {{ template "js" }} -{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/gnoland/website/views/realm_dir.html b/gnoland/website/views/realm_dir.html new file mode 100644 index 00000000000..bb2a462e1fd --- /dev/null +++ b/gnoland/website/views/realm_dir.html @@ -0,0 +1,40 @@ +{{- define "app" -}} + + + + Gno.land + {{ template "html_head" }} + + +
+ + +
+ {{ template "dir_contents" . }} +
+ + {{ template "footer" }} +
+ {{ template "js" }} + + +{{- end -}} + +{{ define "dir_contents" }} +
+ {{ $dirPath := .Data.DirPath }} +
    + {{ range .Data.Files }} +
  • + {{ . }} +
  • + {{ end }} +
+
+{{ end }} \ No newline at end of file From 5a828ef68ae32090a39cc35270fa327b194acbb4 Mon Sep 17 00:00:00 2001 From: Yassin Date: Wed, 8 Mar 2023 11:25:08 +0100 Subject: [PATCH 19/19] Update code for better browser compatibility --- gnoland/website/views/funcs.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gnoland/website/views/funcs.html b/gnoland/website/views/funcs.html index 97619730ae5..e06bb4de05c 100644 --- a/gnoland/website/views/funcs.html +++ b/gnoland/website/views/funcs.html @@ -115,8 +115,10 @@