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