154 lines
5.3 KiB
Python

#!/usr/bin/env python3
"""Rich TUI linting interface for Sandpypi."""
import logging
import subprocess
import time
from datetime import datetime
from pathlib import Path
from rich.console import Console
from rich.layout import Layout
from rich.live import Live
from rich.panel import Panel
from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn
class LinterTUI:
"""Main classs for the linting TUI."""
def __init__(self):
self.console = Console()
self.layout = Layout()
self.layout.split_column(
Layout(name="output", ratio=8), Layout(name="progress", ratio=2)
)
self.setup_logging()
def setup_logging(self):
"""Logging"""
self.log_dir = Path("logs")
self.log_dir.mkdir(exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
self.log_file = f"lint_{timestamp}.log"
logging.basicConfig(
filename=self.log_dir / self.log_file,
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
)
def get_target_path(self):
"""Get thee target path for linting."""
paths = input(
"\nEnter paths to lint (comma-separated NO SPACES)\
or press Enter for default [src,tests]: "
).strip()
target_paths = paths.split(",") if paths else ["src", "tests"]
# Convert to absolute paths and include subdirectories
abs_paths = []
for path in target_paths:
base_path = Path(path).absolute()
if base_path.is_dir():
abs_paths.extend(str(p) for p in base_path.rglob("*.py"))
else:
abs_paths.append(str(base_path))
return abs_paths
def run_linters(self, selected_linters, paths):
"""Run the linters on the selected paths."""
progress = Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
expand=True,
)
self.layout["progress"].update(progress)
with Live(self.layout, refresh_per_second=5, screen=True):
task = progress.add_task(
"[cyan]Linting...", total=len(selected_linters)
)
for linter in selected_linters:
progress.update(task, description=f"[cyan]Running {linter}...")
cmd = [linter] + paths
with subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
bufsize=1,
) as process:
current_output = []
linter_name = linter.split()[0]
while True:
output = process.stdout.readline()
if output == "" and process.poll() is not None:
break
if output:
current_output.append(output.strip())
# Log each line of output
logging.info("Running %s on %s", linter, paths)
logging.info("Output: %s", output.strip())
self.layout["output"].update(
Panel(
"\n".join(current_output[-20:]),
title=f"[bold green]{linter}\
{linter_name}",
border_style="green",
)
)
time.sleep(0.1)
# Log any errors
if not current_output:
stderr_output = process.stderr.read()
output_text = stderr_output or "No output"
self.layout["output"].update(
Panel(
output_text,
title=f"[bold green]{linter} Output",
border_style="green",
)
)
if stderr_output:
logging.error("Errors: %s", stderr_output)
progress.advance(task)
final_message = Panel(
"[bold green]✨ Linting Complete! ✨\n"
"[cyan]Check the logs for detailed results.\n"
f"[green]Logs saved to: {self.log_dir / self.log_file}\n"
"[yellow]Press Enter to exit...",
title="[bold blue]Sandpypi Linter",
border_style="green",
padding=(2, 4),
)
self.layout["output"].update(final_message)
self.layout["progress"].update(
Panel("[bold green]100% Complete!", border_style="green")
)
input()
def main():
"""Runner for the Linting TUI."""
tui = LinterTUI()
linters = ["black", "isort", "mypy", "flake8", "pylint"]
paths = tui.get_target_path()
tui.run_linters(linters, paths)
if __name__ == "__main__":
main()
# scripts\run_tests.py
# !/usr/bin/env python3