|
16 | 16 | from .utils.project_root import find_project_root |
17 | 17 | from .utils.colors import ColorFormatter |
18 | 18 |
|
19 | | -try: |
20 | | - import networkx as nx |
21 | | - from phart import ASCIIRenderer |
22 | | - PHART_AVAILABLE = True |
23 | | -except ImportError: |
24 | | - PHART_AVAILABLE = False |
25 | | - |
26 | 19 |
|
27 | 20 | class CLI: |
28 | 21 | """Command-line interface for Mudyla.""" |
@@ -269,18 +262,7 @@ def run(self, argv: Optional[list[str]] = None) -> int: |
269 | 262 | # Show execution plan |
270 | 263 | execution_order = pruned_graph.get_execution_order() |
271 | 264 | print(f"\n{'📋' if not args.no_color else '▸'} {color.bold('Execution plan:')}") |
272 | | - |
273 | | - if PHART_AVAILABLE and not args.no_color: |
274 | | - # Use phart to visualize the DAG |
275 | | - self._visualize_execution_plan_phart(pruned_graph, execution_order, goals, color) |
276 | | - else: |
277 | | - # Fallback to simple text output |
278 | | - for i, action_name in enumerate(execution_order, 1): |
279 | | - node = pruned_graph.get_node(action_name) |
280 | | - deps = ", ".join(sorted(node.dependencies)) if node.dependencies else "none" |
281 | | - is_goal = action_name in goals |
282 | | - goal_marker = " 🎯" if is_goal and not args.no_color else " [GOAL]" if is_goal else "" |
283 | | - print(f" {color.dim(f'{i}.')} {color.highlight(action_name)}{goal_marker} {color.dim(f'(depends on: {deps})')}") |
| 265 | + self._visualize_execution_plan(pruned_graph, execution_order, goals, color, args.no_color) |
284 | 266 |
|
285 | 267 | if args.dry_run: |
286 | 268 | print(f"\n{'ℹ️' if not args.no_color else 'i'} {color.info('Dry run - not executing')}") |
@@ -367,42 +349,40 @@ def run(self, argv: Optional[list[str]] = None) -> int: |
367 | 349 | traceback.print_exc() |
368 | 350 | return 1 |
369 | 351 |
|
370 | | - def _visualize_execution_plan_phart(self, graph, execution_order: list[str], goals: list[str], color) -> None: |
371 | | - """Visualize execution plan using phart. |
| 352 | + def _visualize_execution_plan(self, graph, execution_order: list[str], goals: list[str], color, no_color: bool) -> None: |
| 353 | + """Visualize execution plan as a tree. |
372 | 354 |
|
373 | 355 | Args: |
374 | 356 | graph: The execution graph |
375 | 357 | execution_order: List of actions in execution order |
376 | 358 | goals: List of goal actions |
377 | 359 | color: Color formatter |
| 360 | + no_color: Whether to disable colors |
378 | 361 | """ |
379 | | - import networkx as nx |
380 | | - from phart import ASCIIRenderer, NodeStyle |
| 362 | + # Create a tree-like visualization showing dependencies |
| 363 | + for i, action_name in enumerate(execution_order, 1): |
| 364 | + node = graph.get_node(action_name) |
| 365 | + is_goal = action_name in goals |
| 366 | + goal_marker = " 🎯" if is_goal and not no_color else " [GOAL]" if is_goal else "" |
381 | 367 |
|
382 | | - # Create a NetworkX DiGraph |
383 | | - # Use labels as node IDs so they show up in the visualization |
384 | | - G = nx.DiGraph() |
| 368 | + # Format the action with its number |
| 369 | + action_label = f"{i}. {action_name}{goal_marker}" |
385 | 370 |
|
386 | | - # Create a mapping from action names to labels |
387 | | - label_map = {} |
388 | | - for action_name in execution_order: |
389 | | - order_num = execution_order.index(action_name) + 1 |
390 | | - is_goal = action_name in goals |
391 | | - goal_marker = " 🎯" if is_goal else "" |
392 | | - label = f"{order_num}. {action_name}{goal_marker}" |
393 | | - label_map[action_name] = label |
394 | | - G.add_node(label) |
395 | | - |
396 | | - # Add edges using labels |
397 | | - for action_name in execution_order: |
398 | | - exec_node = graph.get_node(action_name) |
399 | | - for dep in exec_node.dependencies: |
400 | | - # In a dependency graph, edges go from dependency to dependent |
401 | | - G.add_edge(label_map[dep], label_map[action_name]) |
402 | | - |
403 | | - # Render the graph with minimal style for compact output |
404 | | - renderer = ASCIIRenderer(G, node_style=NodeStyle.MINIMAL, node_spacing=1, layer_spacing=0) |
405 | | - print(renderer.render()) |
| 371 | + if not node.dependencies: |
| 372 | + # No dependencies - just print the action |
| 373 | + print(f" {color.highlight(action_label)}") |
| 374 | + else: |
| 375 | + # Has dependencies - show them |
| 376 | + dep_names = [] |
| 377 | + for dep in sorted(node.dependencies): |
| 378 | + dep_num = execution_order.index(dep) + 1 |
| 379 | + dep_names.append(f"{dep_num}") |
| 380 | + |
| 381 | + deps_str = ",".join(dep_names) |
| 382 | + arrow = "←" if not no_color else "<-" |
| 383 | + print(f" {color.highlight(action_label)} {color.dim(f'{arrow} [{deps_str}]')}") |
| 384 | + |
| 385 | + print() # Empty line after plan |
406 | 386 |
|
407 | 387 | def _list_actions(self, document: ParsedDocument) -> None: |
408 | 388 | """List all available actions.""" |
|
0 commit comments