From 8f17adb96007652f969ea76315ef2a5c1ed603ce Mon Sep 17 00:00:00 2001 From: Michael Goerz Date: Wed, 28 Feb 2024 15:45:10 -0500 Subject: [PATCH] Implement *automatic* fallback --- docs/src/fallback.md | 19 +++++----- src/fallback.jl | 26 +++++++++++--- test/test_fallback.jl | 82 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 12 deletions(-) diff --git a/docs/src/fallback.md b/docs/src/fallback.md index 328c90d..013c945 100644 --- a/docs/src/fallback.md +++ b/docs/src/fallback.md @@ -10,7 +10,7 @@ If the `@ref` link is to another code object, you could include that docstring i The situation is even worse if the `@ref` link is to a section header. For example, the docstring of [`DocInventories.MIME_TYPES`](@extref) includes a reference to the section on [inventory file formats](@extref DocInventories Inventory-File-Formats) in the `DocInventories` documentation. In that case, there is no way to make the reference resolve in your documentation (short of adding an "Inventory File Formats" section to your own documentation). -To remedy this, the `DocumenterInterLinks` package provides a second `Documenter` plugin, [`ExternalFallbacks`](@ref), that can rewrite specific `@ref` links to `@extref` links. It is instantiated by providing a mapping between "slugs" and `@extref` link specifications. For example, +To remedy this, the `DocumenterInterLinks` package provides a special plugin, [`ExternalFallbacks`](@ref), that can rewrite specific `@ref` links to `@extref` links. It is instantiated by providing a mapping between "slugs" and `@extref` link specifications. For example, ``` fallbacks = ExternalFallbacks( @@ -21,15 +21,21 @@ fallbacks = ExternalFallbacks( !!! warning Like any plugin (and like the [`InterLinks`](@ref) object), `fallbacks` must be passed to [`Documenter.makedocs`](@extref) as an element of `plugins`. -The "slug" on the left-hand-side of the mapping can be obtained from message that `Documenter` prints when it fails to resolve the `@ref` link, e.g., +The "slug" on the left-hand-side of the mapping can be obtained from message that `Documenter` prints when it fails to resolve the `@ref` link. Generally, for `[Section Title](@ref)` or `[text](@ref "Section Title)`, the slug is a "sluggified" version of the title (determined internally by `Documenter`, mostly just replacing spaces with dashes); and for ```[`code`](@ref)``` or `[text](@ref code)`, it is `"code"`. The right-hand-side of the mapping is a full `@extref` link. The plugin simply replaces the link target of original `@ref` link with the given `@extref`. + +If there are any unresolvable `@ref` links, and there is no explicit `@extref`-mapping, [`ExternalFallbacks`](@ref) will search in all available inventories to resolve the link in an "automatic" mode. In this case, you will see messages like ``` -Error: Cannot resolve @ref for 'Inventory-File-Formats' +[ Info: ExternalFallbacks automatic resolution of "Inventory-File-Formats" => "@extref DocInventories :std:label:`Inventory-File-Formats`" ``` -In short, for `[Section Title](@ref)` or `[text](@ref "Section Title)`, the slug is a "sluggified" version of the title (determined internally by `Documenter`, mostly just replacing spaces with dashes); and for ```[`code`](@ref)``` or `[text](@ref code)`, it is `"code"`. +in the output of [`Documenter.makedocs`](@extref). If this is correct, you should copy ```"Inventory-File-Formats" => "@extref DocInventories :std:label:`Inventory-File-Formats`"``` explicitly into the instantiation of the [`ExternalFallbacks`](@ref) plugin. The "automatic resolution" is both slow and has the potential for misidentifying the `@extref` link. + +!!! warning + Do not use `@ref` links in your documentation when you could use an `@extref` link! The [`ExternalFallbacks`](@ref) plugin is intended as a "last resort" to be used deliberately, not to automatically resolve all `@ref` links via [`InterLinks`](@ref) (the way the [Intersphinx](@extref sphinx :doc:`usage/extensions/intersphinx`) plugin does in [Sphinx](@extref sphinx :doc:`index`)). -The right-hand-side of the mapping is a full `@extref` link. The plugin simply replaces the link target of original `@ref` link with the given `@extref` link. With this, it is possible to include the docstring for [`DocInventories.MIME_TYPES`](@extref): + +With an `@extref` mapping for `"Inventory-File-Formats"` in place, it is now possible to include the docstring for [`DocInventories.MIME_TYPES`](@extref): ```@docs @@ -37,6 +43,3 @@ DocInventories.MIME_TYPES ``` Note that the link in the last line of the docstring is an external link. - -!!! info - The [`ExternalFallbacks`](@ref) plugin is intended as a "last resort" to be used deliberately. It is not intended to *automatically* try to resolve all unresolvable `@ref` links via [`InterLinks`](@ref) (the way the [Intersphinx](@extref sphinx :doc:`usage/extensions/intersphinx`) plugin does in [Sphinx](@extref sphinx :doc:`index`)). diff --git a/src/fallback.jl b/src/fallback.jl index 0a2b833..88f65cc 100644 --- a/src/fallback.jl +++ b/src/fallback.jl @@ -98,8 +98,7 @@ function Selectors.matcher( doc, errors ) - fallback = Documenter.getplugin(doc, ExternalFallbacks) - return (xref_unresolved(node) && haskey(fallback.mapping, slug)) + return xref_unresolved(node) end @@ -113,9 +112,28 @@ function Selectors.runner( errors ) links = Documenter.getplugin(doc, InterLinks) - fallback = Documenter.getplugin(doc, ExternalFallbacks) + fallbacks = Documenter.getplugin(doc, ExternalFallbacks) @assert node.element isa MarkdownAST.Link - extref = fallback.mapping[slug] + extref = "@extref $slug" + try + extref = fallbacks.mapping[slug] + catch + candidates = links(Regex("[`.]\\Q$slug\\E`")) + if length(candidates) == 0 + # broaden the search + candidates = links(slug) + end + if length(candidates) > 0 + extref = candidates[begin] + if length(candidates) > 1 + msg = "ExternalFallbacks resolution of \"$slug\" is ambiguous. Candidates are\n - " + msg *= join(repr.(candidates), "\n - ") + @warn msg + end + @info "ExternalFallbacks automatic resolution of $(repr(slug)) => $(repr(extref))" + fallbacks.mapping[slug] = extref + end + end m = match(links.rx, extref) @assert !isnothing(m) # Can't think of any way for the match to fail if isnothing(m["spec"]) diff --git a/test/test_fallback.jl b/test/test_fallback.jl index fab3c85..15890bc 100644 --- a/test/test_fallback.jl +++ b/test/test_fallback.jl @@ -1,5 +1,6 @@ using DocumenterInterLinks using Documenter +using DocInventories using Documenter: DOCUMENTER_VERSION using IOCapture: IOCapture using TestingUtilities: @Test @@ -176,3 +177,84 @@ end end end + + +@testset "Fallback Automatic Test" begin + + links = InterLinks( + "Documenter" => ( + "https://documenter.juliadocs.org/stable/", + joinpath(@__DIR__, "..", "docs", "src", "inventories", "Documenter.toml") + ), + "DocInventories" => ( + "https://github.com/JuliaDocs/DocInventories.jl/dev/", + joinpath(@__DIR__, "..", "docs", "src", "inventories", "DocInventories.toml") + ), + "DocumenterInterLinks" => ( + "http://juliadocs.org/DocumenterInterLinks.jl/dev/", + joinpath(splitext(@__FILE__)[1], "DocumenterInterLinks.toml") + ), + ) + + push!( + # Make the lookup for `Documenter.makedocs` ambiguous + links["DocInventories"], + InventoryItem(name="Documenter.makedocs", role="function", uri="#XXX") + ) + + Base.eval(Main, quote + using Documenter + using DocInventories + using DocumenterInterLinks + end) + + run_makedocs( + splitext(@__FILE__)[1]; + sitename="DocumenterInterLinks.jl", + plugins=[links], + format=Documenter.HTML(; + prettyurls = true, + canonical = "https://juliadocs.github.io/DocumenterInterLinks.jl", + footer = "Generated by Test", + edit_link = "", + repolink = "", + ), + warnonly=false, + check_success=(DOCUMENTER_VERSION ≥ v"1.3.0-dev"), + check_failure=(DOCUMENTER_VERSION < v"1.3.0-dev"), + ) do dir, result, success, backtrace, output + + if DOCUMENTER_VERSION >= v"1.3.0-dev" + @test success + @test contains( + output, + "Warning: ExternalFallbacks resolution of \"makedocs\" is ambiguous" + ) + @test contains( + output, + "ExternalFallbacks automatic resolution of \"makedocs\" => \"@extref Documenter :jl:function:`Documenter.makedocs`\"" + ) + @test contains( + output, + "ExternalFallbacks automatic resolution of \"InterLinks\" => \"@extref DocumenterInterLinks :jl:type:`DocumenterInterLinks.InterLinks`\"" + ) + @test contains( + output, + "ExternalFallbacks automatic resolution of \"Other-Output-Formats\" => \"@extref Documenter :std:label:`Other-Output-Formats`\"" + ) + @test contains( + output, + "ExternalFallbacks automatic resolution of \"Document\" => \"@extref Documenter :jl:type:`Documenter.Document`\"" + ) + @test contains( + output, + "ExternalFallbacks automatic resolution of \"Documenter.getplugin\" => \"@extref Documenter :jl:method:`Documenter.getplugin-Union{Tuple{T}, Tuple{Documenter.Document, Type{T}}} where T<:Documenter.Plugin`\"" + ) + else + @test !success + @test contains(output, "no doc found for reference") + end + + end + +end