about summary refs log tree commit diff stats
path: root/internal
diff options
context:
space:
mode:
authorAlan Pearce2024-06-21 15:35:23 +0200
committerAlan Pearce2024-06-21 15:44:12 +0200
commit58a7fc78b8e17dc4ce009767afd9066bbaa9328f (patch)
treee5d4c12584f22d7718aea87540e4e437f1b47528 /internal
parenta1dfc548198a1326e71f1dd70303a5d3441f7a39 (diff)
downloadsearchix-58a7fc78b8e17dc4ce009767afd9066bbaa9328f.tar.lz
searchix-58a7fc78b8e17dc4ce009767afd9066bbaa9328f.tar.zst
searchix-58a7fc78b8e17dc4ce009767afd9066bbaa9328f.zip
refactor: use encoding/xml for OpenSearchDescription
Diffstat (limited to 'internal')
-rw-r--r--internal/config/config.go12
-rw-r--r--internal/config/structs.go12
-rw-r--r--internal/opensearch/opensearch.go21
-rw-r--r--internal/server/mux.go52
-rw-r--r--internal/server/templates.go113
5 files changed, 70 insertions, 140 deletions
diff --git a/internal/config/config.go b/internal/config/config.go
index 83ddd2c..14375d6 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -31,6 +31,18 @@ func (u *URL) UnmarshalText(text []byte) (err error) {
 	return nil
 }
 
+func (u *URL) JoinPath(elems ...string) *URL {
+	return &URL{u.URL.JoinPath(elems...)}
+}
+
+func (u *URL) AddQuery(key, value string) *URL {
+	q := u.URL.Query()
+	q.Add(key, value)
+	u.RawQuery = q.Encode()
+
+	return u
+}
+
 type Duration struct {
 	time.Duration
 }
diff --git a/internal/config/structs.go b/internal/config/structs.go
index 6c6bc13..e6cf20b 100644
--- a/internal/config/structs.go
+++ b/internal/config/structs.go
@@ -4,6 +4,7 @@ package config
 // keep config structs here so that lll ignores the long lines (go doesn't support multi-line struct tags)
 
 import (
+	"fmt"
 	"log/slog"
 )
 
@@ -47,3 +48,14 @@ type Source struct {
 	OutputPath string       `comment:"(Fetcher=channel) Path under ./result symlink to folder containing {options,packages}.json."`
 	Repo       Repository   `comment:"Used to generate declaration/definition links"`
 }
