coverart-cli 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. coverart_cli-0.4.0/.coderabbit.yaml +38 -0
  2. coverart_cli-0.4.0/.editorconfig +17 -0
  3. coverart_cli-0.4.0/.github/CODEOWNERS +2 -0
  4. coverart_cli-0.4.0/.github/dependabot.yml +18 -0
  5. coverart_cli-0.4.0/.github/workflows/auto-enable-automerge.yml +23 -0
  6. coverart_cli-0.4.0/.github/workflows/ci.yml +84 -0
  7. coverart_cli-0.4.0/.github/workflows/codeql.yml +33 -0
  8. coverart_cli-0.4.0/.github/workflows/dependabot-automerge.yml +28 -0
  9. coverart_cli-0.4.0/.github/workflows/release-please.yml +20 -0
  10. coverart_cli-0.4.0/.github/workflows/release.yml +61 -0
  11. coverart_cli-0.4.0/.gitignore +47 -0
  12. coverart_cli-0.4.0/.pre-commit-config.yaml +18 -0
  13. coverart_cli-0.4.0/.release-please-manifest.json +3 -0
  14. coverart_cli-0.4.0/CHANGELOG.md +98 -0
  15. coverart_cli-0.4.0/LICENSE +21 -0
  16. coverart_cli-0.4.0/PKG-INFO +160 -0
  17. coverart_cli-0.4.0/README.md +128 -0
  18. coverart_cli-0.4.0/SECURITY.md +23 -0
  19. coverart_cli-0.4.0/docs/make_demo_report.py +97 -0
  20. coverart_cli-0.4.0/docs/screenshots/report-dark.png +0 -0
  21. coverart_cli-0.4.0/docs/screenshots/report-light.png +0 -0
  22. coverart_cli-0.4.0/pyproject.toml +65 -0
  23. coverart_cli-0.4.0/release-please-config.json +32 -0
  24. coverart_cli-0.4.0/src/coverart_cli/__init__.py +47 -0
  25. coverart_cli-0.4.0/src/coverart_cli/__main__.py +4 -0
  26. coverart_cli-0.4.0/src/coverart_cli/cli.py +257 -0
  27. coverart_cli-0.4.0/src/coverart_cli/config.py +91 -0
  28. coverart_cli-0.4.0/src/coverart_cli/core.py +320 -0
  29. coverart_cli-0.4.0/src/coverart_cli/providers/__init__.py +15 -0
  30. coverart_cli-0.4.0/src/coverart_cli/providers/base.py +73 -0
  31. coverart_cli-0.4.0/src/coverart_cli/providers/deezer.py +48 -0
  32. coverart_cli-0.4.0/src/coverart_cli/providers/itunes.py +59 -0
  33. coverart_cli-0.4.0/src/coverart_cli/providers/lastfm.py +72 -0
  34. coverart_cli-0.4.0/src/coverart_cli/providers/musicbrainz.py +78 -0
  35. coverart_cli-0.4.0/src/coverart_cli/py.typed +0 -0
  36. coverart_cli-0.4.0/src/coverart_cli/report.py +180 -0
  37. coverart_cli-0.4.0/src/coverart_cli/tagging.py +230 -0
  38. coverart_cli-0.4.0/src/coverart_cli/templates/report.html +450 -0
  39. coverart_cli-0.4.0/tests/__init__.py +0 -0
  40. coverart_cli-0.4.0/tests/test_config.py +47 -0
  41. coverart_cli-0.4.0/tests/test_integration.py +225 -0
  42. coverart_cli-0.4.0/tests/test_providers.py +62 -0
  43. coverart_cli-0.4.0/tests/test_report.py +138 -0
  44. coverart_cli-0.4.0/tests/test_tagging.py +57 -0
