pkgwise 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.
- pkgwise/__init__.py +12 -0
- pkgwise/__main__.py +6 -0
- pkgwise/backends/__init__.py +1 -0
- pkgwise/backends/arch.py +223 -0
- pkgwise/backends/base.py +107 -0
- pkgwise/backends/debian.py +247 -0
- pkgwise/backends/factory.py +63 -0
- pkgwise/cli.py +222 -0
- pkgwise/core/__init__.py +1 -0
- pkgwise/core/analyzer.py +169 -0
- pkgwise/core/models.py +126 -0
- pkgwise/core/priority.py +199 -0
- pkgwise/core/storage.py +82 -0
- pkgwise/mockdata.py +43 -0
- pkgwise/py.typed +0 -0
- pkgwise/ui/__init__.py +1 -0
- pkgwise/ui/app.py +228 -0
- pkgwise/ui/screens.py +406 -0
- pkgwise/ui/widgets.py +57 -0
- pkgwise/utils/__init__.py +1 -0
- pkgwise/utils/commands.py +98 -0
- pkgwise/utils/config.py +134 -0
- pkgwise/utils/distro.py +88 -0
- pkgwise/utils/formatting.py +58 -0
- pkgwise-0.1.0.dist-info/METADATA +135 -0
- pkgwise-0.1.0.dist-info/RECORD +30 -0
- pkgwise-0.1.0.dist-info/WHEEL +5 -0
- pkgwise-0.1.0.dist-info/entry_points.txt +2 -0
- pkgwise-0.1.0.dist-info/licenses/LICENSE +21 -0
- pkgwise-0.1.0.dist-info/top_level.txt +1 -0
pkgwise/utils/distro.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Linux distribution detection.
|
|
2
|
+
|
|
3
|
+
Reads ``/etc/os-release`` (the standard, spec-compliant source) so PkgWise can
|
|
4
|
+
select the correct package-manager backend. Falls back to ``uname`` metadata
|
|
5
|
+
when ``os-release`` is missing.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from typing import Dict, Optional
|
|
12
|
+
|
|
13
|
+
from pkgwise.core.models import SystemInfo
|
|
14
|
+
|
|
15
|
+
_OS_RELEASE_PATH = "/etc/os-release"
|
|
16
|
+
_OS_RELEASE_ALT = "/usr/lib/os-release"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _parse_os_release(content: str) -> Dict[str, str]:
|
|
20
|
+
"""Parse os-release key=value lines into a dict.
|
|
21
|
+
|
|
22
|
+
Values may be quoted with single or double quotes which are stripped.
|
|
23
|
+
The ID has special semantics (``ID=arch`` vs ``ID=\"arch\"``) handled here.
|
|
24
|
+
"""
|
|
25
|
+
fields: Dict[str, str] = {}
|
|
26
|
+
for raw in content.splitlines():
|
|
27
|
+
line = raw.strip()
|
|
28
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
29
|
+
continue
|
|
30
|
+
key, _, value = line.partition("=")
|
|
31
|
+
key = key.strip()
|
|
32
|
+
value = value.strip()
|
|
33
|
+
if len(value) >= 2 and value[0] == value[-1] and value[0] in ("'", '"'):
|
|
34
|
+
value = value[1:-1]
|
|
35
|
+
fields[key] = value
|
|
36
|
+
return fields
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def read_os_release() -> Dict[str, str]:
|
|
40
|
+
"""Return parsed os-release fields for the running system.
|
|
41
|
+
|
|
42
|
+
Returns an empty dict when neither standard location exists.
|
|
43
|
+
"""
|
|
44
|
+
for path in (_OS_RELEASE_PATH, _OS_RELEASE_ALT):
|
|
45
|
+
try:
|
|
46
|
+
with open(path, "r", encoding="utf-8") as fh:
|
|
47
|
+
return _parse_os_release(fh.read())
|
|
48
|
+
except (OSError, UnicodeDecodeError):
|
|
49
|
+
continue
|
|
50
|
+
return {}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_system_info() -> SystemInfo:
|
|
54
|
+
"""Detect the running distribution and produce a :class:`SystemInfo`."""
|
|
55
|
+
fields = read_os_release()
|
|
56
|
+
distro_name = fields.get("NAME") or _fallback_name()
|
|
57
|
+
distro_id = fields.get("ID", "").lower()
|
|
58
|
+
version = fields.get("VERSION_ID", "") or fields.get("VERSION", "")
|
|
59
|
+
package_manager = _package_manager_for(distro_id)
|
|
60
|
+
return SystemInfo(
|
|
61
|
+
distro_name=distro_name,
|
|
62
|
+
distro_version=version,
|
|
63
|
+
package_manager=package_manager,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _fallback_name() -> str:
|
|
68
|
+
"""Best-effort name when os-release is unavailable."""
|
|
69
|
+
try:
|
|
70
|
+
with open("/proc/sys/kernel/ostype", "r", encoding="utf-8") as fh:
|
|
71
|
+
return fh.read().strip() or "Linux"
|
|
72
|
+
except OSError:
|
|
73
|
+
return "Linux"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _package_manager_for(distro_id: str) -> str:
|
|
77
|
+
"""Map a distro ID to its native package manager."""
|
|
78
|
+
arch_family = {"arch", "archlinux", "artix", "endeavouros", "manjaro"}
|
|
79
|
+
debian_family = {"debian", "ubuntu", "kali", "raspbian", "linuxmint", "pop"}
|
|
80
|
+
if distro_id in arch_family or "arch" in distro_id:
|
|
81
|
+
return "pacman"
|
|
82
|
+
if distro_id in debian_family or "debian" in distro_id or "ubuntu" in distro_id:
|
|
83
|
+
return "apt"
|
|
84
|
+
return "unknown"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def is_arch(distro_id: str) -> bool:
|
|
88
|
+
return distro_id in {"arch", "archlinux", "artix", "endeavouros", "manjaro"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Human-friendly formatting helpers for bytes and plain text."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
_SIZE_UNITS = ("B", "KB", "MB", "GB", "TB", "PB", "EB")
|
|
8
|
+
|
|
9
|
+
_UNKNOWN_STRING = "Unknown"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def format_bytes(num_bytes: Optional[int], decimals: int = 1) -> str:
|
|
13
|
+
"""Render a byte count as a human-readable string, e.g. ``842 MB``.
|
|
14
|
+
|
|
15
|
+
``None`` renders as "Unknown"; negative or zero values render as ``0 B``.
|
|
16
|
+
"""
|
|
17
|
+
if num_bytes is None:
|
|
18
|
+
return _UNKNOWN_STRING
|
|
19
|
+
return _format_positive_bytes(max(num_bytes, 0), decimals)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _format_positive_bytes(num_bytes: int, decimals: int) -> str:
|
|
23
|
+
if num_bytes < 1024:
|
|
24
|
+
return f"{num_bytes} B"
|
|
25
|
+
value = float(num_bytes)
|
|
26
|
+
for unit in _SIZE_UNITS[1:]:
|
|
27
|
+
value /= 1024.0
|
|
28
|
+
if value < 1024.0 or unit == _SIZE_UNITS[-1]:
|
|
29
|
+
number = f"{value:.{decimals}f}"
|
|
30
|
+
if number.endswith(".0"):
|
|
31
|
+
number = number[:-2]
|
|
32
|
+
return f"{number} {unit}"
|
|
33
|
+
return f"{num_bytes} B" # pragma: no cover - unreachable, defensive
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def format_percent(percentage: float) -> str:
|
|
37
|
+
"""Render a percentage without a decimal when it is effectively whole."""
|
|
38
|
+
if round(percentage) == percentage:
|
|
39
|
+
return f"{percentage:.0f}%"
|
|
40
|
+
return f"{percentage:.1f}%"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def unknown_if_none(value: Optional[str]) -> str:
|
|
44
|
+
"""Render ``None`` as "Unknown" and pass real strings through."""
|
|
45
|
+
return _UNKNOWN_STRING if value is None else value
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def format_duration(seconds: float) -> str:
|
|
49
|
+
"""Format a number of seconds as a short duration string."""
|
|
50
|
+
if seconds < 1:
|
|
51
|
+
return "<1s"
|
|
52
|
+
if seconds < 60:
|
|
53
|
+
return f"{int(seconds)}s"
|
|
54
|
+
minutes, secs = divmod(int(seconds), 60)
|
|
55
|
+
if minutes < 60:
|
|
56
|
+
return f"{minutes}m {secs}s"
|
|
57
|
+
hours, mins = divmod(minutes, 60)
|
|
58
|
+
return f"{hours}h {mins}m"
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pkgwise
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Know what you're updating before you update — a smart Linux update analyzer.
|
|
5
|
+
Author: PkgWise Developers
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Senpi-Station/PkgWise
|
|
8
|
+
Project-URL: Repository, https://github.com/Senpi-Station/PkgWise
|
|
9
|
+
Project-URL: Issues, https://github.com/Senpi-Station/PkgWise/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/Senpi-Station/PkgWise/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: linux,packages,updates,pacman,apt,tui,system-administration
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: System :: Systems Administration
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: psutil<7,>=5.9
|
|
29
|
+
Requires-Dist: textual<2,>=0.60
|
|
30
|
+
Provides-Extra: test
|
|
31
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
32
|
+
Requires-Dist: pytest-cov>=4; extra == "test"
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.23; python_version >= "3.9" and extra == "test"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
<h1 align="center">PkgWise</h1>
|
|
37
|
+
|
|
38
|
+
<p align="center">
|
|
39
|
+
<b>Know what you're updating before you update.</b><br/>
|
|
40
|
+
A conservative Linux package-update analyzer for Arch (pacman) and Debian-family (apt) systems.
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
<p align="center">
|
|
44
|
+
<a href="https://github.com/Senpi-Station/PkgWise/actions"><img src="https://github.com/Senpi-Station/PkgWise/actions/workflows/ci.yaml/badge.svg" alt="CI"></a>
|
|
45
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
|
|
46
|
+
<a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.9%2B-blue" alt="Python 3.9+"></a>
|
|
47
|
+
</p>
|
|
48
|
+
|
|
49
|
+
PkgWise checks for available updates **without installing anything**, shows you what
|
|
50
|
+
is waiting (name, versions, sizes, security and reboot hints), scores each update with
|
|
51
|
+
a **transparent, conservative priority**, and only ever runs a real update after you
|
|
52
|
+
explicitly confirm the exact command.
|
|
53
|
+
|
|
54
|
+
## Features
|
|
55
|
+
|
|
56
|
+
- **Dual package managers** — `pacman` (Arch) and `apt-get`/`apt-cache` (Debian, Kali, Ubuntu).
|
|
57
|
+
- **Sizes you can plan with** — download and installed sizes are read from the package
|
|
58
|
+
manager's own metadata, never guessed.
|
|
59
|
+
- **Transparent priorities** — every CRITICAL/HIGH/MEDIUM/LOW label comes with the
|
|
60
|
+
human-readable reasons behind it. Unknown facts are shown as “Unknown”, never invented.
|
|
61
|
+
- **Security-first updates** — nothing runs automatically, nothing is removed or cleaned,
|
|
62
|
+
empty selections can never trigger a full-system upgrade, and disk-space + privilege
|
|
63
|
+
checks gate every real update.
|
|
64
|
+
- **Two interfaces** — a fast terminal CLI and a Textual TUI with selection, detail and
|
|
65
|
+
confirmation screens.
|
|
66
|
+
- **Mock mode** — preview the whole tool on any machine without touching a real package
|
|
67
|
+
manager.
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install pkgwise # core (storage + CLI)
|
|
73
|
+
pip install "pkgwise[all]" # + TUI (textual) and enhanced storage stats (psutil)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Requires Python 3.9+. Works on Arch-based and Debian-based Linux systems.
|
|
77
|
+
|
|
78
|
+
## Quick start
|
|
79
|
+
|
|
80
|
+
```console
|
|
81
|
+
$ pkgwise check # storage summary + available updates, prioritized
|
|
82
|
+
$ pkgwise updates # just the pending updates
|
|
83
|
+
$ pkgwise storage # disk information only (no package manager needed)
|
|
84
|
+
$ pkgwise --refresh # refresh package indexes first (network)
|
|
85
|
+
$ pkgwise # launch the interactive TUI
|
|
86
|
+
$ pkgwise --version # version and system info
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Example output
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
STORAGE
|
|
93
|
+
Total : 291.4 GB
|
|
94
|
+
Used : 87.3 GB
|
|
95
|
+
Free : 189.2 GB
|
|
96
|
+
Usage : 30.0%
|
|
97
|
+
System: Arch Linux (pacman)
|
|
98
|
+
|
|
99
|
+
UPDATES (3 available, ~150 MB to download)
|
|
100
|
+
🔴 CRITICAL openssl 3.0.1 -> 3.0.2 Security-related update (2.5 MB / 9 MB)
|
|
101
|
+
🟠 HIGH linux 6.0.1 -> 6.0.2 Kernel package — needs reboot (38 MB / 700 MB)
|
|
102
|
+
🟡 MEDIUM figlet 2.2.5 -> 2.2.6 No security metadata detected (0.5 MB / 1 MB)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## How priorities work
|
|
106
|
+
|
|
107
|
+
PkgWise collects only **observable facts** about a package — security metadata, whether it
|
|
108
|
+
is a kernel, a core system component or a system library — and maps them through a small,
|
|
109
|
+
documented scoring table in `pkgwise/core/priority.py`. Labels always come with their
|
|
110
|
+
reasons, so nothing is ever marked important without an explanation.
|
|
111
|
+
|
|
112
|
+
## Safety model
|
|
113
|
+
|
|
114
|
+
| Guarantee | Where enforced |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| Nothing runs automatically | `cli.py`, TUI confirm screens |
|
|
117
|
+
| Exact command shown before running | backends' `install_command()` |
|
|
118
|
+
| Empty selection can't upgrade everything | backends return `[]` for empty selections |
|
|
119
|
+
| Disk-space check before install | `analyzer.prepare_update()` |
|
|
120
|
+
| Privilege check before install | `analyzer.prepare_update()` |
|
|
121
|
+
| No packages removed or cleaned | analyzer never invokes remove/clean |
|
|
122
|
+
|
|
123
|
+
## Development
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
127
|
+
pip install -e ".[all,test]"
|
|
128
|
+
pytest
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
See [CONTRIBUTING](docs/CONTRIBUTING.md) and [DEVELOPMENT](docs/DEVELOPMENT.md).
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
Released under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
pkgwise/__init__.py,sha256=OlE0qyc5Nhf_aTyfLZ0zV6gZJlEWQ1vg6sJStc52_QA,365
|
|
2
|
+
pkgwise/__main__.py,sha256=fVkkp3Ye5DEcwWQjcG6oLPjKe4dWGg0Ac6Fbv6g2w5g,117
|
|
3
|
+
pkgwise/cli.py,sha256=RVV72j2IqzqzAW5umaaVTwkL0m0dcxiKu5JZIGaB1dU,7269
|
|
4
|
+
pkgwise/mockdata.py,sha256=nkDOKPgbJp-TQCK6Ba1wvYI6_omzSzjCdBDsQd6mhfg,1684
|
|
5
|
+
pkgwise/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
pkgwise/backends/__init__.py,sha256=H652nrCMpN4McU2kQLwSe0baEU2IWyWS7cWN29XviFQ,85
|
|
7
|
+
pkgwise/backends/arch.py,sha256=gzKj9RhkPxsxmwmYst-_M28lz0TO2UHc6wRxvYRfvhg,7652
|
|
8
|
+
pkgwise/backends/base.py,sha256=e01568cX-bcYs4gDMYoWr8sn61GviuSP8MM7s1FiDLY,3342
|
|
9
|
+
pkgwise/backends/debian.py,sha256=tWwdDnS4DyA-6jh4HvbvDiR97TEKpOFgFwc7U5WmKa4,8853
|
|
10
|
+
pkgwise/backends/factory.py,sha256=lmXnIEU32pnKv6H8EoB-LVJk0J8Ew6qnd9Hj4lD_BEA,2247
|
|
11
|
+
pkgwise/core/__init__.py,sha256=BFv6g_Ne2egrSapU20SuNReqjLnLYJFvPI9M946gy-w,77
|
|
12
|
+
pkgwise/core/analyzer.py,sha256=8bDnKwq9aIupv0Ox6yzCgvIUNmSMwSZr-DTA1ssLCo8,5895
|
|
13
|
+
pkgwise/core/models.py,sha256=GVVVoAbJY1tXmSwFaT7fRfNU7ky8GNydPhkndDxSXao,3647
|
|
14
|
+
pkgwise/core/priority.py,sha256=WQ_jWldItz6eeZ5WkxodWP8pDM0NGWj5CviAP17fBU0,5734
|
|
15
|
+
pkgwise/core/storage.py,sha256=UUFumE2qUrs8AHuG2ailXZ3xT2VGeFGv6TOHQVqBaH8,2516
|
|
16
|
+
pkgwise/ui/__init__.py,sha256=FhV_LN_qek0ULtSBo500IuA70zaBW9Rp5Ed5IdJAwBE,68
|
|
17
|
+
pkgwise/ui/app.py,sha256=QWhnuFqpEjq5b3hC-NVkXiDMvv8rWzB_JCAjmvhOEtg,8579
|
|
18
|
+
pkgwise/ui/screens.py,sha256=VBZcgPIoFFDUUneAmVO8hOycutiSKukLYZqg8i1i_wA,14624
|
|
19
|
+
pkgwise/ui/widgets.py,sha256=DuyVHiUFzB9BPGhIjPozp-JJDkLHXb8H8xqhJRNIzAs,1821
|
|
20
|
+
pkgwise/utils/__init__.py,sha256=VgvRg1By6tbsdszCgv-GsP3DOvzsYtvAphnUykd8-nI,83
|
|
21
|
+
pkgwise/utils/commands.py,sha256=12_qv7UKSZqjchg_xO0uutVJHtK4QVzitLs4QRWfon4,3009
|
|
22
|
+
pkgwise/utils/config.py,sha256=hXuEE4ad3uuV2xE5ijyZWl_RtRB5PFrS2Q3MK1mFFVo,4007
|
|
23
|
+
pkgwise/utils/distro.py,sha256=ozlbOtJM475mHABhay2yFG-V9JawI7A_9-lVOGgK-r0,2944
|
|
24
|
+
pkgwise/utils/formatting.py,sha256=i2YXTgo4dfMroEggeDDUj3IivNi0PbjgWqnQyASsYtw,1853
|
|
25
|
+
pkgwise-0.1.0.dist-info/licenses/LICENSE,sha256=IxtfAjoB9TmAaU0EZJrIvZSRktBEAoq_Lz7VWJRInT8,1075
|
|
26
|
+
pkgwise-0.1.0.dist-info/METADATA,sha256=zgwUBY2ChD-bbS3K1kKmqHj-rCDS4Qszj2wHXGEaNww,5592
|
|
27
|
+
pkgwise-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
28
|
+
pkgwise-0.1.0.dist-info/entry_points.txt,sha256=5754cuiOi8CUbFQQVb-wgzsvpjLszRduzbbZDeeG2pU,45
|
|
29
|
+
pkgwise-0.1.0.dist-info/top_level.txt,sha256=wen5xI0tTYZtZ2Mcbn5b8FQ1KV_nGuQNlNCGngDG6wk,8
|
|
30
|
+
pkgwise-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PkgWise Developers
|
|
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.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pkgwise
|