Generating Documentation

Under the hood of documenter.jl

Morten Piibeleht

@mortenpi

"""
    Base.runtests(tests=["all"]; ncores=ceil(Int, Sys.CPU_THREADS / 2),
                  exit_on_error=false, [seed])
Run the Julia unit tests listed in `tests`, which can be either a string or an array of
strings, using `ncores` processors. If `exit_on_error` is `false`, when one test
fails, all remaining tests in other files will still be run; they are otherwise discarded,
when `exit_on_error == true`.
If a seed is provided via the keyword argument, it is used to seed the
global RNG in the context where the tests are run; otherwise the seed is chosen randomly.
"""
function runtests(tests = ["all"]; ncores = ceil(Int, Sys.CPU_THREADS / 2),
                  exit_on_error=false,
                  seed::Union{BitInteger,Nothing}=nothing)
    if isa(tests,AbstractString)
        tests = split(tests)
    end
    exit_on_error && push!(tests, "--exit-on-error")
    seed !== nothing && push!(tests, "--seed=0x$(string(seed % UInt128, base=16))") # cast to UInt128 to avoid a minus sign
    ENV2 = copy(ENV)
    ENV2["JULIA_CPU_THREADS"] = "$ncores"
    try
        run(setenv(`$(julia_cmd()) $(joinpath(Sys.BINDIR::String,
            Base.DATAROOTDIR, "julia", "test", "runtests.jl")) $tests`, ENV2))
    catch
        buf = PipeBuffer()
        Base.require(Base, :InteractiveUtils).versioninfo(buf)
        error("A test has failed. Please submit a bug report (https://github.com/JuliaLang/julia/issues)\n" *
              "including error messages above and the output of versioninfo():\n$(read(buf, String))")
    end
end

Inline docstrings in .JL files

help?> Base.runtests
  Base.runtests(tests=["all"]; ncores=ceil(Int, Sys.CPU_THREADS / 2),
                exit_on_error=false, [seed])

  Run the Julia unit tests listed in tests, which can be either a string or an array of strings, using ncores
  processors. If exit_on_error is false, when one test fails, all remaining tests in other files will still be
  run; they are otherwise discarded, when exit_on_error == true. If a seed is provided via the keyword argument,
  it is used to seed the global RNG in the context where the tests are run; otherwise the seed is chosen randomly.
# Unit Testing

```@meta
DocTestSetup = :(using Test)
```

## Testing Base Julia

Julia is under rapid development and has an extensive test suite to verify functionality across
multiple platforms. If you build Julia from source, you can run this test suite with `make test`.
In a binary install, you can run the test suite using `Base.runtests()`.

```@docs
Base.runtests
```

## Basic Unit Tests

The `Test` module provides simple *unit testing* functionality. Unit testing is a way to
see if your code is correct by checking that the results are what you expect. It can be helpful
to ensure your code still works after you make changes, and can be used when developing as a way
of specifying the behaviors your code should have when complete.

Simple unit testing can be performed with the `@test` and `@test_throws` macros:

```@docs
Test.@test
Test.@test_throws
```

For example, suppose we want to check our new function `foo(x)` works as expected:

```jldoctest testfoo
julia> using Test

julia> foo(x) = length(x)^2
foo (generic function with 1 method)
```

If the condition is true, a `Pass` is returned:

```jldoctest testfoo
julia> @test foo("bar") == 9
Test Passed

julia> @test foo("fizz") >= 10
Test Passed
```

If the condition is false, then a `Fail` is returned and an exception is thrown:

```jldoctest testfoo
julia> @test foo("f") == 20
Test Failed at none:1
  Expression: foo("f") == 20
   Evaluated: 1 == 20
ERROR: There was an error during testing
```

Markdown pages

Build pipelines

Build pipelines

Basic setup

# make.jl
using Documenter, Example

