diff --git a/CHANGELOG.md b/CHANGELOG.md index c2f06d13..0927888a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.15.5] - 2026-01-24 + +### Fixed + +- [Issue #235](https://github.com/avendesora/pythonbible/issues/235) - Error getting reference to Jonah + ## [0.15.4] - 2026-01-01 ### Changed @@ -234,7 +240,8 @@ The goal of this release was to address [Issue #90], and to make things related ## [0.0.1] - 2020-10-08 -[unreleased]: https://github.com/avendesora/pythonbible/compare/v0.15.4...HEAD +[unreleased]: https://github.com/avendesora/pythonbible/compare/v0.15.5...HEAD +[0.15.5]: https://github.com/avendesora/pythonbible/compare/v0.15.4...v0.15.5 [0.15.4]: https://github.com/avendesora/pythonbible/compare/v0.15.3...v0.15.4 [0.15.3]: https://github.com/avendesora/pythonbible/compare/v0.15.2...v0.15.3 [0.15.2]: https://github.com/avendesora/pythonbible/compare/v0.15.1...v0.15.2 diff --git a/docs/pyproject.toml b/docs/pyproject.toml index cbd27c04..00ab089d 100644 --- a/docs/pyproject.toml +++ b/docs/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pythonbible-docs" -version = "0.15.4" +version = "0.15.5" description-file = "README.md" requires-python = ">=3.13" authors = [ diff --git a/docs/source/conf.py b/docs/source/conf.py index ba1eb88c..b85d4ee3 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -23,7 +23,7 @@ author = "Nathan Patton" # The full version, including alpha/beta/rc tags -release = "0.15.4" +release = "0.15.5" # -- General configuration --------------------------------------------------- diff --git a/pythonbible/pyproject.toml b/pythonbible/pyproject.toml index 8feeaabd..32e14b28 100644 --- a/pythonbible/pyproject.toml +++ b/pythonbible/pyproject.toml @@ -11,7 +11,7 @@ module-name = "pythonbible" [project] name = "pythonbible" -version = "0.15.4" +version = "0.15.5" description-file = "README.md" requires-python = ">=3.10" authors = [ @@ -172,7 +172,7 @@ ignore = [ [tool.ruff.lint.per-file-ignores] "pythonbible/bible/bible.py" = ["PLR0913", "FBT001", "FBT002"] -"pythonbible/books.py" = ["ARG004", "PYI034"] +"pythonbible/books/__init__.py" = ["ARG004", "PYI034"] "pythonbible/book_groups.py" = ["ARG004", "PYI034"] "pythonbible/formatter.py" = ["ANN401", "FBT001", "FBT002", "PLR0911"] "pythonbible/parser.py" = ["PLR2004"] diff --git a/pythonbible/pythonbible/__init__.py b/pythonbible/pythonbible/__init__.py index fbdbeda2..bb6f6590 100644 --- a/pythonbible/pythonbible/__init__.py +++ b/pythonbible/pythonbible/__init__.py @@ -8,7 +8,7 @@ from __future__ import annotations -__version__ = "0.15.4" +__version__ = "0.15.5" from .bible import add_bible from .bible import get_bible diff --git a/pythonbible/pythonbible/books.py b/pythonbible/pythonbible/books/__init__.py similarity index 91% rename from pythonbible/pythonbible/books.py rename to pythonbible/pythonbible/books/__init__.py index 209da292..f172c29b 100644 --- a/pythonbible/pythonbible/books.py +++ b/pythonbible/pythonbible/books/__init__.py @@ -4,6 +4,13 @@ from typing import Any from typing import Type +from pythonbible.books.common_constants import FIRST +from pythonbible.books.common_constants import FIRST_BOOK +from pythonbible.books.common_constants import SECOND +from pythonbible.books.common_constants import SECOND_BOOK +from pythonbible.books.common_constants import THIRD +from pythonbible.books.john import JOHN_REGULAR_EXPRESSION + def _build_book_regular_expression( book: str, @@ -24,7 +31,6 @@ def _add_suffix(regex: str, suffix: str | None = None) -> str: _SAMUEL_REGULAR_EXPRESSION = r"(Samuel|Sam\.*|Sa\.*|Sm\.*)" _KINGS_REGULAR_EXPRESSION = r"(Kings|Kgs\.*|Kin\.*|Ki\.*)" _CHRONICLES_REGULAR_EXPRESSION = r"(Chronicles|Chron\.*|Chro\.*|Chr\.*|Ch\.*)" -_JOHN_REGULAR_EXPRESSION = r"(John|Joh\.*|Jhn\.*|Jo\.*(?!shua|b|nah|el)|Jn\.*)" _CORINTHIANS_REGULAR_EXPRESSION = r"Co\.*(?:r\.*(?:inthians)?)?" _THESSALONIANS_REGULAR_EXPRESSION = r"Th\.*(?:(s|(es(?:s)?))\.*(?:alonians)?)?" _TIMOTHY_REGULAR_EXPRESSION = r"Ti\.*(?:m\.*(?:othy)?)?" @@ -32,22 +38,15 @@ def _add_suffix(regex: str, suffix: str | None = None) -> str: _MACCABEES_REGULAR_EXPRESSION = r"(Maccabees|Macc\.*|Mac\.*|Ma\.*|M\.*)" -_FIRST = r"1|I\s+|1st\s+|First\s+" -_SECOND = r"2|II|2nd\s+|Second\s+" -_THIRD = r"3|III|3rd\s+|Third\s+" - -_FIRST_BOOK = rf"{_FIRST}|(First\s+Book\s+of(?:\s+the)?)" -_SECOND_BOOK = rf"{_SECOND}|(Second\s+Book\s+of(?:\s+the)?)" - _EPISTLE_OF_PAUL_TO = r"Epistle\s+of\s+Paul\s+(?:the\s+Apostle\s+)?to(?:\s+the)?" _GENERAL_EPISTLE_OF = r"(?:General\s+)?Epistle\s+(?:General\s+)?of" -_FIRST_PAUL_EPISTLE = rf"{_FIRST}|(First\s+{_EPISTLE_OF_PAUL_TO})" -_SECOND_PAUL_EPISTLE = rf"{_SECOND}|(Second\s+{_EPISTLE_OF_PAUL_TO})" +_FIRST_PAUL_EPISTLE = rf"{FIRST}|(First\s+{_EPISTLE_OF_PAUL_TO})" +_SECOND_PAUL_EPISTLE = rf"{SECOND}|(Second\s+{_EPISTLE_OF_PAUL_TO})" -_FIRST_GENERAL_EPISTLE = rf"{_FIRST}|(First\s+{_GENERAL_EPISTLE_OF})" -_SECOND_GENERAL_EPISTLE = rf"{_SECOND}|(Second\s+{_GENERAL_EPISTLE_OF})" -_THIRD_GENERAL_EPISTLE = rf"{_THIRD}|(Third\s+{_GENERAL_EPISTLE_OF})" +_FIRST_GENERAL_EPISTLE = rf"{FIRST}|(First\s+{_GENERAL_EPISTLE_OF})" +_SECOND_GENERAL_EPISTLE = rf"{SECOND}|(Second\s+{_GENERAL_EPISTLE_OF})" +_THIRD_GENERAL_EPISTLE = rf"{THIRD}|(Third\s+{_GENERAL_EPISTLE_OF})" class Book(Enum): @@ -111,7 +110,7 @@ def abbreviations(self: Book) -> tuple[str, ...]: "1 Samuel", _build_book_regular_expression( _SAMUEL_REGULAR_EXPRESSION, - prefix=_FIRST_BOOK, + prefix=FIRST_BOOK, suffix=r"Otherwise\s+Called\s+The\s+First\s+Book\s+of\s+the\s+Kings", ), ("Sa", "Sam", "Sm"), @@ -121,7 +120,7 @@ def abbreviations(self: Book) -> tuple[str, ...]: "2 Samuel", _build_book_regular_expression( _SAMUEL_REGULAR_EXPRESSION, - prefix=_SECOND_BOOK, + prefix=SECOND_BOOK, suffix=r"Otherwise\s+Called\s+The\s+Second\s+Book\s+of\s+the\s+Kings", ), ("Sa", "Sam", "Sm"), @@ -131,7 +130,7 @@ def abbreviations(self: Book) -> tuple[str, ...]: "1 Kings", _build_book_regular_expression( _KINGS_REGULAR_EXPRESSION, - prefix=_FIRST_BOOK, + prefix=FIRST_BOOK, suffix=r"\,\s+Commonly\s+Called\s+the\s+Third\s+Book\s+of\s+the\s+Kings", ), ("Kgs", "Ki", "Kin"), @@ -141,7 +140,7 @@ def abbreviations(self: Book) -> tuple[str, ...]: "2 Kings", _build_book_regular_expression( _KINGS_REGULAR_EXPRESSION, - prefix=_SECOND_BOOK, + prefix=SECOND_BOOK, suffix=r"\,\s+Commonly\s+Called\s+the\s+Fourth\s+Book\s+of\s+the\s+Kings", ), ("Kgs", "Ki", "Kin"), @@ -151,7 +150,7 @@ def abbreviations(self: Book) -> tuple[str, ...]: "1 Chronicles", _build_book_regular_expression( _CHRONICLES_REGULAR_EXPRESSION, - prefix=_FIRST_BOOK, + prefix=FIRST_BOOK, ), ("Ch", "Chr", "Chro", "Chron"), ) @@ -160,7 +159,7 @@ def abbreviations(self: Book) -> tuple[str, ...]: "2 Chronicles", _build_book_regular_expression( _CHRONICLES_REGULAR_EXPRESSION, - prefix=_SECOND_BOOK, + prefix=SECOND_BOOK, ), ("Ch", "Chr", "Chro", "Chron"), ) @@ -229,7 +228,7 @@ def abbreviations(self: Book) -> tuple[str, ...]: JOHN = ( 43, "John", - rf"(? tuple[str, ...]: 62, "1 John", _build_book_regular_expression( - _JOHN_REGULAR_EXPRESSION, + JOHN_REGULAR_EXPRESSION, prefix=_FIRST_GENERAL_EPISTLE, ), ("Jhn", "Jn", "Jo", "Joh"), @@ -345,7 +344,7 @@ def abbreviations(self: Book) -> tuple[str, ...]: 63, "2 John", _build_book_regular_expression( - _JOHN_REGULAR_EXPRESSION, + JOHN_REGULAR_EXPRESSION, prefix=_SECOND_GENERAL_EPISTLE, ), ("Jhn", "Jn", "Jo", "Joh"), @@ -354,7 +353,7 @@ def abbreviations(self: Book) -> tuple[str, ...]: 64, "3 John", _build_book_regular_expression( - _JOHN_REGULAR_EXPRESSION, + JOHN_REGULAR_EXPRESSION, prefix=_THIRD_GENERAL_EPISTLE, ), ("Jhn", "Jn", "Jo", "Joh"), @@ -374,7 +373,7 @@ def abbreviations(self: Book) -> tuple[str, ...]: "1 Esdras", _build_book_regular_expression( r"(Esdras|Esdr\.*|Esd\.*|Es\.*)", - _FIRST, + FIRST, ), ("Es", "Esd", "Esdr"), ) @@ -396,7 +395,7 @@ def abbreviations(self: Book) -> tuple[str, ...]: "1 Maccabees", _build_book_regular_expression( _MACCABEES_REGULAR_EXPRESSION, - _FIRST, + FIRST, ), ("M", "Ma", "Mac", "Macc"), ) @@ -405,7 +404,7 @@ def abbreviations(self: Book) -> tuple[str, ...]: "2 Maccabees", _build_book_regular_expression( _MACCABEES_REGULAR_EXPRESSION, - _SECOND, + SECOND, ), ("M", "Ma", "Mac", "Macc"), ) diff --git a/pythonbible/pythonbible/books/common_constants.py b/pythonbible/pythonbible/books/common_constants.py new file mode 100644 index 00000000..0b9e7a38 --- /dev/null +++ b/pythonbible/pythonbible/books/common_constants.py @@ -0,0 +1,8 @@ +"""Common constants used in Book regular expressions.""" + +FIRST = r"1|I\s+|1st\s+|First\s+" +SECOND = r"2|II|2nd\s+|Second\s+" +THIRD = r"3|III|3rd\s+|Third\s+" + +FIRST_BOOK = rf"{FIRST}|(First\s+Book\s+of(?:\s+the)?)" +SECOND_BOOK = rf"{SECOND}|(Second\s+Book\s+of(?:\s+the)?)" diff --git a/pythonbible/pythonbible/books/john.py b/pythonbible/pythonbible/books/john.py new file mode 100644 index 00000000..6c321258 --- /dev/null +++ b/pythonbible/pythonbible/books/john.py @@ -0,0 +1,23 @@ +"""John book regular expression definitions.""" + +import re + +_JO_EXCLUDE_SUFFIXES: tuple[str, ...] = ( + "shua", # Joshua + "b", # Job + "nah", # Jonah + "n", # Jonah (abbreviation) + "el", # Joel +) + +_JO_EXCLUDED_SUFFIXES_PATTERN = "|".join(map(re.escape, _JO_EXCLUDE_SUFFIXES)) +_JO_NEGATIVE_LOOKAHEAD = f"(?!{_JO_EXCLUDED_SUFFIXES_PATTERN})" + +_ABBREVIATIONS: tuple[str, ...] = ( + r"Joh\.*", + r"Jhn\.*", + r"Jo\.*" + _JO_NEGATIVE_LOOKAHEAD, + r"Jn\.*", +) + +JOHN_REGULAR_EXPRESSION = rf"(John|{'|'.join(_ABBREVIATIONS)})" diff --git a/pythonbible/tests/parser/parser_test.py b/pythonbible/tests/parser/parser_test.py index 8dfa32ab..fc3a46ad 100644 --- a/pythonbible/tests/parser/parser_test.py +++ b/pythonbible/tests/parser/parser_test.py @@ -1,5 +1,7 @@ from __future__ import annotations +import pytest + import pythonbible as bible @@ -334,3 +336,69 @@ def test_book_abbreviations() -> None: if book != book.SONG_OF_SONGS: assert expected == actual_period + + +@pytest.mark.parametrize( + ("input_reference", "expected_output"), + [ + ( + "micah-jon", + bible.NormalizedReference( + book=bible.Book.MICAH, + start_chapter=None, + start_verse=None, + end_chapter=None, + end_verse=None, + end_book=bible.Book.JONAH, + ), + ), + ( + "jon-jon", + bible.NormalizedReference( + book=bible.Book.JONAH, + start_chapter=None, + start_verse=None, + end_chapter=None, + end_verse=None, + end_book=bible.Book.JONAH, + ), + ), + ( + "obadiah-jon", + bible.NormalizedReference( + book=bible.Book.OBADIAH, + start_chapter=None, + start_verse=None, + end_chapter=None, + end_verse=None, + end_book=bible.Book.JONAH, + ), + ), + ( + "jon-john", + bible.NormalizedReference( + book=bible.Book.JONAH, + start_chapter=None, + start_verse=None, + end_chapter=None, + end_verse=None, + end_book=bible.Book.JOHN, + ), + ), + ], +) +def test_issue_235_jonah_abbreviation( + input_reference: str, + expected_output: bible.NormalizedReference, +) -> None: + """Test for Issue 235 "Error getting reference to Jonah". + + :param input_reference: The input reference string to test. + :param expected_output: The expected normalized reference output. + """ + # Given a text string with an abbreviation for the book of Jonah + # When we parse the references from that text + references: list[bible.NormalizedReference] = bible.get_references(input_reference) + + # Then the parser returns the appropriate normalized reference + assert references == [expected_output] diff --git a/pythonbible/uv.lock b/pythonbible/uv.lock index 83a292f5..486b4a04 100644 --- a/pythonbible/uv.lock +++ b/pythonbible/uv.lock @@ -349,7 +349,7 @@ wheels = [ [[package]] name = "pythonbible" -version = "0.15.3" +version = "0.15.5" source = { editable = "." } dependencies = [ { name = "pythonbible-asv" },