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