Browse Source

Add soft scoping rule capabilities

This patch enables "soft" scoping rules (see e.g.
https://github.com/JuliaLang/SoftGlobalScope.jl) for code execution
(markdown and notebook output). This is enabled by default for Jupyter
notebook output (to mimic how the IJulia kernel works), and disabled
otherwise. Soft scope rules can be enabled/disabled with the `softscope
:: Bool` configuration variable. Fixes #227.
pull/230/head
Fredrik Ekre 2 years ago
parent
commit
c157721604
  1. 7
      CHANGELOG.md
  2. 22
      src/Literate.jl
  3. 46
      test/runtests.jl

7
CHANGELOG.md

@ -6,6 +6,12 @@ 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] ## [Unreleased]
### Added
- "Soft" scoping rules (see e.g. https://github.com/JuliaLang/SoftGlobalScope.jl) are now
available for code execution (markdown and notebook output). This is enabled by default
for Jupyter notebook output (to mimic how the IJulia kernel works), and disabled
otherwise. Soft scope rules can be enabled/disabled with the `softscope :: Bool`
configuration variable. ([#227][github-227], [#230][github-230])
### Changed ### Changed
- The minimum Julia version requirement for Literate >= 2.16.0 is now 1.6.0 (from 1.0.0). - The minimum Julia version requirement for Literate >= 2.16.0 is now 1.6.0 (from 1.0.0).
([#230][github-230]) ([#230][github-230])
@ -269,6 +275,7 @@ https://discourse.julialang.org/t/ann-literate-jl/10651 for release announcement
[github-221]: https://github.com/fredrikekre/Literate.jl/pull/221 [github-221]: https://github.com/fredrikekre/Literate.jl/pull/221
[github-222]: https://github.com/fredrikekre/Literate.jl/issues/222 [github-222]: https://github.com/fredrikekre/Literate.jl/issues/222
[github-223]: https://github.com/fredrikekre/Literate.jl/pull/223 [github-223]: https://github.com/fredrikekre/Literate.jl/pull/223
[github-227]: https://github.com/fredrikekre/Literate.jl/issues/227
[github-228]: https://github.com/fredrikekre/Literate.jl/issues/228 [github-228]: https://github.com/fredrikekre/Literate.jl/issues/228
[github-229]: https://github.com/fredrikekre/Literate.jl/pull/229 [github-229]: https://github.com/fredrikekre/Literate.jl/pull/229
[github-230]: https://github.com/fredrikekre/Literate.jl/pull/230 [github-230]: https://github.com/fredrikekre/Literate.jl/pull/230

22
src/Literate.jl

@ -312,6 +312,7 @@ function create_configuration(inputfile; user_config, user_kwargs, type=nothing)
cfg["flavor"] = type === (:md) ? DocumenterFlavor() : DefaultFlavor() cfg["flavor"] = type === (:md) ? DocumenterFlavor() : DefaultFlavor()
cfg["credit"] = true cfg["credit"] = true
cfg["mdstrings"] = false cfg["mdstrings"] = false
cfg["softscope"] = type === (:nb) ? true : false # on for Jupyter notebooks
cfg["keep_comments"] = false cfg["keep_comments"] = false
cfg["execute"] = type === :md ? false : true cfg["execute"] = type === :md ? false : true
cfg["codefence"] = get(user_config, "flavor", cfg["flavor"]) isa DocumenterFlavor && cfg["codefence"] = get(user_config, "flavor", cfg["flavor"]) isa DocumenterFlavor &&
@ -408,6 +409,8 @@ Available options:
- `devurl` (default: `"dev"`): URL for "in-development" docs, see [Documenter docs] - `devurl` (default: `"dev"`): URL for "in-development" docs, see [Documenter docs]
(https://juliadocs.github.io/Documenter.jl/). Unused if `repo_root_url`/ (https://juliadocs.github.io/Documenter.jl/). Unused if `repo_root_url`/
`nbviewer_root_url`/`binder_root_url` are set. `nbviewer_root_url`/`binder_root_url` are set.
- `softscope` (default: `true` for Jupyter notebooks, `false` otherwise): enable/disable
"soft" scoping rules when executing, see e.g. https://github.com/JuliaLang/SoftGlobalScope.jl.
- `repo_root_url`: URL to the root of the repository. Determined automatically on Travis CI, - `repo_root_url`: URL to the root of the repository. Determined automatically on Travis CI,
GitHub Actions and GitLab CI. Used for `@__REPO_ROOT_URL__`. GitHub Actions and GitLab CI. Used for `@__REPO_ROOT_URL__`.
- `nbviewer_root_url`: URL to the root of the repository as seen on nbviewer. Determined - `nbviewer_root_url`: URL to the root of the repository as seen on nbviewer. Determined
@ -580,6 +583,7 @@ function markdown(inputfile, outputdir=pwd(); config::AbstractDict=Dict(), kwarg
flavor=config["flavor"], flavor=config["flavor"],
image_formats=config["image_formats"], image_formats=config["image_formats"],
file_prefix="$(config["name"])-$(chunknum)", file_prefix="$(config["name"])-$(chunknum)",
softscope=config["softscope"],
) )
end end
end end
@ -597,9 +601,10 @@ end
function execute_markdown!(io::IO, sb::Module, block::String, outputdir; function execute_markdown!(io::IO, sb::Module, block::String, outputdir;
inputfile::String, fake_source::String, inputfile::String, fake_source::String,
flavor::AbstractFlavor, image_formats::Vector, file_prefix::String) flavor::AbstractFlavor, image_formats::Vector, file_prefix::String,
softscope::Bool)
# TODO: Deal with explicit display(...) calls # TODO: Deal with explicit display(...) calls
r, str, _ = execute_block(sb, block; inputfile=inputfile, fake_source=fake_source) r, str, _ = execute_block(sb, block; inputfile=inputfile, fake_source=fake_source, softscope=softscope)
# issue #101: consecutive codefenced blocks need newline # issue #101: consecutive codefenced blocks need newline
# issue #144: quadruple backticks allow for triple backticks in the output # issue #144: quadruple backticks allow for triple backticks in the output
plain_fence = "\n````\n" => "\n````" plain_fence = "\n````\n" => "\n````"
@ -734,7 +739,8 @@ function jupyter_notebook(chunks, config)
try try
cd(config["literate_outputdir"]) do cd(config["literate_outputdir"]) do
nb = execute_notebook(nb; inputfile=config["literate_inputfile"], nb = execute_notebook(nb; inputfile=config["literate_inputfile"],
fake_source=config["literate_outputfile"]) fake_source=config["literate_outputfile"],
softscope=config["softscope"])
end end
catch err catch err
@error "error when executing notebook based on input file: " * @error "error when executing notebook based on input file: " *
@ -745,7 +751,7 @@ function jupyter_notebook(chunks, config)
return nb return nb
end end
function execute_notebook(nb; inputfile::String, fake_source::String) function execute_notebook(nb; inputfile::String, fake_source::String, softscope::Bool)
sb = sandbox() sb = sandbox()
execution_count = 0 execution_count = 0
for cell in nb["cells"] for cell in nb["cells"]
@ -753,7 +759,7 @@ function execute_notebook(nb; inputfile::String, fake_source::String)
execution_count += 1 execution_count += 1
cell["execution_count"] = execution_count cell["execution_count"] = execution_count
block = join(cell["source"]) block = join(cell["source"])
r, str, display_dicts = execute_block(sb, block; inputfile=inputfile, fake_source=fake_source) r, str, display_dicts = execute_block(sb, block; inputfile=inputfile, fake_source=fake_source, softscope=softscope)
# str should go into stream # str should go into stream
if !isempty(str) if !isempty(str)
@ -835,7 +841,7 @@ function Base.display(ld::LiterateDisplay, mime::MIME, x)
end end
# Execute a code-block in a module and capture stdout/stderr and the result # 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) function execute_block(sb::Module, block::String; inputfile::String, fake_source::String, softscope::Bool)
@debug """execute_block($sb, block) @debug """execute_block($sb, block)
``` ```
$(block) $(block)
@ -851,8 +857,12 @@ function execute_block(sb::Module, block::String; inputfile::String, fake_source
# `rethrow = Union{}` means that we try-catch all the exceptions thrown in the do-block # `rethrow = Union{}` means that we try-catch all the exceptions thrown in the do-block
# and return them via the return value (they get handled below). # and return them via the return value (they get handled below).
c = IOCapture.capture(rethrow = Union{}) do c = IOCapture.capture(rethrow = Union{}) do
if softscope
include_string(REPL.softscope, sb, block, fake_source)
else
include_string(sb, block, fake_source) include_string(sb, block, fake_source)
end end
end
popdisplay(disp) # IOCapture.capture has a try-catch so should always end up here popdisplay(disp) # IOCapture.capture has a try-catch so should always end up here
if c.error if c.error
error(""" error("""

46
test/runtests.jl

@ -905,6 +905,27 @@ end end
Literate.markdown(inputfile, relpath(outdir); execute=true, Literate.markdown(inputfile, relpath(outdir); execute=true,
flavor=Literate.CommonMarkFlavor()) flavor=Literate.CommonMarkFlavor())
@test read(joinpath(outdir, "inputfile-1.svg"), String) == "issue228" @test read(joinpath(outdir, "inputfile-1.svg"), String) == "issue228"
# Softscope
write(
inputfile,
"""
ret = 0
for k = 1:10
ret += k
end
println("ret = ", ret)
"""
)
Literate.markdown(inputfile, outdir; execute=true, softscope=true)
@test occursin("ret = 55", read(joinpath(outdir, "inputfile.md"), String))
## Disabled softscope
try
Literate.markdown(inputfile, outdir; execute=true, softscope=false)
error("unreachable")
catch err
@test occursin(r"`?ret`? not defined", sprint(Base.showerror, err))
end
end # cd(sandbox) end # cd(sandbox)
end # mktemp end # mktemp
end end end end
@ -1319,8 +1340,33 @@ end end
@test keys(cellout[1]["data"]) == Set(("text/latex",)) @test keys(cellout[1]["data"]) == Set(("text/latex",))
@test cellout[1]["data"]["text/latex"] == "DF(4) as text/latex" @test cellout[1]["data"]["text/latex"] == "DF(4) as text/latex"
@test !haskey(cellout[1], "execution_count") @test !haskey(cellout[1], "execution_count")
# Softscope
# TODO: Windows CI says here, but no longer: The input file that have been used
# above multiple times without problem now gives
# "SystemError: opening file: Invalid argument" for some reason...
new_inputfile = "inputfile2.jl"
write(
new_inputfile,
"""
ret = 0
for k = 1:10
ret += k
end end
println("ret = ", ret)
"""
)
Literate.notebook(new_inputfile, outdir)
@test occursin("ret = 55", read(joinpath(outdir, "inputfile2.ipynb"), String))
## Disabled softscope
try
Literate.notebook(new_inputfile, outdir; softscope=false)
error("unreachable")
catch err
@test occursin(r"`?ret`? not defined", sprint(Base.showerror, err))
end end
end # cd(sandbox)
end # mktempdir
end end end end
@testset "Configuration" begin; Base.CoreLogging.with_logger(Base.CoreLogging.NullLogger()) do @testset "Configuration" begin; Base.CoreLogging.with_logger(Base.CoreLogging.NullLogger()) do

Loading…
Cancel
Save