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.
Files changed (114) hide show
  1. {domd-2.2.4 → domd-2.2.6}/PKG-INFO +1 -1
  2. {domd-2.2.4 → domd-2.2.6}/pyproject.toml +1 -1
  3. domd-2.2.6/src/domd/core/command_detection/__init__.py +18 -0
  4. domd-2.2.6/src/domd/core/command_detection/base_detector.py +195 -0
  5. domd-2.2.6/src/domd/core/command_detection/handlers/__init__.py +6 -0
  6. domd-2.2.6/src/domd/core/command_detection/models.py +98 -0
  7. {domd-2.2.4 → domd-2.2.6}/src/domd/core/project_detection/detector.py +21 -9
  8. {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/package_json.py +20 -9
  9. {domd-2.2.4 → domd-2.2.6}/LICENSE +0 -0
  10. {domd-2.2.4 → domd-2.2.6}/README.md +0 -0
  11. {domd-2.2.4 → domd-2.2.6}/src/domd/__init__.py +0 -0
  12. {domd-2.2.4 → domd-2.2.6}/src/domd/adapters/__init__.py +0 -0
  13. {domd-2.2.4 → domd-2.2.6}/src/domd/adapters/api/__init__.py +0 -0
  14. {domd-2.2.4 → domd-2.2.6}/src/domd/adapters/api/flask_api.py +0 -0
  15. {domd-2.2.4 → domd-2.2.6}/src/domd/adapters/cli/__init__.py +0 -0
  16. {domd-2.2.4 → domd-2.2.6}/src/domd/adapters/cli/command_presenter.py +0 -0
  17. {domd-2.2.4 → domd-2.2.6}/src/domd/adapters/formatters/__init__.py +0 -0
  18. {domd-2.2.4 → domd-2.2.6}/src/domd/adapters/formatters/markdown_formatter.py +0 -0
  19. {domd-2.2.4 → domd-2.2.6}/src/domd/adapters/persistence/__init__.py +0 -0
  20. {domd-2.2.4 → domd-2.2.6}/src/domd/adapters/persistence/in_memory_command_repository.py +0 -0
  21. {domd-2.2.4 → domd-2.2.6}/src/domd/adapters/persistence/shell_command_executor.py +0 -0
  22. {domd-2.2.4 → domd-2.2.6}/src/domd/api.py +0 -0
  23. {domd-2.2.4 → domd-2.2.6}/src/domd/application/__init__.py +0 -0
  24. {domd-2.2.4 → domd-2.2.6}/src/domd/application/factory.py +0 -0
  25. {domd-2.2.4 → domd-2.2.6}/src/domd/cli.py +0 -0
  26. {domd-2.2.4 → domd-2.2.6}/src/domd/command_execution/__init__.py +0 -0
  27. {domd-2.2.4 → domd-2.2.6}/src/domd/core/__init__.py +0 -0
  28. {domd-2.2.4 → domd-2.2.6}/src/domd/core/command_execution/__init__.py +0 -0
  29. {domd-2.2.4 → domd-2.2.6}/src/domd/core/command_execution/command_executor.py +0 -0
  30. {domd-2.2.4 → domd-2.2.6}/src/domd/core/command_execution/command_recorder.py +0 -0
  31. {domd-2.2.4 → domd-2.2.6}/src/domd/core/command_execution/command_runner.py +0 -0
  32. {domd-2.2.4 → domd-2.2.6}/src/domd/core/command_execution/environment_detector.py +0 -0
  33. {domd-2.2.4 → domd-2.2.6}/src/domd/core/command_execution/executor.py +0 -0
  34. {domd-2.2.4 → domd-2.2.6}/src/domd/core/commands/__init__.py +0 -0
  35. {domd-2.2.4 → domd-2.2.6}/src/domd/core/commands/command.py +0 -0
  36. {domd-2.2.4 → domd-2.2.6}/src/domd/core/commands/executor.py +0 -0
  37. {domd-2.2.4 → domd-2.2.6}/src/domd/core/detector.py +0 -0
  38. {domd-2.2.4 → domd-2.2.6}/src/domd/core/domain/__init__.py +0 -0
  39. {domd-2.2.4 → domd-2.2.6}/src/domd/core/domain/command.py +0 -0
  40. {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/__init__.py +0 -0
  41. {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/ansible_galaxy.py +0 -0
  42. {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/ansible_inventory.py +0 -0
  43. {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/ansible_playbook.py +0 -0
  44. {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/ansible_role.py +0 -0
  45. {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/ansible_vault.py +0 -0
  46. {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/base.py +0 -0
  47. {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/cargo_toml.py +0 -0
  48. {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/composer_json.py +0 -0
  49. {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/go_mod.py +0 -0
  50. {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/makefile.py +0 -0
  51. {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/package_json.py +0 -0
  52. {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/pyproject_toml.py +0 -0
  53. {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsers/tox_ini.py +0 -0
  54. {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsing/__init__.py +0 -0
  55. {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsing/base.py +0 -0
  56. {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsing/file_processor.py +0 -0
  57. {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsing/parser_registry.py +0 -0
  58. {domd-2.2.4 → domd-2.2.6}/src/domd/core/parsing/pattern_matcher.py +0 -0
  59. {domd-2.2.4 → domd-2.2.6}/src/domd/core/ports/__init__.py +0 -0
  60. {domd-2.2.4 → domd-2.2.6}/src/domd/core/ports/command_executor.py +0 -0
  61. {domd-2.2.4 → domd-2.2.6}/src/domd/core/ports/command_repository.py +0 -0
  62. {domd-2.2.4 → domd-2.2.6}/src/domd/core/ports/report_formatter.py +0 -0
  63. {domd-2.2.4 → domd-2.2.6}/src/domd/core/project_detection/__init__.py +0 -0
  64. {domd-2.2.4 → domd-2.2.6}/src/domd/core/project_detection/command_handling.py +0 -0
  65. {domd-2.2.4 → domd-2.2.6}/src/domd/core/project_detection/config_files.py +0 -0
  66. {domd-2.2.4 → domd-2.2.6}/src/domd/core/project_detection/virtualenv.py +0 -0
  67. {domd-2.2.4 → domd-2.2.6}/src/domd/core/reporters/__init__.py +0 -0
  68. {domd-2.2.4 → domd-2.2.6}/src/domd/core/reporters/base.py +0 -0
  69. {domd-2.2.4 → domd-2.2.6}/src/domd/core/reporters/done_md.py +0 -0
  70. {domd-2.2.4 → domd-2.2.6}/src/domd/core/reporters/todo_md.py +0 -0
  71. {domd-2.2.4 → domd-2.2.6}/src/domd/core/reporting/__init__.py +0 -0
  72. {domd-2.2.4 → domd-2.2.6}/src/domd/core/reporting/formatters.py +0 -0
  73. {domd-2.2.4 → domd-2.2.6}/src/domd/core/reporting/reporter.py +0 -0
  74. {domd-2.2.4 → domd-2.2.6}/src/domd/core/services/__init__.py +0 -0
  75. {domd-2.2.4 → domd-2.2.6}/src/domd/core/services/command_service.py +0 -0
  76. {domd-2.2.4 → domd-2.2.6}/src/domd/core/services/report_service.py +0 -0
  77. {domd-2.2.4 → domd-2.2.6}/src/domd/core/utils/__init__.py +0 -0
  78. {domd-2.2.4 → domd-2.2.6}/src/domd/core/utils/command_utils.py +0 -0
  79. {domd-2.2.4 → domd-2.2.6}/src/domd/core/utils/environment.py +0 -0
  80. {domd-2.2.4 → domd-2.2.6}/src/domd/core/utils/file_utils.py +0 -0
  81. {domd-2.2.4 → domd-2.2.6}/src/domd/core/utils/logging_utils.py +0 -0
  82. {domd-2.2.4 → domd-2.2.6}/src/domd/core/utils/virtualenv.py +0 -0
  83. {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/__init__.py +0 -0
  84. {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/ansible.py +0 -0
  85. {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/base.py +0 -0
  86. {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/build_systems.py +0 -0
  87. {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/ci_cd.py +0 -0
  88. {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/docker/__init__.py +0 -0
  89. {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/docker/docker_compose.py +0 -0
  90. {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/docker/dockerfile.py +0 -0
  91. {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/docker.py +0 -0
  92. {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/javascript/__init__.py +0 -0
  93. {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/javascript.py +0 -0
  94. {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/python.py +0 -0
  95. {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/test_build_systems.py +0 -0
  96. {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/test_ci_cd.py +0 -0
  97. {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/test_docker.py +0 -0
  98. {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/test_javascript.py +0 -0
  99. {domd-2.2.4 → domd-2.2.6}/src/domd/parsers/test_python.py +0 -0
  100. {domd-2.2.4 → domd-2.2.6}/src/domd/parsing/__init__.py +0 -0
  101. {domd-2.2.4 → domd-2.2.6}/src/domd/parsing/base.py +0 -0
  102. {domd-2.2.4 → domd-2.2.6}/src/domd/reporters/__init__.py +0 -0
  103. {domd-2.2.4 → domd-2.2.6}/src/domd/reporters/console.py +0 -0
  104. {domd-2.2.4 → domd-2.2.6}/src/domd/reporters/json_reporter.py +0 -0
  105. {domd-2.2.4 → domd-2.2.6}/src/domd/reporters/test_console.py +0 -0
  106. {domd-2.2.4 → domd-2.2.6}/src/domd/reporters/test_json_reporter.py +0 -0
  107. {domd-2.2.4 → domd-2.2.6}/src/domd/reporters/test_todo_md.py +0 -0
  108. {domd-2.2.4 → domd-2.2.6}/src/domd/reporters/todo_md.py +0 -0
  109. {domd-2.2.4 → domd-2.2.6}/src/domd/reporting/__init__.py +0 -0
  110. {domd-2.2.4 → domd-2.2.6}/src/domd/utils/__init__.py +0 -0
  111. {domd-2.2.4 → domd-2.2.6}/src/domd/utils/command_runner.py +0 -0
  112. {domd-2.2.4 → domd-2.2.6}/src/domd/utils/file_utils.py +0 -0
  113. {domd-2.2.4 → domd-2.2.6}/src/domd/utils/test_command_runner.py +0 -0
  114. {domd-2.2.4 → domd-2.2.6}/src/domd/utils/test_file_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: domd
3
- Version: 2.2.4
3
+ Version: 2.2.6
4
4
  Summary: Project Command Detector - Automatically detects and tests project commands, generates TODO.md for failed commands
5
5
  License: Apache-2.0
6
6
  Author: Tom Sapletta
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "domd"
3
- version = "2.2.4"
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,6 @@
1
+ """Handlers for command detection and processing."""
2
+
3
+ from .command_handler import CommandHandler
4
+ from .config_handler import ConfigFileHandler
5
+
6
+ __all__ = ["CommandHandler", "ConfigFileHandler"]
@@ -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
- # Read file content
291
- content = file_path.read_text(encoding="utf-8")
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 content
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
- # Try with content as string
299
+ # First try without arguments (parser will handle file reading)
299
300
  try:
300
- file_commands = parser.parse(content)
301
- except TypeError:
302
- # Last resort - try passing content as the first argument
303
- file_commands = parser.parse(content)
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, file_path: Optional[Union[str, Path]] = None
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 self.file_path is None or not self.file_path.exists():
47
- logging.warning(f"File not found: {self.file_path}")
48
- return []
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
- with open(self.file_path, "r", encoding="utf-8") as f:
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