From c0fbf11f843af84e8891a708c4d217dd6c523473 Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Mon, 6 May 2024 10:14:17 +0200 Subject: feat: render markdown examples --- frontend/templates/blocks/options.gotmpl | 18 ++++++-- internal/options/option.go | 62 +++++-------------------- internal/options/process.go | 78 ++++++++++++++++++++++++++++---- internal/server/server.go | 19 +++++++- 4 files changed, 109 insertions(+), 68 deletions(-) diff --git a/frontend/templates/blocks/options.gotmpl b/frontend/templates/blocks/options.gotmpl index 89c6f33..a4c4f12 100644 --- a/frontend/templates/blocks/options.gotmpl +++ b/frontend/templates/blocks/options.gotmpl @@ -5,7 +5,7 @@ {{ .Option }}

- {{ HTML .Description.HTML }} + {{ markdown .Description }}

{{- with .Type }} @@ -15,18 +15,26 @@ {{- with .Default }}
Default
-
{{ .Text }}
+ {{- if .Markdown }} + {{ .Markdown }} + {{- else }} +
{{ .Text }}
+ {{- end }}
{{- end }} {{- with .Example }} - {{- if .Text }} + {{- if or .Text .Markdown }}
Example
-
{{ .Text }}
+ {{- if .Markdown }} + {{ .Markdown }} + {{- else }} +
{{ .Text }}
+ {{- end }}
{{- end }} {{- end }} - {{- with .RelatedPackages.HTML }} + {{- with .RelatedPackages }}
Related Packages
{{ . }}
{{- end }} diff --git a/internal/options/option.go b/internal/options/option.go index b8b838a..a43dd49 100644 --- a/internal/options/option.go +++ b/internal/options/option.go @@ -1,67 +1,27 @@ package options -import ( - "encoding/json" - "strings" - - "github.com/pkg/errors" - "github.com/yuin/goldmark" - "github.com/yuin/goldmark/extension" -) +type Markdown string type NixValue struct { - Type string `json:"_type" mapstructure:"_type"` - Text string `json:"text"` -} - -type HTML struct { - HTML string -} - -var md = goldmark.New( - goldmark.WithExtensions(extension.NewLinkify()), -) - -func (html *HTML) UnmarshalText(text []byte) error { - var out strings.Builder - err := md.Convert(text, &out) - if err != nil { - return errors.WithMessage(err, "failed to convert markdown to HTML") - } - - html.HTML = out.String() - - return nil -} - -func (html *HTML) UnmarshalJSON(raw []byte) error { - var v struct { - HTML string - } - err := json.Unmarshal(raw, &v) - if err != nil { - return errors.WithMessage(err, "error unmarshaling json") - } - html.HTML = v.HTML - - return nil + Text string `json:",omitempty"` + Markdown Markdown `json:",omitempty"` } type Link struct { Name string - URL string `json:"url"` + URL string } type NixOption struct { - Option string + Option string + Declarations []Link - Default NixValue - Description HTML - Example NixValue - ReadOnly bool - Type string + Default *NixValue `json:",omitempty"` + Description Markdown + Example *NixValue `json:",omitempty"` Loc []string - RelatedPackages HTML + RelatedPackages Markdown `json:",omitempty"` + Type string } type NixOptions []NixOption diff --git a/internal/options/process.go b/internal/options/process.go index fde73e4..9721a7b 100644 --- a/internal/options/process.go +++ b/internal/options/process.go @@ -3,6 +3,7 @@ package options import ( "encoding/json" "io" + "log/slog" "os" "github.com/bcicen/jstream" @@ -10,6 +11,27 @@ import ( "github.com/pkg/errors" ) +type nixValueJSON struct { + Type string `mapstructure:"_type"` + Text string +} + +type linkJSON struct { + Name string + URL string `json:"url"` +} + +type nixOptionJSON struct { + Declarations []linkJSON + Default nixValueJSON + Description string + Example nixValueJSON + Loc []string + ReadOnly bool + RelatedPackages string + Type string +} + func ValueTypeToString(valueType jstream.ValueType) string { switch valueType { case jstream.Unknown: @@ -31,6 +53,27 @@ func ValueTypeToString(valueType jstream.ValueType) string { return "very strange" } +func convertNixValue(nj nixValueJSON) *NixValue { + switch nj.Type { + case "", "literalExpression": + if nj.Text == "" { + return nil + } + + return &NixValue{ + Text: nj.Text, + } + case "literalMD": + return &NixValue{ + Markdown: Markdown(nj.Text), + } + default: + slog.Warn("got unexpected NixValue type", "type", nj.Type, "text", nj.Text) + + return nil + } +} + func Process(inpath string, outpath string) error { infile, err := os.Open(inpath) if err != nil { @@ -46,10 +89,11 @@ func Process(inpath string, outpath string) error { } dec := jstream.NewDecoder(infile, 1).EmitKV() - var opt NixOption + var optJSON nixOptionJSON ms, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ ErrorUnused: true, - Result: &opt, + ZeroFields: true, + Result: &optJSON, Squash: true, DecodeHook: mapstructure.TextUnmarshallerHookFunc(), }) @@ -69,17 +113,29 @@ func Process(inpath string, outpath string) error { return errors.Errorf("unexpected object type %s", ValueTypeToString(mv.ValueType)) } kv := mv.Value.(jstream.KV) - if kv.Key == "_module.args" { - continue - } x := kv.Value.(map[string]interface{}) - x["option"] = kv.Key - err = ms.Decode(x) + err = ms.Decode(x) // stores in optJSON if err != nil { return errors.WithMessagef(err, "failed to decode option %#v", x) } + var decs = make([]Link, len(optJSON.Declarations)) + for i, d := range optJSON.Declarations { + decs[i] = Link(d) + } + + opt := NixOption{ + Option: kv.Key, + Declarations: decs, + Default: convertNixValue(optJSON.Default), + Description: Markdown(optJSON.Description), + Example: convertNixValue(optJSON.Example), + RelatedPackages: Markdown(optJSON.RelatedPackages), + Loc: optJSON.Loc, + Type: optJSON.Type, + } + b, err := json.MarshalIndent(opt, "", " ") if err != nil { return errors.WithMessagef(err, "failed to encode option %#v", opt) @@ -95,9 +151,11 @@ func Process(inpath string, outpath string) error { } } - _, err = outfile.Seek(-2, io.SeekCurrent) - if err != nil { - return errors.WithMessage(err, "could not write to output") + if outpath != "/dev/stdout" { + _, err = outfile.Seek(-2, io.SeekCurrent) + if err != nil { + return errors.WithMessage(err, "could not write to output") + } } _, err = outfile.WriteString("]\n") diff --git a/internal/server/server.go b/internal/server/server.go index ad75ac1..76fbecb 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "html" "html/template" "io" "log" @@ -25,6 +26,8 @@ import ( "github.com/osdevisnot/sorvor/pkg/livereload" "github.com/pkg/errors" "github.com/shengyanli1982/law" + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/extension" ) var config *cfg.Config @@ -74,9 +77,20 @@ func applyDevModeOverrides(config *cfg.Config) { config.CSP.ConnectSrc = slices.Insert(config.CSP.ConnectSrc, 0, "'self'") } +var md = goldmark.New( + goldmark.WithExtensions(extension.NewLinkify()), +) var templateFuncs = template.FuncMap{ - "HTML": func(input string) template.HTML { - return template.HTML(input) // #nosec G203 + "markdown": func(input options.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 }, } @@ -177,6 +191,7 @@ func New(runtimeConfig *Config) (*Server, error) { if err != nil { slog.Error(fmt.Sprintf("error parsing json file: %v", err)) } + mux.HandleFunc("/options/results", func(w http.ResponseWriter, r *http.Request) { tdata := OptionResultData{ TemplateData: indexData, -- cgit 1.4.1