epub2text 0.2.2__tar.gz → 0.2.4__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 (58) hide show
  1. epub2text-0.2.4/.codecrate.toml +10 -0
  2. {epub2text-0.2.2 → epub2text-0.2.4}/.gitignore +1 -0
  3. {epub2text-0.2.2 → epub2text-0.2.4}/.ruff.toml +1 -1
  4. {epub2text-0.2.2 → epub2text-0.2.4}/PKG-INFO +3 -4
  5. {epub2text-0.2.2 → epub2text-0.2.4}/docs/installation.rst +0 -4
  6. {epub2text-0.2.2 → epub2text-0.2.4}/docs/make.py +1 -1
  7. {epub2text-0.2.2 → epub2text-0.2.4}/epub2text/__init__.py +2 -2
  8. {epub2text-0.2.2 → epub2text-0.2.4}/epub2text/_version.py +3 -3
  9. {epub2text-0.2.2 → epub2text-0.2.4}/epub2text/bookmarks.py +9 -5
  10. {epub2text-0.2.2 → epub2text-0.2.4}/epub2text/cleaner.py +2 -4
  11. {epub2text-0.2.2 → epub2text-0.2.4}/epub2text/cli.py +19 -20
  12. {epub2text-0.2.2 → epub2text-0.2.4}/epub2text/formatters.py +17 -101
  13. {epub2text-0.2.2 → epub2text-0.2.4}/epub2text/models.py +11 -12
  14. {epub2text-0.2.2 → epub2text-0.2.4}/epub2text/parser.py +81 -143
  15. {epub2text-0.2.2 → epub2text-0.2.4}/epub2text/reader.py +6 -7
  16. {epub2text-0.2.2 → epub2text-0.2.4}/epub2text.egg-info/PKG-INFO +3 -4
  17. {epub2text-0.2.2 → epub2text-0.2.4}/epub2text.egg-info/SOURCES.txt +1 -0
  18. {epub2text-0.2.2 → epub2text-0.2.4}/epub2text.egg-info/requires.txt +1 -3
  19. {epub2text-0.2.2 → epub2text-0.2.4}/examples/demo.py +2 -19
  20. {epub2text-0.2.2 → epub2text-0.2.4}/pyproject.toml +16 -5
  21. {epub2text-0.2.2 → epub2text-0.2.4}/tests/test_epub_download.py +11 -4
  22. {epub2text-0.2.2 → epub2text-0.2.4}/tests/test_epub_generation.py +6 -13
  23. {epub2text-0.2.2 → epub2text-0.2.4}/tests/test_page_extraction_improvements.py +1 -1
  24. {epub2text-0.2.2 → epub2text-0.2.4}/tests/test_title_deduplication.py +4 -2
  25. {epub2text-0.2.2 → epub2text-0.2.4}/.coveragerc +0 -0
  26. {epub2text-0.2.2 → epub2text-0.2.4}/.github/pytest.ini +0 -0
  27. {epub2text-0.2.2 → epub2text-0.2.4}/.github/workflows/codecov.yml +0 -0
  28. {epub2text-0.2.2 → epub2text-0.2.4}/.github/workflows/pre-commit.yml +0 -0
  29. {epub2text-0.2.2 → epub2text-0.2.4}/.github/workflows/python-publish.yml +0 -0
  30. {epub2text-0.2.2 → epub2text-0.2.4}/.github/workflows/tests.yml +0 -0
  31. {epub2text-0.2.2 → epub2text-0.2.4}/.pre-commit-config.yaml +0 -0
  32. {epub2text-0.2.2 → epub2text-0.2.4}/.prettierrc.yml +0 -0
  33. {epub2text-0.2.2 → epub2text-0.2.4}/.readthedocs.yaml +0 -0
  34. {epub2text-0.2.2 → epub2text-0.2.4}/LICENSE +0 -0
  35. {epub2text-0.2.2 → epub2text-0.2.4}/README.md +0 -0
  36. {epub2text-0.2.2 → epub2text-0.2.4}/docs/api.rst +0 -0
  37. {epub2text-0.2.2 → epub2text-0.2.4}/docs/changelog.rst +0 -0
  38. {epub2text-0.2.2 → epub2text-0.2.4}/docs/conf.py +0 -0
  39. {epub2text-0.2.2 → epub2text-0.2.4}/docs/index.rst +0 -0
  40. {epub2text-0.2.2 → epub2text-0.2.4}/docs/make.bat +0 -0
  41. {epub2text-0.2.2 → epub2text-0.2.4}/docs/requirements.txt +0 -0
  42. {epub2text-0.2.2 → epub2text-0.2.4}/docs/usage.rst +0 -0
  43. {epub2text-0.2.2 → epub2text-0.2.4}/epub2text/__main__.py +0 -0
  44. {epub2text-0.2.2 → epub2text-0.2.4}/epub2text/py.typed +0 -0
  45. {epub2text-0.2.2 → epub2text-0.2.4}/epub2text.egg-info/dependency_links.txt +0 -0
  46. {epub2text-0.2.2 → epub2text-0.2.4}/epub2text.egg-info/entry_points.txt +0 -0
  47. {epub2text-0.2.2 → epub2text-0.2.4}/epub2text.egg-info/top_level.txt +0 -0
  48. {epub2text-0.2.2 → epub2text-0.2.4}/examples/__init__.py +0 -0
  49. {epub2text-0.2.2 → epub2text-0.2.4}/examples/__main__.py +0 -0
  50. {epub2text-0.2.2 → epub2text-0.2.4}/requirements-test.txt +0 -0
  51. {epub2text-0.2.2 → epub2text-0.2.4}/setup.cfg +0 -0
  52. {epub2text-0.2.2 → epub2text-0.2.4}/setup.py +0 -0
  53. {epub2text-0.2.2 → epub2text-0.2.4}/tests/__init__.py +0 -0
  54. {epub2text-0.2.2 → epub2text-0.2.4}/tests/test_cleaner.py +0 -0
  55. {epub2text-0.2.2 → epub2text-0.2.4}/tests/test_cli_ranges.py +0 -0
  56. {epub2text-0.2.2 → epub2text-0.2.4}/tests/test_parser_page_list.py +0 -0
  57. {epub2text-0.2.2 → epub2text-0.2.4}/tests/test_parser_spine_slice.py +0 -0
  58. {epub2text-0.2.2 → epub2text-0.2.4}/tests/test_reader_bookmarks.py +0 -0
