Skip to content

Commit 340205f

Browse files
committed
self-hosted
1 parent d11264f commit 340205f

File tree

3 files changed

+160
-166
lines changed

3 files changed

+160
-166
lines changed

.github/.kodiak.toml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
# https://kodiakhq.com/docs/recipes
2-
version = 1
3-
4-
[merge]
5-
method = "squash"
6-
block_on_neutral_required_check_runs = true
7-
[merge.message]
8-
title = "pull_request_title"
9-
body = "pull_request_body"
1+
# https://kodiakhq.com/docs/recipes
2+
version = 1
3+
4+
[merge]
5+
method = "squash"
6+
block_on_neutral_required_check_runs = true
7+
[merge.message]
8+
title = "pull_request_title"
9+
body = "pull_request_body"

.github/ci.jl

Lines changed: 102 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,17 @@
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
199
end
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
4050
end
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
6373
end
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
71114
end
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
73149
main()

0 commit comments

Comments
 (0)