Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Topdown traversal / alternative interface for custom writers #7880

Closed
tarleb opened this issue Feb 1, 2022 · 4 comments · Fixed by #7897
Closed

Topdown traversal / alternative interface for custom writers #7880

tarleb opened this issue Feb 1, 2022 · 4 comments · Fixed by #7897

Comments

@tarleb
Copy link
Collaborator

tarleb commented Feb 1, 2022

I was thinking about how to support the new table style in custom writers (#6573), the asymmetry between custom reader and writer, and how writers for non context-free languages sometimes require obscure hacks (e.g. https://groups.google.com/d/msgid/pandoc-discuss/e249e0e0-d71e-4476-9d1f-12f7d3f2acf5%40googlegroups.com). That's why I started to think about an alternative interface that could be setup in parallel to the existing one.

One idea I had was to check for the existence of a global Writer function and to use it if present. It would get two arguments, the Pandoc document and the writer options, along the lines of custom readers. The full responsibility of rendering the document would then be on that function.

As a convenience, we could add a helper pandoc.writer.make that works similar to a Lua filter, but expects functions to return strings (or Doc elements in case we decide to support that via a doclayout module). It would generate a function that converts any AST element to a string.

local write_html
write_html = pandoc.writer.make{
  -- expects string return values
  Space = function (_) return ' ' end,

  -- allows to use pandoc.write
  Str = function (s) return pandoc.write(pandoc.Pandoc(s.text), 'html') end,

  -- recurse on element content
  Emph = function (emph)
    return ('<em>%s</em>'):format(write_html(emph.content))
  end,
  Para = function (p)
    -- closing tag omitted intentionally, as suggested by
    -- some HTML style guides.
    return ('<p>%s'):format(write_html(p.content))
  end,

  -- Improved error handling by making this the default implementation for
  -- catch-all functions of a type.
  Block = function (b) error(('no converter for block of type %s')format(b.t)) end
}

function Writer(doc, writer_options)
  local head = ('<head><title>%s</title>\n'):write_html(doc.meta.title)
  local body = '<body>' .. write_html(doc.blocks)
  return '<html>\n' .. head .. body
end

A possible downside that I see is that the additional capabilities could blur the distinction between filter and writer, but that could be a good thing in cases where a filter is coupled to a custom writer.

@jgm
Copy link
Owner

jgm commented Feb 2, 2022

One thing people have sometimes requested is the ability to just define certain functions in a custom writer, and have everything else "the same as usual." e.g. override the treatment of Link in HTML while keeping everything else the same.

This can be done using a filter that replaces a link with a RawInline in the output format. But perhaps there'd be a way of facilitating this in custom writers? e.g. make_variant("html", { Link = ... })? This would just be shorthand for "apply a filter then call the html writer". Just thinking aloud.

@denismaier
Copy link
Contributor

One thing people have sometimes requested is the ability to just define certain functions in a custom writer, and have everything else "the same as usual." e.g. override the treatment of Link in HTML while keeping everything else the same.

This can be done using a filter that replaces a link with a RawInline in the output format. But perhaps there'd be a way of facilitating this in custom writers? e.g. make_variant("html", { Link = ... })? This would just be shorthand for "apply a filter then call the html writer". Just thinking aloud.

Awesome idea. I've had a question about custom Haskell writers on the mailing list a few weeks ago. This here is the primary use case I've had in mind.

@tarleb
Copy link
Collaborator Author

tarleb commented Feb 6, 2022

@jgm
Copy link
Owner

jgm commented Feb 7, 2022

Yes, maybe this is simple enough that we don't need make_variant.

@jgm jgm closed this as completed in #7897 Feb 7, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants