library.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. from __future__ import annotations
  2. from pathlib import Path
  3. import logging
  4. from typing import Optional
  5. logger = logging.getLogger(__name__)
  6. # -----------------------
  7. # SECTION: Library Class
  8. # -----------------------
  9. class Library:
  10. """Represents a single library with a specific path."""
  11. def __init__(self, name: str, path: Path, priority: int = 0) -> None:
  12. """Initialize a library instance.
  13. Args:
  14. name: Display name for the library
  15. path: Path to the library directory
  16. priority: Priority for library lookup (higher = checked first)
  17. """
  18. self.name = name
  19. self.path = path
  20. self.priority = priority # Higher priority = checked first
  21. def find_by_id(self, module_name: str, files: list[str], template_id: str) -> tuple[Path, str]:
  22. """Find a template by its ID in this library.
  23. Args:
  24. module_name: The module name (e.g., 'compose', 'terraform')
  25. files: List of files to look for in the template directory
  26. template_id: The template ID to find
  27. Returns:
  28. Path to the template directory if found
  29. Raises:
  30. FileNotFoundError: If the template ID is not found in this library
  31. """
  32. logger.debug(f"Looking for template '{template_id}' in module '{module_name}' in library '{self.name}'")
  33. # Build the path to the specific template directory
  34. template_path = self.path / module_name / template_id
  35. # Check if the template directory exists
  36. if not template_path.exists():
  37. raise FileNotFoundError(f"Template '{template_id}' not found in module '{module_name}' in library '{self.name}'")
  38. if not template_path.is_dir():
  39. raise FileNotFoundError(f"Template '{template_id}' exists but is not a directory in module '{module_name}' in library '{self.name}'")
  40. # If files list is provided, verify at least one of the files exists
  41. if files:
  42. has_any_file = False
  43. for file in files:
  44. file_path = template_path / file
  45. if file_path.exists():
  46. has_any_file = True
  47. break
  48. if not has_any_file:
  49. raise FileNotFoundError(f"Template '{template_id}' found but missing any of the required files: {files}")
  50. logger.debug(f"Found template '{template_id}' at: {template_path}")
  51. return template_path, self.name
  52. def find(self, module_name: str, files: list[str], sort_results: bool = False) -> list[tuple[Path, str]]:
  53. """Find templates in this library for a specific module.
  54. Args:
  55. module_name: The module name (e.g., 'compose', 'terraform')
  56. files: List of files to look for in template directories (optional filter)
  57. sort_results: Whether to return results sorted alphabetically
  58. Returns:
  59. List of Path objects representing template directories
  60. Raises:
  61. FileNotFoundError: If the module directory is not found in this library
  62. """
  63. logger.debug(f"Looking for templates in module '{module_name}' in library '{self.name}'")
  64. # Build the path to the module directory
  65. module_path = self.path / module_name
  66. # Check if the module directory exists
  67. if not module_path.exists():
  68. raise FileNotFoundError(f"Module '{module_name}' not found in library '{self.name}'")
  69. if not module_path.is_dir():
  70. raise FileNotFoundError(f"Module '{module_name}' exists but is not a directory in library '{self.name}'")
  71. # Get all directories in the module path
  72. template_dirs = []
  73. try:
  74. for item in module_path.iterdir():
  75. if item.is_dir():
  76. # If files list is provided, check if template has any of the required files
  77. if files:
  78. has_any_file = False
  79. for file in files:
  80. file_path = item / file
  81. if file_path.exists():
  82. has_any_file = True
  83. break
  84. if has_any_file:
  85. template_dirs.append((item, self.name))
  86. else:
  87. # No file requirements, include all directories
  88. template_dirs.append((item, self.name))
  89. except PermissionError as e:
  90. raise FileNotFoundError(f"Permission denied accessing module '{module_name}' in library '{self.name}': {e}")
  91. # Sort if requested
  92. if sort_results:
  93. template_dirs.sort(key=lambda x: x[0].name.lower())
  94. logger.debug(f"Found {len(template_dirs)} templates in module '{module_name}'")
  95. return template_dirs
  96. # !SECTION
  97. # -----------------------------
  98. # SECTION: LibraryManager Class
  99. # -----------------------------
  100. class LibraryManager:
  101. """Manages multiple libraries and provides methods to find templates."""
  102. # FIXME: For now this is static and only has one library
  103. def __init__(self) -> None:
  104. # get the root path of the repository
  105. repo_root = Path(__file__).parent.parent.parent.resolve()
  106. self.libraries = [
  107. Library(name="default", path=repo_root / "library", priority=0)
  108. ]
  109. def find_by_id(self, module_name: str, files: list[str], template_id: str) -> Optional[tuple[Path, str]]:
  110. """Find a template by its ID across all libraries.
  111. Args:
  112. module_name: The module name (e.g., 'compose', 'terraform')
  113. files: List of files to look for in the template directory
  114. template_id: The template ID to find
  115. Returns:
  116. Path to the template directory if found, None otherwise
  117. """
  118. logger.debug(f"Searching for template '{template_id}' in module '{module_name}' across all libraries")
  119. for library in sorted(self.libraries, key=lambda x: x.priority, reverse=True):
  120. try:
  121. template_path, lib_name = library.find_by_id(module_name, files, template_id)
  122. logger.debug(f"Found template '{template_id}' in library '{library.name}'")
  123. return template_path, lib_name
  124. except FileNotFoundError:
  125. # Continue searching in next library
  126. continue
  127. logger.debug(f"Template '{template_id}' not found in any library")
  128. return None
  129. def find(self, module_name: str, files: list[str], sort_results: bool = False) -> list[tuple[Path, str]]:
  130. """Find templates across all libraries for a specific module.
  131. Args:
  132. module_name: The module name (e.g., 'compose', 'terraform')
  133. files: List of files to look for in template directories (optional filter)
  134. sort_results: Whether to return results sorted alphabetically
  135. Returns:
  136. List of Path objects representing template directories from all libraries
  137. """
  138. logger.debug(f"Searching for templates in module '{module_name}' across all libraries")
  139. all_templates = []
  140. for library in sorted(self.libraries, key=lambda x: x.priority, reverse=True):
  141. try:
  142. templates = library.find(module_name, files, sort_results=False) # Sort at the end
  143. all_templates.extend(templates)
  144. logger.debug(f"Found {len(templates)} templates in library '{library.name}'")
  145. except FileNotFoundError:
  146. # Module not found in this library, continue with next
  147. logger.debug(f"Module '{module_name}' not found in library '{library.name}'")
  148. continue
  149. # Remove duplicates based on template name (directory name)
  150. seen_names = set()
  151. unique_templates = []
  152. for template in all_templates:
  153. name, library_name = template
  154. if name.name not in seen_names:
  155. unique_templates.append((name, library_name))
  156. seen_names.add(name.name)
  157. # Sort if requested
  158. if sort_results:
  159. unique_templates.sort(key=lambda x: x[0].name.lower())
  160. logger.debug(f"Found {len(unique_templates)} unique templates total")
  161. return unique_templates
  162. # !SECTION