domd 2.2.30__tar.gz → 2.2.32__tar.gz
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.
- {domd-2.2.30 → domd-2.2.32}/PKG-INFO +1 -1
- {domd-2.2.30 → domd-2.2.32}/pyproject.toml +1 -1
- {domd-2.2.30 → domd-2.2.32}/src/domd/adapters/persistence/shell_command_executor.py +112 -28
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/command_detection/handlers/command_handler.py +161 -37
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/command_detection/handlers/config_handler.py +2 -2
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/command_detection/parsers/base_parser.py +1 -1
- {domd-2.2.30 → domd-2.2.32}/LICENSE +0 -0
- {domd-2.2.30 → domd-2.2.32}/README.md +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/adapters/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/adapters/api/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/adapters/api/flask_api.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/adapters/cli/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/adapters/cli/command_presenter.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/adapters/formatters/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/adapters/formatters/markdown_formatter.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/adapters/persistence/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/adapters/persistence/in_memory_command_repository.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/api.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/application/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/application/factory.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/cli.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/command_execution/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/command_detection/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/command_detection/base_detector.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/command_detection/handlers/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/command_detection/models.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/command_detection/parsers/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/command_detection/parsers/parser_registry.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/command_detection/project_detector.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/command_detection/utils/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/command_detection/utils/file_utils.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/command_detection/utils/virtualenv.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/command_execution/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/command_execution/command_executor.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/command_execution/command_recorder.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/command_execution/command_runner.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/command_execution/environment_detector.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/command_execution/executor.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/commands/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/commands/command.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/commands/executor.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/detector.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/domain/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/domain/command.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/parsers/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/parsers/ansible_galaxy.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/parsers/ansible_inventory.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/parsers/ansible_playbook.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/parsers/ansible_role.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/parsers/ansible_vault.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/parsers/base.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/parsers/cargo_toml.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/parsers/composer_json.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/parsers/go_mod.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/parsers/makefile.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/parsers/package_json.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/parsers/pyproject_toml.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/parsers/tox_ini.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/parsing/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/parsing/base.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/parsing/file_processor.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/parsing/parser_registry.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/parsing/pattern_matcher.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/ports/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/ports/command_executor.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/ports/command_repository.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/ports/report_formatter.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/project_detection/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/project_detection/command_handling.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/project_detection/config_files.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/project_detection/detector.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/project_detection/virtualenv.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/reporters/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/reporters/base.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/reporters/done_md.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/reporters/todo_md.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/reporting/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/reporting/formatters.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/reporting/reporter.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/services/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/services/command_service.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/services/report_service.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/utils/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/utils/command_utils.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/utils/environment.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/utils/file_utils.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/utils/logging_utils.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/core/utils/virtualenv.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/parsers/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/parsers/ansible.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/parsers/base.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/parsers/build_systems.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/parsers/ci_cd.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/parsers/docker/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/parsers/docker/docker_compose.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/parsers/docker/dockerfile.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/parsers/docker.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/parsers/javascript/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/parsers/javascript.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/parsers/markdown_parser.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/parsers/package_json.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/parsers/python.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/parsers/test_build_systems.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/parsers/test_ci_cd.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/parsers/test_docker.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/parsers/test_javascript.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/parsers/test_python.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/parsing/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/parsing/base.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/reporters/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/reporters/console.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/reporters/json_reporter.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/reporters/test_console.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/reporters/test_json_reporter.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/reporters/test_todo_md.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/reporters/todo_md.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/reporting/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/utils/__init__.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/utils/command_runner.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/utils/file_utils.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/utils/path_utils.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/utils/test_command_runner.py +0 -0
- {domd-2.2.30 → domd-2.2.32}/src/domd/utils/test_file_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "domd"
|
|
3
|
-
version = "2.2.
|
|
3
|
+
version = "2.2.32"
|
|
4
4
|
description = " ✓ DoMD - Do Markdown Docs - automatically detect, run and show working commands in DONE.md and errors in TODO.md markdown files"
|
|
5
5
|
authors = ["Tom Sapletta <info@softreck.dev>"]
|
|
6
6
|
maintainers = ["WRONAI Team"]
|
|
@@ -141,6 +141,17 @@ class ShellCommandExecutor(CommandExecutor):
|
|
|
141
141
|
|
|
142
142
|
cmd_args, needs_shell = self._parse_command(cmd_str)
|
|
143
143
|
|
|
144
|
+
# If cmd_args is empty, this is markdown content that should be ignored
|
|
145
|
+
if not cmd_args:
|
|
146
|
+
logger.debug(f"Skipping markdown content: {cmd_str[:100]}...")
|
|
147
|
+
return CommandResult(
|
|
148
|
+
success=True,
|
|
149
|
+
return_code=0,
|
|
150
|
+
execution_time=0,
|
|
151
|
+
stdout="",
|
|
152
|
+
stderr="",
|
|
153
|
+
)
|
|
154
|
+
|
|
144
155
|
# Prepare execution environment
|
|
145
156
|
execution_env = None
|
|
146
157
|
if env:
|
|
@@ -282,44 +293,111 @@ class ShellCommandExecutor(CommandExecutor):
|
|
|
282
293
|
|
|
283
294
|
def _is_markdown_content(self, command_str: str) -> bool:
|
|
284
295
|
"""
|
|
285
|
-
Check if the given string appears to be markdown content.
|
|
296
|
+
Check if the given string appears to be markdown or other non-command content.
|
|
286
297
|
|
|
287
298
|
Args:
|
|
288
299
|
command_str: The string to check
|
|
289
300
|
|
|
290
301
|
Returns:
|
|
291
|
-
bool: True if the string appears to be
|
|
302
|
+
bool: True if the string appears to be non-command content
|
|
292
303
|
"""
|
|
304
|
+
# Skip empty strings
|
|
305
|
+
if not command_str or not command_str.strip():
|
|
306
|
+
return True
|
|
307
|
+
|
|
293
308
|
# Common markdown patterns that shouldn't be executed as commands
|
|
294
|
-
|
|
309
|
+
markdown_patterns = [
|
|
295
310
|
# Headers
|
|
296
|
-
r"^#+\s",
|
|
311
|
+
(r"^#+\s+", "Markdown header"),
|
|
297
312
|
# Lists
|
|
298
|
-
r"
|
|
299
|
-
r"^\
|
|
313
|
+
(r"^[-*+]\s+", "Markdown list item"),
|
|
314
|
+
(r"^\d+\.\s+", "Numbered list item"),
|
|
300
315
|
# Code blocks
|
|
301
|
-
r"
|
|
316
|
+
(r"^```", "Markdown code block"),
|
|
317
|
+
(r"`[^`]+`", "Inline code"),
|
|
302
318
|
# Tables
|
|
303
|
-
r"
|
|
319
|
+
(r"^\|.*\|$", "Markdown table"),
|
|
304
320
|
# Links and images
|
|
305
|
-
r"\[.*\]\(.*\)",
|
|
321
|
+
(r"\[.*\]\(.*\)", "Markdown link"),
|
|
322
|
+
(r"!\[.*\]\(.*\)", "Markdown image"),
|
|
323
|
+
# Text formatting
|
|
324
|
+
(r"\*\*[^*]+\*\*", "Bold text"),
|
|
325
|
+
(r"__[^_]+__", "Underlined text"),
|
|
326
|
+
(r"~~[^~]+~~", "Strikethrough"),
|
|
327
|
+
(r"\*[^*]+\*", "Italic text"),
|
|
328
|
+
(r"_[^_]+_", "Italic text (underscore)"),
|
|
306
329
|
# Blockquotes
|
|
307
|
-
r"
|
|
330
|
+
(r"^>\s+", "Blockquote"),
|
|
308
331
|
# Horizontal rules
|
|
309
|
-
r"
|
|
310
|
-
#
|
|
311
|
-
r"[
|
|
312
|
-
# Common
|
|
313
|
-
r"
|
|
332
|
+
(r"^\s*[*_-]{3,}\s*$", "Horizontal rule"),
|
|
333
|
+
# HTML tags
|
|
334
|
+
(r"<[a-z][\s\S]*?>", "HTML tag"),
|
|
335
|
+
# Common documentation patterns
|
|
336
|
+
(r"^\s*[A-Z][A-Za-z0-9_\s-]+:", "Documentation line"),
|
|
337
|
+
(r"^\s*<!--.*-->\s*$", "HTML comment"),
|
|
338
|
+
(r"^\s*//", "Single-line comment"),
|
|
339
|
+
(r"^\s*#", "Comment"),
|
|
340
|
+
(r"^\s*/\*[\s\S]*?\*/\s*$", "Multi-line comment"),
|
|
341
|
+
# Common metadata patterns
|
|
342
|
+
(r"^\s*[\w-]+\s*:\s*.+", "YAML/JSON key-value pair"),
|
|
343
|
+
# Directory listing patterns
|
|
344
|
+
(r"^[│└├─]", "Tree-like directory structure"),
|
|
345
|
+
(r"\s+\d+\s+\w+\s+\d+\s+[\d:]+\s+", "File size and date"),
|
|
346
|
+
(r"\d+\s+[BKMGTPEZY]B\s*$", "File size"),
|
|
347
|
+
(r"^total \d+$", "Directory total"),
|
|
348
|
+
# Common documentation sections
|
|
349
|
+
(
|
|
350
|
+
r"^\s*(Installation|Usage|Configuration|Options|Examples):",
|
|
351
|
+
"Documentation section",
|
|
352
|
+
),
|
|
353
|
+
# Empty or whitespace-only lines
|
|
354
|
+
(r"^\s*$", "Empty line"),
|
|
314
355
|
]
|
|
315
356
|
|
|
316
|
-
# Check for markdown
|
|
317
|
-
for pattern in
|
|
318
|
-
if re.search(pattern, command_str):
|
|
357
|
+
# Check for markdown and other non-command patterns
|
|
358
|
+
for pattern, description in markdown_patterns:
|
|
359
|
+
if re.search(pattern, command_str, re.MULTILINE):
|
|
360
|
+
logger.debug(f"Detected {description}: {command_str[:100]}")
|
|
319
361
|
return True
|
|
320
362
|
|
|
321
|
-
# Check
|
|
322
|
-
if
|
|
363
|
+
# Check for suspiciously long lines that aren't commands
|
|
364
|
+
if len(command_str) > 1000 and not any(
|
|
365
|
+
c in command_str for c in ["&&", "|", ">", "<", ";"]
|
|
366
|
+
):
|
|
367
|
+
logger.debug(f"Suspiciously long line detected: {command_str[:100]}...")
|
|
368
|
+
return True
|
|
369
|
+
|
|
370
|
+
# Check for lines that are just special characters or numbers
|
|
371
|
+
if re.match(r"^[\d\s\W]+$", command_str):
|
|
372
|
+
logger.debug(
|
|
373
|
+
f"Line contains only numbers/special chars: {command_str[:100]}"
|
|
374
|
+
)
|
|
375
|
+
return True
|
|
376
|
+
|
|
377
|
+
# Check for common error messages or stack traces
|
|
378
|
+
error_indicators = [
|
|
379
|
+
"error:",
|
|
380
|
+
"warning:",
|
|
381
|
+
"exception:",
|
|
382
|
+
"traceback",
|
|
383
|
+
"stacktrace",
|
|
384
|
+
"at ",
|
|
385
|
+
'File "',
|
|
386
|
+
r"line \d+", # Raw string for regex
|
|
387
|
+
"in <module>",
|
|
388
|
+
"^",
|
|
389
|
+
"~",
|
|
390
|
+
"SyntaxError",
|
|
391
|
+
"NameError",
|
|
392
|
+
"TypeError",
|
|
393
|
+
"ValueError",
|
|
394
|
+
"ImportError",
|
|
395
|
+
]
|
|
396
|
+
|
|
397
|
+
if any(
|
|
398
|
+
indicator.lower() in command_str.lower() for indicator in error_indicators
|
|
399
|
+
):
|
|
400
|
+
logger.debug(f"Error/warning message detected: {command_str[:100]}...")
|
|
323
401
|
return True
|
|
324
402
|
|
|
325
403
|
return False
|
|
@@ -334,21 +412,23 @@ class ShellCommandExecutor(CommandExecutor):
|
|
|
334
412
|
Returns:
|
|
335
413
|
Tuple of (command_args, needs_shell)
|
|
336
414
|
"""
|
|
337
|
-
|
|
338
|
-
|
|
415
|
+
# Handle empty or whitespace-only commands
|
|
416
|
+
if not command_str or not command_str.strip():
|
|
417
|
+
logger.debug("Empty or whitespace-only command")
|
|
418
|
+
return [""], False # Return a non-empty list to avoid IndexError
|
|
419
|
+
|
|
420
|
+
command_str = command_str.strip()
|
|
339
421
|
|
|
340
422
|
# Check if this is markdown content that shouldn't be executed
|
|
341
423
|
if self._is_markdown_content(command_str):
|
|
342
424
|
logger.debug(f"Ignoring markdown content: {command_str[:100]}...")
|
|
343
|
-
return [], False
|
|
425
|
+
return [""], False # Return a non-empty list to avoid IndexError
|
|
344
426
|
|
|
345
427
|
# Check if this is a shell built-in or needs shell features
|
|
346
428
|
needs_shell = self._needs_shell(command_str)
|
|
347
429
|
|
|
348
430
|
# Special handling for 'source' command
|
|
349
|
-
if command_str.
|
|
350
|
-
". "
|
|
351
|
-
):
|
|
431
|
+
if command_str.startswith("source ") or command_str.startswith(". "):
|
|
352
432
|
# For 'source' command, we need to use the shell to execute it
|
|
353
433
|
return [command_str], True
|
|
354
434
|
|
|
@@ -358,9 +438,13 @@ class ShellCommandExecutor(CommandExecutor):
|
|
|
358
438
|
|
|
359
439
|
# For non-shell commands, use shlex for proper argument splitting
|
|
360
440
|
try:
|
|
361
|
-
|
|
441
|
+
args = shlex.split(command_str)
|
|
442
|
+
if not args: # Handle case where shlex.split returns empty list
|
|
443
|
+
return [""], False
|
|
444
|
+
return args, False
|
|
362
445
|
except Exception as e:
|
|
363
446
|
logger.warning(
|
|
364
447
|
f"Error parsing command with shlex, falling back to simple split: {e}"
|
|
365
448
|
)
|
|
366
|
-
|
|
449
|
+
args = command_str.split()
|
|
450
|
+
return (args if args else [""]), False
|
|
@@ -6,7 +6,8 @@ import time
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Any, Dict, List, Optional, Union
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from domd.core.command_execution.command_runner import CommandRunner
|
|
10
|
+
|
|
10
11
|
from ..models import Command
|
|
11
12
|
|
|
12
13
|
logger = logging.getLogger(__name__)
|
|
@@ -162,63 +163,186 @@ class CommandHandler:
|
|
|
162
163
|
def is_valid_command(self, command: Union[str, Dict, Command]) -> tuple[bool, str]:
|
|
163
164
|
"""Check if a command is valid and should be executed.
|
|
164
165
|
|
|
166
|
+
This method performs multiple validations to ensure the command is a valid shell command
|
|
167
|
+
and not markdown, documentation, or other non-command content.
|
|
168
|
+
|
|
165
169
|
Args:
|
|
166
170
|
command: Command to validate (string, dict, or Command object)
|
|
167
171
|
|
|
168
172
|
Returns:
|
|
169
173
|
Tuple of (is_valid, reason) where is_valid is a boolean and reason is a string
|
|
170
174
|
"""
|
|
175
|
+
logger.debug(f"Validating command: {command}")
|
|
176
|
+
|
|
171
177
|
cmd_str = self._extract_command_string(command)
|
|
172
178
|
if not cmd_str or not cmd_str.strip():
|
|
179
|
+
logger.debug("Empty command string")
|
|
180
|
+
return False, "Empty command"
|
|
181
|
+
|
|
182
|
+
cmd_str = cmd_str.strip()
|
|
183
|
+
logger.debug(
|
|
184
|
+
f"Processing command: {cmd_str[:100]}"
|
|
185
|
+
+ ("..." if len(cmd_str) > 100 else "")
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Check for empty or whitespace-only commands
|
|
189
|
+
if not cmd_str:
|
|
190
|
+
logger.debug("Command is empty after stripping")
|
|
173
191
|
return False, "Empty command"
|
|
174
192
|
|
|
175
|
-
# Check
|
|
176
|
-
if
|
|
177
|
-
"
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
"
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
"[]", # Links
|
|
187
|
-
"![", # Images
|
|
188
|
-
"<", # HTML tags
|
|
189
|
-
"> " # Blockquotes
|
|
190
|
-
]):
|
|
191
|
-
return False, "Appears to be markdown content"
|
|
192
|
-
|
|
193
|
-
# Check against non-command patterns
|
|
193
|
+
# Check for very long commands (likely not actual commands)
|
|
194
|
+
if len(cmd_str) > 500:
|
|
195
|
+
logger.debug(f"Command too long ({len(cmd_str)} > 500 characters)")
|
|
196
|
+
return False, "Command is too long to be a valid shell command"
|
|
197
|
+
|
|
198
|
+
# Check for commands that are just numbers or special characters
|
|
199
|
+
if re.match(r"^[\d\s\W]+$", cmd_str):
|
|
200
|
+
logger.debug("Command contains only numbers or special characters")
|
|
201
|
+
return False, "Command contains only numbers or special characters"
|
|
202
|
+
|
|
203
|
+
# Check for common non-command patterns first (fast checks)
|
|
194
204
|
for pattern in self._compiled_non_command_patterns:
|
|
195
205
|
if pattern.search(cmd_str):
|
|
206
|
+
logger.debug(f"Matches non-command pattern: {pattern.pattern}")
|
|
196
207
|
return False, f"Matches non-command pattern: {pattern.pattern}"
|
|
197
208
|
|
|
198
|
-
#
|
|
199
|
-
|
|
200
|
-
|
|
209
|
+
# Enhanced markdown and documentation detection with detailed logging
|
|
210
|
+
markdown_patterns = [
|
|
211
|
+
# Markdown patterns
|
|
212
|
+
(r"^#+\s+", "Markdown header"),
|
|
213
|
+
(r"^[-*+]\s+", "Markdown list item"),
|
|
214
|
+
(r"^\d+\.\s+", "Numbered list item"),
|
|
215
|
+
(r"^\|.*\|$", "Markdown table"),
|
|
216
|
+
(r"^```", "Markdown code block"),
|
|
217
|
+
(r"`[^`]+`", "Inline code"),
|
|
218
|
+
(r"\*\*[^*]+\*\*", "Bold text"),
|
|
219
|
+
(r"__[^_]+__", "Underlined text"),
|
|
220
|
+
(r"~~[^~]+~~", "Strikethrough"),
|
|
221
|
+
(r"\[.*\]\(.*\)", "Markdown link"),
|
|
222
|
+
(r"^>\s+", "Blockquote"),
|
|
223
|
+
(r"^\s*<!--.*-->\s*$", "HTML comment"),
|
|
224
|
+
# Documentation patterns
|
|
225
|
+
(r"^For\s+\w+\s+information", "Documentation line"),
|
|
226
|
+
(r"^To\s+\w+", "Documentation line"),
|
|
227
|
+
(r"^This\s+\w+", "Documentation line"),
|
|
228
|
+
(r"^The\s+\w+", "Documentation line"),
|
|
229
|
+
(r"^[A-Z][a-z]+\s+the\s+\w+", "Documentation line"),
|
|
230
|
+
(r"^[A-Z][a-z]+\s+[A-Z][a-z]+", "Documentation line"),
|
|
231
|
+
(r"^[A-Z][a-z]+\s+[a-z]+\s+[a-z]+", "Documentation line"),
|
|
232
|
+
# Directory tree patterns
|
|
233
|
+
(r"^\s*[│├└─]+\s+", "Directory tree"),
|
|
234
|
+
(r"^\s*[│├└─]+$", "Directory tree connector"),
|
|
235
|
+
(r"^\s*\d+\s+[a-z]+\s+\d+\s+\d{2}:\d{2}\s+", "Directory listing"),
|
|
236
|
+
]
|
|
201
237
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
238
|
+
logger.debug(f"Checking against {len(markdown_patterns)} markdown patterns")
|
|
239
|
+
|
|
240
|
+
# Additional patterns to check with detailed logging
|
|
241
|
+
additional_patterns = [
|
|
242
|
+
# Common documentation phrases
|
|
243
|
+
(r"(?i)for more information", "Documentation phrase"),
|
|
244
|
+
(r"(?i)see also", "Documentation phrase"),
|
|
245
|
+
(r"(?i)example:", "Documentation phrase"),
|
|
246
|
+
(r"(?i)note:", "Documentation note"),
|
|
247
|
+
(r"(?i)warning:", "Warning message"),
|
|
248
|
+
(r"(?i)important:", "Important note"),
|
|
249
|
+
(r"(?i)tip:", "Tip note"),
|
|
250
|
+
(r"(?i)caution:", "Caution note"),
|
|
251
|
+
(r"(?i)see the", "Documentation reference"),
|
|
252
|
+
(r"(?i)refer to", "Documentation reference"),
|
|
253
|
+
# Common command-like patterns that should be ignored
|
|
254
|
+
(r"^\s*\w+\s*=\s*\S+\s*$", "Variable assignment"),
|
|
255
|
+
(r"^\s*\{\s*\}\s*$", "Empty code block"),
|
|
256
|
+
(r"^\s*\[\s*\]\s*$", "Empty array"),
|
|
257
|
+
(r"^\s*\{\s*$", "Opening brace"),
|
|
258
|
+
(r"^\s*\}\s*$", "Closing brace"),
|
|
259
|
+
# Common error patterns
|
|
260
|
+
(r"(?i)error:", "Error message"),
|
|
261
|
+
(r"(?i)exception:", "Exception message"),
|
|
262
|
+
(r"(?i)traceback", "Traceback message"),
|
|
263
|
+
(r"(?i)stacktrace", "Stack trace"),
|
|
264
|
+
# Path patterns
|
|
265
|
+
(r"^\s*/[\w/.-]+$", "File path"),
|
|
266
|
+
(r"^\s*~?/[\w/.-]+$", "Home-relative path"),
|
|
267
|
+
(r"^\s*\./[\w/.-]+$", "Relative path"),
|
|
268
|
+
# URL patterns
|
|
269
|
+
(r"https?://\S+", "URL"),
|
|
270
|
+
(r"www\.\S+\.\w+", "Web address"),
|
|
271
|
+
# Email patterns
|
|
272
|
+
(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", "Email address"),
|
|
273
|
+
]
|
|
205
274
|
|
|
206
|
-
|
|
207
|
-
if any(s in cmd_str.lower() for s in ["traceback", "exception", "stacktrace"]):
|
|
208
|
-
return False, "Appears to contain error output"
|
|
275
|
+
logger.debug(f"Checking against {len(additional_patterns)} additional patterns")
|
|
209
276
|
|
|
210
|
-
#
|
|
211
|
-
|
|
212
|
-
|
|
277
|
+
# Combine all patterns
|
|
278
|
+
all_patterns = markdown_patterns + additional_patterns
|
|
279
|
+
logger.debug(f"Total patterns to check: {len(all_patterns)}")
|
|
213
280
|
|
|
214
|
-
# Check
|
|
215
|
-
|
|
216
|
-
|
|
281
|
+
# Check all patterns with detailed logging
|
|
282
|
+
for pattern, description in all_patterns:
|
|
283
|
+
try:
|
|
284
|
+
if re.search(pattern, cmd_str, re.IGNORECASE):
|
|
285
|
+
logger.debug(
|
|
286
|
+
f"Pattern matched - Type: {description}, Pattern: {pattern}"
|
|
287
|
+
)
|
|
288
|
+
return False, f"{description} detected"
|
|
289
|
+
except Exception as e:
|
|
290
|
+
logger.error(f"Error checking pattern '{pattern}': {str(e)}")
|
|
291
|
+
continue
|
|
292
|
+
|
|
293
|
+
logger.debug("No markdown or documentation patterns matched")
|
|
294
|
+
|
|
295
|
+
# Check for command-like patterns that might be valid
|
|
296
|
+
valid_command_indicators = [
|
|
297
|
+
(r"^\s*[a-z][a-z0-9_-]+(\s+[a-z][a-z0-9_-]+)*\s*$", "Simple command"),
|
|
298
|
+
(r"^\s*[a-z][a-z0-9_-]+\s+[a-z][a-z0-9_-]+", "Command with argument"),
|
|
299
|
+
(r"^\s*[a-z][a-z0-9_-]+\s+--?[a-z0-9-]+", "Command with option"),
|
|
300
|
+
(
|
|
301
|
+
r"^\s*[a-z][a-z0-9_-]+\s+[a-z][a-z0-9_-]+\s+[a-z][a-z0-9_-]+",
|
|
302
|
+
"Command with multiple args",
|
|
303
|
+
),
|
|
304
|
+
]
|
|
305
|
+
|
|
306
|
+
logger.debug("Checking command against valid command patterns")
|
|
307
|
+
|
|
308
|
+
# Track if any command pattern matches
|
|
309
|
+
command_matched = False
|
|
310
|
+
for pattern, pattern_type in valid_command_indicators:
|
|
311
|
+
try:
|
|
312
|
+
if re.match(pattern, cmd_str, re.IGNORECASE):
|
|
313
|
+
logger.debug(f"Command matches {pattern_type} pattern: {pattern}")
|
|
314
|
+
command_matched = True
|
|
315
|
+
break
|
|
316
|
+
except Exception as e:
|
|
317
|
+
logger.error(f"Error checking command pattern '{pattern}': {str(e)}")
|
|
318
|
+
|
|
319
|
+
if not command_matched:
|
|
320
|
+
logger.debug("Command does not match any valid command patterns")
|
|
321
|
+
return False, "Does not match valid command patterns"
|
|
322
|
+
|
|
323
|
+
# Check for commands that are too short or too simple
|
|
324
|
+
if len(cmd_str.split()) == 1 and len(cmd_str) < 3:
|
|
325
|
+
logger.debug("Command is too short or simple")
|
|
326
|
+
return False, "Command is too short or simple"
|
|
217
327
|
|
|
218
|
-
# Check for
|
|
219
|
-
if len(cmd_str) > 500:
|
|
328
|
+
# Check for commands that are too long (likely not actual commands)
|
|
329
|
+
if len(cmd_str) > 500:
|
|
330
|
+
logger.debug(f"Command is too long ({len(cmd_str)} characters)")
|
|
220
331
|
return False, "Command is too long to be a valid shell command"
|
|
221
332
|
|
|
333
|
+
# Check for commands that are just numbers or special characters
|
|
334
|
+
if re.match(r"^[\d\s\W]+$", cmd_str):
|
|
335
|
+
logger.debug("Command contains only numbers or special characters")
|
|
336
|
+
return False, "Command contains only numbers or special characters"
|
|
337
|
+
|
|
338
|
+
# Check for internal tool paths that shouldn't be executed
|
|
339
|
+
internal_paths = ["/tmp/", "/var/", "/usr/local/", "~/.cache/", "/dev/"]
|
|
340
|
+
for path in internal_paths:
|
|
341
|
+
if path in cmd_str:
|
|
342
|
+
logger.debug(f"Command references internal tool path: {path}")
|
|
343
|
+
return False, f"References internal tool path: {path}"
|
|
344
|
+
|
|
345
|
+
logger.debug("Command passed all validation checks")
|
|
222
346
|
return True, ""
|
|
223
347
|
|
|
224
348
|
def should_ignore_command(self, command: Union[str, Dict, Command]) -> bool:
|
|
@@ -4,8 +4,8 @@ import logging
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Any, List, Optional, Set, Union
|
|
6
6
|
|
|
7
|
-
from
|
|
8
|
-
from
|
|
7
|
+
from domd.core.parsing.file_processor import FileProcessor
|
|
8
|
+
from domd.core.parsing.pattern_matcher import PatternMatcher
|
|
9
9
|
|
|
10
10
|
logger = logging.getLogger(__name__)
|
|
11
11
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|