Coverage for src/prosemark/templates/application/use_cases/list_templates_use_case.py: 99%

127 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-10-01 00:05 +0000

1"""Use case for listing available templates.""" 

2 

3from typing import Any 

4 

5from prosemark.templates.domain.exceptions.template_exceptions import ( 

6 TemplateDirectoryNotFoundError, 

7 TemplateError, 

8 TemplateNotFoundError, 

9) 

10from prosemark.templates.domain.services.template_service import TemplateService 

11 

12 

13class ListTemplatesUseCase: 

14 """Use case for listing and discovering available templates.""" 

15 

16 def __init__(self, template_service: TemplateService) -> None: 

17 """Initialize use case with template service. 

18 

19 Args: 

20 template_service: Service providing template operations 

21 

22 """ 

23 self._template_service = template_service 

24 

25 def list_all_templates(self) -> dict[str, Any]: 

26 """List all available templates (both single and directory). 

27 

28 Returns: 

29 Dictionary containing all available templates organized by type 

30 

31 """ 

32 try: 

33 # Get single templates 

34 single_templates = self._template_service.list_templates() 

35 

36 # Get directory templates 

37 directory_templates = self._template_service.list_template_directories() 

38 

39 # Get detailed info for each single template 

40 single_template_details = [] 

41 for template_name in single_templates: 

42 try: 

43 info = self._template_service.get_template_info(template_name) 

44 single_template_details.append(info) 

45 except TemplateError: 

46 # Skip templates that can't be loaded 

47 continue 

48 

49 # Get detailed info for each directory template 

50 directory_template_details = [] 

51 for directory_name in directory_templates: 

52 try: 

53 info = self._template_service.get_directory_template_info(directory_name) 

54 directory_template_details.append(info) 

55 except TemplateError: 

56 # Skip directories that can't be loaded 

57 continue 

58 

59 return { 

60 'success': True, 

61 'single_templates': { 

62 'count': len(single_template_details), 

63 'names': single_templates, 

64 'details': single_template_details, 

65 }, 

66 'directory_templates': { 

67 'count': len(directory_template_details), 

68 'names': directory_templates, 

69 'details': directory_template_details, 

70 }, 

71 'total_templates': len(single_templates) + len(directory_templates), 

72 'summary': ListTemplatesUseCase._create_templates_summary( 

73 single_template_details, directory_template_details 

74 ), 

75 } 

76 

77 except TemplateError as e: 

78 return { 

79 'success': False, 

80 'error': str(e), 

81 'error_type': type(e).__name__, 

82 } 

83 

84 def list_single_templates(self) -> dict[str, Any]: 

85 """List only single templates. 

86 

87 Returns: 

88 Dictionary containing single template information 

89 

90 """ 

91 try: 

92 template_names = self._template_service.list_templates() 

93 

94 # Get detailed info for each template 

95 template_details = [] 

96 failed_templates = [] 

97 

98 for template_name in template_names: 

99 try: 

100 info = self._template_service.get_template_info(template_name) 

101 template_details.append(info) 

102 except TemplateError as e: 

103 failed_templates.append({ 

104 'name': template_name, 

105 'error': str(e), 

106 'error_type': type(e).__name__, 

107 }) 

108 

109 return { 

110 'success': True, 

111 'template_type': 'single', 

112 'count': len(template_details), 

113 'names': [detail['name'] for detail in template_details], 

114 'details': template_details, 

115 'failed': failed_templates, 

116 'summary': ListTemplatesUseCase._create_single_templates_summary(template_details), 

117 } 

118 

119 except TemplateError as e: 

120 return { 

121 'success': False, 

122 'template_type': 'single', 

123 'error': str(e), 

124 'error_type': type(e).__name__, 

125 } 

126 

127 def list_directory_templates(self) -> dict[str, Any]: 

128 """List only directory templates. 

129 

130 Returns: 

131 Dictionary containing directory template information 

132 

133 """ 

134 try: 

135 directory_names = self._template_service.list_template_directories() 

136 

137 # Get detailed info for each directory 

138 directory_details = [] 

139 failed_directories = [] 

140 

141 for directory_name in directory_names: 

142 try: 

143 info = self._template_service.get_directory_template_info(directory_name) 

144 directory_details.append(info) 

145 except TemplateError as e: 

146 failed_directories.append({ 

147 'name': directory_name, 

148 'error': str(e), 

149 'error_type': type(e).__name__, 

150 }) 

151 

152 return { 

153 'success': True, 

154 'template_type': 'directory', 

155 'count': len(directory_details), 

156 'names': [detail['name'] for detail in directory_details], 

157 'details': directory_details, 

158 'failed': failed_directories, 

159 'summary': ListTemplatesUseCase._create_directory_templates_summary(directory_details), 

160 } 

