Skip to content

Commit

Permalink
Add new configuration option continue_on_error, closes #201. (#257)
Browse files Browse the repository at this point in the history
  • Loading branch information
tpapp authored Oct 16, 2024
1 parent 1adce12 commit 28a4126
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 7 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased
### Added
- A new keyword argument configuration `continue_on_error::Bool = false` has been added
which controls the behavior of code execution errors. By default (`continue_on_error =
false`) execution errors are re-thrown by Literate (as before). If `continue_on_error =
true` is set the error is used as the block result and execution continues with following
blocks. ([#201], [#257])

## [v2.19.1] - 2024-09-13
### Fixed
- Set `:SOURCE_PATH` in the task local storage to the output file when executing code so
Expand Down
27 changes: 20 additions & 7 deletions src/Literate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ function create_configuration(inputfile; user_config, user_kwargs, type=nothing)
cfg["softscope"] = type === (:nb) ? true : false # on for Jupyter notebooks
cfg["keep_comments"] = false
cfg["execute"] = type === :md ? false : true
cfg["continue_on_error"] = false
cfg["codefence"] = pick_codefence(
get(user_config, "flavor", cfg["flavor"]),
get(user_config, "execute", cfg["execute"]),
Expand Down Expand Up @@ -433,6 +434,10 @@ Available options:
output script. Only applicable for `Literate.script`.
- `execute` (default: `true` for notebook, `false` for markdown): Whether to execute and
capture the output. Only applicable for `Literate.notebook` and `Literate.markdown`.
- `continue_on_error` (default: `false`): Whether to continue code execution of remaining
blocks after encountering an error. By default execution errors are re-thrown. If
`continue_on_error = true` the error will be used as the output of the block instead and
execution will continue. This option is only applicable when `execute = true`.
- `codefence` (default: `````"````@example \$(name)" => "````"````` for `DocumenterFlavor()`
and `````"````julia" => "````"````` otherwise): Pair containing opening and closing
code fence for wrapping code blocks.
Expand Down Expand Up @@ -621,6 +626,7 @@ function markdown(inputfile, outputdir=pwd(); config::AbstractDict=Dict(), kwarg
image_formats=config["image_formats"],
file_prefix="$(config["name"])-$(chunknum)",
softscope=config["softscope"],
continue_on_error=config["continue_on_error"],
)
end
end
Expand All @@ -639,9 +645,10 @@ end
function execute_markdown!(io::IO, sb::Module, block::String, outputdir;
inputfile::String, fake_source::String,
flavor::AbstractFlavor, image_formats::Vector, file_prefix::String,
softscope::Bool)
softscope::Bool, continue_on_error::Bool)
# TODO: Deal with explicit display(...) calls
r, str, _ = execute_block(sb, block; inputfile=inputfile, fake_source=fake_source, softscope=softscope)
r, str, _ = execute_block(sb, block; inputfile=inputfile, fake_source=fake_source,
softscope=softscope, continue_on_error=continue_on_error)
# issue #101: consecutive codefenced blocks need newline
# issue #144: quadruple backticks allow for triple backticks in the output
plain_fence = "\n````\n" => "\n````"
Expand Down Expand Up @@ -777,7 +784,9 @@ function jupyter_notebook(chunks, config)
cd(config["literate_outputdir"]) do
nb = execute_notebook(nb; inputfile=config["literate_inputfile"],
fake_source=config["literate_outputfile"],
softscope=config["softscope"])
softscope=config["softscope"],
continue_on_error=config["continue_on_error"],
)
end
catch err
@error "error when executing notebook based on input file: " *
Expand All @@ -788,15 +797,18 @@ function jupyter_notebook(chunks, config)
return nb
end

function execute_notebook(nb; inputfile::String, fake_source::String, softscope::Bool)
function execute_notebook(nb; inputfile::String, fake_source::String, softscope::Bool,
continue_on_error=continue_on_error)
sb = sandbox()
execution_count = 0
for cell in nb["cells"]
cell["cell_type"] == "code" || continue
execution_count += 1
cell["execution_count"] = execution_count
block = join(cell["source"])
r, str, display_dicts = execute_block(sb, block; inputfile=inputfile, fake_source=fake_source, softscope=softscope)
r, str, display_dicts = execute_block(sb, block; inputfile=inputfile,
fake_source=fake_source, softscope=softscope,
continue_on_error=continue_on_error)

# str should go into stream
if !isempty(str)
Expand Down Expand Up @@ -878,7 +890,8 @@ function Base.display(ld::LiterateDisplay, mime::MIME, x)
end

# Execute a code-block in a module and capture stdout/stderr and the result
function execute_block(sb::Module, block::String; inputfile::String, fake_source::String, softscope::Bool)
function execute_block(sb::Module, block::String; inputfile::String, fake_source::String,
softscope::Bool, continue_on_error::Bool)
@debug """execute_block($sb, block)
```
$(block)
Expand All @@ -904,7 +917,7 @@ function execute_block(sb::Module, block::String; inputfile::String, fake_source
end
end
popdisplay(disp) # IOCapture.capture has a try-catch so should always end up here
if c.error
if c.error && !continue_on_error
error("""
$(sprint(showerror, c.value))
when executing the following code block from inputfile `$(Base.contractuser(inputfile))`
Expand Down
16 changes: 16 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1459,6 +1459,22 @@ end end
end # mktempdir
end end

@testset "continue_on_error=true" begin
input_with_error =
"""
# The following will error
sqrt(-1.0)
"""
mktempdir(@__DIR__) do sandbox
inputfile = joinpath(sandbox, "input.jl")
write(inputfile, input_with_error)
Literate.markdown(inputfile, sandbox; continue_on_error = true, execute = true)
output_md = read(joinpath(sandbox, "input.md"), String)
@test occursin("DomainError(-1.0", output_md)
end
end

@testset "Configuration" begin; Base.CoreLogging.with_logger(Base.CoreLogging.NullLogger()) do
mktempdir(@__DIR__) do sandbox
cd(sandbox) do
Expand Down

0 comments on commit 28a4126

Please sign in to comment.