tree-sitter-analyzer 0.8.3__py3-none-any.whl → 0.9.2__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 (62) hide show
  1. tree_sitter_analyzer/__init__.py +132 -132
  2. tree_sitter_analyzer/__main__.py +11 -11
  3. tree_sitter_analyzer/api.py +533 -533
  4. tree_sitter_analyzer/cli/__init__.py +39 -39
  5. tree_sitter_analyzer/cli/__main__.py +12 -12
  6. tree_sitter_analyzer/cli/commands/__init__.py +26 -26
  7. tree_sitter_analyzer/cli/commands/advanced_command.py +88 -88
  8. tree_sitter_analyzer/cli/commands/base_command.py +182 -180
  9. tree_sitter_analyzer/cli/commands/structure_command.py +138 -138
  10. tree_sitter_analyzer/cli/commands/summary_command.py +101 -101
  11. tree_sitter_analyzer/core/__init__.py +15 -15
  12. tree_sitter_analyzer/core/analysis_engine.py +74 -78
  13. tree_sitter_analyzer/core/cache_service.py +320 -320
  14. tree_sitter_analyzer/core/engine.py +566 -566
  15. tree_sitter_analyzer/core/parser.py +293 -293
  16. tree_sitter_analyzer/encoding_utils.py +459 -459
  17. tree_sitter_analyzer/file_handler.py +210 -210
  18. tree_sitter_analyzer/formatters/__init__.py +1 -1
  19. tree_sitter_analyzer/formatters/base_formatter.py +167 -167
  20. tree_sitter_analyzer/formatters/formatter_factory.py +78 -78
  21. tree_sitter_analyzer/formatters/java_formatter.py +18 -18
  22. tree_sitter_analyzer/formatters/python_formatter.py +19 -19
  23. tree_sitter_analyzer/interfaces/__init__.py +9 -9
  24. tree_sitter_analyzer/interfaces/cli.py +528 -528
  25. tree_sitter_analyzer/interfaces/cli_adapter.py +344 -343
  26. tree_sitter_analyzer/interfaces/mcp_adapter.py +206 -206
  27. tree_sitter_analyzer/language_detector.py +53 -53
  28. tree_sitter_analyzer/languages/__init__.py +10 -10
  29. tree_sitter_analyzer/languages/java_plugin.py +1 -1
  30. tree_sitter_analyzer/languages/javascript_plugin.py +446 -446
  31. tree_sitter_analyzer/languages/python_plugin.py +755 -755
  32. tree_sitter_analyzer/mcp/__init__.py +34 -31
  33. tree_sitter_analyzer/mcp/resources/__init__.py +44 -44
  34. tree_sitter_analyzer/mcp/resources/code_file_resource.py +209 -209
  35. tree_sitter_analyzer/mcp/server.py +623 -436
  36. tree_sitter_analyzer/mcp/tools/__init__.py +30 -30
  37. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +10 -6
  38. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +247 -242
  39. tree_sitter_analyzer/mcp/tools/base_tool.py +54 -54
  40. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +310 -308
  41. tree_sitter_analyzer/mcp/tools/table_format_tool.py +386 -379
  42. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +563 -559
  43. tree_sitter_analyzer/mcp/utils/__init__.py +107 -107
  44. tree_sitter_analyzer/models.py +10 -10
  45. tree_sitter_analyzer/output_manager.py +253 -253
  46. tree_sitter_analyzer/plugins/__init__.py +280 -280
  47. tree_sitter_analyzer/plugins/base.py +529 -529
  48. tree_sitter_analyzer/plugins/manager.py +379 -379
  49. tree_sitter_analyzer/queries/__init__.py +26 -26
  50. tree_sitter_analyzer/queries/java.py +391 -391
  51. tree_sitter_analyzer/queries/javascript.py +148 -148
  52. tree_sitter_analyzer/queries/python.py +285 -285
  53. tree_sitter_analyzer/queries/typescript.py +229 -229
  54. tree_sitter_analyzer/query_loader.py +257 -257
  55. tree_sitter_analyzer/security/boundary_manager.py +237 -279
  56. tree_sitter_analyzer/security/validator.py +60 -58
  57. tree_sitter_analyzer/utils.py +294 -277
  58. {tree_sitter_analyzer-0.8.3.dist-info → tree_sitter_analyzer-0.9.2.dist-info}/METADATA +28 -19
  59. tree_sitter_analyzer-0.9.2.dist-info/RECORD +77 -0
  60. {tree_sitter_analyzer-0.8.3.dist-info → tree_sitter_analyzer-0.9.2.dist-info}/entry_points.txt +1 -0
  61. tree_sitter_analyzer-0.8.3.dist-info/RECORD +0 -77
  62. {tree_sitter_analyzer-0.8.3.dist-info → tree_sitter_analyzer-0.9.2.dist-info}/WHEEL +0 -0
@@ -1,379 +1,379 @@
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
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