1- using JSON
2- using Literate
1+ using Distributed
2+ using Tables
3+ using MarkdownTables
4+ using SHA
35
4- ENV [" GKSwstype" ] = " 100"
5-
6- function main (; rmsvg= true )
7- file = get (ENV , " NB" , " test.ipynb" )
8- cachedir = get (ENV , " NBCACHE" , " .cache" )
9- nb = if endswith (file, " .jl" )
10- run_literate (file; cachedir)
11- elseif endswith (file, " .ipynb" )
12- lit = to_literate (file)
13- run_literate (lit; cachedir)
14- else
15- error (" $(file) is not a valid notebook file!" )
16- end
17- rmsvg && strip_svg (nb)
18- return nothing
6+ @everywhere begin
7+ ENV [" GKSwstype" ] = " 100"
8+ using Literate, JSON
199end
2010
2111# Strip SVG output from a Jupyter notebook
22- function strip_svg (ipynb )
23- oldfilesize = filesize (ipynb )
24- nb = open (JSON. parse, ipynb , " r" )
12+ @everywhere function strip_svg (nbpath )
13+ oldfilesize = filesize (nbpath )
14+ nb = open (JSON. parse, nbpath , " r" )
2515 for cell in nb[" cells" ]
2616 ! haskey (cell, " outputs" ) && continue
2717 for output in cell[" outputs" ]
@@ -33,14 +23,34 @@ function strip_svg(ipynb)
3323 end
3424 end
3525 end
36- rm (ipynb; force= true )
37- write (ipynb, JSON. json (nb, 1 ))
38- @info " Stripped SVG in $(ipynb) . The original size is $(Base. format_bytes (oldfilesize)) . The new size is $(Base. format_bytes (filesize (ipynb))) ."
39- return ipynb
26+ rm (nbpath; force= true )
27+ write (nbpath, JSON. json (nb, 1 ))
28+ @info " Stripped SVG in $(nbpath) . The original size is $(Base. format_bytes (oldfilesize)) . The new size is $(Base. format_bytes (filesize (nbpath))) ."
29+ return nbpath
30+ end
31+
32+ # Remove cached notebook and sha files if there is no corresponding notebook
33+ function clean_cache (cachedir)
34+ for (root, _, files) in walkdir (cachedir)
35+ for file in files
36+ fn, ext = splitext (file)
37+ if ext == " .sha"
38+ target = joinpath (joinpath (splitpath (root)[2 : end ]), fn)
39+ nb = target * " .ipynb"
40+ lit = target * " .jl"
41+ if ! isfile (nb) && ! isfile (lit)
42+ cachepath = joinpath (root, fn)
43+ @info " Notebook $(nb) or $(lit) not found. Removing $(cachepath) SHA and notebook."
44+ rm (cachepath * " .sha" )
45+ rm (cachepath * " .ipynb" ; force= true )
46+ end
47+ end
48+ end
49+ end
4050end
4151
4252# Convert a Jupyter notebook into a Literate notebook. Adapted from https://github.com/JuliaInterop/NBInclude.jl.
43- function to_literate (nbpath; shell_or_help = r" ^\s *[;?]" )
53+ function to_literate (nbpath; shell_or_help= r" ^\s *[;?]" )
4454 nb = open (JSON. parse, nbpath, " r" )
4555 jlpath = splitext (nbpath)[1 ] * " .jl"
4656 open (jlpath, " w" ) do io
@@ -62,12 +72,78 @@ function to_literate(nbpath; shell_or_help = r"^\s*[;?]")
6272 return jlpath
6373end
6474
65- function run_literate (file; cachedir = " .cache" )
75+ # List notebooks without caches in a file tree
76+ function list_notebooks (basedir, cachedir)
77+ list = String[]
78+ for (root, _, files) in walkdir (basedir)
79+ for file in files
80+ name, ext = splitext (file)
81+ if ext == " .ipynb" || ext == " .jl"
82+ nb = joinpath (root, file)
83+ shaval = read (nb, String) |> sha256 |> bytes2hex
84+ @info " $(nb) SHA256 = $(shaval) "
85+ shafilename = joinpath (cachedir, root, name * " .sha" )
86+ if isfile (shafilename) && read (shafilename, String) == shaval
87+ @info " $(nb) cache hits and will not be executed."
88+ else
89+ @info " $(nb) cache misses. Writing hash to $(shafilename) ."
90+ mkpath (dirname (shafilename))
91+ write (shafilename, shaval)
92+ if ext == " .ipynb"
93+ litnb = to_literate (nb)
94+ rm (nb; force= true )
95+ push! (list, litnb)
96+ elseif ext == " .jl"
97+ push! (list, nb)
98+ end
99+ end
100+ end
101+ end
102+ end
103+ return list
104+ end
105+
106+ # Run a Literate notebook
107+ @everywhere function run_literate (file, cachedir; rmsvg= true )
66108 outpath = joinpath (abspath (pwd ()), cachedir, dirname (file))
67109 mkpath (outpath)
68110 ipynb = Literate. notebook (file, dirname (file); mdstrings= true , execute= true )
111+ rmsvg && strip_svg (ipynb)
69112 cp (ipynb, joinpath (outpath, basename (ipynb)); force= true )
70113 return ipynb
71114end
72115
116+ function main (;
117+ basedir= get (ENV , " DOCDIR" , " docs" ),
118+ cachedir= get (ENV , " NBCACHE" , " .cache" ),
119+ rmsvg= true )
120+
121+ mkpath (cachedir)
122+ clean_cache (cachedir)
123+ litnbs = list_notebooks (basedir, cachedir)
124+
125+ if ! isempty (litnbs)
126+ # Execute literate notebooks in worker process(es)
127+ ts_lit = pmap (litnbs; on_error= identity) do nb
128+ @elapsed run_literate (nb, cachedir; rmsvg)
129+ end
130+ rmprocs (workers ()) # Remove worker processes to release some memory
131+ failed = false
132+ for (nb, t) in zip (litnbs, ts_lit)
133+ if t isa ErrorException
134+ println (" Notebook: " , nb, " failed with error: \n " , t. msg)
135+ failed = true
136+ end
137+ end
138+
139+ if failed
140+ error (" Please check literate notebook error(s)." )
141+ else
142+ # Print execution result
143+ Tables. table ([litnbs ts_lit]; header= [" Notebook" , " Elapsed (s)" ]) |> markdown_table (String) |> print
144+ end
145+ end
146+ end
147+
148+ # Run code
73149main ()
0 commit comments