From d40c0e188a7fe1b36887f59c4a9958faa81b3d44 Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Sat, 8 Jun 2024 20:31:47 +0200 Subject: feat: add detail pages for packages/options --- frontend/static/style.css | 4 -- frontend/templates/blocks/option.gotmpl | 48 ++++++++++++++++++++ frontend/templates/blocks/package.gotmpl | 69 ++++++++++++++++++++++++++++ internal/config/importer-type.go | 11 +++++ internal/index/search.go | 77 +++++++++++++++++++++----------- internal/nix/importable.go | 9 +++- internal/server/mux.go | 62 +++++++++++++++++++++++++ 7 files changed, 249 insertions(+), 31 deletions(-) create mode 100644 frontend/templates/blocks/option.gotmpl create mode 100644 frontend/templates/blocks/package.gotmpl diff --git a/frontend/static/style.css b/frontend/static/style.css index ff4cc6e..8d5977f 100644 --- a/frontend/static/style.css +++ b/frontend/static/style.css @@ -92,10 +92,6 @@ pre { padding: 1ch 1.4ch; } -pre:has(> code) { - background: var(--bg); -} - #pagination { display: flex; justify-content: space-between; diff --git a/frontend/templates/blocks/option.gotmpl b/frontend/templates/blocks/option.gotmpl new file mode 100644 index 0000000..2248708 --- /dev/null +++ b/frontend/templates/blocks/option.gotmpl @@ -0,0 +1,48 @@ +{{- define "main" }} + {{- with .Document }} +

{{ .Name }}

+ {{ markdown .Description }} +
+ {{- with .Type }} +
Type
+
{{ . }}
+ {{- end }} + {{- with .Default }} + {{- if or .Text .Markdown }} +
Default
+
+ {{- if .Markdown }} + {{ markdown .Markdown }} + {{- else }} +
{{ .Text }}
+ {{- end }} +
+ {{- end }} + {{- end }} + {{- with .Example }} + {{- if or .Text .Markdown }} +
Example
+
+ {{- if .Markdown }} + {{ markdown .Markdown }} + {{- else }} +
{{ .Text }}
+ {{- end }} +
+ {{- end }} + {{- end }} + {{- with .RelatedPackages }} +
Related Packages
+
{{ . }}
+ {{- end }} + {{- with .Declarations }} +
Declared
+ {{- range . }} +
+ {{ .Name }} +
+ {{- end }} + {{- end }} +
+ {{- end }} +{{- end }} diff --git a/frontend/templates/blocks/package.gotmpl b/frontend/templates/blocks/package.gotmpl new file mode 100644 index 0000000..a42a8b1 --- /dev/null +++ b/frontend/templates/blocks/package.gotmpl @@ -0,0 +1,69 @@ +{{- define "main" }} + {{- with .Document }} +

+ {{- if .Broken }} + {{ .Attribute }} + {{- else }} + {{ .Attribute }} + {{- end }} +

+ {{- if .LongDescription }} + {{ markdown .LongDescription }} + {{- else }} +

{{ .Description }}

