Add `@evalraw` syntax for programmatically generate raw contents
This PR is motivated by the discussion started in this discourse thread
It simply adds a new synthax block called @evalraw that allows to generate raw html (or LaTeX) contents from Julia that are then rendered as if they were written verbatim inside a
```@raw
contents...
```
block
I think this functionality can be very useful for generating arbitrary HTML from Julia, especially for people that do not have html/js expertise but can rely on packages that already provide HTML outputs from Julia.
The synthax is quite simple, I mostly reused the code for @eval blocks with the simple checks from @raw and just put a hard constraints that the result has to be a String, then the resulting string is used to generate a Documenter.RawNode element.
At the moment I did put a normal error when the result is not a string as I am not well versed in the Documenter internals and how the @docerror macro should be used.
If the maintainers are open to the idea of adding this functionality I will write tests and docs where needed for this.
I am not necessarily against this, and I think it's a use case we should support. However, I wonder if could re-use the at-eval block somehow, without having to create a new one?
On #master / 1.0, Documenter will be very picky about what objects you can create in an at-eval:
https://github.com/JuliaDocs/Documenter.jl/blob/7c97a86a31e360d7d22082a9a783b0cab24163b5/src/Expanders.jl#L606-L629
So I think we could have some sort of an interface where a special returned object gets rendered as HTML or something along those lines. It could even support the case where a single block gets included in different formats depending on the writer (e.g. HTML or LaTeX). Not 100% sure what the interface should be right now though.
Hi @mortenpi, thanks for the fast reply What about something along these lines?
result = if isnothing(result)
nothing
elseif isa(result, Docs.HTML)
content = let io = IOBuffer()
invokelatest(show, io, MIME"text/html"(), result)
String(take!(io))
end
node.element = Documenter.RawNode(:html, content)
return
elseif isa(result, Markdown.MD)
convert(Node, result)
else
I do not know exactly how the Writers work and how the LaTeX part is handled.
The nice thing about Docs.HTML is that it is shipped with Julia so I believe it could be a good and safe type to use for checking whether this should became a rawnode.
I don't know if there is something similar for LaTeX.
Edit: Maybe you were talking about writing the documenter output using the LaTeX backend rather than HTML, and not about whether the RawNode is :html or :latex
Edit2: Ok it seems the two are the same thing... sorry but I started trying out documenter only very recently
Could this not be handled similarly to the result from @example blocks?
Hi @fredrikekre,
From a quick look it seems an interesting approach, but then are you suggesting to directly catch this in the else block and automatcally render things as MultiOutputElement instead of using a RawNode?
Edit: I also actually just tried giving custom HTML/JS as output of the @example block and it correctly renders in the document.
Is there any reason why the @eval block doesn't do the same (but without also showing the generating code) and is much more restrictive on evaulation, or is mostly a historcal thing? (Like the advanced output handling of @example is just newer and @eval was not adapted accordingly)
I am hesitant to just rely on the the show methods like at-example does. In particular, for Markdown.MD, that I think would change the behaviour. Right now, we incorporate the actual AST into the Documenter AST, and so it gets rendered "natively" (i.e. exactly as Documenter would render that Markdown). If you change to show methods, then it would use the Markdown stdlibs HTML rendering, which is different from Documenter's.
I also don't think we should special case Markdown.MD, because that creates complexity, and we almost certainly want to "special case" other things in the future (e.g. MarkdownAST.Nodes), but using show methods implies that we should treat all objects the same.
We could still have a way to explicitly tell Documenter to fall back to show methods though, e.g. with something like this:
```@eval
result = f()
Documenter.UseShowMethods(result)
```
So I would advocate being explicit here somehow. Base.HTML is kind-of deprecated and an unfortunate artifact that should not really be used.. but if stick to at-eval only accepting certain types, we could still add support for that in that if -- it wouldn't really hurt, I don't think.
I liked the idea of being explicit with your suggested Documenter.UseShowMethods so I tried implementing a new type to do that.
I kept the name as suggested even though it is a bit different in style from all the other types defined in Document (i.e. SomethingNode) so I am fine with changing it with another name.
I thought about only creating a function that would make a suitable input for EvalNode instead of creatin a new type but after thinking about this a bit I believe this to be cleaner.
I added methods for HTMLWriter and LatexWriter but in case you are OK with the approach I'd like to get some guidance/feedback on where to put the tests.
Hi @mortenpi,
would you have a chance to review the latest changes and give some feedback on whether the approach here is on the right track?
Sadly this PR has been a bit neglected and now has a bunch of conflicts...
On the upside, we maybe have new opportunities: I am in the process of adding uniform parsing of the "language" part of codeblocks; we already have @eval NAME now; we could easily add @eval NAME ; key1 = value1, key2 = value2 syntax. This then could be used to do things like this:
```@eval ; format=:html, raw=true
to signal: this eval block is only mean to be used if the output format is html; and the output is "raw" (meaning it should be rendered using show / sprint). Of course one could also do something like render=show vs. render=markdown or whatever.