From 0c474a82d04bf9b0ee061080e9e44f92f05f031d Mon Sep 17 00:00:00 2001 From: Snider Date: Fri, 6 Mar 2026 16:37:53 +0000 Subject: [PATCH] refactor(help): remove html/template dependency All rendering now uses go-html HLCRF layout. Templates directory and template parsing code removed. Co-Authored-By: Virgil --- pkg/help/templates.go | 83 ----------------- pkg/help/templates/404.html | 10 -- pkg/help/templates/base.html | 161 --------------------------------- pkg/help/templates/index.html | 19 ---- pkg/help/templates/search.html | 22 ----- pkg/help/templates/topic.html | 35 ------- pkg/help/templates_test.go | 135 --------------------------- 7 files changed, 465 deletions(-) delete mode 100644 pkg/help/templates/404.html delete mode 100644 pkg/help/templates/base.html delete mode 100644 pkg/help/templates/index.html delete mode 100644 pkg/help/templates/search.html delete mode 100644 pkg/help/templates/topic.html diff --git a/pkg/help/templates.go b/pkg/help/templates.go index fd738af..c3cfc13 100644 --- a/pkg/help/templates.go +++ b/pkg/help/templates.go @@ -3,89 +3,15 @@ package help import ( "cmp" - "embed" - "html/template" - "io" "slices" - "strings" ) -//go:embed templates/*.html -var templateFS embed.FS - -// templateFuncs returns the function map for help templates. -func templateFuncs() template.FuncMap { - return template.FuncMap{ - "renderMarkdown": func(content string) template.HTML { - html, err := RenderMarkdown(content) - if err != nil { - return template.HTML("

Error rendering content.

") - } - return template.HTML(html) //nolint:gosec // trusted content from catalog - }, - "truncate": func(s string, n int) string { - // Strip markdown headings for truncation preview - lines := strings.SplitSeq(s, "\n") - var clean []string - for line := range lines { - trimmed := strings.TrimSpace(line) - if trimmed == "" || strings.HasPrefix(trimmed, "#") { - continue - } - clean = append(clean, trimmed) - } - text := strings.Join(clean, " ") - runes := []rune(text) - if len(runes) <= n { - return text - } - return string(runes[:n]) + "..." - }, - "pluralise": func(count int, singular, plural string) string { - if count == 1 { - return singular - } - return plural - }, - "multiply": func(a, b int) int { - return a * b - }, - "sub": func(a, b int) int { - return a - b - }, - } -} - -// parseTemplates parses the base layout together with a page template. -func parseTemplates(page string) (*template.Template, error) { - return template.New("base.html").Funcs(templateFuncs()).ParseFS( - templateFS, "templates/base.html", "templates/"+page, - ) -} - // topicGroup groups topics under a tag for the index page. type topicGroup struct { Tag string Topics []*Topic } -// indexData holds template data for the index page. -type indexData struct { - Topics []*Topic - Groups []topicGroup -} - -// topicData holds template data for a single topic page. -type topicData struct { - Topic *Topic -} - -// searchData holds template data for the search results page. -type searchData struct { - Query string - Results []*SearchResult -} - // groupTopicsByTag groups topics by their first tag. // Topics without tags are grouped under "other". // Groups are sorted alphabetically by tag name. @@ -118,12 +44,3 @@ func groupTopicsByTag(topics []*Topic) []topicGroup { return result } - -// renderPage renders a named page template into the writer. -func renderPage(w io.Writer, page string, data any) error { - tmpl, err := parseTemplates(page) - if err != nil { - return err - } - return tmpl.Execute(w, data) -} diff --git a/pkg/help/templates/404.html b/pkg/help/templates/404.html deleted file mode 100644 index 7acef70..0000000 --- a/pkg/help/templates/404.html +++ /dev/null @@ -1,10 +0,0 @@ -{{define "title"}}Not Found - Help{{end}} -{{define "content"}} -
-

