gitpro 0.3.1__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.
gitpro/undo.py ADDED
@@ -0,0 +1,63 @@
1
+ """Undo scripts: one shell script per writing run, so `sh <script>` puts back what the run changed."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ import re
6
+ import time
7
+ from pathlib import Path
8
+ from typing import List, Optional
9
+
10
+ PROG = "gitpro"
11
+
12
+
13
+ def _stamp() -> str:
14
+ return time.strftime("%Y%m%d-%H%M%S")
15
+
16
+
17
+ def state_dir() -> Path:
18
+ """$XDG_STATE_HOME/gitpro or ~/.local/state/gitpro, created 0700."""
19
+ base = os.environ.get("XDG_STATE_HOME") or str(Path.home() / ".local/state")
20
+ path = Path(base) / PROG
21
+ path.mkdir(mode=0o700, parents=True, exist_ok=True)
22
+ return path
23
+
24
+
25
+ def slug(command: str) -> str:
26
+ """The file-name part of a command: its first word, [A-Za-z0-9-] only ("scan --fix" -> "scan")."""
27
+ word = (command.split() or ["gitpro"])[0]
28
+ return re.sub(r"[^A-Za-z0-9-]", "-", word) or "gitpro"
29
+
30
+
31
+ def tilde(path: Path) -> str:
32
+ """path with the home directory shown as ~ (display only)."""
33
+ home = Path.home()
34
+ p = Path(path)
35
+ if p == home:
36
+ return "~"
37
+ try:
38
+ return "~/" + str(p.relative_to(home))
39
+ except ValueError:
40
+ return str(p)
41
+
42
+
43
+ def write(command: str, lines: List[str], *, what: str, state: Optional[Path] = None) -> Path:
44
+ """Create <state>/<slug>-undo-<YYYYmmdd-HHMMSS>[-n].sh (O_EXCL, 0700) holding `lines`, which the
45
+ caller has shell-quoted. No `set -e`: every line runs even when one fails. Returns the path."""
46
+ state = state or state_dir()
47
+ state.mkdir(mode=0o700, parents=True, exist_ok=True)
48
+ stamp = _stamp()
49
+ text = (f"#!/bin/sh\n# puts back what '{PROG} {command}' changed on {time.strftime('%Y-%m-%d %H:%M')}"
50
+ f" ({what})\n" + "".join(line + "\n" for line in lines))
51
+ for n in range(1000):
52
+ path = state / f"{slug(command)}-undo-{stamp}{f'-{n}' if n else ''}.sh"
53
+ try:
54
+ fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o700)
55
+ except FileExistsError:
56
+ continue
57
+ with os.fdopen(fd, "w") as f:
58
+ f.write(text)
59
+ f.flush()
60
+ os.fsync(f.fileno())
61
+ os.chmod(path, 0o700) # the umask may have narrowed it
62
+ return path
63
+ raise OSError(f"no free undo script name in {state}")
gitpro/view.py ADDED
@@ -0,0 +1,155 @@
1
+ """Terminal output for scan, with rich: colors and wrapping on a terminal, plain text when piped."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Callable
5
+
6
+ from rich.console import Console
7
+ from rich.prompt import Confirm
8
+ from rich.syntax import Syntax
9
+ from rich.table import Table
10
+ from rich.text import Text
11
+ from rich.tree import Tree
12
+
13
+ from .scan import skipped
14
+
15
+
16
+ def console() -> Console:
17
+ c = Console(highlight=False)
18
+ return c if c.is_terminal else Console(highlight=False, width=120)
19
+
20
+
21
+ # One symbol per kind of message, everywhere: ✓ done, ⚠ warning, ✗ error or nothing done, → next step or change.
22
+ def _say(symbol: str, style: str, msg: str, err: bool = False) -> None:
23
+ out = Console(stderr=True, highlight=False) if err else console()
24
+ out.print(Text(symbol + " ", style=style) + Text(msg), soft_wrap=True)
25
+
26
+
27
+ def ok(msg: str) -> None:
28
+ _say("✓", "green", msg)
29
+
30
+
31
+ def warn(msg: str) -> None:
32
+ _say("⚠", "yellow", msg, err=True)
33
+
34
+
35
+ def error(msg: str) -> None:
36
+ _say("✗", "bold red", msg, err=True)
37
+
38
+
39
+ def aborted() -> None:
40
+ _say("✗", "yellow", "aborted, nothing written")
41
+
42
+
43
+ def step(msg: str) -> None:
44
+ _say("→", "cyan", msg)
45
+
46
+
47
+ def confirm(question: str) -> bool:
48
+ return Confirm.ask(question, default=False)
49
+
50
+
51
+ def plural(n: int, word: str) -> str:
52
+ return f"{n} {word}{'s' * (n != 1)}"
53
+
54
+
55
+ def who(d: dict[str, str]) -> str:
56
+ return f"{d['name']} <{d['email']}>".strip() if d["email"] else "(no email)"
57
+
58
+
59
+ def source(src: str) -> str:
60
+ return {"repo": "set in the repo", "pin": "pinned in the repo", "": "nowhere"}.get(src, src)
61
+
62
+
63
+ def scan(rep: dict, show: Callable[[str], str], *, verbose: bool, suggest: bool, fix: bool, prog: str,
64
+ again: str, exact: bool) -> None:
65
+ """again: the command to run the fix for the same directories; exact: rep['roots'] are the repos themselves."""
66
+ out = console()
67
+ bad = rep["unreadable"]
68
+ head = Text(plural(rep["repos"], "repo") + ("" if exact else f" under {', '.join(map(show, rep['roots']))}"),
69
+ style="bold")
70
+ n = rep.get("skipped_excluded", 0)
71
+ skips = f"hidden directories, node_modules and {plural(n, 'excluded dir')}" if n else \
72
+ "hidden directories and node_modules"
73
+ head.append(("" if exact else f" {skips} skipped")
74
+ + (f" · {skipped(bad)}" + ("" if verbose else " (-v lists them)") if bad else ""), style="dim")
75
+ out.print(head, soft_wrap=True)
76
+ if verbose:
77
+ for b in bad:
78
+ out.print(Text(f" ⚠ skipped {show(b['path'])}: {b['error']}", style="dim"), soft_wrap=True)
79
+
80
+ if not fix:
81
+ table = Table(box=None, header_style="dim", pad_edge=False, padding=(0, 2, 0, 0))
82
+ table.add_column("repos", justify="right")
83
+ table.add_column("identity")
84
+ table.add_column("from", style="dim")
85
+ if verbose:
86
+ table.add_column("remote owners", style="dim", ratio=1)
87
+ own = [i for i in rep["identities"] if i["source"] == "repo"]
88
+ for i in rep["identities"]:
89
+ if i["source"] == "repo" and not verbose:
90
+ continue
91
+ owners = ", ".join(f"{o} {n}" for o, n in i["owners"].items())
92
+ table.add_row(str(i["repos"]), Text(who(i)), Text(source(i["source"])), *([Text(owners)] if verbose else []))
93
+ if own and not verbose:
94
+ table.add_row(str(sum(i["repos"] for i in own)), Text("see below", style="dim"), Text(source("repo")))
95
+ out.print()
96
+ out.print(table)
97
+
98
+ over = rep["overrides"]
99
+ if not over:
100
+ out.print(Text("\n✓ no repo sets user.name or user.email itself: gitpro decides everywhere", style="green"))
101
+ return
102
+ out.print(Text(f"\n{plural(len(over), 'repo')} set their own user.name/user.email, which beats {prog}.", style="bold")
103
+ + Text(" Without it they use:"), soft_wrap=True)
104
+ # identity after fix -> identity now -> repos
105
+ groups: dict[str, dict[str, list[dict]]] = {}
106
+ for r in over:
107
+ after = who(r["after"]) if r["fixable"] else ""
108
+ groups.setdefault(after, {}).setdefault(who(r["now"]), []).append(r)
109
+ for after, befores in sorted(groups.items(), key=lambda kv: (not kv[0], kv[0])):
110
+ n = sum(map(len, befores.values()))
111
+ title = Text(after, style="bold green") if after else Text("their own: nothing else sets one, so fix keeps it")
112
+ tree = Tree(title + Text(f" ({n} repo{'s' * (n != 1)})", style="dim"), guide_style="dim")
113
+ for before, rs in sorted(befores.items(), key=lambda kv: (kv[0] == after, kv[0])):
114
+ names = ", ".join(show(r["path"]) if verbose else r["short"] for r in rs)
115
+ if before == after:
116
+ tree.add(Text("unchanged: ", style="dim") + Text(names))
117
+ else:
118
+ tree.add(Text("from ", style="dim") + Text(before, style="yellow") + Text(": ", style="dim")
119
+ + Text(names))
120
+ out.print()
121
+ out.print(tree)
122
+
123
+ if suggest:
124
+ out.print(f"\nTo keep an identity, paste its block into your config and run '{prog} apply' before the fix.")
125
+ out.print("An identity applies to every repo of its owners.")
126
+ for s in rep["suggestions"]:
127
+ out.print()
128
+ if out.is_terminal:
129
+ out.print(Syntax(s["toml"], "toml", theme="ansi_dark", background_color="default"))
130
+ else:
131
+ out.print(s["toml"], markup=False, soft_wrap=True)
132
+ for path in rep["unmatched"]:
133
+ r = next(o for o in over if o["path"] == path)
134
+ out.print(f"\n# {show(path)} ({r['owner']}): no identity can match it; after fixing it uses {who(r['after'])}",
135
+ markup=False, soft_wrap=True)
136
+ elif rep["suggestions"] and not fix:
137
+ out.print(Text(f"\nto keep some of these identities instead: {prog} scan --suggest", style="dim"), soft_wrap=True)
138
+
139
+ if not fix:
140
+ settings = sum(len(r["fixable"]) for r in over)
141
+ if settings:
142
+ out.print(Text(f"\ndry run, nothing changed. Remove {plural(settings, 'setting')} from "
143
+ f"{plural(sum(bool(r['fixable']) for r in over), 'repo')}: ") + Text(again, style="bold cyan"), soft_wrap=True)
144
+
145
+
146
+ def fixed(removed: list[tuple[str, str, str, str]], undo: str) -> None:
147
+ """removed: (repo, key, value, what git uses now) for each setting."""
148
+ out = console()
149
+ out.print()
150
+ for repo, key, value, now in removed:
151
+ out.print(Text(f" {repo} ", style="dim") + Text(f"{key} {value}") + Text(f" → {now}", style="green"),
152
+ soft_wrap=True)
153
+ settings, repos = len(removed), len({r for r, *_ in removed})
154
+ out.print(Text(f"✓ removed {plural(settings, 'setting')} from {plural(repos, 'repo')}", style="green"))
155
+ out.print(Text("↩ undo: ", style="dim") + Text(f"sh {undo}", style="bold"), soft_wrap=True)
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.5
2
+ Name: gitpro
3
+ Version: 0.3.1
4
+ Summary: Per-remote git identities (name, email, SSH key, commit signing) from one TOML file
5
+ Project-URL: Homepage, https://github.com/Yantrakar/gitpro
6
+ Project-URL: Documentation, https://github.com/Yantrakar/gitpro/blob/main/docs/guide.md
7
+ Project-URL: Issues, https://github.com/Yantrakar/gitpro/issues
8
+ Author: Yantrakar, Sandeep Shelke
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: git,gitconfig,identity,includeif,signing,ssh
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: MacOS
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Software Development :: Version Control :: Git
18
+ Requires-Python: >=3.9
19
+ Requires-Dist: rich>=13
20
+ Requires-Dist: tomli>=1.1; python_version < '3.11'
21
+ Provides-Extra: tui
22
+ Requires-Dist: textual>=0.80; extra == 'tui'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # gitpro
26
+
27
+ **The right Git identity, SSH key and signature for every repository.**
28
+
29
+ Work on a company repository, commit with your work email. Switch to a personal
30
+ project, use your personal identity. Gitpro chooses by **remote URL**, not by
31
+ where you happened to clone the repository.
32
+
33
+ Useful when you have more than one GitHub/GitLab account, separate work and
34
+ personal keys, or keep finding the wrong email on your commits.
35
+
36
+ ## Try it
37
+
38
+ Requires **Linux or macOS**, Git **2.36+**, Python **3.9+** and OpenSSH.
39
+ Install from [PyPI](https://pypi.org/project/gitpro/) with pipx.
40
+
41
+ ```sh
42
+ pipx install gitpro
43
+ gitpro setup --write
44
+ ```
45
+
46
+ The wizard helps you choose identities, Git hosts and owners, existing SSH keys,
47
+ and optional signing. **No TOML editing is needed for basic setup.** Review the
48
+ configuration before saving; nothing is applied to Git or SSH yet.
49
+
50
+ ```sh
51
+ gitpro plan # see exactly what would change
52
+ gitpro apply -y # explicitly apply the reviewed changes
53
+ ```
54
+
55
+ Then, inside a repository:
56
+
57
+ ```sh
58
+ gitpro why # see the expected identity and anything overriding it
59
+ ```
60
+
61
+ Already have a config? Run `gitpro setup --write` to add another identity without losing
62
+ your existing settings or comments. Prefer editing files? The
63
+ [user guide](docs/guide.md#configuration) covers the full TOML format.
64
+
65
+ ## What you get
66
+
67
+ - **Work/personal separation:** names, emails, SSH keys and signing per host/owner.
68
+ - **Answers, not guesswork:** `gitpro why` explains mismatches and suggests a fix.
69
+ - **A safety net:** optional hooks can block wrong-identity commits and pushes.
70
+ - **Editor and container support:** see your identity in VS Code and carry it into dev containers.
71
+
72
+ Gitpro generates ordinary Git and SSH configuration; it needs no background
73
+ service. Changes can be previewed, existing hand-written files are backed up,
74
+ and key creation or uploads are never part of the setup wizard.
75
+
76
+ ## Using VS Code?
77
+
78
+ Run **gitpro: Set Up Identities** from the Command Palette to open the same wizard
79
+ in a terminal. The extension also shows the current identity and helps find
80
+ repository-level overrides. [Install the extension](vscode/README.md).
81
+
82
+ ## Go further when you need to
83
+
84
+ For agents, [the versioned workflow](docs/agent-workflows.md) provides structured
85
+ preflight, identity-scoped execution and durable idempotency for commits, pushes
86
+ and GitHub PRs. Persistent changes use exact proposals, human approval by default,
87
+ optional operator-scoped preauthorization, and recoverable apply. MCP execution is opt-in.
88
+
89
+ | I want to… | Start here |
90
+ |---|---|
91
+ | Understand matching, SSH keys or signing | [Configuration](docs/guide.md#configuration) |
92
+ | Find wrong identities or forgotten work | [Daily use](docs/guide.md#daily-use) · [Finding things](docs/guide.md#finding-things) |
93
+ | Use dev containers | [Container setup](docs/guide.md#dev-containers) |
94
+ | Configure team policy, CI or AI assistants | [Teams and CI](docs/guide.md#teams) · [AI assistants](docs/guide.md#ai-assistants) |
95
+ | Look up a command or undo changes | [Command reference](docs/guide.md#commands) · [Undo](docs/guide.md#undo-everything) |
96
+ | Troubleshoot or contribute | [Troubleshooting](docs/guide.md#troubleshooting) · [Development](docs/guide.md#development) |
97
+
98
+ The detailed TOML, YAML and command examples live in the guide, not on the
99
+ getting-started path.
100
+
101
+ ## License
102
+
103
+ [MIT](LICENSE)
@@ -0,0 +1,50 @@
1
+ gitpro/__init__.py,sha256=C1o8ePwaiweYMQ7ZW0o6pEVr31US8cuu2OP6f84lcOU,133
2
+ gitpro/__main__.py,sha256=89BHL7f25RJlr12miUjiApIn06Jovyy-VBSqHm3_RJ0,52
3
+ gitpro/agents.py,sha256=YP9SMGIkZuyq90uljuk3NmBMbYgp7hoacEmHyy-2EzQ,83996
4
+ gitpro/approval_policy.py,sha256=eGT1xqingfhHzBtdL7IjCeUPOxVOFHRhIruU9nMy2Qs,7930
5
+ gitpro/args.py,sha256=iuLpqAe4u8SheMmFBZw2XM8WdpASgFjJfq4qmowRfq8,3252
6
+ gitpro/audit.py,sha256=szlAFCEfDNDnG4-mcmhxW22rpU52pRu0P9Z4JQNE2l0,39018
7
+ gitpro/change_contract.py,sha256=Ak8TLhVulXhKiGOmISVZ-hMbmID_N8OFwHA5UM47bMg,6332
8
+ gitpro/changes.py,sha256=qrg9La5q1Ml2Z4O_FUgEBYzQV9kpBreD6Q1lUdgWmLo,48105
9
+ gitpro/cli.py,sha256=l2IpaX8z6zsBR3gMjiuCeEnPaDBNnR99V1ZWPQb3f3o,21271
10
+ gitpro/clone.py,sha256=YpNGX4rt-yAgkd_K1jsE8u1NivixyWs2-GSQeB4k-K0,22342
11
+ gitpro/completion.py,sha256=c1swRsb4bcUhYJ1lKjhxJOvFHT5oQvL-dEpJXAP5IEQ,16560
12
+ gitpro/config.py,sha256=9gYU6bgG0ckXi_M0YtNTIwHmdlkfHdz37414RPyh-zA,19822
13
+ gitpro/devcontainer.py,sha256=6rxL9z7eT-0jJrBP_Nd144S8H1JjmNREROQN_zajCko,20025
14
+ gitpro/discover.py,sha256=2M9ityzWOLE0lCPgkdJWX7XRiMsG291gmpR6Yj2FUw8,68780
15
+ gitpro/doctor.py,sha256=wmTU_ourdW-yTTuqgE2VlogmVmaRlYgZsE7nGfRorlc,25240
16
+ gitpro/envcmd.py,sha256=s2_FMpm6L-p1CWfDVAnt6yqMNa-wY6IYqevrVnvg3Hs,16643
17
+ gitpro/example.toml,sha256=2L3KVC-QfSXEFet0CPv_YpcmMclwySgf-kG6YSa6xGM,4969
18
+ gitpro/files.py,sha256=KZk3CVacj-pfJNb31jwVP-jiitcIJcSvyeJNMoKAm8E,4846
19
+ gitpro/forge.py,sha256=33Fs_5GO6Dx0doK7PvmGs2y5BZRwQBUpJuw-cj7QGgQ,56072
20
+ gitpro/guard.py,sha256=RyvzQNXceOPTbOh5_0JCfGlhoDuWE8io8vDtBGn7dtk,50938
21
+ gitpro/health.py,sha256=xy2YVG7MEuG1v9Jl9xJGincT6VQKWKscc-H3Bp-SkI0,57012
22
+ gitpro/hot.py,sha256=xVDY8td8h78J8iygQhNUW4L2-n_WUHd3dys7DedSnTI,1538
23
+ gitpro/jsonfile.py,sha256=eScD5akXxh_2VzJ0832ETLvPf8YIVtyQJUN9ggQwhrk,7326
24
+ gitpro/jsonout.py,sha256=dlcH-r3aGylk0YuR7kbjtZDo-p-cPNTVIiZ3j7CE4hc,831
25
+ gitpro/keys.py,sha256=9C98EkTaDB3BIdiQUc_XknfOJm18N79vh7QcPiP7ICk,2079
26
+ gitpro/lint.py,sha256=126rcOBZjr1YYHsT7ZQ7VAgVRA052Asbup4grqXi-b8,2472
27
+ gitpro/lost.py,sha256=Z4GhEgeisLZEifsUByCJKdtw0tswFEnWT7FCLkWBOes,40642
28
+ gitpro/mcp.py,sha256=YGnc7_dSAzHTo9ErW0F8j9mBiYtPkh3arGYmsanKKjk,51789
29
+ gitpro/operation_contract.py,sha256=7SdaPhmOMX05XGCkKCZmEt8P4BvoaRnAb7Hk3IsLYpQ,9335
30
+ gitpro/operation_io.py,sha256=A1EkLC7UlZOPXxL0zjlipF50PHrVuFXUKen2ub3ikLM,10514
31
+ gitpro/operation_reconcile.py,sha256=KUPtVnox7O4Qa1n5fRpMSv52jN-TtCdorgd9IovQFtI,11463
32
+ gitpro/operations.py,sha256=s5FQAiLIgEwVOuB0gDwWKteADjPCelvt9ardY8cBjgk,45859
33
+ gitpro/pin.py,sha256=IEKlU0MFYZGj2RgdujNwxWx8DfVKvyApibTsJt9Mu4Q,4450
34
+ gitpro/plan.py,sha256=gkw6E8ATY6S6roaqbIlYta7YG3MRQjvScz5YQpR1d4I,8689
35
+ gitpro/policy.py,sha256=RMmlA8E2LIR-B_eNRO2KdrO5_Krz-LzTdIkGbCSzRt8,82768
36
+ gitpro/prompt.py,sha256=OVdc2S6dypHe2p7InQxdlSrvdTjQkrlEUruc7_DW6kI,13397
37
+ gitpro/render.py,sha256=Kc7pSifd8O319QgyuHZNrWhRtje2PNOaPm0hx6Lhziw,21350
38
+ gitpro/resolve.py,sha256=_4et00yrqt9FmMX0_qfoxNQ78HSNQu7t_NCd3U66ztY,39794
39
+ gitpro/scan.py,sha256=zwrIA_nOMD0votYGQwvt7aiki5DrbbYDHNhOvWE80_o,15932
40
+ gitpro/scanroots.py,sha256=RNrz-A3SA1sJbuFPNCwOgj-Qf7V3fwNRqgJlAihytTM,6552
41
+ gitpro/setup.py,sha256=TA0WZD8IB-KzPdCwy9euiTbHPRtUMgXYm4bYcYfiC9M,12523
42
+ gitpro/trailers.py,sha256=NmhYkFoa0YuWQXFGe_3J36xY11D6n4j1QsDwT-twh7A,9357
43
+ gitpro/tui.py,sha256=AHvj7s6XJ7TBm2ygNAIQiy_pxbKpwF0hRruEF7WQiGc,7473
44
+ gitpro/undo.py,sha256=WZKBNFY8gCCqiVu6hA0AQxfH8UG4JDblOxBdBzb0L4U,2209
45
+ gitpro/view.py,sha256=KHiWotFuoxqeUOEPE3FUFRtnngxOmKXh7pSjmceHd1A,6848
46
+ gitpro-0.3.1.dist-info/METADATA,sha256=3wtQc_aS7B1QdN2o9iAXNuNz68bBoM5dCnwnauQRSuY,4357
47
+ gitpro-0.3.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
48
+ gitpro-0.3.1.dist-info/entry_points.txt,sha256=2x3GGfk0MITh35CgYxXdFFf0uGVzpn6DUPQ-ElNXGqw,43
49
+ gitpro-0.3.1.dist-info/licenses/LICENSE,sha256=6APC4nv_-AU6LBtOP-oceSBEqRFGVx6gccZrBGk1YIk,1085
50
+ gitpro-0.3.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ gitpro = gitpro.hot:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yantrakar and Sandeep Shelke
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.