badgeshield 0.1.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 (69) hide show
  1. badgeshield-0.1.0/.github/workflows/release.yml +116 -0
  2. badgeshield-0.1.0/.gitignore +190 -0
  3. badgeshield-0.1.0/CLAUDE.md +86 -0
  4. badgeshield-0.1.0/LICENSE.txt +21 -0
  5. badgeshield-0.1.0/PKG-INFO +371 -0
  6. badgeshield-0.1.0/README.md +339 -0
  7. badgeshield-0.1.0/docs/css/extra.css +289 -0
  8. badgeshield-0.1.0/docs/faq.md +227 -0
  9. badgeshield-0.1.0/docs/getting-started/cli_usage.md +319 -0
  10. badgeshield-0.1.0/docs/getting-started/usage.md +276 -0
  11. badgeshield-0.1.0/docs/helpful_links.md +1 -0
  12. badgeshield-0.1.0/docs/img/badgeshield.png +0 -0
  13. badgeshield-0.1.0/docs/img/badgeshield_192x192.png +0 -0
  14. badgeshield-0.1.0/docs/img/favicon/favicon-16x16.png +0 -0
  15. badgeshield-0.1.0/docs/img/favicon/favicon-32x32.png +0 -0
  16. badgeshield-0.1.0/docs/img/favicon/favicon.ico +0 -0
  17. badgeshield-0.1.0/docs/img/logo.png +0 -0
  18. badgeshield-0.1.0/docs/includes/abbreviations.md +0 -0
  19. badgeshield-0.1.0/docs/index.md +143 -0
  20. badgeshield-0.1.0/docs/installation.md +53 -0
  21. badgeshield-0.1.0/docs/overrides/PLACEHOLDER +0 -0
  22. badgeshield-0.1.0/docs/reference/batch_generator.md +2 -0
  23. badgeshield-0.1.0/docs/reference/batch_generator_cli.md +3 -0
  24. badgeshield-0.1.0/docs/reference/coverage.md +2 -0
  25. badgeshield-0.1.0/docs/reference/utils.md +1 -0
  26. badgeshield-0.1.0/mkdocs.yml +221 -0
  27. badgeshield-0.1.0/pyproject.toml +55 -0
  28. badgeshield-0.1.0/requirements.txt +4 -0
  29. badgeshield-0.1.0/setup.cfg +4 -0
  30. badgeshield-0.1.0/setup.py +45 -0
  31. badgeshield-0.1.0/src/badgeshield/__init__.py +17 -0
  32. badgeshield-0.1.0/src/badgeshield/__main__.py +3 -0
  33. badgeshield-0.1.0/src/badgeshield/_version.py +34 -0
  34. badgeshield-0.1.0/src/badgeshield/badge_generator.py +990 -0
  35. badgeshield-0.1.0/src/badgeshield/coverage.py +63 -0
  36. badgeshield-0.1.0/src/badgeshield/fonts/DejaVuSans.ttf +0 -0
  37. badgeshield-0.1.0/src/badgeshield/generate_badge_cli.py +362 -0
  38. badgeshield-0.1.0/src/badgeshield/templates/banner.svg +35 -0
  39. badgeshield-0.1.0/src/badgeshield/templates/circle.svg +50 -0
  40. badgeshield-0.1.0/src/badgeshield/templates/circle_frame.svg +48 -0
  41. badgeshield-0.1.0/src/badgeshield/templates/frames/frame1.png +0 -0
  42. badgeshield-0.1.0/src/badgeshield/templates/frames/frame10.png +0 -0
  43. badgeshield-0.1.0/src/badgeshield/templates/frames/frame11.png +0 -0
  44. badgeshield-0.1.0/src/badgeshield/templates/frames/frame2.png +0 -0
  45. badgeshield-0.1.0/src/badgeshield/templates/frames/frame3.png +0 -0
  46. badgeshield-0.1.0/src/badgeshield/templates/frames/frame4.png +0 -0
  47. badgeshield-0.1.0/src/badgeshield/templates/frames/frame5.png +0 -0
  48. badgeshield-0.1.0/src/badgeshield/templates/frames/frame6.png +0 -0
  49. badgeshield-0.1.0/src/badgeshield/templates/frames/frame7.png +0 -0
  50. badgeshield-0.1.0/src/badgeshield/templates/frames/frame8.png +0 -0
  51. badgeshield-0.1.0/src/badgeshield/templates/frames/frame9.png +0 -0
  52. badgeshield-0.1.0/src/badgeshield/templates/label.svg +70 -0
  53. badgeshield-0.1.0/src/badgeshield/templates/pill.svg +34 -0
  54. badgeshield-0.1.0/src/badgeshield/utils.py +107 -0
  55. badgeshield-0.1.0/src/badgeshield.egg-info/PKG-INFO +371 -0
  56. badgeshield-0.1.0/src/badgeshield.egg-info/SOURCES.txt +67 -0
  57. badgeshield-0.1.0/src/badgeshield.egg-info/dependency_links.txt +1 -0
  58. badgeshield-0.1.0/src/badgeshield.egg-info/entry_points.txt +2 -0
  59. badgeshield-0.1.0/src/badgeshield.egg-info/requires.txt +7 -0
  60. badgeshield-0.1.0/src/badgeshield.egg-info/top_level.txt +1 -0
  61. badgeshield-0.1.0/tests/__init__.py +6 -0
  62. badgeshield-0.1.0/tests/conftest.py +44 -0
  63. badgeshield-0.1.0/tests/fixtures/test_logo.png +0 -0
  64. badgeshield-0.1.0/tests/snapshots/_lighten_hex_4c1d95.txt +1 -0
  65. badgeshield-0.1.0/tests/snapshots/banner_basic.svg +18 -0
  66. badgeshield-0.1.0/tests/snapshots/pill_basic.svg +17 -0
  67. badgeshield-0.1.0/tests/test_badge_generator.py +668 -0
  68. badgeshield-0.1.0/tests/test_coverage.py +123 -0
  69. badgeshield-0.1.0/tests/test_generate_badge_cli.py +288 -0
