Jupyter notebook -- saving code to file?

I’m using Jupyter notebooks in VSCode to do computations that I want to include in a word processor (LyX for now).

Generating plots is no problem: I just save them to file, and link the plots into the word processor.

Question: is there a clean way to save the content of a code cell to file, so that I can link it into a computer listing (of, say LyX)?

MS Copilot suggests to define a macro, say:

macro save_and_run(path, block)
    # Write the code to a file
    open(path, "w") do io
        print(io, block)
    end
    # Evaluate the code in the caller's scope
    return esc(block)
end

and place the code within a macro statement:

@save_and_run "cell1.jl" begin
    x = 1:10
    y = sum(x)
    println("Sum is $y")
end

I have zero experience with writing macros. And for all I know, this functionality already exists?? Suggestions are appreciated.

NBInclude.jl has an nbexport function to convert a notebook to plain text. Notebook files are just JSON, so you can also use any JSON package like JSON.jl to extract whatever you want.

4 Likes

You can also use In[n] in IJulia (including via vscode, if you are using the actual Jupyter kernel instead of vscode’s partial re-implementation) to access the contents of any input code cell as a string.

2 Likes

A problem with this, is that…

  • I typically only want to save specific code segments in a notebook
  • I may re-arrange the order of content in a notebook (including adding markup code, etc.). This makes it challenging to auto-import code into, say LyX computer listing, because I may change the location of the code that I want to import.

I probably use the VSCode partial re-implementation (?).

After some back and forth with MS Copilot, it (=MSCopilot) came up with the following macro:

"""
    @save_run path_expr begin
        code...
    end

Write the contents of the block to the file given by `path_expr`,
preserving user comments but removing Jupyter's line-number annotations.
Then evaluate the statements in the caller's scope.
"""
macro save_run(path_expr, block)
    @assert block.head === :block "Second argument must be a `begin ... end` block."

    # Evaluate the path expression in the caller's module
    path = Core.eval(__module__, path_expr)

    # --- Remove only LineNumberNodes, preserve comments ---
    cleaned_stmts = filter(stmt -> !(stmt isa LineNumberNode), block.args)

    # --- Write each cleaned statement to the file ---
    open(path, "w") do io
        for stmt in cleaned_stmts
            print(io, stmt, "\n")
        end
    end

    # --- Evaluate the cleaned statements in the caller's scope ---
    return Expr(:block, (esc(stmt) for stmt in cleaned_stmts)...)
end

Here is an example of running it:

using Plots

listpath = "..."
listtype = ".jl"

@save_run listpath*"test"*listtype begin
    # Testing comments
    x = range(0,2pi,length=100)
    # Plotting code
    plot(x,sin.(x))
end

The code runs in JupyterNotebook as it should. But the implementation is not perfect…

  • My comments in the code are stripped off in the produced file
  • MS Copilot suggests that indentation I use in code, functions, etc. may be removed

However, MS Copilot suggests that the macro should work in VSCode itself, in Pluto, etc.

You can always do:

code = """
some code
"""
include_string(Main, code)
write(filename, code)

to save everything, including comments, no macros required. (But the syntax highlighting won’t work in your editor.)

Otherwise you have to do something specific to the IJulia kernel (which you can select in vscode by choosing the Jupyter kernel).

3 Likes

That works fine! I just reverse the order of include_string and write so that I get the code response in the below the Jupyter Notebook cell.