From b8f1e92bfc9e690a318c9adc96370d60bbcdedd7 Mon Sep 17 00:00:00 2001 From: tjpcc Date: Fri, 1 Sep 2023 08:14:13 -0600 Subject: gophermap->html conversion with overridable templates --- gopher/gophermap/htmlconv/convert.go | 196 +++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 gopher/gophermap/htmlconv/convert.go (limited to 'gopher/gophermap/htmlconv/convert.go') diff --git a/gopher/gophermap/htmlconv/convert.go b/gopher/gophermap/htmlconv/convert.go new file mode 100644 index 0000000..a669601 --- /dev/null +++ b/gopher/gophermap/htmlconv/convert.go @@ -0,0 +1,196 @@ +package htmlconv + +import ( + "bytes" + "html/template" + "io" + "strings" + + "tildegit.org/tjp/sliderule/gopher" + "tildegit.org/tjp/sliderule/internal/types" +) + +// Convert writes html to a writer from the provided gophermap document. +// +// Templates can be provided to override the output for different line types. +// The supported templates are: +// - "header' is called before any lines and is passed the full document. +// - "footer" is called after all lines and is passed the full document. +// - "message" is called for every group of consecutive info-message lines. It is +// passed a string of all the display components of the included lines, joined by +// newline characters. +// - "image" is called for all lines of any of the following types: GifFileType, +// ImageFileType, BitmapType, PngImageFileType. It is passed the gopher.MapItem. +// - "link" is called for all lines of any other type not yet mentioned, and is passed +// the gopher.MapItem. +// +// There are already default implementations of each of these templates, so the "overrides" +// argument can be nil or include just a subset of the supported templates. +func Convert(wr io.Writer, doc gopher.MapDocument, overrides *template.Template) error { + tmpl, err := baseTmpl.Clone() + if err != nil { + return err + } + + tmpl, err = addOverrides(tmpl, overrides) + if err != nil { + return err + } + + for _, item := range renderItems(doc) { + if err := tmpl.ExecuteTemplate(wr, item.template, item.object); err != nil { + return err + } + } + + return nil +} + +type renderItem struct { + template string + object any +} + +type renderRef struct { + Type types.Status + Display string + Selector template.URL + Hostname string + Port string +} + +func refItem(item gopher.MapItem) renderRef { + return renderRef{ + Type: item.Type, + Display: item.Display, + Selector: template.URL(item.Selector), + Hostname: item.Hostname, + Port: item.Port, + } +} + +func renderItems(doc gopher.MapDocument) []renderItem { + out := make([]renderItem, 0, len(doc)) + out = append(out, renderItem{ + template: "header", + object: doc, + }) + inMsg := false + msg := &bytes.Buffer{} + var currentHost, currentPort string + + for _, mapItem := range doc { + switch mapItem.Type { + case gopher.InfoMessageType: + if inMsg { + _, _ = msg.WriteString("\n") + } else { + msg.Reset() + } + _, _ = msg.WriteString(mapItem.Display) + inMsg = true + + if currentHost == "" { + currentHost = mapItem.Hostname + } + if currentPort == "" { + currentPort = mapItem.Port + } + case gopher.GifFileType, gopher.ImageFileType, gopher.BitmapType, gopher.PngImageFileType: + if inMsg { + out = append(out, renderItem{ + template: "message", + object: msg.String(), + }) + inMsg = false + } + out = append(out, renderItem{ + template: "image", + object: refItem(mapItem), + }) + default: + if inMsg { + out = append(out, renderItem{ + template: "message", + object: msg.String(), + }) + inMsg = false + } + out = append(out, renderItem{ + template: "link", + object: refItem(mapItem), + }) + } + } + + if inMsg { + out = append(out, renderItem{ + template: "message", + object: msg.String(), + }) + } + + simplifyLinks(out, currentHost, currentPort) + + return append(out, renderItem{ + template: "footer", + object: doc, + }) +} + +func simplifyLinks(items []renderItem, currentHost, currentPort string) { + for i, item := range items { + if item.template != "link" && item.template != "image" { + continue + } + + m := item.object.(renderRef) + if m.Hostname == currentHost && m.Port == currentPort { + m.Hostname = "" + m.Port = "" + m.Selector = template.URL(strings.TrimPrefix(string(m.Selector), "URL:")) + items[i].object = m + } + } +} + +func addOverrides(base *template.Template, overrides *template.Template) (*template.Template, error) { + if overrides == nil { + return base, nil + } + + tmpl := base + var err error + for _, override := range overrides.Templates() { + tmpl, err = tmpl.AddParseTree(override.Name(), override.Tree) + if err != nil { + return nil, err + } + } + + return tmpl, nil +} + +var baseTmpl = template.Must(template.New("htmlconv").Parse(` +{{ define "header" -}} + + +{{- end }} + +{{ define "message" -}} +
{{.}}
+
+{{- end }} + +{{ define "link" -}} +

{{.Display}}

+{{- end }} + +{{ define "image" -}} +

{{.Display}}

+{{- end }} + +{{ define "footer" -}} + +{{ end }} +`)) -- cgit v1.2.3