161 

162 except TemplateError as e: 

163 return { 

164 'success': False, 

165 'template_type': 'directory', 

166 'error': str(e), 

167 'error_type': type(e).__name__, 

168 } 

169 

170 def search_templates(self, query: str, *, search_in_descriptions: bool = True) -> dict[str, Any]: 

171 """Search for templates by name or description. 

172 

173 Args: 

174 query: Search query string 

175 search_in_descriptions: Whether to search in template descriptions 

176 

177 Returns: 

178 Dictionary containing search results 

179 

180 """ 

181 try: 

182 query_lower = query.lower() 

183 

184 # Get all templates 

185 all_templates_result = self.list_all_templates() 

186 if not all_templates_result['success']: 

187 return all_templates_result 

188 

189 single_templates = all_templates_result['single_templates']['details'] 

190 directory_templates = all_templates_result['directory_templates']['details'] 

191 

192 # Search single templates 

193 matching_single = [ 

194 template 

195 for template in single_templates 

196 if self._template_matches_query(template, query_lower, search_in_descriptions=search_in_descriptions) 

197 ] 

198 

199 # Search directory templates 

200 matching_directory = [ 

201 template 

202 for template in directory_templates 

203 if self._template_matches_query(template, query_lower, search_in_descriptions=search_in_descriptions) 

204 ] 

205 

206 return { 

207 'success': True, 

208 'query': query, 

209 'search_in_descriptions': search_in_descriptions, 

210 'single_templates': { 

211 'count': len(matching_single), 

212 'results': matching_single, 

213 }, 

214 'directory_templates': { 

215 'count': len(matching_directory), 

216 'results': matching_directory, 

217 }, 

218 'total_matches': len(matching_single) + len(matching_directory), 

219 } 

220 

221 except TemplateError as e: 

222 return { 

223 'success': False, 

224 'query': query, 

225 'error': str(e), 

226 'error_type': type(e).__name__, 

227 } 

228 

229 def get_template_details(self, template_name: str) -> dict[str, Any]: 

230 """Get detailed information about a specific template. 

231 

232 Args: 

233 template_name: Name of template to get details for 

234 

235 Returns: 

236 Dictionary containing detailed template information 

237 

238 """ 

239 # Try as single template first 

240 try: 

241 template_info = self._template_service.get_template_info(template_name) 

242 except TemplateNotFoundError: 

243 pass 

244 else: 

245 return { 

246 'success': True, 

247 'template_name': template_name, 

248 'template_type': 'single', 

249 'found': True, 

250 'details': template_info, 

251 } 

252 

253 # Try as directory template 

254 try: 

255 directory_info = self._template_service.get_directory_template_info(template_name) 

256 except TemplateDirectoryNotFoundError: 

257 pass 

258 else: 

259 return { 

260 'success': True, 

261 'template_name': template_name, 

262 'template_type': 'directory', 

263 'found': True, 

264 'details': directory_info, 

265 } 

266 

267 # Template not found in either location 

268 return { 

269 'success': True, 

270 'template_name': template_name, 

271 'template_type': 'unknown', 

272 'found': False, 

273 'error': f"Template '{template_name}' not found as single template or directory template", 

274 } 

275 

276 def get_templates_with_placeholders(self) -> dict[str, Any]: 

277 """List templates that require placeholder values. 

278 

279 Returns: 

280 Dictionary containing templates with placeholders 

281 

282 """ 

283 try: 

284 all_templates_result = self.list_all_templates() 

285 if not all_templates_result['success']: 285 ↛ 286line 285 didn't jump to line 286 because the condition on line 285 was never true

286 return all_templates_result 

287 

288 single_templates = all_templates_result['single_templates']['details'] 

289 directory_templates = all_templates_result['directory_templates']['details'] 

290 

291 # Filter templates with placeholders 

292 single_with_placeholders = [t for t in single_templates if t.get('placeholder_count', 0) > 0] 

293 

294 directory_with_placeholders = [ 

295 t 

296 for t in directory_templates 

297 if len(t.get('required_placeholders', [])) > 0 or len(t.get('optional_placeholders', [])) > 0 

298 ] 

299 

300 return { 

301 'success': True, 

302 'single_templates': { 

303 'count': len(single_with_placeholders), 

304 'templates': single_with_placeholders, 

305 }, 

306 'directory_templates': { 

307 'count': len(directory_with_placeholders), 

308 'templates': directory_with_placeholders, 

309 }, 

310 'total_with_placeholders': len(single_with_placeholders) + len(directory_with_placeholders), 

311 } 

