Skip to content

Commit

Permalink
Add @(auto)docs; canonical=false
Browse files Browse the repository at this point in the history
This allows @docs to be included multiple times, as long as all but one
are marked as noncanonical. References will then only target the
canonical @docs. Also implemented for @autodocs.
  • Loading branch information
frankier committed Sep 14, 2023
1 parent 975a0d3 commit 6c67964
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 11 deletions.
21 changes: 21 additions & 0 deletions docs/src/man/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,23 @@ have a docstring attached to `foo(::Integer) = ...`, then neither `foo(::Number)
The only way you can splice that docstring is by listing exactly `foo(::Integer)` in
the at-docs block.

### [`@docs; canonical=false` block](@id noncanonical-block)

You can pass the `canonical` keyword argument as `false` to `@docs` to indicate
that the `@docs` should be be considered as non-canonical like so:

````markdown
```@docs; canonical=false
makedocs
```
````

This is useful when you want to include a docstring inline somewhere, e.g. in
a tutorial, but the canonical version of the docstring is already in the API
reference. References will all point to the canonical `@docs` block. For
a particular docstring, you can include it as many times as you like with
`@docs ; canonical=false`, but only once without. Non-canonical `@docs` blocks
are ignored when checking for missing docstrings.

## `@autodocs` block

Expand Down Expand Up @@ -155,6 +172,10 @@ Order = [:type]
When more complex sorting is needed then use `@docs` to define it
explicitly.

As with `@docs`, you can use `@autodocs; canonical=false` to indicate that the
`@autodocs` block in non-canonical. See [`@docs; canonical=false` block](@ref
noncanonical-block).

## `@ref` link

Used in markdown links as the URL to tell Documenter to generate a cross-reference
Expand Down
14 changes: 14 additions & 0 deletions docs/src/showcase.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,20 @@ DocumenterShowcase.bar

If you have very many docstrings, you may also want to consider using the [`@autodocs` block](@ref) which can include a whole set of docstrings automatically based on certain filtering options

Both `@docs` and `@autodocs` support the [`canonical=false` keyword argument](@ref noncanonical-block). This can be used to include a docstring more than once

````markdown
```@docs; canonical=false
DocumenterShowcase.bar
```
````

We then see the same docstring as above

```@docs; canonical=false
DocumenterShowcase.bar
```

### An index of docstrings

The [`@index` block](@ref) can be used to generate a list of all the docstrings on a page (or even across pages) and will look as follows
Expand Down
5 changes: 4 additions & 1 deletion src/docchecks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function missingdocs(doc::Document)
println(b)
print(b, """
These are docstrings in the checked modules (configured with the modules keyword)
that are not included in @docs or @autodocs blocks.
that are not included in canonical @docs or @autodocs blocks.
""")
@docerror(doc, :missing_docs, String(take!(b)))
end
Expand All @@ -40,6 +40,9 @@ function missingbindings(doc::Document)
@debug "checking for missing docstrings."
bindings = allbindings(doc.user.checkdocs, doc.blueprint.modules)
for object in keys(doc.internal.objects)
if !is_canonical(object)
continue
end
# The module references in docs blocks can yield a binding like
# Docs.Binding(Mod, :SubMod) for a module SubMod, a submodule of Mod. However, the
# module bindings that come from Docs.meta() always appear to be of the form
Expand Down
2 changes: 1 addition & 1 deletion src/documents.jl
Original file line number Diff line number Diff line change
Expand Up @@ -940,7 +940,7 @@ function populate!(index::IndexNode, document::Document)
mod = object.binding.mod
# Include *all* signatures, whether they are `Union{}` or not.
cat = Symbol(lowercase(doccat(object.binding, Union{})))
if _isvalid(page, index.pages) && _isvalid(mod, index.modules) && _isvalid(cat, index.order)
if is_canonical(object) && _isvalid(page, index.pages) && _isvalid(mod, index.modules) && _isvalid(cat, index.order)
push!(index.elements, (object, doc, page, mod, cat))
end
end
Expand Down
54 changes: 47 additions & 7 deletions src/expander_pipeline.jl
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,8 @@ Selectors.order(::Type{Expanders.RawBlocks}) = 11.0

Selectors.matcher(::Type{Expanders.TrackHeaders}, node, page, doc) = isa(node.element, MarkdownAST.Heading)
Selectors.matcher(::Type{Expanders.MetaBlocks}, node, page, doc) = iscode(node, "@meta")
Selectors.matcher(::Type{Expanders.DocsBlocks}, node, page, doc) = iscode(node, "@docs")
Selectors.matcher(::Type{Expanders.AutoDocsBlocks}, node, page, doc) = iscode(node, "@autodocs")
Selectors.matcher(::Type{Expanders.DocsBlocks}, node, page, doc) = iscode(node, r"^@docs")
Selectors.matcher(::Type{Expanders.AutoDocsBlocks}, node, page, doc) = iscode(node, r"^@autodocs")
Selectors.matcher(::Type{Expanders.EvalBlocks}, node, page, doc) = iscode(node, "@eval")
Selectors.matcher(::Type{Expanders.IndexBlocks}, node, page, doc) = iscode(node, "@index")
Selectors.matcher(::Type{Expanders.ContentsBlocks}, node, page, doc) = iscode(node, "@contents")
Expand Down Expand Up @@ -306,13 +306,54 @@ function Selectors.runner(::Type{Expanders.MetaBlocks}, node, page, doc)
node.element = MetaNode(x, copy(meta))
end

# @docs / @autodocs utils
# -----------------------

function parse_docs_args(tag, info)
matched = match(Regex("^@$tag\\s*(;.*)?\$"), info)
matched === nothing && error("invalid '@$tag' syntax: $(info)")
kwargs = matched.captures[1]
is_canonical = true
if kwargs !== nothing
matched = match(r"\bcanonical\s*=\s*(true|false)\b", kwargs)
if matched !== nothing
is_canonical = matched[1] == "true"
end
end
return is_canonical
end

function slugify_pagekey(page_ref)
page_ref = slugify(page_ref)
page_ref = replace(page_ref, "/" => "-")
page_ref = replace(page_ref, r"\.md$" => "")
return page_ref
end

function make_object(binding, typesig, is_canonical, doc, page)
object = Documenter.Object(binding, typesig)
if !is_canonical
primary_anchor = string(object)
counter = get!(page.globals.meta, :noncanonical_docs_counter, Dict())
frag_extra = slugify_pagekey(pagekey(doc, page))
if primary_anchor in keys(counter)
counter[primary_anchor] += 1
frag_extra *= "-$(counter[primary_anchor])"

Check warning on line 341 in src/expander_pipeline.jl

View check run for this annotation

Codecov / codecov/patch

src/expander_pipeline.jl#L340-L341

Added lines #L340 - L341 were not covered by tests
else
counter[primary_anchor] = 1
end
object = Documenter.Object(binding, typesig, frag_extra)
end
return object
end

# @docs
# -----

function Selectors.runner(::Type{Expanders.DocsBlocks}, node, page, doc)
@assert node.element isa MarkdownAST.CodeBlock
x = node.element

is_canonical = parse_docs_args("docs", x.info)
docsnodes = Node[]
curmod = get(page.globals.meta, :CurrentModule, Main)
lines = Documenter.find_block_in_file(x.code, page.source)
Expand Down Expand Up @@ -350,8 +391,7 @@ function Selectors.runner(::Type{Expanders.DocsBlocks}, node, page, doc)
continue
end
typesig = Core.eval(curmod, Documenter.DocSystem.signature(ex, str))

object = Documenter.Object(binding, typesig)
object = make_object(binding, typesig, is_canonical, doc, page)
# We can't include the same object more than once in a document.
if haskey(doc.internal.objects, object)
@docerror(doc, :docs_block,
Expand Down Expand Up @@ -410,7 +450,7 @@ const AUTODOCS_DEFAULT_ORDER = [:module, :constant, :type, :function, :macro]
function Selectors.runner(::Type{Expanders.AutoDocsBlocks}, node, page, doc)
@assert node.element isa MarkdownAST.CodeBlock
x = node.element

is_canonical = parse_docs_args("autodocs", x.info)
curmod = get(page.globals.meta, :CurrentModule, Main)
fields = Dict{Symbol, Any}()
lines = Documenter.find_block_in_file(x.code, page.source)
Expand Down Expand Up @@ -482,7 +522,7 @@ function Selectors.runner(::Type{Expanders.AutoDocsBlocks}, node, page, doc)
if filtered
for (typesig, docstr) in multidoc.docs
path = normpath(docstr.data[:path])
object = Documenter.Object(binding, typesig)
object = make_object(binding, typesig, is_canonical, doc, page)
if isempty(pages)
push!(results, (mod, path, category, object, isexported, docstr))
else
Expand Down
10 changes: 8 additions & 2 deletions src/utilities/utilities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -253,13 +253,16 @@ Represents an object stored in the docsystem by its binding and signature.
struct Object
binding :: Binding
signature :: Type
noncanonical_extra :: Union{String, Nothing}

function Object(b::Binding, signature::Type)
function Object(b::Binding, signature::Type, noncanonical_extra=nothing)
m = nameof(b.mod) === b.var ? parentmodule(b.mod) : b.mod
new(Binding(m, b.var), signature)
new(Binding(m, b.var), signature, noncanonical_extra)
end
end

is_canonical(o::Object) = o.noncanonical_extra === nothing

function splitexpr(x::Expr)
isexpr(x, :macrocall) ? splitexpr(x.args[1]) :
isexpr(x, :.) ? (x.args[1], x.args[2]) :
Expand Down Expand Up @@ -292,7 +295,10 @@ end
function Base.print(io::IO, obj::Object)
print(io, obj.binding)
print_signature(io, obj.signature)
print_extra(io, obj.noncanonical_extra)
end
print_extra(io::IO, noncanonical_extra::Nothing ) = nothing
print_extra(io::IO, noncanonical_extra::String ) = print(io, "-", noncanonical_extra)
print_signature(io::IO, signature::Union{Union, Type{Union{}}}) = nothing
print_signature(io::IO, signature) = print(io, '-', signature)

Expand Down
10 changes: 10 additions & 0 deletions test/examples/src/lib/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ while
@assert
```

```@docs; canonical=false
func(x)
T
ccall
for
while
@time
@assert
```

# Foo

```@example
Expand Down

0 comments on commit 6c67964

Please sign in to comment.