txt2ebook 0.1.139__tar.gz → 0.1.141__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 (111) hide show
  1. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/.pre-commit-config.yaml +0 -1
  2. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/CHANGELOG.md +14 -2
  3. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/PKG-INFO +4 -4
  4. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/README.md +3 -3
  5. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/noxfile.py +9 -5
  6. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/pyproject.toml +19 -19
  7. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/__init__.py +4 -3
  8. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/cli.py +4 -4
  9. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/formats/__init__.py +0 -3
  10. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/helpers/__init__.py +1 -4
  11. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/models/book.py +5 -5
  12. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/parser.py +4 -3
  13. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/subcommands/epub.py +5 -5
  14. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/subcommands/gmi.py +1 -2
  15. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/subcommands/md.py +1 -2
  16. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/subcommands/pdf.py +2 -3
  17. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/subcommands/typ.py +2 -3
  18. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/tokenizer.py +0 -1
  19. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/fixtures/sample.txt +10 -0
  20. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_subcommand_epub.py +0 -1
  21. txt2ebook-0.1.141/tests/test_subcommand_massage.py +115 -0
  22. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/uv.lock +95 -88
  23. txt2ebook-0.1.139/tests/test_subcommand_massage.py +0 -36
  24. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/.coveragerc +0 -0
  25. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/.gitignore +0 -0
  26. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/.python-version +0 -0
  27. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/CONTRIBUTING.md +0 -0
  28. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/LICENSE.md +0 -0
  29. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/docs/Makefile +0 -0
  30. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/docs/make.bat +0 -0
  31. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/docs/source/CHANGELOG.md +0 -0
  32. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/docs/source/CONTRIBUTING.md +0 -0
  33. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/docs/source/LICENSE.md +0 -0
  34. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/docs/source/README.md +0 -0
  35. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/docs/source/_static/logo.png +0 -0
  36. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/docs/source/conf.py +0 -0
  37. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/docs/source/index.rst +0 -0
  38. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/docs/source/txt2ebook.formats.rst +0 -0
  39. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/docs/source/txt2ebook.helpers.rst +0 -0
  40. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/docs/source/txt2ebook.models.rst +0 -0
  41. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/docs/source/txt2ebook.parsers.rst +0 -0
  42. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/docs/source/txt2ebook.rst +0 -0
  43. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/__main__.py +0 -0
  44. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/exceptions.py +0 -0
  45. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/formats/base.py +0 -0
  46. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/formats/epub.py +0 -0
  47. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/formats/gmi.py +0 -0
  48. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/formats/md.py +0 -0
  49. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/formats/pdf.py +0 -0
  50. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/formats/templates/__init__.py +0 -0
  51. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/formats/templates/epub/__init__.py +0 -0
  52. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/formats/templates/epub/clean.css +0 -0
  53. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/formats/templates/epub/condense.css +0 -0
  54. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/formats/templates/epub/noindent.css +0 -0
  55. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/formats/tex.py +0 -0
  56. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/formats/txt.py +0 -0
  57. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/formats/typ.py +0 -0
  58. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/languages/__init__.py +0 -0
  59. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/languages/en.py +0 -0
  60. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/languages/zh_cn.py +0 -0
  61. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/languages/zh_tw.py +0 -0
  62. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/locales/en/LC_MESSAGES/txt2ebook.mo +0 -0
  63. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/locales/en/LC_MESSAGES/txt2ebook.po +0 -0
  64. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/locales/txt2ebook.pot +0 -0
  65. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/locales/zh-cn/LC_MESSAGES/txt2ebook.mo +0 -0
  66. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/locales/zh-cn/LC_MESSAGES/txt2ebook.po +0 -0
  67. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/locales/zh-tw/LC_MESSAGES/txt2ebook.mo +0 -0
  68. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/locales/zh-tw/LC_MESSAGES/txt2ebook.po +0 -0
  69. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/models/__init__.py +0 -0
  70. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/models/chapter.py +0 -0
  71. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/models/volume.py +0 -0
  72. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/subcommands/__init__.py +0 -0
  73. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/subcommands/env.py +0 -0
  74. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/subcommands/massage.py +0 -0
  75. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/subcommands/parse.py +0 -0
  76. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/subcommands/tex.py +1 -1
  77. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/src/txt2ebook/zh_utils.py +0 -0
  78. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/__init__.py +0 -0
  79. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/conftest.py +0 -0
  80. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/fixtures/empty_file.txt +0 -0
  81. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/fixtures/missing_chapters.txt +0 -0
  82. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/fixtures/sample_all_headers.txt +0 -0
  83. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/fixtures/sample_long_headers.txt +0 -0
  84. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/fixtures/sample_remove_wrapping.txt +0 -0
  85. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/fixtures/sample_unsorted_headers.txt +0 -0
  86. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/fixtures/sample_with_issues.txt +0 -0
  87. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/fixtures/sample_with_metadata.txt +0 -0
  88. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_epub_writer.py +0 -0
  89. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_filename_format_flag.py +0 -0
  90. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_format_option.py +0 -0
  91. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_header_number_flag.py +0 -0
  92. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_input_file_arg.py +0 -0
  93. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_language_option.py +0 -0
  94. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_output_file_arg.py +0 -0
  95. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_overwrite_flag.py +0 -0
  96. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_parser.py +0 -0
  97. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_purge_flag.py +0 -0
  98. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_quiet_flag.py +0 -0
  99. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_raise_warnings.py +0 -0
  100. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_sort_volume_and_chapter_flag.py +0 -0
  101. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_split_volume_and_chapter_flag.py +0 -0
  102. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_subcommand_env.py +0 -0
  103. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_test_parsing_flag.py +0 -0
  104. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_tokenizer.py +0 -0
  105. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_translator_option.py +0 -0
  106. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_txt2ebook.py +0 -0
  107. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_verbose_flag.py +0 -0
  108. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_volume_page_flag.py +0 -0
  109. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_zh_utils_zh_halfwidth_to_fullwidth.py +0 -0
  110. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_zh_utils_zh_numeric.py +0 -0
  111. {txt2ebook-0.1.139 → txt2ebook-0.1.141}/tests/test_zh_utils_zh_words_to_numbers.py +0 -0
