helper-cli 0.1.11__py3-none-any.whl → 0.1.21__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.
- helper/__init__.py +7 -0
- helper/commands/arch.py +8 -2
- helper/commands/docker.py +378 -210
- helper/commands/file.py +202 -0
- helper/commands/internal_ip.py +10 -2
- helper/commands/nixos.py +57 -31
- helper/commands/public_ip.py +11 -2
- helper/commands/speed.py +38 -0
- helper/commands/system_info.py +326 -0
- helper/commands/venv.py +130 -0
- helper/main.py +68 -27
- helper/table.py +29 -0
- helper_cli-0.1.21.dist-info/METADATA +61 -0
- helper_cli-0.1.21.dist-info/RECORD +19 -0
- {helper_cli-0.1.11.dist-info → helper_cli-0.1.21.dist-info}/entry_points.txt +1 -0
- helper_cli-0.1.11.dist-info/METADATA +0 -6
- helper_cli-0.1.11.dist-info/RECORD +0 -13
- {helper_cli-0.1.11.dist-info → helper_cli-0.1.21.dist-info}/WHEEL +0 -0
- {helper_cli-0.1.11.dist-info → helper_cli-0.1.21.dist-info}/top_level.txt +0 -0
helper/commands/file.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""File management commands."""
|
|
2
|
+
import os
|
|
3
|
+
import time
|
|
4
|
+
import click
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List, Optional, Tuple, Callable
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_sorted_files(
|
|
11
|
+
directory: str,
|
|
12
|
+
extension: Optional[str] = None,
|
|
13
|
+
sort_key: Callable[[os.DirEntry], float] = None,
|
|
14
|
+
reverse: bool = False
|
|
15
|
+
) -> List[Tuple[os.DirEntry, float]]:
|
|
16
|
+
"""Get files sorted by specified key.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
directory: Directory to search in
|
|
20
|
+
extension: Optional file extension to filter by (without dot)
|
|
21
|
+
sort_key: Function to extract sort key from DirEntry
|
|
22
|
+
reverse: If True, sort in descending order
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
List of (file_entry, sort_key) tuples
|
|
26
|
+
"""
|
|
27
|
+
if not os.path.isdir(directory):
|
|
28
|
+
raise click.BadParameter(f"Directory not found: {directory}")
|
|
29
|
+
|
|
30
|
+
files = []
|
|
31
|
+
with os.scandir(directory) as it:
|
|
32
|
+
for entry in it:
|
|
33
|
+
if not entry.is_file():
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
if extension and not entry.name.lower().endswith(f".{extension.lower()}"):
|
|
37
|
+
continue
|
|
38
|
+
|
|
39
|
+
if sort_key:
|
|
40
|
+
try:
|
|
41
|
+
key = sort_key(entry)
|
|
42
|
+
files.append((entry, key))
|
|
43
|
+
except (OSError, ValueError) as e:
|
|
44
|
+
continue
|
|
45
|
+
else:
|
|
46
|
+
files.append((entry, 0))
|
|
47
|
+
|
|
48
|
+
# Sort by the sort key
|
|
49
|
+
files.sort(key=lambda x: x[1], reverse=reverse)
|
|
50
|
+
return files
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def format_file_info(entry: os.DirEntry, size: bool = True, modified: bool = True) -> str:
|
|
54
|
+
"""Format file information for display."""
|
|
55
|
+
info = []
|
|
56
|
+
if size:
|
|
57
|
+
try:
|
|
58
|
+
size_bytes = entry.stat().st_size
|
|
59
|
+
size_str = human_readable_size(size_bytes)
|
|
60
|
+
info.append(f"{size_str:>10}")
|
|
61
|
+
except OSError:
|
|
62
|
+
info.append(" " * 10)
|
|
63
|
+
|
|
64
|
+
if modified:
|
|
65
|
+
try:
|
|
66
|
+
mtime = entry.stat().st_mtime
|
|
67
|
+
mtime_str = datetime.fromtimestamp(mtime).strftime('%Y-%m-%d %H:%M:%S')
|
|
68
|
+
info.append(mtime_str)
|
|
69
|
+
except OSError:
|
|
70
|
+
info.append(" " * 19)
|
|
71
|
+
|
|
72
|
+
info.append(entry.name)
|
|
73
|
+
return " ".join(info)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def human_readable_size(size_bytes: int) -> str:
|
|
77
|
+
"""Convert size in bytes to human readable format."""
|
|
78
|
+
if size_bytes == 0:
|
|
79
|
+
return "0B"
|
|
80
|
+
|
|
81
|
+
units = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
82
|
+
unit_idx = 0
|
|
83
|
+
size = float(size_bytes)
|
|
84
|
+
|
|
85
|
+
while size >= 1024 and unit_idx < len(units) - 1:
|
|
86
|
+
size /= 1024
|
|
87
|
+
unit_idx += 1
|
|
88
|
+
|
|
89
|
+
return f"{size:.1f}{units[unit_idx]}"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@click.group(name="file")
|
|
93
|
+
@click.option(
|
|
94
|
+
"--directory",
|
|
95
|
+
"-d",
|
|
96
|
+
type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True),
|
|
97
|
+
default=".",
|
|
98
|
+
help="Directory to search in (default: current directory)",
|
|
99
|
+
)
|
|
100
|
+
@click.option(
|
|
101
|
+
"--extension",
|
|
102
|
+
"-e",
|
|
103
|
+
type=str,
|
|
104
|
+
help="Filter by file extension (without dot)",
|
|
105
|
+
)
|
|
106
|
+
@click.option(
|
|
107
|
+
"--size",
|
|
108
|
+
"-s",
|
|
109
|
+
is_flag=True,
|
|
110
|
+
help="Show file sizes",
|
|
111
|
+
)
|
|
112
|
+
@click.option(
|
|
113
|
+
"--modified",
|
|
114
|
+
"-m",
|
|
115
|
+
is_flag=True,
|
|
116
|
+
help="Show last modified time",
|
|
117
|
+
)
|
|
118
|
+
@click.pass_context
|
|
119
|
+
def file_cmd(ctx: click.Context, directory: str, extension: str, size: bool, modified: bool) -> None:
|
|
120
|
+
"""File management and processing commands."""
|
|
121
|
+
ctx.ensure_object(dict)
|
|
122
|
+
ctx.obj["directory"] = directory
|
|
123
|
+
ctx.obj["extension"] = extension
|
|
124
|
+
ctx.obj["show_size"] = size
|
|
125
|
+
ctx.obj["show_modified"] = modified
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@file_cmd.command(name="newest")
|
|
129
|
+
@click.option(
|
|
130
|
+
"--count",
|
|
131
|
+
"-n",
|
|
132
|
+
type=int,
|
|
133
|
+
default=1,
|
|
134
|
+
help="Number of newest files to show (default: 1)",
|
|
135
|
+
)
|
|
136
|
+
@click.pass_context
|
|
137
|
+
def newest_files(ctx: click.Context, count: int) -> None:
|
|
138
|
+
"""Show the newest files in the directory."""
|
|
139
|
+
directory = ctx.obj["directory"]
|
|
140
|
+
extension = ctx.obj["extension"]
|
|
141
|
+
show_size = ctx.obj["show_size"]
|
|
142
|
+
show_modified = ctx.obj["show_modified"]
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
files = get_sorted_files(
|
|
146
|
+
directory=directory,
|
|
147
|
+
extension=extension,
|
|
148
|
+
sort_key=lambda e: e.stat().st_mtime,
|
|
149
|
+
reverse=True
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
if not files:
|
|
153
|
+
click.echo("No files found.")
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
click.echo(f"Newest files in {directory}:")
|
|
157
|
+
for i, (entry, _) in enumerate(files[:count], 1):
|
|
158
|
+
click.echo(f"{i}. {format_file_info(entry, show_size, show_modified)}")
|
|
159
|
+
|
|
160
|
+
except Exception as e:
|
|
161
|
+
raise click.ClickException(str(e))
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@file_cmd.command(name="oldest")
|
|
165
|
+
@click.option(
|
|
166
|
+
"--count",
|
|
167
|
+
"-n",
|
|
168
|
+
type=int,
|
|
169
|
+
default=1,
|
|
170
|
+
help="Number of oldest files to show (default: 1)",
|
|
171
|
+
)
|
|
172
|
+
@click.pass_context
|
|
173
|
+
def oldest_files(ctx: click.Context, count: int) -> None:
|
|
174
|
+
"""Show the oldest files in the directory."""
|
|
175
|
+
directory = ctx.obj["directory"]
|
|
176
|
+
extension = ctx.obj["extension"]
|
|
177
|
+
show_size = ctx.obj["show_size"]
|
|
178
|
+
show_modified = ctx.obj["show_modified"]
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
files = get_sorted_files(
|
|
182
|
+
directory=directory,
|
|
183
|
+
extension=extension,
|
|
184
|
+
sort_key=lambda e: e.stat().st_mtime,
|
|
185
|
+
reverse=False
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if not files:
|
|
189
|
+
click.echo("No files found.")
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
click.echo(f"Oldest files in {directory}:")
|
|
193
|
+
for i, (entry, _) in enumerate(files[:count], 1):
|
|
194
|
+
click.echo(f"{i}. {format_file_info(entry, show_size, show_modified)}")
|
|
195
|
+
|
|
196
|
+
except Exception as e:
|
|
197
|
+
raise click.ClickException(str(e))
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# Add this to register the command group
|
|
201
|
+
def file():
|
|
202
|
+
return file_cmd
|
helper/commands/internal_ip.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import platform
|
|
2
2
|
import socket
|
|
3
3
|
import shutil
|
|
4
|
-
from ..utils import run_cmd
|
|
5
4
|
import click
|
|
5
|
+
from helper import __version__
|
|
6
|
+
from helper.utils import run_cmd
|
|
6
7
|
|
|
7
8
|
def get_internal_ip():
|
|
8
9
|
"""Get the internal IP address based on the operating system."""
|
|
@@ -23,5 +24,12 @@ def get_internal_ip():
|
|
|
23
24
|
|
|
24
25
|
@click.command()
|
|
25
26
|
def internal_ip():
|
|
26
|
-
"""Show local/internal IP
|
|
27
|
+
"""Show local/internal IP address.
|
|
28
|
+
|
|
29
|
+
Version: {}
|
|
30
|
+
|
|
31
|
+
Displays the internal IP address of the current machine.
|
|
32
|
+
The command automatically detects the operating system and uses the
|
|
33
|
+
appropriate method to retrieve the IP address.
|
|
34
|
+
""".format(__version__)
|
|
27
35
|
get_internal_ip()
|
helper/commands/nixos.py
CHANGED
|
@@ -1,65 +1,89 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"""NixOS related commands for the helper CLI.
|
|
2
|
+
|
|
3
|
+
This module provides commands to manage NixOS packages and system operations.
|
|
4
|
+
"""
|
|
5
|
+
|
|
3
6
|
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
4
11
|
|
|
5
12
|
def check_nixos():
|
|
6
13
|
"""Check if running on NixOS."""
|
|
7
14
|
try:
|
|
8
|
-
with open(
|
|
9
|
-
return
|
|
15
|
+
with open("/etc/os-release") as f:
|
|
16
|
+
return "NixOS" in f.read()
|
|
10
17
|
except FileNotFoundError:
|
|
11
18
|
return False
|
|
12
|
-
except
|
|
19
|
+
except (IOError, PermissionError) as e:
|
|
13
20
|
click.echo(f"Warning: Could not check if running on NixOS: {e}", err=True)
|
|
14
21
|
return False
|
|
15
22
|
|
|
23
|
+
|
|
16
24
|
def get_nixos_version():
|
|
17
|
-
"""Get NixOS version information.
|
|
25
|
+
"""Get NixOS version information.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
str: NixOS version information or error message
|
|
29
|
+
"""
|
|
18
30
|
if not check_nixos():
|
|
19
31
|
return "Not running NixOS"
|
|
20
|
-
|
|
32
|
+
|
|
21
33
|
try:
|
|
22
|
-
result = subprocess.run(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return "NixOS version
|
|
28
|
-
except
|
|
29
|
-
return
|
|
34
|
+
result = subprocess.run(
|
|
35
|
+
["nixos-version"], capture_output=True, text=True, check=True
|
|
36
|
+
)
|
|
37
|
+
return result.stdout.strip()
|
|
38
|
+
except subprocess.CalledProcessError as e:
|
|
39
|
+
return f"Error getting NixOS version: {e.stderr}"
|
|
40
|
+
except FileNotFoundError:
|
|
41
|
+
return "nixos-version command not found"
|
|
42
|
+
|
|
30
43
|
|
|
31
44
|
@click.group()
|
|
32
45
|
def nixos():
|
|
33
|
-
"""NixOS related commands."""
|
|
46
|
+
"""NixOS related commands (v0.1.19)."""
|
|
34
47
|
if not check_nixos():
|
|
35
|
-
click.echo(
|
|
48
|
+
click.echo(
|
|
49
|
+
"Warning: Not running on NixOS. Some commands may not work as expected.",
|
|
50
|
+
err=True,
|
|
51
|
+
)
|
|
52
|
+
|
|
36
53
|
|
|
37
54
|
@nixos.command()
|
|
38
55
|
def version():
|
|
39
56
|
"""Show NixOS version."""
|
|
40
57
|
click.echo(get_nixos_version())
|
|
41
58
|
|
|
59
|
+
|
|
42
60
|
@nixos.command()
|
|
43
|
-
@click.argument(
|
|
61
|
+
@click.argument("package", required=False)
|
|
44
62
|
def search(package):
|
|
45
63
|
"""Search for Nix packages."""
|
|
46
64
|
if not package:
|
|
47
65
|
click.echo("Please specify a package to search for")
|
|
48
66
|
return
|
|
49
|
-
|
|
67
|
+
|
|
50
68
|
try:
|
|
51
|
-
result = subprocess.run(
|
|
52
|
-
|
|
53
|
-
|
|
69
|
+
result = subprocess.run(
|
|
70
|
+
["nix-env", "-qa", package], capture_output=True, text=True, check=False
|
|
71
|
+
)
|
|
54
72
|
if result.returncode == 0:
|
|
55
73
|
click.echo(result.stdout)
|
|
56
74
|
else:
|
|
57
75
|
click.echo(f"Error searching for package: {result.stderr}", err=True)
|
|
58
|
-
except
|
|
76
|
+
except subprocess.SubprocessError as e:
|
|
59
77
|
click.echo(f"Error: {str(e)}", err=True)
|
|
60
78
|
|
|
79
|
+
|
|
61
80
|
@nixos.command()
|
|
62
|
-
@click.option(
|
|
81
|
+
@click.option(
|
|
82
|
+
"-f",
|
|
83
|
+
"--force",
|
|
84
|
+
is_flag=True,
|
|
85
|
+
help="Force garbage collection and remove all old generations",
|
|
86
|
+
)
|
|
63
87
|
def clean(force):
|
|
64
88
|
"""Clean Nix store and perform garbage collection."""
|
|
65
89
|
if not check_nixos():
|
|
@@ -71,18 +95,20 @@ def clean(force):
|
|
|
71
95
|
if force:
|
|
72
96
|
click.echo("Forcing garbage collection and removing all old generations...")
|
|
73
97
|
# Remove all old generations of all profiles
|
|
74
|
-
subprocess.run([
|
|
98
|
+
subprocess.run(["nix-collect-garbage", "-d"], check=True)
|
|
75
99
|
click.echo("✓ Removed all old generations and ran garbage collection")
|
|
76
100
|
else:
|
|
77
101
|
# Regular garbage collection (safe, only removes unreachable paths)
|
|
78
|
-
subprocess.run([
|
|
102
|
+
subprocess.run(["nix-collect-garbage"], check=True)
|
|
79
103
|
click.echo("✓ Garbage collection completed")
|
|
80
|
-
|
|
104
|
+
|
|
81
105
|
# Show disk space usage after cleanup
|
|
82
106
|
click.echo("\nDisk space usage after cleanup:")
|
|
83
|
-
subprocess.run(
|
|
84
|
-
|
|
107
|
+
subprocess.run(
|
|
108
|
+
["nix-store", "--query", "--disk-usage", "/nix/store"], check=False
|
|
109
|
+
)
|
|
110
|
+
|
|
85
111
|
except subprocess.CalledProcessError as e:
|
|
86
112
|
click.echo(f"Error during cleanup: {e}", err=True)
|
|
87
|
-
except
|
|
88
|
-
click.echo(f"
|
|
113
|
+
except subprocess.SubprocessError as e:
|
|
114
|
+
click.echo(f"Subprocess error: {str(e)}", err=True)
|
helper/commands/public_ip.py
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
from ..utils import run_cmd
|
|
2
1
|
import click
|
|
2
|
+
from helper import __version__
|
|
3
|
+
from helper.utils import run_cmd
|
|
4
|
+
|
|
3
5
|
|
|
4
6
|
@click.command()
|
|
5
7
|
def public_ip():
|
|
6
|
-
"""Show public IP
|
|
8
|
+
"""Show public IP address.
|
|
9
|
+
|
|
10
|
+
Version: {}
|
|
11
|
+
|
|
12
|
+
Retrieves and displays the public IP address of the current machine.
|
|
13
|
+
Uses https://ifconfig.me as the primary service and falls back to curl
|
|
14
|
+
if the primary method fails.
|
|
15
|
+
""".format(__version__)
|
|
7
16
|
cmd = "curl -s ifconfig.me"
|
|
8
17
|
run_cmd(cmd)
|
helper/commands/speed.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import subprocess
|
|
3
|
+
from ..utils import run_cmd
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def check_speedtest_installed():
|
|
7
|
+
"""Check if speedtest-cli is installed."""
|
|
8
|
+
try:
|
|
9
|
+
subprocess.run(
|
|
10
|
+
["which", "speedtest-cli"],
|
|
11
|
+
check=True,
|
|
12
|
+
stdout=subprocess.PIPE,
|
|
13
|
+
stderr=subprocess.PIPE,
|
|
14
|
+
)
|
|
15
|
+
return True
|
|
16
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
17
|
+
return False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@click.command()
|
|
21
|
+
@click.option("--simple", "-s", is_flag=True, help="Only show basic speed information")
|
|
22
|
+
def speed(simple):
|
|
23
|
+
"""Test internet speed using speedtest-cli"""
|
|
24
|
+
if not check_speedtest_installed():
|
|
25
|
+
click.echo("Error: speedtest-cli is not installed. Please install it first.")
|
|
26
|
+
click.echo("You can install it with: pip install speedtest-cli")
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
cmd = "speedtest-cli"
|
|
30
|
+
if simple:
|
|
31
|
+
cmd += " --simple"
|
|
32
|
+
|
|
33
|
+
run_cmd(cmd)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Add aliases for the command
|
|
37
|
+
speed_test = speed
|
|
38
|
+
sp = speed
|