nibble-cli 0.3.0__tar.gz → 0.4.0__tar.gz
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.
- {nibble_cli-0.3.0 → nibble_cli-0.4.0}/PKG-INFO +2 -2
- nibble_cli-0.4.0/nibble_cli/__init__.py +0 -0
- nibble_cli-0.4.0/nibble_cli/checksum.py +50 -0
- nibble_cli-0.4.0/nibble_cli/cleanup.py +69 -0
- nibble_cli-0.4.0/nibble_cli/download.py +24 -0
- nibble_cli-0.4.0/nibble_cli/install.py +117 -0
- nibble_cli-0.4.0/nibble_cli/installer.py +20 -0
- {nibble_cli-0.3.0 → nibble_cli-0.4.0}/nibble_cli.egg-info/PKG-INFO +2 -2
- nibble_cli-0.4.0/nibble_cli.egg-info/SOURCES.txt +12 -0
- nibble_cli-0.4.0/nibble_cli.egg-info/entry_points.txt +3 -0
- nibble_cli-0.4.0/nibble_cli.egg-info/top_level.txt +1 -0
- {nibble_cli-0.3.0 → nibble_cli-0.4.0}/setup.py +5 -4
- nibble_cli-0.3.0/nibble_cli.egg-info/SOURCES.txt +0 -7
- nibble_cli-0.3.0/nibble_cli.egg-info/entry_points.txt +0 -2
- nibble_cli-0.3.0/nibble_cli.egg-info/top_level.txt +0 -1
- nibble_cli-0.3.0/nibble_installer.py +0 -152
- {nibble_cli-0.3.0 → nibble_cli-0.4.0}/nibble_cli.egg-info/dependency_links.txt +0 -0
- {nibble_cli-0.3.0 → nibble_cli-0.4.0}/setup.cfg +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nibble-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Fast local network scanner with hardware identification and a terminal UI
|
|
5
5
|
Home-page: https://github.com/backendsystems/nibble
|
|
6
6
|
Author: saberd
|
|
7
|
-
Author-email:
|
|
7
|
+
Author-email: mail@saberd.com
|
|
8
8
|
Classifier: Development Status :: 4 - Beta
|
|
9
9
|
Classifier: Environment :: Console
|
|
10
10
|
Classifier: Intended Audience :: System Administrators
|
|
File without changes
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_checksum(text, archive_name):
|
|
5
|
+
for raw_line in text.splitlines():
|
|
6
|
+
line = raw_line.strip()
|
|
7
|
+
if not line or line.startswith("#"):
|
|
8
|
+
continue
|
|
9
|
+
parts = line.split()
|
|
10
|
+
if len(parts) < 2:
|
|
11
|
+
continue
|
|
12
|
+
checksum = parts[0].lower()
|
|
13
|
+
filename = parts[-1].lstrip("*")
|
|
14
|
+
if filename == archive_name:
|
|
15
|
+
return checksum
|
|
16
|
+
return None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def sha256_file(path):
|
|
20
|
+
with open(path, "rb") as f:
|
|
21
|
+
return hashlib.sha256(f.read()).hexdigest()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def verify_release_checksum(base_url, project, version, archive_name, archive_path, download_text):
|
|
25
|
+
checksum_candidates = [
|
|
26
|
+
"checksums.txt",
|
|
27
|
+
f"{project}_{version}_checksums.txt",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
checksum_text = None
|
|
31
|
+
checksum_name = None
|
|
32
|
+
for candidate in checksum_candidates:
|
|
33
|
+
text = download_text(f"{base_url}/{candidate}")
|
|
34
|
+
if text is not None:
|
|
35
|
+
checksum_text = text
|
|
36
|
+
checksum_name = candidate
|
|
37
|
+
break
|
|
38
|
+
|
|
39
|
+
if checksum_text is None:
|
|
40
|
+
raise RuntimeError(
|
|
41
|
+
f"no checksum file found for v{version} (tried: {', '.join(checksum_candidates)})"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
expected = get_checksum(checksum_text, archive_name)
|
|
45
|
+
if expected is None:
|
|
46
|
+
raise RuntimeError(f"checksum for {archive_name} not found in {checksum_name}")
|
|
47
|
+
|
|
48
|
+
actual = sha256_file(archive_path)
|
|
49
|
+
if actual != expected:
|
|
50
|
+
raise RuntimeError(f"checksum mismatch for {archive_name}")
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import argparse
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import sys
|
|
6
|
+
from importlib import metadata
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
PROJECT = "nibble"
|
|
10
|
+
DIST_NAME = "nibble-cli"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def cache_root():
|
|
14
|
+
if os.name == "nt":
|
|
15
|
+
base = Path(os.environ.get("LOCALAPPDATA", str(Path.home())))
|
|
16
|
+
else:
|
|
17
|
+
base = Path.home() / ".local" / "share"
|
|
18
|
+
return base / PROJECT
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def installed_version():
|
|
22
|
+
try:
|
|
23
|
+
version = metadata.version(DIST_NAME)
|
|
24
|
+
except metadata.PackageNotFoundError:
|
|
25
|
+
return None
|
|
26
|
+
return version[1:] if version.startswith("v") else version
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def remove_path(path):
|
|
30
|
+
if not path.exists():
|
|
31
|
+
return False
|
|
32
|
+
shutil.rmtree(path)
|
|
33
|
+
return True
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def main():
|
|
37
|
+
parser = argparse.ArgumentParser(description="Clean cached nibble binaries.")
|
|
38
|
+
parser.add_argument(
|
|
39
|
+
"--all",
|
|
40
|
+
action="store_true",
|
|
41
|
+
help="Remove all cached nibble versions.",
|
|
42
|
+
)
|
|
43
|
+
args = parser.parse_args()
|
|
44
|
+
|
|
45
|
+
root = cache_root()
|
|
46
|
+
if args.all:
|
|
47
|
+
removed = remove_path(root)
|
|
48
|
+
if removed:
|
|
49
|
+
print(f"Removed cache: {root}")
|
|
50
|
+
else:
|
|
51
|
+
print(f"No cache found at: {root}")
|
|
52
|
+
return 0
|
|
53
|
+
|
|
54
|
+
version = installed_version()
|
|
55
|
+
if version is None:
|
|
56
|
+
print("nibble-cli is not installed. Use --all to remove cache root.", file=sys.stderr)
|
|
57
|
+
return 1
|
|
58
|
+
|
|
59
|
+
target = root / version
|
|
60
|
+
removed = remove_path(target)
|
|
61
|
+
if removed:
|
|
62
|
+
print(f"Removed cache for v{version}: {target}")
|
|
63
|
+
else:
|
|
64
|
+
print(f"No cache found for v{version}: {target}")
|
|
65
|
+
return 0
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
import urllib.error
|
|
3
|
+
import urllib.request
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def download_asset(url, out_path):
|
|
7
|
+
try:
|
|
8
|
+
with urllib.request.urlopen(url) as resp, open(out_path, "wb") as f:
|
|
9
|
+
shutil.copyfileobj(resp, f)
|
|
10
|
+
return True
|
|
11
|
+
except urllib.error.HTTPError as e:
|
|
12
|
+
if e.code == 404:
|
|
13
|
+
return False
|
|
14
|
+
raise
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def download_text(url):
|
|
18
|
+
try:
|
|
19
|
+
with urllib.request.urlopen(url) as resp:
|
|
20
|
+
return resp.read().decode("utf-8", errors="replace")
|
|
21
|
+
except urllib.error.HTTPError as e:
|
|
22
|
+
if e.code == 404:
|
|
23
|
+
return None
|
|
24
|
+
raise
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import platform
|
|
3
|
+
import shutil
|
|
4
|
+
import tarfile
|
|
5
|
+
import tempfile
|
|
6
|
+
from importlib import metadata
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from nibble_cli.checksum import verify_release_checksum
|
|
10
|
+
from nibble_cli.download import download_asset, download_text
|
|
11
|
+
|
|
12
|
+
OWNER = "backendsystems"
|
|
13
|
+
PROJECT = "nibble"
|
|
14
|
+
DIST_NAME = "nibble-cli"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def platform_triplet():
|
|
18
|
+
system = platform.system().lower()
|
|
19
|
+
machine = platform.machine().lower()
|
|
20
|
+
|
|
21
|
+
os_map = {
|
|
22
|
+
"linux": "linux",
|
|
23
|
+
"darwin": "darwin",
|
|
24
|
+
"windows": "windows",
|
|
25
|
+
}
|
|
26
|
+
arch_map = {
|
|
27
|
+
"x86_64": "amd64",
|
|
28
|
+
"amd64": "amd64",
|
|
29
|
+
"aarch64": "arm64",
|
|
30
|
+
"arm64": "arm64",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
os_name = os_map.get(system)
|
|
34
|
+
arch = arch_map.get(machine)
|
|
35
|
+
if not os_name or not arch:
|
|
36
|
+
raise RuntimeError(f"unsupported platform: system={system}, arch={machine}")
|
|
37
|
+
return os_name, arch
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def install_dir():
|
|
41
|
+
if os.name == "nt":
|
|
42
|
+
base = Path(os.environ.get("LOCALAPPDATA", str(Path.home())))
|
|
43
|
+
else:
|
|
44
|
+
base = Path.home() / ".local" / "share"
|
|
45
|
+
path = base / PROJECT
|
|
46
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
return path
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def dist_version():
|
|
51
|
+
version = metadata.version(DIST_NAME)
|
|
52
|
+
return version[1:] if version.startswith("v") else version
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def binary_name():
|
|
56
|
+
return f"{PROJECT}.exe" if os.name == "nt" else PROJECT
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def release_base(version):
|
|
60
|
+
return f"https://github.com/{OWNER}/{PROJECT}/releases/download/v{version}"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def extract_binary(archive_path, dest_binary):
|
|
64
|
+
wanted = {binary_name(), PROJECT, f"{PROJECT}.exe"}
|
|
65
|
+
|
|
66
|
+
with tarfile.open(archive_path, "r:*") as tf:
|
|
67
|
+
member = next(
|
|
68
|
+
(m for m in tf.getmembers() if m.isfile() and Path(m.name).name in wanted),
|
|
69
|
+
None,
|
|
70
|
+
)
|
|
71
|
+
if member is None:
|
|
72
|
+
raise RuntimeError("binary not found inside release archive")
|
|
73
|
+
|
|
74
|
+
src = tf.extractfile(member)
|
|
75
|
+
if src is None:
|
|
76
|
+
raise RuntimeError("failed to extract binary from release archive")
|
|
77
|
+
|
|
78
|
+
with src, open(dest_binary, "wb") as dst:
|
|
79
|
+
shutil.copyfileobj(src, dst)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def ensure_installed():
|
|
83
|
+
version = dist_version()
|
|
84
|
+
os_name, arch = platform_triplet()
|
|
85
|
+
|
|
86
|
+
# Keep binaries in a versioned local cache directory.
|
|
87
|
+
install_root = install_dir()
|
|
88
|
+
binary_path = install_root / version / "bin" / binary_name()
|
|
89
|
+
binary_path.parent.mkdir(parents=True, exist_ok=True)
|
|
90
|
+
if binary_path.exists():
|
|
91
|
+
return binary_path
|
|
92
|
+
|
|
93
|
+
asset = f"{PROJECT}_{os_name}_{arch}.tar.gz"
|
|
94
|
+
release_url = release_base(version)
|
|
95
|
+
archive_url = f"{release_url}/{asset}"
|
|
96
|
+
|
|
97
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
98
|
+
# Download and verify the release asset before extracting it.
|
|
99
|
+
archive_path = Path(tmp) / asset
|
|
100
|
+
if not download_asset(archive_url, archive_path):
|
|
101
|
+
raise RuntimeError(
|
|
102
|
+
f"release asset not found for {os_name}/{arch} at v{version}: {asset}"
|
|
103
|
+
)
|
|
104
|
+
verify_release_checksum(
|
|
105
|
+
base_url=release_url,
|
|
106
|
+
project=PROJECT,
|
|
107
|
+
version=version,
|
|
108
|
+
archive_name=asset,
|
|
109
|
+
archive_path=archive_path,
|
|
110
|
+
download_text=download_text,
|
|
111
|
+
)
|
|
112
|
+
# Extract the nibble binary from the archive into the cache path.
|
|
113
|
+
extract_binary(archive_path, binary_path)
|
|
114
|
+
|
|
115
|
+
if os.name != "nt":
|
|
116
|
+
binary_path.chmod(0o755)
|
|
117
|
+
return binary_path
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from nibble_cli.install import ensure_installed
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
try:
|
|
10
|
+
binary = ensure_installed()
|
|
11
|
+
except Exception as e:
|
|
12
|
+
print(f"nibble install error: {e}", file=sys.stderr)
|
|
13
|
+
return 1
|
|
14
|
+
|
|
15
|
+
result = subprocess.run([str(binary)] + sys.argv[1:])
|
|
16
|
+
return result.returncode
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
if __name__ == "__main__":
|
|
20
|
+
raise SystemExit(main())
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nibble-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Fast local network scanner with hardware identification and a terminal UI
|
|
5
5
|
Home-page: https://github.com/backendsystems/nibble
|
|
6
6
|
Author: saberd
|
|
7
|
-
Author-email:
|
|
7
|
+
Author-email: mail@saberd.com
|
|
8
8
|
Classifier: Development Status :: 4 - Beta
|
|
9
9
|
Classifier: Environment :: Console
|
|
10
10
|
Classifier: Intended Audience :: System Administrators
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
setup.py
|
|
2
|
+
nibble_cli/__init__.py
|
|
3
|
+
nibble_cli/checksum.py
|
|
4
|
+
nibble_cli/cleanup.py
|
|
5
|
+
nibble_cli/download.py
|
|
6
|
+
nibble_cli/install.py
|
|
7
|
+
nibble_cli/installer.py
|
|
8
|
+
nibble_cli.egg-info/PKG-INFO
|
|
9
|
+
nibble_cli.egg-info/SOURCES.txt
|
|
10
|
+
nibble_cli.egg-info/dependency_links.txt
|
|
11
|
+
nibble_cli.egg-info/entry_points.txt
|
|
12
|
+
nibble_cli.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
nibble_cli
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from setuptools import setup
|
|
1
|
+
from setuptools import find_packages, setup
|
|
2
2
|
import os
|
|
3
3
|
import subprocess
|
|
4
4
|
|
|
@@ -21,12 +21,13 @@ setup(
|
|
|
21
21
|
long_description=open("README.md").read() if os.path.exists("README.md") else "",
|
|
22
22
|
long_description_content_type="text/markdown",
|
|
23
23
|
author="saberd",
|
|
24
|
-
author_email="
|
|
24
|
+
author_email="mail@saberd.com",
|
|
25
25
|
url="https://github.com/backendsystems/nibble",
|
|
26
|
-
|
|
26
|
+
packages=find_packages(),
|
|
27
27
|
entry_points={
|
|
28
28
|
"console_scripts": [
|
|
29
|
-
"nibble=
|
|
29
|
+
"nibble=nibble_cli.installer:main",
|
|
30
|
+
"nibble-cleanup=nibble_cli.cleanup:main",
|
|
30
31
|
],
|
|
31
32
|
},
|
|
32
33
|
install_requires=[],
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
nibble_installer
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Nibble CLI wrapper for pip.
|
|
4
|
-
Downloads the matching GitHub Release binary and executes it.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import os
|
|
8
|
-
import platform
|
|
9
|
-
import shutil
|
|
10
|
-
import subprocess
|
|
11
|
-
import sys
|
|
12
|
-
import tarfile
|
|
13
|
-
import tempfile
|
|
14
|
-
import urllib.error
|
|
15
|
-
import urllib.request
|
|
16
|
-
import zipfile
|
|
17
|
-
from importlib import metadata
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
|
|
20
|
-
REPO = "backendsystems/nibble"
|
|
21
|
-
PROJECT = "nibble"
|
|
22
|
-
DIST_NAME = "nibble-cli"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def _platform_triplet():
|
|
26
|
-
system = platform.system().lower()
|
|
27
|
-
machine = platform.machine().lower()
|
|
28
|
-
|
|
29
|
-
os_map = {
|
|
30
|
-
"linux": "linux",
|
|
31
|
-
"darwin": "darwin",
|
|
32
|
-
"windows": "windows",
|
|
33
|
-
}
|
|
34
|
-
arch_map = {
|
|
35
|
-
"x86_64": "amd64",
|
|
36
|
-
"amd64": "amd64",
|
|
37
|
-
"aarch64": "arm64",
|
|
38
|
-
"arm64": "arm64",
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
os_name = os_map.get(system)
|
|
42
|
-
arch = arch_map.get(machine)
|
|
43
|
-
if not os_name or not arch:
|
|
44
|
-
raise RuntimeError(f"unsupported platform: system={system}, arch={machine}")
|
|
45
|
-
return os_name, arch
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def _install_dir():
|
|
49
|
-
if os.name == "nt":
|
|
50
|
-
base = Path(os.environ.get("LOCALAPPDATA", str(Path.home())))
|
|
51
|
-
else:
|
|
52
|
-
base = Path.home() / ".local" / "share"
|
|
53
|
-
path = base / PROJECT
|
|
54
|
-
path.mkdir(parents=True, exist_ok=True)
|
|
55
|
-
return path
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def _dist_version():
|
|
59
|
-
version = metadata.version(DIST_NAME)
|
|
60
|
-
return version[1:] if version.startswith("v") else version
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def _binary_name():
|
|
64
|
-
return f"{PROJECT}.exe" if os.name == "nt" else PROJECT
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def _download_asset(url, out_path):
|
|
68
|
-
try:
|
|
69
|
-
with urllib.request.urlopen(url) as resp, open(out_path, "wb") as f:
|
|
70
|
-
shutil.copyfileobj(resp, f)
|
|
71
|
-
return True
|
|
72
|
-
except urllib.error.HTTPError as e:
|
|
73
|
-
if e.code == 404:
|
|
74
|
-
return False
|
|
75
|
-
raise
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def _extract_binary(archive_path, dest_binary):
|
|
79
|
-
bin_names = {_binary_name(), PROJECT, f"{PROJECT}.exe"}
|
|
80
|
-
|
|
81
|
-
if str(archive_path).endswith(".zip"):
|
|
82
|
-
with zipfile.ZipFile(archive_path, "r") as zf:
|
|
83
|
-
for member in zf.namelist():
|
|
84
|
-
name = Path(member).name
|
|
85
|
-
if name in bin_names:
|
|
86
|
-
with zf.open(member) as src, open(dest_binary, "wb") as dst:
|
|
87
|
-
shutil.copyfileobj(src, dst)
|
|
88
|
-
return
|
|
89
|
-
else:
|
|
90
|
-
with tarfile.open(archive_path, "r:*") as tf:
|
|
91
|
-
for member in tf.getmembers():
|
|
92
|
-
if not member.isfile():
|
|
93
|
-
continue
|
|
94
|
-
name = Path(member.name).name
|
|
95
|
-
if name in bin_names:
|
|
96
|
-
src = tf.extractfile(member)
|
|
97
|
-
if src is None:
|
|
98
|
-
continue
|
|
99
|
-
with src, open(dest_binary, "wb") as dst:
|
|
100
|
-
shutil.copyfileobj(src, dst)
|
|
101
|
-
return
|
|
102
|
-
|
|
103
|
-
raise RuntimeError("binary not found inside release archive")
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def ensure_installed():
|
|
107
|
-
install_dir = _install_dir()
|
|
108
|
-
version = _dist_version()
|
|
109
|
-
version_dir = install_dir / version / "bin"
|
|
110
|
-
version_dir.mkdir(parents=True, exist_ok=True)
|
|
111
|
-
binary_path = version_dir / _binary_name()
|
|
112
|
-
if binary_path.exists():
|
|
113
|
-
return binary_path
|
|
114
|
-
|
|
115
|
-
os_name, arch = _platform_triplet()
|
|
116
|
-
asset_base = f"{PROJECT}_{os_name}_{arch}"
|
|
117
|
-
candidates = [f"{asset_base}.tar.gz", f"{asset_base}.zip"]
|
|
118
|
-
|
|
119
|
-
with tempfile.TemporaryDirectory() as tmp:
|
|
120
|
-
tmpdir = Path(tmp)
|
|
121
|
-
archive_path = None
|
|
122
|
-
for asset in candidates:
|
|
123
|
-
url = f"https://github.com/{REPO}/releases/download/v{version}/{asset}"
|
|
124
|
-
local = tmpdir / asset
|
|
125
|
-
if _download_asset(url, local):
|
|
126
|
-
archive_path = local
|
|
127
|
-
break
|
|
128
|
-
if archive_path is None:
|
|
129
|
-
raise RuntimeError(
|
|
130
|
-
f"no release asset found for {os_name}/{arch} at v{version} (tried: {', '.join(candidates)})"
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
_extract_binary(archive_path, binary_path)
|
|
134
|
-
|
|
135
|
-
if os.name != "nt":
|
|
136
|
-
binary_path.chmod(0o755)
|
|
137
|
-
return binary_path
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def main():
|
|
141
|
-
try:
|
|
142
|
-
binary = ensure_installed()
|
|
143
|
-
except Exception as e:
|
|
144
|
-
print(f"nibble install error: {e}", file=sys.stderr)
|
|
145
|
-
return 1
|
|
146
|
-
|
|
147
|
-
result = subprocess.run([str(binary)] + sys.argv[1:])
|
|
148
|
-
return result.returncode
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if __name__ == "__main__":
|
|
152
|
-
raise SystemExit(main())
|
|
File without changes
|
|
File without changes
|