Skip to content

Commit

Permalink
Support brief and extended docs (closes #25930) (#34226)
Browse files Browse the repository at this point in the history
  • Loading branch information
timholy authored Jan 25, 2020
1 parent 0234e00 commit 9790a8d
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 15 deletions.
8 changes: 8 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ Language changes
where it used to be incorrectly allowed. This is because `NTuple` refers only to homogeneous
tuples (this meaning has not changed) ([#34272]).

* 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
4 changes: 4 additions & 0 deletions doc/src/manual/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ 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. Such details are best 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 (i.e., "??foo" rather than "?foo").

## Accessing Documentation

Expand Down
75 changes: 64 additions & 11 deletions stdlib/REPL/src/docview.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,18 @@ using InteractiveUtils: subtypes
helpmode(io::IO, line::AbstractString) = :($REPL.insert_hlines($io, $(REPL._helpmode(io, line))))
helpmode(line::AbstractString) = helpmode(stdout, line)

const extended_help_on = Ref{Any}(nothing)

function _helpmode(io::IO, line::AbstractString)
line = strip(line)
if startswith(line, '?')
line = line[2:end]
extended_help_on[] = line
brief = false
else
extended_help_on[] = nothing
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 +47,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 @@ -73,6 +83,48 @@ function parsedoc(d::DocStr)
d.object
end

## Trimming long help ("# Extended help")

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

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

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

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

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

"""
Docs.doc(binding, sig)
Expand Down Expand Up @@ -273,29 +325,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 +401,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 +412,7 @@ function _repl(x)
else
docs
end
:(REPL.trimdocs($docs, $brief))
end

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

# Issue #25930

# Brief and extended docs (issue #25930)
let text =
"""
brief_extended()
Short docs
# Extended help
Long docs
""",
md = Markdown.parse(text)
@test md == REPL.trimdocs(md, false)
@test !isa(md.content[end], REPL.Message)
mdbrief = REPL.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], REPL.Message)
@test occursin("??", mdbrief.content[3].msg)
end

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], REPL.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] == :REPL
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

2 comments on commit 9790a8d

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Executing the daily benchmark build, I will reply here when finished:

@nanosoldier runbenchmarks(ALL, isdaily = true)

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @ararslan

Please sign in to comment.