Coverage for src / ezqt_widgets / cli / commands / _demo.py: 0.00%

151 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-01 22:46 +0000

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

2# CLI Demo Commands 

3# Project: ezqt_widgets 

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

5 

6""" 

7CLI commands for running and listing demo examples. 

8""" 

9 

10from __future__ import annotations 

11 

12# /////////////////////////////////////////////////////////////// 

13# IMPORTS 

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

15# Standard library imports 

16import os 

17import runpy 

18import sys 

19from pathlib import Path 

20 

21# Third-party imports 

22import click 

23 

24# /////////////////////////////////////////////////////////////// 

25# CLASSES 

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

27 

28 

29class ExampleRunner: 

30 """Handles running EzQt Widgets examples. 

31 

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

33 from the EzQt Widgets package. 

34 

35 Args: 

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

37 """ 

38 

39 # /////////////////////////////////////////////////////////////// 

40 # INIT 

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

42 

43 def __init__(self, verbose: bool = False) -> None: 

44 """Initialize the example runner.""" 

45 self.verbose: bool = verbose 

46 self.examples_dir: Path = self._find_examples_dir() 

47 

48 # ------------------------------------------------ 

49 # PRIVATE METHODS 

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

51 

52 def _find_examples_dir(self) -> Path: 

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

54 

55 Returns: 

56 Path to the examples directory. 

57 

58 Raises: 

59 FileNotFoundError: If the examples directory cannot be found. 

60 """ 

61 # First priority: examples in the project root 

62 package_dir = Path(__file__).parent.parent.parent 

63 examples_dir = package_dir / "examples" 

64 

65 if examples_dir.exists(): 

66 return examples_dir 

67 

68 # Second priority: examples inside the package (ezqt_widgets/examples/) 

69 package_examples = Path(__file__).parent.parent / "examples" 

70 if package_examples.exists(): 

71 return package_examples 

72 

73 # Fallback: try to find examples in the current directory 

74 current_examples = Path.cwd() / "examples" 

75 if current_examples.exists(): 

76 return current_examples 

77 

78 raise FileNotFoundError("Examples directory not found") 

79 

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

81 """Execute a specific example file. 

82 

83 Args: 

84 example_path: Path to the example file to execute. 

85 

86 Returns: 

87 True if execution was successful, False otherwise. 

88 """ 

89 if self.verbose: 

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

91 

92 try: 

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

94 original_cwd = Path.cwd() 

95 original_argv = sys.argv[:] 

96 original_sys_path = sys.path.copy() 

97 os.chdir(example_path.parent) 

98 sys.argv = [str(example_path)] 

99 example_dir = str(example_path.parent) 

100 if example_dir not in sys.path: 

101 sys.path.insert(0, example_dir) 

102 

103 runpy.run_path(str(example_path), run_name="__main__") 

104 return True 

105 

106 except SystemExit as exc: 

107 code = exc.code if isinstance(exc.code, int) else 1 

108 if code != 0: 

109 click.echo(f"❌ Error running {example_path.name}: exit {code}") 

110 return False 

111 return True 

112 except Exception as e: 

113 click.echo(f"❌ Exception running {example_path.name}: {e}") 

114 return False 

115 finally: 

116 os.chdir(original_cwd) 

117 sys.argv = original_argv 

118 sys.path = original_sys_path 

119 

120 # /////////////////////////////////////////////////////////////// 

121 # PUBLIC METHODS 

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

123 

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

125 """Get list of available example files. 

126 

127 Returns: 

128 List of paths to available example files. 

129 """ 

130 examples: list[Path] = [] 

131 for pattern in ["_*.py", "run_all_examples.py"]: 

132 examples.extend(self.examples_dir.glob(pattern)) 

133 return sorted(examples) 

134 

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

136 """Run a specific example by name. 

137 

138 Args: 

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

140 

141 Returns: 

142 True if execution was successful, False otherwise. 

143 """ 

144 normalized_name = example_name.removesuffix(".py") 

145 candidates = ( 

146 [normalized_name, normalized_name.lstrip("_")] 

147 if normalized_name.startswith("_") 

148 else [normalized_name, f"_{normalized_name}"] 

149 ) 

150 

151 for candidate in candidates: 

152 example_path = self.examples_dir / f"{candidate}.py" 

153 if example_path.exists(): 

154 return self._execute_example(example_path) 

155 

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

157 return False 

158 

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

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

161 

162 Args: 

163 use_gui_launcher: Whether to use the GUI launcher if available 

164 (default: True). 

165 

166 Returns: 

167 True if all examples ran successfully, False otherwise. 

168 """ 

169 if use_gui_launcher: 

170 launcher_path = self.examples_dir / "run_all_examples.py" 

171 if launcher_path.exists(): 

172 return self._execute_example(launcher_path) 

173 click.echo("⚠️ GUI launcher not found, running examples sequentially") 

174 use_gui_launcher = False 

175 

176 # Run each example sequentially 

177 examples = [ 

178 "_button", 

179 "_input", 

180 "_label", 

181 "_misc", 

182 ] 

183 success_count = 0 

184 

185 for example in examples: 

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

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

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

