jarvis-ai-assistant 0.1.222__py3-none-any.whl → 0.7.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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +1143 -245
- jarvis/jarvis_agent/agent_manager.py +97 -0
- jarvis/jarvis_agent/builtin_input_handler.py +12 -10
- jarvis/jarvis_agent/config_editor.py +57 -0
- jarvis/jarvis_agent/edit_file_handler.py +392 -99
- jarvis/jarvis_agent/event_bus.py +48 -0
- jarvis/jarvis_agent/events.py +157 -0
- jarvis/jarvis_agent/file_context_handler.py +79 -0
- jarvis/jarvis_agent/file_methodology_manager.py +117 -0
- jarvis/jarvis_agent/jarvis.py +1117 -147
- jarvis/jarvis_agent/main.py +78 -34
- jarvis/jarvis_agent/memory_manager.py +195 -0
- jarvis/jarvis_agent/methodology_share_manager.py +174 -0
- jarvis/jarvis_agent/prompt_manager.py +82 -0
- jarvis/jarvis_agent/prompts.py +46 -9
- jarvis/jarvis_agent/protocols.py +4 -1
- jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
- jarvis/jarvis_agent/run_loop.py +146 -0
- jarvis/jarvis_agent/session_manager.py +9 -9
- jarvis/jarvis_agent/share_manager.py +228 -0
- jarvis/jarvis_agent/shell_input_handler.py +23 -3
- jarvis/jarvis_agent/stdio_redirect.py +295 -0
- jarvis/jarvis_agent/task_analyzer.py +212 -0
- jarvis/jarvis_agent/task_manager.py +154 -0
- jarvis/jarvis_agent/task_planner.py +496 -0
- jarvis/jarvis_agent/tool_executor.py +8 -4
- jarvis/jarvis_agent/tool_share_manager.py +139 -0
- jarvis/jarvis_agent/user_interaction.py +42 -0
- jarvis/jarvis_agent/utils.py +54 -0
- jarvis/jarvis_agent/web_bridge.py +189 -0
- jarvis/jarvis_agent/web_output_sink.py +53 -0
- jarvis/jarvis_agent/web_server.py +751 -0
- jarvis/jarvis_c2rust/__init__.py +26 -0
- jarvis/jarvis_c2rust/cli.py +613 -0
- jarvis/jarvis_c2rust/collector.py +258 -0
- jarvis/jarvis_c2rust/library_replacer.py +1122 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
- jarvis/jarvis_c2rust/optimizer.py +960 -0
- jarvis/jarvis_c2rust/scanner.py +1681 -0
- jarvis/jarvis_c2rust/transpiler.py +2325 -0
- jarvis/jarvis_code_agent/build_validation_config.py +133 -0
- jarvis/jarvis_code_agent/code_agent.py +1605 -178
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -0
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +102 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -0
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +89 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
- jarvis/jarvis_code_agent/lint.py +275 -13
- jarvis/jarvis_code_agent/utils.py +142 -0
- jarvis/jarvis_code_analysis/checklists/loader.py +20 -6
- jarvis/jarvis_code_analysis/code_review.py +583 -548
- jarvis/jarvis_data/config_schema.json +339 -28
- jarvis/jarvis_git_squash/main.py +22 -13
- jarvis/jarvis_git_utils/git_commiter.py +171 -55
- jarvis/jarvis_mcp/sse_mcp_client.py +22 -15
- jarvis/jarvis_mcp/stdio_mcp_client.py +4 -4
- jarvis/jarvis_mcp/streamable_mcp_client.py +36 -16
- jarvis/jarvis_memory_organizer/memory_organizer.py +753 -0
- jarvis/jarvis_methodology/main.py +48 -63
- jarvis/jarvis_multi_agent/__init__.py +302 -43
- jarvis/jarvis_multi_agent/main.py +70 -24
- jarvis/jarvis_platform/ai8.py +40 -23
- jarvis/jarvis_platform/base.py +210 -49
- jarvis/jarvis_platform/human.py +11 -1
- jarvis/jarvis_platform/kimi.py +82 -76
- jarvis/jarvis_platform/openai.py +73 -1
- jarvis/jarvis_platform/registry.py +8 -15
- jarvis/jarvis_platform/tongyi.py +115 -101
- jarvis/jarvis_platform/yuanbao.py +89 -63
- jarvis/jarvis_platform_manager/main.py +194 -132
- jarvis/jarvis_platform_manager/service.py +122 -86
- jarvis/jarvis_rag/cli.py +156 -53
- jarvis/jarvis_rag/embedding_manager.py +155 -12
- jarvis/jarvis_rag/llm_interface.py +10 -13
- jarvis/jarvis_rag/query_rewriter.py +63 -12
- jarvis/jarvis_rag/rag_pipeline.py +222 -40
- jarvis/jarvis_rag/reranker.py +26 -3
- jarvis/jarvis_rag/retriever.py +270 -14
- jarvis/jarvis_sec/__init__.py +3605 -0
- jarvis/jarvis_sec/checkers/__init__.py +32 -0
- jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
- jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
- jarvis/jarvis_sec/cli.py +116 -0
- jarvis/jarvis_sec/report.py +257 -0
- jarvis/jarvis_sec/status.py +264 -0
- jarvis/jarvis_sec/types.py +20 -0
- jarvis/jarvis_sec/workflow.py +219 -0
- jarvis/jarvis_smart_shell/main.py +405 -137
- jarvis/jarvis_stats/__init__.py +13 -0
- jarvis/jarvis_stats/cli.py +387 -0
- jarvis/jarvis_stats/stats.py +711 -0
- jarvis/jarvis_stats/storage.py +612 -0
- jarvis/jarvis_stats/visualizer.py +282 -0
- jarvis/jarvis_tools/ask_user.py +1 -0
- jarvis/jarvis_tools/base.py +18 -2
- jarvis/jarvis_tools/clear_memory.py +239 -0
- jarvis/jarvis_tools/cli/main.py +220 -144
- jarvis/jarvis_tools/execute_script.py +52 -12
- jarvis/jarvis_tools/file_analyzer.py +17 -12
- jarvis/jarvis_tools/generate_new_tool.py +46 -24
- jarvis/jarvis_tools/read_code.py +277 -18
- jarvis/jarvis_tools/read_symbols.py +141 -0
- jarvis/jarvis_tools/read_webpage.py +86 -13
- jarvis/jarvis_tools/registry.py +294 -90
- jarvis/jarvis_tools/retrieve_memory.py +227 -0
- jarvis/jarvis_tools/save_memory.py +194 -0
- jarvis/jarvis_tools/search_web.py +62 -28
- jarvis/jarvis_tools/sub_agent.py +205 -0
- jarvis/jarvis_tools/sub_code_agent.py +217 -0
- jarvis/jarvis_tools/virtual_tty.py +330 -62
- jarvis/jarvis_utils/builtin_replace_map.py +4 -5
- jarvis/jarvis_utils/clipboard.py +90 -0
- jarvis/jarvis_utils/config.py +607 -50
- jarvis/jarvis_utils/embedding.py +3 -0
- jarvis/jarvis_utils/fzf.py +57 -0
- jarvis/jarvis_utils/git_utils.py +251 -29
- jarvis/jarvis_utils/globals.py +174 -17
- jarvis/jarvis_utils/http.py +58 -79
- jarvis/jarvis_utils/input.py +899 -153
- jarvis/jarvis_utils/methodology.py +210 -83
- jarvis/jarvis_utils/output.py +220 -137
- jarvis/jarvis_utils/utils.py +1906 -135
- jarvis_ai_assistant-0.7.0.dist-info/METADATA +465 -0
- jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +8 -2
- jarvis/jarvis_git_details/main.py +0 -265
- jarvis/jarvis_platform/oyi.py +0 -357
- jarvis/jarvis_tools/edit_file.py +0 -255
- jarvis/jarvis_tools/rewrite_file.py +0 -195
- jarvis_ai_assistant-0.1.222.dist-info/METADATA +0 -767
- jarvis_ai_assistant-0.1.222.dist-info/RECORD +0 -110
- /jarvis/{jarvis_git_details → jarvis_memory_organizer}/__init__.py +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Go语言支持实现。"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
from typing import List, Optional, Set
|
|
6
|
+
|
|
7
|
+
from tree_sitter import Language, Node
|
|
8
|
+
|
|
9
|
+
from ..base_language import BaseLanguageSupport
|
|
10
|
+
from ..dependency_analyzer import Dependency, DependencyAnalyzer, DependencyGraph
|
|
11
|
+
from ..file_ignore import filter_walk_dirs
|
|
12
|
+
from ..symbol_extractor import Symbol, SymbolExtractor
|
|
13
|
+
from ..tree_sitter_extractor import TreeSitterExtractor
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# --- Go Symbol Query ---
|
|
17
|
+
|
|
18
|
+
GO_SYMBOL_QUERY = """
|
|
19
|
+
(function_declaration
|
|
20
|
+
name: (identifier) @function.name)
|
|
21
|
+
|
|
22
|
+
(method_declaration
|
|
23
|
+
name: (field_identifier) @method.name)
|
|
24
|
+
|
|
25
|
+
(type_declaration
|
|
26
|
+
(type_spec
|
|
27
|
+
name: (type_identifier) @type.name))
|
|
28
|
+
|
|
29
|
+
(interface_declaration
|
|
30
|
+
name: (type_identifier) @interface.name)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
# --- Go Language Setup ---
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
import tree_sitter_go
|
|
37
|
+
GO_LANGUAGE: Optional[Language] = tree_sitter_go.language()
|
|
38
|
+
except (ImportError, Exception):
|
|
39
|
+
GO_LANGUAGE = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# --- Go Symbol Extractor ---
|
|
43
|
+
|
|
44
|
+
class GoSymbolExtractor(TreeSitterExtractor):
|
|
45
|
+
"""Extracts symbols from Go code using tree-sitter."""
|
|
46
|
+
|
|
47
|
+
def __init__(self):
|
|
48
|
+
if not GO_LANGUAGE:
|
|
49
|
+
raise RuntimeError("Go tree-sitter grammar not available.")
|
|
50
|
+
super().__init__(GO_LANGUAGE, GO_SYMBOL_QUERY)
|
|
51
|
+
|
|
52
|
+
def _create_symbol_from_capture(self, node: Node, name: str, file_path: str) -> Optional[Symbol]:
|
|
53
|
+
"""Maps a tree-sitter capture to a Symbol object."""
|
|
54
|
+
kind_map = {
|
|
55
|
+
"function.name": "function",
|
|
56
|
+
"method.name": "method",
|
|
57
|
+
"type.name": "type",
|
|
58
|
+
"interface.name": "interface",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
symbol_kind = kind_map.get(name)
|
|
62
|
+
if not symbol_kind:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
return Symbol(
|
|
66
|
+
name=node.text.decode('utf8'),
|
|
67
|
+
kind=symbol_kind,
|
|
68
|
+
file_path=file_path,
|
|
69
|
+
line_start=node.start_point[0] + 1,
|
|
70
|
+
line_end=node.end_point[0] + 1,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# --- Go Dependency Analyzer ---
|
|
75
|
+
|
|
76
|
+
class GoDependencyAnalyzer(DependencyAnalyzer):
|
|
77
|
+
"""Analyzes Go import dependencies."""
|
|
78
|
+
|
|
79
|
+
def analyze_imports(self, file_path: str, content: str) -> List[Dependency]:
|
|
80
|
+
"""Analyzes Go import statements."""
|
|
81
|
+
dependencies: List[Dependency] = []
|
|
82
|
+
|
|
83
|
+
# Match import statements
|
|
84
|
+
# Format: import "package" or import ( "package1" "package2" )
|
|
85
|
+
# Also: import alias "package"
|
|
86
|
+
re.compile(
|
|
87
|
+
r'import\s+(?:\(([^)]+)\)|(?:"([^"]+)"|`([^`]+)`)|(\w+)\s+(?:"([^"]+)"|`([^`]+)`)))',
|
|
88
|
+
re.MULTILINE
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Handle single import: import "package"
|
|
92
|
+
single_import = re.compile(r'import\s+(?:"([^"]+)"|`([^`]+)`|(\w+)\s+(?:"([^"]+)"|`([^`]+)`))')
|
|
93
|
+
|
|
94
|
+
# Handle block import: import ( ... )
|
|
95
|
+
block_import = re.compile(r'import\s*\(([^)]+)\)', re.DOTALL)
|
|
96
|
+
|
|
97
|
+
# Try block import first
|
|
98
|
+
block_match = block_import.search(content)
|
|
99
|
+
if block_match:
|
|
100
|
+
block_content = block_match.group(1)
|
|
101
|
+
for line in block_content.split('\n'):
|
|
102
|
+
line = line.strip()
|
|
103
|
+
if not line or line.startswith('//'):
|
|
104
|
+
continue
|
|
105
|
+
# Extract package path
|
|
106
|
+
pkg_match = re.search(r'(?:"([^"]+)"|`([^`]+)`|(\w+)\s+(?:"([^"]+)"|`([^`]+)`))', line)
|
|
107
|
+
if pkg_match:
|
|
108
|
+
pkg = pkg_match.group(1) or pkg_match.group(2) or pkg_match.group(4) or pkg_match.group(5)
|
|
109
|
+
alias = pkg_match.group(3)
|
|
110
|
+
line_num = content[:block_match.start()].count('\n') + line.split('\n')[0].count('\n') + 1
|
|
111
|
+
dependencies.append(Dependency(
|
|
112
|
+
from_module=pkg,
|
|
113
|
+
imported_symbol=alias,
|
|
114
|
+
file_path=file_path,
|
|
115
|
+
line=line_num,
|
|
116
|
+
))
|
|
117
|
+
else:
|
|
118
|
+
# Try single import
|
|
119
|
+
for match in single_import.finditer(content):
|
|
120
|
+
pkg = match.group(1) or match.group(2) or match.group(4) or match.group(5)
|
|
121
|
+
alias = match.group(3)
|
|
122
|
+
line_num = content[:match.start()].count('\n') + 1
|
|
123
|
+
dependencies.append(Dependency(
|
|
124
|
+
from_module=pkg,
|
|
125
|
+
imported_symbol=alias,
|
|
126
|
+
file_path=file_path,
|
|
127
|
+
line=line_num,
|
|
128
|
+
))
|
|
129
|
+
|
|
130
|
+
return dependencies
|
|
131
|
+
|
|
132
|
+
def build_dependency_graph(self, project_root: str) -> DependencyGraph:
|
|
133
|
+
"""Builds a dependency graph for a Go project."""
|
|
134
|
+
graph = DependencyGraph()
|
|
135
|
+
|
|
136
|
+
for root, dirs, files in os.walk(project_root):
|
|
137
|
+
dirs[:] = filter_walk_dirs(dirs)
|
|
138
|
+
|
|
139
|
+
for file in files:
|
|
140
|
+
if not file.endswith('.go'):
|
|
141
|
+
continue
|
|
142
|
+
|
|
143
|
+
file_path = os.path.join(root, file)
|
|
144
|
+
try:
|
|
145
|
+
with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
|
|
146
|
+
content = f.read()
|
|
147
|
+
|
|
148
|
+
dependencies = self.analyze_imports(file_path, content)
|
|
149
|
+
for dep in dependencies:
|
|
150
|
+
# For Go, we can resolve to vendor or standard library
|
|
151
|
+
# For now, just track the module name
|
|
152
|
+
# In a real implementation, you'd resolve using go.mod
|
|
153
|
+
pass
|
|
154
|
+
except Exception:
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
return graph
|
|
158
|
+
|
|
159
|
+
def _is_source_file(self, file_path: str) -> bool:
|
|
160
|
+
"""Check if a file is a Go source file."""
|
|
161
|
+
return file_path.endswith('.go')
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class GoLanguageSupport(BaseLanguageSupport):
|
|
165
|
+
"""Go语言支持类。"""
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def language_name(self) -> str:
|
|
169
|
+
return 'go'
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def file_extensions(self) -> Set[str]:
|
|
173
|
+
return {'.go'}
|
|
174
|
+
|
|
175
|
+
def create_symbol_extractor(self) -> Optional[SymbolExtractor]:
|
|
176
|
+
try:
|
|
177
|
+
return GoSymbolExtractor()
|
|
178
|
+
except RuntimeError:
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
def create_dependency_analyzer(self) -> Optional[DependencyAnalyzer]:
|
|
182
|
+
return GoDependencyAnalyzer()
|
|
183
|
+
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""Python语言支持实现。"""
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import os
|
|
5
|
+
from typing import List, Optional, Set, Union
|
|
6
|
+
|
|
7
|
+
from ..base_language import BaseLanguageSupport
|
|
8
|
+
from ..dependency_analyzer import Dependency, DependencyAnalyzer, DependencyGraph
|
|
9
|
+
from ..file_ignore import filter_walk_dirs
|
|
10
|
+
from ..symbol_extractor import Symbol, SymbolExtractor
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PythonSymbolExtractor(SymbolExtractor):
|
|
14
|
+
"""Extracts symbols from Python code using the AST module."""
|
|
15
|
+
|
|
16
|
+
def extract_symbols(self, file_path: str, content: str) -> List[Symbol]:
|
|
17
|
+
symbols: List[Symbol] = []
|
|
18
|
+
try:
|
|
19
|
+
tree = ast.parse(content, filename=file_path)
|
|
20
|
+
self._traverse_node(tree, file_path, symbols, parent_name=None)
|
|
21
|
+
except SyntaxError as e:
|
|
22
|
+
print(f"Error parsing Python file {file_path}: {e}")
|
|
23
|
+
return symbols
|
|
24
|
+
|
|
25
|
+
def _traverse_node(self, node: ast.AST, file_path: str, symbols: List[Symbol], parent_name: Optional[str]):
|
|
26
|
+
if isinstance(node, ast.FunctionDef):
|
|
27
|
+
symbol = self._create_symbol_from_func(node, file_path, parent_name)
|
|
28
|
+
symbols.append(symbol)
|
|
29
|
+
parent_name = node.name
|
|
30
|
+
elif isinstance(node, ast.AsyncFunctionDef):
|
|
31
|
+
symbol = self._create_symbol_from_func(node, file_path, parent_name, is_async=True)
|
|
32
|
+
symbols.append(symbol)
|
|
33
|
+
parent_name = node.name
|
|
34
|
+
elif isinstance(node, ast.ClassDef):
|
|
35
|
+
symbol = self._create_symbol_from_class(node, file_path, parent_name)
|
|
36
|
+
symbols.append(symbol)
|
|
37
|
+
parent_name = node.name
|
|
38
|
+
elif isinstance(node, (ast.Import, ast.ImportFrom)):
|
|
39
|
+
symbols.extend(self._create_symbols_from_import(node, file_path, parent_name))
|
|
40
|
+
|
|
41
|
+
for child in ast.iter_child_nodes(node):
|
|
42
|
+
self._traverse_node(child, file_path, symbols, parent_name=parent_name)
|
|
43
|
+
|
|
44
|
+
def _create_symbol_from_func(self, node: Union[ast.FunctionDef, ast.AsyncFunctionDef], file_path: str, parent: Optional[str], is_async: bool = False) -> Symbol:
|
|
45
|
+
signature = f"{'async ' if is_async else ''}def {node.name}(...)"
|
|
46
|
+
return Symbol(
|
|
47
|
+
name=node.name,
|
|
48
|
+
kind='function',
|
|
49
|
+
file_path=file_path,
|
|
50
|
+
line_start=node.lineno,
|
|
51
|
+
line_end=node.end_lineno or node.lineno,
|
|
52
|
+
signature=signature,
|
|
53
|
+
docstring=ast.get_docstring(node),
|
|
54
|
+
parent=parent,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def _create_symbol_from_class(self, node: ast.ClassDef, file_path: str, parent: Optional[str]) -> Symbol:
|
|
58
|
+
return Symbol(
|
|
59
|
+
name=node.name,
|
|
60
|
+
kind='class',
|
|
61
|
+
file_path=file_path,
|
|
62
|
+
line_start=node.lineno,
|
|
63
|
+
line_end=node.end_lineno or node.lineno,
|
|
64
|
+
docstring=ast.get_docstring(node),
|
|
65
|
+
parent=parent,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def _create_symbols_from_import(self, node: Union[ast.Import, ast.ImportFrom], file_path: str, parent: Optional[str]) -> List[Symbol]:
|
|
69
|
+
symbols = []
|
|
70
|
+
for alias in node.names:
|
|
71
|
+
symbols.append(Symbol(
|
|
72
|
+
name=alias.asname or alias.name,
|
|
73
|
+
kind='import',
|
|
74
|
+
file_path=file_path,
|
|
75
|
+
line_start=node.lineno,
|
|
76
|
+
line_end=node.end_lineno or node.lineno,
|
|
77
|
+
parent=parent,
|
|
78
|
+
))
|
|
79
|
+
return symbols
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class PythonDependencyAnalyzer(DependencyAnalyzer):
|
|
83
|
+
"""Analyzes Python import dependencies using AST."""
|
|
84
|
+
|
|
85
|
+
def analyze_imports(self, file_path: str, content: str) -> List[Dependency]:
|
|
86
|
+
"""Analyzes Python import statements."""
|
|
87
|
+
dependencies: List[Dependency] = []
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
tree = ast.parse(content, filename=file_path)
|
|
91
|
+
for node in ast.walk(tree):
|
|
92
|
+
if isinstance(node, ast.Import):
|
|
93
|
+
# import module
|
|
94
|
+
for alias in node.names:
|
|
95
|
+
dependencies.append(Dependency(
|
|
96
|
+
from_module=alias.name,
|
|
97
|
+
imported_symbol=None,
|
|
98
|
+
file_path=file_path,
|
|
99
|
+
line=node.lineno,
|
|
100
|
+
))
|
|
101
|
+
elif isinstance(node, ast.ImportFrom):
|
|
102
|
+
# from module import symbol
|
|
103
|
+
module = node.module or ""
|
|
104
|
+
for alias in node.names:
|
|
105
|
+
dependencies.append(Dependency(
|
|
106
|
+
from_module=module,
|
|
107
|
+
imported_symbol=alias.name,
|
|
108
|
+
file_path=file_path,
|
|
109
|
+
line=node.lineno,
|
|
110
|
+
))
|
|
111
|
+
except SyntaxError:
|
|
112
|
+
# Skip files with syntax errors
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
return dependencies
|
|
116
|
+
|
|
117
|
+
def build_dependency_graph(self, project_root: str) -> DependencyGraph:
|
|
118
|
+
"""Builds a dependency graph for a Python project."""
|
|
119
|
+
graph = DependencyGraph()
|
|
120
|
+
|
|
121
|
+
# Walk through all Python files
|
|
122
|
+
for root, dirs, files in os.walk(project_root):
|
|
123
|
+
# Skip hidden directories and common ignore patterns
|
|
124
|
+
dirs[:] = filter_walk_dirs(dirs)
|
|
125
|
+
|
|
126
|
+
for file in files:
|
|
127
|
+
if not file.endswith('.py'):
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
file_path = os.path.join(root, file)
|
|
131
|
+
try:
|
|
132
|
+
with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
|
|
133
|
+
content = f.read()
|
|
134
|
+
|
|
135
|
+
dependencies = self.analyze_imports(file_path, content)
|
|
136
|
+
for dep in dependencies:
|
|
137
|
+
# Resolve module to file path
|
|
138
|
+
dep_path = self._resolve_module_path(project_root, dep.from_module, file_path)
|
|
139
|
+
if dep_path and dep_path != file_path:
|
|
140
|
+
graph.add_dependency(file_path, dep_path)
|
|
141
|
+
except Exception:
|
|
142
|
+
continue
|
|
143
|
+
|
|
144
|
+
return graph
|
|
145
|
+
|
|
146
|
+
def _resolve_module_path(self, project_root: str, module_name: str, from_file: str) -> Optional[str]:
|
|
147
|
+
"""Resolve a Python module name to a file path."""
|
|
148
|
+
if not module_name:
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
# Handle relative imports
|
|
152
|
+
if module_name.startswith('.'):
|
|
153
|
+
# Relative import - resolve from the importing file's directory
|
|
154
|
+
base_dir = os.path.dirname(from_file)
|
|
155
|
+
parts = module_name.split('.')
|
|
156
|
+
depth = len([p for p in parts if p == ''])
|
|
157
|
+
module_parts = [p for p in parts if p]
|
|
158
|
+
|
|
159
|
+
# Navigate up directories
|
|
160
|
+
current_dir = base_dir
|
|
161
|
+
for _ in range(depth - 1):
|
|
162
|
+
current_dir = os.path.dirname(current_dir)
|
|
163
|
+
|
|
164
|
+
if module_parts:
|
|
165
|
+
module_path = os.path.join(current_dir, *module_parts)
|
|
166
|
+
else:
|
|
167
|
+
module_path = current_dir
|
|
168
|
+
|
|
169
|
+
# Try to find the module file
|
|
170
|
+
if os.path.isdir(module_path):
|
|
171
|
+
init_path = os.path.join(module_path, '__init__.py')
|
|
172
|
+
if os.path.exists(init_path):
|
|
173
|
+
return init_path
|
|
174
|
+
elif os.path.exists(module_path + '.py'):
|
|
175
|
+
return module_path + '.py'
|
|
176
|
+
else:
|
|
177
|
+
# Absolute import
|
|
178
|
+
parts = module_name.split('.')
|
|
179
|
+
|
|
180
|
+
# Search in project root
|
|
181
|
+
for root, dirs, files in os.walk(project_root):
|
|
182
|
+
# Skip hidden directories and common ignore patterns
|
|
183
|
+
dirs[:] = filter_walk_dirs(dirs)
|
|
184
|
+
|
|
185
|
+
if parts[0] in dirs or f"{parts[0]}.py" in files:
|
|
186
|
+
module_path = os.path.join(root, *parts)
|
|
187
|
+
|
|
188
|
+
if os.path.isdir(module_path):
|
|
189
|
+
init_path = os.path.join(module_path, '__init__.py')
|
|
190
|
+
if os.path.exists(init_path):
|
|
191
|
+
return init_path
|
|
192
|
+
elif os.path.exists(module_path + '.py'):
|
|
193
|
+
return module_path + '.py'
|
|
194
|
+
break
|
|
195
|
+
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
def _is_source_file(self, file_path: str) -> bool:
|
|
199
|
+
"""Check if a file is a Python source file."""
|
|
200
|
+
return file_path.endswith('.py')
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class PythonLanguageSupport(BaseLanguageSupport):
|
|
204
|
+
"""Python语言支持类。"""
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def language_name(self) -> str:
|
|
208
|
+
return 'python'
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def file_extensions(self) -> Set[str]:
|
|
212
|
+
return {'.py', '.pyw', '.pyi'}
|
|
213
|
+
|
|
214
|
+
def create_symbol_extractor(self) -> Optional[SymbolExtractor]:
|
|
215
|
+
return PythonSymbolExtractor()
|
|
216
|
+
|
|
217
|
+
def create_dependency_analyzer(self) -> Optional[DependencyAnalyzer]:
|
|
218
|
+
return PythonDependencyAnalyzer()
|
|
219
|
+
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""Rust语言支持实现。"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
from typing import List, Optional, Set
|
|
6
|
+
|
|
7
|
+
from tree_sitter import Language, Node
|
|
8
|
+
|
|
9
|
+
from ..base_language import BaseLanguageSupport
|
|
10
|
+
from ..dependency_analyzer import Dependency, DependencyAnalyzer, DependencyGraph
|
|
11
|
+
from ..file_ignore import filter_walk_dirs
|
|
12
|
+
from ..symbol_extractor import Symbol, SymbolExtractor
|
|
13
|
+
from ..tree_sitter_extractor import TreeSitterExtractor
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# --- Rust Symbol Query ---
|
|
17
|
+
|
|
18
|
+
RUST_SYMBOL_QUERY = """
|
|
19
|
+
(function_item
|
|
20
|
+
name: (identifier) @function.name)
|
|
21
|
+
|
|
22
|
+
(struct_item
|
|
23
|
+
name: (type_identifier) @struct.name)
|
|
24
|
+
|
|
25
|
+
(trait_item
|
|
26
|
+
name: (type_identifier) @trait.name)
|
|
27
|
+
|
|
28
|
+
(impl_item
|
|
29
|
+
type: (type_identifier) @impl.name)
|
|
30
|
+
|
|
31
|
+
(mod_item
|
|
32
|
+
name: (identifier) @module.name)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# --- Rust Language Setup ---
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
import tree_sitter_rust
|
|
39
|
+
RUST_LANGUAGE: Optional[Language] = tree_sitter_rust.language()
|
|
40
|
+
except (ImportError, Exception):
|
|
41
|
+
RUST_LANGUAGE = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# --- Rust Symbol Extractor ---
|
|
45
|
+
|
|
46
|
+
class RustSymbolExtractor(TreeSitterExtractor):
|
|
47
|
+
"""Extracts symbols from Rust code using tree-sitter."""
|
|
48
|
+
|
|
49
|
+
def __init__(self):
|
|
50
|
+
if not RUST_LANGUAGE:
|
|
51
|
+
raise RuntimeError("Rust tree-sitter grammar not available.")
|
|
52
|
+
super().__init__(RUST_LANGUAGE, RUST_SYMBOL_QUERY)
|
|
53
|
+
|
|
54
|
+
def _create_symbol_from_capture(self, node: Node, name: str, file_path: str) -> Optional[Symbol]:
|
|
55
|
+
"""Maps a tree-sitter capture to a Symbol object."""
|
|
56
|
+
kind_map = {
|
|
57
|
+
"function.name": "function",
|
|
58
|
+
"struct.name": "struct",
|
|
59
|
+
"trait.name": "trait",
|
|
60
|
+
"impl.name": "impl",
|
|
61
|
+
"module.name": "module",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
symbol_kind = kind_map.get(name)
|
|
65
|
+
if not symbol_kind:
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
return Symbol(
|
|
69
|
+
name=node.text.decode('utf8'),
|
|
70
|
+
kind=symbol_kind,
|
|
71
|
+
file_path=file_path,
|
|
72
|
+
line_start=node.start_point[0] + 1,
|
|
73
|
+
line_end=node.end_point[0] + 1,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# --- Rust Dependency Analyzer ---
|
|
78
|
+
|
|
79
|
+
class RustDependencyAnalyzer(DependencyAnalyzer):
|
|
80
|
+
"""Analyzes Rust use and mod dependencies."""
|
|
81
|
+
|
|
82
|
+
def analyze_imports(self, file_path: str, content: str) -> List[Dependency]:
|
|
83
|
+
"""Analyzes Rust use and mod statements."""
|
|
84
|
+
dependencies: List[Dependency] = []
|
|
85
|
+
|
|
86
|
+
# Match use statements: use crate::module or use std::collections
|
|
87
|
+
use_pattern = re.compile(r'use\s+([^;]+);')
|
|
88
|
+
|
|
89
|
+
# Match mod declarations: mod module_name;
|
|
90
|
+
mod_pattern = re.compile(r'mod\s+(\w+)\s*;')
|
|
91
|
+
|
|
92
|
+
for line_num, line in enumerate(content.split('\n'), start=1):
|
|
93
|
+
# Check for use statements
|
|
94
|
+
use_match = use_pattern.search(line)
|
|
95
|
+
if use_match:
|
|
96
|
+
use_path = use_match.group(1).strip()
|
|
97
|
+
# Extract the crate/module name
|
|
98
|
+
parts = use_path.split('::')
|
|
99
|
+
if parts:
|
|
100
|
+
crate_name = parts[0]
|
|
101
|
+
symbol = '::'.join(parts[1:]) if len(parts) > 1 else None
|
|
102
|
+
dependencies.append(Dependency(
|
|
103
|
+
from_module=crate_name,
|
|
104
|
+
imported_symbol=symbol,
|
|
105
|
+
file_path=file_path,
|
|
106
|
+
line=line_num,
|
|
107
|
+
))
|
|
108
|
+
|
|
109
|
+
# Check for mod declarations
|
|
110
|
+
mod_match = mod_pattern.search(line)
|
|
111
|
+
if mod_match:
|
|
112
|
+
mod_name = mod_match.group(1)
|
|
113
|
+
dependencies.append(Dependency(
|
|
114
|
+
from_module=mod_name,
|
|
115
|
+
imported_symbol=None,
|
|
116
|
+
file_path=file_path,
|
|
117
|
+
line=line_num,
|
|
118
|
+
))
|
|
119
|
+
|
|
120
|
+
return dependencies
|
|
121
|
+
|
|
122
|
+
def build_dependency_graph(self, project_root: str) -> DependencyGraph:
|
|
123
|
+
"""Builds a dependency graph for a Rust project."""
|
|
124
|
+
graph = DependencyGraph()
|
|
125
|
+
|
|
126
|
+
for root, dirs, files in os.walk(project_root):
|
|
127
|
+
dirs[:] = filter_walk_dirs(dirs)
|
|
128
|
+
|
|
129
|
+
for file in files:
|
|
130
|
+
if not file.endswith('.rs'):
|
|
131
|
+
continue
|
|
132
|
+
|
|
133
|
+
file_path = os.path.join(root, file)
|
|
134
|
+
try:
|
|
135
|
+
with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
|
|
136
|
+
content = f.read()
|
|
137
|
+
|
|
138
|
+
dependencies = self.analyze_imports(file_path, content)
|
|
139
|
+
for dep in dependencies:
|
|
140
|
+
# For Rust, resolve mod declarations to file paths
|
|
141
|
+
if not dep.from_module.startswith(('std', 'core', 'alloc', 'proc_macro')):
|
|
142
|
+
# Try to resolve local modules
|
|
143
|
+
dep_path = self._resolve_module_path(project_root, dep.from_module, file_path)
|
|
144
|
+
if dep_path and dep_path != file_path:
|
|
145
|
+
graph.add_dependency(file_path, dep_path)
|
|
146
|
+
except Exception:
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
return graph
|
|
150
|
+
|
|
151
|
+
def _resolve_module_path(self, project_root: str, module_name: str, from_file: str) -> Optional[str]:
|
|
152
|
+
"""Resolve a Rust module name to a file path."""
|
|
153
|
+
# Rust modules can be:
|
|
154
|
+
# 1. mod.rs in a directory
|
|
155
|
+
# 2. module_name.rs in the same directory
|
|
156
|
+
# 3. module_name/mod.rs
|
|
157
|
+
|
|
158
|
+
base_dir = os.path.dirname(from_file)
|
|
159
|
+
|
|
160
|
+
# Try module_name.rs in same directory
|
|
161
|
+
module_file = os.path.join(base_dir, f"{module_name}.rs")
|
|
162
|
+
if os.path.exists(module_file):
|
|
163
|
+
return module_file
|
|
164
|
+
|
|
165
|
+
# Try module_name/mod.rs
|
|
166
|
+
module_dir = os.path.join(base_dir, module_name)
|
|
167
|
+
mod_rs = os.path.join(module_dir, "mod.rs")
|
|
168
|
+
if os.path.exists(mod_rs):
|
|
169
|
+
return mod_rs
|
|
170
|
+
|
|
171
|
+
# Try in parent directories (for nested modules)
|
|
172
|
+
current_dir = base_dir
|
|
173
|
+
while current_dir != project_root and current_dir != os.path.dirname(current_dir):
|
|
174
|
+
module_file = os.path.join(current_dir, f"{module_name}.rs")
|
|
175
|
+
if os.path.exists(module_file):
|
|
176
|
+
return module_file
|
|
177
|
+
module_dir = os.path.join(current_dir, module_name)
|
|
178
|
+
mod_rs = os.path.join(module_dir, "mod.rs")
|
|
179
|
+
if os.path.exists(mod_rs):
|
|
180
|
+
return mod_rs
|
|
181
|
+
current_dir = os.path.dirname(current_dir)
|
|
182
|
+
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
def _is_source_file(self, file_path: str) -> bool:
|
|
186
|
+
"""Check if a file is a Rust source file."""
|
|
187
|
+
return file_path.endswith('.rs')
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class RustLanguageSupport(BaseLanguageSupport):
|
|
191
|
+
"""Rust语言支持类。"""
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def language_name(self) -> str:
|
|
195
|
+
return 'rust'
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def file_extensions(self) -> Set[str]:
|
|
199
|
+
return {'.rs'}
|
|
200
|
+
|
|
201
|
+
def create_symbol_extractor(self) -> Optional[SymbolExtractor]:
|
|
202
|
+
try:
|
|
203
|
+
return RustSymbolExtractor()
|
|
204
|
+
except RuntimeError:
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
def create_dependency_analyzer(self) -> Optional[DependencyAnalyzer]:
|
|
208
|
+
return RustDependencyAnalyzer()
|
|
209
|
+
|