@@ -0,0 +1,10 @@
1
+ [codecrate]
2
+ output = "context_epub2text.md"
3
+ keep_docstrings = true
4
+ dedupe = true
5
+ metadata = false
6
+ respect_gitignore = true
7
+ exclude = ["examples", "*/.venv/*"]
8
+ include = ["**/*.py", "**/*.toml", "**/*.rst", "**/*.md"]
9
+ split_max_chars = 0
10
+ layout = "full"
@@ -206,3 +206,4 @@ marimo/_static/
206
206
  marimo/_lsp/
207
207
  __marimo__/
208
208
  epub2text/_version.py
209
+ context_epub2text.md
@@ -1,5 +1,5 @@
1
1
 
2
- target-version = "py39"
2
+ target-version = "py310"
3
3
  fix = true
4
4
 
5
5
  [lint]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: epub2text
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: A simple tool to extract text from EPUB files.
5
5
  Author-email: Holger Nahrstaedt <nahrstaedt@gmail.com>
6
6
  License: MIT License
@@ -37,7 +37,7 @@ Classifier: Programming Language :: Python :: 3.12
37
37
  Classifier: Programming Language :: Python :: 3.13
38
38
  Classifier: Programming Language :: Python :: 3.14
39
39
  Classifier: Topic :: Software Development :: Libraries
40
- Requires-Python: >=3.9
40
+ Requires-Python: >=3.10
41
41
  Description-Content-Type: text/markdown
42
42
  License-File: LICENSE
43
43
  Requires-Dist: click>=8.0.0
@@ -45,10 +45,9 @@ Requires-Dist: rich>=13.0.0
45
45
  Requires-Dist: ebooklib>=0.18
