Skip to content

Commit

Permalink
move Markdown to stdlib
Browse files Browse the repository at this point in the history
  • Loading branch information
JeffBezanson committed Jan 25, 2018
1 parent e23b2ab commit 6f9255f
Show file tree
Hide file tree
Showing 36 changed files with 691 additions and 746 deletions.
3 changes: 0 additions & 3 deletions base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -410,9 +410,6 @@ end
macro __doc__(x)
Expr(:escape, Expr(:block, Expr(:meta, :doc), x))
end
macro doc_str(s)
Expr(:escape, s)
end
atdoc = (source, mod, str, expr) -> Expr(:escape, expr)
atdoc!(λ) = global atdoc = λ

Expand Down
244 changes: 29 additions & 215 deletions base/docs/Docs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,11 @@ function.

include("bindings.jl")

import Base.Markdown: @doc_str, MD
import Base.Meta: quot, isexpr
import Base: Callable, with_output_color
import ..CoreDocs: lazy_iterpolate

export doc
export doc, apropos

# Basic API / Storage

Expand Down Expand Up @@ -174,25 +173,6 @@ end

docexpr(__source__, __module__, args...) = Expr(:call, docstr, args...)

function formatdoc(d::DocStr)
buffer = IOBuffer()
for part in d.text
formatdoc(buffer, d, part)
end
Markdown.MD(Any[Markdown.parse(seekstart(buffer))])
end
@noinline formatdoc(buffer, d, part) = print(buffer, part)

function parsedoc(d::DocStr)
if d.object === nothing
md = formatdoc(d)
md.meta[:module] = d.data[:module]
md.meta[:path] = d.data[:path]
d.object = md
end
d.object
end

