Skip to content

Commit a8b6a13

Browse files
authored
Add ability to generate a notebook index for Try QuTiP. (#90)
* Fix grammar in version notes. * Replace prefix and suffix with Jinja escaping. * Factor out loading the list of tutorials. * Make url_prefix a class variable rather than an accidental global one. * Add notebook index template. * Replace HTML lists with Markdown lists. * Uppercase name of Notebook class. * Update create index to allow more control over what is indexed and where outpout is written. * Move raw after start of Markdown header. * Remove remaining HTML from notebook index. * Rename notebook index template to .notebook.jinja. * Fix typo in root folder reference, update name of notebook index template and add options to argparse parameter help. * Update HTML index creation to use new script. * Add option to generate Try QuTiP URLs. * Update order of arguments to create index. * Fix regular expression escape. * Fix list formatting. * Update try-qutip template to follow the hierarchy of the usual tutorial website. * Remove try-qutip URL hardcoding (jupyterlib seems happier with relative links now).
1 parent 5e563a2 commit a8b6a13

File tree

6 files changed

+273
-99
lines changed

6 files changed

+273
-99
lines changed

.github/workflows/notebook_ci.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,8 @@ jobs:
150150
cd ..
151151
152152
# build the website
153-
python create_index.py
153+
python create_index.py v5 html index.html
154+
python create_index.py v4 html index-v4.html
154155
bundle config path .gems
155156
bundle install
156157
bundle exec jekyll build

website/create_index.py

Lines changed: 146 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,60 @@
1-
import os
1+
""" Script for generating indexes of the notebook. """
2+
3+
import argparse
4+
import pathlib
25
import re
3-
from jinja2 import Environment, FileSystemLoader, select_autoescape
6+
7+
from jinja2 import (
8+
Environment,
9+
FileSystemLoader,
10+
select_autoescape,
11+
)
12+
13+
14+
TUTORIAL_DIRECTORIES = [
15+
'heom',
16+
'lectures',
17+
'pulse-level-circuit-simulation',
18+
'python-introduction',
19+
'quantum-circuits',
20+
'time-evolution',
21+
'visualization',
22+
'miscellaneous'
23+
]
424

525

626
def atoi(text):
727
return int(text) if text.isdigit() else text
828

929

1030
def natural_keys(text):
11-
return [atoi(c) for c in re.split('(\d+)', text)]
31+
return [atoi(c) for c in re.split(r'(\d+)', text)]
32+
1233

34+
class Notebook:
35+
""" Notebook object for use in rendering templates. """
36+
37+
NBVIEWER_URL_PREFIX = "https://nbviewer.org/urls/qutip.org/qutip-tutorials/"
38+
39+
def __init__(self, title, tutorial_folder, path):
40+
self.tutorial_folder = tutorial_folder
41+
self.web_folder = tutorial_folder.parent
1342

14-
class notebook:
15-
def __init__(self, path, title):
16-
# remove ../ from path
17-
self.path = path.replace('../', '')
1843
self.title = title
19-
# set url and update from markdown to ipynb
20-
self.url = url_prefix + self.path.replace(".md", ".ipynb")
2144

45+
self.web_md_path = path.relative_to(self.web_folder)
46+
self.web_ipynb_path = self.web_md_path.with_suffix(".ipynb")
47+
48+
self.tutorial_md_path = path.relative_to(self.tutorial_folder)
49+
self.tutorial_ipynb_path = self.tutorial_md_path.with_suffix(".ipynb")
50+
51+
self.nbviewer_url = self.NBVIEWER_URL_PREFIX + self.web_ipynb_path.as_posix()
52+
self.try_qutip_url = "./tutorials/" + self.tutorial_ipynb_path.as_posix()
2253

23-
def get_title(filename):
54+
55+
def get_title(path):
2456
""" Reads the title from a markdown notebook """
25-
with open(filename, 'r') as f:
57+
with path.open('r') as f:
2658
# get first row that starts with "# "
2759
for line in f.readlines():
2860
# trim leading/trailing whitespaces
@@ -36,98 +68,128 @@ def get_title(filename):
3668
def sort_files_titles(files, titles):
3769
""" Sorts the files and titles either by filenames or titles """
3870
# identify numbered files and sort them
39-
nfiles = [s for s in files if s.split('/')[-1][0].isdigit()]
40-
nfiles = sorted(nfiles, key=natural_keys)
71+
nfiles = [s for s in files if s.name[0].isdigit()]
72+
nfiles = sorted(nfiles, key=lambda s: natural_keys(s.name))
4173
ntitles = [titles[files.index(s)] for s in nfiles]
74+
4275
# sort the files without numbering by the alphabetic order of the titles
4376
atitles = [titles[files.index(s)] for s in files if s not in nfiles]
4477
atitles = sorted(atitles, key=natural_keys)
4578
afiles = [files[titles.index(s)] for s in atitles]
79+
4680
# merge the numbered and unnumbered sorting
4781
return nfiles + afiles, ntitles + atitles
4882

4983

50-
def get_notebooks(path):
84+
def get_notebooks(tutorials_folder, subfolder):
5185
""" Gets a list of all notebooks in a directory """
52-
# get list of files and their titles
53-
try:
54-
files = [path + f for f in os.listdir(path) if f.endswith('.md')]
55-
except FileNotFoundError:
56-
return {}
86+
files = list((tutorials_folder / subfolder).glob("*.md"))
5787
titles = [get_title(f) for f in files]
58-
# sort the files and titles for display
5988
files_sorted, titles_sorted = sort_files_titles(files, titles)
60-
# generate notebook objects from the sorted lists and return
61-
notebooks = [notebook(f, t) for f, t in zip(files_sorted, titles_sorted)]
89+
notebooks = [
90+
Notebook(title, tutorials_folder, path)
91+
for title, path in zip(titles_sorted, files_sorted)
92+
]
6293
return notebooks
6394

6495

65-
def generate_index_html(version_directory, tutorial_directories, title,
66-
version_note):
67-
""" Generates the index html file from the given data"""
68-
# get tutorials from the different directories
96+
def get_tutorials(tutorials_folder, tutorial_directories):
97+
""" Return a dictionary of all tutorials for a particular version. """
6998
tutorials = {}
70-
for dir in tutorial_directories:
71-
tutorials[dir] = get_notebooks(version_directory + dir + '/')
99+
for subfolder in tutorial_directories:
100+
tutorials[subfolder] = get_notebooks(tutorials_folder, subfolder)
101+
return tutorials
72102

73-
# Load environment for Jinja and template
103+
104+
def render_template(template_path, **kw):
105+
""" Render a Jinja template """
74106
env = Environment(
75-
loader=FileSystemLoader("../"),
76-
autoescape=select_autoescape()
107+
loader=FileSystemLoader(str(template_path.parent)),
108+
autoescape=select_autoescape(),
77109
)
78-
template = env.get_template("website/index.html.jinja")
79-
80-
# render template and return
81-
html = template.render(tutorials=tutorials, title=title,
82-
version_note=version_note)
83-
return html
110+
template = env.get_template(template_path.name)
111+
return template.render(**kw)
84112

85113

86-
# url prefix for the links
87-
url_prefix = "https://nbviewer.org/urls/qutip.org/qutip-tutorials/"
88-
# tutorial directories
89-
tutorial_directories = [
90-
'heom',
91-
'lectures',
92-
'pulse-level-circuit-simulation',
93-
'python-introduction',
94-
'quantum-circuits',
95-
'time-evolution',
96-
'visualization',
97-
'miscellaneous'
98-
]
114+
def parse_args():
115+
parser = argparse.ArgumentParser(
116+
description="""
117+
Generate indexes for tutorial notebooks.
99118
100-
# +++ READ PREFIX AND SUFFIX +++
101-
prefix = ""
102-
suffix = ""
103-
104-
with open('prefix.html', 'r') as f:
105-
prefix = f.read()
106-
with open('suffix.html', 'r') as f:
107-
suffix = f.read()
108-
109-
# +++ VERSION 4 INDEX FILE +++
110-
title = 'Tutorials for QuTiP Version 4'
111-
version_note = 'This are the tutorials for QuTiP Version 4. You can \
112-
find the tutorials for QuTiP Version 5 \
113-
<a href="./index.html">here</a>.'
114-
115-
html = generate_index_html('../tutorials-v4/', tutorial_directories, title,
116-
version_note)
117-
with open('index-v4.html', 'w+') as f:
118-
f.write(prefix)
119-
f.write(html)
120-
f.write(suffix)
121-
122-
# +++ VERSION 5 INDEX FILE +++
123-
title = 'Tutorials for QuTiP Version 5'
124-
version_note = 'This are the tutorials for QuTiP Version 5. You can \
125-
find the tutorials for QuTiP Version 4 \
126-
<a href="./index-v4.html">here</a>.'
127-
128-
html = generate_index_html('../tutorials-v5/', tutorial_directories, title,
129-
version_note)
130-
with open('index.html', 'w+') as f:
131-
f.write(prefix)
132-
f.write(html)
133-
f.write(suffix)
119+
This script is used both by this repository to generate the indexes
120+
for the QuTiP tutorial website and by https://github.com/qutip/try-qutip/
121+
to generate the notebook indexes for the Try QuTiP site.
122+
""",
123+
)
124+
parser.add_argument(
125+
"qutip_version", choices=["v4", "v5"],
126+
metavar="QUTIP_VERSION",
127+
help="Which QuTiP version to generate the tutorial index for [v4, v5].",
128+
)
129+
parser.add_argument(
130+
"index_type", choices=["html", "try-qutip"],
131+
metavar="INDEX_TYPE",
132+
help=(
133+
"Whether to generate an HTML index for the website or"
134+
" a Markdown Jupyter notebook index for the Try QuTiP site"
135+
" [html, try-qutip]."
136+
),
137+
)
138+
parser.add_argument(
139+
"output_file",
140+
metavar="OUTPUT_FILE",
141+
help="File to write the index to.",
142+
)
143+
return parser.parse_args()
144+
145+
146+
def main():
147+
args = parse_args()
148+
149+
root_folder = pathlib.Path(__file__).parent.parent
150+
151+
if args.qutip_version == "v4":
152+
title = "Tutorials for QuTiP Version 4"
153+
tutorials_folder = root_folder / "tutorials-v4"
154+
version_note = """
155+
These are the tutorials for QuTiP Version 4. You can
156+
find the tutorials for QuTiP Version 5
157+
<a href="./index.html">here</a>.
158+
""".strip()
159+
elif args.qutip_version == "v5":
160+
title = "Tutorials for QuTiP Version 5"
161+
tutorials_folder = root_folder / "tutorials-v5"
162+
version_note = """
163+
These are the tutorials for QuTiP Version 5. You can
164+
find the tutorials for QuTiP Version 4
165+
<a href="./index-v4.html">here</a>.
166+
""".strip()
167+
else:
168+
raise ValueError(f"Unsupported qutip_version: {args.qutip_version!r}")
169+
170+
tutorials = get_tutorials(tutorials_folder, TUTORIAL_DIRECTORIES)
171+
172+
if args.index_type == "html":
173+
template = root_folder / "website" / "index.html.jinja"
174+
text = render_template(
175+
template,
176+
title=title,
177+
version_note=version_note,
178+
tutorials=tutorials,
179+
)
180+
elif args.index_type == "try-qutip":
181+
template = root_folder / "website" / "index.try-qutip.jinja"
182+
text = render_template(
183+
template,
184+
title=title,
185+
tutorials=tutorials,
186+
)
187+
else:
188+
raise ValueError(f"Unsupported index_type: {args.index_type!r}")
189+
190+
with open(args.output_file, "w") as f:
191+
f.write(text)
192+
193+
194+
if __name__ == "__main__":
195+
main()

website/index.html.jinja

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
---
2+
title: QuTiP Tutorials
3+
---
4+
{% raw %}
5+
{% include header.html %}
6+
{% include navbar.html %}
7+
{% endraw %}
8+
19
<div class="row">
210
<div class="col-md-12">
311
<h1>{{ title }}</h1><br>
@@ -60,7 +68,7 @@ useful to have a look at these IPython notebook
6068
<h4 id="python-introduction">Python Introduction</h4>
6169
<ul>
6270
{% for item in tutorials['python-introduction'] %}
63-
<li><a href="{{ item.url }}">{{ item.title }}</a></li>
71+
<li><a href="{{ item.nbviewer_url }}">{{ item.title }}</a></li>
6472
{% endfor %}
6573
</ul>
6674
<p>For a more in depth discussion see: <a href="https://github.com/jrjohansson/scientific-python-lectures">Lectures on scientific computing with Python</a>.</p>
@@ -69,7 +77,7 @@ useful to have a look at these IPython notebook
6977
<h4 id="visualizations">Visualization</h4>
7078
<ul>
7179
{% for item in tutorials['visualization'] %}
72-
<li><a href="{{ item.url }}">{{ item.title }}</a></li>
80+
<li><a href="{{ item.nbviewer_url }}">{{ item.title }}</a></li>
7381
{% endfor %}
7482
</ul>
7583

@@ -79,14 +87,14 @@ useful to have a look at these IPython notebook
7987
<h5 id="qip-circuits">Quantum circuits and algorithms</h5>
8088
<ul>
8189
{% for item in tutorials['quantum-circuits'] %}
82-
<li><a href="{{ item.url }}">{{ item.title }}</a></li>
90+
<li><a href="{{ item.nbviewer_url }}">{{ item.title }}</a></li>
8391
{% endfor %}
8492
</ul>
8593

8694
<h5 id="qip-pulse-level">Pulse-level circuit simulation</h5>
8795
<ul>
8896
{% for item in tutorials['pulse-level-circuit-simulation'] %}
89-
<li><a href="{{ item.url }}">{{ item.title }}</a></li>
97+
<li><a href="{{ item.nbviewer_url }}">{{ item.title }}</a></li>
9098
{% endfor %}
9199
</ul>
92100

@@ -97,7 +105,7 @@ useful to have a look at these IPython notebook
97105
<h4 id="time-evolution">Time evolution</h4>
98106
<ul>
99107
{% for item in tutorials['time-evolution'] %}
100-
<li><a href="{{ item.url }}">{{ item.title }}</a></li>
108+
<li><a href="{{ item.nbviewer_url }}">{{ item.title }}</a></li>
101109
{% endfor %}
102110
</ul>
103111

@@ -137,14 +145,14 @@ useful to have a look at these IPython notebook
137145
<h4 id="heom">Hierarchical Equations of Motion</h4>
138146
<ul>
139147
{% for item in tutorials['heom'] %}
140-
<li><a href="{{ item.url }}">{{ item.title }}</a></li>
148+
<li><a href="{{ item.nbviewer_url }}">{{ item.title }}</a></li>
141149
{% endfor %}
142150
</ul>
143151

144152
<h4 id="miscellaneous">Miscellaneous tutorials</h4>
145153
<ul>
146154
{% for item in tutorials['miscellaneous'] %}
147-
<li><a href="{{ item.url }}">{{ item.title }}</a></li>
155+
<li><a href="{{ item.nbviewer_url }}">{{ item.title }}</a></li>
148156
{% endfor %}
149157
</ul>
150158

@@ -160,7 +168,7 @@ topics and analyze them numerically using QuTiP (some more detailed than others)
160168

161169
<ul>
162170
{% for item in tutorials['lectures'] %}
163-
<li><a href="{{ item.url }}">{{ item.title }}</a></li>
171+
<li><a href="{{ item.nbviewer_url }}">{{ item.title }}</a></li>
164172
{% endfor %}
165173
</ul>
166174

@@ -182,3 +190,7 @@ a complete archive of older versions of the tutorials is maintained there.
182190

183191
</div>
184192
</div>
193+
194+
{% raw %}
195+
{% include footer.html %}
196+
{% endraw %}

0 commit comments

Comments
 (0)