tree-sitter-analyzer 0.2.0__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tree-sitter-analyzer might be problematic. Click here for more details.

Files changed (78) hide show
  1. tree_sitter_analyzer/__init__.py +134 -121
  2. tree_sitter_analyzer/__main__.py +11 -12
  3. tree_sitter_analyzer/api.py +533 -539
  4. tree_sitter_analyzer/cli/__init__.py +39 -39
  5. tree_sitter_analyzer/cli/__main__.py +12 -13
  6. tree_sitter_analyzer/cli/commands/__init__.py +26 -27
  7. tree_sitter_analyzer/cli/commands/advanced_command.py +88 -88
  8. tree_sitter_analyzer/cli/commands/base_command.py +160 -155
  9. tree_sitter_analyzer/cli/commands/default_command.py +18 -19
  10. tree_sitter_analyzer/cli/commands/partial_read_command.py +141 -133
  11. tree_sitter_analyzer/cli/commands/query_command.py +81 -82
  12. tree_sitter_analyzer/cli/commands/structure_command.py +138 -121
  13. tree_sitter_analyzer/cli/commands/summary_command.py +101 -93
  14. tree_sitter_analyzer/cli/commands/table_command.py +235 -233
  15. tree_sitter_analyzer/cli/info_commands.py +120 -121
  16. tree_sitter_analyzer/cli_main.py +278 -276
  17. tree_sitter_analyzer/core/__init__.py +15 -20
  18. tree_sitter_analyzer/core/analysis_engine.py +555 -574
  19. tree_sitter_analyzer/core/cache_service.py +320 -330
  20. tree_sitter_analyzer/core/engine.py +559 -560
  21. tree_sitter_analyzer/core/parser.py +293 -288
  22. tree_sitter_analyzer/core/query.py +502 -502
  23. tree_sitter_analyzer/encoding_utils.py +456 -460
  24. tree_sitter_analyzer/exceptions.py +337 -340
  25. tree_sitter_analyzer/file_handler.py +210 -222
  26. tree_sitter_analyzer/formatters/__init__.py +1 -1
  27. tree_sitter_analyzer/formatters/base_formatter.py +167 -168
  28. tree_sitter_analyzer/formatters/formatter_factory.py +78 -74
  29. tree_sitter_analyzer/formatters/java_formatter.py +291 -270
  30. tree_sitter_analyzer/formatters/python_formatter.py +259 -235
  31. tree_sitter_analyzer/interfaces/__init__.py +9 -10
  32. tree_sitter_analyzer/interfaces/cli.py +528 -557
  33. tree_sitter_analyzer/interfaces/cli_adapter.py +343 -319
  34. tree_sitter_analyzer/interfaces/mcp_adapter.py +206 -170
  35. tree_sitter_analyzer/interfaces/mcp_server.py +405 -416
  36. tree_sitter_analyzer/java_analyzer.py +187 -219
  37. tree_sitter_analyzer/language_detector.py +398 -400
  38. tree_sitter_analyzer/language_loader.py +224 -228
  39. tree_sitter_analyzer/languages/__init__.py +10 -11
  40. tree_sitter_analyzer/languages/java_plugin.py +1174 -1113
  41. tree_sitter_analyzer/{plugins → languages}/javascript_plugin.py +446 -439
  42. tree_sitter_analyzer/languages/python_plugin.py +747 -712
  43. tree_sitter_analyzer/mcp/__init__.py +31 -32
  44. tree_sitter_analyzer/mcp/resources/__init__.py +44 -47
  45. tree_sitter_analyzer/mcp/resources/code_file_resource.py +209 -213
  46. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +555 -550
  47. tree_sitter_analyzer/mcp/server.py +333 -345
  48. tree_sitter_analyzer/mcp/tools/__init__.py +30 -31
  49. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +654 -557
  50. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +247 -245
  51. tree_sitter_analyzer/mcp/tools/base_tool.py +54 -55
  52. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +300 -302
  53. tree_sitter_analyzer/mcp/tools/table_format_tool.py +362 -359
  54. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +543 -476
  55. tree_sitter_analyzer/mcp/utils/__init__.py +107 -106
  56. tree_sitter_analyzer/mcp/utils/error_handler.py +549 -549
  57. tree_sitter_analyzer/models.py +470 -481
  58. tree_sitter_analyzer/output_manager.py +255 -264
  59. tree_sitter_analyzer/plugins/__init__.py +280 -334
  60. tree_sitter_analyzer/plugins/base.py +496 -446
  61. tree_sitter_analyzer/plugins/manager.py +379 -355
  62. tree_sitter_analyzer/queries/__init__.py +26 -27
  63. tree_sitter_analyzer/queries/java.py +391 -394
  64. tree_sitter_analyzer/queries/javascript.py +148 -149
  65. tree_sitter_analyzer/queries/python.py +285 -286
  66. tree_sitter_analyzer/queries/typescript.py +229 -230
  67. tree_sitter_analyzer/query_loader.py +257 -260
  68. tree_sitter_analyzer/table_formatter.py +471 -448
  69. tree_sitter_analyzer/utils.py +277 -277
  70. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.4.0.dist-info}/METADATA +23 -8
  71. tree_sitter_analyzer-0.4.0.dist-info/RECORD +73 -0
  72. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.4.0.dist-info}/entry_points.txt +2 -1
  73. tree_sitter_analyzer/plugins/java_plugin.py +0 -625
  74. tree_sitter_analyzer/plugins/plugin_loader.py +0 -83
  75. tree_sitter_analyzer/plugins/python_plugin.py +0 -598
  76. tree_sitter_analyzer/plugins/registry.py +0 -366
  77. tree_sitter_analyzer-0.2.0.dist-info/RECORD +0 -77
  78. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.4.0.dist-info}/WHEEL +0 -0
