Skip to content

Commit c47611c

Browse files
authored
Implements Readme.md generator (#63)
* Fixes link to other UDL topic file * Adds Readme generator script * Adds readme * Fixes type in comment
1 parent 300b041 commit c47611c

File tree

3 files changed

+254
-1
lines changed

3 files changed

+254
-1
lines changed

Readme.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!--
2+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
3+
!! Please do not edit this file directly, !!
4+
!! it is auto generated by the ./gen_readme.py script. !!
5+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
6+
-->
7+
8+
# SG20: Teaching Topics
9+
10+
## Modules:
11+
* [C++ object model](#c-object-model)
12+
* [User-defined types](#user-defined-types)
13+
* [Functions](#functions)
14+
15+
### C++ object model
16+
* [rule-of-five](object-model/rule-of-five.md)
17+
* [constant-objects](object-model/constant-objects.md)
18+
* [special-member-functions](object-model/special-member-functions.md)
19+
* [declarations](object-model/declarations.md)
20+
* [objects](object-model/objects.md)
21+
* [types](object-model/types.md)
22+
* [copy-semantics](object-model/copy-semantics.md)
23+
* [rule-of-zero](object-model/rule-of-zero.md)
24+
* [move-semantics](object-model/move-semantics.md)
25+
26+
### User-defined types
27+
* [user-defined-literals](functions/user-defined-literals.md)
28+
29+
### Functions
30+
* [user-defined-literals](functions/user-defined-literals.md)
31+

gen_readme.py

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Small tool to generate a Readme.md file as an overview of all teaching modules
4+
and topics.
5+
6+
> ./gen_readme.py
7+
"""
8+
from pathlib import Path
9+
from typing import List
10+
import re
11+
12+
FOLDER_SKIP_LIST = ['.git', '.github', '.mypy_cache', "__pycache__", 'tools']
13+
README_HEADER = "# SG20: Teaching Topics"
14+
DO_NOT_EDIT_WARNING = """<!--
15+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
16+
!! Please do not edit this file directly, !!
17+
!! it is auto generated by the ./gen_readme.py script. !!
18+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
19+
-->
20+
"""
21+
22+
23+
def create_markdown_anchor(header: str) -> str:
24+
"""
25+
Creates an markdown anchor for a header.
26+
27+
>>> create_markdown_anchor("C++ compilation model")
28+
'(#c-compilation-model)'
29+
>>> create_markdown_anchor("# C++ compilation model")
30+
'(#c-compilation-model)'
31+
"""
32+
header_anchor = header.lower()
33+
header_anchor = header_anchor.lstrip("# ")
34+
header_anchor = header_anchor.replace(' ', '-')
35+
header_anchor = header_anchor.replace('+', '')
36+
return f"(#{header_anchor})"
37+
38+
39+
def create_header_link(header: str) -> str:
40+
"""
41+
Creates a link for the specified header.
42+
43+
>>> create_header_link("# My header")
44+
'[My header](#my-header)'
45+
"""
46+
return f"[{header.lstrip('# ')}]{create_markdown_anchor(header)}"
47+
48+
49+
class TeachingTopic:
50+
"""Abstraction for a teaching topic."""
51+
52+
LINK_REGEX = re.compile(r".*\((?P<path>.*)\)")
53+
54+
def __init__(self, name: str, path: Path) -> None:
55+
self.__name = name
56+
self.__path = TeachingTopic.exchange_path_with_orignal_topic_file_path(
57+
path)
58+
59+
@property
60+
def name(self) -> str:
61+
"""Name of the topic."""
62+
return self.__name
63+
64+
@property
65+
def path(self) -> Path:
66+
"""Path to the topic file."""
67+
return self.__path
68+
69+
@staticmethod
70+
def exchange_path_with_orignal_topic_file_path(path: Path) -> Path:
71+
"""
72+
For topics that occure in more than one module, all except for the
73+
original topic file are placeholders that forward the reader to the
74+
original file. To make navigating easier, we automatically exchange the
75+
path of a topic file with the original path, so users can get directly
76+
to the desired file.
77+
"""
78+
with open(path, "r") as topic_file:
79+
first_line = topic_file.readline()
80+
if first_line.lower().startswith("see "):
81+
match = TeachingTopic.LINK_REGEX.search(first_line)
82+
if match:
83+
# Strip leading ../ because we are at the top level dir
84+
return Path(match.group("path").lstrip("./"))
85+
86+
return path
87+
88+
89+
class TeachingModule:
90+
"""Abstraction for a teaching module folder."""
91+
def __init__(self, title: str, path: Path,
92+
topics: List[TeachingTopic]) -> None:
93+
self.__title = title
94+
self.__path = path
95+
self.__topics = topics
96+
97+
@property
98+
def title(self) -> str:
99+
"""Title of the teaching module."""
100+
return self.__title
101+
102+
@property
103+
def path(self) -> Path:
104+
"""Path to the module folder."""
105+
return self.__path
106+
107+
@property
108+
def topics(self) -> List[TeachingTopic]:
109+
"""List of teaching topics."""
110+
return self.__topics
111+
112+
@staticmethod
113+
def convert_folder_name_to_title(folder_name: str) -> str:
114+
"""Converts a module folder name to the corresponding title.
115+
116+
>>> TeachingModule.convert_folder_name_to_title("user-defined-types")
117+
'User-defined types'
118+
>>> TeachingModule.convert_folder_name_to_title("functions")
119+
'Functions'
120+
>>> TeachingModule.convert_folder_name_to_title("error-handling")
121+
'Error handling'
122+
"""
123+
# Special module names where we need specific name replacements
124+
special_cases = {
125+
"meta-error-handling": "Meta-error handling",
126+
"compile-time-programming": "Compile-time programming",
127+
"user-defined-types": "User-defined types",
128+
"object-model": "C++ object model"
129+
}
130+
title: str = ""
131+
if folder_name in special_cases:
132+
title = special_cases[folder_name]
133+
else:
134+
title = folder_name
135+
title = title.replace("-", " ")
136+
137+
title = title.capitalize()
138+
return title
139+
140+
@staticmethod
141+
def parse_module(module_dir: Path) -> 'TeachingModule':
142+
"""Parse `TeachingModule` from file path."""
143+
module_title = TeachingModule.convert_folder_name_to_title(
144+
module_dir.name)
145+
146+
teaching_topics: List[TeachingTopic] = []
147+
for topic_path in module_dir.iterdir():
148+
teaching_topics.append(TeachingTopic(topic_path.stem, topic_path))
149+
150+
return TeachingModule(module_title, module_dir, teaching_topics)
151+
152+
153+
def create_module_title_with_header_link(module: TeachingModule) -> str:
154+
"""
155+
Creates a module title with string with a link to the corresponding header
156+
anchor.
157+
158+
>>> create_module_title_with_header_link(TeachingModule(\
159+
"C++ compilation model", Path("."),["Foo", "Bar"]))
160+
'[C++ compilation model](#c-compilation-model)'
161+
"""
162+
return create_header_link(module.title)
163+
164+
165+
def create_topic_path_link(topic: TeachingTopic) -> str:
166+
"""
167+
Creates a link for the specified topic.
168+
169+
>>> create_topic_path_link(TeachingTopic(\
170+
"Constant objects",\
171+
Path("./object-model/constant-objects.md")))
172+
'[Constant objects](object-model/constant-objects.md)'
173+
"""
174+
return f"[{topic.name}]({str(topic.path)})"
175+
176+
177+
def create_topic_list_item(topic: TeachingTopic) -> str:
178+
"""Creates a list item for a topic.
179+
180+
>>> create_topic_list_item(TeachingTopic(\
181+
"Constant objects",\
182+
Path("./object-model/constant-objects.md")))
183+
'* [Constant objects](object-model/constant-objects.md)\\n'
184+
"""
185+
return f"* {create_topic_path_link(topic)}\n"
186+
187+
188+
def readme_generator_driver() -> None:
189+
"""
190+
Run the Readme.md generation process.
191+
"""
192+
readme_filename = "Readme.md"
193+
repo_root = Path('.')
194+
teaching_modules: List[TeachingModule] = []
195+
196+
for directory in filter(
197+
lambda x: (x.is_dir() and x.name not in FOLDER_SKIP_LIST),
198+
repo_root.iterdir()):
199+
teaching_modules.append(TeachingModule.parse_module(directory))
200+
201+
with open(readme_filename, 'w') as readme_file:
202+
readme_file.write(DO_NOT_EDIT_WARNING + "\n")
203+
readme_file.write(README_HEADER + "\n\n")
204+
205+
# Print all module links
206+
readme_file.write("## Modules:\n")
207+
for module in teaching_modules:
208+
readme_file.write(
209+
f"* {create_module_title_with_header_link(module)}\n")
210+
211+
readme_file.write("\n")
212+
213+
# Print teaching topics one module at a time
214+
for module in teaching_modules:
215+
readme_file.write(f"### {module.title}\n")
216+
for topic in module.topics:
217+
readme_file.write(create_topic_list_item(topic))
218+
readme_file.write("\n")
219+
220+
221+
if __name__ == "__main__":
222+
readme_generator_driver()
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
See [../functions/user-defined-literals.md].
1+
See [user-defined-literals](../functions/user-defined-literals.md).

0 commit comments

Comments
 (0)