Skip to content
New issue

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

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

Already on GitHub? # to your account

Implement *automatic* fallback #5

Merged
merged 1 commit into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions docs/src/fallback.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -21,22 +21,25 @@ 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
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`)).
26 changes: 22 additions & 4 deletions src/fallback.jl
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,7 @@
doc,
errors
)
fallback = Documenter.getplugin(doc, ExternalFallbacks)
return (xref_unresolved(node) && haskey(fallback.mapping, slug))
return xref_unresolved(node)

Check warning on line 101 in src/fallback.jl

View check run for this annotation

Codecov / codecov/patch

src/fallback.jl#L101

Added line #L101 was not covered by tests
end


Expand All @@ -113,9 +112,28 @@
errors
)
links = Documenter.getplugin(doc, InterLinks)
fallback = Documenter.getplugin(doc, ExternalFallbacks)
fallbacks = Documenter.getplugin(doc, ExternalFallbacks)

Check warning on line 115 in src/fallback.jl

View check run for this annotation

Codecov / codecov/patch

src/fallback.jl#L115

Added line #L115 was not covered by tests
@assert node.element isa MarkdownAST.Link
extref = fallback.mapping[slug]
extref = "@extref $slug"
try
extref = fallbacks.mapping[slug]

Check warning on line 119 in src/fallback.jl

View check run for this annotation

Codecov / codecov/patch

src/fallback.jl#L117-L119

Added lines #L117 - L119 were not covered by tests
catch
candidates = links(Regex("[`.]\\Q$slug\\E`"))
if length(candidates) == 0

Check warning on line 122 in src/fallback.jl

View check run for this annotation

Codecov / codecov/patch

src/fallback.jl#L121-L122

Added lines #L121 - L122 were not covered by tests
# broaden the search
candidates = links(slug)

Check warning on line 124 in src/fallback.jl

View check run for this annotation

Codecov / codecov/patch

src/fallback.jl#L124

Added line #L124 was not covered by tests
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

Check warning on line 131 in src/fallback.jl

View check run for this annotation

Codecov / codecov/patch

src/fallback.jl#L126-L131

Added lines #L126 - L131 were not covered by tests
end
@info "ExternalFallbacks automatic resolution of $(repr(slug)) => $(repr(extref))"
fallbacks.mapping[slug] = extref

Check warning on line 134 in src/fallback.jl

View check run for this annotation

Codecov / codecov/patch

src/fallback.jl#L133-L134

Added lines #L133 - L134 were not covered by tests
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"])
Expand Down
82 changes: 82 additions & 0 deletions test/test_fallback.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using DocumenterInterLinks
using Documenter
using DocInventories
using Documenter: DOCUMENTER_VERSION
using IOCapture: IOCapture
using TestingUtilities: @Test
Expand Down Expand Up @@ -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
Loading