"""
MultiDoc
Expand Down Expand Up @@ -270,167 +250,6 @@ function getdoc end
getdoc(x, sig) = getdoc(x)
getdoc(x) = nothing

"""
Docs.doc(binding, sig)
Return all documentation that matches both `binding` and `sig`.
If `getdoc` returns a non-`nothing` result on the value of the binding, then a
dynamic docstring is returned instead of one based on the binding itself.
"""
function doc(binding::Binding, sig::Type = Union{})
if defined(binding)
result = getdoc(resolve(binding), sig)
result === nothing || return result
end
results, groups = DocStr[], MultiDoc[]
# Lookup `binding` and `sig` for matches in all modules of the docsystem.
for mod in modules
dict = meta(mod)
if haskey(dict, binding)
multidoc = dict[binding]
push!(groups, multidoc)
for msig in multidoc.order
sig <: msig && push!(results, multidoc.docs[msig])
end
end
end
if isempty(groups)
# When no `MultiDoc`s are found that match `binding` then we check whether `binding`
# is an alias of some other `Binding`. When it is we then re-run `doc` with that
# `Binding`, otherwise if it's not an alias then we generate a summary for the
# `binding` and display that to the user instead.
alias = aliasof(binding)
alias == binding ? summarize(alias, sig) : doc(alias, sig)
else
# There was at least one match for `binding` while searching. If there weren't any
# matches for `sig` then we concatenate *all* the docs from the matching `Binding`s.
if isempty(results)
for group in groups, each in group.order
push!(results, group.docs[each])
end
end
# Get parsed docs and concatenate them.
md = catdoc(map(parsedoc, results)...)
# Save metadata in the generated markdown.
if isa(md, Markdown.MD)
md.meta[:results] = results
md.meta[:binding] = binding
md.meta[:typesig] = sig
end
return md
end
end

# Some additional convenience `doc` methods that take objects rather than `Binding`s.
doc(obj::UnionAll) = doc(Base.unwrap_unionall(obj))
doc(object, sig::Type = Union{}) = doc(aliasof(object, typeof(object)), sig)
doc(object, sig...) = doc(object, Tuple{sig...})

"""
Docs.fielddoc(binding, field)
Return documentation for a particular `field` of a type if it exists.
"""
function fielddoc(binding::Binding, field::Symbol)
for mod in modules
dict = meta(mod)
if haskey(dict, binding)
multidoc = dict[binding]
if haskey(multidoc.docs, Union{})
fields = multidoc.docs[Union{}].data[:fields]
if haskey(fields, field)
doc = fields[field]
return isa(doc, Markdown.MD) ? doc : Markdown.parse(doc)
end
end
end
end
fields = join(["`$f`" for f in fieldnames(resolve(binding))], ", ", ", and ")
fields = isempty(fields) ? "no fields" : "fields $fields"
Markdown.parse("`$(resolve(binding))` has $fields.")
end

# As with the additional `doc` methods, this converts an object to a `Binding` first.
fielddoc(object, field::Symbol) = fielddoc(aliasof(object, typeof(object)), field)

# Object Summaries.
# =================

function summarize(binding::Binding, sig)
io = IOBuffer()
println(io, "No documentation found.\n")
if defined(binding)
summarize(io, resolve(binding), binding)
else
println(io, "Binding `", binding, "` does not exist.")
end
md = Markdown.parse(seekstart(io))
# Save metadata in the generated markdown.
md.meta[:results] = DocStr[]
md.meta[:binding] = binding
md.meta[:typesig] = sig
return md
end

function summarize(io::IO, λ::Function, binding)
kind = startswith(string(binding.var), '@') ? "macro" : "`Function`"
println(io, "`", binding, "` is a ", kind, ".")
println(io, "```\n", methods(λ), "\n```")
end

function summarize(io::IO, T::DataType, binding)
println(io, "# Summary")
println(io, "```")
println(io,
T.abstract ? "abstract type" :
T.mutable ? "mutable struct" :
Base.isstructtype(T) ? "struct" : "primitive type",
" ", T, " <: ", supertype(T)
)
println(io, "```")
if !T.abstract && T.name !== Tuple.name && !isempty(fieldnames(T))
println(io, "# Fields")
println(io, "```")
pad = maximum(length(string(f)) for f in fieldnames(T))
for (f, t) in zip(fieldnames(T), T.types)
println(io, rpad(f, pad), " :: ", t)
end
println(io, "```")
end
if !isempty(subtypes(T))
println(io, "# Subtypes")
println(io, "```")
for t in subtypes(T)
println(io, t)
end
println(io, "```")
end
if supertype(T) != Any
println(io, "# Supertype Hierarchy")
println(io, "```")
Base.show_supertypes(io, T)
println(io)
println(io, "```")
end
end

function summarize(io::IO, m::Module, binding)
readme = Pkg.dir(string(m), "README.md")
if isfile(readme)
println(io, "Displaying the `README.md` for the module instead.\n")
println(io, "---\n")
println(io, read(readme, String))
else
println(io, "No docstring or `README.md` found for module `", m, "`.\n")
end
end

function summarize(io::IO, ::T, binding) where T
println(io, "`", binding, "` is of type `", T, "`.\n")
summarize(io, T, binding)
end

# Utilities.
# ==========

Expand All @@ -442,12 +261,6 @@ catdoc(xs...) = vcat(xs...)

const keywords = Dict{Symbol, DocStr}()

isdoc(s::AbstractString) = true

isdoc(x) = isexpr(x, :string) ||
(isexpr(x, :macrocall) && x.args[1] === Symbol("@doc_str")) ||
(isexpr(x, :call) && x.args[1] === Base.Markdown.doc_str)

function unblock(ex)
isexpr(ex, :block) || return ex
exs = filter(ex -> !(isa(ex, LineNumberNode) || isexpr(ex, :line)), ex.args)
Expand Down Expand Up @@ -511,13 +324,20 @@ function metadata(__source__, __module__, expr, ismodule)
if isexpr(expr, :struct)
# Field docs for concrete types.
fields = []
tmp = nothing
last_docstr = nothing
for each in expr.args[3].args
if isdoc(each)
tmp = each
elseif tmp !== nothing && (isa(each, Symbol) || isexpr(each, :(::)))
push!(fields, (namify(each), tmp))
tmp = nothing
if isa(each, Symbol) || isexpr(each, :(::))
# a field declaration
if last_docstr !== nothing
push!(fields, (namify(each), last_docstr))
last_docstr = nothing
end
elseif isexpr(each, :function) || isexpr(each, :(=))
break
elseif isa(each, String) || isexpr(each, :string) || isexpr(each, :call) ||
(isexpr(each, :macrocall) && each.args[1] === Symbol("@doc_str"))
# forms that might be doc strings
last_docstr = each
end
end
dict = :($(Dict)($([:($(Pair)($(quot(f)), $d)) for (f, d) in fields]...)))
Expand Down Expand Up @@ -654,6 +474,16 @@ isquotedmacrocall(x) =
isbasicdoc(x) = isexpr(x, :.) || isa(x, Union{QuoteNode, Symbol})
is_signature(x) = isexpr(x, :call) || (isexpr(x, :(::), 2) && isexpr(x.args[1], :call)) || isexpr(x, :where)

function docm(source::LineNumberNode, mod::Module, ex)
if isexpr(ex, :->) && length(ex.args) > 1
docm(source, mod, ex.args...)
else
# TODO: this is a shim to continue to allow `@doc` for looking up docstrings
REPL = Base.root_module(Base, :REPL)
return REPL.lookup_doc(ex)
end
end

function docm(source::LineNumberNode, mod::Module, meta, ex, define = true)
# Some documented expressions may be decorated with macro calls which obscure the actual
# expression. Expand the macro calls and remove extra blocks.
Expand Down Expand Up @@ -730,27 +560,6 @@ function docerror(ex)
:($(error)($txt, "\n"))
end

function docm(source::LineNumberNode, mod::Module, ex)
if isexpr(ex, :->)
docm(source, mod, ex.args...)
elseif haskey(keywords, ex)
parsedoc(keywords[ex])
elseif isa(ex, Union{Expr, Symbol})
binding = esc(bindingexpr(namify(ex)))
if isexpr(ex, :call) || isexpr(ex, :macrocall)
sig = esc(signature(ex))
:($(doc)($binding, $sig))
else
:($(doc)($binding))
end
else
:($(doc)($(typeof)($(esc(ex)))))
end
end

# MD support
catdoc(md::MD...) = MD(md...)

include("utils.jl")

# Swap out the bootstrap macro with the real one.
Expand All @@ -775,4 +584,9 @@ function loaddocs(docs)
empty!(docs)
end

function formatdoc end
function parsedoc end
function apropos end
function doc end

end
Loading

0 comments on commit 6f9255f

Please sign in to comment.