Skip to content

Conversation

@disberd
Copy link

@disberd disberd commented Jul 12, 2023

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.

@mortenpi
Copy link
Member

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:

result = if isnothing(result)
nothing
elseif isa(result, Markdown.MD)
convert(Node, result)
else
# TODO: we could handle the cases where the user provides some of the Markdown library
# objects, like Paragraph.
@warn """
Invalid type of object in @eval in $(Documenter.locrepr(page.source))
```$(x.info)
$(x.code)
```
Evaluate to `$(typeof(result))`, should be one of
- Nothing
- Markdown.MD
Falling back to code block representation.
If you are seeing this warning after upgrading Documenter and this used to work,
please open an issue on the Documenter issue tracker.
"""
MarkdownAST.@ast MarkdownAST.Document() do
MarkdownAST.CodeBlock("", sprint(show, MIME"text/plain"(), result))
end
end

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.

@disberd
Copy link
Author

disberd commented Jul 12, 2023

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

@fredrikekre
Copy link
Member

Could this not be handled similarly to the result from @example blocks?

@disberd
Copy link
Author

disberd commented Jul 12, 2023

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)

@mortenpi
Copy link
Member

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.

@disberd
Copy link
Author

disberd commented Jul 14, 2023

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.

@disberd
Copy link
Author

disberd commented Aug 10, 2023

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?

@fingolfin fingolfin changed the title Add @evalraw synthax for programmatically generate raw contents Add @evalraw syntax for programmatically generate raw contents Oct 31, 2025
@fingolfin
Copy link
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants