Python

Python File Parser

logshow 2026. 9. 21. 21:39
from pathlib import Path
import tkinter as tk
from tkinter import filedialog, messagebox, ttk


class TextBatchParser:
    """Handles text file discovery, loading, and batch parsing operations."""

    def __init__(self):
        # Collected Path objects: [Path, Path, ...]
        self.file_paths = []
        # In-memory file contents: {Path: "content string"}
        self.loaded_data = {}

    def collect_from_files(self, file_paths: list[str] | tuple[str, ...]):
        """Collect and validate a list of individual file paths."""
        self.file_paths = [
            Path(p)
            for p in file_paths
            if Path(p).is_file() and Path(p).suffix.lower() == ".txt"
        ]
        self.file_paths.sort()

    def collect_from_directory(self, dir_path: str):
        """Recursively collect all .txt files within the selected directory."""
        root_dir = Path(dir_path)
        self.file_paths = sorted(
            [p for p in root_dir.rglob("*.txt") if p.is_file()]
        )

    def load_all_files(self) -> int:
        """Read all collected files into memory with encoding fallback."""
        self.loaded_data.clear()
        success_count = 0

        for path in self.file_paths:
            content = self._safe_read(path)
            if content is not None:
                self.loaded_data[path] = content
                success_count += 1

        return success_count

    def _safe_read(self, path: Path) -> str | None:
        """Attempt reading with UTF-8 first, falling back to CP949 and Latin-1."""
        encodings = ["utf-8", "cp949", "latin-1"]
        for enc in encodings:
            try:
                return path.read_text(encoding=enc)
            except (UnicodeDecodeError, PermissionError):
                continue
        return None


class ParserApp:
    """GUI window and event controller using Tkinter."""

    def __init__(self, root: tk.Tk):
        self.root = root
        self.root.title("Text File Parser")
        self.root.geometry("680x480")
        self.root.minsize(520, 360)

        self.parser = TextBatchParser()
        self._setup_ui()

    def _setup_ui(self):
        # Top action toolbar
        btn_frame = ttk.Frame(self.root, padding=10)
        btn_frame.pack(fill=tk.X)

        # File selection button (supports multiple files)
        ttk.Button(
            btn_frame,
            text="Select Files (Multi-select)",
            command=self._select_files,
        ).pack(side=tk.LEFT, padx=5)

        # Directory selection button (recursive traversal)
        ttk.Button(
            btn_frame,
            text="Select Directory (Recursive)",
            command=self._select_directory,
        ).pack(side=tk.LEFT, padx=5)

        # Status text label
        self.status_var = tk.StringVar(
            value="Please select file(s) or a directory to begin."
        )
        status_label = ttk.Label(
            self.root, textvariable=self.status_var, padding=(10, 0)
        )
        status_label.pack(anchor=tk.W)

        # File list view with vertical scrollbar
        list_frame = ttk.Frame(self.root, padding=10)
        list_frame.pack(fill=tk.BOTH, expand=True)

        self.file_listbox = tk.Listbox(list_frame, selectmode=tk.SINGLE)
        scrollbar = ttk.Scrollbar(
            list_frame, orient=tk.VERTICAL, command=self.file_listbox.yview
        )
        self.file_listbox.configure(yscrollcommand=scrollbar.set)

        self.file_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # Double-click event to open quick preview
        self.file_listbox.bind("<Double-Button-1>", self._show_preview)

    def _select_files(self):
        selected_files = filedialog.askopenfilenames(
            title="Select TXT Files (Hold Ctrl/Shift for Multi-Select)",
            filetypes=[("Text files", "*.txt"), ("All files", "*.*")],
        )
        if not selected_files:
            return

        self.parser.collect_from_files(selected_files)
        self._process_collected_files()

    def _select_directory(self):
        selected_dir = filedialog.askdirectory(
            title="Select Directory to Search"
        )
        if not selected_dir:
            return

        self.parser.collect_from_directory(selected_dir)
        self._process_collected_files()

    def _process_collected_files(self):
        """Update file collection, load text content, and refresh UI elements."""
        self.file_listbox.delete(0, tk.END)

        total_files = len(self.parser.file_paths)
        if total_files == 0:
            self.status_var.set("No .txt files found in the selection.")
            return

        loaded_count = self.parser.load_all_files()

        for path in self.parser.file_paths:
            self.file_listbox.insert(tk.END, str(path))

        self.status_var.set(
            f"Found {total_files} file(s) | {loaded_count} loaded successfully (Double-click to preview)"
        )

    def _show_preview(self, event):
        """Show a modal pop-up containing the first 10 lines of the selected file."""
        selection = self.file_listbox.curselection()
        if not selection:
            return

        idx = selection[0]
        target_path = self.parser.file_paths[idx]
        content = self.parser.loaded_data.get(target_path, "")

        preview_lines = "\n".join(content.splitlines()[:10])
        messagebox.showinfo(
            title=f"Preview: {target_path.name}",
            message=preview_lines if preview_lines else "(Empty file)",
        )


if __name__ == "__main__":
    root = tk.Tk()
    app = ParserApp(root)
    root.mainloop()

'Python' 카테고리의 다른 글

Practical Structure  (0) 2024.12.08