46
46
  Requires-Dist: beautifulsoup4>=4.12.0
47
47
  Requires-Dist: defusedxml>=0.7.1
48
+ Requires-Dist: phrasplit>=0.3.2
48
49
  Provides-Extra: lxml
49
50
  Requires-Dist: lxml>=4.9.0; extra == "lxml"
50
- Provides-Extra: sentences
51
- Requires-Dist: phrasplit>=0.1.0; extra == "sentences"
52
51
  Provides-Extra: dev
53
52
  Requires-Dist: pytest>=7.0.0; extra == "dev"
54
53
  Requires-Dist: ruff>=0.1.0; extra == "dev"
@@ -31,10 +31,6 @@ For better HTML parsing performance::
31
31
 
32
32
  pip install epub2text[lxml]
33
33
 
34
- For sentence-level formatting (requires phrasplit/spaCy)::
35
-
36
- pip install epub2text[sentences]
37
-
38
34
  After installing, download a spaCy language model::
39
35
 
40
36
  python -m spacy download en_core_web_sm
@@ -20,7 +20,7 @@ import subprocess
20
20
  import sys
21
21
 
22
22
 
23
- def main():
23
+ def main() -> int:
24
24
  """Run the script."""
25
25
  sphinx_build = "sphinx-build"
26
26
  build_dir = os.path.join("docs", "build")
@@ -136,7 +136,7 @@ def _download_epub(
136
136
  except Exception as exc:
137
137
  if tmp_path:
138
138
  Path(tmp_path).unlink(missing_ok=True)
139
- if isinstance(exc, (urllib.error.URLError, socket.timeout)):
139
+ if isinstance(exc, urllib.error.URLError | socket.timeout):
140
140
  if isinstance(exc, socket.timeout) or isinstance(
141
141
  getattr(exc, "reason", None), socket.timeout
142
142
  ):
@@ -152,7 +152,7 @@ def epub2txt(
152
152
  max_bytes: int = DEFAULT_MAX_DOWNLOAD_BYTES,
153
153
  user_agent: str | None = None,
154
154
  allowed_schemes: tuple[str, ...] = ("https", "http"),
155
- ) -> Union[str, list[str]]:
155
+ ) -> str | list[str]:
156
156
  """
157
157
  Extract text from EPUB file (compatibility function for old epub2txt API).
158
158
 
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.2.2'
32
- __version_tuple__ = version_tuple = (0, 2, 2)
31
+ __version__ = version = '0.2.4'
32
+ __version_tuple__ = version_tuple = (0, 2, 4)
33
33
 
34
- __commit_id__ = commit_id = 'ga27a7539e'
34
+ __commit_id__ = commit_id = 'ge900e4fef'
@@ -7,7 +7,7 @@ import tempfile
7
7
  from dataclasses import asdict, dataclass
8
8
  from datetime import datetime, timezone
9
9
  from pathlib import Path
10
- from typing import Any, Optional
10
+ from typing import Any, cast
11
11
 
12
12
  logger = logging.getLogger(__name__)
13
13
 
@@ -54,7 +54,7 @@ class Bookmark:
54
54
  class BookmarkManager:
55
55
  """Manages bookmarks for EPUB files."""
56
56
 
57
- def __init__(self, bookmark_file: Optional[Path] = None) -> None:
57
+ def __init__(self, bookmark_file: Path | None = None) -> None:
58
58
  """
59
59
  Initialize bookmark manager.
60
60
 
@@ -119,11 +119,15 @@ class BookmarkManager:
119
119
  """Return the path for the bookmark backup file."""
120
120
  return self.bookmark_file.with_suffix(self.bookmark_file.suffix + ".bak")
121
121
 
122
- def _read_bookmarks_file(self, path: Path) -> Optional[dict[str, Any]]:
122
+ def _read_bookmarks_file(self, path: Path) -> dict[str, Any] | None:
123
123
  """Read bookmarks JSON from disk."""
124
124
  try:
125
125
  with open(path, encoding="utf-8") as f:
126
- return json.load(f)
126
+ data = json.load(f)
127
+ if isinstance(data, dict):
128
+ return cast(dict[str, Any], data)
129
+ logger.warning("Unexpected bookmark data structure in %s", path)
130
+ return None
127
131
  except (json.JSONDecodeError, OSError) as exc:
128
132
  logger.warning("Failed to load bookmarks from %s: %s", path, exc)
129
133
  return None
@@ -144,7 +148,7 @@ class BookmarkManager:
144
148
  self._bookmarks[key] = asdict(bookmark)
145
149
  self._save()
146
150
 
147
- def load(self, epub_path: str) -> Optional[Bookmark]:
151
+ def load(self, epub_path: str) -> Bookmark | None:
148
152
  """
149
153
  Load bookmark for a specific EPUB file.
150
154
 
@@ -96,7 +96,7 @@ class TextCleaner:
96
96
  markers = {}
97
97
  marker_count = [0]
98
98
 
99
- def save_marker(match):
99
+ def save_marker(match: re.Match[str]) -> str:
100
100
  placeholder = f"___MARKER_{marker_count[0]}___"
101
101
  markers[placeholder] = match.group(0)
102
102
  marker_count[0] += 1
@@ -147,8 +147,6 @@ class TextCleaner:
147
147
  def apply_gutenberg_spacing(self, text: str) -> str:
148
148
  """Apply Gutenberg Project formatting: two spaces after sentences and colons.
149
149
 
150
- Uses phrasplit for accurate sentence boundary detection.
151
-
152
150
  Args:
153
151
  text: Text to format
154
152
 
@@ -198,7 +196,7 @@ class TextCleaner:
198
196
  abbrev_map = {}
199
197
  abbrev_counter = [0]
200
198
 
201
- def protect_abbrev(match):
199
+ def protect_abbrev(match: re.Match[str]) -> str:
202
200
  placeholder = f"__ABBREV_{abbrev_counter[0]}__"
203
201
  abbrev_map[placeholder] = match.group(0)
204
202
  abbrev_counter[0] += 1
@@ -7,7 +7,6 @@ import sys
7
7
  from collections.abc import Iterator
8
8
  from contextlib import contextmanager
9
9
  from pathlib import Path
10
- from typing import Optional
11
10
 
12
11
  import click
13
12
  from rich.console import Console
@@ -46,7 +45,7 @@ def spinner(task_description: str) -> Iterator[None]:
46
45
  yield
47
46
 
48
47
 
49
- def parse_range_tokens(range_str: str) -> list[tuple[str, Optional[str]]]:
48
+ def parse_range_tokens(range_str: str) -> list[tuple[str, str | None]]:
50
49
  """
51
50
  Parse a range string into ordered tokens.
52
51
 
@@ -59,7 +58,7 @@ def parse_range_tokens(range_str: str) -> list[tuple[str, Optional[str]]]:
59
58
  if not range_str:
60
59
  raise ValueError("Range string cannot be empty")
61
60
 
62
- tokens: list[tuple[str, Optional[str]]] = []
61
+ tokens: list[tuple[str, str | None]] = []
63
62
  parts = range_str.split(",")
64
63
 
65
64
  for part in parts:
@@ -205,7 +204,7 @@ def parse_page_range(range_str: str, pages: list[Page]) -> list[str]:
205
204
  result: list[str] = []
206
205
  seen: set[str] = set()
207
206
 
208
- def parse_int(value: str) -> Optional[int]:
207
+ def parse_int(value: str) -> int | None:
209
208
  try:
210
209
  return int(value)
211
210
  except ValueError:
@@ -590,8 +589,8 @@ def list_pages(filepath: Path, page_size: int, use_words: bool) -> None:
590
589
  )
591
590
  def extract_pages_cmd(
592
591
  filepath: Path,
593
- output: Optional[Path],
594
- pages: Optional[str],
592
+ output: Path | None,
593
+ pages: str | None,
595
594
  page_size: int,
596
595
  use_words: bool,
597
596
  raw: bool,
@@ -786,13 +785,13 @@ def extract_pages_cmd(
786
785
  )
787
786
  def extract(
788
787
  filepath: Path,
789
- output: Optional[Path],
790
- chapters: Optional[str],
788
+ output: Path | None,
789
+ chapters: str | None,
791
790
  interactive: bool,
792
791
  paragraphs: bool,
793
792
  sentences: bool,
794
793
  comma: bool,
795
- max_length: Optional[int],
794
+ max_length: int | None,
796
795
  empty_lines: bool,
797
796
  separator: str,
798
797
  raw: bool,
@@ -801,7 +800,7 @@ def extract(
801
800
  no_markers: bool,
802
801
  language_model: str,
803
802
  offset: int,
804
- limit: Optional[int],
803
+ limit: int | None,
805
804
  line_numbers: bool,
806
805
  ) -> None:
807
806
  """
@@ -1012,8 +1011,8 @@ def extract(
1012
1011
  )
1013
1012
  def extract_gutenberg(
1014
1013
  filepath: Path,
1015
- output: Optional[Path],
1016
- chapters: Optional[str],
1014
+ output: Path | None,
1015
+ chapters: str | None,
1017
1016
  interactive: bool,
1018
1017
  ) -> None:
1019
1018
  """
@@ -1124,9 +1123,9 @@ def extract_gutenberg(
1124
1123
  # First, normalize the chapter text to help with splitting
1125
1124
  chapter_lines = chapter_text.split("\n")
1126
1125
 
1127
- current_title = None
1128
- current_content = []
1129
- chapters_list = []
1126
+ current_title: str | None = None
1127
+ current_content: list[str] = []
1128
+ chapters_list: list[tuple[str, str]] = []
1130
1129
 
1131
1130
  i = 0
1132
1131
  while i < len(chapter_lines):
@@ -1418,11 +1417,11 @@ def info(filepath: Path, format: str) -> None:
1418
1417
  )
1419
1418
  def read(
1420
1419
  filepath: Path,
1421
- chapter: Optional[int],
1422
- line: Optional[int],
1420
+ chapter: int | None,
1421
+ line: int | None,
1423
1422
  resume: bool,
1424
- bookmark_file: Optional[Path],
1425
- page_size: Optional[int],
1423
+ bookmark_file: Path | None,
1424
+ page_size: int | None,
1426
1425
  no_header: bool,
1427
1426
  no_footer: bool,
1428
1427
  sentences: bool,
@@ -1432,7 +1431,7 @@ def read(
1432
1431
  raw: bool,
1433
1432
  keep_footnotes: bool,
1434
1433
  keep_page_numbers: bool,
1435
- width: Optional[int],
1434
+ width: int | None,
1436
1435
  ) -> None:
1437
1436
  """
1438
1437
  Interactively read an EPUB file in the terminal.
@@ -1,70 +1,15 @@
1
1
  """Text formatting utilities for different output styles."""
2
2
 
3
3
  import re
