Skip to content

Commit

Permalink
refactor(renderer): init and plaintext rendering (#1067)
Browse files Browse the repository at this point in the history
move plaintext rendering to separate funcs

Signed-off-by: Xavier Coulon <xcoulon@redhat.com>
  • Loading branch information
xcoulon authored Jul 7, 2022
1 parent 0bbc37e commit 93d001c
Show file tree
Hide file tree
Showing 27 changed files with 191 additions and 165 deletions.
4 changes: 2 additions & 2 deletions libasciidoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ func ConvertFile(output io.Writer, config *configuration.Configuration) (types.M

// Convert converts the content of the given reader `r` into a full output document, written in the given writer `output`.
// Returns an error if a problem occurred. The default will be HTML5, but depends on the config.BackEnd value.
func Convert(r io.Reader, output io.Writer, config *configuration.Configuration) (types.Metadata, error) {
func Convert(source io.Reader, output io.Writer, config *configuration.Configuration) (types.Metadata, error) {

start := time.Now()
defer func() {
duration := time.Since(start)
log.Debugf("rendered the output in %v", duration)
}()
p, err := parser.Preprocess(r, config)
p, err := parser.Preprocess(source, config)
if err != nil {
return types.Metadata{}, err
}
Expand Down
22 changes: 17 additions & 5 deletions pkg/configuration/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"time"

"github.com/bytesparadise/libasciidoc/pkg/types"
"github.com/davecgh/go-spew/spew"
log "github.com/sirupsen/logrus"
)

Expand All @@ -16,19 +17,24 @@ func NewConfiguration(settings ...Setting) *Configuration {
BackEnd: "html5", // default backend
Macros: map[string]MacroTemplate{},
}
// default backed
WithBackEnd("html5")(config)
// custom settings
for _, set := range settings {
set(config)
}
if log.IsLevelEnabled(log.DebugLevel) {
log.Debugf("new configuration: %s", spew.Sdump(config))
}
return config
}

// Configuration the configuration used when rendering a document
type Configuration struct {
Filename string // TODO: move out of Configuration?
Attributes types.Attributes
LastUpdated time.Time
// WrapInHTMLBodyElement flag to include the content in an html>body element
WrapInHTMLBodyElement bool
Filename string // TODO: move out of Configuration?
Attributes types.Attributes
LastUpdated time.Time
WrapInHTMLBodyElement bool // flag to include the content in an html>body element
CSS []string
BackEnd string
Macros map[string]MacroTemplate
Expand Down Expand Up @@ -88,6 +94,12 @@ func WithCSS(hrefs []string) Setting {
func WithBackEnd(backend string) Setting {
return func(config *Configuration) {
config.Attributes.Set("backend", backend)
switch backend {
case "html", "html5", "xhtml", "xhtml5":
config.Attributes.Set("basebackend-html", true)
default:
config.Attributes.Unset("basebackend-html")
}
config.BackEnd = backend
switch backend {
case "html", "html5", "xhtml", "xhtml5":
Expand Down
2 changes: 1 addition & 1 deletion pkg/renderer/sgml/cross_reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (r *sgmlRenderer) renderInternalCrossReference(ctx *context, xref *types.In
for _, e := range t {
switch e := e.(type) {
case *types.InlineLink:
renderedElement, err := r.renderPlainText(ctx, e)
renderedElement, err := RenderPlainText(e)
if err != nil {
return "", err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/renderer/sgml/delimited_block_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func (r *sgmlRenderer) renderSourceBlockElements(ctx *context, b *types.Delimite
return result.String(), highlighter, language, nil
}

func (r *sgmlRenderer) renderSourceLine(ctx *context, line interface{}) (string, []*types.Callout, error) {
func (r *sgmlRenderer) renderSourceLine(_ *context, line interface{}) (string, []*types.Callout, error) {
elements, ok := line.([]interface{})
if !ok {
return "", nil, fmt.Errorf("invalid type of line: '%T'", line)
Expand All @@ -158,7 +158,7 @@ func (r *sgmlRenderer) renderSourceLine(ctx *context, line interface{}) (string,
for _, e := range elements {
switch e := e.(type) {
case *types.StringElement, *types.SpecialCharacter:
s, err := r.renderPlainText(ctx, e)
s, err := RenderPlainText(e)
if err != nil {
return "", nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/renderer/sgml/delimited_block_verse.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (r *sgmlRenderer) renderVerseBlock(ctx *context, b *types.DelimitedBlock) (

func (r *sgmlRenderer) renderVerseParagraph(ctx *context, p *types.Paragraph) (string, error) {
log.Debug("rendering verse paragraph...")
content, err := r.renderParagraphElements(ctx, p, withRenderer(r.renderPlainText))
content, err := RenderParagraphElements(p)
if err != nil {
return "", errors.Wrap(err, "unable to render verse paragraph lines")
}
Expand Down
35 changes: 0 additions & 35 deletions pkg/renderer/sgml/elements.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"strings"

"github.com/bytesparadise/libasciidoc/pkg/types"
log "github.com/sirupsen/logrus"

"github.com/pkg/errors"
)
Expand Down Expand Up @@ -116,37 +115,3 @@ func (r *sgmlRenderer) renderElement(ctx *context, element interface{}) (string,
return "", errors.Errorf("unsupported type of element: %T", element)
}
}

func (r *sgmlRenderer) renderPlainText(ctx *context, element interface{}) (string, error) {
if log.IsLevelEnabled(log.DebugLevel) {
log.Debugf("rendering plain string for element of type %T", element)
}
switch e := element.(type) {
case []interface{}:
return r.renderInlineElements(ctx, e, withRenderer(r.renderPlainText))
case *types.QuotedText:
return r.renderPlainText(ctx, e.Elements)
case *types.Icon:
return e.Attributes.GetAsStringWithDefault(types.AttrImageAlt, ""), nil
case *types.InlineImage:
return e.Attributes.GetAsStringWithDefault(types.AttrImageAlt, ""), nil
case *types.InlineLink:
if alt, ok := e.Attributes[types.AttrInlineLinkText].([]interface{}); ok {
return r.renderPlainText(ctx, alt)
}
return e.Location.ToDisplayString(), nil
case *types.BlankLine, types.ThematicBreak:
return "\n\n", nil
case *types.SpecialCharacter:
return e.Name, nil
case *types.Symbol:
return r.renderSymbol(e)
case *types.StringElement:
return e.Content, nil
case *types.FootnoteReference:
// footnotes are rendered in HTML so they can appear as such in the table of contents
return r.renderFootnoteReferencePlainText(e)
default:
return "", errors.Errorf("unable to render plain string for element of type '%T'", e)
}
}
15 changes: 0 additions & 15 deletions pkg/renderer/sgml/footnote_reference.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package sgml

import (
"fmt"
"strings"

"github.com/bytesparadise/libasciidoc/pkg/types"
Expand Down Expand Up @@ -57,20 +56,6 @@ func (r *sgmlRenderer) renderFootnoteReference(note *types.FootnoteReference) (s
return result.String(), nil
}

func (r *sgmlRenderer) renderFootnoteReferencePlainText(note *types.FootnoteReference) (string, error) {
if note.ID != types.InvalidFootnoteReference {
// valid case for a footnote with content, with our without an explicit reference
return r.execute(r.footnoteRefPlain, struct {
ID int
Class string
}{
ID: note.ID,
Class: "footnote",
})
}
return "", fmt.Errorf("unable to render missing footnote")
}

func (r *sgmlRenderer) renderFootnotes(ctx *context, notes []*types.Footnote) (string, error) {
// skip if there's no foot note in the doc
if len(notes) == 0 {
Expand Down
9 changes: 4 additions & 5 deletions pkg/renderer/sgml/html5/footnote_reference.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package html5

const (
footnoteTmpl = `<sup class="footnote"{{ if .Ref }} id="_footnote_{{ .Ref }}"{{ end }}>[<a id="_footnoteref_{{ .ID }}" class="footnote" href="#_footnotedef_{{ .ID }}" title="View footnote.">{{ .ID }}</a>]</sup>`
footnoteRefTmpl = `<sup class="footnoteref">[<a class="footnote" href="#_footnotedef_{{ .ID }}" title="View footnote.">{{ .ID }}</a>]</sup>`
footnoteRefPlainTmpl = `<sup class="{{ .Class }}">[{{ .ID }}]</sup>`
invalidFootnoteTmpl = `<sup class="footnoteref red" title="Unresolved footnote reference.">[{{ .Ref }}]</sup>`
footnotesTmpl = "<div id=\"footnotes\">\n<hr>\n{{ .Content }}</div>\n"
footnoteTmpl = `<sup class="footnote"{{ if .Ref }} id="_footnote_{{ .Ref }}"{{ end }}>[<a id="_footnoteref_{{ .ID }}" class="footnote" href="#_footnotedef_{{ .ID }}" title="View footnote.">{{ .ID }}</a>]</sup>`
footnoteRefTmpl = `<sup class="footnoteref">[<a class="footnote" href="#_footnotedef_{{ .ID }}" title="View footnote.">{{ .ID }}</a>]</sup>`
invalidFootnoteTmpl = `<sup class="footnoteref red" title="Unresolved footnote reference.">[{{ .Ref }}]</sup>`
footnotesTmpl = "<div id=\"footnotes\">\n<hr>\n{{ .Content }}</div>\n"

// arguably this should instead be an ordered list.
footnoteElementTmpl = "<div class=\"footnote\" id=\"_footnotedef_{{ .ID }}\">\n" +
Expand Down
13 changes: 10 additions & 3 deletions pkg/renderer/sgml/html5/footnote_reference_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,22 @@ A bold statement!<sup class="footnote" id="_footnote_disclaimer">[<a id="_footno
It("footnotes everywhere", func() {

source := `= title
:toc:
a preamble with a footnote:[foo]
== section 1 footnote:[bar]
a paragraph with another footnote:[baz]`

// WARNING: differs from asciidoc in the order of footnotes in the doc and at the end of the doc, and the section id (numbering)
expected := `<div id="preamble">
// NOTE: differs from asciidoc in the section and footnote numbering (which also impacts the 'footnotes' portion at the end of the doc)
expected := `<div id="toc" class="toc">
<div id="toctitle">Table of Contents</div>
<ul class="sectlevel1">
<li><a href="#_section_1">section 1 <sup class="footnote">[2]</sup></a></li>
</ul>
</div>
<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<p>a preamble with a <sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup></p>
Expand Down
4 changes: 3 additions & 1 deletion pkg/renderer/sgml/html5/manpage.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package html5

const manpageHeaderTmpl = `{{ if.IncludeH1 }}<div id="header">
const (
manpageHeaderTmpl = `{{ if.IncludeH1 }}<div id="header">
<h1>{{ .Header }} Manual Page</h1>
{{ end }}<h2 id="_name">{{ .Name }}</h2>
<div class="sectionbody">
{{ .Content }}</div>
{{ if .IncludeH1 }}</div>
{{ end }}`
)
2 changes: 1 addition & 1 deletion pkg/renderer/sgml/html5/table_of_contents.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ const (

tocSectionTmpl = "<ul class=\"sectlevel{{ .Level }}\">\n{{ .Content }}</ul>\n"

tocEntryTmpl = "<li><a href=\"#{{ .ID }}\">{{ if .Number }}{{ .Number }}. {{ end }}{{ escape .Title }}</a>" +
tocEntryTmpl = "<li><a href=\"#{{ .ID }}\">{{ if .Number }}{{ .Number }}. {{ end }}{{ .Title }}</a>" +
"{{ if .Content }}\n{{ .Content }}{{ end }}</li>\n"
)
1 change: 0 additions & 1 deletion pkg/renderer/sgml/html5/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ var templates = sgml.Templates{
Footnote: footnoteTmpl,
FootnoteElement: footnoteElementTmpl,
FootnoteRef: footnoteRefTmpl,
FootnoteRefPlain: footnoteRefPlainTmpl,
Footnotes: footnotesTmpl,
IconFont: iconFontTmpl,
IconImage: iconImageTmpl,
Expand Down
10 changes: 3 additions & 7 deletions pkg/renderer/sgml/html5/user_macro_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
texttemplate "text/template"

"github.com/bytesparadise/libasciidoc/pkg/configuration"
"github.com/bytesparadise/libasciidoc/pkg/renderer/sgml"
. "github.com/bytesparadise/libasciidoc/testsupport"

. "github.com/onsi/ginkgo/v2"
Expand All @@ -15,17 +14,14 @@ var helloMacroTmpl *texttemplate.Template

func init() {
t := texttemplate.New("hello")
t.Funcs(texttemplate.FuncMap{
"escape": sgml.EscapeString,
})
helloMacroTmpl = texttemplate.Must(t.Parse(`{{- if eq .Kind "block" -}}
<div class="helloblock">
<div class="content">
{{end -}}
<span>
{{- $prefix := index .Attributes "prefix" }}{{ $suffix := index .Attributes "suffix" }}{{ if $prefix }}{{- escape ($prefix) }}{{ else }}hello {{end -}}
{{- if ne .Value "" }}{{ escape .Value }}{{ else }}world{{- end -}}
{{- if $suffix }}{{ escape $suffix -}}{{ end -}}
{{- $prefix := index .Attributes "prefix" }}{{ $suffix := index .Attributes "suffix" }}{{ if $prefix }}{{- ($prefix) }}{{ else }}hello {{end -}}
{{- if ne .Value "" }}{{ .Value }}{{ else }}world{{- end -}}
{{- if $suffix }}{{ $suffix -}}{{ end -}}
</span>
{{- if eq .Kind "block"}}
</div>
Expand Down
6 changes: 3 additions & 3 deletions pkg/renderer/sgml/html_escape.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ import (
"strings"
)

// EscapeString is like html5.Escape func except but bypasses
// escapeString is like html5.Escape func except but bypasses
// a few replacements. It is a bit more conservative.
func EscapeString(s string) string {
func escapeString(s string) string {
return htmlEscaper.Replace(s)
}

var htmlEscaper = strings.NewReplacer(
`&lt;`, "&lt;", // keep as-is (we do not want `&amp;lt;`)
`&gt;`, "&gt;", // keep `&lgt;` as-is (we do not want `&amp;gt;`)
`&amp;`, "&amp;", // keep `&amps` as-is (we do not want `&amp;amp;`)
`&amp;`, "&amp;", // keep `&amps` as-is (we do not want `&amp;amp;`) // TODO: still needed?
`&#`, "&#", // assume this is for an character entity and this keep as-is
// standard escape combinations
`&`, "&amp;",
Expand Down
8 changes: 2 additions & 6 deletions pkg/renderer/sgml/inline_elements.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,14 @@ import (
"github.com/bytesparadise/libasciidoc/pkg/types"
)

func (r *sgmlRenderer) renderInlineElements(ctx *context, elements []interface{}, options ...lineRendererOption) (string, error) {
func (r *sgmlRenderer) renderInlineElements(ctx *context, elements []interface{}) (string, error) {
if len(elements) == 0 {
return "", nil
}
// log.Debugf("rendering line with %d element(s)...", len(elements))
lr := r.newLineRenderer(options...)
// first pass or rendering, using the provided `renderElementFunc`:
buf := &strings.Builder{}
for i, element := range elements {
renderedElement, err := lr.render(ctx, element)
renderedElement, err := r.renderElement(ctx, element)
if err != nil {
return "", err
}
Expand All @@ -35,5 +33,3 @@ func (r *sgmlRenderer) renderInlineElements(ctx *context, elements []interface{}
// }
return buf.String(), nil
}

type renderFunc func(*context, interface{}) (string, error)
43 changes: 5 additions & 38 deletions pkg/renderer/sgml/paragraph.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ func (r *sgmlRenderer) renderParagraph(ctx *context, p *types.Paragraph) (string
}
}

func (r *sgmlRenderer) renderRegularParagraph(ctx *context, p *types.Paragraph, opts ...lineRendererOption) (string, error) {
func (r *sgmlRenderer) renderRegularParagraph(ctx *context, p *types.Paragraph) (string, error) {
log.Debug("rendering a regular paragraph")
content, err := r.renderParagraphElements(ctx, p, opts...)
content, err := r.renderParagraphElements(ctx, p)
if err != nil {
return "", errors.Wrap(err, "unable to render paragraph content")
}
Expand Down Expand Up @@ -146,44 +146,11 @@ func (r *sgmlRenderer) renderElementTitle(ctx *context, attrs types.Attributes)
}
}

type lineRenderer struct {
render renderFunc
hardBreaks bool
}

func (r *sgmlRenderer) newLineRenderer(opts ...lineRendererOption) *lineRenderer {
lr := &lineRenderer{
render: r.renderElement,
}
for _, apply := range opts {
apply(lr)
}
return lr
}

// RenderLinesOption an option to configure the rendering
type lineRendererOption func(c *lineRenderer)

// WithHardBreaks sets the hard break option
func withHardBreaks(hardBreaks bool) lineRendererOption {
return func(lr *lineRenderer) {
lr.hardBreaks = hardBreaks
}
}

// withRenderer sets the render func
func withRenderer(f renderFunc) lineRendererOption {
return func(c *lineRenderer) {
c.render = f
}
}

func (r *sgmlRenderer) renderParagraphElements(ctx *context, p *types.Paragraph, opts ...lineRendererOption) (string, error) {
func (r *sgmlRenderer) renderParagraphElements(ctx *context, p *types.Paragraph) (string, error) {
hardbreaks := p.Attributes.HasOption(types.AttrHardBreaks) || ctx.attributes.HasOption(types.AttrHardBreaks)
lr := r.newLineRenderer(append(opts, withHardBreaks(hardbreaks))...)
buf := &strings.Builder{}
for _, e := range p.Elements {
renderedElement, err := lr.render(ctx, e)
renderedElement, err := r.renderElement(ctx, e)
if err != nil {
return "", errors.Wrap(err, "unable to render paragraph elements")
}
Expand All @@ -192,7 +159,7 @@ func (r *sgmlRenderer) renderParagraphElements(ctx *context, p *types.Paragraph,
}
}
result := buf.String()
if lr.hardBreaks { // TODO: move within the call to `render`?
if hardbreaks { // TODO: move within the call to `render`?
linebreak := &strings.Builder{}
tmpl, err := r.lineBreak()
if err != nil {
Expand Down
Loading

0 comments on commit 93d001c

Please sign in to comment.