pdf-file-renamer 0.4.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,216 @@
1
+ """Display formatters and UI components."""
2
+
3
+ from rich.console import Console
4
+ from rich.layout import Layout
5
+ from rich.panel import Panel
6
+ from rich.prompt import Prompt
7
+ from rich.table import Table
8
+ from rich.text import Text
9
+
10
+ from pdf_renamer.domain.models import FileRenameOperation
11
+
12
+
13
+ class ProgressDisplay:
14
+ """Handles progress display for batch processing."""
15
+
16
+ def __init__(self, console: Console, total_files: int) -> None:
17
+ """
18
+ Initialize the progress display.
19
+
20
+ Args:
21
+ console: Rich console for output
22
+ total_files: Total number of files to process
23
+ """
24
+ self.console = console
25
+ self.total_files = total_files
26
+ self.status_tracker: dict[str, dict[str, str]] = {}
27
+
28
+ def update_status(self, filename: str, status: dict[str, str]) -> None:
29
+ """Update status for a file."""
30
+ self.status_tracker[filename] = status
31
+
32
+ def create_display(self) -> Layout:
33
+ """Create the live display layout."""
34
+ # Create table
35
+ table = Table(
36
+ title="Processing Status",
37
+ expand=True,
38
+ show_header=True,
39
+ header_style="bold magenta",
40
+ )
41
+ table.add_column("File", style="cyan", no_wrap=False, width=40)
42
+ table.add_column("Stage", justify="center", width=8)
43
+ table.add_column("Status", style="yellow", width=12)
44
+ table.add_column("Details", style="dim", no_wrap=False)
45
+
46
+ # Count statuses
47
+ completed = sum(1 for s in self.status_tracker.values() if s.get("status") == "Complete")
48
+ extracting = sum(1 for s in self.status_tracker.values() if s.get("status") == "Extracting")
49
+ analyzing = sum(1 for s in self.status_tracker.values() if s.get("status") == "Analyzing")
50
+ errors = sum(1 for s in self.status_tracker.values() if s.get("status") == "Error")
51
+ pending = self.total_files - completed - extracting - analyzing - errors
52
+
53
+ # Separate files by status
54
+ active_files = []
55
+ completed_files = []
56
+
57
+ for filename, info in self.status_tracker.items():
58
+ if info.get("status") in ["Extracting", "Analyzing"]:
59
+ active_files.append((filename, info))
60
+ elif info.get("status") in ["Complete", "Error"]:
61
+ completed_files.append((filename, info))
62
+
63
+ # Show active files
64
+ for filename, info in active_files:
65
+ display_name = filename if len(filename) <= 40 else filename[:37] + "..."
66
+ table.add_row(
67
+ display_name,
68
+ info.get("stage", ""),
69
+ info.get("status", ""),
70
+ info.get("confidence", ""),
71
+ )
72
+
73
+ # Show last 5 completed files
74
+ for filename, info in completed_files[-5:]:
75
+ display_name = filename if len(filename) <= 40 else filename[:37] + "..."
76
+ status = info.get("status", "")
77
+ style = "green" if status == "Complete" else "red"
78
+ details = info.get("confidence", "") or info.get("error", "")[:50]
79
+ table.add_row(
80
+ f"[{style}]{display_name}[/{style}]",
81
+ info.get("stage", ""),
82
+ status,
83
+ details,
84
+ )
85
+
86
+ # Stats panel
87
+ stats = Text()
88
+ stats.append("Total: ", style="bold")
89
+ stats.append(f"{self.total_files}", style="white")
90
+ stats.append(" | ", style="dim")
91
+ stats.append("Pending: ", style="bold")
92
+ stats.append(f"{pending}", style="cyan")
93
+ stats.append(" | ", style="dim")
94
+ stats.append("Extracting: ", style="bold")
95
+ stats.append(f"{extracting}", style="blue")
96
+ stats.append(" | ", style="dim")
97
+ stats.append("Analyzing: ", style="bold")
98
+ stats.append(f"{analyzing}", style="yellow")
99
+ stats.append(" | ", style="dim")
100
+ stats.append("Complete: ", style="bold green")
101
+ stats.append(f"{completed}", style="green")
102
+
103
+ if errors > 0:
104
+ stats.append(" | ", style="dim")
105
+ stats.append("Errors: ", style="bold red")
106
+ stats.append(f"{errors}", style="red")
107
+
108
+ # Progress bar
109
+ progress_pct = (completed / self.total_files * 100) if self.total_files > 0 else 0
110
+ filled = int(progress_pct / 2)
111
+ progress_bar = f"[{'█' * filled}{' ' * (50 - filled)}] {progress_pct:.1f}%"
112
+
113
+ # Layout
114
+ layout = Layout()
115
+ layout.split_column(
116
+ Layout(Panel(stats, title="📊 Progress", border_style="blue"), size=3),
117
+ Layout(Panel(Text(progress_bar, style="green bold"), border_style="green"), size=3),
118
+ Layout(table),
119
+ )
120
+
121
+ return layout
122
+
123
+
124
+ class InteractivePrompt:
125
+ """Handles interactive prompts for rename confirmation."""
126
+
127
+ def __init__(self, console: Console) -> None:
128
+ """Initialize the interactive prompt."""
129
+ self.console = console
130
+
131
+ async def prompt_for_action(self, operation: FileRenameOperation) -> tuple[str, bool]:
132
+ """
133
+ Prompt user for action on a rename operation.
134
+
135
+ Args:
136
+ operation: The rename operation
137
+
138
+ Returns:
139
+ Tuple of (final_filename, should_rename)
140
+ """
141
+ while True:
142
+ # Display info panel
143
+ info_text = Text()
144
+ info_text.append("Original: ", style="bold cyan")
145
+ info_text.append(f"{operation.original_path.name}\n", style="cyan")
146
+ info_text.append("Suggested: ", style="bold green")
147
+ info_text.append(f"{operation.new_filename}\n", style="green")
148
+ info_text.append("Confidence: ", style="bold yellow")
149
+ info_text.append(f"{operation.confidence.value}\n", style="yellow")
150
+ info_text.append("Reasoning: ", style="bold white")
151
+ info_text.append(operation.reasoning, style="dim white")
152
+
153
+ panel = Panel(
154
+ info_text,
155
+ title="[bold magenta]Rename Suggestion[/bold magenta]",
156
+ border_style="magenta",
157
+ padding=(1, 2),
158
+ )
159
+
160
+ self.console.print("\n")
161
+ self.console.print(panel)
162
+
163
+ # Show options
164
+ self.console.print("\n[bold]Actions:[/bold]")
165
+ self.console.print(
166
+ " [green on default][[/green on default][green bold on default] Y [/green bold on default][green on default]][/green on default] Accept "
167
+ "[yellow on default][[/yellow on default][yellow bold on default] E [/yellow bold on default][yellow on default]][/yellow on default] Edit "
168
+ "[red on default][[/red on default][red bold on default] N [/red bold on default][red on default]][/red on default] Skip"
169
+ )
170
+
171
+ choice = Prompt.ask("\nChoice", default="y", show_default=False).lower().strip()
172
+
173
+ if choice in ["y", "yes", ""]:
174
+ return (operation.suggested_filename, True)
175
+ elif choice in ["e", "edit"]:
176
+ manual_name = Prompt.ask(
177
+ "[yellow]Enter filename (without .pdf)[/yellow]",
178
+ default=operation.suggested_filename,
179
+ ).strip()
180
+ if manual_name:
181
+ return (manual_name, True)
182
+ else:
183
+ self.console.print("[red]Empty filename, try again[/red]")
184
+ continue
185
+ elif choice in ["n", "no", "skip"]:
186
+ self.console.print("[yellow]⊘ Skipped[/yellow]")
187
+ return ("", False)
188
+ else:
189
+ self.console.print(f"[red]Invalid: {choice}[/red]")
190
+ continue
191
+
192
+
193
+ class ResultsTable:
194
+ """Formats results as a table."""
195
+
196
+ @staticmethod
197
+ def create(operations: list[FileRenameOperation], console: Console) -> None:
198
+ """Create and print a results table."""
199
+ table = Table(title="Rename Suggestions")
200
+ table.add_column("Original", style="cyan", no_wrap=False)
201
+ table.add_column("Suggested", style="green", no_wrap=False)
202
+ table.add_column("Confidence", style="yellow")
203
+ table.add_column("Reasoning", style="white", no_wrap=False)
204
+
205
+ for op in operations:
206
+ reasoning = op.reasoning
207
+ if len(reasoning) > 100:
208
+ reasoning = reasoning[:100] + "..."
209
+ table.add_row(
210
+ op.original_path.name,
211
+ op.new_filename,
212
+ op.confidence.value,
213
+ reasoning,
214
+ )
215
+
216
+ console.print(table)