404

-

Topic not found.

-

- Browse all topics or try searching above. -

-
-{{end}} diff --git a/pkg/help/templates/base.html b/pkg/help/templates/base.html deleted file mode 100644 index 62ce765..0000000 --- a/pkg/help/templates/base.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - {{block "title" .}}Help{{end}} - - {{block "head" .}}{{end}} - - - -
-
- {{block "content" .}}{{end}} -
-
-
-
- go-help · forge.lthn.ai/core/go-help -
-
- - diff --git a/pkg/help/templates/index.html b/pkg/help/templates/index.html deleted file mode 100644 index 7ad6e35..0000000 --- a/pkg/help/templates/index.html +++ /dev/null @@ -1,19 +0,0 @@ -{{define "title"}}Help Topics{{end}} -{{define "content"}} -

Help Topics {{len .Topics}} {{pluralise (len .Topics) "topic" "topics"}}

- -{{if .Groups}} -{{range .Groups}} -

{{.Tag}}

-{{range .Topics}} -
-

{{.Title}}

- {{if .Tags}}
{{range .Tags}}{{.}}{{end}}
{{end}} - {{if .Content}}

{{truncate .Content 120}}

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

No topics available.

-{{end}} -{{end}} diff --git a/pkg/help/templates/search.html b/pkg/help/templates/search.html deleted file mode 100644 index 61a70fa..0000000 --- a/pkg/help/templates/search.html +++ /dev/null @@ -1,22 +0,0 @@ -{{define "title"}}Search: {{.Query}} - Help{{end}} -{{define "search_value"}}{{.Query}}{{end}} -{{define "content"}} -

Search Results

-

- {{if .Results}}Found {{len .Results}} {{pluralise (len .Results) "result" "results"}} for “{{.Query}}”{{else}}No results for “{{.Query}}”{{end}} -

- -{{if .Results}} -{{range .Results}} -
-

{{.Topic.Title}} {{printf "%.1f" .Score}}

- {{if .Snippet}}

{{.Snippet}}

{{end}} - {{if .Topic.Tags}}
{{range .Topic.Tags}}{{.}}{{end}}
{{end}} -
-{{end}} -{{else}} -
-

Try a different search term or browse all topics.

