stdl 0.6.7__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.
stdl/st.py ADDED
@@ -0,0 +1,422 @@
1
+ import os
2
+ import re
3
+ import textwrap
4
+ from dataclasses import dataclass
5
+ from platform import system
6
+ from sys import stdout
7
+ from typing import Any, Literal
8
+
9
+ CSI_RESET = "\033["
10
+ NO_COLOR = bool(os.environ.get("NO_COLOR", False))
11
+
12
+
13
+ def ansi_code(n: int) -> str:
14
+ return f"{CSI_RESET}{n}m"
15
+
16
+
17
+ class ColorANSI:
18
+ # Cancel SGR codes if we don't write to a terminal
19
+ if not stdout.isatty():
20
+ for attr in dir():
21
+ if isinstance(attr, str) and attr[0] != "_":
22
+ locals()[attr] = ""
23
+ else:
24
+ # Enable VT mode on Windows
25
+ if system() == "Windows":
26
+ import ctypes
27
+
28
+ kernel32 = ctypes.windll.kernel32
29
+ handle = kernel32.GetStdHandle(-11)
30
+ if handle:
31
+ kernel32.SetConsoleMode(handle, 7)
32
+ del kernel32
33
+ del handle
34
+ del ctypes
35
+
36
+ @classmethod
37
+ def __class_getitem__(cls, key: str) -> str:
38
+ key = key.upper().replace(" ", "_")
39
+ if key in dir(cls):
40
+ return getattr(cls, key)
41
+ raise KeyError(key)
42
+
43
+ @classmethod
44
+ def dict(cls) -> dict[str, str]:
45
+ ignored = ["dict", "print_all", "get_names"]
46
+ data = {}
47
+ for attr in dir(cls):
48
+ if attr[0] != "_" and attr not in ignored:
49
+ data[attr] = cls[attr]
50
+ return data
51
+
52
+ @classmethod
53
+ def get_names(cls) -> list[str]:
54
+ return [i for i in cls.dict.keys()]
55
+
56
+ @classmethod
57
+ def print_all(cls) -> None:
58
+ for k, v in cls.dict().items():
59
+ if k == "RESET":
60
+ continue
61
+ print(colored(k, v)) # type:ignore
62
+
63
+
64
+ class FG(ColorANSI):
65
+ """Foreground Color"""
66
+
67
+ BLACK = ansi_code(30)
68
+ RED = ansi_code(31)
69
+ GREEN = ansi_code(32)
70
+ YELLOW = ansi_code(33)
71
+ BLUE = ansi_code(34)
72
+ MAGENTA = ansi_code(35)
73
+ CYAN = ansi_code(36)
74
+ WHITE = ansi_code(37)
75
+ GRAY = ansi_code(90)
76
+ LIGHT_RED = ansi_code(91)
77
+ LIGHT_GREEN = ansi_code(92)
78
+ LIGHT_YELLOW = ansi_code(93)
79
+ LIGHT_BLUE = ansi_code(94)
80
+ LIGHT_MAGENTA = ansi_code(95)
81
+ LIGHT_CYAN = ansi_code(96)
82
+ LIGHT_WHITE = ansi_code(97)
83
+ BOLD = ansi_code(1)
84
+
85
+
86
+ class BG(ColorANSI):
87
+ """Background Color"""
88
+
89
+ BLACK = ansi_code(40)
90
+ RED = ansi_code(41)
91
+ GREEN = ansi_code(42)
92
+ YELLOW = ansi_code(43)
93
+ BLUE = ansi_code(44)
94
+ MAGENTA = ansi_code(45)
95
+ CYAN = ansi_code(46)
96
+ WHITE = ansi_code(47)
97
+ GRAY = ansi_code(100)
98
+ LIGHT_RED = ansi_code(101)
99
+ LIGHT_GREEN = ansi_code(102)
100
+ LIGHT_YELLOW = ansi_code(103)
101
+ LIGHT_BLUE = ansi_code(104)
102
+ LIGHT_MAGENTA = ansi_code(105)
103
+ LIGHT_CYAN = ansi_code(106)
104
+ LIGHT_WHITE = ansi_code(107)
105
+
106
+
107
+ class ST(ColorANSI):
108
+ """Style"""
109
+
110
+ RESET = ansi_code(0)
111
+ BOLD = ansi_code(1)
112
+ DIM = ansi_code(2)
113
+ ITALIC = ansi_code(3)
114
+ UNDERLINE = ansi_code(4)
115
+ BLINK = ansi_code(5)
116
+
117
+
118
+ ForegroundColor = Literal[
119
+ "black",
120
+ "blue",
121
+ "bold",
122
+ "cyan",
123
+ "gray",
124
+ "green",
125
+ "light_blue",
126
+ "light_cyan",
127
+ "light_green",
128
+ "light_magenta",
129
+ "light_red",
130
+ "light_white",
131
+ "light_yellow",
132
+ "magenta",
133
+ "red",
134
+ "white",
135
+ "yellow",
136
+ ]
137
+
138
+
139
+ BackgroundColor = Literal[
140
+ "black",
141
+ "blue",
142
+ "cyan",
143
+ "gray",
144
+ "green",
145
+ "light_blue",
146
+ "light_cyan",
147
+ "light_green",
148
+ "light_magenta",
149
+ "light_red",
150
+ "light_white",
151
+ "light_yellow",
152
+ "magenta",
153
+ "red",
154
+ "white",
155
+ "yellow",
156
+ ]
157
+
158
+ Style = Literal["blink", "bold", "dim", "italic", "reset", "underline"]
159
+
160
+
161
+ def _get_ansi_value(value: str | None, handler: Any) -> str:
162
+ if not value:
163
+ return ""
164
+ try:
165
+ return handler[value]
166
+ except KeyError:
167
+ return value
168
+
169
+
170
+ def colored(
171
+ text: str,
172
+ color: ForegroundColor | str | None = None,
173
+ background: BackgroundColor | str | None = None,
174
+ style: Style | str | None = None,
175
+ ) -> str:
176
+ """
177
+ Returns the text with ansi color, background color and text style codes.
178
+
179
+ Args:
180
+ text (str): The text that should be colorized.
181
+ color (str, optional): The color to use for the text.
182
+ background (str, optional): The color to use for the background.
183
+ style (str, optional): The style to use for the text.
184
+
185
+ Returns:
186
+ str: The colorized text.
187
+ """
188
+ if NO_COLOR or not stdout.isatty():
189
+ return text
190
+ color = _get_ansi_value(color, FG) # type: ignore
191
+ background = _get_ansi_value(background, BG) # type: ignore
192
+ style = _get_ansi_value(style, ST) # type: ignore
193
+ return f"{color}{background}{style}{text}{ST.RESET}"
194
+
195
+
196
+ @dataclass
197
+ class TextStyle:
198
+ color: ForegroundColor | str | None = None
199
+ background: BackgroundColor | str | None = None
200
+ style: Style | str | None = None
201
+
202
+
203
+ def with_style(text: str, style: TextStyle) -> str:
204
+ return colored(text, color=style.color, background=style.background, style=style.style)
205
+
206
+
207
+ def terminal_link(
208
+ uri: str,
209
+ label: str | None = None,
210
+ color: ForegroundColor = "white",
211
+ background: BackgroundColor | None = None,
212
+ style: Style | None = None,
213
+ ) -> str:
214
+ """
215
+ Returns a hyperlink that can be used in terminals.
216
+
217
+ Note:
218
+ Hyperlinks are not supported in all terminals.
219
+ For more information visit
220
+ <https://github.com/Alhadis/OSC8-Adoption/>
221
+ and
222
+ <https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>
223
+
224
+ Args:
225
+ uri (str): The URI of the link.
226
+ label (str, optional): The label of the link. Defaults to the URI.
227
+ color (str, optional): The color of the link. Defaults to white.
228
+ background (str, optional): The background color of the link.
229
+ style (str, optional): The style of the link.
230
+
231
+ Returns:
232
+ str: The link as a string.
233
+ """
234
+ if label is None:
235
+ label = uri
236
+
237
+ if stdout.isatty():
238
+ link = f"\033]8;;{uri}\033\\{label}\033]8;;\033\\"
239
+ else:
240
+ link = uri
241
+ link = colored(link, color, background, style)
242
+ return link
243
+
244
+
245
+ def remove(s: str, chars: str | set, replace_with: str = "") -> str:
246
+ """
247
+ Remove or replace characters in a string.
248
+
249
+ Args:
250
+ s (str): Input string.
251
+ chars (str | set): Characters to remove
252
+ replace_with (str, optional): If provided, replace the characters with this value.
253
+
254
+ """
255
+ string = []
256
+ chars = set(chars)
257
+ for c in s:
258
+ if c not in chars:
259
+ string.append(c)
260
+ else:
261
+ if replace_with:
262
+ string.append(replace_with)
263
+ return "".join(string)
264
+
265
+
266
+ def keep(s: str, chars: str | set, replace_with: str = "") -> str:
267
+ """
268
+ Keep provided characters in a string. Remove or replace others.
269
+
270
+ Args:
271
+ s (str): Input string
272
+ chars (str | set): Characters to keep
273
+ replace_with (str, optional): If provided, replace other characters with this value.
274
+
275
+ """
276
+ string = []
277
+ chars = set(chars)
278
+ for c in s:
279
+ if c in chars:
280
+ string.append(c)
281
+ else:
282
+ if replace_with:
283
+ string.append(replace_with)
284
+ return "".join(string)
285
+
286
+
287
+ ASCII = set("".join(chr(x) for x in range(128)))
288
+
289
+
290
+ class sf:
291
+ """A collection of functions that can be used to filter strings."""
292
+
293
+ @classmethod
294
+ def filename(cls, filename: str, replace_with: str = "") -> str:
295
+ """Removes or replaces characters that are not allowed to be in a filename."""
296
+ return remove(filename, chars='|?*<>:"\\', replace_with=replace_with)
297
+
298
+ @classmethod
299
+ def filepath(cls, filepath: str, replace_with: str = "") -> str:
300
+ """Removes or replaces characters that are not allowed to be in a filepath."""
301
+ if not filepath:
302
+ return ""
303
+ dirname, filename = os.path.split(filepath)
304
+ filename = sf.filename(filename, replace_with)
305
+ dirname = remove(dirname, '|?*<>:"')
306
+ return f"{dirname}{os.sep}{filename}"
307
+
308
+ @classmethod
309
+ def ascii(cls, s: str, replace_with: str = "") -> str:
310
+ """Removes or replaces non-ASCII characters from the string."""
311
+ return keep(s, ASCII, replace_with)
312
+
313
+
314
+ def snake_case(s: str) -> str:
315
+ """Converts a given string to snake_case."""
316
+ return "_".join(
317
+ re.sub(
318
+ "([A-Z][a-z]+)",
319
+ r" \1",
320
+ re.sub("([A-Z]+)", r" \1", s.replace("-", " ")),
321
+ ).split()
322
+ ).lower()
323
+
324
+
325
+ def camel_case(s: str) -> str:
326
+ """Converts a given string to camelCase."""
327
+ s = re.sub(r"(_|-)+", " ", s).title().replace(" ", "")
328
+ return s[0].lower() + s[1:]
329
+
330
+
331
+ def kebab_case(s: str) -> str:
332
+ """Converts a given string to kebab-case."""
333
+ RE_WORDS = r"[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+"
334
+ RE_SEP = r"(\s|_|-)+"
335
+ return "-".join(
336
+ re.sub(
337
+ RE_SEP,
338
+ " ",
339
+ re.sub(
340
+ RE_WORDS,
341
+ lambda mo: " " + mo.group(0).lower(),
342
+ s,
343
+ ),
344
+ ).split()
345
+ )
346
+
347
+
348
+ def wrapped(text: str, width: int, newline: str = "\n") -> str:
349
+ """Wraps the given text to a specified width and separates lines with the given newline character."""
350
+ return newline.join(textwrap.wrap(text, width=width))
351
+
352
+
353
+ def ansi_len(s: str) -> int:
354
+ """Returns the length of the string without the ANSI codes."""
355
+ ascii_codes = re.findall(r"\x1b\[[0-9;]*[m]", s)
356
+ codes_len = sum(len(code) for code in ascii_codes)
357
+ return len(s) - codes_len
358
+
359
+
360
+ def ansi_ljust(s: str, width: int, fillchar: str = " ") -> str:
361
+ """
362
+ Ljust function that takes into account the length of the ANSI codes in the string.
363
+
364
+ Args:
365
+ s (str): The string to be left justified.
366
+ width (int): The width of the string.
367
+ fillchar (str, optional): The character to fill the string with. Defaults to " ".
368
+ """
369
+ ascii_codes = re.findall(r"\x1b\[[0-9;]*[m]", s)
370
+ codes_len = sum(len(code) for code in ascii_codes)
371
+ new_width = width + codes_len
372
+ return s.ljust(new_width, fillchar)
373
+
374
+
375
+ def ansi_rjust(s: str, width: int, fillchar: str = " ") -> str:
376
+ """
377
+ Rjust function that takes into account the length of the ANSI codes in the string.
378
+
379
+ Args:
380
+ s (str): The string to be right justified.
381
+ width (int): The width of the string.
382
+ fillchar (str, optional): The character to fill the string with. Defaults to " ".
383
+ """
384
+ ascii_codes = re.findall(r"\x1b\[[0-9;]*[m]", s)
385
+ codes_len = sum(len(code) for code in ascii_codes)
386
+ new_width = width + codes_len
387
+ return s.rjust(new_width, fillchar)
388
+
389
+
390
+ def ansi_strip(r: str) -> str:
391
+ """Remove ANSI escape codes from a string."""
392
+ ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
393
+ return ansi_escape.sub("", r)
394
+
395
+
396
+ if __name__ == "__main__":
397
+ FG.print_all()
398
+ BG.print_all()
399
+ ST.print_all()
400
+
401
+
402
+ __all__ = [
403
+ "FG",
404
+ "BG",
405
+ "ST",
406
+ "colored",
407
+ "terminal_link",
408
+ "remove",
409
+ "keep",
410
+ "ASCII",
411
+ "sf",
412
+ "snake_case",
413
+ "camel_case",
414
+ "kebab_case",
415
+ "wrapped",
416
+ "ansi_len",
417
+ "ansi_ljust",
418
+ "ansi_rjust",
419
+ "Style",
420
+ "ForegroundColor",
421
+ "BackgroundColor",
422
+ ]
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.4
2
+ Name: stdl
3
+ Version: 0.6.7
4
+ Summary: Extended Python Standard Library
5
+ Project-URL: Repository, https://github.com/zigai/stdl
6
+ Project-URL: Issues, https://github.com/zigai/stdl/issues
7
+ Project-URL: Homepage, https://github.com/zigai/stdl
8
+ Author-email: Žiga Ivanšek <ziga.ivansek@gmail.com>
9
+ License: MIT License
10
+
11
+ Copyright (c) 2022 Žiga Ivanšek
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: ANSI colors,color formats,filesystem,lazy import,list utils,logging configuration,python lazy import,python utilities,standard library,stdl,string utils,terminal links,terminal output
32
+ Classifier: Intended Audience :: Developers
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Operating System :: OS Independent
35
+ Classifier: Programming Language :: Python
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3 :: Only
38
+ Classifier: Programming Language :: Python :: 3.10
39
+ Classifier: Programming Language :: Python :: 3.11
40
+ Classifier: Programming Language :: Python :: 3.12
41
+ Classifier: Programming Language :: Python :: 3.13
42
+ Classifier: Programming Language :: Python :: 3.14
43
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
44
+ Classifier: Typing :: Typed
45
+ Requires-Python: >=3.10
46
+ Requires-Dist: python-dateutil>=2.8.2
47
+ Requires-Dist: pyyaml>=6.0.2
48
+ Requires-Dist: toml>=0.10.2
49
+ Requires-Dist: typing-extensions>=4.0; python_version < '3.11'
50
+ Provides-Extra: dev
51
+ Requires-Dist: codespell; extra == 'dev'
52
+ Requires-Dist: coverage; extra == 'dev'
53
+ Requires-Dist: mypy; extra == 'dev'
54
+ Requires-Dist: pre-commit; extra == 'dev'
55
+ Requires-Dist: pydantic>=2; extra == 'dev'
56
+ Requires-Dist: pytest; extra == 'dev'
57
+ Requires-Dist: ruff; extra == 'dev'
58
+ Provides-Extra: full
59
+ Requires-Dist: tqdm>=4.67.1; extra == 'full'
60
+ Provides-Extra: test
61
+ Requires-Dist: coverage; extra == 'test'
62
+ Requires-Dist: pydantic>=2; extra == 'test'
63
+ Requires-Dist: pytest; extra == 'test'
64
+ Description-Content-Type: text/markdown
65
+
66
+ # stdl
67
+
68
+ [![Tests](https://github.com/zigai/stdl/actions/workflows/test.yml/badge.svg)](https://github.com/zigai/stdl/actions/workflows/test.yml)
69
+ [![Documentation Status](https://readthedocs.org/projects/stdl/badge/?version=latest)](https://stdl.readthedocs.io/en/latest/?badge=latest)
70
+ [![PyPI version](https://badge.fury.io/py/stdl.svg)](https://badge.fury.io/py/stdl)
71
+ ![Supported versions](https://img.shields.io/badge/python-3.10+-blue.svg)
72
+ [![Downloads](https://static.pepy.tech/badge/stdl)](https://pepy.tech/project/stdl)
73
+ [![license](https://img.shields.io/github/license/zigai/stdl.svg)](https://github.com/zigai/stdl/blob/master/LICENSE)
74
+
75
+ `stdl` is a collection of Python utilities that complement the standard library.
76
+
77
+ ## Features
78
+
79
+ - File and directory operations
80
+ - String manipulation
81
+ - ANSI color support for terminal output
82
+ - Date and time formatting
83
+ - List utils
84
+ - Lazy imports
85
+ - Logging configuration for `logging` and `loguru`
86
+ - [See docs](https://stdl.readthedocs.io/en/latest/?badge=latest)
87
+
88
+ ## Installation
89
+
90
+ ### Using pip
91
+
92
+ ```sh
93
+ pip install stdl
94
+ ```
95
+
96
+ ### Using uv
97
+
98
+ ```sh
99
+ uv add stdl
100
+ ```
101
+
102
+ ### From source
103
+
104
+ ```sh
105
+ pip install git+https://github.com/zigai/stdl.git
106
+ # or
107
+ uv add git+https://github.com/zigai/stdl.git
108
+ ```
109
+
110
+ ## Examples
111
+
112
+ ### Lazy imports
113
+
114
+ ```python
115
+ from typing import TYPE_CHECKING
116
+ from stdl.import_lazy import import_lazy
117
+
118
+ if TYPE_CHECKING:
119
+ from os.path import abspath, join
120
+ import numpy as np
121
+ import torch
122
+ else:
123
+ import_lazy("os.path", ["join", "abspath"], verbose=True)
124
+ import_lazy("numpy", alias="np", verbose=True)
125
+ import_lazy("torch", verbose=True)
126
+
127
+ print(np.zeros(4))
128
+ # importing "numpy" took 0.060s
129
+ # [0. 0. 0. 0.]
130
+ print(torch)
131
+ # <LazyImport: torch>
132
+ print(torch.randn(8))
133
+ # importing "torch" took 1.118s
134
+ # tensor([0., 0., 0., 0., 0., 0., 0., 0.])
135
+ print(torch)
136
+ # <module 'torch' from .../torch/__init__.py'
137
+ ```
138
+
139
+ ## License
140
+
141
+ [MIT License](https://github.com/zigai/stdl/blob/master/LICENSE)
@@ -0,0 +1,14 @@
1
+ stdl/__init__.py,sha256=XXNSego9YjRnY5QY77ZfWVXGSCfbwosm9Z2MLhECMtI,111
2
+ stdl/color.py,sha256=Vi2MAkF6kPUNDTnctFChT2ldGSHyEKR-Maw-amzfwIQ,45055
3
+ stdl/decorators.py,sha256=vsHGXfzCf9m6pyeT0AiWuVFpwPOQ2FT4xaDOK_MSaFY,3256
4
+ stdl/dt.py,sha256=XmEf0BqkmxM3HMsYwELGjKaVppx3NdbmI6sppCNk-zY,10203
5
+ stdl/fs.py,sha256=vFr4OrWM5hr8qsX_GzREJ-P4xsZh-vP0BiBVUECjbDM,52292
6
+ stdl/import_lazy.py,sha256=xqJU5S4ngYitpLwiGuBwGpeW4d3VYRb1-tN3n3ppqDI,4127
7
+ stdl/log.py,sha256=Bro5Oyeo_RMHzWsi_mcbph6kbPSUvPW1KxmOKqRwqN8,4224
8
+ stdl/lst.py,sha256=xeekFnqpuSpQnPETJCgylWrka5nspQkhyUQIr0D3u6U,4067
9
+ stdl/net.py,sha256=xEaet5wM3Olplvn79E-80C-SPUlI8L-2aqCK1qL87-M,2566
10
+ stdl/st.py,sha256=3bEiDf0Q6xs153aPMCVPAO-MDkvuDW9Z243POJ0mT1s,11061
11
+ stdl-0.6.7.dist-info/METADATA,sha256=XyTEN3jbfRprAZRN-A-bIKMlS95zM1Tq6rPQPX--t94,5086
12
+ stdl-0.6.7.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
13
+ stdl-0.6.7.dist-info/licenses/LICENSE,sha256=MwAJrJWzBVMNkmJSjdRctks7VcSbh8yAXezV_uf4yu8,1071
14
+ stdl-0.6.7.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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Žiga Ivanšek
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.