txt2ebook 0.1.157__tar.gz → 0.1.159__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. {txt2ebook-0.1.157/src/txt2ebook.egg-info → txt2ebook-0.1.159}/PKG-INFO +1 -1
  2. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/pyproject.toml +15 -1
  3. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/formats/base.py +107 -0
  4. txt2ebook-0.1.159/src/txt2ebook/formats/gmi.py +176 -0
  5. txt2ebook-0.1.159/src/txt2ebook/formats/md.py +168 -0
  6. txt2ebook-0.1.159/src/txt2ebook/formats/txt.py +219 -0
  7. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/formats/typ.py +48 -3
  8. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/subcommands/epub.py +12 -8
  9. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/subcommands/massage.py +29 -23
  10. {txt2ebook-0.1.157 → txt2ebook-0.1.159/src/txt2ebook.egg-info}/PKG-INFO +1 -1
  11. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook.egg-info/SOURCES.txt +1 -13
  12. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/tests/test_parser.py +5 -3
  13. txt2ebook-0.1.157/src/txt2ebook/formats/gmi.py +0 -177
  14. txt2ebook-0.1.157/src/txt2ebook/formats/md.py +0 -169
  15. txt2ebook-0.1.157/src/txt2ebook/formats/txt.py +0 -199
  16. txt2ebook-0.1.157/tests/test_header_number_flag.py +0 -46
  17. txt2ebook-0.1.157/tests/test_input_file_arg.py +0 -28
  18. txt2ebook-0.1.157/tests/test_language_option.py +0 -33
  19. txt2ebook-0.1.157/tests/test_output_file_arg.py +0 -34
  20. txt2ebook-0.1.157/tests/test_overwrite_flag.py +0 -23
  21. txt2ebook-0.1.157/tests/test_purge_flag.py +0 -49
  22. txt2ebook-0.1.157/tests/test_quiet_flag.py +0 -24
  23. txt2ebook-0.1.157/tests/test_sort_volume_and_chapter_flag.py +0 -44
  24. txt2ebook-0.1.157/tests/test_split_volume_and_chapter_flag.py +0 -51
  25. txt2ebook-0.1.157/tests/test_test_parsing_flag.py +0 -28
  26. txt2ebook-0.1.157/tests/test_verbose_flag.py +0 -92
  27. txt2ebook-0.1.157/tests/test_volume_page_flag.py +0 -23
  28. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/LICENSE.md +0 -0
  29. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/README.md +0 -0
  30. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/setup.cfg +0 -0
  31. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/__init__.py +0 -0
  32. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/__main__.py +0 -0
  33. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/cli.py +0 -0
  34. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/exceptions.py +0 -0
  35. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/formats/__init__.py +0 -0
  36. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/formats/epub.py +0 -0
  37. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/formats/pdf.py +0 -0
  38. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/formats/templates/__init__.py +0 -0
  39. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/formats/templates/epub/__init__.py +0 -0
  40. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/formats/tex.py +0 -0
  41. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/helpers/__init__.py +0 -0
  42. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/languages/__init__.py +0 -0
  43. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/languages/en.py +0 -0
  44. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/languages/zh_cn.py +0 -0
  45. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/languages/zh_tw.py +0 -0
  46. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/models/__init__.py +0 -0
  47. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/models/book.py +0 -0
  48. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/models/chapter.py +0 -0
  49. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/models/volume.py +0 -0
  50. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/parser.py +0 -0
  51. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/subcommands/__init__.py +0 -0
  52. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/subcommands/env.py +0 -0
  53. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/subcommands/gmi.py +0 -0
  54. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/subcommands/md.py +0 -0
  55. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/subcommands/parse.py +0 -0
  56. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/subcommands/pdf.py +0 -0
  57. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/subcommands/tex.py +0 -0
  58. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/subcommands/typ.py +0 -0
  59. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/tokenizer.py +0 -0
  60. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook/zh_utils.py +0 -0
  61. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook.egg-info/dependency_links.txt +0 -0
  62. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook.egg-info/entry_points.txt +0 -0
  63. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook.egg-info/requires.txt +0 -0
  64. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/src/txt2ebook.egg-info/top_level.txt +0 -0
  65. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/tests/test_tokenizer.py +0 -0
  66. {txt2ebook-0.1.157 → txt2ebook-0.1.159}/tests/test_txt2ebook.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: txt2ebook
