From 01ee5a9575f543173670ffab9185851d82498e3a Mon Sep 17 00:00:00 2001
From: Manfred Touron <94029+moul@users.noreply.github.com>
Date: Wed, 18 Sep 2024 10:19:29 +0200
Subject: [PATCH] feat(examples): add p/fqname (#2808)
Extracted from #2551 (also #2516).
Contributors' checklist...
- [ ] Added new tests, or not needed, or not feasible
- [ ] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [ ] Updated the official documentation or not needed
- [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [ ] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
---------
Signed-off-by: moul <94029+moul@users.noreply.github.com>
Co-authored-by: Morgan
---
examples/gno.land/p/demo/fqname/fqname.gno | 72 ++++++++++++++++++
.../gno.land/p/demo/fqname/fqname_test.gno | 74 +++++++++++++++++++
examples/gno.land/p/demo/fqname/gno.mod | 3 +
3 files changed, 149 insertions(+)
create mode 100644 examples/gno.land/p/demo/fqname/fqname.gno
create mode 100644 examples/gno.land/p/demo/fqname/fqname_test.gno
create mode 100644 examples/gno.land/p/demo/fqname/gno.mod
diff --git a/examples/gno.land/p/demo/fqname/fqname.gno b/examples/gno.land/p/demo/fqname/fqname.gno
new file mode 100644
index 00000000000..d28453e5c1b
--- /dev/null
+++ b/examples/gno.land/p/demo/fqname/fqname.gno
@@ -0,0 +1,72 @@
+// Package fqname provides utilities for handling fully qualified identifiers in
+// Gno. A fully qualified identifier typically includes a package path followed
+// by a dot (.) and then the name of a variable, function, type, or other
+// package-level declaration.
+package fqname
+
+import "strings"
+
+// Parse splits a fully qualified identifier into its package path and name
+// components. It handles cases with and without slashes in the package path.
+//
+// pkgpath, name := fqname.Parse("gno.land/p/demo/avl.Tree")
+// ufmt.Sprintf("Package: %s, Name: %s\n", id.Package, id.Name)
+// // Output: Package: gno.land/p/demo/avl, Name: Tree
+func Parse(fqname string) (pkgpath, name string) {
+ // Find the index of the last slash.
+ lastSlashIndex := strings.LastIndex(fqname, "/")
+ if lastSlashIndex == -1 {
+ // No slash found, handle it as a simple package name with dot notation.
+ dotIndex := strings.LastIndex(fqname, ".")
+ if dotIndex == -1 {
+ return fqname, ""
+ }
+ return fqname[:dotIndex], fqname[dotIndex+1:]
+ }
+
+ // Get the part after the last slash.
+ afterSlash := fqname[lastSlashIndex+1:]
+
+ // Check for a dot in the substring after the last slash.
+ dotIndex := strings.Index(afterSlash, ".")
+ if dotIndex == -1 {
+ // No dot found after the last slash
+ return fqname, ""
+ }
+
+ // Split at the dot to separate the base and the suffix.
+ base := fqname[:lastSlashIndex+1+dotIndex]
+ suffix := afterSlash[dotIndex+1:]
+
+ return base, suffix
+}
+
+// Construct a qualified identifier.
+//
+// fqName := fqname.Construct("gno.land/r/demo/foo20", "GRC20")
+// fmt.Println("Fully Qualified Name:", fqName)
+// // Output: gno.land/r/demo/foo20.GRC20
+func Construct(pkgpath, name string) string {
+ // TODO: ensure pkgpath is valid - and as such last part does not contain a dot.
+ if name == "" {
+ return pkgpath
+ }
+ return pkgpath + "." + name
+}
+
+// RenderLink creates a formatted link for a fully qualified identifier.
+// If the package path starts with "gno.land", it converts it to a markdown link.
+// If the domain is different or missing, it returns the input as is.
+func RenderLink(pkgPath, slug string) string {
+ if strings.HasPrefix(pkgPath, "gno.land") {
+ pkgLink := strings.TrimPrefix(pkgPath, "gno.land")
+ if slug != "" {
+ return "[" + pkgPath + "](" + pkgLink + ")." + slug
+ }
+ return "[" + pkgPath + "](" + pkgLink + ")"
+ }
+ if slug != "" {
+ return pkgPath + "." + slug
+ }
+ return pkgPath
+}
diff --git a/examples/gno.land/p/demo/fqname/fqname_test.gno b/examples/gno.land/p/demo/fqname/fqname_test.gno
new file mode 100644
index 00000000000..55a220776be
--- /dev/null
+++ b/examples/gno.land/p/demo/fqname/fqname_test.gno
@@ -0,0 +1,74 @@
+package fqname
+
+import (
+ "testing"
+
+ "gno.land/p/demo/uassert"
+)
+
+func TestParse(t *testing.T) {
+ tests := []struct {
+ input string
+ expectedPkgPath string
+ expectedName string
+ }{
+ {"gno.land/p/demo/avl.Tree", "gno.land/p/demo/avl", "Tree"},
+ {"gno.land/p/demo/avl", "gno.land/p/demo/avl", ""},
+ {"gno.land/p/demo/avl.Tree.Node", "gno.land/p/demo/avl", "Tree.Node"},
+ {"gno.land/p/demo/avl/nested.Package.Func", "gno.land/p/demo/avl/nested", "Package.Func"},
+ {"path/filepath.Split", "path/filepath", "Split"},
+ {"path.Split", "path", "Split"},
+ {"path/filepath", "path/filepath", ""},
+ {"path", "path", ""},
+ {"", "", ""},
+ }
+
+ for _, tt := range tests {
+ pkgpath, name := Parse(tt.input)
+ uassert.Equal(t, tt.expectedPkgPath, pkgpath, "Package path did not match")
+ uassert.Equal(t, tt.expectedName, name, "Name did not match")
+ }
+}
+
+func TestConstruct(t *testing.T) {
+ tests := []struct {
+ pkgpath string
+ name string
+ expected string
+ }{
+ {"gno.land/r/demo/foo20", "GRC20", "gno.land/r/demo/foo20.GRC20"},
+ {"gno.land/r/demo/foo20", "", "gno.land/r/demo/foo20"},
+ {"path", "", "path"},
+ {"path", "Split", "path.Split"},
+ {"path/filepath", "", "path/filepath"},
+ {"path/filepath", "Split", "path/filepath.Split"},
+ {"", "JustName", ".JustName"},
+ {"", "", ""},
+ }
+
+ for _, tt := range tests {
+ result := Construct(tt.pkgpath, tt.name)
+ uassert.Equal(t, tt.expected, result, "Constructed FQName did not match expected")
+ }
+}
+
+func TestRenderLink(t *testing.T) {
+ tests := []struct {
+ pkgPath string
+ slug string
+ expected string
+ }{
+ {"gno.land/p/demo/avl", "Tree", "[gno.land/p/demo/avl](/p/demo/avl).Tree"},
+ {"gno.land/p/demo/avl", "", "[gno.land/p/demo/avl](/p/demo/avl)"},
+ {"github.com/a/b", "C", "github.com/a/b.C"},
+ {"example.com/pkg", "Func", "example.com/pkg.Func"},
+ {"gno.land/r/demo/foo20", "GRC20", "[gno.land/r/demo/foo20](/r/demo/foo20).GRC20"},
+ {"gno.land/r/demo/foo20", "", "[gno.land/r/demo/foo20](/r/demo/foo20)"},
+ {"", "", ""},
+ }
+
+ for _, tt := range tests {
+ result := RenderLink(tt.pkgPath, tt.slug)
+ uassert.Equal(t, tt.expected, result, "Rendered link did not match expected")
+ }
+}
diff --git a/examples/gno.land/p/demo/fqname/gno.mod b/examples/gno.land/p/demo/fqname/gno.mod
new file mode 100644
index 00000000000..1282e262303
--- /dev/null
+++ b/examples/gno.land/p/demo/fqname/gno.mod
@@ -0,0 +1,3 @@
+module gno.land/p/demo/fqname
+
+require gno.land/p/demo/uassert v0.0.0-latest