#!/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