diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml
index 5d606a2a663..ffe40c2db7e 100644
--- a/.github/workflows/examples.yml
+++ b/.github/workflows/examples.yml
@@ -29,7 +29,7 @@ jobs:
with:
go-version: ${{ matrix.goversion }}
- run: go install -v ./gnovm/cmd/gno
- - run: go run ./gnovm/cmd/gno transpile -v --gobuild ./examples
+ - run: go run ./gnovm/cmd/gno tool transpile -v --gobuild ./examples
test:
strategy:
fail-fast: false
diff --git a/.github/workflows/gnoland.yml b/.github/workflows/gnoland.yml
index b02e7b364e6..c4bc26a45fc 100644
--- a/.github/workflows/gnoland.yml
+++ b/.github/workflows/gnoland.yml
@@ -14,6 +14,9 @@ on:
# Changes to examples/ can create failures in gno.land, eg. txtars,
# see: https://github.com/gnolang/gno/pull/3590
- examples/**
+ # We trigger the testing workflow for changes to the main go.mod,
+ # since this can affect test results
+ - go.mod
workflow_dispatch:
jobs:
diff --git a/.github/workflows/gnovm.yml b/.github/workflows/gnovm.yml
index 7a015b74e09..08b0b66c4e8 100644
--- a/.github/workflows/gnovm.yml
+++ b/.github/workflows/gnovm.yml
@@ -8,6 +8,9 @@ on:
paths:
- gnovm/**
- tm2/** # GnoVM has a dependency on TM2 types
+ # We trigger the testing workflow for changes to the main go.mod,
+ # since this can affect test results
+ - go.mod
workflow_dispatch:
jobs:
diff --git a/.github/workflows/tm2.yml b/.github/workflows/tm2.yml
index 757391eab8c..d2157eb8828 100644
--- a/.github/workflows/tm2.yml
+++ b/.github/workflows/tm2.yml
@@ -7,6 +7,9 @@ on:
pull_request:
paths:
- tm2/**
+ # We trigger the testing workflow for changes to the main go.mod,
+ # since this can affect test results
+ - go.mod
workflow_dispatch:
jobs:
diff --git a/.gitignore b/.gitignore
index 7d3f3f92b41..82cc109887e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,3 +34,6 @@ coverage.out
*.swp
*.swo
*.bak
+
+.tmp/
+wal/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b58d63c6c75..9562a531227 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -198,7 +198,7 @@ Additionally, it's not possible to use `gofumpt` for code formatting with
(flycheck-define-checker gno-lint
"A GNO syntax checker using the gno lint tool."
- :command ("gno" "lint" source-original)
+ :command ("gno" "tool" "lint" source-original)
:error-patterns (;; ./file.gno:32: error message (code=1)
(error line-start (file-name) ":" line ": " (message) " (code=" (id (one-or-more digit)) ")." line-end))
;; Ensure the file is saved, to work around
diff --git a/docs/reference/go-gno-compatibility.md b/docs/reference/go-gno-compatibility.md
index 9f9d611e4fd..87c734d935f 100644
--- a/docs/reference/go-gno-compatibility.md
+++ b/docs/reference/go-gno-compatibility.md
@@ -297,31 +297,31 @@ Legend:
## Tooling (`gno` binary)
-| go command | gno command | comment |
-|-------------------|---------------------------|-----------------------------------------------------------------------|
-| go bug | gno bug | same behavior |
-| go build | gno transpile -gobuild | same intention, limited compatibility |
-| go clean | gno clean | same intention, limited compatibility |
-| go doc | gno doc | limited compatibility; see https://github.com/gnolang/gno/issues/522 |
-| go env | gno env | |
-| go fix | | |
-| go fmt | gno fmt | gofmt (& similar tools, like gofumpt) works on gno code. |
-| go generate | | |
-| go get | | see `gno mod download`. |
-| go help | gno $cmd --help | ie. `gno doc --help` |
-| go install | | |
-| go list | | |
-| go mod | gno mod | |
-| + go mod init | gno mod init | same behavior |
-| + go mod download | gno mod download | same behavior |
-| + go mod tidy | gno mod tidy | same behavior |
-| + go mod why | gno mod why | same intention |
-| | gno transpile | |
-| go work | | |
-| | gno repl | |
-| go run | gno run | |
-| go test | gno test | limited compatibility |
-| go tool | | |
-| go version | | |
-| go vet | | |
-| golint | gno lint | same intention |
+| go command | gno command | comment |
+|-------------------|------------------------------|-----------------------------------------------------------------------|
+| go bug | gno bug | same behavior |
+| go build | gno tool transpile -gobuild | same intention, limited compatibility |
+| go clean | gno clean | same intention, limited compatibility |
+| go doc | gno doc | limited compatibility; see https://github.com/gnolang/gno/issues/522 |
+| go env | gno env | |
+| go fix | | |
+| go fmt | gno fmt | gofmt (& similar tools, like gofumpt) works on gno code. |
+| go generate | | |
+| go get | | see `gno mod download`. |
+| go help | gno $cmd --help | ie. `gno doc --help` |
+| go install | | |
+| go list | | |
+| go mod | gno mod | |
+| + go mod init | gno mod init | same behavior |
+| + go mod download | gno mod download | same behavior |
+| + go mod tidy | gno mod tidy | same behavior |
+| + go mod why | gno mod why | same intention |
+| | gno tool transpile | |
+| go work | | |
+| | gno tool repl | |
+| go run | gno run | |
+| go test | gno test | limited compatibility |
+| go tool | | |
+| go version | | |
+| go vet | | |
+| golint | gno tool lint | same intention |
diff --git a/examples/Makefile b/examples/Makefile
index cdc73ee6b3a..63a20f78eb9 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -33,11 +33,11 @@ OFFICIAL_PACKAGES += ./gno.land/r/gov
# Dev tools
.PHONY: transpile
transpile:
- go run ../gnovm/cmd/gno transpile -v .
+ go run ../gnovm/cmd/gno tool transpile -v .
.PHONY: build
build:
- go run ../gnovm/cmd/gno transpile -v --gobuild .
+ go run ../gnovm/cmd/gno tool transpile -v --gobuild .
.PHONY: test
test:
@@ -45,7 +45,7 @@ test:
.PHONY: lint
lint:
- go run ../gnovm/cmd/gno lint -v $(OFFICIAL_PACKAGES)
+ go run ../gnovm/cmd/gno tool lint -v $(OFFICIAL_PACKAGES)
.PHONY: test.sync
test.sync:
diff --git a/examples/gno.land/r/docs/avl_pager_with_params/gno.mod b/examples/gno.land/r/docs/avl_pager_with_params/gno.mod
new file mode 100644
index 00000000000..aeb5b047762
--- /dev/null
+++ b/examples/gno.land/r/docs/avl_pager_with_params/gno.mod
@@ -0,0 +1 @@
+module gno.land/r/docs/avl_pager_params
diff --git a/examples/gno.land/r/docs/avl_pager_with_params/render.gno b/examples/gno.land/r/docs/avl_pager_with_params/render.gno
new file mode 100644
index 00000000000..108f5735b65
--- /dev/null
+++ b/examples/gno.land/r/docs/avl_pager_with_params/render.gno
@@ -0,0 +1,86 @@
+package avl_pager_params
+
+import (
+ "gno.land/p/demo/avl"
+ "gno.land/p/demo/avl/pager"
+ "gno.land/p/demo/seqid"
+ "gno.land/p/demo/ufmt"
+ "gno.land/p/moul/realmpath"
+)
+
+// We'll keep some demo data in an AVL tree to showcase pagination.
+var (
+ items *avl.Tree
+ idCounter seqid.ID
+)
+
+func init() {
+ items = avl.NewTree()
+ // Populate the tree with 15 sample items for demonstration.
+ for i := 1; i <= 15; i++ {
+ id := idCounter.Next().String()
+ items.Set(id, "Some item value: "+id)
+ }
+}
+
+func Render(path string) string {
+ // 1) Parse the incoming path to split route vs. query.
+ req := realmpath.Parse(path)
+ // - req.Path contains everything *before* ? or $ (? - query params, $ - gnoweb params)
+ // - The remaining part (page=2, size=5, etc.) is not in req.Path.
+
+ // 2) If no specific route is provided (req.Path == ""), we’ll show a “home” page
+ // that displays a list of configs in paginated form.
+ if req.Path == "" {
+ return renderHome(path)
+ }
+
+ // 3) If a route *is* provided (e.g. :SomeKey),
+ // we will interpret it as a request for a specific page.
+ return renderConfigItem(req.Path)
+}
+
+// renderHome shows a paginated list of config items if route == "".
+func renderHome(fullPath string) string {
+ // Create a Pager for our config tree, with a default page size of 5.
+ p := pager.NewPager(items, 5, false)
+
+ // MustGetPageByPath uses the *entire* path (including query parts: ?page=2, etc.)
+ page := p.MustGetPageByPath(fullPath)
+
+ // Start building the output (plain text or markdown).
+ out := "# AVL Pager + Render paths\n\n"
+ out += `This realm showcases how to maintain a paginated list while properly parsing render paths.
+You can see how a single page can include a paginated element (like the example below), and how clicking
+an item can take you to a dedicated page for that specific item.
+
+No matter how you browse through the paginated list, the introductory text (this section) remains the same.
+
+`
+
+ out += ufmt.Sprintf("Showing page %d of %d\n\n", page.PageNumber, page.TotalPages)
+
+ // List items for this page.
+ for _, item := range page.Items {
+ // Link each item to a details page: e.g. ":Config01"
+ out += ufmt.Sprintf("- [Item %s](/r/docs/avl_pager_params:%s)\n", item.Key, item.Key)
+ }
+
+ // Insert pagination controls (previous/next links, etc.).
+ out += "\n" + page.Picker() + "\n\n"
+ out += "### [Go back to r/docs](/r/docs)"
+
+ return out
+}
+
+// renderConfigItem shows details for a single item, e.g. ":item001".
+func renderConfigItem(itemName string) string {
+ value, ok := items.Get(itemName)
+ if !ok {
+ return ufmt.Sprintf("**No item found** for key: %s", itemName)
+ }
+
+ out := ufmt.Sprintf("# Item %s\n\n%s\n\n", itemName, value.(string))
+ out += "[Go back](/r/docs/avl_pager_params)"
+ return out
+}
diff --git a/examples/gno.land/r/docs/docs.gno b/examples/gno.land/r/docs/docs.gno
index 28bac4171b5..b4c78205c0a 100644
--- a/examples/gno.land/r/docs/docs.gno
+++ b/examples/gno.land/r/docs/docs.gno
@@ -13,7 +13,9 @@ Explore various examples to learn more about Gno functionality and usage.
- [Source](/r/docs/source) - View realm source code.
- [Buttons](/r/docs/buttons) - Add buttons to your realm's render.
- [AVL Pager](/r/docs/avl_pager) - Paginate through AVL tree items.
+- [AVL Pager + Render paths](/r/docs/avl_pager_params) - Handle render arguments with pagination.
- [Img Embed](/r/docs/img_embed) - Demonstrates how to embed an image.
+- [Optional Render](/r/docs/optional_render) - Render() is optional in realms.
- ...
diff --git a/examples/gno.land/r/docs/optional_render/gno.mod b/examples/gno.land/r/docs/optional_render/gno.mod
new file mode 100644
index 00000000000..4c8162ca46d
--- /dev/null
+++ b/examples/gno.land/r/docs/optional_render/gno.mod
@@ -0,0 +1 @@
+module gno.land/r/docs/optional_render
diff --git a/examples/gno.land/r/docs/optional_render/optional_render.gno b/examples/gno.land/r/docs/optional_render/optional_render.gno
new file mode 100644
index 00000000000..77da30609b3
--- /dev/null
+++ b/examples/gno.land/r/docs/optional_render/optional_render.gno
@@ -0,0 +1,7 @@
+package optional_render
+
+func Info() string {
+ return `Having a Render() function in your realm is optional!
+If you do decide to have a Render() function, it must have the following signature:
+func Render(path string) string { ... }`
+}
diff --git a/examples/gno.land/r/leon/config/config.gno b/examples/gno.land/r/leon/config/config.gno
index bc800ec8263..bb90a6c21d7 100644
--- a/examples/gno.land/r/leon/config/config.gno
+++ b/examples/gno.land/r/leon/config/config.gno
@@ -3,61 +3,116 @@ package config
import (
"errors"
"std"
+ "strconv"
+ "strings"
+ "time"
+
+ "gno.land/p/demo/avl"
+ p "gno.land/p/demo/avl/pager"
+ "gno.land/p/demo/ownable"
+ "gno.land/p/demo/ufmt"
+ "gno.land/p/moul/md"
+ "gno.land/p/moul/realmpath"
)
var (
- main std.Address // leon's main address
- backup std.Address // backup address
+ configs = avl.NewTree()
+ pager = p.NewPager(configs, 10, false)
+ banner = "---\n[[Leon's Home page]](/r/leon/home) | [[GitHub: @leohhhn]](https://github.com/leohhhn)\n\n---"
+ absPath = strings.TrimPrefix(std.CurrentRealm().PkgPath(), std.GetChainDomain())
+
+ // SafeObjects
+ OwnableMain = ownable.NewWithAddress("g125em6arxsnj49vx35f0n0z34putv5ty3376fg5")
+ OwnableBackup = ownable.NewWithAddress("g1lavlav7zwsjqlzzl3qdl3nl242qtf638vnhdjh")
- ErrInvalidAddr = errors.New("leon's config: invalid address")
ErrUnauthorized = errors.New("leon's config: unauthorized")
)
-func init() {
- main = "g125em6arxsnj49vx35f0n0z34putv5ty3376fg5"
+type Config struct {
+ lines string
+ updated time.Time
}
-func Address() std.Address {
- return main
-}
+func AddConfig(name, lines string) {
+ if !IsAuthorized(std.PrevRealm().Addr()) {
+ panic(ErrUnauthorized)
+ }
-func Backup() std.Address {
- return backup
+ configs.Set(name, Config{
+ lines: lines,
+ updated: time.Now(),
+ }) // no overwrite check
}
-func SetAddress(a std.Address) error {
- if !a.IsValid() {
- return ErrInvalidAddr
+func RemoveConfig(name string) {
+ if !IsAuthorized(std.PrevRealm().Addr()) {
+ panic(ErrUnauthorized)
}
- if err := checkAuthorized(); err != nil {
- return err
+ if _, ok := configs.Remove(name); !ok {
+ panic("no config with that name")
}
-
- main = a
- return nil
}
-func SetBackup(a std.Address) error {
- if !a.IsValid() {
- return ErrInvalidAddr
+func UpdateBanner(newBanner string) {
+ if !IsAuthorized(std.PrevRealm().Addr()) {
+ panic(ErrUnauthorized)
}
- if err := checkAuthorized(); err != nil {
- return err
- }
+ banner = newBanner
+}
- backup = a
- return nil
+func IsAuthorized(addr std.Address) bool {
+ return addr == OwnableMain.Owner() || addr == OwnableBackup.Owner()
}
-func checkAuthorized() error {
- caller := std.PrevRealm().Addr()
- isAuthorized := caller == main || caller == backup
+func Banner() string {
+ return banner
+}
+
+func Render(path string) (out string) {
+ req := realmpath.Parse(path)
+ if req.Path == "" {
+ out += md.H1("Leon's config package")
+
+ out += ufmt.Sprintf("Leon's main address: %s\n\n", OwnableMain.Owner().String())
+ out += ufmt.Sprintf("Leon's backup address: %s\n\n", OwnableBackup.Owner().String())
- if !isAuthorized {
- return ErrUnauthorized
+ out += md.H2("Leon's configs")
+
+ if configs.Size() == 0 {
+ out += "No configs yet :c\n\n"
+ }
+
+ page := pager.MustGetPageByPath(path)
+ for _, item := range page.Items {
+ out += ufmt.Sprintf("- [%s](%s:%s)\n\n", item.Key, absPath, item.Key)
+ }
+
+ out += page.Picker()
+ out += "\n\n"
+ out += "Page " + strconv.Itoa(page.PageNumber) + " of " + strconv.Itoa(page.TotalPages) + "\n\n"
+
+ out += Banner()
+
+ return out
}
- return nil
+ return renderConfPage(req.Path)
+}
+
+func renderConfPage(confName string) (out string) {
+ raw, ok := configs.Get(confName)
+ if !ok {
+ out += md.H1("404")
+ out += "That config does not exist :/"
+ return out
+ }
+
+ conf := raw.(Config)
+ out += md.H1(confName)
+ out += ufmt.Sprintf("```\n%s\n```\n\n", conf.lines)
+ out += ufmt.Sprintf("_Last updated on %s_", conf.updated.Format("02 Jan, 2006"))
+
+ return out
}
diff --git a/examples/gno.land/r/leon/hof/hof.gno b/examples/gno.land/r/leon/hof/hof.gno
index 147a0dd1a95..96266ffe380 100644
--- a/examples/gno.land/r/leon/hof/hof.gno
+++ b/examples/gno.land/r/leon/hof/hof.gno
@@ -10,6 +10,8 @@ import (
"gno.land/p/demo/ownable"
"gno.land/p/demo/pausable"
"gno.land/p/demo/seqid"
+
+ "gno.land/r/leon/config"
)
var (
@@ -24,7 +26,7 @@ type (
Exhibition struct {
itemCounter seqid.ID
description string
- items *avl.Tree // pkgPath > Item
+ items *avl.Tree // pkgPath > &Item
itemsSorted *avl.Tree // same data but sorted, storing pointers
}
@@ -43,7 +45,7 @@ func init() {
itemsSorted: avl.NewTree(),
}
- Ownable = ownable.NewWithAddress(std.Address("g125em6arxsnj49vx35f0n0z34putv5ty3376fg5"))
+ Ownable = ownable.NewWithAddress(config.OwnableMain.Owner()) // OrigSendOwnable?
Pausable = pausable.NewFromOwnable(Ownable)
}
@@ -85,14 +87,14 @@ func Register() {
func Upvote(pkgpath string) {
rawItem, ok := exhibition.items.Get(pkgpath)
if !ok {
- panic(ErrNoSuchItem.Error())
+ panic(ErrNoSuchItem)
}
item := rawItem.(*Item)
caller := std.PrevRealm().Addr().String()
if item.upvote.Has(caller) {
- panic(ErrDoubleUpvote.Error())
+ panic(ErrDoubleUpvote)
}
item.upvote.Set(caller, struct{}{})
@@ -101,14 +103,14 @@ func Upvote(pkgpath string) {
func Downvote(pkgpath string) {
rawItem, ok := exhibition.items.Get(pkgpath)
if !ok {
- panic(ErrNoSuchItem.Error())
+ panic(ErrNoSuchItem)
}
item := rawItem.(*Item)
caller := std.PrevRealm().Addr().String()
if item.downvote.Has(caller) {
- panic(ErrDoubleDownvote.Error())
+ panic(ErrDoubleDownvote)
}
item.downvote.Set(caller, struct{}{})
@@ -116,19 +118,19 @@ func Downvote(pkgpath string) {
func Delete(pkgpath string) {
if !Ownable.CallerIsOwner() {
- panic(ownable.ErrUnauthorized.Error())
+ panic(ownable.ErrUnauthorized)
}
i, ok := exhibition.items.Get(pkgpath)
if !ok {
- panic(ErrNoSuchItem.Error())
+ panic(ErrNoSuchItem)
}
if _, removed := exhibition.itemsSorted.Remove(i.(*Item).id.String()); !removed {
- panic(ErrNoSuchItem.Error())
+ panic(ErrNoSuchItem)
}
if _, removed := exhibition.items.Remove(pkgpath); !removed {
- panic(ErrNoSuchItem.Error())
+ panic(ErrNoSuchItem)
}
}
diff --git a/examples/gno.land/r/leon/home/home.gno b/examples/gno.land/r/leon/home/home.gno
index cf33260cc6b..aef261fcd60 100644
--- a/examples/gno.land/r/leon/home/home.gno
+++ b/examples/gno.land/r/leon/home/home.gno
@@ -19,7 +19,24 @@ var (
abtMe [2]string
)
+func Render(path string) string {
+ out := "# Leon's Homepage\n\n"
+
+ out += renderAboutMe()
+ out += renderBlogPosts()
+ out += "\n\n"
+ out += renderArt()
+ out += "\n\n"
+ out += config.Banner()
+ out += "\n\n"
+
+ return out
+}
+
func init() {
+ hof.Register()
+ mirror.Register(std.CurrentRealm().PkgPath(), Render)
+
pfp = "https://i.imgflip.com/91vskx.jpg"
pfpCaption = "[My favourite painting & pfp](https://en.wikipedia.org/wiki/Wanderer_above_the_Sea_of_Fog)"
abtMe = [2]string{
@@ -30,16 +47,12 @@ life-long learner, and sharer of knowledge.`,
My contributions to gno.land can mainly be found
[here](https://github.com/gnolang/gno/issues?q=sort:updated-desc+author:leohhhn).
-TODO import r/gh
-`,
+TODO import r/gh`,
}
-
- hof.Register()
- mirror.Register(std.CurrentRealm().PkgPath(), Render)
}
func UpdatePFP(url, caption string) {
- if !isAuthorized(std.PrevRealm().Addr()) {
+ if !config.IsAuthorized(std.PrevRealm().Addr()) {
panic(config.ErrUnauthorized)
}
@@ -48,7 +61,7 @@ func UpdatePFP(url, caption string) {
}
func UpdateAboutMe(col1, col2 string) {
- if !isAuthorized(std.PrevRealm().Addr()) {
+ if !config.IsAuthorized(std.PrevRealm().Addr()) {
panic(config.ErrUnauthorized)
}
@@ -56,17 +69,6 @@ func UpdateAboutMe(col1, col2 string) {
abtMe[1] = col2
}
-func Render(path string) string {
- out := "# Leon's Homepage\n\n"
-
- out += renderAboutMe()
- out += renderBlogPosts()
- out += "\n\n"
- out += renderArt()
-
- return out
-}
-
func renderBlogPosts() string {
out := ""
//out += "## Leon's Blog Posts"
@@ -130,7 +132,3 @@ func renderMillipede() string {
return out
}
-
-func isAuthorized(addr std.Address) bool {
- return addr == config.Address() || addr == config.Backup()
-}
diff --git a/gno.land/pkg/gnoweb/app_test.go b/gno.land/pkg/gnoweb/app_test.go
index 6fb69c6d984..ce10cae12d5 100644
--- a/gno.land/pkg/gnoweb/app_test.go
+++ b/gno.land/pkg/gnoweb/app_test.go
@@ -47,6 +47,7 @@ func TestRoutes(t *testing.T) {
{"/game-of-realms", found, "/contribute"},
{"/gor", found, "/contribute"},
{"/blog", found, "/r/gnoland/blog"},
+ {"/r/docs/optional_render", http.StatusOK, "No Render"},
{"/r/not/found/", notFound, ""},
{"/404/not/found", notFound, ""},
{"/아스키문자가아닌경로", notFound, ""},
diff --git a/gno.land/pkg/gnoweb/components/layout_index.go b/gno.land/pkg/gnoweb/components/layout_index.go
index 8b49e8f8ada..f56ddfe1514 100644
--- a/gno.land/pkg/gnoweb/components/layout_index.go
+++ b/gno.land/pkg/gnoweb/components/layout_index.go
@@ -15,6 +15,8 @@ type HeadData struct {
ChromaPath string
AssetsPath string
Analytics bool
+ Remote string
+ ChainId string
}
type IndexData struct {
diff --git a/gno.land/pkg/gnoweb/components/layouts/head.html b/gno.land/pkg/gnoweb/components/layouts/head.html
index 1b78eeeb324..72f255ebdf6 100644
--- a/gno.land/pkg/gnoweb/components/layouts/head.html
+++ b/gno.land/pkg/gnoweb/components/layouts/head.html
@@ -40,5 +40,9 @@
+
+
+
+
{{ end }}
diff --git a/gno.land/pkg/gnoweb/components/view_status.go b/gno.land/pkg/gnoweb/components/view_status.go
index 46f998c45cb..56477a4db0a 100644
--- a/gno.land/pkg/gnoweb/components/view_status.go
+++ b/gno.land/pkg/gnoweb/components/view_status.go
@@ -2,10 +2,38 @@ package components
const StatusViewType ViewType = "status-view"
+// StatusData holds the dynamic fields for the "status" template
type StatusData struct {
- Message string
+ Title string
+ Body string
+ ButtonURL string
+ ButtonText string
}
-func StatusComponent(message string) *View {
- return NewTemplateView(StatusViewType, "status", StatusData{message})
+// StatusErrorComponent returns a view for error scenarios
+func StatusErrorComponent(message string) *View {
+ return NewTemplateView(
+ StatusViewType,
+ "status",
+ StatusData{
+ Title: "Error: " + message,
+ Body: "Something went wrong.",
+ ButtonURL: "/",
+ ButtonText: "Go Back Home",
+ },
+ )
+}
+
+// StatusNoRenderComponent returns a view for non-error notifications
+func StatusNoRenderComponent(pkgPath string) *View {
+ return NewTemplateView(
+ StatusViewType,
+ "status",
+ StatusData{
+ Title: "No Render",
+ Body: "This realm does not implement a Render() function.",
+ ButtonURL: pkgPath + "$source",
+ ButtonText: "View Realm Source",
+ },
+ )
}
diff --git a/gno.land/pkg/gnoweb/components/views/status.html b/gno.land/pkg/gnoweb/components/views/status.html
index ab068cbf7e4..f4533275789 100644
--- a/gno.land/pkg/gnoweb/components/views/status.html
+++ b/gno.land/pkg/gnoweb/components/views/status.html
@@ -1,8 +1,12 @@
{{ define "status" }}
{{ end }}
diff --git a/gno.land/pkg/gnoweb/handler.go b/gno.land/pkg/gnoweb/handler.go
index 3fdfc33909c..ac39f4ce0f9 100644
--- a/gno.land/pkg/gnoweb/handler.go
+++ b/gno.land/pkg/gnoweb/handler.go
@@ -90,6 +90,8 @@ func (h *WebHandler) Get(w http.ResponseWriter, r *http.Request) {
HeadData: components.HeadData{
AssetsPath: h.Static.AssetsPath,
ChromaPath: h.Static.ChromaPath,
+ ChainId: h.Static.ChainId,
+ Remote: h.Static.RemoteHelp,
},
FooterData: components.FooterData{
Analytics: h.Static.Analytics,
@@ -112,7 +114,7 @@ func (h *WebHandler) prepareIndexBodyView(r *http.Request, indexData *components
gnourl, err := ParseGnoURL(r.URL)
if err != nil {
h.Logger.Warn("unable to parse url path", "path", r.URL.Path, "error", err)
- return http.StatusNotFound, components.StatusComponent("invalid path")
+ return http.StatusNotFound, components.StatusErrorComponent("invalid path")
}
breadcrumb := generateBreadcrumbPaths(gnourl)
@@ -128,7 +130,7 @@ func (h *WebHandler) prepareIndexBodyView(r *http.Request, indexData *components
return h.GetPackageView(gnourl)
default:
h.Logger.Debug("invalid path: path is neither a pure package or a realm")
- return http.StatusBadRequest, components.StatusComponent("invalid path")
+ return http.StatusBadRequest, components.StatusErrorComponent("invalid path")
}
}
@@ -158,6 +160,10 @@ func (h *WebHandler) GetRealmView(gnourl *GnoURL) (int, *components.View) {
meta, err := h.Client.RenderRealm(&content, gnourl.Path, gnourl.EncodeArgs())
if err != nil {
+ if errors.Is(err, ErrRenderNotDeclared) {
+ return http.StatusOK, components.StatusNoRenderComponent(gnourl.Path)
+ }
+
h.Logger.Error("unable to render realm", "error", err, "path", gnourl.EncodeURL())
return GetClientErrorStatusPage(gnourl, err)
}
@@ -221,7 +227,7 @@ func (h *WebHandler) GetSourceView(gnourl *GnoURL) (int, *components.View) {
if len(files) == 0 {
h.Logger.Debug("no files available", "path", gnourl.Path)
- return http.StatusOK, components.StatusComponent("no files available")
+ return http.StatusOK, components.StatusErrorComponent("no files available")
}
var fileName string
@@ -264,7 +270,7 @@ func (h *WebHandler) GetDirectoryView(gnourl *GnoURL) (int, *components.View) {
if len(files) == 0 {
h.Logger.Debug("no files available", "path", gnourl.Path)
- return http.StatusOK, components.StatusComponent("no files available")
+ return http.StatusOK, components.StatusErrorComponent("no files available")
}
return http.StatusOK, components.DirectoryView(components.DirData{
@@ -281,13 +287,13 @@ func GetClientErrorStatusPage(_ *GnoURL, err error) (int, *components.View) {
switch {
case errors.Is(err, ErrClientPathNotFound):
- return http.StatusNotFound, components.StatusComponent(err.Error())
+ return http.StatusNotFound, components.StatusErrorComponent(err.Error())
case errors.Is(err, ErrClientBadRequest):
- return http.StatusInternalServerError, components.StatusComponent("bad request")
+ return http.StatusInternalServerError, components.StatusErrorComponent("bad request")
case errors.Is(err, ErrClientResponse):
fallthrough // XXX: for now fallback as internal error
default:
- return http.StatusInternalServerError, components.StatusComponent("internal error")
+ return http.StatusInternalServerError, components.StatusErrorComponent("internal error")
}
}
diff --git a/gno.land/pkg/gnoweb/handler_test.go b/gno.land/pkg/gnoweb/handler_test.go
index 624e3390a97..8321ad24be2 100644
--- a/gno.land/pkg/gnoweb/handler_test.go
+++ b/gno.land/pkg/gnoweb/handler_test.go
@@ -24,12 +24,13 @@ func (t *testingLogger) Write(b []byte) (n int, err error) {
// TestWebHandler_Get tests the Get method of WebHandler using table-driven tests.
func TestWebHandler_Get(t *testing.T) {
+ t.Parallel()
// Set up a mock package with some files and functions
mockPackage := &gnoweb.MockPackage{
Domain: "example.com",
Path: "/r/mock/path",
Files: map[string]string{
- "render.gno": `package main; func Render(path string) { return "one more time" }`,
+ "render.gno": `package main; func Render(path string) string { return "one more time" }`,
"gno.mod": `module example.com/r/mock/path`,
"LicEnse": `my super license`,
},
@@ -37,6 +38,10 @@ func TestWebHandler_Get(t *testing.T) {
{FuncName: "SuperRenderFunction", Params: []vm.NamedType{
{Name: "my_super_arg", Type: "string"},
}},
+ {
+ FuncName: "Render", Params: []vm.NamedType{{Name: "path", Type: "string"}},
+ Results: []vm.NamedType{{Name: "", Type: "string"}},
+ },
},
}
@@ -82,6 +87,7 @@ func TestWebHandler_Get(t *testing.T) {
for _, tc := range cases {
t.Run(strings.TrimPrefix(tc.Path, "/"), func(t *testing.T) {
+ t.Parallel()
t.Logf("input: %+v", tc)
// Initialize testing logger
@@ -110,3 +116,38 @@ func TestWebHandler_Get(t *testing.T) {
})
}
}
+
+// TestWebHandler_NoRender checks if gnoweb displays the `No Render` page properly.
+// This happens when the render being queried does not have a Render function declared.
+func TestWebHandler_NoRender(t *testing.T) {
+ t.Parallel()
+
+ mockPath := "/r/mock/path"
+ mockPackage := &gnoweb.MockPackage{
+ Domain: "gno.land",
+ Path: "/r/mock/path",
+ Files: map[string]string{
+ "render.gno": `package main; func init() {}`,
+ "gno.mod": `module gno.land/r/mock/path`,
+ },
+ }
+
+ webclient := gnoweb.NewMockWebClient(mockPackage)
+ config := gnoweb.WebHandlerConfig{
+ WebClient: webclient,
+ }
+
+ logger := slog.New(slog.NewTextHandler(&testingLogger{t}, &slog.HandlerOptions{}))
+ handler, err := gnoweb.NewWebHandler(logger, config)
+ require.NoError(t, err, "failed to create WebHandler")
+
+ req, err := http.NewRequest(http.MethodGet, mockPath, nil)
+ require.NoError(t, err, "failed to create HTTP request")
+
+ rr := httptest.NewRecorder()
+ handler.ServeHTTP(rr, req)
+
+ assert.Equal(t, http.StatusOK, rr.Code, "unexpected status code")
+ expectedBody := "This realm does not implement a Render() function."
+ assert.Contains(t, rr.Body.String(), expectedBody, "rendered body should contain: %q", expectedBody)
+}
diff --git a/gno.land/pkg/gnoweb/webclient.go b/gno.land/pkg/gnoweb/webclient.go
index de44303f352..1def3bc3812 100644
--- a/gno.land/pkg/gnoweb/webclient.go
+++ b/gno.land/pkg/gnoweb/webclient.go
@@ -10,6 +10,7 @@ import (
var (
ErrClientPathNotFound = errors.New("package not found")
+ ErrRenderNotDeclared = errors.New("render function not declared")
ErrClientBadRequest = errors.New("bad request")
ErrClientResponse = errors.New("node response error")
)
@@ -23,7 +24,7 @@ type RealmMeta struct {
Toc md.Toc
}
-// WebClient is an interface for interacting with package and node ressources.
+// WebClient is an interface for interacting with package and node resources.
type WebClient interface {
// RenderRealm renders the content of a realm from a given path and
// arguments into the giver `writer`. The method should ensures the rendered
diff --git a/gno.land/pkg/gnoweb/webclient_html.go b/gno.land/pkg/gnoweb/webclient_html.go
index d856c6f87a0..c04a7f9e457 100644
--- a/gno.land/pkg/gnoweb/webclient_html.go
+++ b/gno.land/pkg/gnoweb/webclient_html.go
@@ -177,6 +177,7 @@ func (s *HTMLWebClient) RenderRealm(w io.Writer, pkgPath string, args string) (*
pkgPath = strings.Trim(pkgPath, "/")
data := fmt.Sprintf("%s/%s:%s", s.domain, pkgPath, args)
+
rawres, err := s.query(qpath, []byte(data))
if err != nil {
return nil, err
@@ -213,6 +214,10 @@ func (s *HTMLWebClient) query(qpath string, data []byte) ([]byte, error) {
return nil, ErrClientPathNotFound
}
+ if errors.Is(err, vm.NoRenderDeclError{}) {
+ return nil, ErrRenderNotDeclared
+ }
+
s.logger.Error("response error", "path", qpath, "log", qres.Response.Log)
return nil, fmt.Errorf("%w: %s", ErrClientResponse, err.Error())
}
diff --git a/gno.land/pkg/gnoweb/webclient_mock.go b/gno.land/pkg/gnoweb/webclient_mock.go
index 451f5e237c3..8a037c181e0 100644
--- a/gno.land/pkg/gnoweb/webclient_mock.go
+++ b/gno.land/pkg/gnoweb/webclient_mock.go
@@ -31,13 +31,18 @@ func NewMockWebClient(pkgs ...*MockPackage) *MockWebClient {
return &MockWebClient{Packages: mpkgs}
}
-// Render simulates rendering a package by writing its content to the writer.
+// RenderRealm simulates rendering a package by writing its content to the writer.
func (m *MockWebClient) RenderRealm(w io.Writer, path string, args string) (*RealmMeta, error) {
pkg, exists := m.Packages[path]
if !exists {
return nil, ErrClientPathNotFound
}
+ if !pkgHasRender(pkg) {
+ return nil, ErrRenderNotDeclared
+ }
+
+ // Write to the realm render
fmt.Fprintf(w, "[%s]%s:", pkg.Domain, pkg.Path)
// Return a dummy RealmMeta for simplicity
@@ -89,3 +94,21 @@ func (m *MockWebClient) Sources(path string) ([]string, error) {
return fileNames, nil
}
+
+func pkgHasRender(pkg *MockPackage) bool {
+ if len(pkg.Functions) == 0 {
+ return false
+ }
+
+ for _, fn := range pkg.Functions {
+ if fn.FuncName == "Render" &&
+ len(fn.Params) == 1 &&
+ len(fn.Results) == 1 &&
+ fn.Params[0].Type == "string" &&
+ fn.Results[0].Type == "string" {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/gno.land/pkg/sdk/vm/errors.go b/gno.land/pkg/sdk/vm/errors.go
index c8d6da98970..208fb074f7e 100644
--- a/gno.land/pkg/sdk/vm/errors.go
+++ b/gno.land/pkg/sdk/vm/errors.go
@@ -16,6 +16,7 @@ func (abciError) AssertABCIError() {}
// NOTE: these are meant to be used in conjunction with pkgs/errors.
type (
InvalidPkgPathError struct{ abciError }
+ NoRenderDeclError struct{ abciError }
PkgExistError struct{ abciError }
InvalidStmtError struct{ abciError }
InvalidExprError struct{ abciError }
@@ -27,6 +28,7 @@ type (
)
func (e InvalidPkgPathError) Error() string { return "invalid package path" }
+func (e NoRenderDeclError) Error() string { return "render function not declared" }
func (e PkgExistError) Error() string { return "package already exists" }
func (e InvalidStmtError) Error() string { return "invalid statement" }
func (e InvalidExprError) Error() string { return "invalid expression" }
diff --git a/gno.land/pkg/sdk/vm/handler.go b/gno.land/pkg/sdk/vm/handler.go
index c484e07e887..5aebf1afe46 100644
--- a/gno.land/pkg/sdk/vm/handler.go
+++ b/gno.land/pkg/sdk/vm/handler.go
@@ -129,9 +129,13 @@ func (vh vmHandler) queryRender(ctx sdk.Context, req abci.RequestQuery) (res abc
expr := fmt.Sprintf("Render(%q)", path)
result, err := vh.vm.QueryEvalString(ctx, pkgPath, expr)
if err != nil {
+ if strings.Contains(err.Error(), "Render not declared") {
+ err = NoRenderDeclError{}
+ }
res = sdk.ABCIResponseQueryFromError(err)
return
}
+
res.Data = []byte(result)
return
}
diff --git a/gno.land/pkg/sdk/vm/package.go b/gno.land/pkg/sdk/vm/package.go
index 0359061ccea..95e97648dac 100644
--- a/gno.land/pkg/sdk/vm/package.go
+++ b/gno.land/pkg/sdk/vm/package.go
@@ -20,6 +20,7 @@ var Package = amino.RegisterPackage(amino.NewPackage(
// errors
InvalidPkgPathError{}, "InvalidPkgPathError",
+ NoRenderDeclError{}, "NoRenderDeclError",
PkgExistError{}, "PkgExistError",
InvalidStmtError{}, "InvalidStmtError",
InvalidExprError{}, "InvalidExprError",
diff --git a/gnovm/Makefile b/gnovm/Makefile
index babb7ad74ca..ce745e44aae 100644
--- a/gnovm/Makefile
+++ b/gnovm/Makefile
@@ -54,8 +54,8 @@ lint:
.PHONY: fmt
fmt:
- go run ./cmd/gno fmt $(GNOFMT_FLAGS) ./stdlibs/... ./tests/stdlibs/...
$(rundep) mvdan.cc/gofumpt $(GOFMT_FLAGS) .
+ go run ./cmd/gno fmt $(GNOFMT_FLAGS) ./stdlibs/... ./tests/stdlibs/...
.PHONY: imports
imports:
@@ -125,7 +125,13 @@ _test.filetest:;
# TODO: move _dev.stringer to go:generate instructions, simplify generate
# to just go generate.
.PHONY: generate
-generate: _dev.stringer _dev.generate
+generate: _dev.stringer _dev.generate _dev.docs
+
+.PHONY: _dev.docs
+_dev.docs:
+ mkdir -p .tmp
+ (go run ./cmd/gno -h 2>&1 || true) | grep -v "exit status 1" > .tmp/gno-help.txt
+ $(rundep) github.com/campoy/embedmd -w `find . -name "*.md"`
stringer_cmd=$(rundep) golang.org/x/tools/cmd/stringer
.PHONY: _dev.stringer
diff --git a/gnovm/cmd/gno/README.md b/gnovm/cmd/gno/README.md
index f900b164783..81b45622c05 100644
--- a/gnovm/cmd/gno/README.md
+++ b/gnovm/cmd/gno/README.md
@@ -6,13 +6,25 @@
`gno [arguments]`
-## Commands
+## Usage
-* `gno run` - run a Gno file
-* `gno transpile` - transpile .gno to .go
-* `gno test` - test a gno package
-* `gno mod` - manages dependencies
-* `gno repl` start a GnoVM REPL
+[embedmd]:#(../../.tmp/gno-help.txt)
+```txt
+USAGE
+ gno [arguments]
+
+SUBCOMMANDS
+ bug start a bug report
+ clean remove generated and cached data
+ doc show documentation for package or symbol
+ env print gno environment information
+ fmt gnofmt (reformat) package sources
+ mod module maintenance
+ run run gno packages
+ test test packages
+ tool run specified gno tool
+
+```
## Install
diff --git a/gnovm/cmd/gno/bug.go b/gnovm/cmd/gno/bug.go
index a1273d9ad59..7a4345fb1ed 100644
--- a/gnovm/cmd/gno/bug.go
+++ b/gnovm/cmd/gno/bug.go
@@ -61,7 +61,7 @@ func newBugCmd(io commands.IO) *commands.Command {
commands.Metadata{
Name: "bug",
ShortUsage: "bug",
- ShortHelp: "Start a bug report",
+ ShortHelp: "start a bug report",
LongHelp: `opens https://github.com/gnolang/gno/issues in a browser.
The new issue body is prefilled for you with the following information:
diff --git a/gnovm/cmd/gno/clean.go b/gnovm/cmd/gno/clean.go
index 0ca2e940d58..7c318c3957e 100644
--- a/gnovm/cmd/gno/clean.go
+++ b/gnovm/cmd/gno/clean.go
@@ -26,7 +26,7 @@ func newCleanCmd(io commands.IO) *commands.Command {
commands.Metadata{
Name: "clean",
ShortUsage: "clean [flags]",
- ShortHelp: "removes generated files and cached data",
+ ShortHelp: "remove generated and cached data",
},
cfg,
func(ctx context.Context, args []string) error {
diff --git a/gnovm/cmd/gno/doc.go b/gnovm/cmd/gno/doc.go
index 794dd1ba7bb..c34ed984d9c 100644
--- a/gnovm/cmd/gno/doc.go
+++ b/gnovm/cmd/gno/doc.go
@@ -29,7 +29,8 @@ func newDocCmd(io commands.IO) *commands.Command {
commands.Metadata{
Name: "doc",
ShortUsage: "doc [flags] ",
- ShortHelp: "get documentation for the specified package or symbol (type, function, method, or variable/constant)",
+ ShortHelp: "show documentation for package or symbol",
+ LongHelp: "get documentation for the specified package or symbol (type, function, method, or variable/constant)",
},
c,
func(_ context.Context, args []string) error {
diff --git a/gnovm/cmd/gno/env.go b/gnovm/cmd/gno/env.go
index 9c601a270c7..20d32c85ec0 100644
--- a/gnovm/cmd/gno/env.go
+++ b/gnovm/cmd/gno/env.go
@@ -18,7 +18,7 @@ func newEnvCmd(io commands.IO) *commands.Command {
commands.Metadata{
Name: "env",
ShortUsage: "env [flags] ",
- ShortHelp: "`env` prints Gno environment information",
+ ShortHelp: "print gno environment information",
},
c,
func(_ context.Context, args []string) error {
diff --git a/gnovm/cmd/gno/fmt.go b/gnovm/cmd/gno/fmt.go
index de6c28c4df0..e37c50a079f 100644
--- a/gnovm/cmd/gno/fmt.go
+++ b/gnovm/cmd/gno/fmt.go
@@ -35,7 +35,7 @@ func newFmtCmd(io commands.IO) *commands.Command {
commands.Metadata{
Name: "fmt",
ShortUsage: "gno fmt [flags] [path ...]",
- ShortHelp: "Run gno file formatter.",
+ ShortHelp: "gnofmt (reformat) package sources",
LongHelp: "The `gno fmt` tool processes, formats, and cleans up `gno` source files.",
},
cfg,
diff --git a/gnovm/cmd/gno/main.go b/gnovm/cmd/gno/main.go
index 7a5799f2835..5f8bb7b522e 100644
--- a/gnovm/cmd/gno/main.go
+++ b/gnovm/cmd/gno/main.go
@@ -16,33 +16,32 @@ func main() {
func newGnocliCmd(io commands.IO) *commands.Command {
cmd := commands.NewCommand(
commands.Metadata{
- ShortUsage: " [flags] [...]",
- LongHelp: "Runs the gno development toolkit",
+ ShortUsage: "gno [arguments]",
},
commands.NewEmptyConfig(),
commands.HelpExec,
)
cmd.AddSubCommands(
- newModCmd(io),
- newTestCmd(io),
- newLintCmd(io),
- newRunCmd(io),
- newTranspileCmd(io),
+ newBugCmd(io),
+ // build
newCleanCmd(io),
- newReplCmd(),
newDocCmd(io),
newEnvCmd(io),
- newBugCmd(io),
+ // fix
newFmtCmd(io),
- // graph
- // vendor -- download deps from the chain in vendor/
- // list -- list packages
- // render -- call render()?
- // publish/release
// generate
- // "vm" -- starts an in-memory chain that can be interacted with?
+ // get
+ // install
+ // list -- list packages
+ newModCmd(io),
+ // work
+ newRunCmd(io),
+ // telemetry
+ newTestCmd(io),
+ newToolCmd(io),
// version -- show cmd/gno, golang versions
+ // vet
)
return cmd
diff --git a/gnovm/cmd/gno/mod.go b/gnovm/cmd/gno/mod.go
index 5479d934ce6..e394684561f 100644
--- a/gnovm/cmd/gno/mod.go
+++ b/gnovm/cmd/gno/mod.go
@@ -25,7 +25,7 @@ func newModCmd(io commands.IO) *commands.Command {
commands.Metadata{
Name: "mod",
ShortUsage: "mod ",
- ShortHelp: "manage gno.mod",
+ ShortHelp: "module maintenance",
},
commands.NewEmptyConfig(),
commands.HelpExec,
@@ -33,8 +33,12 @@ func newModCmd(io commands.IO) *commands.Command {
cmd.AddSubCommands(
newModDownloadCmd(io),
+ // edit
+ newModGraphCmd(io),
newModInitCmd(),
newModTidy(io),
+ // vendor
+ // verify
newModWhy(io),
)
@@ -57,6 +61,21 @@ func newModDownloadCmd(io commands.IO) *commands.Command {
)
}
+func newModGraphCmd(io commands.IO) *commands.Command {
+ cfg := &modGraphCfg{}
+ return commands.NewCommand(
+ commands.Metadata{
+ Name: "graph",
+ ShortUsage: "graph [path]",
+ ShortHelp: "print module requirement graph",
+ },
+ cfg,
+ func(_ context.Context, args []string) error {
+ return execModGraph(cfg, args, io)
+ },
+ )
+}
+
func newModInitCmd() *commands.Command {
return commands.NewCommand(
commands.Metadata{
@@ -140,6 +159,38 @@ func (c *modDownloadCfg) RegisterFlags(fs *flag.FlagSet) {
)
}
+type modGraphCfg struct{}
+
+func (c *modGraphCfg) RegisterFlags(fs *flag.FlagSet) {
+ // /out std
+ // /out remote
+ // /out _test processing
+ // ...
+}
+
+func execModGraph(cfg *modGraphCfg, args []string, io commands.IO) error {
+ // default to current directory if no args provided
+ if len(args) == 0 {
+ args = []string{"."}
+ }
+ if len(args) > 1 {
+ return flag.ErrHelp
+ }
+
+ stdout := io.Out()
+
+ pkgs, err := gnomod.ListPkgs(args[0])
+ if err != nil {
+ return err
+ }
+ for _, pkg := range pkgs {
+ for _, dep := range pkg.Imports {
+ fmt.Fprintf(stdout, "%s %s\n", pkg.Name, dep)
+ }
+ }
+ return nil
+}
+
func execModDownload(cfg *modDownloadCfg, args []string, io commands.IO) error {
if len(args) > 0 {
return flag.ErrHelp
diff --git a/gnovm/cmd/gno/mod_test.go b/gnovm/cmd/gno/mod_test.go
index afce25597cd..e6fdce50a86 100644
--- a/gnovm/cmd/gno/mod_test.go
+++ b/gnovm/cmd/gno/mod_test.go
@@ -210,6 +210,34 @@ func TestModApp(t *testing.T) {
# gno.land/p/demo/avl
valid.gno
+`,
+ },
+
+ // test `gno mod graph`
+ {
+ args: []string{"mod", "graph"},
+ testDir: "../../tests/integ/minimalist_gnomod",
+ simulateExternalRepo: true,
+ stdoutShouldBe: ``,
+ },
+ {
+ args: []string{"mod", "graph"},
+ testDir: "../../tests/integ/valid1",
+ simulateExternalRepo: true,
+ stdoutShouldBe: ``,
+ },
+ {
+ args: []string{"mod", "graph"},
+ testDir: "../../tests/integ/valid2",
+ simulateExternalRepo: true,
+ stdoutShouldBe: `gno.land/p/integ/valid gno.land/p/demo/avl
+`,
+ },
+ {
+ args: []string{"mod", "graph"},
+ testDir: "../../tests/integ/require_remote_module",
+ simulateExternalRepo: true,
+ stdoutShouldBe: `gno.land/tests/importavl gno.land/p/demo/avl
`,
},
}
diff --git a/gnovm/cmd/gno/run.go b/gnovm/cmd/gno/run.go
index 9a9beac5cd1..489016aa3d4 100644
--- a/gnovm/cmd/gno/run.go
+++ b/gnovm/cmd/gno/run.go
@@ -32,7 +32,7 @@ func newRunCmd(io commands.IO) *commands.Command {
commands.Metadata{
Name: "run",
ShortUsage: "run [flags] [...]",
- ShortHelp: "runs the specified gno files",
+ ShortHelp: "run gno packages",
},
cfg,
func(_ context.Context, args []string) error {
diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go
index ea06b25d8e2..ae67d69bc90 100644
--- a/gnovm/cmd/gno/test.go
+++ b/gnovm/cmd/gno/test.go
@@ -35,7 +35,7 @@ func newTestCmd(io commands.IO) *commands.Command {
commands.Metadata{
Name: "test",
ShortUsage: "test [flags] [...]",
- ShortHelp: "runs the tests for the specified packages",
+ ShortHelp: "test packages",
LongHelp: `Runs the tests for the specified packages.
'gno test' recompiles each package along with any files with names matching the
diff --git a/gnovm/cmd/gno/testdata/lint/bad_import.txtar b/gnovm/cmd/gno/testdata/lint/bad_import.txtar
index e2c0431443c..117a699fa6c 100644
--- a/gnovm/cmd/gno/testdata/lint/bad_import.txtar
+++ b/gnovm/cmd/gno/testdata/lint/bad_import.txtar
@@ -1,6 +1,6 @@
-# testing gno lint command: bad import error
+# testing gno tool lint command: bad import error
-! gno lint ./bad_file.gno
+! gno tool lint ./bad_file.gno
cmp stdout stdout.golden
cmp stderr stderr.golden
diff --git a/gnovm/cmd/gno/testdata/lint/file_error.txtar b/gnovm/cmd/gno/testdata/lint/file_error.txtar
index 4fa50c6da81..f3301b46757 100644
--- a/gnovm/cmd/gno/testdata/lint/file_error.txtar
+++ b/gnovm/cmd/gno/testdata/lint/file_error.txtar
@@ -1,6 +1,6 @@
-# gno lint: test file error
+# gno tool lint: test file error
-! gno lint ./i_have_error_test.gno
+! gno tool lint ./i_have_error_test.gno
cmp stdout stdout.golden
cmp stderr stderr.golden
diff --git a/gnovm/cmd/gno/testdata/lint/no_error.txtar b/gnovm/cmd/gno/testdata/lint/no_error.txtar
index 5dd3b164952..5033e054ac6 100644
--- a/gnovm/cmd/gno/testdata/lint/no_error.txtar
+++ b/gnovm/cmd/gno/testdata/lint/no_error.txtar
@@ -1,6 +1,6 @@
-# testing simple gno lint command with any error
+# testing simple gno tool lint command with any error
-gno lint ./good_file.gno
+gno tool lint ./good_file.gno
cmp stdout stdout.golden
cmp stdout stderr.golden
diff --git a/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar b/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar
index b5a046a7095..4564f2d9fff 100644
--- a/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar
+++ b/gnovm/cmd/gno/testdata/lint/no_gnomod.txtar
@@ -1,6 +1,6 @@
-# gno lint: no gnomod
+# gno tool lint: no gnomod
-! gno lint .
+! gno tool lint .
cmp stdout stdout.golden
cmp stderr stderr.golden
diff --git a/gnovm/cmd/gno/testdata/lint/not_declared.txtar b/gnovm/cmd/gno/testdata/lint/not_declared.txtar
index ac56b27e0df..3c3c304797e 100644
--- a/gnovm/cmd/gno/testdata/lint/not_declared.txtar
+++ b/gnovm/cmd/gno/testdata/lint/not_declared.txtar
@@ -1,6 +1,6 @@
-# testing gno lint command: not declared error
+# testing gno tool lint command: not declared error
-! gno lint ./bad_file.gno
+! gno tool lint ./bad_file.gno
cmp stdout stdout.golden
cmp stderr stderr.golden
diff --git a/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar
index 145fe796c09..0b41319a190 100644
--- a/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar
+++ b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_build_error.txtar
@@ -1,7 +1,7 @@
-# Run gno transpile with -gobuild flag
+# Run gno tool transpile with -gobuild flag
# The error messages changed sometime in go1.23, so this avoids errors
-! gno transpile -gobuild .
+! gno tool transpile -gobuild .
! stdout .+
stderr '^main.gno:4:6: .*declared and not used'
diff --git a/gnovm/cmd/gno/testdata/transpile/gobuild_flag_parse_error.txtar b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_parse_error.txtar
index 629de2b7f3d..1008d45adb2 100644
--- a/gnovm/cmd/gno/testdata/transpile/gobuild_flag_parse_error.txtar
+++ b/gnovm/cmd/gno/testdata/transpile/gobuild_flag_parse_error.txtar
@@ -1,6 +1,6 @@
-# Run gno transpile with -gobuild flag on file with parse error
+# Run gno tool transpile with -gobuild flag on file with parse error
-! gno transpile -gobuild .
+! gno tool transpile -gobuild .
! stdout .+
stderr '^main.gno:3:1: expected declaration, found invalid$'
diff --git a/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar b/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar
index 0c51012feb7..23e9e805fe5 100644
--- a/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar
+++ b/gnovm/cmd/gno/testdata/transpile/invalid_import.txtar
@@ -1,6 +1,6 @@
-# Run gno transpile with gno files with an invalid import path
+# Run gno tool transpile with gno files with an invalid import path
-! gno transpile .
+! gno tool transpile .
! stdout .+
stderr '^main.gno:5:2: import "xxx" does not exist$'
diff --git a/gnovm/cmd/gno/testdata/transpile/no_args.txtar b/gnovm/cmd/gno/testdata/transpile/no_args.txtar
index 6d8592d4e3b..30ea24fbf73 100644
--- a/gnovm/cmd/gno/testdata/transpile/no_args.txtar
+++ b/gnovm/cmd/gno/testdata/transpile/no_args.txtar
@@ -1,6 +1,6 @@
-# Run gno transpile without args
+# Run gno tool transpile without args
-! gno transpile
+! gno tool transpile
! stdout .+
stderr 'USAGE'
diff --git a/gnovm/cmd/gno/testdata/transpile/parse_error.txtar b/gnovm/cmd/gno/testdata/transpile/parse_error.txtar
index b94b86992af..c19276ef273 100644
--- a/gnovm/cmd/gno/testdata/transpile/parse_error.txtar
+++ b/gnovm/cmd/gno/testdata/transpile/parse_error.txtar
@@ -1,6 +1,6 @@
-# Run gno transpile with gno files with parse errors
+# Run gno tool transpile with gno files with parse errors
-! gno transpile .
+! gno tool transpile .
! stdout .+
stderr '^main.gno:3:1: expected declaration, found invalid$'
diff --git a/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar b/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar
index 2bd1841d2b4..f9ccd8d8b53 100644
--- a/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar
+++ b/gnovm/cmd/gno/testdata/transpile/valid_empty_dir.txtar
@@ -1,6 +1,6 @@
-# Run gno transpile on an empty dir
+# Run gno tool transpile on an empty dir
-gno transpile .
+gno tool transpile .
! stdout .+
! stderr .+
diff --git a/gnovm/cmd/gno/testdata/transpile/valid_gobuild_file.txtar b/gnovm/cmd/gno/testdata/transpile/valid_gobuild_file.txtar
index 40bb1ecb98a..8e95c5994d0 100644
--- a/gnovm/cmd/gno/testdata/transpile/valid_gobuild_file.txtar
+++ b/gnovm/cmd/gno/testdata/transpile/valid_gobuild_file.txtar
@@ -1,6 +1,6 @@
-# Run gno transpile with -gobuild flag on an individual file
+# Run gno tool transpile with -gobuild flag on an individual file
-gno transpile -gobuild -v main.gno
+gno tool transpile -gobuild -v main.gno
! stdout .+
cmp stderr stderr.golden
diff --git a/gnovm/cmd/gno/testdata/transpile/valid_gobuild_flag.txtar b/gnovm/cmd/gno/testdata/transpile/valid_gobuild_flag.txtar
index 2eacfb9de60..72ca7a4f2f4 100644
--- a/gnovm/cmd/gno/testdata/transpile/valid_gobuild_flag.txtar
+++ b/gnovm/cmd/gno/testdata/transpile/valid_gobuild_flag.txtar
@@ -1,6 +1,6 @@
-# Run gno transpile with -gobuild flag
+# Run gno tool transpile with -gobuild flag
-gno transpile -gobuild -v .
+gno tool transpile -gobuild -v .
! stdout .+
cmp stderr stderr.golden
@@ -12,7 +12,7 @@ cmp sub/sub.gno.gen.go sub/sub.gno.gen.go.golden
rm mai.gno.gen.gosub/sub.gno.gen.go
# Re-try, but use an absolute path.
-gno transpile -gobuild -v $WORK
+gno tool transpile -gobuild -v $WORK
! stdout .+
cmpenv stderr stderr2.golden
diff --git a/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar b/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar
index b1a63890f46..62953e96fb7 100644
--- a/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar
+++ b/gnovm/cmd/gno/testdata/transpile/valid_output_flag.txtar
@@ -1,6 +1,6 @@
-# Run gno transpile with valid gno files, using the -output flag.
+# Run gno tool transpile with valid gno files, using the -output flag.
-gno transpile -v -output directory/hello/ .
+gno tool transpile -v -output directory/hello/ .
! stdout .+
cmp stderr stderr1.golden
@@ -9,7 +9,7 @@ exists directory/hello/main.gno.gen.go
rm directory
# Try running using the absolute path to the directory.
-gno transpile -v -output directory/hello $WORK
+gno tool transpile -v -output directory/hello $WORK
! stdout .+
cmpenv stderr stderr2.golden
@@ -20,7 +20,7 @@ rm directory
# Try running in subdirectory, using a "relative non-local path." (ie. has "../")
mkdir subdir
cd subdir
-gno transpile -v -output hello ..
+gno tool transpile -v -output hello ..
! stdout .+
cmpenv stderr ../stderr3.golden
diff --git a/gnovm/cmd/gno/testdata/transpile/valid_output_gobuild.txtar b/gnovm/cmd/gno/testdata/transpile/valid_output_gobuild.txtar
index 3540e865f3e..c948e53ebb3 100644
--- a/gnovm/cmd/gno/testdata/transpile/valid_output_gobuild.txtar
+++ b/gnovm/cmd/gno/testdata/transpile/valid_output_gobuild.txtar
@@ -1,6 +1,6 @@
-# Run gno transpile with valid gno files, using the -output and -gobuild flags together.
+# Run gno tool transpile with valid gno files, using the -output and -gobuild flags together.
-gno transpile -v -output directory/hello/ -gobuild .
+gno tool transpile -v -output directory/hello/ -gobuild .
! stdout .+
cmp stderr stderr1.golden
@@ -9,7 +9,7 @@ exists directory/hello/main.gno.gen.go
rm directory
# Try running using the absolute path to the directory.
-gno transpile -v -output directory/hello -gobuild $WORK
+gno tool transpile -v -output directory/hello -gobuild $WORK
! stdout .+
cmpenv stderr stderr2.golden
@@ -20,7 +20,7 @@ rm directory
# Try running in subdirectory, using a "relative non-local path." (ie. has "../")
mkdir subdir
cd subdir
-gno transpile -v -output hello -gobuild ..
+gno tool transpile -v -output hello -gobuild ..
! stdout .+
cmpenv stderr ../stderr3.golden
diff --git a/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar
index 86cc6f12f7a..906c43cc41b 100644
--- a/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar
+++ b/gnovm/cmd/gno/testdata/transpile/valid_transpile_file.txtar
@@ -1,8 +1,8 @@
-# Run gno transpile with an individual file.
+# Run gno tool transpile with an individual file.
# Running transpile on the current directory should only precompile
# main.gno.
-gno transpile -v .
+gno tool transpile -v .
! stdout .+
stderr ^\.$
@@ -12,7 +12,7 @@ exists main.gno.gen.go
rm main.gno.gen.go
# Running it using individual filenames should precompile hello_test.gno, as well.
-gno transpile -v main.gno hello_test.gno
+gno tool transpile -v main.gno hello_test.gno
! stdout .+
cmp stderr transpile-files-stderr.golden
diff --git a/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar
index 2a24423598a..74923b9846a 100644
--- a/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar
+++ b/gnovm/cmd/gno/testdata/transpile/valid_transpile_package.txtar
@@ -1,6 +1,6 @@
-# Run gno transpile with valid gno files
+# Run gno tool transpile with valid gno files
-gno transpile .
+gno tool transpile .
! stdout .+
! stderr .+
diff --git a/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar b/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar
index a765ab5093b..e0c42d986e4 100644
--- a/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar
+++ b/gnovm/cmd/gno/testdata/transpile/valid_transpile_tree.txtar
@@ -1,7 +1,7 @@
-# Run gno transpile with dependencies
+# Run gno tool transpile with dependencies
env GNOROOT=$WORK
-gno transpile -v ./examples
+gno tool transpile -v ./examples
! stdout .+
cmpenv stderr stderr.golden
diff --git a/gnovm/cmd/gno/tool.go b/gnovm/cmd/gno/tool.go
new file mode 100644
index 00000000000..0e4f7ff51d7
--- /dev/null
+++ b/gnovm/cmd/gno/tool.go
@@ -0,0 +1,39 @@
+package main
+
+import (
+ "github.com/gnolang/gno/tm2/pkg/commands"
+)
+
+func newToolCmd(io commands.IO) *commands.Command {
+ cmd := commands.NewCommand(
+ commands.Metadata{
+ Name: "tool",
+ ShortUsage: "gno tool command [args...]",
+ ShortHelp: "run specified gno tool",
+ },
+ commands.NewEmptyConfig(),
+ commands.HelpExec,
+ )
+
+ cmd.AddSubCommands(
+ // go equivalent commands:
+ //
+ // compile
+ // transpile
+ // pprof
+ // trace
+ // vet
+
+ // gno specific commands:
+ //
+ // ast
+ newLintCmd(io),
+ // publish/release
+ // render -- call render()?
+ newReplCmd(),
+ newTranspileCmd(io),
+ // "vm" -- starts an in-memory chain that can be interacted with?
+ )
+
+ return cmd
+}
diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/tool_lint.go
similarity index 100%
rename from gnovm/cmd/gno/lint.go
rename to gnovm/cmd/gno/tool_lint.go
diff --git a/gnovm/cmd/gno/lint_test.go b/gnovm/cmd/gno/tool_lint_test.go
similarity index 70%
rename from gnovm/cmd/gno/lint_test.go
rename to gnovm/cmd/gno/tool_lint_test.go
index 4589fc55f92..85b625fa367 100644
--- a/gnovm/cmd/gno/lint_test.go
+++ b/gnovm/cmd/gno/tool_lint_test.go
@@ -8,26 +8,26 @@ import (
func TestLintApp(t *testing.T) {
tc := []testMainCase{
{
- args: []string{"lint"},
+ args: []string{"tool", "lint"},
errShouldBe: "flag: help requested",
}, {
- args: []string{"lint", "../../tests/integ/run_main/"},
+ args: []string{"tool", "lint", "../../tests/integ/run_main/"},
stderrShouldContain: "./../../tests/integ/run_main: gno.mod file not found in current or any parent directory (code=1)",
errShouldBe: "exit code: 1",
}, {
- args: []string{"lint", "../../tests/integ/undefined_variable_test/undefined_variables_test.gno"},
+ args: []string{"tool", "lint", "../../tests/integ/undefined_variable_test/undefined_variables_test.gno"},
stderrShouldContain: "undefined_variables_test.gno:6:28: name toto not declared (code=2)",
errShouldBe: "exit code: 1",
}, {
- args: []string{"lint", "../../tests/integ/package_not_declared/main.gno"},
+ args: []string{"tool", "lint", "../../tests/integ/package_not_declared/main.gno"},
stderrShouldContain: "main.gno:4:2: name fmt not declared (code=2)",
errShouldBe: "exit code: 1",
}, {
- args: []string{"lint", "../../tests/integ/several-lint-errors/main.gno"},
+ args: []string{"tool", "lint", "../../tests/integ/several-lint-errors/main.gno"},
stderrShouldContain: "../../tests/integ/several-lint-errors/main.gno:5:5: expected ';', found example (code=2)\n../../tests/integ/several-lint-errors/main.gno:6",
errShouldBe: "exit code: 1",
}, {
- args: []string{"lint", "../../tests/integ/several-files-multiple-errors/main.gno"},
+ args: []string{"tool", "lint", "../../tests/integ/several-files-multiple-errors/main.gno"},
stderrShouldContain: func() string {
lines := []string{
"../../tests/integ/several-files-multiple-errors/file2.gno:3:5: expected 'IDENT', found '{' (code=2)",
@@ -39,17 +39,17 @@ func TestLintApp(t *testing.T) {
}(),
errShouldBe: "exit code: 1",
}, {
- args: []string{"lint", "../../tests/integ/minimalist_gnomod/"},
+ args: []string{"tool", "lint", "../../tests/integ/minimalist_gnomod/"},
// TODO: raise an error because there is a gno.mod, but no .gno files
}, {
- args: []string{"lint", "../../tests/integ/invalid_module_name/"},
+ args: []string{"tool", "lint", "../../tests/integ/invalid_module_name/"},
// TODO: raise an error because gno.mod is invalid
}, {
- args: []string{"lint", "../../tests/integ/invalid_gno_file/"},
+ args: []string{"tool", "lint", "../../tests/integ/invalid_gno_file/"},
stderrShouldContain: "../../tests/integ/invalid_gno_file/invalid.gno:1:1: expected 'package', found packag (code=2)",
errShouldBe: "exit code: 1",
}, {
- args: []string{"lint", "../../tests/integ/typecheck_missing_return/"},
+ args: []string{"tool", "lint", "../../tests/integ/typecheck_missing_return/"},
stderrShouldContain: "../../tests/integ/typecheck_missing_return/main.gno:5:1: missing return (code=4)",
errShouldBe: "exit code: 1",
},
diff --git a/gnovm/cmd/gno/repl.go b/gnovm/cmd/gno/tool_repl.go
similarity index 100%
rename from gnovm/cmd/gno/repl.go
rename to gnovm/cmd/gno/tool_repl.go
diff --git a/gnovm/cmd/gno/repl_test.go b/gnovm/cmd/gno/tool_repl_test.go
similarity index 100%
rename from gnovm/cmd/gno/repl_test.go
rename to gnovm/cmd/gno/tool_repl_test.go
diff --git a/gnovm/cmd/gno/transpile.go b/gnovm/cmd/gno/tool_transpile.go
similarity index 100%
rename from gnovm/cmd/gno/transpile.go
rename to gnovm/cmd/gno/tool_transpile.go
diff --git a/gnovm/cmd/gno/transpile_test.go b/gnovm/cmd/gno/tool_transpile_test.go
similarity index 100%
rename from gnovm/cmd/gno/transpile_test.go
rename to gnovm/cmd/gno/tool_transpile_test.go
diff --git a/gnovm/pkg/gnolang/fuzz_test.go b/gnovm/pkg/gnolang/fuzz_test.go
index 977c7453b90..f19c35a3da1 100644
--- a/gnovm/pkg/gnolang/fuzz_test.go
+++ b/gnovm/pkg/gnolang/fuzz_test.go
@@ -50,8 +50,8 @@ func FuzzConvertUntypedBigdecToFloat(f *testing.F) {
func FuzzParseFile(f *testing.F) {
// 1. Add the corpra.
- parseFileDir := filepath.Join("testdata", "corpra", "parsefile")
- paths, err := filepath.Glob(filepath.Join(parseFileDir, "*.go"))
+ parseFileDir := filepath.Join("testdata", "corpora", "parsefile")
+ paths, err := filepath.Glob(filepath.Join(parseFileDir, "*.go_fuzz"))
if err != nil {
f.Fatal(err)
}
diff --git a/gnovm/pkg/gnolang/internal/softfloat/copy.sh b/gnovm/pkg/gnolang/internal/softfloat/copy.sh
deleted file mode 100644
index 6d2a8f80462..00000000000
--- a/gnovm/pkg/gnolang/internal/softfloat/copy.sh
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/sh
-
-# softfloat64.go:
-# - add header
-# - change package name
-cat > runtime_softfloat64.go << EOF
-// Code generated by copy.sh. DO NOT EDIT.
-// This file is copied from \$GOROOT/src/runtime/softfloat64.go.
-// It is the software floating point implementation used by the Go runtime.
-
-EOF
-cat "$GOROOT/src/runtime/softfloat64.go" >> ./runtime_softfloat64.go
-sed -i 's/^package runtime$/package softfloat/' runtime_softfloat64.go
-
-# softfloat64_test.go:
-# - add header
-# - change package name
-# - change import to right package
-# - change GOARCH to runtime.GOARCH, and import the "runtime" package
-cat > runtime_softfloat64_test.go << EOF
-// Code generated by copy.sh. DO NOT EDIT.
-// This file is copied from \$GOROOT/src/runtime/softfloat64_test.go.
-// It is the tests for the software floating point implementation
-// used by the Go runtime.
-
-EOF
-cat "$GOROOT/src/runtime/softfloat64_test.go" >> ./runtime_softfloat64_test.go
-sed -i 's/^package runtime_test$/package softfloat_test/
-s#^\t\. "runtime"$#\t. "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat"#
-s/GOARCH/runtime.GOARCH/g
-16a\
- "runtime"' runtime_softfloat64_test.go
\ No newline at end of file
diff --git a/gnovm/pkg/gnolang/internal/softfloat/gen/main.go b/gnovm/pkg/gnolang/internal/softfloat/gen/main.go
new file mode 100644
index 00000000000..7c89ff9b5a9
--- /dev/null
+++ b/gnovm/pkg/gnolang/internal/softfloat/gen/main.go
@@ -0,0 +1,116 @@
+package main
+
+import (
+ "errors"
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+)
+
+func main() {
+ // Process softfloat64.go file
+ processSoftFloat64File()
+
+ // Process softfloat64_test.go file
+ processSoftFloat64TestFile()
+
+ // Run mvdan.cc/gofumpt
+ gofumpt()
+
+ fmt.Println("Files processed successfully.")
+}
+
+func processSoftFloat64File() {
+ // Read source file
+ content, err := os.ReadFile(fmt.Sprintf("%s/src/runtime/softfloat64.go", runtime.GOROOT()))
+ if err != nil {
+ log.Fatal("Error reading source file:", err)
+ }
+
+ // Prepare header
+ header := `// Code generated by github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat/gen. DO NOT EDIT.
+// This file is copied from $GOROOT/src/runtime/softfloat64.go.
+// It is the software floating point implementation used by the Go runtime.
+
+`
+
+ // Combine header with content
+ newContent := header + string(content)
+
+ // Replace package name
+ newContent = strings.Replace(newContent, "package runtime", "package softfloat", 1)
+
+ // Write to destination file
+ err = os.WriteFile("runtime_softfloat64.go", []byte(newContent), 0o644)
+ if err != nil {
+ log.Fatal("Error writing to destination file:", err)
+ }
+}
+
+func processSoftFloat64TestFile() {
+ // Read source test file
+ content, err := os.ReadFile(fmt.Sprintf("%s/src/runtime/softfloat64_test.go", runtime.GOROOT()))
+ if err != nil {
+ log.Fatal("Error reading source test file:", err)
+ }
+
+ // Prepare header
+ header := `// Code generated by github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat/gen. DO NOT EDIT.
+// This file is copied from $GOROOT/src/runtime/softfloat64_test.go.
+// It is the tests for the software floating point implementation
+// used by the Go runtime.
+
+`
+
+ // Combine header with content
+ newContent := header + string(content)
+
+ // Replace package name and imports
+ newContent = strings.Replace(newContent, "package runtime_test", "package softfloat_test", 1)
+ newContent = strings.Replace(newContent, "\t. \"runtime\"", "\t\"runtime\"", 1)
+ newContent = strings.Replace(newContent, "GOARCH", "runtime.GOARCH", 1)
+
+ newContent = strings.Replace(newContent, "import (", "import (\n\t. \"github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat\"", 1)
+
+ // Write to destination file
+ err = os.WriteFile("runtime_softfloat64_test.go", []byte(newContent), 0o644)
+ if err != nil {
+ log.Fatal("Error writing to destination test file:", err)
+ }
+}
+
+func gitRoot() (string, error) {
+ wd, err := os.Getwd()
+ if err != nil {
+ return "", err
+ }
+ p := wd
+ for {
+ if s, e := os.Stat(filepath.Join(p, ".git")); e == nil && s.IsDir() {
+ return p, nil
+ }
+
+ if strings.HasSuffix(p, string(filepath.Separator)) {
+ return "", errors.New("root git not found")
+ }
+
+ p = filepath.Dir(p)
+ }
+}
+
+func gofumpt() {
+ rootPath, err := gitRoot()
+ if err != nil {
+ log.Fatal("error finding git root:", err)
+ }
+
+ cmd := exec.Command("go", "run", "-modfile", filepath.Join(strings.TrimSpace(rootPath), "misc/devdeps/go.mod"), "mvdan.cc/gofumpt", "-w", ".")
+ _, err = cmd.Output()
+ if err != nil {
+ log.Fatal("error gofumpt:", err)
+ }
+}
diff --git a/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go
index cf2ad5afd8a..7623b9c2077 100644
--- a/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go
+++ b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64.go
@@ -1,4 +1,4 @@
-// Code generated by copy.sh. DO NOT EDIT.
+// Code generated by github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat/gen. DO NOT EDIT.
// This file is copied from $GOROOT/src/runtime/softfloat64.go.
// It is the software floating point implementation used by the Go runtime.
diff --git a/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go
index c57fe08b0ef..8b5d34650f1 100644
--- a/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go
+++ b/gnovm/pkg/gnolang/internal/softfloat/runtime_softfloat64_test.go
@@ -1,4 +1,4 @@
-// Code generated by copy.sh. DO NOT EDIT.
+// Code generated by github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat/gen. DO NOT EDIT.
// This file is copied from $GOROOT/src/runtime/softfloat64_test.go.
// It is the tests for the software floating point implementation
// used by the Go runtime.
@@ -10,11 +10,11 @@
package softfloat_test
import (
+ . "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat"
"math"
"math/rand"
- . "github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat"
+ "runtime"
"testing"
- "runtime"
)
// turn uint64 op into float64 op
diff --git a/gnovm/pkg/gnolang/internal/softfloat/softfloat.go b/gnovm/pkg/gnolang/internal/softfloat/softfloat.go
index 30f66dff620..89dcd04d8fb 100644
--- a/gnovm/pkg/gnolang/internal/softfloat/softfloat.go
+++ b/gnovm/pkg/gnolang/internal/softfloat/softfloat.go
@@ -17,7 +17,7 @@ package softfloat
// This file mostly exports the functions from runtime_softfloat64.go
-//go:generate sh copy.sh
+//go:generate go run github.com/gnolang/gno/gnovm/pkg/gnolang/internal/softfloat/gen
const (
mask = 0x7FF
diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/a.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/a.go_fuzz
similarity index 100%
rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/a.go
rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/a.go_fuzz
diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/b.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/b.go_fuzz
similarity index 100%
rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/b.go
rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/b.go_fuzz
diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3013.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3013.go_fuzz
similarity index 100%
rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3013.go
rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3013.go_fuzz
diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine.go_fuzz
similarity index 100%
rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine.go
rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine.go_fuzz
diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine2.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine2.go_fuzz
similarity index 100%
rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine2.go
rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine2.go_fuzz
diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine3.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine3.go_fuzz
similarity index 100%
rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine3.go
rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine3.go_fuzz
diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine4.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine4.go_fuzz
similarity index 100%
rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine4.go
rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine4.go_fuzz
diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine5.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine5.go_fuzz
similarity index 100%
rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine5.go
rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine5.go_fuzz
diff --git a/gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine6.go b/gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine6.go_fuzz
similarity index 100%
rename from gnovm/pkg/gnolang/testdata/corpra/parsefile/bug_3014_redefine6.go
rename to gnovm/pkg/gnolang/testdata/corpora/parsefile/bug_3014_redefine6.go_fuzz
diff --git a/gnovm/tests/challenges/unused0.gno b/gnovm/tests/challenges/unused0.gno
index d5b0ff75c17..dc9aa950dec 100644
--- a/gnovm/tests/challenges/unused0.gno
+++ b/gnovm/tests/challenges/unused0.gno
@@ -1,7 +1,7 @@
package main
// NOTE: instead of patching the vm code, this should be handled by an
-// integration of `gno transpile --gobuild`, which uses `go build` under
+// integration of `gno tool transpile --gobuild`, which uses `go build` under
// the hood and already detects unused variables.
func main() {
var x int
diff --git a/tm2/pkg/p2p/switch.go b/tm2/pkg/p2p/switch.go
index 0dd087026dd..7d9e768dd4b 100644
--- a/tm2/pkg/p2p/switch.go
+++ b/tm2/pkg/p2p/switch.go
@@ -1,11 +1,12 @@
package p2p
import (
+ "bytes"
"context"
"crypto/rand"
+ "encoding/binary"
"fmt"
"math"
- "math/big"
"sync"
"time"
@@ -356,7 +357,7 @@ func (sw *MultiplexSwitch) runRedialLoop(ctx context.Context) {
type backoffItem struct {
lastDialTime time.Time
- attempts int
+ attempts uint
}
var (
@@ -482,65 +483,68 @@ func (sw *MultiplexSwitch) runRedialLoop(ctx context.Context) {
}
}
-// calculateBackoff calculates a backoff time,
-// based on the number of attempts and range limits
+// calculateBackoff calculates the backoff interval by exponentiating the base interval
+// by the number of attempts. The returned interval is capped at maxInterval and has a
+// jitter factor applied to it (+/- 10% of interval, max 10 sec).
func calculateBackoff(
- attempts int,
- minTimeout time.Duration,
- maxTimeout time.Duration,
+ attempts uint,
+ baseInterval time.Duration,
+ maxInterval time.Duration,
) time.Duration {
- var (
- minTime = time.Second * 1
- maxTime = time.Second * 60
- multiplier = float64(2) // exponential
+ const (
+ defaultBaseInterval = time.Second * 1
+ defaultMaxInterval = time.Second * 60
)
- // Check the min limit
- if minTimeout > 0 {
- minTime = minTimeout
+ // Sanitize base interval parameter.
+ if baseInterval <= 0 {
+ baseInterval = defaultBaseInterval
}
- // Check the max limit
- if maxTimeout > 0 {
- maxTime = maxTimeout
+ // Sanitize max interval parameter.
+ if maxInterval <= 0 {
+ maxInterval = defaultMaxInterval
}
- // Sanity check the range
- if minTime >= maxTime {
- return maxTime
+ // Calculate the interval by exponentiating the base interval by the number of attempts.
+ interval := baseInterval << attempts
+
+ // Cap the interval to the maximum interval.
+ if interval > maxInterval {
+ interval = maxInterval
}
- // Calculate the backoff duration
- var (
- base = float64(minTime)
- calculated = base * math.Pow(multiplier, float64(attempts))
- )
+ // Below is the code to add a jitter factor to the interval.
+ // Read random bytes into an 8 bytes buffer (size of an int64).
+ var randBytes [8]byte
+ if _, err := rand.Read(randBytes[:]); err != nil {
+ return interval
+ }
- // Attempt to calculate the jitter factor
- n, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
- if err == nil {
- jitterFactor := float64(n.Int64()) / float64(math.MaxInt64) // range [0, 1]
+ // Convert the random bytes to an int64.
+ var randInt64 int64
+ _ = binary.Read(bytes.NewReader(randBytes[:]), binary.NativeEndian, &randInt64)
- calculated = jitterFactor*(calculated-base) + base
- }
+ // Calculate the random jitter multiplier (float between -1 and 1).
+ jitterMultiplier := float64(randInt64) / float64(math.MaxInt64)
- // Prevent overflow for int64 (duration) cast
- if calculated > float64(math.MaxInt64) {
- return maxTime
- }
+ const (
+ maxJitterDuration = 10 * time.Second
+ maxJitterPercentage = 10 // 10%
+ )
- duration := time.Duration(calculated)
+ // Calculate the maximum jitter based on interval percentage.
+ maxJitter := interval * maxJitterPercentage / 100
- // Clamp the duration within bounds
- if duration < minTime {
- return minTime
+ // Cap the maximum jitter to the maximum duration.
+ if maxJitter > maxJitterDuration {
+ maxJitter = maxJitterDuration
}
- if duration > maxTime {
- return maxTime
- }
+ // Calculate the jitter.
+ jitter := time.Duration(float64(maxJitter) * jitterMultiplier)
- return duration
+ return interval + jitter
}
// DialPeers adds the peers to the dial queue for async dialing.
diff --git a/tm2/pkg/p2p/switch_test.go b/tm2/pkg/p2p/switch_test.go
index 19a5db2efa5..cf0a0c41bb5 100644
--- a/tm2/pkg/p2p/switch_test.go
+++ b/tm2/pkg/p2p/switch_test.go
@@ -823,3 +823,70 @@ func TestMultiplexSwitch_DialPeers(t *testing.T) {
}
})
}
+
+func TestCalculateBackoff(t *testing.T) {
+ t.Parallel()
+
+ checkJitterRange := func(t *testing.T, expectedAbs, actual time.Duration) {
+ t.Helper()
+ require.LessOrEqual(t, actual, expectedAbs)
+ require.GreaterOrEqual(t, actual, expectedAbs*-1)
+ }
+
+ // Test that the default jitter factor is 10% of the backoff duration.
+ t.Run("percentage jitter", func(t *testing.T) {
+ t.Parallel()
+
+ for i := 0; i < 1000; i++ {
+ checkJitterRange(t, 100*time.Millisecond, calculateBackoff(0, time.Second, 10*time.Minute)-time.Second)
+ checkJitterRange(t, 200*time.Millisecond, calculateBackoff(1, time.Second, 10*time.Minute)-2*time.Second)
+ checkJitterRange(t, 400*time.Millisecond, calculateBackoff(2, time.Second, 10*time.Minute)-4*time.Second)
+ checkJitterRange(t, 800*time.Millisecond, calculateBackoff(3, time.Second, 10*time.Minute)-8*time.Second)
+ checkJitterRange(t, 1600*time.Millisecond, calculateBackoff(4, time.Second, 10*time.Minute)-16*time.Second)
+ }
+ })
+
+ // Test that the jitter factor is capped at 10 sec.
+ t.Run("capped jitter", func(t *testing.T) {
+ t.Parallel()
+
+ for i := 0; i < 1000; i++ {
+ checkJitterRange(t, 10*time.Second, calculateBackoff(7, time.Second, 10*time.Minute)-128*time.Second)
+ checkJitterRange(t, 10*time.Second, calculateBackoff(10, time.Second, 20*time.Minute)-1024*time.Second)
+ checkJitterRange(t, 10*time.Second, calculateBackoff(20, time.Second, 300*time.Hour)-1048576*time.Second)
+ }
+ })
+
+ // Test that the backoff interval is based on the baseInterval.
+ t.Run("base interval", func(t *testing.T) {
+ t.Parallel()
+
+ for i := 0; i < 1000; i++ {
+ checkJitterRange(t, 4800*time.Millisecond, calculateBackoff(4, 3*time.Second, 10*time.Minute)-48*time.Second)
+ checkJitterRange(t, 8*time.Second, calculateBackoff(3, 10*time.Second, 10*time.Minute)-80*time.Second)
+ checkJitterRange(t, 10*time.Second, calculateBackoff(5, 3*time.Hour, 100*time.Hour)-96*time.Hour)
+ }
+ })
+
+ // Test that the backoff interval is capped at maxInterval +/- jitter factor.
+ t.Run("max interval", func(t *testing.T) {
+ t.Parallel()
+
+ for i := 0; i < 1000; i++ {
+ checkJitterRange(t, 100*time.Millisecond, calculateBackoff(10, 10*time.Hour, time.Second)-time.Second)
+ checkJitterRange(t, 1600*time.Millisecond, calculateBackoff(10, 10*time.Hour, 16*time.Second)-16*time.Second)
+ checkJitterRange(t, 10*time.Second, calculateBackoff(10, 10*time.Hour, 128*time.Second)-128*time.Second)
+ }
+ })
+
+ // Test parameters sanitization for base and max intervals.
+ t.Run("parameters sanitization", func(t *testing.T) {
+ t.Parallel()
+
+ for i := 0; i < 1000; i++ {
+ checkJitterRange(t, 100*time.Millisecond, calculateBackoff(0, -10, -10)-time.Second)
+ checkJitterRange(t, 1600*time.Millisecond, calculateBackoff(4, -10, -10)-16*time.Second)
+ checkJitterRange(t, 10*time.Second, calculateBackoff(7, -10, 10*time.Minute)-128*time.Second)
+ }
+ })
+}