tree-sitter-analyzer 0.6.2__py3-none-any.whl → 0.8.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 (69) 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 +160 -160
  9. tree_sitter_analyzer/cli/commands/default_command.py +18 -18
  10. tree_sitter_analyzer/cli/commands/partial_read_command.py +141 -141
  11. tree_sitter_analyzer/cli/commands/query_command.py +81 -81
  12. tree_sitter_analyzer/cli/commands/structure_command.py +138 -138
  13. tree_sitter_analyzer/cli/commands/summary_command.py +101 -101
  14. tree_sitter_analyzer/cli/commands/table_command.py +235 -235
  15. tree_sitter_analyzer/cli/info_commands.py +121 -121
  16. tree_sitter_analyzer/cli_main.py +297 -297
  17. tree_sitter_analyzer/core/__init__.py +15 -15
  18. tree_sitter_analyzer/core/analysis_engine.py +555 -555
  19. tree_sitter_analyzer/core/cache_service.py +320 -320
  20. tree_sitter_analyzer/core/engine.py +566 -566
  21. tree_sitter_analyzer/core/parser.py +293 -293
  22. tree_sitter_analyzer/encoding_utils.py +459 -459
  23. tree_sitter_analyzer/exceptions.py +406 -337
  24. tree_sitter_analyzer/file_handler.py +210 -210
  25. tree_sitter_analyzer/formatters/__init__.py +1 -1
  26. tree_sitter_analyzer/formatters/base_formatter.py +167 -167
  27. tree_sitter_analyzer/formatters/formatter_factory.py +78 -78
  28. tree_sitter_analyzer/interfaces/__init__.py +9 -9
  29. tree_sitter_analyzer/interfaces/cli.py +528 -528
  30. tree_sitter_analyzer/interfaces/cli_adapter.py +343 -343
  31. tree_sitter_analyzer/interfaces/mcp_adapter.py +206 -206
  32. tree_sitter_analyzer/interfaces/mcp_server.py +425 -405
  33. tree_sitter_analyzer/languages/__init__.py +10 -10
  34. tree_sitter_analyzer/languages/javascript_plugin.py +446 -446
  35. tree_sitter_analyzer/languages/python_plugin.py +755 -755
  36. tree_sitter_analyzer/mcp/__init__.py +31 -31
  37. tree_sitter_analyzer/mcp/resources/__init__.py +44 -44
  38. tree_sitter_analyzer/mcp/resources/code_file_resource.py +209 -209
  39. tree_sitter_analyzer/mcp/server.py +346 -333
  40. tree_sitter_analyzer/mcp/tools/__init__.py +30 -30
  41. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +654 -654
  42. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +247 -247
  43. tree_sitter_analyzer/mcp/tools/base_tool.py +54 -54
  44. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +300 -300
  45. tree_sitter_analyzer/mcp/tools/table_format_tool.py +362 -362
  46. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +543 -543
  47. tree_sitter_analyzer/mcp/utils/__init__.py +107 -107
  48. tree_sitter_analyzer/mcp/utils/error_handler.py +549 -549
  49. tree_sitter_analyzer/output_manager.py +253 -253
  50. tree_sitter_analyzer/plugins/__init__.py +280 -280
  51. tree_sitter_analyzer/plugins/base.py +529 -529
  52. tree_sitter_analyzer/plugins/manager.py +379 -379
  53. tree_sitter_analyzer/queries/__init__.py +26 -26
  54. tree_sitter_analyzer/queries/java.py +391 -391
  55. tree_sitter_analyzer/queries/javascript.py +148 -148
  56. tree_sitter_analyzer/queries/python.py +285 -285
  57. tree_sitter_analyzer/queries/typescript.py +229 -229
  58. tree_sitter_analyzer/query_loader.py +257 -257
  59. tree_sitter_analyzer/security/__init__.py +22 -0
  60. tree_sitter_analyzer/security/boundary_manager.py +237 -0
  61. tree_sitter_analyzer/security/regex_checker.py +292 -0
  62. tree_sitter_analyzer/security/validator.py +224 -0
  63. tree_sitter_analyzer/table_formatter.py +652 -473
  64. tree_sitter_analyzer/utils.py +277 -277
  65. {tree_sitter_analyzer-0.6.2.dist-info → tree_sitter_analyzer-0.8.0.dist-info}/METADATA +4 -1
  66. tree_sitter_analyzer-0.8.0.dist-info/RECORD +76 -0
  67. tree_sitter_analyzer-0.6.2.dist-info/RECORD +0 -72
  68. {tree_sitter_analyzer-0.6.2.dist-info → tree_sitter_analyzer-0.8.0.dist-info}/WHEEL +0 -0
  69. {tree_sitter_analyzer-0.6.2.dist-info → tree_sitter_analyzer-0.8.0.dist-info}/entry_points.txt +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