Skip to content

Commit

Permalink
refactor(renderer): extract document from rendering context (#533)
Browse files Browse the repository at this point in the history
Fixes #530

Signed-off-by: Xavier Coulon <[email protected]>
  • Loading branch information
xcoulon authored Apr 11, 2020
1 parent 9538b1a commit 333f595
Show file tree
Hide file tree
Showing 16 changed files with 97 additions and 207 deletions.
2 changes: 1 addition & 1 deletion libasciidoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func ConvertToHTML(r io.Reader, output io.Writer, config configuration.Configura
return types.Metadata{}, err
}
rendererCtx := renderer.NewContext(doc, config)
metadata, err := htmlrenderer.Render(rendererCtx, output)
metadata, err := htmlrenderer.Render(rendererCtx, doc, output)
if err != nil {
return types.Metadata{}, err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/parser/document_processing_rearrange_lists_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ var _ = Describe("rearrange lists", func() {
types.LabeledList{
Attributes: types.ElementAttributes{},
Items: []types.LabeledListItem{
types.LabeledListItem{
{
Attributes: types.ElementAttributes{},
Level: 1,
Term: []interface{}{
Expand Down Expand Up @@ -494,7 +494,7 @@ var _ = Describe("rearrange lists", func() {
types.LabeledList{
Attributes: types.ElementAttributes{},
Items: []types.LabeledListItem{
types.LabeledListItem{
{
Attributes: types.ElementAttributes{},
Level: 2,
Term: []interface{}{
Expand Down
154 changes: 19 additions & 135 deletions pkg/renderer/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,80 +3,40 @@ package renderer
import (
"github.com/bytesparadise/libasciidoc/pkg/configuration"
"github.com/bytesparadise/libasciidoc/pkg/types"

log "github.com/sirupsen/logrus"
)

// Context is a custom implementation of the standard golang context.Context interface,
// which carries the types.Document which is being processed
type Context struct {
Document types.Document
Config configuration.Configuration
Config configuration.Configuration
// TableOfContents exists even if the document did not specify the `:toc:` attribute.
// It will take into account the configured `:toclevels:` attribute value.
TableOfContents types.TableOfContents
// macros map[string]MacroTemplate
includeBlankLine bool
withinDelimitedBlock bool
withinList int
TableOfContents types.TableOfContents
IncludeBlankLine bool
WithinDelimitedBlock bool
WithinList int
counters map[string]int
Attributes types.DocumentAttributes
Footnotes types.Footnotes
FootnoteReferences types.FootnoteReferences
ElementReferences types.ElementReferences
HasHeader bool
}

// NewContext returns a new rendering context for the given document.
func NewContext(document types.Document, config configuration.Configuration) Context {
func NewContext(doc types.Document, config configuration.Configuration) Context {
_, hasHeader := doc.Header()
return Context{
Document: document,
Config: config,
counters: make(map[string]int),
// macros: make(map[string]MacroTemplate),
}
}

// SetIncludeBlankLine sets the rendering context to include (or not) the blank lines
func (ctx *Context) SetIncludeBlankLine(b bool) bool {
oldvalue := ctx.includeBlankLine
ctx.includeBlankLine = b
log.Debugf("set 'includeBlankLine' context param to '%t' (was '%t' before)", b, oldvalue)
return oldvalue
}

// IncludeBlankLine indicates if blank lines should be rendered (default false)
func (ctx *Context) IncludeBlankLine() bool {
return ctx.includeBlankLine
}

const withinDelimitedBlock string = "withinDelimitedBlock"

// SetWithinDelimitedBlock sets the rendering context to be within a delimited block
func (ctx *Context) SetWithinDelimitedBlock(b bool) bool {
log.Debugf("set rendering elements within a delimited block to `%t`", b)
oldvalue := ctx.withinDelimitedBlock
log.Debugf("set '%s' context param to '%t' (was '%t' before)", withinDelimitedBlock, b, oldvalue)
ctx.withinDelimitedBlock = b
return oldvalue
}

// WithinDelimitedBlock indicates if the current element to render is within a delimited block or not
func (ctx *Context) WithinDelimitedBlock() bool {
return ctx.withinDelimitedBlock
}

const withinList string = "withinList"

// SetWithinList sets the rendering context to be within a list or a nest list
func (ctx *Context) SetWithinList(w bool) {
if w {
ctx.withinList++
} else {
ctx.withinList--
Config: config,
counters: make(map[string]int),
Attributes: doc.Attributes,
ElementReferences: doc.ElementReferences,
Footnotes: doc.Footnotes,
FootnoteReferences: doc.FootnoteReferences,
HasHeader: hasHeader,
}
}

// WithinList indicates if the current element to render is within a list or not
func (ctx *Context) WithinList() bool {
return ctx.withinList > 0
}

const tableCounter = "tableCounter"

// GetAndIncrementTableCounter returns the current value for the table counter after internally incrementing it.
Expand Down Expand Up @@ -107,79 +67,3 @@ func (ctx *Context) getAndIncrementCounter(name string) int {
ctx.counters[name]++
return ctx.counters[name]
}

// //Option the options when rendering a document
// type Option func(ctx *Context)

// const (
// // keyLastUpdated the key to specify the last update of the document to render.
// // Can be a string or a time, which will be formatted using the 2006/01/02 15:04:05 MST` pattern
// keyLastUpdated string = types.AttrLastUpdated
// // keyIncludeHeaderFooter key to a bool value to indicate if the header and footer should be rendered
// keyIncludeHeaderFooter string = "IncludeHeaderFooter"
// // keyCSS key to the options CSS to add in the document head. Default is empty ("")
// keyCSS string = "CSS"
// // keyEntrypoint key to the entrypoint to start with when parsing the document
// keyEntrypoint string = "Entrypoint"
// // LastUpdatedFormat key to the time format for the `last updated` document attribute
// LastUpdatedFormat string = "2006-01-02 15:04:05 -0700"
// )

// // LastUpdated function to set the `last updated` option in the renderer context (default is `time.Now()`)
// func WithLastUpdated(value time.Time) Option {
// return func(ctx *Context) {
// ctx.options[keyLastUpdated] = value
// }
// }

// // IncludeHeaderFooter function to set the `include header/footer` option in the renderer context
// func WithHeaderFooter(value bool) Option {
// return func(ctx *Context) {
// ctx.options[keyIncludeHeaderFooter] = value
// }
// }

// // IncludeCSS function to set the `css` option in the renderer context
// func WithCSS(href string) Option {
// return func(ctx *Context) {
// ctx.options[keyCSS] = href
// }
// }

// // Entrypoint function to set the `entrypoint` option in the renderer context
// func Entrypoint(entrypoint string) Option {
// return func(ctx *Context) {
// ctx.options[keyEntrypoint] = entrypoint
// }
// }

// // DefineMacro defines the given template to a user macro with the given name
// func WithMacroTemplate(name string, t MacroTemplate) Option {
// return func(ctx *Context) {
// ctx.macros[name] = t
// }
// }

// // LastUpdated returns the value of the 'LastUpdated' Option if it was present,
// // otherwise it returns the current time using the `2006/01/02 15:04:05 MST` format
// func (ctx *Context) WithLastUpdated() string {
// if lastUpdated, ok := ctx.options[keyLastUpdated].(time.Time); ok {
// return lastUpdated.Format(LastUpdatedFormat)
// }
// return time.Now().Format(LastUpdatedFormat)
// }

// IncludeHeaderFooter returns the value of the 'IncludeHeaderFooter' Option if it was present,
// otherwise it returns `false`
// func (ctx *Context) WithHeaderFooter() bool {
// return ctx.IncludeHeaderFooter
// }

// // CSS returns the value of the 'CSS' Option if it was present,
// // otherwise it returns an empty string
// func (ctx *Context) CSS() string {
// if css, ok := ctx.options[keyCSS].(string); ok {
// return css
// }
// return ""
// }
2 changes: 1 addition & 1 deletion pkg/renderer/html5/article_adoc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ var _ = Describe("article.adoc", func() {
GinkgoT().Logf("actual document: `%s`", spew.Sdump(doc))
buff := bytes.NewBuffer(nil)
ctx := renderer.NewContext(doc, config)
_, err = html5.Render(ctx, buff)
_, err = html5.Render(ctx, doc, buff)
Expect(err).ToNot(HaveOccurred())
})
})
2 changes: 1 addition & 1 deletion pkg/renderer/html5/blank_line.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

func renderBlankLine(ctx renderer.Context, l types.BlankLine) ([]byte, error) { //nolint:unparam
if ctx.IncludeBlankLine() {
if ctx.IncludeBlankLine {
log.Debug("rendering blankline")
return []byte("\n\n"), nil
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/renderer/html5/cross_reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func renderInternalCrossReference(ctx renderer.Context, xref types.InternalCross
var label string
if xref.Label != "" {
label = xref.Label
} else if target, found := ctx.Document.ElementReferences[xref.ID]; found {
} else if target, found := ctx.ElementReferences[xref.ID]; found {
if t, ok := target.([]interface{}); ok {
renderedContent, err := renderElement(ctx, t)
if err != nil {
Expand Down
43 changes: 26 additions & 17 deletions pkg/renderer/html5/delimited_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,14 @@ func renderDelimitedBlock(ctx renderer.Context, b types.DelimitedBlock) ([]byte,
}

func renderFencedBlock(ctx renderer.Context, b types.DelimitedBlock) ([]byte, error) {
previouslyWithin := ctx.SetWithinDelimitedBlock(true)
previouslyInclude := ctx.SetIncludeBlankLine(true)
previousWithinDelimitedBlock := ctx.WithinDelimitedBlock
previousIncludeBlankline := ctx.IncludeBlankLine
defer func() {
ctx.SetWithinDelimitedBlock(previouslyWithin)
ctx.SetIncludeBlankLine(previouslyInclude)
ctx.WithinDelimitedBlock = previousWithinDelimitedBlock
ctx.IncludeBlankLine = previousIncludeBlankline
}()
ctx.WithinDelimitedBlock = true
ctx.IncludeBlankLine = true
result := bytes.NewBuffer(nil)
err := fencedBlockTmpl.Execute(result, ContextualPipeline{
Context: ctx,
Expand All @@ -191,12 +193,14 @@ func renderFencedBlock(ctx renderer.Context, b types.DelimitedBlock) ([]byte, er
}

func renderListingBlock(ctx renderer.Context, b types.DelimitedBlock) ([]byte, error) {
previouslyWithin := ctx.SetWithinDelimitedBlock(true)
previouslyInclude := ctx.SetIncludeBlankLine(true)
previousWithinDelimitedBlock := ctx.WithinDelimitedBlock
previousIncludeBlankline := ctx.IncludeBlankLine
defer func() {
ctx.SetWithinDelimitedBlock(previouslyWithin)
ctx.SetIncludeBlankLine(previouslyInclude)
ctx.WithinDelimitedBlock = previousWithinDelimitedBlock
ctx.IncludeBlankLine = previousIncludeBlankline
}()
ctx.WithinDelimitedBlock = true
ctx.IncludeBlankLine = true
result := bytes.NewBuffer(nil)
err := listingBlockTmpl.Execute(result, ContextualPipeline{
Context: ctx,
Expand All @@ -214,12 +218,14 @@ func renderListingBlock(ctx renderer.Context, b types.DelimitedBlock) ([]byte, e
}

func renderSourceBlock(ctx renderer.Context, b types.DelimitedBlock) ([]byte, error) {
previouslyWithin := ctx.SetWithinDelimitedBlock(true)
previouslyInclude := ctx.SetIncludeBlankLine(true)
previousWithinDelimitedBlock := ctx.WithinDelimitedBlock
previousIncludeBlankline := ctx.IncludeBlankLine
defer func() {
ctx.SetWithinDelimitedBlock(previouslyWithin)
ctx.SetIncludeBlankLine(previouslyInclude)
ctx.WithinDelimitedBlock = previousWithinDelimitedBlock
ctx.IncludeBlankLine = previousIncludeBlankline
}()
ctx.WithinDelimitedBlock = true
ctx.IncludeBlankLine = true
// first, render the content
contentBuf := bytes.NewBuffer(nil)
err := sourceBlockContentTmpl.Execute(contentBuf, ContextualPipeline{
Expand All @@ -235,14 +241,14 @@ func renderSourceBlock(ctx renderer.Context, b types.DelimitedBlock) ([]byte, er
content := contentBuf.String()
language := b.Attributes.GetAsString(types.AttrLanguage)

hightligher, _ := ctx.Document.Attributes.GetAsString(types.AttrSyntaxHighlighter)
hightligher, _ := ctx.Attributes.GetAsString(types.AttrSyntaxHighlighter)
if language != "" && hightligher == "pygments" {
// using github.com/alecthomas/chroma to highlight the content
contentBuf = bytes.NewBuffer(nil)
lexer := lexers.Get(language)
lexer = chroma.Coalesce(lexer)
style := styles.Fallback
if s, exists := ctx.Document.Attributes.GetAsString("pygments-style"); exists {
if s, exists := ctx.Attributes.GetAsString("pygments-style"); exists {
style = styles.Get(s)
}
iterator, err := lexer.Tokenise(nil, content)
Expand All @@ -254,7 +260,7 @@ func renderSourceBlock(ctx renderer.Context, b types.DelimitedBlock) ([]byte, er
html.PreventSurroundingPre(true),
}
// extra option: inline CSS instead of classes
if ctx.Document.Attributes.GetAsStringWithDefault("pygments-css", "classes") == "style" {
if ctx.Attributes.GetAsStringWithDefault("pygments-css", "classes") == "style" {
options = append(options, html.WithClasses(false))
} else {
options = append(options, html.WithClasses(true))
Expand Down Expand Up @@ -369,8 +375,11 @@ func renderVerseBlock(ctx renderer.Context, b types.DelimitedBlock) ([]byte, err
}

func renderVerseBlockElement(ctx renderer.Context, element interface{}) ([]byte, error) {
before := ctx.SetIncludeBlankLine(true)
defer ctx.SetIncludeBlankLine(before)
previousIncludeBlankline := ctx.IncludeBlankLine
defer func() {
ctx.IncludeBlankLine = previousIncludeBlankline
}()
ctx.IncludeBlankLine = true
switch e := element.(type) {
case types.Paragraph:
return renderVerseBlockParagraph(ctx, e)
Expand Down
14 changes: 7 additions & 7 deletions pkg/renderer/html5/document_details.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ func init() {
}

func renderDocumentDetails(ctx renderer.Context) (*htmltemplate.HTML, error) {
if ctx.Document.Attributes.Has(types.AttrAuthors) {
if ctx.Attributes.Has(types.AttrAuthors) {
authors, err := renderDocumentAuthorsDetails(ctx)
if err != nil {
return nil, errors.Wrap(err, "error while rendering the document details")
}
documentDetailsBuff := bytes.NewBuffer(nil)
revNumber, _ := ctx.Document.Attributes.GetAsString("revnumber")
revDate, _ := ctx.Document.Attributes.GetAsString("revdate")
revRemark, _ := ctx.Document.Attributes.GetAsString("revremark")
revNumber, _ := ctx.Attributes.GetAsString("revnumber")
revDate, _ := ctx.Attributes.GetAsString("revdate")
revRemark, _ := ctx.Attributes.GetAsString("revremark")
err = documentDetailsTmpl.Execute(documentDetailsBuff, struct {
Authors htmltemplate.HTML
RevNumber string
Expand All @@ -56,7 +56,7 @@ func renderDocumentDetails(ctx renderer.Context) (*htmltemplate.HTML, error) {
return nil, nil
}

func renderDocumentAuthorsDetails(ctx renderer.Context) (*htmltemplate.HTML, error) {
func renderDocumentAuthorsDetails(ctx renderer.Context) (*htmltemplate.HTML, error) { // TODO: use `types.DocumentAuthor` attribute in context
authorsDetailsBuff := bytes.NewBuffer(nil)
i := 1
for {
Expand All @@ -73,9 +73,9 @@ func renderDocumentAuthorsDetails(ctx renderer.Context) (*htmltemplate.HTML, err
emailKey = "email_" + index
}
// having at least one author is the minimal requirement for document details
if author, ok := ctx.Document.Attributes.GetAsString(authorKey); ok {
if author, ok := ctx.Attributes.GetAsString(authorKey); ok {
authorDetailsBuff := bytes.NewBuffer(nil)
email, _ := ctx.Document.Attributes.GetAsString(emailKey)
email, _ := ctx.Attributes.GetAsString(emailKey)
err := documentAuthorDetailsTmpl.Execute(authorDetailsBuff, struct {
Index string
Name string
Expand Down
6 changes: 3 additions & 3 deletions pkg/renderer/html5/elements.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ func renderListElements(ctx renderer.Context, elements []interface{}) ([]byte, e
hasContent := false
for i, element := range elements {
if i == 0 {
ctx.SetWithinList(true)
ctx.WithinList++
}
renderedElement, err := renderElement(ctx, element)
if i == 0 {
ctx.SetWithinList(false)
ctx.WithinList--
}
if err != nil {
return nil, errors.Wrapf(err, "unable to render a list block")
Expand Down Expand Up @@ -171,7 +171,7 @@ func includeNewline(ctx renderer.Context, index int, content interface{}) string
case reflect.Slice, reflect.Array:
s := reflect.ValueOf(content)
if _, match := s.Index(index).Interface().(types.BlankLine); match {
if ctx.IncludeBlankLine() {
if ctx.IncludeBlankLine {
return "\n"
}
return ""
Expand Down
Loading

0 comments on commit 333f595

Please sign in to comment.