Skip to content

Commit f59d068

Browse files
authored
Optionally disable notebook build (#98)
1 parent 4318c78 commit f59d068

File tree

21 files changed

+270
-37
lines changed

21 files changed

+270
-37
lines changed

docs/quickstart/usage_example/basics/configure_sec_and_page.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,45 @@ There are three possible options for `theme`:
4444
* `"list"`: a list like index page.
4545

4646
You can check the "Theme Gallery" to see how different themes look like.
47+
48+
49+
## [Override default values with properties](@ref page_sec_properties)
50+
51+
Another item that could be useful is `properties`, it is written as a dictionary-like format, and
52+
provides a way to override some default values. Properties can be configured at any level and it
53+
follows very simple rules:
54+
55+
- Properties at a higher level item can be propagated into lower level items
56+
- Properties at lower level items are of higher priority
57+
58+
59+
```text
60+
example_page/
61+
├── config.json
62+
├── sec1
63+
│ ├── config.json
64+
│ ├── demo1.jl
65+
│ ├── demo2.jl
66+
│ └── demo3.md
67+
└── sec2
68+
├── demo4.jl
69+
└── demo5.jl
70+
```
71+
72+
For example, in the above folder structure, if we configure `example_page/config.json` with
73+
74+
```json
75+
{
76+
"properties":{
77+
"notebook": "false"
78+
}
79+
}
80+
```
81+
82+
Then all julia demos will not generate jupyter notebook unless it's override somewhere in the lower
83+
level, e.g, `sec1/config.json` or `demo1.jl`.
84+
85+
86+
Only a subset of demo entries support the property propagation, currently supported items are:
87+
88+
* Julia files: `notebook` allows you to enable/disable the jupyter notebook generation.

docs/quickstart/usage_example/julia_demos/1.julia_demo.jl

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@
2020
# 4. jupyter notebook file
2121
#
2222
# Links to nbviewer and source files are provided as well.
23+
24+
# !!! note
25+
# This entails every source file being executed three times: 1) asset generation, 2) notebook
26+
# generation, and 3) Documenter HTML generation. If the generation time is too long for your
27+
# application, you could then choose to write your demo in markdown format. Or you can disable
28+
# the notebook generation via the `notebook` keyword in your demos, this helps reduce the
29+
# runtime to 2x.
30+
2331
#
2432
# The conversions from source demo files to `.jl`, `.md` and `.ipynb` files are mainly handled by
2533
# [`Literate.jl`](https://github.com/fredrikekre/Literate.jl). The syntax to control/filter the
@@ -50,7 +58,7 @@ img = testimage("lena")
5058
# ```
5159

5260
# In addition to the keywords supported by markdown files, Julia format demos accept one extra
53-
# frontmatter config: `julia`. It allows you to specify the compat version of your demo. With this,
61+
# frontmatter keyword: `julia`. It allows you to specify the compat version of your demo. With this,
5462
# `DemoCards` would:
5563
#
5664
# - throw a warning if your demos are generated using a lower version of Julia
@@ -59,6 +67,11 @@ img = testimage("lena")
5967
# The warning is something like "The running Julia version `1.0.5` is older than the declared
6068
# compatible version `1.3.0`."
6169

70+
# Another extra keyword is `notebook`, it allows you to control whether you needs to generate
71+
# jupyter notebook `.ipynb` for the demo. The valid values are `true` and `false`. See also
72+
# [Override default values with properties](@id page_sec_properties) on how to disable all notebook
73+
# generation via the properties entry.
74+
6275
# !!! warning
6376
# You should be careful about the leading whitespaces after the first `#`. Frontmatter as weird as the
6477
# following is not guaranteed to work and it is very likely to hit a YAML parsing error.

src/generate.jl

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -232,46 +232,49 @@ generate(io::IO, page::DemoPage, args...) = write(io, generate(page, args...))
232232

233233

234234
function generate(page::DemoPage, templates)
235-
items = Dict("democards" => generate(page.sections, templates))
235+
items = Dict("democards" => generate(page.sections, templates; properties=page.properties))
236236
Mustache.render(page.template, items)
237237
end
238238

239-
function generate(cards::AbstractVector{<:AbstractDemoCard}, template)
239+
function generate(cards::AbstractVector{<:AbstractDemoCard}, template; properties=Dict{String, Any}())
240240
# for those hidden cards, only generate the necessary assets and files, but don't add them into
241241
# the index.md page
242242
foreach(filter(x->x.hidden, cards)) do x
243-
generate(x, template)
243+
generate(x, template; properties=properties)
244244
end
245245

246246
mapreduce(*, filter(x->!x.hidden, cards); init="") do x
247-
generate(x, template)
247+
generate(x, template; properties=properties)
248248
end
249249
end
250250

251-
function generate(secs::AbstractVector{DemoSection}, templates; level=1)
251+
function generate(secs::AbstractVector{DemoSection}, templates; level=1, properties=Dict{String, Any}())
252252
mapreduce(*, secs; init="") do x
253-
generate(x, templates;level=level)
253+
properties = merge(properties, x.properties) # sec.properties has higher priority
254+
generate(x, templates; level=level, properties=properties)
254255
end
255256
end
256257

257-
function generate(sec::DemoSection, templates; level=1)
258+
function generate(sec::DemoSection, templates; level=1, properties=Dict{String, Any}())
258259
header = repeat("#", level) * " " * sec.title * "\n"
259260
footer = "\n"
261+
properties = merge(properties, sec.properties) # sec.properties has higher priority
262+
260263
# either cards or subsections are empty
261264
# recursively generate the page contents
262265
if isempty(sec.cards)
263-
body = generate(sec.subsections, templates; level=level+1)
266+
body = generate(sec.subsections, templates; level=level+1, properties=properties)
264267
else
265268
items = Dict(
266-
"cards" => generate(sec.cards, templates["card"]),
269+
"cards" => generate(sec.cards, templates["card"], properties=properties),
267270
"description" => sec.description
268271
)
269272
body = Mustache.render(templates["section"], items)
270273
end
271274
header * body * footer
272275
end
273276

274-
function generate(card::AbstractDemoCard, template)
277+
function generate(card::AbstractDemoCard, template; properties=Dict{String, Any})
275278
covername = get_covername(card)
276279

277280
if isnothing(covername)
@@ -287,7 +290,7 @@ function generate(card::AbstractDemoCard, template)
287290
if length(card.description) >= cut_idx
288291
# cut descriptions into ~500 characters
289292
offset = findfirst(' ', description[cut_idx:end])
290-
offset == nothing && (offset = 0)
293+
offset === nothing && (offset = 0)
291294
offset = cut_idx + offset - 2
292295
description = description[1:cut_idx] * "..."
293296
end
@@ -365,13 +368,14 @@ get_logopath() = joinpath(pkgdir(DemoCards), "assets", "democards_logo.svg")
365368
recursively process and save source demo file
366369
"""
367370
function save_democards(root::String, page::DemoPage; kwargs...)
368-
save_democards.(root, page.sections; kwargs...)
371+
save_democards.(root, page.sections; properties=page.properties, kwargs...)
369372
end
370-
function save_democards(root::String, sec::DemoSection; kwargs...)
373+
function save_democards(root::String, sec::DemoSection; properties, kwargs...)
374+
properties = merge(properties, sec.properties) # sec.properties has higher priority
371375
save_democards.(joinpath(root, basename(sec.root)), sec.subsections;
372-
kwargs...)
376+
properties=properties, kwargs...)
373377
save_democards.(joinpath(root, basename(sec.root)), sec.cards;
374-
kwargs...)
378+
properties=properties, kwargs...)
375379
end
376380

377381
### copy assets

src/types/julia.jl

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Besides `path`, this struct has some other fields:
3232
* `description`: multi-line description of the demo card
3333
* `julia`: Julia version compatibility
3434
* `hidden`: whether this card is shown in the generated index page
35+
* `notebook`: enable or disable the jupyter notebook generation. Valid values are `true` or `false`.
3536
3637
# Configuration
3738
@@ -74,11 +75,12 @@ mutable struct JuliaDemoCard <: AbstractDemoCard
7475
date::DateTime
7576
julia::VersionNumber
7677
hidden::Bool
78+
notebook::Union{Nothing, Bool}
7779
end
7880

7981
function JuliaDemoCard(path::String)::JuliaDemoCard
8082
# first consturct an incomplete democard, and then load the config
81-
card = JuliaDemoCard(path, "", "", "", "", "", DateTime(0), JULIA_COMPAT, false)
83+
card = JuliaDemoCard(path, "", "", "", "", "", DateTime(0), JULIA_COMPAT, false, nothing)
8284

8385
config = parse(card)
8486
card.cover = load_config(card, "cover"; config=config)
@@ -91,6 +93,24 @@ function JuliaDemoCard(path::String)::JuliaDemoCard
9193
# default description requires a title
9294
card.description = load_config(card, "description"; config=config)
9395
card.hidden = load_config(card, "hidden"; config=config)
96+
97+
# Because we want bottom-level cards configuration to have higher priority, figuring
98+
# out the default `notebook` option needs a reverse walk from top-level page to the
99+
# bottom-level card.
100+
if haskey(config, "notebook")
101+
notebook = config["notebook"]
102+
if notebook isa Bool
103+
card.notebook = notebook
104+
else
105+
card.notebook = try
106+
parse(Bool, lowercase(notebook))
107+
catch err
108+
@warn "`notebook` option should be either `\"true\"` or `\"false\"`, instead it is: $notebook. Fallback to unconfigured."
109+
nothing
110+
end
111+
end
112+
end
113+
94114
return card
95115
end
96116

@@ -118,6 +138,7 @@ function save_democards(card_dir::String,
118138
project_dir=Base.source_dir(),
119139
src="src",
120140
throw_error = false,
141+
properties = Dict{String, Any}(),
121142
kwargs...)
122143
isdir(card_dir) || mkpath(card_dir)
123144
cardname = splitext(basename(card.path))[1]
@@ -130,6 +151,20 @@ function save_democards(card_dir::String,
130151
@warn "The running Julia version `$(VERSION)` is older than the declared compatible version `$(card.julia)`. You might need to upgrade your Julia."
131152
end
132153

154+
card.notebook = if isnothing(card.notebook)
155+
# Backward compatibility: we used to generate notebooks for all jl files
156+
op = get(properties, "notebook", "true")
157+
op = isa(op, Bool) ? op : try
158+
Base.parse(Bool, lowercase(op))
159+
catch err
160+
@warn "`notebook` option should be either `\"true\"` or `\"false\"`, instead it is: $op. Fallback to \"true\"."
161+
true
162+
end
163+
else
164+
card.notebook
165+
end
166+
@assert !isnothing(card.notebook)
167+
133168
# 1. generating assets
134169
cp(card.path, src_path; force=true)
135170
cd(card_dir) do
@@ -174,14 +209,21 @@ function save_democards(card_dir::String,
174209
isempty(strip(src_header)) || (src_header *= "\n\n")
175210

176211
# insert header badge
177-
badges = make_badges(card; src=src, card_dir=card_dir, nbviewer_root_url=nbviewer_root_url, project_dir=project_dir) * "\n\n"
178-
write(src_path, badges, body)
212+
badges = make_badges(card;
213+
src=src,
214+
card_dir=card_dir,
215+
nbviewer_root_url=nbviewer_root_url,
216+
project_dir=project_dir,
217+
build_notebook=card.notebook)
218+
write(src_path, badges * "\n\n", body)
179219

180220
# 2. notebook
181-
try
182-
@suppress Literate.notebook(src_path, card_dir; credit=credit)
183-
catch err
184-
nothing # early warning in the asserts generation
221+
if card.notebook
222+
try
223+
@suppress Literate.notebook(src_path, card_dir; credit=credit)
224+
catch err
225+
nothing # There are already early warning in the assets generation
226+
end
185227
end
186228

187229
# 3. markdown
@@ -206,19 +248,21 @@ function save_democards(card_dir::String,
206248
end
207249
end
208250

209-
function make_badges(card::JuliaDemoCard; src, card_dir, nbviewer_root_url, project_dir)
251+
function make_badges(card::JuliaDemoCard; src, card_dir, nbviewer_root_url, project_dir, build_notebook)
210252
cardname = splitext(basename(card.path))[1]
211253
badges = ["#md #"]
212254
push!(badges, "[![Source code]($download_badge)]($(cardname).jl)")
213255

214-
if !isempty(nbviewer_root_url)
215-
# Note: this is only reachable in CI environment
216-
nbviewer_folder = normpath(relpath(card_dir, "$project_dir/$src"))
217-
nbviewer_url = replace("$(nbviewer_root_url)/$(nbviewer_folder)/$(cardname).ipynb", Base.Filesystem.path_separator=>'/')
218-
else
219-
nbviewer_url = "$(cardname).ipynb"
256+
if build_notebook
257+
if !isempty(nbviewer_root_url)
258+
# Note: this is only reachable in CI environment
259+
nbviewer_folder = normpath(relpath(card_dir, "$project_dir/$src"))
260+
nbviewer_url = replace("$(nbviewer_root_url)/$(nbviewer_folder)/$(cardname).ipynb", Base.Filesystem.path_separator=>'/')
261+
else
262+
nbviewer_url = "$(cardname).ipynb"
263+
end
264+
push!(badges, "[![notebook]($nbviewer_badge)]($nbviewer_url)")
220265
end
221-
push!(badges, "[![notebook]($nbviewer_badge)]($nbviewer_url)")
222266
if card.julia != JULIA_COMPAT
223267
# It might be over verbose to insert a compat badge for every julia card, only add one for
224268
# cards that users should care about

src/types/page.jl

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ Supported items are:
2222
* `theme`: specify which card theme should be used to generate the index page. If not specified, it
2323
will default to `nothing`.
2424
* `title`: specify the title of this demo page. By default, it's the folder name of `root`. Will be override by `template`.
25+
* `properties`: a dictionary of properties that can be propagated to its children items. The same properties in
26+
the children items, if exist, have higher priority.
2527
2628
The following is an example of `config.json`:
2729
@@ -32,7 +34,12 @@ The following is an example of `config.json`:
3234
"order": [
3335
"basic",
3436
"advanced"
35-
]
37+
],
38+
"properties": {
39+
"notebook": "false",
40+
"julia": "1.6",
41+
"author": "Johnny Chen"
42+
}
3643
}
3744
```
3845
@@ -89,6 +96,8 @@ mutable struct DemoPage
8996
template::String
9097
theme::Union{Nothing, String}
9198
title::String
99+
# These properties will be shared by all children of it during build time
100+
properties::Dict{String, Any}
92101
end
93102

94103
basename(page::DemoPage) = basename(page.root)
@@ -122,7 +131,7 @@ function DemoPage(root::String)::DemoPage
122131
config = merge(json_config, config) # template has higher priority over config file
123132

124133
sections = map(DemoSection, section_paths)
125-
page = DemoPage(root, sections, "", nothing, "")
134+
page = DemoPage(root, sections, "", nothing, "", Dict{String, Any}())
126135
page.theme = load_config(page, "theme"; config=config)
127136

128137
section_orders = load_config(page, "order"; config=config)
@@ -139,6 +148,10 @@ function DemoPage(root::String)::DemoPage
139148
template = load_config(page, "template"; config=config)
140149
page.template = template
141150

151+
if haskey(config, "properties")
152+
properties = config["properties"]::Dict
153+
merge!(page.properties, properties)
154+
end
142155
return page
143156
end
144157

0 commit comments

Comments
 (0)