4
- from typing import Callable, Optional
5
-
6
- # Type aliases for phrasplit functions
7
- _SplitFunc = Callable[[str, str], list[str]]
8
- _SplitLongLinesFunc = Callable[[str, int, str], list[str]]
9
-
10
- # Try to import phrasplit, fall back to None when not available
11
- PHRASPLIT_AVAILABLE: bool
12
- phrasplit_clauses: Optional[_SplitFunc]
13
- phrasplit_long_lines: Optional[_SplitLongLinesFunc]
14
- phrasplit_paragraphs: Optional[Callable[[str], list[str]]]
15
- phrasplit_sentences: Optional[_SplitFunc]
16
-
17
- try:
18
- from phrasplit import ( # type: ignore[import-not-found]
19
- split_clauses as _phrasplit_clauses,
20
- )
21
- from phrasplit import (
22
- split_long_lines as _phrasplit_long_lines,
23
- )
24
- from phrasplit import (
25
- split_paragraphs as _phrasplit_paragraphs,
26
- )
27
- from phrasplit import (
28
- split_sentences as _phrasplit_sentences,
29
- )
30
-
31
- PHRASPLIT_AVAILABLE = True
32
- phrasplit_clauses = _phrasplit_clauses
33
- phrasplit_long_lines = _phrasplit_long_lines
34
- phrasplit_paragraphs = _phrasplit_paragraphs
35
- phrasplit_sentences = _phrasplit_sentences
36
- except ImportError:
37
- PHRASPLIT_AVAILABLE = False
38
- phrasplit_clauses = None
39
- phrasplit_long_lines = None
40
- phrasplit_paragraphs = None
41
- phrasplit_sentences = None
42
-
43
-
44
- def _check_phrasplit() -> None:
45
- """Check if phrasplit is available, raise ImportError if not."""
46
- if not PHRASPLIT_AVAILABLE:
47
- raise ImportError(
48
- "phrasplit is required for this feature. "
49
- "Install with: pip install epub2text[sentences]"
50
- )
51
-
52
-
53
- def split_paragraphs(text: str) -> list[str]:
54
- """
55
- Split text into paragraphs (separated by double newlines).
56
4
 
57
- This function works without phrasplit by using a simple regex split.
58
-
59
- Args:
60
- text: Input text
61
-
62
- Returns:
63
- List of paragraphs (non-empty, stripped)
64
- """
65
- # Simple implementation that doesn't require phrasplit
66
- paragraphs = re.split(r"\n\s*\n", text)
67
- return [p.strip() for p in paragraphs if p.strip()]
5
+ from phrasplit import (
6
+ split_clauses,
7
+ split_paragraphs,
8
+ split_sentences,
9
+ )
10
+ from phrasplit import (
11
+ split_long_lines as _phrasplit_split_long_lines,
12
+ )
68
13
 
69
14
 
70
15
  def collapse_paragraph(paragraph: str) -> str:
