DocOps / liquidoc-gem

The canonical gem source for LiquiDoc, a ruby-based documentation parsing and rendering utility enabling advanced builds with Asciidoctor, Jekyll, Liquid, and semi-structured data files.
https://docs.docops.org/liquidoc-user-manual.html
MIT License
12 stars 5 forks source link

Enable forced conforming of xref markup in asciidocify renderings #58

Closed briandominick closed 5 years ago

briandominick commented 5 years ago

In order for AsciiDoc cross-reference (xrefs) markup to work across document output formats (multi-page website vs article/book-style), we need a solution to the crucial mismatch in markup formatting required by the two kinds, at least when one of the rendering operations is a Jekyll build.

Problem

Asciidoctor is only aware of one document build at a time. So if Asciidoctor building one complete book-formatted doc, it knows where all the internal xrefs should point, and it has "foreknowledge", so to speak, of the titles associated with all those targets. Conventionally, you can write <<some-unique-heading-somewhere>> and see it render as Some Unique Heading Somewhere, which text links to that exact target subsection of the document.

In a Jekyll build, however, each page is a document, the core content rendered one doc at a time by Asciidoctor, with no knowledge of where to find the heading tagged with [[some-unique-heading-somewhere]] (explicitly or implicitly derived from the heading text). Not only is Asciidoctor unable to link to those targets, then, it is also unable to discern the associated header text and represent it as the hyperlinked text, as above.

Most people building Jekyll sites with AsciiDoc solve this by writing links like so: <<relevant-page#some-unique-heading-somewhere,Some Unique Heading Somewhere>>.

This is a painful solution for a couple reasons. First, it requires knowledge of the header text of the target, and this must be manually maintained. Ideally, an ID could be explicitly assigned to each target, then even if the section title text changes (as with internationalization, for instance), the xref key could remain the same.

What's more, this frustrating solution is no solution at all if the goal is to build both a conventional "chunked" website and a linear book-style document from the same source, because those Jekyll-friendly xrefs do not work with conventional xrefs.

Proposed Solution

The solution is not, at this time, to solve this by making the Jekyll or LiquiDoc builds somehow globally aware of what targets are in which external documents (i.e., other pages). I spent a bunch of time trying to figure out how to make LiquiDoc aware of all the subheadings in the entire project, so that I could informatively rewrite xrefs and allow either conventional AsciiDoc to work, or possibly add some kind of Liquid parsing (preprocessing) to AsciiDoc files, which is actually already a feature of the jekyll-asciidoc plugin (already a LiquiDoc dependency). Basically, the procedure falls apart around dynamic IDs and headers. We really don't know what all is where until Asciidoctor is done with its preprocessing. It's a chicken vs egg conundrum: we cannot know what is where until we render AsciiDoc, but we cannot wait to render AsciiDoc until after all the xrefs are rewritten -- at least, not without duplicating the build, and even then, the scope of the task is open-ended.

However, if we let go (for now) of the idea of having Asciidoctor do the heavy lifting of filling in the link text (which is plenty often the case, anyway, when the link text differs from the target heading text)... and if we accept that we need to know which page a target appears in... suddenly, the clunky Jekyll links become good enough for conventional Asciidoctor unified-document builds. All we need to do is drop the page designators from all Jekyll-friendly xrefs.

That is, change <<some-unique-topic-slug#some-globally-unique-header-slug,the text we want>> to <<some-globally-unique-header-slug,the text we want>> across all target AsciiDoc documents, then render them as normal, and all those xrefs will be interpreted conventionally, and they should all work that way.

Limitations

To be clear, this is a labor-intensive solution. It can be combined with solutions like generating xref attributes for all published topic slugs, as in LiquiDoc CMF. Without knowing subheadings and other targets in external topics, this solution has been limited to cross-references to entire pages, not to anchors down the page. The xref attributes solution is cross-format compatible, however, as it builds a set of xref attributes with unique values for each output format.

LiquiDoc/Asciidoctor will still not know the text for subheadings, so users will have to enter and maintain the text manually. This is not the same as the chicken-vs-egg dilemma expressed before around dynamism, since when writing explicit xrefs (with explicit text), any dynamism in the target should also be incorporated into the source. Take the following target header markup for example:

[[some-{environment}-header-text]]
Some {environment} Header Text

Links to this header should be easy to create dymamically: <<some-{environment}-header-text>>. The required foreknowledge either includes a variable or it doesn't; there is no added complexity to maintaining the strings across separate files.

Concerns

All we are doing is removing some variable text that should only ever be hard coded. That is, while the target strings will vary in their makeup, they will not include variables -- they will be explicit in the source. Therefore, copying the entire source base to _build/pre/xref_converted and running the build from there seems to make sense, but it adds a little complexity. Either LiquiDoc needs to wisely handle all path rewrites, or the user needs to perform these renderings using the preprocessed files. So, we might see source: _build/pre/xref_converted/ in non-Jekyll render sections of the config file. I don't think this adds a ton of complexity for the user, whereas URL rewrites would add a lot of complexity and risk to LiquiDoc. This should be the first thing to test.