async def get_passage(self, bible: Bible, verses: VerseRange, /) -> Passage: async with self.session.get( self._passage_url.with_query({ 'search': str(verses), 'version': bible.service_version, 'interface': 'print', })) as response: text = await response.text(errors='replace') strainer = SoupStrainer(class_=re.compile( re.WORD_BOUNDARY, 'result-text-style-', re.either('normal', 'rtl'), re.WORD_BOUNDARY, )) soup = BeautifulSoup(text, 'html.parser', parse_only=strainer) verse_block = soup.select_one( '.result-text-style-normal, .result-text-style-rtl') if verse_block is None: raise DoNotUnderstandError return self.__transform_verse_node(bible, verses, verse_block)
from __future__ import annotations from typing import Final from attrs import define, field from botus_receptus import re from bs4 import BeautifulSoup from yarl import URL from ..data import Passage, SearchResults, VerseRange from ..exceptions import DoNotUnderstandError from ..types import Bible from .base_service import BaseService _number_re: Final = re.compile(re.capture(re.one_or_more(re.DIGITS), re.DOT)) _book_map: Final[dict[str, str]] = { 'Genesis': '01O', 'Exodus': '02O', 'Leviticus': '03O', 'Numbers': '04O', 'Deuteronomy': '05O', 'Joshua': '06O', 'Judges': '07O', 'Ruth': '08O', '1 Samuel': '09O', '2 Samuel': '10O', '1 Kings': '11O', '2 Kings': '12O', '1 Chronicles': '13O', '2 Chronicles': '14O',
class BookDict(TypedDict): name: str osis: str alt: List[str] section: int with (Path(__file__).resolve().parent / 'data' / 'books.json').open() as f: books_data: List[BookDict] = load(f) # Inspired by # https://github.com/TehShrike/verse-reference-regex/blob/master/create-regex.js _book_re = re.compile( re.named_group('book')(re.either(*re.escape_all( unique_everseen( chain.from_iterable([[book['name'], book['osis']] + book['alt'] for book in books_data]))))), re.optional(re.DOT), ) _chapter_start_group = re.named_group('chapter_start') _chapter_end_group = re.named_group('chapter_end') _verse_start_group = re.named_group('verse_start') _verse_end_group = re.named_group('verse_end') _version_group = re.named_group('version') _one_or_more_digit = re.one_or_more(re.DIGIT) _colon = re.combine(re.any_number_of(re.WHITESPACE), ':', re.any_number_of(re.WHITESPACE)) _reference_re = re.compile( _book_re,
from __future__ import annotations from typing import Final from attrs import define, field from botus_receptus import re from bs4 import BeautifulSoup, NavigableString, SoupStrainer, Tag from yarl import URL from ..data import Passage, SearchResults, VerseRange from ..exceptions import DoNotUnderstandError from ..types import Bible from .base_service import BaseService _total_re: Final = re.compile( re.START, re.named_group('total')(re.one_or_more(re.DIGITS))) @define class BibleGateway(BaseService): _passage_url: URL = field(init=False) _search_url: URL = field(init=False) def __attrs_post_init__(self, /) -> None: self._passage_url = URL('https://www.biblegateway.com/passage/') self._search_url = URL('https://www.biblegateway.com/quicksearch/') def __transform_verse_node( self, bible: Bible,
from typing import Any, Optional, List, Dict, AsyncIterator from typing_extensions import TypedDict from attr import dataclass, attrib from aiohttp import BasicAuth from botus_receptus import re from bs4 import BeautifulSoup from contextlib import asynccontextmanager from yarl import URL from .base_service import BaseService from ..data import VerseRange, SearchResults, Passage from ..exceptions import DoNotUnderstandError from ..json import loads, get, has from ..protocols import Bible _img_re = re.compile('src="', re.named_group('src')('[^"]+'), '"') class SummaryDict(TypedDict): total: int class VerseDict(TypedDict): reference: str text: str class SearchResultDict(TypedDict): summary: SummaryDict verses: List[VerseDict]
from __future__ import annotations import logging from abc import abstractmethod from typing import Any, Final import aiohttp from attrs import define from botus_receptus import re from ..data import Passage, SearchResults, VerseRange from ..types import Bible _log: Final = logging.getLogger(__name__) _whitespace_re: Final = re.compile(re.one_or_more(re.WHITESPACE)) _punctuation_re: Final = re.compile(re.one_or_more(re.WHITESPACE), re.capture(r'[,.;:]'), re.one_or_more(re.WHITESPACE)) _bold_re: Final = re.compile(r'__BOLD__') _italic_re: Final = re.compile(r'__ITALIC__') _specials_re: Final = re.compile(re.capture(r'[\*`]')) _number_re: Final = re.compile( re.capture(r'\*\*', re.one_or_more(re.DIGIT), re.DOT, r'\*\*')) @define class BaseService(object): session: aiohttp.ClientSession config: dict[str, Any] | None
import logging from typing import Any, Optional, Dict, List, AsyncContextManager, AsyncIterator from abc import abstractmethod from attr import dataclass from contextlib import asynccontextmanager from botus_receptus import re from yarl import URL from ..data import VerseRange, Passage, SearchResults from ..protocols import Bible from ..exceptions import ServiceLookupTimeout, ServiceSearchTimeout log = logging.getLogger(__name__) whitespace_re = re.compile(re.one_or_more(re.WHITESPACE)) bold_re = re.compile(r'__BOLD__') italic_re = re.compile(r'__ITALIC__') specials_re = re.compile(re.capture(r'[\*`]')) number_re = re.compile( re.capture(r'\*\*', re.one_or_more(re.DIGIT), re.DOT, r'\*\*')) @dataclass(slots=True) class BaseService(object): session: aiohttp.ClientSession config: Optional[Dict[str, Any]] async def get_passage(self, bible: Bible, verses: VerseRange) -> Passage: log.debug(f'Getting passage {verses} ({bible.abbr})')