sujithx 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.
- sujithx/__init__.py +80 -0
- sujithx/_init_.py +0 -0
- sujithx/automation.py +96 -0
- sujithx/cli.py +132 -0
- sujithx/colors.py +85 -0
- sujithx/files.py +108 -0
- sujithx/logger.py +79 -0
- sujithx/network.py +42 -0
- sujithx/security.py +85 -0
- sujithx/system_info.py +57 -0
- sujithx-0.1.0.dist-info/METADATA +171 -0
- sujithx-0.1.0.dist-info/RECORD +16 -0
- sujithx-0.1.0.dist-info/WHEEL +5 -0
- sujithx-0.1.0.dist-info/entry_points.txt +2 -0
- sujithx-0.1.0.dist-info/licenses/LICENSE.txt +17 -0
- sujithx-0.1.0.dist-info/top_level.txt +1 -0
sujithx/__init__.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SUJITHX - The Ultimate Python Toolkit
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
_version_ = "0.1.0"
|
|
6
|
+
_author_ = "Sujith"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def hello(name="Developer"):
|
|
10
|
+
"""Return a personalized greeting."""
|
|
11
|
+
return f"Hello, {name}! Welcome to SUJITHX."
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def add(a, b):
|
|
15
|
+
"""Add two numbers."""
|
|
16
|
+
return a + b
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def multiply(a, b):
|
|
20
|
+
"""Multiply two numbers."""
|
|
21
|
+
return a * b
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def percentage(value, total):
|
|
25
|
+
"""Calculate a percentage safely."""
|
|
26
|
+
if total == 0:
|
|
27
|
+
raise ValueError("Total cannot be zero.")
|
|
28
|
+
|
|
29
|
+
return (value / total) * 100
|
|
30
|
+
from .files import (
|
|
31
|
+
search_files,
|
|
32
|
+
analyze_folder,
|
|
33
|
+
find_duplicates,
|
|
34
|
+
largest_files,
|
|
35
|
+
format_bytes,
|
|
36
|
+
)
|
|
37
|
+
from .security import (
|
|
38
|
+
sha256_text,
|
|
39
|
+
sha256_file,
|
|
40
|
+
generate_token,
|
|
41
|
+
generate_password,
|
|
42
|
+
check_password_strength,
|
|
43
|
+
)
|
|
44
|
+
from .colors import (
|
|
45
|
+
color_text,
|
|
46
|
+
success,
|
|
47
|
+
error,
|
|
48
|
+
warning,
|
|
49
|
+
info,
|
|
50
|
+
banner,
|
|
51
|
+
progress_bar,
|
|
52
|
+
print_progress,
|
|
53
|
+
)
|
|
54
|
+
from .logger import (
|
|
55
|
+
create_logger,
|
|
56
|
+
log_info,
|
|
57
|
+
log_warning,
|
|
58
|
+
log_error,
|
|
59
|
+
log_debug,
|
|
60
|
+
)
|
|
61
|
+
from .automation import (
|
|
62
|
+
ensure_directory,
|
|
63
|
+
list_files,
|
|
64
|
+
get_file_info,
|
|
65
|
+
copy_file,
|
|
66
|
+
read_text,
|
|
67
|
+
write_text,
|
|
68
|
+
file_exists,
|
|
69
|
+
)
|
|
70
|
+
from .system_info import (
|
|
71
|
+
get_system_info,
|
|
72
|
+
get_disk_info,
|
|
73
|
+
display_system_info,
|
|
74
|
+
)
|
|
75
|
+
from .network import (
|
|
76
|
+
get_hostname,
|
|
77
|
+
get_local_ip,
|
|
78
|
+
check_internet,
|
|
79
|
+
get_network_info,
|
|
80
|
+
)
|
sujithx/_init_.py
ADDED
|
File without changes
|
sujithx/automation.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Safe file and folder automation utilities for SUJITHX."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import shutil
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def ensure_directory(folder):
|
|
8
|
+
"""Create a folder if it does not already exist."""
|
|
9
|
+
path = Path(folder).expanduser()
|
|
10
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
11
|
+
return str(path.resolve())
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def list_files(folder, pattern="*"):
|
|
15
|
+
"""List files inside a folder and its subfolders."""
|
|
16
|
+
root = Path(folder).expanduser()
|
|
17
|
+
|
|
18
|
+
if not root.is_dir():
|
|
19
|
+
raise NotADirectoryError(f"Folder not found: {root}")
|
|
20
|
+
|
|
21
|
+
return sorted(
|
|
22
|
+
(str(path) for path in root.rglob(pattern) if path.is_file()),
|
|
23
|
+
key=str.lower,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_file_info(file_path):
|
|
28
|
+
"""Return basic information about a file."""
|
|
29
|
+
path = Path(file_path).expanduser()
|
|
30
|
+
|
|
31
|
+
if not path.is_file():
|
|
32
|
+
raise FileNotFoundError(f"File not found: {path}")
|
|
33
|
+
|
|
34
|
+
details = path.stat()
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
"name": path.name,
|
|
38
|
+
"path": str(path.resolve()),
|
|
39
|
+
"extension": path.suffix.lower(),
|
|
40
|
+
"size_bytes": details.st_size,
|
|
41
|
+
"size_kb": round(details.st_size / 1024, 2),
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def copy_file(source, destination, overwrite=False):
|
|
46
|
+
"""Copy a file without overwriting an existing file by default."""
|
|
47
|
+
source_path = Path(source).expanduser()
|
|
48
|
+
destination_path = Path(destination).expanduser()
|
|
49
|
+
|
|
50
|
+
if not source_path.is_file():
|
|
51
|
+
raise FileNotFoundError(f"Source file not found: {source_path}")
|
|
52
|
+
|
|
53
|
+
if destination_path.exists() and not overwrite:
|
|
54
|
+
raise FileExistsError(
|
|
55
|
+
f"Destination already exists: {destination_path}"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
destination_path.parent.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
shutil.copy2(source_path, destination_path)
|
|
60
|
+
|
|
61
|
+
return str(destination_path.resolve())
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def read_text(file_path, encoding="utf-8"):
|
|
65
|
+
"""Read a UTF-8 text file."""
|
|
66
|
+
path = Path(file_path).expanduser()
|
|
67
|
+
|
|
68
|
+
if not path.is_file():
|
|
69
|
+
raise FileNotFoundError(f"File not found: {path}")
|
|
70
|
+
|
|
71
|
+
return path.read_text(encoding=encoding)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def write_text(file_path, content, overwrite=False, encoding="utf-8"):
|
|
75
|
+
"""Write a text file, refusing to overwrite by default."""
|
|
76
|
+
path = Path(file_path).expanduser()
|
|
77
|
+
|
|
78
|
+
if path.exists() and not overwrite:
|
|
79
|
+
raise FileExistsError(
|
|
80
|
+
f"File already exists: {path}. Set overwrite=True to replace it."
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
84
|
+
|
|
85
|
+
if overwrite:
|
|
86
|
+
path.write_text(content, encoding=encoding)
|
|
87
|
+
else:
|
|
88
|
+
with path.open("x", encoding=encoding) as file:
|
|
89
|
+
file.write(content)
|
|
90
|
+
|
|
91
|
+
return str(path.resolve())
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def file_exists(file_path):
|
|
95
|
+
"""Check whether a file exists."""
|
|
96
|
+
return Path(file_path).expanduser().is_file()
|
sujithx/cli.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""SUJITHX - Unified Python Toolkit Command Center."""
|
|
2
|
+
|
|
3
|
+
from . import (
|
|
4
|
+
hello,
|
|
5
|
+
analyze_folder,
|
|
6
|
+
search_files,
|
|
7
|
+
find_duplicates,
|
|
8
|
+
largest_files,
|
|
9
|
+
format_bytes,
|
|
10
|
+
generate_token,
|
|
11
|
+
generate_password,
|
|
12
|
+
check_password_strength,
|
|
13
|
+
ensure_directory,
|
|
14
|
+
get_file_info,
|
|
15
|
+
banner,
|
|
16
|
+
success,
|
|
17
|
+
error,
|
|
18
|
+
info,
|
|
19
|
+
display_system_info,
|
|
20
|
+
get_network_info,
|
|
21
|
+
create_logger,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def show_menu():
|
|
26
|
+
print("\n1. Welcome")
|
|
27
|
+
print("2. Analyze folder")
|
|
28
|
+
print("3. Search files")
|
|
29
|
+
print("4. Find duplicate files")
|
|
30
|
+
print("5. Show largest files")
|
|
31
|
+
print("6. Generate secure token")
|
|
32
|
+
print("7. Generate password")
|
|
33
|
+
print("8. Check password strength")
|
|
34
|
+
print("9. Create folder")
|
|
35
|
+
print("10. Get file information")
|
|
36
|
+
print("11. System information")
|
|
37
|
+
print("12. Network information")
|
|
38
|
+
print("13. Logging demonstration")
|
|
39
|
+
print("0. Exit")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def main():
|
|
43
|
+
print(banner("SUJITHX COMMAND CENTER"))
|
|
44
|
+
logger = create_logger("SUJITHX_CLI", "sujithx_cli.log")
|
|
45
|
+
|
|
46
|
+
while True:
|
|
47
|
+
show_menu()
|
|
48
|
+
choice = input("\nChoose an option: ").strip()
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
if choice == "1":
|
|
52
|
+
name = input("Your name: ").strip() or "Developer"
|
|
53
|
+
print(success(hello(name)))
|
|
54
|
+
|
|
55
|
+
elif choice == "2":
|
|
56
|
+
folder = input("Folder path: ").strip().strip('"')
|
|
57
|
+
result = analyze_folder(folder)
|
|
58
|
+
print("Folder:", result["folder"])
|
|
59
|
+
print("Files:", result["file_count"])
|
|
60
|
+
print("Size:", format_bytes(result["total_bytes"]))
|
|
61
|
+
print("Extensions:", result["extensions"])
|
|
62
|
+
|
|
63
|
+
elif choice == "3":
|
|
64
|
+
folder = input("Folder path: ").strip().strip('"')
|
|
65
|
+
pattern = input("Pattern (default ): ").strip() or ""
|
|
66
|
+
results = search_files(folder, pattern)
|
|
67
|
+
for path in results:
|
|
68
|
+
print(path)
|
|
69
|
+
print(f"Found {len(results)} file(s).")
|
|
70
|
+
|
|
71
|
+
elif choice == "4":
|
|
72
|
+
folder = input("Folder path: ").strip().strip('"')
|
|
73
|
+
groups = find_duplicates(folder)
|
|
74
|
+
if not groups:
|
|
75
|
+
print(success("No duplicates found."))
|
|
76
|
+
else:
|
|
77
|
+
for group in groups:
|
|
78
|
+
print("\nDuplicate group:")
|
|
79
|
+
for path in group:
|
|
80
|
+
print(" ", path)
|
|
81
|
+
|
|
82
|
+
elif choice == "5":
|
|
83
|
+
folder = input("Folder path: ").strip().strip('"')
|
|
84
|
+
for path, size in largest_files(folder, 10):
|
|
85
|
+
print(f"{format_bytes(size):>12} {path}")
|
|
86
|
+
|
|
87
|
+
elif choice == "6":
|
|
88
|
+
print("Generated token:", generate_token())
|
|
89
|
+
|
|
90
|
+
elif choice == "7":
|
|
91
|
+
print("Generated password:", generate_password())
|
|
92
|
+
|
|
93
|
+
elif choice == "8":
|
|
94
|
+
password = input("Password to evaluate: ")
|
|
95
|
+
result = check_password_strength(password)
|
|
96
|
+
print("Rating:", result["rating"])
|
|
97
|
+
print("Checks:", result["checks"])
|
|
98
|
+
|
|
99
|
+
elif choice == "9":
|
|
100
|
+
folder = input("Folder path to create: ").strip().strip('"')
|
|
101
|
+
print(success(ensure_directory(folder)))
|
|
102
|
+
|
|
103
|
+
elif choice == "10":
|
|
104
|
+
path = input("File path: ").strip().strip('"')
|
|
105
|
+
for key, value in get_file_info(path).items():
|
|
106
|
+
print(f"{key}: {value}")
|
|
107
|
+
|
|
108
|
+
elif choice == "11":
|
|
109
|
+
display_system_info()
|
|
110
|
+
|
|
111
|
+
elif choice == "12":
|
|
112
|
+
for key, value in get_network_info().items():
|
|
113
|
+
print(f"{key.replace('_', ' ').title()}: {value}")
|
|
114
|
+
|
|
115
|
+
elif choice == "13":
|
|
116
|
+
logger.info("SUJITHX logging demonstration.")
|
|
117
|
+
logger.warning("This is a sample warning.")
|
|
118
|
+
print(success("Log messages written to sujithx_cli.log"))
|
|
119
|
+
|
|
120
|
+
elif choice == "0":
|
|
121
|
+
print(success("SUJITHX closed. Goodbye!"))
|
|
122
|
+
break
|
|
123
|
+
|
|
124
|
+
else:
|
|
125
|
+
print(error("Choose a number from 0 to 13."))
|
|
126
|
+
|
|
127
|
+
except (OSError, ValueError, TypeError) as exc:
|
|
128
|
+
print(error(str(exc)))
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
if __name__ == "__main__":
|
|
132
|
+
main()
|
sujithx/colors.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Terminal styling utilities for SUJITHX."""
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
RESET = "\033[0m"
|
|
8
|
+
BOLD = "\033[1m"
|
|
9
|
+
|
|
10
|
+
COLORS = {
|
|
11
|
+
"black": "\033[30m",
|
|
12
|
+
"red": "\033[31m",
|
|
13
|
+
"green": "\033[32m",
|
|
14
|
+
"yellow": "\033[33m",
|
|
15
|
+
"blue": "\033[34m",
|
|
16
|
+
"magenta": "\033[35m",
|
|
17
|
+
"cyan": "\033[36m",
|
|
18
|
+
"white": "\033[37m",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def color_text(text, color="cyan", bold=False):
|
|
23
|
+
"""Return text with ANSI terminal colors."""
|
|
24
|
+
if color not in COLORS:
|
|
25
|
+
raise ValueError(f"Unsupported color: {color}")
|
|
26
|
+
|
|
27
|
+
style = COLORS[color]
|
|
28
|
+
if bold:
|
|
29
|
+
style += BOLD
|
|
30
|
+
|
|
31
|
+
return f"{style}{text}{RESET}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def success(message):
|
|
35
|
+
"""Format a success message."""
|
|
36
|
+
return color_text(f"[SUCCESS] {message}", "green", True)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def error(message):
|
|
40
|
+
"""Format an error message."""
|
|
41
|
+
return color_text(f"[ERROR] {message}", "red", True)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def warning(message):
|
|
45
|
+
"""Format a warning message."""
|
|
46
|
+
return color_text(f"[WARNING] {message}", "yellow", True)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def info(message):
|
|
50
|
+
"""Format an information message."""
|
|
51
|
+
return color_text(f"[INFO] {message}", "cyan")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def banner(title="SUJITHX"):
|
|
55
|
+
"""Create a centered terminal banner."""
|
|
56
|
+
width = max(40, min(shutil.get_terminal_size((80, 20)).columns, 100))
|
|
57
|
+
border = "=" * width
|
|
58
|
+
heading = f" {title} ".center(width, "=")
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
color_text(border, "magenta", True)
|
|
62
|
+
+ "\n"
|
|
63
|
+
+ color_text(heading, "cyan", True)
|
|
64
|
+
+ "\n"
|
|
65
|
+
+ color_text(border, "magenta", True)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def progress_bar(percent, width=30):
|
|
70
|
+
"""Display a simple text progress bar."""
|
|
71
|
+
if not 0 <= percent <= 100:
|
|
72
|
+
raise ValueError("percent must be between 0 and 100")
|
|
73
|
+
|
|
74
|
+
if width < 1:
|
|
75
|
+
raise ValueError("width must be at least 1")
|
|
76
|
+
|
|
77
|
+
filled = round(width * percent / 100)
|
|
78
|
+
bar = "#" * filled + "-" * (width - filled)
|
|
79
|
+
|
|
80
|
+
return f"[{bar}] {percent:.1f}%"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def print_progress(percent, width=30):
|
|
84
|
+
"""Print a progress bar on the terminal."""
|
|
85
|
+
print(progress_bar(percent, width), file=sys.stdout)
|
sujithx/files.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""File intelligence utilities for SUJITHX."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from collections import Counter
|
|
5
|
+
import hashlib
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def search_files(folder, pattern="*"):
|
|
9
|
+
"""Search for files recursively."""
|
|
10
|
+
root = Path(folder).expanduser()
|
|
11
|
+
|
|
12
|
+
if not root.is_dir():
|
|
13
|
+
raise NotADirectoryError(f"Folder not found: {root}")
|
|
14
|
+
|
|
15
|
+
return sorted(
|
|
16
|
+
(p for p in root.rglob(pattern) if p.is_file()),
|
|
17
|
+
key=lambda p: str(p).lower()
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def analyze_folder(folder):
|
|
22
|
+
"""Analyze files and folders."""
|
|
23
|
+
root = Path(folder).expanduser()
|
|
24
|
+
|
|
25
|
+
if not root.is_dir():
|
|
26
|
+
raise NotADirectoryError(f"Folder not found: {root}")
|
|
27
|
+
|
|
28
|
+
files = [p for p in root.rglob("*") if p.is_file()]
|
|
29
|
+
extensions = Counter(
|
|
30
|
+
p.suffix.lower() or "[no extension]" for p in files
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
total_bytes = 0
|
|
34
|
+
|
|
35
|
+
for path in files:
|
|
36
|
+
try:
|
|
37
|
+
total_bytes += path.stat().st_size
|
|
38
|
+
except OSError:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
"folder": str(root.resolve()),
|
|
43
|
+
"file_count": len(files),
|
|
44
|
+
"total_bytes": total_bytes,
|
|
45
|
+
"total_mb": round(total_bytes / (1024 * 1024), 2),
|
|
46
|
+
"extensions": dict(extensions),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def find_duplicates(folder):
|
|
51
|
+
"""Find duplicate files using SHA-256 hashes."""
|
|
52
|
+
root = Path(folder).expanduser()
|
|
53
|
+
|
|
54
|
+
if not root.is_dir():
|
|
55
|
+
raise NotADirectoryError(f"Folder not found: {root}")
|
|
56
|
+
|
|
57
|
+
hashes = {}
|
|
58
|
+
|
|
59
|
+
for path in root.rglob("*"):
|
|
60
|
+
if not path.is_file():
|
|
61
|
+
continue
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
digest = hashlib.sha256()
|
|
65
|
+
|
|
66
|
+
with path.open("rb") as file:
|
|
67
|
+
for chunk in iter(lambda: file.read(1024 * 1024), b""):
|
|
68
|
+
digest.update(chunk)
|
|
69
|
+
|
|
70
|
+
hashes.setdefault(digest.hexdigest(), []).append(str(path))
|
|
71
|
+
|
|
72
|
+
except OSError:
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
return [paths for paths in hashes.values() if len(paths) > 1]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def largest_files(folder, limit=10):
|
|
79
|
+
"""Find the largest files."""
|
|
80
|
+
if limit < 1:
|
|
81
|
+
raise ValueError("limit must be at least 1")
|
|
82
|
+
|
|
83
|
+
root = Path(folder).expanduser()
|
|
84
|
+
|
|
85
|
+
if not root.is_dir():
|
|
86
|
+
raise NotADirectoryError(f"Folder not found: {root}")
|
|
87
|
+
|
|
88
|
+
files = []
|
|
89
|
+
|
|
90
|
+
for path in root.rglob("*"):
|
|
91
|
+
try:
|
|
92
|
+
if path.is_file():
|
|
93
|
+
files.append((str(path), path.stat().st_size))
|
|
94
|
+
except OSError:
|
|
95
|
+
continue
|
|
96
|
+
|
|
97
|
+
return sorted(files, key=lambda item: item[1], reverse=True)[:limit]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def format_bytes(size):
|
|
101
|
+
"""Convert bytes to a readable size."""
|
|
102
|
+
if size < 0:
|
|
103
|
+
raise ValueError("size cannot be negative")
|
|
104
|
+
|
|
105
|
+
for unit in ("B", "KB", "MB", "GB", "TB"):
|
|
106
|
+
if size < 1024 or unit == "TB":
|
|
107
|
+
return f"{size:.2f} {unit}"
|
|
108
|
+
size /= 1024
|
sujithx/logger.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Professional logging utilities for SUJITHX."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def create_logger(
|
|
8
|
+
name="SUJITHX",
|
|
9
|
+
log_file="sujithx.log",
|
|
10
|
+
level="INFO",
|
|
11
|
+
):
|
|
12
|
+
"""Create a logger that writes to both console and file."""
|
|
13
|
+
|
|
14
|
+
valid_levels = {
|
|
15
|
+
"DEBUG": logging.DEBUG,
|
|
16
|
+
"INFO": logging.INFO,
|
|
17
|
+
"WARNING": logging.WARNING,
|
|
18
|
+
"ERROR": logging.ERROR,
|
|
19
|
+
"CRITICAL": logging.CRITICAL,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
level_name = level.upper()
|
|
23
|
+
|
|
24
|
+
if level_name not in valid_levels:
|
|
25
|
+
raise ValueError(
|
|
26
|
+
"level must be DEBUG, INFO, WARNING, ERROR, or CRITICAL"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(name)
|
|
30
|
+
logger.setLevel(valid_levels[level_name])
|
|
31
|
+
logger.propagate = False
|
|
32
|
+
|
|
33
|
+
# Avoid adding duplicate handlers when called repeatedly.
|
|
34
|
+
if logger.handlers:
|
|
35
|
+
return logger
|
|
36
|
+
|
|
37
|
+
formatter = logging.Formatter(
|
|
38
|
+
"%(asctime)s | %(levelname)s | %(name)s | %(message)s",
|
|
39
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
console_handler = logging.StreamHandler()
|
|
43
|
+
console_handler.setLevel(valid_levels[level_name])
|
|
44
|
+
console_handler.setFormatter(formatter)
|
|
45
|
+
logger.addHandler(console_handler)
|
|
46
|
+
|
|
47
|
+
if log_file:
|
|
48
|
+
path = Path(log_file).expanduser()
|
|
49
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
|
|
51
|
+
file_handler = logging.FileHandler(
|
|
52
|
+
path,
|
|
53
|
+
encoding="utf-8",
|
|
54
|
+
)
|
|
55
|
+
file_handler.setLevel(valid_levels[level_name])
|
|
56
|
+
file_handler.setFormatter(formatter)
|
|
57
|
+
logger.addHandler(file_handler)
|
|
58
|
+
|
|
59
|
+
return logger
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def log_info(message, logger=None):
|
|
63
|
+
"""Log an informational message."""
|
|
64
|
+
(logger or create_logger()).info(message)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def log_warning(message, logger=None):
|
|
68
|
+
"""Log a warning message."""
|
|
69
|
+
(logger or create_logger()).warning(message)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def log_error(message, logger=None):
|
|
73
|
+
"""Log an error message."""
|
|
74
|
+
(logger or create_logger()).error(message)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def log_debug(message, logger=None):
|
|
78
|
+
"""Log a debug message."""
|
|
79
|
+
(logger or create_logger(level="DEBUG")).debug(message)
|
sujithx/network.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Basic, read-only network utilities for SUJITHX."""
|
|
2
|
+
|
|
3
|
+
import socket
|
|
4
|
+
import urllib.error
|
|
5
|
+
import urllib.request
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_hostname():
|
|
9
|
+
"""Return this computer's hostname."""
|
|
10
|
+
return socket.gethostname()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_local_ip():
|
|
14
|
+
"""Return the local IP address when available."""
|
|
15
|
+
try:
|
|
16
|
+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
|
|
17
|
+
sock.connect(("192.0.2.1", 80))
|
|
18
|
+
return sock.getsockname()[0]
|
|
19
|
+
except OSError:
|
|
20
|
+
return "Unavailable"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def check_internet(timeout=3):
|
|
24
|
+
"""Check whether an HTTPS connection can be established."""
|
|
25
|
+
try:
|
|
26
|
+
request = urllib.request.Request(
|
|
27
|
+
"https://www.google.com/generate_204",
|
|
28
|
+
method="GET",
|
|
29
|
+
)
|
|
30
|
+
with urllib.request.urlopen(request, timeout=timeout) as response:
|
|
31
|
+
return response.status in (200, 204)
|
|
32
|
+
except (OSError, urllib.error.URLError, TimeoutError):
|
|
33
|
+
return False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_network_info():
|
|
37
|
+
"""Return basic network information."""
|
|
38
|
+
return {
|
|
39
|
+
"hostname": get_hostname(),
|
|
40
|
+
"local_ip": get_local_ip(),
|
|
41
|
+
"internet_available": check_internet(),
|
|
42
|
+
}
|
sujithx/security.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Security utilities for the SUJITHX Python toolkit."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import secrets
|
|
5
|
+
import string
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def sha256_text(text):
|
|
10
|
+
"""Return the SHA-256 hash of text."""
|
|
11
|
+
return hashlib.sha256(text.encode("utf-8")).hexdigest()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def sha256_file(file_path):
|
|
15
|
+
"""Calculate a file's SHA-256 hash."""
|
|
16
|
+
digest = hashlib.sha256()
|
|
17
|
+
|
|
18
|
+
with Path(file_path).open("rb") as file:
|
|
19
|
+
for chunk in iter(lambda: file.read(1024 * 1024), b""):
|
|
20
|
+
digest.update(chunk)
|
|
21
|
+
|
|
22
|
+
return digest.hexdigest()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def generate_token(length=32):
|
|
26
|
+
"""Generate a cryptographically secure hexadecimal token."""
|
|
27
|
+
if length < 16:
|
|
28
|
+
raise ValueError("Token length must be at least 16 characters.")
|
|
29
|
+
|
|
30
|
+
return secrets.token_hex((length + 1) // 2)[:length]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def generate_password(length=16):
|
|
34
|
+
"""Generate a secure password with varied character types."""
|
|
35
|
+
if length < 12:
|
|
36
|
+
raise ValueError("Password length must be at least 12.")
|
|
37
|
+
|
|
38
|
+
groups = [
|
|
39
|
+
string.ascii_lowercase,
|
|
40
|
+
string.ascii_uppercase,
|
|
41
|
+
string.digits,
|
|
42
|
+
"!@#$%^&*()-_=+",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
# Guarantee at least one character from each group.
|
|
46
|
+
password = [secrets.choice(group) for group in groups]
|
|
47
|
+
alphabet = "".join(groups)
|
|
48
|
+
|
|
49
|
+
password.extend(
|
|
50
|
+
secrets.choice(alphabet) for _ in range(length - len(password))
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Securely shuffle the password characters.
|
|
54
|
+
secrets.SystemRandom().shuffle(password)
|
|
55
|
+
|
|
56
|
+
return "".join(password)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def check_password_strength(password):
|
|
60
|
+
"""Provide a basic password-strength assessment."""
|
|
61
|
+
if not isinstance(password, str):
|
|
62
|
+
raise TypeError("Password must be a string.")
|
|
63
|
+
|
|
64
|
+
checks = {
|
|
65
|
+
"length": len(password) >= 12,
|
|
66
|
+
"lowercase": any(c.islower() for c in password),
|
|
67
|
+
"uppercase": any(c.isupper() for c in password),
|
|
68
|
+
"digit": any(c.isdigit() for c in password),
|
|
69
|
+
"symbol": any(not c.isalnum() for c in password),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
score = sum(checks.values())
|
|
73
|
+
|
|
74
|
+
if score <= 2:
|
|
75
|
+
rating = "Weak"
|
|
76
|
+
elif score <= 4:
|
|
77
|
+
rating = "Moderate"
|
|
78
|
+
else:
|
|
79
|
+
rating = "Strong"
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
"rating": rating,
|
|
83
|
+
"score": score,
|
|
84
|
+
"checks": checks,
|
|
85
|
+
}
|
sujithx/system_info.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Read-only system information utilities for SUJITHX."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
import shutil
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_system_info():
|
|
11
|
+
"""Return basic computer and Python information."""
|
|
12
|
+
return {
|
|
13
|
+
"computer_name": platform.node() or "Unknown",
|
|
14
|
+
"operating_system": platform.system(),
|
|
15
|
+
"os_release": platform.release(),
|
|
16
|
+
"architecture": platform.machine(),
|
|
17
|
+
"python_version": platform.python_version(),
|
|
18
|
+
"python_executable": sys.executable,
|
|
19
|
+
"current_directory": str(Path.cwd()),
|
|
20
|
+
"cpu_count": os.cpu_count(),
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_disk_info(path="."):
|
|
25
|
+
"""Return storage capacity information for a drive or folder."""
|
|
26
|
+
target = Path(path).expanduser()
|
|
27
|
+
|
|
28
|
+
if not target.exists():
|
|
29
|
+
raise FileNotFoundError(f"Path not found: {target}")
|
|
30
|
+
|
|
31
|
+
usage = shutil.disk_usage(target.resolve())
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
"path": str(target.resolve()),
|
|
35
|
+
"total_bytes": usage.total,
|
|
36
|
+
"used_bytes": usage.used,
|
|
37
|
+
"free_bytes": usage.free,
|
|
38
|
+
"total_gb": round(usage.total / (1024 ** 3), 2),
|
|
39
|
+
"used_gb": round(usage.used / (1024 ** 3), 2),
|
|
40
|
+
"free_gb": round(usage.free / (1024 ** 3), 2),
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def display_system_info():
|
|
45
|
+
"""Print system information in a readable format."""
|
|
46
|
+
print("\n=== SUJITHX SYSTEM INFORMATION ===")
|
|
47
|
+
|
|
48
|
+
for key, value in get_system_info().items():
|
|
49
|
+
print(f"{key.replace('_', ' ').title()}: {value}")
|
|
50
|
+
|
|
51
|
+
print("\n=== DISK INFORMATION ===")
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
for key, value in get_disk_info().items():
|
|
55
|
+
print(f"{key.replace('_', ' ').title()}: {value}")
|
|
56
|
+
except OSError as exc:
|
|
57
|
+
print(f"Could not read disk information: {exc}")
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sujithx
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A multipurpose Python toolkit for files, security, automation, and system utilities
|
|
5
|
+
Author: Sujith
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE.txt
|
|
10
|
+
Dynamic: license-file
|
|
11
|
+
|
|
12
|
+
SUJITHX — The Ultimate Python Toolkit
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
SUJITHX is a modular Python toolkit that combines file management, security utilities, automation, terminal styling, logging, system information, and network checks in one command-line application.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
Features
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
\- File Intelligence: Search files, analyze folders, identify duplicates, and find large files.
|
|
25
|
+
|
|
26
|
+
\- Security Utilities: Generate random tokens and passwords, calculate SHA-256 hashes, and perform basic password-strength checks.
|
|
27
|
+
|
|
28
|
+
\- Automation: Create directories, inspect files, read text, and copy files safely without overwriting by default.
|
|
29
|
+
|
|
30
|
+
\- Terminal Styling: Colored messages, banners, and progress bars.
|
|
31
|
+
|
|
32
|
+
\- Logging: Write structured log messages to the console and log files.
|
|
33
|
+
|
|
34
|
+
\- System Information: Inspect Python, operating system, and disk usage information.
|
|
35
|
+
|
|
36
|
+
\- Network Utilities: Retrieve hostname and local IP information and check internet connectivity.
|
|
37
|
+
|
|
38
|
+
\- Command Center: Access the tools through an interactive menu.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
Requirements
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
\- Python 3.12 or newer
|
|
47
|
+
|
|
48
|
+
\- Windows, macOS, or Linux
|
|
49
|
+
|
|
50
|
+
\- No third-party runtime dependencies
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
Installation
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
Clone the repository, enter its folder, and install it in editable mode:
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
git clone YOUR\_GITHUB\_REPOSITORY\_URL
|
|
63
|
+
|
|
64
|
+
cd SUJITHX
|
|
65
|
+
|
|
66
|
+
python -m venv .venv
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
Activate the environment on Windows:
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
.venv\\Scripts\\activate
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
Install SUJITHX:
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
python -m pip install -e .
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
Run
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
Launch the command center:
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
python -m sujithx.cli
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
After installation, you can also run:
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
sujithx
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
Example
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
from sujithx import (
|
|
111
|
+
|
|
112
|
+
  hello,
|
|
113
|
+
|
|
114
|
+
  generate\_token,
|
|
115
|
+
|
|
116
|
+
  analyze\_folder,
|
|
117
|
+
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
print(hello("Developer"))
|
|
123
|
+
|
|
124
|
+
print(generate\_token())
|
|
125
|
+
|
|
126
|
+
print(analyze\_folder("."))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
Security Notes
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
\- Tokens and passwords are generated using Python's "secrets" module.
|
|
135
|
+
|
|
136
|
+
\- SHA-256 functions calculate file or text hashes; they do not encrypt data.
|
|
137
|
+
|
|
138
|
+
\- The password-strength checker is a basic heuristic, not a complete security assessment.
|
|
139
|
+
|
|
140
|
+
\- Review file paths before running file operations.
|
|
141
|
+
|
|
142
|
+
\- Keep generated passwords and tokens private.
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
Project Status
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
Version: 0.1.0
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
This project is under active development. Contributions, suggestions, and bug reports are welcome.
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
Author
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
Sujith
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
License
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
MIT License.
|
|
171
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
sujithx/__init__.py,sha256=7WzNX0W97QaSjvPhDxUQ3FBi1UvAJO70fPLuRqpkvAk,1432
|
|
2
|
+
sujithx/_init_.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
sujithx/automation.py,sha256=xKIkMXv1YQRj3l48-L_vi4O4vu4kONByYrndIFv7hb8,2795
|
|
4
|
+
sujithx/cli.py,sha256=ft7JrcnQIzcTPLWFIRsxkBR44h3M1-f4RssnbNWDpHc,4402
|
|
5
|
+
sujithx/colors.py,sha256=utv2wFK1d37hB2hJu2E8mF9js0t982Zfek0uSARoeVo,2087
|
|
6
|
+
sujithx/files.py,sha256=Q_cTjcF1QCQSe7_o_ipswF5AnrfspFjPpsjKj-KPMHw,2811
|
|
7
|
+
sujithx/logger.py,sha256=ytwXDZAr3BoSar86ajlNFYh7C3nfOWPL6U5nnRbSOiA,2119
|
|
8
|
+
sujithx/network.py,sha256=6grrYNjBuFxHt2XZ8_XJX3xiJSPwlgJ6jNlZ7wwC--Q,1186
|
|
9
|
+
sujithx/security.py,sha256=gc-GuBmx2T_LNjoErmoE8S5gaa20VJUCuOJUASxYE4s,2291
|
|
10
|
+
sujithx/system_info.py,sha256=h8t4k4J0WLosEEDgngyQQ0HG9H_fLj-cRhdI9zLKxfo,1759
|
|
11
|
+
sujithx-0.1.0.dist-info/licenses/LICENSE.txt,sha256=XW_tPU-gZnH4YJXgI2W2g7fmqfyhotu6y3GdPHBGt88,819
|
|
12
|
+
sujithx-0.1.0.dist-info/METADATA,sha256=sHYRRVu90uwvj6EW0BxWjKELtZ6NqENrNFkxL_y2Byc,2718
|
|
13
|
+
sujithx-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
14
|
+
sujithx-0.1.0.dist-info/entry_points.txt,sha256=PKIro9f7u4-RZoyuGZ9FkFYbCLf-mXpZYK5qi9iubQE,45
|
|
15
|
+
sujithx-0.1.0.dist-info/top_level.txt,sha256=utVaqKJ4Ll-Dg-xSPaiciSNXxiulfH2Z6BAVH9v9iuo,8
|
|
16
|
+
sujithx-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sujith
|
|
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.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sujithx
|