-
-{{end}} -{{end}} diff --git a/pkg/help/templates/topic.html b/pkg/help/templates/topic.html deleted file mode 100644 index 79db4d5..0000000 --- a/pkg/help/templates/topic.html +++ /dev/null @@ -1,35 +0,0 @@ -{{define "title"}}{{.Topic.Title}} - Help{{end}} -{{define "content"}} -
-
- {{if .Topic.Tags}}
{{range .Topic.Tags}}{{.}}{{end}}
{{end}} -
{{renderMarkdown .Topic.Content}}
-
- - -
-{{end}} diff --git a/pkg/help/templates_test.go b/pkg/help/templates_test.go index c3a4f08..f588a58 100644 --- a/pkg/help/templates_test.go +++ b/pkg/help/templates_test.go @@ -2,104 +2,12 @@ package help import ( - "bytes" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestParseTemplates_Good(t *testing.T) { - pages := []string{"index.html", "topic.html", "search.html", "404.html"} - for _, page := range pages { - t.Run(page, func(t *testing.T) { - tmpl, err := parseTemplates(page) - require.NoError(t, err, "template %s should parse without error", page) - assert.NotNil(t, tmpl) - }) - } -} - -func TestRenderPage_Good_Index(t *testing.T) { - topics := []*Topic{ - {ID: "getting-started", Title: "Getting Started", Tags: []string{"intro"}, Content: "Welcome."}, - {ID: "config", Title: "Configuration", Tags: []string{"setup"}, Content: "Config options."}, - } - - data := indexData{ - Topics: topics, - Groups: groupTopicsByTag(topics), - } - - var buf bytes.Buffer - err := renderPage(&buf, "index.html", data) - require.NoError(t, err) - - html := buf.String() - assert.Contains(t, html, "Getting Started") - assert.Contains(t, html, "Configuration") - assert.Contains(t, html, "2 topics") - assert.Contains(t, html, "core help") -} - -func TestRenderPage_Good_Topic(t *testing.T) { - topic := &Topic{ - ID: "getting-started", - Title: "Getting Started", - Content: "# Getting Started\n\nWelcome to the **guide**.\n", - Tags: []string{"intro"}, - Sections: []Section{ - {ID: "overview", Title: "Overview", Level: 2}, - }, - Related: []string{"config"}, - } - - data := topicData{Topic: topic} - - var buf bytes.Buffer - err := renderPage(&buf, "topic.html", data) - require.NoError(t, err) - - html := buf.String() - assert.Contains(t, html, "Getting Started") - assert.Contains(t, html, "guide") - assert.Contains(t, html, "Overview") - assert.Contains(t, html, "config") -} - -func TestRenderPage_Good_Search(t *testing.T) { - data := searchData{ - Query: "install", - Results: []*SearchResult{ - { - Topic: &Topic{ID: "install", Title: "Installation", Tags: []string{"setup"}}, - Score: 12.5, - Snippet: "How to **install** the tool.", - }, - }, - } - - var buf bytes.Buffer - err := renderPage(&buf, "search.html", data) - require.NoError(t, err) - - html := buf.String() - assert.Contains(t, html, "install") - assert.Contains(t, html, "Installation") - assert.Contains(t, html, "1 result") - assert.Contains(t, html, "12.5") -} - -func TestRenderPage_Good_404(t *testing.T) { - var buf bytes.Buffer - err := renderPage(&buf, "404.html", nil) - require.NoError(t, err) - - html := buf.String() - assert.Contains(t, html, "not found") - assert.Contains(t, html, "404") -} - func TestGroupTopicsByTag_Good(t *testing.T) { topics := []*Topic{ {ID: "a", Title: "Alpha", Tags: []string{"setup"}, Order: 2}, @@ -122,46 +30,3 @@ func TestGroupTopicsByTag_Good(t *testing.T) { assert.Equal(t, "Beta", setupGroup.Topics[0].Title) // Order 1 assert.Equal(t, "Alpha", setupGroup.Topics[1].Title) // Order 2 } - -func TestTemplateFuncs_Good(t *testing.T) { - fns := templateFuncs() - - t.Run("truncate short string", func(t *testing.T) { - fn := fns["truncate"].(func(string, int) string) - assert.Equal(t, "hello", fn("hello", 10)) - }) - - t.Run("truncate long string", func(t *testing.T) { - fn := fns["truncate"].(func(string, int) string) - result := fn("hello world this is long", 11) - assert.Equal(t, "hello world...", result) - }) - - t.Run("truncate strips headings", func(t *testing.T) { - fn := fns["truncate"].(func(string, int) string) - result := fn("# Title\n\nSome content here.", 100) - assert.Equal(t, "Some content here.", result) - assert.NotContains(t, result, "#") - }) - - t.Run("pluralise singular", func(t *testing.T) { - fn := fns["pluralise"].(func(int, string, string) string) - assert.Equal(t, "topic", fn(1, "topic", "topics")) - }) - - t.Run("pluralise plural", func(t *testing.T) { - fn := fns["pluralise"].(func(int, string, string) string) - assert.Equal(t, "topics", fn(0, "topic", "topics")) - assert.Equal(t, "topics", fn(5, "topic", "topics")) - }) - - t.Run("multiply", func(t *testing.T) { - fn := fns["multiply"].(func(int, int) int) - assert.Equal(t, 24, fn(4, 6)) - }) - - t.Run("sub", func(t *testing.T) { - fn := fns["sub"].(func(int, int) int) - assert.Equal(t, 2, fn(5, 3)) - }) -}