Open-AutoTools 0.0.2.post1__py3-none-any.whl → 0.0.3rc1__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.
- Open_AutoTools-0.0.3rc1.dist-info/METADATA +307 -0
- Open_AutoTools-0.0.3rc1.dist-info/RECORD +31 -0
- {Open_AutoTools-0.0.2.post1.dist-info → Open_AutoTools-0.0.3rc1.dist-info}/entry_points.txt +2 -1
- autotools/autocaps/tests/__init__.py +1 -0
- autotools/autocaps/tests/test_autocaps_core.py +45 -0
- autotools/autocaps/tests/test_autocaps_integration.py +46 -0
- autotools/autoip/core.py +133 -40
- autotools/autoip/tests/__init__.py +1 -0
- autotools/autoip/tests/test_autoip_core.py +72 -0
- autotools/autoip/tests/test_autoip_integration.py +92 -0
- autotools/autolower/tests/__init__.py +1 -0
- autotools/autolower/tests/test_autolower_core.py +45 -0
- autotools/autolower/tests/test_autolower_integration.py +46 -0
- autotools/autospell/__init__.py +3 -0
- autotools/autospell/core.py +222 -0
- autotools/autotranslate/core.py +13 -1
- autotools/cli.py +431 -47
- Open_AutoTools-0.0.2.post1.dist-info/METADATA +0 -37
- Open_AutoTools-0.0.2.post1.dist-info/RECORD +0 -20
- {Open_AutoTools-0.0.2.post1.dist-info → Open_AutoTools-0.0.3rc1.dist-info}/LICENSE +0 -0
- {Open_AutoTools-0.0.2.post1.dist-info → Open_AutoTools-0.0.3rc1.dist-info}/WHEEL +0 -0
- {Open_AutoTools-0.0.2.post1.dist-info → Open_AutoTools-0.0.3rc1.dist-info}/top_level.txt +0 -0
- /autotools/{downloader → autodownload}/__init__.py +0 -0
- /autotools/{downloader → autodownload}/core.py +0 -0
- /autotools/{password → autopassword}/__init__.py +0 -0
- /autotools/{password → autopassword}/core.py +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from unittest.mock import patch, Mock
|
|
3
|
+
from autotools.autoip.core import get_public_ip, get_local_ip, get_ip_info
|
|
4
|
+
|
|
5
|
+
# MOCK DATA
|
|
6
|
+
MOCK_IP_INFO = {
|
|
7
|
+
'ip': '8.8.8.8',
|
|
8
|
+
'city': 'Mountain View',
|
|
9
|
+
'region': 'California',
|
|
10
|
+
'country': 'US',
|
|
11
|
+
'loc': '37.4056,-122.0775',
|
|
12
|
+
'org': 'Google LLC',
|
|
13
|
+
'timezone': 'America/Los_Angeles'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
# UNIT TESTS
|
|
17
|
+
|
|
18
|
+
# TEST FOR PUBLIC IP RETRIEVAL
|
|
19
|
+
@patch('requests.get')
|
|
20
|
+
def test_get_public_ip(mock_get):
|
|
21
|
+
"""TEST PUBLIC IP RETRIEVAL"""
|
|
22
|
+
mock_get.return_value.text = "1.2.3.4"
|
|
23
|
+
ip = get_public_ip()
|
|
24
|
+
assert ip == "1.2.3.4"
|
|
25
|
+
mock_get.assert_called_once()
|
|
26
|
+
|
|
27
|
+
# TEST FOR LOCAL IP RETRIEVAL
|
|
28
|
+
@patch('socket.socket')
|
|
29
|
+
@patch('netifaces.gateways')
|
|
30
|
+
@patch('netifaces.ifaddresses')
|
|
31
|
+
def test_get_local_ip(mock_ifaddresses, mock_gateways, mock_socket):
|
|
32
|
+
"""TEST LOCAL IP RETRIEVAL"""
|
|
33
|
+
# MOCK NETIFACES
|
|
34
|
+
mock_gateways.return_value = {'default': {2: ('192.168.1.1', 'eth0')}}
|
|
35
|
+
mock_ifaddresses.return_value = {2: [{'addr': '192.168.1.100'}]}
|
|
36
|
+
|
|
37
|
+
ip = get_local_ip()
|
|
38
|
+
assert ip == "192.168.1.100"
|
|
39
|
+
|
|
40
|
+
# TEST FOR IP INFO RETRIEVAL
|
|
41
|
+
@patch('requests.get')
|
|
42
|
+
def test_get_ip_info(mock_get):
|
|
43
|
+
"""TEST IP INFO RETRIEVAL"""
|
|
44
|
+
mock_get.return_value.json.return_value = MOCK_IP_INFO
|
|
45
|
+
info = get_ip_info()
|
|
46
|
+
assert isinstance(info, dict)
|
|
47
|
+
assert info == MOCK_IP_INFO
|
|
48
|
+
|
|
49
|
+
# TEST FOR IP INFO WITH SPECIFIC IP
|
|
50
|
+
@patch('requests.get')
|
|
51
|
+
def test_get_ip_info_with_ip(mock_get):
|
|
52
|
+
"""TEST IP INFO WITH SPECIFIC IP"""
|
|
53
|
+
mock_get.return_value.json.return_value = MOCK_IP_INFO
|
|
54
|
+
test_ip = "8.8.8.8" # GOOGLE DNS
|
|
55
|
+
info = get_ip_info(test_ip)
|
|
56
|
+
assert isinstance(info, dict)
|
|
57
|
+
assert info['ip'] == test_ip
|
|
58
|
+
assert 'Google' in info['org']
|
|
59
|
+
|
|
60
|
+
# TEST FOR IP INFO WITH INVALID IP
|
|
61
|
+
def test_get_ip_info_invalid():
|
|
62
|
+
"""TEST IP INFO WITH INVALID IP"""
|
|
63
|
+
with pytest.raises(ValueError):
|
|
64
|
+
get_ip_info("invalid.ip.address")
|
|
65
|
+
|
|
66
|
+
# TEST FOR IP INFO WITH PRIVATE IP
|
|
67
|
+
def test_get_ip_info_private():
|
|
68
|
+
"""TEST IP INFO WITH PRIVATE IP"""
|
|
69
|
+
private_ips = ["192.168.1.1", "10.0.0.1", "172.16.0.1"]
|
|
70
|
+
for ip in private_ips:
|
|
71
|
+
with pytest.raises(ValueError):
|
|
72
|
+
get_ip_info(ip)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from unittest.mock import patch, Mock
|
|
3
|
+
from click.testing import CliRunner
|
|
4
|
+
from autotools.cli import autoip
|
|
5
|
+
|
|
6
|
+
# MOCK DATA
|
|
7
|
+
MOCK_IP_INFO = {
|
|
8
|
+
'ip': '8.8.8.8',
|
|
9
|
+
'city': 'Mountain View',
|
|
10
|
+
'region': 'California',
|
|
11
|
+
'country': 'US',
|
|
12
|
+
'loc': '37.4056,-122.0775',
|
|
13
|
+
'org': 'Google LLC',
|
|
14
|
+
'timezone': 'America/Los_Angeles'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
# INTEGRATION TESTS
|
|
18
|
+
|
|
19
|
+
# TEST FOR BASIC CLI FUNCTIONALITY
|
|
20
|
+
@patch('autotools.autoip.core.get_local_ips')
|
|
21
|
+
@patch('autotools.autoip.core.get_public_ips')
|
|
22
|
+
def test_autoip_cli_basic(mock_public_ips, mock_local_ips):
|
|
23
|
+
"""TEST BASIC CLI FUNCTIONALITY"""
|
|
24
|
+
mock_local_ips.return_value = {
|
|
25
|
+
'ipv4': ['192.168.1.100'],
|
|
26
|
+
'ipv6': ['fe80::1']
|
|
27
|
+
}
|
|
28
|
+
mock_public_ips.return_value = {
|
|
29
|
+
'ipv4': '1.2.3.4',
|
|
30
|
+
'ipv6': None
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
runner = CliRunner()
|
|
34
|
+
result = runner.invoke(autoip)
|
|
35
|
+
assert result.exit_code == 0
|
|
36
|
+
assert "192.168.1.100" in result.output
|
|
37
|
+
assert "1.2.3.4" in result.output
|
|
38
|
+
assert "fe80::1" in result.output
|
|
39
|
+
|
|
40
|
+
# TEST FOR CONNECTIVITY TEST
|
|
41
|
+
@patch('autotools.autoip.core.test_connectivity')
|
|
42
|
+
def test_autoip_cli_test(mock_test):
|
|
43
|
+
"""TEST CONNECTIVITY TEST"""
|
|
44
|
+
mock_test.return_value = [
|
|
45
|
+
('Google DNS', True, 20),
|
|
46
|
+
('CloudFlare', False, None)
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
runner = CliRunner()
|
|
50
|
+
result = runner.invoke(autoip, ['--test'])
|
|
51
|
+
assert result.exit_code == 0
|
|
52
|
+
assert "Google DNS" in result.output
|
|
53
|
+
assert "CloudFlare" in result.output
|
|
54
|
+
assert "✓ 20ms" in result.output
|
|
55
|
+
assert "✗ Failed" in result.output
|
|
56
|
+
|
|
57
|
+
# TEST FOR SPEED TEST
|
|
58
|
+
@patch('autotools.autoip.core.run_speedtest')
|
|
59
|
+
def test_autoip_cli_speed(mock_speed):
|
|
60
|
+
"""TEST SPEED TEST"""
|
|
61
|
+
mock_speed.return_value = True
|
|
62
|
+
|
|
63
|
+
runner = CliRunner()
|
|
64
|
+
result = runner.invoke(autoip, ['--speed'])
|
|
65
|
+
assert result.exit_code == 0
|
|
66
|
+
assert "Running speed test" in result.output
|
|
67
|
+
assert "completed successfully" in result.output
|
|
68
|
+
|
|
69
|
+
# TEST FOR LOCATION INFO DISPLAY
|
|
70
|
+
@patch('autotools.autoip.core.get_ip_info')
|
|
71
|
+
def test_autoip_cli_location(mock_get_info):
|
|
72
|
+
"""TEST LOCATION INFO DISPLAY"""
|
|
73
|
+
mock_get_info.return_value = MOCK_IP_INFO
|
|
74
|
+
|
|
75
|
+
runner = CliRunner()
|
|
76
|
+
result = runner.invoke(autoip, ['--location'])
|
|
77
|
+
assert result.exit_code == 0
|
|
78
|
+
assert "Mountain View" in result.output
|
|
79
|
+
assert "California" in result.output
|
|
80
|
+
assert "Google LLC" in result.output
|
|
81
|
+
|
|
82
|
+
# TEST FOR HELP DISPLAY
|
|
83
|
+
def test_autoip_cli_help():
|
|
84
|
+
"""TEST HELP DISPLAY"""
|
|
85
|
+
runner = CliRunner()
|
|
86
|
+
result = runner.invoke(autoip, ['--help'])
|
|
87
|
+
assert result.exit_code == 0
|
|
88
|
+
assert "Usage:" in result.output
|
|
89
|
+
assert "Options:" in result.output
|
|
90
|
+
assert "--test" in result.output
|
|
91
|
+
assert "--speed" in result.output
|
|
92
|
+
assert "--location" in result.output
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# INIT FILE FOR TESTS
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from autotools.autolower.core import autolower_transform
|
|
3
|
+
|
|
4
|
+
# UNIT TESTS
|
|
5
|
+
|
|
6
|
+
# TEST FOR BASIC STRING TRANSFORMATION
|
|
7
|
+
def test_autolower_transform_basic():
|
|
8
|
+
"""TEST BASIC STRING TRANSFORMATION"""
|
|
9
|
+
assert autolower_transform("HELLO") == "hello"
|
|
10
|
+
assert autolower_transform("Hello World") == "hello world"
|
|
11
|
+
assert autolower_transform("123") == "123"
|
|
12
|
+
|
|
13
|
+
# TEST FOR EMPTY STRING
|
|
14
|
+
def test_autolower_transform_empty():
|
|
15
|
+
"""TEST EMPTY STRING"""
|
|
16
|
+
assert autolower_transform("") == ""
|
|
17
|
+
|
|
18
|
+
# TEST FOR SPECIAL CHARACTERS
|
|
19
|
+
def test_autolower_transform_special_chars():
|
|
20
|
+
"""TEST STRING WITH SPECIAL CHARACTERS"""
|
|
21
|
+
assert autolower_transform("HELLO@WORLD.COM") == "hello@world.com"
|
|
22
|
+
assert autolower_transform("HELLO-WORLD!") == "hello-world!"
|
|
23
|
+
|
|
24
|
+
# TEST FOR MIXED CASE STRING
|
|
25
|
+
def test_autolower_transform_mixed_case():
|
|
26
|
+
"""TEST MIXED CASE STRING"""
|
|
27
|
+
assert autolower_transform("HeLLo WoRLD") == "hello world"
|
|
28
|
+
|
|
29
|
+
# TEST FOR WHITESPACE
|
|
30
|
+
def test_autolower_transform_whitespace():
|
|
31
|
+
"""TEST STRING WITH WHITESPACE"""
|
|
32
|
+
assert autolower_transform(" HELLO WORLD ") == " hello world "
|
|
33
|
+
assert autolower_transform("\tHELLO\nWORLD") == "\thello\nworld"
|
|
34
|
+
|
|
35
|
+
# TEST FOR NUMBERS
|
|
36
|
+
def test_autolower_transform_numbers():
|
|
37
|
+
"""TEST STRING WITH NUMBERS"""
|
|
38
|
+
assert autolower_transform("HELLO123WORLD") == "hello123world"
|
|
39
|
+
assert autolower_transform("123HELLO456WORLD789") == "123hello456world789"
|
|
40
|
+
|
|
41
|
+
# TEST FOR UNICODE CHARACTERS
|
|
42
|
+
def test_autolower_transform_unicode():
|
|
43
|
+
"""TEST UNICODE CHARACTERS"""
|
|
44
|
+
assert autolower_transform("HÉLLO WÖRLD") == "héllo wörld"
|
|
45
|
+
assert autolower_transform("こんにちは") == "こんにちは" # JAPANESE SHOULD REMAIN UNCHANGED
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from click.testing import CliRunner
|
|
3
|
+
from autotools.cli import autolower
|
|
4
|
+
|
|
5
|
+
# INTEGRATION TESTS
|
|
6
|
+
|
|
7
|
+
# TEST FOR BASIC CLI FUNCTIONALITY
|
|
8
|
+
def test_autolower_cli_basic():
|
|
9
|
+
"""TEST BASIC CLI FUNCTIONALITY"""
|
|
10
|
+
runner = CliRunner()
|
|
11
|
+
result = runner.invoke(autolower, ["HELLO WORLD"])
|
|
12
|
+
assert result.exit_code == 0
|
|
13
|
+
assert "hello world" in result.output
|
|
14
|
+
|
|
15
|
+
# TEST FOR EMPTY INPUT
|
|
16
|
+
def test_autolower_cli_empty():
|
|
17
|
+
"""TEST CLI WITH EMPTY INPUT"""
|
|
18
|
+
runner = CliRunner()
|
|
19
|
+
result = runner.invoke(autolower, [""])
|
|
20
|
+
assert result.exit_code == 0
|
|
21
|
+
assert "" in result.output
|
|
22
|
+
|
|
23
|
+
# TEST FOR SPECIAL CHARACTERS
|
|
24
|
+
def test_autolower_cli_special_chars():
|
|
25
|
+
"""TEST CLI WITH SPECIAL CHARACTERS"""
|
|
26
|
+
runner = CliRunner()
|
|
27
|
+
result = runner.invoke(autolower, ["HELLO@WORLD.COM"])
|
|
28
|
+
assert result.exit_code == 0
|
|
29
|
+
assert "hello@world.com" in result.output
|
|
30
|
+
|
|
31
|
+
# TEST FOR UNICODE CHARACTERS
|
|
32
|
+
def test_autolower_cli_unicode():
|
|
33
|
+
"""TEST CLI WITH UNICODE CHARACTERS"""
|
|
34
|
+
runner = CliRunner()
|
|
35
|
+
result = runner.invoke(autolower, ["HÉLLO WÖRLD"])
|
|
36
|
+
assert result.exit_code == 0
|
|
37
|
+
assert "héllo wörld" in result.output
|
|
38
|
+
|
|
39
|
+
# TEST FOR MULTIPLE ARGUMENTS
|
|
40
|
+
def test_autolower_cli_multiple_args():
|
|
41
|
+
"""TEST CLI WITH MULTIPLE ARGUMENTS"""
|
|
42
|
+
runner = CliRunner()
|
|
43
|
+
result = runner.invoke(autolower, ["HELLO", "WORLD"])
|
|
44
|
+
assert result.exit_code == 0
|
|
45
|
+
# SHOULD ONLY PROCESS FIRST ARGUMENT
|
|
46
|
+
assert "hello" in result.output
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import language_tool_python
|
|
2
|
+
import spacy
|
|
3
|
+
from typing import List, Dict, Optional
|
|
4
|
+
import pyperclip
|
|
5
|
+
import requests
|
|
6
|
+
from langdetect import detect, detect_langs
|
|
7
|
+
|
|
8
|
+
class SpellChecker:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
# INITIALIZE LANGUAGE TOOL
|
|
11
|
+
self.tool = language_tool_python.LanguageTool('auto')
|
|
12
|
+
|
|
13
|
+
# CACHE FOR SPACY MODELS
|
|
14
|
+
self.nlp_models = {}
|
|
15
|
+
|
|
16
|
+
# LOAD SPACY MODEL FOR GIVEN LANGUAGE
|
|
17
|
+
def _load_spacy_model(self, lang_code: str) -> Optional[spacy.language.Language]:
|
|
18
|
+
"""LOAD SPACY MODEL FOR GIVEN LANGUAGE"""
|
|
19
|
+
try:
|
|
20
|
+
if lang_code not in self.nlp_models:
|
|
21
|
+
# GET ALL INSTALLED MODELS FOR THIS LANGUAGE
|
|
22
|
+
available_models = [
|
|
23
|
+
model for model in spacy.util.get_installed_models()
|
|
24
|
+
if model.startswith(lang_code)
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
if not available_models:
|
|
28
|
+
# TRY TO CREATE A BLANK MODEL IF NO TRAINED MODELS
|
|
29
|
+
self.nlp_models[lang_code] = spacy.blank(lang_code)
|
|
30
|
+
else:
|
|
31
|
+
# USE MOST COMPREHENSIVE MODEL (USUALLY ENDS WITH 'lg' OR 'trf')
|
|
32
|
+
preferred_model = None
|
|
33
|
+
for suffix in ['trf', 'lg', 'md', 'sm']:
|
|
34
|
+
for model in available_models:
|
|
35
|
+
if model.endswith(suffix):
|
|
36
|
+
preferred_model = model
|
|
37
|
+
break
|
|
38
|
+
if preferred_model:
|
|
39
|
+
break
|
|
40
|
+
|
|
41
|
+
# IF NO PREFERRED MODEL, USE FIRST AVAILABLE MODEL
|
|
42
|
+
if not preferred_model:
|
|
43
|
+
preferred_model = available_models[0]
|
|
44
|
+
|
|
45
|
+
self.nlp_models[lang_code] = spacy.load(preferred_model) # LOAD PREFERRED MODEL
|
|
46
|
+
|
|
47
|
+
return self.nlp_models.get(lang_code) # RETURN LOADED MODEL
|
|
48
|
+
except:
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
# CHECK TEXT FOR SPELLING AND GRAMMAR ERRORS
|
|
52
|
+
def check_text(self, text: str, lang: str = 'auto') -> Dict:
|
|
53
|
+
"""CHECK TEXT FOR SPELLING AND GRAMMAR ERRORS
|
|
54
|
+
|
|
55
|
+
ARGS:
|
|
56
|
+
text: Text to check
|
|
57
|
+
lang: Language code (auto for automatic detection)
|
|
58
|
+
|
|
59
|
+
RETURNS:
|
|
60
|
+
Dict with corrections and statistics
|
|
61
|
+
"""
|
|
62
|
+
# DETECT LANGUAGE CONFIDENCE
|
|
63
|
+
if lang == 'auto':
|
|
64
|
+
try:
|
|
65
|
+
lang_scores = detect_langs(text)
|
|
66
|
+
lang = lang_scores[0].lang
|
|
67
|
+
confidence = lang_scores[0].prob
|
|
68
|
+
except:
|
|
69
|
+
confidence = 0
|
|
70
|
+
else:
|
|
71
|
+
confidence = 1.0
|
|
72
|
+
|
|
73
|
+
# SET LANGUAGE
|
|
74
|
+
if lang != 'auto':
|
|
75
|
+
self.tool.language = lang
|
|
76
|
+
|
|
77
|
+
# GET MATCHES
|
|
78
|
+
matches = self.tool.check(text)
|
|
79
|
+
|
|
80
|
+
# PREPARE CORRECTIONS WITH SEVERITY LEVELS
|
|
81
|
+
corrections = []
|
|
82
|
+
for match in matches:
|
|
83
|
+
severity = self._get_error_severity(match)
|
|
84
|
+
correction = {
|
|
85
|
+
'message': match.message,
|
|
86
|
+
'context': match.context,
|
|
87
|
+
'offset': match.offset,
|
|
88
|
+
'length': match.errorLength,
|
|
89
|
+
'category': match.category,
|
|
90
|
+
'rule_id': match.ruleId,
|
|
91
|
+
'replacements': match.replacements,
|
|
92
|
+
'severity': severity
|
|
93
|
+
}
|
|
94
|
+
corrections.append(correction)
|
|
95
|
+
|
|
96
|
+
# GET DETAILED STATISTICS
|
|
97
|
+
stats = self._get_detailed_stats(corrections)
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
'corrections': corrections,
|
|
101
|
+
'statistics': stats,
|
|
102
|
+
'language': {
|
|
103
|
+
'code': lang,
|
|
104
|
+
'name': lang.upper(),
|
|
105
|
+
'confidence': confidence
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# DETERMINE ERROR SEVERITY LEVEL
|
|
110
|
+
def _get_error_severity(self, match) -> str:
|
|
111
|
+
"""DETERMINE ERROR SEVERITY LEVEL"""
|
|
112
|
+
if 'TYPO' in match.ruleId or 'SPELLING' in match.ruleId:
|
|
113
|
+
return 'high'
|
|
114
|
+
elif 'GRAMMAR' in match.ruleId:
|
|
115
|
+
return 'medium'
|
|
116
|
+
else:
|
|
117
|
+
return 'low'
|
|
118
|
+
|
|
119
|
+
# GET DETAILED ERROR STATISTICS
|
|
120
|
+
def _get_detailed_stats(self, corrections: List[Dict]) -> Dict:
|
|
121
|
+
"""GET DETAILED ERROR STATISTICS"""
|
|
122
|
+
stats = {
|
|
123
|
+
'total_errors': len(corrections),
|
|
124
|
+
'categories': {},
|
|
125
|
+
'severity': {
|
|
126
|
+
'high': 0,
|
|
127
|
+
'medium': 0,
|
|
128
|
+
'low': 0
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
# COUNT ERRORS BY CATEGORY AND SEVERITY
|
|
133
|
+
for corr in corrections:
|
|
134
|
+
# COUNT BY CATEGORY
|
|
135
|
+
cat = corr['category']
|
|
136
|
+
stats['categories'][cat] = stats['categories'].get(cat, 0) + 1
|
|
137
|
+
|
|
138
|
+
# COUNT BY SEVERITY
|
|
139
|
+
stats['severity'][corr['severity']] += 1
|
|
140
|
+
|
|
141
|
+
return stats
|
|
142
|
+
|
|
143
|
+
# FIX TEXT AUTOMATICALLY
|
|
144
|
+
def fix_text(self, text: str, lang: str = 'auto', copy_to_clipboard: bool = False,
|
|
145
|
+
ignore: list = None, interactive: bool = False) -> str:
|
|
146
|
+
"""FIX TEXT AUTOMATICALLY"""
|
|
147
|
+
if lang != 'auto':
|
|
148
|
+
self.tool.language = lang
|
|
149
|
+
|
|
150
|
+
if interactive:
|
|
151
|
+
# GET ALL CORRECTIONS
|
|
152
|
+
matches = self.tool.check(text)
|
|
153
|
+
corrected = text
|
|
154
|
+
|
|
155
|
+
# ASK FOR EACH CORRECTION
|
|
156
|
+
for match in matches:
|
|
157
|
+
if ignore and any(t in match.ruleId.lower() for t in ignore):
|
|
158
|
+
continue
|
|
159
|
+
|
|
160
|
+
print(f"\nError: {match.message}")
|
|
161
|
+
print(f"Context: {match.context}")
|
|
162
|
+
if match.replacements:
|
|
163
|
+
print("Suggestions:")
|
|
164
|
+
for i, sugg in enumerate(match.replacements[:3], 1):
|
|
165
|
+
print(f"{i}. {sugg}")
|
|
166
|
+
|
|
167
|
+
# ASK FOR EACH CORRECTION
|
|
168
|
+
choice = input("\nApply correction? (1-3/n): ").lower()
|
|
169
|
+
if choice.isdigit() and 1 <= int(choice) <= len(match.replacements[:3]):
|
|
170
|
+
replacement = match.replacements[int(choice)-1]
|
|
171
|
+
corrected = corrected[:match.offset] + replacement + corrected[match.offset + match.errorLength:]
|
|
172
|
+
else:
|
|
173
|
+
# NORMAL AUTO-FIX
|
|
174
|
+
corrected = self.tool.correct(text)
|
|
175
|
+
|
|
176
|
+
# COPY TO CLIPBOARD IF REQUESTED
|
|
177
|
+
if copy_to_clipboard:
|
|
178
|
+
pyperclip.copy(corrected)
|
|
179
|
+
|
|
180
|
+
return corrected
|
|
181
|
+
|
|
182
|
+
# GET LIST OF SUPPORTED LANGUAGES
|
|
183
|
+
def get_supported_languages(self) -> List[Dict]:
|
|
184
|
+
"""GET LIST OF SUPPORTED LANGUAGES"""
|
|
185
|
+
try:
|
|
186
|
+
# GET LANGUAGES FROM LANGUAGE TOOL PUBLIC API
|
|
187
|
+
response = requests.get('https://api.languagetool.org/v2/languages')
|
|
188
|
+
languages = response.json()
|
|
189
|
+
|
|
190
|
+
# FORMAT LANGUAGES INTO REQUIRED STRUCTURE
|
|
191
|
+
formatted_langs = []
|
|
192
|
+
seen_codes = set()
|
|
193
|
+
|
|
194
|
+
# FORMAT LANGUAGES INTO REQUIRED STRUCTURE
|
|
195
|
+
for lang in languages:
|
|
196
|
+
code = lang['longCode'].split('-')[0]
|
|
197
|
+
|
|
198
|
+
# SKIP DUPLICATES
|
|
199
|
+
if code in seen_codes:
|
|
200
|
+
continue
|
|
201
|
+
|
|
202
|
+
# TEST IF LANGUAGE IS ACTUALLY SUPPORTED BY LOCAL TOOL
|
|
203
|
+
try:
|
|
204
|
+
self.tool.language = code
|
|
205
|
+
formatted_langs.append({
|
|
206
|
+
'code': code,
|
|
207
|
+
'name': lang['name'],
|
|
208
|
+
'variants': [v['name'] for v in lang.get('variants', [])]
|
|
209
|
+
})
|
|
210
|
+
seen_codes.add(code)
|
|
211
|
+
except:
|
|
212
|
+
continue
|
|
213
|
+
|
|
214
|
+
return formatted_langs
|
|
215
|
+
|
|
216
|
+
except Exception as e:
|
|
217
|
+
# IF API FAILS, GET LANGUAGES FROM LOCAL TOOL
|
|
218
|
+
try:
|
|
219
|
+
current_lang = self.tool.language
|
|
220
|
+
return [{'code': current_lang, 'name': current_lang.upper(), 'variants': []}]
|
|
221
|
+
except:
|
|
222
|
+
return []
|
autotools/autotranslate/core.py
CHANGED
|
@@ -10,7 +10,7 @@ def get_supported_languages() -> dict:
|
|
|
10
10
|
return dict(sorted(langs.items(), key=lambda x: x[1].lower()))
|
|
11
11
|
|
|
12
12
|
def translate_text(text: str, to_lang: str = 'en', from_lang: str = None,
|
|
13
|
-
copy: bool = False, detect_lang: bool = False) -> str:
|
|
13
|
+
copy: bool = False, detect_lang: bool = False, output: str = None) -> str:
|
|
14
14
|
"""TRANSLATE TEXT TO SPECIFIED LANGUAGE
|
|
15
15
|
|
|
16
16
|
ARGS:
|
|
@@ -19,6 +19,7 @@ def translate_text(text: str, to_lang: str = 'en', from_lang: str = None,
|
|
|
19
19
|
from_lang (str): SOURCE LANGUAGE CODE (DEFAULT: AUTO-DETECT)
|
|
20
20
|
copy (bool): COPY RESULT TO CLIPBOARD
|
|
21
21
|
detect_lang (bool): SHOW DETECTED SOURCE LANGUAGE
|
|
22
|
+
output (str): PATH TO SAVE TRANSLATION TO FILE
|
|
22
23
|
|
|
23
24
|
RETURNS:
|
|
24
25
|
str: TRANSLATED TEXT
|
|
@@ -34,6 +35,17 @@ def translate_text(text: str, to_lang: str = 'en', from_lang: str = None,
|
|
|
34
35
|
if copy:
|
|
35
36
|
pyperclip.copy(result)
|
|
36
37
|
|
|
38
|
+
# SAVE TO FILE IF OUTPUT PATH PROVIDED
|
|
39
|
+
if output:
|
|
40
|
+
try:
|
|
41
|
+
with open(output, 'w', encoding='utf-8') as f:
|
|
42
|
+
if detect_lang:
|
|
43
|
+
f.write(f"[Detected: {source_lang}] {result}")
|
|
44
|
+
else:
|
|
45
|
+
f.write(result)
|
|
46
|
+
except Exception as e:
|
|
47
|
+
print(f"\nError saving to file: {str(e)}")
|
|
48
|
+
|
|
37
49
|
# RETURN RESULT WITH DETECTED LANGUAGE IF REQUESTED
|
|
38
50
|
if detect_lang:
|
|
39
51
|
return f"[Detected: {source_lang}] {result}"
|