diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1255c66..51a7380 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: version: - - '1.0' + - '1.6' - '1' - 'nightly' os: diff --git a/CHANGELOG.md b/CHANGELOG.md index 99bb761..d004c61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +## [2.16.0] - 2023-11-08 +### 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]) + ## [2.15.1] - 2023-11-08 ### Fixed - Fix a bug where `Literate.markdown` with `execute=true` would (try to) output images in @@ -266,10 +277,13 @@ 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 -[Unreleased]: https://github.com/fredrikekre/Literate.jl/compare/v2.15.1...HEAD +[Unreleased]: https://github.com/fredrikekre/Literate.jl/compare/v2.16.0...HEAD +[2.16.0]: https://github.com/fredrikekre/Literate.jl/compare/v2.15.1...v2.16.0 [2.15.1]: https://github.com/fredrikekre/Literate.jl/compare/v2.15.0...v2.15.1 [2.15.0]: https://github.com/fredrikekre/Literate.jl/compare/v2.14.2...v2.15.0 [2.14.2]: https://github.com/fredrikekre/Literate.jl/compare/v2.14.1...v2.14.2 diff --git a/Project.toml b/Project.toml index 2599739..c869e9e 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "Literate" uuid = "98b081ad-f1c9-55d3-8b20-4c87d4299306" -version = "2.15.1" +version = "2.16.0" [deps] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" @@ -9,11 +9,11 @@ JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" [compat] -Base64 = "<0.0.1, 1" +Base64 = "1" IOCapture = "0.2" JSON = "0.18, 0.19, 0.20, 0.21, 1" -REPL = "<0.0.1, 1" -julia = "1" +REPL = "1" +julia = "1.6" [extras] DisplayAs = "0b91fe84-8a4c-11e9-3e1d-67c38462b6d6" diff --git a/docs/Manifest.toml b/docs/Manifest.toml index d43c01f..16e04cc 100644 --- a/docs/Manifest.toml +++ b/docs/Manifest.toml @@ -420,7 +420,7 @@ uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" deps = ["Base64", "IOCapture", "JSON", "REPL"] path = ".." uuid = "98b081ad-f1c9-55d3-8b20-4c87d4299306" -version = "2.15.1" +version = "2.16.0" [[LogExpFunctions]] deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"] diff --git a/src/Literate.jl b/src/Literate.jl index f12440c..071fa17 100644 --- a/src/Literate.jl +++ b/src/Literate.jl @@ -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 && @@ -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 @@ -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 @@ -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````" @@ -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: " * @@ -745,7 +751,7 @@ 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"] @@ -753,7 +759,7 @@ function execute_notebook(nb; inputfile::String, fake_source::String) 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) @@ -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) @@ -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 diff --git a/test/runtests.jl b/test/runtests.jl index d56f304..419838f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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 @@ -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