├── .gitignore ├── .gitlab-ci.yml ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── gifs ├── 240309_gbib_demo.gif ├── 240309_gbib_random-quotes.gif ├── 5_range-of-verses-i.gif ├── 7_grep.gif ├── 8_usage.gif └── 9_grep.gif ├── grepbible ├── __init__.py ├── bible_manager.py ├── cli.py └── static │ ├── __init__.py │ ├── acronyms.txt │ ├── ch2num.py │ └── full_names.txt ├── poetry.lock ├── pyproject.toml ├── setup.py └── tests ├── __init__.py ├── kjv_counts.py ├── test.sh └── test_gbib.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *~ 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | cover/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | .pybuilder/ 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | # For a library or package, you might want to ignore these files since the code is 88 | # intended to run in multiple environments; otherwise, check them in: 89 | # .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 99 | __pypackages__/ 100 | 101 | # Celery stuff 102 | celerybeat-schedule 103 | celerybeat.pid 104 | 105 | # SageMath parsed files 106 | *.sage.py 107 | 108 | # Environments 109 | .env 110 | .venv 111 | env/ 112 | venv/ 113 | ENV/ 114 | env.bak/ 115 | venv.bak/ 116 | 117 | # Spyder project settings 118 | .spyderproject 119 | .spyproject 120 | 121 | # Rope project settings 122 | .ropeproject 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | 135 | # pytype static type analyzer 136 | .pytype/ 137 | 138 | # Cython debug symbols 139 | cython_debug/ 140 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - test 3 | - static 4 | - build 5 | - release 6 | 7 | variables: 8 | PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" 9 | 10 | cache: 11 | key: "$CI_COMMIT_REF_SLUG" 12 | paths: 13 | - .cache/pip 14 | - venv/ 15 | 16 | .python-base: 17 | image: python:3.11.7 18 | before_script: 19 | - python -V # Print out python version for debugging 20 | - pip install --progress-bar off poetry==1.4.2 # Install Poetry 21 | - poetry -vvv install --no-root 22 | 23 | mypy: 24 | stage: static 25 | extends: .python-base 26 | allow_failure: true 27 | script: 28 | - pip install mypy 29 | - python -m mypy */*.py 30 | when: manual 31 | 32 | flake8: 33 | stage: static 34 | extends: .python-base 35 | allow_failure: true 36 | script: 37 | - pip install flake8 38 | - flake8 --max-line=120 */*.py 39 | when: manual 40 | 41 | pylint: 42 | stage: static 43 | extends: .python-base 44 | allow_failure: true 45 | script: 46 | - pip install pylint 47 | - mkdir ./pylint 48 | - pylint --output-format=text */*.py | tee ./pylint/pylint.log || pylint-exit $? 49 | - PYLINT_SCORE=$(sed -n 's/^Your code has been rated at \([-0-9.]*\)\/.*/\1/p' ./pylint/pylint.log) 50 | - anybadge --label=Pylint --file=pylint/pylint.svg --value=$PYLINT_SCORE 2=red 4=orange 8=yellow 10=green 51 | - echo "Pylint score is $PYLINT_SCORE" 52 | when: manual 53 | artifacts: 54 | paths: 55 | - ./pylint/ 56 | 57 | test: 58 | stage: test 59 | extends: .python-base 60 | script: 61 | - poetry run python -m unittest 62 | 63 | build-package: 64 | stage: build 65 | extends: .python-base 66 | script: 67 | - poetry run python setup.py sdist bdist_wheel 68 | artifacts: 69 | paths: 70 | - dist/*.whl 71 | - dist/*.tar.gz 72 | 73 | release-package: 74 | stage: release 75 | extends: .python-base 76 | only: 77 | - tags 78 | script: 79 | - pip install twine 80 | - python setup.py sdist bdist_wheel 81 | - TWINE_PASSWORD=${PYPI_PASSWORD} TWINE_USERNAME=${PYPI_USERNAME} python -m twine upload --repository pypi dist/* --verbose 82 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024, Maxim Litvak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include grepbible/static/*.txt 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # grepbible 2 | 3 | `grepbible` is a command-line interface (CLI) tool designed to make searching for Bible verses (in ca. 60 languages) locally (like grepping) and looking up the Bible quotes fast and intuitive. 4 | It also represents a new channel for Bible distribution and aids in language learning, as parallel Bible translations have been used for centuries to learn languages. 5 | 6 | Wikipedia article on [Bible citations](https://en.wikipedia.org/wiki/Bible_citation) 7 | The raw text is taken from [Wordproject®](https://www.wordproject.org), and has undergone processing to fit the specific needs and format of this project. 8 | 9 | There's a serparate project for the web UI: [grepbible-server](https://github.com/maxlit/grepbible-server.git). Its demo is available at [langtools.io/gb](https://langtools.io/gb) 10 | 11 | ## Features 12 | 13 | - **Search Capabilities**: Look up individual verses, ranges of chapters, or specific passages across multiple translations. 14 | - **Multiple Bible Versions**: Easily switch between different Bible translations to compare interpretations and wording. 15 | - **Local Caching**: Bible versions are downloaded and stored locally for quick access and offline use. 16 | - **Parallel and interleave text**: Combine text blocks from different translations. 17 | 18 | ## Installation 19 | 20 | To install `grepbible`, ensure you have Python 3.9 or higher installed on your system. You can install `grepbible` directly from PyPI: 21 | 22 | ```sh 23 | pip install grepbible 24 | ``` 25 | 26 | This command installs the `grepbible` package and makes the `gbib` command available in your shell. 27 | 28 | You might need to update your `PATH` as well (e.g. on Ubuntu): 29 | 30 | ```sh 31 | export PATH=$PATH:$(python3 -m site --user-base)/bin 32 | ``` 33 | and add it to `~/.bashrc` to make it persistent 34 | 35 | ![demo](./gifs/240309_gbib_demo.gif) 36 | 37 | ## Usage 38 | 39 | `grepbible` is designed to be straightforward and easy to use from the command line. The default version (unless the flag `-v` is specified) is 'kj' ([KJV](https://en.wikipedia.org/wiki/King_James_Version)), in English. 40 | 41 | Below are some common usage examples: 42 | 43 | ### Look Up a Single Verse 44 | 45 | ```sh 46 | gbib -c "John 3:11" 47 | ``` 48 | 49 | ``` 50 | Verily, verily, I say unto thee, We speak that we do know, and testify that we have seen; and ye receive not our witness. 51 | ``` 52 | 53 | ### Compare Verses in Different Translations 54 | 55 | ```sh 56 | gbib -v kj,pl -c "Romans 8:20" 57 | ``` 58 | 59 | ``` 60 | For the creature was made subject to vanity, not willingly, but by reason of him who hath subjected the same in hope, 61 | 62 | Gdyż stworzenie marności jest poddane, nie dobrowolnie, ale dla tego, który je poddał, 63 | ``` 64 | 65 | ### Lookup a Chapter 66 | 67 | ```sh 68 | gbib -c 'Psalms 117' 69 | ``` 70 | 71 | ``` 72 | O Praise the LORD, all ye nations: praise him, all ye people. 73 | For his merciful kindness is great toward us: and the truth of the LORD endureth for ever. Praise ye the LORD. 74 | ``` 75 | 76 | ### Show a Range of Verses 77 | 78 | ```sh 79 | gbib -c "Gen 41:29-30" 80 | ``` 81 | 82 | ``` 83 | Behold, there come seven years of great plenty throughout all the land of Egypt: 84 | And there shall arise after them seven years of famine; and all the plenty shall be forgotten in the land of Egypt; and the famine shall consume the land; 85 | ``` 86 | 87 | ### Fetch Multiple Disjoint Verses 88 | 89 | ```sh 90 | gbib -c "Genesis 1:1,3" 91 | ``` 92 | 93 | ### Compare different versions 94 | 95 | Show interleave translation of Latin Vulgata to English KJV (line-by-line): 96 | ```sh 97 | gbib -c 'Gen 41:29-30' -v kj,vg -i 98 | ``` 99 | 100 | ![Compare different versions line-by-line](./gifs/5_range-of-verses-i.gif) 101 | 102 | Block-by block translation (omit the flag `-i`): 103 | 104 | ```sh 105 | gbib -c 'Gen 41:29-30' -v kj,vg 106 | ``` 107 | 108 | ``` 109 | Behold, there come seven years of great plenty throughout all the land of Egypt: 110 | And there shall arise after them seven years of famine; and all the plenty shall be forgotten in the land of Egypt; and the famine shall consume the land; 111 | 112 | ecce septem anni venient fertilitatis magnae in universa terra Aegypti 113 | quos sequentur septem anni alii tantae sterilitatis ut oblivioni tradatur cuncta retro abundantia consumptura est enim fames omnem terram 114 | ``` 115 | ### Random quotes 116 | 117 | Time for some fun! One can generate random quotes (in different languages and in parallel as well). 118 | 119 | ```sh 120 | gbib -r 121 | ``` 122 | 123 | ![demo](./gifs/240309_gbib_random-quotes.gif) 124 | 125 | ### Get help 126 | 127 | For more information on command options and additional features, you can run: 128 | 129 | ```sh 130 | gbib --help 131 | ``` 132 | 133 | ![demo](./gifs/8_usage.gif) 134 | 135 | ### Use with grep 136 | 137 | One can literally use `grep` to look up the verses and leverage `gbib` only for downloading the sources. Here's how. 138 | 139 | First, download KJV ('kj') and Vulgata ('vg'). The data is stored in `$HOME/grepbible_data`, thus, it makes sense to store it as a variable, e.g. the path to the 5th chapter of Exodus in KJV will be $HOME/grepbible_data/kj/Exodus/5.txt 140 | 141 | ```sh 142 | gbib -d kj,vg 143 | export GB=$HOME/grepbible_data/kj 144 | ``` 145 | 146 | #### Go to line 147 | 148 | Example, jump 10th line of 5th chapter in Exodus: 149 | ```sh 150 | less +10 $GB/Exodus/5.txt 151 | ``` 152 | 153 | #### Count occurences of words 154 | 155 | How often the word 'camel' appears in the Bible: 156 | ```sh 157 | grep -nr $GB -e camel | wc -l # 59 158 | ``` 159 | 160 | #### What was this quote about the sheep and wolves? 161 | 162 | ```sh 163 | grep -nr $GB -e wolves | grep sheep 164 | ``` 165 | 166 | ![local grep](./gifs/9_grep.gif) 167 | 168 | ## Contributing 169 | 170 | Contributions to `grepbible` are welcome! Whether it's improving code, or reporting issues, or spreading the word, or financial support, your input is valuable. 171 | 172 | ### Issues/bugs 173 | 174 | To raise an issue, go to 'Issues' tab, and click on 'New issue'. 175 | 176 | ### Code 177 | 178 | To contribute code: 179 | 180 | 1. Fork the repository. 181 | 2. Create a new branch for your feature or fix. 182 | 3. Commit your changes with clear, descriptive messages. 183 | 4. Push your branch and submit a pull request. 184 | 185 | Please ensure your code adheres to the project's style and quality standards. For major changes, please open an issue first to discuss what you would like to change. 186 | 187 | ### Social 188 | 189 | Feel free to spread the word or/and use the hashtag `#grepbible` in social media. 190 | 191 | ### Financial 192 | 193 | Feel free to buy me a coffee: [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/J3J1VEX6J) 194 | 195 | ## License 196 | 197 | `grepbible` is open-source software licensed under the MIT License. See the LICENSE file for more details. 198 | -------------------------------------------------------------------------------- /gifs/240309_gbib_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxlit/grepbible/377a60f78a8c9d3fac067b9abfd155b45ffc37ee/gifs/240309_gbib_demo.gif -------------------------------------------------------------------------------- /gifs/240309_gbib_random-quotes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxlit/grepbible/377a60f78a8c9d3fac067b9abfd155b45ffc37ee/gifs/240309_gbib_random-quotes.gif -------------------------------------------------------------------------------- /gifs/5_range-of-verses-i.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxlit/grepbible/377a60f78a8c9d3fac067b9abfd155b45ffc37ee/gifs/5_range-of-verses-i.gif -------------------------------------------------------------------------------- /gifs/7_grep.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxlit/grepbible/377a60f78a8c9d3fac067b9abfd155b45ffc37ee/gifs/7_grep.gif -------------------------------------------------------------------------------- /gifs/8_usage.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxlit/grepbible/377a60f78a8c9d3fac067b9abfd155b45ffc37ee/gifs/8_usage.gif -------------------------------------------------------------------------------- /gifs/9_grep.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxlit/grepbible/377a60f78a8c9d3fac067b9abfd155b45ffc37ee/gifs/9_grep.gif -------------------------------------------------------------------------------- /grepbible/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxlit/grepbible/377a60f78a8c9d3fac067b9abfd155b45ffc37ee/grepbible/__init__.py -------------------------------------------------------------------------------- /grepbible/bible_manager.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import os, re 3 | import requests 4 | from zipfile import ZipFile, BadZipFile 5 | from pathlib import Path 6 | from urllib.parse import urljoin 7 | import random 8 | from grepbible.static.ch2num import BOOK2CHAPTERS, BOOK_ABBREVIATIONS 9 | try: 10 | # Try to use the standard library version if available 11 | from importlib import resources as pkg_resources 12 | except ImportError: 13 | # Use the backported version for older Python versions 14 | import importlib_resources as pkg_resources 15 | 16 | from . import static # Relative import of the static package 17 | 18 | DOWNLOAD_ENDPOINT = "https://powerdb.s3.us-west-2.amazonaws.com/download_endpoint/" 19 | LOCAL_BIBLE_DIR = Path.home() / 'grepbible_data' 20 | 21 | class TextColor: 22 | DARK_GREEN = '\033[32m' # Dark Green ANSI escape code 23 | ORANGE = '\033[33m' # Orange ANSI escape code 24 | RESET = '\033[0m' # Reset to default terminal color 25 | 26 | 27 | def ensure_data_dir_exists(): 28 | try: 29 | LOCAL_BIBLE_DIR.mkdir(parents=True, exist_ok=True) 30 | except PermissionError: 31 | print(f"Permission denied: Unable to create directory at {LOCAL_BIBLE_DIR}. Please check your permissions.") 32 | exit(1) 33 | except Exception as e: 34 | print(f"An error occurred while creating the directory at {LOCAL_BIBLE_DIR}: {e}") 35 | exit(1) 36 | 37 | def ensure_bible_version_exists(version): 38 | ensure_data_dir_exists() 39 | bible_version_path = LOCAL_BIBLE_DIR / version 40 | if not bible_version_path.exists(): 41 | download_and_extract_bible(version) 42 | else: 43 | pass 44 | #print(f"{version} already exists locally.") 45 | 46 | def ensure_book_exists(book): 47 | if book not in BOOK_ABBREVIATIONS.values(): 48 | print(f"Invalid book: {book}") 49 | print("Use the -l flag to list available books.") 50 | return False 51 | return True 52 | 53 | def get_available_versions(): 54 | # Use the 'files' function to get a path-like object for 'acronyms.txt' 55 | resource_path = pkg_resources.files('grepbible.static').joinpath('acronyms.txt') 56 | with resource_path.open('r') as af: 57 | acronyms = af.read().splitlines() 58 | return acronyms 59 | 60 | def is_valid_version(supplied_version): 61 | available_versions = get_available_versions() 62 | return supplied_version in available_versions 63 | 64 | 65 | def list_bibles(): 66 | ensure_data_dir_exists() 67 | # Adjust to use 'files()' for accessing resource files 68 | acronyms_path = pkg_resources.files('grepbible.static').joinpath('acronyms.txt') 69 | full_names_path = pkg_resources.files('grepbible.static').joinpath('full_names.txt') 70 | 71 | # Read acronyms and full names into lists 72 | with acronyms_path.open('r') as af: 73 | acronyms = af.read().splitlines() 74 | with full_names_path.open('r') as fnf: 75 | full_names = fnf.read().splitlines() 76 | 77 | # Create a mapping of acronyms to full names 78 | bible_versions = dict(zip(acronyms, full_names)) 79 | 80 | # Check which Bibles are available locally 81 | local_bibles = [b.name for b in LOCAL_BIBLE_DIR.iterdir() if b.is_dir()] 82 | 83 | for acronym, full_name in bible_versions.items(): 84 | local_indicator = "[local]" if acronym in local_bibles else "" 85 | print(f"{acronym} - {full_name} {local_indicator}") 86 | 87 | def list_books(): 88 | for book in BOOK_ABBREVIATIONS.values(): 89 | print(book) 90 | 91 | def download_and_extract_bible(version): 92 | if not is_valid_version(version): 93 | print(f"Invalid version: {version}") 94 | print("Use the -l flag to list available versions.") 95 | return 96 | zip_url = urljoin(DOWNLOAD_ENDPOINT, f"{version}.zip") 97 | print(f"Downloading {version} from {zip_url}...") 98 | zip_path = LOCAL_BIBLE_DIR / f"{version}.zip" 99 | 100 | # Download zip file 101 | try: 102 | response = requests.get(zip_url, stream=True) 103 | if response.status_code == 200: 104 | with open(zip_path, 'wb') as zip_file: 105 | for chunk in response.iter_content(chunk_size=8192): 106 | zip_file.write(chunk) 107 | # Extract zip file 108 | with ZipFile(zip_path, 'r') as zip_ref: 109 | zip_ref.extractall(LOCAL_BIBLE_DIR) 110 | os.remove(zip_path) # Clean up zip file 111 | else: 112 | print(f"Error downloading file: {response.status_code}") 113 | except requests.exceptions.RequestException as e: 114 | print(f"Request error: {e}") 115 | except BadZipFile as e: 116 | print(f"Zip file error: {e}") 117 | os.remove(zip_path) # Attempt to clean up corrupt zip file 118 | 119 | def get_random_quote(versions='kj', interleave=False): 120 | version_list = [version.strip() for version in versions.split(',')] 121 | version_to_read = version_list[0] 122 | ensure_bible_version_exists(version_to_read) 123 | # Select a random book 124 | book = random.choice(list(BOOK2CHAPTERS.keys())) 125 | chapters = BOOK2CHAPTERS[book] 126 | 127 | # Select a random chapter 128 | chapter = random.randint(1, chapters) 129 | 130 | # Construct the file path for the chapter 131 | chapter_file_path = os.path.join(LOCAL_BIBLE_DIR, version_to_read, book, f"{chapter}.txt") 132 | 133 | # Read the lines from the chapter file 134 | try: 135 | with open(chapter_file_path, 'r') as file: 136 | lines = [line.strip() for line in file if line.strip()] # Exclude empty lines 137 | 138 | # Ensure there are lines to choose from 139 | if lines: 140 | # Select a random line number (verse number) 141 | verse_number = random.randint(1, len(lines)) 142 | 143 | # Construct the Bible quote format 144 | random_quote = f"{book} {chapter}:{verse_number}" 145 | print(f"{random_quote}") 146 | 147 | # Assuming get_verse is defined to take this format and version 148 | # This part might need to adjust based on how get_verse is implemented 149 | return get_verse(versions, random_quote, interleave) 150 | else: 151 | return "No verses found in the selected chapter." 152 | except FileNotFoundError: 153 | return "Chapter file not found." 154 | 155 | def parse_citation(citation): 156 | # Updated pattern to optionally match "BookName Chapter" without specifying verses 157 | # The verse part is now optional; if not provided, the entire chapter is considered 158 | pattern = r'([1-3]?\s?[a-zA-Z]+\.?)\s+(\d+)(?::\s*((?:\d+(?:-\d+)?\s*,?\s*)+))?' 159 | citation = citation.strip() 160 | match = re.match(pattern, citation) 161 | if not match: 162 | print(f"Could not parse the citation: {citation}") 163 | return None 164 | 165 | book_abbr, chapter, verses = match.groups() 166 | 167 | # Normalize the book abbreviation by removing any trailing dot and mapping to full name 168 | book_abbr_normalized = book_abbr.rstrip('.') 169 | book = BOOK_ABBREVIATIONS.get(book_abbr_normalized, book_abbr_normalized) 170 | 171 | # If verses are not specified, return the entire chapter 172 | if verses is None: 173 | verse_parts = None # Indicate that the entire chapter is requested 174 | else: 175 | # Split the verses part into individual verses or verse ranges 176 | verse_parts = [v.strip() for v in verses.split(',') if v] 177 | 178 | return book, chapter, verse_parts 179 | 180 | def get_verse(versions, citation, interleave=False): 181 | version_list = [version.strip() for version in versions.split(',')] 182 | parsed = parse_citation(citation) 183 | if not parsed: 184 | return 185 | 186 | book, chapter, verse_parts = parsed # Adjusted to match new parse_citation output 187 | if not ensure_book_exists(book): 188 | print(f"Invalid book: {book}, use the -b flag to list available books.") 189 | exit(1) 190 | 191 | # Check if verse_parts is None, indicating the whole chapter should be returned 192 | whole_chapter = verse_parts is None 193 | 194 | verses_by_version = {version: [] for version in version_list} 195 | 196 | for version in version_list: 197 | ensure_bible_version_exists(version) 198 | chapter_file = LOCAL_BIBLE_DIR / version / f"{book}/{chapter}.txt" 199 | try: 200 | with open(chapter_file, 'r', encoding='utf-8') as f: 201 | chapter_verses = f.readlines() 202 | 203 | if whole_chapter: 204 | # If the whole chapter is requested, add all verses to the list 205 | verses_by_version[version].extend([line.strip() for line in chapter_verses]) 206 | else: 207 | # Otherwise, process each verse part as before 208 | for part in verse_parts: 209 | if '-' in part: # If the part is a verse range 210 | start_verse, end_verse = map(int, part.split('-')) 211 | verses_to_fetch = range(start_verse, end_verse + 1) 212 | else: # If the part is an individual verse 213 | verses_to_fetch = [int(part)] 214 | 215 | for verse_num in verses_to_fetch: 216 | verse_line = chapter_verses[verse_num - 1].strip() 217 | verses_by_version[version].append(verse_line) 218 | 219 | except FileNotFoundError: 220 | print(f"File not found: {chapter_file}") 221 | return 222 | except IndexError: 223 | print(f"Verse number out of range in {book} chapter {chapter}") 224 | return 225 | 226 | # Interleave and print verses with color coding if interleave flag is True 227 | if interleave: 228 | max_verses = max(len(verses) for verses in verses_by_version.values()) 229 | for verse_num in range(max_verses): 230 | for i, version in enumerate(version_list): 231 | try: 232 | verse_line = verses_by_version[version][verse_num] 233 | if i == 1: # Apply dark green color only to the second version 234 | print(f"{TextColor.DARK_GREEN}{verse_line}{TextColor.RESET}") 235 | elif i == 2: # Apply orage color only to the third version 236 | print(f"{TextColor.ORANGE}{verse_line}{TextColor.RESET}") 237 | else: 238 | print(f"{verse_line}") 239 | except IndexError: 240 | # Handle cases where one version has fewer verses 241 | pass 242 | if i < len(version_list) - 1: 243 | print() # Newline for separation between versions if not interleaving 244 | else: 245 | for i, version in enumerate(version_list): 246 | for verse_line in verses_by_version[version]: 247 | if i == 1: 248 | print(f"{TextColor.DARK_GREEN}{verse_line}{TextColor.RESET}") 249 | elif i == 2: 250 | print(f"{TextColor.ORANGE}{verse_line}{TextColor.RESET}") 251 | else: 252 | print(f"{verse_line}") 253 | if i < len(version_list) - 1: 254 | print() # Newline for separation between versions if not interleaving -------------------------------------------------------------------------------- /grepbible/cli.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | from grepbible.bible_manager import * 4 | 5 | def main(): 6 | parser = argparse.ArgumentParser(description="CLI tool to look up Bible verses.") 7 | parser.add_argument('-c', '--citation', help="Verse citation (e.g., 'Gen 1:1').", required=False) 8 | parser.add_argument('-v', '--version', help="Bible version(s), separated by commas.", required=False, default="kj") 9 | parser.add_argument('-d', '--download', help="Download Bible version(s), separated by commas.", required=False) 10 | parser.add_argument('-l', '--list', action='store_true', help="List all available Bibles.") 11 | parser.add_argument('-b', '--books', action='store_true', help="List all available books (KJV names used).") 12 | parser.add_argument('-i', '--interleave', action='store_true', help="Interleave verses for multiple versions.") 13 | parser.add_argument('-r', '--random', help='Return a random quote.', action='store_true') 14 | parser.add_argument('--parse', action='store_true', help='(technical) Parse the citation and return JSON output') 15 | 16 | 17 | args = parser.parse_args() 18 | 19 | if args.list: 20 | list_bibles() 21 | elif args.citation and args.parse: 22 | parsed_details = parse_citation(args.citation) 23 | if parsed_details: 24 | print(json.dumps(parsed_details)) # Output parsed details as JSON 25 | else: 26 | print(json.dumps({"error": "Could not parse the citation"})) 27 | elif args.citation: 28 | # Pass the interleave flag to the get_verse function 29 | get_verse(args.version, args.citation, args.interleave) 30 | elif args.download: 31 | for version in args.download.split(','): 32 | download_and_extract_bible(version) 33 | elif args.books: 34 | list_books() 35 | elif args.random: 36 | get_random_quote(args.version, args.interleave) 37 | else: 38 | parser.print_help() 39 | 40 | if __name__ == "__main__": 41 | main() -------------------------------------------------------------------------------- /grepbible/static/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxlit/grepbible/377a60f78a8c9d3fac067b9abfd155b45ffc37ee/grepbible/static/__init__.py -------------------------------------------------------------------------------- /grepbible/static/acronyms.txt: -------------------------------------------------------------------------------- 1 | ace 2 | af 3 | al 4 | am 5 | ar 6 | ben 7 | bg 8 | my 9 | ceb 10 | big5 11 | big5_cath 12 | gb 13 | gb_cat 14 | pn 15 | cr 16 | cz 17 | dk 18 | nl 19 | kj 20 | drc 21 | fa 22 | fj 23 | fi 24 | fr 25 | de 26 | gk 27 | gk 28 | guj 29 | he 30 | in 31 | in 32 | hu 33 | id 34 | is 35 | it 36 | jp 37 | kn 38 | kk 39 | kr 40 | vg 41 | lug 42 | ml 43 | mar 44 | mn 45 | ne 46 | no 47 | or 48 | pl 49 | po 50 | po-BR 51 | pa 52 | ro 53 | ru 54 | sr 55 | si 56 | so 57 | sp 58 | sw 59 | se 60 | tl 61 | tm 62 | tel 63 | ti 64 | tr 65 | uk 66 | ur 67 | vt 68 | xho 69 | zu 70 | -------------------------------------------------------------------------------- /grepbible/static/ch2num.py: -------------------------------------------------------------------------------- 1 | BOOK2CHAPTERS = { 2 | '1 Chronicles': 29, 3 | '1 Corinthians': 16, 4 | '1 John': 5, 5 | '1 Kings': 22, 6 | '1 Peter': 5, 7 | '1 Samuel': 31, 8 | '1 Thessalonians': 5, 9 | '1 Timothy': 6, 10 | '2 Chronicles': 36, 11 | '2 Corinthians': 13, 12 | '2 John': 1, 13 | '2 Kings': 25, 14 | '2 Peter': 3, 15 | '2 Samuel': 24, 16 | '2 Thessalonians': 3, 17 | '2 Timothy': 4, 18 | '3 John': 1, 19 | 'Acts': 28, 20 | 'Amos': 9, 21 | 'Colossians': 4, 22 | 'Daniel': 12, 23 | 'Deuteronomy': 34, 24 | 'Ecclesiastes': 12, 25 | 'Ephesians': 6, 26 | 'Esther': 10, 27 | 'Exodus': 40, 28 | 'Ezekiel': 48, 29 | 'Ezra': 10, 30 | 'Galatians': 6, 31 | 'Genesis': 50, 32 | 'Habakkuk': 3, 33 | 'Haggai': 2, 34 | 'Hebrews': 13, 35 | 'Hosea': 14, 36 | 'Isaiah': 66, 37 | 'James': 5, 38 | 'Jeremiah': 52, 39 | 'Job': 42, 40 | 'Joel': 3, 41 | 'John': 21, 42 | 'Jonah': 4, 43 | 'Joshua': 24, 44 | 'Jude': 1, 45 | 'Judges': 21, 46 | 'Lamentations': 5, 47 | 'Leviticus': 27, 48 | 'Luke': 24, 49 | 'Malachi': 4, 50 | 'Mark': 16, 51 | 'Matthew': 28, 52 | 'Micah': 7, 53 | 'Nahum': 3, 54 | 'Nehemiah': 13, 55 | 'Numbers': 36, 56 | 'Obadiah': 1, 57 | 'Philemon': 1, 58 | 'Philippians': 4, 59 | 'Proverbs': 31, 60 | 'Psalms': 150, 61 | 'Revelation': 22, 62 | 'Romans': 16, 63 | 'Ruth': 4, 64 | 'Song of Solomon': 8, 65 | 'Titus': 3, 66 | 'Zechariah': 14, 67 | 'Zephaniah': 3 68 | } 69 | 70 | BOOK_ABBREVIATIONS = { 71 | 'Gen': 'Genesis', 72 | 'Ex': 'Exodus', 73 | 'Lev': 'Leviticus', 74 | 'Num': 'Numbers', 75 | 'Deut': 'Deuteronomy', 76 | 'Josh': 'Joshua', 77 | 'Judg': 'Judges', 78 | 'Ruth': 'Ruth', 79 | '1Sam': '1 Samuel', 80 | '2Sam': '2 Samuel', 81 | '1Kgs': '1 Kings', 82 | '2Kgs': '2 Kings', 83 | '1Chr': '1 Chronicles', 84 | '2Chr': '2 Chronicles', 85 | 'Ezra': 'Ezra', 86 | 'Neh': 'Nehemiah', 87 | 'Est': 'Esther', 88 | 'Job': 'Job', 89 | 'Ps': 'Psalms', 90 | 'Prov': 'Proverbs', 91 | 'Eccles': 'Ecclesiastes', 92 | 'Song': 'Song of Solomon', 93 | 'Isa': 'Isaiah', 94 | 'Jer': 'Jeremiah', 95 | 'Lam': 'Lamentations', 96 | 'Ezek': 'Ezekiel', 97 | 'Dan': 'Daniel', 98 | 'Hos': 'Hosea', 99 | 'Joel': 'Joel', 100 | 'Amos': 'Amos', 101 | 'Obad': 'Obadiah', 102 | 'Jonah': 'Jonah', 103 | 'Mic': 'Micah', 104 | 'Nah': 'Nahum', 105 | 'Hab': 'Habakkuk', 106 | 'Zeph': 'Zephaniah', 107 | 'Hag': 'Haggai', 108 | 'Zech': 'Zechariah', 109 | 'Mal': 'Malachi', 110 | 'Matt': 'Matthew', 111 | 'Mark': 'Mark', 112 | 'Luke': 'Luke', 113 | 'John': 'John', 114 | 'Acts': 'Acts', 115 | 'Rom': 'Romans', 116 | '1Cor': '1 Corinthians', 117 | '2Cor': '2 Corinthians', 118 | 'Gal': 'Galatians', 119 | 'Eph': 'Ephesians', 120 | 'Phil': 'Philippians', 121 | 'Col': 'Colossians', 122 | '1Thess': '1 Thessalonians', 123 | '2Thess': '2 Thessalonians', 124 | '1Tim': '1 Timothy', 125 | '2Tim': '2 Timothy', 126 | 'Titus': 'Titus', 127 | 'Philem': 'Philemon', 128 | 'Heb': 'Hebrews', 129 | 'James': 'James', 130 | '1Pet': '1 Peter', 131 | '2Pet': '2 Peter', 132 | '1John': '1 John', 133 | '2John': '2 John', 134 | '3John': '3 John', 135 | 'Jude': 'Jude', 136 | 'Rev': 'Revelation', 137 | } -------------------------------------------------------------------------------- /grepbible/static/full_names.txt: -------------------------------------------------------------------------------- 1 | Aceh Holy Bible 2 | Afrikaans Holy Bible 3 | Albanian Holy Bible 4 | Amharic Holy Bible 5 | Arabic Holy Bible 6 | Bengali Holy Bible 7 | Bulgarian Holy Bible 8 | Burmese Holy Bible 9 | Cebuano Bible Holy Bible 10 | Chinese - Traditional Characters - CUV 11 | Chinese - Chinese Catholic Bible 12 | Chinese Mandarin Holy Bible - CUV 13 | Chinese Catholic Bible 14 | Chinese Pinyin Bible 15 | Croatian Holy Bible 16 | Czech Holy Bible 17 | Danish Bible 18 | Dutch Bible 19 | English Holy Bible - KJV 20 | The Holy Bible- Catholic version 21 | Farsi (Persian) Holy Bible 22 | Fijian Holy Bible 23 | Finnish Holy Bible 24 | French Holy Bible - Louis Segond 25 | German Holy Bible - Luther 26 | Greek Holy Bible - Old 27 | Greek Holy Bible - Modern 28 | Gujarati Holy Bible 29 | Hebrew Holy Bible 30 | Hindi Bible 31 | Hindi Romanized 32 | Hungarian Holy Bible 33 | Indonesian Bible 34 | Icelandic Holy Bible 35 | Italian Holy Bible - Riveduta 36 | Japanese Holy Bible - Kougo-yaku 37 | Kannada Holy Bible 38 | Kazakh Holy Bible 39 | Korean Holy Bible 40 | Vulgata Latina 41 | Luganda Holy Bible 42 | Malayalam Holy Bible 43 | Marathi Holy Bible 44 | Mongol Holy Bible 45 | Nepali Holy Bible 46 | Norwegian Holy Bible 47 | Oriya Bible 48 | Polish Holy Bible 49 | Portuguese Holy Bible - JFA 50 | Portuguese (Brazil) Holy Bible - JFA 51 | Punjabi Bible 52 | Romanian Holy Bible 53 | Russian Holy Bible 54 | Serbian Holy Bible 55 | Sinhala Holy Bible 56 | Somali Holy Bible 57 | Spanish Holy Bible - Reina Valera 58 | Swahili Bible 59 | Swedish Holy Bible 60 | Tagalog (Filipino) Holy Bible 61 | Tamil Holy Bible 62 | Telugu Holy Bible 63 | Thai Holy Bible 64 | Turkish Holy Bible 65 | Ukrainian Bible 66 | Urdu Bible 67 | Vietnamese Bible 68 | Xhosa Holy Bible 69 | Zulu Holy Bible 70 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "certifi" 5 | version = "2024.2.2" 6 | description = "Python package for providing Mozilla's CA Bundle." 7 | category = "main" 8 | optional = false 9 | python-versions = ">=3.6" 10 | files = [ 11 | {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, 12 | {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, 13 | ] 14 | 15 | [[package]] 16 | name = "charset-normalizer" 17 | version = "3.3.2" 18 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 19 | category = "main" 20 | optional = false 21 | python-versions = ">=3.7.0" 22 | files = [ 23 | {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, 24 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, 25 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, 26 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, 27 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, 28 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, 29 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, 30 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, 31 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, 32 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, 33 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, 34 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, 35 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, 36 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, 37 | {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, 38 | {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, 39 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, 40 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, 41 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, 42 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, 43 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, 44 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, 45 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, 46 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, 47 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, 48 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, 49 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, 50 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, 51 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, 52 | {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, 53 | {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, 54 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, 55 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, 56 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, 57 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, 58 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, 59 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, 60 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, 61 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, 62 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, 63 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, 64 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, 65 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, 66 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, 67 | {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, 68 | {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, 69 | {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, 70 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, 71 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, 72 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, 73 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, 74 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, 75 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, 76 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, 77 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, 78 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, 79 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, 80 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, 81 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, 82 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, 83 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, 84 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, 85 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, 86 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, 87 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, 88 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, 89 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, 90 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, 91 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, 92 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, 93 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, 94 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, 95 | {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, 96 | {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, 97 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, 98 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, 99 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, 100 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, 101 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, 102 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, 103 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, 104 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, 105 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, 106 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, 107 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, 108 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, 109 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, 110 | {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, 111 | {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, 112 | {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, 113 | ] 114 | 115 | [[package]] 116 | name = "colorama" 117 | version = "0.4.6" 118 | description = "Cross-platform colored terminal text." 119 | category = "main" 120 | optional = false 121 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 122 | files = [ 123 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 124 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 125 | ] 126 | 127 | [[package]] 128 | name = "exceptiongroup" 129 | version = "1.2.0" 130 | description = "Backport of PEP 654 (exception groups)" 131 | category = "main" 132 | optional = false 133 | python-versions = ">=3.7" 134 | files = [ 135 | {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, 136 | {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, 137 | ] 138 | 139 | [package.extras] 140 | test = ["pytest (>=6)"] 141 | 142 | [[package]] 143 | name = "idna" 144 | version = "3.6" 145 | description = "Internationalized Domain Names in Applications (IDNA)" 146 | category = "main" 147 | optional = false 148 | python-versions = ">=3.5" 149 | files = [ 150 | {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, 151 | {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, 152 | ] 153 | 154 | [[package]] 155 | name = "importlib-resources" 156 | version = "6.1.3" 157 | description = "Read resources from Python packages" 158 | category = "main" 159 | optional = false 160 | python-versions = ">=3.8" 161 | files = [ 162 | {file = "importlib_resources-6.1.3-py3-none-any.whl", hash = "sha256:4c0269e3580fe2634d364b39b38b961540a7738c02cb984e98add8b4221d793d"}, 163 | {file = "importlib_resources-6.1.3.tar.gz", hash = "sha256:56fb4525197b78544a3354ea27793952ab93f935bb4bf746b846bb1015020f2b"}, 164 | ] 165 | 166 | [package.dependencies] 167 | zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} 168 | 169 | [package.extras] 170 | docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] 171 | testing = ["jaraco.collections", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"] 172 | 173 | [[package]] 174 | name = "iniconfig" 175 | version = "2.0.0" 176 | description = "brain-dead simple config-ini parsing" 177 | category = "main" 178 | optional = false 179 | python-versions = ">=3.7" 180 | files = [ 181 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 182 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 183 | ] 184 | 185 | [[package]] 186 | name = "packaging" 187 | version = "23.2" 188 | description = "Core utilities for Python packages" 189 | category = "main" 190 | optional = false 191 | python-versions = ">=3.7" 192 | files = [ 193 | {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, 194 | {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, 195 | ] 196 | 197 | [[package]] 198 | name = "pluggy" 199 | version = "1.4.0" 200 | description = "plugin and hook calling mechanisms for python" 201 | category = "main" 202 | optional = false 203 | python-versions = ">=3.8" 204 | files = [ 205 | {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, 206 | {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, 207 | ] 208 | 209 | [package.extras] 210 | dev = ["pre-commit", "tox"] 211 | testing = ["pytest", "pytest-benchmark"] 212 | 213 | [[package]] 214 | name = "pytest" 215 | version = "8.0.2" 216 | description = "pytest: simple powerful testing with Python" 217 | category = "main" 218 | optional = false 219 | python-versions = ">=3.8" 220 | files = [ 221 | {file = "pytest-8.0.2-py3-none-any.whl", hash = "sha256:edfaaef32ce5172d5466b5127b42e0d6d35ebbe4453f0e3505d96afd93f6b096"}, 222 | {file = "pytest-8.0.2.tar.gz", hash = "sha256:d4051d623a2e0b7e51960ba963193b09ce6daeb9759a451844a21e4ddedfc1bd"}, 223 | ] 224 | 225 | [package.dependencies] 226 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 227 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 228 | iniconfig = "*" 229 | packaging = "*" 230 | pluggy = ">=1.3.0,<2.0" 231 | tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} 232 | 233 | [package.extras] 234 | testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 235 | 236 | [[package]] 237 | name = "requests" 238 | version = "2.31.0" 239 | description = "Python HTTP for Humans." 240 | category = "main" 241 | optional = false 242 | python-versions = ">=3.7" 243 | files = [ 244 | {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, 245 | {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, 246 | ] 247 | 248 | [package.dependencies] 249 | certifi = ">=2017.4.17" 250 | charset-normalizer = ">=2,<4" 251 | idna = ">=2.5,<4" 252 | urllib3 = ">=1.21.1,<3" 253 | 254 | [package.extras] 255 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 256 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 257 | 258 | [[package]] 259 | name = "tomli" 260 | version = "2.0.1" 261 | description = "A lil' TOML parser" 262 | category = "main" 263 | optional = false 264 | python-versions = ">=3.7" 265 | files = [ 266 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 267 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 268 | ] 269 | 270 | [[package]] 271 | name = "urllib3" 272 | version = "2.2.1" 273 | description = "HTTP library with thread-safe connection pooling, file post, and more." 274 | category = "main" 275 | optional = false 276 | python-versions = ">=3.8" 277 | files = [ 278 | {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, 279 | {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, 280 | ] 281 | 282 | [package.extras] 283 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] 284 | h2 = ["h2 (>=4,<5)"] 285 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 286 | zstd = ["zstandard (>=0.18.0)"] 287 | 288 | [[package]] 289 | name = "zipp" 290 | version = "3.17.0" 291 | description = "Backport of pathlib-compatible object wrapper for zip files" 292 | category = "main" 293 | optional = false 294 | python-versions = ">=3.8" 295 | files = [ 296 | {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, 297 | {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, 298 | ] 299 | 300 | [package.extras] 301 | docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] 302 | testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] 303 | 304 | [metadata] 305 | lock-version = "2.0" 306 | python-versions = "^3.9" 307 | content-hash = "63abf7ab3b3b580450b42dc40ce8c53107444dcb5a88d77bdc83aa19d8a5f649" 308 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "grepbible" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Maxim Litvak "] 6 | license = "MIT" 7 | readme = "README.md" 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.9" 11 | requests = "^2.31.0" 12 | pytest = "^8.0.2" 13 | importlib-resources = "^6.1.3" 14 | 15 | 16 | [build-system] 17 | requires = ["poetry-core"] 18 | build-backend = "poetry.core.masonry.api" 19 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | import os 5 | 6 | setup( 7 | name='grepbible', 8 | version = os.environ.get('CI_COMMIT_TAG', '0.0.0-dev'), 9 | packages=find_packages(), 10 | entry_points={ 11 | 'console_scripts': [ 12 | 'gbib=grepbible.cli:main', 13 | ], 14 | }, 15 | author='Maxim Litvak', 16 | author_email='maxim@litvak.eu', 17 | description='A CLI tool to look up Bible verses locally.', 18 | long_description=open('README.md').read(), 19 | long_description_content_type='text/markdown', 20 | install_requires=[ 21 | 'requests', # List other dependencies as needed 22 | ], 23 | include_package_data=True, 24 | keywords=['bible', 'KJV'], 25 | classifiers=[ 26 | 'Development Status :: 3 - Alpha', 27 | 'Environment :: Console', 28 | 'Intended Audience :: Developers', 29 | 'Intended Audience :: Science/Research', 30 | 'Intended Audience :: Education', 31 | 'License :: OSI Approved :: MIT License', 32 | 'Operating System :: MacOS :: MacOS X', 33 | 'Operating System :: Microsoft :: Windows', 34 | 'Operating System :: POSIX', 35 | 'Programming Language :: Python', 36 | 'Topic :: Education', 37 | 'Topic :: Religion' 38 | ], 39 | url='https://gitlab.com/maxlit/grepbible', 40 | license='MIT', 41 | ) -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxlit/grepbible/377a60f78a8c9d3fac067b9abfd155b45ffc37ee/tests/__init__.py -------------------------------------------------------------------------------- /tests/kjv_counts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from pathlib import Path 3 | import os 4 | import argparse 5 | 6 | # Parse command line arguments 7 | parser = argparse.ArgumentParser(description='Calculate verse count discrepancies and compare with a standard.') 8 | parser.add_argument('--dir', type=str, help='Directory containing the Bible text files (default: ~/grepbible_data/kj)') 9 | parser.add_argument('--eps', type=float, default=0.0, help='Epsilon for approximate comparison in percentage (default: 1%).') 10 | args = parser.parse_args() 11 | 12 | if args.dir: 13 | LOCAL_BIBLE_DIR = Path(args.dir) 14 | else: 15 | # Default directory if not specified 16 | LOCAL_BIBLE_DIR = Path.home() / 'grepbible_data/kj' 17 | 18 | _books_and_verse_counts = [ 19 | ("Genesis", 1533), 20 | ("Exodus", 1213), 21 | ("Leviticus", 859), 22 | ("Numbers", 1288), 23 | ("Deuteronomy", 959), 24 | ("Joshua", 658), 25 | ("Judges", 618), 26 | ("Ruth", 85), 27 | ("1 Samuel", 810), 28 | ("2 Samuel", 695), 29 | ("1 Kings", 816), 30 | ("2 Kings", 719), 31 | ("1 Chronicles", 942), 32 | ("2 Chronicles", 822), 33 | ("Ezra", 280), 34 | ("Nehemiah", 406), 35 | ("Esther", 167), 36 | ("Job", 1070), 37 | ("Psalms", 2461), 38 | ("Proverbs", 915), 39 | ("Ecclesiastes", 222), 40 | ("Song of Solomon", 117), 41 | ("Isaiah", 1292), 42 | ("Jeremiah", 1364), 43 | ("Lamentations", 154), 44 | ("Ezekiel", 1273), 45 | ("Daniel", 357), 46 | ("Hosea", 197), 47 | ("Joel", 73), 48 | ("Amos", 146), 49 | ("Obadiah", 21), 50 | ("Jonah", 48), 51 | ("Micah", 105), 52 | ("Nahum", 47), 53 | ("Habakkuk", 56), 54 | ("Zephaniah", 53), 55 | ("Haggai", 38), 56 | ("Zechariah", 211), 57 | ("Malachi", 55), 58 | ("Matthew", 1071), 59 | ("Mark", 678), 60 | ("Luke", 1151), 61 | ("John", 879), 62 | ("Acts", 1007), 63 | ("Romans", 433), 64 | ("1 Corinthians", 437), 65 | ("2 Corinthians", 257), 66 | ("Galatians", 149), 67 | ("Ephesians", 155), 68 | ("Philippians", 104), 69 | ("Colossians", 95), 70 | ("1 Thessalonians", 89), 71 | ("2 Thessalonians", 47), 72 | ("1 Timothy", 113), 73 | ("2 Timothy", 83), 74 | ("Titus", 46), 75 | ("Philemon", 25), 76 | ("Hebrews", 303), 77 | ("James", 108), 78 | ("1 Peter", 105), 79 | ("2 Peter", 61), 80 | ("1 John", 105), 81 | ("2 John", 13), 82 | ("3 John", 14), 83 | ("Jude", 25), 84 | ("Revelation", 404) 85 | ] 86 | 87 | books_and_verse_counts = dict(_books_and_verse_counts) 88 | 89 | if False: 90 | # books_and_verse_counts as dictionary 91 | 92 | 93 | lines_per_book = {} 94 | 95 | for book in os.listdir(LOCAL_BIBLE_DIR): 96 | book_path = os.path.join(LOCAL_BIBLE_DIR, book) 97 | if not os.path.isdir(book_path): 98 | continue 99 | 100 | lines_count = 0 101 | for chapter_file in os.listdir(book_path): 102 | chapter_path = os.path.join(book_path, chapter_file) 103 | if os.path.isfile(chapter_path): 104 | with open(chapter_path, 'r') as file: 105 | lines_count += len(file.readlines()) 106 | 107 | lines_per_book[book] = lines_count 108 | 109 | discrepancies = {} 110 | 111 | for book, target_count in books_and_verse_counts.items(): 112 | real_count = lines_per_book.get(book, 0) # Default to 0 if book not found in real counts 113 | if real_count != target_count: 114 | discrepancies[book] = {"observed": real_count, "expected": target_count} 115 | 116 | if len(discrepancies) == 0: 117 | print("All books have the expected verse counts.") 118 | else: 119 | for key in discrepancies: 120 | print(key + ": ", discrepancies[key]) 121 | 122 | # Function to calculate verse counts in specified directory 123 | def calculate_verse_counts(directory): 124 | lines_per_book = {} 125 | for book in os.listdir(directory): 126 | book_path = os.path.join(directory, book) 127 | if not os.path.isdir(book_path): 128 | continue 129 | 130 | lines_count = 0 131 | for chapter_file in os.listdir(book_path): 132 | chapter_path = os.path.join(book_path, chapter_file) 133 | if os.path.isfile(chapter_path): 134 | with open(chapter_path, 'r') as file: 135 | lines_count += len(file.readlines()) 136 | 137 | lines_per_book[book] = lines_count 138 | return lines_per_book 139 | 140 | # Perform comparison with KJV counts 141 | def compare_with_kjv(directory_counts, epsilon): 142 | discrepancies = {} 143 | for book, kjv_count in books_and_verse_counts.items(): 144 | directory_count = directory_counts.get(book, 0) 145 | if not (1 - epsilon / 100) * kjv_count <= directory_count <= (1 + epsilon / 100) * kjv_count: 146 | discrepancies[book] = {"observed": directory_count, "expected": kjv_count} 147 | return discrepancies 148 | 149 | directory_counts = calculate_verse_counts(LOCAL_BIBLE_DIR) 150 | 151 | eps = 0 152 | if args.eps: 153 | eps = args.eps 154 | discrepancies = compare_with_kjv(directory_counts, eps) 155 | 156 | if len(discrepancies) == 0: 157 | print("All books are within the expected verse count range.") 158 | else: 159 | for book, counts in discrepancies.items(): 160 | print(f"{book}: Observed {counts['observed']}, Expected {counts['expected']}") -------------------------------------------------------------------------------- /tests/test.sh: -------------------------------------------------------------------------------- 1 | gbib -c "-- Look Up a Single Verse" 2 | gbib -c "John 3:11" 3 | 4 | echo "-- Compare Verses in Different Translations" 5 | gbib -v kj,pl -c "Romans 8:20" 6 | 7 | echo "-- Lookup a Chapter" 8 | gbib -c 'Psalms 117' 9 | 10 | echo "-- Show a Range of Verses" 11 | gbib -c "Gen 41:29-30" 12 | 13 | echo "-- Fetch Multiple Disjoint Verses" 14 | gbib -c "Genesis 1:1,3" 15 | 16 | echo "-- Compare different versions" 17 | gbib -c 'Gen 41:29-30' -v kj,vg -i 18 | 19 | echo "-- random quote" 20 | gbib -r 21 | 22 | echo "-- random quote i different languages" 23 | gbib -r -v kj,de,vg 24 | 25 | echo "parse a quote" 26 | gbib -c "Gen 41:29-30" --parse -------------------------------------------------------------------------------- /tests/test_gbib.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sys 3 | from pathlib import Path 4 | 5 | # Add the root directory to PYTHONPATH 6 | sys.path.append(str(Path(__file__).parent.parent)) 7 | #import grepbible as gb 8 | from grepbible.bible_manager import * 9 | 10 | class TestGB(unittest.TestCase): 11 | def test_parse_citation(self): 12 | quotes = ['Genesis 1:1', 'Gen 1:1', 'Gen. 1:1'] 13 | for quote in quotes: 14 | book, chapter, verse_parts = parse_citation(quote) 15 | self.assertEqual(book, 'Genesis') 16 | self.assertEqual(chapter, '1') 17 | self.assertEqual(verse_parts[0], '1') 18 | 19 | def test_parse_citation_range(self): 20 | book, chapter, verse_parts = parse_citation("Genesis 1:1-10") 21 | self.assertEqual(book, 'Genesis') 22 | self.assertEqual(chapter, '1') 23 | self.assertEqual(verse_parts[0], '1-10') 24 | 25 | def test_parse_citation_disjoint(self): 26 | book, chapter, verse_parts = parse_citation("Genesis 1:1,10") 27 | self.assertEqual(book, 'Genesis') 28 | self.assertEqual(chapter, '1') 29 | self.assertEqual(verse_parts[1], '10') 30 | 31 | def test_parse_chapter(self): 32 | book, chapter, verse_parts = parse_citation("Psalms 23") 33 | self.assertEqual(book, 'Psalms') 34 | self.assertEqual(chapter, '23') 35 | self.assertEqual(verse_parts, None) 36 | 37 | def test_get_available_versions(self): 38 | accros = get_available_versions() 39 | self.assertTrue('kj' in accros) 40 | self.assertFalse('xyz' in accros) 41 | 42 | def test_is_valid_version(self): 43 | self.assertTrue(is_valid_version('bg')) 44 | self.assertFalse(is_valid_version('xyz')) 45 | 46 | def test_random_quote(self): 47 | quote = get_random_quote('kj') 48 | self.assertTrue(True) # the check is that it runs through, the quote itself goes to stdout 49 | 50 | def main(): 51 | unittest.main() 52 | 53 | if __name__=='__main__': 54 | main() 55 | --------------------------------------------------------------------------------