@@ -146,8 +91,6 @@ def format_sentences(
146
91
  """
147
92
  Format text with one sentence per line.
148
93
 
149
- Requires phrasplit to be installed.
150
-
151
94
  Args:
152
95
  text: Input text with paragraph breaks
153
96
  separator: String to prepend at paragraph boundaries (default: " ")
@@ -157,9 +100,6 @@ def format_sentences(
157
100
  Text with one sentence per line, separator at paragraph boundaries.
158
101
  Chapter titles are preserved.
159
102
  """
160
- _check_phrasplit()
161
- assert phrasplit_sentences is not None
162
-
163
103
  paragraphs = split_paragraphs(text)
164
104
 
165
105
  if not paragraphs:
@@ -180,7 +120,7 @@ def format_sentences(
180
120
  continue
181
121
 
182
122
  # Process paragraph into sentences using phrasplit
183
- sentences = phrasplit_sentences(para, language_model)
123
+ sentences = split_sentences(para, language_model)
184
124
 
185
125
  if not sentences:
186
126
  continue
@@ -224,8 +164,6 @@ def format_clauses(
224
164
  "I do like coffee,
225
165
  and I like wine."
226
166
  """
227
- _check_phrasplit()
228
- assert phrasplit_clauses is not None
229
167
 
230
168
  paragraphs = split_paragraphs(text)
231
169
 
@@ -246,7 +184,7 @@ def format_clauses(
246
184
  continue
247
185
 
248
186
  # Process paragraph into clauses using phrasplit
249
- clauses = phrasplit_clauses(para, language_model)
187
+ clauses = split_clauses(para, language_model)
250
188
 
251
189
  if not clauses:
252
190
  continue
@@ -277,8 +215,6 @@ def split_long_lines(
277
215
  2. If still too long, split at clause boundaries (commas, semicolons, etc.)
278
216
  3. If still too long, split at word boundaries
279
217
 
280
- Requires phrasplit to be installed.
281
-
282
218
  Args:
283
219
  text: Input text (may already be formatted)
284
220
  max_length: Maximum line length in characters
@@ -288,9 +224,6 @@ def split_long_lines(
288
224
  Returns:
289
225
  Text with long lines split. Chapter titles are preserved.
290
226
  """
291
- _check_phrasplit()
292
- assert phrasplit_long_lines is not None
293
-
294
227
  lines = text.split("\n")
295
228
  result_lines: list[str] = []
296
229
 
@@ -315,9 +248,13 @@ def split_long_lines(
315
248
  has_separator = line.startswith(separator) if separator else False
316
249
  content = line[len(separator) :] if has_separator else line
317
250
 
318
- # Split the long line using phrasplit
319
- split_lines_list = phrasplit_long_lines(content, max_length, language_model)
320
-
251
+ # Split the long line using phrasplit (avoid calling ourselves)
252
+ split_lines_list = _phrasplit_split_long_lines(
253
+ content, max_length, language_model
254
+ )
255
+ # Defensive: if upstream ever returns a string, normalize to list
256
+ if isinstance(split_lines_list, str):
257
+ split_lines_list = [split_lines_list]
321
258
  # Add separator to first line if original had it
322
259
  for k, split_line in enumerate(split_lines_list):
323
260
  if k == 0 and has_separator:
@@ -328,32 +265,11 @@ def split_long_lines(
328
265
  return "\n".join(result_lines)
329
266
 
330
267
 
331
- # Keep backward compatibility alias
332
- def format_as_sentences(text: str, language_model: str = "en_core_web_sm") -> str:
333
- """
334
- Format text with one sentence per line using spaCy.
335
-
336
- Deprecated: Use format_sentences() instead.
337
-
338
- Requires phrasplit to be installed.
339
-
340
- Args:
341
- text: Input text with paragraph breaks
342
- language_model: spaCy language model to use (default: "en_core_web_sm")
343
-
344
- Returns:
345
- Text with sentences separated by newlines
346
- """
347
- return format_sentences(text, separator="", language_model=language_model)
348
-
349
-
350
268
  __all__ = [
351
- "PHRASPLIT_AVAILABLE",
352
269
  "collapse_paragraph",
353
270
  "format_paragraphs",
354
271
  "format_sentences",
355
272
  "format_clauses",
356
273
  "split_long_lines",
357
- "format_as_sentences",
358
274
  "split_paragraphs",
359
275
  ]
@@ -2,7 +2,6 @@
2
2
 
3
3
  from dataclasses import dataclass, field
4
4
  from enum import Enum
5
- from typing import Optional
6
5
 
7
6
 
8
7
  class PageSource(Enum):
@@ -25,8 +24,8 @@ class Page:
25
24
  text: str
26
25
  char_count: int
27
26
  source: PageSource
28
- chapter_id: Optional[str] = None # Which chapter this page belongs to
29
- chapter_title: Optional[str] = None # Title of the chapter
27
+ chapter_id: str | None = None # Which chapter this page belongs to
28
+ chapter_title: str | None = None # Title of the chapter
30
29
 
31
30
  def __str__(self) -> str:
32
31
  source_str = "print" if self.source == PageSource.EPUB_PAGE_LIST else "syn"
@@ -41,7 +40,7 @@ class Chapter:
41
40
  title: str
42
41
  text: str
43
42
  char_count: int
44
- parent_id: Optional[str] = None
43
+ parent_id: str | None = None
45
44
  level: int = 0
46
45
 
47
46
  def __str__(self) -> str:
@@ -52,18 +51,18 @@ class Chapter:
52
51
  class Metadata:
53
52
  """EPUB metadata."""
54
53
 
55
- title: Optional[str] = None
54
+ title: str | None = None
56
55
  authors: list[str] = field(default_factory=list)
57
- publisher: Optional[str] = None
58
- publication_year: Optional[str] = None
59
- description: Optional[str] = None
56
+ publisher: str | None = None
57
+ publication_year: str | None = None
58
+ description: str | None = None
60
59
  # Required EPUB3 metadata
61
- identifier: Optional[str] = None
62
- language: Optional[str] = None
60
+ identifier: str | None = None
61
+ language: str | None = None
63
62
  # Optional Dublin Core metadata
64
63
  contributors: list[str] = field(default_factory=list)
65
- rights: Optional[str] = None
66
- coverage: Optional[str] = None
64
+ rights: str | None = None
65
+ coverage: str | None = None
67
66
 
68
67
  def __str__(self) -> str:
69
68
  lines = []