Skip to content

Commit

Permalink
Better log messages for failed at-meta, at-docs,
Browse files Browse the repository at this point in the history
at-autodocs, at-eval, at-example and at-setup blocks,
fixes #78 and fixes #248.
  • Loading branch information
fredrikekre committed Jan 24, 2019
1 parent 8d17f68 commit 9c62788
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 36 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

* ![Enhancement][badge-enhancement] Documentation is no longer deployed on Travis CI cron jobs. ([#917][github-917])

* ![Enhancement][badge-enhancement] Log messages from failed `@meta`, `@docs`, `@autodocs`,
`@eval`, `@example` and `@setup` blocks now include information about the source location
of the block. ([#929][github-929])

* ![Bugfix][badge-bugfix] `@repl` blocks now work correctly together with quoted
expressions. ([#923][github-923], [#926][github-926])

Expand Down Expand Up @@ -180,8 +184,10 @@
[github-898]: https://github.com/JuliaDocs/Documenter.jl/pull/898
[github-905]: https://github.com/JuliaDocs/Documenter.jl/pull/905
[github-907]: https://github.com/JuliaDocs/Documenter.jl/pull/907
[github-917]: https://github.com/JuliaDocs/Documenter.jl/pull/917
[github-923]: https://github.com/JuliaDocs/Documenter.jl/pull/923
[github-926]: https://github.com/JuliaDocs/Documenter.jl/pull/926
[github-929]: https://github.com/JuliaDocs/Documenter.jl/pull/929

[documenterlatex]: https://github.com/JuliaDocs/DocumenterLaTeX.jl
[documentermarkdown]: https://github.com/JuliaDocs/DocumenterMarkdown.jl
Expand Down
29 changes: 5 additions & 24 deletions src/DocTests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,6 @@ import Markdown, REPL
# Julia code block testing.
# -------------------------

# escape characters that has a meaning in regex
regex_escape(str) = sprint(escape_string, str, "\\^\$.|?*+()[{")

# helper to display linerange for error printing
function find_codeblock_in_file(code, file)
content = read(Base.find_source_file(file), String)
content = replace(content, "\r\n" => "\n")
# make a regex of the code that matches leading whitespace
rcode = "\\h*" * replace(regex_escape(code), "\\n" => "\\n\\h*")
blockidx = findfirst(Regex(rcode), content)
if blockidx !== nothing
startline = countlines(IOBuffer(content[1:prevind(content, first(blockidx))]))
endline = startline + countlines(IOBuffer(code)) + 1 # +1 to include the closing ```
return ":$(startline)-$(endline)"
else
return ""
end
end

"""
$(SIGNATURES)
Expand Down Expand Up @@ -78,7 +59,7 @@ function doctest(block::Markdown.Code, meta::Dict, doc::Documents.Document, page
for kwarg in kwargs.args
if !(isa(kwarg, Expr) && kwarg.head === :(=) && isa(kwarg.args[1], Symbol))
file = meta[:CurrentFile]
lines = find_codeblock_in_file(block.code, file)
lines = Utilities.find_block_in_file(block.code, file)
@warn("""
invalid syntax for doctest keyword arguments in $(Utilities.locrepr(file, lines))
Use ```jldoctest name; key1 = value1, key2 = value2
Expand Down Expand Up @@ -112,7 +93,7 @@ function doctest(block::Markdown.Code, meta::Dict, doc::Documents.Document, page
else
push!(doc.internal.errors, :doctest)
file = meta[:CurrentFile]
lines = find_codeblock_in_file(block.code, file)
lines = Utilities.find_block_in_file(block.code, file)
@warn("""
invalid doctest block in $(Utilities.locrepr(file, lines))
Requires `julia> ` or `# output`
Expand Down Expand Up @@ -294,7 +275,7 @@ function report(result::Result, str, doc::Documents.Document)
println(ioc, "=====[Test Error]", "="^30)
println(ioc)
printstyled(ioc, "> Location: ", result.file, color=:cyan)
printstyled(ioc, find_codeblock_in_file(result.block.code, result.file), color=:cyan)
printstyled(ioc, Utilities.find_block_in_file(result.block.code, result.file), color=:cyan)
printstyled(ioc, "\n\n> Code block:\n", color=:cyan)
println(ioc, "\n```$(result.block.language)")
println(ioc, result.block.code)
Expand Down Expand Up @@ -328,7 +309,7 @@ function fix_doctest(result::Result, str, doc::Documents.Document)
io = IOBuffer(sizehint = sizeof(content))
# first look for the entire code block
# make a regex of the code that matches leading whitespace
rcode = "(\\h*)" * replace(regex_escape(code), "\\n" => "\\n\\h*")
rcode = "(\\h*)" * replace(Utilities.regex_escape(code), "\\n" => "\\n\\h*")
r = Regex(rcode)
codeidx = findfirst(r, content)
if codeidx === nothing
Expand All @@ -341,7 +322,7 @@ function fix_doctest(result::Result, str, doc::Documents.Document)
write(io, content[1:prevind(content, first(codeidx))])
# next look for the particular input string in the given code block
# make a regex of the input that matches leading whitespace (for multiline input)
rinput = "\\h*" * replace(regex_escape(result.input), "\\n" => "\\n\\h*")
rinput = "\\h*" * replace(Utilities.regex_escape(result.input), "\\n" => "\\n\\h*")
r = Regex(rinput)
inputidx = findfirst(r, code)
if inputidx === nothing
Expand Down
83 changes: 72 additions & 11 deletions src/Expanders.jl
Original file line number Diff line number Diff line change
Expand Up @@ -237,13 +237,19 @@ end

function Selectors.runner(::Type{MetaBlocks}, x, page, doc)
meta = page.globals.meta
lines = Utilities.find_block_in_file(x.code, page.source)
for (ex, str) in Utilities.parseblock(x.code, doc, page)
if Utilities.isassign(ex)
try
meta[ex.args[1]] = Core.eval(Main, ex.args[2])
catch err
push!(doc.internal.errors, :meta_block)
@warn "failed to evaluate `$(strip(str))` in `@meta` block in $(Utilities.locrepr(page.source))" exception = err
@warn("""
failed to evaluate `$(strip(str))` in `@meta` block in $(Utilities.locrepr(page.source, lines))
```$(x.language)
$(x.code)
```
""", exception = err)
end
end
end
Expand All @@ -257,19 +263,31 @@ function Selectors.runner(::Type{DocsBlocks}, x, page, doc)
failed = false
nodes = DocsNode[]
curmod = get(page.globals.meta, :CurrentModule, Main)
lines = Utilities.find_block_in_file(x.code, page.source)
for (ex, str) in Utilities.parseblock(x.code, doc, page)
binding = try
Documenter.DocSystem.binding(curmod, ex)
catch err
push!(doc.internal.errors, :docs_block)
@warn "unable to get the binding for '$(strip(str))' in $(Utilities.locrepr(page.source)) from expression '$(repr(ex))' in module $(curmod)" exception = err
@warn("""
unable to get the binding for '$(strip(str))' in `@docs` block in $(Utilities.locrepr(page.source, lines)) from expression '$(repr(ex))' in module $(curmod)
```$(x.language)
$(x.code)
```
""",
exception = err)
failed = true
continue
end
# Undefined `Bindings` get discarded.
if !Documenter.DocSystem.iskeyword(binding) && !Documenter.DocSystem.defined(binding)
push!(doc.internal.errors, :docs_block)
@warn "undefined binding '$(binding)' in $(Utilities.locrepr(page.source))."
@warn("""
undefined binding '$(binding)' in `@docs` block in $(Utilities.locrepr(page.source, lines))
```$(x.language)
$(x.code)
```
""")
failed = true
continue
end
Expand All @@ -279,7 +297,12 @@ function Selectors.runner(::Type{DocsBlocks}, x, page, doc)
# We can't include the same object more than once in a document.
if haskey(doc.internal.objects, object)
push!(doc.internal.errors, :docs_block)
@warn "duplicate docs found for '$(strip(str))' in $(Utilities.locrepr(page.source))."
@warn("""
duplicate docs found for '$(strip(str))' in `@docs` block in $(Utilities.locrepr(page.source, lines))
```$(x.language)
$(x.code)
```
""")
failed = true
continue
end
Expand All @@ -295,7 +318,12 @@ function Selectors.runner(::Type{DocsBlocks}, x, page, doc)
# Check that we aren't printing an empty docs list. Skip block when empty.
if isempty(docs)
push!(doc.internal.errors, :docs_block)
@warn "no docs found for '$(strip(str))' in $(Utilities.locrepr(page.source))."
@warn("""
no docs found for '$(strip(str))' in `@docs` block in $(Utilities.locrepr(page.source, lines))
```$(x.language)
$(x.code)
```
""")
failed = true
continue
end
Expand Down Expand Up @@ -328,6 +356,7 @@ const AUTODOCS_DEFAULT_ORDER = [:module, :constant, :type, :function, :macro]
function Selectors.runner(::Type{AutoDocsBlocks}, x, page, doc)
curmod = get(page.globals.meta, :CurrentModule, Main)
fields = Dict{Symbol, Any}()
lines = Utilities.find_block_in_file(x.code, page.source)
for (ex, str) in Utilities.parseblock(x.code, doc, page)
if Utilities.isassign(ex)
try
Expand All @@ -338,7 +367,12 @@ function Selectors.runner(::Type{AutoDocsBlocks}, x, page, doc)
end
catch err
push!(doc.internal.errors, :autodocs_block)
@warn "failed to evaluate `$(strip(str))` in `@autodocs` block in $(Utilities.locrepr(page.source))" exception = err
@warn("""
failed to evaluate `$(strip(str))` in `@autodocs` block in $(Utilities.locrepr(page.source, lines))
```$(x.language)
$(x.code)
```
""", exception = err)
end
end
end
Expand Down Expand Up @@ -402,7 +436,12 @@ function Selectors.runner(::Type{AutoDocsBlocks}, x, page, doc)
for (mod, path, category, object, isexported, docstr) in results
if haskey(doc.internal.objects, object)
push!(doc.internal.errors, :autodocs_block)
@warn "duplicate docs found for '$(object.binding)' in $(Utilities.locrepr(page.source)).."
@warn("""
duplicate docs found for '$(object.binding)' in $(Utilities.locrepr(page.source, lines))
```$(x.language)
$(x.code)
```
""")
continue
end
markdown = Markdown.MD(Documenter.DocSystem.parsedoc(docstr))
Expand All @@ -420,7 +459,12 @@ function Selectors.runner(::Type{AutoDocsBlocks}, x, page, doc)
page.mapping[x] = DocsNodes(nodes)
else
push!(doc.internal.errors, :autodocs_block)
@warn "'@autodocs' missing 'Modules = ...' in $(Utilities.locrepr(page.source)).."
@warn("""
'@autodocs' missing 'Modules = ...' in $(Utilities.locrepr(page.source, lines))
```$(x.language)
$(x.code)
```
""")
page.mapping[x] = x
end
end
Expand All @@ -430,14 +474,20 @@ end

function Selectors.runner(::Type{EvalBlocks}, x, page, doc)
sandbox = Module(:EvalBlockSandbox)
lines = Utilities.find_block_in_file(x.code, page.source)
cd(dirname(page.build)) do
result = nothing
for (ex, str) in Utilities.parseblock(x.code, doc, page)
try
result = Core.eval(sandbox, ex)
catch err
push!(doc.internal.errors, :eval_block)
@warn "failed to evaluate `@eval` block in $(Utilities.locrepr(page.source))" exception = err
@warn("""
failed to evaluate `@eval` block in $(Utilities.locrepr(page.source))
```$(x.language)
$(x.code)
```
""", exception = err)
end
end
page.mapping[x] = EvalNode(x, result)
Expand Down Expand Up @@ -470,6 +520,7 @@ function Selectors.runner(::Type{ExampleBlocks}, x, page, doc)
name = match(r"^@example[ ]?(.*)$", first(split(x.language, ';', limit = 2)))[1]
sym = isempty(name) ? gensym("ex-") : Symbol("ex-", name)
mod = get!(() -> get_new_sandbox(sym), page.globals.meta, sym)
lines = Utilities.find_block_in_file(x.code, page.source)

# "parse" keyword arguments to example (we only need to look for continued = true)
continued = occursin(r"continued\s*=\s*true", x.language)
Expand All @@ -494,7 +545,12 @@ function Selectors.runner(::Type{ExampleBlocks}, x, page, doc)
print(buffer, text)
if !success
push!(doc.internal.errors, :example_block)
@warn "failed to run code block in $(Utilities.locrepr(page.source)):" value
@warn("""
failed to run `@example` block in $(Utilities.locrepr(page.source, lines))
```$(x.language)
$(x.code)
```
""", value)
page.mapping[x] = x
return
end
Expand Down Expand Up @@ -588,7 +644,12 @@ function Selectors.runner(::Type{SetupBlocks}, x, page, doc)
Markdown.MD([])
catch err
push!(doc.internal.errors, :setup_block)
@warn "failed to run `@setup` block in $(Utilities.locrepr(page.source)):" exception=err
@warn("""
failed to run `@setup` block in $(Utilities.locrepr(page.source))
```$(x.language)
$(x.code)
```
""", exception=err)
x
end
# ... and finally map the original code block to the newly generated ones.
Expand Down
21 changes: 20 additions & 1 deletion src/Utilities/Utilities.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,29 @@ using DocStringExtensions
import Markdown, LibGit2
import Base64: stringmime

# escape characters that has a meaning in regex
regex_escape(str) = sprint(escape_string, str, "\\^\$.|?*+()[{")

# helper to display linerange for error printing
function find_block_in_file(code, file)
content = read(Base.find_source_file(file), String)
content = replace(content, "\r\n" => "\n")
# make a regex of the code that matches leading whitespace
rcode = "\\h*" * replace(regex_escape(code), "\\n" => "\\n\\h*")
blockidx = findfirst(Regex(rcode), content)
if blockidx !== nothing
startline = countlines(IOBuffer(content[1:prevind(content, first(blockidx))]))
endline = startline + countlines(IOBuffer(code)) + 1 # +1 to include the closing ```
return ":$(startline)-$(endline)"
else
return ""
end
end

# Pretty-printing locations
function locrepr(file, line=nothing)
str = Base.contractuser(file) # TODO: Maybe print this relative the doc-root??
line !== nothing && (str = str * ":$(line)")
line !== nothing && (str = str * "$(line)")
return "`$(str)`"
end

Expand Down

0 comments on commit 9c62788

Please sign in to comment.