+
+func (source *Source) String() string {
+	switch source.Importer {
+	case Options:
+		return source.Name + " " + source.Importer.String()
+	case Packages:
+		return source.Name
+	default:
+		return fmt.Sprintf("Source(%s)", source.Name)
+	}
+}
diff --git a/internal/opensearch/opensearch.go b/internal/opensearch/opensearch.go
new file mode 100644
index 0000000..a9b3c9c
--- /dev/null
+++ b/internal/opensearch/opensearch.go
@@ -0,0 +1,21 @@
+package opensearch
+
+import (
+	"encoding/xml"
+	"searchix/internal/config"
+)
+
+type Description struct {
+	XMLName     xml.Name `xml:"http://a9.com/-/spec/opensearch/1.1/ OpenSearchDescription"`
+	Description string
+	LongName    string
+	ShortName   string
+	SearchForm  *config.URL `xml:"http://www.mozilla.org/2006/browser/search/ SearchForm"`
+	URL         URL         `xml:"Url"`
+}
+
+type URL struct {
+	Method   string      `xml:"method,attr"`
+	Template *config.URL `xml:"template,attr"`
+	Type     string      `xml:"type,attr"`
+}
diff --git a/internal/server/mux.go b/internal/server/mux.go
index 89ce952..8ca5758 100644
--- a/internal/server/mux.go
+++ b/internal/server/mux.go
@@ -2,9 +2,9 @@ package server
 
 import (
 	"context"
+	"encoding/xml"
 	"fmt"
 	"io"
-	"log"
 	"log/slog"
 	"math"
 	"net/http"
@@ -18,6 +18,7 @@ import (
 	"searchix/internal/components"
 	"searchix/internal/config"
 	search "searchix/internal/index"
+	"searchix/internal/opensearch"
 
 	sentryhttp "github.com/getsentry/sentry-go/http"
 	"github.com/osdevisnot/sorvor/pkg/livereload"
@@ -32,8 +33,7 @@ type HTTPError struct {
 }
 
 var (
-	templates TemplateCollection
-	sources   []*config.Source
+	sources []*config.Source
 )
 
 func applyDevModeOverrides(cfg *config.Config) {
@@ -59,7 +59,6 @@ func NewMux(
 	index *search.ReadIndex,
 	liveReload bool,
 ) (*http.ServeMux, error) {
-	var err error
 	if cfg == nil {
 		return nil, errors.New("cfg is nil")
 	}
@@ -69,10 +68,6 @@ func NewMux(
 	sentryHandler := sentryhttp.New(sentryhttp.Options{
 		Repanic: true,
 	})
-	templates, err = loadTemplates()
-	if err != nil {
-		log.Panicf("could not load templates: %v", err)
-	}
 	sortSources(cfg.Importer.Sources)
 
 	errorHandler := createErrorHandler(cfg)
@@ -271,11 +266,6 @@ func NewMux(
 
 	createOpenSearchXMLHandler := func(importerType config.ImporterType) func(http.ResponseWriter, *http.Request) {
 		return func(w http.ResponseWriter, r *http.Request) {
-			type openSearchData struct {
-				BaseURL string
-				Source  *config.Source
-			}
-
 			source := cfg.Importer.Sources[r.PathValue("source")]
 			if source == nil || importerType != source.Importer {
 				errorHandler(w, r, http.StatusText(http.StatusNotFound), http.StatusNotFound)
@@ -285,19 +275,33 @@ func NewMux(
 
 			w.Header().Add("Cache-Control", "max-age=604800")
 			w.Header().Set("Content-Type", "application/opensearchdescription+xml")
-			err := templates["opensearch.xml"].ExecuteTemplate(
-				w,
-				"opensearch.xml.gotmpl",
-				openSearchData{
-					BaseURL: cfg.Web.BaseURL.String(),
-					Source:  source,
+			osd := &opensearch.Description{
+				ShortName:   fmt.Sprintf("Searchix %s", source),
+				LongName:    fmt.Sprintf("Search %s with Searchix", source),
+				Description: fmt.Sprintf("Search %s", source),
+				SearchForm: cfg.Web.BaseURL.JoinPath(
+					source.Importer.String(),
+					source.Key,
+					"search",
+				),
+				URL: opensearch.URL{
+					Method: "get",
+					Type:   "text/html",
+					Template: cfg.Web.BaseURL.JoinPath(
+						source.Importer.String(),
+						source.Key,
+						"search",
+					).AddQuery("query", "{searchTerms}"),
 				},
-			)
+			}
+			enc := xml.NewEncoder(w)
+			enc.Indent("", "    ")
+			err := enc.Encode(osd)
 			if err != nil {
 				// no errorHandler; HTML does not make sense here
 				http.Error(
 					w,
-					fmt.Sprintf("Template render error: %v", err),
+					fmt.Sprintf("OpenSearch XML encoding error: %v", err),
 					http.StatusInternalServerError,
 				)
 			}
@@ -342,12 +346,6 @@ func NewMux(
 					slog.Error("failed to re-hash frontend assets", "error", err)
 				}
 			}
-			if path.Ext(filename) == ".gotmpl" {
-				templates, err = loadTemplates()
-				if err != nil {
-					slog.Error(fmt.Sprintf("could not reload templates: %v", err))
-				}
-			}
 			liveReload.Reload()
 		})
 	}
diff --git a/internal/server/templates.go b/internal/server/templates.go
deleted file mode 100644
index fa95425..0000000
--- a/internal/server/templates.go
+++ /dev/null
@@ -1,113 +0,0 @@
-package server
-
-import (
-	"fmt"
-	"html"
-	"html/template"
-	"io/fs"
-	"log/slog"
-	"path"
-	"regexp"
-	"searchix/frontend"
-	"searchix/internal/config"
-	"searchix/internal/nix"
-	"strings"
-
-	"github.com/pkg/errors"
-	"github.com/yuin/goldmark"
-	"github.com/yuin/goldmark/extension"
-)
-
-type TemplateCollection map[string]*template.Template
-
-var (
-	md = goldmark.New(
-		goldmark.WithExtensions(extension.NewLinkify()),
-	)
-	firstSentenceRegexp = regexp.MustCompile(`^.*?\.[[:space:]]`)
-)
-var templateFuncs = template.FuncMap{
-	"firstSentence": func(input nix.Markdown) nix.Markdown {
-		if fs := firstSentenceRegexp.FindString(string(input)); fs != "" {
-			return nix.Markdown(fs)
-		}
-
-		return input
-	},
-	"markdown": func(input nix.Markdown) template.HTML {
-		var out strings.Builder
-		err := md.Convert([]byte(input), &out)
-		if err != nil {
-			slog.Warn(fmt.Sprintf("markdown conversion failed: %v", err))
-
-			return template.HTML(html.EscapeString(err.Error())) // #nosec G203 -- duh?
-		}
-
-		return template.HTML(out.String()) // #nosec G203
-	},
-	"sourceNameAndType": func(source config.Source) (string, error) {
-		switch source.Importer {
-		case config.Options:
-			return source.Name + " " + source.Importer.String(), nil
-		case config.Packages:
-			return source.Name, nil
-		default:
-			return "", errors.Errorf("unknown source importer type %s", source.Importer.String())
-		}
-	},
-	"sourceName": func(input string) string {
-		switch input {
-		case "nixos":
-			return "NixOS"
-		case "darwin":
-			return "Darwin"
-		case "home-manager":
-			return "Home Manager"
-		}
-
-		return input
-	},
-}
-
-func loadTemplate(
-	layoutFile string,
-	filenames ...string,
-) (*template.Template, error) {
-	tpl := template.New("index.gotmpl")
-
-	tpl.Funcs(templateFuncs)
-	_, err := tpl.ParseFS(frontend.Files, layoutFile)
-	if err != nil {
-		return nil, errors.WithMessage(err, "could not parse layout template")
-	}
-
-	if len(filenames) > 0 {
-		_, err = tpl.ParseFS(frontend.Files, filenames...)
-		if err != nil {
-			return nil, errors.WithMessage(err, "could not parse template")
-		}
-	}
-
-	return tpl, nil
-}
-
-func loadTemplates() (TemplateCollection, error) {
-	templateDir := "templates"
-	templates := make(TemplateCollection, 0)
-
-	glob := path.Join(templateDir, "*.gotmpl")
-	templatePaths, err := fs.Glob(frontend.Files, glob)
-	if err != nil {
-		return nil, errors.WithMessage(err, "could not glob main templates")
-	}
-	for _, fullname := range templatePaths {
-		tpl, err := loadTemplate(fullname)
-		if err != nil {
-			return nil, err
-		}
-		name, _ := strings.CutSuffix(path.Base(fullname), ".gotmpl")
-		templates[name] = tpl
-	}
-
-	return templates, nil
-}