From 378f417135dbfb40f28c6eb43d56fc4974377b5e Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Sun, 1 Feb 2026 21:45:50 +0000 Subject: [PATCH] Initial upload: Devtoolbelt v1.0.0 - unified CLI toolkit for developers --- devtoolbelt/commands/utils.py | 413 ++++++++++++++++++++++++++++++++++ 1 file changed, 413 insertions(+) create mode 100644 devtoolbelt/commands/utils.py diff --git a/devtoolbelt/commands/utils.py b/devtoolbelt/commands/utils.py new file mode 100644 index 0000000..5c65a04 --- /dev/null +++ b/devtoolbelt/commands/utils.py @@ -0,0 +1,413 @@ +"""Utility commands for Devtoolbelt.""" + +import hashlib +import ipaddress +import json +import random +import string +import time +from datetime import datetime, timezone +from typing import Any, Dict, List, Optional + +import click +from rich import print as rprint +from rich.table import Table + +from ..utils import console, format_duration + + +@click.group() +def utils(): + """Common utility functions.""" + pass + + +@utils.command("timestamp") +@click.option( + "--to-iso", "-i", + is_flag=True, + help="Convert to ISO format." +) +@click.option( + "--to-unix", "-u", + is_flag=True, + help="Convert to Unix timestamp." +) +@click.option( + "--timezone", "-z", + type=click.Choice(["local", "utc"]), + default="local", + help="Timezone for display." +) +def timestamp(to_iso: bool, to_unix: bool, tz: str): + """Get current timestamp in various formats.""" + now = datetime.now(timezone.utc) + + table = Table(title="Current Timestamp") + table.add_column("Format", style="cyan") + table.add_column("Value", style="green") + + if tz == "utc": + now_local = now + else: + now_local = datetime.now() + + table.add_row("ISO 8601", now_local.isoformat()) + table.add_row("Unix", str(int(now_local.timestamp()))) + table.add_row("RFC 2822", now_local.strftime("%a, %d %b %Y %H:%M:%S %z")) + table.add_row("Human Readable", now_local.strftime("%Y-%m-%d %H:%M:%S")) + + console.print(table) + + +@utils.command("hash") +@click.argument("input_string") +@click.option( + "--algorithm", "-a", + type=click.Choice(["md5", "sha1", "sha256", "sha512"]), + default="sha256", + help="Hash algorithm to use." +) +def hash_string(input_string: str, algorithm: str): + """Generate hash of a string.""" + hasher = hashlib.new(algorithm) + hasher.update(input_string.encode('utf-8')) + result = hasher.hexdigest() + + rprint(f"[bold cyan]{algorithm.upper()}:[/bold cyan] [green]{result}[/green]") + + +@utils.command("hash-file") +@click.argument("file_path", type=click.Path(exists=True)) +@click.option( + "--algorithm", "-a", + type=click.Choice(["md5", "sha1", "sha256", "sha512"]), + default="sha256", + help="Hash algorithm to use." +) +def hash_file(file_path: str, algorithm: str): + """Generate hash of a file.""" + hasher = hashlib.new(algorithm) + with open(file_path, 'rb') as f: + hasher.update(f.read()) + result = hasher.hexdigest() + + rprint(f"[bold cyan]{algorithm.upper()}:[/bold cyan] [green]{result}[/green]") + + +@utils.command("uuid") +@click.option( + "--count", "-n", + type=int, + default=1, + help="Number of UUIDs to generate." +) +def generate_uuid(count: int): + """Generate UUID(s).""" + import uuid + + for _ in range(min(count, 100)): + rprint(str(uuid.uuid4())) + + +@utils.command("random") +@click.option( + "--length", "-l", + type=int, + default=32, + help="Length of random string." +) +@click.option( + "--chars", "-c", + type=click.Choice(["all", "letters", "digits", "hex"]), + default="all", + help="Character set to use." +) +def random_string(length: int, chars: str): + """Generate random string.""" + if chars == "all": + characters = string.ascii_letters + string.digits + string.punctuation + elif chars == "letters": + characters = string.ascii_letters + elif chars == "digits": + characters = string.digits + elif chars == "hex": + characters = string.hexdigits.lower() + + result = ''.join(random.choice(characters) for _ in range(length)) + rprint(result) + + +@utils.command("json") +@click.argument("input_string") +@click.option( + "--validate", "-v", + is_flag=True, + help="Only validate JSON format." +) +@click.option( + "--minify", "-m", + is_flag=True, + help="Minify JSON output." +) +def json_tool(input_string: str, validate: bool, minify: bool): + """Format or validate JSON.""" + try: + data = json.loads(input_string) + + if validate: + rprint("[green]Valid JSON[/green]") + return + + if minify: + result = json.dumps(data) + else: + result = json.dumps(data, indent=2) + + rprint(result) + except json.JSONDecodeError as e: + rprint(f"[red]Invalid JSON: {e}[/red]") + + +@utils.command("base64") +@click.argument("input_string") +@click.option( + "--decode", "-d", + is_flag=True, + help="Decode instead of encode." +) +def base64_tool(input_string: str, decode: bool): + """Encode or decode Base64.""" + import base64 + + try: + if decode: + result = base64.b64decode(input_string).decode('utf-8') + else: + result = base64.b64encode(input_string.encode('utf-8')).decode('utf-8') + rprint(result) + except Exception as e: + rprint(f"[red]Error: {e}[/red]") + + +@utils.command("url") +@click.argument("input_string") +@click.option( + "--decode", "-d", + is_flag=True, + help="Decode instead of encode." +) +def url_tool(input_string: str, decode: bool): + """Encode or decode URL.""" + from urllib.parse import quote, unquote + + try: + if decode: + result = unquote(input_string) + else: + result = quote(input_string, safe='') + rprint(result) + except Exception as e: + rprint(f"[red]Error: {e}[/red]") + + +@utils.command("ip") +@click.argument("ip_address") +@click.option( + "--details", "-d", + is_flag=True, + help="Show detailed IP information." +) +def ip_info(ip_address: str, details: bool): + """Get information about an IP address.""" + try: + ip = ipaddress.ip_address(ip_address) + + table = Table(title=f"IP Information: {ip_address}") + table.add_column("Property", style="cyan") + table.add_column("Value", style="green") + + table.add_row("Address", str(ip)) + table.add_row("Version", f"IPv{ip.version}") + table.add_row("Is Private", str(ip.is_private)) + table.add_row("Is Global", str(ip.is_global)) + table.add_row("Is Loopback", str(ip.is_loopback)) + table.add_row("Is Multicast", str(ip.is_multicast)) + + if details: + table.add_row("Packed", ip.packed.hex()) + table.add_row("Reverse DNS", str(ip.reverse_pointer) if ip.is_global else "N/A") + + console.print(table) + + except ValueError as e: + rprint(f"[red]Invalid IP address: {e}[/red]") + + +@utils.command("jwt") +@click.argument("token") +@click.option( + "--decode", "-d", + is_flag=True, + help="Decode JWT without verification." +) +@click.option( + "--show-header", "-H", + is_flag=True, + help="Show JWT header." +) +def jwt_tool(token: str, decode: bool, show_header: bool): + """Decode JWT token.""" + import base64 + + parts = token.split('.') + if len(parts) != 3: + rprint("[red]Invalid JWT format[/red]") + return + + try: + def decode_part(part: str) -> Dict[str, Any]: + padding = 4 - len(part) % 4 + if padding != 4: + part += '=' * padding + decoded = base64.urlsafe_b64decode(part) + return json.loads(decoded.decode('utf-8')) + + if show_header or decode: + header = decode_part(parts[0]) + rprint("[bold cyan]Header:[/bold cyan]") + rprint(json.dumps(header, indent=2)) + + payload = decode_part(parts[1]) + rprint("[bold cyan]Payload:[/bold cyan]") + rprint(json.dumps(payload, indent=2)) + + except Exception as e: + rprint(f"[red]Error decoding JWT: {e}[/red]") + + +@utils.command("cron") +@click.argument("cron_expression") +def cron_info(cron_expression: str): + """Parse and describe cron expression.""" + parts = cron_expression.split() + if len(parts) != 5: + rprint("[red]Invalid cron expression (expected 5 fields)[/red]") + return + + minute, hour, day, month, dow = parts + + table = Table(title=f"Cron Expression: {cron_expression}") + table.add_column("Field", style="cyan") + table.add_column("Value", style="green") + + table.add_row("Minute", _describe_cron_field(minute, 0, 59, "minutes")) + table.add_row("Hour", _describe_cron_field(hour, 0, 23, "hours")) + table.add_row("Day of Month", _describe_cron_field(day, 1, 31, "days")) + table.add_row("Month", _describe_cron_field(month, 1, 12, "months")) + table.add_row("Day of Week", _describe_cron_field(dow, 0, 6, "weekdays", ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"])) + + console.print(table) + + +def _describe_cron_field(value: str, min_val: int, max_val: int, unit: str, names: Optional[List[str]] = None) -> str: + """Describe a cron field.""" + if value == "*": + return f"Every {unit}" + + result = [] + parts = value.split(',') + + for part in parts: + if "/" in part: + base, step = part.split("/") + if base == "*": + result.append(f"Every {step} {unit}") + else: + result.append(f"Every {step} {unit} starting from {base}") + elif "-" in part: + start, end = part.split("-") + result.append(f"{start} to {end}") + else: + if names and part.isdigit(): + idx = int(part) + if 0 <= idx < len(names): + result.append(names[idx]) + continue + result.append(part) + + return ", ".join(result) + + +@utils.command("password") +@click.option( + "--length", "-l", + type=int, + default=16, + help="Password length." +) +@click.option( + "--no-special", "-n", + is_flag=True, + help="Exclude special characters." +) +@click.option( + "--count", "-c", + type=int, + default=1, + help="Number of passwords to generate." +) +def generate_password(length: int, no_special: bool, count: int): + """Generate secure password(s).""" + alphabet = string.ascii_letters + string.digits + if not no_special: + alphabet += string.punctuation + + for _ in range(min(count, 20)): + password = ''.join(random.choice(alphabet) for _ in range(length)) + rprint(password) + + +@utils.command("timer") +@click.option( + "--seconds", "-s", + type=int, + default=0, + help="Timer duration in seconds." +) +@click.option( + "--minutes", "-m", + type=int, + default=0, + help="Timer duration in minutes." +) +def timer(seconds: int, minutes: int): + """Simple countdown timer.""" + total_seconds = seconds + minutes * 60 + + if total_seconds <= 0: + rprint("[yellow]Please specify a duration.[/yellow]") + return + + rprint(f"[bold]Starting timer for {total_seconds} seconds...[/bold]") + + for remaining in range(total_seconds, 0, -1): + mins, secs = divmod(remaining, 60) + rprint(f"\r[bold]{mins:02d}:{secs:02d}[/bold] ", end="", flush=True) + time.sleep(1) + + rprint("\n[bold green]⏰ Time's up![/bold green]") + + +@utils.command("stopwatch") +def stopwatch(): + """Simple stopwatch.""" + rprint("[bold]Press Enter to start, Enter to stop...[/bold]") + input() + + start_time = time.time() + rprint("[bold]Stopwatch running... Press Enter to stop.[/bold]") + input() + + elapsed = time.time() - start_time + rprint(f"[bold green]Elapsed: {format_duration(elapsed)}[/bold green]")