txt2ebook 0.1.158__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.
- {txt2ebook-0.1.158/src/txt2ebook.egg-info → txt2ebook-0.1.159}/PKG-INFO +1 -1
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/pyproject.toml +15 -1
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/formats/base.py +26 -12
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/formats/gmi.py +17 -5
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/formats/md.py +17 -6
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/formats/txt.py +78 -28
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/formats/typ.py +11 -3
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/subcommands/epub.py +12 -8
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/subcommands/massage.py +29 -23
- {txt2ebook-0.1.158 → txt2ebook-0.1.159/src/txt2ebook.egg-info}/PKG-INFO +1 -1
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook.egg-info/SOURCES.txt +1 -13
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/tests/test_parser.py +2 -3
- txt2ebook-0.1.158/tests/test_header_number_flag.py +0 -46
- txt2ebook-0.1.158/tests/test_input_file_arg.py +0 -28
- txt2ebook-0.1.158/tests/test_language_option.py +0 -33
- txt2ebook-0.1.158/tests/test_output_file_arg.py +0 -34
- txt2ebook-0.1.158/tests/test_overwrite_flag.py +0 -23
- txt2ebook-0.1.158/tests/test_purge_flag.py +0 -49
- txt2ebook-0.1.158/tests/test_quiet_flag.py +0 -24
- txt2ebook-0.1.158/tests/test_sort_volume_and_chapter_flag.py +0 -48
- txt2ebook-0.1.158/tests/test_split_volume_and_chapter_flag.py +0 -51
- txt2ebook-0.1.158/tests/test_test_parsing_flag.py +0 -28
- txt2ebook-0.1.158/tests/test_verbose_flag.py +0 -92
- txt2ebook-0.1.158/tests/test_volume_page_flag.py +0 -23
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/LICENSE.md +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/README.md +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/setup.cfg +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/__init__.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/__main__.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/cli.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/exceptions.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/formats/__init__.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/formats/epub.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/formats/pdf.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/formats/templates/__init__.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/formats/templates/epub/__init__.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/formats/tex.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/helpers/__init__.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/languages/__init__.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/languages/en.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/languages/zh_cn.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/languages/zh_tw.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/models/__init__.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/models/book.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/models/chapter.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/models/volume.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/parser.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/subcommands/__init__.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/subcommands/env.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/subcommands/gmi.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/subcommands/md.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/subcommands/parse.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/subcommands/pdf.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/subcommands/tex.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/subcommands/typ.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/tokenizer.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook/zh_utils.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook.egg-info/dependency_links.txt +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook.egg-info/entry_points.txt +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook.egg-info/requires.txt +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/src/txt2ebook.egg-info/top_level.txt +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/tests/test_tokenizer.py +0 -0
- {txt2ebook-0.1.158 → txt2ebook-0.1.159}/tests/test_txt2ebook.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "txt2ebook"
|
3
|
-
version = "0.1.
|
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"]
|
@@ -24,7 +24,6 @@ import shutil
|
|
24
24
|
import subprocess
|
25
25
|
import sys
|
26
26
|
from abc import ABC, abstractmethod
|
27
|
-
from datetime import datetime as dt
|
28
27
|
from importlib import import_module
|
29
28
|
from pathlib import Path
|
30
29
|
|
@@ -155,7 +154,9 @@ class BaseWriter(ABC):
|
|
155
154
|
extension = self._get_file_extension_for_split()
|
156
155
|
txt_filename = Path(self.config.input_file.name)
|
157
156
|
|
158
|
-
export_filename = self._get_metadata_filename_for_split(
|
157
|
+
export_filename = self._get_metadata_filename_for_split(
|
158
|
+
txt_filename, extension
|
159
|
+
)
|
159
160
|
export_filename.parent.mkdir(parents=True, exist_ok=True)
|
160
161
|
logger.info("Creating %s", export_filename)
|
161
162
|
with open(export_filename, "w", encoding="utf8") as file:
|
@@ -163,7 +164,9 @@ class BaseWriter(ABC):
|
|
163
164
|
|
164
165
|
sc_seq = 1
|
165
166
|
if self.config.with_toc:
|
166
|
-
export_filename = self._get_toc_filename_for_split(
|
167
|
+
export_filename = self._get_toc_filename_for_split(
|
168
|
+
txt_filename, extension
|
169
|
+
)
|
167
170
|
export_filename.parent.mkdir(parents=True, exist_ok=True)
|
168
171
|
logger.info("Creating %s", export_filename)
|
169
172
|
with open(export_filename, "w", encoding="utf8") as file:
|
@@ -178,8 +181,15 @@ class BaseWriter(ABC):
|
|
178
181
|
if isinstance(section, Volume):
|
179
182
|
for chapter in section.chapters:
|
180
183
|
chapter_seq = str(ct_seq).rjust(2, "0")
|
181
|
-
export_filename =
|
182
|
-
|
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
|
+
)
|
183
193
|
)
|
184
194
|
export_filename.parent.mkdir(parents=True, exist_ok=True)
|
185
195
|
logger.info("Creating %s", export_filename)
|
@@ -201,15 +211,16 @@ class BaseWriter(ABC):
|
|
201
211
|
|
202
212
|
sc_seq = sc_seq + 1
|
203
213
|
|
204
|
-
|
205
|
-
|
214
|
+
def _get_metadata_filename_for_split(
|
215
|
+
self, txt_filename: Path, extension: str
|
216
|
+
) -> Path:
|
206
217
|
raise NotImplementedError
|
207
218
|
|
208
|
-
|
209
|
-
|
219
|
+
def _get_toc_filename_for_split(
|
220
|
+
self, txt_filename: Path, extension: str
|
221
|
+
) -> Path:
|
210
222
|
raise NotImplementedError
|
211
223
|
|
212
|
-
@abstractmethod
|
213
224
|
def _get_volume_chapter_filename_for_split(
|
214
225
|
self,
|
215
226
|
txt_filename: Path,
|
@@ -221,9 +232,12 @@ class BaseWriter(ABC):
|
|
221
232
|
) -> Path:
|
222
233
|
raise NotImplementedError
|
223
234
|
|
224
|
-
@abstractmethod
|
225
235
|
def _get_chapter_filename_for_split(
|
226
|
-
self,
|
236
|
+
self,
|
237
|
+
txt_filename: Path,
|
238
|
+
section_seq: str,
|
239
|
+
chapter: Chapter,
|
240
|
+
extension: str,
|
227
241
|
) -> Path:
|
228
242
|
raise NotImplementedError
|
229
243
|
|
@@ -38,7 +38,9 @@ class GmiWriter(BaseWriter):
|
|
38
38
|
output_filename.parent.mkdir(parents=True, exist_ok=True)
|
39
39
|
|
40
40
|
with open(output_filename, "w", encoding="utf8") as file:
|
41
|
-
logger.info(
|
41
|
+
logger.info(
|
42
|
+
"Generate Gemini file: %s", output_filename.resolve()
|
43
|
+
)
|
42
44
|
file.write(self._to_gmi())
|
43
45
|
|
44
46
|
if self.config.open:
|
@@ -58,7 +60,9 @@ class GmiWriter(BaseWriter):
|
|
58
60
|
def _get_file_extension_for_split(self) -> str:
|
59
61
|
return ".gmi"
|
60
62
|
|
61
|
-
def _get_metadata_filename_for_split(
|
63
|
+
def _get_metadata_filename_for_split(
|
64
|
+
self, txt_filename: Path, extension: str
|
65
|
+
) -> Path:
|
62
66
|
return Path(
|
63
67
|
txt_filename.resolve().parent.joinpath(
|
64
68
|
self.config.output_folder,
|
@@ -68,7 +72,9 @@ class GmiWriter(BaseWriter):
|
|
68
72
|
)
|
69
73
|
)
|
70
74
|
|
71
|
-
def _get_toc_filename_for_split(
|
75
|
+
def _get_toc_filename_for_split(
|
76
|
+
self, txt_filename: Path, extension: str
|
77
|
+
) -> Path:
|
72
78
|
return Path(
|
73
79
|
txt_filename.resolve().parent.joinpath(
|
74
80
|
self.config.output_folder,
|
@@ -104,13 +110,19 @@ class GmiWriter(BaseWriter):
|
|
104
110
|
)
|
105
111
|
|
106
112
|
def _get_chapter_filename_for_split(
|
107
|
-
self,
|
113
|
+
self,
|
114
|
+
txt_filename: Path,
|
115
|
+
section_seq: str,
|
116
|
+
chapter: Chapter,
|
117
|
+
extension: str,
|
108
118
|
) -> Path:
|
109
119
|
return Path(
|
110
120
|
txt_filename.resolve().parent.joinpath(
|
111
121
|
self.config.output_folder,
|
112
122
|
lower_underscore(
|
113
|
-
(
|
123
|
+
(
|
124
|
+
f"{section_seq}_{txt_filename.stem}_{chapter.title}{extension}"
|
125
|
+
)
|
114
126
|
),
|
115
127
|
)
|
116
128
|
)
|
@@ -17,7 +17,6 @@
|
|
17
17
|
|
18
18
|
import logging
|
19
19
|
from pathlib import Path
|
20
|
-
from pathlib import Path
|
21
20
|
|
22
21
|
from txt2ebook.formats.base import BaseWriter
|
23
22
|
from txt2ebook.helpers import lower_underscore
|
@@ -38,7 +37,9 @@ class MdWriter(BaseWriter):
|
|
38
37
|
output_filename.parent.mkdir(parents=True, exist_ok=True)
|
39
38
|
|
40
39
|
with open(output_filename, "w", encoding="utf8") as file:
|
41
|
-
logger.info(
|
40
|
+
logger.info(
|
41
|
+
"Generate Markdown file: %s", output_filename.resolve()
|
42
|
+
)
|
42
43
|
file.write(self._to_md())
|
43
44
|
|
44
45
|
if self.config.open:
|
@@ -58,7 +59,9 @@ class MdWriter(BaseWriter):
|
|
58
59
|
def _get_file_extension_for_split(self) -> str:
|
59
60
|
return ".md"
|
60
61
|
|
61
|
-
def _get_metadata_filename_for_split(
|
62
|
+
def _get_metadata_filename_for_split(
|
63
|
+
self, txt_filename: Path, extension: str
|
64
|
+
) -> Path:
|
62
65
|
return Path(
|
63
66
|
txt_filename.resolve().parent.joinpath(
|
64
67
|
self.config.output_folder,
|
@@ -68,7 +71,9 @@ class MdWriter(BaseWriter):
|
|
68
71
|
)
|
69
72
|
)
|
70
73
|
|
71
|
-
def _get_toc_filename_for_split(
|
74
|
+
def _get_toc_filename_for_split(
|
75
|
+
self, txt_filename: Path, extension: str
|
76
|
+
) -> Path:
|
72
77
|
return Path(
|
73
78
|
txt_filename.resolve().parent.joinpath(
|
74
79
|
self.config.output_folder,
|
@@ -104,13 +109,19 @@ class MdWriter(BaseWriter):
|
|
104
109
|
)
|
105
110
|
|
106
111
|
def _get_chapter_filename_for_split(
|
107
|
-
self,
|
112
|
+
self,
|
113
|
+
txt_filename: Path,
|
114
|
+
section_seq: str,
|
115
|
+
chapter: Chapter,
|
116
|
+
extension: str,
|
108
117
|
) -> Path:
|
109
118
|
return Path(
|
110
119
|
txt_filename.resolve().parent.joinpath(
|
111
120
|
self.config.output_folder,
|
112
121
|
lower_underscore(
|
113
|
-
(
|
122
|
+
(
|
123
|
+
f"{section_seq}_{txt_filename.stem}_{chapter.title}{extension}"
|
124
|
+
)
|
114
125
|
),
|
115
126
|
)
|
116
127
|
)
|
@@ -54,11 +54,15 @@ class TxtWriter(BaseWriter):
|
|
54
54
|
.parent.joinpath(
|
55
55
|
lower_underscore(
|
56
56
|
Path(self.config.input_file.name).stem
|
57
|
-
+ "_"
|
57
|
+
+ "_"
|
58
|
+
+ ymd_hms
|
59
|
+
+ ".txt"
|
58
60
|
)
|
59
61
|
)
|
60
62
|
)
|
61
|
-
logger.info(
|
63
|
+
logger.info(
|
64
|
+
"Backup source text file: %s", backup_filename.resolve()
|
65
|
+
)
|
62
66
|
shutil.copyfile(output_filename, backup_filename)
|
63
67
|
|
64
68
|
with open(output_filename, "w", encoding="utf8") as file:
|
@@ -68,27 +72,23 @@ class TxtWriter(BaseWriter):
|
|
68
72
|
if self.config.open:
|
69
73
|
self._open_file(output_filename)
|
70
74
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
def _get_metadata_filename_for_split(self, txt_filename: Path, extension: str) -> Path:
|
75
|
+
def _get_metadata_filename_for_split(
|
76
|
+
self, txt_filename: Path, extension: str
|
77
|
+
) -> Path:
|
76
78
|
return Path(
|
77
79
|
txt_filename.resolve().parent.joinpath(
|
78
80
|
self.config.output_folder,
|
79
|
-
|
80
|
-
f"00_{txt_filename.stem}_" + self._("metadata") + extension
|
81
|
-
),
|
81
|
+
f"00_{txt_filename.stem}_" + self._("metadata") + extension,
|
82
82
|
)
|
83
83
|
)
|
84
84
|
|
85
|
-
def _get_toc_filename_for_split(
|
85
|
+
def _get_toc_filename_for_split(
|
86
|
+
self, txt_filename: Path, extension: str
|
87
|
+
) -> Path:
|
86
88
|
return Path(
|
87
89
|
txt_filename.resolve().parent.joinpath(
|
88
90
|
self.config.output_folder,
|
89
|
-
|
90
|
-
f"01_{txt_filename.stem}_" + self._("toc") + extension
|
91
|
-
),
|
91
|
+
f"01_{txt_filename.stem}_" + self._("toc") + extension,
|
92
92
|
)
|
93
93
|
)
|
94
94
|
|
@@ -104,36 +104,86 @@ class TxtWriter(BaseWriter):
|
|
104
104
|
return Path(
|
105
105
|
txt_filename.resolve().parent.joinpath(
|
106
106
|
self.config.output_folder,
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
f"{extension}"
|
115
|
-
)
|
107
|
+
(
|
108
|
+
f"{section_seq}"
|
109
|
+
f"_{chapter_seq}"
|
110
|
+
f"_{txt_filename.stem}"
|
111
|
+
f"_{volume.title}"
|
112
|
+
f"_{chapter.title}"
|
113
|
+
f"{extension}"
|
116
114
|
),
|
117
115
|
)
|
118
116
|
)
|
119
117
|
|
120
118
|
def _get_chapter_filename_for_split(
|
121
|
-
self,
|
119
|
+
self,
|
120
|
+
txt_filename: Path,
|
121
|
+
section_seq: str,
|
122
|
+
chapter: Chapter,
|
123
|
+
extension: str,
|
122
124
|
) -> Path:
|
123
125
|
return Path(
|
124
126
|
txt_filename.resolve().parent.joinpath(
|
125
127
|
self.config.output_folder,
|
126
|
-
|
127
|
-
|
128
|
+
(
|
129
|
+
f"{section_seq}_{txt_filename.stem}_{chapter.title}{extension}"
|
128
130
|
),
|
129
131
|
)
|
130
132
|
)
|
131
133
|
|
134
|
+
def _export_multiple_files(self) -> None:
|
135
|
+
"""Export multiple files based on volume and chapter."""
|
136
|
+
txt_filename = Path(self.config.input_file.name)
|
137
|
+
txt_filename.parent.joinpath(self.config.output_folder).mkdir(
|
138
|
+
parents=True, exist_ok=True
|
139
|
+
)
|
140
|
+
|
141
|
+
# 1. Write metadata file
|
142
|
+
metadata_filename = self._get_metadata_filename_for_split(
|
143
|
+
txt_filename, ".txt"
|
144
|
+
)
|
145
|
+
with open(metadata_filename, "w", encoding="utf8") as file:
|
146
|
+
logger.info("Creating %s", metadata_filename.resolve())
|
147
|
+
file.write(self._to_metadata_txt())
|
148
|
+
|
149
|
+
# 2. Write volume/chapter files
|
150
|
+
section_seq = 0
|
151
|
+
chapter_seq = 0
|
152
|
+
for section in self.book.toc:
|
153
|
+
if isinstance(section, Volume):
|
154
|
+
section_seq += 1
|
155
|
+
chapter_seq = 0
|
156
|
+
for chapter in section.chapters:
|
157
|
+
chapter_seq += 1
|
158
|
+
output_filename = self._get_volume_chapter_filename_for_split(
|
159
|
+
txt_filename,
|
160
|
+
str(section_seq).rjust(2, "0"),
|
161
|
+
str(chapter_seq).rjust(2, "0"),
|
162
|
+
section,
|
163
|
+
chapter,
|
164
|
+
".txt",
|
165
|
+
)
|
166
|
+
with open(output_filename, "w", encoding="utf8") as file:
|
167
|
+
logger.info("Creating %s", output_filename.resolve())
|
168
|
+
file.write(self._to_volume_chapter_txt(section, chapter))
|
169
|
+
elif isinstance(section, Chapter):
|
170
|
+
section_seq += 1
|
171
|
+
output_filename = self._get_chapter_filename_for_split(
|
172
|
+
txt_filename,
|
173
|
+
str(section_seq).rjust(2, "0"),
|
174
|
+
section,
|
175
|
+
".txt",
|
176
|
+
)
|
177
|
+
with open(output_filename, "w", encoding="utf8") as file:
|
178
|
+
logger.info("Creating %s", output_filename.resolve())
|
179
|
+
file.write(self._to_chapter_txt(section))
|
180
|
+
|
181
|
+
if self.config.open:
|
182
|
+
self._open_file(metadata_filename)
|
183
|
+
|
132
184
|
def _to_txt(self) -> str:
|
133
185
|
toc = self._to_toc("-") if self.config.with_toc else ""
|
134
186
|
return self._to_metadata_txt() + toc + self._to_body_txt()
|
135
|
-
|
136
|
-
def _to_body_txt(self) -> str:
|
137
187
|
content = []
|
138
188
|
for section in self.book.toc:
|
139
189
|
if isinstance(section, Volume):
|
@@ -235,10 +235,14 @@ class TypWriter(BaseWriter):
|
|
235
235
|
"""
|
236
236
|
)
|
237
237
|
|
238
|
-
def _get_metadata_filename_for_split(
|
238
|
+
def _get_metadata_filename_for_split(
|
239
|
+
self, txt_filename: Path, extension: str
|
240
|
+
) -> Path:
|
239
241
|
return Path(self._output_folder(), "metadata").with_suffix(extension)
|
240
242
|
|
241
|
-
def _get_toc_filename_for_split(
|
243
|
+
def _get_toc_filename_for_split(
|
244
|
+
self, txt_filename: Path, extension: str
|
245
|
+
) -> Path:
|
242
246
|
return Path(self._output_folder(), "toc").with_suffix(extension)
|
243
247
|
|
244
248
|
def _get_volume_chapter_filename_for_split(
|
@@ -254,7 +258,11 @@ class TypWriter(BaseWriter):
|
|
254
258
|
return Path(self._output_folder(), filename).with_suffix(extension)
|
255
259
|
|
256
260
|
def _get_chapter_filename_for_split(
|
257
|
-
self,
|
261
|
+
self,
|
262
|
+
txt_filename: Path,
|
263
|
+
section_seq: str,
|
264
|
+
chapter: Chapter,
|
265
|
+
extension: str,
|
258
266
|
) -> Path:
|
259
267
|
filename = f"{section_seq}-{lower_underscore(chapter.title)}"
|
260
268
|
return Path(self._output_folder(), filename).with_suffix(extension)
|
@@ -38,10 +38,10 @@ def build_subparser(subparsers) -> None:
|
|
38
38
|
|
39
39
|
epub_parser.add_argument(
|
40
40
|
"input_file",
|
41
|
-
nargs=
|
41
|
+
nargs=1,
|
42
42
|
type=argparse.FileType("rb"),
|
43
|
-
help="source text
|
44
|
-
metavar="
|
43
|
+
help="source text filename",
|
44
|
+
metavar="TXT_FILENAME",
|
45
45
|
)
|
46
46
|
|
47
47
|
epub_parser.add_argument(
|
@@ -125,12 +125,12 @@ def run(args: argparse.Namespace) -> None:
|
|
125
125
|
"""
|
126
126
|
input_sources = []
|
127
127
|
|
128
|
-
if
|
129
|
-
#
|
130
|
-
input_sources.append(sys.stdin)
|
131
|
-
elif args.input_file:
|
132
|
-
# multiple file(s)
|
128
|
+
if args.input_file:
|
129
|
+
# File path(s) were explicitly provided on the command line
|
133
130
|
input_sources.extend(args.input_file)
|
131
|
+
elif not sys.stdin.isatty():
|
132
|
+
# No file path provided, check for piped input
|
133
|
+
input_sources.append(sys.stdin)
|
134
134
|
else:
|
135
135
|
logger.error("No input files provided.")
|
136
136
|
sys.exit(1)
|
@@ -147,6 +147,10 @@ def run(args: argparse.Namespace) -> None:
|
|
147
147
|
current_file_args = argparse.Namespace(**vars(args))
|
148
148
|
current_file_args.input_file = current_input_stream
|
149
149
|
|
150
|
+
logger.debug(
|
151
|
+
"Create separate volume page: %s", current_file_args.volume_page
|
152
|
+
)
|
153
|
+
|
150
154
|
# if an explicit output_file was provided, it must apply to the first
|
151
155
|
# input
|
152
156
|
if i > 0 and args.output_file:
|
@@ -28,7 +28,9 @@ from bs4 import UnicodeDammit
|
|
28
28
|
|
29
29
|
from txt2ebook import detect_and_expect_language
|
30
30
|
from txt2ebook.exceptions import EmptyFileError
|
31
|
+
from txt2ebook.formats.txt import TxtWriter
|
31
32
|
from txt2ebook.models.book import Book
|
33
|
+
from txt2ebook.parser import Parser
|
32
34
|
from txt2ebook.zh_utils import zh_halfwidth_to_fullwidth, zh_words_to_numbers
|
33
35
|
|
34
36
|
logger = logging.getLogger(__name__)
|
@@ -216,26 +218,27 @@ def run(args: argparse.Namespace) -> None:
|
|
216
218
|
None
|
217
219
|
"""
|
218
220
|
massaged_txt = massage_txt(args)
|
219
|
-
if args.overwrite:
|
220
|
-
_overwrite_file(args, massaged_txt)
|
221
|
-
else:
|
222
|
-
_new_file(args, massaged_txt)
|
223
221
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
222
|
+
if args.split_volume_and_chapter:
|
223
|
+
args.language = detect_and_expect_language(massaged_txt, args.language)
|
224
|
+
config_lang = args.language.replace("-", "_")
|
225
|
+
langconf = import_module(f"txt2ebook.languages.{config_lang}")
|
226
|
+
args.with_toc = False
|
227
|
+
parser = Parser(massaged_txt, args, langconf)
|
228
|
+
book = parser.parse()
|
230
229
|
|
231
|
-
|
232
|
-
|
230
|
+
if args.debug:
|
231
|
+
book.debug(args.verbose)
|
233
232
|
|
234
|
-
|
235
|
-
|
233
|
+
if args.header_number:
|
234
|
+
book = header_number(args, book)
|
236
235
|
|
237
|
-
|
238
|
-
|
236
|
+
writer = TxtWriter(book, args)
|
237
|
+
writer.write()
|
238
|
+
elif args.overwrite:
|
239
|
+
_overwrite_file(args, massaged_txt)
|
240
|
+
else:
|
241
|
+
_new_file(args, massaged_txt)
|
239
242
|
|
240
243
|
|
241
244
|
def _overwrite_file(args, massaged_txt) -> None:
|
@@ -356,11 +359,13 @@ def massage_txt(args: argparse.Namespace) -> str:
|
|
356
359
|
if args.re_delete_line:
|
357
360
|
body = do_delete_line_regex(args, body)
|
358
361
|
|
359
|
-
if args.single_newline:
|
360
|
-
body = do_single_newline(args, body)
|
361
|
-
|
362
362
|
if args.width:
|
363
363
|
body = do_wrapping(args, body)
|
364
|
+
elif args.single_newline:
|
365
|
+
body = do_single_newline(args, body)
|
366
|
+
else:
|
367
|
+
# Apply paragraph separation and line unwrapping by default
|
368
|
+
body = _unwrap_content(args, body)
|
364
369
|
|
365
370
|
return f"{metadata}{body}"
|
366
371
|
|
@@ -376,6 +381,7 @@ def to_unix_newline(content: str) -> str:
|
|
376
381
|
"""
|
377
382
|
return content.replace("\r\n", "\n").replace("\r", "\n")
|
378
383
|
|
384
|
+
|
379
385
|
def do_reindent_paragraph(args, content: str) -> str:
|
380
386
|
"""Reindent each paragraph.
|
381
387
|
|
@@ -385,16 +391,16 @@ def do_reindent_paragraph(args, content: str) -> str:
|
|
385
391
|
Returns:
|
386
392
|
str: The formatted book content.
|
387
393
|
"""
|
388
|
-
paragraphs = re.split(r
|
394
|
+
paragraphs = re.split(r"\n\s*\n+", content)
|
389
395
|
reindented_paragraphs = []
|
390
396
|
for paragraph in paragraphs:
|
391
|
-
lines = paragraph.split(
|
397
|
+
lines = paragraph.split("\n")
|
392
398
|
reindented_lines = []
|
393
399
|
for line in lines:
|
394
400
|
stripped_line = line.strip()
|
395
401
|
reindented_lines.append(stripped_line)
|
396
402
|
|
397
|
-
reindented_paragraph =
|
403
|
+
reindented_paragraph = "\n".join(reindented_lines)
|
398
404
|
reindented_paragraph = " " + reindented_paragraph
|
399
405
|
reindented_paragraphs.append(reindented_paragraph)
|
400
406
|
|
@@ -542,7 +548,7 @@ def _unwrap_content(args: argparse.Namespace, content: str) -> str:
|
|
542
548
|
Returns:
|
543
549
|
str: The formatted book content.
|
544
550
|
"""
|
545
|
-
paragraphs =
|
551
|
+
paragraphs = re.split(r"\n\s*\n+", content)
|
546
552
|
processed_paragraphs = []
|
547
553
|
for paragraph in paragraphs:
|
548
554
|
single_line_paragraph = " ".join(paragraph.splitlines())
|
@@ -44,18 +44,6 @@ src/txt2ebook/subcommands/parse.py
|
|
44
44
|
src/txt2ebook/subcommands/pdf.py
|
45
45
|
src/txt2ebook/subcommands/tex.py
|
46
46
|
src/txt2ebook/subcommands/typ.py
|
47
|
-
tests/test_header_number_flag.py
|
48
|
-
tests/test_input_file_arg.py
|
49
|
-
tests/test_language_option.py
|
50
|
-
tests/test_output_file_arg.py
|
51
|
-
tests/test_overwrite_flag.py
|
52
47
|
tests/test_parser.py
|
53
|
-
tests/test_purge_flag.py
|
54
|
-
tests/test_quiet_flag.py
|
55
|
-
tests/test_sort_volume_and_chapter_flag.py
|
56
|
-
tests/test_split_volume_and_chapter_flag.py
|
57
|
-
tests/test_test_parsing_flag.py
|
58
48
|
tests/test_tokenizer.py
|
59
|
-
tests/test_txt2ebook.py
|
60
|
-
tests/test_verbose_flag.py
|
61
|
-
tests/test_volume_page_flag.py
|
49
|
+
tests/test_txt2ebook.py
|
@@ -15,7 +15,6 @@
|
|
15
15
|
import argparse
|
16
16
|
import pytest
|
17
17
|
from importlib import import_module
|
18
|
-
from argparse import Namespace
|
19
18
|
|
20
19
|
from txt2ebook.parser import Parser
|
21
20
|
|
@@ -67,7 +66,7 @@ def test_parsing_two_newlines_as_paragraph_separator(config):
|
|
67
66
|
|
68
67
|
剑号巨阙,珠称夜光,果珍李柰,菜重芥姜。(paragraph 1)
|
69
68
|
"""
|
70
|
-
langconf = import_module(
|
69
|
+
langconf = import_module("txt2ebook.languages.en")
|
71
70
|
parser = Parser(content, config, langconf)
|
72
71
|
[chapter1, chapter2] = parser.parse().toc
|
73
72
|
assert len(chapter1.paragraphs) == 2
|
@@ -89,7 +88,7 @@ def test_parsing_one_newline_as_paragraph_separator(config):
|
|
89
88
|
剑号巨阙,珠称夜光,果珍李柰,菜重芥姜。(paragraph 1)
|
90
89
|
"""
|
91
90
|
config.paragraph_separator = "\n"
|
92
|
-
langconf = import_module(
|
91
|
+
langconf = import_module("txt2ebook.languages.en")
|
93
92
|
parser = Parser(content, config, langconf)
|
94
93
|
book = parser.parse()
|
95
94
|
[chapter1, chapter2] = book.toc
|
@@ -1,46 +0,0 @@
|
|
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
|
-
from textwrap import dedent
|
17
|
-
|
18
|
-
import pytest
|
19
|
-
|
20
|
-
|
21
|
-
@pytest.mark.parametrize("option", ["-hn", "--header-number"])
|
22
|
-
def test_header_to_numbers_conversion(cli_runner, infile, option):
|
23
|
-
txtfile = infile("sample_long_headers.txt")
|
24
|
-
|
25
|
-
output = cli_runner("-d", option, txtfile)
|
26
|
-
expected_output1 = dedent(
|
27
|
-
"""\
|
28
|
-
DEBUG: Convert header to numbers: 第一卷 -> 第1卷
|
29
|
-
DEBUG: Convert header to numbers: 第一章 月既不解饮 -> 第1章 月既不解饮
|
30
|
-
DEBUG: Convert header to numbers: 第二章 影徒随我身 -> 第2章 影徒随我身
|
31
|
-
"""
|
32
|
-
)
|
33
|
-
assert expected_output1 in output.stdout
|
34
|
-
|
35
|
-
output = cli_runner("-d", "--header-number", txtfile)
|
36
|
-
expected_output2 = dedent(
|
37
|
-
"""\
|
38
|
-
DEBUG: Convert header to numbers: 第二卷 -> 第2卷
|
39
|
-
DEBUG: Convert header to numbers: 第三章 暂伴月将影 -> 第3章 暂伴月将影
|
40
|
-
DEBUG: Convert header to numbers: 第二百章 暂伴月将影 -> 第200章 暂伴月将
|
41
|
-
DEBUG: Convert header to numbers: 第九百九十九章 暂伴 -> 第999章 暂伴月将
|
42
|
-
DEBUG: Convert header to numbers: 第九千百九十九章 暂 -> 第9919章 暂伴月
|
43
|
-
"""
|
44
|
-
)
|
45
|
-
|
46
|
-
assert expected_output2 in output.stdout
|
@@ -1,28 +0,0 @@
|
|
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
|
-
|
17
|
-
def test_nonexistent_filename(cli_runner):
|
18
|
-
output = cli_runner("parse", "nonexistent.txt")
|
19
|
-
assert (
|
20
|
-
"[Errno 2] No such file or directory: 'nonexistent.txt'"
|
21
|
-
in output.stderr
|
22
|
-
)
|
23
|
-
|
24
|
-
|
25
|
-
def test_empty_file_content(cli_runner, infile):
|
26
|
-
txt = infile("empty_file.txt")
|
27
|
-
output = cli_runner("parse", str(txt))
|
28
|
-
assert f"error: Empty file content in {str(txt)}" in output.stdout
|
@@ -1,33 +0,0 @@
|
|
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
|
-
import pytest
|
17
|
-
|
18
|
-
|
19
|
-
def test_auto_detect_language(cli_runner, infile):
|
20
|
-
txtfile = infile("sample.txt")
|
21
|
-
output = cli_runner(txtfile)
|
22
|
-
assert "Detect language: zh-cn" in output.stdout
|
23
|
-
|
24
|
-
|
25
|
-
@pytest.mark.parametrize("option", ["-l", "--language"])
|
26
|
-
def test_warning_log_for_mismatch_configured_and_detect_language(
|
27
|
-
cli_runner, infile, option
|
28
|
-
):
|
29
|
-
txtfile = infile("missing_chapters.txt")
|
30
|
-
output = cli_runner(txtfile, option, "en")
|
31
|
-
assert "Config language: en" in output.stdout
|
32
|
-
assert "Detect language: ko" in output.stdout
|
33
|
-
assert "Config (en) and detect (ko) language mismatch" in output.stdout
|
@@ -1,34 +0,0 @@
|
|
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
|
-
|
17
|
-
def test_output_basename_default_to_input_basename(
|
18
|
-
tte, infile, outfile
|
19
|
-
):
|
20
|
-
txt = infile("sample.txt")
|
21
|
-
epub = outfile("output/sample.epub")
|
22
|
-
output = tte("epub", str(txt))
|
23
|
-
|
24
|
-
assert epub.exists()
|
25
|
-
assert f"Generate EPUB file: {str(epub)}" in output.stdout
|
26
|
-
|
27
|
-
|
28
|
-
def test_set_output_filename(tte, infile, outfile):
|
29
|
-
txt = infile("sample.txt")
|
30
|
-
epub = outfile("foobar.epub")
|
31
|
-
output = tte("epub", str(txt), str(epub))
|
32
|
-
|
33
|
-
assert epub.exists()
|
34
|
-
assert f"Generate EPUB file: {str(epub)}" in output.stdout
|
@@ -1,23 +0,0 @@
|
|
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
|
-
import pytest
|
17
|
-
|
18
|
-
|
19
|
-
@pytest.mark.parametrize("option", ["-ow", "--overwrite"])
|
20
|
-
def test_overwrite_source_txt_file(cli_runner, infile, option):
|
21
|
-
txtfile = infile("sample.txt")
|
22
|
-
output = cli_runner(txtfile, option)
|
23
|
-
assert "Backup txt file" not in output.stdout
|
@@ -1,49 +0,0 @@
|
|
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
|
-
import pytest
|
17
|
-
|
18
|
-
|
19
|
-
@pytest.mark.parametrize("option", ["-p", "--purge"])
|
20
|
-
def test_debug_log(cli_runner, infile, option):
|
21
|
-
txt = infile("sample.txt")
|
22
|
-
output = cli_runner(txt, "-d", option)
|
23
|
-
assert "purge=True" in output.stdout
|
24
|
-
|
25
|
-
|
26
|
-
def test_purge_output_folder_if_exists(cli_runner, infile, tmpdir):
|
27
|
-
csv = infile("sample.txt")
|
28
|
-
opf = f"{tmpdir}/output"
|
29
|
-
_ = cli_runner(csv, "-d", "-of", opf)
|
30
|
-
output = cli_runner(csv, "-d", "-of", opf, "-p", "-y")
|
31
|
-
assert f"Purge output folder: {opf}" in output.stdout
|
32
|
-
|
33
|
-
|
34
|
-
def test_prompt_when_purging_output_folder(cli_runner, infile, tmpdir):
|
35
|
-
csv = infile("sample.txt")
|
36
|
-
opf = f"{tmpdir}/output"
|
37
|
-
_ = cli_runner(csv, "-d", "-of", opf)
|
38
|
-
|
39
|
-
output = cli_runner(csv, "-d", "-of", opf, "-p", stdin=b"y")
|
40
|
-
assert (
|
41
|
-
f"Are you sure to purge output folder: {opf}? [y/N] " in output.stdout
|
42
|
-
)
|
43
|
-
|
44
|
-
|
45
|
-
def test_no_purge_output_folder_if_not_exists(cli_runner, infile):
|
46
|
-
csv = infile("sample.txt")
|
47
|
-
output_folder = csv.resolve().parent.joinpath("output")
|
48
|
-
output = cli_runner(csv, "-d", "-p")
|
49
|
-
assert f"Purge output folder: {output_folder}" not in output.stdout
|
@@ -1,24 +0,0 @@
|
|
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
|
-
import pytest
|
17
|
-
|
18
|
-
|
19
|
-
@pytest.mark.parametrize("option", ["-q", "--quiet"])
|
20
|
-
def test_show_chapter_headers_only(cli_runner, infile, option):
|
21
|
-
txtfile = infile("sample.txt")
|
22
|
-
output = cli_runner(txtfile, option)
|
23
|
-
|
24
|
-
assert output.stdout == ""
|
@@ -1,48 +0,0 @@
|
|
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
|
-
from textwrap import dedent
|
17
|
-
from importlib import import_module
|
18
|
-
|
19
|
-
import pytest
|
20
|
-
from txt2ebook.parser import Parser
|
21
|
-
|
22
|
-
|
23
|
-
@pytest.mark.parametrize("option", ["-ss", "--sort-volume-and-chapter"])
|
24
|
-
def test_sort_logs(cli_runner, infile, option, config):
|
25
|
-
txtfile = infile("sample_unsorted_headers.txt")
|
26
|
-
|
27
|
-
output = cli_runner("-d", "-l", "zh-cn", "-vv", "parse", option, txtfile)
|
28
|
-
|
29
|
-
combined_output = output.stdout + output.stderr
|
30
|
-
filtered_output_lines = [
|
31
|
-
line
|
32
|
-
for line in combined_output.splitlines()
|
33
|
-
if line.startswith("DEBUG: Chapter") or line.startswith("DEBUG: Volume")
|
34
|
-
]
|
35
|
-
filtered_output = "\n".join(filtered_output_lines)
|
36
|
-
|
37
|
-
expected_output = dedent(
|
38
|
-
"""\
|
39
|
-
DEBUG: Chapter(title='序章', paragraphs='1')
|
40
|
-
DEBUG: Volume(title='第一卷', chapters='1')
|
41
|
-
DEBUG: Chapter(title='第3章 暂伴月将影', paragraphs='1')
|
42
|
-
DEBUG: Volume(title='第二卷', chapters='2')
|
43
|
-
DEBUG: Chapter(title='第1章 月既不解饮', paragraphs='1')
|
44
|
-
DEBUG: Chapter(title='第2章 影徒随我身', paragraphs='2')
|
45
|
-
"""
|
46
|
-
).strip()
|
47
|
-
|
48
|
-
|
@@ -1,51 +0,0 @@
|
|
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
|
-
from pathlib import Path
|
17
|
-
|
18
|
-
import pytest
|
19
|
-
|
20
|
-
|
21
|
-
@pytest.mark.parametrize("option", ["-sp", "--split-volume-and-chapter"])
|
22
|
-
def test_logging_of_split_text_files(cli_runner, infile, tmpdir, option):
|
23
|
-
txtfile = infile("sample_all_headers.txt")
|
24
|
-
output = cli_runner("-f", "txt", "-d", option, txtfile)
|
25
|
-
|
26
|
-
logs = [
|
27
|
-
f"INFO: Creating {tmpdir}/output/00_sample_all_headers_元数据.txt",
|
28
|
-
f"INFO: Creating {tmpdir}/output/01_00_sample_all_headers_第一卷_第1章_月既不解饮.txt",
|
29
|
-
f"INFO: Creating {tmpdir}/output/01_01_sample_all_headers_第一卷_第2章_影徒随我身.txt",
|
30
|
-
f"INFO: Creating {tmpdir}/output/02_00_sample_all_headers_第二卷_第3章_暂伴月将影.txt",
|
31
|
-
]
|
32
|
-
for log in logs:
|
33
|
-
assert log in output.stdout
|
34
|
-
|
35
|
-
|
36
|
-
@pytest.mark.parametrize("option", ["-sp", "--split-volume-and-chapter"])
|
37
|
-
def test_split_filenames_by_sequence_volume_chapter(
|
38
|
-
cli_runner, infile, tmpdir, option
|
39
|
-
):
|
40
|
-
txtfile = infile("sample_all_headers.txt")
|
41
|
-
cli_runner("-f", "txt", "-d", option, txtfile)
|
42
|
-
|
43
|
-
files = [
|
44
|
-
f"{tmpdir}/output/00_sample_all_headers_元数据.txt",
|
45
|
-
f"{tmpdir}/output/01_00_sample_all_headers_第一卷_第1章_月既不解饮.txt",
|
46
|
-
f"{tmpdir}/output/01_01_sample_all_headers_第一卷_第2章_影徒随我身.txt",
|
47
|
-
f"{tmpdir}/output/02_00_sample_all_headers_第二卷_第3章_暂伴月将影.txt",
|
48
|
-
]
|
49
|
-
|
50
|
-
for file in files:
|
51
|
-
assert Path(file).exists()
|
@@ -1,28 +0,0 @@
|
|
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
|
-
import pytest
|
17
|
-
|
18
|
-
|
19
|
-
@pytest.mark.parametrize("option", ["-tp", "--test-parsing"])
|
20
|
-
def test_show_chapter_headers_only(cli_runner, infile, option):
|
21
|
-
txtfile = infile("sample.txt")
|
22
|
-
output = cli_runner(txtfile, "-d", option)
|
23
|
-
|
24
|
-
assert "EPUB CSS template" not in output.stdout
|
25
|
-
assert "Generate EPUB file" not in output.stdout
|
26
|
-
assert "Backup txt file" not in output.stdout
|
27
|
-
assert "Overwrite txt file" not in output.stdout
|
28
|
-
assert "Chapter(title='第一千九百二十四章" in output.stdout
|
@@ -1,92 +0,0 @@
|
|
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/>.,line-too-long
|
15
|
-
|
16
|
-
from textwrap import dedent
|
17
|
-
|
18
|
-
|
19
|
-
def test_default_verbosity(cli_runner, infile):
|
20
|
-
txtfile = infile("sample_all_headers.txt")
|
21
|
-
|
22
|
-
output = cli_runner("-d", txtfile)
|
23
|
-
assert (
|
24
|
-
dedent(
|
25
|
-
"""\
|
26
|
-
DEBUG: Volume(title='第一卷', chapters='2')
|
27
|
-
DEBUG: Chapter(title='第1章 月既不解饮', paragraphs='2')
|
28
|
-
DEBUG: Chapter(title='第2章 影徒随我身', paragraphs='1')
|
29
|
-
DEBUG: Volume(title='第二卷', chapters='1')
|
30
|
-
DEBUG: Chapter(title='第3章 暂伴月将影', paragraphs='1')
|
31
|
-
"""
|
32
|
-
)
|
33
|
-
not in output.stdout
|
34
|
-
)
|
35
|
-
|
36
|
-
output = cli_runner("-d", "-v", txtfile)
|
37
|
-
assert (
|
38
|
-
dedent(
|
39
|
-
"""\
|
40
|
-
DEBUG: Volume(title='第一卷', chapters='2')
|
41
|
-
DEBUG: Chapter(title='第1章 月既不解饮', paragraphs='2')
|
42
|
-
DEBUG: Chapter(title='第2章 影徒随我身', paragraphs='1')
|
43
|
-
DEBUG: Volume(title='第二卷', chapters='1')
|
44
|
-
DEBUG: Chapter(title='第3章 暂伴月将影', paragraphs='1')
|
45
|
-
"""
|
46
|
-
)
|
47
|
-
not in output.stdout
|
48
|
-
)
|
49
|
-
|
50
|
-
|
51
|
-
def test_second_level_verbosity(cli_runner, infile):
|
52
|
-
txtfile = infile("sample_all_headers.txt")
|
53
|
-
|
54
|
-
output = cli_runner("-d", "-vv", txtfile)
|
55
|
-
assert (
|
56
|
-
dedent(
|
57
|
-
"""\
|
58
|
-
DEBUG: Volume(title='第一卷', chapters='2')
|
59
|
-
DEBUG: Chapter(title='第1章 月既不解饮', paragraphs='2')
|
60
|
-
DEBUG: Chapter(title='第2章 影徒随我身', paragraphs='1')
|
61
|
-
DEBUG: Volume(title='第二卷', chapters='1')
|
62
|
-
DEBUG: Chapter(title='第3章 暂伴月将影', paragraphs='1')
|
63
|
-
"""
|
64
|
-
)
|
65
|
-
in output.stdout
|
66
|
-
)
|
67
|
-
|
68
|
-
|
69
|
-
def test_third_level_verbosity(cli_runner, infile):
|
70
|
-
txtfile = infile("sample_all_headers.txt")
|
71
|
-
|
72
|
-
output = cli_runner("-d", "-vvv", txtfile)
|
73
|
-
assert (
|
74
|
-
dedent(
|
75
|
-
"""\
|
76
|
-
DEBUG: Token stats: Counter({'PARAGRAPH': 5, 'CHAPTER': 3, 'VOLUME': 2, 'TITLE': 1, 'AUTHOR': 1})
|
77
|
-
DEBUG: Token(type='TITLE', line_no='0', value='月下独酌·其一')
|
78
|
-
DEBUG: Token(type='AUTHOR', line_no='6', value='李白')
|
79
|
-
DEBUG: Token(type='PARAGRAPH', line_no='6', value='李白')
|
80
|
-
DEBUG: Token(type='VOLUME', line_no='8', value='第一卷')
|
81
|
-
DEBUG: Token(type='CHAPTER', line_no='10', value='第1章 月既不解饮')
|
82
|
-
DEBUG: Token(type='PARAGRAPH', line_no='12', value='花间一壶酒,独酌无相')
|
83
|
-
DEBUG: Token(type='PARAGRAPH', line_no='29', value='我歌月徘徊,我舞影零')
|
84
|
-
DEBUG: Token(type='CHAPTER', line_no='19', value='第2章 影徒随我身')
|
85
|
-
DEBUG: Token(type='PARAGRAPH', line_no='29', value='我歌月徘徊,我舞影零')
|
86
|
-
DEBUG: Token(type='VOLUME', line_no='25', value='第二卷')
|
87
|
-
DEBUG: Token(type='CHAPTER', line_no='27', value='第3章 暂伴月将影')
|
88
|
-
DEBUG: Token(type='PARAGRAPH', line_no='29', value='我歌月徘徊,我舞影零')
|
89
|
-
"""
|
90
|
-
)
|
91
|
-
in output.stdout
|
92
|
-
)
|
@@ -1,23 +0,0 @@
|
|
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
|
-
import pytest
|
17
|
-
|
18
|
-
|
19
|
-
@pytest.mark.parametrize("option", ["-vp", "--volume-page"])
|
20
|
-
def test_logging(cli_runner, infile, option):
|
21
|
-
txtfile = infile("sample_all_headers.txt")
|
22
|
-
output = cli_runner("-d", option, txtfile)
|
23
|
-
assert "Create separate volume page: " in output.stdout
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|