Initial upload: Devtoolbelt v1.0.0 - unified CLI toolkit for developers
This commit is contained in:
184
devtoolbelt/utils.py
Normal file
184
devtoolbelt/utils.py
Normal file
@@ -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"
|
||||
Reference in New Issue
Block a user