Initial upload: Devtoolbelt v1.0.0 - unified CLI toolkit for developers
Some checks failed
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled

This commit is contained in:
2026-02-01 21:45:45 +00:00
parent c481eb125e
commit b36cb4c5ec

184
devtoolbelt/utils.py Normal file
View 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"