pomera-ai-commander 0.1.0 → 1.2.1

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.
Files changed (191) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +105 -680
  3. package/bin/pomera-ai-commander.js +62 -62
  4. package/core/__init__.py +65 -65
  5. package/core/app_context.py +482 -482
  6. package/core/async_text_processor.py +421 -421
  7. package/core/backup_manager.py +655 -655
  8. package/core/backup_recovery_manager.py +1033 -1033
  9. package/core/content_hash_cache.py +508 -508
  10. package/core/context_menu.py +313 -313
  11. package/core/data_validator.py +1066 -1066
  12. package/core/database_connection_manager.py +744 -744
  13. package/core/database_curl_settings_manager.py +608 -608
  14. package/core/database_promera_ai_settings_manager.py +446 -446
  15. package/core/database_schema.py +411 -411
  16. package/core/database_schema_manager.py +395 -395
  17. package/core/database_settings_manager.py +1507 -1507
  18. package/core/database_settings_manager_interface.py +456 -456
  19. package/core/dialog_manager.py +734 -734
  20. package/core/efficient_line_numbers.py +510 -510
  21. package/core/error_handler.py +746 -746
  22. package/core/error_service.py +431 -431
  23. package/core/event_consolidator.py +511 -511
  24. package/core/mcp/__init__.py +43 -43
  25. package/core/mcp/protocol.py +288 -288
  26. package/core/mcp/schema.py +251 -251
  27. package/core/mcp/server_stdio.py +299 -299
  28. package/core/mcp/tool_registry.py +2372 -2345
  29. package/core/memory_efficient_text_widget.py +711 -711
  30. package/core/migration_manager.py +914 -914
  31. package/core/migration_test_suite.py +1085 -1085
  32. package/core/migration_validator.py +1143 -1143
  33. package/core/optimized_find_replace.py +714 -714
  34. package/core/optimized_pattern_engine.py +424 -424
  35. package/core/optimized_search_highlighter.py +552 -552
  36. package/core/performance_monitor.py +674 -674
  37. package/core/persistence_manager.py +712 -712
  38. package/core/progressive_stats_calculator.py +632 -632
  39. package/core/regex_pattern_cache.py +529 -529
  40. package/core/regex_pattern_library.py +350 -350
  41. package/core/search_operation_manager.py +434 -434
  42. package/core/settings_defaults_registry.py +1087 -1087
  43. package/core/settings_integrity_validator.py +1111 -1111
  44. package/core/settings_serializer.py +557 -557
  45. package/core/settings_validator.py +1823 -1823
  46. package/core/smart_stats_calculator.py +709 -709
  47. package/core/statistics_update_manager.py +619 -619
  48. package/core/stats_config_manager.py +858 -858
  49. package/core/streaming_text_handler.py +723 -723
  50. package/core/task_scheduler.py +596 -596
  51. package/core/update_pattern_library.py +168 -168
  52. package/core/visibility_monitor.py +596 -596
  53. package/core/widget_cache.py +498 -498
  54. package/mcp.json +51 -61
  55. package/package.json +61 -57
  56. package/pomera.py +7482 -7482
  57. package/pomera_mcp_server.py +183 -144
  58. package/requirements.txt +32 -0
  59. package/tools/__init__.py +4 -4
  60. package/tools/ai_tools.py +2891 -2891
  61. package/tools/ascii_art_generator.py +352 -352
  62. package/tools/base64_tools.py +183 -183
  63. package/tools/base_tool.py +511 -511
  64. package/tools/case_tool.py +308 -308
  65. package/tools/column_tools.py +395 -395
  66. package/tools/cron_tool.py +884 -884
  67. package/tools/curl_history.py +600 -600
  68. package/tools/curl_processor.py +1207 -1207
  69. package/tools/curl_settings.py +502 -502
  70. package/tools/curl_tool.py +5467 -5467
  71. package/tools/diff_viewer.py +1071 -1071
  72. package/tools/email_extraction_tool.py +248 -248
  73. package/tools/email_header_analyzer.py +425 -425
  74. package/tools/extraction_tools.py +250 -250
  75. package/tools/find_replace.py +1750 -1750
  76. package/tools/folder_file_reporter.py +1463 -1463
  77. package/tools/folder_file_reporter_adapter.py +480 -480
  78. package/tools/generator_tools.py +1216 -1216
  79. package/tools/hash_generator.py +255 -255
  80. package/tools/html_tool.py +656 -656
  81. package/tools/jsonxml_tool.py +729 -729
  82. package/tools/line_tools.py +419 -419
  83. package/tools/markdown_tools.py +561 -561
  84. package/tools/mcp_widget.py +1417 -1417
  85. package/tools/notes_widget.py +973 -973
  86. package/tools/number_base_converter.py +372 -372
  87. package/tools/regex_extractor.py +571 -571
  88. package/tools/slug_generator.py +310 -310
  89. package/tools/sorter_tools.py +458 -458
  90. package/tools/string_escape_tool.py +392 -392
  91. package/tools/text_statistics_tool.py +365 -365
  92. package/tools/text_wrapper.py +430 -430
  93. package/tools/timestamp_converter.py +421 -421
  94. package/tools/tool_loader.py +710 -710
  95. package/tools/translator_tools.py +522 -522
  96. package/tools/url_link_extractor.py +261 -261
  97. package/tools/url_parser.py +204 -204
  98. package/tools/whitespace_tools.py +355 -355
  99. package/tools/word_frequency_counter.py +146 -146
  100. package/core/__pycache__/__init__.cpython-313.pyc +0 -0
  101. package/core/__pycache__/app_context.cpython-313.pyc +0 -0
  102. package/core/__pycache__/async_text_processor.cpython-313.pyc +0 -0
  103. package/core/__pycache__/backup_manager.cpython-313.pyc +0 -0
  104. package/core/__pycache__/backup_recovery_manager.cpython-313.pyc +0 -0
  105. package/core/__pycache__/content_hash_cache.cpython-313.pyc +0 -0
  106. package/core/__pycache__/context_menu.cpython-313.pyc +0 -0
  107. package/core/__pycache__/data_validator.cpython-313.pyc +0 -0
  108. package/core/__pycache__/database_connection_manager.cpython-313.pyc +0 -0
  109. package/core/__pycache__/database_curl_settings_manager.cpython-313.pyc +0 -0
  110. package/core/__pycache__/database_promera_ai_settings_manager.cpython-313.pyc +0 -0
  111. package/core/__pycache__/database_schema.cpython-313.pyc +0 -0
  112. package/core/__pycache__/database_schema_manager.cpython-313.pyc +0 -0
  113. package/core/__pycache__/database_settings_manager.cpython-313.pyc +0 -0
  114. package/core/__pycache__/database_settings_manager_interface.cpython-313.pyc +0 -0
  115. package/core/__pycache__/dialog_manager.cpython-313.pyc +0 -0
  116. package/core/__pycache__/efficient_line_numbers.cpython-313.pyc +0 -0
  117. package/core/__pycache__/error_handler.cpython-313.pyc +0 -0
  118. package/core/__pycache__/error_service.cpython-313.pyc +0 -0
  119. package/core/__pycache__/event_consolidator.cpython-313.pyc +0 -0
  120. package/core/__pycache__/memory_efficient_text_widget.cpython-313.pyc +0 -0
  121. package/core/__pycache__/migration_manager.cpython-313.pyc +0 -0
  122. package/core/__pycache__/migration_test_suite.cpython-313.pyc +0 -0
  123. package/core/__pycache__/migration_validator.cpython-313.pyc +0 -0
  124. package/core/__pycache__/optimized_find_replace.cpython-313.pyc +0 -0
  125. package/core/__pycache__/optimized_pattern_engine.cpython-313.pyc +0 -0
  126. package/core/__pycache__/optimized_search_highlighter.cpython-313.pyc +0 -0
  127. package/core/__pycache__/performance_monitor.cpython-313.pyc +0 -0
  128. package/core/__pycache__/persistence_manager.cpython-313.pyc +0 -0
  129. package/core/__pycache__/progressive_stats_calculator.cpython-313.pyc +0 -0
  130. package/core/__pycache__/regex_pattern_cache.cpython-313.pyc +0 -0
  131. package/core/__pycache__/regex_pattern_library.cpython-313.pyc +0 -0
  132. package/core/__pycache__/search_operation_manager.cpython-313.pyc +0 -0
  133. package/core/__pycache__/settings_defaults_registry.cpython-313.pyc +0 -0
  134. package/core/__pycache__/settings_integrity_validator.cpython-313.pyc +0 -0
  135. package/core/__pycache__/settings_serializer.cpython-313.pyc +0 -0
  136. package/core/__pycache__/settings_validator.cpython-313.pyc +0 -0
  137. package/core/__pycache__/smart_stats_calculator.cpython-313.pyc +0 -0
  138. package/core/__pycache__/statistics_update_manager.cpython-313.pyc +0 -0
  139. package/core/__pycache__/stats_config_manager.cpython-313.pyc +0 -0
  140. package/core/__pycache__/streaming_text_handler.cpython-313.pyc +0 -0
  141. package/core/__pycache__/task_scheduler.cpython-313.pyc +0 -0
  142. package/core/__pycache__/visibility_monitor.cpython-313.pyc +0 -0
  143. package/core/__pycache__/widget_cache.cpython-313.pyc +0 -0
  144. package/core/mcp/__pycache__/__init__.cpython-313.pyc +0 -0
  145. package/core/mcp/__pycache__/protocol.cpython-313.pyc +0 -0
  146. package/core/mcp/__pycache__/schema.cpython-313.pyc +0 -0
  147. package/core/mcp/__pycache__/server_stdio.cpython-313.pyc +0 -0
  148. package/core/mcp/__pycache__/tool_registry.cpython-313.pyc +0 -0
  149. package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  150. package/tools/__pycache__/ai_tools.cpython-313.pyc +0 -0
  151. package/tools/__pycache__/ascii_art_generator.cpython-313.pyc +0 -0
  152. package/tools/__pycache__/base64_tools.cpython-313.pyc +0 -0
  153. package/tools/__pycache__/base_tool.cpython-313.pyc +0 -0
  154. package/tools/__pycache__/case_tool.cpython-313.pyc +0 -0
  155. package/tools/__pycache__/column_tools.cpython-313.pyc +0 -0
  156. package/tools/__pycache__/cron_tool.cpython-313.pyc +0 -0
  157. package/tools/__pycache__/curl_history.cpython-313.pyc +0 -0
  158. package/tools/__pycache__/curl_processor.cpython-313.pyc +0 -0
  159. package/tools/__pycache__/curl_settings.cpython-313.pyc +0 -0
  160. package/tools/__pycache__/curl_tool.cpython-313.pyc +0 -0
  161. package/tools/__pycache__/diff_viewer.cpython-313.pyc +0 -0
  162. package/tools/__pycache__/email_extraction_tool.cpython-313.pyc +0 -0
  163. package/tools/__pycache__/email_header_analyzer.cpython-313.pyc +0 -0
  164. package/tools/__pycache__/extraction_tools.cpython-313.pyc +0 -0
  165. package/tools/__pycache__/find_replace.cpython-313.pyc +0 -0
  166. package/tools/__pycache__/folder_file_reporter.cpython-313.pyc +0 -0
  167. package/tools/__pycache__/folder_file_reporter_adapter.cpython-313.pyc +0 -0
  168. package/tools/__pycache__/generator_tools.cpython-313.pyc +0 -0
  169. package/tools/__pycache__/hash_generator.cpython-313.pyc +0 -0
  170. package/tools/__pycache__/html_tool.cpython-313.pyc +0 -0
  171. package/tools/__pycache__/huggingface_helper.cpython-313.pyc +0 -0
  172. package/tools/__pycache__/jsonxml_tool.cpython-313.pyc +0 -0
  173. package/tools/__pycache__/line_tools.cpython-313.pyc +0 -0
  174. package/tools/__pycache__/list_comparator.cpython-313.pyc +0 -0
  175. package/tools/__pycache__/markdown_tools.cpython-313.pyc +0 -0
  176. package/tools/__pycache__/mcp_widget.cpython-313.pyc +0 -0
  177. package/tools/__pycache__/notes_widget.cpython-313.pyc +0 -0
  178. package/tools/__pycache__/number_base_converter.cpython-313.pyc +0 -0
  179. package/tools/__pycache__/regex_extractor.cpython-313.pyc +0 -0
  180. package/tools/__pycache__/slug_generator.cpython-313.pyc +0 -0
  181. package/tools/__pycache__/sorter_tools.cpython-313.pyc +0 -0
  182. package/tools/__pycache__/string_escape_tool.cpython-313.pyc +0 -0
  183. package/tools/__pycache__/text_statistics_tool.cpython-313.pyc +0 -0
  184. package/tools/__pycache__/text_wrapper.cpython-313.pyc +0 -0
  185. package/tools/__pycache__/timestamp_converter.cpython-313.pyc +0 -0
  186. package/tools/__pycache__/tool_loader.cpython-313.pyc +0 -0
  187. package/tools/__pycache__/translator_tools.cpython-313.pyc +0 -0
  188. package/tools/__pycache__/url_link_extractor.cpython-313.pyc +0 -0
  189. package/tools/__pycache__/url_parser.cpython-313.pyc +0 -0
  190. package/tools/__pycache__/whitespace_tools.cpython-313.pyc +0 -0
  191. package/tools/__pycache__/word_frequency_counter.cpython-313.pyc +0 -0
