domd 2.2.4__tar.gz → 2.2.6__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.4 → domd-2.2.6}/PKG-INFO +1 -1
- {domd-2.2.4 → domd-2.2.6}/pyproject.toml +1 -1
- domd-2.2.6/src/domd/core/command_detection/__init__.py +18 -0
- domd-2.2.6/src/domd/core/command_detection/base_detector.py +195 -0
- domd-2.2.6/src/domd/core/command_detection/handlers/__init__.py +6 -0
- domd-2.2.6/src/domd/core/command_detection/models.py +98 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/project_detection/detector.py +21 -9
- {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/package_json.py +20 -9
- {domd-2.2.4 → domd-2.2.6}/LICENSE +0 -0
- {domd-2.2.4 → domd-2.2.6}/README.md +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/adapters/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/adapters/api/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/adapters/api/flask_api.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/adapters/cli/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/adapters/cli/command_presenter.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/adapters/formatters/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/adapters/formatters/markdown_formatter.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/adapters/persistence/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/adapters/persistence/in_memory_command_repository.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/adapters/persistence/shell_command_executor.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/api.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/application/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/application/factory.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/cli.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/command_execution/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/command_execution/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/command_execution/command_executor.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/command_execution/command_recorder.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/command_execution/command_runner.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/command_execution/environment_detector.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/command_execution/executor.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/commands/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/commands/command.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/commands/executor.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/detector.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/domain/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/domain/command.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/ansible_galaxy.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/ansible_inventory.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/ansible_playbook.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/ansible_role.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/ansible_vault.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/base.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/cargo_toml.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/composer_json.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/go_mod.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/makefile.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/package_json.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/pyproject_toml.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/tox_ini.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsing/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsing/base.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsing/file_processor.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsing/parser_registry.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsing/pattern_matcher.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/ports/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/ports/command_executor.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/ports/command_repository.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/ports/report_formatter.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/project_detection/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/project_detection/command_handling.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/project_detection/config_files.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/project_detection/virtualenv.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/reporters/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/reporters/base.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/reporters/done_md.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/reporters/todo_md.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/reporting/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/reporting/formatters.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/reporting/reporter.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/services/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/services/command_service.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/services/report_service.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/utils/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/utils/command_utils.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/utils/environment.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/utils/file_utils.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/utils/logging_utils.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/core/utils/virtualenv.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/ansible.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/base.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/build_systems.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/ci_cd.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/docker/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/docker/docker_compose.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/docker/dockerfile.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/docker.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/javascript/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/javascript.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/python.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/test_build_systems.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/test_ci_cd.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/test_docker.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/test_javascript.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/test_python.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/parsing/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/parsing/base.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/reporters/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/reporters/console.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/reporters/json_reporter.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/reporters/test_console.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/reporters/test_json_reporter.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/reporters/test_todo_md.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/reporters/todo_md.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/reporting/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/utils/__init__.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/utils/command_runner.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/utils/file_utils.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/src/domd/utils/test_command_runner.py +0 -0
- {domd-2.2.4 → domd-2.2.6}/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.6"
|
|
4
4
|
description = "Project Command Detector - Automatically detects and tests project commands, generates TODO.md for failed commands"
|
|
5
5
|
authors = ["Tom Sapletta <info@softreck.dev>"]
|
|
6
6
|
maintainers = ["WRONAI Team"]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Command detection module for finding and managing project commands."""
|
|
2
|
+
|
|
3
|
+
from .base_detector import CommandDetector
|
|
4
|
+
from .handlers import CommandHandler, ConfigFileHandler
|
|
5
|
+
from .models import Command, CommandResult
|
|
6
|
+
from .parsers import ParserRegistry
|
|
7
|
+
from .utils import get_virtualenv_environment, get_virtualenv_info
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"CommandDetector",
|
|
11
|
+
"CommandHandler",
|
|
12
|
+
"ConfigFileHandler",
|
|
13
|
+
"Command",
|
|
14
|
+
"CommandResult",
|
|
15
|
+
"ParserRegistry",
|
|
16
|
+
"get_virtualenv_info",
|
|
17
|
+
"get_virtualenv_environment",
|
|
18
|
+
]
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""Base command detector class for finding and managing project commands."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, List, Optional, Union
|
|
6
|
+
|
|
7
|
+
from .handlers import CommandHandler, ConfigFileHandler
|
|
8
|
+
from .models import Command, CommandResult
|
|
9
|
+
from .parsers import ParserRegistry
|
|
10
|
+
from .utils import get_virtualenv_info
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CommandDetector:
|
|
16
|
+
"""Base class for detecting and managing project commands."""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
project_path: Union[str, Path] = ".",
|
|
21
|
+
timeout: int = 60,
|
|
22
|
+
exclude_patterns: Optional[List[str]] = None,
|
|
23
|
+
include_patterns: Optional[List[str]] = None,
|
|
24
|
+
todo_file: Union[str, Path] = "TODO.md",
|
|
25
|
+
done_file: Union[str, Path] = "DONE.md",
|
|
26
|
+
script_file: Union[str, Path] = "todo.sh",
|
|
27
|
+
ignore_file: str = ".doignore",
|
|
28
|
+
venv_path: Optional[str] = None,
|
|
29
|
+
):
|
|
30
|
+
"""Initialize the command detector.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
project_path: Path to the project root
|
|
34
|
+
timeout: Command execution timeout in seconds
|
|
35
|
+
exclude_patterns: List of file patterns to exclude
|
|
36
|
+
include_patterns: List of file patterns to include
|
|
37
|
+
todo_file: Path to the TODO file
|
|
38
|
+
done_file: Path to the DONE file
|
|
39
|
+
script_file: Path to the script file
|
|
40
|
+
ignore_file: Path to the ignore file
|
|
41
|
+
venv_path: Path to the virtual environment
|
|
42
|
+
"""
|
|
43
|
+
self.project_path = Path(project_path).resolve()
|
|
44
|
+
self.timeout = timeout
|
|
45
|
+
self.exclude_patterns = exclude_patterns or []
|
|
46
|
+
self.include_patterns = include_patterns or []
|
|
47
|
+
|
|
48
|
+
# Resolve file paths relative to project_path
|
|
49
|
+
self.todo_file = (self.project_path / Path(todo_file)).resolve()
|
|
50
|
+
self.done_file = (self.project_path / Path(done_file)).resolve()
|
|
51
|
+
self.script_file = (self.project_path / Path(script_file)).resolve()
|
|
52
|
+
self.ignore_file = self.project_path / Path(ignore_file)
|
|
53
|
+
|
|
54
|
+
# Initialize virtual environment
|
|
55
|
+
self.venv_path = venv_path
|
|
56
|
+
self.venv_info = get_virtualenv_info(venv_path or self.project_path)
|
|
57
|
+
|
|
58
|
+
# Command ignore patterns (separate from file exclude patterns)
|
|
59
|
+
self.ignore_patterns = []
|
|
60
|
+
|
|
61
|
+
# Initialize components
|
|
62
|
+
self._initialize_components()
|
|
63
|
+
|
|
64
|
+
def _initialize_components(self):
|
|
65
|
+
"""Initialize all component handlers and services."""
|
|
66
|
+
# Initialize command execution components
|
|
67
|
+
self.command_executor = self._create_command_executor()
|
|
68
|
+
self.command_runner = self._create_command_runner()
|
|
69
|
+
|
|
70
|
+
# Initialize handlers
|
|
71
|
+
self.config_handler = self._create_config_handler()
|
|
72
|
+
self.command_handler = self._create_command_handler()
|
|
73
|
+
|
|
74
|
+
# Initialize parsers
|
|
75
|
+
self.parser_registry = self._create_parser_registry()
|
|
76
|
+
self.parsers = self._initialize_parsers()
|
|
77
|
+
|
|
78
|
+
# Initialize command storage
|
|
79
|
+
self._initialize_command_storage()
|
|
80
|
+
|
|
81
|
+
def _create_command_executor(self):
|
|
82
|
+
"""Create and return a command executor instance."""
|
|
83
|
+
from ..services.command_executor import CommandExecutor
|
|
84
|
+
|
|
85
|
+
return CommandExecutor(timeout=self.timeout)
|
|
86
|
+
|
|
87
|
+
def _create_command_runner(self):
|
|
88
|
+
"""Create and return a command runner instance."""
|
|
89
|
+
from ..services.command_runner import CommandRunner
|
|
90
|
+
|
|
91
|
+
return CommandRunner(executor=self.command_executor)
|
|
92
|
+
|
|
93
|
+
def _create_config_handler(self) -> ConfigFileHandler:
|
|
94
|
+
"""Create and return a config file handler."""
|
|
95
|
+
return ConfigFileHandler(
|
|
96
|
+
project_path=self.project_path,
|
|
97
|
+
exclude_patterns=self.exclude_patterns,
|
|
98
|
+
include_patterns=self.include_patterns,
|
|
99
|
+
ignore_file=self.ignore_file,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def _create_command_handler(self) -> CommandHandler:
|
|
103
|
+
"""Create and return a command handler."""
|
|
104
|
+
return CommandHandler(
|
|
105
|
+
project_path=self.project_path,
|
|
106
|
+
command_runner=self.command_runner,
|
|
107
|
+
timeout=self.timeout,
|
|
108
|
+
ignore_patterns=self.ignore_patterns,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def _create_parser_registry(self) -> ParserRegistry:
|
|
112
|
+
"""Create and return a parser registry."""
|
|
113
|
+
return ParserRegistry()
|
|
114
|
+
|
|
115
|
+
def _initialize_command_storage(self):
|
|
116
|
+
"""Initialize command storage references."""
|
|
117
|
+
self.failed_commands = self.command_handler.failed_commands
|
|
118
|
+
self.successful_commands = self.command_handler.successful_commands
|
|
119
|
+
self.ignored_commands = self.command_handler.ignored_commands
|
|
120
|
+
|
|
121
|
+
def _initialize_parsers(self) -> List[Any]:
|
|
122
|
+
"""Initialize parsers for detecting commands in configuration files.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
List of parser instances
|
|
126
|
+
"""
|
|
127
|
+
parsers = []
|
|
128
|
+
|
|
129
|
+
# Try to get parsers from registry first
|
|
130
|
+
try:
|
|
131
|
+
parsers = self.parser_registry.get_parsers()
|
|
132
|
+
logger.debug(
|
|
133
|
+
f"Found {len(parsers)} parser(s) in registry: "
|
|
134
|
+
f"{[p.__class__.__name__ for p in parsers]}"
|
|
135
|
+
)
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.warning(f"Failed to get parsers from registry: {e}")
|
|
138
|
+
parsers = self._get_legacy_parsers()
|
|
139
|
+
|
|
140
|
+
logger.info(f"Initialized {len(parsers)} parsers")
|
|
141
|
+
return parsers
|
|
142
|
+
|
|
143
|
+
def _get_legacy_parsers(self) -> List[Any]:
|
|
144
|
+
"""Get legacy parsers as fallback."""
|
|
145
|
+
try:
|
|
146
|
+
from domd.parsers import get_all_parsers
|
|
147
|
+
|
|
148
|
+
parser_classes = get_all_parsers()
|
|
149
|
+
return [cls() for cls in parser_classes]
|
|
150
|
+
except ImportError as e:
|
|
151
|
+
logger.warning(f"Failed to import legacy parsers: {e}")
|
|
152
|
+
return []
|
|
153
|
+
|
|
154
|
+
def scan_project(self) -> List[Command]:
|
|
155
|
+
"""Scan the project for commands in configuration files.
|
|
156
|
+
|
|
157
|
+
This should be implemented by subclasses to provide specific
|
|
158
|
+
scanning behavior for different types of projects.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
List of Command objects
|
|
162
|
+
"""
|
|
163
|
+
raise NotImplementedError("Subclasses must implement scan_project()")
|
|
164
|
+
|
|
165
|
+
def test_commands(self, commands: List[Union[Command, Dict]]) -> None:
|
|
166
|
+
"""Test a list of commands and update internal state.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
commands: List of Command objects or command dictionaries to test
|
|
170
|
+
"""
|
|
171
|
+
self.command_handler.test_commands(commands)
|
|
172
|
+
|
|
173
|
+
def should_ignore_command(self, command: Union[str, Dict, Command]) -> bool:
|
|
174
|
+
"""Check if a command should be ignored based on ignore patterns.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
command: Command to check (string, dict, or Command object)
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
True if the command should be ignored, False otherwise
|
|
181
|
+
"""
|
|
182
|
+
return self.command_handler.should_ignore_command(command)
|
|
183
|
+
|
|
184
|
+
def get_ignore_reason(self, command: Union[str, Dict, Command]) -> Optional[str]:
|
|
185
|
+
"""Get the reason why a command is ignored.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
command: Command to check (string, dict, or Command object)
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Reason string or None if not ignored
|
|
192
|
+
"""
|
|
193
|
+
if self.should_ignore_command(command):
|
|
194
|
+
return "Matched ignore pattern in .doignore file"
|
|
195
|
+
return None
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Data models for command detection and execution."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, List, Optional, Union
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class CommandResult:
|
|
10
|
+
"""Result of a command execution."""
|
|
11
|
+
|
|
12
|
+
command: str
|
|
13
|
+
return_code: int
|
|
14
|
+
stdout: str = ""
|
|
15
|
+
stderr: str = ""
|
|
16
|
+
success: bool = False
|
|
17
|
+
error: Optional[str] = None
|
|
18
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
19
|
+
execution_time: float = 0.0
|
|
20
|
+
|
|
21
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
22
|
+
"""Convert the result to a dictionary."""
|
|
23
|
+
return {
|
|
24
|
+
"command": self.command,
|
|
25
|
+
"return_code": self.return_code,
|
|
26
|
+
"stdout": self.stdout,
|
|
27
|
+
"stderr": self.stderr,
|
|
28
|
+
"success": self.success,
|
|
29
|
+
"error": self.error,
|
|
30
|
+
"metadata": self.metadata,
|
|
31
|
+
"execution_time": self.execution_time,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class Command:
|
|
37
|
+
"""Represents a command to be executed."""
|
|
38
|
+
|
|
39
|
+
command: str
|
|
40
|
+
type: str = ""
|
|
41
|
+
description: str = ""
|
|
42
|
+
source: str = ""
|
|
43
|
+
file: Union[str, Path] = ""
|
|
44
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
45
|
+
|
|
46
|
+
# These fields are populated during/after execution
|
|
47
|
+
return_code: Optional[int] = None
|
|
48
|
+
stdout: str = ""
|
|
49
|
+
stderr: str = ""
|
|
50
|
+
success: bool = False
|
|
51
|
+
error: Optional[str] = None
|
|
52
|
+
execution_time: float = 0.0
|
|
53
|
+
|
|
54
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
55
|
+
"""Convert the command to a dictionary."""
|
|
56
|
+
return {
|
|
57
|
+
"command": self.command,
|
|
58
|
+
"type": self.type,
|
|
59
|
+
"description": self.description,
|
|
60
|
+
"source": self.source,
|
|
61
|
+
"file": str(self.file),
|
|
62
|
+
"metadata": self.metadata,
|
|
63
|
+
"return_code": self.return_code,
|
|
64
|
+
"stdout": self.stdout,
|
|
65
|
+
"stderr": self.stderr,
|
|
66
|
+
"success": self.success,
|
|
67
|
+
"error": self.error,
|
|
68
|
+
"execution_time": self.execution_time,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def from_dict(cls, data: Dict[str, Any]) -> "Command":
|
|
73
|
+
"""Create a Command from a dictionary."""
|
|
74
|
+
return cls(**data)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass
|
|
78
|
+
class CommandBatch:
|
|
79
|
+
"""A batch of commands to be executed."""
|
|
80
|
+
|
|
81
|
+
commands: List[Command] = field(default_factory=list)
|
|
82
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
83
|
+
|
|
84
|
+
def add_command(self, command: Command) -> None:
|
|
85
|
+
"""Add a command to the batch."""
|
|
86
|
+
self.commands.append(command)
|
|
87
|
+
|
|
88
|
+
def extend(self, commands: List[Command]) -> None:
|
|
89
|
+
"""Extend the batch with multiple commands."""
|
|
90
|
+
self.commands.extend(commands)
|
|
91
|
+
|
|
92
|
+
def to_list(self) -> List[Dict[str, Any]]:
|
|
93
|
+
"""Convert the batch to a list of command dictionaries."""
|
|
94
|
+
return [cmd.to_dict() for cmd in self.commands]
|
|
95
|
+
|
|
96
|
+
def __len__(self) -> int:
|
|
97
|
+
"""Return the number of commands in the batch."""
|
|
98
|
+
return len(self.commands)
|
|
@@ -287,20 +287,32 @@ class ProjectCommandDetector:
|
|
|
287
287
|
return commands
|
|
288
288
|
|
|
289
289
|
try:
|
|
290
|
-
#
|
|
291
|
-
|
|
290
|
+
# Set the file_path on the parser if it has that attribute
|
|
291
|
+
if hasattr(parser, "file_path"):
|
|
292
|
+
parser.file_path = str(file_path)
|
|
292
293
|
|
|
293
|
-
# Parse commands from file
|
|
294
|
+
# Parse commands from file
|
|
294
295
|
file_commands = []
|
|
295
296
|
if hasattr(parser, "parse_file") and callable(parser.parse_file):
|
|
296
|
-
file_commands = parser.parse_file(file_path)
|
|
297
|
+
file_commands = parser.parse_file(str(file_path))
|
|
297
298
|
elif hasattr(parser, "parse") and callable(parser.parse):
|
|
298
|
-
#
|
|
299
|
+
# First try without arguments (parser will handle file reading)
|
|
299
300
|
try:
|
|
300
|
-
file_commands = parser.parse(
|
|
301
|
-
except TypeError:
|
|
302
|
-
#
|
|
303
|
-
|
|
301
|
+
file_commands = parser.parse()
|
|
302
|
+
except (TypeError, AttributeError):
|
|
303
|
+
# If that fails, try with file path
|
|
304
|
+
try:
|
|
305
|
+
file_commands = parser.parse(str(file_path))
|
|
306
|
+
except (TypeError, AttributeError):
|
|
307
|
+
# As a last resort, try with file content
|
|
308
|
+
try:
|
|
309
|
+
content = file_path.read_text(encoding="utf-8")
|
|
310
|
+
file_commands = parser.parse(content)
|
|
311
|
+
except Exception as e:
|
|
312
|
+
logger.error(
|
|
313
|
+
f"Error parsing {file_path} with content: {e}"
|
|
314
|
+
)
|
|
315
|
+
return []
|
|
304
316
|
|
|
305
317
|
# Add file path to commands if not already present
|
|
306
318
|
for cmd in file_commands:
|
|
@@ -30,12 +30,15 @@ class PackageJsonParser(BaseParser):
|
|
|
30
30
|
supported_file_patterns = ["package.json"]
|
|
31
31
|
|
|
32
32
|
def parse(
|
|
33
|
-
self,
|
|
33
|
+
self,
|
|
34
|
+
file_path: Optional[Union[str, Path]] = None,
|
|
35
|
+
content: Optional[str] = None,
|
|
34
36
|
) -> List[Dict[str, Any]]:
|
|
35
37
|
"""Parse package.json and extract npm scripts.
|
|
36
38
|
|
|
37
39
|
Args:
|
|
38
40
|
file_path: Path to the file to parse (overrides self.file_path if provided)
|
|
41
|
+
content: Optional content to parse instead of reading from file
|
|
39
42
|
|
|
40
43
|
Returns:
|
|
41
44
|
List of command dictionaries with 'command', 'description', 'source', and 'type' keys
|
|
@@ -43,15 +46,24 @@ class PackageJsonParser(BaseParser):
|
|
|
43
46
|
if file_path is not None:
|
|
44
47
|
self.file_path = Path(file_path).resolve()
|
|
45
48
|
|
|
46
|
-
if
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
if content is None:
|
|
50
|
+
if self.file_path is None or not self.file_path.exists():
|
|
51
|
+
logging.warning(f"File not found: {self.file_path}")
|
|
52
|
+
return []
|
|
53
|
+
try:
|
|
54
|
+
content = self.file_path.read_text(encoding="utf-8")
|
|
55
|
+
except Exception as e:
|
|
56
|
+
logging.error(f"Error reading file {self.file_path}: {e}")
|
|
57
|
+
return []
|
|
49
58
|
|
|
50
59
|
self.initialize()
|
|
51
|
-
return self._parse_commands()
|
|
60
|
+
return self._parse_commands(content)
|
|
52
61
|
|
|
53
|
-
def _parse_commands(self) -> List[Dict[str, Any]]:
|
|
54
|
-
"""Parse commands from package.json.
|
|
62
|
+
def _parse_commands(self, content: str) -> List[Dict[str, Any]]:
|
|
63
|
+
"""Parse commands from package.json content.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
content: The content of the package.json file
|
|
55
67
|
|
|
56
68
|
Returns:
|
|
57
69
|
List of command dictionaries with 'command', 'description', 'source', and 'type' keys
|
|
@@ -59,8 +71,7 @@ class PackageJsonParser(BaseParser):
|
|
|
59
71
|
commands = []
|
|
60
72
|
|
|
61
73
|
try:
|
|
62
|
-
|
|
63
|
-
data = json.load(f)
|
|
74
|
+
data = json.loads(content)
|
|
64
75
|
|
|
65
76
|
# Extract scripts
|
|
66
77
|
scripts = data.get("scripts", {})
|
|
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
|