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 |
|---|