@@ -0,0 +1,116 @@
1
+ # ==============================================================================
2
+ # 🚢 Release Workflow — Project Caller
3
+ # ==============================================================================
4
+ # Save this file in YOUR PROJECT repo at:
5
+ # .github/workflows/release.yml
6
+ #
7
+ # TRIGGERS & JOB MATRIX:
8
+ #
9
+ # Event │ docs │ publish
10
+ # ─────────────────┼──────┼────────
11
+ # Push to main │ ✅ │ ⛔
12
+ # Push tag X.X.X │ ⛔ │ ✅
13
+ # Manual dispatch │ ✅ │ ⛔
14
+ #
15
+ # PUBLISH TARGET FLAG (controls where the package is published):
16
+ #
17
+ # publish-target: 'both' → TestPyPI first, then PyPI ← default
18
+ # publish-target: 'pypi' → PyPI only
19
+ # publish-target: 'testpypi' → TestPyPI only
20
+ #
21
+ # RELEASE FLOW:
22
+ # 1. Merge PRs to main as normal — docs deploy automatically
23
+ # 2. When ready to release, push a bare SemVer tag:
24
+ # git tag 1.2.1
25
+ # git push origin 1.2.1
26
+ # 3. The publish job runs: validate → test → build → testpypi → pypi
27
+ #
28
+ # SECRETS REQUIRED (Settings → Secrets and variables → Actions):
29
+ # TEST_PYPI_API_TOKEN — from test.pypi.org/manage/account/token/
30
+ # PYPI_API_TOKEN — from pypi.org (only if not using Trusted Publishing)
31
+ #
32
+ # PRE-REQUISITES (once per project):
33
+ #
34
+ # 1. Enable GitHub Pages:
35
+ # Settings → Pages → Source → GitHub Actions
36
+ #
37
+ # 2. Create GitHub Environments:
38
+ # Settings → Environments → New environment
39
+ # - "pypi" (real PyPI — add Required reviewers for safety)
40
+ # - "test-pypi" (TestPyPI sandbox — no reviewers needed)
41
+ #
42
+ # 3. Register Trusted Publisher on pypi.org:
43
+ # pypi.org → your project → Publishing → Add publisher
44
+ # owner = vertex-ai-automations
45
+ # repo = THIS-REPO-NAME
46
+ # workflow = release.yml
47
+ # env = pypi
48
+ #
49
+ # 4. Create docs/requirements.txt:
50
+ # mkdocs-material==9.5.18
51
+ # mkdocstrings[python]==0.24.3
52
+ #
53
+ # CUSTOMISE PER PROJECT:
54
+ # python-version — match your project's Python version
55
+ # test-command — your test runner command
56
+ # publish-target — 'both' | 'pypi' | 'testpypi'
57
+ # src-layout — true if you use a src/ directory layout
58
+ # mkdocs-strict — false to allow doc build warnings
59
+ # ==============================================================================
60
+
61
+ name: 🚢 Release
62
+
63
+ on:
64
+ push:
65
+ branches:
66
+ - main # Docs deploy only
67
+ tags:
68
+ - "*.*.*" # Publish only — bare SemVer e.g. 1.2.1
69
+ workflow_dispatch: # Manual trigger — docs only
70
+
71
+ jobs:
72
+
73
+ # ============================================================
74
+ # 📚 Deploy MkDocs to GitHub Pages
75
+ # Runs on: push to main, manual dispatch
76
+ # ============================================================
77
+ docs:
78
+ name: 📚 Deploy Docs
79
+ uses: vertex-ai-automations/shared-workflows/.github/workflows/publish-mkdocs.yml@main
80
+ permissions:
81
+ contents: read
82
+ pages: write
83
+ id-token: write
84
+ with:
85
+ python-version: "3.11"
86
+ docs-requirements: "docs/requirements.txt"
87
+ src-layout: true # ← set false if not using src/ layout
88
+ mkdocs-strict: true # ← set false to allow doc build warnings
89
+ secrets: inherit
90
+
91
+ # ============================================================
92
+ # 📦 Build and Publish Package
93
+ # Runs on: tag push only (tag guard is enforced inside the
94
+ # reusable workflow — non-tag pushes skip cleanly)
95
+ #
96
+ # publish-target controls which registries receive the package:
97
+ # 'both' → TestPyPI first, then PyPI (recommended)
98
+ # 'pypi' → PyPI only
99
+ # 'testpypi' → TestPyPI only
100
+ #
101
+ # No permissions block needed — declared inside python-publish.yml
102
+ # ============================================================
103
+ publish:
104
+ name: 📦 Publish Package
105
+ uses: vertex-ai-automations/shared-workflows/.github/workflows/python-publish.yml@main
106
+ permissions:
107
+ contents: read
108
+ id-token: write
109
+ with:
110
+ python-version: "3.11"
111
+ run-tests: true
112
+ test-command: "pytest tests/ -v --tb=short"
113
+ publish-target: "pypi" # 'both' | 'pypi' | 'testpypi'
114
+ use-trusted-publishing: false # set false to use PYPI_API_TOKEN instead
115
+ secrets: inherit
116
+
@@ -0,0 +1,190 @@
1
+ # Derived from basic .gitignore template for python projects:
2
+ # https://github.com/github/gitignore/blob/main/Python.gitignore
3
+ # Please maintain the alphabetic order of the section titles
4
+ # To debug why a file is being ignored, use the command:
5
+ # git check-ignore -v $my_ignored_file
6
+
7
+ # Byte-compiled / optimized / DLL files
8
+ __pycache__/
9
+ *.py[cod]
10
+ *$py.class
11
+
12
+ # C extensions
13
+ *.so
14
+
15
+ # Cython debug symbols
16
+ cython_debug/
17
+
18
+ # Celery stuff
19
+ celerybeat-schedule
20
+ celerybeat.pid
21
+
22
+ # Distribution / packaging
23
+ .Python
24
+ build/
25
+ develop-eggs/
26
+ dist/
27
+ downloads/
28
+ eggs/
29
+ .eggs/
30
+ lib/
31
+ lib64/
32
+ parts/
33
+ sdist/
34
+ var/
35
+ wheels/
36
+ share/python-wheels/
37
+ *.egg-info/
38
+ .installed.cfg
39
+ *.egg
40
+ MANIFEST
41
+
42
+ # Django stuff
43
+ *.log
44
+ local_settings.py
45
+ db.sqlite3
46
+ db.sqlite3-journal
47
+
48
+ # Environments
49
+ .env
50
+ .venv
51
+ env/
52
+ venv/
53
+ ENV/
54
+ env.bak/
55
+ venv.bak/
56
+
57
+ # Flask stuff
58
+ instance/
59
+ .webassets-cache
60
+
61
+ # Installer logs
62
+ pip-log.txt
63
+ pip-delete-this-directory.txt
64
+
65
+ # IPython
66
+ profile_default/
67
+ ipython_config.py
68
+
69
+ # Jupyter Notebook
70
+ *.ipynb_checkpoints
71
+
72
+ # mkdocs documentation
73
+ /site
74
+
75
+ # Model saving / checkpointing
76
+ *.pt
77
+ *.pth
78
+ *.ckpt
79
+
80
+ # mypy
81
+ .mypy_cache/
82
+ .dmypy.json
83
+ dmypy.json
84
+
85
+ # PyBuilder
86
+ .pybuilder/
87
+ target/
88
+
89
+ # PyCharm
90
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
91
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
92
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
93
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
94
+ #.idea/
95
+
96
+ # PyInstaller
97
+ # Usually these files are written by a python script from a template
98
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
99
+ *.manifest
100
+ #*.spec
101
+
102
+ # pyenv
103
+ # For a library or package, you might want to ignore these files since the code is
104
+ # intended to run in multiple environments; otherwise, check them in:
105
+ # .python-version
106
+
107
+ # pipenv
108
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
109
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
110
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
111
+ # install all needed dependencies.
112
+ # Pipfile.lock
113
+
114
+ # poetry
115
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
116
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
117
+ # commonly ignored for libraries.
118
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
119
+ # poetry.lock
120
+
121
+ # PEP 582: https://peps.python.org/pep-0582/
122
+ # This PEP proposes to add to Python a mechanism to automatically recognize a __pypackages__
123
+ # directory and prefer importing packages installed in this location over user or global site-packages.
124
+ # This will avoid the steps to create, activate or deactivate virtual environments. Python will use
125
+ # the __pypackages__ from the base directory of the script when present.
126
+ __pypackages__/
127
+
128
+ # Pyre type checker
129
+ .pyre/
130
+
131
+ # pytype static type analyzer
132
+ .pytype/
133
+
134
+ # Rope project settings
135
+ .ropeproject
136
+
137
+ # SageMath parsed files
138
+ *.sage.py
139
+
140
+ # Scrapy stuff:
141
+ .scrapy
142
+
143
+ # Sphinx documentation
144
+ docs/build
145
+ # sphinx-gallery
146
+ docs/source/generated_examples/
147
+ docs/source/gen_modules/
148
+ docs/source/generated/
149
+ docs/source/sg_execution_times.rst
150
+ # pytorch-sphinx-theme gets installed here
151
+ docs/src
152
+
153
+ # Spyder project settings
154
+ .spyderproject
155
+ .spyproject
156
+
157
+ # System / program generated files
158
+ *.err
159
+ *.log
160
+ *.swp
161
+ .DS_Store
162
+
163
+ # Translations
164
+ *.mo
165
+ *.pot
166
+
167
+ # TorchX
168
+ *.torchxconfig
169
+
170
+ # Unit test / coverage reports
171
+ htmlcov/
172
+ .tox/
173
+ .nox/
174
+ .coverage
175
+ .coverage.*
176
+ .cache
177
+ nosetests.xml
178
+ coverage.xml
179
+ *.cover
180
+ *.py,cover
181
+ .hypothesis/
182
+ .pytest_cache/
183
+ cover/
184
+
185
+ # VSCode
186
+ .vscode/
187
+
188
+ # wandb
189
+ wandb/
190
+ .worktrees/
@@ -0,0 +1,86 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ **badgeshield** is a Python package for generating customizable SVG badges. It supports 5 templates (DEFAULT, CIRCLE, CIRCLE_FRAME, PILL, BANNER), 4 visual styles, 51 predefined colors, logo embedding, batch processing, and both a programmatic API and CLI.
8
+
9
+ ## Commands
10
+
11
+ ### Testing
12
+ ```bash
13
+ pytest tests/ -v --tb=short
14
+ # Run a single test file
15
+ pytest tests/test_badge_generator.py -v
16
+ # Run a single test
17
+ pytest tests/test_badge_generator.py::test_name -v
18
+ ```
19
+
20
+ ### Documentation
21
+ ```bash
22
+ mkdocs build # Build docs
23
+ mkdocs serve # Serve docs locally
24
+ ```
25
+
26
+ ### Install for development
27
+ ```bash
28
+ pip install -e .
29
+ pip install -r requirements.txt
30
+ ```
31
+
32
+ ### CLI usage
33
+ ```bash
34
+ badgeshield single --left_text "Build" --left_color GREEN --badge_name build_badge.svg
35
+ badgeshield single --left_text "Status" --left_color "#555555" --badge_name s.svg --style gradient
36
+ badgeshield batch config.json --output_path ./badges/ --style rounded
37
+ badgeshield coverage coverage.xml --badge_name coverage.svg --metric line
38
+ badgeshield audit badge.svg # check SVG for external URL references
39
+ badgeshield audit badge.svg --json # machine-readable output
40
+ ```
41
+
42
+ Note: `badge_name` must always end with `.svg`. The `--style` option accepts `FLAT | ROUNDED | GRADIENT | SHADOWED` (case-insensitive; defaults to `flat`). Per-entry `style` keys in batch JSON take priority over the CLI flag.
43
+
44
+ ## Architecture
45
+
46
+ ### Source layout (`src/badgeshield/`)
47
+
48
+ - **`badge_generator.py`**: Two main classes:
49
+ - `BadgeBatchGenerator`: Concurrent badge generation via `ThreadPoolExecutor`. Aggregates failures and raises `RuntimeError` with a summary if any badge fails.
50
+ - `BadgeGenerator`: Core SVG generation. Uses Jinja2 to render templates. Text width is calculated using Pillow's `ImageFont` (DejaVuSans.ttf) with a character-width dict fallback. Logo images are base64-embedded and can be color-tinted by converting to RGBA and applying the tint while preserving alpha.
51
+
52
+ - **`generate_badge_cli.py`**: Typer + Rich CLI with four subcommands: `single`, `batch`, `coverage`, and `audit`. Enum validation (template, frame, style, log_level) is done at CLI entry before calling `BadgeGenerator`. The `batch` command shows a Rich progress bar and summary table; failures exit with code 1. The `audit` command scans an SVG for external `http`/`https` URLs in attributes and inline styles.
53
+
54
+ - **`coverage.py`**: Two standalone functions — `parse_coverage_xml(path, metric)` reads `line-rate` or `branch-rate` from a `coverage.xml` produced by `coverage run`, returning a float 0–100; `coverage_color(pct)` maps the percentage to a hex color. The `coverage` CLI subcommand wires these together to produce a DEFAULT-template badge.
55
+
56
+ - **`utils.py`**: Four enums — `BadgeColor` (51 colors), `FrameType` (11 PNG frames), `BadgeTemplate` (5 templates), `BadgeStyle` (FLAT/ROUNDED/GRADIENT/SHADOWED). These are the canonical type-safe inputs for the generator.
57
+
58
+ - **`templates/`**: Jinja2 SVG templates with autoescape enabled.
59
+ - `label.svg` — DEFAULT: two-part horizontal badge, dynamic width, optional logo and links
60
+ - `circle.svg` — CIRCLE: circle with font-size scaling based on text length
61
+ - `circle_frame.svg` — CIRCLE_FRAME: circle overlaid with a PNG frame asset; requires `FrameType`
62
+ - `pill.svg` — PILL: fully rounded ends (rx=10 always), always uses style_ctx but overrides `rx`
63
+ - `banner.svg` — BANNER: fixed icon-zone on left (28px) + right text section
64
+
65
+ ### Public API (`from badgeshield import ...`)
66
+
67
+ `BadgeGenerator`, `BadgeBatchGenerator`, `BadgeColor`, `BadgeTemplate`, `BadgeStyle`, `FrameType`, `LogLevel`, `parse_coverage_xml`, `coverage_color`
68
+
69
+ ### Batch JSON config format
70
+
71
+ The JSON file passed to `batch` must be a list of objects, each with the same keys as `BadgeGenerator.generate_badge` kwargs. Required keys per entry: `left_text`, `left_color`, `badge_name` (ending in `.svg`). The `output_path` and `template` are supplied via CLI flags, not in the JSON. An optional `style` key per entry (string, case-insensitive) overrides the CLI `--style` flag for that badge.
72
+
73
+ ### Key design decisions
74
+
75
+ - **Versioning**: `setuptools_scm` derives version from git tags (format `X.X.X`). The `_version.py` file is auto-generated — do not edit it manually.
76
+ - **Logging**: Uses `pylogshield` (`get_logger`), not stdlib `logging`. Log level can be passed as `LogLevel` enum or string.
77
+ - **Color input**: Accepts either a `BadgeColor` enum member or a hex string (`#RRGGBB`). Validation is done via `is_valid_hex_color()` and `validate_color()` in `badge_generator.py`.
78
+ - **Pillow is optional**: Only `jinja2` and `pylogshield` are required (`requirements.txt`). If Pillow is not installed, text width falls back to a char-width heuristic dict and logo tinting is silently skipped. Install the extras group for accurate text sizing: `pip install badgeshield[image]`.
79
+ - **CIRCLE_FRAME template**: Requires `frame` parameter (a `FrameType` enum); it is not optional for that template.
80
+ - **Renderer dispatch**: `BadgeGenerator._RENDERERS` is a class-level dict mapping `BadgeTemplate` → `_render_*` method, populated after the class body. To add a new template, add a `_render_<name>` method and register it in `_RENDERERS`.
81
+ - **Style system**: `_style_context()` returns a dict of Jinja2 context vars (`rx`, `gradient_id`, `gradient_stop`, `gradient_base`, `shadow_id`). Templates that don't support gradient (e.g. CIRCLE_FRAME) clear gradient keys after calling `_style_context`. PILL always forces `rx="10"` regardless of style.
82
+
83
+ ### CI/CD (`.github/workflows/release.yml`)
84
+
85
+ - **Docs job**: Triggers on push to `main` or manual dispatch — deploys MkDocs to GitHub Pages.
86
+ - **Publish job**: Triggers on tag push matching `X.X.X` — runs tests, builds, then publishes to TestPyPI then PyPI using GitHub OIDC trusted publishing.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Muhammad Hassan
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.