crackerjack 0.31.10__py3-none-any.whl → 0.31.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of crackerjack might be problematic. Click here for more details.
- crackerjack/CLAUDE.md +288 -705
- crackerjack/__main__.py +22 -8
- crackerjack/agents/__init__.py +0 -3
- crackerjack/agents/architect_agent.py +0 -43
- crackerjack/agents/base.py +1 -9
- crackerjack/agents/coordinator.py +2 -148
- crackerjack/agents/documentation_agent.py +109 -81
- crackerjack/agents/dry_agent.py +122 -97
- crackerjack/agents/formatting_agent.py +3 -16
- crackerjack/agents/import_optimization_agent.py +1174 -130
- crackerjack/agents/performance_agent.py +956 -188
- crackerjack/agents/performance_helpers.py +229 -0
- crackerjack/agents/proactive_agent.py +1 -48
- crackerjack/agents/refactoring_agent.py +516 -246
- crackerjack/agents/refactoring_helpers.py +282 -0
- crackerjack/agents/security_agent.py +393 -90
- crackerjack/agents/test_creation_agent.py +1776 -120
- crackerjack/agents/test_specialist_agent.py +59 -15
- crackerjack/agents/tracker.py +0 -102
- crackerjack/api.py +145 -37
- crackerjack/cli/handlers.py +48 -30
- crackerjack/cli/interactive.py +11 -11
- crackerjack/cli/options.py +66 -4
- crackerjack/code_cleaner.py +808 -148
- crackerjack/config/global_lock_config.py +110 -0
- crackerjack/config/hooks.py +43 -64
- crackerjack/core/async_workflow_orchestrator.py +247 -97
- crackerjack/core/autofix_coordinator.py +192 -109
- crackerjack/core/enhanced_container.py +46 -63
- crackerjack/core/file_lifecycle.py +549 -0
- crackerjack/core/performance.py +9 -8
- crackerjack/core/performance_monitor.py +395 -0
- crackerjack/core/phase_coordinator.py +281 -94
- crackerjack/core/proactive_workflow.py +9 -58
- crackerjack/core/resource_manager.py +501 -0
- crackerjack/core/service_watchdog.py +490 -0
- crackerjack/core/session_coordinator.py +4 -8
- crackerjack/core/timeout_manager.py +504 -0
- crackerjack/core/websocket_lifecycle.py +475 -0
- crackerjack/core/workflow_orchestrator.py +343 -209
- crackerjack/dynamic_config.py +47 -6
- crackerjack/errors.py +3 -4
- crackerjack/executors/async_hook_executor.py +63 -13
- crackerjack/executors/cached_hook_executor.py +14 -14
- crackerjack/executors/hook_executor.py +100 -37
- crackerjack/executors/hook_lock_manager.py +856 -0
- crackerjack/executors/individual_hook_executor.py +120 -86
- crackerjack/intelligence/__init__.py +0 -7
- crackerjack/intelligence/adaptive_learning.py +13 -86
- crackerjack/intelligence/agent_orchestrator.py +15 -78
- crackerjack/intelligence/agent_registry.py +12 -59
- crackerjack/intelligence/agent_selector.py +31 -92
- crackerjack/intelligence/integration.py +1 -41
- crackerjack/interactive.py +9 -9
- crackerjack/managers/async_hook_manager.py +25 -8
- crackerjack/managers/hook_manager.py +9 -9
- crackerjack/managers/publish_manager.py +57 -59
- crackerjack/managers/test_command_builder.py +6 -36
- crackerjack/managers/test_executor.py +9 -61
- crackerjack/managers/test_manager.py +17 -63
- crackerjack/managers/test_manager_backup.py +77 -127
- crackerjack/managers/test_progress.py +4 -23
- crackerjack/mcp/cache.py +5 -12
- crackerjack/mcp/client_runner.py +10 -10
- crackerjack/mcp/context.py +64 -6
- crackerjack/mcp/dashboard.py +14 -11
- crackerjack/mcp/enhanced_progress_monitor.py +55 -55
- crackerjack/mcp/file_monitor.py +72 -42
- crackerjack/mcp/progress_components.py +103 -84
- crackerjack/mcp/progress_monitor.py +122 -49
- crackerjack/mcp/rate_limiter.py +12 -12
- crackerjack/mcp/server_core.py +16 -22
- crackerjack/mcp/service_watchdog.py +26 -26
- crackerjack/mcp/state.py +15 -0
- crackerjack/mcp/tools/core_tools.py +95 -39
- crackerjack/mcp/tools/error_analyzer.py +6 -32
- crackerjack/mcp/tools/execution_tools.py +1 -56
- crackerjack/mcp/tools/execution_tools_backup.py +35 -131
- crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
- crackerjack/mcp/tools/intelligence_tools.py +2 -55
- crackerjack/mcp/tools/monitoring_tools.py +308 -145
- crackerjack/mcp/tools/proactive_tools.py +12 -42
- crackerjack/mcp/tools/progress_tools.py +23 -15
- crackerjack/mcp/tools/utility_tools.py +3 -40
- crackerjack/mcp/tools/workflow_executor.py +40 -60
- crackerjack/mcp/websocket/app.py +0 -3
- crackerjack/mcp/websocket/endpoints.py +206 -268
- crackerjack/mcp/websocket/jobs.py +213 -66
- crackerjack/mcp/websocket/server.py +84 -6
- crackerjack/mcp/websocket/websocket_handler.py +137 -29
- crackerjack/models/config_adapter.py +3 -16
- crackerjack/models/protocols.py +162 -3
- crackerjack/models/resource_protocols.py +454 -0
- crackerjack/models/task.py +3 -3
- crackerjack/monitoring/__init__.py +0 -0
- crackerjack/monitoring/ai_agent_watchdog.py +25 -71
- crackerjack/monitoring/regression_prevention.py +28 -87
- crackerjack/orchestration/advanced_orchestrator.py +44 -78
- crackerjack/orchestration/coverage_improvement.py +10 -60
- crackerjack/orchestration/execution_strategies.py +16 -16
- crackerjack/orchestration/test_progress_streamer.py +61 -53
- crackerjack/plugins/base.py +1 -1
- crackerjack/plugins/managers.py +22 -20
- crackerjack/py313.py +65 -21
- crackerjack/services/backup_service.py +467 -0
- crackerjack/services/bounded_status_operations.py +627 -0
- crackerjack/services/cache.py +7 -9
- crackerjack/services/config.py +35 -52
- crackerjack/services/config_integrity.py +5 -16
- crackerjack/services/config_merge.py +542 -0
- crackerjack/services/contextual_ai_assistant.py +17 -19
- crackerjack/services/coverage_ratchet.py +44 -73
- crackerjack/services/debug.py +25 -39
- crackerjack/services/dependency_monitor.py +52 -50
- crackerjack/services/enhanced_filesystem.py +14 -11
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/filesystem.py +1 -12
- crackerjack/services/git.py +71 -47
- crackerjack/services/health_metrics.py +31 -27
- crackerjack/services/initialization.py +276 -428
- crackerjack/services/input_validator.py +760 -0
- crackerjack/services/log_manager.py +16 -16
- crackerjack/services/logging.py +7 -6
- crackerjack/services/metrics.py +43 -43
- crackerjack/services/pattern_cache.py +2 -31
- crackerjack/services/pattern_detector.py +26 -63
- crackerjack/services/performance_benchmarks.py +20 -45
- crackerjack/services/regex_patterns.py +2887 -0
- crackerjack/services/regex_utils.py +537 -0
- crackerjack/services/secure_path_utils.py +683 -0
- crackerjack/services/secure_status_formatter.py +534 -0
- crackerjack/services/secure_subprocess.py +605 -0
- crackerjack/services/security.py +47 -10
- crackerjack/services/security_logger.py +492 -0
- crackerjack/services/server_manager.py +109 -50
- crackerjack/services/smart_scheduling.py +8 -25
- crackerjack/services/status_authentication.py +603 -0
- crackerjack/services/status_security_manager.py +442 -0
- crackerjack/services/thread_safe_status_collector.py +546 -0
- crackerjack/services/tool_version_service.py +1 -23
- crackerjack/services/unified_config.py +36 -58
- crackerjack/services/validation_rate_limiter.py +269 -0
- crackerjack/services/version_checker.py +9 -40
- crackerjack/services/websocket_resource_limiter.py +572 -0
- crackerjack/slash_commands/__init__.py +52 -2
- crackerjack/tools/__init__.py +0 -0
- crackerjack/tools/validate_input_validator_patterns.py +262 -0
- crackerjack/tools/validate_regex_patterns.py +198 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/METADATA +197 -12
- crackerjack-0.31.12.dist-info/RECORD +178 -0
- crackerjack/cli/facade.py +0 -104
- crackerjack-0.31.10.dist-info/RECORD +0 -149
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/WHEEL +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for immediate regex pattern testing and validation.
|
|
3
|
+
|
|
4
|
+
Provides quick functions for testing regex patterns before adding them
|
|
5
|
+
to the centralized registry, and utilities for migrating existing re.sub() calls.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
import typing as t
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from crackerjack.services.regex_patterns import SAFE_PATTERNS, CompiledPatternCache
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_pattern_immediately(
|
|
16
|
+
pattern: str,
|
|
17
|
+
replacement: str,
|
|
18
|
+
test_cases: list[tuple[str, str]],
|
|
19
|
+
description: str = "",
|
|
20
|
+
) -> dict[str, t.Any]:
|
|
21
|
+
"""
|
|
22
|
+
Test a regex pattern immediately without adding to registry.
|
|
23
|
+
|
|
24
|
+
Returns a report of test results for quick validation.
|
|
25
|
+
"""
|
|
26
|
+
results: dict[str, t.Any] = {
|
|
27
|
+
"pattern": pattern,
|
|
28
|
+
"replacement": replacement,
|
|
29
|
+
"description": description,
|
|
30
|
+
"all_passed": True,
|
|
31
|
+
"test_results": [],
|
|
32
|
+
"warnings": [],
|
|
33
|
+
"errors": [],
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# Check for forbidden replacement syntax first using safe patterns\n forbidden_checks = [\n (r\"\\\\g\\\\s*<\\\\s*\\\\d+\\\\s*>\", \"\\\\g < 1 > with spaces\"),\n (r\"\\\\g<\\\\s+\\\\d+>\", \"\\\\g< 1> with space after <\"),\n (r\"\\\\g<\\\\d+\\\\s+>\", \"\\\\g<1 > with space before >\"),\n ]
|
|
37
|
+
|
|
38
|
+
# Validate pattern compilation using safe cache
|
|
39
|
+
try:
|
|
40
|
+
compiled = CompiledPatternCache.get_compiled_pattern(pattern)
|
|
41
|
+
except ValueError as e:
|
|
42
|
+
results["errors"].append(f"Invalid regex pattern: {e}")
|
|
43
|
+
results["all_passed"] = False
|
|
44
|
+
return results
|
|
45
|
+
|
|
46
|
+
# Test all cases
|
|
47
|
+
for i, (input_text, expected) in enumerate(test_cases):
|
|
48
|
+
try:
|
|
49
|
+
result = compiled.sub(replacement, input_text)
|
|
50
|
+
passed = result == expected
|
|
51
|
+
results["test_results"].append(
|
|
52
|
+
{
|
|
53
|
+
"test_case": i + 1,
|
|
54
|
+
"input": input_text,
|
|
55
|
+
"expected": expected,
|
|
56
|
+
"actual": result,
|
|
57
|
+
"passed": passed,
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
if not passed:
|
|
61
|
+
results["all_passed"] = False
|
|
62
|
+
except Exception as e:
|
|
63
|
+
results["test_results"].append(
|
|
64
|
+
{
|
|
65
|
+
"test_case": i + 1,
|
|
66
|
+
"input": input_text,
|
|
67
|
+
"expected": expected,
|
|
68
|
+
"actual": f"ERROR: {e}",
|
|
69
|
+
"passed": False,
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
results["all_passed"] = False
|
|
73
|
+
|
|
74
|
+
# Safety warnings
|
|
75
|
+
if ".*.*" in pattern:
|
|
76
|
+
results["warnings"].append(
|
|
77
|
+
"Multiple .* constructs may cause performance issues"
|
|
78
|
+
)
|
|
79
|
+
if ".+.+" in pattern:
|
|
80
|
+
results["warnings"].append(
|
|
81
|
+
"Multiple .+ constructs may cause performance issues"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return results
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def print_pattern_test_report(results: dict[str, t.Any]) -> None:
|
|
88
|
+
"""Print a formatted report of pattern test results."""
|
|
89
|
+
print("\n🔍 REGEX PATTERN TEST REPORT")
|
|
90
|
+
print("=" * 50)
|
|
91
|
+
print(f"Pattern: {results['pattern']}")
|
|
92
|
+
print(f"Replacement: {results['replacement']}")
|
|
93
|
+
if results["description"]:
|
|
94
|
+
print(f"Description: {results['description']}")
|
|
95
|
+
print()
|
|
96
|
+
|
|
97
|
+
if results["errors"]:
|
|
98
|
+
print("❌ ERRORS:")
|
|
99
|
+
for error in results["errors"]:
|
|
100
|
+
print(f" • {error}")
|
|
101
|
+
print()
|
|
102
|
+
|
|
103
|
+
if results["warnings"]:
|
|
104
|
+
print("⚠️ WARNINGS:")
|
|
105
|
+
for warning in results["warnings"]:
|
|
106
|
+
print(f" • {warning}")
|
|
107
|
+
print()
|
|
108
|
+
|
|
109
|
+
print("📋 TEST CASES:")
|
|
110
|
+
for test in results["test_results"]:
|
|
111
|
+
status = "✅ PASS" if test["passed"] else "❌ FAIL"
|
|
112
|
+
print(
|
|
113
|
+
f" {status} Test {test['test_case']}: '{test['input']}' → '{test['actual']}'"
|
|
114
|
+
)
|
|
115
|
+
if not test["passed"]:
|
|
116
|
+
print(f" Expected: '{test['expected']}'")
|
|
117
|
+
|
|
118
|
+
print(
|
|
119
|
+
f"\n🎯 OVERALL: {'✅ ALL TESTS PASSED' if results['all_passed'] else '❌ TESTS FAILED'}"
|
|
120
|
+
)
|
|
121
|
+
print("=" * 50)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def quick_pattern_test(
|
|
125
|
+
pattern: str,
|
|
126
|
+
replacement: str,
|
|
127
|
+
test_cases: list[tuple[str, str]],
|
|
128
|
+
description: str = "",
|
|
129
|
+
) -> bool:
|
|
130
|
+
"""Quick test function that returns True if all tests pass."""
|
|
131
|
+
results = test_pattern_immediately(pattern, replacement, test_cases, description)
|
|
132
|
+
print_pattern_test_report(results)
|
|
133
|
+
return results["all_passed"]
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def find_safe_pattern_for_text(text: str) -> list[str]:
|
|
137
|
+
"""Find which existing safe patterns would match the given text."""
|
|
138
|
+
matches = []
|
|
139
|
+
for name, pattern in SAFE_PATTERNS.items():
|
|
140
|
+
try:
|
|
141
|
+
if pattern.test(text):
|
|
142
|
+
matches.append(name)
|
|
143
|
+
except Exception:
|
|
144
|
+
# Skip patterns that error
|
|
145
|
+
continue
|
|
146
|
+
return matches
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def suggest_migration_for_re_sub(
|
|
150
|
+
original_pattern: str, original_replacement: str, sample_text: str = ""
|
|
151
|
+
) -> dict[str, t.Any]:
|
|
152
|
+
"""
|
|
153
|
+
Suggest how to migrate a raw re.sub() call to use safe patterns.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
original_pattern: The original regex pattern
|
|
157
|
+
original_replacement: The original replacement string
|
|
158
|
+
sample_text: Optional sample text to test against
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Dictionary with migration suggestions
|
|
162
|
+
"""
|
|
163
|
+
suggestion: dict[str, t.Any] = {
|
|
164
|
+
"original_pattern": original_pattern,
|
|
165
|
+
"original_replacement": original_replacement,
|
|
166
|
+
"existing_matches": [],
|
|
167
|
+
"needs_new_pattern": True,
|
|
168
|
+
"safety_issues": [],
|
|
169
|
+
"suggested_name": "",
|
|
170
|
+
"test_cases_needed": [],
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# Check for safety issues first using safe patterns
|
|
174
|
+
forbidden_checks = [
|
|
175
|
+
(r"\\g\s*<\s*\d+\s*>", "\\g < 1 > with spaces"),
|
|
176
|
+
(r"\\g<\s+\d+>", "\\g< 1> with space after <"),
|
|
177
|
+
(r"\\\\g<\\d+\\s+>", "\\\\g<1 > with space before >"),
|
|
178
|
+
]
|
|
179
|
+
for forbidden_pattern, _ in forbidden_checks:
|
|
180
|
+
compiled = CompiledPatternCache.get_compiled_pattern(forbidden_pattern)
|
|
181
|
+
if compiled.search(original_replacement):
|
|
182
|
+
suggestion["safety_issues"].append(
|
|
183
|
+
"CRITICAL: Bad replacement syntax - spaces in \\g<1>"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Look for existing patterns that might work
|
|
187
|
+
if sample_text:
|
|
188
|
+
matches = find_safe_pattern_for_text(sample_text)
|
|
189
|
+
suggestion["existing_matches"] = matches
|
|
190
|
+
if matches:
|
|
191
|
+
suggestion["needs_new_pattern"] = False
|
|
192
|
+
|
|
193
|
+
# Generate suggested pattern name based on original pattern
|
|
194
|
+
if "python.*-.*m" in original_pattern:
|
|
195
|
+
suggestion["suggested_name"] = "fix_python_command_spacing"
|
|
196
|
+
elif r"\-\s*\-" in original_pattern:
|
|
197
|
+
suggestion["suggested_name"] = "fix_double_dash_spacing"
|
|
198
|
+
elif "token" in original_pattern.lower():
|
|
199
|
+
suggestion["suggested_name"] = "fix_token_pattern"
|
|
200
|
+
elif "password" in original_pattern.lower():
|
|
201
|
+
suggestion["suggested_name"] = "fix_password_pattern"
|
|
202
|
+
else:
|
|
203
|
+
# Generate name from pattern keywords using safe pattern
|
|
204
|
+
keyword_pattern = CompiledPatternCache.get_compiled_pattern(r"[a-zA-Z]+")
|
|
205
|
+
keywords = keyword_pattern.findall(original_pattern)
|
|
206
|
+
if keywords:
|
|
207
|
+
suggestion["suggested_name"] = (
|
|
208
|
+
f"fix_{'_'.join(keywords[:3])}_pattern".lower()
|
|
209
|
+
)
|
|
210
|
+
else:
|
|
211
|
+
suggestion["suggested_name"] = "fix_custom_pattern"
|
|
212
|
+
|
|
213
|
+
# Suggest test cases based on common patterns
|
|
214
|
+
if sample_text:
|
|
215
|
+
suggestion["test_cases_needed"].append((sample_text, "Expected output needed"))
|
|
216
|
+
|
|
217
|
+
# Common test cases for spacing issues
|
|
218
|
+
if "-" in original_pattern:
|
|
219
|
+
suggestion["test_cases_needed"].extend(
|
|
220
|
+
[
|
|
221
|
+
("word - word", "word-word"),
|
|
222
|
+
("already-good", "already-good"), # No change
|
|
223
|
+
("multiple - word - spacing", "multiple-word - spacing"), # Partial fix
|
|
224
|
+
]
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
return suggestion
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def print_migration_suggestion(suggestion: dict[str, t.Any]) -> None:
|
|
231
|
+
"""Print a formatted migration suggestion report."""
|
|
232
|
+
print("\n🔄 REGEX MIGRATION SUGGESTION")
|
|
233
|
+
print("=" * 50)
|
|
234
|
+
print(f"Original Pattern: {suggestion['original_pattern']}")
|
|
235
|
+
print(f"Original Replacement: {suggestion['original_replacement']}")
|
|
236
|
+
print()
|
|
237
|
+
|
|
238
|
+
if suggestion["safety_issues"]:
|
|
239
|
+
print("❌ SAFETY ISSUES:")
|
|
240
|
+
for issue in suggestion["safety_issues"]:
|
|
241
|
+
print(f" • {issue}")
|
|
242
|
+
print()
|
|
243
|
+
|
|
244
|
+
if suggestion["existing_matches"]:
|
|
245
|
+
print("✅ EXISTING PATTERNS AVAILABLE:")
|
|
246
|
+
for pattern_name in suggestion["existing_matches"]:
|
|
247
|
+
pattern = SAFE_PATTERNS[pattern_name]
|
|
248
|
+
print(f" • {pattern_name}: {pattern.description}")
|
|
249
|
+
print("💡 Consider using existing patterns instead of creating new ones.")
|
|
250
|
+
print()
|
|
251
|
+
|
|
252
|
+
if suggestion["needs_new_pattern"]:
|
|
253
|
+
print("🆕 NEW PATTERN NEEDED:")
|
|
254
|
+
print(f" Suggested Name: {suggestion['suggested_name']}")
|
|
255
|
+
print(" Add to crackerjack/services/regex_patterns.py:")
|
|
256
|
+
print()
|
|
257
|
+
print(" ```python")
|
|
258
|
+
print(f' "{suggestion["suggested_name"]}": ValidatedPattern(')
|
|
259
|
+
print(f' name="{suggestion["suggested_name"]}",')
|
|
260
|
+
print(f' pattern=r"{suggestion["original_pattern"]}",')
|
|
261
|
+
print(f' replacement=r"{suggestion["original_replacement"]}",')
|
|
262
|
+
print(' description="TODO: Add description",')
|
|
263
|
+
print(" test_cases=[")
|
|
264
|
+
for test_input, test_output in suggestion["test_cases_needed"]:
|
|
265
|
+
print(f' ("{test_input}", "{test_output}"),')
|
|
266
|
+
print(" ]")
|
|
267
|
+
print(" ),")
|
|
268
|
+
print(" ```")
|
|
269
|
+
print()
|
|
270
|
+
|
|
271
|
+
print("🔧 MIGRATION STEPS:")
|
|
272
|
+
print(" 1. Fix any safety issues in replacement syntax")
|
|
273
|
+
if suggestion["existing_matches"]:
|
|
274
|
+
print(" 2. Use existing safe patterns if possible:")
|
|
275
|
+
for pattern_name in suggestion["existing_matches"]:
|
|
276
|
+
print(f" SAFE_PATTERNS['{pattern_name}'].apply(text)")
|
|
277
|
+
if suggestion["needs_new_pattern"]:
|
|
278
|
+
print(" 3. Add new ValidatedPattern to regex_patterns.py")
|
|
279
|
+
print(" 4. Test thoroughly with comprehensive test cases")
|
|
280
|
+
print(" 5. Replace re.sub() call with safe pattern usage")
|
|
281
|
+
print(" 6. Run pre-commit hook to validate")
|
|
282
|
+
|
|
283
|
+
print("=" * 50)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def audit_file_for_re_sub(file_path: Path) -> list[dict[str, t.Any]]:
|
|
287
|
+
"""
|
|
288
|
+
Audit a file for re.sub() calls and return migration suggestions.
|
|
289
|
+
|
|
290
|
+
Returns list of findings with line numbers and suggestions.
|
|
291
|
+
"""
|
|
292
|
+
findings = []
|
|
293
|
+
|
|
294
|
+
try:
|
|
295
|
+
content = file_path.read_text(encoding="utf-8")
|
|
296
|
+
lines = content.split("\n")
|
|
297
|
+
|
|
298
|
+
for i, line in enumerate(lines, 1):
|
|
299
|
+
# Look for re.sub() calls using safe pattern
|
|
300
|
+
re_sub_pattern = CompiledPatternCache.get_compiled_pattern(
|
|
301
|
+
r're\.sub\s*\(\s*[r]?["\']([^"\']+)["\'],\s*[r]?["\']([^"\']*)["\']'
|
|
302
|
+
)
|
|
303
|
+
re_sub_match = re_sub_pattern.search(line)
|
|
304
|
+
if re_sub_match:
|
|
305
|
+
pattern = re_sub_match.group(1)
|
|
306
|
+
replacement = re_sub_match.group(2)
|
|
307
|
+
|
|
308
|
+
finding = {
|
|
309
|
+
"file": str(file_path),
|
|
310
|
+
"line_number": i,
|
|
311
|
+
"line_content": line.strip(),
|
|
312
|
+
"pattern": pattern,
|
|
313
|
+
"replacement": replacement,
|
|
314
|
+
"suggestion": suggest_migration_for_re_sub(pattern, replacement),
|
|
315
|
+
}
|
|
316
|
+
findings.append(finding)
|
|
317
|
+
|
|
318
|
+
except Exception as e:
|
|
319
|
+
findings.append(
|
|
320
|
+
{
|
|
321
|
+
"file": str(file_path),
|
|
322
|
+
"line_number": 0,
|
|
323
|
+
"error": f"Failed to audit file: {e}",
|
|
324
|
+
}
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
return findings
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def audit_codebase_re_sub() -> dict[str, list[dict[str, t.Any]]]:
|
|
331
|
+
"""
|
|
332
|
+
Audit entire crackerjack codebase for re.sub() usage.
|
|
333
|
+
|
|
334
|
+
Returns dictionary mapping file paths to findings.
|
|
335
|
+
"""
|
|
336
|
+
findings_by_file = {}
|
|
337
|
+
|
|
338
|
+
# Audit crackerjack package
|
|
339
|
+
crackerjack_dir = Path(__file__).parent.parent
|
|
340
|
+
|
|
341
|
+
for py_file in crackerjack_dir.rglob("*.py"):
|
|
342
|
+
# Skip test files and __pycache__
|
|
343
|
+
if "test_" in py_file.name or "__pycache__" in str(py_file):
|
|
344
|
+
continue
|
|
345
|
+
|
|
346
|
+
findings = audit_file_for_re_sub(py_file)
|
|
347
|
+
if findings:
|
|
348
|
+
findings_by_file[str(py_file)] = findings
|
|
349
|
+
|
|
350
|
+
return findings_by_file
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def replace_unsafe_regex_with_safe_patterns(content: str) -> str:
|
|
354
|
+
"""
|
|
355
|
+
Replace unsafe regex patterns in content with safe alternatives.
|
|
356
|
+
|
|
357
|
+
This function looks for common regex patterns and replaces them with
|
|
358
|
+
calls to SAFE_PATTERNS where possible.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
content: The source code content to fix
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
Fixed content with safe patterns applied
|
|
365
|
+
"""
|
|
366
|
+
lines = content.split("\n")
|
|
367
|
+
modified = False
|
|
368
|
+
|
|
369
|
+
has_safe_patterns_import = _check_for_safe_patterns_import(lines)
|
|
370
|
+
|
|
371
|
+
for i, line in enumerate(lines):
|
|
372
|
+
original_line = line
|
|
373
|
+
|
|
374
|
+
# Fix critical replacement syntax issues first
|
|
375
|
+
line = _fix_replacement_syntax_issues(line)
|
|
376
|
+
|
|
377
|
+
# Process re.sub patterns
|
|
378
|
+
line, _, needs_import = _process_re_sub_patterns(line, has_safe_patterns_import)
|
|
379
|
+
|
|
380
|
+
if needs_import and not has_safe_patterns_import:
|
|
381
|
+
import_index = _find_import_insertion_point(lines)
|
|
382
|
+
lines.insert(
|
|
383
|
+
import_index,
|
|
384
|
+
"from crackerjack.services.regex_patterns import SAFE_PATTERNS",
|
|
385
|
+
)
|
|
386
|
+
has_safe_patterns_import = True
|
|
387
|
+
i += 1 # Adjust index for inserted line
|
|
388
|
+
|
|
389
|
+
# Update the line if it changed
|
|
390
|
+
if line != original_line:
|
|
391
|
+
lines[i] = line
|
|
392
|
+
modified = True
|
|
393
|
+
|
|
394
|
+
return "\n".join(lines) if modified else content
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def _check_for_safe_patterns_import(lines: list[str]) -> bool:
|
|
398
|
+
"""Check if SAFE_PATTERNS import is already present."""
|
|
399
|
+
return any(
|
|
400
|
+
"from crackerjack.services.regex_patterns import SAFE_PATTERNS" in line
|
|
401
|
+
or "SAFE_PATTERNS" in line
|
|
402
|
+
for line in lines
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _fix_replacement_syntax_issues(line: str) -> str:
|
|
407
|
+
"""Fix critical replacement syntax issues in regex patterns."""
|
|
408
|
+
if r"\g < " in line or r"\g< " in line or r"\g <" in line:
|
|
409
|
+
# Fix spacing in replacement groups using safe pattern
|
|
410
|
+
spacing_fix_pattern = CompiledPatternCache.get_compiled_pattern(
|
|
411
|
+
r"\\g\s*<\s*(\d+)\s*>"
|
|
412
|
+
)
|
|
413
|
+
line = spacing_fix_pattern.sub(r"\\g<\1>", line)
|
|
414
|
+
return line
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _process_re_sub_patterns(
|
|
418
|
+
line: str, has_safe_patterns_import: bool
|
|
419
|
+
) -> tuple[str, bool, bool]:
|
|
420
|
+
"""Process re.sub patterns and replace with safe alternatives."""
|
|
421
|
+
re_sub_match = CompiledPatternCache.get_compiled_pattern(
|
|
422
|
+
r're\.sub\s*\(\s*r?["\']([^"\']+)["\']\s*,\s*r?["\']([^"\']*)["\']'
|
|
423
|
+
).search(line)
|
|
424
|
+
|
|
425
|
+
if not re_sub_match:
|
|
426
|
+
return line, False, False
|
|
427
|
+
|
|
428
|
+
pattern = re_sub_match.group(1)
|
|
429
|
+
replacement = re_sub_match.group(2)
|
|
430
|
+
|
|
431
|
+
safe_pattern_name = _identify_safe_pattern(pattern, replacement)
|
|
432
|
+
if not safe_pattern_name:
|
|
433
|
+
return line, False, False
|
|
434
|
+
|
|
435
|
+
return _replace_with_safe_pattern(line, re_sub_match, safe_pattern_name)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _identify_safe_pattern(pattern: str, replacement: str) -> str | None:
|
|
439
|
+
"""Identify which safe pattern matches the given regex pattern."""
|
|
440
|
+
# Common patterns we can automatically replace
|
|
441
|
+
if pattern == r"(\w+)\s*-\s*(\w+)" and replacement in (
|
|
442
|
+
r"\1-\2",
|
|
443
|
+
r"\g<1>-\g<2>",
|
|
444
|
+
):
|
|
445
|
+
return "fix_hyphenated_names"
|
|
446
|
+
elif "token" in pattern.lower() and "*" in replacement:
|
|
447
|
+
return "mask_tokens"
|
|
448
|
+
elif r"python\s*-\s*m" in pattern:
|
|
449
|
+
return "fix_python_command_spacing"
|
|
450
|
+
return None
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def _replace_with_safe_pattern(
|
|
454
|
+
line: str, re_sub_match: re.Match[str], safe_pattern_name: str
|
|
455
|
+
) -> tuple[str, bool, bool]:
|
|
456
|
+
"""Replace re.sub call with safe pattern call."""
|
|
457
|
+
before_re_sub = line[: re_sub_match.start()]
|
|
458
|
+
after_re_sub = line[re_sub_match.end() :]
|
|
459
|
+
|
|
460
|
+
# Look for assignment pattern: var = re.sub(...)
|
|
461
|
+
assign_match = CompiledPatternCache.get_compiled_pattern(r"(\w+)\s*=\s*$").search(
|
|
462
|
+
before_re_sub
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
if assign_match:
|
|
466
|
+
return _handle_assignment_pattern(
|
|
467
|
+
line, assign_match, before_re_sub, after_re_sub, safe_pattern_name
|
|
468
|
+
)
|
|
469
|
+
return _handle_direct_replacement(line, re_sub_match, safe_pattern_name)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _handle_assignment_pattern(
|
|
473
|
+
line: str,
|
|
474
|
+
assign_match: re.Match[str],
|
|
475
|
+
before_re_sub: str,
|
|
476
|
+
after_re_sub: str,
|
|
477
|
+
safe_pattern_name: str,
|
|
478
|
+
) -> tuple[str, bool, bool]:
|
|
479
|
+
"""Handle assignment pattern replacement."""
|
|
480
|
+
var_name = assign_match.group(1)
|
|
481
|
+
text_var = _extract_source_variable(line)
|
|
482
|
+
new_line = f"{var_name} = SAFE_PATTERNS['{safe_pattern_name}'].apply({text_var})"
|
|
483
|
+
return before_re_sub + new_line + after_re_sub, True, True
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def _handle_direct_replacement(
|
|
487
|
+
line: str, re_sub_match: re.Match[str], safe_pattern_name: str
|
|
488
|
+
) -> tuple[str, bool, bool]:
|
|
489
|
+
"""Handle direct replacement of re.sub call."""
|
|
490
|
+
text_var = _extract_source_variable(line)
|
|
491
|
+
new_line = line.replace(
|
|
492
|
+
re_sub_match.group(0),
|
|
493
|
+
f"SAFE_PATTERNS['{safe_pattern_name}'].apply({text_var})",
|
|
494
|
+
)
|
|
495
|
+
return new_line, True, True
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def _extract_source_variable(line: str) -> str:
|
|
499
|
+
"""Extract the source variable from re.sub call."""
|
|
500
|
+
full_match = CompiledPatternCache.get_compiled_pattern(
|
|
501
|
+
r"re\.sub\s*\([^,]+,\s*[^,]+,\s*(\w+)"
|
|
502
|
+
).search(line)
|
|
503
|
+
return full_match.group(1) if full_match else "text"
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def _find_import_insertion_point(lines: list[str]) -> int:
|
|
507
|
+
"""Find the right place to insert the import statement."""
|
|
508
|
+
import_index = 0
|
|
509
|
+
for j, check_line in enumerate(lines):
|
|
510
|
+
if check_line.strip().startswith(("import ", "from ")):
|
|
511
|
+
import_index = j + 1
|
|
512
|
+
elif check_line.strip() == "":
|
|
513
|
+
continue
|
|
514
|
+
else:
|
|
515
|
+
break
|
|
516
|
+
return import_index
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
if __name__ == "__main__":
|
|
520
|
+
# Example usage for testing patterns
|
|
521
|
+
test_result = quick_pattern_test(
|
|
522
|
+
pattern=r"(\w+)\s*-\s*(\w+)",
|
|
523
|
+
replacement=r"\1-\2",
|
|
524
|
+
test_cases=[
|
|
525
|
+
("python - pro", "python-pro"),
|
|
526
|
+
("already-good", "already-good"),
|
|
527
|
+
("test - case - multiple", "test-case - multiple"),
|
|
528
|
+
],
|
|
529
|
+
description="Fix spacing in hyphenated names",
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
# Example migration suggestion
|
|
533
|
+
print("\n" + "=" * 60)
|
|
534
|
+
migration = suggest_migration_for_re_sub(
|
|
535
|
+
r"python\s*-\s*m\s+", "python -m ", "python - m crackerjack"
|
|
536
|
+
)
|
|
537
|
+
print_migration_suggestion(migration)
|