sphinx-rustdoc-postprocess 0.1.0__py3-none-any.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,320 @@
1
+ """Post-process sphinxcontrib-rust RST output via pandoc.
2
+
3
+ sphinxcontrib-rust dumps rustdoc (markdown) doc-comments verbatim into RST
4
+ directive bodies. This extension runs *after* sphinxcontrib_rust's
5
+ ``builder-inited`` handler (priority 600 > default 500), walks the generated
6
+ ``.rst`` files, extracts the markdown content blocks, and converts them to
7
+ proper RST through ``pandoc -f markdown -t rst``.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import re
13
+ import subprocess
14
+ import textwrap
15
+ from pathlib import Path
16
+
17
+ from sphinx.application import Sphinx
18
+ from sphinx.util import logging
19
+
20
+ from sphinx_rustdoc_postprocess._version import ( # noqa: F401
21
+ __version__,
22
+ __version_tuple__,
23
+ )
24
+
25
+ _log = logging.getLogger(__name__)
26
+
27
+ # Matches an indented markdown fenced code block:
28
+ # <indent>```lang
29
+ # ...code...
30
+ # <indent>```
31
+ _FENCE_RE = re.compile(
32
+ r"^(?P<indent>[ ]+)```(?P<lang>\w*)\s*\n"
33
+ r"(?P<body>.*?)"
34
+ r"^(?P=indent)```[ ]*$",
35
+ re.MULTILINE | re.DOTALL,
36
+ )
37
+
38
+ # Matches an indented markdown table (header + separator + rows).
39
+ _TABLE_RE = re.compile(
40
+ r"^(?P<indent>[ ]+)\|.+\|[ ]*\n" # header row
41
+ r"(?P=indent)\|[-| :]+\|[ ]*\n" # separator row
42
+ r"(?:(?P=indent)\|.+\|[ ]*\n)+", # data rows
43
+ re.MULTILINE,
44
+ )
45
+
46
+ # Matches an indented markdown ATX heading (## Heading).
47
+ _HEADING_RE = re.compile(
48
+ r"^(?P<indent>[ ]+)(?P<hashes>#{1,6})[ ]+(?P<text>.+)$",
49
+ re.MULTILINE,
50
+ )
51
+
52
+ # Matches markdown inline code (`code`) that is NOT already double-backtick RST.
53
+ # Handles the common case where `code`<letter> breaks RST inline markup rules.
54
+ # Excludes <> so RST links (`text <url>`_) are not mangled.
55
+ _INLINE_CODE_RE = re.compile(
56
+ r"(?<!`)(`)((?!`)(?:[^`\n<>])+)\1(?!`)",
57
+ )
58
+
59
+ # Matches markdown hyperlinks: [text](url)
60
+ _MD_LINK_RE = re.compile(
61
+ r"\[(?P<text>[^\[\]]+)\]\((?P<url>https?://[^)]+)\)",
62
+ )
63
+
64
+ # Matches rustdoc intra-doc links: [`code`] or [``code``] (without a URL part).
65
+ _INTRADOC_LINK_RE = re.compile(
66
+ r"\[`{1,2}(?P<name>[^`\]]+)`{1,2}\](?!\()",
67
+ )
68
+
69
+
70
+ def _pandoc(markdown: str) -> str:
71
+ """Convert a markdown fragment to RST via pandoc.
72
+
73
+ Parameters
74
+ ----------
75
+ markdown : str
76
+ The markdown text to convert.
77
+
78
+ Returns
79
+ -------
80
+ str
81
+ The converted RST text, or the original markdown if pandoc fails.
82
+ """
83
+ result = subprocess.run(
84
+ ["pandoc", "-f", "markdown-smart", "-t", "rst", "--wrap=none"],
85
+ input=markdown,
86
+ capture_output=True,
87
+ text=True,
88
+ timeout=10,
89
+ )
90
+ if result.returncode != 0:
91
+ _log.warning("[rustdoc_postprocess] pandoc failed: %s", result.stderr)
92
+ return markdown
93
+ return result.stdout
94
+
95
+
96
+ def _convert_fences(content: str) -> str:
97
+ """Convert markdown code fences to RST code-block directives.
98
+
99
+ Parameters
100
+ ----------
101
+ content : str
102
+ RST file content potentially containing indented markdown code fences.
103
+
104
+ Returns
105
+ -------
106
+ str
107
+ Content with code fences replaced by ``.. code-block::`` directives.
108
+ """
109
+
110
+ def _replace(m: re.Match) -> str:
111
+ indent = m.group("indent")
112
+ lang = m.group("lang") or "none"
113
+ body = m.group("body")
114
+
115
+ body_indent = indent + " "
116
+ lines = []
117
+ for line in body.split("\n"):
118
+ stripped = line.rstrip()
119
+ if stripped:
120
+ if stripped.startswith(indent):
121
+ stripped = stripped[len(indent) :]
122
+ lines.append(body_indent + stripped)
123
+ else:
124
+ lines.append("")
125
+ while lines and not lines[-1].strip():
126
+ lines.pop()
127
+
128
+ body_text = "\n".join(lines)
129
+ return f"{indent}.. code-block:: {lang}\n\n{body_text}\n"
130
+
131
+ return _FENCE_RE.sub(_replace, content)
132
+
133
+
134
+ def _convert_tables(content: str) -> str:
135
+ """Convert markdown tables to RST tables via pandoc.
136
+
137
+ Parameters
138
+ ----------
139
+ content : str
140
+ RST file content potentially containing indented markdown tables.
141
+
142
+ Returns
143
+ -------
144
+ str
145
+ Content with markdown tables replaced by RST grid tables.
146
+ """
147
+
148
+ def _replace(m: re.Match) -> str:
149
+ indent = m.group("indent")
150
+ table_md = textwrap.dedent(m.group(0))
151
+ rst = _pandoc(table_md).rstrip("\n")
152
+ lines = [indent + line if line.strip() else "" for line in rst.split("\n")]
153
+ return "\n".join(lines) + "\n"
154
+
155
+ return _TABLE_RE.sub(_replace, content)
156
+
157
+
158
+ def _convert_links(content: str) -> str:
159
+ """Convert markdown links to RST.
160
+
161
+ Parameters
162
+ ----------
163
+ content : str
164
+ RST file content potentially containing markdown-style links.
165
+
166
+ Returns
167
+ -------
168
+ str
169
+ Content with ``[text](url)`` converted to ```text <url>`_`` and
170
+ rustdoc intra-doc links ``[`name`]`` converted to ````name````.
171
+ """
172
+
173
+ def _process_line(line: str) -> str:
174
+ stripped = line.lstrip()
175
+ if stripped.startswith("..") or stripped.startswith(":"):
176
+ return line
177
+ line = _MD_LINK_RE.sub(r"`\g<text> <\g<url>>`_", line)
178
+ line = _INTRADOC_LINK_RE.sub(r"``\g<name>``", line)
179
+ return line
180
+
181
+ return "\n".join(_process_line(line) for line in content.split("\n"))
182
+
183
+
184
+ def _convert_inline_code(content: str) -> str:
185
+ """Convert markdown inline code to RST double-backtick literals.
186
+
187
+ Parameters
188
+ ----------
189
+ content : str
190
+ RST file content potentially containing markdown single-backtick code.
191
+
192
+ Returns
193
+ -------
194
+ str
195
+ Content with single-backtick code converted to double-backtick literals.
196
+ """
197
+
198
+ def _process_line(line: str) -> str:
199
+ stripped = line.lstrip()
200
+ if stripped.startswith("..") or stripped.startswith(":"):
201
+ return line
202
+ return _INLINE_CODE_RE.sub(r"``\2``", line)
203
+
204
+ return "\n".join(_process_line(line) for line in content.split("\n"))
205
+
206
+
207
+ def _convert_headings(content: str) -> str:
208
+ """Convert markdown ATX headings to bold labels.
209
+
210
+ RST section headings cannot appear inside directive bodies, so we
211
+ convert ``## Heading`` to ``**Heading**`` which renders as bold.
212
+
213
+ Parameters
214
+ ----------
215
+ content : str
216
+ RST file content potentially containing markdown ATX headings.
217
+
218
+ Returns
219
+ -------
220
+ str
221
+ Content with headings replaced by bold text.
222
+ """
223
+
224
+ def _replace(m: re.Match) -> str:
225
+ indent = m.group("indent")
226
+ text = m.group("text").strip()
227
+ return f"{indent}**{text}**"
228
+
229
+ return _HEADING_RE.sub(_replace, content)
230
+
231
+
232
+ def postprocess_rst_files(app: Sphinx) -> None:
233
+ """Walk generated RST files and convert markdown fragments.
234
+
235
+ Scans the directory specified by the ``rustdoc_postprocess_rst_dir``
236
+ config value (relative to ``app.srcdir``) for ``.rst`` files and applies
237
+ all markdown-to-RST conversions in sequence.
238
+
239
+ Parameters
240
+ ----------
241
+ app : Sphinx
242
+ The Sphinx application instance.
243
+ """
244
+ rst_dir = Path(app.srcdir) / app.config.rustdoc_postprocess_rst_dir
245
+ if not rst_dir.exists():
246
+ return
247
+
248
+ for rst_file in sorted(rst_dir.rglob("*.rst")):
249
+ original = rst_file.read_text(encoding="utf-8")
250
+ converted = original
251
+ converted = _convert_fences(converted)
252
+ converted = _convert_links(converted)
253
+ converted = _convert_tables(converted)
254
+ converted = _convert_headings(converted)
255
+ converted = _convert_inline_code(converted)
256
+ if converted != original:
257
+ _log.info(
258
+ "[rustdoc_postprocess] Converted markdown in %s",
259
+ rst_file.relative_to(app.srcdir),
260
+ )
261
+ rst_file.write_text(converted, encoding="utf-8")
262
+
263
+
264
+ def inject_rust_toctree(app: Sphinx) -> None:
265
+ """Append a toctree snippet to a target RST file.
266
+
267
+ Reads the ``rustdoc_postprocess_toctree_target`` and
268
+ ``rustdoc_postprocess_toctree_rst`` config values. If either is empty,
269
+ the injection is skipped.
270
+
271
+ Parameters
272
+ ----------
273
+ app : Sphinx
274
+ The Sphinx application instance.
275
+ """
276
+ target = app.config.rustdoc_postprocess_toctree_target
277
+ toctree_rst = app.config.rustdoc_postprocess_toctree_rst
278
+ if not target or not toctree_rst:
279
+ return
280
+
281
+ target_path = Path(app.srcdir) / target
282
+ if not target_path.exists():
283
+ return
284
+
285
+ content = target_path.read_text(encoding="utf-8")
286
+ if toctree_rst.strip() in content:
287
+ return
288
+
289
+ target_path.write_text(content.rstrip("\n") + "\n" + toctree_rst, encoding="utf-8")
290
+ _log.info("[rustdoc_postprocess] Injected toctree into %s", target)
291
+
292
+
293
+ def _on_builder_inited(app: Sphinx) -> None:
294
+ """builder-inited callback: postprocess then inject toctree."""
295
+ postprocess_rst_files(app)
296
+ inject_rust_toctree(app)
297
+
298
+
299
+ def setup(app: Sphinx) -> dict:
300
+ """Register the extension with Sphinx.
301
+
302
+ Parameters
303
+ ----------
304
+ app : Sphinx
305
+ The Sphinx application instance.
306
+
307
+ Returns
308
+ -------
309
+ dict
310
+ Extension metadata with version and parallel safety flags.
311
+ """
312
+ app.add_config_value("rustdoc_postprocess_rst_dir", "crates", "env")
313
+ app.add_config_value("rustdoc_postprocess_toctree_target", "", "env")
314
+ app.add_config_value("rustdoc_postprocess_toctree_rst", "", "env")
315
+ app.connect("builder-inited", _on_builder_inited, priority=600)
316
+ return {
317
+ "version": __version__,
318
+ "parallel_read_safe": True,
319
+ "parallel_write_safe": True,
320
+ }
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '0.1.0'
32
+ __version_tuple__ = version_tuple = (0, 1, 0)
33
+
34
+ __commit_id__ = commit_id = None
File without changes
@@ -0,0 +1,258 @@
1
+ Metadata-Version: 2.4
2
+ Name: sphinx-rustdoc-postprocess
3
+ Version: 0.1.0
4
+ Summary: Post-process sphinxcontrib-rust RST output, converting leftover markdown to proper RST via pandoc.
5
+ Author-email: Rohit Goswami <rgoswami@ieee.org>
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: sphinx>=7
10
+ Description-Content-Type: text/markdown
11
+
12
+
13
+
14
+ # sphinx-rustdoc-postprocess
15
+
16
+ Post-process [sphinxcontrib-rust](https://github.com/aspect-build/sphinxcontrib-rust) RST output, converting leftover markdown
17
+ fragments (code fences, tables, links, headings, inline code) to proper RST
18
+ via [pandoc](https://pandoc.org/).
19
+
20
+
21
+ ## The problem
22
+
23
+ [sphinxcontrib-rust](https://github.com/aspect-build/sphinxcontrib-rust) generates RST files from Rust crates, but `rustdoc`
24
+ doc-comments are written in markdown. The generated RST ends up with markdown
25
+ fragments embedded verbatim inside directive bodies, which Sphinx cannot render
26
+ correctly.
27
+
28
+ For a real-world example, see [rgpot](https://rgpot.rgoswami.me/) ([source](https://github.com/HaoZeke/rgpot)), which uses this extension to
29
+ document its Rust core library alongside C++ API docs.
30
+
31
+
32
+ ### Before (raw sphinxcontrib-rust output)
33
+
34
+ Given Rust doc-comments like these in `lib.rs`:
35
+
36
+ //! ## Module Overview
37
+ //!
38
+ //! | Module | Purpose |
39
+ //! |--------|---------|
40
+ //! | [`types`] | `#[repr(C)]` data structures for force/energy I/O |
41
+ //! | [`tensor`] | DLPack tensor helpers |
42
+
43
+ sphinxcontrib-rust produces RST with the markdown still intact inside
44
+ directives:
45
+
46
+ .. py:module:: rgpot_core
47
+
48
+ ## Module Overview
49
+
50
+ | Module | Purpose |
51
+ |--------|---------|
52
+ | [`types`] | `#[repr(C)]` data structures for force/energy I/O |
53
+ | [`tensor`] | DLPack tensor helpers |
54
+
55
+ This renders incorrectly in Sphinx: headings inside directives break the
56
+ document structure, markdown tables appear as literal pipe characters, and
57
+ single-backtick code is not valid RST.
58
+
59
+
60
+ ### After (postprocessed output)
61
+
62
+ After this extension runs, the same file becomes:
63
+
64
+ .. py:module:: rgpot_core
65
+
66
+ **Module Overview**
67
+
68
+ +------------+----------------------------------------------------+
69
+ | Module | Purpose |
70
+ +============+====================================================+
71
+ | ``types`` | ``#[repr(C)]`` data structures for force/energy IO |
72
+ +------------+----------------------------------------------------+
73
+ | ``tensor`` | DLPack tensor helpers |
74
+ +------------+----------------------------------------------------+
75
+
76
+ Similarly, markdown code fences:
77
+
78
+ ```c
79
+ rgpot_status_t s = rgpot_potential_calculate(pot, &input, &output);
80
+ if (s != RGPOT_SUCCESS) {
81
+ fprintf(stderr, "rgpot error: %s\n", rgpot_last_error());
82
+ }
83
+ ```
84
+
85
+ become proper RST code-block directives:
86
+
87
+ .. code-block:: c
88
+
89
+ rgpot_status_t s = rgpot_potential_calculate(pot, &input, &output);
90
+ if (s != RGPOT_SUCCESS) {
91
+ fprintf(stderr, "rgpot error: %s\n", rgpot_last_error());
92
+ }
93
+
94
+ And markdown links like `[metatensor](https://docs.metatensor.org/)` become
95
+ `` `metatensor <https://docs.metatensor.org/>`_ ``, while rustdoc intra-doc links
96
+ like ``[`types`]`` become `` ``types`` ``.
97
+
98
+
99
+ ## Installation
100
+
101
+ pip install sphinx-rustdoc-postprocess
102
+
103
+ Pandoc must be available on your `PATH`. See [pandoc.org](https://pandoc.org/installing.html) for installation
104
+ instructions.
105
+
106
+
107
+ ## Usage
108
+
109
+ Add to your Sphinx `conf.py`:
110
+
111
+ extensions = [
112
+ "sphinxcontrib_rust",
113
+ "sphinx_rustdoc_postprocess",
114
+ ]
115
+
116
+ The extension hooks into `builder-inited` at priority 600 (after
117
+ sphinxcontrib-rust's default 500), so it automatically runs on the generated
118
+ RST files before Sphinx reads them.
119
+
120
+
121
+ ### Configuration
122
+
123
+ <table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
124
+
125
+
126
+ <colgroup>
127
+ <col class="org-left" />
128
+
129
+ <col class="org-left" />
130
+
131
+ <col class="org-left" />
132
+ </colgroup>
133
+ <thead>
134
+ <tr>
135
+ <th scope="col" class="org-left">Config value</th>
136
+ <th scope="col" class="org-left">Default</th>
137
+ <th scope="col" class="org-left">Description</th>
138
+ </tr>
139
+ </thead>
140
+ <tbody>
141
+ <tr>
142
+ <td class="org-left"><code>rustdoc_postprocess_rst_dir</code></td>
143
+ <td class="org-left"><code>"crates"</code></td>
144
+ <td class="org-left">Subdirectory of <code>srcdir</code> to scan for RST files</td>
145
+ </tr>
146
+
147
+ <tr>
148
+ <td class="org-left"><code>rustdoc_postprocess_toctree_target</code></td>
149
+ <td class="org-left"><code>""</code></td>
150
+ <td class="org-left">RST file to inject a toctree snippet into (empty = skip)</td>
151
+ </tr>
152
+
153
+ <tr>
154
+ <td class="org-left"><code>rustdoc_postprocess_toctree_rst</code></td>
155
+ <td class="org-left"><code>""</code></td>
156
+ <td class="org-left">RST snippet to append to the target file (empty = skip)</td>
157
+ </tr>
158
+ </tbody>
159
+ </table>
160
+
161
+
162
+ ### Full configuration example
163
+
164
+ # conf.py
165
+ import os
166
+
167
+ extensions = [
168
+ "sphinxcontrib_rust",
169
+ "sphinx_rustdoc_postprocess",
170
+ ]
171
+
172
+ # sphinxcontrib-rust settings
173
+ rust_crates = {
174
+ "my_crate": os.path.abspath("../../my-crate/"),
175
+ }
176
+ rust_doc_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "crates")
177
+ rust_rustdoc_fmt = "rst"
178
+
179
+ # Inject a toctree entry for the Rust docs into an existing index page
180
+ rustdoc_postprocess_toctree_target = "api/index.rst"
181
+ rustdoc_postprocess_toctree_rst = """
182
+
183
+ Rust API
184
+ --------
185
+
186
+ .. toctree::
187
+ :maxdepth: 2
188
+
189
+ ../crates/my_crate/lib
190
+ """
191
+
192
+
193
+ ## What gets converted
194
+
195
+ <table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
196
+
197
+
198
+ <colgroup>
199
+ <col class="org-left" />
200
+
201
+ <col class="org-left" />
202
+ </colgroup>
203
+ <thead>
204
+ <tr>
205
+ <th scope="col" class="org-left">Markdown construct</th>
206
+ <th scope="col" class="org-left">RST output</th>
207
+ </tr>
208
+ </thead>
209
+ <tbody>
210
+ <tr>
211
+ <td class="org-left"><code>```lang</code> code fences</td>
212
+ <td class="org-left"><code>.. code-block:: lang</code> directives</td>
213
+ </tr>
214
+
215
+ <tr>
216
+ <td class="org-left"><code>\vert table \vert</code> pipe tables</td>
217
+ <td class="org-left">RST grid tables (via pandoc)</td>
218
+ </tr>
219
+
220
+ <tr>
221
+ <td class="org-left"><code>[text](url)</code> links</td>
222
+ <td class="org-left"><code>`text &lt;url&gt;`_</code></td>
223
+ </tr>
224
+
225
+ <tr>
226
+ <td class="org-left"><code>[`Name`]</code> intra-doc links</td>
227
+ <td class="org-left"><code>``Name``</code></td>
228
+ </tr>
229
+
230
+ <tr>
231
+ <td class="org-left"><code>`code`</code> inline code</td>
232
+ <td class="org-left"><code>``code``</code></td>
233
+ </tr>
234
+
235
+ <tr>
236
+ <td class="org-left"><code>## Heading</code> ATX headings</td>
237
+ <td class="org-left"><code>**Heading**</code> (bold, since RST headings can't nest in directives)</td>
238
+ </tr>
239
+ </tbody>
240
+ </table>
241
+
242
+
243
+ ## Development
244
+
245
+ pixi install
246
+ pixi run test
247
+
248
+ A `pre-commit` job is setup on CI to enforce consistent styles, so it is best
249
+ to set it up locally as well (using [uvx](https://docs.astral.sh/uv/guides/tools/) for isolation):
250
+
251
+ # Run before committing
252
+ uvx pre-commit run --all-files
253
+
254
+
255
+ ## License
256
+
257
+ MIT
258
+
@@ -0,0 +1,7 @@
1
+ sphinx_rustdoc_postprocess/__init__.py,sha256=gab2A2_nHrAY3pOKfv8JcyOr5Et6FizPAXZI0MfEOcg,9396
2
+ sphinx_rustdoc_postprocess/_version.py,sha256=5jwwVncvCiTnhOedfkzzxmxsggwmTBORdFL_4wq0ZeY,704
3
+ sphinx_rustdoc_postprocess/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ sphinx_rustdoc_postprocess-0.1.0.dist-info/METADATA,sha256=QPdl16iCTnBrBGsdaEFprdluo-W1PYo8omG1MsF_VcY,7040
5
+ sphinx_rustdoc_postprocess-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
6
+ sphinx_rustdoc_postprocess-0.1.0.dist-info/licenses/LICENSE,sha256=bqBLQGg818jLAkkiLe-4rr7oElM-AtfQsBeyoWjmK9Q,1123
7
+ sphinx_rustdoc_postprocess-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,19 @@
1
+ MIT License Copyright (c) 2026 Rohit Goswami <rgoswami[at]ieee.org>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is furnished
8
+ to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice (including the next
11
+ paragraph) shall be included in all copies or substantial portions of the
12
+ Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
17
+ OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
19
+ OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.