domd 2.2.1__tar.gz → 2.2.3__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 (110) hide show
  1. {domd-2.2.1 → domd-2.2.3}/PKG-INFO +1 -1
  2. {domd-2.2.1 → domd-2.2.3}/pyproject.toml +1 -1
  3. {domd-2.2.1 → domd-2.2.3}/src/domd/core/project_detection/detector.py +112 -67
  4. {domd-2.2.1 → domd-2.2.3}/LICENSE +0 -0
  5. {domd-2.2.1 → domd-2.2.3}/README.md +0 -0
  6. {domd-2.2.1 → domd-2.2.3}/src/domd/__init__.py +0 -0
  7. {domd-2.2.1 → domd-2.2.3}/src/domd/adapters/__init__.py +0 -0
  8. {domd-2.2.1 → domd-2.2.3}/src/domd/adapters/api/__init__.py +0 -0
  9. {domd-2.2.1 → domd-2.2.3}/src/domd/adapters/api/flask_api.py +0 -0
  10. {domd-2.2.1 → domd-2.2.3}/src/domd/adapters/cli/__init__.py +0 -0
  11. {domd-2.2.1 → domd-2.2.3}/src/domd/adapters/cli/command_presenter.py +0 -0
  12. {domd-2.2.1 → domd-2.2.3}/src/domd/adapters/formatters/__init__.py +0 -0
  13. {domd-2.2.1 → domd-2.2.3}/src/domd/adapters/formatters/markdown_formatter.py +0 -0
  14. {domd-2.2.1 → domd-2.2.3}/src/domd/adapters/persistence/__init__.py +0 -0
  15. {domd-2.2.1 → domd-2.2.3}/src/domd/adapters/persistence/in_memory_command_repository.py +0 -0
  16. {domd-2.2.1 → domd-2.2.3}/src/domd/adapters/persistence/shell_command_executor.py +0 -0
  17. {domd-2.2.1 → domd-2.2.3}/src/domd/api.py +0 -0
  18. {domd-2.2.1 → domd-2.2.3}/src/domd/application/__init__.py +0 -0
  19. {domd-2.2.1 → domd-2.2.3}/src/domd/application/factory.py +0 -0
  20. {domd-2.2.1 → domd-2.2.3}/src/domd/cli.py +0 -0
  21. {domd-2.2.1 → domd-2.2.3}/src/domd/command_execution/__init__.py +0 -0
  22. {domd-2.2.1 → domd-2.2.3}/src/domd/core/__init__.py +0 -0
  23. {domd-2.2.1 → domd-2.2.3}/src/domd/core/command_execution/__init__.py +0 -0
  24. {domd-2.2.1 → domd-2.2.3}/src/domd/core/command_execution/command_executor.py +0 -0
  25. {domd-2.2.1 → domd-2.2.3}/src/domd/core/command_execution/command_recorder.py +0 -0
  26. {domd-2.2.1 → domd-2.2.3}/src/domd/core/command_execution/command_runner.py +0 -0
  27. {domd-2.2.1 → domd-2.2.3}/src/domd/core/command_execution/environment_detector.py +0 -0
  28. {domd-2.2.1 → domd-2.2.3}/src/domd/core/command_execution/executor.py +0 -0
  29. {domd-2.2.1 → domd-2.2.3}/src/domd/core/commands/__init__.py +0 -0
  30. {domd-2.2.1 → domd-2.2.3}/src/domd/core/commands/command.py +0 -0
  31. {domd-2.2.1 → domd-2.2.3}/src/domd/core/commands/executor.py +0 -0
  32. {domd-2.2.1 → domd-2.2.3}/src/domd/core/detector.py +0 -0
  33. {domd-2.2.1 → domd-2.2.3}/src/domd/core/domain/__init__.py +0 -0
  34. {domd-2.2.1 → domd-2.2.3}/src/domd/core/domain/command.py +0 -0
  35. {domd-2.2.1 → domd-2.2.3}/src/domd/core/parsers/__init__.py +0 -0
  36. {domd-2.2.1 → domd-2.2.3}/src/domd/core/parsers/ansible_galaxy.py +0 -0
  37. {domd-2.2.1 → domd-2.2.3}/src/domd/core/parsers/ansible_inventory.py +0 -0
  38. {domd-2.2.1 → domd-2.2.3}/src/domd/core/parsers/ansible_playbook.py +0 -0
  39. {domd-2.2.1 → domd-2.2.3}/src/domd/core/parsers/ansible_role.py +0 -0
  40. {domd-2.2.1 → domd-2.2.3}/src/domd/core/parsers/ansible_vault.py +0 -0
  41. {domd-2.2.1 → domd-2.2.3}/src/domd/core/parsers/base.py +0 -0
  42. {domd-2.2.1 → domd-2.2.3}/src/domd/core/parsers/cargo_toml.py +0 -0
  43. {domd-2.2.1 → domd-2.2.3}/src/domd/core/parsers/composer_json.py +0 -0
  44. {domd-2.2.1 → domd-2.2.3}/src/domd/core/parsers/go_mod.py +0 -0
  45. {domd-2.2.1 → domd-2.2.3}/src/domd/core/parsers/makefile.py +0 -0
  46. {domd-2.2.1 → domd-2.2.3}/src/domd/core/parsers/package_json.py +0 -0
  47. {domd-2.2.1 → domd-2.2.3}/src/domd/core/parsers/pyproject_toml.py +0 -0
  48. {domd-2.2.1 → domd-2.2.3}/src/domd/core/parsers/tox_ini.py +0 -0
  49. {domd-2.2.1 → domd-2.2.3}/src/domd/core/parsing/__init__.py +0 -0
  50. {domd-2.2.1 → domd-2.2.3}/src/domd/core/parsing/base.py +0 -0
  51. {domd-2.2.1 → domd-2.2.3}/src/domd/core/parsing/file_processor.py +0 -0
  52. {domd-2.2.1 → domd-2.2.3}/src/domd/core/parsing/parser_registry.py +0 -0
  53. {domd-2.2.1 → domd-2.2.3}/src/domd/core/parsing/pattern_matcher.py +0 -0
  54. {domd-2.2.1 → domd-2.2.3}/src/domd/core/ports/__init__.py +0 -0
  55. {domd-2.2.1 → domd-2.2.3}/src/domd/core/ports/command_executor.py +0 -0
  56. {domd-2.2.1 → domd-2.2.3}/src/domd/core/ports/command_repository.py +0 -0
  57. {domd-2.2.1 → domd-2.2.3}/src/domd/core/ports/report_formatter.py +0 -0
  58. {domd-2.2.1 → domd-2.2.3}/src/domd/core/project_detection/__init__.py +0 -0
  59. {domd-2.2.1 → domd-2.2.3}/src/domd/core/project_detection/command_handling.py +0 -0
  60. {domd-2.2.1 → domd-2.2.3}/src/domd/core/project_detection/config_files.py +0 -0
  61. {domd-2.2.1 → domd-2.2.3}/src/domd/core/project_detection/virtualenv.py +0 -0
  62. {domd-2.2.1 → domd-2.2.3}/src/domd/core/reporters/__init__.py +0 -0
  63. {domd-2.2.1 → domd-2.2.3}/src/domd/core/reporters/base.py +0 -0
  64. {domd-2.2.1 → domd-2.2.3}/src/domd/core/reporters/done_md.py +0 -0
  65. {domd-2.2.1 → domd-2.2.3}/src/domd/core/reporters/todo_md.py +0 -0
  66. {domd-2.2.1 → domd-2.2.3}/src/domd/core/reporting/__init__.py +0 -0
  67. {domd-2.2.1 → domd-2.2.3}/src/domd/core/reporting/formatters.py +0 -0
  68. {domd-2.2.1 → domd-2.2.3}/src/domd/core/reporting/reporter.py +0 -0
  69. {domd-2.2.1 → domd-2.2.3}/src/domd/core/services/__init__.py +0 -0
  70. {domd-2.2.1 → domd-2.2.3}/src/domd/core/services/command_service.py +0 -0
  71. {domd-2.2.1 → domd-2.2.3}/src/domd/core/services/report_service.py +0 -0
  72. {domd-2.2.1 → domd-2.2.3}/src/domd/core/utils/__init__.py +0 -0
  73. {domd-2.2.1 → domd-2.2.3}/src/domd/core/utils/command_utils.py +0 -0
  74. {domd-2.2.1 → domd-2.2.3}/src/domd/core/utils/environment.py +0 -0
  75. {domd-2.2.1 → domd-2.2.3}/src/domd/core/utils/file_utils.py +0 -0
  76. {domd-2.2.1 → domd-2.2.3}/src/domd/core/utils/logging_utils.py +0 -0
  77. {domd-2.2.1 → domd-2.2.3}/src/domd/core/utils/virtualenv.py +0 -0
  78. {domd-2.2.1 → domd-2.2.3}/src/domd/parsers/__init__.py +0 -0
  79. {domd-2.2.1 → domd-2.2.3}/src/domd/parsers/ansible.py +0 -0
  80. {domd-2.2.1 → domd-2.2.3}/src/domd/parsers/base.py +0 -0
  81. {domd-2.2.1 → domd-2.2.3}/src/domd/parsers/build_systems.py +0 -0
  82. {domd-2.2.1 → domd-2.2.3}/src/domd/parsers/ci_cd.py +0 -0
  83. {domd-2.2.1 → domd-2.2.3}/src/domd/parsers/docker/__init__.py +0 -0
  84. {domd-2.2.1 → domd-2.2.3}/src/domd/parsers/docker/docker_compose.py +0 -0
  85. {domd-2.2.1 → domd-2.2.3}/src/domd/parsers/docker/dockerfile.py +0 -0
  86. {domd-2.2.1 → domd-2.2.3}/src/domd/parsers/docker.py +0 -0
  87. {domd-2.2.1 → domd-2.2.3}/src/domd/parsers/javascript/__init__.py +0 -0
  88. {domd-2.2.1 → domd-2.2.3}/src/domd/parsers/javascript.py +0 -0
  89. {domd-2.2.1 → domd-2.2.3}/src/domd/parsers/package_json.py +0 -0
  90. {domd-2.2.1 → domd-2.2.3}/src/domd/parsers/python.py +0 -0
  91. {domd-2.2.1 → domd-2.2.3}/src/domd/parsers/test_build_systems.py +0 -0
  92. {domd-2.2.1 → domd-2.2.3}/src/domd/parsers/test_ci_cd.py +0 -0
  93. {domd-2.2.1 → domd-2.2.3}/src/domd/parsers/test_docker.py +0 -0
  94. {domd-2.2.1 → domd-2.2.3}/src/domd/parsers/test_javascript.py +0 -0
  95. {domd-2.2.1 → domd-2.2.3}/src/domd/parsers/test_python.py +0 -0
  96. {domd-2.2.1 → domd-2.2.3}/src/domd/parsing/__init__.py +0 -0
  97. {domd-2.2.1 → domd-2.2.3}/src/domd/parsing/base.py +0 -0
  98. {domd-2.2.1 → domd-2.2.3}/src/domd/reporters/__init__.py +0 -0
  99. {domd-2.2.1 → domd-2.2.3}/src/domd/reporters/console.py +0 -0
  100. {domd-2.2.1 → domd-2.2.3}/src/domd/reporters/json_reporter.py +0 -0
  101. {domd-2.2.1 → domd-2.2.3}/src/domd/reporters/test_console.py +0 -0
  102. {domd-2.2.1 → domd-2.2.3}/src/domd/reporters/test_json_reporter.py +0 -0
  103. {domd-2.2.1 → domd-2.2.3}/src/domd/reporters/test_todo_md.py +0 -0
  104. {domd-2.2.1 → domd-2.2.3}/src/domd/reporters/todo_md.py +0 -0
  105. {domd-2.2.1 → domd-2.2.3}/src/domd/reporting/__init__.py +0 -0
  106. {domd-2.2.1 → domd-2.2.3}/src/domd/utils/__init__.py +0 -0
  107. {domd-2.2.1 → domd-2.2.3}/src/domd/utils/command_runner.py +0 -0
  108. {domd-2.2.1 → domd-2.2.3}/src/domd/utils/file_utils.py +0 -0
  109. {domd-2.2.1 → domd-2.2.3}/src/domd/utils/test_command_runner.py +0 -0
  110. {domd-2.2.1 → domd-2.2.3}/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.1