3
- Version: 0.1.157
3
+ Version: 0.1.159
4
4
  Summary: CLI tool to convert txt file to ebook format
5
5
  Author-email: Kian-Meng Ang <kianmeng@cpan.org>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "txt2ebook"
3
- version = "0.1.157"
3
+ version = "0.1.159"
4
4
  description = "CLI tool to convert txt file to ebook format"
5
5
  authors = [{ name = "Kian-Meng Ang", email = "kianmeng@cpan.org" }]
6
6
  requires-python = "~=3.9"
@@ -89,6 +89,20 @@ build-backend = "setuptools.build_meta"
89
89
  # verify through: uv run ruff check --show-settings
90
90
  [tool.ruff]
91
91
  line-length = 79
92
+ target-version = "py313"
93
+ exclude = [
94
+ "docs/",
95
+ "docs/source/conf.py",
96
+ ]
97
+
98
+ [tool.ruff.lint]
99
+ extend-select = [
100
+ "E",
101
+ "W",
102
+ ]
103
+
104
+ [tool.ruff.lint.pydocstyle]
105
+ convention = "google"
92
106
 
93
107
  [tool.setuptools.packages.find]
94
108
  where = ["src"]
@@ -134,6 +134,113 @@ class BaseWriter(ABC):
134
134
  file.parent, self.config.output_folder, lower_underscore(file.stem)
135
135
  ).with_suffix(extension)
136
136
 
137
+ def _get_toc_content_for_split(self) -> str:
138
+ raise NotImplementedError
139
+
140
+ def _get_volume_chapter_content_for_split(
141
+ self, volume: Volume, chapter: Chapter
142
+ ) -> str:
143
+ raise NotImplementedError
144
+
145
+ def _get_chapter_content_for_split(self, chapter: Chapter) -> str:
146
+ raise NotImplementedError
147
+
148
+ def _get_file_extension_for_split(self) -> str:
149
+ raise NotImplementedError
150
+
151
+ def _export_multiple_files(self) -> None:
152
+ logger.info("Split multiple files")
153
+
154
+ extension = self._get_file_extension_for_split()
155
+ txt_filename = Path(self.config.input_file.name)
156
+
157
+ export_filename = self._get_metadata_filename_for_split(
158
+ txt_filename, extension
159
+ )
160
+ export_filename.parent.mkdir(parents=True, exist_ok=True)
161
+ logger.info("Creating %s", export_filename)
162
+ with open(export_filename, "w", encoding="utf8") as file:
163
+ file.write(self._to_metadata_txt())
164
+
165
+ sc_seq = 1
166
+ if self.config.with_toc:
167
+ export_filename = self._get_toc_filename_for_split(
168
+ txt_filename, extension
169
+ )
170
+ export_filename.parent.mkdir(parents=True, exist_ok=True)
171
+ logger.info("Creating %s", export_filename)
172
+ with open(export_filename, "w", encoding="utf8") as file:
173
+ file.write(self._get_toc_content_for_split())
174
+
175
+ sc_seq = 2
176
+
177
+ for section in self.book.toc:
178
+ section_seq = str(sc_seq).rjust(2, "0")
179
+
180
+ ct_seq = 0
181
+ if isinstance(section, Volume):
182
+ for chapter in section.chapters:
183
+ chapter_seq = str(ct_seq).rjust(2, "0")
184
+ export_filename = (
185
+ self._get_volume_chapter_filename_for_split(
186
+ txt_filename,
187
+ section_seq,
188
+ chapter_seq,
189
+ section,
190
+ chapter,
191
+ extension,
192
+ )
193
+ )
194
+ export_filename.parent.mkdir(parents=True, exist_ok=True)
195
+ logger.info("Creating %s", export_filename)
196
+ with open(export_filename, "w", encoding="utf8") as file:
197
+ file.write(
198
+ self._get_volume_chapter_content_for_split(
199
+ section, chapter
200
+ )
201
+ )
202
+ ct_seq = ct_seq + 1
203
+ if isinstance(section, Chapter):
204
+ export_filename = self._get_chapter_filename_for_split(
205
+ txt_filename, section_seq, section, extension
206
+ )
207
+ export_filename.parent.mkdir(parents=True, exist_ok=True)
208
+ logger.info("Creating %s", export_filename)
209
+ with open(export_filename, "w", encoding="utf8") as file:
210
+ file.write(self._get_chapter_content_for_split(section))
211
+
212
+ sc_seq = sc_seq + 1
213
+
214
+ def _get_metadata_filename_for_split(
215
+ self, txt_filename: Path, extension: str
216
+ ) -> Path:
217
+ raise NotImplementedError
218
+
219
+ def _get_toc_filename_for_split(
220
+ self, txt_filename: Path, extension: str
221
+ ) -> Path:
222
+ raise NotImplementedError
223
+
224
+ def _get_volume_chapter_filename_for_split(
225
+ self,
226
+ txt_filename: Path,
227
+ section_seq: str,
228
+ chapter_seq: str,
229
+ volume: Volume,
230
+ chapter: Chapter,
231
+ extension: str,
232
+ ) -> Path:
233
+ raise NotImplementedError
234
+
235
+ def _get_chapter_filename_for_split(
236
+ self,
237
+ txt_filename: Path,
238
+ section_seq: str,
239
+ chapter: Chapter,
240
+ extension: str,
241
+ ) -> Path:
242
+ raise NotImplementedError
243
+
137
244
  def _to_metadata_txt(self) -> str:
