Open-AutoTools 0.0.3rc1__py3-none-any.whl → 0.0.3rc3__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/cli.py CHANGED
@@ -1,103 +1,32 @@
1
- import os
2
1
  import click
3
- import base64
4
- import json as json_module
5
2
  from importlib.metadata import version as get_version, PackageNotFoundError
3
+ import pkg_resources
4
+ import requests
6
5
  from packaging.version import parse as parse_version
7
- from autotools.autocaps.core import autocaps_transform
8
- from autotools.autolower.core import autolower_transform
9
- from autotools.autodownload.core import (
10
- download_youtube_video,
11
- download_file,
12
- validate_youtube_url
13
- )
14
- from autotools.autopassword.core import (
15
- generate_password,
16
- generate_encryption_key,
17
- analyze_password_strength
18
- )
6
+ from dotenv import load_dotenv
7
+ from datetime import datetime
8
+ import base64
9
+ import json as json_module
19
10
  from translate import Translator
20
- from autotools.autotranslate.core import translate_text, get_supported_languages
21
11
  import yt_dlp
22
- from autotools import autodownload, autolower, autocaps, autoip
23
12
  import argparse
24
- from autotools.autospell.core import SpellChecker
25
13
  from urllib.parse import urlparse
26
- import requests
27
- from datetime import datetime
28
- import pytest
29
- import sys
30
- import subprocess
31
- from dotenv import load_dotenv
14
+
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
+ from .utils.version import print_version
32
26
 
33
27
  # LOAD ENVIRONMENT VARIABLES FROM .ENV FILE
34
28
  load_dotenv()
35
29
 
36
- # VERSION CALLBACK
37
- def print_version(ctx, param, value):
38
- """PRINT VERSION AND CHECK FOR UPDATES"""
39
-
40
- # EXIT IF VERSION IS NOT REQUESTED
41
- if not value or ctx.resilient_parsing:
42
- return
43
-
44
- # GET CURRENT VERSION
45
- try:
46
- pkg_version = get_version('Open-AutoTools')
47
- click.echo(f"Open-AutoTools version {pkg_version}")
48
-
49
- try:
50
- # CHECK IF PACKAGE IS FROM TESTPYPI
51
- is_testpypi = False
52
- try:
53
- import pkg_resources
54
- dist = pkg_resources.get_distribution('Open-AutoTools')
55
- is_testpypi = 'test.pypi.org' in dist.location
56
- except:
57
- pass
58
-
59
- # DETERMINE PYPI URL BASED ON SOURCE
60
- pypi_url = "https://test.pypi.org/pypi/Open-AutoTools/json" if is_testpypi else "https://pypi.org/pypi/Open-AutoTools/json"
61
-
62
- # CHECK LATEST VERSION FROM PYPI
63
- response = requests.get(pypi_url)
64
- if response.status_code == 200:
65
- data = response.json()
66
- latest_version = data["info"]["version"]
67
- # GET RELEASE DATE
68
- releases = data["releases"]
69
- if latest_version in releases and releases[latest_version]:
70
- try:
71
- upload_time = releases[latest_version][0]["upload_time"]
72
- # TRY DIFFERENT DATE FORMATS
73
- try:
74
- published_date = datetime.strptime(upload_time, "%Y-%m-%dT%H:%M:%S")
75
- except ValueError:
76
- try:
77
- published_date = datetime.strptime(upload_time, "%Y-%m-%dT%H:%M:%S.%fZ")
78
- except ValueError:
79
- published_date = datetime.strptime(upload_time, "%Y-%m-%d %H:%M:%S")
80
- formatted_date = published_date.strftime("%d %B %Y at %H:%M:%S")
81
- click.echo(f"Released: {formatted_date}")
82
- except Exception:
83
- # IF DATE PARSING FAILS, SKIP SHOWING THE DATE
84
- pass
85
-
86
- # PARSE VERSIONS FOR COMPARISON
87
- current_parsed = parse_version(pkg_version)
88
- latest_parsed = parse_version(latest_version)
89
-
90
- # COMPARE VERSIONS AND PRINT UPDATE MESSAGE IF NEEDED
91
- if latest_parsed > current_parsed:
92
- update_cmd = "pip install --upgrade -i https://test.pypi.org/simple/ Open-AutoTools" if is_testpypi else "pip install --upgrade Open-AutoTools"
93
- click.echo(click.style(f"\nUpdate available: v{latest_version}", fg='red', bold=True))
94
- click.echo(click.style(f"Run '{update_cmd}' to update", fg='red'))
95
- except Exception as e:
96
- click.echo(f"Error checking updates: {str(e)}")
97
- except PackageNotFoundError:
98
- click.echo("Open-AutoTools version information not available")
99
- ctx.exit()
100
-
101
30
  # CLI FUNCTION DEFINITION