@@ -1,419 +1,419 @@
1
- """
2
- Line Tools Module - Line manipulation utilities
3
-
4
- This module provides comprehensive line manipulation functionality with a tabbed UI interface
5
- for the Pomera AI Commander application.
6
-
7
- Features:
8
- - Remove Duplicates: Remove duplicate lines (keep first/last, case-sensitive option)
9
- - Remove Empty Lines: Remove blank/whitespace-only lines
10
- - Add Line Numbers: Prefix lines with numbers
11
- - Remove Line Numbers: Strip leading numbers from lines
12
- - Reverse Lines: Reverse line order
13
- - Shuffle Lines: Randomize line order
14
- """
15
-
16
- import tkinter as tk
17
- from tkinter import ttk
18
- import random
19
- import re
20
-
21
-
22
- class LineToolsProcessor:
23
- """Line tools processor with various line manipulation capabilities."""
24
-
25
- @staticmethod
26
- def remove_duplicates(text, mode="keep_first", case_sensitive=True):
27
- """Remove duplicate lines from text."""
28
- lines = text.splitlines()
29
-
30
- if mode == "keep_first":
31
- seen = set()
32
- result = []
33
- for line in lines:
34
- key = line if case_sensitive else line.lower()
35
- if key not in seen:
36
- seen.add(key)
37
- result.append(line)
38
- else: # keep_last
39
- seen = {}
40
- for i, line in enumerate(lines):
41
- key = line if case_sensitive else line.lower()
42
- seen[key] = (i, line)
43
- result = [v[1] for v in sorted(seen.values(), key=lambda x: x[0])]
44
-
45
- return '\n'.join(result)
46
-
47
- @staticmethod
48
- def remove_empty_lines(text, preserve_single=False):
49
- """Remove empty or whitespace-only lines."""
50
- lines = text.splitlines()
51
-
52
- if preserve_single:
53
- result = []
54
- prev_empty = False
55
- for line in lines:
56
- is_empty = not line.strip()
57
- if is_empty:
58
- if not prev_empty:
59
- result.append('')
60
- prev_empty = True
61
- else:
62
- result.append(line)
63
- prev_empty = False
64
- else:
65
- result = [line for line in lines if line.strip()]
66
-
67
- return '\n'.join(result)
68
-
69
- @staticmethod
70
- def add_line_numbers(text, format_style="1. ", start_number=1, skip_empty=False):
71
- """Add line numbers to each line."""
72
- lines = text.splitlines()
73
- result = []
74
- num = start_number
75
-
76
- for line in lines:
77
- if skip_empty and not line.strip():
78
- result.append(line)
79
- else:
80
- if format_style == "1. ":
81
- prefix = f"{num}. "
82
- elif format_style == "1) ":
83
- prefix = f"{num}) "
84
- elif format_style == "[1] ":
85
- prefix = f"[{num}] "
86
- elif format_style == "1: ":
87
- prefix = f"{num}: "
88
- else:
89
- prefix = f"{num}. "
90
- result.append(f"{prefix}{line}")
91
- num += 1
92
-
93
- return '\n'.join(result)
94
-
95
- @staticmethod
96
- def remove_line_numbers(text):
97
- """Remove line numbers from the beginning of each line."""
98
- lines = text.splitlines()
99
- result = []
100
- pattern = r'^(\d+[\.\)\:]?\s*|\[\d+\]\s*)'
101
-
102
- for line in lines:
103
- result.append(re.sub(pattern, '', line))
104
-
105
- return '\n'.join(result)
106
-
107
- @staticmethod
108
- def reverse_lines(text):
109
- """Reverse the order of lines."""
110
- lines = text.splitlines()
111
- return '\n'.join(reversed(lines))
112
-
113
- @staticmethod
114
- def shuffle_lines(text):
115
- """Randomly shuffle the order of lines."""
116
- lines = text.splitlines()
117
- random.shuffle(lines)
118
- return '\n'.join(lines)
119
-
120
- @staticmethod
121
- def process_text(input_text, tool_type, settings):
122
- """Process text using the specified line tool and settings."""
123
- if tool_type == "Remove Duplicates":
124
- return LineToolsProcessor.remove_duplicates(
125
- input_text,
126
- settings.get("duplicate_mode", "keep_first"),
127
- settings.get("case_sensitive", True)
128
- )
129
- elif tool_type == "Remove Empty Lines":
130
- return LineToolsProcessor.remove_empty_lines(
131
- input_text,
132
- settings.get("preserve_single", False)
133
- )
134
- elif tool_type == "Add Line Numbers":
135
- return LineToolsProcessor.add_line_numbers(
136
- input_text,
137
- settings.get("number_format", "1. "),
138
- settings.get("start_number", 1),
139
- settings.get("skip_empty", False)
140
- )
141
- elif tool_type == "Remove Line Numbers":
142
- return LineToolsProcessor.remove_line_numbers(input_text)
143
- elif tool_type == "Reverse Lines":
144
- return LineToolsProcessor.reverse_lines(input_text)
145
- elif tool_type == "Shuffle Lines":
146
- return LineToolsProcessor.shuffle_lines(input_text)
147
- else:
148
- return f"Unknown line tool: {tool_type}"
149
-
150
-
151
- class LineToolsWidget(ttk.Frame):
152
- """Tabbed interface widget for line tools."""
153
-
154
- def __init__(self, parent, app):
155
- super().__init__(parent)
156
- self.app = app
157
- self.processor = LineToolsProcessor()
158
-
159
- self.duplicate_mode = tk.StringVar(value="keep_first")
160
- self.case_sensitive = tk.BooleanVar(value=True)
161
- self.preserve_single = tk.BooleanVar(value=False)
162
- self.number_format = tk.StringVar(value="1. ")
163
- self.start_number = tk.IntVar(value=1)
164
- self.skip_empty = tk.BooleanVar(value=False)
165
-
166
- self.create_widgets()
167
- self.load_settings()
168
-
169
- def create_widgets(self):
170
- """Creates the tabbed interface for line tools."""
171
- self.notebook = ttk.Notebook(self)
172
- self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
173
-
174
- self.create_remove_duplicates_tab()
175
- self.create_remove_empty_tab()
176
- self.create_add_numbers_tab()
177
- self.create_remove_numbers_tab()
178
- self.create_reverse_tab()
179
- self.create_shuffle_tab()
180
-
181
- def create_remove_duplicates_tab(self):
182
- """Creates the Remove Duplicates tab."""
183
- frame = ttk.Frame(self.notebook)
184
- self.notebook.add(frame, text="Remove Duplicates")
185
-
186
- mode_frame = ttk.LabelFrame(frame, text="Duplicate Handling", padding=10)
187
- mode_frame.pack(fill=tk.X, padx=5, pady=5)
188
-
189
- ttk.Radiobutton(mode_frame, text="Keep First Occurrence",
190
- variable=self.duplicate_mode, value="keep_first",
191
- command=self.on_setting_change).pack(anchor=tk.W)
192
- ttk.Radiobutton(mode_frame, text="Keep Last Occurrence",
193
- variable=self.duplicate_mode, value="keep_last",
194
- command=self.on_setting_change).pack(anchor=tk.W)
195
-
196
- ttk.Checkbutton(frame, text="Case Sensitive",
197
- variable=self.case_sensitive,
198
- command=self.on_setting_change).pack(anchor=tk.W, padx=5, pady=5)
199
-
200
- ttk.Button(frame, text="Remove Duplicates",
201
- command=lambda: self.process("Remove Duplicates")).pack(pady=10)
202
-
203
- def create_remove_empty_tab(self):
204
- """Creates the Remove Empty Lines tab."""
205
- frame = ttk.Frame(self.notebook)
206
- self.notebook.add(frame, text="Remove Empty")
207
-
208
- options_frame = ttk.LabelFrame(frame, text="Options", padding=10)
209
- options_frame.pack(fill=tk.X, padx=5, pady=5)
210
-
211
- ttk.Checkbutton(options_frame, text="Preserve Single Empty Lines (collapse multiple)",
212
- variable=self.preserve_single,
213
- command=self.on_setting_change).pack(anchor=tk.W)
214
-
215
- ttk.Button(frame, text="Remove Empty Lines",
216
- command=lambda: self.process("Remove Empty Lines")).pack(pady=10)
217
-
218
- def create_add_numbers_tab(self):
219
- """Creates the Add Line Numbers tab."""
220
- frame = ttk.Frame(self.notebook)
221
- self.notebook.add(frame, text="Add Numbers")
222
-
223
- format_frame = ttk.LabelFrame(frame, text="Number Format", padding=10)
224
- format_frame.pack(fill=tk.X, padx=5, pady=5)
225
-
226
- formats = [("1. (dot)", "1. "), ("1) (parenthesis)", "1) "),
227
- ("[1] (brackets)", "[1] "), ("1: (colon)", "1: ")]
228
- for text, value in formats:
229
- ttk.Radiobutton(format_frame, text=text,
230
- variable=self.number_format, value=value,
231
- command=self.on_setting_change).pack(anchor=tk.W)
232
-
233
- start_frame = ttk.Frame(frame)
234
- start_frame.pack(fill=tk.X, padx=5, pady=5)
235
- ttk.Label(start_frame, text="Start Number:").pack(side=tk.LEFT)
236
- ttk.Spinbox(start_frame, from_=0, to=9999, width=6,
237
- textvariable=self.start_number,
238
- command=self.on_setting_change).pack(side=tk.LEFT, padx=5)
239
-
240
- ttk.Checkbutton(frame, text="Skip Empty Lines",
241
- variable=self.skip_empty,
242
- command=self.on_setting_change).pack(anchor=tk.W, padx=5)
243
-
244
- ttk.Button(frame, text="Add Line Numbers",
245
- command=lambda: self.process("Add Line Numbers")).pack(pady=10)
246
-
247
- def create_remove_numbers_tab(self):
248
- """Creates the Remove Line Numbers tab."""
249
- frame = ttk.Frame(self.notebook)
250
- self.notebook.add(frame, text="Remove Numbers")
251
-
252
- info = ttk.Label(frame, text="Removes line numbers from the beginning of each line.\n"
253
- "Supports formats: 1. , 1) , [1] , 1: , 1 ",
254
- justify=tk.CENTER)
255
- info.pack(pady=20)
256
-
257
- ttk.Button(frame, text="Remove Line Numbers",
258
- command=lambda: self.process("Remove Line Numbers")).pack(pady=10)
259
-
260
- def create_reverse_tab(self):
261
- """Creates the Reverse Lines tab."""
262
- frame = ttk.Frame(self.notebook)
263
- self.notebook.add(frame, text="Reverse")
264
-
265
- info = ttk.Label(frame, text="Reverses the order of all lines in the text.",
266
- justify=tk.CENTER)
267
- info.pack(pady=20)
268
-
269
- ttk.Button(frame, text="Reverse Lines",
270
- command=lambda: self.process("Reverse Lines")).pack(pady=10)
271
-
272
- def create_shuffle_tab(self):
273
- """Creates the Shuffle Lines tab."""
274
- frame = ttk.Frame(self.notebook)
275
- self.notebook.add(frame, text="Shuffle")
276
-
277
- info = ttk.Label(frame, text="Randomly shuffles the order of all lines.",
278
- justify=tk.CENTER)
279
- info.pack(pady=20)
280
-
281
- ttk.Button(frame, text="Shuffle Lines",
282
- command=lambda: self.process("Shuffle Lines")).pack(pady=10)
283
-
284
- def load_settings(self):
285
- """Load settings from the application."""
286
- settings = self.app.settings.get("tool_settings", {}).get("Line Tools", {})
287
-
288
- self.duplicate_mode.set(settings.get("duplicate_mode", "keep_first"))
289
- self.case_sensitive.set(settings.get("case_sensitive", True))
290
- self.preserve_single.set(settings.get("preserve_single", False))
291
- self.number_format.set(settings.get("number_format", "1. "))
292
- self.start_number.set(settings.get("start_number", 1))
293
- self.skip_empty.set(settings.get("skip_empty", False))
294
-
295
- def save_settings(self):
296
- """Save current settings to the application."""
297
- if "Line Tools" not in self.app.settings["tool_settings"]:
298
- self.app.settings["tool_settings"]["Line Tools"] = {}
299
-
300
- self.app.settings["tool_settings"]["Line Tools"].update({
301
- "duplicate_mode": self.duplicate_mode.get(),
302
- "case_sensitive": self.case_sensitive.get(),
303
- "preserve_single": self.preserve_single.get(),
304
- "number_format": self.number_format.get(),
305
- "start_number": self.start_number.get(),
306
- "skip_empty": self.skip_empty.get()
307
- })
308
-
309
- self.app.save_settings()
310
-
311
- def on_setting_change(self, *args):
312
- """Handle setting changes."""
313
- self.save_settings()
314
-
315
- def process(self, tool_type):
316
- """Process the input text with the selected tool."""
317
- active_input_tab = self.app.input_tabs[self.app.input_notebook.index(self.app.input_notebook.select())]
318
- input_text = active_input_tab.text.get("1.0", tk.END).rstrip('\n')
319
-
320
- if not input_text.strip():
321
- return
322
-
323
- settings = {
324
- "duplicate_mode": self.duplicate_mode.get(),
325
- "case_sensitive": self.case_sensitive.get(),
326
- "preserve_single": self.preserve_single.get(),
327
- "number_format": self.number_format.get(),
328
- "start_number": self.start_number.get(),
329
- "skip_empty": self.skip_empty.get()
330
- }
331
-
332
- result = LineToolsProcessor.process_text(input_text, tool_type, settings)
333
-
334
- active_output_tab = self.app.output_tabs[self.app.output_notebook.index(self.app.output_notebook.select())]
335
- active_output_tab.text.config(state="normal")
336
- active_output_tab.text.delete("1.0", tk.END)
337
- active_output_tab.text.insert("1.0", result)
338
- active_output_tab.text.config(state="disabled")
339
-
340
- self.app.update_all_stats()
341
-
342
-
343
- class LineTools:
344
- """Main class for Line Tools integration."""
345
-
346
- def __init__(self):
347
- self.processor = LineToolsProcessor()
348
-
349
- def create_widget(self, parent, app):
350
- """Create and return the Line Tools widget."""
351
- return LineToolsWidget(parent, app)
352
-
353
- def get_default_settings(self):
354
- """Return default settings for Line Tools."""
355
- return {
356
- "duplicate_mode": "keep_first",
357
- "case_sensitive": True,
358
- "preserve_single": False,
359
- "number_format": "1. ",
360
- "start_number": 1,
361
- "skip_empty": False
362
- }
363
-
364
- def process_text(self, input_text, tool_type, settings):
365
- """Process text using the specified tool and settings."""
366
- return LineToolsProcessor.process_text(input_text, tool_type, settings)
367
-
368
-
369
- # BaseTool-compatible wrapper
370
- try:
371
- from tools.base_tool import ToolWithOptions
372
- from typing import Dict, Any
373
-
374
- class LineToolsV2(ToolWithOptions):
375
- """
376
- BaseTool-compatible version of LineTools.
377
- """
378
-
379
- TOOL_NAME = "Line Tools"
380
- TOOL_DESCRIPTION = "Line manipulation: remove duplicates, add numbers, reverse, shuffle"
381
- TOOL_VERSION = "2.0.0"
382
-
383
- OPTIONS = [
384
- ("Remove Duplicates", "remove_duplicates"),
385
- ("Remove Empty Lines", "remove_empty"),
386
- ("Add Line Numbers", "add_numbers"),
387
- ("Remove Line Numbers", "remove_numbers"),
388
- ("Reverse Lines", "reverse"),
389
- ("Shuffle Lines", "shuffle"),
390
- ]
391
- OPTIONS_LABEL = "Operation"
392
- USE_DROPDOWN = True
393
- DEFAULT_OPTION = "remove_duplicates"
394
-
395
- def __init__(self):
396
- super().__init__()
397
- self._processor = LineToolsProcessor()
398
-
399
- def process_text(self, input_text: str, settings: Dict[str, Any]) -> str:
400
- """Process text using the specified line operation."""
401
- mode = settings.get("mode", "remove_duplicates")
402
-
403
- if mode == "remove_duplicates":
404
- return LineToolsProcessor.remove_duplicates(input_text)
405
- elif mode == "remove_empty":
406
- return LineToolsProcessor.remove_empty_lines(input_text)
407
- elif mode == "add_numbers":
408
- return LineToolsProcessor.add_line_numbers(input_text)
409
- elif mode == "remove_numbers":
410
- return LineToolsProcessor.remove_line_numbers(input_text)
411
- elif mode == "reverse":
412
- return LineToolsProcessor.reverse_lines(input_text)
413
- elif mode == "shuffle":
414
- return LineToolsProcessor.shuffle_lines(input_text)
415
- else:
416
- return input_text
417
-
418
- except ImportError:
419
- pass
1
+ """
2
+ Line Tools Module - Line manipulation utilities
3
+
4
+ This module provides comprehensive line manipulation functionality with a tabbed UI interface
5
+ for the Pomera AI Commander application.
6
+
7
+ Features:
8
+ - Remove Duplicates: Remove duplicate lines (keep first/last, case-sensitive option)
9
+ - Remove Empty Lines: Remove blank/whitespace-only lines
10
+ - Add Line Numbers: Prefix lines with numbers
11
+ - Remove Line Numbers: Strip leading numbers from lines
12
+ - Reverse Lines: Reverse line order
13
+ - Shuffle Lines: Randomize line order
14
+ """
15
+
16
+ import tkinter as tk
17
+ from tkinter import ttk
18
+ import random
19
+ import re
20
+
21
+
22
+ class LineToolsProcessor:
23
+ """Line tools processor with various line manipulation capabilities."""
24
+
25
+ @staticmethod
26
+ def remove_duplicates(text, mode="keep_first", case_sensitive=True):
27
+ """Remove duplicate lines from text."""
28
+ lines = text.splitlines()
29
+
30
+ if mode == "keep_first":
31
+ seen = set()
32
+ result = []
33
+ for line in lines:
34
+ key = line if case_sensitive else line.lower()
35
+ if key not in seen:
36
+ seen.add(key)
37
+ result.append(line)
38
+ else: # keep_last
39
+ seen = {}
40
+ for i, line in enumerate(lines):
41
+ key = line if case_sensitive else line.lower()
42
+ seen[key] = (i, line)
43
+ result = [v[1] for v in sorted(seen.values(), key=lambda x: x[0])]
44
+
45
+ return '\n'.join(result)
46
+
47
+ @staticmethod
48
+ def remove_empty_lines(text, preserve_single=False):
49
+ """Remove empty or whitespace-only lines."""
50
+ lines = text.splitlines()
51
+
52
+ if preserve_single:
53
+ result = []
54
+ prev_empty = False
55
+ for line in lines:
56
+ is_empty = not line.strip()
57
+ if is_empty:
58
+ if not prev_empty:
59
+ result.append('')
60
+ prev_empty = True
61
+ else:
62
+ result.append(line)
63
+ prev_empty = False
64
+ else:
65
+ result = [line for line in lines if line.strip()]
66
+
67
+ return '\n'.join(result)
68
+
69
+ @staticmethod
70
+ def add_line_numbers(text, format_style="1. ", start_number=1, skip_empty=False):
71
+ """Add line numbers to each line."""
72
+ lines = text.splitlines()
73
+ result = []
74
+ num = start_number
75
+
76
+ for line in lines:
77
+ if skip_empty and not line.strip():
78
+ result.append(line)
79
+ else:
80
+ if format_style == "1. ":
81
+ prefix = f"{num}. "
82
+ elif format_style == "1) ":
83
+ prefix = f"{num}) "
84
+ elif format_style == "[1] ":
85
+ prefix = f"[{num}] "
86
+ elif format_style == "1: ":
87
+ prefix = f"{num}: "
88
+ else:
89
+ prefix = f"{num}. "
90
+ result.append(f"{prefix}{line}")
91
+ num += 1
92
+
93
+ return '\n'.join(result)
94
+
95
+ @staticmethod
96
+ def remove_line_numbers(text):
97
+ """Remove line numbers from the beginning of each line."""
98
+ lines = text.splitlines()
99
+ result = []
100
+ pattern = r'^(\d+[\.\)\:]?\s*|\[\d+\]\s*)'
101
+
102
+ for line in lines:
103
+ result.append(re.sub(pattern, '', line))
104
+
105
+ return '\n'.join(result)
106
+
107
+ @staticmethod
108
+ def reverse_lines(text):
109
+ """Reverse the order of lines."""
110
+ lines = text.splitlines()
111
+ return '\n'.join(reversed(lines))
112
+
113
+ @staticmethod
114
+ def shuffle_lines(text):
115
+ """Randomly shuffle the order of lines."""
116
+ lines = text.splitlines()
117
+ random.shuffle(lines)
118
+ return '\n'.join(lines)
119
+
120
+ @staticmethod
121
+ def process_text(input_text, tool_type, settings):
122
+ """Process text using the specified line tool and settings."""
123
+ if tool_type == "Remove Duplicates":
124
+ return LineToolsProcessor.remove_duplicates(
125
+ input_text,
126
+ settings.get("duplicate_mode", "keep_first"),
127
+ settings.get("case_sensitive", True)
128
+ )
129
+ elif tool_type == "Remove Empty Lines":
130
+ return LineToolsProcessor.remove_empty_lines(
131
+ input_text,
132
+ settings.get("preserve_single", False)
133
+ )
134
+ elif tool_type == "Add Line Numbers":
135
+ return LineToolsProcessor.add_line_numbers(
136
+ input_text,
137
+ settings.get("number_format", "1. "),
138
+ settings.get("start_number", 1),
139
+ settings.get("skip_empty", False)
140
+ )
141
+ elif tool_type == "Remove Line Numbers":
142
+ return LineToolsProcessor.remove_line_numbers(input_text)
143
+ elif tool_type == "Reverse Lines":
144
+ return LineToolsProcessor.reverse_lines(input_text)
145
+ elif tool_type == "Shuffle Lines":
146
+ return LineToolsProcessor.shuffle_lines(input_text)
147
+ else:
148
+ return f"Unknown line tool: {tool_type}"
149
+
150
+
151
+ class LineToolsWidget(ttk.Frame):
152
+ """Tabbed interface widget for line tools."""
153
+
154
+ def __init__(self, parent, app):
155
+ super().__init__(parent)
156
+ self.app = app
157
+ self.processor = LineToolsProcessor()
158
+
159
+ self.duplicate_mode = tk.StringVar(value="keep_first")
160
+ self.case_sensitive = tk.BooleanVar(value=True)
161
+ self.preserve_single = tk.BooleanVar(value=False)
162
+ self.number_format = tk.StringVar(value="1. ")
163
+ self.start_number = tk.IntVar(value=1)
164
+ self.skip_empty = tk.BooleanVar(value=False)
165
+
166
+ self.create_widgets()
167
+ self.load_settings()
168
+
169
+ def create_widgets(self):
170
+ """Creates the tabbed interface for line tools."""
171
+ self.notebook = ttk.Notebook(self)
172
+ self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
173
+
174
+ self.create_remove_duplicates_tab()
175
+ self.create_remove_empty_tab()
176
+ self.create_add_numbers_tab()
177
+ self.create_remove_numbers_tab()
178
+ self.create_reverse_tab()
179
+ self.create_shuffle_tab()
180
+
181
+ def create_remove_duplicates_tab(self):
182
+ """Creates the Remove Duplicates tab."""
183
+ frame = ttk.Frame(self.notebook)
184
+ self.notebook.add(frame, text="Remove Duplicates")
185
+
186
+ mode_frame = ttk.LabelFrame(frame, text="Duplicate Handling", padding=10)
187
+ mode_frame.pack(fill=tk.X, padx=5, pady=5)
188
+
189
+ ttk.Radiobutton(mode_frame, text="Keep First Occurrence",
190
+ variable=self.duplicate_mode, value="keep_first",
191
+ command=self.on_setting_change).pack(anchor=tk.W)
192
+ ttk.Radiobutton(mode_frame, text="Keep Last Occurrence",
193
+ variable=self.duplicate_mode, value="keep_last",
194
+ command=self.on_setting_change).pack(anchor=tk.W)
195
+
196
+ ttk.Checkbutton(frame, text="Case Sensitive",
197
+ variable=self.case_sensitive,
198
+ command=self.on_setting_change).pack(anchor=tk.W, padx=5, pady=5)
199
+
200
+ ttk.Button(frame, text="Remove Duplicates",
201
+ command=lambda: self.process("Remove Duplicates")).pack(pady=10)
202
+
203
+ def create_remove_empty_tab(self):
204
+ """Creates the Remove Empty Lines tab."""
205
+ frame = ttk.Frame(self.notebook)
206
+ self.notebook.add(frame, text="Remove Empty")
207
+
208
+ options_frame = ttk.LabelFrame(frame, text="Options", padding=10)
209
+ options_frame.pack(fill=tk.X, padx=5, pady=5)
210
+
211
+ ttk.Checkbutton(options_frame, text="Preserve Single Empty Lines (collapse multiple)",
212
+ variable=self.preserve_single,
213
+ command=self.on_setting_change).pack(anchor=tk.W)
214
+
215
+ ttk.Button(frame, text="Remove Empty Lines",
216
+ command=lambda: self.process("Remove Empty Lines")).pack(pady=10)
217
+
218
+ def create_add_numbers_tab(self):
219
+ """Creates the Add Line Numbers tab."""
220
+ frame = ttk.Frame(self.notebook)
221
+ self.notebook.add(frame, text="Add Numbers")
222
+
223
+ format_frame = ttk.LabelFrame(frame, text="Number Format", padding=10)
224
+ format_frame.pack(fill=tk.X, padx=5, pady=5)
225
+
226
+ formats = [("1. (dot)", "1. "), ("1) (parenthesis)", "1) "),
227
+ ("[1] (brackets)", "[1] "), ("1: (colon)", "1: ")]
228
+ for text, value in formats:
229
+ ttk.Radiobutton(format_frame, text=text,
230
+ variable=self.number_format, value=value,
231
+ command=self.on_setting_change).pack(anchor=tk.W)
232
+
233
+ start_frame = ttk.Frame(frame)
234
+ start_frame.pack(fill=tk.X, padx=5, pady=5)
235
+ ttk.Label(start_frame, text="Start Number:").pack(side=tk.LEFT)
236
+ ttk.Spinbox(start_frame, from_=0, to=9999, width=6,
237
+ textvariable=self.start_number,
238
+ command=self.on_setting_change).pack(side=tk.LEFT, padx=5)
239
+
240
+ ttk.Checkbutton(frame, text="Skip Empty Lines",
241
+ variable=self.skip_empty,
242
+ command=self.on_setting_change).pack(anchor=tk.W, padx=5)
243
+
244
+ ttk.Button(frame, text="Add Line Numbers",
245
+ command=lambda: self.process("Add Line Numbers")).pack(pady=10)
246
+
247
+ def create_remove_numbers_tab(self):
248
+ """Creates the Remove Line Numbers tab."""
249
+ frame = ttk.Frame(self.notebook)
250
+ self.notebook.add(frame, text="Remove Numbers")
251
+
252
+ info = ttk.Label(frame, text="Removes line numbers from the beginning of each line.\n"
253
+ "Supports formats: 1. , 1) , [1] , 1: , 1 ",
254
+ justify=tk.CENTER)
255
+ info.pack(pady=20)
256
+
257
+ ttk.Button(frame, text="Remove Line Numbers",
258
+ command=lambda: self.process("Remove Line Numbers")).pack(pady=10)
259
+
260
+ def create_reverse_tab(self):
261
+ """Creates the Reverse Lines tab."""
262
+ frame = ttk.Frame(self.notebook)
263
+ self.notebook.add(frame, text="Reverse")
264
+
265
+ info = ttk.Label(frame, text="Reverses the order of all lines in the text.",
266
+ justify=tk.CENTER)
267
+ info.pack(pady=20)
268
+
269
+ ttk.Button(frame, text="Reverse Lines",
270
+ command=lambda: self.process("Reverse Lines")).pack(pady=10)
271
+
272
+ def create_shuffle_tab(self):
273
+ """Creates the Shuffle Lines tab."""
274
+ frame = ttk.Frame(self.notebook)
275
+ self.notebook.add(frame, text="Shuffle")
276
+
277
+ info = ttk.Label(frame, text="Randomly shuffles the order of all lines.",
278
+ justify=tk.CENTER)
279
+ info.pack(pady=20)
280
+
281
+ ttk.Button(frame, text="Shuffle Lines",
282
+ command=lambda: self.process("Shuffle Lines")).pack(pady=10)
283
+
284
+ def load_settings(self):
285
+ """Load settings from the application."""
286
+ settings = self.app.settings.get("tool_settings", {}).get("Line Tools", {})
287
+
288
+ self.duplicate_mode.set(settings.get("duplicate_mode", "keep_first"))
289
+ self.case_sensitive.set(settings.get("case_sensitive", True))
290
+ self.preserve_single.set(settings.get("preserve_single", False))
291
+ self.number_format.set(settings.get("number_format", "1. "))
292
+ self.start_number.set(settings.get("start_number", 1))
293
+ self.skip_empty.set(settings.get("skip_empty", False))
294
+
295
+ def save_settings(self):
296
+ """Save current settings to the application."""
297
+ if "Line Tools" not in self.app.settings["tool_settings"]:
298
+ self.app.settings["tool_settings"]["Line Tools"] = {}
299
+
300
+ self.app.settings["tool_settings"]["Line Tools"].update({
301
+ "duplicate_mode": self.duplicate_mode.get(),
302
+ "case_sensitive": self.case_sensitive.get(),
303
+ "preserve_single": self.preserve_single.get(),
304
+ "number_format": self.number_format.get(),
305
+ "start_number": self.start_number.get(),
306
+ "skip_empty": self.skip_empty.get()
307
+ })
308
+
309
+ self.app.save_settings()
310
+
311
+ def on_setting_change(self, *args):
312
+ """Handle setting changes."""
313
+ self.save_settings()
314
+
315
+ def process(self, tool_type):
316
+ """Process the input text with the selected tool."""
317
+ active_input_tab = self.app.input_tabs[self.app.input_notebook.index(self.app.input_notebook.select())]
318
+ input_text = active_input_tab.text.get("1.0", tk.END).rstrip('\n')
319
+
320
+ if not input_text.strip():
321
+ return
322
+
323
+ settings = {
324
+ "duplicate_mode": self.duplicate_mode.get(),
325
+ "case_sensitive": self.case_sensitive.get(),
326
+ "preserve_single": self.preserve_single.get(),
327
+ "number_format": self.number_format.get(),
328
+ "start_number": self.start_number.get(),
329
+ "skip_empty": self.skip_empty.get()
330
+ }
331
+
332
+ result = LineToolsProcessor.process_text(input_text, tool_type, settings)
333
+
334
+ active_output_tab = self.app.output_tabs[self.app.output_notebook.index(self.app.output_notebook.select())]
335
+ active_output_tab.text.config(state="normal")
336
+ active_output_tab.text.delete("1.0", tk.END)
337
+ active_output_tab.text.insert("1.0", result)
338
+ active_output_tab.text.config(state="disabled")
339
+
340
+ self.app.update_all_stats()
341
+
342
+
343
+ class LineTools:
344
+ """Main class for Line Tools integration."""
345
+
346
+ def __init__(self):
347
+ self.processor = LineToolsProcessor()
348
+
349
+ def create_widget(self, parent, app):
350
+ """Create and return the Line Tools widget."""
351
+ return LineToolsWidget(parent, app)
352
+
353
+ def get_default_settings(self):
354
+ """Return default settings for Line Tools."""
355
+ return {
356
+ "duplicate_mode": "keep_first",
357
+ "case_sensitive": True,
358
+ "preserve_single": False,
359
+ "number_format": "1. ",
360
+ "start_number": 1,
361
+ "skip_empty": False
362
+ }
363
+
364
+ def process_text(self, input_text, tool_type, settings):
365
+ """Process text using the specified tool and settings."""
366
+ return LineToolsProcessor.process_text(input_text, tool_type, settings)
367
+
368
+
369
+ # BaseTool-compatible wrapper
370
+ try:
371
+ from tools.base_tool import ToolWithOptions
372
+ from typing import Dict, Any
373
+
374
+ class LineToolsV2(ToolWithOptions):
375
+ """
376
+ BaseTool-compatible version of LineTools.
377
+ """
378
+
379
+ TOOL_NAME = "Line Tools"
380
+ TOOL_DESCRIPTION = "Line manipulation: remove duplicates, add numbers, reverse, shuffle"
381
+ TOOL_VERSION = "2.0.0"
382
+
383
+ OPTIONS = [
384
+ ("Remove Duplicates", "remove_duplicates"),
385
+ ("Remove Empty Lines", "remove_empty"),
386
+ ("Add Line Numbers", "add_numbers"),
387
+ ("Remove Line Numbers", "remove_numbers"),
388
+ ("Reverse Lines", "reverse"),
389
+ ("Shuffle Lines", "shuffle"),
390
+ ]
391
+ OPTIONS_LABEL = "Operation"
392
+ USE_DROPDOWN = True
393
+ DEFAULT_OPTION = "remove_duplicates"
394
+
395
+ def __init__(self):
396
+ super().__init__()
397
+ self._processor = LineToolsProcessor()
398
+
399
+ def process_text(self, input_text: str, settings: Dict[str, Any]) -> str:
400
+ """Process text using the specified line operation."""
401
+ mode = settings.get("mode", "remove_duplicates")
402
+
403
+ if mode == "remove_duplicates":
404
+ return LineToolsProcessor.remove_duplicates(input_text)
405
+ elif mode == "remove_empty":
406
+ return LineToolsProcessor.remove_empty_lines(input_text)
407
+ elif mode == "add_numbers":
408
+ return LineToolsProcessor.add_line_numbers(input_text)
409
+ elif mode == "remove_numbers":
410
+ return LineToolsProcessor.remove_line_numbers(input_text)
411
+ elif mode == "reverse":
412
+ return LineToolsProcessor.reverse_lines(input_text)
413
+ elif mode == "shuffle":
414
+ return LineToolsProcessor.shuffle_lines(input_text)
415
+ else:
416
+ return input_text
417
+
418
+ except ImportError:
419
+ pass