PYTHON
files.py🐍python
"""
File management commands.
"""
import os
from pathlib import Path
import click
from rich.console import Console
from rich.table import Table
from rich.tree import Tree
console = Console()
@click.group()
def files():
"""File management utilities."""
pass
@files.command()
@click.argument('path', type=click.Path(exists=True), default='.')
@click.option('--pattern', '-p', default='*', help='Glob pattern to match')
def count(path: str, pattern: str):
"""Count files matching a pattern."""
path = Path(path)
if path.is_file():
console.print(f"[yellow]1 file[/yellow]")
return
files = list(path.rglob(pattern))
dirs = [f for f in files if f.is_dir()]
regular_files = [f for f in files if f.is_file()]
console.print(f"[green]{len(regular_files)}[/green] files, "
f"[blue]{len(dirs)}[/blue] directories "
f"matching [yellow]{pattern}[/yellow]")
@files.command()
@click.argument('path', type=click.Path(exists=True), default='.')
@click.option('--pattern', '-p', default='*.py', help='Glob pattern to find')
@click.option('--limit', '-l', default=20, help='Maximum results')
def find(path: str, pattern: str, limit: int):
"""Find files matching a pattern."""
path = Path(path)
files = list(path.rglob(pattern))[:limit]
if not files:
console.print(f"[yellow]No files matching {pattern}[/yellow]")
return
table = Table(title=f"Files matching {pattern}")
table.add_column("File", style="cyan")
table.add_column("Size", style="green", justify="right")
for f in files:
if f.is_file():
size = f.stat().st_size
size_str = _format_size(size)
table.add_row(str(f), size_str)
console.print(table)
@files.command()
@click.argument('path', type=click.Path(exists=True), default='.')
def size(path: str):
"""Calculate total size of a directory."""
path = Path(path)
if path.is_file():
console.print(f"Size: [green]{_format_size(path.stat().st_size)}[/green]")
return
total = sum(f.stat().st_size for f in path.rglob('*') if f.is_file())
file_count = len([f for f in path.rglob('*') if f.is_file()])
console.print(f"Total: [green]{_format_size(total)}[/green] "
f"across [cyan]{file_count}[/cyan] files")
@files.command()
@click.argument('path', type=click.Path(exists=True), default='.')
@click.option('--depth', '-d', default=2, help='Max depth to display')
def tree(path: str, depth: int):
"""Display directory tree structure."""
path = Path(path)
tree = Tree(f"[bold blue]{path}[/bold blue]")
_add_to_tree(tree, path, depth)
console.print(tree)
def _add_to_tree(tree: Tree, path: Path, depth: int, current_depth: int = 0):
"""Recursively add directories to tree."""
if current_depth >= depth:
return
try:
items = sorted(path.iterdir(), key=lambda x: (x.is_file(), x.name.lower()))
for item in items:
if item.name.startswith('.'):
continue
if item.is_dir():
branch = tree.add(f"[bold blue]{item.name}/[/bold blue]")
_add_to_tree(branch, item, depth, current_depth + 1)
else:
tree.add(f"[green]{item.name}[/green]")
except PermissionError:
tree.add("[red]Permission denied[/red]")
def _format_size(size: int) -> str:
"""Format file size to human readable."""
for unit in ['B', 'KB', 'MB', 'GB']:
if size < 1024:
return f"{size:.1f} {unit}"
size /= 1024
return f"{size:.1f} TB"