Open-AutoTools 0.0.3rc5__py3-none-any.whl → 0.0.4rc1__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.
- autotools/autocaps/commands.py +3 -7
- autotools/autocaps/core.py +5 -4
- autotools/autoip/commands.py +6 -11
- autotools/autoip/core.py +151 -200
- autotools/autolower/commands.py +3 -7
- autotools/autolower/core.py +4 -3
- autotools/autopassword/commands.py +27 -33
- autotools/autopassword/core.py +32 -73
- autotools/autotest/__init__.py +2 -0
- autotools/autotest/commands.py +205 -0
- autotools/cli.py +123 -62
- autotools/utils/commands.py +13 -0
- autotools/utils/loading.py +14 -6
- autotools/utils/performance.py +392 -0
- autotools/utils/updates.py +30 -22
- autotools/utils/version.py +69 -63
- open_autotools-0.0.4rc1.dist-info/METADATA +103 -0
- open_autotools-0.0.4rc1.dist-info/RECORD +28 -0
- {Open_AutoTools-0.0.3rc5.dist-info → open_autotools-0.0.4rc1.dist-info}/WHEEL +1 -1
- {Open_AutoTools-0.0.3rc5.dist-info → open_autotools-0.0.4rc1.dist-info}/entry_points.txt +0 -3
- Open_AutoTools-0.0.3rc5.dist-info/METADATA +0 -317
- Open_AutoTools-0.0.3rc5.dist-info/RECORD +0 -44
- autotools/autocaps/tests/__init__.py +0 -1
- autotools/autocaps/tests/test_autocaps_core.py +0 -45
- autotools/autocaps/tests/test_autocaps_integration.py +0 -46
- autotools/autodownload/__init__.py +0 -0
- autotools/autodownload/commands.py +0 -38
- autotools/autodownload/core.py +0 -433
- autotools/autoip/tests/__init__.py +0 -1
- autotools/autoip/tests/test_autoip_core.py +0 -72
- autotools/autoip/tests/test_autoip_integration.py +0 -92
- autotools/autolower/tests/__init__.py +0 -1
- autotools/autolower/tests/test_autolower_core.py +0 -45
- autotools/autolower/tests/test_autolower_integration.py +0 -46
- autotools/autospell/__init__.py +0 -3
- autotools/autospell/commands.py +0 -123
- autotools/autospell/core.py +0 -222
- autotools/autotranslate/__init__.py +0 -3
- autotools/autotranslate/commands.py +0 -42
- autotools/autotranslate/core.py +0 -52
- autotools/test/__init__.py +0 -3
- autotools/test/commands.py +0 -118
- {Open_AutoTools-0.0.3rc5.dist-info → open_autotools-0.0.4rc1.dist-info/licenses}/LICENSE +0 -0
- {Open_AutoTools-0.0.3rc5.dist-info → open_autotools-0.0.4rc1.dist-info}/top_level.txt +0 -0
autotools/autopassword/core.py
CHANGED
|
@@ -6,96 +6,55 @@ from cryptography.fernet import Fernet
|
|
|
6
6
|
from cryptography.hazmat.primitives import hashes
|
|
7
7
|
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"""Generate a secure random password with specified requirements."""
|
|
12
|
-
|
|
13
|
-
# DEFINE CHARACTER SETS
|
|
9
|
+
# GENERATES A SECURE RANDOM PASSWORD WITH SPECIFIED REQUIREMENTS
|
|
10
|
+
def generate_password(length=12, use_uppercase=True, use_numbers=True, use_special=True, min_special=1, min_numbers=1):
|
|
14
11
|
lowercase = string.ascii_lowercase
|
|
15
12
|
uppercase = string.ascii_uppercase if use_uppercase else ''
|
|
16
13
|
numbers = string.digits if use_numbers else ''
|
|
17
14
|
special = "!@#$%^&*()_+-=[]{}|;:,.<>?" if use_special else ''
|
|
18
|
-
|
|
19
|
-
# COMBINE ALL ALLOWED CHARACTERS
|
|
20
15
|
all_chars = lowercase + uppercase + numbers + special
|
|
21
|
-
|
|
22
|
-
# ENSURE MINIMUM REQUIREMENTS
|
|
23
16
|
password = []
|
|
24
17
|
password.append(secrets.choice(lowercase))
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if use_numbers:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
password.extend(secrets.choice(special) for _ in range(min_special))
|
|
31
|
-
|
|
32
|
-
# FILL REST OF PASSWORD
|
|
18
|
+
|
|
19
|
+
if use_uppercase: password.append(secrets.choice(uppercase))
|
|
20
|
+
if use_numbers: password.extend(secrets.choice(numbers) for _ in range(min_numbers))
|
|
21
|
+
if use_special: password.extend(secrets.choice(special) for _ in range(min_special))
|
|
22
|
+
|
|
33
23
|
remaining_length = length - len(password)
|
|
34
24
|
password.extend(secrets.choice(all_chars) for _ in range(remaining_length))
|
|
35
|
-
|
|
36
|
-
random.shuffle(password) # SHUFFLE PASSWORD
|
|
37
|
-
|
|
38
|
-
return ''.join(password) ## RETURN PASSWORD
|
|
25
|
+
random.shuffle(password)
|
|
39
26
|
|
|
27
|
+
return ''.join(password)
|
|
28
|
+
|
|
29
|
+
# GENERATES ENCRYPTION KEY FOR FERNET SYMMETRIC ENCRYPTION
|
|
40
30
|
def generate_encryption_key(password=None, salt=None):
|
|
41
|
-
|
|
42
|
-
if not
|
|
43
|
-
|
|
44
|
-
return Fernet.generate_key()
|
|
45
|
-
|
|
46
|
-
if not salt:
|
|
47
|
-
salt = secrets.token_bytes(16)
|
|
48
|
-
|
|
49
|
-
# DERIVE A KEY FROM PASSWORD AND SALT
|
|
50
|
-
kdf = PBKDF2HMAC(
|
|
51
|
-
algorithm=hashes.SHA256(),
|
|
52
|
-
length=32,
|
|
53
|
-
salt=salt,
|
|
54
|
-
iterations=100000,
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
# ENCODE KEY IN BASE64
|
|
31
|
+
if not password: return Fernet.generate_key()
|
|
32
|
+
if not salt: salt = secrets.token_bytes(16)
|
|
33
|
+
kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=100000)
|
|
58
34
|
key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
|
|
59
35
|
return key, salt
|
|
60
36
|
|
|
37
|
+
# ANALYZES PASSWORD STRENGTH AND PROVIDES IMPROVEMENT SUGGESTIONS
|
|
61
38
|
def analyze_password_strength(password):
|
|
62
|
-
"""Analyze password strength and return a score and suggestions."""
|
|
63
39
|
score = 0
|
|
64
40
|
suggestions = []
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
else:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
else:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
score += 1
|
|
83
|
-
else:
|
|
84
|
-
suggestions.append("Add lowercase letters")
|
|
85
|
-
|
|
86
|
-
# CHECK FOR CHARACTER TYPES
|
|
87
|
-
if any(c.isdigit() for c in password):
|
|
88
|
-
score += 1
|
|
89
|
-
else:
|
|
90
|
-
suggestions.append("Add numbers")
|
|
91
|
-
|
|
92
|
-
# CHECK FOR CHARACTER TYPES
|
|
93
|
-
if any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in password):
|
|
94
|
-
score += 1
|
|
95
|
-
else:
|
|
96
|
-
suggestions.append("Add special characters")
|
|
97
|
-
|
|
98
|
-
# RETURN SCORE AND SUGGESTIONS
|
|
41
|
+
|
|
42
|
+
if len(password) >= 12: score += 2
|
|
43
|
+
elif len(password) >= 8: score += 1
|
|
44
|
+
else: suggestions.append("Password should be at least 8 characters long")
|
|
45
|
+
|
|
46
|
+
if any(c.isupper() for c in password): score += 1
|
|
47
|
+
else: suggestions.append("Add uppercase letters")
|
|
48
|
+
|
|
49
|
+
if any(c.islower() for c in password): score += 1
|
|
50
|
+
else: suggestions.append("Add lowercase letters")
|
|
51
|
+
|
|
52
|
+
if any(c.isdigit() for c in password): score += 1
|
|
53
|
+
else: suggestions.append("Add numbers")
|
|
54
|
+
|
|
55
|
+
if any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in password): score += 1
|
|
56
|
+
else: suggestions.append("Add special characters")
|
|
57
|
+
|
|
99
58
|
return {
|
|
100
59
|
'score': score,
|
|
101
60
|
'strength': ['Very Weak', 'Weak', 'Medium', 'Strong', 'Very Strong'][min(score, 4)],
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
from ..utils.updates import check_for_updates
|
|
7
|
+
|
|
8
|
+
# CLI COMMAND TO RUN TEST SUITE WITH PYTEST
|
|
9
|
+
@click.command()
|
|
10
|
+
@click.option('--unit', '-u', is_flag=True, help='Run only unit tests')
|
|
11
|
+
@click.option('--integration', '-i', is_flag=True, help='Run only integration tests')
|
|
12
|
+
@click.option('--no-cov', is_flag=True, help='Disable coverage report')
|
|
13
|
+
@click.option('--html', is_flag=True, help='Generate HTML coverage report')
|
|
14
|
+
@click.option('--module', '-m', help='Test specific module (e.g., autocaps, autolower)')
|
|
15
|
+
def autotest(unit, integration, no_cov, html, module):
|
|
16
|
+
_install_test_dependencies()
|
|
17
|
+
|
|
18
|
+
cmd = _build_test_command(unit, integration, no_cov, html, module)
|
|
19
|
+
|
|
20
|
+
click.echo(click.style("\nRunning tests with command:", fg='blue', bold=True))
|
|
21
|
+
click.echo(" ".join(cmd))
|
|
22
|
+
click.echo()
|
|
23
|
+
|
|
24
|
+
_run_test_process(cmd)
|
|
25
|
+
|
|
26
|
+
update_msg = check_for_updates()
|
|
27
|
+
if update_msg: click.echo(update_msg)
|
|
28
|
+
|
|
29
|
+
# INSTALLS TEST DEPENDENCIES IF MISSING BY RUNNING PIP INSTALL COMMAND
|
|
30
|
+
def _install_test_dependencies():
|
|
31
|
+
try:
|
|
32
|
+
import pytest
|
|
33
|
+
import pytest_cov
|
|
34
|
+
except ImportError:
|
|
35
|
+
click.echo(click.style("\n❌ pytest and/or pytest-cov not found. Installing...", fg='yellow', bold=True))
|
|
36
|
+
try:
|
|
37
|
+
subprocess.run(['pip', 'install', 'pytest', 'pytest-cov'], check=True)
|
|
38
|
+
click.echo(click.style("✅ Successfully installed pytest and pytest-cov", fg='green', bold=True))
|
|
39
|
+
except subprocess.CalledProcessError as e:
|
|
40
|
+
click.echo(click.style(f"\n❌ Failed to install dependencies: {str(e)}", fg='red', bold=True))
|
|
41
|
+
sys.exit(1)
|
|
42
|
+
|
|
43
|
+
# BUILDS THE TEST COMMAND ARGUMENTS BY ADDING THE CORRECT TEST PATH AND OPTIONS
|
|
44
|
+
def _build_test_command(unit, integration, no_cov, html, module):
|
|
45
|
+
cmd = [sys.executable, '-m', 'pytest', '-vv', '--capture=no', '--showlocals', '--log-cli-level=DEBUG', '-s']
|
|
46
|
+
|
|
47
|
+
if not no_cov:
|
|
48
|
+
if html: cmd.extend(['--cov-report=html', '--cov=autotools'])
|
|
49
|
+
else: cmd.extend(['--cov-report=term-missing', '--cov=autotools'])
|
|
50
|
+
|
|
51
|
+
if module:
|
|
52
|
+
test_path = f'tests/autotools/{module}'
|
|
53
|
+
if unit and not integration: test_path = f'{test_path}/unit'
|
|
54
|
+
elif integration and not unit: test_path = f'{test_path}/integration'
|
|
55
|
+
cmd.append(test_path)
|
|
56
|
+
else:
|
|
57
|
+
cmd.append('tests')
|
|
58
|
+
|
|
59
|
+
return cmd
|
|
60
|
+
|
|
61
|
+
# PROCESSES TEST OUTPUT LINE BY REMOVING UNNECESSARY CHARACTERS AND FORMATTING
|
|
62
|
+
def _process_test_output_line(line):
|
|
63
|
+
if not line: return None
|
|
64
|
+
line = line.strip()
|
|
65
|
+
if not line: return None
|
|
66
|
+
|
|
67
|
+
if '::' in line and 'autotools/' in line:
|
|
68
|
+
line = line.split('autotools/')[-1].replace('/tests/', '/')
|
|
69
|
+
parts = line.split('/')
|
|
70
|
+
if len(parts) > 1: line = parts[-1]
|
|
71
|
+
|
|
72
|
+
line = re.sub(r'\s+', ' ', line)
|
|
73
|
+
line = re.sub(r'\.+', '.', line)
|
|
74
|
+
|
|
75
|
+
if line.strip('. '): return line
|
|
76
|
+
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
# EXTRACTS COVERAGE DATA FROM TOTAL LINE
|
|
80
|
+
def _parse_coverage_line(line):
|
|
81
|
+
parts = line.split()
|
|
82
|
+
try:
|
|
83
|
+
if len(parts) >= 4:
|
|
84
|
+
stmts = int(parts[1])
|
|
85
|
+
missed = int(parts[2])
|
|
86
|
+
|
|
87
|
+
# CHECK IF BRANCHES ARE PRESENT
|
|
88
|
+
if len(parts) >= 6 and parts[3].isdigit() and parts[4].isdigit():
|
|
89
|
+
branches = int(parts[3])
|
|
90
|
+
branch_partial = int(parts[4])
|
|
91
|
+
coverage_pct = float(parts[5].rstrip('%'))
|
|
92
|
+
return {
|
|
93
|
+
'statements': stmts,
|
|
94
|
+
'missed': missed,
|
|
95
|
+
'branches': branches,
|
|
96
|
+
'branch_partial': branch_partial,
|
|
97
|
+
'coverage': coverage_pct
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
coverage_pct = float(parts[3].rstrip('%'))
|
|
101
|
+
return {'statements': stmts, 'missed': missed, 'coverage': coverage_pct}
|
|
102
|
+
except (ValueError, IndexError):
|
|
103
|
+
match = re.search(r'(\d+\.\d+)%', line)
|
|
104
|
+
if match: return {'coverage': float(match.group(1))}
|
|
105
|
+
return {}
|
|
106
|
+
|
|
107
|
+
# DETERMINES COLOR BASED ON COVERAGE PERCENTAGE
|
|
108
|
+
def _get_coverage_color(percentage):
|
|
109
|
+
if percentage >= 80: return 'green'
|
|
110
|
+
if percentage >= 60: return 'yellow'
|
|
111
|
+
return 'red'
|
|
112
|
+
|
|
113
|
+
# DISPLAYS COVERAGE METRICS
|
|
114
|
+
def _display_coverage_metrics(coverage_data):
|
|
115
|
+
if not coverage_data: return
|
|
116
|
+
|
|
117
|
+
click.echo()
|
|
118
|
+
click.echo(click.style("COVERAGE METRICS", fg='blue', bold=True))
|
|
119
|
+
|
|
120
|
+
# STATEMENTS COVERAGE
|
|
121
|
+
if 'statements' in coverage_data and 'missed' in coverage_data:
|
|
122
|
+
stmts = coverage_data['statements']
|
|
123
|
+
missed = coverage_data['missed']
|
|
124
|
+
covered = stmts - missed
|
|
125
|
+
stmts_pct = (covered / stmts * 100) if stmts > 0 else 0
|
|
126
|
+
color = _get_coverage_color(stmts_pct)
|
|
127
|
+
click.echo(click.style(f"Statements: {covered}/{stmts} ({stmts_pct:.2f}%)", fg=color, bold=True))
|
|
128
|
+
|
|
129
|
+
# BRANCHES COVERAGE
|
|
130
|
+
if 'branches' in coverage_data and coverage_data['branches'] > 0:
|
|
131
|
+
branches = coverage_data['branches']
|
|
132
|
+
branch_partial = coverage_data.get('branch_partial', 0)
|
|
133
|
+
branch_covered = branches - branch_partial
|
|
134
|
+
branch_pct = (branch_covered / branches * 100) if branches > 0 else 0
|
|
135
|
+
color = _get_coverage_color(branch_pct)
|
|
136
|
+
click.echo(click.style(f"Branches: {branch_covered}/{branches} ({branch_pct:.2f}%)", fg=color, bold=True))
|
|
137
|
+
|
|
138
|
+
# OVERALL COVERAGE
|
|
139
|
+
if 'coverage' in coverage_data:
|
|
140
|
+
overall = coverage_data['coverage']
|
|
141
|
+
color = _get_coverage_color(overall)
|
|
142
|
+
click.echo(click.style(f"Overall: {overall:.2f}%", fg=color, bold=True))
|
|
143
|
+
|
|
144
|
+
# RUNS THE TEST PROCESS AND CAPTURES OUTPUT
|
|
145
|
+
def _run_test_process(cmd):
|
|
146
|
+
try:
|
|
147
|
+
env = _prepare_test_environment()
|
|
148
|
+
process = _start_test_process(cmd, env)
|
|
149
|
+
coverage_data = _process_test_output(process)
|
|
150
|
+
_handle_test_result(process, coverage_data)
|
|
151
|
+
except subprocess.CalledProcessError as e:
|
|
152
|
+
click.echo(click.style(f"\n❌ TESTS FAILED WITH RETURN CODE {e.returncode}", fg='red', bold=True))
|
|
153
|
+
sys.exit(1)
|
|
154
|
+
except Exception as e:
|
|
155
|
+
click.echo(click.style(f"\n❌ ERROR RUNNING TESTS: {str(e)}", fg='red', bold=True))
|
|
156
|
+
sys.exit(1)
|
|
157
|
+
|
|
158
|
+
# PREPARES ENVIRONMENT FOR TEST PROCESS
|
|
159
|
+
def _prepare_test_environment():
|
|
160
|
+
env = dict(os.environ)
|
|
161
|
+
env['PYTHONPATH'] = os.getcwd()
|
|
162
|
+
env['FORCE_COLOR'] = '1'
|
|
163
|
+
return env
|
|
164
|
+
|
|
165
|
+
# STARTS THE TEST PROCESS
|
|
166
|
+
def _start_test_process(cmd, env):
|
|
167
|
+
return subprocess.Popen(
|
|
168
|
+
cmd,
|
|
169
|
+
env=env,
|
|
170
|
+
stdout=subprocess.PIPE,
|
|
171
|
+
stderr=subprocess.STDOUT,
|
|
172
|
+
universal_newlines=True,
|
|
173
|
+
bufsize=1
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# PROCESSES TEST OUTPUT AND EXTRACTS COVERAGE DATA
|
|
177
|
+
def _process_test_output(process):
|
|
178
|
+
coverage_data = {}
|
|
179
|
+
|
|
180
|
+
while True:
|
|
181
|
+
line = process.stdout.readline()
|
|
182
|
+
if not line and process.poll() is not None: break
|
|
183
|
+
|
|
184
|
+
if 'TOTAL' in line and '%' in line:
|
|
185
|
+
coverage_data = _parse_coverage_line(line)
|
|
186
|
+
sys.stdout.write(line)
|
|
187
|
+
sys.stdout.flush()
|
|
188
|
+
continue
|
|
189
|
+
|
|
190
|
+
processed_line = _process_test_output_line(line)
|
|
191
|
+
if processed_line:
|
|
192
|
+
sys.stdout.write(processed_line + '\n')
|
|
193
|
+
sys.stdout.flush()
|
|
194
|
+
|
|
195
|
+
process.wait()
|
|
196
|
+
return coverage_data
|
|
197
|
+
|
|
198
|
+
# HANDLES TEST RESULT AND DISPLAYS COVERAGE
|
|
199
|
+
def _handle_test_result(process, coverage_data):
|
|
200
|
+
if process.returncode == 0:
|
|
201
|
+
click.echo(click.style("\n✅ ALL TESTS PASSED !", fg='green', bold=True))
|
|
202
|
+
_display_coverage_metrics(coverage_data)
|
|
203
|
+
else:
|
|
204
|
+
click.echo(click.style("\n❌ SOME TESTS FAILED!", fg='red', bold=True))
|
|
205
|
+
sys.exit(1)
|
autotools/cli.py
CHANGED
|
@@ -1,95 +1,156 @@
|
|
|
1
1
|
import click
|
|
2
|
-
from importlib.metadata import version as get_version, PackageNotFoundError
|
|
3
|
-
import pkg_resources
|
|
4
2
|
import requests
|
|
5
|
-
from packaging.version import parse as parse_version
|
|
6
|
-
from dotenv import load_dotenv
|
|
7
|
-
from datetime import datetime
|
|
8
3
|
import base64
|
|
9
|
-
import json as json_module
|
|
10
|
-
from translate import Translator
|
|
11
|
-
import yt_dlp
|
|
12
4
|
import argparse
|
|
5
|
+
import json as json_module
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from dotenv import load_dotenv
|
|
9
|
+
from datetime import datetime
|
|
13
10
|
from urllib.parse import urlparse
|
|
11
|
+
from packaging.version import parse as parse_version
|
|
12
|
+
from importlib.metadata import version as get_version, PackageNotFoundError
|
|
14
13
|
|
|
15
|
-
# IMPORT COMMANDS FROM EACH MODULE
|
|
16
|
-
from .autocaps.commands import autocaps
|
|
17
|
-
from .autolower.commands import autolower
|
|
18
|
-
from .autodownload.commands import autodownload
|
|
19
|
-
from .autopassword.commands import autopassword
|
|
20
|
-
from .autotranslate.commands import autotranslate
|
|
21
|
-
from .autoip.commands import autoip
|
|
22
|
-
from .autospell.commands import autospell
|
|
23
|
-
from .test.commands import test
|
|
24
|
-
from .utils.updates import check_for_updates
|
|
25
14
|
from .utils.version import print_version
|
|
15
|
+
from .utils.updates import check_for_updates
|
|
16
|
+
from .utils.commands import autocaps, autolower, autopassword, autoip, autotest
|
|
17
|
+
from .utils.performance import init_metrics, finalize_metrics, get_metrics, should_enable_metrics, track_step
|
|
26
18
|
|
|
27
|
-
# LOAD ENVIRONMENT VARIABLES FROM .ENV FILE
|
|
28
19
|
load_dotenv()
|
|
29
20
|
|
|
30
|
-
# CLI
|
|
31
|
-
@click.group()
|
|
32
|
-
@click.option('--version', '--v', is_flag=True, callback=print_version,
|
|
21
|
+
# MAIN CLI ENTRY POINT - REGISTERS ALL COMMANDS AND HANDLES GLOBAL OPTIONS
|
|
22
|
+
@click.group(invoke_without_command=True)
|
|
23
|
+
@click.option('--version', '--v', is_flag=True, callback=lambda ctx, param, value: print_version(ctx, value),
|
|
33
24
|
expose_value=False, is_eager=True, help='Show version and check for updates')
|
|
34
25
|
@click.option('--help', '-h', is_flag=True, callback=lambda ctx, param, value:
|
|
35
26
|
None if not value else (click.echo(ctx.get_help() + '\n' +
|
|
36
27
|
(check_for_updates() or '')) or ctx.exit()),
|
|
37
28
|
is_eager=True, expose_value=False, help='Show this message and exit.')
|
|
38
|
-
|
|
39
|
-
|
|
29
|
+
@click.option('--perf', is_flag=True, help='Display performance metrics')
|
|
30
|
+
@click.pass_context
|
|
31
|
+
def cli(ctx, perf):
|
|
32
|
+
"""
|
|
33
|
+
A suite of automated tools for various tasks:\n
|
|
34
|
+
- autocaps: Convert text to uppercase\n
|
|
35
|
+
- autolower: Convert text to lowercase\n
|
|
36
|
+
- autopassword: Generate secure passwords and encryption keys\n
|
|
37
|
+
- autoip: Display network information and run diagnostics\n
|
|
38
|
+
- test: Run the test suite\n
|
|
39
|
+
\n
|
|
40
|
+
Run 'autotools COMMAND --help' for more information on each command.\n
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
# INITIALIZE METRICS IF NEEDED
|
|
44
|
+
if should_enable_metrics(ctx):
|
|
45
|
+
init_metrics()
|
|
46
|
+
get_metrics().step_start('startup')
|
|
47
|
+
get_metrics().step_end()
|
|
48
|
+
get_metrics().end_startup()
|
|
40
49
|
|
|
41
|
-
|
|
42
|
-
|
|
50
|
+
# IF NO COMMAND INVOKED, SHOW HELP
|
|
51
|
+
if ctx.invoked_subcommand is None:
|
|
52
|
+
autotools()
|
|
53
|
+
if should_enable_metrics(ctx):
|
|
54
|
+
get_metrics().end_process()
|
|
55
|
+
finalize_metrics(ctx)
|
|
43
56
|
|
|
44
|
-
#
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
# EXECUTES COMMAND WITH PERFORMANCE TRACKING
|
|
58
|
+
def _execute_with_metrics(ctx, original_callback, *args, **kwargs):
|
|
59
|
+
metrics = get_metrics()
|
|
60
|
+
# REMOVE 'perf' FROM kwargs IF PRESENT (IT'S NOT PART OF THE ORIGINAL CALLBACK SIGNATURE)
|
|
61
|
+
kwargs.pop('perf', None)
|
|
62
|
+
|
|
63
|
+
if not should_enable_metrics(ctx): return original_callback(*args, **kwargs)
|
|
64
|
+
|
|
65
|
+
if metrics.process_start is None:
|
|
66
|
+
init_metrics()
|
|
67
|
+
get_metrics().end_startup()
|
|
68
|
+
|
|
69
|
+
metrics.start_command()
|
|
70
|
+
cmd_name = ctx.invoked_subcommand or ctx.command.name or 'unknown'
|
|
71
|
+
try:
|
|
72
|
+
with track_step(f'command_{cmd_name}'): result = original_callback(*args, **kwargs)
|
|
73
|
+
metrics.end_command()
|
|
74
|
+
return result
|
|
75
|
+
finally:
|
|
76
|
+
if metrics.process_end is None:
|
|
77
|
+
metrics.end_process()
|
|
78
|
+
finalize_metrics(ctx)
|
|
53
79
|
|
|
54
|
-
#
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
# SHOW COMMANDS LIST WITH BETTER FORMATTING
|
|
59
|
-
ctx = click.get_current_context()
|
|
60
|
-
commands = cli.list_commands(ctx)
|
|
80
|
+
# WRAPS COMMANDS WITH PERFORMANCE TRACKING
|
|
81
|
+
def _wrap_command_with_metrics(cmd):
|
|
82
|
+
import inspect
|
|
83
|
+
original_callback = cmd.callback
|
|
61
84
|
|
|
85
|
+
# ADD --perf OPTION TO THE COMMAND SO IT CAN BE USED DIRECTLY ON SUBCOMMANDS
|
|
86
|
+
has_perf_option = any(param.opts == ['--perf'] for param in cmd.params if isinstance(param, click.Option))
|
|
87
|
+
if not has_perf_option:
|
|
88
|
+
perf_option = click.Option(['--perf'], is_flag=True, help='Display performance metrics')
|
|
89
|
+
cmd.params.append(perf_option)
|
|
90
|
+
|
|
91
|
+
sig = inspect.signature(original_callback)
|
|
92
|
+
expects_ctx = 'ctx' in sig.parameters
|
|
93
|
+
|
|
94
|
+
if expects_ctx:
|
|
95
|
+
@click.pass_context
|
|
96
|
+
def wrapped_callback(ctx, *args, **kwargs):
|
|
97
|
+
return _execute_with_metrics(ctx, original_callback, ctx, *args, **kwargs)
|
|
98
|
+
else:
|
|
99
|
+
def wrapped_callback(*args, **kwargs):
|
|
100
|
+
ctx = click.get_current_context()
|
|
101
|
+
return _execute_with_metrics(ctx, original_callback, *args, **kwargs)
|
|
102
|
+
|
|
103
|
+
cmd.callback = wrapped_callback
|
|
104
|
+
return cmd
|
|
105
|
+
|
|
106
|
+
cli.add_command(_wrap_command_with_metrics(autocaps))
|
|
107
|
+
cli.add_command(_wrap_command_with_metrics(autolower))
|
|
108
|
+
cli.add_command(_wrap_command_with_metrics(autopassword))
|
|
109
|
+
cli.add_command(_wrap_command_with_metrics(autoip))
|
|
110
|
+
cli.add_command(_wrap_command_with_metrics(autotest), name='test')
|
|
111
|
+
|
|
112
|
+
# DISPLAYS COMMAND OPTIONS
|
|
113
|
+
def _display_command_options(cmd_obj):
|
|
114
|
+
if not hasattr(cmd_obj, 'params'): return
|
|
115
|
+
click.echo(click.style("\n Options:", fg='yellow'))
|
|
116
|
+
for param in cmd_obj.params:
|
|
117
|
+
if isinstance(param, click.Option):
|
|
118
|
+
opts = '/'.join(param.opts)
|
|
119
|
+
help_text = param.help or ''
|
|
120
|
+
click.echo(f" {click.style(opts, fg='yellow')}")
|
|
121
|
+
click.echo(f" {help_text}")
|
|
122
|
+
|
|
123
|
+
# DISPLAYS ALL AVAILABLE COMMANDS
|
|
124
|
+
def _display_commands(ctx, commands):
|
|
62
125
|
click.echo(click.style("\nOpen-AutoTools Commands:", fg='blue', bold=True))
|
|
63
126
|
for cmd in sorted(commands):
|
|
64
|
-
if cmd
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
# GET OPTIONS FOR EACH COMMAND
|
|
71
|
-
if hasattr(cmd_obj, 'params'):
|
|
72
|
-
click.echo(click.style("\n Options:", fg='yellow'))
|
|
73
|
-
for param in cmd_obj.params:
|
|
74
|
-
if isinstance(param, click.Option):
|
|
75
|
-
opts = '/'.join(param.opts)
|
|
76
|
-
help_text = param.help or ''
|
|
77
|
-
click.echo(f" {click.style(opts, fg='yellow')}")
|
|
78
|
-
click.echo(f" {help_text}")
|
|
127
|
+
if cmd == 'autotools': continue
|
|
128
|
+
cmd_obj = cli.get_command(ctx, cmd)
|
|
129
|
+
help_text = cmd_obj.help or cmd_obj.short_help or ''
|
|
130
|
+
click.echo(f"\n{click.style(cmd, fg='green', bold=True)}")
|
|
131
|
+
click.echo(f" {help_text}")
|
|
132
|
+
_display_command_options(cmd_obj)
|
|
79
133
|
|
|
80
|
-
|
|
134
|
+
# DISPLAYS USAGE EXAMPLES
|
|
135
|
+
def _display_usage_examples():
|
|
81
136
|
click.echo(click.style("\nUsage Examples:", fg='blue', bold=True))
|
|
82
137
|
click.echo(" autotools --help Show this help message")
|
|
83
138
|
click.echo(" autotools --version Show version information")
|
|
84
139
|
click.echo(" autotools COMMAND Run a specific command")
|
|
85
140
|
click.echo(" autotools COMMAND --help Show help for a specific command")
|
|
86
141
|
|
|
87
|
-
|
|
142
|
+
# DISPLAYS ALL AVAILABLE COMMANDS WITH THEIR OPTIONS AND USAGE EXAMPLES
|
|
143
|
+
@cli.command()
|
|
144
|
+
def autotools():
|
|
145
|
+
ctx = click.get_current_context()
|
|
146
|
+
commands = cli.list_commands(ctx)
|
|
147
|
+
|
|
148
|
+
_display_commands(ctx, commands)
|
|
149
|
+
_display_usage_examples()
|
|
150
|
+
|
|
88
151
|
update_msg = check_for_updates()
|
|
89
152
|
if update_msg:
|
|
90
153
|
click.echo(click.style("\nUpdate Available:", fg='red', bold=True))
|
|
91
154
|
click.echo(update_msg)
|
|
92
155
|
|
|
93
|
-
|
|
94
|
-
if __name__ == '__main__':
|
|
95
|
-
cli()
|
|
156
|
+
if __name__ == '__main__': cli()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from ..autocaps.commands import autocaps
|
|
2
|
+
from ..autolower.commands import autolower
|
|
3
|
+
from ..autopassword.commands import autopassword
|
|
4
|
+
from ..autoip.commands import autoip
|
|
5
|
+
from ..autotest.commands import autotest
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
'autocaps',
|
|
9
|
+
'autolower',
|
|
10
|
+
'autopassword',
|
|
11
|
+
'autoip',
|
|
12
|
+
'autotest'
|
|
13
|
+
]
|
autotools/utils/loading.py
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
|
+
import threading
|
|
1
2
|
from halo import Halo
|
|
2
3
|
|
|
4
|
+
# PATCHES THREAD SET DAEMON METHOD TO PREVENT WARNINGS
|
|
5
|
+
def _patched_set_daemon(self, daemon):
|
|
6
|
+
self.daemon = daemon
|
|
7
|
+
|
|
8
|
+
_original_set_daemon = threading.Thread.setDaemon
|
|
9
|
+
threading.Thread.setDaemon = _patched_set_daemon
|
|
10
|
+
|
|
11
|
+
# CONTEXT MANAGER FOR DISPLAYING LOADING ANIMATION
|
|
3
12
|
class LoadingAnimation:
|
|
4
|
-
|
|
13
|
+
# INITIALIZES SPINNER WITH CUSTOM ANIMATION FRAMES
|
|
5
14
|
def __init__(self):
|
|
6
|
-
self._spinner = Halo(spinner={
|
|
7
|
-
'interval': 200,
|
|
8
|
-
'frames': [' ', '. ', '.. ', '...'],
|
|
9
|
-
})
|
|
15
|
+
self._spinner = Halo(spinner={'interval': 200, 'frames': [' ', '. ', '.. ', '...']})
|
|
10
16
|
|
|
17
|
+
# STARTS THE LOADING ANIMATION
|
|
11
18
|
def __enter__(self):
|
|
12
19
|
self._spinner.start()
|
|
13
20
|
return self
|
|
14
21
|
|
|
22
|
+
# STOPS THE LOADING ANIMATION
|
|
15
23
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
16
|
-
self._spinner.stop()
|
|
24
|
+
self._spinner.stop()
|