Open-AutoTools 0.0.4rc1__py3-none-any.whl → 0.0.5__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 +21 -0
- autotools/autocolor/__init__.py +0 -0
- autotools/autocolor/commands.py +60 -0
- autotools/autocolor/core.py +99 -0
- autotools/autoconvert/__init__.py +0 -0
- autotools/autoconvert/commands.py +79 -0
- autotools/autoconvert/conversion/__init__.py +0 -0
- autotools/autoconvert/conversion/convert_audio.py +24 -0
- autotools/autoconvert/conversion/convert_image.py +29 -0
- autotools/autoconvert/conversion/convert_text.py +101 -0
- autotools/autoconvert/conversion/convert_video.py +25 -0
- autotools/autoconvert/core.py +54 -0
- autotools/autoip/commands.py +39 -1
- autotools/autoip/core.py +100 -43
- autotools/autolower/commands.py +21 -0
- autotools/autonote/__init__.py +0 -0
- autotools/autonote/commands.py +70 -0
- autotools/autonote/core.py +106 -0
- autotools/autopassword/commands.py +39 -1
- autotools/autotest/commands.py +43 -12
- autotools/autotodo/__init__.py +87 -0
- autotools/autotodo/commands.py +115 -0
- autotools/autotodo/core.py +567 -0
- autotools/autounit/__init__.py +0 -0
- autotools/autounit/commands.py +55 -0
- autotools/autounit/core.py +36 -0
- autotools/autozip/__init__.py +0 -0
- autotools/autozip/commands.py +88 -0
- autotools/autozip/core.py +107 -0
- autotools/cli.py +66 -62
- autotools/utils/commands.py +141 -10
- autotools/utils/performance.py +67 -35
- autotools/utils/requirements.py +21 -0
- autotools/utils/smoke.py +246 -0
- autotools/utils/text.py +73 -0
- open_autotools-0.0.5.dist-info/METADATA +100 -0
- open_autotools-0.0.5.dist-info/RECORD +54 -0
- {open_autotools-0.0.4rc1.dist-info → open_autotools-0.0.5.dist-info}/WHEEL +1 -1
- open_autotools-0.0.5.dist-info/entry_points.txt +12 -0
- open_autotools-0.0.4rc1.dist-info/METADATA +0 -103
- open_autotools-0.0.4rc1.dist-info/RECORD +0 -28
- open_autotools-0.0.4rc1.dist-info/entry_points.txt +0 -6
- {open_autotools-0.0.4rc1.dist-info → open_autotools-0.0.5.dist-info}/licenses/LICENSE +0 -0
- {open_autotools-0.0.4rc1.dist-info → open_autotools-0.0.5.dist-info}/top_level.txt +0 -0
autotools/autocaps/commands.py
CHANGED
|
@@ -3,10 +3,31 @@ from .core import autocaps_transform
|
|
|
3
3
|
from ..utils.loading import LoadingAnimation
|
|
4
4
|
from ..utils.updates import check_for_updates
|
|
5
5
|
|
|
6
|
+
# TOOL CATEGORY (USED BY 'autotools smoke')
|
|
7
|
+
TOOL_CATEGORY = 'Text'
|
|
8
|
+
|
|
9
|
+
# SMOKE TEST CASES (USED BY 'autotools smoke')
|
|
10
|
+
SMOKE_TESTS = [
|
|
11
|
+
{'name': 'basic', 'args': ['test', 'with', 'multiple', 'words']},
|
|
12
|
+
{'name': 'special', 'args': ['special', 'chars:', '!@#$%^&*()']},
|
|
13
|
+
{'name': 'mixed', 'args': ['123', 'mixed', 'WITH', 'lowercase', '456']},
|
|
14
|
+
{'name': 'unicode', 'args': ['áccênts', 'ànd', 'émojis', '🚀', '⭐']},
|
|
15
|
+
]
|
|
16
|
+
|
|
6
17
|
# CLI COMMAND TO TRANSFORM TEXT TO UPPERCASE
|
|
7
18
|
@click.command()
|
|
8
19
|
@click.argument('text', nargs=-1)
|
|
9
20
|
def autocaps(text):
|
|
21
|
+
"""
|
|
22
|
+
TRANSFORMS TEXT TO UPPERCASE.
|
|
23
|
+
|
|
24
|
+
\b
|
|
25
|
+
EXAMPLES:
|
|
26
|
+
autocaps hello world
|
|
27
|
+
autocaps "this is a test"
|
|
28
|
+
echo "text" | autocaps
|
|
29
|
+
"""
|
|
30
|
+
|
|
10
31
|
with LoadingAnimation(): result = autocaps_transform(" ".join(text))
|
|
11
32
|
click.echo(result)
|
|
12
33
|
update_msg = check_for_updates()
|
|
File without changes
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from .core import autocolor_convert
|
|
3
|
+
from ..utils.loading import LoadingAnimation
|
|
4
|
+
from ..utils.updates import check_for_updates
|
|
5
|
+
|
|
6
|
+
# TOOL CATEGORY (USED BY 'autotools smoke')
|
|
7
|
+
TOOL_CATEGORY = 'Color'
|
|
8
|
+
|
|
9
|
+
# SMOKE TEST CASES (USED BY 'autotools smoke')
|
|
10
|
+
SMOKE_TESTS = [
|
|
11
|
+
{'name': 'hex-default', 'args': ['#FF5733']},
|
|
12
|
+
{'name': 'hex-rgb', 'args': ['#FF5733', '--format', 'rgb']},
|
|
13
|
+
{'name': 'rgb-hsl', 'args': ['rgb(255,87,51)', '--format', 'hsl']},
|
|
14
|
+
{'name': 'hsl-rgba', 'args': ['hsl(9,100%,60%)', '--format', 'rgba']},
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
# CLI COMMAND TO CONVERT COLOR CODES BETWEEN FORMATS
|
|
18
|
+
@click.command()
|
|
19
|
+
@click.argument('color', nargs=-1)
|
|
20
|
+
@click.option('--format', '-f', 'output_format',
|
|
21
|
+
type=click.Choice(['hex', 'rgb', 'rgba', 'hsl', 'hsla'], case_sensitive=False),
|
|
22
|
+
default='hex', help='OUTPUT FORMAT (hex, rgb, rgba, hsl, hsla)')
|
|
23
|
+
def autocolor(color, output_format):
|
|
24
|
+
"""
|
|
25
|
+
CONVERTS COLOR CODES BETWEEN DIFFERENT FORMATS.
|
|
26
|
+
|
|
27
|
+
\b
|
|
28
|
+
SUPPORTS INPUT FORMATS:
|
|
29
|
+
- HEX: #RRGGBB, #RRGGBBAA, #RGB
|
|
30
|
+
- RGB: rgb(r, g, b)
|
|
31
|
+
- RGBA: rgba(r, g, b, a)
|
|
32
|
+
- HSL: hsl(h, s%, l%)
|
|
33
|
+
- HSLA: hsla(h, s%, l%, a)
|
|
34
|
+
|
|
35
|
+
\b
|
|
36
|
+
EXAMPLES:
|
|
37
|
+
autocolor "#FF5733"
|
|
38
|
+
autocolor "rgb(255, 87, 51)" --format hsl
|
|
39
|
+
autocolor "hsl(9, 100%, 60%)" --format hex
|
|
40
|
+
autocolor "#F73" --format rgba
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
if not color:
|
|
44
|
+
click.echo(click.style("ERROR: COLOR ARGUMENT IS REQUIRED", fg='red'), err=True)
|
|
45
|
+
click.echo(click.get_current_context().get_help())
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
color_input = " ".join(color)
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
with LoadingAnimation(): result = autocolor_convert(color_input, output_format)
|
|
52
|
+
click.echo(result)
|
|
53
|
+
update_msg = check_for_updates()
|
|
54
|
+
if update_msg: click.echo(update_msg)
|
|
55
|
+
except ValueError as e:
|
|
56
|
+
click.echo(click.style(f"ERROR: {str(e)}", fg='red'), err=True)
|
|
57
|
+
raise click.Abort()
|
|
58
|
+
except Exception as e:
|
|
59
|
+
click.echo(click.style(f"UNEXPECTED ERROR: {str(e)}", fg='red'), err=True)
|
|
60
|
+
raise click.Abort()
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import colorsys
|
|
3
|
+
import pyperclip
|
|
4
|
+
|
|
5
|
+
# CONVERTS HEX COLOR TO RGB TUPLE
|
|
6
|
+
def _hex_to_rgb(hex_color):
|
|
7
|
+
hex_color = hex_color.lstrip('#')
|
|
8
|
+
if len(hex_color) == 3:
|
|
9
|
+
hex_color = ''.join([c * 2 for c in hex_color])
|
|
10
|
+
if len(hex_color) == 6:
|
|
11
|
+
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
|
|
12
|
+
elif len(hex_color) == 8:
|
|
13
|
+
rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
|
|
14
|
+
alpha = int(hex_color[6:8], 16) / 255.0
|
|
15
|
+
return (*rgb, alpha)
|
|
16
|
+
raise ValueError(f"INVALID HEX COLOR: {hex_color}")
|
|
17
|
+
|
|
18
|
+
# CONVERTS RGB TUPLE TO HEX
|
|
19
|
+
def _rgb_to_hex(r, g, b, alpha=None):
|
|
20
|
+
hex_color = f"#{r:02x}{g:02x}{b:02x}"
|
|
21
|
+
if alpha is not None: hex_color += f"{int(alpha * 255):02x}"
|
|
22
|
+
return hex_color.upper()
|
|
23
|
+
|
|
24
|
+
# PARSES RGB/RGBA STRING
|
|
25
|
+
def _parse_rgb(rgb_str):
|
|
26
|
+
match = re.match(r'rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)', rgb_str.lower())
|
|
27
|
+
if not match:
|
|
28
|
+
raise ValueError(f"INVALID RGB/RGBA FORMAT: {rgb_str}")
|
|
29
|
+
r, g, b = int(match.group(1)), int(match.group(2)), int(match.group(3))
|
|
30
|
+
alpha = float(match.group(4)) if match.group(4) else None
|
|
31
|
+
if alpha is not None and alpha > 1:
|
|
32
|
+
alpha = alpha / 255.0
|
|
33
|
+
return (r, g, b, alpha) if alpha is not None else (r, g, b)
|
|
34
|
+
|
|
35
|
+
# PARSES HSL/HSLA STRING
|
|
36
|
+
def _parse_hsl(hsl_str):
|
|
37
|
+
match = re.match(r'hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*([\d.]+))?\)', hsl_str.lower())
|
|
38
|
+
if not match:
|
|
39
|
+
raise ValueError(f"INVALID HSL/HSLA FORMAT: {hsl_str}")
|
|
40
|
+
h = int(match.group(1)) / 360.0
|
|
41
|
+
s = int(match.group(2)) / 100.0
|
|
42
|
+
l = int(match.group(3)) / 100.0
|
|
43
|
+
alpha = float(match.group(4)) if match.group(4) else None
|
|
44
|
+
return (h, s, l, alpha) if alpha is not None else (h, s, l)
|
|
45
|
+
|
|
46
|
+
# PARSES INPUT COLOR AND RETURNS RGB VALUES WITH ALPHA
|
|
47
|
+
def _parse_color_input(color_input):
|
|
48
|
+
if color_input.startswith('#'):
|
|
49
|
+
rgb_result = _hex_to_rgb(color_input)
|
|
50
|
+
return rgb_result if len(rgb_result) == 4 else (*rgb_result, None)
|
|
51
|
+
elif color_input.startswith('rgb'):
|
|
52
|
+
rgb_result = _parse_rgb(color_input)
|
|
53
|
+
return rgb_result if len(rgb_result) == 4 else (*rgb_result, None)
|
|
54
|
+
elif color_input.startswith('hsl'):
|
|
55
|
+
hsl_result = _parse_hsl(color_input)
|
|
56
|
+
if len(hsl_result) == 4:
|
|
57
|
+
h, s, l, alpha = hsl_result
|
|
58
|
+
else:
|
|
59
|
+
h, s, l = hsl_result
|
|
60
|
+
alpha = None
|
|
61
|
+
r, g, b = [int(c * 255) for c in colorsys.hls_to_rgb(h, l, s)]
|
|
62
|
+
return (r, g, b, alpha)
|
|
63
|
+
raise ValueError(f"UNSUPPORTED COLOR FORMAT: {color_input}")
|
|
64
|
+
|
|
65
|
+
# CONVERTS RGB TO HSL VALUES
|
|
66
|
+
def _rgb_to_hsl_values(r, g, b):
|
|
67
|
+
h, l, s = colorsys.rgb_to_hls(r / 255.0, g / 255.0, b / 255.0)
|
|
68
|
+
return (int(h * 360), int(s * 100), int(l * 100))
|
|
69
|
+
|
|
70
|
+
# FORMATS OUTPUT COLOR BASED ON FORMAT TYPE
|
|
71
|
+
def _format_output(r, g, b, alpha, output_format):
|
|
72
|
+
if output_format == 'hex':
|
|
73
|
+
return _rgb_to_hex(r, g, b, alpha)
|
|
74
|
+
elif output_format == 'rgb':
|
|
75
|
+
return f"rgb({r}, {g}, {b})"
|
|
76
|
+
elif output_format == 'rgba':
|
|
77
|
+
alpha = alpha if alpha is not None else 1.0
|
|
78
|
+
return f"rgba({r}, {g}, {b}, {alpha:.2f})"
|
|
79
|
+
elif output_format == 'hsl':
|
|
80
|
+
h, s, l = _rgb_to_hsl_values(r, g, b)
|
|
81
|
+
return f"hsl({h}, {s}%, {l}%)"
|
|
82
|
+
elif output_format == 'hsla':
|
|
83
|
+
alpha = alpha if alpha is not None else 1.0
|
|
84
|
+
h, s, l = _rgb_to_hsl_values(r, g, b)
|
|
85
|
+
return f"hsla({h}, {s}%, {l}%, {alpha:.2f})"
|
|
86
|
+
raise ValueError(f"UNSUPPORTED OUTPUT FORMAT: {output_format}")
|
|
87
|
+
|
|
88
|
+
# CONVERTS COLOR CODE TO DIFFERENT FORMATS
|
|
89
|
+
def autocolor_convert(color_input, output_format='hex'):
|
|
90
|
+
color_input = color_input.strip()
|
|
91
|
+
output_format = output_format.lower()
|
|
92
|
+
|
|
93
|
+
r, g, b, alpha = _parse_color_input(color_input)
|
|
94
|
+
result = _format_output(r, g, b, alpha, output_format)
|
|
95
|
+
|
|
96
|
+
try: pyperclip.copy(result)
|
|
97
|
+
except pyperclip.PyperclipException: pass
|
|
98
|
+
|
|
99
|
+
return result
|
|
File without changes
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import click
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from .core import convert_file, detect_file_type
|
|
5
|
+
from ..utils.loading import LoadingAnimation
|
|
6
|
+
from ..utils.updates import check_for_updates
|
|
7
|
+
|
|
8
|
+
# TOOL CATEGORY (USED BY 'autotools smoke')
|
|
9
|
+
TOOL_CATEGORY = 'Files'
|
|
10
|
+
|
|
11
|
+
# SMOKE TEST CASES (USED BY 'autotools smoke')
|
|
12
|
+
SMOKE_TESTS = [
|
|
13
|
+
{'name': 'md-json', 'args': ['README.md', '/tmp/autoconvert-smoke.json']},
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
# CLI COMMAND TO CONVERT FILES BETWEEN DIFFERENT FORMATS
|
|
17
|
+
@click.command()
|
|
18
|
+
@click.argument('input_file', type=click.Path())
|
|
19
|
+
@click.argument('output_file', type=click.Path())
|
|
20
|
+
@click.option('--input-type', '-i', help='FORCE INPUT FILE TYPE (text/image/audio/video)')
|
|
21
|
+
@click.option('--output-type', '-o', help='FORCE OUTPUT FILE TYPE (text/image/audio/video)')
|
|
22
|
+
@click.option('--format', '-f', help='OUTPUT FORMAT (OVERRIDES OUTPUT FILE EXTENSION)')
|
|
23
|
+
def autoconvert(input_file, output_file, input_type, output_type, format):
|
|
24
|
+
"""
|
|
25
|
+
CONVERTS FILES BETWEEN DIFFERENT FORMATS.
|
|
26
|
+
|
|
27
|
+
\b
|
|
28
|
+
SUPPORTS:
|
|
29
|
+
- TEXT: txt, md, markdown, json, xml, html, htm, csv
|
|
30
|
+
- IMAGES: jpg, jpeg, png, gif, webp, bmp, tiff, tif, ico, svg
|
|
31
|
+
- AUDIO: mp3, wav, ogg, flac, aac, m4a, wma, opus
|
|
32
|
+
- VIDEO: mp4, avi, mov, mkv, wmv, flv, webm, m4v
|
|
33
|
+
|
|
34
|
+
\b
|
|
35
|
+
EXAMPLES:
|
|
36
|
+
autoconvert input.txt output.json
|
|
37
|
+
autoconvert image.jpg image.png
|
|
38
|
+
autoconvert audio.mp3 audio.wav
|
|
39
|
+
autoconvert video.mp4 video.avi
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
# TRY TO CONVERT FILE
|
|
43
|
+
# - CHECK IF INPUT FILE EXISTS, OTHERWISE FAIL
|
|
44
|
+
# - HANDLE --FORMAT: UPDATE OUTPUT NAME/EXT, DETECT OUTPUT TYPE IF UNSET
|
|
45
|
+
# - MAKE OUTPUT DIRECTORY IF IT DOESN'T EXIST, RUN CONVERT WITH LOADING SPINNER
|
|
46
|
+
# - SHOW RESULT, PRINT UPDATE NOTICE
|
|
47
|
+
try:
|
|
48
|
+
if not os.path.exists(input_file):
|
|
49
|
+
raise FileNotFoundError(f"INPUT FILE NOT FOUND: {input_file}")
|
|
50
|
+
|
|
51
|
+
if format:
|
|
52
|
+
output_path = Path(output_file)
|
|
53
|
+
if not output_file.endswith(f'.{format}'): output_file = str(output_path.with_suffix(f'.{format}'))
|
|
54
|
+
if output_type is None: output_type = detect_file_type(output_file)
|
|
55
|
+
|
|
56
|
+
output_path = Path(output_file)
|
|
57
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
|
|
59
|
+
with LoadingAnimation(): success, message = convert_file(input_file, output_file, input_type, output_type)
|
|
60
|
+
|
|
61
|
+
if success:
|
|
62
|
+
click.echo(click.style(f"✓ {message}", fg='green'))
|
|
63
|
+
click.echo(click.style(f"OUTPUT: {output_file}", fg='blue'))
|
|
64
|
+
else:
|
|
65
|
+
click.echo(click.style(f"✗ {message}", fg='red'), err=True)
|
|
66
|
+
raise click.Abort()
|
|
67
|
+
|
|
68
|
+
update_msg = check_for_updates()
|
|
69
|
+
if update_msg: click.echo(update_msg)
|
|
70
|
+
|
|
71
|
+
except FileNotFoundError as e:
|
|
72
|
+
click.echo(click.style(f"✗ FILE NOT FOUND: {str(e)}", fg='red'), err=True)
|
|
73
|
+
raise click.Abort()
|
|
74
|
+
except ImportError as e:
|
|
75
|
+
click.echo(click.style(f"✗ {str(e)}", fg='red'), err=True)
|
|
76
|
+
raise click.Abort()
|
|
77
|
+
except Exception as e:
|
|
78
|
+
click.echo(click.style(f"✗ ERROR: {str(e)}", fg='red'), err=True)
|
|
79
|
+
raise click.Abort()
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
# CONVERTS AUDIO BETWEEN FORMATS
|
|
6
|
+
def convert_audio(input_path: str, output_path: str, output_format: Optional[str] = None) -> bool:
|
|
7
|
+
try:
|
|
8
|
+
from pydub import AudioSegment
|
|
9
|
+
|
|
10
|
+
if not os.path.exists(input_path):
|
|
11
|
+
raise FileNotFoundError(f"INPUT FILE NOT FOUND: {input_path}")
|
|
12
|
+
if output_format is None: output_format = Path(output_path).suffix[1:].lower()
|
|
13
|
+
|
|
14
|
+
audio = AudioSegment.from_file(input_path)
|
|
15
|
+
audio.export(output_path, format=output_format)
|
|
16
|
+
|
|
17
|
+
return True
|
|
18
|
+
|
|
19
|
+
except ImportError:
|
|
20
|
+
raise ImportError("PYDUB is required for audio conversion. Install with: pip install pydub. Also install FFMPEG.")
|
|
21
|
+
except (OSError, ValueError, IOError) as e:
|
|
22
|
+
raise RuntimeError(f"AUDIO CONVERSION FAILED: {str(e)}")
|
|
23
|
+
except Exception as e:
|
|
24
|
+
raise RuntimeError(f"AUDIO CONVERSION FAILED: {str(e)}")
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
# CONVERTS IMAGE BETWEEN FORMATS
|
|
6
|
+
def convert_image(input_path: str, output_path: str, output_format: Optional[str] = None) -> bool:
|
|
7
|
+
try:
|
|
8
|
+
from PIL import Image
|
|
9
|
+
|
|
10
|
+
if not os.path.exists(input_path):
|
|
11
|
+
raise FileNotFoundError(f"INPUT FILE NOT FOUND: {input_path}")
|
|
12
|
+
if output_format is None: output_format = Path(output_path).suffix[1:].upper()
|
|
13
|
+
|
|
14
|
+
with Image.open(input_path) as img:
|
|
15
|
+
if output_format in ['JPG', 'JPEG'] and img.mode in ('RGBA', 'LA', 'P'):
|
|
16
|
+
rgb_img = Image.new('RGB', img.size, (255, 255, 255))
|
|
17
|
+
if img.mode == 'P': img = img.convert('RGBA')
|
|
18
|
+
rgb_img.paste(img, mask=img.split()[-1] if img.mode in ('RGBA', 'LA') else None)
|
|
19
|
+
img = rgb_img
|
|
20
|
+
|
|
21
|
+
img.save(output_path, format=output_format)
|
|
22
|
+
return True
|
|
23
|
+
|
|
24
|
+
except ImportError:
|
|
25
|
+
raise ImportError("PILLOW (PIL) is required for image conversion. Install with: pip install Pillow")
|
|
26
|
+
except (OSError, ValueError, IOError) as e:
|
|
27
|
+
raise RuntimeError(f"IMAGE CONVERSION FAILED: {str(e)}")
|
|
28
|
+
except Exception as e:
|
|
29
|
+
raise RuntimeError(f"IMAGE CONVERSION FAILED: {str(e)}")
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import xml.etree.ElementTree as ET
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Tuple
|
|
6
|
+
|
|
7
|
+
# CONVERTS TEXT TO JSON FORMAT
|
|
8
|
+
def text_to_json(text: str, indent: int = 2) -> str:
|
|
9
|
+
data = {"text": text}
|
|
10
|
+
return json.dumps(data, indent=indent, ensure_ascii=False)
|
|
11
|
+
|
|
12
|
+
# CONVERTS TEXT TO XML FORMAT
|
|
13
|
+
def text_to_xml(text: str, root_tag: str = "text") -> str:
|
|
14
|
+
root = ET.Element(root_tag)
|
|
15
|
+
root.text = text
|
|
16
|
+
return ET.tostring(root, encoding='unicode')
|
|
17
|
+
|
|
18
|
+
# CONVERTS TEXT TO HTML FORMAT
|
|
19
|
+
def text_to_html(text: str, title: str = "Document") -> str:
|
|
20
|
+
escaped_text = text.replace("&", "&").replace("<", "<").replace(">", ">")
|
|
21
|
+
return f"""<!DOCTYPE html>
|
|
22
|
+
<html>
|
|
23
|
+
<head>
|
|
24
|
+
<title>{title}</title>
|
|
25
|
+
</head>
|
|
26
|
+
<body>
|
|
27
|
+
<pre>{escaped_text}</pre>
|
|
28
|
+
</body>
|
|
29
|
+
</html>"""
|
|
30
|
+
|
|
31
|
+
# CONVERTS TEXT TO MARKDOWN FORMAT
|
|
32
|
+
def text_to_markdown(text: str) -> str:
|
|
33
|
+
return text
|
|
34
|
+
|
|
35
|
+
# CONVERTS JSON TO TEXT
|
|
36
|
+
def json_to_text(json_str: str) -> str:
|
|
37
|
+
try:
|
|
38
|
+
data = json.loads(json_str)
|
|
39
|
+
if isinstance(data, dict) and "text" in data: return str(data["text"])
|
|
40
|
+
return json.dumps(data, indent=2, ensure_ascii=False)
|
|
41
|
+
except json.JSONDecodeError:
|
|
42
|
+
return json_str
|
|
43
|
+
|
|
44
|
+
# CONVERTS XML TO TEXT
|
|
45
|
+
def xml_to_text(xml_str: str) -> str:
|
|
46
|
+
try:
|
|
47
|
+
root = ET.fromstring(xml_str)
|
|
48
|
+
text_parts = []
|
|
49
|
+
|
|
50
|
+
for elem in root.iter():
|
|
51
|
+
if elem.text and elem.text.strip(): text_parts.append(elem.text.strip())
|
|
52
|
+
if elem.tail and elem.tail.strip(): text_parts.append(elem.tail.strip())
|
|
53
|
+
|
|
54
|
+
result = " ".join(text_parts)
|
|
55
|
+
return result
|
|
56
|
+
except ET.ParseError:
|
|
57
|
+
return xml_str
|
|
58
|
+
|
|
59
|
+
# CONVERTS TEXT FILE FROM ONE FORMAT TO ANOTHER
|
|
60
|
+
def convert_text_file(input_path: str, output_path: str) -> Tuple[bool, str]:
|
|
61
|
+
if not os.path.exists(input_path):
|
|
62
|
+
raise FileNotFoundError(f"INPUT FILE NOT FOUND: {input_path}")
|
|
63
|
+
with open(input_path, 'r', encoding='utf-8') as f: content = f.read()
|
|
64
|
+
|
|
65
|
+
input_ext = Path(input_path).suffix[1:].lower()
|
|
66
|
+
output_ext = Path(output_path).suffix[1:].lower()
|
|
67
|
+
|
|
68
|
+
# CONVERT BASED ON INPUT FORMAT
|
|
69
|
+
if input_ext == 'json':
|
|
70
|
+
content = json_to_text(content)
|
|
71
|
+
elif input_ext == 'xml':
|
|
72
|
+
content = xml_to_text(content)
|
|
73
|
+
elif input_ext in ['html', 'htm']:
|
|
74
|
+
from html.parser import HTMLParser
|
|
75
|
+
class TextExtractor(HTMLParser):
|
|
76
|
+
def __init__(self):
|
|
77
|
+
super().__init__()
|
|
78
|
+
self.text = []
|
|
79
|
+
def handle_data(self, data):
|
|
80
|
+
self.text.append(data)
|
|
81
|
+
parser = TextExtractor()
|
|
82
|
+
parser.feed(content)
|
|
83
|
+
content = ' '.join(parser.text)
|
|
84
|
+
|
|
85
|
+
# CONVERT TO OUTPUT FORMAT
|
|
86
|
+
if output_ext == 'json': content = text_to_json(content)
|
|
87
|
+
elif output_ext == 'xml': content = text_to_xml(content)
|
|
88
|
+
elif output_ext in ['html', 'htm']: content = text_to_html(content)
|
|
89
|
+
|
|
90
|
+
# CREATE OUTPUT DIRECTORY IF IT DOESN'T EXIST
|
|
91
|
+
output_dir = os.path.dirname(output_path)
|
|
92
|
+
if output_dir and not os.path.exists(output_dir):
|
|
93
|
+
try: os.makedirs(output_dir, exist_ok=True)
|
|
94
|
+
except OSError as e: return False, f"CONVERSION FAILED: Cannot create output directory: {str(e)}"
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
with open(output_path, 'w', encoding='utf-8') as f: f.write(content)
|
|
98
|
+
except OSError as e:
|
|
99
|
+
return False, f"CONVERSION FAILED: Cannot write output file: {str(e)}"
|
|
100
|
+
|
|
101
|
+
return True, f"TEXT CONVERTED FROM {input_ext.upper()} TO {output_ext.upper()}"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
# CONVERTS VIDEO BETWEEN FORMATS
|
|
6
|
+
def convert_video(input_path: str, output_path: str, output_format: Optional[str] = None) -> bool:
|
|
7
|
+
try:
|
|
8
|
+
from moviepy.editor import VideoFileClip
|
|
9
|
+
|
|
10
|
+
if not os.path.exists(input_path):
|
|
11
|
+
raise FileNotFoundError(f"INPUT FILE NOT FOUND: {input_path}")
|
|
12
|
+
if output_format is None: output_format = Path(output_path).suffix[1:].lower()
|
|
13
|
+
|
|
14
|
+
with VideoFileClip(input_path) as video: video.write_videofile(output_path, codec='libx264', audio_codec='aac')
|
|
15
|
+
|
|
16
|
+
return True
|
|
17
|
+
|
|
18
|
+
except ImportError:
|
|
19
|
+
raise ImportError("MOVIEPY IS REQUIRED FOR VIDEO CONVERSION. INSTALL WITH: pip install moviepy. ALSO INSTALL FFMPEG.")
|
|
20
|
+
|
|
21
|
+
except (OSError, ValueError, IOError) as e:
|
|
22
|
+
raise RuntimeError(f"VIDEO CONVERSION FAILED: {str(e)}")
|
|
23
|
+
|
|
24
|
+
except Exception as e:
|
|
25
|
+
raise RuntimeError(f"VIDEO CONVERSION FAILED: {str(e)}")
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Optional, Tuple
|
|
3
|
+
|
|
4
|
+
from .conversion.convert_text import convert_text_file
|
|
5
|
+
from .conversion.convert_image import convert_image
|
|
6
|
+
from .conversion.convert_audio import convert_audio
|
|
7
|
+
from .conversion.convert_video import convert_video
|
|
8
|
+
|
|
9
|
+
# DETECTS FILE TYPE AND CONVERTS ACCORDINGLY
|
|
10
|
+
def detect_file_type(file_path: str) -> str:
|
|
11
|
+
ext = Path(file_path).suffix[1:].lower()
|
|
12
|
+
|
|
13
|
+
# TEXT FORMATS
|
|
14
|
+
text_formats = ['txt', 'md', 'markdown', 'json', 'xml', 'html', 'htm', 'csv']
|
|
15
|
+
if ext in text_formats: return 'text'
|
|
16
|
+
|
|
17
|
+
# IMAGE FORMATS
|
|
18
|
+
image_formats = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'tiff', 'tif', 'ico', 'svg']
|
|
19
|
+
if ext in image_formats: return 'image'
|
|
20
|
+
|
|
21
|
+
# AUDIO FORMATS
|
|
22
|
+
audio_formats = ['mp3', 'wav', 'ogg', 'flac', 'aac', 'm4a', 'wma', 'opus']
|
|
23
|
+
if ext in audio_formats: return 'audio'
|
|
24
|
+
|
|
25
|
+
# VIDEO FORMATS
|
|
26
|
+
video_formats = ['mp4', 'avi', 'mov', 'mkv', 'wmv', 'flv', 'webm', 'm4v']
|
|
27
|
+
if ext in video_formats: return 'video'
|
|
28
|
+
|
|
29
|
+
return 'unknown'
|
|
30
|
+
|
|
31
|
+
# CONVERTS FILE FROM ONE FORMAT TO ANOTHER
|
|
32
|
+
def convert_file(input_path: str, output_path: str, input_type: Optional[str] = None, output_type: Optional[str] = None) -> Tuple[bool, str]:
|
|
33
|
+
try:
|
|
34
|
+
if input_type is None: input_type = detect_file_type(input_path)
|
|
35
|
+
if output_type is None: output_type = detect_file_type(output_path)
|
|
36
|
+
if input_type == 'text' and output_type == 'text': return convert_text_file(input_path, output_path)
|
|
37
|
+
|
|
38
|
+
# HANDLE CONVERSIONS WITHIN THE SAME MEDIA TYPE
|
|
39
|
+
elif input_type == 'image' and output_type == 'image':
|
|
40
|
+
convert_image(input_path, output_path)
|
|
41
|
+
return True, "IMAGE CONVERTED SUCCESSFULLY"
|
|
42
|
+
elif input_type == 'audio' and output_type == 'audio':
|
|
43
|
+
convert_audio(input_path, output_path)
|
|
44
|
+
return True, "AUDIO CONVERTED SUCCESSFULLY"
|
|
45
|
+
elif input_type == 'video' and output_type == 'video':
|
|
46
|
+
convert_video(input_path, output_path)
|
|
47
|
+
return True, "VIDEO CONVERTED SUCCESSFULLY"
|
|
48
|
+
else:
|
|
49
|
+
return False, f"UNSUPPORTED CONVERSION: {input_type} TO {output_type}"
|
|
50
|
+
|
|
51
|
+
except FileNotFoundError:
|
|
52
|
+
raise
|
|
53
|
+
except Exception as e:
|
|
54
|
+
return False, f"CONVERSION FAILED: {str(e)}"
|
autotools/autoip/commands.py
CHANGED
|
@@ -2,6 +2,18 @@ import click
|
|
|
2
2
|
from .core import run
|
|
3
3
|
from ..utils.loading import LoadingAnimation
|
|
4
4
|
from ..utils.updates import check_for_updates
|
|
5
|
+
from ..utils.text import safe_text, is_ci_environment, mask_sensitive_info
|
|
6
|
+
|
|
7
|
+
# TOOL CATEGORY (USED BY 'autotools smoke')
|
|
8
|
+
TOOL_CATEGORY = 'Network'
|
|
9
|
+
|
|
10
|
+
# SMOKE TEST CASES (USED BY 'autotools smoke')
|
|
11
|
+
SMOKE_TESTS = [
|
|
12
|
+
{'name': 'basic', 'args': ['--no-ip']},
|
|
13
|
+
{'name': 'connectivity', 'args': ['--test']},
|
|
14
|
+
{'name': 'dns', 'args': ['--dns']},
|
|
15
|
+
{'name': 'ports', 'args': ['--ports']},
|
|
16
|
+
]
|
|
5
17
|
|
|
6
18
|
# CLI COMMAND TO DISPLAY NETWORK INFORMATION AND RUN DIAGNOSTICS
|
|
7
19
|
@click.command()
|
|
@@ -14,11 +26,37 @@ from ..utils.updates import check_for_updates
|
|
|
14
26
|
@click.option('--location', '-l', is_flag=True, help='Show IP location info')
|
|
15
27
|
@click.option('--no-ip', '-n', is_flag=True, help='Hide IP addresses')
|
|
16
28
|
def autoip(test, speed, monitor, interval, ports, dns, location, no_ip):
|
|
29
|
+
"""
|
|
30
|
+
DISPLAYS NETWORK INFORMATION AND RUNS DIAGNOSTICS.
|
|
31
|
+
|
|
32
|
+
\b
|
|
33
|
+
FEATURES:
|
|
34
|
+
- Show IP addresses (public and private)
|
|
35
|
+
- RUN CONNECTIVITY TESTS
|
|
36
|
+
- Measure internet speed
|
|
37
|
+
- Monitor network traffic
|
|
38
|
+
- Check port status
|
|
39
|
+
- Display DNS servers
|
|
40
|
+
- Show IP location information
|
|
41
|
+
|
|
42
|
+
\b
|
|
43
|
+
EXAMPLES:
|
|
44
|
+
autoip
|
|
45
|
+
autoip --test
|
|
46
|
+
autoip --speed
|
|
47
|
+
autoip --monitor --interval 5
|
|
48
|
+
autoip --ports --dns --location
|
|
49
|
+
autoip --no-ip
|
|
50
|
+
"""
|
|
51
|
+
|
|
17
52
|
with LoadingAnimation():
|
|
18
53
|
output = run(
|
|
19
54
|
test=test, speed=speed, monitor=monitor, interval=interval,
|
|
20
55
|
ports=ports, dns=dns, location=location, no_ip=no_ip
|
|
21
56
|
)
|
|
22
|
-
|
|
57
|
+
|
|
58
|
+
if is_ci_environment(): output = mask_sensitive_info(output, mask_ips=True)
|
|
59
|
+
|
|
60
|
+
click.echo(safe_text(output))
|
|
23
61
|
update_msg = check_for_updates()
|
|
24
62
|
if update_msg: click.echo(update_msg)
|