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.
- package/LICENSE +21 -21
- package/README.md +105 -680
- package/bin/pomera-ai-commander.js +62 -62
- package/core/__init__.py +65 -65
- package/core/app_context.py +482 -482
- package/core/async_text_processor.py +421 -421
- package/core/backup_manager.py +655 -655
- package/core/backup_recovery_manager.py +1033 -1033
- package/core/content_hash_cache.py +508 -508
- package/core/context_menu.py +313 -313
- package/core/data_validator.py +1066 -1066
- package/core/database_connection_manager.py +744 -744
- package/core/database_curl_settings_manager.py +608 -608
- package/core/database_promera_ai_settings_manager.py +446 -446
- package/core/database_schema.py +411 -411
- package/core/database_schema_manager.py +395 -395
- package/core/database_settings_manager.py +1507 -1507
- package/core/database_settings_manager_interface.py +456 -456
- package/core/dialog_manager.py +734 -734
- package/core/efficient_line_numbers.py +510 -510
- package/core/error_handler.py +746 -746
- package/core/error_service.py +431 -431
- package/core/event_consolidator.py +511 -511
- package/core/mcp/__init__.py +43 -43
- package/core/mcp/protocol.py +288 -288
- package/core/mcp/schema.py +251 -251
- package/core/mcp/server_stdio.py +299 -299
- package/core/mcp/tool_registry.py +2372 -2345
- package/core/memory_efficient_text_widget.py +711 -711
- package/core/migration_manager.py +914 -914
- package/core/migration_test_suite.py +1085 -1085
- package/core/migration_validator.py +1143 -1143
- package/core/optimized_find_replace.py +714 -714
- package/core/optimized_pattern_engine.py +424 -424
- package/core/optimized_search_highlighter.py +552 -552
- package/core/performance_monitor.py +674 -674
- package/core/persistence_manager.py +712 -712
- package/core/progressive_stats_calculator.py +632 -632
- package/core/regex_pattern_cache.py +529 -529
- package/core/regex_pattern_library.py +350 -350
- package/core/search_operation_manager.py +434 -434
- package/core/settings_defaults_registry.py +1087 -1087
- package/core/settings_integrity_validator.py +1111 -1111
- package/core/settings_serializer.py +557 -557
- package/core/settings_validator.py +1823 -1823
- package/core/smart_stats_calculator.py +709 -709
- package/core/statistics_update_manager.py +619 -619
- package/core/stats_config_manager.py +858 -858
- package/core/streaming_text_handler.py +723 -723
- package/core/task_scheduler.py +596 -596
- package/core/update_pattern_library.py +168 -168
- package/core/visibility_monitor.py +596 -596
- package/core/widget_cache.py +498 -498
- package/mcp.json +51 -61
- package/package.json +61 -57
- package/pomera.py +7482 -7482
- package/pomera_mcp_server.py +183 -144
- package/requirements.txt +32 -0
- package/tools/__init__.py +4 -4
- package/tools/ai_tools.py +2891 -2891
- package/tools/ascii_art_generator.py +352 -352
- package/tools/base64_tools.py +183 -183
- package/tools/base_tool.py +511 -511
- package/tools/case_tool.py +308 -308
- package/tools/column_tools.py +395 -395
- package/tools/cron_tool.py +884 -884
- package/tools/curl_history.py +600 -600
- package/tools/curl_processor.py +1207 -1207
- package/tools/curl_settings.py +502 -502
- package/tools/curl_tool.py +5467 -5467
- package/tools/diff_viewer.py +1071 -1071
- package/tools/email_extraction_tool.py +248 -248
- package/tools/email_header_analyzer.py +425 -425
- package/tools/extraction_tools.py +250 -250
- package/tools/find_replace.py +1750 -1750
- package/tools/folder_file_reporter.py +1463 -1463
- package/tools/folder_file_reporter_adapter.py +480 -480
- package/tools/generator_tools.py +1216 -1216
- package/tools/hash_generator.py +255 -255
- package/tools/html_tool.py +656 -656
- package/tools/jsonxml_tool.py +729 -729
- package/tools/line_tools.py +419 -419
- package/tools/markdown_tools.py +561 -561
- package/tools/mcp_widget.py +1417 -1417
- package/tools/notes_widget.py +973 -973
- package/tools/number_base_converter.py +372 -372
- package/tools/regex_extractor.py +571 -571
- package/tools/slug_generator.py +310 -310
- package/tools/sorter_tools.py +458 -458
- package/tools/string_escape_tool.py +392 -392
- package/tools/text_statistics_tool.py +365 -365
- package/tools/text_wrapper.py +430 -430
- package/tools/timestamp_converter.py +421 -421
- package/tools/tool_loader.py +710 -710
- package/tools/translator_tools.py +522 -522
- package/tools/url_link_extractor.py +261 -261
- package/tools/url_parser.py +204 -204
- package/tools/whitespace_tools.py +355 -355
- package/tools/word_frequency_counter.py +146 -146
- package/core/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/__pycache__/app_context.cpython-313.pyc +0 -0
- package/core/__pycache__/async_text_processor.cpython-313.pyc +0 -0
- package/core/__pycache__/backup_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/backup_recovery_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/content_hash_cache.cpython-313.pyc +0 -0
- package/core/__pycache__/context_menu.cpython-313.pyc +0 -0
- package/core/__pycache__/data_validator.cpython-313.pyc +0 -0
- package/core/__pycache__/database_connection_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_curl_settings_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_promera_ai_settings_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_schema.cpython-313.pyc +0 -0
- package/core/__pycache__/database_schema_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_settings_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/database_settings_manager_interface.cpython-313.pyc +0 -0
- package/core/__pycache__/dialog_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/efficient_line_numbers.cpython-313.pyc +0 -0
- package/core/__pycache__/error_handler.cpython-313.pyc +0 -0
- package/core/__pycache__/error_service.cpython-313.pyc +0 -0
- package/core/__pycache__/event_consolidator.cpython-313.pyc +0 -0
- package/core/__pycache__/memory_efficient_text_widget.cpython-313.pyc +0 -0
- package/core/__pycache__/migration_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/migration_test_suite.cpython-313.pyc +0 -0
- package/core/__pycache__/migration_validator.cpython-313.pyc +0 -0
- package/core/__pycache__/optimized_find_replace.cpython-313.pyc +0 -0
- package/core/__pycache__/optimized_pattern_engine.cpython-313.pyc +0 -0
- package/core/__pycache__/optimized_search_highlighter.cpython-313.pyc +0 -0
- package/core/__pycache__/performance_monitor.cpython-313.pyc +0 -0
- package/core/__pycache__/persistence_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/progressive_stats_calculator.cpython-313.pyc +0 -0
- package/core/__pycache__/regex_pattern_cache.cpython-313.pyc +0 -0
- package/core/__pycache__/regex_pattern_library.cpython-313.pyc +0 -0
- package/core/__pycache__/search_operation_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/settings_defaults_registry.cpython-313.pyc +0 -0
- package/core/__pycache__/settings_integrity_validator.cpython-313.pyc +0 -0
- package/core/__pycache__/settings_serializer.cpython-313.pyc +0 -0
- package/core/__pycache__/settings_validator.cpython-313.pyc +0 -0
- package/core/__pycache__/smart_stats_calculator.cpython-313.pyc +0 -0
- package/core/__pycache__/statistics_update_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/stats_config_manager.cpython-313.pyc +0 -0
- package/core/__pycache__/streaming_text_handler.cpython-313.pyc +0 -0
- package/core/__pycache__/task_scheduler.cpython-313.pyc +0 -0
- package/core/__pycache__/visibility_monitor.cpython-313.pyc +0 -0
- package/core/__pycache__/widget_cache.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/protocol.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/schema.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/server_stdio.cpython-313.pyc +0 -0
- package/core/mcp/__pycache__/tool_registry.cpython-313.pyc +0 -0
- package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- package/tools/__pycache__/ai_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/ascii_art_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/base64_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/base_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/case_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/column_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/cron_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/curl_history.cpython-313.pyc +0 -0
- package/tools/__pycache__/curl_processor.cpython-313.pyc +0 -0
- package/tools/__pycache__/curl_settings.cpython-313.pyc +0 -0
- package/tools/__pycache__/curl_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/diff_viewer.cpython-313.pyc +0 -0
- package/tools/__pycache__/email_extraction_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/email_header_analyzer.cpython-313.pyc +0 -0
- package/tools/__pycache__/extraction_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/find_replace.cpython-313.pyc +0 -0
- package/tools/__pycache__/folder_file_reporter.cpython-313.pyc +0 -0
- package/tools/__pycache__/folder_file_reporter_adapter.cpython-313.pyc +0 -0
- package/tools/__pycache__/generator_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/hash_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/html_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/huggingface_helper.cpython-313.pyc +0 -0
- package/tools/__pycache__/jsonxml_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/line_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/list_comparator.cpython-313.pyc +0 -0
- package/tools/__pycache__/markdown_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/mcp_widget.cpython-313.pyc +0 -0
- package/tools/__pycache__/notes_widget.cpython-313.pyc +0 -0
- package/tools/__pycache__/number_base_converter.cpython-313.pyc +0 -0
- package/tools/__pycache__/regex_extractor.cpython-313.pyc +0 -0
- package/tools/__pycache__/slug_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/sorter_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/string_escape_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/text_statistics_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/text_wrapper.cpython-313.pyc +0 -0
- package/tools/__pycache__/timestamp_converter.cpython-313.pyc +0 -0
- package/tools/__pycache__/tool_loader.cpython-313.pyc +0 -0
- package/tools/__pycache__/translator_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/url_link_extractor.cpython-313.pyc +0 -0
- package/tools/__pycache__/url_parser.cpython-313.pyc +0 -0
- package/tools/__pycache__/whitespace_tools.cpython-313.pyc +0 -0
- package/tools/__pycache__/word_frequency_counter.cpython-313.pyc +0 -0
package/tools/line_tools.py
CHANGED
|
@@ -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
|