3
+ Version: 2.2.3
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.1"
3
+ version = "2.2.3"
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"]
@@ -213,6 +213,56 @@ class ProjectCommandDetector:
213
213
  """
214
214
  return get_virtualenv_environment(self.venv_info)
215
215
 
216
+ def _process_directory_readme(
217
+ self, directory: Path, all_commands: list, parent_dir: str = None
218
+ ) -> None:
219
+ """Process a README.md file in a directory and update command metadata.
220
+
221
+ Args:
222
+ directory: Directory containing the README.md
223
+ all_commands: List to append found commands to
224
+ parent_dir: Name of parent directory (for second-level directories)
225
+ """
226
+ logger.debug(f"Checking for README.md in: {directory}")
227
+ readme_path = directory / "README.md"
228
+
229
+ if not readme_path.exists():
230
+ logger.debug(f" README.md not found in {directory}")
231
+ return
232
+
233
+ if not readme_path.is_file():
234
+ logger.debug(f" README.md exists but is not a file in {directory}")
235
+ return
236
+
237
+ logger.info(f"Found README.md in directory: {directory}")
238
+
239
+ # Determine display path for logging and source
240
+ if parent_dir:
241
+ display_path = f"{parent_dir}/{directory.name}/README.md"
242
+ else:
243
+ display_path = f"{directory.name}/README.md"
244
+
245
+ logger.info(f"Found README.md: {display_path}")
246
+
247
+ # Process README.md in directory
248
+ commands = self._process_file_commands(readme_path)
249
+
250
+ # Update command metadata with directory context
251
+ for cmd in commands:
252
+ if isinstance(cmd, dict):
253
+ cmd_metadata = cmd.setdefault("metadata", {})
254
+ cmd_metadata["cwd"] = str(directory)
255
+ cmd_metadata["source"] = display_path
256
+ elif hasattr(cmd, "metadata"):
257
+ if not hasattr(cmd.metadata, "get") or not callable(cmd.metadata.get):
258
+ cmd.metadata = {"original_metadata": cmd.metadata}
259
+ cmd.metadata["cwd"] = str(directory)
260
+ if not getattr(cmd, "source", None):
261
+ cmd.source = display_path
262
+
263
+ all_commands.extend(commands)
264
+ logger.info(f"Found {len(commands)} commands in {display_path}")
265
+
216
266
  def _process_file_commands(self, file_path: Path) -> List[Dict]:
217
267
  """Process a single file and extract commands.
