stormtrack 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 (69) hide show
  1. stormtrack-0.4.0/.github/dependabot.yml +28 -0
  2. stormtrack-0.4.0/.github/workflows/ci.yml +82 -0
  3. stormtrack-0.4.0/.github/workflows/release.yml +71 -0
  4. stormtrack-0.4.0/.github/workflows/update-dataset.yml +104 -0
  5. stormtrack-0.4.0/.gitignore +30 -0
  6. stormtrack-0.4.0/.pre-commit-config.yaml +25 -0
  7. stormtrack-0.4.0/.readthedocs.yaml +22 -0
  8. stormtrack-0.4.0/.zenodo.json +20 -0
  9. stormtrack-0.4.0/CITATION.cff +36 -0
  10. stormtrack-0.4.0/CONTRIBUTING.md +55 -0
  11. stormtrack-0.4.0/LICENSE +28 -0
  12. stormtrack-0.4.0/PKG-INFO +321 -0
  13. stormtrack-0.4.0/README.md +260 -0
  14. stormtrack-0.4.0/codecov.yml +21 -0
  15. stormtrack-0.4.0/data/ibtracs/manifest.json +12 -0
  16. stormtrack-0.4.0/data/ibtracs/observations.parquet +0 -0
  17. stormtrack-0.4.0/data/ibtracs/storms.parquet +0 -0
  18. stormtrack-0.4.0/data/manifest.json +11 -0
  19. stormtrack-0.4.0/data/observations.parquet +0 -0
  20. stormtrack-0.4.0/data/remarks.parquet +0 -0
  21. stormtrack-0.4.0/data/storms.parquet +0 -0
  22. stormtrack-0.4.0/docs/_static/all_tracks_basin.png +0 -0
  23. stormtrack-0.4.0/docs/_static/all_tracks_grade.png +0 -0
  24. stormtrack-0.4.0/docs/_static/track_tauktae.png +0 -0
  25. stormtrack-0.4.0/docs/api.md +54 -0
  26. stormtrack-0.4.0/docs/conf.py +95 -0
  27. stormtrack-0.4.0/docs/contributing.md +2 -0
  28. stormtrack-0.4.0/docs/examples/climatology.py +478 -0
  29. stormtrack-0.4.0/docs/examples/plotting.py +73 -0
  30. stormtrack-0.4.0/docs/examples/quickstart.py +94 -0
  31. stormtrack-0.4.0/docs/index.md +45 -0
  32. stormtrack-0.4.0/docs/installation.md +107 -0
  33. stormtrack-0.4.0/docs/references.md +39 -0
  34. stormtrack-0.4.0/docs/userguide/datamodel.md +48 -0
  35. stormtrack-0.4.0/docs/userguide/ibtracs.md +62 -0
  36. stormtrack-0.4.0/docs/userguide/loading.md +44 -0
  37. stormtrack-0.4.0/docs/userguide/quality.md +39 -0
  38. stormtrack-0.4.0/docs/userguide/realtime.md +80 -0
  39. stormtrack-0.4.0/docs/userguide/updates.md +33 -0
  40. stormtrack-0.4.0/docs/userguide.md +28 -0
  41. stormtrack-0.4.0/pyproject.toml +106 -0
  42. stormtrack-0.4.0/src/stormtrack/__init__.py +67 -0
  43. stormtrack-0.4.0/src/stormtrack/_repo.py +57 -0
  44. stormtrack-0.4.0/src/stormtrack/_schema.py +254 -0
  45. stormtrack-0.4.0/src/stormtrack/_version.py +24 -0
  46. stormtrack-0.4.0/src/stormtrack/analysis.py +312 -0
  47. stormtrack-0.4.0/src/stormtrack/atcf.py +197 -0
  48. stormtrack-0.4.0/src/stormtrack/cli.py +147 -0
  49. stormtrack-0.4.0/src/stormtrack/compare.py +187 -0
  50. stormtrack-0.4.0/src/stormtrack/core.py +256 -0
  51. stormtrack-0.4.0/src/stormtrack/dataset.py +113 -0
  52. stormtrack-0.4.0/src/stormtrack/fetch.py +209 -0
  53. stormtrack-0.4.0/src/stormtrack/ibtracs.py +413 -0
  54. stormtrack-0.4.0/src/stormtrack/parse.py +411 -0
  55. stormtrack-0.4.0/src/stormtrack/pipeline.py +123 -0
  56. stormtrack-0.4.0/src/stormtrack/plot.py +545 -0
  57. stormtrack-0.4.0/src/stormtrack/qc.py +192 -0
  58. stormtrack-0.4.0/src/stormtrack/realtime.py +268 -0
  59. stormtrack-0.4.0/src/stormtrack/store.py +127 -0
  60. stormtrack-0.4.0/src/stormtrack/validate.py +161 -0
  61. stormtrack-0.4.0/src/stormtrack/verification.py +271 -0
  62. stormtrack-0.4.0/tests/test_ibtracs.py +246 -0
  63. stormtrack-0.4.0/tests/test_load.py +109 -0
  64. stormtrack-0.4.0/tests/test_parse.py +574 -0
  65. stormtrack-0.4.0/tests/test_pipeline.py +212 -0
  66. stormtrack-0.4.0/tests/test_plot.py +57 -0
  67. stormtrack-0.4.0/tests/test_qc.py +288 -0
  68. stormtrack-0.4.0/tests/test_realtime.py +401 -0
  69. stormtrack-0.4.0/uv.lock +5837 -0
