Repository: maragudk/gomponents
Pull request #108 (2022-09-21 09:48:56)

Add generic Map implementation
Instead of waiting for a good time to switch the `Map` implementation, I've decided to just offer two implementations: one for Go versions before 1.18, and one for 1.18 and onwards. This is achieved using build tags in the source files.

This is obviously a breaking change for consumers of this library that use Go 1.18 and onwards.

See #88


Patch:
diff --git a/examples/simple/simple.go b/examples/simple/simple.go
index 93aa28f..0a7138a 100644
--- a/examples/simple/simple.go
+++ b/examples/simple/simple.go
@@ -1,3 +1,6 @@
+//go:build go1.18
+// +build go1.18
+
 package main
 
 import (
@@ -58,8 +61,8 @@ func Navbar(currentPath string, links []PageLink) g.Node {
 		Ul(
 			NavbarLink("/", "Home", currentPath),
 
-			g.Group(g.Map(len(links), func(i int) g.Node {
-				return NavbarLink(links[i].Path, links[i].Name, currentPath)
+			g.Group(g.Map(links, func(pl PageLink) g.Node {
+				return NavbarLink(pl.Path, pl.Name, currentPath)
 			})),
 		),
 
diff --git a/examples/tailwindcss/tailwindcss.go b/examples/tailwindcss/tailwindcss.go
index 2af2d33..39a012e 100644
--- a/examples/tailwindcss/tailwindcss.go
+++ b/examples/tailwindcss/tailwindcss.go
@@ -1,3 +1,6 @@
+//go:build go1.18
+// +build go1.18
+
 package main
 
 import (
@@ -81,8 +84,8 @@ func Navbar(currentPath string, links []PageLink) g.Node {
 				NavbarLink("/", "Home", currentPath == "/"),
 
 				// We can Map custom slices to Nodes
-				g.Group(g.Map(len(links), func(i int) g.Node {
-					return NavbarLink(links[i].Path, links[i].Name, currentPath == links[i].Path)
+				g.Group(g.Map(links, func(pl PageLink) g.Node {
+					return NavbarLink(pl.Path, pl.Name, currentPath == pl.Path)
 				})),
 			),
 		),
diff --git a/go.mod b/go.mod
index 5a1df18..2e147e2 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,3 @@
 module "github.com/maragudk/gomponents"
 
-go 1.15
+go 1.18
diff --git a/gomponents.go b/gomponents.go
index a97691e..ddc3cc3 100644
--- a/gomponents.go
+++ b/gomponents.go
@@ -244,15 +244,6 @@ func Group(children []Node) Node {
 	return group{children: children}
 }
 
-// Map something enumerable to a list of Nodes.
-func Map(length int, cb func(i int) Node) []Node {
-	var nodes []Node
-	for i := 0; i < length; i++ {
-		nodes = append(nodes, cb(i))
-	}
-	return nodes
-}
-
 // If condition is true, return the given Node. Otherwise, return nil.
 // This helper function is good for inlining elements conditionally.
 func If(condition bool, n Node) Node {
diff --git a/gomponents_generic.go b/gomponents_generic.go
new file mode 100644
index 0000000..6e6500b
--- /dev/null
+++ b/gomponents_generic.go
@@ -0,0 +1,13 @@
+//go:build go1.18
+// +build go1.18
+
+package gomponents
+
+// Map a slice of anything to a slice of Nodes.
+func Map[T any](ts []T, cb func(T) Node) []Node {
+	var nodes []Node
+	for _, t := range ts {
+		nodes = append(nodes, cb(t))
+	}
+	return nodes
+}
diff --git a/gomponents_non_generic.go b/gomponents_non_generic.go
new file mode 100644
index 0000000..56570d0
--- /dev/null
+++ b/gomponents_non_generic.go
@@ -0,0 +1,13 @@
+//go:build !go1.18
+// +build !go1.18
+
+package gomponents
+
+// Map something enumerable to a list of Nodes.
+func Map(length int, cb func(i int) Node) []Node {
+	var nodes []Node
+	for i := 0; i < length; i++ {
+		nodes = append(nodes, cb(i))
+	}
+	return nodes
+}


Test patch:
diff --git a/gomponents_generic_test.go b/gomponents_generic_test.go
new file mode 100644
index 0000000..0492ae3
--- /dev/null
+++ b/gomponents_generic_test.go
@@ -0,0 +1,34 @@
+//go:build go1.18
+// +build go1.18
+
+package gomponents_test
+
+import (
+	"os"
+	"testing"
+
+	g "github.com/maragudk/gomponents"
+	"github.com/maragudk/gomponents/internal/assert"
+)
+
+func TestMap(t *testing.T) {
+	t.Run("maps slices to nodes", func(t *testing.T) {
+		items := []string{"hat", "partyhat", "turtlehat"}
+		lis := g.Map(items, func(i string) g.Node {
+			return g.El("li", g.Text(i))
+		})
+
+		list := g.El("ul", lis...)
+
+		assert.Equal(t, `<ul><li>hat</li><li>partyhat</li><li>turtlehat</li></ul>`, list)
+	})
+}
+
+func ExampleMap() {
+	items := []string{"party hat", "super hat"}
+	e := g.El("ul", g.Group(g.Map(items, func(i string) g.Node {
+		return g.El("li", g.Text(i))
+	})))
+	_ = e.Render(os.Stdout)
+	// Output: <ul><li>party hat</li><li>super hat</li></ul>
+}
diff --git a/gomponents_non_generic_test.go b/gomponents_non_generic_test.go
new file mode 100644
index 0000000..4186808
--- /dev/null
+++ b/gomponents_non_generic_test.go
@@ -0,0 +1,34 @@
+//go:build !go1.18
+// +build !go1.18
+
+package gomponents_test
+
+import (
+	"os"
+	"testing"
+
+	g "github.com/maragudk/gomponents"
+	"github.com/maragudk/gomponents/internal/assert"
+)
+
+func TestMap(t *testing.T) {
+	t.Run("maps slices to nodes", func(t *testing.T) {
+		items := []string{"hat", "partyhat", "turtlehat"}
+		lis := g.Map(len(items), func(i int) g.Node {
+			return g.El("li", g.Text(items[i]))
+		})
+
+		list := g.El("ul", lis...)
+
+		assert.Equal(t, `<ul><li>hat</li><li>partyhat</li><li>turtlehat</li></ul>`, list)
+	})
+}
+
+func ExampleMap() {
+	items := []string{"party hat", "super hat"}
+	e := g.El("ul", g.Group(g.Map(len(items), func(i int) g.Node {
+		return g.El("li", g.Text(items[i]))
+	})))
+	_ = e.Render(os.Stdout)
+	// Output: <ul><li>party hat</li><li>super hat</li></ul>
+}
diff --git a/gomponents_test.go b/gomponents_test.go
index d8e9e07..ffde46d 100644
--- a/gomponents_test.go
+++ b/gomponents_test.go
@@ -133,7 +133,7 @@ func TestEl(t *testing.T) {
 		assert.Equal(t, `<div class="hat"><br></div>`, e)
 	})
 
-	t.Run("renders outside if node does not implement placer", func(t *testing.T) {
+	t.Run("renders outside if node does not implement nodeTypeDescriber", func(t *testing.T) {
 		e := g.El("div", outsider{})
 		assert.Equal(t, `<div>outsider</div>`, e)
 	})
@@ -248,28 +248,6 @@ func TestGroup(t *testing.T) {
 	})
 }
 
-func TestMap(t *testing.T) {
-	t.Run("maps slices to nodes", func(t *testing.T) {
-		items := []string{"hat", "partyhat", "turtlehat"}
-		lis := g.Map(len(items), func(i int) g.Node {
-			return g.El("li", g.Text(items[i]))
-		})
-
-		list := g.El("ul", lis...)
-
-		assert.Equal(t, `<ul><li>hat</li><li>partyhat</li><li>turtlehat</li></ul>`, list)
-	})
-}
-
-func ExampleMap() {
-	items := []string{"party hat", "super hat"}
-	e := g.El("ul", g.Group(g.Map(len(items), func(i int) g.Node {
-		return g.El("li", g.Text(items[i]))
-	})))
-	_ = e.Render(os.Stdout)
-	// Output: <ul><li>party hat</li><li>super hat</li></ul>
-}
-
 func TestIf(t *testing.T) {
 	t.Run("returns node if condition is true", func(t *testing.T) {
 		n := g.El("div", g.If(true, g.El("span")))