domd 2.2.24__tar.gz → 2.2.26__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.24 → domd-2.2.26}/PKG-INFO +1 -1
- {domd-2.2.24 → domd-2.2.26}/pyproject.toml +1 -1
- {domd-2.2.24 → domd-2.2.26}/src/domd/cli.py +31 -31
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/command_execution/command_executor.py +1 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/parsers/ansible_playbook.py +19 -9
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/project_detection/command_handling.py +23 -11
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/project_detection/detector.py +181 -41
- {domd-2.2.24 → domd-2.2.26}/LICENSE +0 -0
- {domd-2.2.24 → domd-2.2.26}/README.md +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/adapters/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/adapters/api/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/adapters/api/flask_api.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/adapters/cli/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/adapters/cli/command_presenter.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/adapters/formatters/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/adapters/formatters/markdown_formatter.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/adapters/persistence/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/adapters/persistence/in_memory_command_repository.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/adapters/persistence/shell_command_executor.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/api.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/application/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/application/factory.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/command_execution/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/command_detection/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/command_detection/base_detector.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/command_detection/handlers/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/command_detection/handlers/command_handler.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/command_detection/handlers/config_handler.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/command_detection/models.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/command_detection/parsers/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/command_detection/parsers/base_parser.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/command_detection/parsers/parser_registry.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/command_detection/project_detector.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/command_detection/utils/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/command_detection/utils/file_utils.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/command_detection/utils/virtualenv.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/command_execution/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/command_execution/command_recorder.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/command_execution/command_runner.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/command_execution/environment_detector.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/command_execution/executor.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/commands/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/commands/command.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/commands/executor.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/detector.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/domain/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/domain/command.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/parsers/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/parsers/ansible_galaxy.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/parsers/ansible_inventory.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/parsers/ansible_role.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/parsers/ansible_vault.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/parsers/base.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/parsers/cargo_toml.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/parsers/composer_json.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/parsers/go_mod.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/parsers/makefile.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/parsers/package_json.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/parsers/pyproject_toml.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/parsers/tox_ini.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/parsing/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/parsing/base.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/parsing/file_processor.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/parsing/parser_registry.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/parsing/pattern_matcher.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/ports/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/ports/command_executor.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/ports/command_repository.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/ports/report_formatter.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/project_detection/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/project_detection/config_files.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/project_detection/virtualenv.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/reporters/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/reporters/base.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/reporters/done_md.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/reporters/todo_md.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/reporting/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/reporting/formatters.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/reporting/reporter.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/services/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/services/command_service.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/services/report_service.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/utils/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/utils/command_utils.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/utils/environment.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/utils/file_utils.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/utils/logging_utils.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/core/utils/virtualenv.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/parsers/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/parsers/ansible.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/parsers/base.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/parsers/build_systems.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/parsers/ci_cd.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/parsers/docker/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/parsers/docker/docker_compose.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/parsers/docker/dockerfile.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/parsers/docker.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/parsers/javascript/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/parsers/javascript.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/parsers/markdown_parser.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/parsers/package_json.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/parsers/python.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/parsers/test_build_systems.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/parsers/test_ci_cd.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/parsers/test_docker.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/parsers/test_javascript.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/parsers/test_python.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/parsing/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/parsing/base.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/reporters/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/reporters/console.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/reporters/json_reporter.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/reporters/test_console.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/reporters/test_json_reporter.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/reporters/test_todo_md.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/reporters/todo_md.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/reporting/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/utils/__init__.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/utils/command_runner.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/utils/file_utils.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/utils/path_utils.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/src/domd/utils/test_command_runner.py +0 -0
- {domd-2.2.24 → domd-2.2.26}/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.26"
|
|
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"]
|
|
@@ -32,37 +32,34 @@ logger = logging.getLogger(__name__)
|
|
|
32
32
|
|
|
33
33
|
def _check_node_installed() -> bool:
|
|
34
34
|
"""Check if Node.js and npm are installed.
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
Returns:
|
|
37
37
|
bool: True if both Node.js and npm are installed, False otherwise
|
|
38
38
|
"""
|
|
39
39
|
try:
|
|
40
40
|
# Check Node.js
|
|
41
41
|
node_version = subprocess.check_output(
|
|
42
|
-
["node", "--version"],
|
|
43
|
-
stderr=subprocess.PIPE,
|
|
44
|
-
universal_newlines=True
|
|
42
|
+
["node", "--version"], stderr=subprocess.PIPE, universal_newlines=True
|
|
45
43
|
).strip()
|
|
46
|
-
|
|
44
|
+
|
|
47
45
|
# Check npm
|
|
48
46
|
npm_version = subprocess.check_output(
|
|
49
|
-
["npm", "--version"],
|
|
50
|
-
stderr=subprocess.PIPE,
|
|
51
|
-
universal_newlines=True
|
|
47
|
+
["npm", "--version"], stderr=subprocess.PIPE, universal_newlines=True
|
|
52
48
|
).strip()
|
|
53
|
-
|
|
49
|
+
|
|
54
50
|
print(f"✓ Found {node_version} and npm {npm_version}")
|
|
55
51
|
return True
|
|
56
|
-
|
|
52
|
+
|
|
57
53
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
58
54
|
return False
|
|
59
55
|
|
|
56
|
+
|
|
60
57
|
def _install_dependencies(frontend_dir: Path) -> bool:
|
|
61
58
|
"""Install frontend dependencies using npm.
|
|
62
|
-
|
|
59
|
+
|
|
63
60
|
Args:
|
|
64
61
|
frontend_dir: Path to the frontend directory
|
|
65
|
-
|
|
62
|
+
|
|
66
63
|
Returns:
|
|
67
64
|
bool: True if installation was successful, False otherwise
|
|
68
65
|
"""
|
|
@@ -80,59 +77,62 @@ def _install_dependencies(frontend_dir: Path) -> bool:
|
|
|
80
77
|
print(f"❌ Failed to install dependencies: {e}")
|
|
81
78
|
return False
|
|
82
79
|
|
|
80
|
+
|
|
83
81
|
def start_web_interface(args: argparse.Namespace) -> int:
|
|
84
82
|
"""Start the web interface.
|
|
85
|
-
|
|
83
|
+
|
|
86
84
|
Args:
|
|
87
85
|
args: Command line arguments
|
|
88
|
-
|
|
86
|
+
|
|
89
87
|
Returns:
|
|
90
88
|
int: Exit code (0 for success, non-zero for error)
|
|
91
89
|
"""
|
|
92
90
|
port = args.port
|
|
93
91
|
open_browser = not args.no_browser
|
|
94
|
-
|
|
92
|
+
|
|
95
93
|
# Check if Node.js and npm are installed
|
|
96
94
|
print("🔍 Checking for Node.js and npm...")
|
|
97
95
|
if not _check_node_installed():
|
|
98
|
-
print(
|
|
96
|
+
print(
|
|
97
|
+
"""
|
|
99
98
|
❌ Node.js and npm are required to run the web interface.
|
|
100
99
|
Please install them from https://nodejs.org/ and try again.
|
|
101
|
-
|
|
100
|
+
|
|
102
101
|
On Ubuntu/Debian: sudo apt-get install nodejs npm
|
|
103
102
|
On macOS: brew install node
|
|
104
103
|
On Windows: Download from https://nodejs.org/
|
|
105
|
-
"""
|
|
104
|
+
"""
|
|
105
|
+
)
|
|
106
106
|
return 1
|
|
107
|
-
|
|
107
|
+
|
|
108
108
|
# Get the directory of this file
|
|
109
109
|
current_dir = Path(__file__).parent
|
|
110
110
|
frontend_dir = current_dir.parent.parent / "frontend"
|
|
111
|
-
|
|
111
|
+
|
|
112
112
|
if not frontend_dir.exists():
|
|
113
113
|
print(f"❌ Error: Frontend directory not found at {frontend_dir}")
|
|
114
114
|
return 1
|
|
115
|
-
|
|
115
|
+
|
|
116
116
|
# Check if node_modules exists, if not, install dependencies
|
|
117
117
|
if not (frontend_dir / "node_modules").exists():
|
|
118
118
|
if not _install_dependencies(frontend_dir):
|
|
119
119
|
return 1
|
|
120
|
-
|
|
120
|
+
|
|
121
121
|
# Change to the frontend directory
|
|
122
122
|
os.chdir(frontend_dir)
|
|
123
|
-
|
|
123
|
+
|
|
124
124
|
# Set the port environment variable
|
|
125
125
|
os.environ["PORT"] = str(port)
|
|
126
|
-
|
|
126
|
+
|
|
127
127
|
print(f"🚀 Starting DoMD web interface on port {port}...")
|
|
128
128
|
print(" Press Ctrl+C to stop the server")
|
|
129
|
-
|
|
129
|
+
|
|
130
130
|
# Start the React development server
|
|
131
131
|
try:
|
|
132
132
|
cmd = ["npm", "start"]
|
|
133
133
|
if sys.platform == "win32":
|
|
134
134
|
cmd = ["npm.cmd", "start"]
|
|
135
|
-
|
|
135
|
+
|
|
136
136
|
# Start the process
|
|
137
137
|
process = subprocess.Popen(
|
|
138
138
|
cmd,
|
|
@@ -141,25 +141,25 @@ def start_web_interface(args: argparse.Namespace) -> int:
|
|
|
141
141
|
stderr=subprocess.STDOUT,
|
|
142
142
|
universal_newlines=True,
|
|
143
143
|
)
|
|
144
|
-
|
|
144
|
+
|
|
145
145
|
# Open browser after a short delay
|
|
146
146
|
if open_browser:
|
|
147
147
|
time.sleep(2) # Give the server a moment to start
|
|
148
148
|
print(f"🌐 Opening browser to http://localhost:{port}...")
|
|
149
149
|
webbrowser.open(f"http://localhost:{port}")
|
|
150
|
-
|
|
150
|
+
|
|
151
151
|
# Stream the output
|
|
152
152
|
for line in process.stdout:
|
|
153
153
|
print(line, end="")
|
|
154
|
-
|
|
154
|
+
|
|
155
155
|
# Wait for the process to complete
|
|
156
156
|
process.wait()
|
|
157
157
|
return process.returncode
|
|
158
|
-
|
|
158
|
+
|
|
159
159
|
except KeyboardInterrupt:
|
|
160
160
|
print("\n🛑 Web interface stopped by user")
|
|
161
161
|
return 0
|
|
162
|
-
|
|
162
|
+
|
|
163
163
|
except Exception as e:
|
|
164
164
|
print(f"❌ Error starting web interface: {e}")
|
|
165
165
|
print("\n💡 Try these troubleshooting steps:")
|
|
@@ -54,31 +54,41 @@ class AnsiblePlaybookParser(BaseParser):
|
|
|
54
54
|
"""
|
|
55
55
|
from domd.core.commands import Command
|
|
56
56
|
|
|
57
|
-
if not self
|
|
57
|
+
if not hasattr(self, "file_path") or self.file_path is None:
|
|
58
58
|
return []
|
|
59
59
|
|
|
60
60
|
self._commands: List[Command] = []
|
|
61
61
|
|
|
62
62
|
# If content is not provided, read from file
|
|
63
|
-
if
|
|
63
|
+
if (
|
|
64
|
+
content is None
|
|
65
|
+
and hasattr(self, "file_path")
|
|
66
|
+
and not self.file_path.is_dir()
|
|
67
|
+
):
|
|
64
68
|
try:
|
|
65
|
-
with open(self.file_path, "r", encoding="utf-8") as f:
|
|
69
|
+
with open(str(self.file_path), "r", encoding="utf-8") as f:
|
|
66
70
|
content = f.read()
|
|
67
|
-
except (IOError, UnicodeDecodeError):
|
|
71
|
+
except (IOError, UnicodeDecodeError) as e:
|
|
68
72
|
# If we can't read the file, return empty list
|
|
73
|
+
import logging
|
|
74
|
+
|
|
75
|
+
logger = logging.getLogger(__name__)
|
|
76
|
+
logger.warning(f"Error reading file {self.file_path}: {e}")
|
|
69
77
|
return []
|
|
70
78
|
|
|
71
79
|
try:
|
|
72
|
-
playbook = yaml.safe_load(content)
|
|
80
|
+
playbook = yaml.safe_load(content) if content else []
|
|
73
81
|
|
|
74
82
|
if not isinstance(playbook, list):
|
|
75
83
|
return []
|
|
76
84
|
|
|
77
85
|
# Get relative path if file is in project directory, otherwise use full path
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
86
|
+
playbook_path = str(self.file_path)
|
|
87
|
+
if hasattr(self, "project_root") and self.project_root:
|
|
88
|
+
try:
|
|
89
|
+
playbook_path = str(self.file_path.relative_to(self.project_root))
|
|
90
|
+
except ValueError:
|
|
91
|
+
pass
|
|
82
92
|
|
|
83
93
|
for play in playbook:
|
|
84
94
|
if not isinstance(play, dict):
|
|
@@ -250,18 +250,30 @@ class CommandHandler:
|
|
|
250
250
|
bin_dir = "bin"
|
|
251
251
|
python_exe = "python"
|
|
252
252
|
|
|
253
|
-
|
|
254
|
-
|
|
253
|
+
# First try the direct path from venv_env if available
|
|
254
|
+
python_path = venv_env.get("python_path")
|
|
255
|
+
if not python_path or not os.path.isfile(python_path):
|
|
256
|
+
# Fall back to standard location
|
|
257
|
+
python_path = os.path.join(venv_path, bin_dir, python_exe)
|
|
258
|
+
|
|
259
|
+
# If that doesn't exist, try version-specific python
|
|
260
|
+
if not os.path.isfile(python_path):
|
|
261
|
+
python_path = os.path.join(
|
|
262
|
+
venv_path,
|
|
263
|
+
bin_dir,
|
|
264
|
+
f"python{sys.version_info.major}.{sys.version_info.minor}",
|
|
265
|
+
)
|
|
266
|
+
# If still not found, try without minor version
|
|
267
|
+
if not os.path.isfile(python_path):
|
|
268
|
+
python_path = os.path.join(
|
|
269
|
+
venv_path,
|
|
270
|
+
bin_dir,
|
|
271
|
+
f"python{sys.version_info.major}",
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# If we found a valid Python executable, use it
|
|
275
|
+
if python_path and os.path.isfile(python_path):
|
|
255
276
|
cmd_list[0] = python_path
|
|
256
|
-
else:
|
|
257
|
-
# Fallback to the Python executable in the virtualenv's bin directory
|
|
258
|
-
python_path = os.path.join(
|
|
259
|
-
venv_path,
|
|
260
|
-
bin_dir,
|
|
261
|
-
f"python{sys.version_info.major}.{sys.version_info.minor}",
|
|
262
|
-
)
|
|
263
|
-
if os.path.isfile(python_path):
|
|
264
|
-
cmd_list[0] = python_path
|
|
265
277
|
|
|
266
278
|
# Merge environments, with user-provided env taking precedence
|
|
267
279
|
env = kwargs.pop("env", {})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Project command detector for finding and executing commands in project files."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
import os
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from typing import Any, Dict, List, Optional, Union
|
|
6
7
|
|
|
@@ -263,78 +264,137 @@ class ProjectCommandDetector:
|
|
|
263
264
|
all_commands.extend(commands)
|
|
264
265
|
logger.info(f"Found {len(commands)} commands in {display_path}")
|
|
265
266
|
|
|
266
|
-
def _process_file_commands(self, file_path: Path) -> List[Dict]:
|
|
267
|
+
def _process_file_commands(self, file_path: Union[str, Path]) -> List[Dict]:
|
|
267
268
|
"""Process a single file and extract commands.
|
|
268
269
|
|
|
269
270
|
Args:
|
|
270
|
-
file_path: Path to the file to process
|
|
271
|
+
file_path: Path to the file to process (can be str or Path)
|
|
271
272
|
|
|
272
273
|
Returns:
|
|
273
274
|
List of command dictionaries with metadata
|
|
274
275
|
"""
|
|
275
|
-
|
|
276
|
+
# Convert to Path object if needed
|
|
277
|
+
path = Path(file_path) if isinstance(file_path, str) else file_path
|
|
278
|
+
logger.debug(f"Processing file: {path}")
|
|
276
279
|
commands = []
|
|
277
280
|
|
|
278
281
|
try:
|
|
279
|
-
if
|
|
280
|
-
|
|
282
|
+
# Check if file exists and is a file
|
|
283
|
+
if not path.exists():
|
|
284
|
+
logger.warning(f"File not found: {path}")
|
|
285
|
+
return commands
|
|
286
|
+
|
|
287
|
+
if not path.is_file():
|
|
288
|
+
logger.debug(f"Path is not a file: {path}")
|
|
281
289
|
return commands
|
|
282
290
|
|
|
283
291
|
# Get the appropriate parser for the file
|
|
284
|
-
parser = self._get_parser_for_file(
|
|
292
|
+
parser = self._get_parser_for_file(path)
|
|
285
293
|
if not parser:
|
|
286
|
-
logger.debug(f"No parser found for file: {
|
|
294
|
+
logger.debug(f"No suitable parser found for file: {path}")
|
|
287
295
|
return commands
|
|
288
296
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
parser.file_path = str(file_path)
|
|
297
|
+
# Set the file_path on the parser if it has that attribute
|
|
298
|
+
if hasattr(parser, "file_path"):
|
|
299
|
+
parser.file_path = path
|
|
293
300
|
|
|
294
|
-
|
|
295
|
-
|
|
301
|
+
# Set project root if needed
|
|
302
|
+
if hasattr(parser, "project_root") and not hasattr(
|
|
303
|
+
parser, "_project_root_set"
|
|
304
|
+
):
|
|
305
|
+
parser.project_root = self.project_path
|
|
306
|
+
parser._project_root_set = True
|
|
307
|
+
|
|
308
|
+
# Parse commands from file
|
|
309
|
+
file_commands = []
|
|
310
|
+
try:
|
|
311
|
+
# Try different parsing methods in order of preference
|
|
296
312
|
if hasattr(parser, "parse_file") and callable(parser.parse_file):
|
|
297
|
-
file_commands = parser.parse_file(
|
|
313
|
+
file_commands = parser.parse_file(path)
|
|
298
314
|
elif hasattr(parser, "parse") and callable(parser.parse):
|
|
299
315
|
# First try without arguments (parser will handle file reading)
|
|
300
316
|
try:
|
|
301
317
|
file_commands = parser.parse()
|
|
302
|
-
except (TypeError, AttributeError):
|
|
318
|
+
except (TypeError, AttributeError) as e:
|
|
319
|
+
logger.debug(
|
|
320
|
+
f"Parser.parse() failed, trying with file path: {e}"
|
|
321
|
+
)
|
|
303
322
|
# If that fails, try with file path
|
|
304
323
|
try:
|
|
305
|
-
file_commands = parser.parse(
|
|
306
|
-
except (TypeError, AttributeError):
|
|
324
|
+
file_commands = parser.parse(path)
|
|
325
|
+
except (TypeError, AttributeError) as e:
|
|
326
|
+
logger.debug(
|
|
327
|
+
f"Parser.parse(path) failed, trying with file content: {e}"
|
|
328
|
+
)
|
|
307
329
|
# As a last resort, try with file content
|
|
308
330
|
try:
|
|
309
|
-
content =
|
|
331
|
+
content = path.read_text(encoding="utf-8")
|
|
310
332
|
file_commands = parser.parse(content)
|
|
311
333
|
except Exception as e:
|
|
312
334
|
logger.error(
|
|
313
|
-
f"Error parsing {
|
|
335
|
+
f"Error parsing {path} with content: {e}",
|
|
336
|
+
exc_info=logger.isEnabledFor(logging.DEBUG),
|
|
314
337
|
)
|
|
315
338
|
return []
|
|
316
339
|
|
|
317
|
-
|
|
340
|
+
if not isinstance(file_commands, list):
|
|
341
|
+
logger.warning(
|
|
342
|
+
f"Parser returned non-list result: {type(file_commands)}"
|
|
343
|
+
)
|
|
344
|
+
file_commands = []
|
|
345
|
+
|
|
346
|
+
# Process the parsed commands
|
|
347
|
+
processed_commands = []
|
|
318
348
|
for cmd in file_commands:
|
|
319
|
-
|
|
320
|
-
cmd
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
349
|
+
try:
|
|
350
|
+
if cmd is None:
|
|
351
|
+
continue
|
|
352
|
+
|
|
353
|
+
# Handle both dict and object-style commands
|
|
354
|
+
if isinstance(cmd, dict):
|
|
355
|
+
# Ensure required fields exist
|
|
356
|
+
cmd.setdefault("file", str(path))
|
|
357
|
+
if "source" not in cmd:
|
|
358
|
+
try:
|
|
359
|
+
cmd["source"] = str(
|
|
360
|
+
path.relative_to(self.project_path)
|
|
361
|
+
)
|
|
362
|
+
except ValueError:
|
|
363
|
+
cmd["source"] = str(path)
|
|
364
|
+
processed_commands.append(cmd)
|
|
365
|
+
|
|
366
|
+
elif hasattr(cmd, "__dict__"): # Object with attributes
|
|
367
|
+
if not hasattr(cmd, "file") or not cmd.file:
|
|
368
|
+
cmd.file = str(path)
|
|
369
|
+
if not hasattr(cmd, "source") or not cmd.source:
|
|
370
|
+
try:
|
|
371
|
+
cmd.source = str(
|
|
372
|
+
path.relative_to(self.project_path)
|
|
373
|
+
)
|
|
374
|
+
except ValueError:
|
|
375
|
+
cmd.source = str(path)
|
|
376
|
+
processed_commands.append(cmd)
|
|
377
|
+
|
|
378
|
+
except Exception as e:
|
|
379
|
+
logger.warning(
|
|
380
|
+
f"Error processing command from {path}: {e}",
|
|
381
|
+
exc_info=logger.isEnabledFor(logging.DEBUG),
|
|
382
|
+
)
|
|
330
383
|
|
|
331
|
-
commands.extend(
|
|
332
|
-
logger.debug(f"Found {len(
|
|
384
|
+
commands.extend(processed_commands)
|
|
385
|
+
logger.debug(f"Found {len(processed_commands)} commands in {path}")
|
|
333
386
|
|
|
334
387
|
except Exception as e:
|
|
335
|
-
logger.error(
|
|
388
|
+
logger.error(
|
|
389
|
+
f"Error parsing {path}: {e}",
|
|
390
|
+
exc_info=logger.isEnabledFor(logging.DEBUG),
|
|
391
|
+
)
|
|
392
|
+
|
|
336
393
|
except Exception as e:
|
|
337
|
-
logger.error(
|
|
394
|
+
logger.error(
|
|
395
|
+
f"Unexpected error processing {path}: {e}",
|
|
396
|
+
exc_info=logger.isEnabledFor(logging.DEBUG),
|
|
397
|
+
)
|
|
338
398
|
|
|
339
399
|
return commands
|
|
340
400
|
|
|
@@ -421,24 +481,65 @@ class ProjectCommandDetector:
|
|
|
421
481
|
|
|
422
482
|
return result_commands
|
|
423
483
|
|
|
424
|
-
def _get_parser_for_file(self, file_path: Path) -> Optional[BaseParser]:
|
|
484
|
+
def _get_parser_for_file(self, file_path: Union[str, Path]) -> Optional[BaseParser]:
|
|
425
485
|
"""Get a parser for a specific file (legacy method).
|
|
426
486
|
|
|
427
487
|
Args:
|
|
428
|
-
file_path: Path to the file
|
|
488
|
+
file_path: Path to the file (can be str or Path)
|
|
429
489
|
|
|
430
490
|
Returns:
|
|
431
491
|
Parser instance or None if no parser found
|
|
432
492
|
"""
|
|
493
|
+
# Ensure we have a Path object
|
|
494
|
+
path = Path(file_path) if isinstance(file_path, str) else file_path
|
|
495
|
+
|
|
496
|
+
if not path.exists():
|
|
497
|
+
logger.debug(f"File does not exist: {path}")
|
|
498
|
+
return None
|
|
499
|
+
|
|
500
|
+
logger.debug(f"Looking for parser for file: {path}")
|
|
501
|
+
|
|
433
502
|
for parser in self.parsers:
|
|
434
503
|
try:
|
|
435
|
-
if hasattr(parser, "can_parse")
|
|
504
|
+
if not hasattr(parser, "can_parse"):
|
|
505
|
+
logger.debug(
|
|
506
|
+
f"Parser {parser.__class__.__name__} has no can_parse method"
|
|
507
|
+
)
|
|
508
|
+
continue
|
|
509
|
+
|
|
510
|
+
# Check if this parser can handle the file
|
|
511
|
+
can_parse = False
|
|
512
|
+
try:
|
|
513
|
+
can_parse = parser.can_parse(path)
|
|
514
|
+
except Exception as e:
|
|
515
|
+
logger.warning(
|
|
516
|
+
f"Error in can_parse for {parser.__class__.__name__} on {path}: {e}",
|
|
517
|
+
exc_info=logger.isEnabledFor(logging.DEBUG),
|
|
518
|
+
)
|
|
519
|
+
continue
|
|
520
|
+
|
|
521
|
+
if can_parse:
|
|
522
|
+
logger.debug(
|
|
523
|
+
f"Found matching parser: {parser.__class__.__name__} for {path}"
|
|
524
|
+
)
|
|
525
|
+
# Set the file_path on the parser if it has that attribute
|
|
526
|
+
if hasattr(parser, "file_path"):
|
|
527
|
+
parser.file_path = path
|
|
528
|
+
if hasattr(parser, "project_root") and not hasattr(
|
|
529
|
+
parser, "_project_root_set"
|
|
530
|
+
):
|
|
531
|
+
parser.project_root = self.project_path
|
|
532
|
+
parser._project_root_set = True
|
|
436
533
|
return parser
|
|
534
|
+
|
|
437
535
|
except Exception as e:
|
|
438
536
|
logger.warning(
|
|
439
|
-
f"Error checking parser {parser.__class__.__name__}: {e}"
|
|
537
|
+
f"Error checking parser {parser.__class__.__name__} on {path}: {e}",
|
|
538
|
+
exc_info=logger.isEnabledFor(logging.DEBUG),
|
|
440
539
|
)
|
|
540
|
+
continue
|
|
441
541
|
|
|
542
|
+
logger.debug(f"No parser found for file: {path}")
|
|
442
543
|
return None
|
|
443
544
|
|
|
444
545
|
def test_commands(self, commands: List) -> None:
|
|
@@ -489,11 +590,50 @@ class ProjectCommandDetector:
|
|
|
489
590
|
Returns:
|
|
490
591
|
Dictionary with command execution results
|
|
491
592
|
"""
|
|
593
|
+
logger.debug(f"Running command in virtualenv: {command}")
|
|
594
|
+
|
|
492
595
|
# Get environment variables for virtualenv
|
|
493
596
|
venv_env = self._get_environment()
|
|
597
|
+
logger.debug(f"Initial venv_env: {venv_env}")
|
|
598
|
+
|
|
599
|
+
# Ensure we have the virtual environment path in the environment
|
|
600
|
+
if self.venv_info.get("exists"):
|
|
601
|
+
if "VIRTUAL_ENV" not in venv_env:
|
|
602
|
+
venv_env["VIRTUAL_ENV"] = self.venv_info["path"]
|
|
603
|
+
logger.debug(f"Set VIRTUAL_ENV to: {venv_env['VIRTUAL_ENV']}")
|
|
604
|
+
|
|
605
|
+
# Ensure we have the Python path in the environment
|
|
606
|
+
if self.venv_info.get("python_path"):
|
|
607
|
+
if "python_path" not in venv_env:
|
|
608
|
+
venv_env["python_path"] = self.venv_info["python_path"]
|
|
609
|
+
logger.debug(f"Set python_path to: {venv_env['python_path']}")
|
|
610
|
+
|
|
611
|
+
# Ensure the virtualenv's bin/Scripts directory is in PATH
|
|
612
|
+
if self.venv_info.get("path"):
|
|
613
|
+
venv_path = Path(self.venv_info["path"])
|
|
614
|
+
bin_dir = "Scripts" if os.name == "nt" else "bin"
|
|
615
|
+
venv_bin = str(venv_path / bin_dir)
|
|
616
|
+
|
|
617
|
+
# Add to the beginning of PATH
|
|
618
|
+
path = venv_env.get("PATH", "")
|
|
619
|
+
if venv_bin not in path.split(os.pathsep):
|
|
620
|
+
venv_env["PATH"] = f"{venv_bin}{os.pathsep}{path}"
|
|
621
|
+
logger.debug(f"Updated PATH to: {venv_env['PATH']}")
|
|
622
|
+
else:
|
|
623
|
+
logger.warning("No virtual environment path found in venv_info")
|
|
494
624
|
|
|
495
|
-
|
|
496
|
-
|
|
625
|
+
logger.debug(f"Final environment for command execution: {venv_env}")
|
|
626
|
+
|
|
627
|
+
try:
|
|
628
|
+
# Pass the environment to command_handler.run_in_venv
|
|
629
|
+
result = self.command_handler.run_in_venv(
|
|
630
|
+
command, venv_env=venv_env, **kwargs
|
|
631
|
+
)
|
|
632
|
+
logger.debug(f"Command execution result: {result}")
|
|
633
|
+
return result
|
|
634
|
+
except Exception as e:
|
|
635
|
+
logger.error(f"Error executing command in virtualenv: {e}", exc_info=True)
|
|
636
|
+
raise
|
|
497
637
|
|
|
498
638
|
def _execute_command(self, command_info: Dict[str, Any]) -> Dict[str, Any]:
|
|
499
639
|
"""Execute a command with the given information.
|
|
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
|