1+ import importlib .metadata
12import shutil
23import subprocess
34import sys
6566 "posix" : ["matlab" ],
6667 "windows" : ["matlab" ],
6768 },
68- "version_flag" : "-batch \ " disp('ok')\" " ,
69+ "version_flag" : [ "-batch" , "disp('ok')" ] ,
6970 "config_keys" : ["MATLABEXE" , "MATLABWIN" ],
7071 "install_hints" : {
7172 "Linux" : "Install from https://mathworks.com/downloads/" ,
103104 "matplotlib" : "pip install concore[demo]" ,
104105}
105106
106- # Map import names that differ from package names
107- IMPORT_NAME_MAP = {
108- "beautifulsoup4" : "bs4" ,
109- "pyzmq" : "zmq" ,
110- }
111-
112-
113107def _get_platform_key ():
114108 """Return 'posix' or 'windows' based on OS."""
115109 return "windows" if os .name == "nt" else "posix"
@@ -146,8 +140,12 @@ def _detect_tool(names):
146140def _get_version (path , version_flag ):
147141 """Run tool with version flag and return first line of output."""
148142 try :
143+ if isinstance (version_flag , list ):
144+ cmd = [path ] + version_flag
145+ else :
146+ cmd = [path , version_flag ]
149147 result = subprocess .run (
150- [ path , version_flag ] ,
148+ cmd ,
151149 capture_output = True ,
152150 text = True ,
153151 timeout = 10 ,
@@ -176,14 +174,10 @@ def _check_docker_daemon(docker_path):
176174
177175def _check_package (package_name ):
178176 """Check if a Python package is importable and get its version."""
179- import_name = IMPORT_NAME_MAP .get (package_name , package_name )
180177 try :
181- mod = __import__ (import_name )
182- version = getattr (mod , "__version__" , None )
183- if version is None :
184- version = getattr (mod , "VERSION" , "installed" )
185- return True , str (version )
186- except ImportError :
178+ version = importlib .metadata .version (package_name )
179+ return True , version
180+ except importlib .metadata .PackageNotFoundError :
187181 return False , None
188182
189183
@@ -207,8 +201,7 @@ def doctor_check(console):
207201
208202 # Python version
209203 py_version = platform .python_version ()
210- py_major , py_minor = sys .version_info .major , sys .version_info .minor
211- if py_major >= 3 and py_minor >= 9 :
204+ if sys .version_info >= (3 , 9 ):
212205 console .print (f" [green]✓[/green] Python { py_version } (>= 3.9 required)" )
213206 passed += 1
214207 else :
@@ -261,6 +254,7 @@ def doctor_check(console):
261254 if path :
262255 version = _get_version (path , tool_def ["version_flag" ])
263256 version_str = f" ({ version } )" if version else ""
257+ exe_info = f" [{ found_name } ]" if found_name else ""
264258 extra = ""
265259 if tool_label == "Docker" :
266260 daemon_ok = _check_docker_daemon (path )
@@ -272,24 +266,20 @@ def doctor_check(console):
272266 if not daemon_ok :
273267 warnings += 1
274268 console .print (
275- f" [yellow]![/yellow] { tool_label } { version_str } "
276- f"→ { path } { extra } "
269+ f" [yellow]![/yellow] { tool_label } { exe_info } "
270+ f"{ version_str } → { path } { extra } "
277271 )
278272 continue
279273 console .print (
280- f" [green]✓[/green] { tool_label } { version_str } → { path } { extra } "
274+ f" [green]✓[/green] { tool_label } { exe_info } "
275+ f"{ version_str } → { path } { extra } "
281276 )
282277 passed += 1
283278 else :
284279 hint = tool_def ["install_hints" ].get (plat_name , "" )
285280 hint_str = f" (install: { hint } )" if hint else ""
286- # MATLAB is optional if Octave is available, show as warning
287- if tool_label == "MATLAB" :
288- console .print (
289- f" [yellow]![/yellow] { tool_label } → Not found{ hint_str } "
290- )
291- warnings += 1
292- elif tool_label == "Verilog (iverilog)" :
281+ # Docker, MATLAB, Verilog are optional — show as warning
282+ if tool_label in ("MATLAB" , "Verilog (iverilog)" , "Docker" ):
293283 console .print (
294284 f" [yellow]![/yellow] { tool_label } → Not found{ hint_str } "
295285 )
@@ -309,6 +299,7 @@ def doctor_check(console):
309299 "concore.tools" : "Tool path overrides" ,
310300 "concore.octave" : "Treat .m files as Octave" ,
311301 "concore.mcr" : "MATLAB Compiler Runtime path" ,
302+ "concore.repo" : "Docker repository path" ,
312303 "concore.sudo" : "Docker executable override" ,
313304 }
314305
@@ -331,17 +322,22 @@ def doctor_check(console):
331322 console .print (
332323 f" [green]✓[/green] { filename } → { content } "
333324 )
325+ passed += 1
334326 else :
335327 console .print (
336328 f" [yellow]![/yellow] { filename } → "
337329 f"path does not exist: { content } "
338330 )
339331 warnings += 1
340- continue
332+ continue
341333 elif filename == "concore.sudo" :
342334 console .print (
343335 f" [green]✓[/green] { filename } → { content } "
344336 )
337+ elif filename == "concore.repo" :
338+ console .print (
339+ f" [green]✓[/green] { filename } → { content } "
340+ )
345341 else :
346342 console .print (
347343 f" [green]✓[/green] { filename } → Enabled"
@@ -357,11 +353,12 @@ def doctor_check(console):
357353 f" [dim]—[/dim] { filename } → Not set ({ description } )"
358354 )
359355
360- # Check environment variables
361- env_vars = [
362- "CONCORE_CPPEXE" , "CONCORE_PYTHONEXE" , "CONCORE_VEXE" ,
363- "CONCORE_OCTAVEEXE" , "CONCORE_MATLABEXE" , "DOCKEREXE" ,
364- ]
356+ # Build environment variable list from TOOL_DEFINITIONS config_keys
357+ env_vars = []
358+ for tool_def in TOOL_DEFINITIONS .values ():
359+ for key in tool_def .get ("config_keys" , []):
360+ env_vars .append (f"CONCORE_{ key } " )
361+ env_vars .append ("DOCKEREXE" )
365362 env_set = [v for v in env_vars if os .environ .get (v )]
366363 if env_set :
367364 console .print (
@@ -412,7 +409,12 @@ def doctor_check(console):
412409 if errors :
413410 summary_parts .append (f"[red]{ errors } error(s)[/red]" )
414411
415- console .print (f"[bold]Summary:[/bold] { ', ' .join (summary_parts )} " )
412+ if summary_parts :
413+ summary_text = ", " .join (summary_parts )
414+ else :
415+ summary_text = "[yellow]No checks were run.[/yellow]"
416+
417+ console .print (f"[bold]Summary:[/bold] { summary_text } " )
416418
417419 if errors == 0 :
418420 console .print ()
0 commit comments