189 

190 if self.run_example(example): 

191 success_count += 1 

192 else: 

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

194 

195 click.echo(f"\n✅ Successfully ran {success_count}/{len(examples)} examples") 

196 return success_count == len(examples) 

197 

198 def list_examples(self) -> None: 

199 """List all available examples.""" 

200 examples = self.get_available_examples() 

201 

202 if not examples: 

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

204 return 

205 

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

207 click.echo("=" * 40) 

208 

209 for example in examples: 

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

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

212 

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

214 

215 

216# /////////////////////////////////////////////////////////////// 

217# PUBLIC FUNCTIONS 

218# /////////////////////////////////////////////////////////////// 

219 

220 

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

222 """Run examples by category. 

223 

224 Args: 

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

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

227 

228 Returns: 

229 True if execution was successful, False otherwise. 

230 """ 

231 runner = ExampleRunner(verbose) 

232 

233 category_mapping = { 

234 "buttons": "_button", 

235 "inputs": "_input", 

236 "labels": "_label", 

237 "misc": "_misc", 

238 } 

239 

240 if category not in category_mapping: 

241 click.echo(f"❌ Unknown category: {category}") 

242 click.echo(f"Available categories: {', '.join(category_mapping.keys())}") 

243 return False 

244 

245 return runner.run_example(category_mapping[category]) 

246 

247 

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

249 """Run all examples. 

250 

251 Args: 

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

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

254 

255 Returns: 

256 True if all examples ran successfully, False otherwise. 

257 """ 

258 runner = ExampleRunner(verbose) 

259 return runner.run_all_examples(use_gui) 

260 

261 

262def list_available_examples() -> None: 

263 """List all available examples.""" 

264 runner = ExampleRunner() 

265 runner.list_examples() 

266 

267 

268# /////////////////////////////////////////////////////////////// 

269# COMMANDS 

270# /////////////////////////////////////////////////////////////// 

271 

272 

273@click.group(name="demo", help="Run and list demo examples") 

274def demo_group() -> None: 

275 """Demo command group.""" 

276 

277 

278@demo_group.command(name="run", help="Run widget examples") 

279@click.option( 

280 "--all", "-a", "run_all", is_flag=True, help="Run all examples with GUI launcher" 

281) 

282@click.option( 

283 "--buttons", 

284 "-b", 

285 is_flag=True, 

286 help="Run button examples (DateButton, IconButton, LoaderButton)", 

287) 

288@click.option( 

289 "--inputs", 

290 "-i", 

291 is_flag=True, 

292 help="Run input examples (AutoComplete, Password, Search, TabReplace)", 

293) 

294@click.option( 

295 "--labels", 

296 "-l", 

297 is_flag=True, 

298 help="Run label examples (ClickableTag, Framed, Hover, Indicator)", 

299) 

300@click.option( 

301 "--misc", 

302 "-m", 

303 is_flag=True, 

304 help="Run misc examples (CircularTimer, DraggableList, OptionSelector, ToggleIcon, ToggleSwitch)", 

305) 

306@click.option( 

307 "--verbose", "-v", is_flag=True, help="Verbose output with detailed information" 

308) 

309def run_command( 

310 run_all: bool, 

311 buttons: bool, 

312 inputs: bool, 

313 labels: bool, 

314 misc: bool, 

315 verbose: bool, 

316) -> None: 

317 """Run EzQt Widgets examples.""" 

318 options_selected = any([run_all, buttons, inputs, labels, misc]) 

319 

320 if not options_selected: 

321 click.echo("❌ Please specify which examples to run.") 

322 click.echo("\n📋 Available options:") 

323 click.echo(" --all, -a Run all examples with GUI launcher") 

324 click.echo(" --buttons, -b Run button examples") 

325 click.echo(" --inputs, -i Run input examples") 

326 click.echo(" --labels, -l Run label examples") 

327 click.echo(" --misc, -m Run misc examples") 

328 click.echo("\n💡 Example: ezqt run --buttons") 

329 return 

330 

331 if verbose: 

332 click.echo("🔍 Verbose mode enabled") 

333 

334 success = True 

335 

336 if run_all: 

337 click.echo("🎯 Running all examples...") 

338 success = run_all_examples(use_gui=True, verbose=verbose) 

339 

340 elif buttons: 

341 click.echo("🎛️ Running button examples...") 

342 success = run_example_by_category("buttons", verbose) 

343 

344 elif inputs: 

345 click.echo("⌨️ Running input examples...") 

346 success = run_example_by_category("inputs", verbose) 

347 

348 elif labels: 

349 click.echo("🏷️ Running label examples...") 

350 success = run_example_by_category("labels", verbose) 

351 

352 elif misc: 

353 click.echo("🔧 Running misc examples...") 

354 success = run_example_by_category("misc", verbose) 

355 

356 if success: 

357 click.echo("✅ Examples completed successfully!") 

358 else: 

359 click.echo("❌ Some examples failed to run.") 

360 raise SystemExit(1) 

361 

362 

363@demo_group.command(name="list", help="List available examples") 

364def list_command() -> None: 

365 """List available examples.""" 

366 list_available_examples()