notignored-cli 0.1.2__py3-none-win_amd64.whl

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.
@@ -0,0 +1,416 @@
1
+ Metadata-Version: 2.4
2
+ Name: notignored-cli
3
+ Version: 0.1.2
4
+ Classifier: Development Status :: 4 - Beta
5
+ Classifier: Environment :: Console
6
+ Classifier: Intended Audience :: Developers
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Programming Language :: Rust
10
+ Classifier: Topic :: Software Development :: Quality Assurance
11
+ Classifier: Topic :: Software Development :: Testing
12
+ License-File: LICENSE
13
+ Summary: Extract lint and type-check suppression comments from source files natively and fast — no linter subprocesses.
14
+ Keywords: cli,lint,noqa,suppression,code-review
15
+ Home-Page: https://github.com/nickderobertis/notignored
16
+ Author: Nick DeRobertis
17
+ License: MIT
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
20
+ Project-URL: Homepage, https://github.com/nickderobertis/notignored
21
+ Project-URL: Repository, https://github.com/nickderobertis/notignored
22
+
23
+ # notignored
24
+
25
+ Find every lint and type-check suppression comment in a codebase — natively, and
26
+ fast.
27
+
28
+ ```console
29
+ $ notignored src/
30
+ src/app.py:3:12 ruff F401 (line) -- re-exported for the public API
31
+ src/app.py:5:58 ruff E501 (line) -- long wrapped URL
32
+ src/app.py:10:17 ruff * (line)
33
+ src/vendored.py:1:1 ruff E501 (file) -- vendored upstream, not ours to reformat
34
+ notignored: 4 ignores in 2 files
35
+ ```
36
+
37
+ ## Why
38
+
39
+ Suppression comments are where lint and type-check debt hides. A `# noqa` costs
40
+ one line to add and scrolls past review unremarked — especially in agent-written
41
+ code, where silencing a rule is often easier than fixing it.
42
+
43
+ `notignored` turns every suppression into a first-class, queryable record: which
44
+ tool, which rules, the stated reason, and exactly where it lives. That makes a
45
+ high-level review of a large change possible — you can see the bypasses and their
46
+ justifications without reading every line.
47
+
48
+ It parses the directives itself and **never invokes the tool whose rule is being
49
+ silenced**, so scanning a tree costs a read and a scan instead of ten linter
50
+ startups. That is what makes it cheap enough to run on every pull request.
51
+
52
+ ## Install
53
+
54
+ ```console
55
+ # From PyPI or npm — both ship the prebuilt binary, so no Rust toolchain is
56
+ # needed and nothing is compiled at install time:
57
+ pip install notignored-cli
58
+ npm install -g notignored-cli
59
+
60
+ # Or without installing at all:
61
+ npx notignored-cli src/
62
+
63
+ # Cross-platform, from source (Linux, macOS, Windows):
64
+ cargo install --git https://github.com/nickderobertis/notignored --locked
65
+
66
+ # Or a prebuilt binary (Linux, macOS, and Windows under a POSIX shell):
67
+ curl -fsSL https://raw.githubusercontent.com/nickderobertis/notignored/main/scripts/install.sh | sh
68
+ ```
69
+
70
+ All four install the same `notignored` command. The `notignored-cli`
71
+ distributions carry the release binary for your platform — a wheel per platform
72
+ on PyPI, a package per platform on npm, picked automatically — so they are the
73
+ fastest path on a CI image with no Rust toolchain, and the only one that works
74
+ where github.com is blocked but the package registries are not. Prebuilt targets
75
+ are Linux (x64, arm64), macOS (x64, arm64), and Windows (x64); anywhere else,
76
+ `cargo install` builds from source.
77
+
78
+ The installer honours `NOTIGNORED_VERSION` / `NOTIGNORED_INSTALL_DIR` (or the
79
+ `--version` / `--to` flags), verifies the archive against the SHA-256 checksum
80
+ published beside it, and refuses to install a binary it cannot verify. Every
81
+ tagged release attaches per-platform archives built on native runners.
82
+
83
+ In CI there is nothing to install: the [GitHub Action](#on-a-pull-request-the-github-action)
84
+ fetches the release binary itself and posts what the pull request added.
85
+
86
+ ```yaml
87
+ - uses: nickderobertis/notignored@main
88
+ ```
89
+
90
+ ## Try it
91
+
92
+ [`examples/`](examples) holds a handful of tiny files — Python, TypeScript,
93
+ shell, Rust — each carrying the kind of suppression, and the kind of reason,
94
+ real code collects. Point the binary at them:
95
+
96
+ ```console
97
+ $ notignored examples/
98
+ examples/api_client.py:3:20 mypy import-untyped (line) -- the vendored SDK ships no type stubs
99
+ examples/api_client.py:4:28 ruff F401 (line) -- re-exported so callers can configure retries
100
+ examples/deploy.sh:8:1 shellcheck SC2086 (next-line) -- the flags file is ours, and has to split into separate arguments
101
+ examples/deploy.sh:13:3 llmlint tool_output_is_signal (file) -- example input the README quickstart scans, not a script this project runs
102
+ examples/retry.rs:6:1 rust dead_code (next-line) -- the scheduler starts calling this once backoff lands
103
+ examples/widget.ts:6:3 eslint no-console (next-line) -- the mount path is traced in production
104
+ examples/widget.ts:9:3 typescript * (next-line) -- the vendored analytics global is declared without its options bag
105
+ notignored: 7 ignores in 4 files
106
+ ```
107
+
108
+ Seven suppressions, seven tools, one pass over four files — no linter was run. Each
109
+ line is `path:line:column tool rules (scope) -- reason`; `--format json` gives
110
+ the same records as the envelope [below](#output), and `--format markdown` gives
111
+ the comment the action posts. That block is checked against the real binary by
112
+ `tests/e2e/examples.rs`, so it is output, not an illustration.
113
+
114
+ ## Usage
115
+
116
+ ```
117
+ notignored [PATHS...] [--format human|json|markdown] [--tool NAME]... [--fail-if-found]
118
+ [--diff [--diff-base REF]] [--github-repo OWNER/REPO] [--github-sha SHA]
119
+ ```
120
+
121
+ - `PATHS` — files and/or directories. Directories are walked recursively,
122
+ honouring `.gitignore`. Defaults to `.`.
123
+ - `--format` — `human` (default), `json`, or `markdown` (a pull-request comment
124
+ body; see the action below).
125
+ - `--tool` — only report this tool; repeat to allow several. Omit for all.
126
+ - `--fail-if-found` — exit 1 when any suppression is reported.
127
+ - `--diff` — report only the suppressions the change added (see below).
128
+ - `--diff-base` — the git revision or range `--diff` compares against.
129
+ - `--github-repo` / `--github-sha` — the `owner/repo` and commit the `markdown`
130
+ format builds its permalinks from.
131
+
132
+ ### Reviewing a pull request
133
+
134
+ A reviewer cares about the suppressions a change *introduces*, not the
135
+ inventory it inherited. `--diff` reports only those: a directive is new when the
136
+ diff added at least one of the lines it occupies.
137
+
138
+ ```console
139
+ $ notignored --diff --diff-base main --fail-if-found
140
+ src/app.py:42:20 ruff E501 (line) -- long wrapped URL
141
+ notignored: 1 ignore in 1 file
142
+ ```
143
+
144
+ - Bare `--diff` compares the work tree — staged *and* unstaged — against `HEAD`.
145
+ - `--diff-base REF` takes any git revision or range. A **plain ref** is compared
146
+ from the **merge base**, the way a pull request's "Files changed" is, so
147
+ commits that landed on the base branch after this one forked are never
148
+ reported as this branch's own. An explicit `A..B` **range** is passed to git
149
+ as-is, two-dot semantics and all. These are llmlint's `--diff` / `--diff-base`
150
+ semantics exactly.
151
+ - `PATHS` still narrow the result: `notignored --diff --diff-base main src/`
152
+ reports the new suppressions under `src/` only. An empty intersection is a
153
+ clean exit 0.
154
+ - Only the files the change touched are read, so a diff run stays fast on a
155
+ large repository. Files the change deleted are skipped; a renamed file reports
156
+ what the change added to it, not the lines that merely moved.
157
+ - Git names a file in bytes and a report names it with a string, so a path that
158
+ is not valid UTF-8 has no faithful spelling here. It becomes an `errors` entry
159
+ (and exit 2) rather than a file quietly dropped from the review — the lossy
160
+ spelling would name a file that does not exist.
161
+
162
+ `--diff` shells out to `git` — infrastructure, not one of the linters whose
163
+ directives are parsed natively — so it needs `git` on `PATH` and a work tree.
164
+
165
+ ### On a pull request: the GitHub Action
166
+
167
+ The action posts one sticky comment naming every suppression the pull request
168
+ added, with its stated reason and a link to the line. It edits that same comment
169
+ on each push instead of adding another, and on a pull request that adds none it
170
+ posts nothing at all.
171
+
172
+ ```yaml
173
+ # .github/workflows/notignored.yml
174
+ name: notignored
175
+
176
+ on:
177
+ pull_request:
178
+
179
+ permissions:
180
+ contents: read
181
+ pull-requests: write
182
+
183
+ jobs:
184
+ suppressions:
185
+ runs-on: ubuntu-latest
186
+ steps:
187
+ - uses: actions/checkout@v4
188
+ with:
189
+ fetch-depth: 0 # the base branch has to be fetched to diff against it
190
+ - uses: nickderobertis/notignored@main
191
+ ```
192
+
193
+ | Input | Default | Meaning |
194
+ | --- | --- | --- |
195
+ | `github-token` | `${{ github.token }}` | Token used to upsert the comment. Needs `pull-requests: write`. |
196
+ | `diff-base` | the pull request's base branch | Any git revision or range, as `--diff-base` takes. |
197
+ | `paths` | the whole repository | Whitespace-separated files and directories to scan. |
198
+ | `version` | `latest` | A release tag such as `v0.1.0`, or `local` to build the action's own source with `cargo`. |
199
+
200
+ It exposes `count` (how many suppressions the change added) and `report-path`
201
+ (the JSON report), so a later step can fail the build, upload the report, or
202
+ gate on a threshold:
203
+
204
+ ```yaml
205
+ - uses: nickderobertis/notignored@main
206
+ id: notignored
207
+ - if: steps.notignored.outputs.count != '0'
208
+ run: echo "this change adds ${{ steps.notignored.outputs.count }} suppression(s)"
209
+ ```
210
+
211
+ The steps are `bash`, so the action runs on the Linux and macOS runners; it needs
212
+ `jq` and the `gh` CLI (both preinstalled there), plus `cargo` when
213
+ `version: local`.
214
+
215
+ `--format markdown` renders exactly the body the action posts, so it can be
216
+ previewed locally:
217
+
218
+ ```console
219
+ $ notignored --diff --diff-base main --format markdown \
220
+ --github-repo nickderobertis/notignored --github-sha "$(git rev-parse HEAD)"
221
+ ```
222
+
223
+ Both permalink flags are optional; without them each location renders as plain
224
+ `path:line` text. When a body names fewer than four suppressions, each one also
225
+ carries the source line with two lines of context on either side.
226
+
227
+ ### Exit codes
228
+
229
+ | Code | Meaning |
230
+ | --- | --- |
231
+ | `0` | The scan completed. |
232
+ | `1` | `--fail-if-found` was given and at least one suppression was reported. |
233
+ | `2` | The scan could not complete — an unreadable path or file, or a bad argument. |
234
+
235
+ Findings go to stdout; the summary and any errors go to stderr, so
236
+ `notignored --format json > report.json` captures only the report. A downstream
237
+ consumer that stops reading (`| head`, `| grep -q`) is not an error: the scan's
238
+ own verdict still decides the exit code.
239
+
240
+ ## Output
241
+
242
+ The `json` format emits the full report envelope:
243
+
244
+ ```json
245
+ {
246
+ "version": 1,
247
+ "ignores": [
248
+ {
249
+ "tool": "ruff",
250
+ "scope": "line",
251
+ "rules": ["E501"],
252
+ "reason": "long wrapped URL",
253
+ "path": "src/app.py",
254
+ "line": 12,
255
+ "end_line": 12,
256
+ "column": 20,
257
+ "raw": "# noqa: E501 # long wrapped URL",
258
+ "suppressed": { "start_line": 12, "end_line": 12 }
259
+ }
260
+ ],
261
+ "errors": []
262
+ }
263
+ ```
264
+
265
+ - `scope` — `line`, `next-line`, `file`, or `block`.
266
+ - `rules` — rule names/codes exactly as written. `[]` means a blanket
267
+ suppression of every rule the tool would apply (rendered as `*` in the human
268
+ format).
269
+ - `reason` — the stated justification, or `null`. Taken from the tool's native
270
+ reason syntax where one exists, otherwise from the trailing comment on the
271
+ directive line; whitespace is collapsed to single spaces.
272
+ - `line` / `end_line` / `column` — 1-based.
273
+ - `suppressed` — the best-effort range the directive silences. `end_line` is
274
+ `null` when it runs to end-of-file or is unterminated.
275
+ - `errors` — files that could not be read. Never a panic.
276
+
277
+ `version` is the envelope version; it changes only when the shape does.
278
+
279
+ ## Supported tools
280
+
281
+ | Tool | Directives |
282
+ | --- | --- |
283
+ | `eslint` | `// eslint-disable-line rule`, `// eslint-disable-next-line rule -- reason`, `/* eslint-disable rule -- reason */` … `/* eslint-enable rule */` |
284
+ | `biome` | `// biome-ignore lint/group/rule: reason`, `// biome-ignore-all lint/group/rule: reason`, `// biome-ignore-start lint/group/rule: reason` … `// biome-ignore-end lint/group/rule: reason` |
285
+ | `ruff` | `# noqa`, `# noqa: E501, F401`, `# ruff: noqa`, `# ruff: noqa: E501` |
286
+ | `typescript` | `// @ts-ignore`, `// @ts-expect-error reason`, `/* @ts-ignore */`, `// @ts-nocheck` |
287
+ | `mypy` | `# type: ignore`, `# type: ignore[arg-type, index]`, `# mypy: ignore-errors`, `# mypy: disable-error-code="arg-type"` |
288
+ | `pyright` | `# pyright: ignore`, `# pyright: ignore[reportArgumentType]`, `# pyright: reportMissingImports=false` |
289
+ | `ty` | `# ty: ignore`, `# ty: ignore[invalid-argument-type]` |
290
+ | `rust` | `#[allow(dead_code)]`, `#[allow(clippy::needless_collect, dead_code)]`, `#[expect(dead_code, reason = "…")]`, `#![allow(…)]`, `#![expect(…, reason = "…")]` |
291
+ | `shellcheck` | `# shellcheck disable=SC2086`, `# shellcheck disable=SC2086,SC2046`, `# shellcheck disable=SC2000-SC2100`, `# shellcheck disable=all`, `# shellcheck disable=SC2086 # reason` |
292
+ | `llmlint` | `ignore[rule, …] reason`, `ignore-file[rule, …] reason`, `ignore-block[rule, …] reason` … `ignore-end[rule, …]` — each written after the `llmlint` keyword and a colon, in the host language's comment syntax |
293
+
294
+ Scope follows each tool's own rules, not a house convention:
295
+
296
+ - **rust** — an outer attribute is `next-line` and its `suppressed` range runs
297
+ through the end of the item it annotates; an inner `#![…]` is `file`. A
298
+ `reason = "…"` is the record's reason, even when the string wraps.
299
+ - **shellcheck** — a directive above the first command is `file`; anywhere else
300
+ it is `next-line`. A directive ShellCheck itself rejects (trailing prose with
301
+ no `#`, or one placed after a command) is reported by neither tool.
302
+ - **typescript** — the parity claim is pinned to one compiler: the `typescript`
303
+ version in `tests/js-toolchain/package.json` — **7.0.2**, the Go port — which
304
+ is what `tests/e2e/typescript_parity.rs` drives. The 5.x compiler is a
305
+ separate implementation of the same directives and is not guaranteed to read
306
+ every form the same way, so the claim here is parity with the pinned compiler
307
+ rather than with every `tsc` ever shipped.
308
+ - **llmlint** — `ignore` is `line`, `ignore-file` is `file`, and
309
+ `ignore-block` … `ignore-end` is one `block` record spanning both directives.
310
+ A block left unclosed keeps a null `suppressed.end_line` and adds an `errors`
311
+ entry.
312
+
313
+ Each tool's own reason syntax is what gets captured: ESLint's ` -- description`,
314
+ Biome's mandatory `: explanation`, ruff's trailing `# comment`, and — for
315
+ TypeScript, which defines no separator — whatever text trails the directive. A
316
+ directive that lists no rules is a blanket suppression (`rules: []`).
317
+
318
+ Scope follows the tool rather than the syntax. `// eslint-disable-line` is
319
+ `line`; `// eslint-disable-next-line`, `// biome-ignore` and
320
+ `// @ts-expect-error` are `next-line`; `# ruff: noqa`, `// biome-ignore-all` and
321
+ `// @ts-nocheck` are `file`; and the delimited pairs
322
+ (`/* eslint-disable */` … `/* eslint-enable */`,
323
+ `// biome-ignore-start` … `// biome-ignore-end`) are `block`, running to
324
+ end-of-file with `suppressed.end_line: null` when they are never closed.
325
+
326
+ Adding one is four touch points: a module under `src/tools/`, one line in
327
+ `src/tools/mod.rs::registry()`, one row above, and a directive in
328
+ `tests/fixtures/polyglot/`. `tests/tools_contract.rs` fails the build if a row
329
+ here and the registered parsers disagree, and `tests/e2e/polyglot.rs` fails it
330
+ if a registered tool is missing from that fixture tree.
331
+
332
+ ## Where a directive reaches, and who honours it
333
+
334
+ Every `# type: ignore` and `# pyright: ignore` is `line`-scoped; the module-wide
335
+ forms are mypy's two `# mypy:` config comments and pyright's rule override; for
336
+ ty, where the comment sits is the scope; and a Rust attribute reaches to the end
337
+ of the item it annotates:
338
+
339
+ | Source | Reported as |
340
+ | --- | --- |
341
+ | `f(x) # type: ignore` | `mypy`, `line` |
342
+ | `# mypy: ignore-errors` on its own line | `mypy`, `file` |
343
+ | `# mypy: disable-error-code="arg-type"` on its own line | `mypy`, `file` |
344
+ | `f(x) # pyright: ignore` | `pyright`, `line` |
345
+ | `# pyright: reportMissingImports=false` | `pyright`, `file` |
346
+ | `f(x) # ty: ignore` | `ty`, `line` |
347
+ | `# ty: ignore` above every statement | `ty`, `file` |
348
+ | `# ty: ignore` on its own line in the body | `ty`, `next-line` |
349
+ | `#[allow(dead_code)]` above an item | `rust`, `next-line`, `suppressed` through the item's last line |
350
+ | `#[expect(dead_code, reason = "…")]` above an item | `rust`, `next-line`, `reason` from the attribute |
351
+ | `#![allow(dead_code)]` at the top of the file | `rust`, `file` |
352
+
353
+ A Rust attribute's `scope` is `next-line` — that is where the item it annotates
354
+ starts, and where a reviewer has to look — while its `suppressed` range covers
355
+ the whole item, however many lines that item runs to. An inner `#![…]` attribute
356
+ exempts the file it opens.
357
+
358
+ Pyright's `<rule>=<value>` override is reported only for the two values that
359
+ switch a rule off, `false` and `none`; `true`, `error`, `warning`, and
360
+ `information` turn a rule on or move its severity, so they are configuration
361
+ rather than suppression. Pyright reads the rest of that line as its own item
362
+ list, so the form can carry no reason — and a comment it refuses (a trailing
363
+ `# why`, a value outside those six, or a directive that does not open the
364
+ comment) silences nothing and is not reported.
365
+
366
+ Several tools honour a directive they did not invent: pyright and ty both act on
367
+ mypy's `# type: ignore`, and ruff, pyright, and ty all act on one that does not
368
+ open its comment (mypy does not). A directive is reported **once, under the tool
369
+ whose syntax it is** — `# type: ignore` is one `mypy` record, not three — so a
370
+ count of records is a count of suppressions written, not of checkers affected.
371
+
372
+ One line can still carry directives for several tools, and each record covers
373
+ **its own directive only**. Given
374
+
375
+ ```python
376
+ import legacy # type: ignore[import-not-found] # no stubs published # noqa: F401 # imported for its side effects
377
+ ```
378
+
379
+ the `mypy` record's `raw` stops at `# no stubs published` and its `reason` is
380
+ `"no stubs published"`; the `ruff` record's `raw` starts at `# noqa: F401` and
381
+ its `reason` is `"imported for its side effects"`. A record's `raw` and `reason`
382
+ always end where the next tool's directive begins, so one tool's live suppression
383
+ can never be filed as another's justification.
384
+
385
+ `# pyright: basic` and `# pyright: strict` switch pyright's type-checking mode
386
+ rather than silencing a diagnostic, and are deliberately not reported.
387
+
388
+ ## How it works
389
+
390
+ Source is scanned once per file by a language-aware comment extractor
391
+ (`src/comments.rs`) that understands `#` comments, `//` line comments, multi-line
392
+ `/* … */` blocks (nested, for Rust), Rust attributes, and the punctuation that
393
+ delimits a Rust item — and that knows a string literal when it sees one, so
394
+ `MESSAGE = "# noqa: E501"` is never reported. Tool parsers consume that
395
+ extraction; they never re-scan raw lines.
396
+
397
+ ## Development
398
+
399
+ ```console
400
+ just bootstrap # from a clean clone
401
+ just check # the full gate: format, clippy, tests + coverage, docs
402
+ just --list # everything else
403
+ ```
404
+
405
+ `just check` runs the end-to-end suite, which drives the compiled binary as a
406
+ subprocess and the **real, pinned** tools — `ruff`, `mypy`, `pyright`, `ty`,
407
+ `shellcheck`, and `llmlint` (see the `.<tool>-version` files), `eslint` /
408
+ `biome` / `tsc` (see `tests/js-toolchain/package.json`), plus the pinned
409
+ toolchain's own `rustc` and `clippy-driver` — to prove that what `notignored`
410
+ reports is what those tools actually suppress. `just bootstrap` installs them
411
+ all under `.dev/`; it needs `uv` and Node.js 20+ on `PATH`.
412
+
413
+ ## License
414
+
415
+ MIT — see [LICENSE](LICENSE).
416
+
@@ -0,0 +1,6 @@
1
+ notignored_cli-0.1.2.data/scripts/notignored.exe,sha256=gdpmo7OWJ9H9NM3-TuWD-Fbu-uxG8I_psDl9ECpYEbA,2377216
2
+ notignored_cli-0.1.2.dist-info/METADATA,sha256=7RWlBW4iPlmTIktsngGpYEHTjZRcCxgNWYP4M-HJubg,19703
3
+ notignored_cli-0.1.2.dist-info/WHEEL,sha256=2zDlIYIdD4m4N3p5DVEG3iJhGLdhsBQgdH-FqVkAur8,94
4
+ notignored_cli-0.1.2.dist-info/licenses/LICENSE,sha256=JeBGdcXIUkoTZEHhkKd-FmKgdgumkH509Ob1QWJomVE,1072
5
+ notignored_cli-0.1.2.dist-info/sboms/notignored.cyclonedx.json,sha256=zPK7cw78freeESThSQYQF4ws8SkCFssvooQd6XwAAE4,53304
6
+ notignored_cli-0.1.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.14.1)
3
+ Root-Is-Purelib: false
4
+ Tag: py3-none-win_amd64
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nick DeRobertis
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.