markinp 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 (59) hide show
  1. markinp-0.1.0/.github/workflows/ci.yml +31 -0
  2. markinp-0.1.0/.github/workflows/release.yml +57 -0
  3. markinp-0.1.0/.gitignore +31 -0
  4. markinp-0.1.0/.pre-commit-hooks.yaml +7 -0
  5. markinp-0.1.0/CHANGELOG.md +25 -0
  6. markinp-0.1.0/CITATION.cff +23 -0
  7. markinp-0.1.0/CLAUDE.md +261 -0
  8. markinp-0.1.0/LICENSE +21 -0
  9. markinp-0.1.0/PKG-INFO +242 -0
  10. markinp-0.1.0/README.md +211 -0
  11. markinp-0.1.0/action.yml +50 -0
  12. markinp-0.1.0/conda-recipe/meta.yaml +57 -0
  13. markinp-0.1.0/docs/error-codes.md +71 -0
  14. markinp-0.1.0/markinp/__init__.py +47 -0
  15. markinp-0.1.0/markinp/build.py +314 -0
  16. markinp-0.1.0/markinp/cli.py +233 -0
  17. markinp-0.1.0/markinp/diagnostics.py +226 -0
  18. markinp-0.1.0/markinp/model.py +115 -0
  19. markinp-0.1.0/markinp/parse.py +257 -0
  20. markinp-0.1.0/markinp/py.typed +0 -0
  21. markinp-0.1.0/markinp/report.py +173 -0
  22. markinp-0.1.0/markinp/tokens.py +48 -0
  23. markinp-0.1.0/markinp/validate.py +191 -0
  24. markinp-0.1.0/markinp/write.py +40 -0
  25. markinp-0.1.0/plan.md +295 -0
  26. markinp-0.1.0/pyproject.toml +76 -0
  27. markinp-0.1.0/tests/__init__.py +0 -0
  28. markinp-0.1.0/tests/conftest.py +22 -0
  29. markinp-0.1.0/tests/fixtures/all_zero_history.inp +2 -0
  30. markinp-0.1.0/tests/fixtures/bom.inp +2 -0
  31. markinp-0.1.0/tests/fixtures/captures_long.csv +7 -0
  32. markinp-0.1.0/tests/fixtures/captures_wide.csv +3 -0
  33. markinp-0.1.0/tests/fixtures/content_after_semicolon.inp +2 -0
  34. markinp-0.1.0/tests/fixtures/covariate_count.inp +3 -0
  35. markinp-0.1.0/tests/fixtures/duplicates.inp +3 -0
  36. markinp-0.1.0/tests/fixtures/empty_file.inp +2 -0
  37. markinp-0.1.0/tests/fixtures/empty_group.inp +3 -0
  38. markinp-0.1.0/tests/fixtures/frequency_count.inp +3 -0
  39. markinp-0.1.0/tests/fixtures/illegal_history_char.inp +2 -0
  40. markinp-0.1.0/tests/fixtures/illegal_stratum.inp +3 -0
  41. markinp-0.1.0/tests/fixtures/missing_covariate.inp +3 -0
  42. markinp-0.1.0/tests/fixtures/missing_semicolon.inp +2 -0
  43. markinp-0.1.0/tests/fixtures/mixed_whitespace.inp +2 -0
  44. markinp-0.1.0/tests/fixtures/multistrata.inp +2 -0
  45. markinp-0.1.0/tests/fixtures/negative_frequency.inp +2 -0
  46. markinp-0.1.0/tests/fixtures/noninteger_frequency.inp +2 -0
  47. markinp-0.1.0/tests/fixtures/nonnumeric_frequency.inp +2 -0
  48. markinp-0.1.0/tests/fixtures/ragged_history.inp +3 -0
  49. markinp-0.1.0/tests/fixtures/unterminated_comment.inp +3 -0
  50. markinp-0.1.0/tests/fixtures/valid_comments.inp +4 -0
  51. markinp-0.1.0/tests/fixtures/valid_covariates.inp +3 -0
  52. markinp-0.1.0/tests/fixtures/valid_multi_group.inp +3 -0
  53. markinp-0.1.0/tests/fixtures/valid_single_group.inp +3 -0
  54. markinp-0.1.0/tests/test_build.py +96 -0
  55. markinp-0.1.0/tests/test_cli.py +120 -0
  56. markinp-0.1.0/tests/test_parse.py +77 -0
  57. markinp-0.1.0/tests/test_report.py +64 -0
  58. markinp-0.1.0/tests/test_validate.py +146 -0
  59. markinp-0.1.0/tests/test_write.py +46 -0