@@ -86,7 +86,6 @@ repos:
86
86
  - flake8-pytest-style
87
87
  - flake8-simplify
88
88
  args:
89
- - --docstring-convention=google
90
89
  - --show-source
91
90
  - --max-line-length=79
92
91
  - --docstring-convention=google
@@ -7,6 +7,19 @@ and this project adheres to [0-based versioning](https://0ver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## v0.1.141 (2025-05-25)
11
+
12
+ - Bump and sort deps
13
+ - Switch `venv` backend to `uv` in `nox`
14
+
15
+ ## v0.1.140 (2025-05-18)
16
+
17
+ - Bump deps
18
+ - Bump local editable installation
19
+ - Remove duplicate `pre-commit` config item
20
+ - Remove unused `cjkwrap` import
21
+ - Remove unused field import
22
+
10
23
  ## v0.1.139 (2025-05-11)
11
24
 
12
25
  - Bump deps
@@ -189,13 +202,12 @@ and this project adheres to [0-based versioning](https://0ver.org/).
189
202
 
190
203
  - Bump deps and `pre-commit` hooks
191
204
  - Migrate `typ` and `pdf` format to subcommand
192
- - Migrate deprecated regex-* options test cases
205
+ - Migrate deprecated regex-\* options test cases
193
206
  - Refactor `massage` subcommand
194
207
  - Refactor language detection and validation
195
208
  - Remove Tokenizer from handling `fullwidth` flag
196
209
  - Set `deps` job in `nox` to Python 3.9
197
210
 
198
-
199
211
  ## v0.1.113 (2024-11-10)
200
212
 
201
213
  - Extract top keywords from txt file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: txt2ebook
3
- Version: 0.1.139
3
+ Version: 0.1.141
4
4
  Summary: CLI tool to convert txt file to ebook format
5
5
  Project-URL: Homepage, https://github.com/kianmeng/txt2ebook
6
6
  Project-URL: Repository, https://github.com/kianmeng/txt2ebook
@@ -108,12 +108,12 @@ positional arguments:
108
108
  typ
109
109
  generate ebook in Typst format
110
110
 
111
- options:
112
- -of, --output-folder OUTPUT_FOLDER
111
+ optional arguments:
112
+ -of OUTPUT_FOLDER, --output-folder OUTPUT_FOLDER
113
113
  set default output folder (default: 'output')
114
114
  -p, --purge
115
115
  remove converted ebooks specified by --output-folder option (default: 'False')
116
- -l, --language LANGUAGE
116
+ -l LANGUAGE, --language LANGUAGE
117
117
  language of the ebook (default: 'None')
118
118
  -rw, --raise-on-warning
119
119
  raise exception and stop parsing upon warning
@@ -65,12 +65,12 @@ positional arguments:
65
65
  typ
66
66
  generate ebook in Typst format
67
67
 
68
- options:
69
- -of, --output-folder OUTPUT_FOLDER
68
+ optional arguments:
69
+ -of OUTPUT_FOLDER, --output-folder OUTPUT_FOLDER
70
70
  set default output folder (default: 'output')
71
71
  -p, --purge
72
72
  remove converted ebooks specified by --output-folder option (default: 'False')
73
- -l, --language LANGUAGE
73
+ -l LANGUAGE, --language LANGUAGE
74
74
  language of the ebook (default: 'None')
75
75
  -rw, --raise-on-warning
76
76
  raise exception and stop parsing upon warning
@@ -16,10 +16,11 @@
16
16
  """Nox configuration."""
17
17
 
18
18
  import datetime
19
- import re
20
19
 
21
20
  import nox
22
21
 
22
+ nox.options.default_venv_backend = "uv"
23
+
23
24
 
24
25
  @nox.session(python="3.9")
25
26
  def deps(session: nox.Session) -> None:
@@ -188,9 +189,13 @@ def release(session: nox.Session) -> None:
188
189
  """Bump release."""
189
190
  _uv_install(session)
190
191
 
191
- before_version = session.run("uv", "version", "--short", silent=True).strip()
192
+ before_version = session.run(
193
+ "uv", "version", "--short", silent=True
194
+ ).strip()
192
195
  session.run("uv", "version", "--bump", "patch")
193
- after_version = session.run("uv", "version", "--short", silent=True).strip()
196
+ after_version = session.run(
197
+ "uv", "version", "--short", silent=True
198
+ ).strip()
194
199
 
195
200
  _search_and_replace(
196
201
  "src/txt2ebook/__init__.py", before_version, after_version
@@ -216,8 +221,7 @@ def release(session: nox.Session) -> None:
216
221
 
217
222
 
218
223
  def _uv_install(session: nox.Session) -> None:
219
- session.install("uv")
220
- session.run("uv", "sync")
224
+ session.run("uv", "sync", "--active")
221
225
 
222
226
 
223
227
  def _search_and_replace(file, search, replace) -> None:
@@ -1,16 +1,16 @@
1
1
  [project]
2
2
  name = "txt2ebook"
3
- version = "0.1.139"
3
+ version = "0.1.141"
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"
7
7
  readme = "README.md"
8
8
  license = "AGPL-3.0-or-later"
9
9
  keywords = [
10
- "txt",
10
+ "cjk",
11
11
  "ebook",
12
12
  "epub",
13
- "cjk",
13
+ "txt",
14
14
  ]
15
15
  classifiers = [
16
16
  "Development Status :: 4 - Beta",
@@ -35,16 +35,16 @@ dependencies = [
35
35
  "CJKwrap~=2.2",
36
36
  "EbookLib>=0.17.1,<0.18",
37
37
  "bs4>=0.0.1,<0.0.2",
38
+ "importlib-resources>=6.1.1,<7",
39
+ "jieba>=0.42.1,<0.43",
38
40
  "langdetect>=1.0.9,<2",
39
- "regex>=2021.11.10,<2022",
41
+ "lxml>=5.2.2,<6",
42
+ "pylatex>=1.4.2,<2",
40
43
  "pypandoc~=1.11",
41
- "typing-extensions>=4.5.0,<5",
44
+ "regex>=2021.11.10,<2022",
42
45
  "reportlab>=4.0.0,<5",
46
+ "typing-extensions>=4.5.0,<5",
43
47
  "typst>=0.13.0",
44
- "importlib-resources>=6.1.1,<7",
45
- "pylatex>=1.4.2,<2",
46
- "lxml>=5.2.2,<6",
47
- "jieba>=0.42.1,<0.43",
48
48
  ]
49
49
 
50
50
  [project.urls]
@@ -58,24 +58,24 @@ tte = "txt2ebook.cli:main"
58
58
  [dependency-groups]
59
59
  dev = [
60
60
  "babel>=2.12.1,<3",
61
- "scripttest~=1.3",
61
+ "bandit~=1.7.1",
62
62
  "flake8-simplify>=0.21.0,<0.22",
63
- "nox>=2024.4.15,<2025",
64
- "nox-poetry>=1.0.3,<2",
65
- "vulture~=2.11",
66
63
  "mypy>=1.10.0,<2",
67
- "bandit~=1.7.1",
64
+ "myst-parser>=3.0.1,<4",
65
+ "nox-poetry>=1.0.3,<2",
66
+ "nox>=2024.4.15,<2025",
68
67
  "pep8-naming>=0.13.3,<0.14",
68
+ "pre-commit>=2.20,<2.21",
69
69
  "pylint>=3.2.0,<4",
70
- "pytest>=8.2.0,<9",
71
70
  "pytest-cov>=5.0.0,<6",
72
71
  "pytest-randomly>=3.15.0,<4",
73
72
  "pytest-xdist>=3.6.1,<4",
74
- "pre-commit>=2.20,<2.21",
75
- "sphinx>=7.3.7,<8",
76
- "myst-parser>=3.0.1,<4",
77
- "sphinx-copybutton>=0.5.2,<0.6",
73
+ "pytest>=8.2.0,<9",
74
+ "scripttest~=1.3",
78
75
  "sphinx-autodoc-typehints>=2.2.2,<3",
76
+ "sphinx-copybutton>=0.5.2,<0.6",
77
+ "sphinx>=7.3.7,<8",
78
+ "vulture~=2.11",
79
79
  ]
80
80
 
81
81
  [build-system]
@@ -18,7 +18,6 @@
18
18
  import argparse
19
19
  import logging
20
20
  import platform
21
- from typing import Optional
22
21
  import sys
23
22
 
24
23
  import langdetect
@@ -51,7 +50,9 @@ def setup_logger(config: argparse.Namespace) -> None:
51
50
  )
52
51
 
53
52
 
54
- def log_or_raise_on_warning(message: str, raise_on_warning: bool = False) -> None:
53
+ def log_or_raise_on_warning(
54
+ message: str, raise_on_warning: bool = False
55
+ ) -> None:
55
56
  """Logs a warning message or raises an exception.
56
57
 
57
58
  Args:
@@ -87,7 +88,7 @@ def detect_and_expect_language(content: str, config_language: str) -> str:
87
88
  Returns:
88
89
  The configured language, or the detected language if none is
89
90
  configured.
90
- """
91
+ """
91
92
  detect_language = langdetect.detect(content)
92
93
  config_language = config_language or detect_language
93
94
  logger.info("Config language: %s", config_language)
@@ -15,9 +15,9 @@
15
15
 
16
16
  """txt2ebook/tte is a cli tool to convert txt file to ebook format.
17
17
 
18
- website: https://github.com/kianmeng/txt2ebook
19
- changelog: https://github.com/kianmeng/txt2ebook/blob/master/CHANGELOG.md
20
- issues: https://github.com/kianmeng/txt2ebook/issues
18
+ website: https://github.com/kianmeng/txt2ebook
19
+ changelog: https://github.com/kianmeng/txt2ebook/blob/master/CHANGELOG.md
20
+ issues: https://github.com/kianmeng/txt2ebook/issues
21
21
  """
22
22
 
23
23
  import argparse
@@ -150,7 +150,7 @@ def main(args: Optional[Sequence[str]] = None):
150
150
  else:
151
151
  logger.error(
152
152
  "subcommand '%s' is missing its execution function.",
153
- parsed_args.command
153
+ parsed_args.command,
154
154
  )
155
155
  parser.print_help(sys.stderr)
156
156
 
@@ -15,10 +15,7 @@
15
15
 
16
16
  """Packpage of different e-book formats."""
17
17
 
18
- import argparse
19
- from typing import Union
20
18
 
21
- import txt2ebook.models.book
22
19
  from txt2ebook.formats.epub import TEMPLATES as EPUB_TEMPLATES
23
20
  from txt2ebook.formats.epub import EpubWriter
24
21
  from txt2ebook.formats.gmi import GmiWriter
@@ -17,9 +17,6 @@
17
17
 
18
18
  import logging
19
19
  import re
20
- import sys
21
- from importlib import import_module
22
- from typing import Any
23
20
 
24
21
  logger = logging.getLogger(__name__)
25
22
 
@@ -42,4 +39,4 @@ def lower_underscore(string: str) -> str:
42
39
  >>> lower_underscore("Hello\tWorld")
43
40
  'hello_world'
44
41
  """
45
- return re.sub(r'\s+', '_', string.lower().strip())
42
+ return re.sub(r"\s+", "_", string.lower().strip())
@@ -40,9 +40,7 @@ class Book:
40
40
  language: str = field(default="")
41
41
  cover: str = field(default="", repr=False)
42
42
  raw_content: str = field(default="", repr=False)
43
- toc: List[Union[Volume, Chapter]] = field(
44
- default_factory=list, repr=False
45
- )
43
+ toc: List[Union[Volume, Chapter]] = field(default_factory=list, repr=False)
46
44
 
47
45
  def stats(self) -> Counter:
48
46
  """Returns the statistics count for the parsed tokens.
@@ -61,12 +59,14 @@ class Book:
61
59
  authors = ", ".join(self.authors)
62
60
  format_options = {
63
61
  1: f"{self.title}_{authors}",
64
- 2: f"{authors}_{self.title}"
62
+ 2: f"{authors}_{self.title}",
65
63
  }
66
64
  try:
67
65
  return format_options[filename_format]
68
66
  except KeyError:
69
- raise AttributeError(f"Invalid filename format: '{filename_format}'!")
67
+ raise AttributeError(
68
+ f"Invalid filename format: '{filename_format}'!"
69
+ )
70
70
 
71
71
  def debug(self, verbosity: int = 1) -> None:
72
72
  """Dump debug log of sections in self.toc."""
@@ -17,11 +17,10 @@
17
17
 
18
18
  import argparse
19
19
  import logging
20
- from dataclasses import dataclass, field
20
+ from dataclasses import dataclass
21
21
  from importlib import import_module
22
22
  from typing import List, Tuple, Union
23
23
 
24
- import cjkwrap
25
24
  import regex as re
26
25
 
27
26
  from txt2ebook.models import Book, Chapter, Volume
@@ -88,7 +87,9 @@ class Parser:
88
87
  Returns:
89
88
  str: The formatted section header.
90
89
  """
91
- if not getattr(self.config, "header_number", False) or self.config.language not in (
90
+ if not getattr(
91
+ self.config, "header_number", False
92
+ ) or self.config.language not in (
92
93
  "zh-cn",
93
94
  "zh-tw",
94
95
  ):
@@ -19,10 +19,9 @@ import argparse
19
19
  import logging
20
20
  import sys
21
21
 
22
- from txt2ebook.subcommands.parse import run as parse_txt
23
- from txt2ebook.formats.epub import EpubWriter
24
22
  from txt2ebook.formats import EPUB_TEMPLATES
25
-
23
+ from txt2ebook.formats.epub import EpubWriter
24
+ from txt2ebook.subcommands.parse import run as parse_txt
26
25
 
27
26
  logger = logging.getLogger(__name__)
28
27
 
@@ -30,8 +29,9 @@ logger = logging.getLogger(__name__)
30
29
  def build_subparser(subparsers) -> None:
31
30
  """Build the subparser."""
32
31
  epub_parser = subparsers.add_parser(
33
- "epub", help="generate ebook in EPUB format",
34
- formatter_class=argparse.RawTextHelpFormatter
32
+ "epub",
33
+ help="generate ebook in EPUB format",
34
+ formatter_class=argparse.RawTextHelpFormatter,
35
35
  )
36
36
 
37
37
  epub_parser.set_defaults(func=run)
@@ -19,9 +19,8 @@ import argparse
19
19
  import logging
20
20
  import sys
21
21
 
22
- from txt2ebook.subcommands.parse import run as parse_txt
23
22
  from txt2ebook.formats.gmi import GmiWriter
24
-
23
+ from txt2ebook.subcommands.parse import run as parse_txt
25
24
 
26
25
  logger = logging.getLogger(__name__)
27
26
 
@@ -19,9 +19,8 @@ import argparse
19
19
  import logging
20
20
  import sys
21
21
 
22
- from txt2ebook.subcommands.parse import run as parse_txt
23
22
  from txt2ebook.formats.md import MdWriter as MarkdownWriter
24
-
23
+ from txt2ebook.subcommands.parse import run as parse_txt
25
24
 
26
25
  logger = logging.getLogger(__name__)
27
26
 
@@ -19,10 +19,9 @@ import argparse
19
19
  import logging
20
20
  import sys
21
21
 
22
- from txt2ebook.subcommands.parse import run as parse_txt
23
- from txt2ebook.formats.pdf import PdfWriter
24
22
  from txt2ebook.formats import PAGE_SIZES
25
-
23
+ from txt2ebook.formats.pdf import PdfWriter
24
+ from txt2ebook.subcommands.parse import run as parse_txt
26
25
 
27
26
  logger = logging.getLogger(__name__)
28
27
 
@@ -19,10 +19,9 @@ import argparse
19
19
  import logging
20
20
  import sys
21
21
 
22
- from txt2ebook.subcommands.parse import run as parse_txt
23
- from txt2ebook.formats.typ import TypWriter
24
22
  from txt2ebook.formats import PAGE_SIZES
25
-
23
+ from txt2ebook.formats.typ import TypWriter
24
+ from txt2ebook.subcommands.parse import run as parse_txt
26
25
 
27
26
  logger = logging.getLogger(__name__)
28
27
 
@@ -169,7 +169,6 @@ class Tokenizer:
169
169
 
170
170
  return metadata
171
171
 
172
-
173
172
  def _tokenize_content(self) -> None:
174
173
  # Determine the actual content part, after any metadata block
175
174
  metadata_block_re = (
@@ -8,6 +8,16 @@
8
8
  花间一壶酒,独酌无相亲。
9
9
  举杯邀明月,对影成三人。
10
10
 
11
+ This is a paragraph with some halfwidth characters like 123, ABC, and symbols !@#$.
12
+
13
+ This paragraph has
14
+ multiple newlines
15
+
16
+
17
+ between lines.
18
+
19
+ This is a very long line that should be wrapped when a width is specified. It needs to be long enough to exceed the typical default width and force wrapping. Let's make it even longer to be sure. This is a very long line that should be wrapped when a width is specified. It needs to be long enough to exceed the typical default width and force wrapping. Let's make it even longer to be sure.
20
+
11
21
  第1章 月既不解饮
12
22
 
13
23
  我歌月徘徊,我舞影零乱。醒时同交欢,醉后各分散。永结无情游,相期邈云汉。
@@ -1,7 +1,6 @@
1
1
  # pylint: disable=C0114,C0116
2
2
 
3
3
  import pytest
4
-
5
4
  from ebooklib import epub
6
5
 
7
6
 
@@ -0,0 +1,115 @@
1
+ # pylint: disable=C0114,C0116
2
+
3
+ import pytest
4
+
5
+
6
+ @pytest.mark.parametrize("option", ["-rl", "--regex-delete-line"])
7
+ def test_delete_line_regex(tte, infile, option):
8
+ txtfile = infile("sample.txt")
9
+ tte("massage", txtfile, "-ow", option, "我歌月徘徊")
10
+
11
+ with open(txtfile, encoding="utf8") as file:
12
+ content = file.read()
13
+ assert "我歌月徘徊" not in content
14
+
15
+
16
+ @pytest.mark.parametrize("option", ["-rr", "--regex-replace"])
17
+ def test_single_replace_regex(tte, infile, option):
18
+ txtfile = infile("sample.txt")
19
+
20
+ tte("massage", txtfile, "-ow", option, "章", "章:")
21
+
22
+ with open(txtfile, encoding="utf8") as file:
23
+ content = file.read()
24
+ assert "第1章:" in content
25
+ assert "第2章:" in content
26
+ assert "第3章:" in content
27
+
28
+
29
+ @pytest.mark.parametrize("option", ["-rd", "--regex-delete"])
30
+ def test_single_delete_regex(tte, infile, option):
31
+ txtfile = infile("sample.txt")
32
+ tte("massage", txtfile, "-ow", option, "歌月", option, "我")
33
+
34
+ with open(txtfile, encoding="utf8") as file:
35
+ content = file.read()
36
+ assert "徘徊,舞影零乱。" in content
37
+
38
+
39
+ @pytest.mark.parametrize("option", ["-fw", "--fullwidth"])
40
+ def test_fullwidth(tte, infile, option):
41
+ txtfile = infile("sample.txt")
42
+ tte("massage", txtfile, "-ow", option)
43
+
44
+ with open(txtfile, encoding="utf8") as file:
45
+ content = file.read()
46
+ # Check for conversion of halfwidth characters
47
+ assert "123" in content
48
+ assert "ABC" in content
49
+ assert "!@#$" in content
50
+
51
+
52
+ @pytest.mark.parametrize("option", ["-sn", "--single-newline"])
53
+ def test_single_newline(tte, infile, option):
54
+ txtfile = infile("sample.txt")
55
+ tte("massage", txtfile, "-ow", option)
56
+
57
+ with open(txtfile, encoding="utf8") as file:
58
+ content = file.read()
59
+ # Check that multiple newlines are reduced to single newlines between paragraphs
60
+ assert "This paragraph has\n\nmultiple newlines" in content
61
+ assert "between lines.\n\nThis is a very long line" in content
62
+ # Ensure single newlines within a paragraph are preserved by wrapping logic
63
+ # (though single_newline runs before wrapping, the effect is tested here)
64
+ assert "花间一壶酒,独酌无相亲。\n\n举杯邀明月,对影成三人。" in content
65
+
66
+
67
+ @pytest.mark.parametrize("option", ["-w", "--width"])
68
+ def test_width(tte, infile, option):
69
+ txtfile = infile("sample.txt")
70
+ # Use a small width to force wrapping
71
+ tte("massage", txtfile, "-ow", option, "40")
72
+
73
+ with open(txtfile, encoding="utf8") as file:
74
+ content = file.read()
75
+ # Check that the long line is wrapped
76
+ long_line_wrapped = "This is a very long line that should be\nwrapped when a width is specified. It needs\nto be long enough to exceed the typical\ndefault width and force wrapping. Let's\nmake it even longer to be sure. This is a\nvery long line that should be wrapped when\na width is specified. It needs to be long\nenough to exceed the typical default width\nand force wrapping. Let's make it even\nlonger to be sure."
77
+ assert long_line_wrapped in content
78
+
79
+
80
+ @pytest.mark.parametrize("option", ["-ps", "--paragraph_separator"])
81
+ def test_paragraph_separator(tte, infile, option):
82
+ txtfile = infile("sample.txt")
83
+ separator = "<br>"
84
+ tte("massage", txtfile, "-ow", option, separator)
85
+
86
+ with open(txtfile, encoding="utf8") as file:
87
+ content = file.read()
88
+ # Check that the custom separator is used between paragraphs
89
+ assert "花间一壶酒,独酌无相亲。" + separator + "举杯邀明月,对影成三人。" in content
90
+ assert "between lines." + separator + "This is a very long line" in content
91
+
92
+
93
+ def test_multiple_regex(tte, infile):
94
+ txtfile = infile("sample.txt")
95
+ # Apply multiple regex options
96
+ tte(
97
+ "massage",
98
+ txtfile,
99
+ "-ow",
100
+ "-rl",
101
+ "我歌月徘徊", # Delete line
102
+ "-rr",
103
+ "章",
104
+ "章:", # Replace
105
+ "-rd",
106
+ "无相亲", # Delete word/phrase
107
+ )
108
+
109
+ with open(txtfile, encoding="utf8") as file:
110
+ content = file.read()
111
+ # Check all regex effects
112
+ assert "我歌月徘徊" not in content # Line deleted
113
+ assert "第1章:" in content # Replace applied
114
+ assert "独酌无相亲" not in content # Word/phrase deleted
115
+ assert "花间一壶酒,独酌。" in content # Check surrounding text after deletion