Skip to content

Commit

Permalink
Support brief and extended docs (closes #25930)
Browse files Browse the repository at this point in the history
A level-1 header "Extended help" indicates that the content that follows
requires `??foo` rather than just `?foo`.
  • Loading branch information
timholy committed Dec 31, 2019
1 parent 13d1bd4 commit 4ec6194
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 15 deletions.
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ New language features
Language changes
----------------

* In docstrings, a level-1 markdown header "Extended help" is now
interpreted as a marker dividing "brief help" from "extended help."
The REPL help mode only shows the brief help (the content before the
"Extended help" header) by default; prepend the expression with '?'
(in addition to the one that enters the help mode) to see the full
docstring. ([#25903])

Multi-threading changes
-----------------------
Expand Down
5 changes: 5 additions & 0 deletions doc/src/manual/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,11 @@ As in the example above, we recommend following some simple conventions when wri
rather than users, explaining e.g. which functions should be overridden and which functions
automatically use appropriate fallbacks, are better kept separate from the main description of
the function's behavior.
5. For long docstrings, consider splitting the documentation with an
`# Extended help` header. The typical help-mode will show only the
material above the header; you can access the full help by adding a '?'
at the beginning of the expression.


## Accessing Documentation

Expand Down
26 changes: 26 additions & 0 deletions stdlib/Markdown/src/Markdown.jl
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,30 @@ import Base.Docs: catdoc

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

function trimdocs(md::MD, brief::Bool)
brief || return md
md, trimmed = _trimdocs(md, brief)
if trimmed
push!(md.content, Message("Extended help is available with `??`", (color=Base.info_color(), bold=true)))
end
return md
end

function _trimdocs(md::MD, brief::Bool)
content, trimmed = [], false
for c in md.content
if isa(c, Header{1}) && isa(c.text, AbstractArray) &&
lowercase(c.text[1]) == "extended help"
trimmed = true
break
end
c, trm = _trimdocs(c, brief)
trimmed |= trm
push!(content, c)
end
return MD(content, md.meta), trimmed
end

_trimdocs(md, brief::Bool) = md, false

end
7 changes: 7 additions & 0 deletions stdlib/Markdown/src/parse/parse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ mutable struct MD
new(content, meta)
end

# For direct messages to the terminal
struct Message
msg # AbstractString
fmt # keywords to `printstyled`
end
Message(msg) = Message(msg, ())

MD(xs...) = MD(vcat(xs...))

function MD(cfg::Config, xs...)
Expand Down
4 changes: 4 additions & 0 deletions stdlib/Markdown/src/render/terminal/render.jl
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ function term(io::IO, br::HorizontalRule, columns)
print(io, ' '^margin, ''^(columns - 2margin))
end

function term(io::IO, msg::Message, columns)
printstyled(io, msg.msg; msg.fmt...)
end

term(io::IO, x, _) = show(io, MIME"text/plain"(), x)

# Inline Content
Expand Down
22 changes: 22 additions & 0 deletions stdlib/Markdown/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1131,3 +1131,25 @@ let m = Markdown.parse("---"), io = IOBuffer()
show(io, "text/latex", m)
@test String(take!(io)) == "\\rule{\\textwidth}{1pt}\n"
end

# Brief and extended docs (issue #25930)
let text =
"""
brief_extended()
Short docs
# Extended help
Long docs
""",
md = Markdown.parse(text)
@test md == Markdown.trimdocs(md, false)
@test !isa(md.content[end], Markdown.Message)
mdbrief = Markdown.trimdocs(md, true)
@test length(mdbrief.content) == 3
@test isa(mdbrief.content[1], Markdown.Code)
@test isa(mdbrief.content[2], Markdown.Paragraph)
@test isa(mdbrief.content[3], Markdown.Message)
@test occursin("??", mdbrief.content[3].msg)
end
29 changes: 18 additions & 11 deletions stdlib/REPL/src/docview.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ helpmode(line::AbstractString) = helpmode(stdout, line)

function _helpmode(io::IO, line::AbstractString)
line = strip(line)
if startswith(line, '?')
line = line[2:end]
brief = false
else
brief = true
end
x = Meta.parse(line, raise = false, depwarn = false)
expr =
if haskey(keywords, Symbol(line)) || isexpr(x, :error) || isexpr(x, :invalid)
Expand All @@ -37,7 +43,7 @@ function _helpmode(io::IO, line::AbstractString)
end
# the following must call repl(io, expr) via the @repl macro
# so that the resulting expressions are evaluated in the Base.Docs namespace
:($REPL.@repl $io $expr)
:($REPL.@repl $io $expr $brief)
end
_helpmode(line::AbstractString) = _helpmode(stdout, line)

Expand Down Expand Up @@ -273,29 +279,29 @@ function repl_latex(io::IO, s::String)
end
repl_latex(s::String) = repl_latex(stdout, s)

macro repl(ex) repl(ex) end
macro repl(io, ex) repl(io, ex) end
macro repl(ex, brief=false) repl(ex; brief=brief) end
macro repl(io, ex, brief) repl(io, ex; brief=brief) end

function repl(io::IO, s::Symbol)
function repl(io::IO, s::Symbol; brief::Bool=true)
str = string(s)
quote
repl_latex($io, $str)
repl_search($io, $str)
$(if !isdefined(Main, s) && !haskey(keywords, s)
:(repl_corrections($io, $str))
end)
$(_repl(s))
$(_repl(s, brief))
end
end
isregex(x) = isexpr(x, :macrocall, 3) && x.args[1] === Symbol("@r_str") && !isempty(x.args[3])
repl(io::IO, ex::Expr) = isregex(ex) ? :(apropos($io, $ex)) : _repl(ex)
repl(io::IO, str::AbstractString) = :(apropos($io, $str))
repl(io::IO, other) = esc(:(@doc $other))
repl(io::IO, ex::Expr; brief::Bool=true) = isregex(ex) ? :(apropos($io, $ex)) : _repl(ex, brief)
repl(io::IO, str::AbstractString; brief::Bool=true) = :(apropos($io, $str))
repl(io::IO, other; brief::Bool=true) = esc(:(@doc $other))
#repl(io::IO, other) = lookup_doc(other) # TODO

repl(x) = repl(stdout, x)
repl(x; brief=true) = repl(stdout, x; brief=brief)

function _repl(x)
function _repl(x, brief=true)
if isexpr(x, :call)
# determine the types of the values
kwargs = nothing
Expand Down Expand Up @@ -349,7 +355,7 @@ function _repl(x)
end
#docs = lookup_doc(x) # TODO
docs = esc(:(@doc $x))
if isfield(x)
docs = if isfield(x)
quote
if isa($(esc(x.args[1])), DataType)
fielddoc($(esc(x.args[1])), $(esc(x.args[2])))
Expand All @@ -360,6 +366,7 @@ function _repl(x)
else
docs
end
:(Markdown.trimdocs($docs, $brief))
end

"""
Expand Down
20 changes: 20 additions & 0 deletions stdlib/REPL/test/repl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1040,6 +1040,26 @@ for line in ["′", "abstract", "type", "|=", ".="]
sprint(show, Base.eval(REPL._helpmode(IOBuffer(), line))::Union{Markdown.MD,Nothing}))
end

# Issue #25930
module BriefExtended
"""
f()
Short docs
# Extended help
Long docs
"""
f() = nothing
end # module BriefExtended
buf = IOBuffer()
md = Base.eval(REPL._helpmode(buf, "$(@__MODULE__).BriefExtended.f"))
@test length(md.content) == 2 && isa(md.content[2], Markdown.Message)
buf = IOBuffer()
md = Base.eval(REPL._helpmode(buf, "?$(@__MODULE__).BriefExtended.f"))
@test length(md.content) == 1 && length(md.content[1].content[1].content) == 4

# PR #27562
fake_repl() do stdin_write, stdout_read, repl
repltask = @async begin
Expand Down
22 changes: 18 additions & 4 deletions test/docs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1011,28 +1011,42 @@ dynamic_test.x = "test 2"
@test @doc(dynamic_test) == "test 2 Union{}"
@test @doc(dynamic_test(::String)) == "test 2 Tuple{String}"

let dt1 = _repl(:(dynamic_test(1.0)))
# For testing purposes, strip off the `trimdocs(expr)` wrapper
function striptrimdocs(expr)
if Meta.isexpr(expr, :call)
fex = expr.args[1]
if Meta.isexpr(fex, :.) && fex.args[1] == :Markdown
fmex = fex.args[2]
if isa(fmex, QuoteNode) && fmex.value == :trimdocs
expr = expr.args[2]
end
end
end
return expr
end

let dt1 = striptrimdocs(_repl(:(dynamic_test(1.0))))
@test dt1 isa Expr
@test dt1.args[1] isa Expr
@test dt1.args[1].head === :macrocall
@test dt1.args[1].args[1] == Symbol("@doc")
@test dt1.args[1].args[3] == :(dynamic_test(::typeof(1.0)))
end
let dt2 = _repl(:(dynamic_test(::String)))
let dt2 = striptrimdocs(_repl(:(dynamic_test(::String))))
@test dt2 isa Expr
@test dt2.args[1] isa Expr
@test dt2.args[1].head === :macrocall
@test dt2.args[1].args[1] == Symbol("@doc")
@test dt2.args[1].args[3] == :(dynamic_test(::String))
end
let dt3 = _repl(:(dynamic_test(a)))
let dt3 = striptrimdocs(_repl(:(dynamic_test(a))))
@test dt3 isa Expr
@test dt3.args[1] isa Expr
@test dt3.args[1].head === :macrocall
@test dt3.args[1].args[1] == Symbol("@doc")
@test dt3.args[1].args[3].args[2].head == :(::) # can't test equality due to line numbers
end
let dt4 = _repl(:(dynamic_test(1.0,u=2.0)))
let dt4 = striptrimdocs(_repl(:(dynamic_test(1.0,u=2.0))))
@test dt4 isa Expr
@test dt4.args[1] isa Expr
@test dt4.args[1].head === :macrocall
Expand Down

0 comments on commit 4ec6194

Please sign in to comment.