@@ -0,0 +1,31 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ name: ${{ matrix.os }} / Python ${{ matrix.python-version }}
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [ubuntu-latest, macos-latest, windows-latest]
16
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: actions/setup-python@v5
20
+ with:
21
+ python-version: ${{ matrix.python-version }}
22
+ - name: Install
23
+ run: python -m pip install -e ".[dev]"
24
+ - name: Lint
25
+ run: ruff check .
26
+ - name: Type check
27
+ run: mypy markinp
28
+ - name: Test
29
+ run: pytest
30
+ - name: Smoke-test the CLI
31
+ run: markinp --help
@@ -0,0 +1,57 @@
1
+ name: Release
2
+
3
+ # Publishes to PyPI via Trusted Publishing (OIDC) when a version tag is pushed
4
+ # or a GitHub Release is published. No API tokens are stored in the repo.
5
+ #
6
+ # One-time setup on PyPI: create the project's "pending publisher" pointing at
7
+ # owner: leonbzt
8
+ # repo: markinp
9
+ # workflow: release.yml
10
+ # environment: pypi
11
+
12
+ on:
13
+ push:
14
+ tags: ["v*"]
15
+ release:
16
+ types: [published]
17
+
18
+ permissions:
19
+ contents: read
20
+
21
+ jobs:
22
+ build:
23
+ name: Build distributions
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - uses: actions/setup-python@v5
28
+ with:
29
+ python-version: "3.12"
30
+ - name: Build sdist and wheel
31
+ run: |
32
+ python -m pip install --upgrade build
33
+ python -m build
34
+ - name: Check metadata
35
+ run: |
36
+ python -m pip install --upgrade twine
37
+ twine check dist/*
38
+ - uses: actions/upload-artifact@v4
39
+ with:
40
+ name: dist
41
+ path: dist/
42
+
43
+ publish:
44
+ name: Publish to PyPI
45
+ needs: build
46
+ runs-on: ubuntu-latest
47
+ environment:
48
+ name: pypi
49
+ url: https://pypi.org/p/markinp
50
+ permissions:
51
+ id-token: write # required for Trusted Publishing
52
+ steps:
53
+ - uses: actions/download-artifact@v4
54
+ with:
55
+ name: dist
56
+ path: dist/
57
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,31 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ *.egg
9
+
10
+ # Virtual environments
11
+ .venv/
12
+ venv/
13
+ env/
14
+
15
+ # Tooling caches
16
+ .pytest_cache/
17
+ .ruff_cache/
18
+ .mypy_cache/
19
+ .hypothesis/
20
+ .coverage
21
+ htmlcov/
22
+
23
+ # Editors / OS
24
+ .vscode/
25
+ .idea/
26
+ *.swp
27
+ .DS_Store
28
+
29
+ # Third-party MARK example data used for local corpus testing only.
30
+ # Kept out of the repo for licensing reasons; see README "Testing".
31
+ sample_data/
@@ -0,0 +1,7 @@
1
+ - id: markinp-validate
2
+ name: markinp validate
3
+ description: "Validate Program MARK encounter-history (.inp) files."
4
+ entry: markinp validate
5
+ language: python
6
+ files: '\.inp$'
7
+ types: [file]
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format is based on
4
+ [Keep a Changelog](https://keepachangelog.com/), and this project adheres to
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] — 2026-07-02
10
+
11
+ Initial release.
12
+
13
+ ### Added
14
+ - Core library: `parse`, `validate`, `inspect`, `build`, `write`.
15
+ - CLI (`markinp`) with `validate`, `inspect`, and `build` commands;
16
+ `validate` accepts multiple files and exits non-zero if any fail.
17
+ - Human-readable and `--json` (schema version 1) output for every command.
18
+ - Diagnostic taxonomy MK001–MK020 and MK900 (partial-support detection),
19
+ documented in `docs/error-codes.md`.
20
+ - Deterministic `.inp` writer and `parse -> write -> parse` round-trip stability.
21
+ - Distribution: PyPI Trusted Publishing workflow, a reusable GitHub Action,
22
+ a pre-commit hook, and a Bioconda recipe.
23
+
24
+ [Unreleased]: https://github.com/leonbzt/markinp/compare/v0.1.0...HEAD
25
+ [0.1.0]: https://github.com/leonbzt/markinp/releases/tag/v0.1.0
@@ -0,0 +1,23 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use markinp, please cite it as below."
3
+ title: "markinp: read, validate, and build Program MARK encounter-history (.inp) files"
4
+ type: software
5
+ authors:
6
+ - family-names: Botzenhardt
7
+ given-names: Leon
8
+ email: leon.botzenhardt@gmail.com
9
+ version: 0.1.0
10
+ license: MIT
11
+ repository-code: "https://github.com/leonbzt/markinp"
12
+ keywords:
13
+ - capture-recapture
14
+ - mark-recapture
15
+ - Program MARK
16
+ - encounter history
17
+ - ecology
18
+ abstract: >-
19
+ markinp is an independent, unofficial command-line utility and Python library
20
+ for reading, validating, and building Program MARK encounter-history (.inp)
21
+ files. It performs file I/O and validation only and does not fit models or
22
+ compute statistics. It is not affiliated with or endorsed by the authors of
23
+ Program MARK or RMark.
@@ -0,0 +1,261 @@
1
+ # CLAUDE.md
2
+
3
+ Guidance for Claude Code (and any AI assistant) working in this repository.
4
+ Read this file **and** `plan.md` before making changes. `plan.md` holds the
5
+ scope, milestones, and the full error taxonomy; this file holds the rules,
6
+ the domain primer, and the commands.
7
+
8
+ ---
9
+
10
+ ## 1. What this project is
11
+
12
+ `markio` is a small, well-tested command-line utility and Python library for
13
+ **reading, validating, and building Program MARK encounter-history (`.inp`)
14
+ files**. It removes the most common friction in the capture-recapture
15
+ workflow: hand-building `.inp` files in a text editor or Excel, and discovering
16
+ formatting mistakes only when MARK rejects the file with an unhelpful message.
17
+
18
+ It does **three** things and nothing more:
19
+
20
+ 1. **validate** an existing `.inp` file and report precise, actionable errors.
21
+ 2. **inspect** an `.inp` file and summarize its structure.
22
+ 3. **build** a valid `.inp` file from a tidy capture table (CSV).
23
+
24
+ Target users: wildlife ecologists, conservation biologists, and fisheries
25
+ scientists who use Program MARK (directly or via RMark). Most are not
26
+ programmers. **UX and error-message quality are the product**, not an add-on.
27
+
28
+ > **Disclaimer to preserve in the README:** `markio` is an independent,
29
+ > unofficial utility. It is not affiliated with, endorsed by, or maintained by
30
+ > the authors of Program MARK or RMark. "MARK" is referenced only to describe
31
+ > the file format it interoperates with.
32
+
33
+ ---
34
+
35
+ ## 2. Golden rules (non-negotiable)
36
+
37
+ 1. **Never reimplement MARK's statistics.** This tool does file I/O and
38
+ validation only. No model fitting, no likelihoods, no estimation. Ever.
39
+ 2. **Library-first.** Every capability lives in an importable function with a
40
+ clean signature and type hints. The CLI is a thin wrapper over the library
41
+ so that RMark users and pipelines can call the same code from Python.
42
+ 3. **Errors are the product.** Every problem is reported as a structured
43
+ *diagnostic* with a stable code, a severity, the line number, a plain-English
44
+ message, and an actionable hint. Never raise a raw traceback at the user.
45
+ 4. **Never silently change a user's data.** Report a diagnostic; only modify
46
+ data when the user explicitly asks (e.g. `--fix`, `--collapse`). Silent
47
+ "helpful" reinterpretation is forbidden — it is the exact failure mode we
48
+ are replacing.
49
+ 5. **Deterministic output.** Given the same input, output bytes are identical.
50
+ Sort predictably. This keeps diffs clean and makes CI usable.
51
+ 6. **Two output modes, always.** Human-readable by default; `--json` for
52
+ machines. Exit non-zero when errors are found so the tool works in CI.
53
+ 7. **Keep dependencies minimal.** It must install cleanly on Linux, macOS, and
54
+ Windows and be packageable for Bioconda. Prefer the standard library. Do not
55
+ add a heavy dependency without recording the reason in `plan.md`.
56
+ 8. **Respect the spec exactly.** When unsure about the `.inp` format, consult
57
+ Chapter 2 of the MARK "Gentle Introduction" book (the field's canonical
58
+ reference) and encode the rule as a test rather than guessing.
59
+ 9. **No GUI.** Command line and library only.
60
+
61
+ ---
62
+
63
+ ## 3. MARK `.inp` format primer (authoritative for this repo)
64
+
65
+ An `.inp` file is a plain-text file. Each record is one line, terminated by a
66
+ semicolon. Fields are separated by whitespace (spaces or tabs).
67
+
68
+ **Grammar of one record:**
69
+
70
+ ```
71
+ [/* comment */] HISTORY FREQ_1 [FREQ_2 ... FREQ_g] [COV_1 ... COV_c] ; [/* comment */]
72
+ ```
73
+
74
+ - **HISTORY** — the encounter history, one character per sampling occasion.
75
+ For the standard format: `1` = detected/captured, `0` = not.
76
+ - **FREQ_1 … FREQ_g** — one integer frequency **per group** `g`. The frequency
77
+ is the count of individuals sharing this history in that group. A record for
78
+ a single individual uses `1` in its group's column and `0` in the others.
79
+ Frequencies may be **negative** to denote losses on capture (removals).
80
+ - **COV_1 … COV_c** — optional numeric individual covariates, `c` of them.
81
+ **Covariates cannot have missing values.**
82
+ - **`;`** — terminates the record. **The single most common user error is a
83
+ missing semicolon.**
84
+ - **Comments** are delimited by `/* ... */` and may appear anywhere; they are
85
+ frequently used at the start of a line to label the individual.
86
+
87
+ **Worked example** — 2 groups (e.g. Male/Female) and 1 covariate (weight):
88
+
89
+ ```
90
+ /* ind 001 */ 1001 1 0 10;
91
+ /* ind 002 */ 1101 0 2 5;
92
+ 0101 3 1 6;
93
+ ```
94
+
95
+ Record 1: history `1001`, group-1 freq `1`, group-2 freq `0`, covariate `10`.
96
+ Record 2: history `1101`, group-1 freq `0`, group-2 freq `2`, covariate `5`.
97
+ Record 3: history `0101`, group-1 freq `3`, group-2 freq `1`, covariate `6`.
98
+
99
+ **Cross-record invariants:**
100
+
101
+ - Every HISTORY must have the **same length** (= number of occasions).
102
+ - Every record must have the **same number of frequency columns** (= number of
103
+ groups) and the **same number of covariate columns**.
104
+ - The data type, number of occasions, groups, covariates, and strata are set by
105
+ the user when the MARK project is created; they are usually **not** stored in
106
+ the file itself. `markio` therefore *infers* them and lets the user assert
107
+ expected values with flags for stricter checking.
108
+
109
+ **Data types (scope note):**
110
+
111
+ - v0 fully supports the **standard 0/1 encounter-history format** used by live
112
+ recapture (CJS / Jolly-Seber) and closed-captures models. This is the same
113
+ subset RMark's `convert.inp` handles.
114
+ - **Known-fate** and **dead-recovery (Brownie)** use paired `LDLD…` columns;
115
+ **multistrata** uses letters for states. v0 should *detect* these and validate
116
+ what it safely can (structural checks), but full support is a later milestone.
117
+ Do not pretend to fully validate a format we have not implemented — say so in
118
+ a diagnostic.
119
+
120
+ **Encoding & line endings:** MARK is a Windows-origin tool and input often comes
121
+ from Excel, so files are commonly UTF-8 or Latin-1 with CRLF line endings, and
122
+ may carry a BOM. Read robustly; normalize internally; flag odd encodings.
123
+
124
+ ---
125
+
126
+ ## 4. Architecture
127
+
128
+ Keep the library and the CLI strictly separated.
129
+
130
+ ```
131
+ markio/
132
+ __init__.py # version, public API re-exports
133
+ model.py # dataclasses: Dataset, EncounterHistory, DataType, Diagnostic, Severity
134
+ parse.py # .inp text -> Dataset (the reader/decoder); tracks line numbers
135
+ validate.py # Dataset (+ optional asserted params) -> list[Diagnostic]
136
+ build.py # tidy CSV (long/wide) -> Dataset (the builder)
137
+ write.py # Dataset -> .inp text (the encoder), deterministic
138
+ report.py # render list[Diagnostic] as human text or JSON
139
+ cli.py # Typer app; thin wrappers that call the functions above
140
+ tests/
141
+ fixtures/ # small valid and invalid .inp files, one concern each
142
+ ...
143
+ ```
144
+
145
+ **Rule:** `cli.py` contains no domain logic. It parses arguments, calls library
146
+ functions, and hands results to `report.py`. Anything the CLI can do must be
147
+ doable in three lines of Python via the library.
148
+
149
+ ---
150
+
151
+ ## 5. Tech stack & commands
152
+
153
+ - **Language:** Python 3.10+ (use modern typing: `list[str]`, `X | None`).
154
+ - **CLI:** [Typer](https://typer.tiangolo.com/) (auto-generates `--help`).
155
+ - **Core parsing/building:** standard library only (`csv`, `io`, `re`).
156
+ Do not require pandas in the core. (A pandas convenience layer at the CLI
157
+ edge is allowed only if justified in `plan.md`.)
158
+ - **Tests:** pytest. Optional `hypothesis` for round-trip property tests.
159
+ - **Lint + format:** [Ruff](https://docs.astral.sh/ruff/) (does both).
160
+ - **Type check:** mypy (strict) or pyright.
161
+ - **Build backend:** hatchling, via `pyproject.toml`.
162
+
163
+ Standard commands (assume a virtualenv; keep these working):
164
+
165
+ ```bash
166
+ pip install -e ".[dev]" # install package + dev extras
167
+ ruff format . # format
168
+ ruff check . --fix # lint
169
+ mypy markio # type check
170
+ pytest -q # run tests
171
+ markio --help # smoke-test the CLI
172
+ ```
173
+
174
+ CI must run `ruff check`, `mypy`, and `pytest` on a matrix of
175
+ {Linux, macOS, Windows} × {3.10, 3.11, 3.12, 3.13}. Windows is not optional —
176
+ our users are on Windows and our files carry CRLF.
177
+
178
+ ---
179
+
180
+ ## 6. Coding conventions
181
+
182
+ - Type-hint every function. No untyped public functions.
183
+ - Prefer small pure functions. Parsing returns data; it does not print.
184
+ - No bare `except:`. Catch specific exceptions and convert to diagnostics.
185
+ - User-facing strings are plain, specific, and kind. A good message names the
186
+ line, says what is wrong, and says what to do. Example:
187
+ `line 12: record has 3 frequency columns but the file uses 2 groups — remove one value or check for a stray space before the semicolon`.
188
+ - Docstrings on every public function: one-line summary, args, returns, and a
189
+ tiny example where it helps.
190
+ - Keep functions under ~40 lines where reasonable; if a function grows a
191
+ branch per diagnostic, that is a signal to move the rule into `validate.py`.
192
+
193
+ ---
194
+
195
+ ## 7. The diagnostic contract
196
+
197
+ All problems flow through one type (see `model.py`):
198
+
199
+ ```python
200
+ @dataclass(frozen=True)
201
+ class Diagnostic:
202
+ code: str # stable, e.g. "MK001"
203
+ severity: Severity # ERROR | WARNING | INFO
204
+ message: str # what is wrong, in plain English
205
+ hint: str # what to do about it
206
+ line: int | None # 1-based source line, when known
207
+ col: int | None = None
208
+ ```
209
+
210
+ - Codes are stable and documented in `plan.md`. Never renumber a released code.
211
+ - `validate()` returns `list[Diagnostic]`; it never exits or prints.
212
+ - `report.py` decides presentation. Exit codes: `0` if no ERROR-severity
213
+ diagnostics, `1` if any ERROR. `--strict` promotes WARNING to ERROR.
214
+ - `--json` emits a versioned object: `{"schema_version": 1, "diagnostics": [...], "summary": {...}}`.
215
+
216
+ ---
217
+
218
+ ## 8. Testing rules
219
+
220
+ - **Every diagnostic code has at least one fixture** that triggers it and a
221
+ test asserting the exact code appears (and clean files do not trigger it).
222
+ - **Round-trip tests:** `parse -> write -> parse` must be stable; and
223
+ `build -> validate` on generated files must produce zero errors.
224
+ - Fixtures live in `tests/fixtures/`, are tiny, and target one concern each.
225
+ Name them by intent, e.g. `missing_semicolon.inp`, `ragged_history.inp`.
226
+ - Prefer golden-file / snapshot tests for CLI output so UX regressions are
227
+ caught.
228
+ - A change to parsing or a format rule is not complete without a test.
229
+
230
+ ---
231
+
232
+ ## 9. What NOT to do
233
+
234
+ - Do not add model fitting, estimation, or anything statistical.
235
+ - Do not "auto-correct" a file unless the user passed an explicit fix flag.
236
+ - Do not invent format rules — verify against Chapter 2 of the MARK book.
237
+ - Do not break determinism (no unsorted sets in output, no timestamps in files).
238
+ - Do not add a dependency for convenience; justify every one in `plan.md`.
239
+ - Do not claim full validation of known-fate / dead-recovery / multistrata
240
+ until those milestones land; emit an honest "partial support" diagnostic.
241
+ - Do not build a GUI or a web app.
242
+
243
+ ---
244
+
245
+ ## 10. Distribution (keep these paths open from day one)
246
+
247
+ - Publish to **PyPI** via GitHub Actions Trusted Publishing.
248
+ - Then submit a **Bioconda** recipe (our users install via conda).
249
+ - Ship a **GitHub Action** and a **pre-commit hook** so `markio validate` can
250
+ guard other people's pipelines.
251
+ - Maintain `CITATION.cff` and semantic versioning.
252
+
253
+ ---
254
+
255
+ ## 11. How to work in this repo
256
+
257
+ 1. Read `plan.md`. Pick the current milestone; do not skip ahead.
258
+ 2. Make the smallest change that advances one task. Keep tests green.
259
+ 3. For any new format behavior: add a fixture, add a test, then implement.
260
+ 4. Update `plan.md` checkboxes and the error-taxonomy table when codes change.
261
+ 5. Never leave the CLI able to do something the library cannot.
markinp-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Leon Botzenhardt
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.