102
31
  @click.group()
103
32
  @click.option('--version', '--v', is_flag=True, callback=print_version,
@@ -112,7 +41,17 @@ def cli():
112
41
  Run 'autotools COMMAND --help' for more information on each command."""
113
42
  pass
114
43
 
115
- # AUTOTOOLS COMMAND LINE INTERFACE FUNCTION DEFINITION FOR SHOW HELP MESSAGE
44
+ # REGISTER COMMANDS
45
+ cli.add_command(autocaps)
46
+ cli.add_command(autolower)
47
+ cli.add_command(autodownload)
48
+ cli.add_command(autopassword)
49
+ cli.add_command(autotranslate)
50
+ cli.add_command(autoip)
51
+ cli.add_command(autospell)
52
+ cli.add_command(test)
53
+
54
+ # MAIN COMMAND DEFINITION
116
55
  @cli.command()
117
56
  def autotools():
118
57
  """Display available commands and tool information."""
@@ -151,417 +90,6 @@ def autotools():
151
90
  click.echo(click.style("\nUpdate Available:", fg='red', bold=True))
152
91
  click.echo(update_msg)
153
92
 
154
- def check_for_updates():
155
- """CHECK IF AN UPDATE IS AVAILABLE AND RETURN UPDATE MESSAGE IF NEEDED"""
156
-
157
- # GET CURRENT VERSION
158
- try:
159
- current_version = get_version('Open-AutoTools')
160
-
161
- # CHECK IF PACKAGE IS FROM TESTPYPI
162
- is_testpypi = False
163
- try:
164
- # ATTEMPT TO GET PACKAGE METADATA
165
- import pkg_resources
166
- dist = pkg_resources.get_distribution('Open-AutoTools')
167
- is_testpypi = 'test.pypi.org' in dist.location
168
- except:
169
- pass
170
-
171
- # DETERMINE PYPI URL BASED ON SOURCE
172
- pypi_url = "https://test.pypi.org/pypi/Open-AutoTools/json" if is_testpypi else "https://pypi.org/pypi/Open-AutoTools/json"
173
-
174
- # CHECK FOR UPDATES FROM PYPI
175
- response = requests.get(pypi_url)
176
-
177
- # CHECK IF RESPONSE IS SUCCESSFUL
178
- if response.status_code == 200:
179
- data = response.json()
180
- latest_version = data["info"]["version"]
181
-
182
- # PARSE VERSIONS FOR COMPARISON
183
- current_parsed = parse_version(current_version)
184
- latest_parsed = parse_version(latest_version)
185
-
186
- # PRINT UPDATE MESSAGE IF NEEDED
187
- if latest_parsed > current_parsed:
188
- update_cmd = "pip install --upgrade -i https://test.pypi.org/simple/ Open-AutoTools" if is_testpypi else "pip install --upgrade Open-AutoTools"
189
- return (
190
- click.style(f"\nUpdate available: v{latest_version}", fg='red', bold=True) + "\n" +
191
- click.style(f"Run '{update_cmd}' to update", fg='red')
192
- )
193
- except Exception as e:
194
- # FOR DEBUGGING, LOG ERROR
195
- print(f"Error checking updates: {str(e)}")
196
- pass
197
- return None
198
-
199
- # AUTOCAPS COMMAND LINE INTERFACE FUNCTION DEFINITION
200
- @cli.command()
201
- @click.argument('text', nargs=-1)
202
- def autocaps(text):
203
- """Convert text to UPPERCASE."""
204
- result = autocaps_transform(" ".join(text))
205
- click.echo(result)
206
-
207
- # UPDATE CHECK AT THE END
208
- update_msg = check_for_updates()
209
- if update_msg:
210
- click.echo(update_msg)
211
-
212
- # AUTOLOWER CASE COMMAND LINE INTERFACE FUNCTION DEFINITION
213
- @cli.command()
214
- @click.argument('text', nargs=-1)
215
- def autolower(text):
216
- """Convert text to lowercase."""
217
- result = autolower_transform(" ".join(text))
218
- click.echo(result)
219
-
220
- # UPDATE CHECK AT THE END
221
- update_msg = check_for_updates()
222
- if update_msg:
223
- click.echo(update_msg)
224
-
225
- # AUTODOWNLOAD COMMAND LINE INTERFACE FUNCTION DEFINITION
226
- @cli.command()
227
- @click.argument('url')
228
- @click.option('--format', '-f', type=click.Choice(['mp4', 'mp3'], case_sensitive=False),
229
- default='mp4', help='Output file format')
230
- @click.option('--quality', '-q', type=click.Choice(['best', '1440p', '1080p', '720p', '480p', '360p', '240p'],
231
- case_sensitive=False), default='best', help='Video quality (mp4 only)')
232
- def autodownload(url, format, quality):
233
- """Download videos from YouTube or files from any URL.
234
-
235
- Supports YouTube video download with quality selection and format conversion (mp4/mp3).
236
- For non-YouTube URLs, downloads the file directly."""
237
- if "youtube.com" in url or "youtu.be" in url:
238
- # VALIDATE YOUTUBE URL FIRST
239
- if not validate_youtube_url(url):
240
- click.echo("Invalid YouTube URL", err=True)
241
- sys.exit(1)
242
- download_youtube_video(url, format, quality)
243
- else:
244
- download_file(url)
245
-
246
- # UPDATE CHECK AT THE END
247
- update_msg = check_for_updates()
248
- if update_msg:
249
- click.echo(update_msg)
250
-
251
- # AUTOPASSWORD COMMAND LINE INTERFACE FUNCTION DEFINITION
252
- @cli.command()
253
- @click.option('--length', '-l', default=12, help='Password length (default: 12)')
254
- @click.option('--no-uppercase', '-u', is_flag=True, help='Exclude uppercase letters')
255
- @click.option('--no-numbers', '-n', is_flag=True, help='Exclude numbers')
256
- @click.option('--no-special', '-s', is_flag=True, help='Exclude special characters')
257
- @click.option('--min-special', '-m', default=1, help='Minimum number of special characters')
258
- @click.option('--min-numbers', '-d', default=1, help='Minimum number of numbers')
259
- @click.option('--analyze', '-a', is_flag=True, help='Analyze password strength')
260
- @click.option('--gen-key', '-g', is_flag=True, help='Generate encryption key')
261
- @click.option('--password-key', '-p', help='Generate key from password')
262
- def autopassword(length, no_uppercase, no_numbers, no_special,
263
- min_special, min_numbers, analyze, gen_key, password_key):
264
- """Generate secure passwords and encryption keys."""
265
-
266
- ## HELPER FUNCTION TO SHOW PASSWORD/KEY ANALYSIS
267
- def show_analysis(text, prefix=""):
268
- """Helper function to show password/key analysis"""
269
- if analyze:
270
- analysis = analyze_password_strength(text)
271
- click.echo(f"\n{prefix}Strength Analysis:")
272
- click.echo(f"Strength: {analysis['strength']}")
273
- click.echo(f"Score: {analysis['score']}/5")
274
- if analysis['suggestions']:
275
- click.echo("\nSuggestions for improvement:")
276
- for suggestion in analysis['suggestions']:
277
- click.echo(f"- {suggestion}")
278
-
279
- # GENERATE KEY
280
- if gen_key:
281
- key = generate_encryption_key()
282
- key_str = key.decode()
283
- click.echo(f"Encryption Key: {key_str}")
284
- if analyze:
285
- show_analysis(key_str, "Key ")
286
- return
287
-
288
- # GENERATE KEY FROM PASSWORD
289
- if password_key:
290
- key, salt = generate_encryption_key(password_key)
291
- key_str = key.decode()
292
- click.echo(f"Derived Key: {key_str}")
293
- click.echo(f"Salt: {base64.b64encode(salt).decode()}")
294
- if analyze:
295
- click.echo("\nAnalyzing source password:")
296
- show_analysis(password_key, "Password ")
297
- click.echo("\nAnalyzing generated key:")
298
- show_analysis(key_str, "Key ")
299
- return
300
-
301
- # GENERATE PASSWORD
302
- password = generate_password(
303
- length=length,
304
- use_uppercase=not no_uppercase,
305
- use_numbers=not no_numbers,
306
- use_special=not no_special,
307
- min_special=min_special,
308
- min_numbers=min_numbers,
309
- )
310
-
311
- # SHOW PASSWORD
312
- click.echo(f"Generated Password: {password}")
313
- show_analysis(password, "Password ")
314
-
315
- # UPDATE CHECK AT THE END
316
- update_msg = check_for_updates()
317
- if update_msg:
318
- click.echo(update_msg)
319
-
320
- # TRANSLATE COMMAND LINE INTERFACE FUNCTION DEFINITION
321
- @cli.command()
322
- @click.argument('text', required=False)
323
- @click.option('--to', default='en', help='Target language (default: en)')
324
- @click.option('--from', 'from_lang', help='Source language (default: auto-detect)')
325
- @click.option('--list-languages', is_flag=True, help='List all supported languages')
326
- @click.option('--copy', is_flag=True, help='Copy translation to clipboard')
327
- @click.option('--detect', is_flag=True, help='Show detected source language')
328
- @click.option('--output', '-o', type=click.Path(), help='Save translation to file')
329
- def autotranslate(text: str, to: str, from_lang: str, list_languages: bool,
330
- copy: bool, detect: bool, output: str):
331
- """Translate text to specified language.
332
-
333
- Supports automatic language detection, multiple target languages,
334
- clipboard operations and file output. Use --list-languages to see
335
- all supported language codes."""
336
- # LIST ALL SUPPORTED LANGUAGES
337
- if list_languages:
338
- click.echo("\nSupported Languages:")
339
- for code, name in get_supported_languages().items():
340
- click.echo(f"{code:<8} {name}")
341
- return
342
-
343
- # CHECK IF TEXT IS PROVIDED
344
- if not text:
345
- click.echo("Error: Please provide text to translate")
346
- return
347
-
348
- result = translate_text(text, to_lang=to, from_lang=from_lang,
349
- copy=copy, detect_lang=detect, output=output)
350
- click.echo(result)
351
-
352
- # UPDATE CHECK AT THE END
353
- update_msg = check_for_updates()
354
- if update_msg:
355
- click.echo(update_msg)
356
-
357
- # AUTOIP COMMAND LINE INTERFACE FUNCTION DEFINITION
358
- @cli.command()
359
- @click.option('--test', '-t', is_flag=True, help='Run connectivity tests')
360
- @click.option('--speed', '-s', is_flag=True, help='Run internet speed test')
361
- @click.option('--monitor', '-m', is_flag=True, help='Monitor network traffic')
362
- @click.option('--interval', '-i', default=1, help='Monitoring interval in seconds')
363
- @click.option('--ports', '-p', is_flag=True, help='Check common ports status')
364
- @click.option('--dns', '-d', is_flag=True, help='Show DNS servers')
365
- @click.option('--location', '-l', is_flag=True, help='Show IP location info')
366
- @click.option('--no-ip', '-n', is_flag=True, help='Hide IP addresses')
367
- def autoip(test, speed, monitor, interval, ports, dns, location, no_ip):
368
- """Display network information and diagnostics.
369
-
370
- Shows local and public IP addresses, runs network diagnostics,
371
- performs speed tests, monitors traffic with custom intervals,
372
- checks ports, displays DNS information and provides geolocation data."""
373
- from autotools.autoip.core import run
374
- output = run(test=test, speed=speed, monitor=monitor, interval=interval,
375
- ports=ports, dns=dns, location=location, no_ip=no_ip)
376
- click.echo(output)
377
-
378
- # UPDATE CHECK AT THE END
379
- update_msg = check_for_updates()
380
- if update_msg:
381
- click.echo(update_msg)
382
-
383
- # AUTOSPELL COMMAND LINE INTERFACE FUNCTION DEFINITION
384
- @cli.command()
385
- @click.argument('texts', nargs=-1)
386
- @click.option('--lang', '-l', default='auto', help='Language code (auto for detection)')
387
- @click.option('--fix', '-f', is_flag=True, help='Auto-fix text and copy to clipboard')
388
- @click.option('--copy', '-c', is_flag=True, help='Copy result to clipboard')
389
- @click.option('--list-languages', is_flag=True, help='List supported languages')
390
- @click.option('--json', '-j', is_flag=True, help='Output results as JSON')
391
- @click.option('--ignore', '-i', multiple=True,
392
- type=click.Choice(['spelling', 'grammar', 'style', 'punctuation']),
393
- help='Error types to ignore')
394
- @click.option('--interactive', '-n', is_flag=True,
395
- help='Interactive mode - confirm each correction')
396
- @click.option('--output', '-o', type=click.Path(),
397
- help='Save corrections to file')
398
- def autospell(texts: tuple, lang: str, fix: bool, copy: bool, list_languages: bool,
399
- json: bool, ignore: tuple, interactive: bool, output: str):
400
- """Check and fix text for spelling, grammar, style, and punctuation errors.
401
-
402
- Provides comprehensive text analysis with support for multiple languages,
403
- interactive corrections, and various output formats (text/JSON).
404
- Can ignore specific error types: spelling, grammar, style, or punctuation."""
405
- checker = SpellChecker()
406
-
407
- # LIST ALL SUPPORTED LANGUAGES
408
- if list_languages:
409
- languages = checker.get_supported_languages()
410
- if json:
411
- result = {'languages': languages}
412
- click.echo(json_module.dumps(result, indent=2))
413
- else:
414
- click.echo("\nSupported Languages:")
415
- for lang in languages:
416
- click.echo(f"{lang['code']:<8} {lang['name']}")
417
- return
418
-
419
- # CHECK AND FIX SPELLING/GRAMMAR IN TEXT
420
- for text in texts:
421
- if not text:
422
- click.echo("Error: Please provide text to check")
423
- continue
424
-
425
- # FIX SPELLING/GRAMMAR IN TEXT
426
- if fix:
427
- # CORRECT TEXT WITH SPELL CHECKER
428
- corrected = checker.fix_text(text, lang, copy_to_clipboard=True,
429
- ignore=ignore, interactive=interactive)
430
- result = {'corrected_text': corrected} # RESULT TO RETURN
431
-
432
- # OUTPUT RESULTS AS JSON
433
- if json:
434
- click.echo(json_module.dumps(result, indent=2))
435
- else:
436
- # LANGUAGE INFORMATION
437
- check_result = checker.check_text(text, lang)
438
- lang_info = check_result['language']
439
- click.echo(f"\nLanguage detected: {lang_info['name']} ({lang_info['code']})")
440
- click.echo(f"Confidence: {lang_info['confidence']:.2%}")
441
- click.echo("\nCorrected text (copied to clipboard):")
442
- click.echo(corrected)
443
-
444
- # SAVE CORRECTIONS TO FILE
445
- if output:
446
- with open(output, 'w', encoding='utf-8') as f:
447
- if json:
448
- json_module.dump(result, f, indent=2)
449
- else:
450
- f.write(corrected)
451
- else:
452
- # CHECK SPELLING/GRAMMAR IN TEXT
453
- check_result = checker.check_text(text, lang)
454
-
455
- # OUTPUT RESULTS AS JSON
456
- if json:
457
- click.echo(json_module.dumps(check_result, indent=2))
458
- else:
459
- lang_info = check_result['language']
460
- click.echo(f"\nLanguage detected: {lang_info['name']} ({lang_info['code']})")
461
- click.echo(f"Confidence: {lang_info['confidence']:.2%}")
462
- click.echo(f"Total errors found: {check_result['statistics']['total_errors']}")
463
-
464
- # CORRECTIONS SUGGESTED
465
- if check_result['corrections']:
466
- click.echo("\nCorrections suggested:")
467
- for i, corr in enumerate(check_result['corrections'], 1):
468
- click.echo(f"\n{i}. [{corr['severity'].upper()}] {corr['message']}")
469
- click.echo(f" Context: {corr['context']}")
470
- if corr['replacements']:
471
- click.echo(f" Suggestions: {', '.join(corr['replacements'][:3])}")
472
-
473
- # SAVE CHECK RESULT TO FILE
474
- if output:
475
- with open(output, 'w', encoding='utf-8') as f:
476
- if json:
477
- json_module.dump(check_result, f, indent=2)
478
- else:
479
- # WRITE A HUMAN-READABLE REPORT
480
- f.write(f"Language: {lang_info['name']} ({lang_info['code']})\n")
481
- f.write(f"Confidence: {lang_info['confidence']:.2%}\n")
482
- f.write(f"Total errors: {check_result['statistics']['total_errors']}\n\n")
483
-
484
- # CORRECTIONS SUGGESTED
485
- if check_result['corrections']:
486
- f.write("Corrections suggested:\n")
487
- for i, corr in enumerate(check_result['corrections'], 1):
488
- f.write(f"\n{i}. [{corr['severity'].upper()}] {corr['message']}\n")
489
- f.write(f" Context: {corr['context']}\n")
490
- if corr['replacements']:
491
- f.write(f" Suggestions: {', '.join(corr['replacements'][:3])}\n")
492
-
493
- # UPDATE CHECK AT THE END
494
- update_msg = check_for_updates()
495
- if update_msg:
496
- click.echo(update_msg)
497
-
498
- # TEST COMMAND LINE INTERFACE FUNCTION DEFINITION
499
- @cli.command()
500
- @click.option('--unit', '-u', is_flag=True, help='Run only unit tests')
501
- @click.option('--integration', '-i', is_flag=True, help='Run only integration tests')
502
- @click.option('--no-cov', is_flag=True, help='Disable coverage report')
503
- @click.option('--html', is_flag=True, help='Generate HTML coverage report')
504
- @click.option('--module', '-m', help='Test specific module (e.g., autocaps, autolower)')
505
- def test(unit, integration, no_cov, html, module):
506
- """Run test suite with various options."""
507
- # CHECK IF PYTEST IS INSTALLED
508
- try:
509
- import pytest
510
- import pytest_cov
511
- except ImportError:
512
- click.echo(click.style("\n❌ pytest and/or pytest-cov not found. Installing...", fg='yellow', bold=True))
513
- try:
514
- subprocess.run(['pip', 'install', 'pytest', 'pytest-cov'], check=True)
515
- click.echo(click.style("✅ Successfully installed pytest and pytest-cov", fg='green', bold=True))
516
- except subprocess.CalledProcessError as e:
517
- click.echo(click.style(f"\n❌ Failed to install dependencies: {str(e)}", fg='red', bold=True))
518
- sys.exit(1)
519
-
520
- cmd = ['python', '-m', 'pytest', '-v'] # BASE COMMAND
521
-
522
- # COVERAGE OPTIONS
523
- if not no_cov:
524
- cmd.extend(['--cov=autotools'])
525
- if html:
526
- cmd.extend(['--cov-report=html'])
527
- else:
528
- cmd.extend(['--cov-report=term-missing'])
529
-
530
- # TEST SELECTION
531
- test_path = 'autotools'
532
- if module:
533
- if unit and not integration:
534
- cmd.append(f'autotools/{module}/tests/test_{module}_core.py')
535
- elif integration and not unit:
536
- cmd.append(f'autotools/{module}/tests/test_{module}_integration.py')
537
- else:
538
- cmd.append(f'autotools/{module}/tests')
539
-
540
- # SHOW COMMAND BEING RUN
541
- click.echo(click.style("\nRunning tests with command:", fg='blue', bold=True))
542
- click.echo(" ".join(cmd))
543
- click.echo()
544
-
545
- # RUN TESTS
546
- try:
547
- result = subprocess.run(cmd, check=True)
548
- if result.returncode == 0:
549
- click.echo(click.style("\n✅ All tests passed!", fg='green', bold=True))
550
- else:
551
- click.echo(click.style("\n❌ Some tests failed!", fg='red', bold=True))
552
- sys.exit(1)
553
- except subprocess.CalledProcessError as e:
554
- click.echo(click.style(f"\n❌ Tests failed with return code {e.returncode}", fg='red', bold=True))
555
- sys.exit(1)
556
- except Exception as e:
557
- click.echo(click.style(f"\n❌ Error running tests: {str(e)}", fg='red', bold=True))
558
- sys.exit(1)
559
-
560
- # UPDATE CHECK AT THE END
561
- update_msg = check_for_updates()
562
- if update_msg:
563
- click.echo(update_msg)
564
-
565
- # MAIN FUNCTION TO RUN CLI
93
+ # ENTRY POINT
566
94
  if __name__ == '__main__':
567
95
  cli()
@@ -0,0 +1,3 @@
1
+ from .commands import test
2
+
3
+ __all__ = ['test']
@@ -0,0 +1,120 @@
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
+ @click.command()
9
+ @click.option('--unit', '-u', is_flag=True, help='Run only unit tests')
10
+ @click.option('--integration', '-i', is_flag=True, help='Run only integration tests')
11
+ @click.option('--no-cov', is_flag=True, help='Disable coverage report')
12
+ @click.option('--html', is_flag=True, help='Generate HTML coverage report')
13
+ @click.option('--module', '-m', help='Test specific module (e.g., autocaps, autolower)')
14
+ def test(unit, integration, no_cov, html, module):
15
+ """Run test suite with various options."""
16
+ # CHECK IF PYTEST IS INSTALLED
17
+ try:
18
+ import pytest
19
+ import pytest_cov
20
+ except ImportError:
21
+ click.echo(click.style("\n❌ pytest and/or pytest-cov not found. Installing...", fg='yellow', bold=True))
22
+ try:
23
+ subprocess.run(['pip', 'install', 'pytest', 'pytest-cov'], check=True)
24
+ click.echo(click.style("✅ Successfully installed pytest and pytest-cov", fg='green', bold=True))
25
+ except subprocess.CalledProcessError as e:
26
+ click.echo(click.style(f"\n❌ Failed to install dependencies: {str(e)}", fg='red', bold=True))
27
+ sys.exit(1)
28
+
29
+ # BASE COMMAND WITH ENHANCED VERBOSITY
30
+ cmd = [
31
+ 'python', '-m', 'pytest', # PYTHON MODULE AND TEST COMMAND
32
+ '--capture=no', # SHOW PRINT STATEMENTS AND CAPTURED OUTPUT
33
+ '--full-trace', # SHOW FULL TRACEBACK
34
+ '-vv', # VERY VERBOSE OUTPUT
35
+ '--durations=0', # SHOW ALL TEST DURATIONS
36
+ '--showlocals', # SHOW LOCAL VARIABLES IN TRACEBACKS
37
+ '--log-cli-level=DEBUG', # SHOW DEBUG LOGS
38
+ '--tb=long', # LONG TRACEBACK STYLE
39
+ '-s' # SHORTCUT FOR --capture=no
40
+ ]
41
+
42
+ # COVERAGE OPTIONS
43
+ if not no_cov:
44
+ cmd.extend(['--cov=autotools'])
45
+ if html:
46
+ cmd.extend(['--cov-report=html'])
47
+ else:
48
+ cmd.extend(['--cov-report=term-missing'])
49
+
50
+ # TEST SELECTION
51
+ test_path = 'autotools'
52
+ if module:
53
+ if unit and not integration:
54
+ cmd.append(f'autotools/{module}/tests/test_{module}_core.py')
55
+ elif integration and not unit:
56
+ cmd.append(f'autotools/{module}/tests/test_{module}_integration.py')
57
+ else:
58
+ cmd.append(f'autotools/{module}/tests')
59
+
60
+ # SHOW COMMAND BEING RUN
61
+ click.echo(click.style("\nRunning tests with command:", fg='blue', bold=True))
62
+ click.echo(" ".join(cmd))
63
+ click.echo()
64
+
65
+ # RUN TESTS
66
+ try:
67
+ env = dict(os.environ)
68
+ env['PYTHONPATH'] = os.getcwd()
69
+ env['FORCE_COLOR'] = '1' # FORCE COLORS IN OUTPUT
70
+
71
+ process = subprocess.Popen(
72
+ cmd,
73
+ env=env,
74
+ stdout=subprocess.PIPE,
75
+ stderr=subprocess.STDOUT,
76
+ universal_newlines=True,
77
+ bufsize=1
78
+ )
79
+
80
+ # READ AND PROCESS OUTPUT IN REAL-TIME
81
+ while True:
82
+ line = process.stdout.readline()
83
+ if not line and process.poll() is not None:
84
+ break
85
+ if line:
86
+ # CLEAN THE LINE
87
+ line = line.strip()
88
+ if line: # ONLY PROCESS NON-EMPTY LINES
89
+ if '::' in line and 'autotools/' in line:
90
+ # REMOVE PARENT DIRECTORY PATHS
91
+ line = line.split('autotools/')[-1].replace('/tests/', '/')
92
+ # REMOVE MODULE PARENT DIRECTORY
93
+ parts = line.split('/')
94
+ if len(parts) > 1:
95
+ line = parts[-1]
96
+ # REMOVE MULTIPLE SPACES AND DOTS
97
+ line = re.sub(r'\s+', ' ', line)
98
+ line = re.sub(r'\.+', '.', line)
99
+ # REMOVE EMPTY LINES WITH JUST DOTS OR SPACES
100
+ if line.strip('. '):
101
+ sys.stdout.write(line + '\n')
102
+ sys.stdout.flush()
103
+
104
+ process.wait()
105
+ if process.returncode == 0:
106
+ click.echo(click.style("\n✅ All tests passed!", fg='green', bold=True))
107
+ else:
108
+ click.echo(click.style("\n❌ Some tests failed!", fg='red', bold=True))
109
+ sys.exit(1)
110
+ except subprocess.CalledProcessError as e:
111
+ click.echo(click.style(f"\n❌ Tests failed with return code {e.returncode}", fg='red', bold=True))
112
+ sys.exit(1)
113
+ except Exception as e:
114
+ click.echo(click.style(f"\n❌ Error running tests: {str(e)}", fg='red', bold=True))
115
+ sys.exit(1)
116
+
117
+ # UPDATE CHECK AT THE END
118
+ update_msg = check_for_updates()
119
+ if update_msg:
120
+ click.echo(update_msg)