Skip to content

Commit

Permalink
Add docstring templates
Browse files Browse the repository at this point in the history
Fixes JuliaDocs#22.

Implements a `at-template` macro used to define 'template' docstrings
that can be applied to different categories of docstrings on a per-module
basis.
  • Loading branch information
MichaelHatherly committed Nov 19, 2016
1 parent bd2a28a commit a448107
Show file tree
Hide file tree
Showing 8 changed files with 283 additions and 5 deletions.
2 changes: 1 addition & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ using Documenter, DocStringExtensions
makedocs(
sitename = "DocStringExtensions.jl",
modules = [DocStringExtensions],
format = Documenter.Formats.HTML,
format = :html,
clean = false,
pages = Any["Home" => "index.md"],
)
Expand Down
2 changes: 1 addition & 1 deletion docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ Modules = [DocStringExtensions]

```@autodocs
Modules = [DocStringExtensions]
Order = [:constant, :function, :type]
Order = [:constant, :function, :macro, :type]
```

8 changes: 5 additions & 3 deletions src/DocStringExtensions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ using Compat

# Exports.

export FIELDS, EXPORTS, METHODLIST, IMPORTS, SIGNATURES, TYPEDEF
export @template, FIELDS, EXPORTS, METHODLIST, IMPORTS, SIGNATURES, TYPEDEF, DOCSTRING


# Note:
Expand All @@ -113,7 +113,7 @@ export FIELDS, EXPORTS, METHODLIST, IMPORTS, SIGNATURES, TYPEDEF
#
if VERSION < v"0.5.0-dev"

const FIELDS, EXPORTS, METHODLIST, IMPORTS, SIGNATURES, TYPEDEF = "", "", "", "", "", ""
include("mock.jl")

else # VERSION < v"0.5.0-dev"

Expand All @@ -129,7 +129,7 @@ end

include("utilities.jl")
include("abbreviations.jl")

include("templates.jl")

#
# Bootstrap abbreviations.
Expand All @@ -151,6 +151,8 @@ let λ = s -> isa(s, Symbol) ? getfield(DocStringExtensions, s) : s
end
end

__init__() = (hook!(template_hook); nothing)

end # VERSION < v"0.5.0-dev"

end # module
21 changes: 21 additions & 0 deletions src/abbreviations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -372,3 +372,24 @@ function format(::TypeDefinition, buf, doc)
println(buf, "```\n")
end
end

#
# `DocStringTemplate`
#

"""
The singleton type for [`DOCSTRING`](@ref) abbreviations.
"""
immutable DocStringTemplate <: Abbreviation end

"""
An [`Abbreviation`](@ref) used in [`@template`](@ref) definitions to represent the location
of the docstring body that should be spliced into a template.
!!! warning
This abbreviation must only ever be used in template strings; never normal docstrings.
"""
const DOCSTRING = DocStringTemplate()

# NOTE: no `format` needed for this 'mock' abbreviation.
7 changes: 7 additions & 0 deletions src/mock.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Mock definitions for 0.4.

const FIELDS, EXPORTS, METHODLIST, IMPORTS, SIGNATURES, TYPEDEF, DOCSTRING =
"", "", "", "", "", "", ""

macro template(expr) nothing end

134 changes: 134 additions & 0 deletions src/templates.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@

const (expander, setter!) = isdefined(Base, :DocBootStrap) ?
(Base.DocBootStrap._expand_, Base.DocBootStrap.setexpand!) : # Julia 0.4
(Core.atdoc, Core.atdoc!) # Julia 0.5+

"""
$(:SIGNATURES)
Set the docstring expander function to first call `func` before calling the default expander.
To remove a hook that has been applied using this method call [`hook!()`](@ref).
"""
hook!(func) = setter!((args...) -> expander(func(args...)...))

"""
$(:SIGNATURES)
Reset the docstring expander to only call the default expander function. This clears any
'hook' that has been set using [`hook!(func)`](@ref).
"""
hook!() = setter!(expander)

"""
$(:SIGNATURES)
Defines a docstring template that will be applied to all docstrings in a module that match
the specified category or tuple of categories.
# Examples
```julia
@template DEFAULT =
\"""
\$(SIGNATURES)
\$(DOCSTRING)
\"""
```
`DEFAULT` is the default template that is applied to a docstring if no other template
definitions match the documented expression. The `DOCSTRING` abbreviation is used to mark
the location in the template where the actual docstring body will be spliced into each
docstring.
```julia
@template (FUNCTIONS, METHODS, MACROS) =
\"""
\$(SIGNATURES)
\$(DOCSTRING)
\$(METHODLIST)
\"""
```
A tuple of categories can be specified when a docstring template should be used for several
different categories.
```julia
@template MODULES = ModName
```
The template definition above will define a template for module docstrings based on the
template for modules found in module `ModName`.
!!! note
Supported categories are `DEFAULT`, `FUNCTIONS`, `METHODS`, `MACROS`, `TYPES`,
`MODULES`, and `CONSTANTS`.
"""
macro template(ex) template(ex) end

const TEMP_SYM = gensym("templates")