makedocs(
    sitename = "Example.jl",
    modules = [Example],
    pages = Any[
        "Home" => "index.md",
        "Showcase" => "showcase.md",
    ],
)
Example/
├── .travis.yml
├── Project.toml
├── README.md
├── LICENSE.md
├── src
│   ├── Example.jl
│   └── utilities.jl
└── test
    └── runtests.jl
Example/
├── .travis.yml
├── Project.toml
├── README.md
├── LICENSE.md
├── src
│   ├── Example.jl
│   └── utilities.jl
├── test
│   └── runtests.jl
└── docs
Example/
├── .travis.yml
├── Project.toml
├── README.md
├── LICENSE.md
├── src
│   ├── Example.jl
│   └── utilities.jl
├── test
│   └── runtests.jl
└── docs
    ├── Project.toml
    ├── src
    │   ├── assets
    │   │   ├── favicon.ico
    │   │   └── logo.svg
    │   ├── index.md
    │   └── showcase.md
    └── make.jl
Example/
├── .travis.yml
├── Project.toml
├── README.md
├── LICENSE.md
├── src
│   ├── Example.jl
│   └── utilities.jl
├── test
│   └── runtests.jl
└── docs
    ├── Project.toml
    ├── src
    │   ├── assets
    │   │   ├── favicon.ico
    │   │   └── logo.svg
    │   ├── index.md
    │   └── showcase.md
    ├── make.jl
    └── build
Example/
├── .travis.yml
├── Project.toml
├── README.md
├── LICENSE.md
├── src
│   ├── Example.jl
│   └── utilities.jl
├── test
│   └── runtests.jl
└── docs
    ├── Project.toml
    ├── src
    │   ├── assets
    │   │   ├── favicon.ico
    │   │   └── logo.svg
    │   ├── index.md
    │   └── showcase.md
    ├── make.jl
    └── build
        ├── assets
        │   ├── favicon.ico
        │   ├── logo.svg
        │   ├── documenter.js
        │   ├── search.js
        │   └── documenter.css
        ├── index.html
        ├── showcase
        │   └── index.html
        ├── search_index.js
        └── search
            └── index.html
