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