1- import os
1+ """ Script for generating indexes of the notebook. """
2+
3+ import argparse
4+ import pathlib
25import 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
626def atoi (text ):
727 return int (text ) if text .isdigit () else text
828
929
1030def 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):
3668def 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 ()
0 commit comments