$ julia --project=docs/ docs/make.jl
[ Info: SetupBuildDirectory: setting up build directory.
[ Info: Doctest: running doctests.
[ Info: ExpandTemplates: expanding markdown templates.
[ Info: CrossReferences: building cross-references.
[ Info: CheckDocument: running document checks.
[ Info: Populate: populating indices.
[ Info: RenderDocument: rendering document.
[ Info: HTMLWriter: rendering HTML pages.

REPL WORKFLOW

pkg> activate docs/

julia> using Revise

julia> include("docs/make.jl")
[ Info: SetupBuildDirectory: setting up build directory.
[ Info: Doctest: running doctests.
[ Info: ExpandTemplates: expanding markdown templates.
[ Info: CrossReferences: building cross-references.
[ Info: CheckDocument: running document checks.
[ Info: Populate: populating indices.
[ Info: RenderDocument: rendering document.
[ Info: HTMLWriter: rendering HTML pages.

Travis: Basic setup

using Documenter, DocStringExtensions

makedocs(
    sitename = "DocStringExtensions.jl",
    modules = [DocStringExtensions],
    pages = Any[
        "Home" => "index.md",
        "Showcase" => "showcase.md",
    ],
)

deploydocs(
    repo = "github.com/JuliaDocs/DocStringExtensions.jl.git",
)
DocStringExtensions/
├── .travis.yml
├── Project.toml
├── README.md
├── LICENSE.md
├── src
│   ├── abbreviations.jl
│   ├── DocStringExtensions.jl
│   ├── templates.jl
│   └── utilities.jl
└── test
    ├── coverage.jl
    └── runtests.jl
DocStringExtensions/
├── .travis.yml
├── Project.toml
├── README.md
├── LICENSE.md
├── src
│   ├── abbreviations.jl
│   ├── DocStringExtensions.jl
│   ├── templates.jl
│   └── utilities.jl
├── test
│   ├── coverage.jl
│   └── runtests.jl
└── docs
    ├── build
    ├── make.jl
    ├── Project.toml
    └── src
        ├── assets
        │   ├── favicon.ico
        │   └── logo.svg
        ├── index.md
        └── showcase.md

Travis

jobs:
  include:
    - stage: "Documentation"
      julia: 1.0
      os: linux
      script:
        - julia --project=docs/ -e'
              using Pkg; Pkg.instantiate();
              Pkg.develop(PackageSpec(path=pwd()))'
        - julia --project=docs/ docs/make.jl
      after_success: skip

Splicing docstrings

## Public Interface

```@docs
Documenter
makedocs
hide
deploydocs
Deps
Deps.pip
doctest
DocMeta
DocMeta.getdocmeta
DocMeta.setdocmeta!
```

Explicit declaration

```@docs
foo
foo()
foo(::Int, ::AbstractString)
```

Type signatures

Docstrings

julia> using LinearAlgebra

julia> Docs.meta(LinearAlgebra)
IdDict{Any,Any} with 139 entries:
  Base.Math.acot                 => MultiDoc(Type[Union{Tuple{AbstractArray{T,2}}, Tu…
  LinearAlgebra.normalize        => MultiDoc(Type[Union{Tuple{AbstractArray{T,1} wher…
  LinearAlgebra.lowrankdowndate! => MultiDoc(Type[Tuple{Cholesky,Union{DenseArray{T,1…
  Base.copy                      => MultiDoc(Type[Tuple{Union{Adjoint, Transpose}}], …
  LinearAlgebra.QRPivoted        => MultiDoc(Type[Union{}], IdDict{Any,Any}(Union{}=>…
  LinearAlgebra.logdet           => MultiDoc(Type[Tuple{AbstractArray{T,2} where T}],…
  LinearAlgebra.ldlt!            => MultiDoc(Type[Union{Tuple{SymTridiagonal{T,V}}, T…
  Base.:*                        => MultiDoc(Type[Tuple{AbstractArray{T,2} where T,Ab…
  LinearAlgebra.eigvecs          => MultiDoc(Type[Tuple{SymTridiagonal{#s621,V} where…
  Base.exp                       => MultiDoc(Type[Tuple{Union{DenseArray{#s622,2}, Re…
  LinearAlgebra.triu             => MultiDoc(Type[Tuple{AbstractArray{T,2} where T}, …
  LinearAlgebra.ldlt             => MultiDoc(Type[Union{Tuple{SymTridiagonal{T,V} whe…
  LinearAlgebra.rank             => MultiDoc(Type[Tuple{AbstractArray{T,2} where T}],…
  LinearAlgebra.lowrankdowndate  => MultiDoc(Type[Tuple{Cholesky,Union{DenseArray{T,1…
  Base.copyto!                   => MultiDoc(Type[Tuple{AbstractArray{T,2} where T,Un…
  Base.sin                       => MultiDoc(Type[Tuple{AbstractArray{#s622,2} where …
  LinearAlgebra.Hermitian        => MultiDoc(Type[Union{Tuple{AbstractArray{T,2} wher…
  LinearAlgebra.triu!            => MultiDoc(Type[Tuple{AbstractArray{T,2} where T}, …
  Base.Math.sincos               => MultiDoc(Type[Tuple{AbstractArray{#s620,2} where …
  LinearAlgebra.LinearAlgebra    => MultiDoc(Type[Union{}], IdDict{Any,Any}(Union{}=>…
  Base.Math.sec                  => MultiDoc(Type[Union{Tuple{AbstractArray{T,2}}, Tu…
  Base.cosh                      => MultiDoc(Type[Tuple{AbstractArray{T,2} where T}],…
  LinearAlgebra.opnorm           => MultiDoc(Type[Union{Tuple{AbstractArray{T,2} wher…
  LinearAlgebra.eigmin           => MultiDoc(Type[Tuple{Union{Number, AbstractArray{T…
  LinearAlgebra.cond             => MultiDoc(Type[Union{Tuple{AbstractArray{T,2} wher…
  LinearAlgebra.isposdef!        => MultiDoc(Type[Tuple{AbstractArray{T,2} where T}],…
  LinearAlgebra.lowrankupdate!   => MultiDoc(Type[Tuple{Cholesky,Union{DenseArray{T,1…
  LinearAlgebra.svdvals          => MultiDoc(Type[Union{Tuple{AbstractArray{T,2}}, Tu…
  LinearAlgebra.nullspace        => MultiDoc(Type[Tuple{AbstractArray{T,2} where T}],…
  LinearAlgebra.logabsdet        => MultiDoc(Type[Tuple{AbstractArray{T,2} where T}],…
  Base.Math.acoth                => MultiDoc(Type[Union{Tuple{AbstractArray{T,2}}, Tu…
  ⋮                              => ⋮
  • Handled by Base.Docs module
  • Each module has a metadata dictionary
    • Access: Docs.meta(::Module)

    • Docs.Binding => Docs.MultiDoc

  • Docs.Binding: (:: Module, :: Symbol) pair

    • e.g. for eigen Docs.Binding(LinearAlgebra, :eigen)
  • ​Docs.MultiDoc

    • ​A dictionary of docstrings

    • Keys: type signatures

Docstrings

module Foo

    "function bar"
    function bar end

    "bar()"
    bar() = 1

    "bar(int, string)"
    bar(x::Int, s::AbstractString) = 1

end
julia> m = Docs.meta(Foo)
IdDict{Any,Any} with 1 entry:
  Foo.bar => MultiDoc(Type[Union{}, Tuple{}, Tuple{Int64,AbstractString}], IdDict{Any,Any}(Union{}=>DocStr(svec(…














julia> m = Docs.meta(Foo)
IdDict{Any,Any} with 1 entry:
  Foo.bar => MultiDoc(Type[Union{}, Tuple{}, Tuple{Int64,AbstractString}], IdDict{Any,Any}(Union{}=>DocStr(svec(…

julia> multidoc = m[Docs.Binding(Foo, :bar)];












julia> m = Docs.meta(Foo)
IdDict{Any,Any} with 1 entry:
  Foo.bar => MultiDoc(Type[Union{}, Tuple{}, Tuple{Int64,AbstractString}], IdDict{Any,Any}(Union{}=>DocStr(svec(…

julia> multidoc = m[Docs.Binding(Foo, :bar)];

julia> multidoc.docs
IdDict{Any,Any} with 3 entries:
  Union{}                     => DocStr(svec("function bar"), function bar…
  Tuple{}                     => DocStr(svec("bar()"), bar()…
  Tuple{Int64,AbstractString} => DocStr(svec("bar(int, string)"), bar(int, string)…






julia> m = Docs.meta(Foo)
IdDict{Any,Any} with 1 entry:
  Foo.bar => MultiDoc(Type[Union{}, Tuple{}, Tuple{Int64,AbstractString}], IdDict{Any,Any}(Union{}=>DocStr(svec(…

julia> multidoc = m[Docs.Binding(Foo, :bar)];

julia> multidoc.docs
IdDict{Any,Any} with 3 entries:
  Union{}                     => DocStr(svec("function bar"), function bar…
  Tuple{}                     => DocStr(svec("bar()"), bar()…
  Tuple{Int64,AbstractString} => DocStr(svec("bar(int, string)"), bar(int, string)…

julia> multidoc.docs[Tuple{Int,AbstractString}]
Base.Docs.DocStr(svec("bar(int, string)"), ...)



julia> m = Docs.meta(Foo)
IdDict{Any,Any} with 1 entry:
  Foo.bar => MultiDoc(Type[Union{}, Tuple{}, Tuple{Int64,AbstractString}], IdDict{Any,Any}(Union{}=>DocStr(svec(…

julia> multidoc = m[Docs.Binding(Foo, :bar)];

julia> multidoc.docs
IdDict{Any,Any} with 3 entries:
  Union{}                     => DocStr(svec("function bar"), function bar…
  Tuple{}                     => DocStr(svec("bar()"), bar()…
  Tuple{Int64,AbstractString} => DocStr(svec("bar(int, string)"), bar(int, string)…

julia> multidoc.docs[Tuple{Int,AbstractString}]
Base.Docs.DocStr(svec("bar(int, string)"), ...)

julia> multidoc.docs[Union{}]
Base.Docs.DocStr(svec("function bar"), ...)
# Methods

```@docs
Foo.bar
Foo.bar()
Foo.bar(::Int, ::AbstractString)
```

Julia source

Documentation

Splicing docstrings

# Documents

```@autodocs
Modules = [Documenter.Documents]
```

Automatic inclusion:

  • @autodocs block
  • Supports the following filters
    • Modules: by module
    • Pages: by .jl filename
    • Public/Private: (un)exported names
    • Filter: arbitrary user function

doctesting

[text encodings](https://en.wikipedia.org/wiki/Character_encoding).) Here is how `Char` values are
input and shown:

```
julia> 'x'
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)

julia> typeof(ans)
Char
```

You can easily convert a `Char` to its integer value, i.e. code point:

```
julia> Int('x')
120

julia> typeof(ans)
Int64
```

On 32-bit architectures, [`typeof(ans)`](@ref) will be [`Int32`](@ref). You can convert an
integer value back to a `Char` just as easily:

```
julia> Char(120)
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)
```
[text encodings](https://en.wikipedia.org/wiki/Character_encoding).) Here is how `Char` values are
input and shown:

```jldoctest
julia> 'x'
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)

julia> typeof(ans)
Char
```

You can easily convert a `Char` to its integer value, i.e. code point:

```jldoctest
julia> Int('x')
120

julia> typeof(ans)
Int64
```

On 32-bit architectures, [`typeof(ans)`](@ref) will be [`Int32`](@ref). You can convert an
integer value back to a `Char` just as easily:

```jldoctest
julia> Char(120)
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)
```

doctesting

module Foo

    """
        hello(who::String)
    
    Return "Hello, `who`".
    
    ```jldoctest
    julia> hello("Stranger")
    "Hi, Stranger"
    ```
    """
    hello(who::String) = "Hello, $who"

end
julia> using Foo, Documenter

julia> doctest(Foo)






























using Test, Documenter, Example
@testset "Example" begin
    ... # other tests & testsets
    doctest(Example)
    ... # other tests & testsets
end

Test suite integration:

AVAILABLE NOW

in a registry near you!

julia> using Foo, Documenter

julia> doctest(Foo)
[ Info: SetupBuildDirectory: setting up build directory.
[ Info: Doctest: running doctests.
┌ Error: doctest failure in ~/Julia/JuliaDocs/Example/src/Example.jl:9-12                                                                                                                                            
│                                                                                                                                                                                                                    
│ ```jldoctest; setup = :(using Example)                                                                                                                                                                             
│ julia> hello("Stranger")                                                                                                                                                                                           
│ Hi, Stranger
│ ```
│ 
│ Subexpression:
│ 
│ hello("Stranger")
│ 
│ Evaluated output:
│ 
│ "Hello, Stranger"
│ 
│ Expected output:
│ 
│ Hi, Stranger
│ 
│   diff = Hi, Stranger"Hello, Stranger"
└ @ Documenter.DocTests ~/Julia/JuliaDocs/Documenter/src/DocTests.jl:356
┌ Error: Doctesting failed
└ @ Documenter ~/Julia/JuliaDocs/Documenter/src/Documenter.jl:911
`makedocs` encountered a doctest error. Terminating build
Stacktrace:
 [...]
Test Summary:     | Fail  Total
Doctests: Example |    1      1
ERROR: Some tests did not pass: 0 passed, 1 failed, 0 errored, 0 broken.

doctesting: outdated doctests

julia> doctest(Example, fix=true)
            
            
            
            
            
            
            
            
            













julia> doctest(Example, fix=true)
[ Info: SetupBuildDirectory: setting up build directory.
[ Info: Doctest: running doctests.
[ Info: Skipped ExpandTemplates step (doctest only).
[ Info: Skipped CrossReferences step (doctest only).
[ Info: Skipped CheckDocument step (doctest only).
[ Info: Skipped Populate step (doctest only).
[ Info: Skipped RenderDocument step (doctest only).
Test Summary:     | Pass  Total
Doctests: Example |    1      1
Test.DefaultTestSet("Doctests: Example", Any[], 1, false)












julia> doctest(Example, fix=true)
[ Info: SetupBuildDirectory: setting up build directory.
[ Info: Doctest: running doctests.
[ Info: Skipped ExpandTemplates step (doctest only).
[ Info: Skipped CrossReferences step (doctest only).
[ Info: Skipped CheckDocument step (doctest only).
[ Info: Skipped Populate step (doctest only).
[ Info: Skipped RenderDocument step (doctest only).
Test Summary:     | Pass  Total
Doctests: Example |    1      1
Test.DefaultTestSet("Doctests: Example", Any[], 1, false)

julia> doctest(Example)
[ Info: SetupBuildDirectory: setting up build directory.
[ Info: Doctest: running doctests.
[ Info: Skipped ExpandTemplates step (doctest only).
[ Info: Skipped CrossReferences step (doctest only).
[ Info: Skipped CheckDocument step (doctest only).
[ Info: Skipped Populate step (doctest only).
[ Info: Skipped RenderDocument step (doctest only).
Test Summary:     | Pass  Total
Doctests: Example |    1      1
Test.DefaultTestSet("Doctests: Example", Any[], 1, false)

Code evaluation

# Code Evaluation: `@example`

```@example
A = rand(ComplexF64, 10, 15)
real.(A)
```

Code evaluation

# Code Evaluation: `@repl`

```@repl
using LinearAlgebra
A = rand(3, 3)
A^2
norm(ans)
```

Code evaluation

# Plotting

```@example
using Luxor
d = Drawing(600, 400)
origin()
fontsize(50)
circle(O, 150, :stroke)
text("hello world", halign=:center, valign=:middle)
finish()
d # hide
```

Object showable with other MIME types:

New HTML FronT End

WIP

Themes & Plugins

@charset "UTF-8";

@import "darkly/variables";

$sidebar-background: $grey-darker;
$shadow: $grey-darker;
$sidebar-color: $text;
$lightness-unit: -8%;

$docstring-pre-background: adjust-color($background, $lightness: 5);

@import "documenter/utilities";
@import "documenter/variables";

@import "bulma/utilities/all";
@import "bulma/base/all";

@import "documenter/overrides";

@import "bulma/elements/all";
@import "bulma/form/all";
@import "bulma/components/all";
@import "bulma/grid/all";
@import "bulma/layout/all";
@import "bulma-dashboard";

@import "darkly/overrides";

@import "documenter/components";
@import "documenter/patches";
@import "documenter/layout/all";

.docstring > section {
  pre {
    background-color: $docstring-pre-background;
  }
}
  • Uses the Bulma CSS framework
  • Creating custom themes:
    • Overload variables
    • Compile w/ DocumenterTools
  • Better APIs for JS extensions

Generating Documentation

Under the hood of documenter.jl

Morten Piibeleht

@mortenpi

Michael Hatherly, Fredrik Ekre, and many others

Generating Documentation in Julia

By Morten Piibeleht

Generating Documentation in Julia

Under the hood of Documenter.jl (JuliaCon 2019) https://www.youtube.com/watch?v=m3c8Z6HBn48

  • 1,126