├── .coveragerc ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── all_core_tests.yml │ ├── ready_doctest.yml │ ├── ready_linting.yml │ ├── ready_test_matrix.yml │ ├── ready_test_nonloc.yml │ ├── release_check_sdist.yml │ ├── release_doc_warnings.yml │ ├── release_flake8_noqa_nofail.yml │ ├── release_readme_doctest.yml │ └── release_test_file_coverage.yml ├── .gitignore ├── .readthedocs.yaml ├── AUTHORS.md ├── CHANGELOG.md ├── CONTENT_LICENSE.txt ├── CONTRIBUTING.md ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── SECURITY.md ├── conftest.py ├── doc ├── Makefile ├── make.bat └── source │ ├── _static │ ├── css │ │ └── custom.css │ ├── extlink.svg │ ├── mouseover_example.png │ ├── no-leven.csv │ ├── soi-logo.png │ ├── soi-logo_duo.png │ ├── soi-logo_duo_border.png │ ├── soi-logo_duo_border.xcf │ ├── suggest_timing.png │ ├── suggest_timing.xlsx │ ├── suggest_timing_commands.txt │ └── with-leven.csv │ ├── _templates │ └── footer.html │ ├── api │ ├── data.rst │ ├── enum.rst │ ├── error.rst │ ├── fileops.rst │ ├── index.rst │ ├── inventory.rst │ ├── re.rst │ ├── schema.rst │ └── zlib.rst │ ├── api_usage.rst │ ├── cli │ ├── convert.rst │ ├── implementation │ │ ├── convert.rst │ │ ├── core.rst │ │ ├── index.rst │ │ ├── load.rst │ │ ├── parser.rst │ │ ├── paths.rst │ │ ├── suggest.rst │ │ ├── ui.rst │ │ └── write.rst │ ├── index.rst │ └── suggest.rst │ ├── conf.py │ ├── customfile.rst │ ├── index.rst │ ├── isphx │ └── objpull.py │ ├── levenshtein.rst │ └── syntax.rst ├── pyproject.toml ├── requirements-ci.txt ├── requirements-dev.txt ├── requirements-flake8.txt ├── requirements-interrogate.txt ├── requirements-rtd.txt ├── setup.py ├── src └── sphobjinv │ ├── __init__.py │ ├── __main__.py │ ├── _vendored │ ├── __init__.py │ └── fuzzywuzzy │ │ ├── LICENSE.txt │ │ ├── __init__.py │ │ ├── fuzz.py │ │ ├── process.py │ │ ├── tests.py │ │ └── utils.py │ ├── cli │ ├── __init__.py │ ├── convert.py │ ├── core.py │ ├── load.py │ ├── parser.py │ ├── paths.py │ ├── suggest.py │ ├── ui.py │ └── write.py │ ├── data.py │ ├── enum.py │ ├── error.py │ ├── fileops.py │ ├── inventory.py │ ├── re.py │ ├── schema.py │ ├── version.py │ └── zlib.py ├── tests ├── __init__.py ├── resource │ ├── objects_NAPALM.inv │ ├── objects_attrs.inv │ ├── objects_attrs.json │ ├── objects_attrs.txt │ ├── objects_attrs_17_2_0.inv │ ├── objects_attrs_20_3_0.inv │ ├── objects_beaker.inv │ ├── objects_bokeh.inv │ ├── objects_bootstrap_datepicker.inv │ ├── objects_cclib.inv │ ├── objects_celery.inv │ ├── objects_click.inv │ ├── objects_cookiecutter.inv │ ├── objects_coverage.inv │ ├── objects_django.inv │ ├── objects_django_channels.inv │ ├── objects_eyeD3.inv │ ├── objects_faker.inv │ ├── objects_flake8.inv │ ├── objects_flask.inv │ ├── objects_fonttools.inv │ ├── objects_gspread.inv │ ├── objects_h5py.inv │ ├── objects_hypothesis.inv │ ├── objects_jinja2.inv │ ├── objects_jsonschema.inv │ ├── objects_matplotlib.inv │ ├── objects_mdn.bad_inv │ ├── objects_mistune.inv │ ├── objects_mkdoc_zlib0.inv │ ├── objects_mypy.inv │ ├── objects_nltk.inv │ ├── objects_noinfo.inv │ ├── objects_noproject.inv │ ├── objects_numpy.inv │ ├── objects_opencv.inv │ ├── objects_pandas.inv │ ├── objects_pdfminer.inv │ ├── objects_pelican.inv │ ├── objects_pingo.inv │ ├── objects_plone.inv │ ├── objects_psutil.inv │ ├── objects_pyexcel.inv │ ├── objects_pygame.inv │ ├── objects_pymongo.inv │ ├── objects_pyqt.inv │ ├── objects_pyserial.inv │ ├── objects_pytest.inv │ ├── objects_python.inv │ ├── objects_requests.inv │ ├── objects_rocket.inv │ ├── objects_sarge.inv │ ├── objects_scapy.inv │ ├── objects_scipy.inv │ ├── objects_scrapy.inv │ ├── objects_sklearn.inv │ ├── objects_sphinx.inv │ ├── objects_sphinx_1_6_6.inv │ ├── objects_sqlalchemy.inv │ ├── objects_sympy.inv │ ├── objects_tinydb.inv │ ├── objects_tox.inv │ ├── objects_twython.inv │ └── objects_yt.inv ├── test_api_fail.py ├── test_api_good.py ├── test_api_good_nonlocal.py ├── test_cli.py ├── test_cli_nonlocal.py ├── test_fixture.py ├── test_flake8_ext.py ├── test_intersphinx.py └── test_valid_objects.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = 3 | src 4 | 5 | omit = 6 | # Don't worry about covering vendored libraries 7 | src/sphobjinv/_vendored/* 8 | 9 | [report] 10 | exclude_lines = 11 | pragma: no cover 12 | ^\s*pass\s*$ 13 | 14 | show_missing = True 15 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | tests/resource/objects_mkdoc_zlib0.inv binary 2 | tests/resource/objects_attrs.txt binary 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [bskinn] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: pypi/sphobjinv # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # bskinn # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # ['https://paypal.me/btskinn'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a bug report to help improve the project 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Brief description** 11 | 12 | 13 | **Expected behavior** 14 | 15 | 16 | **Actual behavior** 17 | 18 | 19 | **To reproduce** 20 | 26 | 27 | **Attachments** 28 | 36 | 37 | **System information** 38 | - Device: 39 | - OS: 40 | 41 | **Python environment** 42 | 43 | *Python* 44 | 48 | 49 | *Libraries* 50 | 54 | 55 | **Additional information** 56 | 57 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | 12 | 13 | **Describe the solution you'd like** 14 | 15 | 16 | **Describe alternatives you've considered** 17 | 18 | 19 | **Additional context** 20 | 24 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Is the PR a fix or a feature?** 4 | 5 | 6 | **Describe the changes in the PR** 7 | 13 | 14 | **Does this PR close any issues?** 15 | 24 | 25 | **Does the PR change/update the following, if relevant?** 26 | 36 | 37 | - [ ] Documentation 38 | - [ ] Tests 39 | - [ ] CHANGELOG 40 | 41 | -------------------------------------------------------------------------------- /.github/workflows/all_core_tests.yml: -------------------------------------------------------------------------------- 1 | name: 'ALL: Run tests on Python 3.12' 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | current_python_tests: 11 | name: ${{ matrix.os }} 12 | runs-on: ${{ matrix.os }} 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ matrix.os }}-${{ github.ref }} 15 | cancel-in-progress: true 16 | strategy: 17 | matrix: 18 | os: ['ubuntu-latest', 'windows-latest'] 19 | 20 | steps: 21 | - name: Check out repo 22 | uses: actions/checkout@v4 23 | 24 | - name: Install Python 25 | uses: actions/setup-python@v5 26 | with: 27 | python-version: '3.12' 28 | cache: 'pip' 29 | cache-dependency-path: requirements-ci.txt 30 | 31 | - name: Update pip 32 | run: python -m pip install -U pip 33 | 34 | - name: Install & report CI dependencies 35 | run: | 36 | python -m pip install -U --force-reinstall -r requirements-ci.txt 37 | python --version 38 | pip list 39 | 40 | - name: Build docs 41 | run: | 42 | cd doc 43 | make html 44 | mkdir scratch 45 | 46 | - name: Run tests & report source coverage 47 | run: | 48 | pytest --cov --testall 49 | -------------------------------------------------------------------------------- /.github/workflows/ready_doctest.yml: -------------------------------------------------------------------------------- 1 | name: 'READY: Run doctests' 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - reopened 8 | - synchronize 9 | - ready_for_review 10 | branches: 11 | - main 12 | - stable 13 | 14 | jobs: 15 | code_doctests: 16 | name: in code 17 | runs-on: 'ubuntu-latest' 18 | concurrency: 19 | group: ${{ github.workflow }}-${{ github.ref }} 20 | cancel-in-progress: true 21 | if: ${{ !github.event.pull_request.draft }} 22 | 23 | steps: 24 | - name: Check out repo 25 | uses: actions/checkout@v4 26 | 27 | - name: Install Python 28 | uses: actions/setup-python@v5 29 | with: 30 | python-version: '3.12' 31 | cache: 'pip' 32 | cache-dependency-path: requirements-ci.txt 33 | 34 | - name: Update pip 35 | run: python -m pip install -U pip 36 | 37 | - name: Install dev requirements 38 | run: python -m pip install -r requirements-ci.txt 39 | 40 | - name: Run doctests 41 | run: | 42 | cd doc 43 | make doctest 44 | -------------------------------------------------------------------------------- /.github/workflows/ready_linting.yml: -------------------------------------------------------------------------------- 1 | name: 'READY: Lint codebase' 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - reopened 8 | - synchronize 9 | - ready_for_review 10 | branches: 11 | - main 12 | - stable 13 | 14 | jobs: 15 | lint_flake8: 16 | name: flake8 17 | runs-on: 'ubuntu-latest' 18 | concurrency: 19 | group: ${{ github.workflow }}-flake8-${{ github.ref }} 20 | cancel-in-progress: true 21 | if: ${{ !github.event.pull_request.draft }} 22 | 23 | steps: 24 | - name: Check out repo 25 | uses: actions/checkout@v4 26 | 27 | - name: Install Python 28 | uses: actions/setup-python@v5 29 | with: 30 | python-version: '3.12' 31 | cache: 'pip' 32 | cache-dependency-path: requirements-flake8.txt 33 | 34 | - name: Update pip 35 | run: python -m pip install -U pip 36 | 37 | - name: Install tox 38 | run: python -m pip install -U tox 39 | 40 | - name: Run flake8 41 | run: tox -e flake8 42 | 43 | lint_interrogate: 44 | name: interrogate 45 | runs-on: 'ubuntu-latest' 46 | concurrency: 47 | group: ${{ github.workflow }}-interrogate-${{ github.ref }} 48 | cancel-in-progress: true 49 | if: ${{ !github.event.pull_request.draft }} 50 | 51 | steps: 52 | - name: Check out repo 53 | uses: actions/checkout@v4 54 | 55 | - name: Install Python 56 | uses: actions/setup-python@v5 57 | with: 58 | python-version: '3.12' 59 | cache: 'pip' 60 | cache-dependency-path: requirements-interrogate.txt 61 | 62 | - name: Update pip 63 | run: python -m pip install -U pip 64 | 65 | - name: Install tox 66 | run: python -m pip install -U tox 67 | 68 | - name: Run interrogate 69 | run: tox -e interrogate 70 | -------------------------------------------------------------------------------- /.github/workflows/ready_test_matrix.yml: -------------------------------------------------------------------------------- 1 | name: 'READY: Run OS/Python test matrix' 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - reopened 8 | - synchronize 9 | - ready_for_review 10 | branches: 11 | - main 12 | - stable 13 | 14 | jobs: 15 | python_os_test_matrix: 16 | name: ${{ matrix.os }} python ${{ matrix.py }} 17 | runs-on: ${{ matrix.os }} 18 | concurrency: 19 | group: ${{ github.workflow }}-${{ matrix.os }}-${{ matrix.py }}-${{ github.ref }} 20 | cancel-in-progress: true 21 | if: ${{ !github.event.pull_request.draft }} 22 | strategy: 23 | matrix: 24 | os: ['windows-latest', 'ubuntu-latest', 'macos-latest'] 25 | py: ['3.9', '3.10', '3.11', '3.13'] 26 | include: 27 | - os: 'macos-latest' 28 | py: '3.12' 29 | 30 | steps: 31 | - name: Check out repo 32 | uses: actions/checkout@v4 33 | 34 | - name: Install Python 35 | uses: actions/setup-python@v5 36 | with: 37 | python-version: ${{ matrix.py }} 38 | cache: 'pip' 39 | cache-dependency-path: requirements-ci.txt 40 | 41 | - name: Update pip 42 | run: python -m pip install -U pip 43 | 44 | - name: Install & report CI dependencies 45 | run: | 46 | python -m pip install -U --force-reinstall -r requirements-ci.txt 47 | python --version 48 | pip list 49 | 50 | - name: Build docs 51 | run: | 52 | cd doc 53 | make html 54 | mkdir scratch 55 | 56 | - name: Run tests 57 | run: | 58 | pytest --testall 59 | -------------------------------------------------------------------------------- /.github/workflows/ready_test_nonloc.yml: -------------------------------------------------------------------------------- 1 | name: 'READY: Run nonlocal tests' 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - reopened 8 | - synchronize 9 | - ready_for_review 10 | branches: 11 | - main 12 | - stable 13 | 14 | jobs: 15 | python_os_test_matrix: 16 | name: ${{ matrix.os }} python 3.12 17 | runs-on: ${{ matrix.os }} 18 | concurrency: 19 | group: ${{ github.workflow }}-${{ matrix.os }}-${{ github.ref }} 20 | cancel-in-progress: true 21 | if: ${{ !github.event.pull_request.draft }} 22 | strategy: 23 | matrix: 24 | os: ['windows-latest', 'ubuntu-latest', 'macos-latest'] 25 | 26 | steps: 27 | - name: Check out repo 28 | uses: actions/checkout@v4 29 | 30 | - name: Install Python 31 | uses: actions/setup-python@v5 32 | with: 33 | python-version: '3.12' 34 | cache: 'pip' 35 | cache-dependency-path: requirements-ci.txt 36 | 37 | - name: Update pip 38 | run: python -m pip install -U pip 39 | 40 | - name: Install & report CI dependencies 41 | run: | 42 | python -m pip install -U --force-reinstall -r requirements-ci.txt 43 | python --version 44 | pip list 45 | 46 | - name: Build docs 47 | run: | 48 | cd doc 49 | make html 50 | mkdir scratch 51 | 52 | - name: Run nonlocal tests 53 | run: | 54 | pytest --nonloc 55 | -------------------------------------------------------------------------------- /.github/workflows/release_check_sdist.yml: -------------------------------------------------------------------------------- 1 | name: 'RELEASE: Check sdist' 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - reopened 8 | - synchronize 9 | - ready_for_review 10 | branches: 11 | - stable 12 | 13 | jobs: 14 | sdist_build_and_check: 15 | name: builds & is testable 16 | runs-on: 'ubuntu-latest' 17 | concurrency: 18 | group: ${{ github.workflow }}-${{ github.ref }} 19 | cancel-in-progress: true 20 | if: ${{ !github.event.pull_request.draft }} 21 | 22 | steps: 23 | - name: Check out repo 24 | uses: actions/checkout@v4 25 | 26 | - name: Install Python 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: '3.12' 30 | cache: 'pip' 31 | cache-dependency-path: requirements-dev.txt 32 | 33 | - name: Install 'build' package 34 | run: python -m pip install build 35 | 36 | - name: Build sdist 37 | run: | 38 | python -m build -s 39 | ls -lah dist 40 | 41 | - name: Create sandbox 42 | run: mkdir sandbox 43 | 44 | - name: Unpack sdist in sandbox 45 | run: | 46 | cp dist/*.gz sandbox/ 47 | cd sandbox 48 | tar xvf *.gz 49 | 50 | - name: Create venv 51 | run: | 52 | cd sandbox 53 | python -m venv env 54 | 55 | # Only the dir of the unpacked sdist will have a digit in its name 56 | - name: Store sdist unpack path 57 | run: echo "UNPACK_PATH=$( find sandbox -maxdepth 1 -type d -regex 'sandbox/.+[0-9].+' )" >> $GITHUB_ENV 58 | 59 | - name: Report sdist unpack path 60 | run: echo $UNPACK_PATH 61 | 62 | - name: Install dev req'ts to venv 63 | run: | 64 | source sandbox/env/bin/activate 65 | cd "$UNPACK_PATH" 66 | python -m pip install -r requirements-dev.txt 67 | 68 | - name: Build docs in sandbox 69 | run: | 70 | source sandbox/env/bin/activate 71 | cd "$UNPACK_PATH"/doc 72 | O=-Ean make html 73 | 74 | - name: Run test suite in sandbox 75 | run: | 76 | source sandbox/env/bin/activate 77 | cd "$UNPACK_PATH" 78 | pytest --nonloc 79 | -------------------------------------------------------------------------------- /.github/workflows/release_doc_warnings.yml: -------------------------------------------------------------------------------- 1 | name: 'RELEASE: Build docs' 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - reopened 8 | - synchronize 9 | - ready_for_review 10 | branches: 11 | - stable 12 | 13 | jobs: 14 | docs_warnings_as_errors: 15 | name: with warnings as errors 16 | runs-on: 'ubuntu-latest' 17 | concurrency: 18 | group: ${{ github.workflow }}-${{ github.ref }} 19 | cancel-in-progress: true 20 | if: ${{ !github.event.pull_request.draft }} 21 | 22 | steps: 23 | - name: Check out repo 24 | uses: actions/checkout@v4 25 | 26 | - name: Install Python 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: '3.12' 30 | cache: 'pip' 31 | cache-dependency-path: requirements-rtd.txt 32 | 33 | - name: Install project and docs requirements 34 | run: pip install . -r requirements-rtd.txt 35 | 36 | - name: Build docs with warnings as errors 37 | run: | 38 | cd doc 39 | make html -Wn --keep-going 40 | -------------------------------------------------------------------------------- /.github/workflows/release_flake8_noqa_nofail.yml: -------------------------------------------------------------------------------- 1 | name: 'RELEASE: Check flake8 noqas' 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - reopened 8 | - synchronize 9 | - ready_for_review 10 | branches: 11 | - stable 12 | 13 | jobs: 14 | check_flake8_noqa: 15 | name: are all relevant (nofail) 16 | runs-on: 'ubuntu-latest' 17 | concurrency: 18 | group: ${{ github.workflow }}-${{ github.ref }} 19 | cancel-in-progress: true 20 | if: ${{ !github.event.pull_request.draft }} 21 | 22 | steps: 23 | - name: Check out repo 24 | uses: actions/checkout@v4 25 | 26 | - name: Install Python 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: '3.12' 30 | cache: 'pip' 31 | cache-dependency-path: requirements-ci.txt 32 | 33 | - name: Install tox 34 | run: pip install tox 35 | 36 | - name: Run never-fail flake8-with-noqa 37 | run: tox -e flake8_noqa 38 | -------------------------------------------------------------------------------- /.github/workflows/release_readme_doctest.yml: -------------------------------------------------------------------------------- 1 | name: 'RELEASE: Run doctests' 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - reopened 8 | - synchronize 9 | - ready_for_review 10 | branches: 11 | - stable 12 | 13 | jobs: 14 | readme_doctest: 15 | name: on readme 16 | runs-on: 'ubuntu-latest' 17 | concurrency: 18 | group: ${{ github.workflow }}-${{ github.ref }} 19 | cancel-in-progress: true 20 | if: ${{ !github.event.pull_request.draft }} 21 | 22 | steps: 23 | - name: Check out repo 24 | uses: actions/checkout@v4 25 | 26 | - name: Install Python 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: '3.12' 30 | cache: 'pip' 31 | cache-dependency-path: requirements-ci.txt 32 | 33 | - name: Install CI requirements 34 | run: pip install -r requirements-ci.txt 35 | 36 | - name: Build docs 37 | run: | 38 | cd doc 39 | make html 40 | 41 | - name: Run README doctests 42 | run: pytest -k readme --doctest-glob="README.md" 43 | -------------------------------------------------------------------------------- /.github/workflows/release_test_file_coverage.yml: -------------------------------------------------------------------------------- 1 | name: 'RELEASE: Check test code' 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - reopened 8 | - synchronize 9 | - ready_for_review 10 | branches: 11 | - stable 12 | 13 | jobs: 14 | check_all_tests_ran: 15 | name: all executed 16 | runs-on: 'ubuntu-latest' 17 | concurrency: 18 | group: ${{ github.workflow }}-${{ github.ref }} 19 | cancel-in-progress: true 20 | if: ${{ !github.event.pull_request.draft }} 21 | 22 | steps: 23 | - name: Check out repo 24 | uses: actions/checkout@v4 25 | 26 | - name: Install Python 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: '3.12' 30 | cache: 'pip' 31 | cache-dependency-path: | 32 | requirements-ci.txt 33 | requirements-flake8.txt 34 | 35 | - name: Install CI requirements 36 | run: pip install -r requirements-ci.txt -r requirements-flake8.txt 37 | 38 | - name: Build docs & ensure scratch 39 | run: | 40 | cd doc 41 | make html 42 | mkdir scratch 43 | 44 | - name: Run pytest covering entire project tree 45 | run: pytest --cov=. --nonloc --flake8_ext 46 | 47 | - name: Check 100% test code execution 48 | run: coverage report --include="tests/*" --fail-under=100 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env*/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | pip-wheel-metadata/ 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *,cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | 56 | # Sphinx documentation 57 | docs/_build/ 58 | 59 | # PyBuilder 60 | target/ 61 | 62 | #Ipython Notebook 63 | .ipynb_checkpoints 64 | 65 | # Editor backup files 66 | *.bak 67 | *.swp 68 | 69 | # intersphinx objects.inv hive 70 | doc/source/isphx/*.inv 71 | 72 | # Test scratch 73 | sphobjinv/test/scratch/* 74 | 75 | # Doctest scratch 76 | doc/scratch/* 77 | 78 | # Mutmut 79 | .mutmut-cache 80 | 81 | # VS Code 82 | .vscode 83 | 84 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build and VM configuration 9 | build: 10 | os: 'ubuntu-22.04' 11 | tools: 12 | python: '3.12' 13 | 14 | # Python requirements 15 | python: 16 | install: 17 | - requirements: requirements-rtd.txt 18 | - method: pip 19 | path: . 20 | 21 | # Build with sphinx 22 | sphinx: 23 | configuration: doc/source/conf.py 24 | 25 | # Build all the things 26 | formats: all 27 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | Credits 2 | ======= 3 | 4 | `sphobjinv` is authored and maintained by Brian Skinn 5 | ([Blog](https://bskinn.github.io)) 6 | ([Mastodon](https://fosstodon.org/@btskinn)). 7 | 8 | The idea for the project came about as I was starting to deepen my expertise 9 | with Sphinx, and found it hugely frustrating to debug cross-references to 10 | objects in code. I discovered the `objects.inv` files relatively quickly, but 11 | struggled with trying to get at the actual object information. At the time 12 | (2016), the ability to 13 | [execute `sphinx.ext.intersphinx` as a module](https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#showing-all-links-of-an-intersphinx-mapping-file) 14 | hadn't yet been documented (that happened in 15 | [2018](https://github.com/sphinx-doc/sphinx/commit/7aaba1758a4622298d15339fddd8556eb221af86)), 16 | and a fair bit of searching didn't turn up anything promising. 17 | 18 | Once I dug into the Sphinx code to figure out how to zlib-decompress the object 19 | data, it was relatively straightforward to put together the initial v1.0 of 20 | `sphobjinv`, which could only (de)compress `objects.inv` files to/from 21 | plaintext. As I started to use it regularly in my own documentation work, it 22 | became clear that there would be significant advantages to also implement object 23 | searches, especially in large documentation sets. Also, it seemed likely that a 24 | robust API for creation/manipulation of inventory contents would be useful, in 25 | order to assist with things like scraping a non-Sphinx website to generate an 26 | `objects.inv` for cross-referencing in other docs. This led to the current 27 | object-oriented API of `sphobjinv` v2.x. 28 | 29 | While there are [numerous](https://github.com/bskinn/sphobjinv/issues) possible 30 | enhancements to the project, I'm satisfied with its ease of use and usefulness, 31 | at least for my purposes, and thus consider it fully stable. I'm always glad to 32 | receive feature requests and bug reports, though. 33 | -------------------------------------------------------------------------------- /CONTENT_LICENSE.txt: -------------------------------------------------------------------------------- 1 | The sphobjinv documentation (including docstrings and README) is licensed under 2 | a Creative Commons Attribution 4.0 International License (CC-BY). 3 | 4 | See http://creativecommons.org/licenses/by/4.0/. 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2025 Brian Skinn and community contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.md CHANGELOG.md CONTRIBUTING.md LICENSE.txt pyproject.toml 2 | include README.md requirements-dev.txt requirements-flake8.txt tox.ini 3 | 4 | graft src/sphobjinv/_vendored/fuzzywuzzy 5 | 6 | graft doc/source 7 | include doc/make.bat doc/Makefile 8 | 9 | include conftest.py 10 | graft tests 11 | prune tests/resource 12 | include tests/resource/objects_attrs* tests/resource/objects_sarge* 13 | 14 | global-exclude __pycache__/* 15 | prune **/*.egg-info 16 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | `sphobjinv` development currently does not use any maintenance branches, 6 | so any security fixes will be released inline with the primary development 7 | branch. 8 | 9 | 10 | ## Reporting a Vulnerability 11 | 12 | To report a security vulnerability, please use the 13 | [Tidelift security contact](https://tidelift.com/security). 14 | Tidelift will coordinate the fix and disclosure, including 15 | any updates on progress toward a fix on a vulnerability and 16 | on accept/decline status of the vulnerability report. 17 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Custom for sphinx-autobuild 18 | livehtml: 19 | sphinx-autobuild "$(SOURCEDIR)" "$(BUILDDIR)"/html -nb html $(SPHINXOPTS) $(O) 20 | 21 | # Catch-all target: route all unknown targets to Sphinx using the new 22 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 23 | %: Makefile 24 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 25 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | 29 | if "%1" == "livehtml" ( 30 | sphinx-autobuild %SOURCEDIR% %BUILDDIR%/html -nb html %SPHINXOPTS% %O% %2 31 | goto end 32 | ) 33 | 34 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% %2 35 | goto end 36 | 37 | :help 38 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 39 | 40 | :end 41 | popd 42 | -------------------------------------------------------------------------------- /doc/source/_static/css/custom.css: -------------------------------------------------------------------------------- 1 | /* Lighten the header blue color */ 2 | div.wy-side-nav-search { 3 | background-color: #7ebbff; 4 | } 5 | 6 | /* White version text in header */ 7 | div.version { 8 | color: #ffffff !important; 9 | font-weight: bolder !important; 10 | } 11 | 12 | /* Blue border around the search bar */ 13 | form.wy-form input { 14 | border: 3px solid #6eabef !important; 15 | } 16 | 17 | /* 18 | ul.current selects only the sidebar ul. Otherwise, 19 | toctrees in the text get colored white, too 20 | */ 21 | ul.current li.toctree-l1:not(.current) a { 22 | color: #f8f8f8 !important; 23 | } 24 | 25 | /* Increase font size for code object names in navbar toctree */ 26 | a.reference.internal code.docutils.literal.notranslate { 27 | font-size: 85%; 28 | } 29 | 30 | /* 31 | Reduce margin at bottom of 'property' docs. 32 | Too much vertical whitespace previously. 33 | */ 34 | .rst-content dl.property { 35 | margin-bottom: 10px !important; 36 | } 37 | 38 | /* 39 | Reduced font size in footer 40 | */ 41 | footer, footer div[role=contentinfo] p, footer p.footer-license { 42 | font-size: 13px; 43 | } 44 | 45 | footer p.footer-license { 46 | line-height: 20px; 47 | } 48 | -------------------------------------------------------------------------------- /doc/source/_static/extlink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 15 | 16 | -------------------------------------------------------------------------------- /doc/source/_static/mouseover_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/doc/source/_static/mouseover_example.png -------------------------------------------------------------------------------- /doc/source/_static/no-leven.csv: -------------------------------------------------------------------------------- 1 | objects_NAPALM.inv,130,0.11515661986055648 2 | objects_tox.inv,88,0.0683006333228036 3 | objects_matplotlib.inv,8953,7.64818928720864 4 | objects_attrs.inv,56,0.03558660290635487 5 | objects_sarge.inv,38,0.02755600001109997 6 | objects_celery.inv,3923,3.458657090239697 7 | objects_faker.inv,84,0.06208212610586372 8 | objects_flake8.inv,382,0.3444451205225533 9 | objects_nltk.inv,5116,5.172296742273704 10 | objects_numpy.inv,5788,4.830400875248978 11 | objects_scapy.inv,15,0.012303170722907453 12 | objects_sqlalchemy.inv,5091,4.315117286601753 13 | objects_bokeh.inv,5000,4.5330708350306566 14 | objects_sklearn.inv,3770,3.4456311832337407 15 | objects_yt.inv,17420,17.30672209541367 16 | objects_mypy.inv,86,0.06447999298280252 17 | objects_python.inv,11758,8.650611394120062 18 | objects_requests.inv,204,0.17869077736821737 19 | objects_pandas.inv,4350,3.9645195237969175 20 | objects_pingo.inv,31,0.032406772669554586 21 | objects_plone.inv,971,0.8242771353060278 22 | objects_pygame.inv,730,0.49247837856100884 23 | objects_cclib.inv,4,0.002771108091582164 24 | objects_django_channels.inv,58,0.04141235044569953 25 | objects_scrapy.inv,749,0.626478651977493 26 | objects_scipy.inv,6749,5.791793671671792 27 | objects_twython.inv,156,0.13935449598312744 28 | objects_cookiecutter.inv,159,0.11767726735197641 29 | objects_eyeD3.inv,908,0.8202465134454201 30 | objects_beaker.inv,140,0.10308000554734634 31 | objects_jsonschema.inv,78,0.06223058890839752 32 | objects_flask.inv,471,0.40689920373364713 33 | objects_pelican.inv,57,0.0405015217684138 34 | objects_pyexcel.inv,252,0.21774874285688384 35 | objects_pyserial.inv,166,0.1388799089033114 36 | objects_click.inv,217,0.18250199432067973 37 | objects_mistune.inv,36,0.02741383922237901 38 | objects_coverage.inv,151,0.12183406777806453 39 | objects_pytest.inv,466,0.3634280900709996 40 | objects_sphinx.inv,1069,0.865545171610347 41 | objects_tinydb.inv,82,0.06814130497913311 42 | objects_pyqt.inv,3285,3.0031304809246535 43 | objects_jinja2.inv,401,0.32361544360842914 44 | objects_h5py.inv,159,0.12122711865208088 45 | objects_opencv.inv,1144,0.6058922446509201 46 | objects_psutil.inv,176,0.14105603971968889 47 | objects_sympy.inv,4987,3.72812511729843 48 | objects_hypothesis.inv,149,0.09890921820654626 49 | objects_pdfminer.inv,6,0.004549224259631046 50 | objects_gspread.inv,75,0.06063720669458803 51 | objects_rocket.inv,5,0.0036870139398843095 52 | objects_pymongo.inv,773,0.6657593413032487 53 | -------------------------------------------------------------------------------- /doc/source/_static/soi-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/doc/source/_static/soi-logo.png -------------------------------------------------------------------------------- /doc/source/_static/soi-logo_duo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/doc/source/_static/soi-logo_duo.png -------------------------------------------------------------------------------- /doc/source/_static/soi-logo_duo_border.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/doc/source/_static/soi-logo_duo_border.png -------------------------------------------------------------------------------- /doc/source/_static/soi-logo_duo_border.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/doc/source/_static/soi-logo_duo_border.xcf -------------------------------------------------------------------------------- /doc/source/_static/suggest_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/doc/source/_static/suggest_timing.png -------------------------------------------------------------------------------- /doc/source/_static/suggest_timing.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/doc/source/_static/suggest_timing.xlsx -------------------------------------------------------------------------------- /doc/source/_static/suggest_timing_commands.txt: -------------------------------------------------------------------------------- 1 | In [19]: print(*ho.get_range(711), sep='\n') 2 | (711, 1, 'import sphobjinv as soi') 3 | (711, 2, 'import os') 4 | (711, 3, 'import timeit') 5 | (711, 4, 'import csv') 6 | (711, 5, 'results = {}') 7 | (711, 6, 'lengths = {}') 8 | (711, 7, 'for fn in os.listdir():\n if fn.endswith(\'.inv\'):\n inv = soi.Inventory(fn)\n timings = timeit.repeat("inv.suggest(\'function\')", repeat=count, number=1, globals=globals())\n results.update({fn: sum(timings) / len(timings)})\n lengths.update({fn: inv.count})\n print((fn, results[fn], lengths[fn]))\n ') 9 | (711, 8, 'count = 20') 10 | (711, 9, 'for fn in os.listdir():\n if fn.endswith(\'.inv\'):\n inv = soi.Inventory(fn)\n timings = timeit.repeat("inv.suggest(\'function\')", repeat=count, number=1, globals=globals())\n results.update({fn: sum(timings) / len(timings)})\n lengths.update({fn: inv.count})\n print((fn, results[fn], lengths[fn]))\n ') 11 | (711, 10, "with open('\\\\git\\\\with-leven.csv', 'w') as f:\n csvw = csv.writer(f)\n for fn in results:\n csvw.writerow([fn, lengths[fn], results[fn]])\n ") 12 | 13 | In [20]: print(*ho.get_range(710), sep='\n') 14 | (710, 1, 'import sphobjinv as soi') 15 | (710, 2, 'results = {}') 16 | (710, 3, 'import os') 17 | (710, 4, "for fn in os.listdir():\n if fn.endswith('.inv'):\n inv = soi.Inventory(fn)\n ") 18 | (710, 5, 'import timeit') 19 | (710, 6, 'count = 20') 20 | (710, 7, 'timeit.repeat?') 21 | (710, 8, 'for fn in os.listdir():\n if fn.endswith(\'.inv\'):\n inv = soi.Inventory(fn)\n timings = timeit.repeat("inv.suggest(\'function\')", repeat=count, number=1, globals=globals())\n results.update({fn: sum(timings) / len(timings)})\n ') 22 | (710, 9, 'results') 23 | (710, 10, 'lengths = {}') 24 | (710, 11, 'results = {}') 25 | (710, 12, 'for fn in os.listdir():\n if fn.endswith(\'.inv\'):\n inv = soi.Inventory(fn)\n timings = timeit.repeat("inv.suggest(\'function\')", repeat=count, number=1, globals=globals())\n results.update({fn: sum(timings) / len(timings)})\n lengths.update({fn: inv.count})\n ') 26 | (710, 13, 'results') 27 | (710, 14, 'lengths') 28 | (710, 15, 'results = {}') 29 | (710, 16, 'lengths = {}') 30 | (710, 17, 'for fn in os.listdir():\n if fn.endswith(\'.inv\'):\n inv = soi.Inventory(fn)\n timings = timeit.repeat("inv.suggest(\'function\')", repeat=count, number=1, globals=globals())\n results.update({fn: sum(timings) / len(timings)})\n lengths.update({fn: inv.count})\n print((fn, results[fn], lengths[fn]))\n ') 31 | (710, 18, 'import csv') 32 | (710, 19, "with open('\\\\git\\\\no-leven.csv', 'w') as f:\n csvw = csv.writer(f)\n for fn in results:\n csvw.writeline([fn, lengths[fn], results[fn]])\n ") 33 | (710, 20, "with open('\\\\git\\\\no-leven.csv', 'w') as f:\n csvw = csv.writer(f)\n for fn in results:\n csvw.writerow([fn, lengths[fn], results[fn]])\n ") -------------------------------------------------------------------------------- /doc/source/_static/with-leven.csv: -------------------------------------------------------------------------------- 1 | objects_sklearn.inv,3770,0.5595020737379116 2 | objects_scrapy.inv,749,0.10424044533704376 3 | objects_rocket.inv,5,0.0006751057019570794 4 | objects_python.inv,11758,1.5405005339919597 5 | objects_h5py.inv,159,0.020739275612154273 6 | objects_jsonschema.inv,78,0.01139018869296109 7 | objects_mypy.inv,86,0.011174557090911464 8 | objects_pyqt.inv,3285,0.4755398099597102 9 | objects_plone.inv,971,0.14140265048083123 10 | objects_yt.inv,17420,2.7540412503353435 11 | objects_pytest.inv,466,0.06242308678564825 12 | objects_django_channels.inv,58,0.007237378883788992 13 | objects_flask.inv,471,0.06570884617041059 14 | objects_opencv.inv,1144,0.12659738270845936 15 | objects_scapy.inv,15,0.0019016079815500574 16 | objects_sqlalchemy.inv,5091,0.7308581367296014 17 | objects_eyeD3.inv,908,0.13062778725823687 18 | objects_numpy.inv,5788,0.8150918019689974 19 | objects_requests.inv,204,0.02754663589018804 20 | objects_sympy.inv,4987,0.6616934360673582 21 | objects_attrs.inv,56,0.00730448841674052 22 | objects_psutil.inv,176,0.02321377418718811 23 | objects_beaker.inv,140,0.01950421918838754 24 | objects_gspread.inv,75,0.010038989512579376 25 | objects_matplotlib.inv,8953,1.3064366872822202 26 | objects_pymongo.inv,773,0.1132662132641002 27 | objects_cclib.inv,4,0.0005350785104488942 28 | objects_sarge.inv,38,0.004963023575857051 29 | objects_tox.inv,88,0.012009583802365853 30 | objects_NAPALM.inv,130,0.018531990326111015 31 | objects_pandas.inv,4350,0.6302736891514897 32 | objects_pyexcel.inv,252,0.034985007528833025 33 | objects_cookiecutter.inv,159,0.02084629131424407 34 | objects_tinydb.inv,82,0.0111470771496883 35 | objects_pdfminer.inv,6,0.0007919201465675485 36 | objects_coverage.inv,151,0.020564004556414696 37 | objects_pingo.inv,31,0.004558213025448765 38 | objects_nltk.inv,5116,0.8097258446180206 39 | objects_faker.inv,84,0.010932808679157269 40 | objects_hypothesis.inv,149,0.01897518586989655 41 | objects_pelican.inv,57,0.007269284063571746 42 | objects_mistune.inv,36,0.005061702444588789 43 | objects_twython.inv,156,0.021934998777132363 44 | objects_sphinx.inv,1069,0.14731972783202424 45 | objects_flake8.inv,382,0.05498021088632399 46 | objects_pyserial.inv,166,0.023005610175208347 47 | objects_jinja2.inv,401,0.055498121841830894 48 | objects_pygame.inv,730,0.093677302121705 49 | objects_bokeh.inv,5000,0.7286385635043465 50 | objects_scipy.inv,6749,0.9440434708568404 51 | objects_celery.inv,3923,0.5720658791585889 52 | objects_click.inv,217,0.029648288361187623 53 | -------------------------------------------------------------------------------- /doc/source/_templates/footer.html: -------------------------------------------------------------------------------- 1 | {% extends "!footer.html" %} 2 | 3 | {%-block extrafooter %} 4 | 5 | {{ super() }} 6 | 7 |

