Coverage for src / ezqt_widgets / cli / _runner.py: 0.00%
95 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-31 10:03 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-31 10:03 +0000
1# ///////////////////////////////////////////////////////////////
2# CLI_RUNNER - Example Runner Module
3# Project: ezqt_widgets
4# ///////////////////////////////////////////////////////////////
6"""
7Example runner module for EzQt Widgets CLI.
9Handles the execution of example files with proper error handling and feedback.
10"""
12from __future__ import annotations
14# ///////////////////////////////////////////////////////////////
15# IMPORTS
16# ///////////////////////////////////////////////////////////////
17# Standard library imports
18import os
19import subprocess
20import sys
21from pathlib import Path
23# Third-party imports
24import click
26# ///////////////////////////////////////////////////////////////
27# CLASSES
28# ///////////////////////////////////////////////////////////////
31class ExampleRunner:
32 """Handles running EzQt Widgets examples.
34 Provides functionality to discover, list, and execute example files
35 from the EzQt Widgets package.
37 Args:
38 verbose: Whether to enable verbose output (default: False).
39 """
41 # ///////////////////////////////////////////////////////////////
42 # INIT
43 # ///////////////////////////////////////////////////////////////
45 def __init__(self, verbose: bool = False) -> None:
46 """Initialize the example runner."""
47 self.verbose: bool = verbose
48 self.examples_dir: Path = self._find_examples_dir()
50 # ------------------------------------------------
51 # PRIVATE METHODS
52 # ------------------------------------------------
54 def _find_examples_dir(self) -> Path:
55 """Find the examples directory relative to the package.
57 Returns:
58 Path to the examples directory.
60 Raises:
61 FileNotFoundError: If the examples directory cannot be found.
62 """
63 # First priority: examples in the project root
64 package_dir = Path(__file__).parent.parent.parent
65 examples_dir = package_dir / "examples"
67 if examples_dir.exists():
68 return examples_dir
70 # Second priority: examples inside the package (ezqt_widgets/examples/)
71 package_examples = Path(__file__).parent.parent / "examples"
72 if package_examples.exists():
73 return package_examples
75 # Fallback: try to find examples in the current directory
76 current_examples = Path.cwd() / "examples"
77 if current_examples.exists():
78 return current_examples
80 raise FileNotFoundError("Examples directory not found")
82 def _execute_example(self, example_path: Path) -> bool:
83 """Execute a specific example file.
85 Args:
86 example_path: Path to the example file to execute.
88 Returns:
89 True if execution was successful, False otherwise.
90 """
91 if self.verbose:
92 click.echo(f"🚀 Running: {example_path.name}")
94 try:
95 # Change to the examples directory to ensure relative imports work
96 original_cwd = Path.cwd()
97 os.chdir(example_path.parent)
99 result = subprocess.run( # noqa: S603
100 [sys.executable, str(example_path)],
101 capture_output=True,
102 text=True,
103 timeout=30,
104 )
106 # Restore original working directory
107 os.chdir(original_cwd)
109 if result.returncode != 0:
110 click.echo(f"❌ Error running {example_path.name}: {result.stderr}")
111 return False
113 return True
115 except subprocess.TimeoutExpired:
116 click.echo(f"⏰ Timeout running {example_path.name}")
117 return False
118 except Exception as e:
119 click.echo(f"❌ Exception running {example_path.name}: {e}")
120 return False
122 # ///////////////////////////////////////////////////////////////
123 # PUBLIC METHODS
124 # ///////////////////////////////////////////////////////////////
126 def get_available_examples(self) -> list[Path]:
127 """Get list of available example files.
129 Returns:
130 List of paths to available example files.
131 """
132 examples: list[Path] = []
133 for pattern in ["*_example.py", "run_all_examples.py"]:
134 examples.extend(self.examples_dir.glob(pattern))
135 return sorted(examples)
137 def run_example(self, example_name: str) -> bool:
138 """Run a specific example by name.
140 Args:
141 example_name: Name of the example to run (without .py extension).
143 Returns:
144 True if execution was successful, False otherwise.
145 """
146 example_path = self.examples_dir / f"{example_name}.py"
148 if not example_path.exists():
149 click.echo(f"❌ Example not found: {example_name}")
150 return False
152 return self._execute_example(example_path)
154 def run_all_examples(self, use_gui_launcher: bool = True) -> bool:
155 """Run all examples or use the GUI launcher.
157 Args:
158 use_gui_launcher: Whether to use the GUI launcher if available
159 (default: True).
161 Returns:
162 True if all examples ran successfully, False otherwise.
163 """
164 if use_gui_launcher:
165 launcher_path = self.examples_dir / "run_all_examples.py"
166 if launcher_path.exists():
167 return self._execute_example(launcher_path)
168 else:
169 click.echo("⚠️ GUI launcher not found, running examples sequentially")
170 use_gui_launcher = False
172 if not use_gui_launcher:
173 # Run each example sequentially
174 examples = [
175 "button_example",
176 "input_example",
177 "label_example",
178 "misc_example",
179 ]
180 success_count = 0
182 for example in examples:
183 click.echo(f"\n{'=' * 50}")
184 click.echo(f"🚀 Running: {example}")
185 click.echo(f"{'=' * 50}")
187 if self.run_example(example):
188 success_count += 1
189 else:
190 click.echo(f"❌ Failed to run: {example}")
192 click.echo(
193 f"\n✅ Successfully ran {success_count}/{len(examples)} examples"
194 )
195 return success_count == len(examples)
197 def list_examples(self) -> None:
198 """List all available examples."""
199 examples = self.get_available_examples()
201 if not examples:
202 click.echo("❌ No examples found")
203 return
205 click.echo("📋 Available examples:")
206 click.echo("=" * 40)
208 for example in examples:
209 status = "✅" if example.exists() else "❌"
210 click.echo(f"{status} {example.stem}")
212 click.echo(f"\nTotal: {len(examples)} examples found")
215# ///////////////////////////////////////////////////////////////
216# PUBLIC FUNCTIONS
217# ///////////////////////////////////////////////////////////////
220def run_example_by_category(category: str, verbose: bool = False) -> bool:
221 """Run examples by category.
223 Args:
224 category: Category name (buttons, inputs, labels, misc).
225 verbose: Whether to enable verbose output (default: False).
227 Returns:
228 True if execution was successful, False otherwise.
229 """
230 runner = ExampleRunner(verbose)
232 category_mapping = {
233 "buttons": "button_example",
234 "inputs": "input_example",
235 "labels": "label_example",
236 "misc": "misc_example",
237 }
239 if category not in category_mapping:
240 click.echo(f"❌ Unknown category: {category}")
241 click.echo(f"Available categories: {', '.join(category_mapping.keys())}")
242 return False
244 return runner.run_example(category_mapping[category])
247def run_all_examples(use_gui: bool = True, verbose: bool = False) -> bool:
248 """Run all examples.
250 Args:
251 use_gui: Whether to use the GUI launcher if available (default: True).
252 verbose: Whether to enable verbose output (default: False).
254 Returns:
255 True if all examples ran successfully, False otherwise.
256 """
257 runner = ExampleRunner(verbose)
258 return runner.run_all_examples(use_gui)
261def list_available_examples() -> None:
262 """List all available examples."""
263 runner = ExampleRunner()
264 runner.list_examples()