From a4481079a341bb8297355325fa847a0237bcd81d Mon Sep 17 00:00:00 2001 From: Michael Hatherly Date: Sat, 19 Nov 2016 00:59:05 +0200 Subject: [PATCH] Add docstring templates Fixes #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. --- docs/make.jl | 2 +- docs/src/index.md | 2 +- src/DocStringExtensions.jl | 8 ++- src/abbreviations.jl | 21 ++++++ src/mock.jl | 7 ++ src/templates.jl | 134 +++++++++++++++++++++++++++++++++++++ test/templates.jl | 95 ++++++++++++++++++++++++++ test/tests.jl | 19 ++++++ 8 files changed, 283 insertions(+), 5 deletions(-) create mode 100644 src/mock.jl create mode 100644 src/templates.jl create mode 100644 test/templates.jl diff --git a/docs/make.jl b/docs/make.jl index 775d793..61844dc 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -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"], ) diff --git a/docs/src/index.md b/docs/src/index.md index d2869ff..b87fdce 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -14,6 +14,6 @@ Modules = [DocStringExtensions] ```@autodocs Modules = [DocStringExtensions] -Order = [:constant, :function, :type] +Order = [:constant, :function, :macro, :type] ``` diff --git a/src/DocStringExtensions.jl b/src/DocStringExtensions.jl index 2e9626d..c278d83 100644 --- a/src/DocStringExtensions.jl +++ b/src/DocStringExtensions.jl @@ -93,7 +93,7 @@ using Compat # Exports. -export FIELDS, EXPORTS, METHODLIST, IMPORTS, SIGNATURES, TYPEDEF +export @template, FIELDS, EXPORTS, METHODLIST, IMPORTS, SIGNATURES, TYPEDEF, DOCSTRING # Note: @@ -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" @@ -129,7 +129,7 @@ end include("utilities.jl") include("abbreviations.jl") - +include("templates.jl") # # Bootstrap abbreviations. @@ -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 diff --git a/src/abbreviations.jl b/src/abbreviations.jl index 5b24765..0074792 100644 --- a/src/abbreviations.jl +++ b/src/abbreviations.jl @@ -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. diff --git a/src/mock.jl b/src/mock.jl new file mode 100644 index 0000000..3585356 --- /dev/null +++ b/src/mock.jl @@ -0,0 +1,7 @@ +# Mock definitions for 0.4. + +const FIELDS, EXPORTS, METHODLIST, IMPORTS, SIGNATURES, TYPEDEF, DOCSTRING = + "", "", "", "", "", "", "" + +macro template(expr) nothing end + diff --git a/src/templates.jl b/src/templates.jl new file mode 100644 index 0000000..9cbefe2 --- /dev/null +++ b/src/templates.jl @@ -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 diff --git a/test/templates.jl b/test/templates.jl new file mode 100644 index 0000000..f0ddb17 --- /dev/null +++ b/test/templates.jl @@ -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 diff --git a/test/tests.jl b/test/tests.jl index 596a5cc..3267435 100644 --- a/test/tests.jl +++ b/test/tests.jl @@ -1,6 +1,8 @@ const DSE = DocStringExtensions +include("templates.jl") + module M export f @@ -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[]