function template(ex::Expr)
Meta.isexpr(ex, :(=), 2) || error("invalid `@template` syntax.")
template(ex.args[1], ex.args[2])
end

function template(tuple::Expr, docstr::Union{Symbol, Expr})
Meta.isexpr(tuple, :tuple) || error("invalid `@template` syntax on LHS.")
local curmod = current_module()
isdefined(curmod, TEMP_SYM) || eval(curmod, :(const $(TEMP_SYM) = $(Dict{Symbol, Vector}())))
local block = Expr(:block)
for category in tuple.args
local key = Meta.quot(category)
local vec = Meta.isexpr(docstr, :string) ?
Expr(:vect, docstr.args...) : :($(docstr).$(TEMP_SYM)[$(key)])
push!(block.args, :($(TEMP_SYM)[$(key)] = $(vec)))
end
push!(block.args, nothing)
return esc(block)
end
template(sym::Symbol, docstr::Union{Symbol, Expr}) = template(Expr(:tuple, sym), docstr)


function template_hook(docstr, expr::Expr)
local curmod = current_module()
local docex = interp_string(docstr)
if isdefined(curmod, TEMP_SYM) && Meta.isexpr(docex, :string)
local templates = getfield(curmod, TEMP_SYM)
local template = get_template(templates, expression_type(expr))
local out = Expr(:string)
for t in template
t == DOCSTRING ? append!(out.args, docex.args) : push!(out.args, t)
end
return (out, expr)
else
return (docstr, expr)
end
end
template_hook(args...) = args

interp_string(str::AbstractString) = Expr(:string, str)
interp_string(ex::Expr) = ex

get_template(t::Dict, k::Symbol) = haskey(t, k) ? t[k] : get(t, :DEFAULT, Any[DOCSTRING])

function expression_type(ex::Expr)
if Meta.isexpr(ex, :module)
:MODULES
elseif Meta.isexpr(ex, [:type, :abstract, :typealias])
:TYPES
elseif Meta.isexpr(ex, :macro)
:MACROS
elseif Meta.isexpr(ex, [:function, :(=)]) && Meta.isexpr(ex.args[1], :call)
:METHODS
elseif Meta.isexpr(ex, :function)
:FUNCTIONS
elseif Meta.isexpr(ex, [:const, :(=)])
:CONSTANTS
else
:DEFAULT
end
end
expression_type(other) = :DEFAULT
95 changes: 95 additions & 0 deletions test/templates.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
module TemplateTests

using DocStringExtensions

@template DEFAULT =
"""
(DEFAULT)
$(DOCSTRING)
"""

@template TYPES =
"""
(TYPES)
$(TYPEDEF)
$(DOCSTRING)
"""

@template (METHODS, MACROS) =
"""
(METHODS, MACROS)
$(SIGNATURES)
$(DOCSTRING)
$(METHODLIST)
"""

"constant `K`"
const K = 1

"type `T`"
type T end

"method `f`"
f(x) = x

"macro `@m`"
macro m(x) end

module InnerModule

import ..TemplateTests

using DocStringExtensions

@template DEFAULT = TemplateTests

@template METHODS = TemplateTests

@template MACROS =
"""
(MACROS)
$(DOCSTRING)
$(SIGNATURES)
"""

"constant `K`"
const K = 1

"type `T`"
type T end

"method `f`"
f(x) = x

"macro `@m`"
macro m(x) end
end

module OtherModule

import ..TemplateTests

using DocStringExtensions

@template TYPES = TemplateTests
@template MACROS = TemplateTests.InnerModule

"type `T`"
type T end

"macro `@m`"
macro m(x) end

"method `f`"
f(x) = x
end

end
19 changes: 19 additions & 0 deletions test/tests.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

const DSE = DocStringExtensions

include("templates.jl")

module M

export f
Expand Down Expand Up @@ -177,6 +179,23 @@ end
@test contains(str, "\n```\n")
end
end
@testset "templates" begin
let fmt = expr -> Markdown.plain(eval(:(@doc $expr)))
@test contains(fmt(:(TemplateTests.K)), "(DEFAULT)")
@test contains(fmt(:(TemplateTests.T)), "(TYPES)")
@test contains(fmt(:(TemplateTests.f)), "(METHODS, MACROS)")
@test contains(fmt(:(TemplateTests.@m)), "(METHODS, MACROS)")

@test contains(fmt(:(TemplateTests.InnerModule.K)), "(DEFAULT)")
@test contains(fmt(:(TemplateTests.InnerModule.T)), "(DEFAULT)")
@test contains(fmt(:(TemplateTests.InnerModule.f)), "(METHODS, MACROS)")
@test contains(fmt(:(TemplateTests.InnerModule.@m)), "(MACROS)")

@test contains(fmt(:(TemplateTests.OtherModule.T)), "(TYPES)")
@test contains(fmt(:(TemplateTests.OtherModule.@m)), "(MACROS)")
@test fmt(:(TemplateTests.OtherModule.f)) == "method `f`\n"
end
end
@testset "utilities" begin
@testset "keywords" begin
@test DSE.keywords(M.T, first(methods(M.T))) == Symbol[]
Expand Down

0 comments on commit a448107

Please sign in to comment.