@@ -1,355 +1,379 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- Plugin Manager
5
-
6
- Dynamic plugin discovery and management system.
7
- Handles loading plugins from entry points and local directories.
8
- """
9
-
10
- import importlib
11
- import importlib.metadata
12
- import pkgutil
13
- from pathlib import Path
14
- from typing import List, Dict, Optional, Type
15
- import logging
16
-
17
- from .base import LanguagePlugin
18
- from ..utils import log_error, log_info, log_warning, log_debug
19
-
20
- logger = logging.getLogger(__name__)
21
-
22
-
23
- class PluginManager:
24
- """
25
- Manages dynamic discovery and loading of language plugins.
26
-
27
- This class handles:
28
- - Discovery of plugins via entry points
29
- - Loading plugins from local directories
30
- - Plugin lifecycle management
31
- - Error handling and fallback mechanisms
32
- """
33
-
34
- def __init__(self):
35
- """Initialize the plugin manager."""
36
- self._loaded_plugins: Dict[str, LanguagePlugin] = {}
37
- self._plugin_classes: Dict[str, Type[LanguagePlugin]] = {}
38
- self._entry_point_group = "tree_sitter_analyzer.plugins"
39
-
40
- def load_plugins(self) -> List[LanguagePlugin]:
41
- """
42
- Load all available plugins from various sources.
43
-
44
- Returns:
45
- List of successfully loaded plugin instances
46
- """
47
- loaded_plugins = []
48
-
49
- # Load plugins from entry points (installed packages)
50
- entry_point_plugins = self._load_from_entry_points()
51
- loaded_plugins.extend(entry_point_plugins)
52
-
53
- # Load plugins from local languages directory
54
- local_plugins = self._load_from_local_directory()
55
- loaded_plugins.extend(local_plugins)
56
-
57
- # Store loaded plugins
58
- for plugin in loaded_plugins:
59
- language = plugin.get_language_name()
60
- self._loaded_plugins[language] = plugin
61
-
62
- log_info(f"Successfully loaded {len(loaded_plugins)} plugins")
63
- return loaded_plugins
64
-
65
- def _load_from_entry_points(self) -> List[LanguagePlugin]:
66
- """
67
- Load plugins from setuptools entry points.
68
-
69
- Returns:
70
- List of plugin instances loaded from entry points
71
- """
72
- plugins = []
73
-
74
- try:
75
- # Get entry points for our plugin group
76
- entry_points = importlib.metadata.entry_points()
77
-
78
- # Handle both old and new entry_points API
79
- if hasattr(entry_points, 'select'):
80
- # New API (Python 3.10+)
81
- plugin_entries = entry_points.select(group=self._entry_point_group)
82
- else:
83
- # Old API
84
- plugin_entries = entry_points.get(self._entry_point_group, [])
85
-
86
- for entry_point in plugin_entries:
87
- try:
88
- # Load the plugin class
89
- plugin_class = entry_point.load()
90
-
91
- # Validate it's a LanguagePlugin
92
- if not issubclass(plugin_class, LanguagePlugin):
93
- log_warning(f"Entry point {entry_point.name} is not a LanguagePlugin")
94
- continue
95
-
96
- # Create instance
97
- plugin_instance = plugin_class()
98
- plugins.append(plugin_instance)
99
-
100
- log_debug(f"Loaded plugin from entry point: {entry_point.name}")
101
-
102
- except Exception as e:
103
- log_error(f"Failed to load plugin from entry point {entry_point.name}: {e}")
104
-
105
- except Exception as e:
106
- log_warning(f"Failed to load plugins from entry points: {e}")
107
-
108
- return plugins
109
-
110
- def _load_from_local_directory(self) -> List[LanguagePlugin]:
111
- """
112
- Load plugins from the local languages directory.
113
-
114
- Returns:
115
- List of plugin instances loaded from local directory
116
- """
117
- plugins = []
118
-
119
- try:
120
- # Get the languages directory path
121
- current_dir = Path(__file__).parent.parent
122
- languages_dir = current_dir / "languages"
123
-
124
- if not languages_dir.exists():
125
- log_debug("Languages directory does not exist, creating it")
126
- languages_dir.mkdir(exist_ok=True)
127
- # Create __init__.py
128
- (languages_dir / "__init__.py").touch()
129
- return plugins
130
-
131
- # Import the languages package
132
- languages_package = "tree_sitter_analyzer.languages"
133
-
134
- try:
135
- languages_module = importlib.import_module(languages_package)
136
- except ImportError as e:
137
- log_warning(f"Could not import languages package: {e}")
138
- return plugins
139
-
140
- # Discover plugin modules in the languages directory
141
- for finder, name, ispkg in pkgutil.iter_modules(
142
- languages_module.__path__,
143
- languages_module.__name__ + "."
144
- ):
145
- if ispkg:
146
- continue
147
-
148
- try:
149
- # Import the module
150
- module = importlib.import_module(name)
151
-
152
- # Look for LanguagePlugin classes
153
- plugin_classes = self._find_plugin_classes(module)
154
-
155
- for plugin_class in plugin_classes:
156
- try:
157
- plugin_instance = plugin_class()
158
- plugins.append(plugin_instance)
159
- log_debug(f"Loaded local plugin: {plugin_class.__name__}")
160
- except Exception as e:
161
- log_error(f"Failed to instantiate plugin {plugin_class.__name__}: {e}")
162
-
163
- except Exception as e:
164
- log_error(f"Failed to load plugin module {name}: {e}")
165
-
166
- except Exception as e:
167
- log_warning(f"Failed to load plugins from local directory: {e}")
168
-
169
- return plugins
170
-
171
- def _find_plugin_classes(self, module) -> List[Type[LanguagePlugin]]:
172
- """
173
- Find LanguagePlugin classes in a module.
174
-
175
- Args:
176
- module: Python module to search
177
-
178
- Returns:
179
- List of LanguagePlugin classes found in the module
180
- """
181
- plugin_classes = []
182
-
183
- for attr_name in dir(module):
184
- attr = getattr(module, attr_name)
185
-
186
- # Check if it's a class and subclass of LanguagePlugin
187
- if (isinstance(attr, type) and
188
- issubclass(attr, LanguagePlugin) and
189
- attr is not LanguagePlugin):
190
- plugin_classes.append(attr)
191
-
192
- return plugin_classes
193
-
194
- def get_plugin(self, language: str) -> Optional[LanguagePlugin]:
195
- """
196
- Get a plugin for a specific language.
197
-
198
- Args:
199
- language: Programming language name
200
-
201
- Returns:
202
- Plugin instance or None if not found
203
- """
204
- return self._loaded_plugins.get(language)
205
-
206
- def get_all_plugins(self) -> Dict[str, LanguagePlugin]:
207
- """
208
- Get all loaded plugins.
209
-
210
- Returns:
211
- Dictionary mapping language names to plugin instances
212
- """
213
- return self._loaded_plugins.copy()
214
-
215
- def get_supported_languages(self) -> List[str]:
216
- """
217
- Get list of all supported languages.
218
-
219
- Returns:
220
- List of supported language names
221
- """
222
- return list(self._loaded_plugins.keys())
223
-
224
- def reload_plugins(self) -> List[LanguagePlugin]:
225
- """
226
- Reload all plugins (useful for development).
227
-
228
- Returns:
229
- List of reloaded plugin instances
230
- """
231
- log_info("Reloading all plugins")
232
-
233
- # Clear existing plugins
234
- self._loaded_plugins.clear()
235
- self._plugin_classes.clear()
236
-
237
- # Reload
238
- return self.load_plugins()
239
-
240
- def register_plugin(self, plugin: LanguagePlugin) -> bool:
241
- """
242
- Manually register a plugin instance.
243
-
244
- Args:
245
- plugin: Plugin instance to register
246
-
247
- Returns:
248
- True if registration was successful
249
- """
250
- try:
251
- language = plugin.get_language_name()
252
-
253
- if language in self._loaded_plugins:
254
- log_warning(f"Plugin for language '{language}' already exists, replacing")
255
-
256
- self._loaded_plugins[language] = plugin
257
- log_debug(f"Manually registered plugin for language: {language}")
258
- return True
259
-
260
- except Exception as e:
261
- log_error(f"Failed to register plugin: {e}")
262
- return False
263
-
264
- def unregister_plugin(self, language: str) -> bool:
265
- """
266
- Unregister a plugin for a specific language.
267
-
268
- Args:
269
- language: Programming language name
270
-
271
- Returns:
272
- True if unregistration was successful
273
- """
274
- if language in self._loaded_plugins:
275
- del self._loaded_plugins[language]
276
- log_debug(f"Unregistered plugin for language: {language}")
277
- return True
278
-
279
- return False
280
-
281
- def get_plugin_info(self, language: str) -> Optional[Dict[str, any]]:
282
- """
283
- Get information about a specific plugin.
284
-
285
- Args:
286
- language: Programming language name
287
-
288
- Returns:
289
- Plugin information dictionary or None
290
- """
291
- plugin = self.get_plugin(language)
292
- if not plugin:
293
- return None
294
-
295
- try:
296
- return {
297
- "language": plugin.get_language_name(),
298
- "extensions": plugin.get_file_extensions(),
299
- "class_name": plugin.__class__.__name__,
300
- "module": plugin.__class__.__module__,
301
- "has_extractor": hasattr(plugin, 'create_extractor')
302
- }
303
- except Exception as e:
304
- log_error(f"Failed to get plugin info for {language}: {e}")
305
- return None
306
-
307
- def validate_plugin(self, plugin: LanguagePlugin) -> bool:
308
- """
309
- Validate that a plugin implements the required interface correctly.
310
-
311
- Args:
312
- plugin: Plugin instance to validate
313
-
314
- Returns:
315
- True if the plugin is valid
316
- """
317
- try:
318
- # Check required methods
319
- required_methods = [
320
- 'get_language_name',
321
- 'get_file_extensions',
322
- 'create_extractor'
323
- ]
324
-
325
- for method_name in required_methods:
326
- if not hasattr(plugin, method_name):
327
- log_error(f"Plugin missing required method: {method_name}")
328
- return False
329
-
330
- method = getattr(plugin, method_name)
331
- if not callable(method):
332
- log_error(f"Plugin method {method_name} is not callable")
333
- return False
334
-
335
- # Test basic functionality
336
- language = plugin.get_language_name()
337
- if not language or not isinstance(language, str):
338
- log_error("Plugin get_language_name() must return a non-empty string")
339
- return False
340
-
341
- extensions = plugin.get_file_extensions()
342
- if not isinstance(extensions, list):
343
- log_error("Plugin get_file_extensions() must return a list")
344
- return False
345
-
346
- extractor = plugin.create_extractor()
347
- if not extractor:
348
- log_error("Plugin create_extractor() must return an extractor instance")
349
- return False
350
-
351
- return True
352
-
353
- except Exception as e:
354
- log_error(f"Plugin validation failed: {e}")
355
- return False
1
+ #!/usr/bin/env python3
2
+ """
3
+ Plugin Manager
4
+
5
+ Dynamic plugin discovery and management system.
6
+ Handles loading plugins from entry points and local directories.
7
+ """
8
+
9
+ import importlib
10
+ import importlib.metadata
11
+ import logging
12
+ import pkgutil
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ from ..utils import log_debug, log_error, log_info, log_warning
17
+ from .base import LanguagePlugin
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class PluginManager:
23
+ """
24
+ Manages dynamic discovery and loading of language plugins.
25
+
26
+ This class handles:
27
+ - Discovery of plugins via entry points
28
+ - Loading plugins from local directories
29
+ - Plugin lifecycle management
30
+ - Error handling and fallback mechanisms
31
+ """
32
+
33
+ def __init__(self) -> None:
34
+ """Initialize the plugin manager."""
35
+ self._loaded_plugins: dict[str, LanguagePlugin] = {}
36
+ self._plugin_classes: dict[str, type[LanguagePlugin]] = {}
37
+ self._entry_point_group = "tree_sitter_analyzer.plugins"
38
+
39
+ def load_plugins(self) -> list[LanguagePlugin]:
40
+ """
41
+ Load all available plugins from various sources.
42
+
43
+ Returns:
44
+ List of successfully loaded plugin instances
45
+ """
46
+ loaded_plugins = []
47
+
48
+ # Load plugins from entry points (installed packages)
49
+ entry_point_plugins = self._load_from_entry_points()
50
+ loaded_plugins.extend(entry_point_plugins)
51
+
52
+ # Load plugins from local languages directory
53
+ local_plugins = self._load_from_local_directory()
54
+ loaded_plugins.extend(local_plugins)
55
+
56
+ # Store loaded plugins and deduplicate by language
57
+ unique_plugins = {}
58
+ for plugin in loaded_plugins:
59
+ language = plugin.get_language_name()
60
+ if language not in unique_plugins:
61
+ unique_plugins[language] = plugin
62
+ self._loaded_plugins[language] = plugin
63
+ else:
64
+ log_debug(f"Skipping duplicate plugin for language: {language}")
65
+
66
+ final_plugins = list(unique_plugins.values())
67
+ log_info(f"Successfully loaded {len(final_plugins)} plugins")
68
+ return final_plugins
69
+
70
+ def _load_from_entry_points(self) -> list[LanguagePlugin]:
71
+ """
72
+ Load plugins from setuptools entry points.
73
+
74
+ Returns:
75
+ List of plugin instances loaded from entry points
76
+ """
77
+ plugins = []
78
+
79
+ try:
80
+ # Get entry points for our plugin group
81
+ entry_points = importlib.metadata.entry_points()
82
+
83
+ # Handle both old and new entry_points API
84
+ plugin_entries: Any = []
85
+ if hasattr(entry_points, "select"):
86
+ # New API (Python 3.10+)
87
+ plugin_entries = entry_points.select(group=self._entry_point_group)
88
+ else:
89
+ # Old API - handle different return types
90
+ try:
91
+ # Try to get entry points, handling different API versions
92
+ if hasattr(entry_points, "get"):
93
+ result = entry_points.get(self._entry_point_group)
94
+ plugin_entries = list(result) if result else []
95
+ else:
96
+ plugin_entries = []
97
+ except (TypeError, AttributeError):
98
+ # Fallback for incompatible entry_points types
99
+ plugin_entries = []
100
+
101
+ for entry_point in plugin_entries:
102
+ try:
103
+ # Load the plugin class
104
+ plugin_class = entry_point.load()
105
+
106
+ # Validate it's a LanguagePlugin
107
+ if not issubclass(plugin_class, LanguagePlugin):
108
+ log_warning(
109
+ f"Entry point {entry_point.name} is not a LanguagePlugin"
110
+ )
111
+ continue
112
+
113
+ # Create instance
114
+ plugin_instance = plugin_class()
115
+ plugins.append(plugin_instance)
116
+
117
+ log_debug(f"Loaded plugin from entry point: {entry_point.name}")
118
+
119
+ except Exception as e:
120
+ log_error(
121
+ f"Failed to load plugin from entry point {entry_point.name}: {e}"
122
+ )
123
+
124
+ except Exception as e:
125
+ log_warning(f"Failed to load plugins from entry points: {e}")
126
+
127
+ return plugins
128
+
129
+ def _load_from_local_directory(self) -> list[LanguagePlugin]:
130
+ """
131
+ Load plugins from the local languages directory.
132
+
133
+ Returns:
134
+ List of plugin instances loaded from local directory
135
+ """
136
+ plugins: list[LanguagePlugin] = []
137
+
138
+ try:
139
+ # Get the languages directory path
140
+ current_dir = Path(__file__).parent.parent
141
+ languages_dir = current_dir / "languages"
142
+
143
+ if not languages_dir.exists():
144
+ log_debug("Languages directory does not exist, creating it")
145
+ languages_dir.mkdir(exist_ok=True)
146
+ # Create __init__.py
147
+ (languages_dir / "__init__.py").touch()
148
+ return plugins
149
+
150
+ # Import the languages package
151
+ languages_package = "tree_sitter_analyzer.languages"
152
+
153
+ try:
154
+ languages_module = importlib.import_module(languages_package)
155
+ except ImportError as e:
156
+ log_warning(f"Could not import languages package: {e}")
157
+ return plugins
158
+
159
+ # Discover plugin modules in the languages directory
160
+ for _finder, name, ispkg in pkgutil.iter_modules(
161
+ languages_module.__path__, languages_module.__name__ + "."
162
+ ):
163
+ if ispkg:
164
+ continue
165
+
166
+ try:
167
+ # Import the module
168
+ module = importlib.import_module(name)
169
+
170
+ # Look for LanguagePlugin classes
171
+ plugin_classes = self._find_plugin_classes(module)
172
+
173
+ for plugin_class in plugin_classes:
174
+ try:
175
+ plugin_instance = plugin_class()
176
+ plugins.append(plugin_instance)
177
+ log_debug(f"Loaded local plugin: {plugin_class.__name__}")
178
+ except Exception as e:
179
+ log_error(
180
+ f"Failed to instantiate plugin {plugin_class.__name__}: {e}"
181
+ )
182
+
183
+ except Exception as e:
184
+ log_error(f"Failed to load plugin module {name}: {e}")
185
+
186
+ except Exception as e:
187
+ log_warning(f"Failed to load plugins from local directory: {e}")
188
+
189
+ return plugins
190
+
191
+ def _find_plugin_classes(self, module: Any) -> list[type[LanguagePlugin]]:
192
+ """
193
+ Find LanguagePlugin classes in a module.
194
+
195
+ Args:
196
+ module: Python module to search
197
+
198
+ Returns:
199
+ List of LanguagePlugin classes found in the module
200
+ """
201
+ plugin_classes: list[type[LanguagePlugin]] = []
202
+
203
+ for attr_name in dir(module):
204
+ attr = getattr(module, attr_name)
205
+
206
+ # Check if it's a class and subclass of LanguagePlugin
207
+ if (
208
+ isinstance(attr, type)
209
+ and issubclass(attr, LanguagePlugin)
210
+ and attr is not LanguagePlugin
211
+ ):
212
+ plugin_classes.append(attr)
213
+
214
+ return plugin_classes
215
+
216
+ def get_plugin(self, language: str) -> LanguagePlugin | None:
217
+ """
218
+ Get a plugin for a specific language.
219
+
220
+ Args:
221
+ language: Programming language name
222
+
223
+ Returns:
224
+ Plugin instance or None if not found
225
+ """
226
+ return self._loaded_plugins.get(language)
227
+
228
+ def get_all_plugins(self) -> dict[str, LanguagePlugin]:
229
+ """
230
+ Get all loaded plugins.
231
+
232
+ Returns:
233
+ Dictionary mapping language names to plugin instances
234
+ """
235
+ return self._loaded_plugins.copy()
236
+
237
+ def get_supported_languages(self) -> list[str]:
238
+ """
239
+ Get list of all supported languages.
240
+
241
+ Returns:
242
+ List of supported language names
243
+ """
244
+ return list(self._loaded_plugins.keys())
245
+
246
+ def reload_plugins(self) -> list[LanguagePlugin]:
247
+ """
248
+ Reload all plugins (useful for development).
249
+
250
+ Returns:
251
+ List of reloaded plugin instances
252
+ """
253
+ log_info("Reloading all plugins")
254
+
255
+ # Clear existing plugins
256
+ self._loaded_plugins.clear()
257
+ self._plugin_classes.clear()
258
+
259
+ # Reload
260
+ return self.load_plugins()
261
+
262
+ def register_plugin(self, plugin: LanguagePlugin) -> bool:
263
+ """
264
+ Manually register a plugin instance.
265
+
266
+ Args:
267
+ plugin: Plugin instance to register
268
+
269
+ Returns:
270
+ True if registration was successful
271
+ """
272
+ try:
273
+ language = plugin.get_language_name()
274
+
275
+ if language in self._loaded_plugins:
276
+ log_warning(
277
+ f"Plugin for language '{language}' already exists, replacing"
278
+ )
279
+
280
+ self._loaded_plugins[language] = plugin
281
+ log_debug(f"Manually registered plugin for language: {language}")
282
+ return True
283
+
284
+ except Exception as e:
285
+ log_error(f"Failed to register plugin: {e}")
286
+ return False
287
+
288
+ def unregister_plugin(self, language: str) -> bool:
289
+ """
290
+ Unregister a plugin for a specific language.
291
+
292
+ Args:
293
+ language: Programming language name
294
+
295
+ Returns:
296
+ True if unregistration was successful
297
+ """
298
+ if language in self._loaded_plugins:
299
+ del self._loaded_plugins[language]
300
+ log_debug(f"Unregistered plugin for language: {language}")
301
+ return True
302
+
303
+ return False
304
+
305
+ def get_plugin_info(self, language: str) -> dict[str, Any] | None:
306
+ """
307
+ Get information about a specific plugin.
308
+
309
+ Args:
310
+ language: Programming language name
311
+
312
+ Returns:
313
+ Plugin information dictionary or None
314
+ """
315
+ plugin = self.get_plugin(language)
316
+ if not plugin:
317
+ return None
318
+
319
+ try:
320
+ return {
321
+ "language": plugin.get_language_name(),
322
+ "extensions": plugin.get_file_extensions(),
323
+ "class_name": plugin.__class__.__name__,
324
+ "module": plugin.__class__.__module__,
325
+ "has_extractor": hasattr(plugin, "create_extractor"),
326
+ }
327
+ except Exception as e:
328
+ log_error(f"Failed to get plugin info for {language}: {e}")
329
+ return None
330
+
331
+ def validate_plugin(self, plugin: LanguagePlugin) -> bool:
332
+ """
333
+ Validate that a plugin implements the required interface correctly.
334
+
335
+ Args:
336
+ plugin: Plugin instance to validate
337
+
338
+ Returns:
339
+ True if the plugin is valid
340
+ """
341
+ try:
342
+ # Check required methods
343
+ required_methods = [
344
+ "get_language_name",
345
+ "get_file_extensions",
346
+ "create_extractor",
347
+ ]
348
+
349
+ for method_name in required_methods:
350
+ if not hasattr(plugin, method_name):
351
+ log_error(f"Plugin missing required method: {method_name}")
352
+ return False
353
+
354
+ method = getattr(plugin, method_name)
355
+ if not callable(method):
356
+ log_error(f"Plugin method {method_name} is not callable")
357
+ return False
358
+
359
+ # Test basic functionality
360
+ language = plugin.get_language_name()
361
+ if not language or not isinstance(language, str):
362
+ log_error("Plugin get_language_name() must return a non-empty string")
363
+ return False
364
+
365
+ extensions = plugin.get_file_extensions()
366
+ if not isinstance(extensions, list):
367
+ log_error("Plugin get_file_extensions() must return a list") # type: ignore[unreachable]
368
+ return False
369
+
370
+ extractor = plugin.create_extractor()
371
+ if not extractor:
372
+ log_error("Plugin create_extractor() must return an extractor instance")
373
+ return False
374
+
375
+ return True
376
+
377
+ except Exception as e:
378
+ log_error(f"Plugin validation failed: {e}")
379
+ return False