Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7c4037c

Browse files
committed
Added: build_ebook_v2.py
1 parent f4c63ed commit 7c4037c

2 files changed

Lines changed: 239 additions & 0 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
*.html
22
ads.txt
3+
.DS_Store
4+
build_ebook.log
5+
temp_ebook.md

build_ebook_v2.py

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
# """Generates epub and pdf from sources."""
2+
3+
import pathlib
4+
import re
5+
import subprocess
6+
7+
8+
class VTLogger:
9+
"""A logger"""
10+
def __init__(self, filename:str, log_to_file:bool=True):
11+
if log_to_file is True:
12+
self.log_file = open(filename, "w", encoding="utf-8")
13+
14+
def __del__(self):
15+
if self.log_file is not None:
16+
self.log_file.close()
17+
18+
def detail(self, message: str):
19+
"""Logs a detail message without printing it."""
20+
self._log(message, True)
21+
22+
def error(self, message: str):
23+
"""Logs an error."""
24+
message = f"Error: {message}"
25+
self._log(message)
26+
27+
def info(self, message: str):
28+
"""Logs an info message."""
29+
print(message)
30+
self._log(f"Info: {message}", True)
31+
32+
def warning(self, message: str):
33+
"""Logs an warning."""
34+
message = f"Warning: {message}"
35+
self._log(message)
36+
37+
def _log(self, message: str, no_print:bool=False):
38+
if no_print is False:
39+
print(message)
40+
41+
if self.log_file is not None:
42+
self.log_file.write(f"{message}\n")
43+
44+
class VTEMarkdownFile:
45+
"""Markdown file."""
46+
47+
def __init__(self, content: str, depth: int, prefix: str, title: str) -> None:
48+
self.content: str = content
49+
self.depth: str = depth
50+
self.prefix: str = prefix
51+
self.title: str = title
52+
53+
def __repr__(self):
54+
return (
55+
f"<VTEMarkdownFile depth: {self.depth}, prefix: '{self.prefix}',"
56+
f" title: '{self.title}', content: '{self.content}'>"
57+
)
58+
59+
def __str__(self):
60+
return (
61+
f"<VTEMarkdownFile depth: {self.depth}, prefix: '{self.prefix}',"
62+
f" title: '{self.title}', content: '{self.content}'>"
63+
)
64+
65+
class VTEBookBuilder:
66+
"""A 'Markdown' to 'epub' and 'pdf' converter."""
67+
68+
def __init__(self, logger: VTLogger):
69+
self.log = logger
70+
71+
def build_pdf_book(self, language: str):
72+
"""Builds a pdf file"""
73+
74+
self.log.info("Building 'pdf'...")
75+
76+
subprocess.check_output(
77+
[
78+
'pandoc',
79+
'ebook.md',
80+
'-V', 'documentclass=report',
81+
'-t', 'latex',
82+
'-s',
83+
'--toc',
84+
'--listings',
85+
'-H', 'ebook/listings-setup.tex',
86+
'-o', 'ebook/Vulkan Tutorial ' + language + '.pdf',
87+
'--pdf-engine=xelatex'
88+
]
89+
)
90+
91+
def build_epub_book(self, language: str):
92+
"""Buids a epub file"""
93+
94+
self.log.info("Building 'epub'...")
95+
96+
subprocess.check_output(
97+
[
98+
'pandoc',
99+
'ebook.md',
100+
'--toc',
101+
'-o', 'ebook/Vulkan Tutorial ' + language + '.epub',
102+
'--epub-cover-image=ebook/cover.png'
103+
]
104+
)
105+
106+
def convert_svg_to_png(self, images_folder: str) -> list[pathlib.Path]:
107+
"""Converts *.svg images to *.png using Inkscape"""
108+
109+
self.log.info("Converting 'svg' images...")
110+
111+
pngs = list[pathlib.Path]()
112+
113+
for entry in pathlib.Path(images_folder).iterdir():
114+
if entry.suffix == ".svg":
115+
new_path = entry.with_suffix(".png")
116+
try:
117+
# subprocess.check_output(
118+
# [
119+
# 'inkscape',
120+
# '--export-filename=' + new_path.as_posix(),
121+
# new_path.parent.as_posix() + "/" + new_path.stem
122+
# ],
123+
# stderr=subprocess.STDOUT
124+
# )
125+
pngs.append(new_path)
126+
except FileNotFoundError as error:
127+
self.log.error(error)
128+
self.log.warning("Install 'Inkscape' (https://www.inkscape.org)!")
129+
130+
raise RuntimeError from error
131+
132+
return pngs
133+
134+
def generate_markdown_from_sources(self, language: str, output_filename: pathlib.Path):
135+
"""Processes the markdown sources and produces a joined file."""
136+
137+
self.log.info(
138+
f"Generating a temporary 'Markdown' file: '{output_filename}'" \
139+
f" for language '{language}'..."
140+
)
141+
142+
md_files = self._process_files_in_directory(language)
143+
md_files = sorted(md_files, key=lambda file: file.prefix)
144+
145+
temp_markdown: str = str()
146+
147+
for entry in md_files:
148+
# Add title.
149+
content: str = '# ' + entry.title + '\n\n' + entry.content
150+
151+
# Fix image links.
152+
content = re.sub(r'\/images\/', 'images/', content)
153+
content = re.sub(r'\.svg', '.png', content)
154+
155+
# Fix remaining relative links (e.g. code files).
156+
content = re.sub(r'\]\(\/', '](https://vulkan-tutorial.com/', content)
157+
158+
# Fix chapter references
159+
def repl(match):
160+
target = match.group(1)
161+
target = target.lower()
162+
target = re.sub('_', '-', target)
163+
target = target.split('/')[-1]
164+
165+
return '](#' + target + ')'
166+
167+
content = re.sub(r'\]\(!([^)]+)\)', repl, content)
168+
169+
temp_markdown += content + '\n\n'
170+
171+
log.info("Writing markdown file...")
172+
173+
with open(output_filename, "w", encoding="utf-8") as file:
174+
file.write(temp_markdown)
175+
176+
def _process_files_in_directory(
177+
self,
178+
directory_path: pathlib.Path,
179+
current_depth: int=int(0),
180+
parent_prefix: str=str(),
181+
markdown_files: list[VTEMarkdownFile]=None
182+
) -> list[VTEMarkdownFile]:
183+
"""Traverses the directory tree, processes `Markdown` files."""
184+
if markdown_files is None:
185+
markdown_files = list[VTEMarkdownFile]()
186+
187+
for entry in pathlib.Path(directory_path).iterdir():
188+
title_tokens = entry.stem.replace('_', ' ').split(" ")
189+
prefix = f"{parent_prefix}{title_tokens[0]}."
190+
191+
if entry.is_dir() is True:
192+
log.info(f"Processing directory: {entry}")
193+
194+
self._process_files_in_directory(entry, (current_depth + 1), prefix, markdown_files)
195+
else:
196+
log.info(f"Processing: {entry}")
197+
198+
title = ' '.join(title_tokens[1:])
199+
200+
with open(entry, 'r', encoding="utf-8") as file:
201+
content = file.read()
202+
markdown_files.append(VTEMarkdownFile(content, current_depth, prefix, title))
203+
204+
return markdown_files
205+
206+
207+
###############################################################################
208+
209+
210+
if __name__ == "__main__":
211+
212+
log = VTLogger("./build_ebook.log")
213+
eBookBuilder = VTEBookBuilder(log)
214+
215+
generated_pngs = eBookBuilder.convert_svg_to_png("./images")
216+
217+
LANGUAGES = [ "en", "fr" ]
218+
OUTPUT_FILENAME = pathlib.Path("./temp_ebook.md")
219+
220+
for lang in LANGUAGES:
221+
lang = f"./{lang}"
222+
223+
eBookBuilder.generate_markdown_from_sources(lang, OUTPUT_FILENAME)
224+
225+
# eBookBuilder.build_epub_book(lang)
226+
# eBookBuilder.build_pdf_book(lang)
227+
228+
# Clean up
229+
OUTPUT_FILENAME.unlink()
230+
231+
# Clean up temporary files
232+
for png_path in generated_pngs:
233+
try:
234+
png_path.unlink()
235+
except FileNotFoundError as fileError:
236+
log.error(fileError)

0 commit comments

Comments
 (0)