+ {{- end }} +
+ {{- with .MainProgram }} +
Main Program
+
+ {{ . }} +
+ {{- end }} + {{- with .Homepages }} +
Homepage
+
+ {{- range . }} + {{ . }} + {{- end }} +
+ {{- end }} + {{- with .Version }} +
Version
+
{{ . }}
+ {{- end }} + {{- with .Licenses }} +
License
+
+ {{- range . }} + {{- if .URL }} + {{ or .FullName .Name }} + {{- else }} + {{ or .FullName .Name }} + {{- end }} + {{- with .AppendixURL }} + Appendix + {{- end }} + {{- end }} +
+ {{- end }} + {{- with .Maintainers }} +
Maintainer{{ if gt (len .) 1 }}s{{ end }}
+
+ {{- range . }} + {{- if .Github }} + {{ .Name }} + {{- else }} + {{ .Name }} + {{- end }} + {{- end }} +
+ {{- end }} + {{- with .Definition }} +
Defined
+
+ Source +
+ {{- end }} +
+ {{- end }} +{{- end }} diff --git a/internal/config/importer-type.go b/internal/config/importer-type.go index 8f64d58..9bd50b6 100644 --- a/internal/config/importer-type.go +++ b/internal/config/importer-type.go @@ -25,6 +25,17 @@ func (i ImporterType) String() string { return fmt.Sprintf("Type(%d)", i) } +func (i ImporterType) Singular() string { + switch i { + case Packages: + return "package" + case Options: + return "option" + } + + return fmt.Sprintf("Type(%d)", i) +} + func ParseImporterType(name string) (ImporterType, error) { switch strcase.KebabCase(name) { case "packages": diff --git a/internal/index/search.go b/internal/index/search.go index 47f8ac1..3198afb 100644 --- a/internal/index/search.go +++ b/internal/index/search.go @@ -82,6 +82,42 @@ func setField( return q } +func (index *ReadIndex) search( + ctx context.Context, + request *bleve.SearchRequest, +) (*Result, error) { + request.Fields = []string{"_data"} + + bleveResult, err := index.index.SearchInContext(ctx, request) + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err != nil { + return nil, errors.WithMessage(err, "failed to execute search query") + } + + results := make([]DocumentMatch, min(ResultsPerPage, bleveResult.Total)) + var buf bytes.Buffer + for i, result := range bleveResult.Hits { + _, err = buf.WriteString(result.Fields["_data"].(string)) + if err != nil { + return nil, errors.WithMessage(err, "error fetching result data") + } + err = gob.NewDecoder(&buf).Decode(&results[i].Data) + if err != nil { + return nil, errors.WithMessagef(err, "error decoding gob data: %s", buf.String()) + } + buf.Reset() + } + + return &Result{ + SearchResult: bleveResult, + Hits: results, + }, nil + } +} + func (index *ReadIndex) Search( ctx context.Context, source *config.Source, @@ -127,38 +163,27 @@ func (index *ReadIndex) Search( search := bleve.NewSearchRequest(query) search.Size = ResultsPerPage - search.Fields = []string{"_data"} if from != 0 { search.From = int(from) } - bleveResult, err := index.index.SearchInContext(ctx, search) - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - if err != nil { - return nil, errors.WithMessage(err, "failed to execute search query") - } + return index.search(ctx, search) +} - results := make([]DocumentMatch, min(ResultsPerPage, bleveResult.Total)) - var buf bytes.Buffer - for i, result := range bleveResult.Hits { - _, err = buf.WriteString(result.Fields["_data"].(string)) - if err != nil { - return nil, errors.WithMessage(err, "error fetching result data") - } - err = gob.NewDecoder(&buf).Decode(&results[i].Data) - if err != nil { - return nil, errors.WithMessagef(err, "error decoding gob data: %s", buf.String()) - } - buf.Reset() - } +func (index *ReadIndex) GetDocument( + ctx context.Context, + source *config.Source, + id string, +) (*nix.Importable, error) { + key := nix.MakeKey(source, id) + query := bleve.NewDocIDQuery([]string{key}) + search := bleve.NewSearchRequest(query) - return &Result{ - SearchResult: bleveResult, - Hits: results, - }, nil + result, err := index.search(ctx, search) + if err != nil { + return nil, err } + + return &result.Hits[0].Data, err } diff --git a/internal/nix/importable.go b/internal/nix/importable.go index 37532cd..67ba96d 100644 --- a/internal/nix/importable.go +++ b/internal/nix/importable.go @@ -1,6 +1,9 @@ package nix -import "encoding/gob" +import ( + "encoding/gob" + "searchix/internal/config" +) type Importable interface { BleveType() string @@ -12,6 +15,10 @@ func GetKey(i Importable) string { return i.BleveType() + "/" + i.GetSource() + "/" + i.GetName() } +func MakeKey(source *config.Source, id string) string { + return source.Importer.Singular() + "/" + source.Key + "/" + id +} + func init() { gob.Register(Option{}) gob.Register(Package{}) diff --git a/internal/server/mux.go b/internal/server/mux.go index 7bddbe5..79e24cd 100644 --- a/internal/server/mux.go +++ b/internal/server/mux.go @@ -18,6 +18,7 @@ import ( "searchix/frontend" "searchix/internal/config" search "searchix/internal/index" + "searchix/internal/nix" "github.com/blevesearch/bleve/v2" sentryhttp "github.com/getsentry/sentry-go/http" @@ -55,6 +56,12 @@ type ResultData struct { Next string } +type DocumentData struct { + TemplateData + Document *nix.Importable + Children *search.Result +} + var templates TemplateCollection func applyDevModeOverrides(cfg *config.Config) { @@ -222,6 +229,61 @@ func NewMux( mux.HandleFunc("/options/{source}/search", createSearchHandler(config.Options)) mux.HandleFunc("/packages/{source}/search", createSearchHandler(config.Packages)) + createSourceIDHandler := func(importerType config.ImporterType) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + source := cfg.Importer.Sources[r.PathValue("source")] + if source == nil || source.Importer != importerType { + errorHandler(w, r, http.StatusText(http.StatusNotFound), http.StatusNotFound) + + return + } + importerSingular := importerType.Singular() + + ctx, cancel := context.WithTimeout(r.Context(), searchTimeout) + defer cancel() + + doc, err := index.GetDocument(ctx, source, r.PathValue("id")) + if err != nil { + errorHandler( + w, + r, + http.StatusText(http.StatusInternalServerError), + http.StatusInternalServerError, + ) + + return + } + + if doc == nil { + errorHandler(w, r, http.StatusText(http.StatusNotFound), http.StatusNotFound) + + return + } + + tdata := DocumentData{ + TemplateData: TemplateData{ + ExtraHeadHTML: cfg.Web.ExtraHeadHTML, + Source: *source, + Sources: cfg.Importer.Sources, + Assets: frontend.Assets, + }, + Document: doc, + } + if r.Header.Get("Fetch") == "true" { + w.Header().Add("Content-Type", "text/html; charset=utf-8") + err = templates[importerSingular].ExecuteTemplate(w, "main", tdata) + } else { + err = templates[importerSingular].Execute(w, tdata) + } + if err != nil { + slog.Error("template error", "template", importerSingular, "error", err) + errorHandler(w, r, err.Error(), http.StatusInternalServerError) + } + } + } + mux.HandleFunc("/options/{source}/{id}", createSourceIDHandler(config.Options)) + mux.HandleFunc("/packages/{source}/{id}", createSourceIDHandler(config.Packages)) + createOpenSearchXMLHandler := func(importerType config.ImporterType) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { type openSearchData struct { -- cgit 1.4.1