txt2ebook 0.1.145__tar.gz → 0.1.147__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.145 → txt2ebook-0.1.147}/.pre-commit-config.yaml +4 -1
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/CHANGELOG.md +16 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/PKG-INFO +7 -13
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/README.md +5 -11
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/noxfile.py +15 -4
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/pyproject.toml +6 -1
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/__init__.py +6 -2
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/formats/base.py +9 -2
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/formats/epub.py +18 -6
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/formats/gmi.py +7 -2
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/formats/md.py +7 -2
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/formats/pdf.py +3 -1
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/formats/tex.py +6 -2
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/formats/txt.py +6 -2
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/formats/typ.py +4 -1
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/languages/en.py +3 -2
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/models/book.py +3 -1
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/parser.py +10 -5
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/subcommands/__init__.py +2 -1
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/subcommands/gmi.py +3 -1
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/subcommands/massage.py +46 -14
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/subcommands/md.py +3 -1
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/subcommands/pdf.py +3 -1
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/subcommands/tex.py +3 -1
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/subcommands/typ.py +3 -1
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/tokenizer.py +10 -3
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/zh_utils.py +4 -1
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/conftest.py +25 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_input_file_arg.py +4 -1
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_output_file_arg.py +3 -1
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_purge_flag.py +3 -1
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_split_volume_and_chapter_flag.py +3 -1
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_subcommand_massage.py +10 -3
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/uv.lock +210 -185
- txt2ebook-0.1.145/tests/test_epub_writer.py +0 -28
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/.coveragerc +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/.gitignore +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/.python-version +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/CONTRIBUTING.md +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/LICENSE.md +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/docs/Makefile +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/docs/make.bat +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/docs/source/CHANGELOG.md +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/docs/source/CONTRIBUTING.md +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/docs/source/LICENSE.md +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/docs/source/README.md +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/docs/source/_static/logo.png +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/docs/source/conf.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/docs/source/index.rst +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/docs/source/txt2ebook.formats.rst +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/docs/source/txt2ebook.helpers.rst +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/docs/source/txt2ebook.models.rst +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/docs/source/txt2ebook.parsers.rst +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/docs/source/txt2ebook.rst +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/__main__.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/cli.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/exceptions.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/formats/__init__.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/formats/templates/__init__.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/formats/templates/epub/__init__.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/formats/templates/epub/clean.css +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/formats/templates/epub/condense.css +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/formats/templates/epub/noindent.css +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/helpers/__init__.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/languages/__init__.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/languages/zh_cn.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/languages/zh_tw.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/locales/en/LC_MESSAGES/txt2ebook.mo +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/locales/en/LC_MESSAGES/txt2ebook.po +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/locales/txt2ebook.pot +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/locales/zh-cn/LC_MESSAGES/txt2ebook.mo +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/locales/zh-cn/LC_MESSAGES/txt2ebook.po +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/locales/zh-tw/LC_MESSAGES/txt2ebook.mo +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/locales/zh-tw/LC_MESSAGES/txt2ebook.po +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/models/__init__.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/models/chapter.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/models/volume.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/subcommands/env.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/subcommands/epub.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/src/txt2ebook/subcommands/parse.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/__init__.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/fixtures/empty_file.txt +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/fixtures/missing_chapters.txt +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/fixtures/sample.txt +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/fixtures/sample_all_headers.txt +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/fixtures/sample_long_headers.txt +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/fixtures/sample_remove_wrapping.txt +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/fixtures/sample_unsorted_headers.txt +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/fixtures/sample_with_issues.txt +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/fixtures/sample_with_metadata.txt +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_filename_format_flag.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_format_option.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_header_number_flag.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_language_option.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_overwrite_flag.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_parser.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_quiet_flag.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_raise_warnings.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_sort_volume_and_chapter_flag.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_subcommand_env.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_subcommand_epub.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_test_parsing_flag.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_tokenizer.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_translator_option.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_txt2ebook.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_verbose_flag.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_volume_page_flag.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_zh_utils_zh_halfwidth_to_fullwidth.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_zh_utils_zh_numeric.py +0 -0
- {txt2ebook-0.1.145 → txt2ebook-0.1.147}/tests/test_zh_utils_zh_words_to_numbers.py +0 -0
@@ -35,12 +35,15 @@ repos:
|
|
35
35
|
exclude: (poetry.lock)
|
36
36
|
|
37
37
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
38
|
-
rev: v0.12.
|
38
|
+
rev: v0.12.2
|
39
39
|
hooks:
|
40
40
|
- id: ruff-check
|
41
41
|
args:
|
42
42
|
- --fix
|
43
43
|
- --exit-non-zero-on-fix
|
44
|
+
- id: ruff-format
|
45
|
+
args:
|
46
|
+
- --line-length=79
|
44
47
|
|
45
48
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
46
49
|
rev: v1.16.1
|
@@ -7,6 +7,22 @@ and this project adheres to [0-based versioning](https://0ver.org/).
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## v0.1.147 (2025-07-06)
|
11
|
+
|
12
|
+
- Bump deps and `pre-commit` hook for `ruff`
|
13
|
+
- Enable `ruff format` and code format
|
14
|
+
- Resync `uv.lock` file during release `nox` job
|
15
|
+
|
16
|
+
## v0.1.146 (2025-06-29)
|
17
|
+
|
18
|
+
- Bump deps and `pre-commit` hook for `ruff`
|
19
|
+
- Fix `massage` subcommand did not retain the original content
|
20
|
+
- Fix missing regex for translator for en language
|
21
|
+
- Move fixture to the right file
|
22
|
+
- Update help message in readme
|
23
|
+
- Update installation step using `uv` tool
|
24
|
+
- Update project classifier keywords
|
25
|
+
|
10
26
|
## v0.1.145 (2025-06-22)
|
11
27
|
|
12
28
|
- Bump deps and `pre-commit` hooks
|
@@ -1,13 +1,13 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: txt2ebook
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.147
|
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
|
7
7
|
Author-email: Kian-Meng Ang <kianmeng@cpan.org>
|
8
8
|
License-Expression: AGPL-3.0-or-later
|
9
9
|
License-File: LICENSE.md
|
10
|
-
Keywords: cjk,ebook,epub,txt
|
10
|
+
Keywords: cjk,ebook,epub,gmi,latex,md,pdf,txt,typst
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
12
12
|
Classifier: Environment :: Console
|
13
13
|
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
@@ -50,19 +50,13 @@ A console tool to convert txt file to different ebook formats.
|
|
50
50
|
Stable version From PyPI:
|
51
51
|
|
52
52
|
```console
|
53
|
-
|
53
|
+
uv tool install txt2ebook
|
54
54
|
```
|
55
55
|
|
56
56
|
Upgrade to latest stable version:
|
57
57
|
|
58
58
|
```console
|
59
|
-
|
60
|
-
```
|
61
|
-
|
62
|
-
Latest development version from GitHub:
|
63
|
-
|
64
|
-
```console
|
65
|
-
python3 -m pip install -e git+https://github.com/kianmeng/txt2ebook.git
|
59
|
+
uv tool upgrade txt2ebook
|
66
60
|
```
|
67
61
|
|
68
62
|
## Usage
|
@@ -108,12 +102,12 @@ positional arguments:
|
|
108
102
|
typ
|
109
103
|
generate ebook in Typst format
|
110
104
|
|
111
|
-
|
112
|
-
-of
|
105
|
+
options:
|
106
|
+
-of, --output-folder OUTPUT_FOLDER
|
113
107
|
set default output folder (default: 'output')
|
114
108
|
-p, --purge
|
115
109
|
remove converted ebooks specified by --output-folder option (default: 'False')
|
116
|
-
-l
|
110
|
+
-l, --language LANGUAGE
|
117
111
|
language of the ebook (default: 'None')
|
118
112
|
-rw, --raise-on-warning
|
119
113
|
raise exception and stop parsing upon warning
|
@@ -7,19 +7,13 @@ A console tool to convert txt file to different ebook formats.
|
|
7
7
|
Stable version From PyPI:
|
8
8
|
|
9
9
|
```console
|
10
|
-
|
10
|
+
uv tool install txt2ebook
|
11
11
|
```
|
12
12
|
|
13
13
|
Upgrade to latest stable version:
|
14
14
|
|
15
15
|
```console
|
16
|
-
|
17
|
-
```
|
18
|
-
|
19
|
-
Latest development version from GitHub:
|
20
|
-
|
21
|
-
```console
|
22
|
-
python3 -m pip install -e git+https://github.com/kianmeng/txt2ebook.git
|
16
|
+
uv tool upgrade txt2ebook
|
23
17
|
```
|
24
18
|
|
25
19
|
## Usage
|
@@ -65,12 +59,12 @@ positional arguments:
|
|
65
59
|
typ
|
66
60
|
generate ebook in Typst format
|
67
61
|
|
68
|
-
|
69
|
-
-of
|
62
|
+
options:
|
63
|
+
-of, --output-folder OUTPUT_FOLDER
|
70
64
|
set default output folder (default: 'output')
|
71
65
|
-p, --purge
|
72
66
|
remove converted ebooks specified by --output-folder option (default: 'False')
|
73
|
-
-l
|
67
|
+
-l, --language LANGUAGE
|
74
68
|
language of the ebook (default: 'None')
|
75
69
|
-rw, --raise-on-warning
|
76
70
|
raise exception and stop parsing upon warning
|
@@ -74,7 +74,9 @@ def cov(session: nox.Session) -> None:
|
|
74
74
|
def doc(session: nox.Session) -> None:
|
75
75
|
"""Build doc with sphinx."""
|
76
76
|
_uv_install(session)
|
77
|
-
session.run(
|
77
|
+
session.run(
|
78
|
+
"sphinx-build", "docs/source/", "docs/build/html", *session.posargs
|
79
|
+
)
|
78
80
|
|
79
81
|
|
80
82
|
@nox.session(python="3.13")
|
@@ -192,17 +194,26 @@ def release(session: nox.Session) -> None:
|
|
192
194
|
"""Bump release."""
|
193
195
|
_uv_install(session)
|
194
196
|
|
195
|
-
before_version = session.run(
|
197
|
+
before_version = session.run(
|
198
|
+
"uv", "version", "--short", silent=True
|
199
|
+
).strip()
|
196
200
|
session.run("uv", "version", "--bump", "patch")
|
197
|
-
after_version = session.run(
|
201
|
+
after_version = session.run(
|
202
|
+
"uv", "version", "--short", silent=True
|
203
|
+
).strip()
|
198
204
|
|
199
|
-
_search_and_replace(
|
205
|
+
_search_and_replace(
|
206
|
+
"src/txt2ebook/__init__.py", before_version, after_version
|
207
|
+
)
|
200
208
|
|
201
209
|
date = datetime.date.today().strftime("%Y-%m-%d")
|
202
210
|
before_header = "## [Unreleased]\n\n"
|
203
211
|
after_header = f"## [Unreleased]\n\n## v{after_version} ({date})\n\n"
|
204
212
|
_search_and_replace("CHANGELOG.md", before_header, after_header)
|
205
213
|
|
214
|
+
# resync to update the bumped version to uv.lock
|
215
|
+
_uv_install(session)
|
216
|
+
|
206
217
|
session.run(
|
207
218
|
"git",
|
208
219
|
"commit",
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "txt2ebook"
|
3
|
-
version = "0.1.
|
3
|
+
version = "0.1.147"
|
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"
|
@@ -10,7 +10,12 @@ keywords = [
|
|
10
10
|
"cjk",
|
11
11
|
"ebook",
|
12
12
|
"epub",
|
13
|
+
"gmi",
|
14
|
+
"latex",
|
15
|
+
"md",
|
16
|
+
"pdf",
|
13
17
|
"txt",
|
18
|
+
"typst",
|
14
19
|
]
|
15
20
|
classifiers = [
|
16
21
|
"Development Status :: 4 - Beta",
|
@@ -38,7 +38,9 @@ def setup_logger(config: argparse.Namespace) -> None:
|
|
38
38
|
return
|
39
39
|
|
40
40
|
log_level = logging.DEBUG if config.debug else logging.INFO
|
41
|
-
log_format =
|
41
|
+
log_format = (
|
42
|
+
"%(levelname)5s: %(message)s" if config.debug else "%(message)s"
|
43
|
+
)
|
42
44
|
|
43
45
|
logging.basicConfig(
|
44
46
|
level=log_level,
|
@@ -48,7 +50,9 @@ def setup_logger(config: argparse.Namespace) -> None:
|
|
48
50
|
)
|
49
51
|
|
50
52
|
|
51
|
-
def log_or_raise_on_warning(
|
53
|
+
def log_or_raise_on_warning(
|
54
|
+
message: str, raise_on_warning: bool = False
|
55
|
+
) -> None:
|
52
56
|
"""Logs a warning message or raises an exception.
|
53
57
|
|
54
58
|
Args:
|
@@ -126,7 +126,9 @@ class BaseWriter(ABC):
|
|
126
126
|
# do not create to output folder when we explicit set the output path
|
127
127
|
# and file
|
128
128
|
if self.config.output_file:
|
129
|
-
return Path(file.parent, lower_underscore(file.stem)).with_suffix(
|
129
|
+
return Path(file.parent, lower_underscore(file.stem)).with_suffix(
|
130
|
+
extension
|
131
|
+
)
|
130
132
|
|
131
133
|
return Path(
|
132
134
|
file.parent, self.config.output_folder, lower_underscore(file.stem)
|
@@ -139,7 +141,12 @@ class BaseWriter(ABC):
|
|
139
141
|
self._("translator:") + ",".join(self.book.translators),
|
140
142
|
self._("tag:") + ",".join(self.book.tags),
|
141
143
|
]
|
142
|
-
return
|
144
|
+
return (
|
145
|
+
"---\n"
|
146
|
+
+ "\n".join(metadata)
|
147
|
+
+ "\n---"
|
148
|
+
+ self.config.paragraph_separator
|
149
|
+
)
|
143
150
|
|
144
151
|
def _to_toc(self, list_symbol, header_symbol="") -> str:
|
145
152
|
toc = ""
|
@@ -81,7 +81,9 @@ class EpubWriter(BaseWriter):
|
|
81
81
|
logger.debug("Create separate volume page: %s", section)
|
82
82
|
book.toc.append((html_volume, html_chapters))
|
83
83
|
else:
|
84
|
-
book.toc.append(
|
84
|
+
book.toc.append(
|
85
|
+
(epub.Section(section.title), html_chapters)
|
86
|
+
)
|
85
87
|
|
86
88
|
if isinstance(section, Chapter):
|
87
89
|
html_chapter = self._build_chapter(section)
|
@@ -113,12 +115,16 @@ class EpubWriter(BaseWriter):
|
|
113
115
|
book.add_item(book_css)
|
114
116
|
|
115
117
|
nav = epub.EpubNav()
|
116
|
-
nav.add_link(
|
118
|
+
nav.add_link(
|
119
|
+
href="style/book.css", rel="stylesheet", type="text/css"
|
120
|
+
)
|
117
121
|
book.add_item(nav)
|
118
122
|
book.spine.append("nav")
|
119
123
|
|
120
124
|
except FileNotFoundError as error:
|
121
|
-
logger.error(
|
125
|
+
logger.error(
|
126
|
+
"Unknown EPUB template name: %s", self.config.epub_template
|
127
|
+
)
|
122
128
|
raise SystemExit() from error
|
123
129
|
|
124
130
|
def _gen_id(self) -> str:
|
@@ -143,7 +149,9 @@ class EpubWriter(BaseWriter):
|
|
143
149
|
lang=self.book.language,
|
144
150
|
content=html,
|
145
151
|
)
|
146
|
-
cover.add_link(
|
152
|
+
cover.add_link(
|
153
|
+
href="style/book.css", rel="stylesheet", type="text/css"
|
154
|
+
)
|
147
155
|
return cover
|
148
156
|
|
149
157
|
def _build_volume(self, volume: Volume) -> epub.EpubHtml:
|
@@ -166,7 +174,9 @@ class EpubWriter(BaseWriter):
|
|
166
174
|
lang=self.book.language,
|
167
175
|
content=html,
|
168
176
|
)
|
169
|
-
epub_html.add_link(
|
177
|
+
epub_html.add_link(
|
178
|
+
href="style/book.css", rel="stylesheet", type="text/css"
|
179
|
+
)
|
170
180
|
|
171
181
|
return epub_html
|
172
182
|
|
@@ -192,6 +202,8 @@ class EpubWriter(BaseWriter):
|
|
192
202
|
lang=self.book.language,
|
193
203
|
content=html,
|
194
204
|
)
|
195
|
-
epub_html.add_link(
|
205
|
+
epub_html.add_link(
|
206
|
+
href="style/book.css", rel="stylesheet", type="text/css"
|
207
|
+
)
|
196
208
|
|
197
209
|
return epub_html
|
@@ -96,7 +96,9 @@ class GmiWriter(BaseWriter):
|
|
96
96
|
export_filename.parent.mkdir(parents=True, exist_ok=True)
|
97
97
|
logger.info("Creating %s", export_filename)
|
98
98
|
with open(export_filename, "w", encoding="utf8") as file:
|
99
|
-
file.write(
|
99
|
+
file.write(
|
100
|
+
self._to_volume_chapter_txt(section, chapter)
|
101
|
+
)
|
100
102
|
ct_seq = ct_seq + 1
|
101
103
|
if isinstance(section, Chapter):
|
102
104
|
filename = lower_underscore(
|
@@ -145,7 +147,10 @@ class GmiWriter(BaseWriter):
|
|
145
147
|
f"# {volume.title}"
|
146
148
|
+ self.config.paragraph_separator
|
147
149
|
+ self.config.paragraph_separator.join(
|
148
|
-
[
|
150
|
+
[
|
151
|
+
self._to_chapter_txt(chapter, True)
|
152
|
+
for chapter in volume.chapters
|
153
|
+
]
|
149
154
|
)
|
150
155
|
)
|
151
156
|
|
@@ -95,7 +95,9 @@ class MdWriter(BaseWriter):
|
|
95
95
|
export_filename.parent.mkdir(parents=True, exist_ok=True)
|
96
96
|
logger.info("Creating %s", export_filename)
|
97
97
|
with open(export_filename, "w", encoding="utf8") as file:
|
98
|
-
file.write(
|
98
|
+
file.write(
|
99
|
+
self._to_volume_chapter_txt(section, chapter)
|
100
|
+
)
|
99
101
|
ct_seq = ct_seq + 1
|
100
102
|
if isinstance(section, Chapter):
|
101
103
|
filename = lower_underscore(
|
@@ -144,7 +146,10 @@ class MdWriter(BaseWriter):
|
|
144
146
|
f"# {volume.title}"
|
145
147
|
+ self.config.paragraph_separator
|
146
148
|
+ self.config.paragraph_separator.join(
|
147
|
-
[
|
149
|
+
[
|
150
|
+
self._to_chapter_txt(chapter, True)
|
151
|
+
for chapter in volume.chapters
|
152
|
+
]
|
148
153
|
)
|
149
154
|
)
|
150
155
|
|
@@ -153,7 +153,9 @@ class PdfWriter(BaseWriter):
|
|
153
153
|
canvas.restoreState()
|
154
154
|
|
155
155
|
def _get_pagesize(self) -> Tuple:
|
156
|
-
page_size =
|
156
|
+
page_size = (
|
157
|
+
self.config.page_size or self.langconf.DEFAULT_PDF_PAGE_SIZE
|
158
|
+
)
|
157
159
|
return portrait(getattr(reportlab.lib.pagesizes, page_size.upper()))
|
158
160
|
|
159
161
|
def _init_styles(self) -> None:
|
@@ -100,7 +100,9 @@ class TexWriter(BaseWriter):
|
|
100
100
|
doc.append(NoEsc(r"\maketitle"))
|
101
101
|
doc.append(NoEsc(r"\thispagestyle{empty}"))
|
102
102
|
doc.append(NoEsc(r"\addtocontents{toc}{\protect\pagestyle{empty}}"))
|
103
|
-
doc.append(
|
103
|
+
doc.append(
|
104
|
+
NoEsc(r"\addtocontents{toc}{\protect\thispagestyle{empty}}")
|
105
|
+
)
|
104
106
|
doc.append(NoEsc(r"\tableofcontents"))
|
105
107
|
doc.append(NoEsc(r"\pagestyle{empty}"))
|
106
108
|
doc.append(NoEsc(r"\cleardoublepage"))
|
@@ -126,7 +128,9 @@ class TexWriter(BaseWriter):
|
|
126
128
|
|
127
129
|
filename = str(new_filename.parent / new_filename.stem)
|
128
130
|
pdf_filename = Path(filename).with_suffix(".pdf")
|
129
|
-
doc.generate_pdf(
|
131
|
+
doc.generate_pdf(
|
132
|
+
filename, compiler="latexmk", clean_tex=self.config.clean_tex
|
133
|
+
)
|
130
134
|
logger.info("Generate PDF file: %s", pdf_filename.resolve())
|
131
135
|
|
132
136
|
if self.config.open:
|
@@ -105,7 +105,9 @@ class TxtWriter(BaseWriter):
|
|
105
105
|
export_filename.parent.mkdir(parents=True, exist_ok=True)
|
106
106
|
logger.info("Creating %s", export_filename)
|
107
107
|
with open(export_filename, "w", encoding="utf8") as file:
|
108
|
-
file.write(
|
108
|
+
file.write(
|
109
|
+
self._to_volume_chapter_txt(section, chapter)
|
110
|
+
)
|
109
111
|
ct_seq = ct_seq + 1
|
110
112
|
if isinstance(section, Chapter):
|
111
113
|
filename = lower_underscore(
|
@@ -132,7 +134,9 @@ class TxtWriter(BaseWriter):
|
|
132
134
|
ymd_hms = dt.now().strftime("%Y%m%d_%H%M%S")
|
133
135
|
new_filename = Path(
|
134
136
|
txt_filename.resolve().parent.joinpath(
|
135
|
-
lower_underscore(
|
137
|
+
lower_underscore(
|
138
|
+
txt_filename.stem + "_" + ymd_hms + ".txt"
|
139
|
+
)
|
136
140
|
)
|
137
141
|
)
|
138
142
|
|
@@ -175,7 +175,10 @@ class TypWriter(BaseWriter):
|
|
175
175
|
f"= {volume.title}"
|
176
176
|
+ self.config.paragraph_separator
|
177
177
|
+ self.config.paragraph_separator.join(
|
178
|
-
[
|
178
|
+
[
|
179
|
+
self._to_chapter_txt(chapter, True)
|
180
|
+
for chapter in volume.chapters
|
181
|
+
]
|
179
182
|
)
|
180
183
|
)
|
181
184
|
|
@@ -17,7 +17,8 @@
|
|
17
17
|
|
18
18
|
DEFAULT_RE_TITLE = r"Title:(.*)"
|
19
19
|
DEFAULT_RE_AUTHOR = r"Author:(.*)"
|
20
|
-
DEFAULT_RE_TAG = r"Tags
|
21
|
-
DEFAULT_RE_INDEX = r"Index
|
20
|
+
DEFAULT_RE_TAG = r"Tags:(.*)"
|
21
|
+
DEFAULT_RE_INDEX = r"Index:(.*)"
|
22
22
|
DEFAULT_RE_VOLUME = r"(Volume.*)"
|
23
23
|
DEFAULT_RE_CHAPTER = r"(Chapter.*)"
|
24
|
+
DEFAULT_RE_TRANSLATOR = r"Translator:(.*)"
|
@@ -60,7 +60,9 @@ class Book:
|
|
60
60
|
try:
|
61
61
|
return format_options[filename_format]
|
62
62
|
except KeyError:
|
63
|
-
raise AttributeError(
|
63
|
+
raise AttributeError(
|
64
|
+
f"Invalid filename format: '{filename_format}'!"
|
65
|
+
)
|
64
66
|
|
65
67
|
def debug(self, verbosity: int = 1) -> None:
|
66
68
|
"""Dump debug log of sections in self.toc."""
|
@@ -57,8 +57,8 @@ class Parser:
|
|
57
57
|
"""
|
58
58
|
tokenizer = Tokenizer(self.raw_content, self.config)
|
59
59
|
|
60
|
-
(book_title, authors, translators, tags, index, toc) =
|
61
|
-
tokenizer
|
60
|
+
(book_title, authors, translators, tags, index, toc) = (
|
61
|
+
self.parse_tokens(tokenizer)
|
62
62
|
)
|
63
63
|
|
64
64
|
book = Book(
|
@@ -103,13 +103,17 @@ class Parser:
|
|
103
103
|
match = re.match(rf"第([{self.langconf.HALFWIDTH_NUMS}]*)", words)
|
104
104
|
if match and match.group(1) != "":
|
105
105
|
header_nums = match.group(1)
|
106
|
-
return words.replace(
|
106
|
+
return words.replace(
|
107
|
+
header_nums, str(header_nums).rjust(length, "0")
|
108
|
+
)
|
107
109
|
|
108
110
|
# left pad the section number if found as fullwidth integer
|
109
111
|
match = re.match(rf"第([{self.langconf.FULLWIDTH_NUMS}]*)", words)
|
110
112
|
if match and match.group(1) != "":
|
111
113
|
header_nums = match.group(1)
|
112
|
-
return words.replace(
|
114
|
+
return words.replace(
|
115
|
+
header_nums, str(header_nums).rjust(length, "0")
|
116
|
+
)
|
113
117
|
|
114
118
|
replaced_words = zh_words_to_numbers(words, length=length)
|
115
119
|
|
@@ -148,7 +152,8 @@ class Parser:
|
|
148
152
|
if (
|
149
153
|
token.type not in ["CHAPTER", "PARAGRAPH"]
|
150
154
|
or (
|
151
|
-
token.type == "CHAPTER"
|
155
|
+
token.type == "CHAPTER"
|
156
|
+
and self.config.verbose >= chapter_verbosity
|
152
157
|
)
|
153
158
|
or (
|
154
159
|
token.type == "PARAGRAPH"
|
@@ -24,7 +24,8 @@ def build_subparser(subparsers):
|
|
24
24
|
iter_namespace = pkgutil.iter_modules(__path__, __name__ + ".")
|
25
25
|
|
26
26
|
subcommands = {
|
27
|
-
name: importlib.import_module(name)
|
27
|
+
name: importlib.import_module(name)
|
28
|
+
for finder, name, ispkg in iter_namespace
|
28
29
|
}
|
29
30
|
|
30
31
|
for subcommand in subcommands.values():
|
@@ -27,7 +27,9 @@ logger = logging.getLogger(__name__)
|
|
27
27
|
|
28
28
|
def build_subparser(subparsers) -> None:
|
29
29
|
"""Build the subparser."""
|
30
|
-
gmi_parser = subparsers.add_parser(
|
30
|
+
gmi_parser = subparsers.add_parser(
|
31
|
+
"gmi", help="generate ebook in Gemtext format"
|
32
|
+
)
|
31
33
|
|
32
34
|
gmi_parser.set_defaults(func=run)
|
33
35
|
|
@@ -19,6 +19,7 @@ import argparse
|
|
19
19
|
import logging
|
20
20
|
import sys
|
21
21
|
from importlib import import_module
|
22
|
+
from pathlib import Path
|
22
23
|
|
23
24
|
import cjkwrap
|
24
25
|
import regex as re
|
@@ -201,21 +202,48 @@ def run(args: argparse.Namespace) -> None:
|
|
201
202
|
None
|
202
203
|
"""
|
203
204
|
massaged_txt = massage_txt(args)
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
205
|
+
if args.overwrite:
|
206
|
+
_overwrite_file(args, massaged_txt)
|
207
|
+
else:
|
208
|
+
_new_file(args, massaged_txt)
|
209
|
+
|
210
|
+
# args.language = detect_and_expect_language(massaged_txt, args.language)
|
211
|
+
# config_lang = args.language.replace("-", "_")
|
212
|
+
# langconf = import_module(f"txt2ebook.languages.{config_lang}")
|
213
|
+
# args.with_toc = False
|
214
|
+
# parser = Parser(massaged_txt, args, langconf)
|
215
|
+
# book = parser.parse()
|
216
|
+
|
217
|
+
# if args.debug:
|
218
|
+
# book.debug(args.verbose)
|
219
|
+
|
220
|
+
# if args.header_number:
|
221
|
+
# book = header_number(args, book)
|
222
|
+
|
223
|
+
# writer = TxtWriter(book, args)
|
224
|
+
# writer.write()
|
225
|
+
|
210
226
|
|
211
|
-
|
212
|
-
|
227
|
+
def _overwrite_file(args, massaged_txt) -> None:
|
228
|
+
txt_filename = Path(args.input_file.name)
|
213
229
|
|
214
|
-
|
215
|
-
|
230
|
+
with open(txt_filename, "w", encoding="utf8") as file:
|
231
|
+
file.write(massaged_txt)
|
232
|
+
logger.info("Overwrite txt file: %s", txt_filename.resolve())
|
216
233
|
|
217
|
-
|
218
|
-
|
234
|
+
|
235
|
+
def _new_file(args, massaged_txt) -> None:
|
236
|
+
txt_filename = Path(args.input_file.name)
|
237
|
+
export_filename = Path(
|
238
|
+
txt_filename.resolve().parent.joinpath(
|
239
|
+
args.output_folder, txt_filename.name
|
240
|
+
)
|
241
|
+
)
|
242
|
+
export_filename.parent.mkdir(parents=True, exist_ok=True)
|
243
|
+
|
244
|
+
with open(export_filename, "w", encoding="utf8") as file:
|
245
|
+
file.write(massaged_txt)
|
246
|
+
logger.info("New txt file: %s", export_filename.resolve())
|
219
247
|
|
220
248
|
|
221
249
|
def header_number(args: argparse.Namespace, book: Book) -> Book:
|
@@ -230,7 +258,9 @@ def header_number(args: argparse.Namespace, book: Book) -> Book:
|
|
230
258
|
for toc_item in book.toc:
|
231
259
|
toc_type = type(toc_item).__name__
|
232
260
|
if toc_type in seq_lengths:
|
233
|
-
toc_item.title = words_to_nums(
|
261
|
+
toc_item.title = words_to_nums(
|
262
|
+
args, toc_item.title, seq_lengths[toc_type]
|
263
|
+
)
|
234
264
|
|
235
265
|
return book
|
236
266
|
|
@@ -337,7 +367,9 @@ def do_delete_regex(args, content: str) -> str:
|
|
337
367
|
str: The formatted book content.
|
338
368
|
"""
|
339
369
|
for delete_regex in args.re_delete:
|
340
|
-
content = re.sub(
|
370
|
+
content = re.sub(
|
371
|
+
re.compile(rf"{delete_regex}", re.MULTILINE), "", content
|
372
|
+
)
|
341
373
|
return content
|
342
374
|
|
343
375
|
|
@@ -27,7 +27,9 @@ logger = logging.getLogger(__name__)
|
|
27
27
|
|
28
28
|
def build_subparser(subparsers) -> None:
|
29
29
|
"""Build the subparser."""
|
30
|
-
md_parser = subparsers.add_parser(
|
30
|
+
md_parser = subparsers.add_parser(
|
31
|
+
"md", help="generate ebook in Markdown format"
|
32
|
+
)
|
31
33
|
|
32
34
|
md_parser.set_defaults(func=run)
|
33
35
|
|
@@ -28,7 +28,9 @@ logger = logging.getLogger(__name__)
|
|
28
28
|
|
29
29
|
def build_subparser(subparsers) -> None:
|
30
30
|
"""Build the subparser."""
|
31
|
-
pdf_parser = subparsers.add_parser(
|
31
|
+
pdf_parser = subparsers.add_parser(
|
32
|
+
"pdf", help="generate ebook in Markdown format"
|
33
|
+
)
|
32
34
|
|
33
35
|
pdf_parser.set_defaults(func=run)
|
34
36
|
|
@@ -27,7 +27,9 @@ logger = logging.getLogger(__name__)
|
|
27
27
|
|
28
28
|
def build_subparser(subparsers) -> None:
|
29
29
|
"""Build the subparser."""
|
30
|
-
tex_parser = subparsers.add_parser(
|
30
|
+
tex_parser = subparsers.add_parser(
|
31
|
+
"tex", help="generate ebook in TeX/PDF format"
|
32
|
+
)
|
31
33
|
|
32
34
|
tex_parser.add_argument(
|
33
35
|
"input_file",
|
@@ -28,7 +28,9 @@ logger = logging.getLogger(__name__)
|
|
28
28
|
|
29
29
|
def build_subparser(subparsers) -> None:
|
30
30
|
"""Build the subparser."""
|
31
|
-
typ_parser = subparsers.add_parser(
|
31
|
+
typ_parser = subparsers.add_parser(
|
32
|
+
"typ", help="generate ebook in Typst format"
|
33
|
+
)
|
32
34
|
|
33
35
|
typ_parser.set_defaults(func=run)
|
34
36
|
|