Skip to content

Commit

Permalink
Add soft scoping rule capabilities
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
fredrikekre committed Nov 8, 2023
1 parent db98f5a commit 4d5f73c
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 9 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

## [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
- The minimum Julia version requirement for Literate >= 2.16.0 is now 1.6.0 (from 1.0.0).
([#230][github-230])
Expand Down Expand Up @@ -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-222]: https://github.com/fredrikekre/Literate.jl/issues/222
[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-229]: https://github.com/fredrikekre/Literate.jl/pull/229
[github-230]: https://github.com/fredrikekre/Literate.jl/pull/230
Expand Down
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
IOCapture = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
SoftGlobalScope = "b85f4697-e234-5449-a836-ec8e2f98b302"

[compat]
Base64 = "1"
Expand Down
24 changes: 17 additions & 7 deletions src/Literate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ function create_configuration(inputfile; user_config, user_kwargs, type=nothing)
cfg["flavor"] = type === (:md) ? DocumenterFlavor() : DefaultFlavor()
cfg["credit"] = true
cfg["mdstrings"] = false
cfg["softscope"] = type === (:nb) ? true : false # on for Jupyter notebooks
cfg["keep_comments"] = false
cfg["execute"] = type === :md ? false : true
cfg["codefence"] = get(user_config, "flavor", cfg["flavor"]) isa DocumenterFlavor &&
Expand Down Expand Up @@ -408,6 +409,8 @@ Available options:
- `devurl` (default: `"dev"`): URL for "in-development" docs, see [Documenter docs]
(https://juliadocs.github.io/Documenter.jl/). Unused if `repo_root_url`/
`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,
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
Expand Down Expand Up @@ -580,6 +583,7 @@ function markdown(inputfile, outputdir=pwd(); config::AbstractDict=Dict(), kwarg
flavor=config["flavor"],
image_formats=config["image_formats"],
file_prefix="$(config["name"])-$(chunknum)",
softscope=config["softscope"],
)
end
end
Expand All @@ -597,9 +601,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)
flavor::AbstractFlavor, image_formats::Vector, file_prefix::String,
softscope::Bool)
# 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 #144: quadruple backticks allow for triple backticks in the output
plain_fence = "\n````\n" => "\n````"
Expand Down Expand Up @@ -734,7 +739,8 @@ function jupyter_notebook(chunks, config)
try
cd(config["literate_outputdir"]) do
nb = execute_notebook(nb; inputfile=config["literate_inputfile"],
fake_source=config["literate_outputfile"])
fake_source=config["literate_outputfile"],
softscope=config["softscope"])
end
catch err
@error "error when executing notebook based on input file: " *
Expand All @@ -745,15 +751,15 @@ function jupyter_notebook(chunks, config)
return nb
end

function execute_notebook(nb; inputfile::String, fake_source::String)
function execute_notebook(nb; inputfile::String, fake_source::String, softscope::Bool)
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)
r, str, display_dicts = execute_block(sb, block; inputfile=inputfile, fake_source=fake_source, softscope=softscope)

# str should go into stream
if !isempty(str)
Expand Down Expand Up @@ -835,7 +841,7 @@ 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)
function execute_block(sb::Module, block::String; inputfile::String, fake_source::String, softscope::Bool)
@debug """execute_block($sb, block)
```
$(block)
Expand All @@ -851,7 +857,11 @@ 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
# and return them via the return value (they get handled below).
c = IOCapture.capture(rethrow = Union{}) do
include_string(sb, block, fake_source)
if softscope
include_string(REPL.softscope, sb, block, fake_source)
else
include_string(sb, block, fake_source)
end
end
popdisplay(disp) # IOCapture.capture has a try-catch so should always end up here
if c.error
Expand Down
46 changes: 44 additions & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,27 @@ end end
Literate.markdown(inputfile, relpath(outdir); execute=true,
flavor=Literate.CommonMarkFlavor())
@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 # mktemp
end end
Expand Down Expand Up @@ -1319,8 +1340,29 @@ end end
@test keys(cellout[1]["data"]) == Set(("text/latex",))
@test cellout[1]["data"]["text/latex"] == "DF(4) as text/latex"
@test !haskey(cellout[1], "execution_count")
end
end

# Softscope
write(
inputfile,
"""
ret = 0
for k = 1:10
ret += k
end
println("ret = ", ret)
"""
)
Literate.notebook(inputfile, outdir)
@test occursin("ret = 55", read(joinpath(outdir, "inputfile.ipynb"), String))
## Disabled softscope
try
Literate.notebook(inputfile, outdir; softscope=false)
error("unreachable")
catch err
@test occursin(r"`?ret`? not defined", sprint(Base.showerror, err))
end
end # cd(sandbox)
end # mktempdir
end end

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

0 comments on commit 4d5f73c

Please sign in to comment.