From 58a7fc78b8e17dc4ce009767afd9066bbaa9328f Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Fri, 21 Jun 2024 15:35:23 +0200 Subject: refactor: use encoding/xml for OpenSearchDescription --- internal/config/config.go | 12 ++++ internal/config/structs.go | 12 ++++ internal/opensearch/opensearch.go | 21 +++++++ internal/server/mux.go | 52 +++++++++--------- internal/server/templates.go | 113 -------------------------------------- 5 files changed, 70 insertions(+), 140 deletions(-) create mode 100644 internal/opensearch/opensearch.go delete mode 100644 internal/server/templates.go (limited to 'internal') 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 -} -- cgit 1.4.1