218
268
 
@@ -220,54 +270,41 @@ class ProjectCommandDetector:
220
270
  file_path: Path to the file to process
221
271
 
222
272
  Returns:
223
- List of command dictionaries
273
+ List of command dictionaries with metadata
224
274
  """
275
+ logger.debug(f"Processing file: {file_path}")
225
276
  commands = []
226
- try:
227
- # Try to get parser from registry first
228
- parser = self.parser_registry.get_parser_for_file(file_path)
229
-
230
- # If no parser found in registry, try legacy method
231
- if not parser:
232
- parser = self._get_parser_for_file(file_path)
233
277
 
234
- if not parser:
235
- logger.warning(f"No parser found for {file_path}")
278
+ try:
279
+ if not file_path.exists():
280
+ logger.warning(f"File not found: {file_path}")
236
281
  return commands
237
282
 
238
- logger.debug(f"Parsing {file_path} with {parser.__class__.__name__}")
239
-
240
- # Read file content
241
- try:
242
- with open(file_path, "r", encoding="utf-8") as f:
243
- content = f.read()
244
- except Exception as e:
245
- logger.error(f"Error reading file {file_path}: {e}")
283
+ # Get the appropriate parser for the file
284
+ parser = self._get_parser_for_file(file_path)
285
+ if not parser:
286
+ logger.debug(f"No parser found for file: {file_path}")
246
287
  return commands
247
288
 
248
- # Set the file_path on the parser if it has that attribute
249
- if hasattr(parser, "file_path"):
250
- parser.file_path = file_path
251
-
252
- # Try different ways to call the parse method
253
289
  try:
254
- import inspect
255
-
256
- sig = inspect.signature(parser.parse)
257
-
258
- if "file_path" in sig.parameters and "content" in sig.parameters:
259
- file_commands = parser.parse(content=content, file_path=file_path)
260
- elif "content" in sig.parameters:
261
- file_commands = parser.parse(content=content)
262
- elif "file_path" in sig.parameters:
263
- file_commands = parser.parse(file_path=file_path)
264
- else:
265
- # Last resort - try passing content as the first argument
266
- file_commands = parser.parse(content)
290
+ # Read file content
291
+ content = file_path.read_text(encoding="utf-8")
292
+
293
+ # Parse commands from file content
294
+ file_commands = []
295
+ if hasattr(parser, "parse_file") and callable(parser.parse_file):
296
+ file_commands = parser.parse_file(file_path)
297
+ elif hasattr(parser, "parse") and callable(parser.parse):
298
+ # Try with content as string
299
+ 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)
267
304
 
268
305
  # Add file path to commands if not already present
269
306
  for cmd in file_commands:
270
- if isinstance(cmd, dict) and "file" not in cmd:
307
+ if isinstance(cmd, dict):
271
308
  cmd["file"] = str(file_path)
272
309
  # Add relative path as source if not present
273
310
  if "source" not in cmd:
@@ -309,37 +346,47 @@ class ProjectCommandDetector:
309
346
  for file_path in config_files:
310
347
  all_commands.extend(self._process_file_commands(file_path))
311
348
 
312
- # 2. Scan first-level subdirectories for README.md files
349
+ # 2. Scan first and second level subdirectories for README.md files
313
350
  try:
314
- for item in self.project_path.iterdir():
315
- if item.is_dir() and not item.name.startswith("."):
316
- readme_path = item / "README.md"
317
- if readme_path.exists() and readme_path.is_file():
318
- logger.info(f"Found README.md in subdirectory: {item.name}")
319
- # Process README.md in subdirectory
320
- commands = self._process_file_commands(readme_path)
321
-
322
- # Update command metadata with subdirectory context
323
- for cmd in commands:
324
- if isinstance(cmd, dict):
325
- cmd_metadata = cmd.setdefault("metadata", {})
326
- cmd_metadata["cwd"] = str(item)
327
- cmd_metadata["source"] = f"{item.name}/README.md"
328
- elif hasattr(cmd, "metadata"):
329
- if not hasattr(cmd.metadata, "get") or not callable(
330
- cmd.metadata.get
331
- ):
332
- cmd.metadata = {"original_metadata": cmd.metadata}
333
- cmd.metadata["cwd"] = str(item)
334
- if not getattr(cmd, "source", None):
335
- cmd.source = f"{item.name}/README.md"
336
-
337
- all_commands.extend(commands)
351
+ logger.debug(f"Starting directory scan in: {self.project_path}")
352
+ # First level directories
353
+ for level1_item in sorted(self.project_path.iterdir()):
354
+ logger.debug(f"Checking first level item: {level1_item}")
355
+ if not level1_item.is_dir() or level1_item.name.startswith("."):
356
+ logger.debug(f"Skipping (not a directory or hidden): {level1_item}")
357
+ continue
358
+
359
+ logger.info(f"Processing first level directory: {level1_item.name}")
360
+ # Process level 1 README
361
+ self._process_directory_readme(level1_item, all_commands)
362
+
363
+ # Scan second level
364
+ try:
365
+ for level2_item in sorted(level1_item.iterdir()):
366
+ logger.debug(f" Checking second level item: {level2_item}")
367
+ if not level2_item.is_dir() or level2_item.name.startswith("."):
368
+ logger.debug(
369
+ f" Skipping (not a directory or hidden): {level2_item}"
370
+ )
371
+ continue
372
+
338
373
  logger.info(
339
- f"Found {len(commands)} commands in {item.name}/README.md"
374
+ f" Processing second level directory: {level1_item.name}/{level2_item.name}"
340
375
  )
376
+ # Process level 2 README
377
+ self._process_directory_readme(
378
+ level2_item, all_commands, level1_item.name
379
+ )
380
+ except Exception as e:
381
+ logger.error(
382
+ f"Error scanning subdirectory {level1_item}: {e}", exc_info=True
383
+ )
341
384
  except Exception as e:
342
- logger.error(f"Error scanning subdirectories: {e}", exc_info=True)
385
+ logger.error(f"Error scanning directories: {e}", exc_info=True)
386
+
387
+ logger.debug(
388
+ f"Finished directory scan. Found {len(all_commands)} commands total."
389
+ )
343
390
 
344
391
  logger.info(f"Found {len(all_commands)} commands in total")
345
392
 
@@ -654,9 +701,7 @@ class ProjectCommandDetector:
654
701
  source = cmd_dict.get("source", "Unknown")
655
702
 
656
703
  f.write(f"### 🔧 Fix: {command}\n") # noqa: E231
657
- f.write(
658
- f"- [ ] **Command**: `{command}` \n"
659
- ) # noqa: E201,E231
704
+ f.write(f"- [ ] **Command**: `{command}` \n") # noqa: E201,E231
660
705
  f.write(f"- **Error**: {error} \n") # noqa: E231
661
706
  f.write(f"- **Source**: `{source}`\n") # noqa: E231
662
707
  f.write("- **Fix Suggestion**: \n\n") # noqa: E231
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