312 

313 except TemplateError as e: 

314 return { 

315 'success': False, 

316 'error': str(e), 

317 'error_type': type(e).__name__, 

318 } 

319 

320 @staticmethod 

321 def _create_templates_summary( 

322 single_templates: list[dict[str, Any]], 

323 directory_templates: list[dict[str, Any]], 

324 ) -> dict[str, Any]: 

325 """Create summary statistics for all templates. 

326 

327 Args: 

328 single_templates: List of single template details 

329 directory_templates: List of directory template details 

330 

331 Returns: 

332 Summary statistics dictionary 

333 

334 """ 

335 single_summary = ListTemplatesUseCase._create_single_templates_summary(single_templates) 

336 directory_summary = ListTemplatesUseCase._create_directory_templates_summary(directory_templates) 

337 

338 return { 

339 'total_templates': len(single_templates) + len(directory_templates), 

340 'single_templates': single_summary, 

341 'directory_templates': directory_summary, 

342 'templates_with_placeholders': ( 

343 single_summary['with_placeholders'] + directory_summary['with_placeholders'] 

344 ), 

345 'templates_without_placeholders': ( 

346 single_summary['without_placeholders'] + directory_summary['without_placeholders'] 

347 ), 

348 } 

349 

350 @staticmethod 

351 def _create_single_templates_summary(templates: list[dict[str, Any]]) -> dict[str, Any]: 

352 """Create summary statistics for single templates. 

353 

354 Args: 

355 templates: List of single template details 

356 

357 Returns: 

358 Summary statistics dictionary 

359 

360 """ 

361 if not templates: 

362 return { 

363 'count': 0, 

364 'with_placeholders': 0, 

365 'without_placeholders': 0, 

366 'total_placeholders': 0, 

367 'avg_placeholders_per_template': 0.0, 

368 } 

369 

370 with_placeholders = sum(1 for t in templates if t.get('placeholder_count', 0) > 0) 

371 without_placeholders = len(templates) - with_placeholders 

372 total_placeholders = sum(t.get('placeholder_count', 0) for t in templates) 

373 

374 return { 

375 'count': len(templates), 

376 'with_placeholders': with_placeholders, 

377 'without_placeholders': without_placeholders, 

378 'total_placeholders': total_placeholders, 

379 'avg_placeholders_per_template': total_placeholders / len(templates), 

380 } 

381 

382 @staticmethod 

383 def _create_directory_templates_summary(directories: list[dict[str, Any]]) -> dict[str, Any]: 

384 """Create summary statistics for directory templates. 

385 

386 Args: 

387 directories: List of directory template details 

388 

389 Returns: 

390 Summary statistics dictionary 

391 

392 """ 

393 if not directories: 

394 return { 

395 'count': 0, 

396 'with_placeholders': 0, 

397 'without_placeholders': 0, 

398 'total_template_files': 0, 

399 'avg_files_per_directory': 0.0, 

400 } 

401 

402 with_placeholders = sum( 

403 1 

404 for d in directories 

405 if len(d.get('required_placeholders', [])) > 0 or len(d.get('optional_placeholders', [])) > 0 

406 ) 

407 without_placeholders = len(directories) - with_placeholders 

408 total_files = sum(d.get('template_count', 0) for d in directories) 

409 

410 return { 

411 'count': len(directories), 

412 'with_placeholders': with_placeholders, 

413 'without_placeholders': without_placeholders, 

414 'total_template_files': total_files, 

415 'avg_files_per_directory': total_files / len(directories), 

416 } 

417 

418 @staticmethod 

419 def _template_matches_query(template: dict[str, Any], query_lower: str, *, search_in_descriptions: bool) -> bool: 

420 """Check if a template matches the search query. 

421 

422 Args: 

423 template: Template details dictionary 

424 query_lower: Lowercase search query 

425 search_in_descriptions: Whether to search in descriptions 

426 

427 Returns: 

428 True if template matches query 

429 

430 """ 

431 # Search in name 

432 if query_lower in template.get('name', '').lower(): 

433 return True 

434 

435 # Search in placeholder names 

436 placeholder_names = template.get('required_placeholders', []) + template.get('optional_placeholders', []) 

437 for placeholder_name in placeholder_names: 

438 if query_lower in placeholder_name.lower(): 

439 return True 

440 

441 # Search in descriptions if enabled 

442 if search_in_descriptions: 

443 # Search in frontmatter values if available 

444 frontmatter = template.get('frontmatter', {}) 

445 for value in frontmatter.values(): 

446 if isinstance(value, str) and query_lower in value.lower(): 

447 return True 

448 

449 return False