pomera-ai-commander 1.1.1 → 1.2.2
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 +1199 -1033
- package/core/content_hash_cache.py +508 -508
- package/core/context_menu.py +313 -313
- package/core/data_directory.py +549 -0
- 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/diff_utils.py +239 -0
- package/core/efficient_line_numbers.py +540 -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/find_replace_diff.py +334 -0
- 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 +2699 -2345
- package/core/memento.py +275 -0
- 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/migrate_data.py +127 -0
- package/package.json +64 -57
- package/pomera.py +7883 -7482
- package/pomera_mcp_server.py +183 -144
- package/requirements.txt +33 -0
- package/scripts/Dockerfile.alpine +43 -0
- package/scripts/Dockerfile.gui-test +54 -0
- package/scripts/Dockerfile.linux +43 -0
- package/scripts/Dockerfile.test-linux +80 -0
- package/scripts/Dockerfile.ubuntu +39 -0
- package/scripts/README.md +53 -0
- package/scripts/build-all.bat +113 -0
- package/scripts/build-docker.bat +53 -0
- package/scripts/build-docker.sh +55 -0
- package/scripts/build-optimized.bat +101 -0
- package/scripts/build.sh +78 -0
- package/scripts/docker-compose.test.yml +27 -0
- package/scripts/docker-compose.yml +32 -0
- package/scripts/postinstall.js +62 -0
- package/scripts/requirements-minimal.txt +33 -0
- package/scripts/test-linux-simple.bat +28 -0
- package/scripts/validate-release-workflow.py +450 -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 +1817 -1072
- 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 +2289 -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 +978 -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/regex_extractor.py
CHANGED
|
@@ -1,572 +1,572 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Regex Extractor Module - Regex pattern extraction utility
|
|
3
|
-
|
|
4
|
-
This module provides regex pattern extraction functionality with UI components
|
|
5
|
-
for the Promera AI Commander application.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import tkinter as tk
|
|
9
|
-
from tkinter import ttk
|
|
10
|
-
import re
|
|
11
|
-
from collections import Counter
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class RegexExtractorProcessor:
|
|
15
|
-
"""Regex extractor processor that extracts matches from text using regex patterns."""
|
|
16
|
-
|
|
17
|
-
@staticmethod
|
|
18
|
-
def extract_matches(text, pattern, match_mode="all_per_line", omit_duplicates=False, hide_counts=True, sort_results=False, case_sensitive=False):
|
|
19
|
-
"""
|
|
20
|
-
Extract matches from text using a regex pattern.
|
|
21
|
-
|
|
22
|
-
Args:
|
|
23
|
-
text: Input text to search
|
|
24
|
-
pattern: Regex pattern to search for
|
|
25
|
-
match_mode: "first_per_line" to match only first occurrence per line, "all_per_line" to match all occurrences per line
|
|
26
|
-
omit_duplicates: If True, only return unique matches
|
|
27
|
-
hide_counts: If True, don't show match counts
|
|
28
|
-
sort_results: If True, sort the results
|
|
29
|
-
case_sensitive: If True, perform case-sensitive matching
|
|
30
|
-
|
|
31
|
-
Returns:
|
|
32
|
-
String containing extracted matches (one per line) or error message
|
|
33
|
-
"""
|
|
34
|
-
if not pattern or not pattern.strip():
|
|
35
|
-
return "Please enter a regex pattern in the Find field."
|
|
36
|
-
|
|
37
|
-
try:
|
|
38
|
-
# Compile the regex pattern
|
|
39
|
-
flags = 0 if case_sensitive else re.IGNORECASE
|
|
40
|
-
regex = re.compile(pattern, flags)
|
|
41
|
-
|
|
42
|
-
processed_matches = []
|
|
43
|
-
|
|
44
|
-
# Process based on match mode
|
|
45
|
-
if match_mode == "first_per_line":
|
|
46
|
-
# Process line by line, taking only the first match per line
|
|
47
|
-
lines = text.split('\n')
|
|
48
|
-
for line in lines:
|
|
49
|
-
matches = regex.findall(line)
|
|
50
|
-
if matches:
|
|
51
|
-
# Take only the first match from this line
|
|
52
|
-
match = matches[0]
|
|
53
|
-
if isinstance(match, tuple):
|
|
54
|
-
# Join tuple elements with a separator
|
|
55
|
-
processed_matches.append(' | '.join(str(m) if m else '' for m in match))
|
|
56
|
-
else:
|
|
57
|
-
processed_matches.append(str(match))
|
|
58
|
-
else:
|
|
59
|
-
# Match all occurrences (original behavior)
|
|
60
|
-
matches = regex.findall(text)
|
|
61
|
-
|
|
62
|
-
if not matches:
|
|
63
|
-
return "No matches found for the regex pattern."
|
|
64
|
-
|
|
65
|
-
# Handle different match types (strings vs tuples)
|
|
66
|
-
# If pattern has groups, findall returns tuples, otherwise strings
|
|
67
|
-
for match in matches:
|
|
68
|
-
if isinstance(match, tuple):
|
|
69
|
-
# Join tuple elements with a separator
|
|
70
|
-
processed_matches.append(' | '.join(str(m) if m else '' for m in match))
|
|
71
|
-
else:
|
|
72
|
-
processed_matches.append(str(match))
|
|
73
|
-
|
|
74
|
-
if not processed_matches:
|
|
75
|
-
return "No matches found for the regex pattern."
|
|
76
|
-
|
|
77
|
-
# Count occurrences
|
|
78
|
-
match_counts = Counter(processed_matches)
|
|
79
|
-
|
|
80
|
-
# Get unique matches if omit_duplicates is True
|
|
81
|
-
if omit_duplicates:
|
|
82
|
-
unique_matches = list(match_counts.keys())
|
|
83
|
-
if sort_results:
|
|
84
|
-
unique_matches.sort()
|
|
85
|
-
|
|
86
|
-
# Format output
|
|
87
|
-
if hide_counts:
|
|
88
|
-
return '\n'.join(unique_matches)
|
|
89
|
-
else:
|
|
90
|
-
# When omit_duplicates=True, show count as (1) for all
|
|
91
|
-
return '\n'.join([f"{match} (1)" for match in unique_matches])
|
|
92
|
-
else:
|
|
93
|
-
# Keep all matches including duplicates
|
|
94
|
-
if sort_results:
|
|
95
|
-
processed_matches.sort()
|
|
96
|
-
|
|
97
|
-
if hide_counts:
|
|
98
|
-
return '\n'.join(processed_matches)
|
|
99
|
-
else:
|
|
100
|
-
# Show actual counts for each unique match
|
|
101
|
-
result = []
|
|
102
|
-
processed = set()
|
|
103
|
-
for match in processed_matches:
|
|
104
|
-
if match not in processed:
|
|
105
|
-
result.append(f"{match} ({match_counts[match]})")
|
|
106
|
-
processed.add(match)
|
|
107
|
-
|
|
108
|
-
if sort_results:
|
|
109
|
-
result.sort()
|
|
110
|
-
|
|
111
|
-
return '\n'.join(result)
|
|
112
|
-
|
|
113
|
-
except re.error as e:
|
|
114
|
-
return f"Regex Error: {str(e)}\n\nPlease check your regex pattern syntax."
|
|
115
|
-
except Exception as e:
|
|
116
|
-
return f"Error: {str(e)}"
|
|
117
|
-
|
|
118
|
-
@staticmethod
|
|
119
|
-
def process_text(input_text, settings):
|
|
120
|
-
"""Process text using the current settings."""
|
|
121
|
-
return RegexExtractorProcessor.extract_matches(
|
|
122
|
-
input_text,
|
|
123
|
-
settings.get("pattern", ""),
|
|
124
|
-
settings.get("match_mode", "all_per_line"),
|
|
125
|
-
settings.get("omit_duplicates", False),
|
|
126
|
-
settings.get("hide_counts", True),
|
|
127
|
-
settings.get("sort_results", False),
|
|
128
|
-
settings.get("case_sensitive", False)
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
class RegexExtractorUI:
|
|
133
|
-
"""UI components for the Regex Extractor."""
|
|
134
|
-
|
|
135
|
-
def __init__(self, parent, settings, on_setting_change_callback=None, apply_tool_callback=None, settings_manager=None):
|
|
136
|
-
"""
|
|
137
|
-
Initialize the Regex Extractor UI.
|
|
138
|
-
|
|
139
|
-
Args:
|
|
140
|
-
parent: Parent widget
|
|
141
|
-
settings: Dictionary containing tool settings
|
|
142
|
-
on_setting_change_callback: Callback function for setting changes
|
|
143
|
-
apply_tool_callback: Callback function for applying the tool
|
|
144
|
-
settings_manager: Settings manager for accessing pattern library
|
|
145
|
-
"""
|
|
146
|
-
self.parent = parent
|
|
147
|
-
self.settings = settings
|
|
148
|
-
self.on_setting_change_callback = on_setting_change_callback
|
|
149
|
-
self.apply_tool_callback = apply_tool_callback
|
|
150
|
-
self.settings_manager = settings_manager
|
|
151
|
-
|
|
152
|
-
# Initialize UI variables
|
|
153
|
-
self.pattern_var = tk.StringVar(value=settings.get("pattern", ""))
|
|
154
|
-
self.match_mode_var = tk.StringVar(value=settings.get("match_mode", "all_per_line"))
|
|
155
|
-
self.regex_omit_duplicates_var = tk.BooleanVar(value=settings.get("omit_duplicates", False))
|
|
156
|
-
self.regex_hide_counts_var = tk.BooleanVar(value=settings.get("hide_counts", True))
|
|
157
|
-
self.regex_sort_results_var = tk.BooleanVar(value=settings.get("sort_results", False))
|
|
158
|
-
self.regex_case_sensitive_var = tk.BooleanVar(value=settings.get("case_sensitive", False))
|
|
159
|
-
|
|
160
|
-
self.create_widgets()
|
|
161
|
-
|
|
162
|
-
def create_widgets(self):
|
|
163
|
-
"""Creates the UI widgets for the Regex Extractor."""
|
|
164
|
-
# Find field (Regex pattern)
|
|
165
|
-
find_frame = ttk.Frame(self.parent)
|
|
166
|
-
find_frame.pack(fill=tk.X, padx=5, pady=5)
|
|
167
|
-
ttk.Label(find_frame, text="Find:").pack(side=tk.LEFT, padx=(0, 5))
|
|
168
|
-
find_entry = ttk.Entry(find_frame, textvariable=self.pattern_var, width=40)
|
|
169
|
-
find_entry.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=(0, 5))
|
|
170
|
-
self.pattern_var.trace_add("write", self._on_pattern_change)
|
|
171
|
-
|
|
172
|
-
# Pattern Library button
|
|
173
|
-
if self.settings_manager:
|
|
174
|
-
ttk.Button(
|
|
175
|
-
find_frame,
|
|
176
|
-
text="Pattern Library",
|
|
177
|
-
command=self.show_pattern_library
|
|
178
|
-
).pack(side=tk.LEFT, padx=(5, 0))
|
|
179
|
-
|
|
180
|
-
# Match mode option (first per line or all per line)
|
|
181
|
-
match_mode_frame = ttk.Frame(self.parent)
|
|
182
|
-
match_mode_frame.pack(fill=tk.X, padx=5, pady=(5, 2))
|
|
183
|
-
ttk.Label(match_mode_frame, text="Match mode:").pack(side=tk.LEFT, padx=(0, 5))
|
|
184
|
-
ttk.Radiobutton(
|
|
185
|
-
match_mode_frame,
|
|
186
|
-
text="First match per line",
|
|
187
|
-
variable=self.match_mode_var,
|
|
188
|
-
value="first_per_line",
|
|
189
|
-
command=self._on_setting_change
|
|
190
|
-
).pack(side=tk.LEFT, padx=5)
|
|
191
|
-
ttk.Radiobutton(
|
|
192
|
-
match_mode_frame,
|
|
193
|
-
text="All occurrences",
|
|
194
|
-
variable=self.match_mode_var,
|
|
195
|
-
value="all_per_line",
|
|
196
|
-
command=self._on_setting_change
|
|
197
|
-
).pack(side=tk.LEFT, padx=5)
|
|
198
|
-
|
|
199
|
-
# Checkboxes for various options
|
|
200
|
-
options_frame = ttk.Frame(self.parent)
|
|
201
|
-
options_frame.pack(fill=tk.X, padx=5, pady=5)
|
|
202
|
-
|
|
203
|
-
ttk.Checkbutton(
|
|
204
|
-
options_frame,
|
|
205
|
-
text="Omit duplicates",
|
|
206
|
-
variable=self.regex_omit_duplicates_var,
|
|
207
|
-
command=self._on_setting_change
|
|
208
|
-
).pack(side=tk.LEFT, padx=5)
|
|
209
|
-
|
|
210
|
-
ttk.Checkbutton(
|
|
211
|
-
options_frame,
|
|
212
|
-
text="Hide counts",
|
|
213
|
-
variable=self.regex_hide_counts_var,
|
|
214
|
-
command=self._on_setting_change
|
|
215
|
-
).pack(side=tk.LEFT, padx=5)
|
|
216
|
-
|
|
217
|
-
ttk.Checkbutton(
|
|
218
|
-
options_frame,
|
|
219
|
-
text="Sort results",
|
|
220
|
-
variable=self.regex_sort_results_var,
|
|
221
|
-
command=self._on_setting_change
|
|
222
|
-
).pack(side=tk.LEFT, padx=5)
|
|
223
|
-
|
|
224
|
-
ttk.Checkbutton(
|
|
225
|
-
options_frame,
|
|
226
|
-
text="Case sensitive",
|
|
227
|
-
variable=self.regex_case_sensitive_var,
|
|
228
|
-
command=self._on_setting_change
|
|
229
|
-
).pack(side=tk.LEFT, padx=5)
|
|
230
|
-
|
|
231
|
-
# Extract button
|
|
232
|
-
button_frame = ttk.Frame(self.parent)
|
|
233
|
-
button_frame.pack(fill=tk.X, padx=5, pady=5)
|
|
234
|
-
|
|
235
|
-
if self.apply_tool_callback:
|
|
236
|
-
ttk.Button(
|
|
237
|
-
button_frame,
|
|
238
|
-
text="Extract",
|
|
239
|
-
command=self.apply_tool_callback
|
|
240
|
-
).pack(side=tk.LEFT, padx=5)
|
|
241
|
-
|
|
242
|
-
def _on_setting_change(self):
|
|
243
|
-
"""Handle setting changes."""
|
|
244
|
-
if self.on_setting_change_callback:
|
|
245
|
-
self.on_setting_change_callback()
|
|
246
|
-
|
|
247
|
-
def _on_pattern_change(self, *args):
|
|
248
|
-
"""Handle pattern text changes."""
|
|
249
|
-
if self.on_setting_change_callback:
|
|
250
|
-
self.on_setting_change_callback()
|
|
251
|
-
|
|
252
|
-
def get_current_settings(self):
|
|
253
|
-
"""Get the current settings from the UI."""
|
|
254
|
-
return {
|
|
255
|
-
"pattern": self.pattern_var.get(),
|
|
256
|
-
"match_mode": self.match_mode_var.get(),
|
|
257
|
-
"omit_duplicates": self.regex_omit_duplicates_var.get(),
|
|
258
|
-
"hide_counts": self.regex_hide_counts_var.get(),
|
|
259
|
-
"sort_results": self.regex_sort_results_var.get(),
|
|
260
|
-
"case_sensitive": self.regex_case_sensitive_var.get()
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
def update_settings(self, settings):
|
|
264
|
-
"""Update the UI with new settings."""
|
|
265
|
-
self.pattern_var.set(settings.get("pattern", ""))
|
|
266
|
-
self.match_mode_var.set(settings.get("match_mode", "all_per_line"))
|
|
267
|
-
self.regex_omit_duplicates_var.set(settings.get("omit_duplicates", False))
|
|
268
|
-
self.regex_hide_counts_var.set(settings.get("hide_counts", True))
|
|
269
|
-
self.regex_sort_results_var.set(settings.get("sort_results", False))
|
|
270
|
-
self.regex_case_sensitive_var.set(settings.get("case_sensitive", False))
|
|
271
|
-
|
|
272
|
-
def show_pattern_library(self):
|
|
273
|
-
"""Shows the Pattern Library window with regex patterns."""
|
|
274
|
-
if not self.settings_manager:
|
|
275
|
-
return
|
|
276
|
-
|
|
277
|
-
# Get pattern library from settings
|
|
278
|
-
pattern_library = self.settings_manager.get_pattern_library()
|
|
279
|
-
|
|
280
|
-
popup = tk.Toplevel(self.parent)
|
|
281
|
-
popup.title("Regex Pattern Library")
|
|
282
|
-
popup.geometry("800x500")
|
|
283
|
-
popup.transient(self.parent)
|
|
284
|
-
popup.grab_set()
|
|
285
|
-
|
|
286
|
-
# Center the popup
|
|
287
|
-
popup.update_idletasks()
|
|
288
|
-
x = (popup.winfo_screenwidth() // 2) - (popup.winfo_width() // 2)
|
|
289
|
-
y = (popup.winfo_screenheight() // 2) - (popup.winfo_height() // 2)
|
|
290
|
-
popup.geometry(f"+{x}+{y}")
|
|
291
|
-
|
|
292
|
-
# Main frame
|
|
293
|
-
main_frame = ttk.Frame(popup)
|
|
294
|
-
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
|
295
|
-
|
|
296
|
-
# Title
|
|
297
|
-
ttk.Label(main_frame, text="Regex Pattern Library", font=("Arial", 12, "bold")).pack(anchor="w", pady=(0,10))
|
|
298
|
-
|
|
299
|
-
# Treeview for the table
|
|
300
|
-
tree_frame = ttk.Frame(main_frame)
|
|
301
|
-
tree_frame.pack(fill=tk.BOTH, expand=True)
|
|
302
|
-
|
|
303
|
-
# Create Treeview with scrollbars
|
|
304
|
-
tree_scroll_y = ttk.Scrollbar(tree_frame)
|
|
305
|
-
tree_scroll_y.pack(side=tk.RIGHT, fill=tk.Y)
|
|
306
|
-
|
|
307
|
-
tree_scroll_x = ttk.Scrollbar(tree_frame, orient=tk.HORIZONTAL)
|
|
308
|
-
tree_scroll_x.pack(side=tk.BOTTOM, fill=tk.X)
|
|
309
|
-
|
|
310
|
-
tree = ttk.Treeview(tree_frame,
|
|
311
|
-
columns=("Pattern", "Purpose"),
|
|
312
|
-
show="headings",
|
|
313
|
-
yscrollcommand=tree_scroll_y.set,
|
|
314
|
-
xscrollcommand=tree_scroll_x.set)
|
|
315
|
-
tree.pack(fill=tk.BOTH, expand=True)
|
|
316
|
-
|
|
317
|
-
tree_scroll_y.config(command=tree.yview)
|
|
318
|
-
tree_scroll_x.config(command=tree.xview)
|
|
319
|
-
|
|
320
|
-
# Configure columns
|
|
321
|
-
tree.heading("Pattern", text="Pattern")
|
|
322
|
-
tree.heading("Purpose", text="Purpose")
|
|
323
|
-
|
|
324
|
-
tree.column("Pattern", width=300, minwidth=200)
|
|
325
|
-
tree.column("Purpose", width=450, minwidth=300)
|
|
326
|
-
|
|
327
|
-
# Populate tree with patterns
|
|
328
|
-
def refresh_tree():
|
|
329
|
-
tree.delete(*tree.get_children())
|
|
330
|
-
for i, pattern in enumerate(pattern_library):
|
|
331
|
-
# For Regex Extractor, we only show the find pattern and purpose
|
|
332
|
-
find_pattern = pattern.get("find", "")
|
|
333
|
-
purpose = pattern.get("purpose", "")
|
|
334
|
-
tree.insert("", tk.END, iid=i, values=(find_pattern, purpose))
|
|
335
|
-
|
|
336
|
-
refresh_tree()
|
|
337
|
-
|
|
338
|
-
# Management buttons frame
|
|
339
|
-
button_frame = ttk.Frame(main_frame)
|
|
340
|
-
button_frame.pack(fill=tk.X, pady=(10,0))
|
|
341
|
-
|
|
342
|
-
# Left side buttons (management)
|
|
343
|
-
left_buttons = ttk.Frame(button_frame)
|
|
344
|
-
left_buttons.pack(side=tk.LEFT)
|
|
345
|
-
|
|
346
|
-
def add_pattern():
|
|
347
|
-
pattern_library.append({"find": "", "replace": "", "purpose": ""})
|
|
348
|
-
refresh_tree()
|
|
349
|
-
# Select the new item for editing
|
|
350
|
-
new_item_id = len(pattern_library) - 1
|
|
351
|
-
tree.selection_set(str(new_item_id))
|
|
352
|
-
tree.focus(str(new_item_id))
|
|
353
|
-
self.settings_manager.save_settings()
|
|
354
|
-
|
|
355
|
-
def delete_pattern():
|
|
356
|
-
selection = tree.selection()
|
|
357
|
-
if selection:
|
|
358
|
-
item_id = int(selection[0])
|
|
359
|
-
del pattern_library[item_id]
|
|
360
|
-
refresh_tree()
|
|
361
|
-
self.settings_manager.save_settings()
|
|
362
|
-
|
|
363
|
-
def move_up():
|
|
364
|
-
selection = tree.selection()
|
|
365
|
-
if selection:
|
|
366
|
-
item_id = int(selection[0])
|
|
367
|
-
if item_id > 0:
|
|
368
|
-
# Swap with previous item
|
|
369
|
-
pattern_library[item_id], pattern_library[item_id-1] = \
|
|
370
|
-
pattern_library[item_id-1], pattern_library[item_id]
|
|
371
|
-
refresh_tree()
|
|
372
|
-
tree.selection_set(str(item_id-1))
|
|
373
|
-
tree.focus(str(item_id-1))
|
|
374
|
-
self.settings_manager.save_settings()
|
|
375
|
-
|
|
376
|
-
def move_down():
|
|
377
|
-
selection = tree.selection()
|
|
378
|
-
if selection:
|
|
379
|
-
item_id = int(selection[0])
|
|
380
|
-
if item_id < len(pattern_library) - 1:
|
|
381
|
-
# Swap with next item
|
|
382
|
-
pattern_library[item_id], pattern_library[item_id+1] = \
|
|
383
|
-
pattern_library[item_id+1], pattern_library[item_id]
|
|
384
|
-
refresh_tree()
|
|
385
|
-
tree.selection_set(str(item_id+1))
|
|
386
|
-
tree.focus(str(item_id+1))
|
|
387
|
-
self.settings_manager.save_settings()
|
|
388
|
-
|
|
389
|
-
ttk.Button(left_buttons, text="Add", command=add_pattern).pack(side=tk.LEFT, padx=(0,5))
|
|
390
|
-
ttk.Button(left_buttons, text="Delete", command=delete_pattern).pack(side=tk.LEFT, padx=5)
|
|
391
|
-
ttk.Button(left_buttons, text="Move Up", command=move_up).pack(side=tk.LEFT, padx=5)
|
|
392
|
-
ttk.Button(left_buttons, text="Move Down", command=move_down).pack(side=tk.LEFT, padx=5)
|
|
393
|
-
|
|
394
|
-
# Right side buttons (use/close)
|
|
395
|
-
right_buttons = ttk.Frame(button_frame)
|
|
396
|
-
right_buttons.pack(side=tk.RIGHT)
|
|
397
|
-
|
|
398
|
-
def use_pattern():
|
|
399
|
-
selection = tree.selection()
|
|
400
|
-
if selection:
|
|
401
|
-
item_id = int(selection[0])
|
|
402
|
-
pattern = pattern_library[item_id]
|
|
403
|
-
# Only fill the Find field for Regex Extractor
|
|
404
|
-
self.pattern_var.set(pattern.get("find", ""))
|
|
405
|
-
popup.destroy()
|
|
406
|
-
self._on_pattern_change()
|
|
407
|
-
|
|
408
|
-
use_pattern_button = ttk.Button(right_buttons, text="Use Pattern", command=use_pattern)
|
|
409
|
-
use_pattern_button.pack(side=tk.LEFT, padx=5)
|
|
410
|
-
ttk.Button(right_buttons, text="Close", command=popup.destroy).pack(side=tk.LEFT, padx=(5,0))
|
|
411
|
-
|
|
412
|
-
# Function to update button states based on selection
|
|
413
|
-
def update_button_states():
|
|
414
|
-
selection = tree.selection()
|
|
415
|
-
state = "normal" if selection else "disabled"
|
|
416
|
-
use_pattern_button.config(state=state)
|
|
417
|
-
|
|
418
|
-
# Bind selection change to update button states
|
|
419
|
-
tree.bind('<<TreeviewSelect>>', lambda e: update_button_states())
|
|
420
|
-
|
|
421
|
-
# Initial button state update
|
|
422
|
-
update_button_states()
|
|
423
|
-
|
|
424
|
-
# Double-click to use pattern
|
|
425
|
-
tree.bind('<Double-Button-1>', lambda e: use_pattern())
|
|
426
|
-
|
|
427
|
-
# Cell editing functionality
|
|
428
|
-
def on_cell_click(event):
|
|
429
|
-
item = tree.selection()[0] if tree.selection() else None
|
|
430
|
-
if item:
|
|
431
|
-
column = tree.identify_column(event.x)
|
|
432
|
-
if column in ['#1', '#2']: # Pattern, Purpose columns
|
|
433
|
-
self._edit_cell(tree, item, column, popup, pattern_library)
|
|
434
|
-
|
|
435
|
-
tree.bind('<Button-1>', on_cell_click)
|
|
436
|
-
|
|
437
|
-
def _edit_cell(self, tree, item, column, parent_window, pattern_library):
|
|
438
|
-
"""Edit a cell in the pattern library tree."""
|
|
439
|
-
# Get current value
|
|
440
|
-
item_id = int(item)
|
|
441
|
-
pattern = pattern_library[item_id]
|
|
442
|
-
|
|
443
|
-
column_map = {'#1': 'find', '#2': 'purpose'}
|
|
444
|
-
field_name = column_map[column]
|
|
445
|
-
|
|
446
|
-
# For Pattern column, edit the 'find' field
|
|
447
|
-
if field_name == 'find':
|
|
448
|
-
current_value = pattern.get("find", "")
|
|
449
|
-
else:
|
|
450
|
-
current_value = pattern.get("purpose", "")
|
|
451
|
-
|
|
452
|
-
# Get cell position
|
|
453
|
-
bbox = tree.bbox(item, column)
|
|
454
|
-
if not bbox:
|
|
455
|
-
return
|
|
456
|
-
|
|
457
|
-
# Create entry widget for editing
|
|
458
|
-
entry = tk.Entry(tree)
|
|
459
|
-
entry.place(x=bbox[0], y=bbox[1], width=bbox[2], height=bbox[3])
|
|
460
|
-
entry.insert(0, current_value)
|
|
461
|
-
entry.select_range(0, tk.END)
|
|
462
|
-
entry.focus()
|
|
463
|
-
|
|
464
|
-
def save_edit():
|
|
465
|
-
new_value = entry.get()
|
|
466
|
-
if field_name == 'find':
|
|
467
|
-
pattern["find"] = new_value
|
|
468
|
-
tree.set(item, column, new_value)
|
|
469
|
-
else:
|
|
470
|
-
pattern["purpose"] = new_value
|
|
471
|
-
tree.set(item, column, new_value)
|
|
472
|
-
entry.destroy()
|
|
473
|
-
self.settings_manager.save_settings()
|
|
474
|
-
|
|
475
|
-
def cancel_edit():
|
|
476
|
-
entry.destroy()
|
|
477
|
-
|
|
478
|
-
entry.bind('<Return>', lambda e: save_edit())
|
|
479
|
-
entry.bind('<Escape>', lambda e: cancel_edit())
|
|
480
|
-
entry.bind('<FocusOut>', lambda e: save_edit())
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
class RegexExtractor:
|
|
484
|
-
"""Main Regex Extractor class that combines processor and UI functionality."""
|
|
485
|
-
|
|
486
|
-
def __init__(self):
|
|
487
|
-
self.processor = RegexExtractorProcessor()
|
|
488
|
-
self.ui = None
|
|
489
|
-
|
|
490
|
-
def create_ui(self, parent, settings, on_setting_change_callback=None, apply_tool_callback=None, settings_manager=None):
|
|
491
|
-
"""Create and return the UI component."""
|
|
492
|
-
self.ui = RegexExtractorUI(parent, settings, on_setting_change_callback, apply_tool_callback, settings_manager)
|
|
493
|
-
return self.ui
|
|
494
|
-
|
|
495
|
-
def process_text(self, input_text, settings):
|
|
496
|
-
"""Process text using the current settings."""
|
|
497
|
-
return self.processor.process_text(input_text, settings)
|
|
498
|
-
|
|
499
|
-
def get_default_settings(self):
|
|
500
|
-
"""Get default settings for the Regex Extractor."""
|
|
501
|
-
return {
|
|
502
|
-
"pattern": "",
|
|
503
|
-
"match_mode": "all_per_line",
|
|
504
|
-
"omit_duplicates": False,
|
|
505
|
-
"hide_counts": True,
|
|
506
|
-
"sort_results": False,
|
|
507
|
-
"case_sensitive": False
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
# Convenience functions for backward compatibility
|
|
512
|
-
def extract_regex_matches(text, pattern, match_mode="all_per_line", omit_duplicates=False, hide_counts=True, sort_results=False, case_sensitive=False):
|
|
513
|
-
"""Extract matches with specified options."""
|
|
514
|
-
return RegexExtractorProcessor.extract_matches(
|
|
515
|
-
text, pattern, match_mode, omit_duplicates, hide_counts, sort_results, case_sensitive
|
|
516
|
-
)
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
def process_regex_extraction(input_text, settings):
|
|
520
|
-
"""Process regex extraction with the specified settings."""
|
|
521
|
-
return RegexExtractorProcessor.process_text(input_text, settings)
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
# BaseTool-compatible wrapper
|
|
525
|
-
try:
|
|
526
|
-
from tools.base_tool import BaseTool
|
|
527
|
-
from typing import Dict, Any
|
|
528
|
-
import tkinter as tk
|
|
529
|
-
from tkinter import ttk
|
|
530
|
-
|
|
531
|
-
class RegexExtractorV2(BaseTool):
|
|
532
|
-
"""
|
|
533
|
-
BaseTool-compatible version of RegexExtractor.
|
|
534
|
-
"""
|
|
535
|
-
|
|
536
|
-
TOOL_NAME = "Regex Extractor"
|
|
537
|
-
TOOL_DESCRIPTION = "Extract text matches using regular expressions"
|
|
538
|
-
TOOL_VERSION = "2.0.0"
|
|
539
|
-
|
|
540
|
-
def process_text(self, input_text: str, settings: Dict[str, Any]) -> str:
|
|
541
|
-
"""Extract regex matches from text."""
|
|
542
|
-
return RegexExtractorProcessor.extract_matches(
|
|
543
|
-
input_text,
|
|
544
|
-
settings.get("pattern", ""),
|
|
545
|
-
settings.get("match_mode", "all_per_line"),
|
|
546
|
-
settings.get("omit_duplicates", False),
|
|
547
|
-
settings.get("hide_counts", True),
|
|
548
|
-
settings.get("sort_results", False),
|
|
549
|
-
settings.get("case_sensitive", False)
|
|
550
|
-
)
|
|
551
|
-
|
|
552
|
-
def get_default_settings(self) -> Dict[str, Any]:
|
|
553
|
-
return {
|
|
554
|
-
"pattern": "",
|
|
555
|
-
"match_mode": "all_per_line",
|
|
556
|
-
"omit_duplicates": False,
|
|
557
|
-
"hide_counts": True,
|
|
558
|
-
"sort_results": False,
|
|
559
|
-
"case_sensitive": False
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
def create_ui(self, parent: tk.Widget, settings: Dict[str, Any],
|
|
563
|
-
on_change=None, on_apply=None) -> tk.Widget:
|
|
564
|
-
"""Create a simple UI for Regex Extractor."""
|
|
565
|
-
frame = ttk.Frame(parent)
|
|
566
|
-
ttk.Label(frame, text="Extract regex matches").pack(side=tk.LEFT, padx=5)
|
|
567
|
-
if on_apply:
|
|
568
|
-
ttk.Button(frame, text="Extract", command=on_apply).pack(side=tk.LEFT, padx=5)
|
|
569
|
-
return frame
|
|
570
|
-
|
|
571
|
-
except ImportError:
|
|
1
|
+
"""
|
|
2
|
+
Regex Extractor Module - Regex pattern extraction utility
|
|
3
|
+
|
|
4
|
+
This module provides regex pattern extraction functionality with UI components
|
|
5
|
+
for the Promera AI Commander application.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import tkinter as tk
|
|
9
|
+
from tkinter import ttk
|
|
10
|
+
import re
|
|
11
|
+
from collections import Counter
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RegexExtractorProcessor:
|
|
15
|
+
"""Regex extractor processor that extracts matches from text using regex patterns."""
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def extract_matches(text, pattern, match_mode="all_per_line", omit_duplicates=False, hide_counts=True, sort_results=False, case_sensitive=False):
|
|
19
|
+
"""
|
|
20
|
+
Extract matches from text using a regex pattern.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
text: Input text to search
|
|
24
|
+
pattern: Regex pattern to search for
|
|
25
|
+
match_mode: "first_per_line" to match only first occurrence per line, "all_per_line" to match all occurrences per line
|
|
26
|
+
omit_duplicates: If True, only return unique matches
|
|
27
|
+
hide_counts: If True, don't show match counts
|
|
28
|
+
sort_results: If True, sort the results
|
|
29
|
+
case_sensitive: If True, perform case-sensitive matching
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
String containing extracted matches (one per line) or error message
|
|
33
|
+
"""
|
|
34
|
+
if not pattern or not pattern.strip():
|
|
35
|
+
return "Please enter a regex pattern in the Find field."
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
# Compile the regex pattern
|
|
39
|
+
flags = 0 if case_sensitive else re.IGNORECASE
|
|
40
|
+
regex = re.compile(pattern, flags)
|
|
41
|
+
|
|
42
|
+
processed_matches = []
|
|
43
|
+
|
|
44
|
+
# Process based on match mode
|
|
45
|
+
if match_mode == "first_per_line":
|
|
46
|
+
# Process line by line, taking only the first match per line
|
|
47
|
+
lines = text.split('\n')
|
|
48
|
+
for line in lines:
|
|
49
|
+
matches = regex.findall(line)
|
|
50
|
+
if matches:
|
|
51
|
+
# Take only the first match from this line
|
|
52
|
+
match = matches[0]
|
|
53
|
+
if isinstance(match, tuple):
|
|
54
|
+
# Join tuple elements with a separator
|
|
55
|
+
processed_matches.append(' | '.join(str(m) if m else '' for m in match))
|
|
56
|
+
else:
|
|
57
|
+
processed_matches.append(str(match))
|
|
58
|
+
else:
|
|
59
|
+
# Match all occurrences (original behavior)
|
|
60
|
+
matches = regex.findall(text)
|
|
61
|
+
|
|
62
|
+
if not matches:
|
|
63
|
+
return "No matches found for the regex pattern."
|
|
64
|
+
|
|
65
|
+
# Handle different match types (strings vs tuples)
|
|
66
|
+
# If pattern has groups, findall returns tuples, otherwise strings
|
|
67
|
+
for match in matches:
|
|
68
|
+
if isinstance(match, tuple):
|
|
69
|
+
# Join tuple elements with a separator
|
|
70
|
+
processed_matches.append(' | '.join(str(m) if m else '' for m in match))
|
|
71
|
+
else:
|
|
72
|
+
processed_matches.append(str(match))
|
|
73
|
+
|
|
74
|
+
if not processed_matches:
|
|
75
|
+
return "No matches found for the regex pattern."
|
|
76
|
+
|
|
77
|
+
# Count occurrences
|
|
78
|
+
match_counts = Counter(processed_matches)
|
|
79
|
+
|
|
80
|
+
# Get unique matches if omit_duplicates is True
|
|
81
|
+
if omit_duplicates:
|
|
82
|
+
unique_matches = list(match_counts.keys())
|
|
83
|
+
if sort_results:
|
|
84
|
+
unique_matches.sort()
|
|
85
|
+
|
|
86
|
+
# Format output
|
|
87
|
+
if hide_counts:
|
|
88
|
+
return '\n'.join(unique_matches)
|
|
89
|
+
else:
|
|
90
|
+
# When omit_duplicates=True, show count as (1) for all
|
|
91
|
+
return '\n'.join([f"{match} (1)" for match in unique_matches])
|
|
92
|
+
else:
|
|
93
|
+
# Keep all matches including duplicates
|
|
94
|
+
if sort_results:
|
|
95
|
+
processed_matches.sort()
|
|
96
|
+
|
|
97
|
+
if hide_counts:
|
|
98
|
+
return '\n'.join(processed_matches)
|
|
99
|
+
else:
|
|
100
|
+
# Show actual counts for each unique match
|
|
101
|
+
result = []
|
|
102
|
+
processed = set()
|
|
103
|
+
for match in processed_matches:
|
|
104
|
+
if match not in processed:
|
|
105
|
+
result.append(f"{match} ({match_counts[match]})")
|
|
106
|
+
processed.add(match)
|
|
107
|
+
|
|
108
|
+
if sort_results:
|
|
109
|
+
result.sort()
|
|
110
|
+
|
|
111
|
+
return '\n'.join(result)
|
|
112
|
+
|
|
113
|
+
except re.error as e:
|
|
114
|
+
return f"Regex Error: {str(e)}\n\nPlease check your regex pattern syntax."
|
|
115
|
+
except Exception as e:
|
|
116
|
+
return f"Error: {str(e)}"
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def process_text(input_text, settings):
|
|
120
|
+
"""Process text using the current settings."""
|
|
121
|
+
return RegexExtractorProcessor.extract_matches(
|
|
122
|
+
input_text,
|
|
123
|
+
settings.get("pattern", ""),
|
|
124
|
+
settings.get("match_mode", "all_per_line"),
|
|
125
|
+
settings.get("omit_duplicates", False),
|
|
126
|
+
settings.get("hide_counts", True),
|
|
127
|
+
settings.get("sort_results", False),
|
|
128
|
+
settings.get("case_sensitive", False)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class RegexExtractorUI:
|
|
133
|
+
"""UI components for the Regex Extractor."""
|
|
134
|
+
|
|
135
|
+
def __init__(self, parent, settings, on_setting_change_callback=None, apply_tool_callback=None, settings_manager=None):
|
|
136
|
+
"""
|
|
137
|
+
Initialize the Regex Extractor UI.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
parent: Parent widget
|
|
141
|
+
settings: Dictionary containing tool settings
|
|
142
|
+
on_setting_change_callback: Callback function for setting changes
|
|
143
|
+
apply_tool_callback: Callback function for applying the tool
|
|
144
|
+
settings_manager: Settings manager for accessing pattern library
|
|
145
|
+
"""
|
|
146
|
+
self.parent = parent
|
|
147
|
+
self.settings = settings
|
|
148
|
+
self.on_setting_change_callback = on_setting_change_callback
|
|
149
|
+
self.apply_tool_callback = apply_tool_callback
|
|
150
|
+
self.settings_manager = settings_manager
|
|
151
|
+
|
|
152
|
+
# Initialize UI variables
|
|
153
|
+
self.pattern_var = tk.StringVar(value=settings.get("pattern", ""))
|
|
154
|
+
self.match_mode_var = tk.StringVar(value=settings.get("match_mode", "all_per_line"))
|
|
155
|
+
self.regex_omit_duplicates_var = tk.BooleanVar(value=settings.get("omit_duplicates", False))
|
|
156
|
+
self.regex_hide_counts_var = tk.BooleanVar(value=settings.get("hide_counts", True))
|
|
157
|
+
self.regex_sort_results_var = tk.BooleanVar(value=settings.get("sort_results", False))
|
|
158
|
+
self.regex_case_sensitive_var = tk.BooleanVar(value=settings.get("case_sensitive", False))
|
|
159
|
+
|
|
160
|
+
self.create_widgets()
|
|
161
|
+
|
|
162
|
+
def create_widgets(self):
|
|
163
|
+
"""Creates the UI widgets for the Regex Extractor."""
|
|
164
|
+
# Find field (Regex pattern)
|
|
165
|
+
find_frame = ttk.Frame(self.parent)
|
|
166
|
+
find_frame.pack(fill=tk.X, padx=5, pady=5)
|
|
167
|
+
ttk.Label(find_frame, text="Find:").pack(side=tk.LEFT, padx=(0, 5))
|
|
168
|
+
find_entry = ttk.Entry(find_frame, textvariable=self.pattern_var, width=40)
|
|
169
|
+
find_entry.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=(0, 5))
|
|
170
|
+
self.pattern_var.trace_add("write", self._on_pattern_change)
|
|
171
|
+
|
|
172
|
+
# Pattern Library button
|
|
173
|
+
if self.settings_manager:
|
|
174
|
+
ttk.Button(
|
|
175
|
+
find_frame,
|
|
176
|
+
text="Pattern Library",
|
|
177
|
+
command=self.show_pattern_library
|
|
178
|
+
).pack(side=tk.LEFT, padx=(5, 0))
|
|
179
|
+
|
|
180
|
+
# Match mode option (first per line or all per line)
|
|
181
|
+
match_mode_frame = ttk.Frame(self.parent)
|
|
182
|
+
match_mode_frame.pack(fill=tk.X, padx=5, pady=(5, 2))
|
|
183
|
+
ttk.Label(match_mode_frame, text="Match mode:").pack(side=tk.LEFT, padx=(0, 5))
|
|
184
|
+
ttk.Radiobutton(
|
|
185
|
+
match_mode_frame,
|
|
186
|
+
text="First match per line",
|
|
187
|
+
variable=self.match_mode_var,
|
|
188
|
+
value="first_per_line",
|
|
189
|
+
command=self._on_setting_change
|
|
190
|
+
).pack(side=tk.LEFT, padx=5)
|
|
191
|
+
ttk.Radiobutton(
|
|
192
|
+
match_mode_frame,
|
|
193
|
+
text="All occurrences",
|
|
194
|
+
variable=self.match_mode_var,
|
|
195
|
+
value="all_per_line",
|
|
196
|
+
command=self._on_setting_change
|
|
197
|
+
).pack(side=tk.LEFT, padx=5)
|
|
198
|
+
|
|
199
|
+
# Checkboxes for various options
|
|
200
|
+
options_frame = ttk.Frame(self.parent)
|
|
201
|
+
options_frame.pack(fill=tk.X, padx=5, pady=5)
|
|
202
|
+
|
|
203
|
+
ttk.Checkbutton(
|
|
204
|
+
options_frame,
|
|
205
|
+
text="Omit duplicates",
|
|
206
|
+
variable=self.regex_omit_duplicates_var,
|
|
207
|
+
command=self._on_setting_change
|
|
208
|
+
).pack(side=tk.LEFT, padx=5)
|
|
209
|
+
|
|
210
|
+
ttk.Checkbutton(
|
|
211
|
+
options_frame,
|
|
212
|
+
text="Hide counts",
|
|
213
|
+
variable=self.regex_hide_counts_var,
|
|
214
|
+
command=self._on_setting_change
|
|
215
|
+
).pack(side=tk.LEFT, padx=5)
|
|
216
|
+
|
|
217
|
+
ttk.Checkbutton(
|
|
218
|
+
options_frame,
|
|
219
|
+
text="Sort results",
|
|
220
|
+
variable=self.regex_sort_results_var,
|
|
221
|
+
command=self._on_setting_change
|
|
222
|
+
).pack(side=tk.LEFT, padx=5)
|
|
223
|
+
|
|
224
|
+
ttk.Checkbutton(
|
|
225
|
+
options_frame,
|
|
226
|
+
text="Case sensitive",
|
|
227
|
+
variable=self.regex_case_sensitive_var,
|
|
228
|
+
command=self._on_setting_change
|
|
229
|
+
).pack(side=tk.LEFT, padx=5)
|
|
230
|
+
|
|
231
|
+
# Extract button
|
|
232
|
+
button_frame = ttk.Frame(self.parent)
|
|
233
|
+
button_frame.pack(fill=tk.X, padx=5, pady=5)
|
|
234
|
+
|
|
235
|
+
if self.apply_tool_callback:
|
|
236
|
+
ttk.Button(
|
|
237
|
+
button_frame,
|
|
238
|
+
text="Extract",
|
|
239
|
+
command=self.apply_tool_callback
|
|
240
|
+
).pack(side=tk.LEFT, padx=5)
|
|
241
|
+
|
|
242
|
+
def _on_setting_change(self):
|
|
243
|
+
"""Handle setting changes."""
|
|
244
|
+
if self.on_setting_change_callback:
|
|
245
|
+
self.on_setting_change_callback()
|
|
246
|
+
|
|
247
|
+
def _on_pattern_change(self, *args):
|
|
248
|
+
"""Handle pattern text changes."""
|
|
249
|
+
if self.on_setting_change_callback:
|
|
250
|
+
self.on_setting_change_callback()
|
|
251
|
+
|
|
252
|
+
def get_current_settings(self):
|
|
253
|
+
"""Get the current settings from the UI."""
|
|
254
|
+
return {
|
|
255
|
+
"pattern": self.pattern_var.get(),
|
|
256
|
+
"match_mode": self.match_mode_var.get(),
|
|
257
|
+
"omit_duplicates": self.regex_omit_duplicates_var.get(),
|
|
258
|
+
"hide_counts": self.regex_hide_counts_var.get(),
|
|
259
|
+
"sort_results": self.regex_sort_results_var.get(),
|
|
260
|
+
"case_sensitive": self.regex_case_sensitive_var.get()
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
def update_settings(self, settings):
|
|
264
|
+
"""Update the UI with new settings."""
|
|
265
|
+
self.pattern_var.set(settings.get("pattern", ""))
|
|
266
|
+
self.match_mode_var.set(settings.get("match_mode", "all_per_line"))
|
|
267
|
+
self.regex_omit_duplicates_var.set(settings.get("omit_duplicates", False))
|
|
268
|
+
self.regex_hide_counts_var.set(settings.get("hide_counts", True))
|
|
269
|
+
self.regex_sort_results_var.set(settings.get("sort_results", False))
|
|
270
|
+
self.regex_case_sensitive_var.set(settings.get("case_sensitive", False))
|
|
271
|
+
|
|
272
|
+
def show_pattern_library(self):
|
|
273
|
+
"""Shows the Pattern Library window with regex patterns."""
|
|
274
|
+
if not self.settings_manager:
|
|
275
|
+
return
|
|
276
|
+
|
|
277
|
+
# Get pattern library from settings
|
|
278
|
+
pattern_library = self.settings_manager.get_pattern_library()
|
|
279
|
+
|
|
280
|
+
popup = tk.Toplevel(self.parent)
|
|
281
|
+
popup.title("Regex Pattern Library")
|
|
282
|
+
popup.geometry("800x500")
|
|
283
|
+
popup.transient(self.parent)
|
|
284
|
+
popup.grab_set()
|
|
285
|
+
|
|
286
|
+
# Center the popup
|
|
287
|
+
popup.update_idletasks()
|
|
288
|
+
x = (popup.winfo_screenwidth() // 2) - (popup.winfo_width() // 2)
|
|
289
|
+
y = (popup.winfo_screenheight() // 2) - (popup.winfo_height() // 2)
|
|
290
|
+
popup.geometry(f"+{x}+{y}")
|
|
291
|
+
|
|
292
|
+
# Main frame
|
|
293
|
+
main_frame = ttk.Frame(popup)
|
|
294
|
+
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
|
295
|
+
|
|
296
|
+
# Title
|
|
297
|
+
ttk.Label(main_frame, text="Regex Pattern Library", font=("Arial", 12, "bold")).pack(anchor="w", pady=(0,10))
|
|
298
|
+
|
|
299
|
+
# Treeview for the table
|
|
300
|
+
tree_frame = ttk.Frame(main_frame)
|
|
301
|
+
tree_frame.pack(fill=tk.BOTH, expand=True)
|
|
302
|
+
|
|
303
|
+
# Create Treeview with scrollbars
|
|
304
|
+
tree_scroll_y = ttk.Scrollbar(tree_frame)
|
|
305
|
+
tree_scroll_y.pack(side=tk.RIGHT, fill=tk.Y)
|
|
306
|
+
|
|
307
|
+
tree_scroll_x = ttk.Scrollbar(tree_frame, orient=tk.HORIZONTAL)
|
|
308
|
+
tree_scroll_x.pack(side=tk.BOTTOM, fill=tk.X)
|
|
309
|
+
|
|
310
|
+
tree = ttk.Treeview(tree_frame,
|
|
311
|
+
columns=("Pattern", "Purpose"),
|
|
312
|
+
show="headings",
|
|
313
|
+
yscrollcommand=tree_scroll_y.set,
|
|
314
|
+
xscrollcommand=tree_scroll_x.set)
|
|
315
|
+
tree.pack(fill=tk.BOTH, expand=True)
|
|
316
|
+
|
|
317
|
+
tree_scroll_y.config(command=tree.yview)
|
|
318
|
+
tree_scroll_x.config(command=tree.xview)
|
|
319
|
+
|
|
320
|
+
# Configure columns
|
|
321
|
+
tree.heading("Pattern", text="Pattern")
|
|
322
|
+
tree.heading("Purpose", text="Purpose")
|
|
323
|
+
|
|
324
|
+
tree.column("Pattern", width=300, minwidth=200)
|
|
325
|
+
tree.column("Purpose", width=450, minwidth=300)
|
|
326
|
+
|
|
327
|
+
# Populate tree with patterns
|
|
328
|
+
def refresh_tree():
|
|
329
|
+
tree.delete(*tree.get_children())
|
|
330
|
+
for i, pattern in enumerate(pattern_library):
|
|
331
|
+
# For Regex Extractor, we only show the find pattern and purpose
|
|
332
|
+
find_pattern = pattern.get("find", "")
|
|
333
|
+
purpose = pattern.get("purpose", "")
|
|
334
|
+
tree.insert("", tk.END, iid=i, values=(find_pattern, purpose))
|
|
335
|
+
|
|
336
|
+
refresh_tree()
|
|
337
|
+
|
|
338
|
+
# Management buttons frame
|
|
339
|
+
button_frame = ttk.Frame(main_frame)
|
|
340
|
+
button_frame.pack(fill=tk.X, pady=(10,0))
|
|
341
|
+
|
|
342
|
+
# Left side buttons (management)
|
|
343
|
+
left_buttons = ttk.Frame(button_frame)
|
|
344
|
+
left_buttons.pack(side=tk.LEFT)
|
|
345
|
+
|
|
346
|
+
def add_pattern():
|
|
347
|
+
pattern_library.append({"find": "", "replace": "", "purpose": ""})
|
|
348
|
+
refresh_tree()
|
|
349
|
+
# Select the new item for editing
|
|
350
|
+
new_item_id = len(pattern_library) - 1
|
|
351
|
+
tree.selection_set(str(new_item_id))
|
|
352
|
+
tree.focus(str(new_item_id))
|
|
353
|
+
self.settings_manager.set_pattern_library(pattern_library) if hasattr(self.settings_manager, 'set_pattern_library') else self.settings_manager.save_settings()
|
|
354
|
+
|
|
355
|
+
def delete_pattern():
|
|
356
|
+
selection = tree.selection()
|
|
357
|
+
if selection:
|
|
358
|
+
item_id = int(selection[0])
|
|
359
|
+
del pattern_library[item_id]
|
|
360
|
+
refresh_tree()
|
|
361
|
+
self.settings_manager.set_pattern_library(pattern_library) if hasattr(self.settings_manager, 'set_pattern_library') else self.settings_manager.save_settings()
|
|
362
|
+
|
|
363
|
+
def move_up():
|
|
364
|
+
selection = tree.selection()
|
|
365
|
+
if selection:
|
|
366
|
+
item_id = int(selection[0])
|
|
367
|
+
if item_id > 0:
|
|
368
|
+
# Swap with previous item
|
|
369
|
+
pattern_library[item_id], pattern_library[item_id-1] = \
|
|
370
|
+
pattern_library[item_id-1], pattern_library[item_id]
|
|
371
|
+
refresh_tree()
|
|
372
|
+
tree.selection_set(str(item_id-1))
|
|
373
|
+
tree.focus(str(item_id-1))
|
|
374
|
+
self.settings_manager.set_pattern_library(pattern_library) if hasattr(self.settings_manager, 'set_pattern_library') else self.settings_manager.save_settings()
|
|
375
|
+
|
|
376
|
+
def move_down():
|
|
377
|
+
selection = tree.selection()
|
|
378
|
+
if selection:
|
|
379
|
+
item_id = int(selection[0])
|
|
380
|
+
if item_id < len(pattern_library) - 1:
|
|
381
|
+
# Swap with next item
|
|
382
|
+
pattern_library[item_id], pattern_library[item_id+1] = \
|
|
383
|
+
pattern_library[item_id+1], pattern_library[item_id]
|
|
384
|
+
refresh_tree()
|
|
385
|
+
tree.selection_set(str(item_id+1))
|
|
386
|
+
tree.focus(str(item_id+1))
|
|
387
|
+
self.settings_manager.set_pattern_library(pattern_library) if hasattr(self.settings_manager, 'set_pattern_library') else self.settings_manager.save_settings()
|
|
388
|
+
|
|
389
|
+
ttk.Button(left_buttons, text="Add", command=add_pattern).pack(side=tk.LEFT, padx=(0,5))
|
|
390
|
+
ttk.Button(left_buttons, text="Delete", command=delete_pattern).pack(side=tk.LEFT, padx=5)
|
|
391
|
+
ttk.Button(left_buttons, text="Move Up", command=move_up).pack(side=tk.LEFT, padx=5)
|
|
392
|
+
ttk.Button(left_buttons, text="Move Down", command=move_down).pack(side=tk.LEFT, padx=5)
|
|
393
|
+
|
|
394
|
+
# Right side buttons (use/close)
|
|
395
|
+
right_buttons = ttk.Frame(button_frame)
|
|
396
|
+
right_buttons.pack(side=tk.RIGHT)
|
|
397
|
+
|
|
398
|
+
def use_pattern():
|
|
399
|
+
selection = tree.selection()
|
|
400
|
+
if selection:
|
|
401
|
+
item_id = int(selection[0])
|
|
402
|
+
pattern = pattern_library[item_id]
|
|
403
|
+
# Only fill the Find field for Regex Extractor
|
|
404
|
+
self.pattern_var.set(pattern.get("find", ""))
|
|
405
|
+
popup.destroy()
|
|
406
|
+
self._on_pattern_change()
|
|
407
|
+
|
|
408
|
+
use_pattern_button = ttk.Button(right_buttons, text="Use Pattern", command=use_pattern)
|
|
409
|
+
use_pattern_button.pack(side=tk.LEFT, padx=5)
|
|
410
|
+
ttk.Button(right_buttons, text="Close", command=popup.destroy).pack(side=tk.LEFT, padx=(5,0))
|
|
411
|
+
|
|
412
|
+
# Function to update button states based on selection
|
|
413
|
+
def update_button_states():
|
|
414
|
+
selection = tree.selection()
|
|
415
|
+
state = "normal" if selection else "disabled"
|
|
416
|
+
use_pattern_button.config(state=state)
|
|
417
|
+
|
|
418
|
+
# Bind selection change to update button states
|
|
419
|
+
tree.bind('<<TreeviewSelect>>', lambda e: update_button_states())
|
|
420
|
+
|
|
421
|
+
# Initial button state update
|
|
422
|
+
update_button_states()
|
|
423
|
+
|
|
424
|
+
# Double-click to use pattern
|
|
425
|
+
tree.bind('<Double-Button-1>', lambda e: use_pattern())
|
|
426
|
+
|
|
427
|
+
# Cell editing functionality
|
|
428
|
+
def on_cell_click(event):
|
|
429
|
+
item = tree.selection()[0] if tree.selection() else None
|
|
430
|
+
if item:
|
|
431
|
+
column = tree.identify_column(event.x)
|
|
432
|
+
if column in ['#1', '#2']: # Pattern, Purpose columns
|
|
433
|
+
self._edit_cell(tree, item, column, popup, pattern_library)
|
|
434
|
+
|
|
435
|
+
tree.bind('<Button-1>', on_cell_click)
|
|
436
|
+
|
|
437
|
+
def _edit_cell(self, tree, item, column, parent_window, pattern_library):
|
|
438
|
+
"""Edit a cell in the pattern library tree."""
|
|
439
|
+
# Get current value
|
|
440
|
+
item_id = int(item)
|
|
441
|
+
pattern = pattern_library[item_id]
|
|
442
|
+
|
|
443
|
+
column_map = {'#1': 'find', '#2': 'purpose'}
|
|
444
|
+
field_name = column_map[column]
|
|
445
|
+
|
|
446
|
+
# For Pattern column, edit the 'find' field
|
|
447
|
+
if field_name == 'find':
|
|
448
|
+
current_value = pattern.get("find", "")
|
|
449
|
+
else:
|
|
450
|
+
current_value = pattern.get("purpose", "")
|
|
451
|
+
|
|
452
|
+
# Get cell position
|
|
453
|
+
bbox = tree.bbox(item, column)
|
|
454
|
+
if not bbox:
|
|
455
|
+
return
|
|
456
|
+
|
|
457
|
+
# Create entry widget for editing
|
|
458
|
+
entry = tk.Entry(tree)
|
|
459
|
+
entry.place(x=bbox[0], y=bbox[1], width=bbox[2], height=bbox[3])
|
|
460
|
+
entry.insert(0, current_value)
|
|
461
|
+
entry.select_range(0, tk.END)
|
|
462
|
+
entry.focus()
|
|
463
|
+
|
|
464
|
+
def save_edit():
|
|
465
|
+
new_value = entry.get()
|
|
466
|
+
if field_name == 'find':
|
|
467
|
+
pattern["find"] = new_value
|
|
468
|
+
tree.set(item, column, new_value)
|
|
469
|
+
else:
|
|
470
|
+
pattern["purpose"] = new_value
|
|
471
|
+
tree.set(item, column, new_value)
|
|
472
|
+
entry.destroy()
|
|
473
|
+
self.settings_manager.set_pattern_library(pattern_library) if hasattr(self.settings_manager, 'set_pattern_library') else self.settings_manager.save_settings()
|
|
474
|
+
|
|
475
|
+
def cancel_edit():
|
|
476
|
+
entry.destroy()
|
|
477
|
+
|
|
478
|
+
entry.bind('<Return>', lambda e: save_edit())
|
|
479
|
+
entry.bind('<Escape>', lambda e: cancel_edit())
|
|
480
|
+
entry.bind('<FocusOut>', lambda e: save_edit())
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
class RegexExtractor:
|
|
484
|
+
"""Main Regex Extractor class that combines processor and UI functionality."""
|
|
485
|
+
|
|
486
|
+
def __init__(self):
|
|
487
|
+
self.processor = RegexExtractorProcessor()
|
|
488
|
+
self.ui = None
|
|
489
|
+
|
|
490
|
+
def create_ui(self, parent, settings, on_setting_change_callback=None, apply_tool_callback=None, settings_manager=None):
|
|
491
|
+
"""Create and return the UI component."""
|
|
492
|
+
self.ui = RegexExtractorUI(parent, settings, on_setting_change_callback, apply_tool_callback, settings_manager)
|
|
493
|
+
return self.ui
|
|
494
|
+
|
|
495
|
+
def process_text(self, input_text, settings):
|
|
496
|
+
"""Process text using the current settings."""
|
|
497
|
+
return self.processor.process_text(input_text, settings)
|
|
498
|
+
|
|
499
|
+
def get_default_settings(self):
|
|
500
|
+
"""Get default settings for the Regex Extractor."""
|
|
501
|
+
return {
|
|
502
|
+
"pattern": "",
|
|
503
|
+
"match_mode": "all_per_line",
|
|
504
|
+
"omit_duplicates": False,
|
|
505
|
+
"hide_counts": True,
|
|
506
|
+
"sort_results": False,
|
|
507
|
+
"case_sensitive": False
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
# Convenience functions for backward compatibility
|
|
512
|
+
def extract_regex_matches(text, pattern, match_mode="all_per_line", omit_duplicates=False, hide_counts=True, sort_results=False, case_sensitive=False):
|
|
513
|
+
"""Extract matches with specified options."""
|
|
514
|
+
return RegexExtractorProcessor.extract_matches(
|
|
515
|
+
text, pattern, match_mode, omit_duplicates, hide_counts, sort_results, case_sensitive
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def process_regex_extraction(input_text, settings):
|
|
520
|
+
"""Process regex extraction with the specified settings."""
|
|
521
|
+
return RegexExtractorProcessor.process_text(input_text, settings)
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
# BaseTool-compatible wrapper
|
|
525
|
+
try:
|
|
526
|
+
from tools.base_tool import BaseTool
|
|
527
|
+
from typing import Dict, Any
|
|
528
|
+
import tkinter as tk
|
|
529
|
+
from tkinter import ttk
|
|
530
|
+
|
|
531
|
+
class RegexExtractorV2(BaseTool):
|
|
532
|
+
"""
|
|
533
|
+
BaseTool-compatible version of RegexExtractor.
|
|
534
|
+
"""
|
|
535
|
+
|
|
536
|
+
TOOL_NAME = "Regex Extractor"
|
|
537
|
+
TOOL_DESCRIPTION = "Extract text matches using regular expressions"
|
|
538
|
+
TOOL_VERSION = "2.0.0"
|
|
539
|
+
|
|
540
|
+
def process_text(self, input_text: str, settings: Dict[str, Any]) -> str:
|
|
541
|
+
"""Extract regex matches from text."""
|
|
542
|
+
return RegexExtractorProcessor.extract_matches(
|
|
543
|
+
input_text,
|
|
544
|
+
settings.get("pattern", ""),
|
|
545
|
+
settings.get("match_mode", "all_per_line"),
|
|
546
|
+
settings.get("omit_duplicates", False),
|
|
547
|
+
settings.get("hide_counts", True),
|
|
548
|
+
settings.get("sort_results", False),
|
|
549
|
+
settings.get("case_sensitive", False)
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
def get_default_settings(self) -> Dict[str, Any]:
|
|
553
|
+
return {
|
|
554
|
+
"pattern": "",
|
|
555
|
+
"match_mode": "all_per_line",
|
|
556
|
+
"omit_duplicates": False,
|
|
557
|
+
"hide_counts": True,
|
|
558
|
+
"sort_results": False,
|
|
559
|
+
"case_sensitive": False
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
def create_ui(self, parent: tk.Widget, settings: Dict[str, Any],
|
|
563
|
+
on_change=None, on_apply=None) -> tk.Widget:
|
|
564
|
+
"""Create a simple UI for Regex Extractor."""
|
|
565
|
+
frame = ttk.Frame(parent)
|
|
566
|
+
ttk.Label(frame, text="Extract regex matches").pack(side=tk.LEFT, padx=5)
|
|
567
|
+
if on_apply:
|
|
568
|
+
ttk.Button(frame, text="Extract", command=on_apply).pack(side=tk.LEFT, padx=5)
|
|
569
|
+
return frame
|
|
570
|
+
|
|
571
|
+
except ImportError:
|
|
572
572
|
pass
|