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.

2 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.