Skip to content

Commit 0d96daf

Browse files
Issue 235 - Error getting reference to Jonah (#236)
* correctly failing unit test * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix John regular expression to avoid confusion with Jonah * refactor book regular expressions to be easier to read/understand (especially John) * bump version and update CHANGELOG --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 5a516cb commit 0d96daf

10 files changed

Lines changed: 138 additions & 33 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.15.5] - 2026-01-24
11+
12+
### Fixed
13+
14+
- [Issue #235](https://github.com/avendesora/pythonbible/issues/235) - Error getting reference to Jonah
15+
1016
## [0.15.4] - 2026-01-01
1117

1218
### Changed
@@ -234,7 +240,8 @@ The goal of this release was to address [Issue #90], and to make things related
234240

235241
## [0.0.1] - 2020-10-08
236242

237-
[unreleased]: https://github.com/avendesora/pythonbible/compare/v0.15.4...HEAD
243+
[unreleased]: https://github.com/avendesora/pythonbible/compare/v0.15.5...HEAD
244+
[0.15.5]: https://github.com/avendesora/pythonbible/compare/v0.15.4...v0.15.5
238245
[0.15.4]: https://github.com/avendesora/pythonbible/compare/v0.15.3...v0.15.4
239246
[0.15.3]: https://github.com/avendesora/pythonbible/compare/v0.15.2...v0.15.3
240247
[0.15.2]: https://github.com/avendesora/pythonbible/compare/v0.15.1...v0.15.2

docs/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "pythonbible-docs"
3-
version = "0.15.4"
3+
version = "0.15.5"
44
description-file = "README.md"
55
requires-python = ">=3.13"
66
authors = [

docs/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
author = "Nathan Patton"
2424

2525
# The full version, including alpha/beta/rc tags
26-
release = "0.15.4"
26+
release = "0.15.5"
2727

2828

2929
# -- General configuration ---------------------------------------------------

pythonbible/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ module-name = "pythonbible"
1111

1212
[project]
1313
name = "pythonbible"
14-
version = "0.15.4"
14+
version = "0.15.5"
1515
description-file = "README.md"
1616
requires-python = ">=3.10"
1717
authors = [
@@ -172,7 +172,7 @@ ignore = [
172172

173173
[tool.ruff.lint.per-file-ignores]
174174
"pythonbible/bible/bible.py" = ["PLR0913", "FBT001", "FBT002"]
175-
"pythonbible/books.py" = ["ARG004", "PYI034"]
175+
"pythonbible/books/__init__.py" = ["ARG004", "PYI034"]
176176
"pythonbible/book_groups.py" = ["ARG004", "PYI034"]
177177
"pythonbible/formatter.py" = ["ANN401", "FBT001", "FBT002", "PLR0911"]
178178
"pythonbible/parser.py" = ["PLR2004"]

pythonbible/pythonbible/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from __future__ import annotations
1010

11-
__version__ = "0.15.4"
11+
__version__ = "0.15.5"
1212

1313
from .bible import add_bible
1414
from .bible import get_bible
Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44
from typing import Any
55
from typing import Type
66

7+
from pythonbible.books.common_constants import FIRST
8+
from pythonbible.books.common_constants import FIRST_BOOK
9+
from pythonbible.books.common_constants import SECOND
10+
from pythonbible.books.common_constants import SECOND_BOOK
11+
from pythonbible.books.common_constants import THIRD
12+
from pythonbible.books.john import JOHN_REGULAR_EXPRESSION
13+
714

815
def _build_book_regular_expression(
916
book: str,
@@ -24,30 +31,22 @@ def _add_suffix(regex: str, suffix: str | None = None) -> str:
2431
_SAMUEL_REGULAR_EXPRESSION = r"(Samuel|Sam\.*|Sa\.*|Sm\.*)"
2532
_KINGS_REGULAR_EXPRESSION = r"(Kings|Kgs\.*|Kin\.*|Ki\.*)"
2633
_CHRONICLES_REGULAR_EXPRESSION = r"(Chronicles|Chron\.*|Chro\.*|Chr\.*|Ch\.*)"
27-
_JOHN_REGULAR_EXPRESSION = r"(John|Joh\.*|Jhn\.*|Jo\.*(?!shua|b|nah|el)|Jn\.*)"
2834
_CORINTHIANS_REGULAR_EXPRESSION = r"Co\.*(?:r\.*(?:inthians)?)?"
2935
_THESSALONIANS_REGULAR_EXPRESSION = r"Th\.*(?:(s|(es(?:s)?))\.*(?:alonians)?)?"
3036
_TIMOTHY_REGULAR_EXPRESSION = r"Ti\.*(?:m\.*(?:othy)?)?"
3137
_PETER_REGULAR_EXPRESSION = r"(Pe\.*(?:t\.*(?:er)?)?|Pt\.*)"
3238

3339
_MACCABEES_REGULAR_EXPRESSION = r"(Maccabees|Macc\.*|Mac\.*|Ma\.*|M\.*)"
3440

35-
_FIRST = r"1|I\s+|1st\s+|First\s+"
36-
_SECOND = r"2|II|2nd\s+|Second\s+"
37-
_THIRD = r"3|III|3rd\s+|Third\s+"
38-
39-
_FIRST_BOOK = rf"{_FIRST}|(First\s+Book\s+of(?:\s+the)?)"
40-
_SECOND_BOOK = rf"{_SECOND}|(Second\s+Book\s+of(?:\s+the)?)"
41-
4241
_EPISTLE_OF_PAUL_TO = r"Epistle\s+of\s+Paul\s+(?:the\s+Apostle\s+)?to(?:\s+the)?"
4342
_GENERAL_EPISTLE_OF = r"(?:General\s+)?Epistle\s+(?:General\s+)?of"
4443

45-
_FIRST_PAUL_EPISTLE = rf"{_FIRST}|(First\s+{_EPISTLE_OF_PAUL_TO})"
46-
_SECOND_PAUL_EPISTLE = rf"{_SECOND}|(Second\s+{_EPISTLE_OF_PAUL_TO})"
44+
_FIRST_PAUL_EPISTLE = rf"{FIRST}|(First\s+{_EPISTLE_OF_PAUL_TO})"
45+
_SECOND_PAUL_EPISTLE = rf"{SECOND}|(Second\s+{_EPISTLE_OF_PAUL_TO})"
4746

48-
_FIRST_GENERAL_EPISTLE = rf"{_FIRST}|(First\s+{_GENERAL_EPISTLE_OF})"
49-
_SECOND_GENERAL_EPISTLE = rf"{_SECOND}|(Second\s+{_GENERAL_EPISTLE_OF})"
50-
_THIRD_GENERAL_EPISTLE = rf"{_THIRD}|(Third\s+{_GENERAL_EPISTLE_OF})"
47+
_FIRST_GENERAL_EPISTLE = rf"{FIRST}|(First\s+{_GENERAL_EPISTLE_OF})"
48+
_SECOND_GENERAL_EPISTLE = rf"{SECOND}|(Second\s+{_GENERAL_EPISTLE_OF})"
49+
_THIRD_GENERAL_EPISTLE = rf"{THIRD}|(Third\s+{_GENERAL_EPISTLE_OF})"
5150

5251

5352
class Book(Enum):
@@ -111,7 +110,7 @@ def abbreviations(self: Book) -> tuple[str, ...]:
111110
"1 Samuel",
112111
_build_book_regular_expression(
113112
_SAMUEL_REGULAR_EXPRESSION,
114-
prefix=_FIRST_BOOK,
113+
prefix=FIRST_BOOK,
115114
suffix=r"Otherwise\s+Called\s+The\s+First\s+Book\s+of\s+the\s+Kings",
116115
),
117116
("Sa", "Sam", "Sm"),
@@ -121,7 +120,7 @@ def abbreviations(self: Book) -> tuple[str, ...]:
121120
"2 Samuel",
122121
_build_book_regular_expression(
123122
_SAMUEL_REGULAR_EXPRESSION,
124-
prefix=_SECOND_BOOK,
123+
prefix=SECOND_BOOK,
125124
suffix=r"Otherwise\s+Called\s+The\s+Second\s+Book\s+of\s+the\s+Kings",
126125
),
127126
("Sa", "Sam", "Sm"),
@@ -131,7 +130,7 @@ def abbreviations(self: Book) -> tuple[str, ...]:
131130
"1 Kings",
132131
_build_book_regular_expression(
133132
_KINGS_REGULAR_EXPRESSION,
134-
prefix=_FIRST_BOOK,
133+
prefix=FIRST_BOOK,
135134
suffix=r"\,\s+Commonly\s+Called\s+the\s+Third\s+Book\s+of\s+the\s+Kings",
136135
),
137136
("Kgs", "Ki", "Kin"),
@@ -141,7 +140,7 @@ def abbreviations(self: Book) -> tuple[str, ...]:
141140
"2 Kings",
142141
_build_book_regular_expression(
143142
_KINGS_REGULAR_EXPRESSION,
144-
prefix=_SECOND_BOOK,
143+
prefix=SECOND_BOOK,
145144
suffix=r"\,\s+Commonly\s+Called\s+the\s+Fourth\s+Book\s+of\s+the\s+Kings",
146145
),
147146
("Kgs", "Ki", "Kin"),
@@ -151,7 +150,7 @@ def abbreviations(self: Book) -> tuple[str, ...]:
151150
"1 Chronicles",
152151
_build_book_regular_expression(
153152
_CHRONICLES_REGULAR_EXPRESSION,
154-
prefix=_FIRST_BOOK,
153+
prefix=FIRST_BOOK,
155154
),
156155
("Ch", "Chr", "Chro", "Chron"),
157156
)
@@ -160,7 +159,7 @@ def abbreviations(self: Book) -> tuple[str, ...]:
160159
"2 Chronicles",
161160
_build_book_regular_expression(
162161
_CHRONICLES_REGULAR_EXPRESSION,
163-
prefix=_SECOND_BOOK,
162+
prefix=SECOND_BOOK,
164163
),
165164
("Ch", "Chr", "Chro", "Chron"),
166165
)
@@ -229,7 +228,7 @@ def abbreviations(self: Book) -> tuple[str, ...]:
229228
JOHN = (
230229
43,
231230
"John",
232-
rf"(?<!(?:1|2|3|I)\s)(?<!(?:1|2|3|I)){_JOHN_REGULAR_EXPRESSION}",
231+
rf"(?<!(?:1|2|3|I)\s)(?<!(?:1|2|3|I)){JOHN_REGULAR_EXPRESSION}",
233232
("Jhn", "Jn", "Jo", "Joh"),
234233
)
235234
ACTS = (
@@ -336,7 +335,7 @@ def abbreviations(self: Book) -> tuple[str, ...]:
336335
62,
337336
"1 John",
338337
_build_book_regular_expression(
339-
_JOHN_REGULAR_EXPRESSION,
338+
JOHN_REGULAR_EXPRESSION,
340339
prefix=_FIRST_GENERAL_EPISTLE,
341340
),
342341
("Jhn", "Jn", "Jo", "Joh"),
@@ -345,7 +344,7 @@ def abbreviations(self: Book) -> tuple[str, ...]:
345344
63,
346345
"2 John",
347346
_build_book_regular_expression(
348-
_JOHN_REGULAR_EXPRESSION,
347+
JOHN_REGULAR_EXPRESSION,
349348
prefix=_SECOND_GENERAL_EPISTLE,
350349
),
351350
("Jhn", "Jn", "Jo", "Joh"),
@@ -354,7 +353,7 @@ def abbreviations(self: Book) -> tuple[str, ...]:
354353
64,
355354
"3 John",
356355
_build_book_regular_expression(
357-
_JOHN_REGULAR_EXPRESSION,
356+
JOHN_REGULAR_EXPRESSION,
358357
prefix=_THIRD_GENERAL_EPISTLE,
359358
),
360359
("Jhn", "Jn", "Jo", "Joh"),
@@ -374,7 +373,7 @@ def abbreviations(self: Book) -> tuple[str, ...]:
374373
"1 Esdras",
375374
_build_book_regular_expression(
376375
r"(Esdras|Esdr\.*|Esd\.*|Es\.*)",
377-
_FIRST,
376+
FIRST,
378377
),
379378
("Es", "Esd", "Esdr"),
380379
)
@@ -396,7 +395,7 @@ def abbreviations(self: Book) -> tuple[str, ...]:
396395
"1 Maccabees",
397396
_build_book_regular_expression(
398397
_MACCABEES_REGULAR_EXPRESSION,
399-
_FIRST,
398+
FIRST,
400399
),
401400
("M", "Ma", "Mac", "Macc"),
402401
)
@@ -405,7 +404,7 @@ def abbreviations(self: Book) -> tuple[str, ...]:
405404
"2 Maccabees",
406405
_build_book_regular_expression(
407406
_MACCABEES_REGULAR_EXPRESSION,
408-
_SECOND,
407+
SECOND,
409408
),
410409
("M", "Ma", "Mac", "Macc"),
411410
)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""Common constants used in Book regular expressions."""
2+
3+
FIRST = r"1|I\s+|1st\s+|First\s+"
4+
SECOND = r"2|II|2nd\s+|Second\s+"
5+
THIRD = r"3|III|3rd\s+|Third\s+"
6+
7+
FIRST_BOOK = rf"{FIRST}|(First\s+Book\s+of(?:\s+the)?)"
8+
SECOND_BOOK = rf"{SECOND}|(Second\s+Book\s+of(?:\s+the)?)"
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""John book regular expression definitions."""
2+
3+
import re
4+
5+
_JO_EXCLUDE_SUFFIXES: tuple[str, ...] = (
6+
"shua", # Joshua
7+
"b", # Job
8+
"nah", # Jonah
9+
"n", # Jonah (abbreviation)
10+
"el", # Joel
11+
)
12+
13+
_JO_EXCLUDED_SUFFIXES_PATTERN = "|".join(map(re.escape, _JO_EXCLUDE_SUFFIXES))
14+
_JO_NEGATIVE_LOOKAHEAD = f"(?!{_JO_EXCLUDED_SUFFIXES_PATTERN})"
15+
16+
_ABBREVIATIONS: tuple[str, ...] = (
17+
r"Joh\.*",
18+
r"Jhn\.*",
19+
r"Jo\.*" + _JO_NEGATIVE_LOOKAHEAD,
20+
r"Jn\.*",
21+
)
22+
23+
JOHN_REGULAR_EXPRESSION = rf"(John|{'|'.join(_ABBREVIATIONS)})"

pythonbible/tests/parser/parser_test.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
import pytest
4+
35
import pythonbible as bible
46

57

@@ -334,3 +336,69 @@ def test_book_abbreviations() -> None:
334336

335337
if book != book.SONG_OF_SONGS:
336338
assert expected == actual_period
339+
340+
341+
@pytest.mark.parametrize(
342+
("input_reference", "expected_output"),
343+
[
344+
(
345+
"micah-jon",
346+
bible.NormalizedReference(
347+
book=bible.Book.MICAH,
348+
start_chapter=None,
349+
start_verse=None,
350+
end_chapter=None,
351+
end_verse=None,
352+
end_book=bible.Book.JONAH,
353+
),
354+
),
355+
(
356+
"jon-jon",
357+
bible.NormalizedReference(
358+
book=bible.Book.JONAH,
359+
start_chapter=None,
360+
start_verse=None,
361+
end_chapter=None,
362+
end_verse=None,
363+
end_book=bible.Book.JONAH,
364+
),
365+
),
366+
(
367+
"obadiah-jon",
368+
bible.NormalizedReference(
369+
book=bible.Book.OBADIAH,
370+
start_chapter=None,
371+
start_verse=None,
372+
end_chapter=None,
373+
end_verse=None,
374+
end_book=bible.Book.JONAH,
375+
),
376+
),
377+
(
378+
"jon-john",
379+
bible.NormalizedReference(
380+
book=bible.Book.JONAH,
381+
start_chapter=None,
382+
start_verse=None,
383+
end_chapter=None,
384+
end_verse=None,
385+
end_book=bible.Book.JOHN,
386+
),
387+
),
388+
],
389+
)
390+
def test_issue_235_jonah_abbreviation(
391+
input_reference: str,
392+
expected_output: bible.NormalizedReference,
393+
) -> None:
394+
"""Test for Issue 235 "Error getting reference to Jonah".
395+
396+
:param input_reference: The input reference string to test.
397+
:param expected_output: The expected normalized reference output.
398+
"""
399+
# Given a text string with an abbreviation for the book of Jonah
400+
# When we parse the references from that text
401+
references: list[bible.NormalizedReference] = bible.get_references(input_reference)
402+
403+
# Then the parser returns the appropriate normalized reference
404+
assert references == [expected_output]

pythonbible/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)