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" -}} {{- end }} {{ define "image" -}} {{- end }} {{ define "footer" -}} {{ end }} `))