feat: modernise to Go 1.26 — iterators, slices/maps, EachSeq
Some checks failed
Security Scan / security (push) Successful in 10s
Test / test (push) Failing after 36s

- Add EachSeq[T](iter.Seq[T], fn) for iterator-based template rendering
- Use slices.Collect(maps.Keys()) + slices.Sort for deterministic attr output
- Use strings.SplitSeq in codegen TagToClassName and path parsing
- Use range over int in layout and pipeline loops
- Refresh go.sum

Co-Authored-By: Gemini <noreply@google.com>
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-02-23 05:11:04 +00:00
parent 70980d7105
commit 78d5b45b0c
6 changed files with 37 additions and 21 deletions

View file

@ -54,9 +54,8 @@ func GenerateRegistration(tag, className string) string {
// TagToClassName converts a kebab-case tag to PascalCase class name.
func TagToClassName(tag string) string {
parts := strings.Split(tag, "-")
var b strings.Builder
for _, p := range parts {
for p := range strings.SplitSeq(tag, "-") {
if len(p) > 0 {
b.WriteString(strings.ToUpper(p[:1]))
b.WriteString(p[1:])

15
go.sum
View file

@ -1,14 +1,29 @@
forge.lthn.ai/core/go-i18n v0.0.1 h1:7I2cOv3GCc7MssLny/CAnwz3L7/Y4iqwzrCRQMQ+teA=
forge.lthn.ai/core/go-i18n v0.0.1/go.mod h1:nyiGwZ3jV4h9Yge6mSrKVTo7CI1LI/p3ydI+9jUnMtk=
forge.lthn.ai/core/go-inference v0.0.1 h1:hf5eOzm5sNDifhb0BscMTyKEkB44r2Tv58wakHGvtz4=
forge.lthn.ai/core/go-inference v0.0.1/go.mod h1:pq2JCmbWLHgik0QdAflGb3raJcCGC44xt8rCUtDjFys=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -74,7 +74,7 @@ func (l *Layout) blockID(slot byte) string {
func (l *Layout) Render(ctx *Context) string {
var b strings.Builder
for i := 0; i < len(l.variant); i++ {
for i := range len(l.variant) {
slot := l.variant[i]
children := l.slots[slot]
if len(children) == 0 {

27
node.go
View file

@ -1,7 +1,9 @@
package html
import (
"sort"
"iter"
"maps"
"slices"
"strings"
i18n "forge.lthn.ai/core/go-i18n"
@ -87,11 +89,8 @@ func (n *elNode) Render(ctx *Context) string {
b.WriteString(n.tag)
// Sort attribute keys for deterministic output.
keys := make([]string, 0, len(n.attrs))
for k := range n.attrs {
keys = append(keys, k)
}
sort.Strings(keys)
keys := slices.Collect(maps.Keys(n.attrs))
slices.Sort(keys)
for _, key := range keys {
b.WriteByte(' ')
b.WriteString(key)
@ -106,8 +105,8 @@ func (n *elNode) Render(ctx *Context) string {
return b.String()
}
for _, child := range n.children {
b.WriteString(child.Render(ctx))
for i := range len(n.children) {
b.WriteString(n.children[i].Render(ctx))
}
b.WriteString("</")
@ -233,21 +232,23 @@ func (n *switchNode) Render(ctx *Context) string {
// --- eachNode ---
type eachNode[T any] struct {
items []T
items iter.Seq[T]
fn func(T) Node
}
// Each iterates items and renders each via fn.
func Each[T any](items []T, fn func(T) Node) Node {
return EachSeq(slices.Values(items), fn)
}
// EachSeq iterates an iter.Seq and renders each via fn.
func EachSeq[T any](items iter.Seq[T], fn func(T) Node) Node {
return &eachNode[T]{items: items, fn: fn}
}
func (n *eachNode[T]) Render(ctx *Context) string {
if len(n.items) == 0 {
return ""
}
var b strings.Builder
for _, item := range n.items {
for item := range n.items {
b.WriteString(n.fn(item).Render(ctx))
}
return b.String()

View file

@ -11,12 +11,13 @@ func ParseBlockID(id string) []byte {
// Split on "-" and take every other element (the slot letters).
// Format: "X-0" or "X-0-Y-0-Z-0"
parts := strings.Split(id, "-")
var slots []byte
for i := 0; i < len(parts); i += 2 {
if len(parts[i]) == 1 {
slots = append(slots, parts[i][0])
i := 0
for part := range strings.SplitSeq(id, "-") {
if i%2 == 0 && len(part) == 1 {
slots = append(slots, part[0])
}
i++
}
return slots
}

View file

@ -75,7 +75,7 @@ func CompareVariants(r *Responsive, ctx *Context) map[string]float64 {
}
scores := make(map[string]float64)
for i := 0; i < len(imprints); i++ {
for i := range len(imprints) {
for j := i + 1; j < len(imprints); j++ {
key := imprints[i].name + ":" + imprints[j].name
scores[key] = imprints[i].imp.Similar(imprints[j].imp)