@@ -0,0 +1,28 @@
1
+ version: 2
2
+ updates:
3
+ # Python dependencies declared in pyproject.toml.
4
+ - package-ecosystem: "pip"
5
+ directory: "/"
6
+ schedule:
7
+ interval: "weekly"
8
+ open-pull-requests-limit: 5
9
+ groups:
10
+ # One PR for routine minor/patch bumps to keep the noise down.
11
+ python-minor:
12
+ update-types: ["minor", "patch"]
13
+ labels: ["dependencies"]
14
+ commit-message:
15
+ prefix: "deps"
16
+
17
+ # GitHub Actions used in the workflows (checkout, setup-python, publish, ...).
18
+ - package-ecosystem: "github-actions"
19
+ directory: "/"
20
+ schedule:
21
+ interval: "weekly"
22
+ open-pull-requests-limit: 5
23
+ groups:
24
+ actions:
25
+ patterns: ["*"]
26
+ labels: ["ci", "dependencies"]
27
+ commit-message:
28
+ prefix: "ci"
@@ -0,0 +1,82 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ workflow_dispatch:
9
+
10
+ concurrency:
11
+ group: ci-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ jobs:
15
+ lint:
16
+ name: lint (black + ruff)
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v7
20
+ with:
21
+ fetch-depth: 0 # tags for hatch-vcs version
22
+ - uses: astral-sh/setup-uv@v7
23
+ with:
24
+ enable-cache: true
25
+ - run: uv sync --locked --extra lint
26
+ - name: black --check
27
+ run: uv run black --check .
28
+ - name: ruff check
29
+ run: uv run ruff check .
30
+
31
+ test:
32
+ name: test (py${{ matrix.python-version }})
33
+ runs-on: ubuntu-latest
34
+ strategy:
35
+ fail-fast: false
36
+ matrix:
37
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
38
+
39
+ steps:
40
+ - name: Checkout
41
+ uses: actions/checkout@v7
42
+ with:
43
+ fetch-depth: 0 # tags for hatch-vcs version
44
+
45
+ - uses: astral-sh/setup-uv@v7
46
+ with:
47
+ enable-cache: true
48
+
49
+ - name: Sync (locked) with dev extras
50
+ run: uv sync --locked --extra dev --python ${{ matrix.python-version }}
51
+
52
+ - name: Run tests with coverage
53
+ run: uv run pytest --cov=stormtrack --cov-report=xml --cov-report=term-missing
54
+
55
+ - name: Upload coverage to Codecov
56
+ uses: codecov/codecov-action@v7
57
+ with:
58
+ token: ${{ secrets.CODECOV_TOKEN }}
59
+ files: ./coverage.xml
60
+ flags: py${{ matrix.python-version }}
61
+ name: py${{ matrix.python-version }}
62
+ fail_ci_if_error: false
63
+
64
+ docs:
65
+ name: build docs (validation only)
66
+ runs-on: ubuntu-latest
67
+ # Only build docs once every other check (tests + lint) is green.
68
+ needs: [test, lint]
69
+ steps:
70
+ - uses: actions/checkout@v7
71
+ with:
72
+ fetch-depth: 0 # tags for hatch-vcs version
73
+ - uses: astral-sh/setup-uv@v7
74
+ with:
75
+ enable-cache: true
76
+ - name: Sync (locked) with docs extras
77
+ run: uv sync --locked --extra docs
78
+ # Build only, to catch broken docs on a push/PR. Nothing is uploaded or
79
+ # published from here — the hosted docs (incl. per-PR previews) are built
80
+ # by Read the Docs, not GitHub.
81
+ - name: Build HTML docs (executes example notebooks)
82
+ run: uv run sphinx-build -b html -W --keep-going docs docs/_build/html
@@ -0,0 +1,71 @@
1
+ name: Release
2
+
3
+ # On a new release (a v* tag, or the update-dataset pipeline after it bumps the
4
+ # version), build the distributions and publish them straight to TestPyPI + PyPI.
5
+ # Nothing is uploaded to GitHub: no workflow artifacts, and the GitHub Release
6
+ # carries notes only (no dist files attached).
7
+ on:
8
+ push:
9
+ tags: ["v*"]
10
+ workflow_dispatch:
11
+ workflow_call:
12
+ inputs:
13
+ ref:
14
+ description: "Tag/ref to build and release"
15
+ required: false
16
+ type: string
17
+
18
+ permissions:
19
+ contents: read
20
+
21
+ jobs:
22
+ release:
23
+ name: Build and publish
24
+ runs-on: ubuntu-latest
25
+ permissions:
26
+ contents: write # create the GitHub Release (notes only)
27
+ id-token: write # trusted publishing to PyPI/TestPyPI (OIDC)
28
+ steps:
29
+ - uses: actions/checkout@v7
30
+ with:
31
+ ref: ${{ inputs.ref || github.ref }}
32
+ fetch-depth: 0
33
+
34
+ - uses: actions/setup-python@v7
35
+ with:
36
+ python-version: "3.12"
37
+
38
+ - uses: astral-sh/setup-uv@v7
39
+ with:
40
+ enable-cache: true
41
+
42
+ - name: Build distributions
43
+ run: |
44
+ uv build # sdist + wheel (version from the git tag)
45
+ uvx twine check dist/*
46
+
47
+ - name: Publish to TestPyPI
48
+ # TestPyPI is a smoke test, not the release — don't let its flakiness
49
+ # (it times out sometimes) block the real PyPI publish below.
50
+ continue-on-error: true
51
+ uses: pypa/gh-action-pypi-publish@release/v1
52
+ with:
53
+ repository-url: https://test.pypi.org/legacy/
54
+ skip-existing: true
55
+ # Both publishes share one dist/; let only the PyPI step generate the
56
+ # PEP 740 attestations, otherwise the second publish errors on the
57
+ # attestation files the first one left behind.
58
+ attestations: false
59
+
60
+ - name: Publish to PyPI
61
+ uses: pypa/gh-action-pypi-publish@release/v1
62
+ with:
63
+ skip-existing: true
64
+
65
+ # Marks the release in GitHub with auto-generated notes. No dist files are
66
+ # attached — the artifacts live on PyPI/TestPyPI.
67
+ - name: Create GitHub Release (notes only)
68
+ uses: softprops/action-gh-release@v3
69
+ with:
70
+ tag_name: ${{ inputs.ref || github.ref_name }}
71
+ generate_release_notes: true
@@ -0,0 +1,104 @@
1
+ name: Update dataset
2
+
3
+ # Rebuild the committed datasets (IMD workbook + IBTrACS North Indian Ocean) once a month.
4
+ # - If IMD hasn't changed, nothing is committed and no release is made.
5
+ # - If IMD changed and the new file parses + validates, data/ is updated,
6
+ # committed, the version is bumped + tagged, and a release is published.
7
+ # - If the new file fails to parse or validate, the job FAILS (red X + GitHub's
8
+ # failure notification email) and the committed dataset is left untouched.
9
+ on:
10
+ schedule:
11
+ - cron: "0 6 1 * *" # 06:00 UTC on the 1st of every month
12
+ workflow_dispatch:
13
+ inputs:
14
+ force:
15
+ description: "Rebuild even if the IMD source is unchanged"
16
+ type: boolean
17
+ default: false
18
+
19
+ permissions:
20
+ contents: write # push the refreshed data/, version bump, and tag
21
+
22
+ concurrency:
23
+ group: update-dataset
24
+ cancel-in-progress: false
25
+
26
+ jobs:
27
+ update:
28
+ runs-on: ubuntu-latest
29
+ outputs:
30
+ changed: ${{ steps.commit.outputs.changed }}
31
+ version: ${{ steps.commit.outputs.version }}
32
+ steps:
33
+ - name: Checkout
34
+ uses: actions/checkout@v7
35
+ with:
36
+ fetch-depth: 0 # tags needed so hatch-vcs derives the version
37
+
38
+ - name: Set up Python
39
+ uses: actions/setup-python@v7
40
+ with:
41
+ python-version: "3.12"
42
+
43
+ - uses: astral-sh/setup-uv@v7
44
+ with:
45
+ enable-cache: true
46
+
47
+ - name: Install package (with pipeline extra)
48
+ run: uv pip install --system -e ".[all]"
49
+
50
+ # If this step exits non-zero (parse/validation failure) the job fails and
51
+ # no commit happens — the previous dataset stays in place.
52
+ - name: Rebuild dataset from IMD
53
+ run: |
54
+ stormtrack build --data-dir data \
55
+ ${{ (github.event_name == 'workflow_dispatch' && inputs.force) && '--force' || '' }}
56
+
57
+ # Independent fail-safe: a bad IBTrACS file leaves data/ibtracs untouched.
58
+ - name: Rebuild IBTrACS (North Indian Ocean) dataset
59
+ run: stormtrack build-ibtracs --data-dir data ${{ (github.event_name == 'workflow_dispatch' && inputs.force) && '--force' || '' }}
60
+
61
+ - name: Commit and tag if the dataset changed
62
+ id: commit
63
+ run: |
64
+ if [ -z "$(git status --porcelain data)" ]; then
65
+ echo "No dataset changes to commit."
66
+ echo "changed=false" >> "$GITHUB_OUTPUT"
67
+ exit 0
68
+ fi
69
+
70
+ git config user.name "github-actions[bot]"
71
+ git config user.email "github-actions[bot]@users.noreply.github.com"
72
+
73
+ # Version comes from the git tag (hatch-vcs): bump the patch of the
74
+ # latest v* tag. No source file to edit.
75
+ LATEST="$(git describe --tags --abbrev=0 --match 'v*' 2>/dev/null || echo v0.0.0)"
76
+ NEW_VERSION="$(python - "$LATEST" <<'PY'
77
+ import re, sys
78
+ m = re.match(r"v(\d+)\.(\d+)\.(\d+)", sys.argv[1])
79
+ major, minor, patch = map(int, m.groups())
80
+ print(f"v{major}.{minor}.{patch + 1}")
81
+ PY
82
+ )"
83
+
84
+ git add data
85
+ git commit -m "data: refresh IMD best tracks and IBTrACS NI (${NEW_VERSION})"
86
+ git tag "${NEW_VERSION}"
87
+ git push origin HEAD --follow-tags
88
+
89
+ echo "Committed dataset update and tagged ${NEW_VERSION}."
90
+ echo "changed=true" >> "$GITHUB_OUTPUT"
91
+ echo "version=${NEW_VERSION}" >> "$GITHUB_OUTPUT"
92
+
93
+ # Publish only when the dataset actually changed. Reuses the release pipeline
94
+ # (build -> TestPyPI -> PyPI -> GitHub Release) against the freshly-created tag.
95
+ release:
96
+ needs: update
97
+ if: needs.update.outputs.changed == 'true'
98
+ uses: ./.github/workflows/release.yml
99
+ with:
100
+ ref: ${{ needs.update.outputs.version }}
101
+ permissions:
102
+ contents: write
103
+ id-token: write
104
+ secrets: inherit
@@ -0,0 +1,30 @@
1
+ .DS_Store
2
+ src/stormtrack/_version.py
3
+ __pycache__/
4
+ *.py[cod]
5
+ *.egg-info/
6
+ build/
7
+ dist/
8
+ .venv/
9
+ .pytest_cache/
10
+ .coverage
11
+ coverage.xml
12
+ htmlcov/
13
+
14
+ # Docs build artifacts
15
+ docs/_build/
16
+ docs/jupyter_execute/
17
+ docs/**/jupyter_execute/
18
+ .jupyter_cache/
19
+ docs/api/_autosummary/
20
+ *.xlsx
21
+ *.parquet
22
+ .cache/
23
+
24
+ # ...but the *published* dataset committed by the pipeline must be tracked.
25
+ !data/
26
+ !data/*.parquet
27
+ !data/manifest.json
28
+ !data/ibtracs/
29
+ !data/ibtracs/*.parquet
30
+ !data/ibtracs/manifest.json
@@ -0,0 +1,25 @@
1
+ # Style / syntax gate. Runs on `git commit` and `git push`.
2
+ # Install once: pre-commit install --hook-type pre-commit --hook-type pre-push
3
+ default_stages: [pre-commit, pre-push]
4
+
5
+ repos:
6
+ - repo: https://github.com/pre-commit/pre-commit-hooks
7
+ rev: v4.6.0
8
+ hooks:
9
+ - id: trailing-whitespace
10
+ - id: end-of-file-fixer
11
+ - id: check-yaml
12
+ - id: check-toml
13
+ - id: check-added-large-files
14
+ args: ["--maxkb=2048"]
15
+
16
+ - repo: https://github.com/astral-sh/ruff-pre-commit
17
+ rev: v0.15.21
18
+ hooks:
19
+ - id: ruff
20
+ args: ["--fix"]
21
+
22
+ - repo: https://github.com/psf/black
23
+ rev: 26.5.1
24
+ hooks:
25
+ - id: black
@@ -0,0 +1,22 @@
1
+ # Read the Docs build configuration
2
+ # https://docs.readthedocs.io/en/stable/config-file/v2.html
3
+ version: 2
4
+
5
+ build:
6
+ os: ubuntu-24.04
7
+ tools:
8
+ python: "3.12"
9
+
10
+ sphinx:
11
+ configuration: docs/conf.py
12
+ fail_on_warning: false
13
+
14
+ python:
15
+ install:
16
+ - method: pip
17
+ path: .
18
+ extra_requirements:
19
+ - docs
20
+
21
+ formats:
22
+ - htmlzip
@@ -0,0 +1,20 @@
1
+ {
2
+ "title": "stormtrack: Tropical-cyclone best tracks and forecasts in Python (IMD, IBTrACS, NHC/JTWC)",
3
+ "description": "A Python library that serves tropical-cyclone best tracks (the India Meteorological Department RSMC New Delhi record and IBTrACS) and real-time NHC/JTWC forecasts as tidy pandas DataFrames and CF-style xarray Datasets, with analysis, comparison and forecast-verification tools. The parsed datasets are kept up to date automatically.",
4
+ "upload_type": "software",
5
+ "access_right": "open",
6
+ "license": "BSD-3-Clause",
7
+ "creators": [
8
+ { "name": "Syed, Hamid Ali", "orcid": "0000-0002-7188-2544" }
9
+ ],
10
+ "keywords": [
11
+ "cyclone",
12
+ "best-track",
13
+ "IMD",
14
+ "IBTrACS",
15
+ "North Indian Ocean",
16
+ "meteorology",
17
+ "xarray",
18
+ "pandas"
19
+ ]
20
+ }
@@ -0,0 +1,36 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite it using the metadata below."
3
+ title: "stormtrack: Tropical-cyclone best tracks and forecasts in Python (IMD, IBTrACS, NHC/JTWC)"
4
+ type: software
5
+ authors:
6
+ - family-names: Syed
7
+ given-names: Hamid Ali
8
+ orcid: "https://orcid.org/0000-0002-7188-2544"
9
+ repository-code: "https://github.com/atmospy/stormtrack"
10
+ url: "https://stormtrack.readthedocs.io/"
11
+ license: BSD-3-Clause
12
+ keywords:
13
+ - cyclone
14
+ - best-track
15
+ - IMD
16
+ - IBTrACS
17
+ - forecast verification
18
+ - North Indian Ocean
19
+ - meteorology
20
+ - xarray
21
+ - pandas
22
+ # Zenodo Concept DOI (represents ALL versions and always resolves to the latest,
23
+ # so it never changes between releases — this is the one to cite).
24
+ doi: 10.5281/zenodo.21301659
25
+ # Preferred citation: a release-agnostic reference (year only, no per-release
26
+ # date or version) so citations stay uniform across releases.
27
+ preferred-citation:
28
+ type: software
29
+ title: "stormtrack: Tropical-cyclone best tracks and forecasts in Python (IMD, IBTrACS, NHC/JTWC)"
30
+ authors:
31
+ - family-names: Syed
32
+ given-names: Hamid Ali
33
+ orcid: "https://orcid.org/0000-0002-7188-2544"
34
+ year: 2026
35
+ doi: 10.5281/zenodo.21301659
36
+ url: "https://stormtrack.readthedocs.io/"
@@ -0,0 +1,55 @@
1
+ # Contributing to stormtrack
2
+
3
+ Thanks for helping! Bug reports, fixes, and features are all welcome via
4
+ [issues](https://github.com/atmospy/stormtrack/issues) and pull requests.
5
+
6
+ ## Development setup ([uv](https://docs.astral.sh/uv/))
7
+
8
+ [uv](https://docs.astral.sh/uv/) is the fastest way to get a dev environment.
9
+ `uv sync` installs the **exact, locked** dependencies from `uv.lock` into `.venv`:
10
+
11
+ ```bash
12
+ uv sync --extra dev --extra plot --extra docs # everything, reproducibly
13
+ ```
14
+
15
+ (Plain `pip` works too: `pip install -e ".[dev]"`.) The version is derived from
16
+ git tags by [hatch-vcs](https://github.com/ofek/hatch-vcs), so there's no
17
+ `__version__` to edit. If you change dependencies in `pyproject.toml`, refresh
18
+ the lock with `uv lock` and commit it.
19
+
20
+ ## Checks
21
+
22
+ ```bash
23
+ uv run pytest # tests (cartopy plot tests skip without cartopy)
24
+ uv run black . && uv run ruff check . # format + lint
25
+ ```
26
+
27
+ Install the pre-commit hooks so these run automatically:
28
+
29
+ ```bash
30
+ uv pip install pre-commit
31
+ pre-commit install --hook-type pre-commit --hook-type pre-push
32
+ ```
33
+
34
+ ## Workflow
35
+
36
+ Never push to `main` directly. Create a branch, open a PR, and let CI (lint +
37
+ tests on Python 3.9–3.14 + docs build) go green before merging:
38
+
39
+ ```bash
40
+ git switch -c my-change
41
+ # ... commit ...
42
+ git push -u origin my-change # then open a PR
43
+ ```
44
+
45
+ ## Releases
46
+
47
+ Releases are cut from a git tag — no source edit. Tag `main` and push:
48
+
49
+ ```bash
50
+ git tag v0.2.6 && git push origin v0.2.6
51
+ ```
52
+
53
+ That triggers the release workflow (build → TestPyPI → PyPI → GitHub Release →
54
+ Zenodo). The dataset in `data/` also auto-updates monthly and cuts its own
55
+ patch release when IMD publishes new data.
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Hamid Ali Syed
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.