138
245
  metadata = [
139
246
  self._("title:") + self.book.title,
@@ -0,0 +1,176 @@
1
+ # Copyright (c) 2021,2022,2023,2024,2025 Kian-Meng Ang
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU Affero General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU Affero General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ """Convert and back source text file into text as well."""
17
+
18
+ import logging
19
+ from pathlib import Path
20
+ from typing import List
21
+
22
+ from txt2ebook.formats.base import BaseWriter
23
+ from txt2ebook.helpers import lower_underscore
24
+ from txt2ebook.models import Chapter, Volume
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ class GmiWriter(BaseWriter):
30
+ """Module for writing ebook in GemText (gmi) format."""
31
+
32
+ def write(self) -> None:
33
+ """Generate GemText files."""
34
+ if self.config.split_volume_and_chapter:
35
+ self._export_multiple_files()
36
+ else:
37
+ output_filename = self._output_filename(".gmi")
38
+ output_filename.parent.mkdir(parents=True, exist_ok=True)
39
+
40
+ with open(output_filename, "w", encoding="utf8") as file:
41
+ logger.info(
42
+ "Generate Gemini file: %s", output_filename.resolve()
43
+ )
44
+ file.write(self._to_gmi())
45
+
46
+ if self.config.open:
47
+ self._open_file(output_filename)
48
+
49
+ def _get_toc_content_for_split(self) -> str:
50
+ return self._to_toc("*", "# ")
51
+
52
+ def _get_volume_chapter_content_for_split(
53
+ self, volume: Volume, chapter: Chapter
54
+ ) -> str:
55
+ return self._to_volume_chapter_txt(volume, chapter)
56
+
57
+ def _get_chapter_content_for_split(self, chapter: Chapter) -> str:
58
+ return self._to_chapter_txt(chapter)
59
+
60
+ def _get_file_extension_for_split(self) -> str:
61
+ return ".gmi"
62
+
63
+ def _get_metadata_filename_for_split(
64
+ self, txt_filename: Path, extension: str
65
+ ) -> Path:
66
+ return Path(
67
+ txt_filename.resolve().parent.joinpath(
68
+ self.config.output_folder,
69
+ lower_underscore(
70
+ f"00_{txt_filename.stem}_" + self._("metadata") + extension
71
+ ),
72
+ )
73
+ )
74
+
75
+ def _get_toc_filename_for_split(
76
+ self, txt_filename: Path, extension: str
77
+ ) -> Path:
78
+ return Path(
79
+ txt_filename.resolve().parent.joinpath(
80
+ self.config.output_folder,
81
+ lower_underscore(
82
+ f"01_{txt_filename.stem}_" + self._("toc") + extension
83
+ ),
84
+ )
85
+ )
86
+
87
+ def _get_volume_chapter_filename_for_split(
88
+ self,
89
+ txt_filename: Path,
90
+ section_seq: str,
91
+ chapter_seq: str,
92
+ volume: Volume,
93
+ chapter: Chapter,
94
+ extension: str,
95
+ ) -> Path:
96
+ return Path(
97
+ txt_filename.resolve().parent.joinpath(
98
+ self.config.output_folder,
99
+ lower_underscore(
100
+ (
101
+ f"{section_seq}"
102
+ f"_{chapter_seq}"
103
+ f"_{txt_filename.stem}"
104
+ f"_{volume.title}"
105
+ f"_{chapter.title}"
106
+ f"{extension}"
107
+ )
108
+ ),
109
+ )
110
+ )
111
+
112
+ def _get_chapter_filename_for_split(
113
+ self,
114
+ txt_filename: Path,
115
+ section_seq: str,
116
+ chapter: Chapter,
117
+ extension: str,
118
+ ) -> Path:
119
+ return Path(
120
+ txt_filename.resolve().parent.joinpath(
121
+ self.config.output_folder,
122
+ lower_underscore(
123
+ (
124
+ f"{section_seq}_{txt_filename.stem}_{chapter.title}{extension}"
125
+ )
126
+ ),
127
+ )
128
+ )
129
+
130
+ def _to_gmi(self) -> str:
131
+ toc = self._to_toc("*", "# ") if self.config.with_toc else ""
132
+ return self._to_metadata_txt() + toc + self._to_body_txt()
133
+
134
+ def _to_body_txt(self) -> str:
135
+ content = []
136
+ for section in self.book.toc:
137
+ if isinstance(section, Volume):
138
+ content.append(self._to_volume_txt(section))
139
+ if isinstance(section, Chapter):
140
+ content.append(self._to_chapter_txt(section))
141
+
142
+ return f"{self.config.paragraph_separator}".join(content)
143
+
144
+ def _to_volume_txt(self, volume) -> str:
145
+ return (
146
+ f"# {volume.title}"
147
+ + self.config.paragraph_separator
148
+ + self.config.paragraph_separator.join(
149
+ [
150
+ self._to_chapter_txt(chapter, True)
151
+ for chapter in volume.chapters
152
+ ]
153
+ )
154
+ )
155
+
156
+ def _to_chapter_txt(self, chapter, part_of_volume=False) -> str:
157
+ header = "##" if part_of_volume else "#"
158
+ return (
159
+ f"{header} {chapter.title}"
160
+ + self.config.paragraph_separator
161
+ + self.config.paragraph_separator.join(
162
+ self._remove_newline(chapter.paragraphs)
163
+ )
164
+ )
165
+
166
+ def _to_volume_chapter_txt(self, volume, chapter) -> str:
167
+ return (
168
+ f"# {volume.title} {chapter.title}"
169
+ + self.config.paragraph_separator
170
+ + self.config.paragraph_separator.join(
171
+ self._remove_newline(chapter.paragraphs)
172
+ )
173
+ )
174
+
175
+ def _remove_newline(self, paragraphs) -> List:
176
+ return list(map(lambda p: p.replace("\n", ""), paragraphs))
@@ -0,0 +1,168 @@
1
+ # Copyright (c) 2021,2022,2023,2024,2025 Kian-Meng Ang
2
+ #
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU Affero General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU Affero General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Affero General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ """Convert and back source text file into text as well."""
17
+
18
+ import logging
19
+ from pathlib import Path
20
+
21
+ from txt2ebook.formats.base import BaseWriter
22
+ from txt2ebook.helpers import lower_underscore
23
+ from txt2ebook.models import Chapter, Volume
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ class MdWriter(BaseWriter):
29
+ """Module for writing ebook in Markdown (md) format."""
30
+
31
+ def write(self) -> None:
32
+ """Generate Markdown files."""
33
+ if self.config.split_volume_and_chapter:
34
+ self._export_multiple_files()
35
+ else:
36
+ output_filename = self._output_filename(".md")
37
+ output_filename.parent.mkdir(parents=True, exist_ok=True)
38
+
39
+ with open(output_filename, "w", encoding="utf8") as file:
40
+ logger.info(
41
+ "Generate Markdown file: %s", output_filename.resolve()
42
+ )
43
+ file.write(self._to_md())
44
+
45
+ if self.config.open:
46
+ self._open_file(output_filename)
47
+
48
+ def _get_toc_content_for_split(self) -> str:
49
+ return self._to_toc("-", "# ")
50
+
51
+ def _get_volume_chapter_content_for_split(
52
+ self, volume: Volume, chapter: Chapter
53
+ ) -> str:
54
+ return self._to_volume_chapter_txt(volume, chapter)
55
+
56
+ def _get_chapter_content_for_split(self, chapter: Chapter) -> str:
57
+ return self._to_chapter_txt(chapter)
58
+
59
+ def _get_file_extension_for_split(self) -> str:
60
+ return ".md"
61
+
62
+ def _get_metadata_filename_for_split(
63
+ self, txt_filename: Path, extension: str
64
+ ) -> Path:
65
+ return Path(
66
+ txt_filename.resolve().parent.joinpath(
67
+ self.config.output_folder,
68
+ lower_underscore(
69
+ f"00_{txt_filename.stem}_" + self._("metadata") + extension
70
+ ),
71
+ )
72
+ )
73
+
74
+ def _get_toc_filename_for_split(
75
+ self, txt_filename: Path, extension: str
76
+ ) -> Path:
77
+ return Path(
78
+ txt_filename.resolve().parent.joinpath(
79
+ self.config.output_folder,
80
+ lower_underscore(
81
+ f"01_{txt_filename.stem}_" + self._("toc") + extension
82
+ ),
83
+ )
84
+ )
85
+
86
+ def _get_volume_chapter_filename_for_split(
87
+ self,
88
+ txt_filename: Path,
89
+ section_seq: str,
90
+ chapter_seq: str,
91
+ volume: Volume,
92
+ chapter: Chapter,
93
+ extension: str,
94
+ ) -> Path:
95
+ return Path(
96
+ txt_filename.resolve().parent.joinpath(
97
+ self.config.output_folder,
98
+ lower_underscore(
99
+ (
100
+ f"{section_seq}"
101
+ f"_{chapter_seq}"
102
+ f"_{txt_filename.stem}"
103
+ f"_{volume.title}"
104
+ f"_{chapter.title}"
105
+ f"{extension}"
106
+ )
107
+ ),
108
+ )
109
+ )
110
+
111
+ def _get_chapter_filename_for_split(
112
+ self,
113
+ txt_filename: Path,
114
+ section_seq: str,
115
+ chapter: Chapter,
116
+ extension: str,
117
+ ) -> Path:
118
+ return Path(
119
+ txt_filename.resolve().parent.joinpath(
120
+ self.config.output_folder,
121
+ lower_underscore(
122
+ (
123
+ f"{section_seq}_{txt_filename.stem}_{chapter.title}{extension}"
124
+ )
125
+ ),
126
+ )
127
+ )
128
+
129
+ def _to_md(self) -> str:
130
+ toc = self._to_toc("-", "# ") if self.config.with_toc else ""
131
+ return self._to_metadata_txt() + toc + self._to_body_txt()
132
+
133
+ def _to_body_txt(self) -> str:
134
+ content = []
135
+ for section in self.book.toc:
136
+ if isinstance(section, Volume):
137
+ content.append(self._to_volume_txt(section))
138
+ if isinstance(section, Chapter):
139
+ content.append(self._to_chapter_txt(section))
140
+
141
+ return f"{self.config.paragraph_separator}".join(content)
142
+
143
+ def _to_volume_txt(self, volume) -> str:
144
+ return (
145
+ f"# {volume.title}"
146
+ + self.config.paragraph_separator
147
+ + self.config.paragraph_separator.join(
148
+ [
149
+ self._to_chapter_txt(chapter, True)
150
+ for chapter in volume.chapters
151
+ ]
152
+ )
153
+ )
154
+
155
+ def _to_chapter_txt(self, chapter, part_of_volume=False) -> str:
156
+ header = "##" if part_of_volume else "#"
157
+ return (
158
+ f"{header} {chapter.title}"
159
+ + self.config.paragraph_separator
160
+ + self.config.paragraph_separator.join(chapter.paragraphs)
161
+ )
162
+
163
+ def _to_volume_chapter_txt(self, volume, chapter) -> str:
164
+ return (
165
+ f"# {volume.title} {chapter.title}"
166
+ + self.config.paragraph_separator
167
+ + self.config.paragraph_separator.join(chapter.paragraphs)
168
+ )