@@ -0,0 +1,38 @@
1
+ # CodeRabbit configuration for coverart-cli
2
+ # Docs: https://docs.coderabbit.ai/getting-started/configure-coderabbit
3
+
4
+ language: en-US
5
+ early_access: false
6
+
7
+ reviews:
8
+ profile: chill # less nitpicky; chill | assertive
9
+ request_changes_workflow: false
10
+ high_level_summary: true
11
+ poem: false
12
+ review_status: true
13
+ collapse_walkthrough: true
14
+ auto_review:
15
+ enabled: true
16
+ drafts: false
17
+ base_branches: [main]
18
+ path_filters:
19
+ - "!docs/screenshots/**" # don't review screenshots
20
+ - "!**/*.md" # docs don't need code review
21
+ - "!CHANGELOG.md"
22
+ - "!.release-please-manifest.json"
23
+ path_instructions:
24
+ - path: "src/coverart_cli/providers/**"
25
+ instructions: >
26
+ Providers must inherit CoverProvider and call _http_get with retries.
27
+ Reject placeholder images. Respect MIN_COVER_BYTES.
28
+ - path: "src/coverart_cli/templates/**"
29
+ instructions: >
30
+ HTML templates must use safe DOM construction
31
+ (createElement / textContent / replaceChildren),
32
+ never innerHTML with non-escaped JSON.
33
+ - path: "tests/**"
34
+ instructions: >
35
+ Tests should be deterministic — use the FakeProvider, no real network.
36
+
37
+ chat:
38
+ auto_reply: true
@@ -0,0 +1,17 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ trim_trailing_whitespace = true
8
+ indent_style = space
9
+
10
+ [*.py]
11
+ indent_size = 4
12
+
13
+ [*.{json,toml,yml,yaml,html,css,js}]
14
+ indent_size = 2
15
+
16
+ [Makefile]
17
+ indent_style = tab
@@ -0,0 +1,2 @@
1
+ # Default reviewer for everything.
2
+ * @WildDragonKing
@@ -0,0 +1,18 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ open-pull-requests-limit: 5
8
+ groups:
9
+ gh-actions:
10
+ patterns: ["*"]
11
+ - package-ecosystem: "pip"
12
+ directory: "/"
13
+ schedule:
14
+ interval: "weekly"
15
+ open-pull-requests-limit: 5
16
+ groups:
17
+ python-deps:
18
+ patterns: ["*"]
@@ -0,0 +1,23 @@
1
+ # Auto-enable squash auto-merge on every non-draft PR.
2
+ # Branch protection still gates the merge — it only fires once all required
3
+ # checks (test matrix, CodeQL, smoke, CodeRabbit) pass.
4
+ name: Enable auto-merge
5
+
6
+ on:
7
+ pull_request_target:
8
+ types: [opened, reopened, ready_for_review]
9
+
10
+ permissions:
11
+ contents: write
12
+ pull-requests: write
13
+
14
+ jobs:
15
+ enable:
16
+ if: github.event.pull_request.draft == false
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - name: Enable squash auto-merge
20
+ env:
21
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22
+ PR_URL: ${{ github.event.pull_request.html_url }}
23
+ run: gh pr merge --auto --squash "$PR_URL"
@@ -0,0 +1,84 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ test:
14
+ name: test (py${{ matrix.python-version }})
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ python-version: ["3.11", "3.12", "3.13"]
20
+ steps:
21
+ - uses: actions/checkout@v6
22
+ - name: Set up Python ${{ matrix.python-version }}
23
+ uses: actions/setup-python@v6
24
+ with:
25
+ python-version: ${{ matrix.python-version }}
26
+ cache: pip
27
+ cache-dependency-path: pyproject.toml
28
+ - name: Install
29
+ run: |
30
+ python -m pip install --upgrade pip
31
+ pip install -e ".[dev]"
32
+ - name: Lint
33
+ run: ruff check .
34
+ - name: Unit + integration tests
35
+ run: pytest --cov=coverart_cli --cov-report=term-missing
36
+
37
+ smoke:
38
+ name: smoke (install wheel + run end-to-end)
39
+ needs: test
40
+ runs-on: ubuntu-latest
41
+ steps:
42
+ - uses: actions/checkout@v6
43
+ - uses: actions/setup-python@v6
44
+ with:
45
+ python-version: "3.12"
46
+ cache: pip
47
+ cache-dependency-path: pyproject.toml
48
+ - name: Build wheel
49
+ run: |
50
+ python -m pip install --upgrade pip build
51
+ python -m build --wheel
52
+ - name: Install wheel into a fresh venv
53
+ run: |
54
+ python -m venv /tmp/smoke-venv
55
+ /tmp/smoke-venv/bin/pip install --upgrade pip
56
+ /tmp/smoke-venv/bin/pip install dist/*.whl
57
+ - name: Verify CLI is installed
58
+ run: |
59
+ /tmp/smoke-venv/bin/coverart --version
60
+ /tmp/smoke-venv/bin/coverart --help > /dev/null
61
+ - name: Build a fixture library
62
+ # Use the smoke-venv python so we can import mutagen (installed as a dep of coverart-cli).
63
+ run: |
64
+ /tmp/smoke-venv/bin/python <<'PY'
65
+ from pathlib import Path
66
+ from mutagen.id3 import ID3, TPE1, TPE2, TALB, TIT2
67
+ p = Path("/tmp/fixture-lib/Pink Floyd/The Wall/01.mp3")
68
+ p.parent.mkdir(parents=True, exist_ok=True)
69
+ p.write_bytes(b"\xff\xfb\x90\x00" + b"\x00" * 1000)
70
+ tags = ID3()
71
+ tags.add(TPE1(encoding=3, text=["Pink Floyd"]))
72
+ tags.add(TPE2(encoding=3, text=["Pink Floyd"]))
73
+ tags.add(TALB(encoding=3, text=["The Wall"]))
74
+ tags.add(TIT2(encoding=3, text=["Mother"]))
75
+ tags.save(str(p))
76
+ PY
77
+ - name: Run end-to-end
78
+ run: |
79
+ /tmp/smoke-venv/bin/coverart /tmp/fixture-lib -v
80
+ test -f "/tmp/fixture-lib/Pink Floyd/The Wall/cover.jpg" && echo "✓ cover.jpg written"
81
+ - name: Generate HTML report against fixture
82
+ run: |
83
+ /tmp/smoke-venv/bin/coverart /tmp/fixture-lib --report-only --report-html /tmp/report.html
84
+ test -s /tmp/report.html && echo "✓ report written ($(wc -c </tmp/report.html) bytes)"
@@ -0,0 +1,33 @@
1
+ name: CodeQL
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ schedule:
9
+ # Weekly extra scan to catch newly disclosed CVEs in dependencies.
10
+ - cron: "13 7 * * 1"
11
+
12
+ permissions:
13
+ contents: read
14
+ security-events: write
15
+ actions: read
16
+
17
+ jobs:
18
+ analyze:
19
+ name: Analyze (python)
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v6
23
+ - name: Initialize CodeQL
24
+ uses: github/codeql-action/init@v4
25
+ with:
26
+ languages: python
27
+ queries: security-extended,security-and-quality
28
+ - name: Autobuild
29
+ uses: github/codeql-action/autobuild@v4
30
+ - name: Perform analysis
31
+ uses: github/codeql-action/analyze@v4
32
+ with:
33
+ category: "/language:python"
@@ -0,0 +1,28 @@
1
+ # Auto-merge Dependabot PRs once all required checks pass.
2
+ # Branch protection requires the status checks (test matrix + CodeQL + smoke);
3
+ # this workflow just flips the auto-merge bit, GitHub does the rest.
4
+ name: Dependabot auto-merge
5
+
6
+ on:
7
+ pull_request_target:
8
+ types: [opened, reopened, synchronize, ready_for_review]
9
+
10
+ permissions:
11
+ contents: write
12
+ pull-requests: write
13
+
14
+ jobs:
15
+ automerge:
16
+ if: github.actor == 'dependabot[bot]'
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - name: Fetch Dependabot metadata
20
+ id: meta
21
+ uses: dependabot/fetch-metadata@v2
22
+ with:
23
+ github-token: ${{ secrets.GITHUB_TOKEN }}
24
+ - name: Enable auto-merge (squash)
25
+ env:
26
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
27
+ PR_URL: ${{ github.event.pull_request.html_url }}
28
+ run: gh pr merge --auto --squash "$PR_URL"
@@ -0,0 +1,20 @@
1
+ name: Release-please
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ permissions:
8
+ contents: write
9
+ pull-requests: write
10
+ id-token: write
11
+
12
+ jobs:
13
+ release-please:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: googleapis/release-please-action@v4
17
+ id: release
18
+ with:
19
+ config-file: release-please-config.json
20
+ manifest-file: .release-please-manifest.json
@@ -0,0 +1,61 @@
1
+ name: Release to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ permissions:
8
+ contents: read
9
+ id-token: write # PyPI Trusted Publishing (OIDC)
10
+
11
+ jobs:
12
+ # Gate: must pass tests before we ever build a release artifact.
13
+ test:
14
+ name: test (py${{ matrix.python-version }})
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ fail-fast: true
18
+ matrix:
19
+ python-version: ["3.11", "3.12", "3.13"]
20
+ steps:
21
+ - uses: actions/checkout@v6
22
+ - uses: actions/setup-python@v6
23
+ with:
24
+ python-version: ${{ matrix.python-version }}
25
+ cache: pip
26
+ - run: |
27
+ python -m pip install --upgrade pip
28
+ pip install -e ".[dev]"
29
+ - run: ruff check .
30
+ - run: pytest
31
+
32
+ build:
33
+ needs: test
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - uses: actions/checkout@v6
37
+ - uses: actions/setup-python@v6
38
+ with:
39
+ python-version: "3.12"
40
+ - run: python -m pip install --upgrade pip build
41
+ - run: python -m build
42
+ - uses: actions/upload-artifact@v7
43
+ with:
44
+ name: dist
45
+ path: dist/
46
+
47
+ publish:
48
+ needs: build
49
+ runs-on: ubuntu-latest
50
+ environment:
51
+ name: pypi
52
+ url: https://pypi.org/p/coverart-cli
53
+ steps:
54
+ - uses: actions/download-artifact@v8
55
+ with:
56
+ name: dist
57
+ path: dist/
58
+ # Pinned to a release tag; bump deliberately, signed by maintainer.
59
+ - uses: pypa/gh-action-pypi-publish@release/v1
60
+ with:
61
+ attestations: true
@@ -0,0 +1,47 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ dist/
9
+ *.egg-info/
10
+ *.egg
11
+ .eggs/
12
+
13
+ # venv
14
+ .venv/
15
+ venv/
16
+ env/
17
+ ENV/
18
+
19
+ # Test / coverage
20
+ .pytest_cache/
21
+ .coverage
22
+ .coverage.*
23
+ htmlcov/
24
+ .tox/
25
+ .ruff_cache/
26
+ .mypy_cache/
27
+
28
+ # IDE
29
+ .vscode/
30
+ .idea/
31
+ *.swp
32
+ *~
33
+
34
+ # OS
35
+ .DS_Store
36
+ Thumbs.db
37
+
38
+ # Local
39
+ *.local
40
+ .env
41
+ .envrc
42
+
43
+ # Project output
44
+ missing-covers.csv
45
+ *.log
46
+ docs/screenshots/demo-report.html
47
+ docs/screenshots/*.tmp
@@ -0,0 +1,18 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v5.0.0
4
+ hooks:
5
+ - id: check-yaml
6
+ - id: check-toml
7
+ - id: check-added-large-files
8
+ args: [--maxkb=2048]
9
+ - id: end-of-file-fixer
10
+ - id: trailing-whitespace
11
+ - id: check-merge-conflict
12
+ - id: mixed-line-ending
13
+ - repo: https://github.com/astral-sh/ruff-pre-commit
14
+ rev: v0.7.4
15
+ hooks:
16
+ - id: ruff
17
+ args: [--fix]
18
+ - id: ruff-format
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.4.0"
3
+ }
@@ -0,0 +1,98 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.4.0](https://github.com/WildDragonKing/coverart-cli/compare/v0.3.0...v0.4.0) (2026-05-17)
9
+
10
+
11
+ ### Features
12
+
13
+ * add release-please for merge-driven version bumps ([#9](https://github.com/WildDragonKing/coverart-cli/issues/9)) ([28ec876](https://github.com/WildDragonKing/coverart-cli/commit/28ec8768d70e22c50ebd58e6ca170fb65393eb6c))
14
+ * auto-enable squash auto-merge + CodeRabbit config ([#11](https://github.com/WildDragonKing/coverart-cli/issues/11)) ([3756926](https://github.com/WildDragonKing/coverart-cli/commit/37569263e397e61d34d3cd865f4935c94432e3a9))
15
+
16
+
17
+ ### Bug Fixes
18
+
19
+ * drop failing approve step from Dependabot auto-merge ([#12](https://github.com/WildDragonKing/coverart-cli/issues/12)) ([c2f984d](https://github.com/WildDragonKing/coverart-cli/commit/c2f984d7417ee811bc80e90ec9b3d2f587ffddb2))
20
+
21
+ ## [0.3.0] - 2026-05-17
22
+
23
+ ### Added
24
+
25
+ - **TOML config file** support — `~/.config/coverart-cli/config.toml`,
26
+ `./coverart.toml`, or `--config PATH`. CLI flags still override the config.
27
+ - **`py.typed`** marker file for PEP 561 — type checkers can now see our types.
28
+ - **Public library API** explicitly defined via `__all__` in
29
+ `coverart_cli/__init__.py`. You can now do
30
+ `from coverart_cli import RunOptions, run, ITunesProvider`.
31
+ - **`.editorconfig`** for cross-IDE consistency.
32
+ - **`.pre-commit-config.yaml`** with ruff + ruff-format + standard checks.
33
+ - **`SECURITY.md`** with private vulnerability reporting link.
34
+ - **PyPI release workflow** (`.github/workflows/release.yml`) — push a
35
+ `v*` tag and the package is built and uploaded via PyPI Trusted Publishing.
36
+ Includes a **`needs: test`** gate so a red main can't ship a release.
37
+ - **Dependabot** monthly checks for GitHub Actions and pip.
38
+
39
+ ### Changed
40
+
41
+ - Minimum Python is now **3.11** (was 3.10) — needed for stdlib `tomllib`.
42
+ - Metadata now uses **PEP 639 SPDX** (`license = "MIT"` + `license-files`),
43
+ no more deprecated `License ::` classifier.
44
+ - Description and CLI help text now reflect the actual provider and format
45
+ coverage (4 providers, 5 formats).
46
+ - All providers share a single `_default_user_agent()` derived from
47
+ `__version__` — no more stale `0.1.0` strings hard-coded in defaults.
48
+ - `iTunesProvider` and `DeezerProvider` correctly accept `user_agent=`
49
+ (was silently ignored, broke library usage).
50
+ - `MusicBrainzProvider` dead `release` fallback branch removed.
51
+ - Size threshold for accepted covers unified across providers via
52
+ `tagging.MIN_COVER_BYTES`.
53
+ - `scan_library` path-traversal logic simplified to `relative_to` +
54
+ `ValueError` guard.
55
+
56
+ ## [0.2.0] - 2026-05-17
57
+
58
+ ### Added
59
+
60
+ - **iTunes provider** (Apple Music public search) — no API key required.
61
+ - **Deezer provider** — no API key required.
62
+ - **FLAC, Ogg Vorbis, and Opus** file format support (read + embed).
63
+ - HTML report shows iTunes / Deezer / Embedded filter chips and badges.
64
+ - Demo report generator (`docs/make_demo_report.py`).
65
+ - **`--min-bytes N`**: upgrade existing covers smaller than N bytes. Default 0
66
+ (keep all existing). Setting e.g. `--min-bytes 50000` causes the tool to
67
+ re-fetch covers for any album whose current artwork is below 50 KB.
68
+ - **`--replace-smaller`**: when the freshly fetched cover is larger than the
69
+ existing one, replace it. Default is to keep the larger existing cover when
70
+ both qualify.
71
+ - `existing_embedded_size()` helper in `coverart_cli.tagging` for inspecting
72
+ in-tag artwork sizes across all five formats.
73
+
74
+ ### Changed
75
+
76
+ - Last.fm is now optional — if no key is supplied, the tool runs with the three
77
+ free providers instead of failing.
78
+ - README rewritten — significantly shorter, with screenshots and an
79
+ alternatives table.
80
+
81
+ ## [0.1.0] - 2026-05-17
82
+
83
+ ### Added
84
+
85
+ - Initial release.
86
+ - Tag-first album metadata reading via `mutagen`, directory-name fallback.
87
+ - Last.fm `album.getinfo` provider with placeholder-image rejection.
88
+ - MusicBrainz + Cover Art Archive fallback provider with rate limiting.
89
+ - Embed into MP3 (`ID3 APIC`) and M4A (`covr` atom).
90
+ - Sidecar `cover.jpg` / `cover.png` writing with magic-byte MIME detection.
91
+ - CLI: `--lastfm-key`, `--no-lastfm`, `--no-musicbrainz`, `--no-embed`,
92
+ `--no-sidecar`, `--dry-run`, `--missing-csv`, `-v`/`-vv`.
93
+ - **HTML coverage report**: `--report-html PATH`, `--report-only`, `--no-thumbs`.
94
+ Self-contained single-file output with embedded thumbnails, dark/light theme,
95
+ searchable + filterable album grid. Editorial/liner-notes aesthetic with
96
+ Fraunces, Geist, and JetBrains Mono typography.
97
+ - Pytest smoke suite (25 tests) for tagging, provider helpers, and report module.
98
+ - GitHub Actions CI for lint + tests on Python 3.10–3.13.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 WildDragonKing
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.