8 | 9 | 14 | 15 | 20 | 21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /doc/source/api/data.rst: -------------------------------------------------------------------------------- 1 | .. Module API page for data.py 2 | 3 | sphobjinv.data 4 | ============== 5 | 6 | .. automodule:: sphobjinv.data 7 | :members: -------------------------------------------------------------------------------- /doc/source/api/enum.rst: -------------------------------------------------------------------------------- 1 | .. Module API page for enum.py 2 | 3 | sphobjinv.enum 4 | ============== 5 | 6 | .. automodule:: sphobjinv.enum 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/source/api/error.rst: -------------------------------------------------------------------------------- 1 | .. Module API page for error.py 2 | 3 | sphobjinv.error 4 | =============== 5 | 6 | .. automodule:: sphobjinv.error 7 | :members: -------------------------------------------------------------------------------- /doc/source/api/fileops.rst: -------------------------------------------------------------------------------- 1 | .. Module API page for fileops.py 2 | 3 | sphobjinv.fileops 4 | ================= 5 | 6 | .. automodule:: sphobjinv.fileops 7 | :members: -------------------------------------------------------------------------------- /doc/source/api/index.rst: -------------------------------------------------------------------------------- 1 | .. API page 2 | 3 | API 4 | === 5 | 6 | Most (all?) of the objects documented in the below submodules 7 | are also exposed at the |soi| package root. For example, 8 | both of the following will work to import the 9 | :class:`~sphobjinv.inventory.Inventory` class: 10 | 11 | .. doctest:: api_index 12 | 13 | >>> from sphobjinv import Inventory 14 | >>> from sphobjinv.inventory import Inventory 15 | 16 | 17 | .. toctree:: 18 | :maxdepth: 1 19 | 20 | data 21 | enum 22 | error 23 | fileops 24 | inventory 25 | re 26 | schema 27 | zlib 28 | -------------------------------------------------------------------------------- /doc/source/api/inventory.rst: -------------------------------------------------------------------------------- 1 | .. Module API page for inventory.py 2 | 3 | sphobjinv.inventory 4 | =================== 5 | 6 | .. automodule:: sphobjinv.inventory 7 | :members: 8 | 9 | -------------------------------------------------------------------------------- /doc/source/api/re.rst: -------------------------------------------------------------------------------- 1 | .. Module API page for re.py 2 | 3 | sphobjinv.re 4 | ============ 5 | 6 | .. automodule:: sphobjinv.re 7 | :members: -------------------------------------------------------------------------------- /doc/source/api/schema.rst: -------------------------------------------------------------------------------- 1 | .. Module API page for schema.py 2 | 3 | sphobjinv.schema 4 | ================ 5 | 6 | .. automodule:: sphobjinv.schema 7 | :members: -------------------------------------------------------------------------------- /doc/source/api/zlib.rst: -------------------------------------------------------------------------------- 1 | .. Module API page for zlib.py 2 | 3 | sphobjinv.zlib 4 | ============== 5 | 6 | .. automodule:: sphobjinv.zlib 7 | :members: -------------------------------------------------------------------------------- /doc/source/api_usage.rst: -------------------------------------------------------------------------------- 1 | .. API usage page 2 | 3 | API Usage 4 | ========= 5 | 6 | In all of the below, the |soi| package has been imported as 7 | |cour|\ soi\ |/cour|, and the working temp directory has 8 | been populated with the |cour|\ objects_attrs.inv\ |/cour| inventory. 9 | 10 | Inspecting an Inventory 11 | ----------------------- 12 | 13 | Inspecting the contents of an existing inventory is handled entirely by the 14 | :class:`~sphobjinv.inventory.Inventory` class: 15 | 16 | .. doctest:: api_inspect 17 | 18 | >>> inv = soi.Inventory('objects_attrs.inv') 19 | >>> print(inv) 20 | 21 | >>> inv.version 22 | '22.1' 23 | >>> inv.count 24 | 129 25 | 26 | The location of the inventory file to import can also be provided as 27 | a :class:`pathlib.Path`, instead of as a string: 28 | 29 | .. doctest:: api_inspect 30 | 31 | >>> soi.Inventory(Path('objects_attrs.inv')).project 32 | 'attrs' 33 | 34 | The individual objects contained in the inventory are represented by instances 35 | of the :class:`~sphobjinv.data.DataObjStr` class, which are stored in 36 | a |list| in the :attr:`~sphobjinv.inventory.Inventory.objects` attribute: 37 | 38 | .. doctest:: api_inspect 39 | 40 | >>> len(inv.objects) 41 | 129 42 | >>> dobj = inv.objects[0] 43 | >>> dobj 44 | DataObjStr(name='attr', domain='py', role='module', priority='0', uri='index.html#module-$', dispname='-') 45 | >>> dobj.name 46 | 'attr' 47 | >>> dobj.domain 48 | 'py' 49 | >>> [d.name for d in inv.objects if 'validator' in d.uri] 50 | ['api_validators', 'examples_validators'] 51 | 52 | :class:`~sphobjinv.inventory.Inventory` objects can also import from plaintext or zlib-compressed 53 | inventories, as |bytes|: 54 | 55 | .. doctest:: api_inspect 56 | 57 | >>> inv2 = soi.Inventory(inv.data_file()) 58 | >>> print(inv2) 59 | 60 | >>> inv3 = soi.Inventory(soi.compress(inv.data_file())) 61 | >>> print(inv3) 62 | 63 | 64 | Remote |objects.inv| files can also be retrieved via URL, with the *url* keyword argument: 65 | 66 | .. doctest:: api_inspect 67 | 68 | >>> inv4 = soi.Inventory(url='https://github.com/bskinn/sphobjinv/raw/main/tests/resource/objects_attrs.inv') 69 | >>> print(inv4) 70 | 71 | 72 | Comparing Inventories 73 | --------------------- 74 | 75 | |Inventory| instances compare equal when they have the same :attr:`~sphobjinv.inventory.Inventory.project` and 76 | :attr:`~sphobjinv.inventory.Inventory.version`, and when all the members of 77 | :attr:`~sphobjinv.inventory.Inventory.objects` are identical between the two instances: 78 | 79 | .. doctest:: api_compare 80 | 81 | >>> inv = soi.Inventory("objects_attrs.inv") 82 | >>> inv2 = soi.Inventory(inv.data_file()) 83 | >>> inv is inv2 84 | False 85 | >>> inv == inv2 86 | True 87 | >>> inv2.project = "foo" 88 | >>> inv == inv2 89 | False 90 | 91 | Individual |DataObjStr| and (|DataObjBytes|) instances compare equal if all of 92 | :attr:`~sphobjinv.data.SuperDataObj.name`, :attr:`~sphobjinv.data.SuperDataObj.domain`, 93 | :attr:`~sphobjinv.data.SuperDataObj.role`, :attr:`~sphobjinv.data.SuperDataObj.priority`, 94 | :attr:`~sphobjinv.data.SuperDataObj.uri`, and :attr:`~sphobjinv.data.SuperDataObj.dispname` 95 | are equal: 96 | 97 | .. doctest:: api_compare 98 | 99 | >>> obj1 = inv.objects[0] 100 | >>> obj2 = inv.objects[1] 101 | >>> obj1 == obj1 102 | True 103 | >>> obj1 == obj2 104 | False 105 | >>> obj1 == obj1.evolve(name="foo") 106 | False 107 | 108 | .. versionchanged:: 2.1 109 | Previously, |Inventory| instances would only compare equal to themselves, 110 | and comparison attempts on |SuperDataObj| subclass instances would raise :exc:`RecursionError`. 111 | 112 | Modifying an Inventory 113 | ---------------------- 114 | 115 | The :class:`~sphobjinv.data.DataObjStr` instances can be edited in place: 116 | 117 | .. doctest:: api_modify 118 | 119 | >>> inv = soi.Inventory('objects_attrs.inv') 120 | >>> inv.objects[0] 121 | DataObjStr(name='attr', domain='py', role='module', priority='0', uri='index.html#module-$', dispname='-') 122 | >>> inv.objects[0].uri = 'attribute.html' 123 | >>> inv.objects[0] 124 | DataObjStr(name='attr', domain='py', role='module', priority='0', uri='attribute.html', dispname='-') 125 | 126 | New instances can be easily created either by direct instantiation, or by 127 | :meth:`~sphobjinv.data.SuperDataObj.evolve`: 128 | 129 | .. doctest:: api_modify 130 | 131 | >>> inv.objects.append(inv.objects[0].evolve(name='attr.Generator', uri='generator.html')) 132 | >>> inv.count 133 | 130 134 | >>> inv.objects[-1] 135 | DataObjStr(name='attr.Generator', domain='py', role='module', priority='0', uri='generator.html', dispname='-') 136 | 137 | The other attributes of the :class:`~sphobjinv.inventory.Inventory` instance can also be freely modified: 138 | 139 | .. doctest:: api_modify 140 | 141 | >>> inv.project = 'not_attrs' 142 | >>> inv.version = '0.1' 143 | >>> print(inv) 144 | 145 | 146 | 147 | Formatting Inventory Contents 148 | ----------------------------- 149 | 150 | The contents of the :class:`~sphobjinv.inventory.Inventory` can be converted to 151 | the plaintext |objects.inv| format **as** |bytes| via :meth:`~sphobjinv.inventory.Inventory.data_file`: 152 | 153 | .. doctest:: api_formatting 154 | 155 | >>> inv = soi.Inventory('objects_attrs.inv') 156 | >>> print(*inv.data_file().splitlines()[:6], sep='\n') 157 | b'# Sphinx inventory version 2' 158 | b'# Project: attrs' 159 | b'# Version: 22.1' 160 | b'# The remainder of this file is compressed using zlib.' 161 | b'attr py:module 0 index.html#module-$ -' 162 | b'attr.VersionInfo py:class 1 api.html#$ -' 163 | 164 | This method makes use of the :meth:`DataObjStr.data_line ` 165 | method to format each of the object information lines. 166 | 167 | If desired, the :ref:`shorthand ` used for the 168 | :attr:`~sphobjinv.data.SuperDataObj.uri` and 169 | :attr:`~sphobjinv.data.SuperDataObj.dispname` fields can be expanded: 170 | 171 | .. doctest:: api_formatting 172 | 173 | >>> print(*inv.data_file(expand=True).splitlines()[4:6], sep='\n') 174 | b'attr py:module 0 index.html#module-attr attr' 175 | b'attr.VersionInfo py:class 1 api.html#attr.VersionInfo attr.VersionInfo' 176 | >>> do = inv.objects[0] 177 | >>> do.data_line(expand=True) 178 | 'attr py:module 0 index.html#module-attr attr' 179 | 180 | 181 | Exporting an Inventory 182 | ---------------------- 183 | 184 | :class:`~sphobjinv.inventory.Inventory` instances can be written to disk 185 | in three formats: zlib-compressed |objects.inv|, 186 | plaintext |objects.txt|, and JSON. The API does not provide single-function 187 | means to do this, however. 188 | 189 | To start, load the source |objects.inv|: 190 | 191 | .. doctest:: api_exporting 192 | 193 | >>> from pathlib import Path 194 | >>> inv = soi.Inventory('objects_attrs.inv') 195 | 196 | To export plaintext: 197 | 198 | .. doctest:: api_exporting 199 | 200 | >>> df = inv.data_file() 201 | >>> soi.writebytes('objects_attrs.txt', df) 202 | >>> print(*Path('objects_attrs.txt').read_text().splitlines()[:6], sep='\n') 203 | # Sphinx inventory version 2 204 | # Project: attrs 205 | # Version: 22.1 206 | # The remainder of this file is compressed using zlib. 207 | attr py:module 0 index.html#module-$ - 208 | attr.VersionInfo py:class 1 api.html#$ - 209 | 210 | For zlib-compressed: 211 | 212 | .. doctest:: api_exporting 213 | 214 | >>> dfc = soi.compress(df) 215 | >>> soi.writebytes('objects_attrs_new.inv', dfc) 216 | >>> print(*Path('objects_attrs_new.inv').read_bytes().splitlines()[:4], sep='\n') 217 | b'# Sphinx inventory version 2' 218 | b'# Project: attrs' 219 | b'# Version: 22.1' 220 | b'# The remainder of this file is compressed using zlib.' 221 | >>> print(Path('objects_attrs_new.inv').read_bytes().splitlines()[6][:10]) 222 | b'\xbf\x86\x8fL49\xc4\x91\xb8\x8c' 223 | 224 | For JSON: 225 | 226 | .. doctest:: api_exporting 227 | 228 | >>> jd = inv.json_dict() 229 | >>> soi.writejson('objects_attrs.json', jd) 230 | >>> print(Path('objects_attrs.json').read_text()[:51]) # doctest: +SKIP 231 | {"project": "attrs", "version": "17.2", "count": 56 232 | -------------------------------------------------------------------------------- /doc/source/cli/convert.rst: -------------------------------------------------------------------------------- 1 | .. Description of convert commandline usage 2 | 3 | Command-Line Usage: "convert" Subcommand 4 | ======================================== 5 | 6 | .. program:: sphobjinv convert 7 | 8 | The |cour|\ convert\ |/cour| subcommand is used for all conversions of 9 | "version 2" Sphinx inventory 10 | files among plaintext, zlib-compressed, and (unique to |soi|) JSON formats. 11 | The |soi| CLI can read and write inventory data from local files 12 | in any of these three formats, as well as read the standard zlib-compressed format 13 | from files in remote locations (see :option:`--url`). 14 | 15 | As of v2.1, the |soi| CLI can also read/write inventories at ``stdin``/``stdout`` 16 | in the plaintext and JSON formats; see :ref:`below `. 17 | 18 | ---- 19 | 20 | Basic file conversion to the default output filename is straightforward: 21 | 22 | .. doctest:: convert_main 23 | 24 | >>> Path('objects_attrs.txt').is_file() 25 | False 26 | >>> cli_run('sphobjinv convert plain objects_attrs.inv') 27 | 28 | Conversion completed. 29 | '...objects_attrs.inv' converted to '...objects_attrs.txt' (plain). 30 | 31 | 32 | >>> print(file_head('objects_attrs.txt', head=6)) 33 | # Sphinx inventory version 2 34 | # Project: attrs 35 | # Version: 22.1 36 | # The remainder of this file is compressed using zlib. 37 | attr py:module 0 index.html#module-$ - 38 | attr.VersionInfo py:class 1 api.html#$ - 39 | 40 | A different target filename can be specified, to avoid overwriting an existing 41 | file: 42 | 43 | .. doctest:: convert_main 44 | 45 | >>> cli_run('sphobjinv convert plain objects_attrs.inv', inp='n\n') 46 | File exists. Overwrite (Y/N)? n 47 | 48 | 49 | Exiting... 50 | 51 | >>> cli_run('sphobjinv convert plain objects_attrs.inv objects_attrs_foo.txt') 52 | 53 | Conversion completed. 54 | '...objects_attrs.inv' converted to '...objects_attrs_foo.txt' (plain). 55 | 56 | 57 | 58 | If you don't provide an output file extension, the |soi| defaults 59 | (`.inv`/`.txt`/`.json`) will be used. 60 | 61 | If you want to pull an input file directly from the internet, use 62 | :option:`--url` (note that the base filename is **not** inferred from the 63 | indicated URL): 64 | 65 | .. doctest:: convert_url 66 | 67 | >>> cli_run('sphobjinv convert plain -u https://github.com/bskinn/sphobjinv/raw/main/tests/resource/objects_attrs.inv') 68 | 69 | Attempting https://github.com/bskinn/sphobjinv/raw/main/tests/resource/objects_attrs.inv ... 70 | ... inventory found. 71 | 72 | Conversion completed. 73 | 'https://github.com/b[...]ce/objects_attrs.inv' converted to '...objects.txt' (plain). 74 | 75 | 76 | >>> print(file_head('objects.txt', head=6)) 77 | # Sphinx inventory version 2 78 | # Project: attrs 79 | # Version: 22.1 80 | # The remainder of this file is compressed using zlib. 81 | attr py:module 0 index.html#module-$ - 82 | attr.VersionInfo py:class 1 api.html#$ - 83 | 84 | The URL provided **MUST** have the leading protocol specified (here, 85 | |cour|\ https\ ://\ |/cour|). 86 | 87 | It is not necessary to locate the |objects.inv| file before running |soi|; 88 | for most Sphinx documentation sets, if you provide a URL to any page in the docs, 89 | it will automatically find and use the correct |objects.inv|: 90 | 91 | .. doctest:: convert_url 92 | 93 | >>> cli_run('sphobjinv convert plain -ou https://docs.python.org/3/library/urllib.error.html#urllib.error.URLError') 94 | 95 | Attempting https://docs.python.org/3/library/urllib.error.html#urllib.error.URLError ... 96 | ... no recognized inventory. 97 | Attempting "https://docs.python.org/3/library/urllib.error.html/objects.inv" ... 98 | ... HTTP error: 404 Not Found. 99 | Attempting "https://docs.python.org/3/library/objects.inv" ... 100 | ... HTTP error: 404 Not Found. 101 | Attempting "https://docs.python.org/3/objects.inv" ... 102 | ... inventory found. 103 | 104 | Conversion completed. 105 | '...objects.inv' converted to '...objects.txt' (plain). 106 | 107 | 108 | 109 | |soi| only supports download of zlib-compressed |objects.inv| files by URL. 110 | Plaintext download by URL is unreliable, presumably due to encoding problems. 111 | If processing of JSON files by API URL is desirable, please 112 | `submit an issue `__. 113 | 114 | .. versionadded:: 2.1 115 | The URL at which a remote inventory is found is now included 116 | in JSON output: 117 | 118 | .. doctest:: json-url 119 | 120 | >>> cli_run('sphobjinv convert json -qu https://docs.python.org/3/ objects.json') 121 | 122 | >>> data = json.loads(Path('objects.json').read_text()) 123 | >>> data["metadata"]["url"] 124 | 'https://docs.python.org/3/objects.inv' 125 | 126 | .. _cli_usage_json_added: 127 | 128 | .. versionadded:: 2.1 129 | JSON and plaintext inventories can now be read from ``stdin`` and 130 | written to ``stdout``, by using the special value ``-`` in the invocation. 131 | E.g., to print to ``stdout``: 132 | 133 | .. doctest:: stdio 134 | 135 | >>> cli_run('sphobjinv co plain objects_attrs.inv -') 136 | # Sphinx inventory version 2 137 | # Project: attrs 138 | # Version: 22.1 139 | # The remainder of this file is compressed using zlib. 140 | attr py:module 0 index.html#module-$ - 141 | attr.VersionInfo py:class 1 api.html#$ - 142 | attr._make.Attribute py:class -1 api.html#attrs.Attribute - 143 | ... 144 | 145 | 146 | **Usage** 147 | 148 | .. command-output:: sphobjinv convert --help 149 | :ellipsis: 4 150 | 151 | 152 | **Positional Arguments** 153 | 154 | .. option:: mode 155 | 156 | Conversion output format. 157 | 158 | Must be one of `plain`, `zlib`, or `json` 159 | 160 | .. option:: infile 161 | 162 | Path (or URL, if :option:`--url` is specified) to file to be converted. 163 | 164 | If passed as ``-``, |soi| will attempt import of a plaintext or JSON 165 | inventory from ``stdin`` (incompatible with :option:`--url`). 166 | 167 | .. option:: outfile 168 | 169 | *(Optional)* Path to desired output file. Defaults to same directory 170 | and main file name as input file but with extension 171 | |cour|\ .inv/.txt/.json\ |/cour|, as appropriate for the output format. 172 | 173 | A bare path is accepted here, using the default output 174 | file name/extension. 175 | 176 | If passed as ``-``, or if omitted when `infile` is passed as ``-``, 177 | |soi| will emit plaintext or JSON (but *not* 178 | zlib-compressed) inventory contents to ``stdout``. 179 | 180 | **Flags** 181 | 182 | .. option:: -h, --help 183 | 184 | Display `convert` help message and exit. 185 | 186 | .. option:: -o, --overwrite 187 | 188 | If the output file already exists, overwrite without prompting 189 | for confirmation. 190 | 191 | .. option:: -q, --quiet 192 | 193 | Suppress all status message output, regardless of success or failure. 194 | Useful for scripting/automation. Implies :option:`--overwrite`. 195 | 196 | .. option:: -u, --url 197 | 198 | Treat :option:`infile` as a URL for download. Cannot be used when 199 | :option:`infile` is passed as ``-``. 200 | 201 | .. option:: -e, --expand 202 | 203 | Expand any abbreviations in `uri` or `dispname` fields before writing to output; 204 | see :ref:`here `. Cannot be specified with 205 | :option:`--contract`. 206 | 207 | .. option:: -c, --contract 208 | 209 | Contract `uri` and `dispname` fields, if possible, before writing to output; 210 | see :ref:`here `. Cannot be specified with 211 | :option:`--expand`. 212 | -------------------------------------------------------------------------------- /doc/source/cli/implementation/convert.rst: -------------------------------------------------------------------------------- 1 | .. Module API page for cli/convert.py 2 | 3 | sphobjinv.cli.convert 4 | ===================== 5 | 6 | .. automodule:: sphobjinv.cli.convert 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/source/cli/implementation/core.rst: -------------------------------------------------------------------------------- 1 | .. Module API page for cli/core.py 2 | 3 | sphobjinv.cli.core 4 | ================== 5 | 6 | .. automodule:: sphobjinv.cli.core 7 | :members: 8 | 9 | -------------------------------------------------------------------------------- /doc/source/cli/implementation/index.rst: -------------------------------------------------------------------------------- 1 | .. Module API page for CLI submodule code 2 | 3 | sphobjinv.cli (non-API) 4 | ======================= 5 | 6 | .. toctree:: 7 | :maxdepth: 1 8 | 9 | convert 10 | core 11 | load 12 | parser 13 | paths 14 | suggest 15 | ui 16 | write 17 | 18 | 19 | .. .. |argparse| replace:: :mod:`argparse` 20 | -------------------------------------------------------------------------------- /doc/source/cli/implementation/load.rst: -------------------------------------------------------------------------------- 1 | .. Module API page for cli/load.py 2 | 3 | sphobjinv.cli.load 4 | ================== 5 | 6 | .. automodule:: sphobjinv.cli.load 7 | :members: 8 | 9 | -------------------------------------------------------------------------------- /doc/source/cli/implementation/parser.rst: -------------------------------------------------------------------------------- 1 | .. Module API page for cli/parser.py 2 | 3 | sphobjinv.cli.parser 4 | ==================== 5 | 6 | .. automodule:: sphobjinv.cli.parser 7 | :members: 8 | 9 | -------------------------------------------------------------------------------- /doc/source/cli/implementation/paths.rst: -------------------------------------------------------------------------------- 1 | .. Module API page for cli/paths.py 2 | 3 | sphobjinv.cli.paths 4 | =================== 5 | 6 | .. automodule:: sphobjinv.cli.paths 7 | :members: 8 | 9 | -------------------------------------------------------------------------------- /doc/source/cli/implementation/suggest.rst: -------------------------------------------------------------------------------- 1 | .. Module API page for cli/suggest.py 2 | 3 | sphobjinv.cli.suggest 4 | ===================== 5 | 6 | .. automodule:: sphobjinv.cli.suggest 7 | :members: 8 | -------------------------------------------------------------------------------- /doc/source/cli/implementation/ui.rst: -------------------------------------------------------------------------------- 1 | .. Module API page for cli/ui.py 2 | 3 | sphobjinv.cli.ui 4 | ================ 5 | 6 | .. automodule:: sphobjinv.cli.ui 7 | :members: 8 | 9 | -------------------------------------------------------------------------------- /doc/source/cli/implementation/write.rst: -------------------------------------------------------------------------------- 1 | .. Module API page for cli/write.py 2 | 3 | sphobjinv.cli.write 4 | =================== 5 | 6 | .. automodule:: sphobjinv.cli.write 7 | :members: 8 | 9 | -------------------------------------------------------------------------------- /doc/source/cli/index.rst: -------------------------------------------------------------------------------- 1 | .. Description of commandline usage 2 | 3 | Command-Line Usage 4 | ================== 5 | 6 | The CLI for |soi| is implemented using two subcommands: 7 | 8 | - A :doc:`convert ` subcommand, which handles conversion of 9 | inventories between supported formats (currently zlib-compressed, 10 | plaintext, and JSON). 11 | - A :doc:`suggest ` subcommand, which provides suggestions for 12 | objects in an inventory matching a desired search term. 13 | 14 | More information about the underlying implementation of these subcommands can 15 | be found :doc:`here ` and in the documentation for the 16 | :class:`~sphobjinv.inventory.Inventory` object, in particular the 17 | :meth:`~sphobjinv.inventory.Inventory.data_file` and 18 | :meth:`~sphobjinv.inventory.Inventory.suggest` methods. 19 | 20 | Some notes on these CLI docs: 21 | 22 | * CLI docs examples are executed in a sandboxed directory pre-loaded with 23 | |cour|\ objects_attrs.inv\ |/cour| (from, e.g., 24 | `here `__). 26 | 27 | * :class:`~pathlib.Path` (from :mod:`pathlib`) 28 | is imported into the namespace before all tests. 29 | 30 | * |cour|\ cli_run\ |/cour| is a helper function that enables doctesting 31 | of CLI examples by mimicking execution of a shell command. 32 | It is described in more detail 33 | `here `__. 34 | 35 | * |cour|\ file_head\ |/cour| is a helper function 36 | that retrieves the head of a specified file. 37 | 38 | 39 | .. program:: sphobjinv 40 | 41 | The options for the parent |soi| command are: 42 | 43 | .. option:: -h, --help 44 | 45 | Show help message and exit 46 | 47 | .. program-output:: sphobjinv --help 48 | 49 | 50 | .. option:: -v, --version 51 | 52 | Print package version & other info 53 | 54 | .. program-output:: sphobjinv --version 55 | 56 | 57 | .. toctree:: 58 | :maxdepth: 1 59 | :hidden: 60 | 61 | "convert" Mode 62 | "suggest" Mode 63 | -------------------------------------------------------------------------------- /doc/source/cli/suggest.rst: -------------------------------------------------------------------------------- 1 | .. Description of suggest commandline usage 2 | 3 | Command-Line Usage: "suggest" Subcommand 4 | ======================================== 5 | 6 | .. program:: sphobjinv suggest 7 | 8 | The |cour|\ suggest\ |/cour| subcommand is used to query an inventory for objects 9 | fuzzy-matching a given search string. Fuzzy-matching is carried out via the 10 | |fuzzywuzzy|_ library, against the Restructured Text-like representation of each 11 | object exposed by :attr:`SuperDataObj.as_rst `: 12 | 13 | .. command-output:: sphobjinv suggest objects_attrs.inv instance 14 | :cwd: /../../tests/resource 15 | 16 | The |fuzzywuzzy|_ match score and the index of the object within the inventory can 17 | be printed by passing the :option:`--score` and :option:`--index` options, 18 | respectively: 19 | 20 | .. command-output:: sphobjinv suggest objects_attrs.inv instance -s -i 21 | :cwd: /../../tests/resource 22 | 23 | If too few or too many matches are returned, the reporting threshold can be changed 24 | via :option:`--thresh`: 25 | 26 | .. command-output:: sphobjinv suggest objects_attrs.inv instance -s -i -t 48 27 | :cwd: /../../tests/resource 28 | 29 | Remote |objects.inv| files can be retrieved for inspection by passing the 30 | :option:`--url` flag: 31 | 32 | .. command-output:: sphobjinv suggest https://github.com/bskinn/sphobjinv/raw/main/tests/resource/objects_attrs.inv instance -u -t 48 33 | :cwd: /../../tests/resource 34 | 35 | The URL provided **MUST** have the leading protocol specified (here, 36 | |cour|\ https\ ://\ |/cour|). 37 | 38 | It is usually not necessary to locate the |objects.inv| file before running |soi|; 39 | for most Sphinx documentation sets, if you provide a URL to any page in the docs, 40 | it will automatically find and use the correct |objects.inv|: 41 | 42 | .. command-output:: sphobjinv suggest -u https://sphobjinv.readthedocs.io/en/stable/cli/convert.html compress 43 | :cwd: /../../tests/resource 44 | 45 | |soi| only supports download of zlib-compressed |objects.inv| files by URL. 46 | Plaintext download by URL is unreliable, presumably due to encoding problems. 47 | If download of JSON files by URL is desirable, please 48 | `submit an issue `__. 49 | 50 | .. versionadded:: 2.1 51 | The |soi| CLI can now read JSON and plaintext inventories from ``stdin`` 52 | by passing the special ``-`` argument for `infile`: 53 | 54 | .. command-output:: sphobjinv suggest -s - valid < objects_attrs.txt 55 | :cwd: /../../tests/resource 56 | :shell: 57 | 58 | **Usage** 59 | 60 | .. command-output:: sphobjinv suggest --help 61 | :ellipsis: 4 62 | 63 | **Positional Arguments** 64 | 65 | .. option:: infile 66 | 67 | Path (or URL, if :option:`--url` is specified) to file to be searched. 68 | 69 | If passed as ``-``, |soi| will attempt import of a plaintext or JSON 70 | inventory from ``stdin``. This is incompatible with :option:`--url`, 71 | and automatically enables :option:`--all`. 72 | 73 | .. option:: search 74 | 75 | Search term for |fuzzywuzzy|_ matching. 76 | 77 | **Flags** 78 | 79 | .. option:: -h, --help 80 | 81 | Display `suggest` help message and exit. 82 | 83 | .. option:: -a, --all 84 | 85 | Display all search results without prompting, regardless of the number of hits. 86 | Otherwise, prompt if number of results exceeds 87 | :attr:`~sphobjinv.cli.parser.PrsConst.SUGGEST_CONFIRM_LENGTH`. 88 | 89 | .. option:: -i, --index 90 | 91 | Display the index position within the 92 | :attr:`Inventory.objects ` list 93 | for each search result returned. 94 | 95 | .. option:: -s, --score 96 | 97 | Display the |fuzzywuzzy|_ match score for each search result returned. 98 | 99 | .. option:: -t, --thresh <#> 100 | 101 | Change the |fuzzywuzzy|_ match quality threshold (0-100; higher values 102 | yield fewer results). Default is specified in 103 | :attr:`~sphobjinv.cli.parser.PrsConst.DEF_THRESH`. 104 | 105 | .. option:: -u, --url 106 | 107 | Treat :option:`infile` as a URL for download. Cannot be used when 108 | :option:`infile` is passed as ``-``. 109 | -------------------------------------------------------------------------------- /doc/source/customfile.rst: -------------------------------------------------------------------------------- 1 | .. Instructions for creating and using a custom objects.inv file 2 | 3 | 4 | Creating and Using a Custom objects.inv 5 | ======================================= 6 | 7 | The workflow presented here is introduced in the context of manually 8 | assembling an objects inventory, but the functionality is mainly 9 | intended for use downstream of a web-scraping or other automated 10 | content-extraction tool. 11 | 12 | A (possibly obsolete) representative example of such a custom |objects.inv| 13 | can be found at the GitHub repo 14 | `here `__. 15 | 16 | .. note:: 17 | 18 | These instructions are for |soi| v2.x; 19 | the prior instructions for v1.0 can be found 20 | `here `__. 21 | 22 | #. Identify the head of the URI to the documentation. |br| |br| 23 | 24 | 25 | #. Construct an |Inventory| containing all of the objects of interest. 26 | The :attr:`~sphobjinv.data.SuperDataObj.uri` and 27 | :attr:`~sphobjinv.data.SuperDataObj.dispname` values 28 | can be entered with or without the 29 | :ref:`standard abbreviations `. 30 | 31 | * Create an empty |Inventory|: 32 | 33 | .. doctest:: customfile 34 | 35 | >>> import sphobjinv as soi 36 | >>> inv = soi.Inventory() 37 | >>> print(inv) 38 | , 0 objects> 39 | 40 | * Define the :attr:`~sphobjinv.inventory.Inventory.project` 41 | and :attr:`~sphobjinv.inventory.Inventory.version` attributes: 42 | 43 | .. doctest:: customfile 44 | 45 | >>> inv.project = 'foobar' 46 | >>> inv.version = '1.5' 47 | >>> print(inv) 48 | 49 | 50 | * Append new :class:`~sphobjinv.data.DataObjStr` instances to 51 | :attr:`~sphobjinv.inventory.Inventory.objects` as needed 52 | to populate the inventory: 53 | 54 | .. doctest:: customfile 55 | 56 | >>> o = soi.DataObjStr(name='baz', domain='py', role='class', 57 | ... priority='1', uri='api.html#$', dispname='-') 58 | >>> print(o) 59 | 60 | >>> inv.objects.append(o) 61 | >>> print(inv) 62 | 63 | >>> inv.objects.append(soi.DataObjStr(name='baz.quux', domain='py', 64 | ... role='method', priority='1', uri='api.html#$', dispname='-')) 65 | >>> inv.objects.append(soi.DataObjStr(name='quuux', domain='py', 66 | ... role='function', priority='1', uri='api.html#$', dispname='-')) 67 | >>> print(inv) 68 | 69 | 70 | .. note:: 71 | 72 | The `role` values here must be the **full** role names ("`block directives`"), 73 | described as the "directives" in the `Sphinx documentation for 74 | domains `__, 75 | and not the abbreviated forms ("`inline directives`") 76 | `used when constructing cross-references 77 | `__. 78 | 79 | Thus, for example, a :class:`~sphobjinv.data.DataObjStr` corresponding 80 | to a method on a class should be constructed with 81 | |cour|\ role='method'\ |/cour|, not |cour|\ role='meth'\ |/cour|. 82 | 83 | 84 | 85 | #. Export the |Inventory| in compressed form. 86 | 87 | * Generate the text of the inventory file 88 | with :meth:`~sphobjinv.inventory.Inventory.data_file`, 89 | optionally :ref:`contracting ` the 90 | :attr:`~sphobjinv.data.SuperDataObj.uri` and 91 | :attr:`~sphobjinv.data.SuperDataObj.dispname` fields: 92 | 93 | .. doctest:: customfile 94 | 95 | >>> text = inv.data_file(contract=True) 96 | 97 | * Compress the file text: 98 | 99 | .. doctest:: customfile 100 | 101 | >>> ztext = soi.compress(text) 102 | 103 | * Save to disk: 104 | 105 | .. doctest:: customfile 106 | 107 | >>> soi.writebytes('objects_foobar.inv', ztext) 108 | 109 | 110 | #. Transfer the compressed file to its distribution location. 111 | 112 | * If only local access is needed, it can be kept local. 113 | 114 | * If external access needed, upload to a suitable host. |br| 115 | 116 | #. Add an element to the |isphxmap|_ parameter in ``conf.py``. 117 | 118 | * The key of the element is an arbitrary name, which can be used 119 | to specify the desired documentation set to be searched 120 | for the target object, in the event of a `name` collision 121 | between one or more documentation projects; e.g.:: 122 | 123 | :meth:`python:str.join` 124 | 125 | * The value of the element is a |tuple| of length two: 126 | 127 | * The first element of the value tuple is the head URI for the 128 | documentation repository, 129 | identified in step (1), 130 | to which the 131 | :attr:`~sphobjinv.data.SuperDataObj.uri` of given object 132 | is appended when constructing an |isphx| cross-reference. 133 | 134 | * The second element of the value tuple can be |None|, in which case 135 | the |objects.inv| file is assumed to be at the repository head URI. 136 | Otherwise, this element is the complete address of the 137 | distribution location of the compressed inventory file, 138 | from step (4), whether a local path or a remote URL. 139 | 140 | Examples: 141 | 142 | .. code:: 143 | 144 | intersphinx_mapping = { 145 | # Standard reference to web docs, with web objects.inv 146 | 'python': ('https://docs.python.org/3.12', None), 147 | 148 | # Django puts its objects.inv file in a non-standard location 149 | 'django': ('https://docs.djangoproject.com/en/dev/', 'https://docs.djangoproject.com/en/dev/_objects/'), 150 | 151 | # Drawing the Sphinx objects.inv from a local copy, but referring to the current web docs 152 | 'sphinx': ('https://www.sphinx-doc.org/en/master/', '/path/to/local/objects.inv'), 153 | } 154 | 155 | .. MAKE SURE TO UPDATE THESE TWO STEP REFERENCES IF NUMBERING CHANGES!! 156 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | .. Sphinx Objects.inv Converter documentation master file, created by 2 | sphinx-quickstart on Wed May 18 22:42:29 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to sphobjinv! 7 | ===================== 8 | 9 | *A toolkit for inspection/manipulation of Sphinx objects inventories* 10 | 11 | 12 | When documentation is built using, e.g., Sphinx's :obj:`~sphinx.builders.html.StandaloneHTMLBuilder`, 13 | an inventory of the named objects in the documentation set `is dumped 14 | `__ 15 | to a file called |objects.inv| in the html build directory. 16 | (One common location is, |cour|\ doc/build/html\ |/cour|, though the exact location will vary 17 | depending on the details of how Sphinx is configured.) This file is read by |isphx| when 18 | generating links in other documentation. 19 | 20 | Since version 1.0 of Sphinx (~July 2010), the data in these |objects.inv| inventories is compressed by 21 | :mod:`zlib` (presumably to reduce storage requirements and improve download speeds; "version 2"), 22 | whereas prior to that date the data was left uncompressed ("version 1"). This compression renders 23 | the files non-human-readable. **It is the purpose of this package to enable quick and simple 24 | compression/decompression and inspection of these "version 2" inventory files.** 25 | 26 | In particular, |soi| was developed to satisfy two primary use cases: 27 | 28 | #. Searching and inspection of |objects.inv| contents in order to identify 29 | how to properly construct |isphx| references. |br| |br| 30 | 31 | #. Assembly of new |objects.inv| files in order to allow |isphx| cross-referencing 32 | of other documentation sets that were not created by Sphinx. 33 | 34 | For more background on the mechanics of the Sphinx data model and 35 | Sphinx cross-references generally, see 36 | `this talk `__ from PyOhio 2019. 37 | 38 | ---- 39 | 40 | Install |soi| via |cour|\ pip\ |/cour|:: 41 | 42 | $ pip install sphobjinv 43 | 44 | Or, if you only plan to use the |soi| CLI, another option is |pipx|_:: 45 | 46 | $ pipx install sphobjinv 47 | 48 | As of Nov 2022, |soi| is also available via conda-forge. After activating the desired conda environment:: 49 | 50 | $ conda install -c conda-forge sphobjinv 51 | 52 | Alternatively, |soi| is packaged with 53 | `multiple POSIX distributions `__ 54 | and package managers, including: 55 | 56 | * Alpine Linux: ``py3-sphobjinv`` (`info `__) 57 | 58 | * Arch Linux: ``python-sphobjinv`` 59 | 60 | * Fedora: ``python-sphobjinv`` (`info `__) 61 | 62 | * Gentoo: ``dev-python/sphobjinv`` (`info `__) 63 | 64 | * Guix: ``python-sphobjinv`` 65 | 66 | * Manjaro: ``python-sphobjinv`` 67 | 68 | * OpenEuler: ``python-sphobjinv`` 69 | 70 | * openSUSE: ``python-sphobjinv`` (`info `__) 71 | 72 | * Parabola: ``python-sphobjinv`` (`info `__) 73 | 74 | * pkgsrc: ``textproc/py-sphobjinv`` (`info `__) 75 | 76 | * spack: ``py-sphobjinv`` 77 | 78 | 79 | |soi| is configured for use both as a 80 | :doc:`command-line script ` and as a 81 | :doc:`Python package `. 82 | 83 | The optional dependency |python-Levenshtein|_ for accelerating 84 | the "suggest" functionality is no longer available due to a 85 | licensing conflict, and has been deprecated. See 86 | :doc:`here ` for more information. 87 | 88 | The project source repository is on GitHub: `bskinn/sphobjinv 89 | `__. 90 | 91 | 92 | 93 | .. toctree:: 94 | :maxdepth: 1 95 | :hidden: 96 | 97 | cli/index 98 | api_usage 99 | customfile 100 | levenshtein 101 | syntax 102 | api/index 103 | CLI Implementation (non-API) 104 | 105 | 106 | 107 | Indices and Tables 108 | ------------------ 109 | 110 | :ref:`genindex` --- :ref:`modindex` --- :ref:`search` 111 | -------------------------------------------------------------------------------- /doc/source/isphx/objpull.py: -------------------------------------------------------------------------------- 1 | # Quickie script for refreshing the local objects.inv cache 2 | # OVERWRITES EXISTING FILES, WITH PRE-DELETION 3 | 4 | 5 | 6 | def pullobjs(): 7 | 8 | import os 9 | import urllib.request as urlrq 10 | 11 | import certifi 12 | 13 | # Open conf.py, retrieve content and compile 14 | with open(os.path.join(os.pardir, 'conf.py'), 'r') as f: 15 | confcode = compile(f.read(), 'conf.py', 'exec') 16 | 17 | # Execute conf.py into the global namespace (I know, sloppy) 18 | exec(confcode, globals()) 19 | 20 | # Iterate intersphinx_mapping from conf.py to retrieve the objects.inv files 21 | # Make use of the conf.py 'isphx_objstr' substitution string, too 22 | for n, t in intersphinx_mapping.items(): 23 | 24 | print('{0}:\n'.format(n) + '-' * 16) 25 | 26 | try: 27 | os.remove(isphx_objstr.format(n)) 28 | except FileNotFoundError: 29 | pass # No big deal 30 | 31 | try: 32 | resp = urlrq.urlopen(t[0] + '/objects.inv', cafile=certifi.where()) 33 | except Exception as e: 34 | print('HTTP request failed:\n' + str(e) + '\n') 35 | continue 36 | else: 37 | print('... located ...') 38 | 39 | try: 40 | b_s = resp.read() 41 | except Exception as e: 42 | print('Download failed:\n' + str(e) + '\n') 43 | continue 44 | else: 45 | print('... downloaded ...') 46 | 47 | try: 48 | with open(isphx_objstr.format(n), 'wb') as f: 49 | f.write(b_s) 50 | except Exception as e: 51 | print('Write failed:\n' + str(e) + '\n') 52 | continue 53 | else: 54 | print('... done.') 55 | 56 | print('') 57 | 58 | 59 | if __name__ == '__main__': 60 | 61 | pullobjs() 62 | -------------------------------------------------------------------------------- /doc/source/levenshtein.rst: -------------------------------------------------------------------------------- 1 | .. Info on speedups from python-Levenshtein 2 | 3 | Speeding up "suggest" with python-Levenshtein (DEPRECATED) 4 | ========================================================== 5 | 6 | |soi| uses |fuzzywuzzy|_ for fuzzy-match searching of object 7 | names/domains/roles as part of the 8 | :meth:`Inventory.suggest() ` functionality, 9 | also implemented as the CLI :doc:`suggest ` subcommand. 10 | 11 | |fuzzywuzzy|_ uses :class:`difflib.SequenceMatcher` 12 | from the Python standard library for its fuzzy searching. 13 | While earlier versions of |soi| were able to make use of 14 | |fuzzywuzzy|_\ 's optional link to |python-Levenshtein|_, 15 | a Python C extension providing similar functionality, 16 | due to a licensing conflict this is no longer possible. 17 | |soi| now uses a vendored copy of |fuzzywuzzy|_ from an 18 | era when it was released under the MIT License. 19 | 20 | Formally: 21 | 22 | .. versionremoved:: 2.2 23 | 24 | Acceleration of the |soi| "suggest" mode via |python-Levenshtein|_ 25 | has been deprecated and is no longer available. 26 | 27 | The discussion of performance benchmarks and variations in matching 28 | behavior is kept below for historical interest. 29 | 30 | 31 | Performance Benchmark 32 | --------------------- 33 | 34 | The chart below presents one dataset illustrating the performance enhancement 35 | that can be obtained by installing |python-Levenshtein|_. 36 | The timings plotted here are from execution of 37 | :func:`timeit.repeat` around a 38 | :meth:`~sphobjinv.inventory.Inventory.suggest` call, 39 | searching for the term "function", for a number of 40 | |objects.inv| files from different projects (see 41 | `here `__). 43 | 44 | The timings were collected using the following code:: 45 | 46 | import sphobjinv as soi 47 | 48 | durations = {} 49 | obj_counts = {} 50 | 51 | for fn in os.listdir(): 52 | if fn.endswith('.inv'): 53 | inv = soi.Inventory(fn) 54 | 55 | # Execute the 'suggest' operation 20 times for each file 56 | timings = timeit.repeat("inv.suggest('function')", repeat=20, number=1, globals=globals()) 57 | 58 | # Store the average timing for each file 59 | durations.update({fn: sum(timings) / len(timings)}) 60 | 61 | # Store the number of objects 62 | obj_counts.update({fn: inv.count}) 63 | 64 | 65 | As can be seen, the fifty-two |objects.inv| files in this dataset 66 | contain widely varying numbers of objects :math:`n`: 67 | 68 | .. image:: /_static/suggest_timing.png 69 | 70 | Unsurprisingly, larger inventories require more time to search. 71 | Also relatively unsurprisingly, the time required appears to be 72 | roughly :math:`O(n)`, since the fuzzy search must be performed once on the 73 | :attr:`~sphobjinv.data.SuperDataObj.as_rst` representation of each object. 74 | 75 | For this specific search, using |python-Levenshtein|_ instead of 76 | :mod:`difflib` decreases the time required from 0.90 seconds per thousand objects 77 | down to 0.15 seconds per thousand objects, 78 | representing a performance improvement of almost exactly six-fold. 79 | Other searches will likely exhibit somewhat better or worse 80 | improvement from the use of |python-Levenshtein|_, 81 | depending on the average length of the reST-like representations 82 | of the objects in an |objects.inv| 83 | and the length of the search term. 84 | 85 | 86 | Variations in Matching Behavior 87 | ------------------------------- 88 | 89 | Note that the matching scores calculated by 90 | :mod:`difflib` and |python-Levenshtein|_ can often 91 | differ appreciably. (This is illustrated in 92 | `issue #128 `__ 93 | of the |fuzzywuzzy|_ GitHub repo.) 94 | This difference in behavior doesn't have much practical significance, 95 | save for the potential of causing some confusion between users with/without 96 | |python-Levenshtein|_ installed. 97 | 98 | 99 | As an example, the following shows an excerpt of the results of a representative 100 | CLI :doc:`suggest ` call **without** 101 | |python-Levenshtein|_:: 102 | 103 | $ sphobjinv suggest objects_scipy.inv surface -asit 40 104 | 105 | Name Score Index 106 | ------------------------------------------------------ ------- ------- 107 | :py:function:`scipy.misc.face` 64 1018 108 | :py:function:`scipy.misc.source` 64 1032 109 | :std:doc:`generated/scipy.misc.face` 64 4042 110 | :std:doc:`generated/scipy.misc.source` 64 4056 111 | :py:data:`scipy.stats.rice` 56 2688 112 | :std:label:`continuous-rice` 56 2896 113 | :py:method:`scipy.integrate.complex_ode.successful` 51 156 114 | :py:method:`scipy.integrate.ode.successful` 51 171 115 | :py:function:`scipy.linalg.lu_factor` 51 967 116 | 117 | ... more with score 51 ... 118 | 119 | :py:attribute:`scipy.LowLevelCallable.signature` 50 5 120 | :py:function:`scipy.constants.convert_temperature` 50 53 121 | :py:function:`scipy.integrate.quadrature` 50 176 122 | 123 | ... more with score 50 and below ... 124 | 125 | This is a similar excerpt **with** |python-Levenshtein|_:: 126 | 127 | Name Score Index 128 | ------------------------------------------------------ ------- ------- 129 | :py:function:`scipy.misc.face` 64 1018 130 | :py:function:`scipy.misc.source` 64 1032 131 | :std:doc:`generated/scipy.misc.face` 64 4042 132 | :std:doc:`generated/scipy.misc.source` 64 4056 133 | :py:method:`scipy.integrate.ode.successful` 51 171 134 | :py:function:`scipy.linalg.lu_factor` 51 967 135 | :py:function:`scipy.linalg.subspace_angles` 51 1003 136 | 137 | ... more with score 51 ... 138 | 139 | :py:function:`scipy.cluster.hierarchy.fcluster` 49 23 140 | :py:function:`scipy.cluster.hierarchy.fclusterdata` 49 24 141 | :py:method:`scipy.integrate.complex_ode.successful` 49 156 142 | 143 | ... more with score 49 and below ... 144 | 145 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | build-backend = "setuptools.build_meta" 3 | requires = [ 4 | "setuptools>=61.2", 5 | "wheel", 6 | ] 7 | 8 | [project] 9 | name = "sphobjinv" 10 | description = "Sphinx objects.inv Inspection/Manipulation Tool" 11 | license = {text = "MIT License"} 12 | authors = [{name = "Brian Skinn", email = "brian.skinn@gmail.com"}] 13 | classifiers = [ 14 | "License :: OSI Approved", 15 | "License :: OSI Approved :: MIT License", 16 | "Natural Language :: English", 17 | "Environment :: Console", 18 | "Framework :: Sphinx", 19 | "Intended Audience :: Developers", 20 | "Operating System :: OS Independent", 21 | "Programming Language :: Python", 22 | "Programming Language :: Python :: 3", 23 | "Programming Language :: Python :: 3 :: Only", 24 | "Programming Language :: Python :: 3.9", 25 | "Programming Language :: Python :: 3.10", 26 | "Programming Language :: Python :: 3.11", 27 | "Programming Language :: Python :: 3.12", 28 | "Programming Language :: Python :: 3.13", 29 | "Topic :: Documentation", 30 | "Topic :: Documentation :: Sphinx", 31 | "Topic :: Software Development", 32 | "Topic :: Software Development :: Documentation", 33 | "Topic :: Utilities", 34 | "Development Status :: 5 - Production/Stable", 35 | ] 36 | keywords = ["sphinx", "sphinx-doc", "inventory", "manager", "inspector"] 37 | requires-python = ">=3.9" 38 | dependencies = [ 39 | "attrs>=19.2", 40 | "certifi", 41 | "jsonschema>=3.0", 42 | ] 43 | dynamic = ["version", "readme"] 44 | 45 | [project.urls] 46 | Homepage = "https://github.com/bskinn/sphobjinv" 47 | Changelog = "https://github.com/bskinn/sphobjinv/blob/main/CHANGELOG.md" 48 | Docs = "https://sphobjinv.readthedocs.io/en/stable/" 49 | Thank = "https://fosstodon.org/@btskinn" 50 | Donate = "https://github.com/sponsors/bskinn" 51 | 52 | [project.scripts] 53 | sphobjinv = "sphobjinv.cli.core:main" 54 | 55 | [tool.setuptools] 56 | package-dir = {"" = "src"} 57 | platforms = ["any"] 58 | license-files = ["LICENSE.txt"] 59 | include-package-data = false 60 | 61 | [tool.setuptools.packages.find] 62 | where = ["src"] 63 | namespaces = false 64 | 65 | [tool.setuptools.dynamic] 66 | version = {attr = "sphobjinv.version.__version__"} 67 | 68 | [tool.black] 69 | line-length = 88 70 | include = ''' 71 | ( 72 | ^/tests/.*[.]py$ 73 | | ^/src/sphobjinv/.*[.]py$ 74 | | ^/setup[.]py 75 | | ^/conftest[.]py 76 | ) 77 | ''' 78 | exclude = ''' 79 | ( 80 | __pycache__ 81 | | ^/[.] 82 | | ^/doc 83 | | ^/env 84 | | ^/src/sphobjinv/_vendored 85 | ) 86 | ''' 87 | 88 | [tool.interrogate] 89 | exclude = ["src/sphobjinv/_vendored"] 90 | fail-under = 100 91 | verbose = 1 92 | -------------------------------------------------------------------------------- /requirements-ci.txt: -------------------------------------------------------------------------------- 1 | attrs>=19.2 2 | certifi 3 | coverage 4 | dictdiffer 5 | jsonschema 6 | md-toc 7 | pytest>=4.4.0 8 | pytest-check>=1.1.2 9 | pytest-cov 10 | pytest-retry 11 | pytest-timeout 12 | sphinx==7.4.7 13 | sphinx-issues 14 | sphinx-rtd-theme>=0.5.1 15 | sphinxcontrib-programoutput 16 | stdio-mgr>=1.0.1 17 | tox 18 | wheel 19 | -e . 20 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | attrs>=19.2 2 | build 3 | certifi 4 | coverage 5 | dictdiffer 6 | jsonschema 7 | md-toc 8 | pytest>=4.4.0 9 | pytest-check>=1.1.2 10 | pytest-cov 11 | pytest-retry 12 | pytest-timeout 13 | restview 14 | sphinx==7.4.7 # Staying <8 since 8.0 drops Python 3.9 15 | sphinx-autobuild 16 | sphinx-issues 17 | sphinx-rtd-theme>=0.5.1 18 | sphinxcontrib-programoutput 19 | stdio-mgr>=1.0.1 20 | tox 21 | twine 22 | -r requirements-flake8.txt 23 | -e . 24 | -------------------------------------------------------------------------------- /requirements-flake8.txt: -------------------------------------------------------------------------------- 1 | colorama 2 | flake8>=6.1.0 # Avoids pycodestyle bug @ v6.0 w.r.t re.py 3 | flake8-2020 4 | flake8-absolute-import 5 | flake8-bandit 6 | flake8-black 7 | flake8-bugbear 8 | flake8-builtins 9 | flake8-comprehensions 10 | flake8-docstrings>=1.3.1 11 | flake8-eradicate 12 | flake8-implicit-str-concat 13 | flake8-import-order 14 | flake8-pie 15 | flake8-raise 16 | flake8-rst-docstrings 17 | pep8-naming 18 | -------------------------------------------------------------------------------- /requirements-interrogate.txt: -------------------------------------------------------------------------------- 1 | interrogate 2 | -------------------------------------------------------------------------------- /requirements-rtd.txt: -------------------------------------------------------------------------------- 1 | attrs>=19.2 2 | sphinx==7.4.7 3 | sphinx-issues 4 | sphinx-rtd-theme>=0.5.1 5 | sphinxcontrib-programoutput 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import re 2 | from pathlib import Path 3 | 4 | from setuptools import setup 5 | 6 | NAME = "sphobjinv" 7 | 8 | exec_ns = {} 9 | exec(Path("src", "sphobjinv", "version.py").read_text(encoding="utf-8"), exec_ns) 10 | __version__ = exec_ns["__version__"] 11 | 12 | version_override = "2.3.1.2" 13 | 14 | 15 | def readme(): 16 | content = Path("README.md").read_text(encoding="utf-8") 17 | 18 | new_ver = version_override if version_override else __version__ 19 | 20 | # Helper function 21 | def content_update(content, pattern, sub): 22 | return re.sub(pattern, sub, content, flags=re.M | re.I) 23 | 24 | # Docs reference updates to current release version, for PyPI 25 | # This one gets the badge image 26 | content = content_update( 27 | content, r"(?<=/readthedocs/{0}/)\S+?(?=\.svg$)".format(NAME), "v" + new_ver 28 | ) 29 | 30 | # This one gets the RtD links 31 | content = content_update( 32 | content, r"(?<={0}\.readthedocs\.io/en/)\S+?(?=/)".format(NAME), "v" + new_ver 33 | ) 34 | 35 | return content 36 | 37 | 38 | setup( 39 | name=NAME, 40 | long_description=readme(), 41 | long_description_content_type="text/markdown", 42 | ) 43 | -------------------------------------------------------------------------------- /src/sphobjinv/__init__.py: -------------------------------------------------------------------------------- 1 | r"""``sphobjinv`` *package definition module*. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | **Author** 7 | Brian Skinn (brian.skinn@gmail.com) 8 | 9 | **File Created** 10 | 17 May 2016 11 | 12 | **Copyright** 13 | \(c) Brian Skinn 2016-2025 14 | 15 | **Source Repository** 16 | https://github.com/bskinn/sphobjinv 17 | 18 | **Documentation** 19 | https://sphobjinv.readthedocs.io/en/stable 20 | 21 | **License** 22 | Code: `MIT License`_ 23 | 24 | Docs & Docstrings: |CC BY 4.0|_ 25 | 26 | See |license_txt|_ for full license terms. 27 | 28 | **Members** 29 | 30 | """ 31 | 32 | from sphobjinv.data import DataFields, DataObjBytes, DataObjStr 33 | from sphobjinv.enum import HeaderFields, SourceTypes 34 | from sphobjinv.error import SphobjinvError, VersionError 35 | from sphobjinv.fileops import readbytes, readjson, urlwalk, writebytes, writejson 36 | from sphobjinv.inventory import Inventory 37 | from sphobjinv.re import p_data, pb_comments, pb_data, pb_project, pb_version 38 | from sphobjinv.schema import json_schema 39 | from sphobjinv.version import __version__ 40 | from sphobjinv.zlib import compress, decompress 41 | -------------------------------------------------------------------------------- /src/sphobjinv/__main__.py: -------------------------------------------------------------------------------- 1 | r"""``sphobjinv`` *package execution module*. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | **Author** 7 | Brian Skinn (brian.skinn@gmail.com) 8 | 9 | **File Created** 10 | 15 May 2020 11 | 12 | **Copyright** 13 | \(c) Brian Skinn 2016-2025 14 | 15 | **Source Repository** 16 | https://github.com/bskinn/sphobjinv 17 | 18 | **Documentation** 19 | https://sphobjinv.readthedocs.io/en/stable 20 | 21 | **License** 22 | Code: `MIT License`_ 23 | 24 | Docs & Docstrings: |CC BY 4.0|_ 25 | 26 | See |license_txt|_ for full license terms. 27 | 28 | **Members** 29 | 30 | """ 31 | 32 | import sys 33 | 34 | from sphobjinv.cli.core import main 35 | 36 | if __name__ == "__main__": 37 | # Spoof so 'help' usage display shows "sphobjinv" and 38 | # not "__main__.py" 39 | sys.argv[0] = "sphobjinv" 40 | 41 | sys.exit(main()) 42 | -------------------------------------------------------------------------------- /src/sphobjinv/_vendored/__init__.py: -------------------------------------------------------------------------------- 1 | r"""``sphobjinv._vendored`` *package definition module*. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | Subpackage marker module for vendored packages. 7 | 8 | **Author** 9 | Brian Skinn (brian.skinn@gmail.com) 10 | 11 | **File Created** 12 | 11 Dec 2021 13 | 14 | **Copyright** 15 | \(c) Brian Skinn 2016-2025 16 | 17 | **Source Repository** 18 | https://github.com/bskinn/sphobjinv 19 | 20 | **Documentation** 21 | https://sphobjinv.readthedocs.io/en/stable 22 | 23 | **License** 24 | Code: `MIT License`_ 25 | 26 | Docs & Docstrings: |CC BY 4.0|_ 27 | 28 | See |license_txt|_ for full license terms. 29 | 30 | **Members** 31 | 32 | """ 33 | -------------------------------------------------------------------------------- /src/sphobjinv/_vendored/fuzzywuzzy/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Adam Cohen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/sphobjinv/_vendored/fuzzywuzzy/__init__.py: -------------------------------------------------------------------------------- 1 | r"""``sphobjinv._vendored.fuzzywuzzy`` *package definition module*. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | This subpackage vendors an archival version of fuzzywuzzy from 7 | before it incorporated python-Levenshtein and thus acquired 8 | the GPL licence by flow-through, overriding the prior 9 | MIT license of the package. 10 | 11 | All code in this subpackage vendored from: 12 | https://github.com/seatgeek/fuzzywuzzy/tree/4bf28161f7005f3aa9d4d931455ac55126918df7 13 | 14 | Imports and code details were changed as required to make the code compatible 15 | with current (2021) versions of Python 3. 16 | 17 | Link to MIT-licensed fuzzywuzzy version obtained from the bottom of the README at: 18 | https://github.com/maxbachmann/RapidFuzz/tree/460d291a38922c7fead920de8147798b817653cb 19 | 20 | 21 | **Author** 22 | Brian Skinn (brian.skinn@gmail.com) 23 | 24 | **File Created** 25 | 11 Dec 2021 26 | 27 | **Copyright** 28 | \(c) Brian Skinn 2016-2025 29 | 30 | **Source Repository** 31 | https://github.com/bskinn/sphobjinv 32 | 33 | **Documentation** 34 | https://sphobjinv.readthedocs.io/en/stable 35 | 36 | **License** 37 | Code: `MIT License`_ 38 | 39 | Docs & Docstrings: |CC BY 4.0|_ 40 | 41 | See |license_txt|_ for full license terms. 42 | 43 | **Members** 44 | 45 | """ 46 | -------------------------------------------------------------------------------- /src/sphobjinv/_vendored/fuzzywuzzy/fuzz.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | score.py 5 | 6 | Copyright (c) 2011 Adam Cohen 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | """ 27 | 28 | import sys 29 | import os 30 | import re 31 | from difflib import SequenceMatcher 32 | from sphobjinv._vendored.fuzzywuzzy import utils 33 | 34 | REG_TOKEN = re.compile(r"[\w\d]+") # B Skinn 2021-12-11 35 | 36 | ########################### 37 | # Basic Scoring Functions # 38 | ########################### 39 | 40 | def ratio(s1, s2): 41 | 42 | if s1 is None: raise TypeError("s1 is None") 43 | if s2 is None: raise TypeError("s2 is None") 44 | 45 | m = SequenceMatcher(None, s1, s2) 46 | return int(100 * m.ratio()) 47 | 48 | # todo: skip duplicate indexes for a little more speed 49 | def partial_ratio(s1, s2): 50 | 51 | if s1 is None: raise TypeError("s1 is None") 52 | if s2 is None: raise TypeError("s2 is None") 53 | 54 | if len(s1) <= len(s2): 55 | shorter = s1; longer = s2; 56 | else: 57 | shorter = s2; longer = s1 58 | 59 | m = SequenceMatcher(None, shorter, longer) 60 | blocks = m.get_matching_blocks() 61 | 62 | # each block represents a sequence of matching characters in a string 63 | # of the form (idx_1, idx_2, len) 64 | # the best partial match will block align with at least one of those blocks 65 | # e.g. shorter = "abcd", longer = XXXbcdeEEE 66 | # block = (1,3,3) 67 | # best score === ratio("abcd", "Xbcd") 68 | scores = [] 69 | for block in blocks: 70 | long_start = block[1] - block[0] if (block[1] - block[0]) > 0 else 0 71 | long_end = long_start + len(shorter) 72 | long_substr = longer[long_start:long_end] 73 | 74 | m2 = SequenceMatcher(None, shorter, long_substr) 75 | r = m2.ratio() 76 | if r > .995: return 100 77 | else: scores.append(r) 78 | 79 | return int(100 * max(scores)) 80 | 81 | ############################## 82 | # Advanced Scoring Functions # 83 | ############################## 84 | 85 | # Sorted Token 86 | # find all alphanumeric tokens in the string 87 | # sort those tokens and take ratio of resulting joined strings 88 | # controls for unordered string elements 89 | def _token_sort(s1, s2, partial=True): 90 | 91 | if s1 is None: raise TypeError("s1 is None") 92 | if s2 is None: raise TypeError("s2 is None") 93 | 94 | # pull tokens 95 | tokens1 = REG_TOKEN.findall(s1) 96 | tokens2 = REG_TOKEN.findall(s2) 97 | 98 | # sort tokens and join 99 | sorted1 = u" ".join(sorted(tokens1)) 100 | sorted2 = u" ".join(sorted(tokens2)) 101 | 102 | sorted1 = sorted1.strip() 103 | sorted2 = sorted2.strip() 104 | 105 | if partial: 106 | return partial_ratio(sorted1, sorted2) 107 | else: 108 | return ratio(sorted1, sorted2) 109 | 110 | def token_sort_ratio(s1, s2): 111 | return _token_sort(s1, s2, False) 112 | 113 | def partial_token_sort_ratio(s1, s2): 114 | return _token_sort(s1, s2, True) 115 | 116 | # Token Set 117 | # find all alphanumeric tokens in each string...treat them as a set 118 | # construct two strings of the form 119 | # 120 | # take ratios of those two strings 121 | # controls for unordered partial matches 122 | def _token_set(s1, s2, partial=True): 123 | 124 | if s1 is None: raise TypeError("s1 is None") 125 | if s2 is None: raise TypeError("s2 is None") 126 | 127 | # pull tokens 128 | tokens1 = set(REG_TOKEN.findall(s1)) 129 | tokens2 = set(REG_TOKEN.findall(s2)) 130 | 131 | intersection = tokens1.intersection(tokens2) 132 | diff1to2 = tokens1.difference(tokens2) 133 | diff2to1 = tokens2.difference(tokens1) 134 | 135 | sorted_sect = u" ".join(sorted(intersection)) 136 | sorted_1to2 = u" ".join(sorted(diff1to2)) 137 | sorted_2to1 = u" ".join(sorted(diff2to1)) 138 | 139 | combined_1to2 = sorted_sect + " " + sorted_1to2 140 | combined_2to1 = sorted_sect + " " + sorted_2to1 141 | 142 | # strip 143 | sorted_sect = sorted_sect.strip() 144 | combined_1to2 = combined_1to2.strip() 145 | combined_2to1 = combined_2to1.strip() 146 | 147 | pairwise = [ 148 | ratio(sorted_sect, combined_1to2), 149 | ratio(sorted_sect, combined_2to1), 150 | ratio(combined_1to2, combined_2to1) 151 | ] 152 | return max(pairwise) 153 | 154 | # if partial: 155 | # # partial_token_set_ratio 156 | # 157 | # else: 158 | # # token_set_ratio 159 | # tsr = ratio(combined_1to2, combined_2to1) 160 | # return tsr 161 | 162 | def token_set_ratio(s1, s2): 163 | return _token_set(s1, s2, False) 164 | 165 | def partial_token_set_ratio(s1, s2): 166 | return _token_set(s1, s2, True) 167 | 168 | # TODO: numerics 169 | 170 | ################### 171 | # Combination API # 172 | ################### 173 | 174 | # q is for quick 175 | def QRatio(s1, s2): 176 | if not utils.validate_string(s1): return 0 177 | if not utils.validate_string(s2): return 0 178 | 179 | p1 = utils.full_process(s1) 180 | p2 = utils.full_process(s2) 181 | 182 | return ratio(p1, p2) 183 | 184 | # w is for weighted 185 | def WRatio(s1, s2): 186 | p1 = utils.full_process(s1) 187 | p2 = utils.full_process(s2) 188 | if not utils.validate_string(p1): return 0 189 | if not utils.validate_string(p2): return 0 190 | 191 | # should we look at partials? 192 | try_partial = True 193 | unbase_scale = .95 194 | partial_scale = .90 195 | 196 | base = ratio(p1, p2) 197 | len_ratio = float(max(len(p1),len(p2)))/min(len(p1),len(p2)) 198 | 199 | # if strings are similar length, don't use partials 200 | if len_ratio < 1.5: try_partial = False 201 | 202 | # if one string is much much shorter than the other 203 | if len_ratio > 8: partial_scale = .6 204 | 205 | if try_partial: 206 | partial = partial_ratio(p1, p2) * partial_scale 207 | ptsor = partial_token_sort_ratio(p1, p2) * unbase_scale * partial_scale 208 | ptser = partial_token_set_ratio(p1, p2) * unbase_scale * partial_scale 209 | 210 | return int(max(base, partial, ptsor, ptser)) 211 | else: 212 | tsor = token_sort_ratio(p1, p2) * unbase_scale 213 | tser = token_set_ratio(p1, p2) * unbase_scale 214 | 215 | return int(max(base, tsor, tser)) 216 | 217 | -------------------------------------------------------------------------------- /src/sphobjinv/_vendored/fuzzywuzzy/process.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | process.py 5 | 6 | Copyright (c) 2011 Adam Cohen 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | """ 27 | from sphobjinv._vendored.fuzzywuzzy.fuzz import * 28 | 29 | import sys, os 30 | from sphobjinv._vendored.fuzzywuzzy import utils 31 | 32 | ####################################### 33 | # Find Best Matchs In List Of Choices # 34 | ####################################### 35 | 36 | def extract(query, choices, processor=None, scorer=None, limit=5): 37 | 38 | # choices = a list of objects we are attempting to extract values from 39 | # query = an object representing the thing we want to find 40 | # scorer f(OBJ, QUERY) --> INT. We will return the objects with the highest score 41 | # by default, we use score.WRatio() and both OBJ and QUERY should be strings 42 | # processor f(OBJ_A) --> OBJ_B, where the output is an input to scorer 43 | # for example, "processor = lambda x: x[0]" would return the first element in a collection x (of, say, strings) 44 | # this would then be used in the scoring collection 45 | 46 | if choices is None or len(choices) == 0: 47 | return [] 48 | 49 | # default, turn whatever the choice is into a string 50 | if processor is None: 51 | processor = lambda x: utils.asciidammit(x) 52 | 53 | # default: wratio 54 | if scorer is None: 55 | scorer = WRatio 56 | 57 | sl = list() 58 | 59 | for choice in choices: 60 | processed = processor(choice) 61 | score = scorer(query, processed) 62 | tuple = (choice, score) 63 | sl.append(tuple) 64 | 65 | sl.sort(key=lambda i: -1*i[1]) 66 | return sl[:limit] 67 | 68 | ########################## 69 | # Find Single Best Match # 70 | ########################## 71 | 72 | def extractOne(query, choices, processor=None, scorer=None, score_cutoff=0): 73 | 74 | # convenience method which returns the single best choice 75 | # optional parameter: score_cutoff. 76 | # If the best choice has a score of less than score_cutoff 77 | # we will return none (intuition: not a good enough match) 78 | 79 | best_list = extract(query, choices, processor, scorer, limit=1) 80 | if len(best_list) > 0: 81 | best = best_list[0] 82 | if best[1] > score_cutoff: 83 | return best 84 | else: 85 | return None 86 | else: 87 | return None 88 | -------------------------------------------------------------------------------- /src/sphobjinv/_vendored/fuzzywuzzy/utils.py: -------------------------------------------------------------------------------- 1 | import string 2 | 3 | bad_chars=b"" # B Skinn 2021-12-11 4 | for i in range(128,256): 5 | bad_chars+=chr(i).encode() # B Skinn 2021-12-11 6 | table_from=string.punctuation+string.ascii_uppercase 7 | table_to=' '*len(string.punctuation)+string.ascii_lowercase 8 | trans_table=bytes.maketrans(table_from.encode(), table_to.encode()) # B Skinn 2021-12-11 9 | 10 | 11 | def asciionly(s): 12 | return s.encode().translate(None, bad_chars).decode(errors='replace') # B Skinn 2021-12-11 13 | 14 | # remove non-ASCII characters from strings 15 | def asciidammit(s): 16 | if type(s) is str: 17 | return asciionly(s) 18 | elif type(s) is unicode: 19 | return asciionly(s.encode('ascii', 'ignore')) 20 | else: 21 | return asciidammit(unicode(s)) 22 | 23 | def validate_string(s): 24 | try: 25 | if len(s)>0: 26 | return True 27 | else: 28 | return False 29 | except: 30 | return False 31 | 32 | def full_process(s): 33 | s = asciidammit(s) 34 | # B Skinn 2021-12-11 35 | return s.encode().translate(trans_table, bad_chars).decode(errors='replace').strip() 36 | -------------------------------------------------------------------------------- /src/sphobjinv/cli/__init__.py: -------------------------------------------------------------------------------- 1 | r"""``sphobjinv.cli`` *subpackage definition module*. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | **Author** 7 | Brian Skinn (brian.skinn@gmail.com) 8 | 9 | **File Created** 10 | 15 Nov 2020 11 | 12 | **Copyright** 13 | \(c) Brian Skinn 2016-2025 14 | 15 | **Source Repository** 16 | https://github.com/bskinn/sphobjinv 17 | 18 | **Documentation** 19 | https://sphobjinv.readthedocs.io/en/stable 20 | 21 | **License** 22 | Code: `MIT License`_ 23 | 24 | Docs & Docstrings: |CC BY 4.0|_ 25 | 26 | See |license_txt|_ for full license terms. 27 | 28 | **Members** 29 | 30 | """ 31 | 32 | from sphobjinv.cli.core import main 33 | -------------------------------------------------------------------------------- /src/sphobjinv/cli/convert.py: -------------------------------------------------------------------------------- 1 | r"""``sphobjinv`` *module for CLI convert functionality*. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | **Author** 7 | Brian Skinn (brian.skinn@gmail.com) 8 | 9 | **File Created** 10 | 20 Oct 2022 11 | 12 | **Copyright** 13 | \(c) Brian Skinn 2016-2025 14 | 15 | **Source Repository** 16 | https://github.com/bskinn/sphobjinv 17 | 18 | **Documentation** 19 | https://sphobjinv.readthedocs.io/en/stable 20 | 21 | **License** 22 | Code: `MIT License`_ 23 | 24 | Docs & Docstrings: |CC BY 4.0|_ 25 | 26 | See |license_txt|_ for full license terms. 27 | 28 | **Members** 29 | 30 | """ 31 | 32 | from sphobjinv.cli.parser import PrsConst 33 | from sphobjinv.cli.write import write_file, write_stdout 34 | 35 | 36 | def do_convert(inv, in_path, params): 37 | r"""Carry out the conversion operation, including writing output. 38 | 39 | If |cli:OVERWRITE| is passed and the output file 40 | (the default location, or as passed to |cli:OUTFILE|) 41 | exists, it will be overwritten without a prompt. Otherwise, 42 | the user will be queried if it is desired to overwrite 43 | the existing file. 44 | 45 | If |cli:QUIET| is passed, nothing will be 46 | printed to |cour|\ stdout\ |/cour| 47 | (potentially useful for scripting), 48 | and any existing output file will be overwritten 49 | without prompting. 50 | 51 | Parameters 52 | ---------- 53 | inv 54 | 55 | |Inventory| -- Inventory object to be output in the format 56 | indicated by |cli:MODE|. 57 | 58 | in_path 59 | 60 | |str| -- For a local input file, its absolute path. 61 | For a URL, the (possibly truncated) URL text. 62 | 63 | params 64 | 65 | |dict| -- Parameters/values mapping from the active subparser 66 | 67 | """ 68 | if params[PrsConst.OUTFILE] == "-" or ( 69 | params[PrsConst.INFILE] == "-" and params[PrsConst.OUTFILE] is None 70 | ): 71 | write_stdout(inv, params) 72 | else: 73 | write_file(inv, in_path, params) 74 | -------------------------------------------------------------------------------- /src/sphobjinv/cli/core.py: -------------------------------------------------------------------------------- 1 | r"""``sphobjinv`` *CLI core execution module*. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | **Author** 7 | Brian Skinn (brian.skinn@gmail.com) 8 | 9 | **File Created** 10 | 15 Nov 2020 11 | 12 | **Copyright** 13 | \(c) Brian Skinn 2016-2025 14 | 15 | **Source Repository** 16 | https://github.com/bskinn/sphobjinv 17 | 18 | **Documentation** 19 | https://sphobjinv.readthedocs.io/en/stable 20 | 21 | **License** 22 | Code: `MIT License`_ 23 | 24 | Docs & Docstrings: |CC BY 4.0|_ 25 | 26 | See |license_txt|_ for full license terms. 27 | 28 | **Members** 29 | 30 | """ 31 | 32 | import sys 33 | 34 | from sphobjinv.cli.convert import do_convert 35 | from sphobjinv.cli.load import inv_local, inv_stdin, inv_url 36 | from sphobjinv.cli.parser import getparser, PrsConst 37 | from sphobjinv.cli.suggest import do_suggest 38 | from sphobjinv.cli.ui import print_stderr 39 | 40 | 41 | def main(): 42 | r"""Handle command line invocation. 43 | 44 | Parses command line arguments, 45 | handling the no-arguments and 46 | |cli:VERSION| cases. 47 | 48 | Creates the |Inventory| from the indicated source 49 | and method. 50 | 51 | Invokes :func:`~sphobjinv.cli.convert.do_convert` or 52 | :func:`~sphobjinv.cli.suggest.do_suggest` 53 | per the subparser name stored in |cli:SUBPARSER_NAME|. 54 | 55 | """ 56 | # If no args passed, stick in '-h' 57 | if len(sys.argv) == 1: 58 | sys.argv.append("-h") 59 | 60 | # Parse commandline arguments, discarding any unknown ones 61 | # I forget why I set it up to discard these, it might be 62 | # more confusing than it's worth to swallow them this way.... 63 | prs = getparser() 64 | ns, _ = prs.parse_known_args() 65 | params = vars(ns) 66 | 67 | # Print version &c. and exit if indicated 68 | if params[PrsConst.VERSION]: 69 | print(PrsConst.VER_TXT) 70 | sys.exit(0) 71 | 72 | # At this point, need to trap for a null subparser 73 | if not params[PrsConst.SUBPARSER_NAME]: 74 | prs.error("No subparser selected") 75 | 76 | # Regardless of mode, insert extra blank line 77 | # for cosmetics 78 | print_stderr(" ", params) 79 | 80 | # Generate the input Inventory based on --url or stdio or file. 81 | # These inventory-load functions should call 82 | # sys.exit(n) internally in error-exit situations 83 | if params[PrsConst.URL]: 84 | if params[PrsConst.INFILE] == "-": 85 | prs.error("argument -u/--url not allowed with '-' as infile") 86 | inv, in_path = inv_url(params) 87 | elif params[PrsConst.INFILE] == "-": 88 | inv = inv_stdin(params) 89 | in_path = None 90 | else: 91 | inv, in_path = inv_local(params) 92 | 93 | # Perform action based upon mode 94 | if params[PrsConst.SUBPARSER_NAME][:2] == PrsConst.CONVERT[:2]: 95 | do_convert(inv, in_path, params) 96 | elif params[PrsConst.SUBPARSER_NAME][:2] == PrsConst.SUGGEST[:2]: 97 | do_suggest(inv, params) 98 | 99 | # Cosmetic final blank line 100 | print_stderr(" ", params) 101 | 102 | # Clean exit 103 | sys.exit(0) 104 | -------------------------------------------------------------------------------- /src/sphobjinv/cli/load.py: -------------------------------------------------------------------------------- 1 | r"""*Module for* ``sphobjinv`` *CLI* |Inventory| *loading*. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | **Author** 7 | Brian Skinn (brian.skinn@gmail.com) 8 | 9 | **File Created** 10 | 17 Nov 2020 11 | 12 | **Copyright** 13 | \(c) Brian Skinn 2016-2025 14 | 15 | **Source Repository** 16 | https://github.com/bskinn/sphobjinv 17 | 18 | **Documentation** 19 | https://sphobjinv.readthedocs.io/en/stable 20 | 21 | **License** 22 | Code: `MIT License`_ 23 | 24 | Docs & Docstrings: |CC BY 4.0|_ 25 | 26 | See |license_txt|_ for full license terms. 27 | 28 | **Members** 29 | 30 | """ 31 | 32 | import json 33 | import sys 34 | from json import JSONDecodeError 35 | from urllib.error import HTTPError, URLError 36 | 37 | from jsonschema.exceptions import ValidationError 38 | 39 | from sphobjinv import Inventory, readjson, urlwalk, VersionError 40 | from sphobjinv.cli.parser import PrsConst 41 | from sphobjinv.cli.paths import resolve_inpath 42 | from sphobjinv.cli.ui import err_format, print_stderr 43 | 44 | 45 | def import_infile(in_path): 46 | """Attempt import of indicated file. 47 | 48 | Convenience function wrapping attempts to load an 49 | |Inventory| from a local path. 50 | 51 | Parameters 52 | ---------- 53 | in_path 54 | 55 | |str| -- Path to input file 56 | 57 | Returns 58 | ------- 59 | inv 60 | 61 | |Inventory| or |None| -- If instantiation with the file at 62 | `in_path` succeeds, the resulting |Inventory| instance; 63 | otherwise, |None| 64 | 65 | """ 66 | # Try general import, for zlib or plaintext files 67 | try: 68 | inv = Inventory(in_path) 69 | except AttributeError: 70 | pass # Punt to JSON attempt 71 | else: 72 | return inv 73 | 74 | # Maybe it's JSON 75 | try: 76 | inv = Inventory(readjson(in_path)) 77 | except JSONDecodeError: 78 | return None 79 | else: 80 | return inv 81 | 82 | 83 | def inv_local(params): 84 | """Create |Inventory| from local source. 85 | 86 | Uses |resolve_inpath| to sanity-check and/or convert 87 | |cli:INFILE|. 88 | 89 | Calls :func:`sys.exit` internally in error-exit situations. 90 | 91 | Parameters 92 | ---------- 93 | params 94 | 95 | |dict| -- Parameters/values mapping from the active subparser 96 | 97 | Returns 98 | ------- 99 | inv 100 | 101 | |Inventory| -- Object representation of the inventory 102 | at |cli:INFILE| 103 | 104 | in_path 105 | 106 | |str| -- Input file path as resolved/checked by 107 | |resolve_inpath| 108 | 109 | """ 110 | # Resolve input file path 111 | try: 112 | in_path = resolve_inpath(params[PrsConst.INFILE]) 113 | except Exception as e: 114 | print_stderr("\nError while parsing input file path:", params) 115 | print_stderr(err_format(e), params) 116 | sys.exit(1) 117 | 118 | # Attempt import 119 | inv = import_infile(in_path) 120 | if inv is None: 121 | print_stderr("\nError: Unrecognized file format", params) 122 | sys.exit(1) 123 | 124 | return inv, in_path 125 | 126 | 127 | def inv_url(params): 128 | """Create |Inventory| from file downloaded from URL. 129 | 130 | Initially, treats |cli:INFILE| as a download URL to be passed to 131 | the `url` initialization argument 132 | of :class:`~sphobjinv.inventory.Inventory`. 133 | 134 | If an inventory is not found at that exact URL, progressively 135 | searches the directory tree of the URL for |objects.inv|. 136 | 137 | Injects the URL at which an inventory was found into `params` 138 | under the |cli:FOUND_URL| key. 139 | 140 | Calls :func:`sys.exit` internally in error-exit situations. 141 | 142 | Parameters 143 | ---------- 144 | params 145 | 146 | |dict| -- Parameters/values mapping from the active subparser 147 | 148 | Returns 149 | ------- 150 | inv 151 | 152 | |Inventory| -- Object representation of the inventory 153 | at |cli:INFILE| 154 | 155 | ret_path 156 | 157 | |str| -- URL from |cli:INFILE| used to construct `inv`. 158 | If URL is longer than 45 characters, the central portion is elided. 159 | 160 | """ 161 | in_file = params[PrsConst.INFILE] 162 | 163 | def attempt_inv_load(url, params): 164 | """Attempt the Inventory load and report outcome.""" 165 | inv = None 166 | 167 | try: 168 | inv = Inventory(url=url) 169 | except HTTPError as e: 170 | print_stderr(f" ... HTTP error: {e.code} {e.reason}.", params) 171 | except URLError: # pragma: no cover 172 | print_stderr(" ... error attempting to retrieve URL.", params) 173 | except VersionError: # pragma: no cover 174 | print_stderr(" ... no recognized inventory.", params) 175 | except ValueError: 176 | print_stderr( 177 | ( 178 | " ... file found but inventory could not be loaded. " 179 | "(Did you forget https:// ?)" 180 | ), 181 | params, 182 | ) 183 | else: 184 | print_stderr(" ... inventory found.", params) 185 | 186 | return inv 187 | 188 | # Disallow --url mode on local files 189 | if in_file.startswith("file:/"): 190 | print_stderr("\nError: URL mode on local file is invalid", params) 191 | sys.exit(1) 192 | 193 | print_stderr(f"Attempting {in_file} ...", params) 194 | inv = attempt_inv_load(in_file, params) 195 | 196 | if inv: 197 | url = in_file 198 | else: 199 | for url in urlwalk(in_file): 200 | print_stderr(f'Attempting "{url}" ...', params) 201 | inv = attempt_inv_load(url, params) 202 | if inv: 203 | break 204 | 205 | # Cosmetic line break 206 | print_stderr(" ", params) 207 | 208 | # Success or no? 209 | if not inv: 210 | print_stderr("No inventory found!", params) 211 | sys.exit(1) 212 | 213 | params.update({PrsConst.FOUND_URL: url}) 214 | if len(url) > 45: 215 | ret_path = url[:20] + "[...]" + url[-20:] 216 | else: # pragma: no cover 217 | ret_path = url 218 | 219 | return inv, ret_path 220 | 221 | 222 | def inv_stdin(params): 223 | """Create |Inventory| from contents of stdin. 224 | 225 | Due to stdin's encoding and formatting assumptions, only 226 | text-based inventory formats can be sanely parsed. 227 | 228 | Thus, only plaintext and JSON inventory formats can be 229 | used as inputs here. 230 | 231 | Parameters 232 | ---------- 233 | params 234 | 235 | |dict| -- Parameters/values mapping from the active subparser 236 | 237 | Returns 238 | ------- 239 | inv 240 | 241 | |Inventory| -- Object representation of the inventory 242 | provided at stdin 243 | 244 | """ 245 | data = sys.stdin.read() 246 | 247 | try: 248 | return Inventory(dict_json=json.loads(data)) 249 | except (JSONDecodeError, ValidationError): 250 | pass 251 | 252 | try: 253 | return Inventory(plaintext=data) 254 | except (AttributeError, UnicodeEncodeError, TypeError): 255 | pass 256 | 257 | print_stderr("Invalid plaintext or JSON inventory format.", params) 258 | sys.exit(1) 259 | -------------------------------------------------------------------------------- /src/sphobjinv/cli/paths.py: -------------------------------------------------------------------------------- 1 | r"""``sphobjinv`` *CLI path resolution module*. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | **Author** 7 | Brian Skinn (brian.skinn@gmail.com) 8 | 9 | **File Created** 10 | 19 Nov 2020 11 | 12 | **Copyright** 13 | \(c) Brian Skinn 2016-2025 14 | 15 | **Source Repository** 16 | https://github.com/bskinn/sphobjinv 17 | 18 | **Documentation** 19 | https://sphobjinv.readthedocs.io/en/stable 20 | 21 | **License** 22 | Code: `MIT License`_ 23 | 24 | Docs & Docstrings: |CC BY 4.0|_ 25 | 26 | See |license_txt|_ for full license terms. 27 | 28 | **Members** 29 | 30 | """ 31 | 32 | import os 33 | 34 | from sphobjinv.cli.parser import PrsConst 35 | 36 | 37 | def resolve_inpath(in_path): 38 | """Resolve the input file, handling invalid values. 39 | 40 | Currently, only checks for existence and not-directory. 41 | 42 | Parameters 43 | ---------- 44 | in_path 45 | 46 | |str| -- Path to desired input file 47 | 48 | Returns 49 | ------- 50 | abs_path 51 | 52 | |str| -- Absolute path to indicated file 53 | 54 | Raises 55 | ------ 56 | :exc:`FileNotFoundError` 57 | 58 | If a file is not found at the given path 59 | 60 | """ 61 | # Path MUST be to a file, that exists 62 | if not os.path.isfile(in_path): 63 | raise FileNotFoundError("Indicated path is not a valid file") 64 | 65 | # Return the path as absolute 66 | return os.path.abspath(in_path) 67 | 68 | 69 | def resolve_outpath(out_path, in_path, params): 70 | r"""Resolve the output location, handling mode-specific defaults. 71 | 72 | If the output path or basename are not specified, they are 73 | taken as the same as the input file. If the extension is 74 | unspecified, it is taken as the appropriate mode-specific value 75 | from |cli:DEF_OUT_EXT|. 76 | 77 | If |cli:URL| is passed, the input directory 78 | is taken to be :func:`os.getcwd` and the input basename 79 | is taken as |cli:DEF_BASENAME|. 80 | 81 | Parameters 82 | ---------- 83 | out_path 84 | 85 | |str| or |None| -- Output location provided by the user, 86 | or |None| if omitted 87 | 88 | in_path 89 | 90 | |str| -- For a local input file, its absolute path. 91 | For a URL, the (possibly truncated) URL text. 92 | 93 | params 94 | 95 | |dict| -- Parameters/values mapping from the active subparser 96 | 97 | Returns 98 | ------- 99 | out_path 100 | 101 | |str| -- Absolute path to the target output file 102 | 103 | """ 104 | mode = params[PrsConst.MODE] 105 | 106 | if params[PrsConst.URL] or in_path is None: 107 | in_fld = os.getcwd() 108 | in_fname = PrsConst.DEF_BASENAME 109 | else: 110 | in_fld, in_fname = os.path.split(in_path) 111 | 112 | if out_path: 113 | # Must check if the path entered is a folder 114 | if os.path.isdir(out_path): 115 | # Set just the folder and leave the name blank 116 | out_fld = out_path 117 | out_fname = None 118 | else: 119 | # Split appropriately 120 | out_fld, out_fname = os.path.split(out_path) 121 | 122 | # Output to same folder if unspecified 123 | if not out_fld: 124 | out_fld = in_fld 125 | 126 | # Use same base filename if not specified 127 | if not out_fname: 128 | out_fname = os.path.splitext(in_fname)[0] + PrsConst.DEF_OUT_EXT[mode] 129 | 130 | # Composite the full output path 131 | out_path = os.path.join(out_fld, out_fname) 132 | else: 133 | # No output location specified; use defaults 134 | out_fname = os.path.splitext(in_fname)[0] + PrsConst.DEF_OUT_EXT[mode] 135 | out_path = os.path.join(in_fld, out_fname) 136 | 137 | return out_path 138 | -------------------------------------------------------------------------------- /src/sphobjinv/cli/ui.py: -------------------------------------------------------------------------------- 1 | r"""``sphobjinv`` *CLI UI functions*. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | **Author** 7 | Brian Skinn (brian.skinn@gmail.com) 8 | 9 | **File Created** 10 | 19 Nov 2020 11 | 12 | **Copyright** 13 | \(c) Brian Skinn 2016-2025 14 | 15 | **Source Repository** 16 | https://github.com/bskinn/sphobjinv 17 | 18 | **Documentation** 19 | https://sphobjinv.readthedocs.io/en/stable 20 | 21 | **License** 22 | Code: `MIT License`_ 23 | 24 | Docs & Docstrings: |CC BY 4.0|_ 25 | 26 | See |license_txt|_ for full license terms. 27 | 28 | **Members** 29 | 30 | """ 31 | 32 | import sys 33 | 34 | from sphobjinv.cli.parser import PrsConst 35 | 36 | 37 | def print_stderr(thing, params, *, end="\n"): 38 | r"""Print `thing` to stderr if not in quiet mode. 39 | 40 | Quiet mode is indicated by the value at the |cli:QUIET| key 41 | within `params`. 42 | 43 | Quiet mode is not implemented for the ":doc:`suggest `" 44 | CLI mode. 45 | 46 | Parameters 47 | ---------- 48 | thing 49 | 50 | *any* -- Object to be printed 51 | 52 | params 53 | 54 | |dict| -- Parameters/values mapping from the active subparser 55 | 56 | end 57 | 58 | |str| -- String to append to printed content (default: ``\n``\ ) 59 | 60 | """ 61 | if params[PrsConst.SUBPARSER_NAME][:2] == "su" or not params[PrsConst.QUIET]: 62 | print(thing, file=sys.stderr, end=end) 63 | 64 | 65 | def err_format(exc): 66 | r"""Pretty-format an exception. 67 | 68 | Parameters 69 | ---------- 70 | exc 71 | 72 | :class:`Exception` -- Exception instance to pretty-format 73 | 74 | Returns 75 | ------- 76 | pretty_exc 77 | 78 | |str| -- Exception type and message formatted as 79 | |cour|\ '{type}: {message}'\ |/cour| 80 | 81 | """ 82 | return f"{type(exc).__name__}: {str(exc)}" 83 | 84 | 85 | def yesno_prompt(prompt): 86 | r"""Query user at `stdin` for yes/no confirmation. 87 | 88 | Uses :func:`input`, so will hang if used programmatically 89 | unless `stdin` is suitably mocked. 90 | 91 | The value returned from :func:`input` must satisfy either 92 | |cour|\ resp.lower() == 'n'\ |/cour| or 93 | |cour|\ resp.lower() == 'y'\ |/cour|, 94 | or else the query will be repeated *ad infinitum*. 95 | This function does **NOT** augment `prompt` 96 | to indicate the constraints on the accepted values. 97 | 98 | Parameters 99 | ---------- 100 | prompt 101 | 102 | |str| -- Prompt to display to user that 103 | requests a 'Y' or 'N' response 104 | 105 | Returns 106 | ------- 107 | resp 108 | 109 | |str| -- User response 110 | 111 | """ 112 | resp = "" 113 | while not (resp.lower() == "n" or resp.lower() == "y"): 114 | resp = input(prompt) 115 | return resp 116 | -------------------------------------------------------------------------------- /src/sphobjinv/cli/write.py: -------------------------------------------------------------------------------- 1 | r"""*Module for* ``sphobjinv`` *CLI* |Inventory| *writing*. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | **Author** 7 | Brian Skinn (brian.skinn@gmail.com) 8 | 9 | **File Created** 10 | 19 Nov 2020 11 | 12 | **Copyright** 13 | \(c) Brian Skinn 2016-2025 14 | 15 | **Source Repository** 16 | https://github.com/bskinn/sphobjinv 17 | 18 | **Documentation** 19 | https://sphobjinv.readthedocs.io/en/stable 20 | 21 | **License** 22 | Code: `MIT License`_ 23 | 24 | Docs & Docstrings: |CC BY 4.0|_ 25 | 26 | See |license_txt|_ for full license terms. 27 | 28 | **Members** 29 | 30 | """ 31 | 32 | import json 33 | import os 34 | import sys 35 | 36 | from sphobjinv.cli.parser import PrsConst 37 | from sphobjinv.cli.paths import resolve_outpath 38 | from sphobjinv.cli.ui import err_format, print_stderr, yesno_prompt 39 | from sphobjinv.fileops import writebytes, writejson 40 | from sphobjinv.zlib import compress 41 | 42 | 43 | def write_plaintext(inv, path, *, expand=False, contract=False): 44 | """Write an |Inventory| to plaintext. 45 | 46 | Newlines are inserted in an OS-aware manner, 47 | based on the value of :data:`os.linesep`. 48 | 49 | Calling with both `expand` and `contract` as |True| is invalid. 50 | 51 | Parameters 52 | ---------- 53 | inv 54 | 55 | |Inventory| -- Objects inventory to be written as plaintext 56 | 57 | path 58 | 59 | |str| -- Path to output file 60 | 61 | expand 62 | 63 | |bool| *(optional)* -- Generate output with any 64 | :data:`~sphobjinv.data.SuperDataObj.uri` or 65 | :data:`~sphobjinv.data.SuperDataObj.dispname` 66 | abbreviations expanded 67 | 68 | contract 69 | 70 | |bool| *(optional)* -- Generate output with abbreviated 71 | :data:`~sphobjinv.data.SuperDataObj.uri` and 72 | :data:`~sphobjinv.data.SuperDataObj.dispname` values 73 | 74 | Raises 75 | ------ 76 | ValueError 77 | 78 | If both `expand` and `contract` are |True| 79 | 80 | """ 81 | b_str = inv.data_file(expand=expand, contract=contract) 82 | writebytes(path, b_str.replace(b"\n", os.linesep.encode("utf-8"))) 83 | 84 | 85 | def write_zlib(inv, path, *, expand=False, contract=False): 86 | """Write an |Inventory| to zlib-compressed format. 87 | 88 | Calling with both `expand` and `contract` as |True| is invalid. 89 | 90 | Parameters 91 | ---------- 92 | inv 93 | 94 | |Inventory| -- Objects inventory to be written zlib-compressed 95 | 96 | path 97 | 98 | |str| -- Path to output file 99 | 100 | expand 101 | 102 | |bool| *(optional)* -- Generate output with any 103 | :data:`~sphobjinv.data.SuperDataObj.uri` or 104 | :data:`~sphobjinv.data.SuperDataObj.dispname` 105 | abbreviations expanded 106 | 107 | contract 108 | 109 | |bool| *(optional)* -- Generate output with abbreviated 110 | :data:`~sphobjinv.data.SuperDataObj.uri` and 111 | :data:`~sphobjinv.data.SuperDataObj.dispname` values 112 | 113 | Raises 114 | ------ 115 | ValueError 116 | 117 | If both `expand` and `contract` are |True| 118 | 119 | """ 120 | b_str = inv.data_file(expand=expand, contract=contract) 121 | bz_str = compress(b_str) 122 | writebytes(path, bz_str) 123 | 124 | 125 | def write_json(inv, path, params): 126 | """Write an |Inventory| to JSON. 127 | 128 | Writes output via 129 | :func:`fileops.writejson() `. 130 | 131 | Calling with both `expand` and `contract` as |True| is invalid. 132 | 133 | Parameters 134 | ---------- 135 | inv 136 | 137 | |Inventory| -- Objects inventory to be written zlib-compressed 138 | 139 | path 140 | 141 | |str| -- Path to output file 142 | 143 | params 144 | 145 | dict -- `argparse` parameters 146 | 147 | Raises 148 | ------ 149 | ValueError 150 | 151 | If both `params["expand"]` and `params["contract"]` are |True| 152 | 153 | """ 154 | json_dict = inv.json_dict( 155 | expand=params[PrsConst.EXPAND], contract=params[PrsConst.CONTRACT] 156 | ) 157 | 158 | if params.get(PrsConst.FOUND_URL, False): 159 | json_dict.update({"metadata": {PrsConst.URL: params[PrsConst.FOUND_URL]}}) 160 | 161 | writejson(path, json_dict) 162 | 163 | 164 | def write_stdout(inv, params): 165 | r"""Write the inventory contents to stdout. 166 | 167 | Parameters 168 | ---------- 169 | inv 170 | 171 | |Inventory| -- Objects inventory to be written to stdout 172 | 173 | params 174 | 175 | dict -- `argparse` parameters 176 | 177 | Raises 178 | ------ 179 | ValueError 180 | 181 | If both `params["expand"]` and `params["contract"]` are |True| 182 | 183 | """ 184 | if params[PrsConst.MODE] == PrsConst.PLAIN: 185 | print( 186 | inv.data_file( 187 | expand=params[PrsConst.EXPAND], contract=params[PrsConst.CONTRACT] 188 | ).decode() 189 | ) 190 | elif params[PrsConst.MODE] == PrsConst.JSON: 191 | json_dict = inv.json_dict( 192 | expand=params[PrsConst.EXPAND], contract=params[PrsConst.CONTRACT] 193 | ) 194 | 195 | if params.get(PrsConst.FOUND_URL, False): 196 | json_dict.update({"metadata": {PrsConst.URL: params[PrsConst.FOUND_URL]}}) 197 | 198 | print(json.dumps(json_dict)) 199 | else: 200 | print_stderr("Error: Only plaintext and JSON can be emitted to stdout.", params) 201 | sys.exit(1) 202 | 203 | 204 | def write_file(inv, in_path, params): 205 | r"""Write the inventory contents to a file on disk. 206 | 207 | Parameters 208 | ---------- 209 | inv 210 | 211 | |Inventory| -- Objects inventory to be written to stdout 212 | 213 | in_path 214 | 215 | |str| -- For a local input file, its absolute path. 216 | For a URL, the (possibly truncated) URL text. 217 | 218 | params 219 | 220 | dict -- `argparse` parameters 221 | 222 | Raises 223 | ------ 224 | ValueError 225 | 226 | If both `params["expand"]` and `params["contract"]` are |True| 227 | 228 | """ 229 | mode = params[PrsConst.MODE] 230 | 231 | # Work up the output location 232 | try: 233 | out_path = resolve_outpath(params[PrsConst.OUTFILE], in_path, params) 234 | except Exception as e: # pragma: no cover 235 | # This may not actually be reachable except in exceptional situations 236 | print_stderr("\nError while constructing output file path:", params) 237 | print_stderr(err_format(e), params) 238 | sys.exit(1) 239 | 240 | # If exists, must handle overwrite 241 | if os.path.isfile(out_path) and not params[PrsConst.OVERWRITE]: 242 | if params[PrsConst.INFILE] == "-": 243 | # If reading from stdin, just alert and don't overwrite 244 | print_stderr("\nFile exists. To overwrite, supply '-o'. Exiting...", params) 245 | sys.exit(0) 246 | # This could be written w/o nesting via elif, but would be harder to read. 247 | else: 248 | if not params[PrsConst.QUIET]: 249 | # If not a stdin read, confirm overwrite; or, just clobber if QUIET 250 | resp = yesno_prompt("File exists. Overwrite (Y/N)? ") 251 | if resp.lower() == "n": 252 | print_stderr("\nExiting...", params) 253 | sys.exit(0) 254 | 255 | # Write the output file 256 | try: 257 | if mode == PrsConst.ZLIB: 258 | write_zlib( 259 | inv, 260 | out_path, 261 | expand=params[PrsConst.EXPAND], 262 | contract=params[PrsConst.CONTRACT], 263 | ) 264 | if mode == PrsConst.PLAIN: 265 | write_plaintext( 266 | inv, 267 | out_path, 268 | expand=params[PrsConst.EXPAND], 269 | contract=params[PrsConst.CONTRACT], 270 | ) 271 | if mode == PrsConst.JSON: 272 | write_json(inv, out_path, params) 273 | except Exception as e: 274 | print_stderr("\nError during write of output file:", params) 275 | print_stderr(err_format(e), params) 276 | sys.exit(1) 277 | 278 | # Report success, if not QUIET 279 | print_stderr( 280 | "Conversion completed.\n" 281 | f"'{in_path if in_path else 'stdin'}' converted to '{out_path}' ({mode}).", 282 | params, 283 | ) 284 | -------------------------------------------------------------------------------- /src/sphobjinv/enum.py: -------------------------------------------------------------------------------- 1 | r"""*Helper enums for* ``sphobjinv``. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | **Author** 7 | Brian Skinn (brian.skinn@gmail.com) 8 | 9 | **File Created** 10 | 4 May 2019 11 | 12 | **Copyright** 13 | \(c) Brian Skinn 2016-2025 14 | 15 | **Source Repository** 16 | https://github.com/bskinn/sphobjinv 17 | 18 | **Documentation** 19 | https://sphobjinv.readthedocs.io/en/stable 20 | 21 | **License** 22 | Code: `MIT License`_ 23 | 24 | Docs & Docstrings: |CC BY 4.0|_ 25 | 26 | See |license_txt|_ for full license terms. 27 | 28 | **Members** 29 | 30 | """ 31 | 32 | from enum import Enum 33 | 34 | 35 | class SourceTypes(Enum): 36 | """|Enum| for the import mode used in instantiating an |Inventory|. 37 | 38 | Since |Enum| keys iterate in definition order, the 39 | definition order here defines the order in which |Inventory| 40 | objects attempt to parse a source object passed to 41 | :class:`Inventory.__init__() ` 42 | either as a positional argument 43 | or via the generic `source` keyword argument. 44 | 45 | This order **DIFFERS** from the documentation order, which is 46 | alphabetical. 47 | 48 | """ 49 | 50 | #: No source; |Inventory| was instantiated with 51 | #: :data:`~sphobjinv.inventory.Inventory.project` 52 | #: and :data:`~sphobjinv.inventory.Inventory.version` 53 | #: as empty strings and 54 | #: :data:`~sphobjinv.inventory.Inventory.objects` as an empty |list|. 55 | Manual = "manual" 56 | 57 | #: Instantiation from a plaintext |objects.inv| |bytes|. 58 | BytesPlaintext = "bytes_plain" 59 | 60 | #: Instantiation from a zlib-compressed 61 | #: |objects.inv| |bytes|. 62 | BytesZlib = "bytes_zlib" 63 | 64 | #: Instantiation from a plaintext |objects.inv| file on disk. 65 | FnamePlaintext = "fname_plain" 66 | 67 | #: Instantiation from a zlib-compressed |objects.inv| file on disk. 68 | FnameZlib = "fname_zlib" 69 | 70 | #: Instantiation from a |dict| validated against 71 | #: :data:`schema.json_schema `. 72 | DictJSON = "dict_json" 73 | 74 | #: Instantiation from a zlib-compressed |objects.inv| file 75 | #: downloaded from a URL. 76 | URL = "url" 77 | 78 | 79 | class HeaderFields(Enum): 80 | """|Enum| for various inventory-level data items. 81 | 82 | A subset of these |Enum| values is used in various Regex, 83 | JSON, and string formatting contexts within 84 | class:`~sphobjinv.inventory.Inventory` 85 | and :data:`schema.json_schema `. 86 | 87 | """ 88 | 89 | #: Project name associated with an inventory 90 | Project = "project" 91 | 92 | #: Project version associated with an inventory 93 | Version = "version" 94 | 95 | #: Number of objects contained in the inventory 96 | Count = "count" 97 | 98 | #: The |str| value of this |Enum| member is accepted as a root-level 99 | #: key in a |dict| to be imported into an 100 | #: :class:`~sphobjinv.inventory.Inventory`. 101 | #: The corresponding value in the |dict| may contain any arbitrary data. 102 | #: Its possible presence is accounted for in 103 | #: :data:`schema.json_schema `. 104 | #: 105 | #: The data associated with this key are **ignored** 106 | #: during import into an 107 | #: :class:`~sphobjinv.inventory.Inventory`. 108 | Metadata = "metadata" 109 | -------------------------------------------------------------------------------- /src/sphobjinv/error.py: -------------------------------------------------------------------------------- 1 | r"""*Custom errors for* ``sphobjinv``. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | **Author** 7 | Brian Skinn (brian.skinn@gmail.com) 8 | 9 | **File Created** 10 | 5 Nov 2017 11 | 12 | **Copyright** 13 | \(c) Brian Skinn 2016-2025 14 | 15 | **Source Repository** 16 | https://github.com/bskinn/sphobjinv 17 | 18 | **Documentation** 19 | https://sphobjinv.readthedocs.io/en/stable 20 | 21 | **License** 22 | Code: `MIT License`_ 23 | 24 | Docs & Docstrings: |CC BY 4.0|_ 25 | 26 | See |license_txt|_ for full license terms. 27 | 28 | **Members** 29 | 30 | """ 31 | 32 | 33 | class SphobjinvError(Exception): 34 | """Custom ``sphobjinv`` error superclass.""" 35 | 36 | 37 | class VersionError(SphobjinvError): 38 | """Raised when attempting an operation on an unsupported version. 39 | 40 | The current version of ``sphobjinv`` only supports 'version 2' 41 | |objects.inv| files (see :doc:`here `). 42 | 43 | """ 44 | 45 | # TODO: Add SOI prefix to this class name as part of the exceptions refactor 46 | -------------------------------------------------------------------------------- /src/sphobjinv/fileops.py: -------------------------------------------------------------------------------- 1 | r"""*File I/O helpers for* ``sphobjinv``. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | **Author** 7 | Brian Skinn (brian.skinn@gmail.com) 8 | 9 | **File Created** 10 | 5 Nov 2017 11 | 12 | **Copyright** 13 | \(c) Brian Skinn 2016-2025 14 | 15 | **Source Repository** 16 | https://github.com/bskinn/sphobjinv 17 | 18 | **Documentation** 19 | https://sphobjinv.readthedocs.io/en/stable 20 | 21 | **License** 22 | Code: `MIT License`_ 23 | 24 | Docs & Docstrings: |CC BY 4.0|_ 25 | 26 | See |license_txt|_ for full license terms. 27 | 28 | **Members** 29 | 30 | """ 31 | 32 | import json 33 | from pathlib import Path 34 | 35 | 36 | def readbytes(path): 37 | """Read file contents and return as |bytes|. 38 | 39 | .. versionchanged:: 2.1 40 | 41 | `path` can now be |Path| or |str|. Previously, it had to be |str|. 42 | 43 | Parameters 44 | ---------- 45 | path 46 | 47 | |str| or |Path| -- Path to file to be opened. 48 | 49 | Returns 50 | ------- 51 | b 52 | 53 | |bytes| -- Contents of the indicated file. 54 | 55 | """ 56 | return Path(path).read_bytes() 57 | 58 | 59 | def writebytes(path, contents): 60 | """Write indicated file contents. 61 | 62 | Any existing file at `path` will be overwritten. 63 | 64 | .. versionchanged:: 2.1 65 | 66 | `path` can now be |Path| or |str|. Previously, it had to be |str|. 67 | 68 | Parameters 69 | ---------- 70 | path 71 | 72 | |str| or |Path| -- Path to file to be written. 73 | 74 | contents 75 | 76 | |bytes| -- Content to be written to file. 77 | 78 | """ 79 | Path(path).write_bytes(contents) 80 | 81 | 82 | def readjson(path): 83 | """Create |dict| from JSON file. 84 | 85 | No data or schema validation is performed. 86 | 87 | .. versionchanged:: 2.1 88 | 89 | `path` can now be |Path| or |str|. Previously, it had to be |str|. 90 | 91 | Parameters 92 | ---------- 93 | path 94 | 95 | |str| or |Path| -- Path to JSON file to be read. 96 | 97 | Returns 98 | ------- 99 | d 100 | 101 | |dict| -- Deserialized JSON. 102 | 103 | """ 104 | return json.loads(Path(path).read_text()) 105 | 106 | 107 | def writejson(path, d): 108 | """Create JSON file from |dict|. 109 | 110 | No data or schema validation is performed. 111 | Any existing file at `path` will be overwritten. 112 | 113 | .. versionchanged:: 2.1 114 | 115 | `path` can now be |Path| or |str|. Previously, it had to be |str|. 116 | 117 | Parameters 118 | ---------- 119 | path 120 | 121 | |str| or |Path| -- Path to output JSON file. 122 | 123 | d 124 | 125 | |dict| -- Data structure to serialize. 126 | 127 | """ 128 | Path(path).write_text(json.dumps(d)) 129 | 130 | 131 | def urlwalk(url): 132 | r"""Generate a series of candidate |objects.inv| URLs. 133 | 134 | URLs are based on the seed `url` passed in. Ensure that the 135 | path separator in `url` is the standard **forward** slash 136 | ('|cour|\ /\ |/cour|'). 137 | 138 | Parameters 139 | ---------- 140 | url 141 | 142 | |str| -- Seed URL defining directory structure to walk through. 143 | 144 | Yields 145 | ------ 146 | inv_url 147 | 148 | |str| -- Candidate URL for |objects.inv| location. 149 | 150 | """ 151 | # Scrub any anchor, as it fouls things 152 | url = url.partition("#")[0] 153 | 154 | urlparts = url.rstrip("/").split("/") 155 | 156 | # This loop condition results in the yielded values stopping at 157 | # 'http[s]://domain.com/objects.inv', since the URL protocol 158 | # specifier has two forward slashes 159 | while len(urlparts) >= 3: 160 | urlparts.append("objects.inv") 161 | yield "/".join(urlparts) 162 | urlparts.pop() 163 | urlparts.pop() 164 | -------------------------------------------------------------------------------- /src/sphobjinv/re.py: -------------------------------------------------------------------------------- 1 | r"""*Helper regexes for* ``sphobjinv``. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | **Author** 7 | Brian Skinn (brian.skinn@gmail.com) 8 | 9 | **File Created** 10 | 5 Nov 2017 11 | 12 | **Copyright** 13 | \(c) Brian Skinn 2016-2025 14 | 15 | **Source Repository** 16 | https://github.com/bskinn/sphobjinv 17 | 18 | **Documentation** 19 | https://sphobjinv.readthedocs.io/en/stable 20 | 21 | **License** 22 | Code: `MIT License`_ 23 | 24 | Docs & Docstrings: |CC BY 4.0|_ 25 | 26 | See |license_txt|_ for full license terms. 27 | 28 | **Members** 29 | 30 | """ 31 | 32 | import re 33 | 34 | from sphobjinv.data import DataFields as DF # noqa: N817 35 | from sphobjinv.enum import HeaderFields as HF # noqa: N817 36 | 37 | 38 | #: Compiled |re| |bytes| pattern for comment lines in decompressed 39 | #: inventory files 40 | pb_comments = re.compile(b"^#.*$", re.M) 41 | 42 | #: Compiled |re| |bytes| pattern for project line 43 | pb_project = re.compile( 44 | rf""" 45 | ^ # Start of line 46 | [#][ ]Project:[ ] # Preamble 47 | (?P<{HF.Project.value}>.*?) # Lazy rest of line is project name 48 | \r?$ # Ignore possible CR at EOL 49 | """.encode( 50 | encoding="utf-8" 51 | ), 52 | re.M | re.X, 53 | ) 54 | 55 | #: Compiled |re| |bytes| pattern for version line 56 | pb_version = re.compile( 57 | rf""" 58 | ^ # Start of line 59 | [#][ ]Version:[ ] # Preamble 60 | (?P<{HF.Version.value}>.*?) # Lazy rest of line is version 61 | \r?$ # Ignore possible CR at EOL 62 | """.encode( 63 | encoding="utf-8" 64 | ), 65 | re.M | re.X, 66 | ) 67 | 68 | #: Regex pattern string used to compile 69 | #: :data:`~sphobjinv.re.p_data` and 70 | #: :data:`~sphobjinv.re.pb_data` 71 | ptn_data = rf""" 72 | ^ # Start of line 73 | (?P<{DF.Name.value}>.+?) # --> Name 74 | \s+ # Dividing space 75 | (?P<{DF.Domain.value}>[^\s:]+) # --> Domain 76 | : # Dividing colon 77 | (?P<{DF.Role.value}>[^\s]+) # --> Role 78 | \s+ # Dividing space 79 | (?P<{DF.Priority.value}>-?\d+) # --> Priority 80 | \s+? # Dividing space 81 | (?P<{DF.URI.value}>\S*) # --> URI 82 | \s+ # Dividing space 83 | (?P<{DF.DispName.value}>.+?) # --> Display name, lazy b/c possible CR 84 | \r?$ # Ignore possible CR at EOL 85 | """ 86 | 87 | #: Compiled |re| |bytes| regex pattern for data lines in |bytes| decompressed 88 | #: inventory files 89 | pb_data = re.compile(ptn_data.encode(encoding="utf-8"), re.M | re.X) 90 | 91 | #: Compiled |re| |str| regex pattern for data lines in |str| decompressed 92 | #: inventory files 93 | p_data = re.compile(ptn_data, re.M | re.X) 94 | -------------------------------------------------------------------------------- /src/sphobjinv/schema.py: -------------------------------------------------------------------------------- 1 | r"""*JSON schema to validate inventory dictionaries*. 2 | 3 | This module is part of ``sphobjinv``, 4 | a toolkit for manipulation and inspection of 5 | Sphinx |objects.inv| files. 6 | 7 | **Author** 8 | Brian Skinn (brian.skinn@gmail.com) 9 | 10 | **File Created** 11 | 7 Dec 2017 12 | 13 | **Copyright** 14 | \(c) Brian Skinn 2016-2025 15 | 16 | **Source Repository** 17 | https://github.com/bskinn/sphobjinv 18 | 19 | **Documentation** 20 | https://sphobjinv.readthedocs.io/en/stable 21 | 22 | **License** 23 | Code: `MIT License`_ 24 | 25 | Docs & Docstrings: |CC BY 4.0|_ 26 | 27 | See |license_txt|_ for full license terms. 28 | 29 | **Members** 30 | 31 | """ 32 | 33 | # For jsonschema Draft 4. 34 | # Schemas are defined with static field names as a versioning 35 | # guarantee, instead of basing them dynamically on DataFields, etc. 36 | 37 | # JSON dict schema 38 | # Subschema for the inner data, both for clarity and to make it 39 | # possible to satisfy flake8 40 | subschema_json = { 41 | "name": {"type": "string"}, 42 | "domain": {"type": "string"}, 43 | "role": {"type": "string"}, 44 | "priority": {"type": "string"}, 45 | "uri": {"type": "string"}, 46 | "dispname": {"type": "string"}, 47 | } 48 | 49 | #: JSON schema for validating the |dict| forms of 50 | #: Sphinx |objects.inv| inventories 51 | #: as generated from or expected by 52 | #: :class:`~sphobjinv.inventory.Inventory` classes. 53 | json_schema = { 54 | "$schema": "https://json-schema.org/draft-04/schema#", 55 | "type": "object", 56 | "properties": { 57 | "project": {"type": "string"}, 58 | "version": {"type": "string"}, 59 | "count": {"type": "integer"}, 60 | "metadata": {}, 61 | }, 62 | "patternProperties": { 63 | r"^\d+": { 64 | "type": "object", 65 | "properties": subschema_json, 66 | "additionalProperties": False, 67 | "required": list(subschema_json), 68 | } 69 | }, 70 | "additionalProperties": False, 71 | "required": ["project", "version", "count"], 72 | } 73 | -------------------------------------------------------------------------------- /src/sphobjinv/version.py: -------------------------------------------------------------------------------- 1 | r"""``sphobjinv`` *version definition module*. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | **Author** 7 | Brian Skinn (brian.skinn@gmail.com) 8 | 9 | **File Created** 10 | 18 Mar 2019 11 | 12 | **Copyright** 13 | \(c) Brian Skinn 2016-2025 14 | 15 | **Source Repository** 16 | https://github.com/bskinn/sphobjinv 17 | 18 | **Documentation** 19 | https://sphobjinv.readthedocs.io/en/stable 20 | 21 | **License** 22 | Code: `MIT License`_ 23 | 24 | Docs & Docstrings: |CC BY 4.0|_ 25 | 26 | See |license_txt|_ for full license terms. 27 | 28 | **Members** 29 | 30 | """ 31 | 32 | __version__ = "2.3.2.dev0" 33 | -------------------------------------------------------------------------------- /src/sphobjinv/zlib.py: -------------------------------------------------------------------------------- 1 | r"""*zlib (de)compression helpers for* ``sphobjinv``. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | **Author** 7 | Brian Skinn (brian.skinn@gmail.com) 8 | 9 | **File Created** 10 | 5 Nov 2017 11 | 12 | **Copyright** 13 | \(c) Brian Skinn 2016-2025 14 | 15 | **Source Repository** 16 | https://github.com/bskinn/sphobjinv 17 | 18 | **Documentation** 19 | https://sphobjinv.readthedocs.io/en/stable 20 | 21 | **License** 22 | Code: `MIT License`_ 23 | 24 | Docs & Docstrings: |CC BY 4.0|_ 25 | 26 | See |license_txt|_ for full license terms. 27 | 28 | **Members** 29 | 30 | """ 31 | 32 | import io 33 | import os 34 | import zlib 35 | 36 | 37 | BUFSIZE = 16 * 1024 # 16k chunks 38 | 39 | 40 | def decompress(bstr): 41 | """Decompress a version 2 |isphx| |objects.inv| bytestring. 42 | 43 | The `#`-prefixed comment lines are left unchanged, whereas the 44 | :mod:`zlib`-compressed data lines are decompressed to plaintext. 45 | 46 | Parameters 47 | ---------- 48 | bstr 49 | 50 | |bytes| -- Binary string containing a compressed |objects.inv| 51 | file. 52 | 53 | Returns 54 | ------- 55 | out_b 56 | 57 | |bytes| -- Decompressed binary string containing the plaintext 58 | |objects.inv| content. 59 | 60 | """ 61 | from sphobjinv.error import VersionError 62 | 63 | def decompress_chunks(bstrm): 64 | """Handle chunk-wise zlib decompression. 65 | 66 | Internal function pulled from intersphinx.py@v1.4.1: 67 | https://github.com/sphinx-doc/sphinx/blob/1.4.1/sphinx/ 68 | ext/intersphinx.py#L79-L124. 69 | 70 | BUFSIZE taken as the default value from intersphinx signature 71 | Modified slightly to take the stream as a parameter, 72 | rather than assuming one from the parent namespace. 73 | 74 | """ 75 | decompressor = zlib.decompressobj() 76 | for chunk in iter(lambda: bstrm.read(BUFSIZE), b""): 77 | yield decompressor.decompress(chunk) 78 | yield decompressor.flush() 79 | 80 | # Make stream and output string 81 | strm = io.BytesIO(bstr) 82 | 83 | # Check to be sure it's v2 84 | out_b = strm.readline() 85 | if not out_b.endswith(b"2\n"): # pragma: no cover 86 | raise VersionError("Only v2 objects.inv files currently supported") 87 | 88 | # Pull name, version, and description lines 89 | for _ in range(3): 90 | out_b += strm.readline() 91 | 92 | # Decompress chunks and append 93 | for chunk in decompress_chunks(strm): 94 | out_b += chunk 95 | 96 | # Replace newlines with the OS-local newlines, and return 97 | return out_b.replace(b"\n", os.linesep.encode("utf-8")) 98 | 99 | 100 | def compress(bstr): 101 | """Compress a version 2 |isphx| |objects.inv| bytestring. 102 | 103 | The `#`-prefixed comment lines are left unchanged, whereas the 104 | plaintext data lines are compressed with :mod:`zlib`. 105 | 106 | Parameters 107 | ---------- 108 | bstr 109 | 110 | |bytes| -- Binary string containing the decompressed contents of an 111 | |objects.inv| file. 112 | 113 | Returns 114 | ------- 115 | out_b 116 | 117 | |bytes| -- Binary string containing the compressed |objects.inv| 118 | content. 119 | 120 | """ 121 | from sphobjinv.re import pb_comments, pb_data 122 | 123 | # Preconvert any DOS newlines to Unix 124 | s = bstr.replace(b"\r\n", b"\n") 125 | 126 | # Pull all of the lines 127 | m_comments = pb_comments.findall(s) 128 | m_data = pb_data.finditer(s) 129 | 130 | # Assemble the binary header comments and data 131 | # Comments and data blocks must end in newlines 132 | hb = b"\n".join(m_comments) + b"\n" 133 | db = b"\n".join(_.group(0) for _ in m_data) + b"\n" 134 | 135 | # Compress the data block 136 | # Compression level nine is to match that specified in 137 | # sphinx html builder: 138 | # https://github.com/sphinx-doc/sphinx/blob/1.4.1/sphinx/ 139 | # builders/html.py#L843 140 | dbc = zlib.compress(db, 9) 141 | 142 | # Return the composited bytestring 143 | return hb + dbc 144 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Blank __init__.py for pytest namespace isolation.""" 2 | -------------------------------------------------------------------------------- /tests/resource/objects_NAPALM.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_NAPALM.inv -------------------------------------------------------------------------------- /tests/resource/objects_attrs.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_attrs.inv -------------------------------------------------------------------------------- /tests/resource/objects_attrs.txt: -------------------------------------------------------------------------------- 1 | # Sphinx inventory version 2 2 | # Project: attrs 3 | # Version: 22.1 4 | # The remainder of this file is compressed using zlib. 5 | attr py:module 0 index.html#module-$ - 6 | attr.VersionInfo py:class 1 api.html#$ - 7 | attr._make.Attribute py:class -1 api.html#attrs.Attribute - 8 | attr._make.Factory py:class -1 api.html#attrs.Factory - 9 | attr._version_info.VersionInfo py:class -1 api.html#attr.VersionInfo - 10 | attr.asdict py:function 1 api.html#$ - 11 | attr.assoc py:function 1 api.html#$ - 12 | attr.astuple py:function 1 api.html#$ - 13 | attr.attr.NOTHING py:data 1 api.html#$ - 14 | attr.attr.cmp_using py:function 1 api.html#$ - 15 | attr.attr.evolve py:function 1 api.html#$ - 16 | attr.attr.fields py:function 1 api.html#$ - 17 | attr.attr.fields_dict py:function 1 api.html#$ - 18 | attr.attr.filters.exclude py:function 1 api.html#$ - 19 | attr.attr.filters.include py:function 1 api.html#$ - 20 | attr.attr.has py:function 1 api.html#$ - 21 | attr.attr.resolve_types py:function 1 api.html#$ - 22 | attr.attr.validate py:function 1 api.html#$ - 23 | attr.attrs.frozen py:function 1 api.html#$ - 24 | attr.attrs.mutable py:function 1 api.html#$ - 25 | attr.attrs.setters.NO_OP py:data 1 api.html#$ - 26 | attr.define py:function 1 api.html#$ - 27 | attr.exceptions.AttrsAttributeNotFoundError py:exception -1 api.html#attrs.exceptions.AttrsAttributeNotFoundError - 28 | attr.exceptions.DefaultAlreadySetError py:exception -1 api.html#attrs.exceptions.DefaultAlreadySetError - 29 | attr.exceptions.FrozenAttributeError py:exception -1 api.html#attrs.exceptions.FrozenAttributeError - 30 | attr.exceptions.FrozenError py:exception -1 api.html#attrs.exceptions.FrozenError - 31 | attr.exceptions.FrozenInstanceError py:exception -1 api.html#attrs.exceptions.FrozenInstanceError - 32 | attr.exceptions.NotAnAttrsClassError py:exception -1 api.html#attrs.exceptions.NotAnAttrsClassError - 33 | attr.exceptions.NotCallableError py:exception -1 api.html#attrs.exceptions.NotCallableError - 34 | attr.exceptions.PythonTooOldError py:exception -1 api.html#attrs.exceptions.PythonTooOldError - 35 | attr.exceptions.UnannotatedAttributeError py:exception -1 api.html#attrs.exceptions.UnannotatedAttributeError - 36 | attr.field py:function 1 api.html#$ - 37 | attr.frozen py:function 1 api.html#$ - 38 | attr.get_run_validators py:function 1 api.html#$ - 39 | attr.ib py:function 1 api.html#$ - 40 | attr.mutable py:function 1 api.html#$ - 41 | attr.s py:function 1 api.html#$ - 42 | attr.set_run_validators py:function 1 api.html#$ - 43 | attrs py:module 0 index.html#module-$ - 44 | attrs.Attribute py:class 1 api.html#$ - 45 | attrs.Attribute.evolve py:method 1 api.html#$ - 46 | attrs.Factory py:class 1 api.html#$ - 47 | attrs.NOTHING py:data 1 api.html#$ - 48 | attrs.asdict py:function 1 api.html#$ - 49 | attrs.astuple py:function 1 api.html#$ - 50 | attrs.cmp_using py:function 1 api.html#$ - 51 | attrs.converters.default_if_none py:function 1 api.html#$ - 52 | attrs.converters.optional py:function 1 api.html#$ - 53 | attrs.converters.pipe py:function 1 api.html#$ - 54 | attrs.converters.to_bool py:function 1 api.html#$ - 55 | attrs.define py:function 1 api.html#$ - 56 | attrs.evolve py:function 1 api.html#$ - 57 | attrs.exceptions.AttrsAttributeNotFoundError py:exception 1 api.html#$ - 58 | attrs.exceptions.DefaultAlreadySetError py:exception 1 api.html#$ - 59 | attrs.exceptions.FrozenAttributeError py:exception 1 api.html#$ - 60 | attrs.exceptions.FrozenError py:exception 1 api.html#$ - 61 | attrs.exceptions.FrozenInstanceError py:exception 1 api.html#$ - 62 | attrs.exceptions.NotAnAttrsClassError py:exception 1 api.html#$ - 63 | attrs.exceptions.NotCallableError py:exception 1 api.html#$ - 64 | attrs.exceptions.PythonTooOldError py:exception 1 api.html#$ - 65 | attrs.exceptions.UnannotatedAttributeError py:exception 1 api.html#$ - 66 | attrs.field py:function 1 api.html#$ - 67 | attrs.fields py:function 1 api.html#$ - 68 | attrs.fields_dict py:function 1 api.html#$ - 69 | attrs.filters.exclude py:function 1 api.html#$ - 70 | attrs.filters.include py:function 1 api.html#$ - 71 | attrs.has py:function 1 api.html#$ - 72 | attrs.make_class py:function 1 api.html#$ - 73 | attrs.resolve_types py:function 1 api.html#$ - 74 | attrs.setters.convert py:function 1 api.html#$ - 75 | attrs.setters.frozen py:function 1 api.html#$ - 76 | attrs.setters.pipe py:function 1 api.html#$ - 77 | attrs.setters.validate py:function 1 api.html#$ - 78 | attrs.validate py:function 1 api.html#$ - 79 | attrs.validators.and_ py:function 1 api.html#$ - 80 | attrs.validators.deep_iterable py:function 1 api.html#$ - 81 | attrs.validators.deep_mapping py:function 1 api.html#$ - 82 | attrs.validators.disabled py:function 1 api.html#$ - 83 | attrs.validators.ge py:function 1 api.html#$ - 84 | attrs.validators.get_disabled py:function 1 api.html#$ - 85 | attrs.validators.gt py:function 1 api.html#$ - 86 | attrs.validators.in_ py:function 1 api.html#$ - 87 | attrs.validators.instance_of py:function 1 api.html#$ - 88 | attrs.validators.is_callable py:function 1 api.html#$ - 89 | attrs.validators.le py:function 1 api.html#$ - 90 | attrs.validators.lt py:function 1 api.html#$ - 91 | attrs.validators.matches_re py:function 1 api.html#$ - 92 | attrs.validators.max_len py:function 1 api.html#$ - 93 | attrs.validators.min_len py:function 1 api.html#$ - 94 | attrs.validators.optional py:function 1 api.html#$ - 95 | attrs.validators.provides py:function 1 api.html#$ - 96 | attrs.validators.set_disabled py:function 1 api.html#$ - 97 | api std:doc -1 api.html API Reference 98 | api_setters std:label -1 api.html#api-setters Setters 99 | api_validators std:label -1 api.html#api-validators Validators 100 | asdict std:label -1 examples.html#$ Converting to Collections Types 101 | changelog std:doc -1 changelog.html Changelog 102 | comparison std:doc -1 comparison.html Comparison 103 | converters std:label -1 init.html#$ Converters 104 | custom-comparison std:label -1 comparison.html#$ Customization 105 | dict classes std:term -1 glossary.html#term-dict-classes - 106 | dunder methods std:term -1 glossary.html#term-dunder-methods - 107 | examples std:doc -1 examples.html attrs by Example 108 | examples_validators std:label -1 examples.html#examples-validators Validators 109 | extending std:doc -1 extending.html Extending 110 | extending_metadata std:label -1 extending.html#extending-metadata Metadata 111 | genindex std:label -1 genindex.html Index 112 | glossary std:doc -1 glossary.html Glossary 113 | hashing std:doc -1 hashing.html Hashing 114 | helpers std:label -1 api.html#$ Helpers 115 | how std:label -1 how-does-it-work.html#$ How Does It Work? 116 | how-does-it-work std:doc -1 how-does-it-work.html How Does It Work? 117 | how-frozen std:label -1 how-does-it-work.html#$ Immutability 118 | index std:doc -1 index.html attrs: Classes Without Boilerplate 119 | init std:doc -1 init.html Initialization 120 | license std:doc -1 license.html License and Credits 121 | metadata std:label -1 examples.html#$ Metadata 122 | modindex std:label -1 py-modindex.html Module Index 123 | names std:doc -1 names.html On The Core API Names 124 | overview std:doc -1 overview.html Overview 125 | philosophy std:label -1 overview.html#$ Philosophy 126 | py-modindex std:label -1 py-modindex.html Python Module Index 127 | search std:label -1 search.html Search Page 128 | slotted classes std:term -1 glossary.html#term-slotted-classes - 129 | transform-fields std:label -1 extending.html#$ Automatic Field Transformation and Modification 130 | types std:doc -1 types.html Type Annotations 131 | validators std:label -1 init.html#$ Validators 132 | version-info std:label -1 api.html#$ - 133 | why std:doc -1 why.html Why not… 134 | -------------------------------------------------------------------------------- /tests/resource/objects_attrs_17_2_0.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_attrs_17_2_0.inv -------------------------------------------------------------------------------- /tests/resource/objects_attrs_20_3_0.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_attrs_20_3_0.inv -------------------------------------------------------------------------------- /tests/resource/objects_beaker.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_beaker.inv -------------------------------------------------------------------------------- /tests/resource/objects_bokeh.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_bokeh.inv -------------------------------------------------------------------------------- /tests/resource/objects_bootstrap_datepicker.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_bootstrap_datepicker.inv -------------------------------------------------------------------------------- /tests/resource/objects_cclib.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_cclib.inv -------------------------------------------------------------------------------- /tests/resource/objects_celery.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_celery.inv -------------------------------------------------------------------------------- /tests/resource/objects_click.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_click.inv -------------------------------------------------------------------------------- /tests/resource/objects_cookiecutter.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_cookiecutter.inv -------------------------------------------------------------------------------- /tests/resource/objects_coverage.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_coverage.inv -------------------------------------------------------------------------------- /tests/resource/objects_django.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_django.inv -------------------------------------------------------------------------------- /tests/resource/objects_django_channels.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_django_channels.inv -------------------------------------------------------------------------------- /tests/resource/objects_eyeD3.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_eyeD3.inv -------------------------------------------------------------------------------- /tests/resource/objects_faker.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_faker.inv -------------------------------------------------------------------------------- /tests/resource/objects_flake8.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_flake8.inv -------------------------------------------------------------------------------- /tests/resource/objects_flask.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_flask.inv -------------------------------------------------------------------------------- /tests/resource/objects_fonttools.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_fonttools.inv -------------------------------------------------------------------------------- /tests/resource/objects_gspread.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_gspread.inv -------------------------------------------------------------------------------- /tests/resource/objects_h5py.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_h5py.inv -------------------------------------------------------------------------------- /tests/resource/objects_hypothesis.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_hypothesis.inv -------------------------------------------------------------------------------- /tests/resource/objects_jinja2.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_jinja2.inv -------------------------------------------------------------------------------- /tests/resource/objects_jsonschema.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_jsonschema.inv -------------------------------------------------------------------------------- /tests/resource/objects_matplotlib.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_matplotlib.inv -------------------------------------------------------------------------------- /tests/resource/objects_mdn.bad_inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_mdn.bad_inv -------------------------------------------------------------------------------- /tests/resource/objects_mistune.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_mistune.inv -------------------------------------------------------------------------------- /tests/resource/objects_mkdoc_zlib0.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_mkdoc_zlib0.inv -------------------------------------------------------------------------------- /tests/resource/objects_mypy.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_mypy.inv -------------------------------------------------------------------------------- /tests/resource/objects_nltk.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_nltk.inv -------------------------------------------------------------------------------- /tests/resource/objects_noinfo.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_noinfo.inv -------------------------------------------------------------------------------- /tests/resource/objects_noproject.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_noproject.inv -------------------------------------------------------------------------------- /tests/resource/objects_numpy.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_numpy.inv -------------------------------------------------------------------------------- /tests/resource/objects_opencv.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_opencv.inv -------------------------------------------------------------------------------- /tests/resource/objects_pandas.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_pandas.inv -------------------------------------------------------------------------------- /tests/resource/objects_pdfminer.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_pdfminer.inv -------------------------------------------------------------------------------- /tests/resource/objects_pelican.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_pelican.inv -------------------------------------------------------------------------------- /tests/resource/objects_pingo.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_pingo.inv -------------------------------------------------------------------------------- /tests/resource/objects_plone.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_plone.inv -------------------------------------------------------------------------------- /tests/resource/objects_psutil.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_psutil.inv -------------------------------------------------------------------------------- /tests/resource/objects_pyexcel.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_pyexcel.inv -------------------------------------------------------------------------------- /tests/resource/objects_pygame.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_pygame.inv -------------------------------------------------------------------------------- /tests/resource/objects_pymongo.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_pymongo.inv -------------------------------------------------------------------------------- /tests/resource/objects_pyqt.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_pyqt.inv -------------------------------------------------------------------------------- /tests/resource/objects_pyserial.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_pyserial.inv -------------------------------------------------------------------------------- /tests/resource/objects_pytest.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_pytest.inv -------------------------------------------------------------------------------- /tests/resource/objects_python.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_python.inv -------------------------------------------------------------------------------- /tests/resource/objects_requests.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_requests.inv -------------------------------------------------------------------------------- /tests/resource/objects_rocket.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_rocket.inv -------------------------------------------------------------------------------- /tests/resource/objects_sarge.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_sarge.inv -------------------------------------------------------------------------------- /tests/resource/objects_scapy.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_scapy.inv -------------------------------------------------------------------------------- /tests/resource/objects_scipy.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_scipy.inv -------------------------------------------------------------------------------- /tests/resource/objects_scrapy.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_scrapy.inv -------------------------------------------------------------------------------- /tests/resource/objects_sklearn.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_sklearn.inv -------------------------------------------------------------------------------- /tests/resource/objects_sphinx.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_sphinx.inv -------------------------------------------------------------------------------- /tests/resource/objects_sphinx_1_6_6.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_sphinx_1_6_6.inv -------------------------------------------------------------------------------- /tests/resource/objects_sqlalchemy.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_sqlalchemy.inv -------------------------------------------------------------------------------- /tests/resource/objects_sympy.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_sympy.inv -------------------------------------------------------------------------------- /tests/resource/objects_tinydb.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_tinydb.inv -------------------------------------------------------------------------------- /tests/resource/objects_tox.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_tox.inv -------------------------------------------------------------------------------- /tests/resource/objects_twython.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_twython.inv -------------------------------------------------------------------------------- /tests/resource/objects_yt.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bskinn/sphobjinv/ae11a0efc473368028d7a0885300643ce312403d/tests/resource/objects_yt.inv -------------------------------------------------------------------------------- /tests/test_api_good_nonlocal.py: -------------------------------------------------------------------------------- 1 | r"""*Direct, NONLOCAL expect-good API tests for* ``sphobjinv``. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | **Author** 7 | Brian Skinn (brian.skinn@gmail.com) 8 | 9 | **File Created** 10 | 21 Mar 2019 11 | 12 | **Copyright** 13 | \(c) Brian Skinn 2016-2025 14 | 15 | **Source Repository** 16 | http://www.github.com/bskinn/sphobjinv 17 | 18 | **Documentation** 19 | https://sphobjinv.readthedocs.io/en/stable 20 | 21 | **License** 22 | Code: `MIT License`_ 23 | 24 | Docs & Docstrings: |CC BY 4.0|_ 25 | 26 | See |license_txt|_ for full license terms. 27 | 28 | **Members** 29 | 30 | """ 31 | 32 | import pytest 33 | 34 | import sphobjinv as soi 35 | 36 | 37 | pytestmark = [ 38 | pytest.mark.api, 39 | pytest.mark.nonloc, 40 | pytest.mark.flaky(retries=2, delay=5), 41 | ] 42 | 43 | 44 | @pytest.fixture(scope="module", autouse=True) 45 | def skip_if_no_nonloc(pytestconfig): 46 | """Skip test if --nonloc not provided. 47 | 48 | Auto-applied to all functions in module, since module is nonlocal. 49 | 50 | """ 51 | if not pytestconfig.getoption("--nonloc"): 52 | pytest.skip("'--nonloc' not specified") # pragma: no cover 53 | 54 | 55 | @pytest.mark.parametrize( 56 | ["name", "url"], 57 | [ 58 | ("flask", "http://flask.palletsprojects.com/en/1.1.x/objects.inv"), 59 | ("h5py", "https://docs.h5py.org/en/stable/objects.inv"), 60 | ], 61 | ids=(lambda x: "" if "://" in x else x), 62 | ) 63 | @pytest.mark.timeout(30) 64 | def test_api_inventory_known_header_required(name, url): 65 | """Confirm URL load works on docs pages requiring HTTP header config.""" 66 | inv = soi.Inventory(url=url) 67 | assert inv.count > 0 68 | 69 | 70 | @pytest.mark.testall 71 | @pytest.mark.timeout(30) 72 | def test_api_inventory_many_url_imports( 73 | testall_inv_path, 74 | res_path, 75 | scratch_path, 76 | misc_info, 77 | sphinx_load_test, 78 | pytestconfig, 79 | ): 80 | """Confirm a plethora of .inv files downloads properly via url arg. 81 | 82 | This test is SLOW, and so does not run by default. Invoke with `--nonloc` 83 | to run it; invoke with `--testall` to test over all .inv files in 84 | tests/resource. 85 | 86 | """ 87 | fname = testall_inv_path.name 88 | scr_fpath = scratch_path / fname 89 | 90 | # Drop most unless testall 91 | if not pytestconfig.getoption("--testall") and fname != "objects_attrs.inv": 92 | pytest.skip("'--testall' not specified") 93 | 94 | # Construct inventories for comparison 95 | mch = misc_info.p_inv.match(fname) 96 | proj_name = mch.group(1) 97 | inv1 = soi.Inventory(str(res_path / fname)) 98 | inv2 = soi.Inventory(url=misc_info.remote_url.format(proj_name)) 99 | 100 | # Test the things 101 | assert inv1 == inv2 102 | 103 | # Ensure sphinx likes the regenerated inventory 104 | data = inv2.data_file() 105 | cmp_data = soi.compress(data) 106 | soi.writebytes(scr_fpath, cmp_data) 107 | sphinx_load_test(scr_fpath) 108 | -------------------------------------------------------------------------------- /tests/test_cli_nonlocal.py: -------------------------------------------------------------------------------- 1 | r"""*Nonlocal CLI tests for* ``sphobjinv``. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | **Author** 7 | Brian Skinn (brian.skinn@gmail.com) 8 | 9 | **File Created** 10 | 20 Mar 2019 11 | 12 | **Copyright** 13 | \(c) Brian Skinn 2016-2025 14 | 15 | **Source Repository** 16 | http://www.github.com/bskinn/sphobjinv 17 | 18 | **Documentation** 19 | https://sphobjinv.readthedocs.io/en/stable 20 | 21 | **License** 22 | Code: `MIT License`_ 23 | 24 | Docs & Docstrings: |CC BY 4.0|_ 25 | 26 | See |license_txt|_ for full license terms. 27 | 28 | **Members** 29 | 30 | """ 31 | 32 | import json 33 | import re 34 | 35 | import pytest 36 | from stdio_mgr import stdio_mgr 37 | 38 | from sphobjinv import Inventory 39 | 40 | CLI_TEST_TIMEOUT = 5 41 | 42 | p_instance_of = re.compile("^.*instance_of.*$", re.M) 43 | p_inventory = re.compile("^.*nventory.*$", re.I | re.M) 44 | 45 | pytestmark = [ 46 | pytest.mark.cli, 47 | pytest.mark.nonloc, 48 | pytest.mark.flaky(retries=2, delay=5), 49 | ] 50 | 51 | 52 | @pytest.fixture(scope="module", autouse=True) 53 | def skip_if_no_nonloc(pytestconfig): 54 | """Skip test if --nonloc not provided. 55 | 56 | Auto-applied to all functions in module, since module is nonlocal. 57 | 58 | """ 59 | if not pytestconfig.getoption("--nonloc"): 60 | pytest.skip("'--nonloc' not specified") # pragma: no cover 61 | 62 | 63 | class TestConvert: 64 | """Test nonlocal CLI convert mode functionality.""" 65 | 66 | @pytest.mark.timeout(CLI_TEST_TIMEOUT * 4) 67 | def test_cli_convert_from_url_with_dest( 68 | self, scratch_path, misc_info, run_cmdline_test, monkeypatch 69 | ): 70 | """Confirm CLI URL D/L, convert works w/outfile supplied.""" 71 | monkeypatch.chdir(scratch_path) 72 | 73 | dest_path = scratch_path / (misc_info.FNames.MOD + misc_info.Extensions.DEC) 74 | run_cmdline_test( 75 | [ 76 | "convert", 77 | "plain", 78 | "-u", 79 | misc_info.remote_url.format("attrs"), 80 | str(dest_path), 81 | ] 82 | ) 83 | 84 | assert dest_path.is_file() 85 | 86 | @pytest.mark.timeout(CLI_TEST_TIMEOUT * 4) 87 | def test_cli_convert_from_url_no_dest( 88 | self, scratch_path, misc_info, run_cmdline_test, monkeypatch 89 | ): 90 | """Confirm CLI URL D/L, convert works w/o outfile supplied.""" 91 | monkeypatch.chdir(scratch_path) 92 | dest_path = scratch_path / (misc_info.FNames.INIT + misc_info.Extensions.DEC) 93 | dest_path.unlink() 94 | run_cmdline_test( 95 | ["convert", "plain", "-u", misc_info.remote_url.format("attrs")] 96 | ) 97 | assert dest_path.is_file() 98 | 99 | @pytest.mark.timeout(CLI_TEST_TIMEOUT * 4) 100 | def test_cli_url_in_json( 101 | self, scratch_path, misc_info, run_cmdline_test, monkeypatch 102 | ): 103 | """Confirm URL is present when using CLI URL mode.""" 104 | monkeypatch.chdir(scratch_path) 105 | dest_path = scratch_path / (misc_info.FNames.MOD + misc_info.Extensions.JSON) 106 | run_cmdline_test( 107 | [ 108 | "convert", 109 | "json", 110 | "-u", 111 | misc_info.remote_url.format("attrs"), 112 | str(dest_path.resolve()), 113 | ] 114 | ) 115 | 116 | d = json.loads(dest_path.read_text()) 117 | 118 | assert "objects" in d.get("metadata", {}).get("url", {}) 119 | 120 | @pytest.mark.timeout(CLI_TEST_TIMEOUT * 4) 121 | def test_clifail_bad_url(self, run_cmdline_test, misc_info, scratch_path): 122 | """Confirm proper error behavior when a bad URL is passed.""" 123 | with stdio_mgr() as (in_, out_, err_): 124 | run_cmdline_test( 125 | [ 126 | "convert", 127 | "plain", 128 | "-u", 129 | misc_info.remote_url.format("blarghers"), 130 | str(scratch_path), 131 | ], 132 | expect=1, 133 | ) 134 | assert "HTTP error: 404 Not Found." in err_.getvalue() 135 | 136 | @pytest.mark.timeout(CLI_TEST_TIMEOUT * 4) 137 | def test_clifail_url_no_leading_http(self, run_cmdline_test, scratch_path): 138 | """Confirm proper error behavior when a URL w/o leading 'http://' is passed.""" 139 | with stdio_mgr() as (in_, out_, err_): 140 | run_cmdline_test( 141 | [ 142 | "convert", 143 | "plain", 144 | "-u", 145 | "sphobjinv.readthedocs.io/en/latest", 146 | str(scratch_path), 147 | ], 148 | expect=1, 149 | ) 150 | assert "file found but inventory could not be loaded" in err_.getvalue() 151 | 152 | def test_cli_json_export_import( 153 | self, res_cmp, scratch_path, misc_info, run_cmdline_test, sphinx_load_test 154 | ): 155 | """Confirm JSON sent to stdout from local source imports ok.""" 156 | inv_url = misc_info.remote_url.format("attrs") 157 | mod_path = scratch_path / (misc_info.FNames.MOD + misc_info.Extensions.CMP) 158 | 159 | with stdio_mgr() as (in_, out_, err_): 160 | run_cmdline_test(["convert", "json", "-u", inv_url, "-"]) 161 | 162 | data = out_.getvalue() 163 | 164 | with stdio_mgr(data) as (in_, out_, err_): 165 | run_cmdline_test(["convert", "zlib", "-", str(mod_path.resolve())]) 166 | 167 | assert Inventory(json.loads(data)) 168 | assert Inventory(mod_path) 169 | sphinx_load_test(mod_path) 170 | 171 | 172 | class TestSuggest: 173 | """Test nonlocal CLI suggest mode functionality.""" 174 | 175 | @pytest.mark.timeout(CLI_TEST_TIMEOUT * 4) 176 | def test_cli_suggest_from_url(self, misc_info, run_cmdline_test): 177 | """Confirm reST-only suggest output works from URL.""" 178 | with stdio_mgr() as (in_, out_, err_): 179 | run_cmdline_test( 180 | [ 181 | "suggest", 182 | "-u", 183 | misc_info.remote_url.format("attrs"), 184 | "instance", 185 | "-t", 186 | "50", 187 | ] 188 | ) 189 | assert p_instance_of.search(out_.getvalue()) 190 | 191 | @pytest.mark.parametrize( 192 | "url", 193 | [ 194 | "http://sphobjinv.readthedocs.io/en/v2.0/modules/", 195 | "http://sphobjinv.readthedocs.io/en/v2.0/modules/cmdline.html", 196 | ( 197 | "http://sphobjinv.readthedocs.io/en/v2.0/modules/" 198 | "cmdline.html#sphobjinv.cmdline.do_convert" 199 | ), 200 | ], 201 | ) 202 | @pytest.mark.timeout(CLI_TEST_TIMEOUT * 4) 203 | def test_cli_suggest_from_docset_urls(self, url, run_cmdline_test, check): 204 | """Confirm reST-only suggest output works from URLs within a docset.""" 205 | with stdio_mgr() as (in_, out_, err_): 206 | run_cmdline_test(["suggest", "-u", url, "inventory", "-at", "50"]) 207 | 208 | check.is_true(p_inventory.search(out_.getvalue())) 209 | check.is_in("LIKELY", err_.getvalue()) 210 | check.is_in( 211 | "(http://sphobjinv.readthedocs.io/en/v2.0/, None)", err_.getvalue() 212 | ) 213 | 214 | @pytest.mark.timeout(CLI_TEST_TIMEOUT * 4) 215 | def test_cli_suggest_from_typical_objinv_url(self, run_cmdline_test, check): 216 | """Confirm reST-only suggest works for direct objects.inv URL.""" 217 | url = "http://sphobjinv.readthedocs.io/en/v2.0/objects.inv" 218 | with stdio_mgr() as (in_, out_, err_): 219 | run_cmdline_test(["suggest", "-u", url, "inventory", "-at", "50"]) 220 | 221 | check.is_true(p_inventory.search(out_.getvalue())) 222 | check.is_in("PROBABLY", err_.getvalue()) 223 | check.is_in( 224 | "(http://sphobjinv.readthedocs.io/en/v2.0/, None)", err_.getvalue() 225 | ) 226 | 227 | @pytest.mark.timeout(CLI_TEST_TIMEOUT * 4) 228 | def test_cli_suggest_from_django_objinv_url(self, run_cmdline_test, check): 229 | """Confirm reST-only suggest works for direct objects.inv URL.""" 230 | url = "https://docs.djangoproject.com/en/4.1/_objects/" 231 | with stdio_mgr() as (in_, out_, err_): 232 | run_cmdline_test(["suggest", "-u", url, "route", "-a"]) 233 | 234 | check.is_true(re.search("DATABASE_ROUTERS", out_.getvalue())) 235 | check.is_in("Cannot infer intersphinx_mapping", err_.getvalue()) 236 | -------------------------------------------------------------------------------- /tests/test_fixture.py: -------------------------------------------------------------------------------- 1 | r"""*Trivial fixture tests for* ``sphobjinv``. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | **Author** 7 | Brian Skinn (brian.skinn@gmail.com) 8 | 9 | **File Created** 10 | 20 Mar 2019 11 | 12 | **Copyright** 13 | \(c) Brian Skinn 2016-2025 14 | 15 | **Source Repository** 16 | http://www.github.com/bskinn/sphobjinv 17 | 18 | **Documentation** 19 | https://sphobjinv.readthedocs.io/en/stable 20 | 21 | **License** 22 | Code: `MIT License`_ 23 | 24 | Docs & Docstrings: |CC BY 4.0|_ 25 | 26 | See |license_txt|_ for full license terms. 27 | 28 | **Members** 29 | 30 | """ 31 | 32 | import pytest 33 | 34 | pytestmark = pytest.mark.fixture 35 | 36 | 37 | def test_info_fixture(misc_info): 38 | """Confirm arbitrary member of misc_info fixture.""" 39 | assert True in misc_info.byte_lines 40 | 41 | 42 | def test_populate_scratch(misc_info, scratch_path, check): 43 | """Ensure the scratch_path fixture populates the scratch dir correctly.""" 44 | scr_base = misc_info.FNames.INIT.value 45 | 46 | for ext in [_.value for _ in misc_info.Extensions]: 47 | with check(msg=ext): 48 | assert (scratch_path / f"{scr_base}{ext}").is_file(), ext 49 | 50 | 51 | def test_sphinx_load(res_path, sphinx_load_test): 52 | """Confirm sphinx_load_test fixture works on known-good inventory.""" 53 | sphinx_load_test(res_path / "objects_attrs.inv") 54 | 55 | 56 | def test_cli_invoke(run_cmdline_test): 57 | """Confirm CLI test with no args exits ok. 58 | 59 | Should just print help and exit. 60 | 61 | """ 62 | run_cmdline_test([]) 63 | 64 | 65 | def test_decomp_comp_fixture(misc_info, decomp_cmp_test, scratch_path): 66 | """Test decomp_cmp_test works in 'identity' case. 67 | 68 | Basically is telling filecmp.cmp to compare a reference inventory file 69 | with itself. 70 | 71 | """ 72 | decomp_cmp_test( 73 | scratch_path / f"{misc_info.FNames.INIT.value}{misc_info.Extensions.DEC.value}" 74 | ) 75 | -------------------------------------------------------------------------------- /tests/test_flake8_ext.py: -------------------------------------------------------------------------------- 1 | r"""*Test(s) to ensure full loading of flake8 extensions*. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | **Author** 7 | Brian Skinn (brian.skinn@gmail.com) 8 | 9 | **File Created** 10 | 27 Apr 2019 11 | 12 | **Copyright** 13 | \(c) Brian Skinn 2016-2025 14 | 15 | **Source Repository** 16 | http://www.github.com/bskinn/sphobjinv 17 | 18 | **Documentation** 19 | https://sphobjinv.readthedocs.io/en/stable 20 | 21 | **License** 22 | Code: `MIT License`_ 23 | 24 | Docs & Docstrings: |CC BY 4.0|_ 25 | 26 | See |license_txt|_ for full license terms. 27 | 28 | **Members** 29 | 30 | """ 31 | 32 | import re 33 | import subprocess as sp # noqa: S404 34 | import sys 35 | from pathlib import Path 36 | 37 | import pytest 38 | 39 | pytestmark = [pytest.mark.flake8_ext] 40 | 41 | 42 | @pytest.fixture(scope="module", autouse=True) 43 | def skip_if_no_flake8_ext(pytestconfig): 44 | """Skip test if --flake8_ext not provided. 45 | 46 | Auto-applied to all functions in module. 47 | 48 | """ 49 | if not pytestconfig.getoption("--flake8_ext"): 50 | pytest.skip("'--flake8_ext' not specified") # pragma: no cover 51 | 52 | 53 | @pytest.mark.skipif( 54 | sys.version_info < (3, 6), 55 | reason="Some flake8 extensions require Python 3.6 or later", 56 | ) 57 | def test_flake8_version_output(check): 58 | """Confirm that all desired plugins actually report as loaded.""" 59 | p_pkgname = re.compile("^[0-9a-z_-]+", re.I) 60 | plugins = Path("requirements-flake8.txt").read_text().splitlines()[1:] 61 | plugins = [p_pkgname.search(p).group(0) for p in plugins] 62 | 63 | # This is fragile if anything ends up not having a prefix that needs 64 | # stripping 65 | plugins = [p.partition("flake8-")[-1] for p in plugins] 66 | 67 | flake8_ver_output = sp.check_output( # noqa: S607,S603 68 | ["flake8", "--version"], universal_newlines=True 69 | ) # noqa: S607,S603 70 | 71 | for p in plugins: 72 | with check(msg=p): 73 | assert p in flake8_ver_output.replace("_", "-").replace("\n", "") 74 | -------------------------------------------------------------------------------- /tests/test_intersphinx.py: -------------------------------------------------------------------------------- 1 | r"""*Tests for intersphinx-related functionality for* ``sphobjinv``. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | **Author** 7 | Brian Skinn (brian.skinn@gmail.com) 8 | 9 | **File Created** 10 | 21 Jun 2022 11 | 12 | **Copyright** 13 | \(c) Brian Skinn 2016-2025 14 | 15 | **Source Repository** 16 | http://www.github.com/bskinn/sphobjinv 17 | 18 | **Documentation** 19 | https://sphobjinv.readthedocs.io/en/stable 20 | 21 | **License** 22 | Code: `MIT License`_ 23 | 24 | Docs & Docstrings: |CC BY 4.0|_ 25 | 26 | See |license_txt|_ for full license terms. 27 | 28 | **Members** 29 | 30 | """ 31 | 32 | import pytest 33 | 34 | import sphobjinv.cli.suggest as soi_cli_suggest 35 | 36 | 37 | pytestmark = [pytest.mark.intersphinx, pytest.mark.local] 38 | 39 | 40 | @pytest.mark.parametrize( 41 | ("uri", "trimmed", "with_scheme"), 42 | [ 43 | ("cli/implementation/parser.html#$", "cli/implementation/parser.html", False), 44 | ( 45 | ( 46 | "https://sphobjinv.readthedocs.io/en/stable/api/" 47 | "enum.html#sphobjinv.enum.HeaderFields" 48 | ), 49 | "//sphobjinv.readthedocs.io/en/stable/api/enum.html", 50 | False, 51 | ), 52 | ( 53 | ( 54 | "https://sphobjinv.readthedocs.io/en/stable/api/" 55 | "enum.html#sphobjinv.enum.HeaderFields" 56 | ), 57 | "https://sphobjinv.readthedocs.io/en/stable/api/enum.html", 58 | True, 59 | ), 60 | ], 61 | ) 62 | def test_strip_netloc_path(uri, trimmed, with_scheme): 63 | """Confirm that object URI trimming is working.""" 64 | assert trimmed == soi_cli_suggest._strip_url_to_netloc_path( 65 | uri, with_scheme=with_scheme 66 | ) 67 | 68 | 69 | @pytest.mark.parametrize( 70 | ("url", "trimmed"), 71 | [ 72 | ( 73 | "https://sphobjinv.readthedocs.io/en/latest/objects.inv", 74 | "https://sphobjinv.readthedocs.io/en/latest/", 75 | ) 76 | ], 77 | ) 78 | def test_extract_objinv_url_base(url, trimmed): 79 | """Confirm that inventory URL trimming is working.""" 80 | assert trimmed == soi_cli_suggest.extract_objectsinv_url_base(url) 81 | -------------------------------------------------------------------------------- /tests/test_valid_objects.py: -------------------------------------------------------------------------------- 1 | r"""*Valid/invalid object data tests for* ``sphobjinv``. 2 | 3 | ``sphobjinv`` is a toolkit for manipulation and inspection of 4 | Sphinx |objects.inv| files. 5 | 6 | **Author** 7 | Brian Skinn (brian.skinn@gmail.com) 8 | 9 | **File Created** 10 | 13 Feb 2021 11 | 12 | **Copyright** 13 | \(c) Brian Skinn 2016-2025 14 | 15 | **Source Repository** 16 | http://www.github.com/bskinn/sphobjinv 17 | 18 | **Documentation** 19 | https://sphobjinv.readthedocs.io/en/stable 20 | 21 | **License** 22 | Code: `MIT License`_ 23 | 24 | Docs & Docstrings: |CC BY 4.0|_ 25 | 26 | See |license_txt|_ for full license terms. 27 | 28 | **Members** 29 | 30 | """ 31 | 32 | import os.path as osp 33 | import zlib 34 | from io import BytesIO 35 | 36 | import pytest 37 | import sphinx 38 | from sphinx.util.inventory import InventoryFile as IFile 39 | 40 | import sphobjinv as soi 41 | 42 | 43 | @pytest.fixture(autouse=True) 44 | def skip_on_sphinx_version(sphinx_version): 45 | """Trigger test skip if Sphinx version is too low. 46 | 47 | Changes to the Sphinx InventoryFile regex &c. cause older 48 | versions of Sphinx to have different behavior. 49 | 50 | This skip *should* only trigger during the tox matrix of 51 | environments with various old dependency versions. 52 | 53 | """ 54 | if sphinx_version < (3, 3, 0): # pragma: no cover 55 | pytest.skip("Sphinx version too low") 56 | 57 | 58 | # Once DataObjStr instance validation is in place, this will probably 59 | # be a good place to use hypothesis 60 | @pytest.mark.parametrize( 61 | ("name", "domain", "role", "prio", "uri", "dispname"), 62 | [ 63 | ("foo", "py", "data", 1, "quux.html#$", "-"), # Priorities 64 | ("foo", "py", "data", 0, "quux.html#$", "-"), 65 | ("foo", "py", "data", -1, "quux.html#$", "-"), 66 | ("foo", "py", "data", -1235778, "quux.html#$", "-"), 67 | ("foo", "py", "data", 2214888, "quux.html#$", "-"), 68 | ("foo bar", "std", "term", 1, "quux.html#$", "-"), # Space in name 69 | ("foo\tbar", "std", "term", 1, "quux.html#$", "-"), # Valid but discouraged 70 | ("Index Page", "std", "doc", 1, "index.html", "-"), 71 | ("Index Page", "std", "doc", 1, "index.html", "Index Page Thing"), 72 | ("Index Page", "std", "doc", 1, "index.html", "Index\tPage\tThing"), 73 | ("Index Page", "std", "doc", 1, "", "-"), # Zero-length uri 74 | ("Index # Page", "std", "doc", 1, "index.html", "-"), # Symbol in name 75 | ("Thing \u33a4", "std", "ref", 1, "index.html#$", "-"), # Unicode in name 76 | ("Thing One", "std", "ref", 1, "index.html#$", "\u33a4"), # Unicode in dispname 77 | ("foo", "py", "da:ta", 1, "data.html#$", "-"), # Colon in role (used in Sphinx) 78 | ("foo", "py$", "data", 1, "data.html#$", "-"), # Valid but discouraged 79 | ("foo", "py\u33a4", "data", 1, "data.html#$", "-"), # Valid but discouraged 80 | ("foo", "py", "data$", 1, "data.html#$", "-"), # Valid but discouraged 81 | ("foo", "py", "data\u33a4", 1, "data.html#$", "-"), # Valid but discouraged 82 | ("foo", "py", "data", 1, "data/\u33a4.html#$", "-"), # Valid but discouraged 83 | (" foo", "py", "data", 1, "data.html#$", "-"), # Valid but discouraged 84 | # Colon in domain (invalid but undetectable) 85 | ("foo", "p:y", "data", 1, "data.html#$", "-"), 86 | ], 87 | ) 88 | def test_dataobjstr_valid_objects( 89 | misc_info, sphinx_ifile_data_count, name, domain, role, prio, uri, dispname 90 | ): 91 | """Run sphobjinv/sphinx comparison on specific object data lines.""" 92 | dos = soi.DataObjStr( 93 | name=name, 94 | domain=domain, 95 | role=role, 96 | priority=str(prio), 97 | uri=uri, 98 | dispname=dispname, 99 | ) 100 | 101 | assert dos 102 | 103 | inv = soi.Inventory() 104 | inv.project = "Foo" 105 | inv.version = "1.0" 106 | inv.objects.append( 107 | soi.DataObjStr( 108 | name="bar", domain="py", role="data", priority="1", uri="$", dispname="-" 109 | ) 110 | ) 111 | inv.objects.append(dos) 112 | 113 | df = inv.data_file(contract=True) 114 | 115 | ifile_data = IFile.load(BytesIO(soi.compress(df)), "", osp.join) 116 | 117 | ifile_count = sphinx_ifile_data_count(ifile_data) 118 | 119 | assert inv.count == ifile_count 120 | 121 | domrole = "{dos.domain}:{dos.role}".format(dos=dos) 122 | 123 | assert domrole in ifile_data 124 | assert dos.name in ifile_data[domrole] 125 | 126 | 127 | @pytest.mark.parametrize( 128 | ("name", "domain", "role", "prio", "uri", "dispname"), 129 | [ 130 | ("", "std", "doc", 1, "index.html", "-"), # Missing name 131 | ("foo ", "py", "data", 1, "data.html#$", "-"), # Name w/trailing space 132 | ("# Index Page", "std", "doc", 1, "index.html", "-"), # '#' @ name start 133 | ("X Y Z 0 foo", "std", "doc", 1, "index.html", "-"), # Int in name 134 | ("foo", "py thon", "data", 1, "data.html#$", "-"), # Space in domain 135 | ("foo", "", "data", 1, "data.html#$", "-"), # Missing domain 136 | ("foo", "py", "da ta", 1, "data.html#$", "-"), # Space in role 137 | ("foo", "py", "", 1, "data.html#$", "-"), # Missing role 138 | ("foo", "py", "data", 0.5, "data.html#$", "-"), # Non-integer prio 139 | ("foo", "py", "data", "", "data.html#$", "-"), # Missing prio 140 | ("foo", "py", "data", "quux", "data.html#$", "-"), # Non-numeric prio 141 | ("Index Page", "std", "doc", 1, "index.html", ""), # Missing dispname 142 | ("Index Page", "std", "doc", 1, "", ""), # Missing uri & dispname 143 | ], 144 | ) 145 | def test_dataobjstr_invalid_objects( 146 | misc_info, sphinx_ifile_data_count, name, domain, role, prio, uri, dispname 147 | ): 148 | """Run sphobjinv/sphinx comparison on specific invalid data lines.""" 149 | with pytest.raises((AssertionError, zlib.error)): 150 | test_dataobjstr_valid_objects( 151 | misc_info, sphinx_ifile_data_count, name, domain, role, prio, uri, dispname 152 | ) 153 | 154 | 155 | def int_to_latin_1(val): 156 | """Provide the latin-1 string equivalent of an 8-bit int.""" 157 | return bytes((val,)).decode("latin-1") 158 | 159 | 160 | def latin_1_id(val): 161 | """Provide the value-and-character string for a latin-1 int value.""" 162 | return str(val) + "_" + int_to_latin_1(val) 163 | 164 | 165 | @pytest.mark.parametrize("leadint", range(255), ids=latin_1_id) 166 | def test_name_lead_chars(misc_info, sphinx_ifile_data_count, leadint): 167 | """Screen for valid/invalid first characters.""" 168 | name = int_to_latin_1(leadint) + " foo" 169 | 170 | # For Sphinx < 8.2 expect only two fail cases, newline and '#' 171 | if leadint in (10, 35): 172 | pytest.xfail("Known invalid name lead char") 173 | 174 | # Sphinx >= 8.2 uses splitlines(), which strips more line boundary characters. 175 | # See https://github.com/bskinn/sphobjinv/issues/314 176 | if sphinx.version_info >= (8, 2) and leadint in (11, 12, 13, 28, 29, 30, 133): 177 | pytest.xfail( 178 | "Known invalid name lead char for Sphinx >= 8.2" 179 | ) # pragma: no cover 180 | 181 | test_dataobjstr_valid_objects( 182 | misc_info, 183 | sphinx_ifile_data_count, 184 | name=name, 185 | domain="py", 186 | role="data", 187 | prio=1, 188 | uri="data.html#$", 189 | dispname="-", 190 | ) 191 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion=2.0 3 | isolated_build=True 4 | envlist= 5 | # Test all Python versions on latest lib versions 6 | py3{9,10,11,12,13}-sphx_latest-attrs_latest-jsch_latest 7 | # Test leading Python version on current in-repo dev lib versions 8 | py313-sphx_dev-attrs_dev-jsch_dev 9 | # Scan across Sphinx versions 10 | py313-sphx_{1_6_x,1_x,2_x,4_x,5_x,6_x,7_x,dev}-attrs_latest-jsch_latest 11 | # sphx_3_x is incompatible with py310 due to a typing import. Test on py39 instead. 12 | py39-sphx_3_x-attrs_latest-jsch_latest 13 | # Scan attrs versions 14 | py313-sphx_latest-attrs_{19_2,19_3,20_3,21_3,22_2,23_2,24_3,dev}-jsch_latest 15 | # Scan jsonschema versions 16 | py313-sphx_latest-attrs_latest-jsch_{3_0,3_x,4_0,4_8,4_14,4_20,dev} 17 | # Earliest supported Python and lib versions all together 18 | py39-sphx_1_6_x-attrs_19_2-jsch_3_0 19 | # Spot matrix of early Python, Sphinx, attrs versions 20 | py3{9,10}-sphx_{1,2}_x-attrs_{19,20}_2-jsch_latest 21 | # Test the specific Sphinx threshold cases where behavior changed 22 | py312-sphx_{2_3_1,2_4_0,3_2_1,3_3_0,3_4_0,8_1_3,8_2_0}-attrs_latest-jsch_latest 23 | # Simple 'does the sdist install' check 24 | sdist_install 25 | # Lints 26 | flake8 27 | # Sphinx link check 28 | linkcheck 29 | 30 | [testenv] 31 | commands= 32 | python --version 33 | pip list 34 | # Want the tox *matrix* to ignore warnings since it's primarily 35 | # a compatibility check. The defaults for bare pytest enable -Werror 36 | pytest {posargs:--nonloc -Wignore} 37 | deps= 38 | sphx_1_6_x: sphinx<1.7 39 | sphx_1_x: sphinx<2 40 | sphx_2_x: sphinx<3 41 | sphx_3_x: sphinx<4 42 | sphx_4_x: sphinx<5 43 | sphx_5_x: sphinx<6 44 | sphx_6_x: sphinx<7 45 | sphx_7_x: sphinx<8 46 | sphx_2_3_1: sphinx==2.3.1 47 | sphx_2_4_0: sphinx==2.4.0 48 | sphx_3_2_1: sphinx==3.2.1 49 | sphx_3_3_0: sphinx==3.3.0 50 | sphx_3_4_0: sphinx==3.4.0 51 | sphx_8_1_3: sphinx==8.1.3 52 | sphx_8_2_0: sphinx==8.2.0 53 | sphx_latest: sphinx 54 | sphx_dev: git+https://github.com/sphinx-doc/sphinx 55 | 56 | attrs_19_2: attrs==19.2 57 | attrs_19_3: attrs==19.3 58 | attrs_20_3: attrs==20.3 59 | attrs_21_3: attrs==21.3 60 | attrs_22_2: attrs==22.2 61 | attrs_23_2: attrs==23.2 62 | attrs_24_3: attrs==24.3 63 | attrs_latest: attrs 64 | attrs_dev: git+https://github.com/python-attrs/attrs 65 | 66 | jsch_3_0: jsonschema==3.0 67 | jsch_3_x: jsonschema<4 68 | jsch_4_0: jsonschema<4.1 69 | jsch_4_8: jsonschema<4.9 70 | jsch_4_14: jsonschema<4.15 71 | jsch_4_20: jsonschema<4.21 72 | jsch_latest: jsonschema 73 | jsch_dev: git+https://github.com/Julian/jsonschema 74 | 75 | dictdiffer 76 | pytest>=4.4.0 77 | pytest-check>=1.1.2 78 | pytest-ordering 79 | pytest-retry 80 | pytest-timeout 81 | stdio-mgr>=1.0.1 82 | sphinx-issues 83 | sphinx-rtd-theme 84 | sphinxcontrib-programoutput 85 | 86 | [testenv:linux] 87 | platform=linux 88 | basepython= 89 | py313: python3.13 90 | py312: python3.12 91 | py311: python3.11 92 | py310: python3.10 93 | py39: python3.9 94 | 95 | [testenv:black] 96 | skip_install=True 97 | deps=black 98 | commands= 99 | black {posargs} . 100 | 101 | [testenv:flake8] 102 | skip_install=True 103 | deps=-rrequirements-flake8.txt 104 | commands= 105 | flake8 ./conftest.py src tests 106 | 107 | [testenv:flake8_noqa] 108 | skip_install=True 109 | deps=-rrequirements-flake8.txt 110 | commands= 111 | pip install flake8-noqa 112 | flake8 --color=never --exit-zero ./conftest.py tests src 113 | 114 | [testenv:interrogate] 115 | skip_install=True 116 | deps=interrogate 117 | commands= 118 | interrogate {posargs} conftest.py tests src 119 | 120 | [testenv:linkcheck] 121 | skip_install=True 122 | deps=-rrequirements-dev.txt 123 | allowlist_externals= 124 | make 125 | changedir=doc 126 | commands= 127 | make linkcheck 128 | 129 | [testenv:sdist_install] 130 | commands= 131 | python -Werror -c "import sphobjinv" 132 | deps= 133 | 134 | [pytest] 135 | markers = 136 | local: Tests not requiring Internet access 137 | nonloc: Tests requiring Internet access 138 | cli: Command-line interface tests 139 | api: Direct API tests 140 | intersphinx: Tests on intersphinx-related functionality 141 | fixture: Trivial tests for test suite fixtures 142 | testall: Tests that use *all* objects_xyz.inv files in tests/resource, if --testall is specified 143 | flake8_ext: Test checking that all desired plugins are active 144 | first: Inherited marker from `pytest-ordering` 145 | timeout: Inherited marker from `pytest-timeout` 146 | 147 | addopts = --strict-markers -rsxX -Werror 148 | 149 | norecursedirs = .* env* src *.egg dist build 150 | 151 | xfail_strict = True 152 | 153 | 154 | [flake8] 155 | exclude = 156 | src/sphobjinv/_vendored 157 | 158 | # W503: black formats binary operators to start of line 159 | # A005: Submodules are going to be shadowing builtins for the moment 160 | ignore = W503,A005 161 | show_source = True 162 | max_line_length = 88 163 | format = %(cyan)s%(path)s%(reset)s:%(yellow)s%(row)d%(reset)s:%(green)s%(col)d%(reset)s %(red)s(%(code)s)%(reset)s %(text)s 164 | rst-roles = 165 | attr, 166 | class, 167 | data, 168 | doc, 169 | exc, 170 | func, 171 | meth, 172 | mod, 173 | option, 174 | ref, 175 | rst-directives = 176 | doctest, 177 | versionadded, 178 | versionchanged, 179 | per_file_ignores = 180 | # D202: No-space-after-docstring is ugly when the first command is a class/def 181 | # S101: pytest uses asserts liberally 182 | # RST30x: linter can't know about substitutions/references in rst_epilog 183 | src/*: RST305,RST306 184 | tests/*: S101, RST305,RST306 185 | conftest.py: D202, S101, RST305,RST306 186 | # F401: MANY things imported but unused in __init__.py files 187 | src/sphobjinv/__init__.py: F401, RST305,RST306 188 | src/sphobjinv/cli/__init__.py: F401, RST305,RST306 189 | # PIE786: CLI uses 'except Exception:' as a catchall... to be changed, eventually 190 | src/sphobjinv/cli/*: PIE786, RST305,RST306 191 | 192 | #flake8-import-order 193 | import-order-style = smarkets 194 | application-import-names = sphobjinv 195 | --------------------------------------------------------------------------------