From b36cb4c5ecbb9fe840571fb2e29a796767ccb83a Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Sun, 1 Feb 2026 21:45:45 +0000 Subject: [PATCH] Initial upload: Devtoolbelt v1.0.0 - unified CLI toolkit for developers --- devtoolbelt/utils.py | 184 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 devtoolbelt/utils.py diff --git a/devtoolbelt/utils.py b/devtoolbelt/utils.py new file mode 100644 index 0000000..2fc4785 --- /dev/null +++ b/devtoolbelt/utils.py @@ -0,0 +1,184 @@ +"""Common utility functions for Devtoolbelt.""" + +import json +from datetime import datetime +from typing import Any, Dict, List, Optional + +from rich.console import Console +from rich.table import Table + + +console = Console() + + +def format_timestamp(timestamp: str, fmt: str = "%Y-%m-%d %H:%M:%S") -> str: + """Format a timestamp string. + + Args: + timestamp: Timestamp string to format. + fmt: Output format string. + + Returns: + Formatted timestamp string. + """ + try: + dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00')) + return dt.strftime(fmt) + except (ValueError, AttributeError): + return timestamp + + +def format_table( + title: str, + headers: List[str], + rows: List[List[str]], + **kwargs +) -> Table: + """Create a formatted table. + + Args: + title: Table title. + headers: Column headers. + rows: List of row data. + **kwargs: Additional arguments for Table. + + Returns: + Rich Table object. + """ + table = Table(title=title, **kwargs) + + for header in headers: + table.add_column(header) + + for row in rows: + table.add_row(*row) + + return table + + +def format_json(data: Any) -> str: + """Format data as pretty JSON. + + Args: + data: Data to format. + + Returns: + Formatted JSON string. + """ + return json.dumps(data, indent=2, default=str) + + +def truncate(text: str, max_length: int = 50, suffix: str = "...") -> str: + """Truncate text to maximum length. + + Args: + text: Text to truncate. + max_length: Maximum length. + suffix: Suffix to add when truncating. + + Returns: + Truncated text. + """ + if len(text) <= max_length: + return text + return text[:max_length - len(suffix)] + suffix + + +def parse_key_value(kv_string: str) -> Dict[str, str]: + """Parse a key=value string into a dictionary. + + Args: + kv_string: String in format "key1=value1,key2=value2". + + Returns: + Dictionary of key-value pairs. + """ + result = {} + if not kv_string: + return result + + pairs = kv_string.split(',') + for pair in pairs: + if '=' in pair: + key, value = pair.split('=', 1) + result[key.strip()] = value.strip() + + return result + + +def get_nested_value(data: Dict[str, Any], key_path: str, default: Any = None) -> Any: + """Get a nested value from a dictionary. + + Args: + data: Dictionary to search. + key_path: Dot-separated key path (e.g., "user.address.city"). + default: Default value if path not found. + + Returns: + Value at path or default. + """ + keys = key_path.split('.') + value = data + + for key in keys: + if isinstance(value, dict): + value = value.get(key) + if value is None: + return default + else: + return default + + return value + + +def pluralize(count: int, singular: str, plural: Optional[str] = None) -> str: + """Return singular or plural form based on count. + + Args: + count: Item count. + singular: Singular form. + plural: Plural form (defaults to singular + 's'). + + Returns: + Appropriate form of the word. + """ + if plural is None: + plural = singular + 's' + return singular if count == 1 else plural + + +def format_bytes(size: int) -> str: + """Format bytes to human-readable string. + + Args: + size: Size in bytes. + + Returns: + Formatted size string. + """ + for unit in ['B', 'KB', 'MB', 'GB', 'TB']: + if size < 1024: + return f"{size:.2f} {unit}" + size = int(size / 1024) + return f"{size:.2f} PB" + + +def format_duration(seconds: float) -> str: + """Format seconds to human-readable duration. + + Args: + seconds: Duration in seconds. + + Returns: + Formatted duration string. + """ + if seconds < 1: + return f"{seconds * 1000:.0f}ms" + elif seconds < 60: + return f"{seconds:.1f}s" + elif seconds < 3600: + return f"{seconds / 60:.1f}m" + elif seconds < 86400: + return f"{seconds / 3600:.1f}h" + else: + return f"{seconds / 86400:.1f}d"