ai-coding-assistant 0.5.0__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.
- ai_coding_assistant-0.5.0.dist-info/METADATA +226 -0
- ai_coding_assistant-0.5.0.dist-info/RECORD +89 -0
- ai_coding_assistant-0.5.0.dist-info/WHEEL +4 -0
- ai_coding_assistant-0.5.0.dist-info/entry_points.txt +3 -0
- ai_coding_assistant-0.5.0.dist-info/licenses/LICENSE +21 -0
- coding_assistant/__init__.py +3 -0
- coding_assistant/__main__.py +19 -0
- coding_assistant/cli/__init__.py +1 -0
- coding_assistant/cli/app.py +158 -0
- coding_assistant/cli/commands/__init__.py +19 -0
- coding_assistant/cli/commands/ask.py +178 -0
- coding_assistant/cli/commands/config.py +438 -0
- coding_assistant/cli/commands/diagram.py +267 -0
- coding_assistant/cli/commands/document.py +410 -0
- coding_assistant/cli/commands/explain.py +192 -0
- coding_assistant/cli/commands/fix.py +249 -0
- coding_assistant/cli/commands/index.py +162 -0
- coding_assistant/cli/commands/refactor.py +245 -0
- coding_assistant/cli/commands/search.py +182 -0
- coding_assistant/cli/commands/serve_docs.py +128 -0
- coding_assistant/cli/repl.py +381 -0
- coding_assistant/cli/theme.py +90 -0
- coding_assistant/codebase/__init__.py +1 -0
- coding_assistant/codebase/crawler.py +93 -0
- coding_assistant/codebase/parser.py +266 -0
- coding_assistant/config/__init__.py +25 -0
- coding_assistant/config/config_manager.py +615 -0
- coding_assistant/config/settings.py +82 -0
- coding_assistant/context/__init__.py +19 -0
- coding_assistant/context/chunker.py +443 -0
- coding_assistant/context/enhanced_retriever.py +322 -0
- coding_assistant/context/hybrid_search.py +311 -0
- coding_assistant/context/ranker.py +355 -0
- coding_assistant/context/retriever.py +119 -0
- coding_assistant/context/window.py +362 -0
- coding_assistant/documentation/__init__.py +23 -0
- coding_assistant/documentation/agents/__init__.py +27 -0
- coding_assistant/documentation/agents/coordinator.py +510 -0
- coding_assistant/documentation/agents/module_documenter.py +111 -0
- coding_assistant/documentation/agents/synthesizer.py +139 -0
- coding_assistant/documentation/agents/task_delegator.py +100 -0
- coding_assistant/documentation/decomposition/__init__.py +21 -0
- coding_assistant/documentation/decomposition/context_preserver.py +477 -0
- coding_assistant/documentation/decomposition/module_detector.py +302 -0
- coding_assistant/documentation/decomposition/partitioner.py +621 -0
- coding_assistant/documentation/generators/__init__.py +14 -0
- coding_assistant/documentation/generators/dataflow_generator.py +440 -0
- coding_assistant/documentation/generators/diagram_generator.py +511 -0
- coding_assistant/documentation/graph/__init__.py +13 -0
- coding_assistant/documentation/graph/dependency_builder.py +468 -0
- coding_assistant/documentation/graph/module_analyzer.py +475 -0
- coding_assistant/documentation/writers/__init__.py +11 -0
- coding_assistant/documentation/writers/markdown_writer.py +322 -0
- coding_assistant/embeddings/__init__.py +0 -0
- coding_assistant/embeddings/generator.py +89 -0
- coding_assistant/embeddings/store.py +187 -0
- coding_assistant/exceptions/__init__.py +50 -0
- coding_assistant/exceptions/base.py +110 -0
- coding_assistant/exceptions/llm.py +249 -0
- coding_assistant/exceptions/recovery.py +263 -0
- coding_assistant/exceptions/storage.py +213 -0
- coding_assistant/exceptions/validation.py +230 -0
- coding_assistant/llm/__init__.py +1 -0
- coding_assistant/llm/client.py +277 -0
- coding_assistant/llm/gemini_client.py +181 -0
- coding_assistant/llm/groq_client.py +160 -0
- coding_assistant/llm/prompts.py +98 -0
- coding_assistant/llm/together_client.py +160 -0
- coding_assistant/operations/__init__.py +13 -0
- coding_assistant/operations/differ.py +369 -0
- coding_assistant/operations/generator.py +347 -0
- coding_assistant/operations/linter.py +430 -0
- coding_assistant/operations/validator.py +406 -0
- coding_assistant/storage/__init__.py +9 -0
- coding_assistant/storage/database.py +363 -0
- coding_assistant/storage/session.py +231 -0
- coding_assistant/utils/__init__.py +31 -0
- coding_assistant/utils/cache.py +477 -0
- coding_assistant/utils/hardware.py +132 -0
- coding_assistant/utils/keystore.py +206 -0
- coding_assistant/utils/logger.py +32 -0
- coding_assistant/utils/progress.py +311 -0
- coding_assistant/validation/__init__.py +13 -0
- coding_assistant/validation/files.py +305 -0
- coding_assistant/validation/inputs.py +335 -0
- coding_assistant/validation/params.py +280 -0
- coding_assistant/validation/sanitizers.py +243 -0
- coding_assistant/vcs/__init__.py +5 -0
- coding_assistant/vcs/git.py +269 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"""Parameter validation for CLI commands."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, List, Any
|
|
4
|
+
|
|
5
|
+
from coding_assistant.exceptions.validation import (
|
|
6
|
+
ValidationError,
|
|
7
|
+
InvalidParameterError
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ParameterValidator:
|
|
12
|
+
"""
|
|
13
|
+
Validates CLI command parameters.
|
|
14
|
+
|
|
15
|
+
Ensures parameters are within acceptable ranges and formats.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def validate_top_k(value: int) -> int:
|
|
20
|
+
"""
|
|
21
|
+
Validate top_k parameter for search results.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
value: Number of results to return
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Validated value
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
InvalidParameterError: If value is invalid
|
|
31
|
+
"""
|
|
32
|
+
if not isinstance(value, int):
|
|
33
|
+
raise InvalidParameterError(
|
|
34
|
+
"top_k",
|
|
35
|
+
value,
|
|
36
|
+
"positive integer"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
if value < 1:
|
|
40
|
+
raise InvalidParameterError(
|
|
41
|
+
"top_k",
|
|
42
|
+
value,
|
|
43
|
+
"positive integer (>= 1)"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if value > 100:
|
|
47
|
+
raise InvalidParameterError(
|
|
48
|
+
"top_k",
|
|
49
|
+
value,
|
|
50
|
+
"positive integer (<= 100)",
|
|
51
|
+
suggestion="Use a smaller value (1-100) for better performance"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return value
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def validate_max_files(value: int) -> int:
|
|
58
|
+
"""
|
|
59
|
+
Validate max_files parameter for indexing.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
value: Maximum number of files to index
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Validated value
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
InvalidParameterError: If value is invalid
|
|
69
|
+
"""
|
|
70
|
+
if not isinstance(value, int):
|
|
71
|
+
raise InvalidParameterError(
|
|
72
|
+
"max_files",
|
|
73
|
+
value,
|
|
74
|
+
"positive integer"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if value < 1:
|
|
78
|
+
raise InvalidParameterError(
|
|
79
|
+
"max_files",
|
|
80
|
+
value,
|
|
81
|
+
"positive integer (>= 1)"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
if value > 10000:
|
|
85
|
+
raise InvalidParameterError(
|
|
86
|
+
"max_files",
|
|
87
|
+
value,
|
|
88
|
+
"positive integer (<= 10000)",
|
|
89
|
+
suggestion="Use a smaller value for better performance. 100-1000 is usually sufficient."
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return value
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def validate_temperature(value: float) -> float:
|
|
96
|
+
"""
|
|
97
|
+
Validate LLM temperature parameter.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
value: Temperature value
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Validated value
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
InvalidParameterError: If value is invalid
|
|
107
|
+
"""
|
|
108
|
+
if not isinstance(value, (int, float)):
|
|
109
|
+
raise InvalidParameterError(
|
|
110
|
+
"temperature",
|
|
111
|
+
value,
|
|
112
|
+
"number between 0.0 and 2.0"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
value = float(value)
|
|
116
|
+
|
|
117
|
+
if value < 0.0 or value > 2.0:
|
|
118
|
+
raise InvalidParameterError(
|
|
119
|
+
"temperature",
|
|
120
|
+
value,
|
|
121
|
+
"number between 0.0 and 2.0",
|
|
122
|
+
suggestion="Use 0.0 for deterministic, 0.7 for balanced, 1.0+ for creative"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
return value
|
|
126
|
+
|
|
127
|
+
@staticmethod
|
|
128
|
+
def validate_hybrid_alpha(value: float) -> float:
|
|
129
|
+
"""
|
|
130
|
+
Validate hybrid search alpha parameter.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
value: Alpha value (weight for vector search)
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Validated value
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
InvalidParameterError: If value is invalid
|
|
140
|
+
"""
|
|
141
|
+
if not isinstance(value, (int, float)):
|
|
142
|
+
raise InvalidParameterError(
|
|
143
|
+
"hybrid_alpha",
|
|
144
|
+
value,
|
|
145
|
+
"number between 0.0 and 1.0"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
value = float(value)
|
|
149
|
+
|
|
150
|
+
if value < 0.0 or value > 1.0:
|
|
151
|
+
raise InvalidParameterError(
|
|
152
|
+
"hybrid_alpha",
|
|
153
|
+
value,
|
|
154
|
+
"number between 0.0 and 1.0",
|
|
155
|
+
suggestion="Use 0.0 for keyword-only, 0.5 for balanced, 1.0 for vector-only"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
return value
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def validate_provider(value: str, allowed_providers: Optional[List[str]] = None) -> str:
|
|
162
|
+
"""
|
|
163
|
+
Validate LLM provider parameter.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
value: Provider name
|
|
167
|
+
allowed_providers: List of allowed providers
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Validated provider name
|
|
171
|
+
|
|
172
|
+
Raises:
|
|
173
|
+
InvalidParameterError: If provider is invalid
|
|
174
|
+
"""
|
|
175
|
+
if allowed_providers is None:
|
|
176
|
+
allowed_providers = ['ollama', 'openai', 'claude', 'mock']
|
|
177
|
+
|
|
178
|
+
value_lower = value.lower()
|
|
179
|
+
|
|
180
|
+
if value_lower not in allowed_providers:
|
|
181
|
+
raise InvalidParameterError(
|
|
182
|
+
"provider",
|
|
183
|
+
value,
|
|
184
|
+
f"one of: {', '.join(allowed_providers)}",
|
|
185
|
+
suggestion=f"Choose from: {', '.join(allowed_providers)}"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
return value_lower
|
|
189
|
+
|
|
190
|
+
@staticmethod
|
|
191
|
+
def validate_file_type(value: str) -> str:
|
|
192
|
+
"""
|
|
193
|
+
Validate file type filter parameter.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
value: File type (extension or language)
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Validated file type
|
|
200
|
+
|
|
201
|
+
Raises:
|
|
202
|
+
InvalidParameterError: If file type is invalid
|
|
203
|
+
"""
|
|
204
|
+
common_types = [
|
|
205
|
+
'python', 'py',
|
|
206
|
+
'javascript', 'js', 'jsx',
|
|
207
|
+
'typescript', 'ts', 'tsx',
|
|
208
|
+
'java',
|
|
209
|
+
'cpp', 'c', 'h',
|
|
210
|
+
'csharp', 'cs',
|
|
211
|
+
'go',
|
|
212
|
+
'rust', 'rs',
|
|
213
|
+
'ruby', 'rb',
|
|
214
|
+
'php',
|
|
215
|
+
'swift',
|
|
216
|
+
'kotlin', 'kt'
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
value_lower = value.lower()
|
|
220
|
+
|
|
221
|
+
# Map common names to extensions
|
|
222
|
+
type_map = {
|
|
223
|
+
'python': 'py',
|
|
224
|
+
'javascript': 'js',
|
|
225
|
+
'typescript': 'ts',
|
|
226
|
+
'csharp': 'cs',
|
|
227
|
+
'rust': 'rs',
|
|
228
|
+
'ruby': 'rb',
|
|
229
|
+
'kotlin': 'kt'
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if value_lower in type_map:
|
|
233
|
+
return type_map[value_lower]
|
|
234
|
+
|
|
235
|
+
if value_lower in common_types:
|
|
236
|
+
return value_lower
|
|
237
|
+
|
|
238
|
+
# Allow any extension starting with .
|
|
239
|
+
if value.startswith('.'):
|
|
240
|
+
return value[1:] # Remove dot
|
|
241
|
+
|
|
242
|
+
return value_lower
|
|
243
|
+
|
|
244
|
+
@staticmethod
|
|
245
|
+
def validate_context_size(value: int) -> int:
|
|
246
|
+
"""
|
|
247
|
+
Validate context size parameter.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
value: Number of context chunks
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
Validated value
|
|
254
|
+
|
|
255
|
+
Raises:
|
|
256
|
+
InvalidParameterError: If value is invalid
|
|
257
|
+
"""
|
|
258
|
+
if not isinstance(value, int):
|
|
259
|
+
raise InvalidParameterError(
|
|
260
|
+
"context_size",
|
|
261
|
+
value,
|
|
262
|
+
"positive integer"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if value < 1:
|
|
266
|
+
raise InvalidParameterError(
|
|
267
|
+
"context_size",
|
|
268
|
+
value,
|
|
269
|
+
"positive integer (>= 1)"
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
if value > 50:
|
|
273
|
+
raise InvalidParameterError(
|
|
274
|
+
"context_size",
|
|
275
|
+
value,
|
|
276
|
+
"positive integer (<= 50)",
|
|
277
|
+
suggestion="Use a smaller value (1-20) for better performance"
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
return value
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""Input sanitization utilities."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import html
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class InputSanitizer:
|
|
9
|
+
"""
|
|
10
|
+
Sanitizes user inputs to prevent injection attacks.
|
|
11
|
+
|
|
12
|
+
Provides methods for cleaning and escaping potentially dangerous input.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def sanitize_query(query: str, max_length: int = 500) -> str:
|
|
17
|
+
"""
|
|
18
|
+
Sanitize search query.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
query: User query
|
|
22
|
+
max_length: Maximum query length
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Sanitized query
|
|
26
|
+
"""
|
|
27
|
+
if not query:
|
|
28
|
+
return ""
|
|
29
|
+
|
|
30
|
+
# Strip whitespace
|
|
31
|
+
query = query.strip()
|
|
32
|
+
|
|
33
|
+
# Remove null bytes
|
|
34
|
+
query = query.replace('\x00', '')
|
|
35
|
+
|
|
36
|
+
# Remove control characters except newline and tab
|
|
37
|
+
query = ''.join(char for char in query if ord(char) >= 32 or char in '\n\t')
|
|
38
|
+
|
|
39
|
+
# Normalize whitespace
|
|
40
|
+
query = re.sub(r'\s+', ' ', query)
|
|
41
|
+
|
|
42
|
+
# Truncate if needed
|
|
43
|
+
if len(query) > max_length:
|
|
44
|
+
query = query[:max_length]
|
|
45
|
+
|
|
46
|
+
return query
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def sanitize_file_path(path: str) -> str:
|
|
50
|
+
"""
|
|
51
|
+
Sanitize file path.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
path: File path
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Sanitized path
|
|
58
|
+
"""
|
|
59
|
+
if not path:
|
|
60
|
+
return ""
|
|
61
|
+
|
|
62
|
+
# Remove null bytes
|
|
63
|
+
path = path.replace('\x00', '')
|
|
64
|
+
|
|
65
|
+
# Remove dangerous characters
|
|
66
|
+
dangerous_chars = ['<', '>', '|', '&', ';', '`', '$', '(', ')']
|
|
67
|
+
for char in dangerous_chars:
|
|
68
|
+
path = path.replace(char, '')
|
|
69
|
+
|
|
70
|
+
# Normalize path separators
|
|
71
|
+
path = path.replace('\\', '/')
|
|
72
|
+
|
|
73
|
+
# Remove consecutive slashes
|
|
74
|
+
path = re.sub(r'/+', '/', path)
|
|
75
|
+
|
|
76
|
+
return path.strip()
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def sanitize_command(command: str) -> str:
|
|
80
|
+
"""
|
|
81
|
+
Sanitize command string.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
command: Command string
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Sanitized command
|
|
88
|
+
"""
|
|
89
|
+
if not command:
|
|
90
|
+
return ""
|
|
91
|
+
|
|
92
|
+
# Remove null bytes
|
|
93
|
+
command = command.replace('\x00', '')
|
|
94
|
+
|
|
95
|
+
# Remove dangerous shell characters
|
|
96
|
+
dangerous_chars = ['|', '&', ';', '`', '$', '(', ')', '<', '>', '\n']
|
|
97
|
+
for char in dangerous_chars:
|
|
98
|
+
command = command.replace(char, '')
|
|
99
|
+
|
|
100
|
+
return command.strip()
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def escape_html(text: str) -> str:
|
|
104
|
+
"""
|
|
105
|
+
Escape HTML special characters.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
text: Text to escape
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
HTML-escaped text
|
|
112
|
+
"""
|
|
113
|
+
if not text:
|
|
114
|
+
return ""
|
|
115
|
+
|
|
116
|
+
return html.escape(text)
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def sanitize_code(code: str, language: Optional[str] = None) -> str:
|
|
120
|
+
"""
|
|
121
|
+
Sanitize code input.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
code: Code string
|
|
125
|
+
language: Programming language (optional)
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Sanitized code
|
|
129
|
+
"""
|
|
130
|
+
if not code:
|
|
131
|
+
return ""
|
|
132
|
+
|
|
133
|
+
# Remove null bytes
|
|
134
|
+
code = code.replace('\x00', '')
|
|
135
|
+
|
|
136
|
+
# Remove BOM if present
|
|
137
|
+
if code.startswith('\ufeff'):
|
|
138
|
+
code = code[1:]
|
|
139
|
+
|
|
140
|
+
# Normalize line endings
|
|
141
|
+
code = code.replace('\r\n', '\n')
|
|
142
|
+
code = code.replace('\r', '\n')
|
|
143
|
+
|
|
144
|
+
return code
|
|
145
|
+
|
|
146
|
+
@staticmethod
|
|
147
|
+
def sanitize_identifier(identifier: str) -> str:
|
|
148
|
+
"""
|
|
149
|
+
Sanitize variable/function identifier.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
identifier: Identifier name
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Sanitized identifier (only alphanumeric and underscore)
|
|
156
|
+
"""
|
|
157
|
+
if not identifier:
|
|
158
|
+
return ""
|
|
159
|
+
|
|
160
|
+
# Keep only alphanumeric and underscore
|
|
161
|
+
identifier = re.sub(r'[^a-zA-Z0-9_]', '', identifier)
|
|
162
|
+
|
|
163
|
+
# Ensure doesn't start with number
|
|
164
|
+
if identifier and identifier[0].isdigit():
|
|
165
|
+
identifier = '_' + identifier
|
|
166
|
+
|
|
167
|
+
return identifier
|
|
168
|
+
|
|
169
|
+
@staticmethod
|
|
170
|
+
def sanitize_string(
|
|
171
|
+
value: str,
|
|
172
|
+
max_length: Optional[int] = None,
|
|
173
|
+
allowed_chars: Optional[str] = None
|
|
174
|
+
) -> str:
|
|
175
|
+
"""
|
|
176
|
+
General string sanitization.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
value: String to sanitize
|
|
180
|
+
max_length: Maximum length
|
|
181
|
+
allowed_chars: Regex pattern of allowed characters
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Sanitized string
|
|
185
|
+
"""
|
|
186
|
+
if not value:
|
|
187
|
+
return ""
|
|
188
|
+
|
|
189
|
+
# Remove null bytes
|
|
190
|
+
value = value.replace('\x00', '')
|
|
191
|
+
|
|
192
|
+
# Filter by allowed characters
|
|
193
|
+
if allowed_chars:
|
|
194
|
+
value = ''.join(char for char in value if re.match(allowed_chars, char))
|
|
195
|
+
|
|
196
|
+
# Strip whitespace
|
|
197
|
+
value = value.strip()
|
|
198
|
+
|
|
199
|
+
# Truncate if needed
|
|
200
|
+
if max_length and len(value) > max_length:
|
|
201
|
+
value = value[:max_length]
|
|
202
|
+
|
|
203
|
+
return value
|
|
204
|
+
|
|
205
|
+
@staticmethod
|
|
206
|
+
def remove_ansi_codes(text: str) -> str:
|
|
207
|
+
"""
|
|
208
|
+
Remove ANSI color codes from text.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
text: Text with potential ANSI codes
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Text without ANSI codes
|
|
215
|
+
"""
|
|
216
|
+
if not text:
|
|
217
|
+
return ""
|
|
218
|
+
|
|
219
|
+
# Remove ANSI escape sequences
|
|
220
|
+
ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
|
|
221
|
+
return ansi_escape.sub('', text)
|
|
222
|
+
|
|
223
|
+
@staticmethod
|
|
224
|
+
def truncate_string(
|
|
225
|
+
text: str,
|
|
226
|
+
max_length: int,
|
|
227
|
+
suffix: str = "..."
|
|
228
|
+
) -> str:
|
|
229
|
+
"""
|
|
230
|
+
Truncate string to maximum length.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
text: Text to truncate
|
|
234
|
+
max_length: Maximum length
|
|
235
|
+
suffix: Suffix to add when truncated
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Truncated string
|
|
239
|
+
"""
|
|
240
|
+
if not text or len(text) <= max_length:
|
|
241
|
+
return text
|
|
242
|
+
|
|
243
|
+
return text[:max_length - len(suffix)] + suffix
|