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

1# /////////////////////////////////////////////////////////////// 

2# CLI_RUNNER - Example Runner Module 

3# Project: ezqt_widgets 

4# /////////////////////////////////////////////////////////////// 

5 

6""" 

7Example runner module for EzQt Widgets CLI. 

8 

9Handles the execution of example files with proper error handling and feedback. 

10""" 

11 

12from __future__ import annotations 

13 

14# /////////////////////////////////////////////////////////////// 

15# IMPORTS 

16# /////////////////////////////////////////////////////////////// 

17# Standard library imports 

18import os 

19import subprocess 

20import sys 

21from pathlib import Path 

22 

23# Third-party imports 

24import click 

25 

26# /////////////////////////////////////////////////////////////// 

27# CLASSES 

28# /////////////////////////////////////////////////////////////// 

29 

30 

31class ExampleRunner: 

32 """Handles running EzQt Widgets examples. 

33 

34 Provides functionality to discover, list, and execute example files 

35 from the EzQt Widgets package. 

36 

37 Args: 

38 verbose: Whether to enable verbose output (default: False). 

39 """ 

40 

41 # /////////////////////////////////////////////////////////////// 

42 # INIT 

43 # /////////////////////////////////////////////////////////////// 

44 

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() 

49 

50 # ------------------------------------------------ 

51 # PRIVATE METHODS 

52 # ------------------------------------------------ 

53 

54 def _find_examples_dir(self) -> Path: 

55 """Find the examples directory relative to the package. 

56 

57 Returns: 

58 Path to the examples directory. 

59 

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" 

66 

67 if examples_dir.exists(): 

68 return examples_dir 

69 

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 

74 

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 

79 

80 raise FileNotFoundError("Examples directory not found") 

81 

82 def _execute_example(self, example_path: Path) -> bool: 

83 """Execute a specific example file. 

84 

85 Args: 

86 example_path: Path to the example file to execute. 

87 

88 Returns: 

89 True if execution was successful, False otherwise. 

90 """ 

91 if self.verbose: 

92 click.echo(f"🚀 Running: {example_path.name}") 

93 

94 try: 

95 # Change to the examples directory to ensure relative imports work 

96 original_cwd = Path.cwd() 

97 os.chdir(example_path.parent) 

98 

99 result = subprocess.run( # noqa: S603 

100 [sys.executable, str(example_path)], 

101 capture_output=True, 

102 text=True, 

103 timeout=30, 

104 ) 

105 

106 # Restore original working directory 

107 os.chdir(original_cwd) 

108 

109 if result.returncode != 0: 

110 click.echo(f"❌ Error running {example_path.name}: {result.stderr}") 

111 return False 

112 

113 return True 

114 

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 

121 

122 # /////////////////////////////////////////////////////////////// 

123 # PUBLIC METHODS 

124 # /////////////////////////////////////////////////////////////// 

125 

126 def get_available_examples(self) -> list[Path]: 

127 """Get list of available example files. 

128 

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) 

136 

137 def run_example(self, example_name: str) -> bool: 

138 """Run a specific example by name. 

139 

140 Args: 

141 example_name: Name of the example to run (without .py extension). 

142 

143 Returns: 

144 True if execution was successful, False otherwise. 

145 """ 

146 example_path = self.examples_dir / f"{example_name}.py" 

147 

148 if not example_path.exists(): 

149 click.echo(f"❌ Example not found: {example_name}") 

150 return False 

151 

152 return self._execute_example(example_path) 

153 

154 def run_all_examples(self, use_gui_launcher: bool = True) -> bool: 

155 """Run all examples or use the GUI launcher. 

156 

157 Args: 

158 use_gui_launcher: Whether to use the GUI launcher if available 

159 (default: True). 

160 

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 

171 

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 

181 

182 for example in examples: 

183 click.echo(f"\n{'=' * 50}") 

184 click.echo(f"🚀 Running: {example}") 

185 click.echo(f"{'=' * 50}") 

186 

187 if self.run_example(example): 

188 success_count += 1 

189 else: 

190 click.echo(f"❌ Failed to run: {example}") 

191 

192 click.echo( 

193 f"\n✅ Successfully ran {success_count}/{len(examples)} examples" 

194 ) 

195 return success_count == len(examples) 

196 

197 def list_examples(self) -> None: 

198 """List all available examples.""" 

199 examples = self.get_available_examples() 

200 

201 if not examples: 

202 click.echo("❌ No examples found") 

203 return 

204 

205 click.echo("📋 Available examples:") 

206 click.echo("=" * 40) 

207 

208 for example in examples: 

209 status = "✅" if example.exists() else "❌" 

210 click.echo(f"{status} {example.stem}") 

211 

212 click.echo(f"\nTotal: {len(examples)} examples found") 

213 

214 

215# /////////////////////////////////////////////////////////////// 

216# PUBLIC FUNCTIONS 

217# /////////////////////////////////////////////////////////////// 

218 

219 

220def run_example_by_category(category: str, verbose: bool = False) -> bool: 

221 """Run examples by category. 

222 

223 Args: 

224 category: Category name (buttons, inputs, labels, misc). 

225 verbose: Whether to enable verbose output (default: False). 

226 

227 Returns: 

228 True if execution was successful, False otherwise. 

229 """ 

230 runner = ExampleRunner(verbose) 

231 

232 category_mapping = { 

233 "buttons": "button_example", 

234 "inputs": "input_example", 

235 "labels": "label_example", 

236 "misc": "misc_example", 

237 } 

238 

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 

243 

244 return runner.run_example(category_mapping[category]) 

245 

246 

247def run_all_examples(use_gui: bool = True, verbose: bool = False) -> bool: 

248 """Run all examples. 

249 

250 Args: 

251 use_gui: Whether to use the GUI launcher if available (default: True). 

252 verbose: Whether to enable verbose output (default: False). 

253 

254 Returns: 

255 True if all examples ran successfully, False otherwise. 

256 """ 

257 runner = ExampleRunner(verbose) 

258 return runner.run_all_examples(use_gui) 

259 

260 

261def list_available_examples() -> None: 

262 """List all available examples.""" 

263 runner = ExampleRunner() 

264 runner.list_examples()