jarvis-ai-assistant 0.7.16__py3-none-any.whl → 1.0.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.
Files changed (279) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +567 -222
  3. jarvis/jarvis_agent/agent_manager.py +19 -12
  4. jarvis/jarvis_agent/builtin_input_handler.py +79 -11
  5. jarvis/jarvis_agent/config_editor.py +7 -2
  6. jarvis/jarvis_agent/event_bus.py +24 -13
  7. jarvis/jarvis_agent/events.py +19 -1
  8. jarvis/jarvis_agent/file_context_handler.py +67 -64
  9. jarvis/jarvis_agent/file_methodology_manager.py +38 -24
  10. jarvis/jarvis_agent/jarvis.py +186 -114
  11. jarvis/jarvis_agent/language_extractors/__init__.py +8 -1
  12. jarvis/jarvis_agent/language_extractors/c_extractor.py +7 -4
  13. jarvis/jarvis_agent/language_extractors/cpp_extractor.py +9 -4
  14. jarvis/jarvis_agent/language_extractors/go_extractor.py +7 -4
  15. jarvis/jarvis_agent/language_extractors/java_extractor.py +27 -20
  16. jarvis/jarvis_agent/language_extractors/javascript_extractor.py +22 -17
  17. jarvis/jarvis_agent/language_extractors/python_extractor.py +7 -4
  18. jarvis/jarvis_agent/language_extractors/rust_extractor.py +7 -4
  19. jarvis/jarvis_agent/language_extractors/typescript_extractor.py +22 -17
  20. jarvis/jarvis_agent/language_support_info.py +250 -219
  21. jarvis/jarvis_agent/main.py +19 -23
  22. jarvis/jarvis_agent/memory_manager.py +9 -6
  23. jarvis/jarvis_agent/methodology_share_manager.py +21 -15
  24. jarvis/jarvis_agent/output_handler.py +4 -2
  25. jarvis/jarvis_agent/prompt_builder.py +7 -6
  26. jarvis/jarvis_agent/prompt_manager.py +113 -8
  27. jarvis/jarvis_agent/prompts.py +317 -85
  28. jarvis/jarvis_agent/protocols.py +5 -2
  29. jarvis/jarvis_agent/run_loop.py +192 -32
  30. jarvis/jarvis_agent/session_manager.py +7 -3
  31. jarvis/jarvis_agent/share_manager.py +23 -13
  32. jarvis/jarvis_agent/shell_input_handler.py +12 -8
  33. jarvis/jarvis_agent/stdio_redirect.py +25 -26
  34. jarvis/jarvis_agent/task_analyzer.py +29 -23
  35. jarvis/jarvis_agent/task_list.py +869 -0
  36. jarvis/jarvis_agent/task_manager.py +26 -23
  37. jarvis/jarvis_agent/tool_executor.py +6 -5
  38. jarvis/jarvis_agent/tool_share_manager.py +24 -14
  39. jarvis/jarvis_agent/user_interaction.py +3 -3
  40. jarvis/jarvis_agent/utils.py +9 -1
  41. jarvis/jarvis_agent/web_bridge.py +37 -17
  42. jarvis/jarvis_agent/web_output_sink.py +5 -2
  43. jarvis/jarvis_agent/web_server.py +165 -36
  44. jarvis/jarvis_c2rust/__init__.py +1 -1
  45. jarvis/jarvis_c2rust/cli.py +260 -141
  46. jarvis/jarvis_c2rust/collector.py +37 -18
  47. jarvis/jarvis_c2rust/constants.py +60 -0
  48. jarvis/jarvis_c2rust/library_replacer.py +242 -1010
  49. jarvis/jarvis_c2rust/library_replacer_checkpoint.py +133 -0
  50. jarvis/jarvis_c2rust/library_replacer_llm.py +287 -0
  51. jarvis/jarvis_c2rust/library_replacer_loader.py +191 -0
  52. jarvis/jarvis_c2rust/library_replacer_output.py +134 -0
  53. jarvis/jarvis_c2rust/library_replacer_prompts.py +124 -0
  54. jarvis/jarvis_c2rust/library_replacer_utils.py +188 -0
  55. jarvis/jarvis_c2rust/llm_module_agent.py +98 -1044
  56. jarvis/jarvis_c2rust/llm_module_agent_apply.py +170 -0
  57. jarvis/jarvis_c2rust/llm_module_agent_executor.py +288 -0
  58. jarvis/jarvis_c2rust/llm_module_agent_loader.py +170 -0
  59. jarvis/jarvis_c2rust/llm_module_agent_prompts.py +268 -0
  60. jarvis/jarvis_c2rust/llm_module_agent_types.py +57 -0
  61. jarvis/jarvis_c2rust/llm_module_agent_utils.py +150 -0
  62. jarvis/jarvis_c2rust/llm_module_agent_validator.py +119 -0
  63. jarvis/jarvis_c2rust/loaders.py +28 -10
  64. jarvis/jarvis_c2rust/models.py +5 -2
  65. jarvis/jarvis_c2rust/optimizer.py +192 -1974
  66. jarvis/jarvis_c2rust/optimizer_build_fix.py +286 -0
  67. jarvis/jarvis_c2rust/optimizer_clippy.py +766 -0
  68. jarvis/jarvis_c2rust/optimizer_config.py +49 -0
  69. jarvis/jarvis_c2rust/optimizer_docs.py +183 -0
  70. jarvis/jarvis_c2rust/optimizer_options.py +48 -0
  71. jarvis/jarvis_c2rust/optimizer_progress.py +469 -0
  72. jarvis/jarvis_c2rust/optimizer_report.py +52 -0
  73. jarvis/jarvis_c2rust/optimizer_unsafe.py +309 -0
  74. jarvis/jarvis_c2rust/optimizer_utils.py +469 -0
  75. jarvis/jarvis_c2rust/optimizer_visibility.py +185 -0
  76. jarvis/jarvis_c2rust/scanner.py +229 -166
  77. jarvis/jarvis_c2rust/transpiler.py +531 -2732
  78. jarvis/jarvis_c2rust/transpiler_agents.py +503 -0
  79. jarvis/jarvis_c2rust/transpiler_build.py +1294 -0
  80. jarvis/jarvis_c2rust/transpiler_codegen.py +204 -0
  81. jarvis/jarvis_c2rust/transpiler_compile.py +146 -0
  82. jarvis/jarvis_c2rust/transpiler_config.py +178 -0
  83. jarvis/jarvis_c2rust/transpiler_context.py +122 -0
  84. jarvis/jarvis_c2rust/transpiler_executor.py +516 -0
  85. jarvis/jarvis_c2rust/transpiler_generation.py +278 -0
  86. jarvis/jarvis_c2rust/transpiler_git.py +163 -0
  87. jarvis/jarvis_c2rust/transpiler_mod_utils.py +225 -0
  88. jarvis/jarvis_c2rust/transpiler_modules.py +336 -0
  89. jarvis/jarvis_c2rust/transpiler_planning.py +394 -0
  90. jarvis/jarvis_c2rust/transpiler_review.py +1196 -0
  91. jarvis/jarvis_c2rust/transpiler_symbols.py +176 -0
  92. jarvis/jarvis_c2rust/utils.py +269 -79
  93. jarvis/jarvis_code_agent/after_change.py +233 -0
  94. jarvis/jarvis_code_agent/build_validation_config.py +37 -30
  95. jarvis/jarvis_code_agent/builtin_rules.py +68 -0
  96. jarvis/jarvis_code_agent/code_agent.py +976 -1517
  97. jarvis/jarvis_code_agent/code_agent_build.py +227 -0
  98. jarvis/jarvis_code_agent/code_agent_diff.py +246 -0
  99. jarvis/jarvis_code_agent/code_agent_git.py +525 -0
  100. jarvis/jarvis_code_agent/code_agent_impact.py +177 -0
  101. jarvis/jarvis_code_agent/code_agent_lint.py +283 -0
  102. jarvis/jarvis_code_agent/code_agent_llm.py +159 -0
  103. jarvis/jarvis_code_agent/code_agent_postprocess.py +105 -0
  104. jarvis/jarvis_code_agent/code_agent_prompts.py +46 -0
  105. jarvis/jarvis_code_agent/code_agent_rules.py +305 -0
  106. jarvis/jarvis_code_agent/code_analyzer/__init__.py +52 -48
  107. jarvis/jarvis_code_agent/code_analyzer/base_language.py +12 -10
  108. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +12 -11
  109. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +16 -12
  110. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +26 -17
  111. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +558 -104
  112. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +27 -16
  113. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +22 -18
  114. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +21 -16
  115. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +20 -16
  116. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +27 -16
  117. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +47 -23
  118. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +71 -37
  119. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +162 -35
  120. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +111 -57
  121. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +18 -12
  122. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +185 -183
  123. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +2 -1
  124. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +24 -15
  125. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +227 -141
  126. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +321 -247
  127. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +37 -29
  128. jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -13
  129. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +15 -9
  130. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +75 -45
  131. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +87 -52
  132. jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +84 -51
  133. jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +94 -64
  134. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +109 -71
  135. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +97 -63
  136. jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +103 -69
  137. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +271 -268
  138. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +76 -64
  139. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +92 -19
  140. jarvis/jarvis_code_agent/diff_visualizer.py +998 -0
  141. jarvis/jarvis_code_agent/lint.py +223 -524
  142. jarvis/jarvis_code_agent/rule_share_manager.py +158 -0
  143. jarvis/jarvis_code_agent/rules/clean_code.md +144 -0
  144. jarvis/jarvis_code_agent/rules/code_review.md +115 -0
  145. jarvis/jarvis_code_agent/rules/documentation.md +165 -0
  146. jarvis/jarvis_code_agent/rules/generate_rules.md +52 -0
  147. jarvis/jarvis_code_agent/rules/performance.md +158 -0
  148. jarvis/jarvis_code_agent/rules/refactoring.md +139 -0
  149. jarvis/jarvis_code_agent/rules/security.md +160 -0
  150. jarvis/jarvis_code_agent/rules/tdd.md +78 -0
  151. jarvis/jarvis_code_agent/test_rules/cpp_test.md +118 -0
  152. jarvis/jarvis_code_agent/test_rules/go_test.md +98 -0
  153. jarvis/jarvis_code_agent/test_rules/java_test.md +99 -0
  154. jarvis/jarvis_code_agent/test_rules/javascript_test.md +113 -0
  155. jarvis/jarvis_code_agent/test_rules/php_test.md +117 -0
  156. jarvis/jarvis_code_agent/test_rules/python_test.md +91 -0
  157. jarvis/jarvis_code_agent/test_rules/ruby_test.md +102 -0
  158. jarvis/jarvis_code_agent/test_rules/rust_test.md +86 -0
  159. jarvis/jarvis_code_agent/utils.py +36 -26
  160. jarvis/jarvis_code_analysis/checklists/loader.py +21 -21
  161. jarvis/jarvis_code_analysis/code_review.py +64 -33
  162. jarvis/jarvis_data/config_schema.json +285 -192
  163. jarvis/jarvis_git_squash/main.py +8 -6
  164. jarvis/jarvis_git_utils/git_commiter.py +53 -76
  165. jarvis/jarvis_mcp/__init__.py +5 -2
  166. jarvis/jarvis_mcp/sse_mcp_client.py +40 -30
  167. jarvis/jarvis_mcp/stdio_mcp_client.py +27 -19
  168. jarvis/jarvis_mcp/streamable_mcp_client.py +35 -26
  169. jarvis/jarvis_memory_organizer/memory_organizer.py +78 -55
  170. jarvis/jarvis_methodology/main.py +48 -39
  171. jarvis/jarvis_multi_agent/__init__.py +56 -23
  172. jarvis/jarvis_multi_agent/main.py +15 -18
  173. jarvis/jarvis_platform/base.py +179 -111
  174. jarvis/jarvis_platform/human.py +27 -16
  175. jarvis/jarvis_platform/kimi.py +52 -45
  176. jarvis/jarvis_platform/openai.py +101 -40
  177. jarvis/jarvis_platform/registry.py +51 -33
  178. jarvis/jarvis_platform/tongyi.py +68 -38
  179. jarvis/jarvis_platform/yuanbao.py +59 -43
  180. jarvis/jarvis_platform_manager/main.py +68 -76
  181. jarvis/jarvis_platform_manager/service.py +24 -14
  182. jarvis/jarvis_rag/README_CONFIG.md +314 -0
  183. jarvis/jarvis_rag/README_DYNAMIC_LOADING.md +311 -0
  184. jarvis/jarvis_rag/README_ONLINE_MODELS.md +230 -0
  185. jarvis/jarvis_rag/__init__.py +57 -4
  186. jarvis/jarvis_rag/cache.py +3 -1
  187. jarvis/jarvis_rag/cli.py +48 -68
  188. jarvis/jarvis_rag/embedding_interface.py +39 -0
  189. jarvis/jarvis_rag/embedding_manager.py +7 -230
  190. jarvis/jarvis_rag/embeddings/__init__.py +41 -0
  191. jarvis/jarvis_rag/embeddings/base.py +114 -0
  192. jarvis/jarvis_rag/embeddings/cohere.py +66 -0
  193. jarvis/jarvis_rag/embeddings/edgefn.py +117 -0
  194. jarvis/jarvis_rag/embeddings/local.py +260 -0
  195. jarvis/jarvis_rag/embeddings/openai.py +62 -0
  196. jarvis/jarvis_rag/embeddings/registry.py +293 -0
  197. jarvis/jarvis_rag/llm_interface.py +8 -6
  198. jarvis/jarvis_rag/query_rewriter.py +8 -9
  199. jarvis/jarvis_rag/rag_pipeline.py +61 -52
  200. jarvis/jarvis_rag/reranker.py +7 -75
  201. jarvis/jarvis_rag/reranker_interface.py +32 -0
  202. jarvis/jarvis_rag/rerankers/__init__.py +41 -0
  203. jarvis/jarvis_rag/rerankers/base.py +109 -0
  204. jarvis/jarvis_rag/rerankers/cohere.py +67 -0
  205. jarvis/jarvis_rag/rerankers/edgefn.py +140 -0
  206. jarvis/jarvis_rag/rerankers/jina.py +79 -0
  207. jarvis/jarvis_rag/rerankers/local.py +89 -0
  208. jarvis/jarvis_rag/rerankers/registry.py +293 -0
  209. jarvis/jarvis_rag/retriever.py +58 -43
  210. jarvis/jarvis_sec/__init__.py +66 -141
  211. jarvis/jarvis_sec/agents.py +21 -17
  212. jarvis/jarvis_sec/analysis.py +80 -33
  213. jarvis/jarvis_sec/checkers/__init__.py +7 -13
  214. jarvis/jarvis_sec/checkers/c_checker.py +356 -164
  215. jarvis/jarvis_sec/checkers/rust_checker.py +47 -29
  216. jarvis/jarvis_sec/cli.py +43 -21
  217. jarvis/jarvis_sec/clustering.py +430 -272
  218. jarvis/jarvis_sec/file_manager.py +99 -55
  219. jarvis/jarvis_sec/parsers.py +9 -6
  220. jarvis/jarvis_sec/prompts.py +4 -3
  221. jarvis/jarvis_sec/report.py +44 -22
  222. jarvis/jarvis_sec/review.py +180 -107
  223. jarvis/jarvis_sec/status.py +50 -41
  224. jarvis/jarvis_sec/types.py +3 -0
  225. jarvis/jarvis_sec/utils.py +160 -83
  226. jarvis/jarvis_sec/verification.py +411 -181
  227. jarvis/jarvis_sec/workflow.py +132 -21
  228. jarvis/jarvis_smart_shell/main.py +28 -41
  229. jarvis/jarvis_stats/cli.py +14 -12
  230. jarvis/jarvis_stats/stats.py +28 -19
  231. jarvis/jarvis_stats/storage.py +14 -8
  232. jarvis/jarvis_stats/visualizer.py +12 -7
  233. jarvis/jarvis_tools/base.py +5 -2
  234. jarvis/jarvis_tools/clear_memory.py +13 -9
  235. jarvis/jarvis_tools/cli/main.py +23 -18
  236. jarvis/jarvis_tools/edit_file.py +572 -873
  237. jarvis/jarvis_tools/execute_script.py +10 -7
  238. jarvis/jarvis_tools/file_analyzer.py +7 -8
  239. jarvis/jarvis_tools/meta_agent.py +287 -0
  240. jarvis/jarvis_tools/methodology.py +5 -3
  241. jarvis/jarvis_tools/read_code.py +305 -1438
  242. jarvis/jarvis_tools/read_symbols.py +50 -17
  243. jarvis/jarvis_tools/read_webpage.py +19 -18
  244. jarvis/jarvis_tools/registry.py +435 -156
  245. jarvis/jarvis_tools/retrieve_memory.py +16 -11
  246. jarvis/jarvis_tools/save_memory.py +8 -6
  247. jarvis/jarvis_tools/search_web.py +31 -31
  248. jarvis/jarvis_tools/sub_agent.py +32 -28
  249. jarvis/jarvis_tools/sub_code_agent.py +44 -60
  250. jarvis/jarvis_tools/task_list_manager.py +1811 -0
  251. jarvis/jarvis_tools/virtual_tty.py +29 -19
  252. jarvis/jarvis_utils/__init__.py +4 -0
  253. jarvis/jarvis_utils/builtin_replace_map.py +2 -1
  254. jarvis/jarvis_utils/clipboard.py +9 -8
  255. jarvis/jarvis_utils/collections.py +331 -0
  256. jarvis/jarvis_utils/config.py +699 -194
  257. jarvis/jarvis_utils/dialogue_recorder.py +294 -0
  258. jarvis/jarvis_utils/embedding.py +6 -3
  259. jarvis/jarvis_utils/file_processors.py +7 -1
  260. jarvis/jarvis_utils/fzf.py +9 -3
  261. jarvis/jarvis_utils/git_utils.py +71 -42
  262. jarvis/jarvis_utils/globals.py +116 -32
  263. jarvis/jarvis_utils/http.py +6 -2
  264. jarvis/jarvis_utils/input.py +318 -83
  265. jarvis/jarvis_utils/jsonnet_compat.py +119 -104
  266. jarvis/jarvis_utils/methodology.py +37 -28
  267. jarvis/jarvis_utils/output.py +201 -44
  268. jarvis/jarvis_utils/utils.py +986 -628
  269. {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/METADATA +49 -33
  270. jarvis_ai_assistant-1.0.2.dist-info/RECORD +304 -0
  271. jarvis/jarvis_code_agent/code_analyzer/structured_code.py +0 -556
  272. jarvis/jarvis_tools/generate_new_tool.py +0 -205
  273. jarvis/jarvis_tools/lsp_client.py +0 -1552
  274. jarvis/jarvis_tools/rewrite_file.py +0 -105
  275. jarvis_ai_assistant-0.7.16.dist-info/RECORD +0 -218
  276. {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/WHEEL +0 -0
  277. {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/entry_points.txt +0 -0
  278. {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/licenses/LICENSE +0 -0
  279. {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,278 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 代码生成模块
4
+ """
5
+
6
+ import json
7
+ import re
8
+ from pathlib import Path
9
+ from typing import List
10
+ from typing import cast
11
+
12
+ from jarvis.jarvis_utils.output import PrettyOutput
13
+
14
+ from jarvis.jarvis_c2rust.models import FnRecord
15
+
16
+
17
+ class GenerationManager:
18
+ """代码生成管理器"""
19
+
20
+ def __init__(
21
+ self,
22
+ project_root: Path,
23
+ crate_dir: Path,
24
+ data_dir: Path,
25
+ disabled_libraries: List[str],
26
+ extract_compile_flags_func,
27
+ append_additional_notes_func,
28
+ is_root_symbol_func,
29
+ get_generation_agent_func,
30
+ compose_prompt_with_context_func,
31
+ check_and_handle_test_deletion_func,
32
+ get_crate_commit_hash_func,
33
+ ensure_top_level_pub_mod_func,
34
+ ) -> None:
35
+ self.project_root = project_root
36
+ self.crate_dir = crate_dir
37
+ self.data_dir = data_dir
38
+ self.disabled_libraries = disabled_libraries
39
+ self.extract_compile_flags = extract_compile_flags_func
40
+ self.append_additional_notes = append_additional_notes_func
41
+ self.is_root_symbol = is_root_symbol_func
42
+ self.get_generation_agent = get_generation_agent_func
43
+ self.compose_prompt_with_context = compose_prompt_with_context_func
44
+ self.check_and_handle_test_deletion = check_and_handle_test_deletion_func
45
+ self.get_crate_commit_hash = get_crate_commit_hash_func
46
+ self.ensure_top_level_pub_mod = ensure_top_level_pub_mod_func
47
+
48
+ def build_generate_impl_prompt(
49
+ self,
50
+ rec: FnRecord,
51
+ c_code: str,
52
+ module: str,
53
+ rust_sig: str,
54
+ unresolved: List[str],
55
+ ) -> str:
56
+ """
57
+ 构建代码生成提示词。
58
+
59
+ 返回完整的提示词字符串。
60
+ """
61
+ symbols_path = str((self.data_dir / "symbols.jsonl").resolve())
62
+ is_root = self.is_root_symbol(rec)
63
+ # 获取 C 源文件位置信息
64
+ c_file_location = ""
65
+ if hasattr(rec, "file") and rec.file:
66
+ if (
67
+ hasattr(rec, "start_line")
68
+ and hasattr(rec, "end_line")
69
+ and rec.start_line
70
+ and rec.end_line
71
+ ):
72
+ c_file_location = f"{rec.file}:{rec.start_line}-{rec.end_line}"
73
+ else:
74
+ c_file_location = str(rec.file)
75
+
76
+ requirement_lines = [
77
+ f"目标:在 {module} 中,使用 TDD 方法为 C 函数 {rec.qname or rec.name} 生成 Rust 实现。",
78
+ f"函数签名:{rust_sig}",
79
+ f"crate 目录:{self.crate_dir.resolve()}",
80
+ f"C 工程目录:{self.project_root.resolve()}",
81
+ *([f"C 源文件位置:{c_file_location}"] if c_file_location else []),
82
+ *(
83
+ ["根符号要求:必须使用 `pub` 关键字,模块必须在 src/lib.rs 中导出"]
84
+ if is_root
85
+ else []
86
+ ),
87
+ "",
88
+ "【TDD 流程】",
89
+ "1. Red:先写测试(#[cfg(test)] mod tests),基于 C 函数行为设计测试用例",
90
+ "2. Green:编写实现使测试通过,确保与 C 语义等价",
91
+ "3. Refactor:优化代码,保持测试通过",
92
+ " - 如果发现现有测试用例有错误,优先修复测试用例而不是删除",
93
+ "",
94
+ "【核心要求】",
95
+ "- 先写测试再写实现,测试必须可编译通过",
96
+ "- ⚠️ 重要:如果发现现有测试用例有错误(如测试逻辑错误、断言不正确、测试用例设计不当等),应该修复测试用例而不是删除它们。只有在测试用例完全重复、过时或确实不需要时才能删除。",
97
+ "- ⚠️ 重要:不要将正式代码写到测试区域。所有正式的函数实现、类型定义、常量等都应该写在 `#[cfg(test)] mod tests { ... }` 块之外。测试代码(测试函数、测试辅助函数等)才应该写在 `#[cfg(test)] mod tests { ... }` 块内部。",
98
+ "- ⚠️ 重要:测试用例必须尽可能完备,因为后续 review 阶段会检测测试用例完备性,避免返工。测试用例应该包括:",
99
+ " * 主要功能路径的测试:覆盖函数的核心功能和预期行为",
100
+ " * 边界情况测试:空输入(空字符串、空数组、空指针等)、极值输入(最大值、最小值、零值等)、边界值(数组边界、字符串长度边界等)、特殊值(负数、NaN、无穷大等,如果适用)",
101
+ " * 错误情况测试:如果 C 实现有错误处理(如返回错误码、设置 errno 等),测试用例应该覆盖这些错误情况。如果 Rust 实现使用 Result<T, E> 或 Option<T> 处理错误,测试用例应该验证错误情况",
102
+ " * 与 C 实现行为一致性:测试用例的预期结果应该与 C 实现的行为一致",
103
+ " * 测试用例质量:测试名称清晰、断言适当、测试逻辑正确,能够真正验证函数的功能",
104
+ " * 注意:如果函数是资源释放类函数(如 fclose、free 等),在 Rust 中通过 RAII 自动管理,测试用例可以非常简单(如仅验证函数可以调用而不崩溃),这是可以接受的",
105
+ "- 禁止使用 todo!/unimplemented!,必须实现完整功能",
106
+ "- 使用 Rust 原生类型(i32/u32、&str/String、&[T]/&mut [T]、Result<T,E>),避免 C 风格类型",
107
+ '- 禁止使用 extern "C",使用标准 Rust 调用约定',
108
+ "- 保持最小变更,避免无关重构",
109
+ "- 注释使用中文,禁止 use ...::* 通配导入",
110
+ "- 资源释放类函数(fclose/free 等)可通过 RAII 自动管理,提供空实现并在文档中说明",
111
+ *(
112
+ [f"- 禁用库:{', '.join(self.disabled_libraries)}"]
113
+ if self.disabled_libraries
114
+ else []
115
+ ),
116
+ "",
117
+ "【依赖处理】",
118
+ "- 检查依赖函数是否已实现,未实现的需一并补齐(遵循 TDD:先测试后实现)",
119
+ "- 使用 read_symbols/read_code 获取 C 源码",
120
+ "- 优先处理底层依赖,确保所有测试通过",
121
+ "",
122
+ "【工具】",
123
+ f'- read_symbols: {{"symbols_file": "{symbols_path}", "symbols": [...]}}',
124
+ "- read_code: 读取 C 源码或 Rust 模块",
125
+ "",
126
+ *([f"未转换符号:{', '.join(unresolved)}"] if unresolved else []),
127
+ "",
128
+ "C 源码:",
129
+ "<C_SOURCE>",
130
+ c_code,
131
+ "</C_SOURCE>",
132
+ "",
133
+ "签名参考:",
134
+ json.dumps(
135
+ {
136
+ "signature": getattr(rec, "signature", ""),
137
+ "params": getattr(rec, "params", None),
138
+ },
139
+ ensure_ascii=False,
140
+ indent=2,
141
+ ),
142
+ "",
143
+ "仅输出补丁,不要解释。",
144
+ ]
145
+ # 若存在库替代上下文,则附加到实现提示中,便于生成器参考(多库组合、参考API、备注等)
146
+ librep_ctx = None
147
+ try:
148
+ librep_ctx = getattr(rec, "lib_replacement", None)
149
+ except Exception:
150
+ librep_ctx = None
151
+ if isinstance(librep_ctx, dict) and librep_ctx:
152
+ requirement_lines.extend(
153
+ [
154
+ "",
155
+ "库替代上下文(若存在):",
156
+ json.dumps(librep_ctx, ensure_ascii=False, indent=2),
157
+ "",
158
+ ]
159
+ )
160
+ # 添加编译参数(如果存在)
161
+ compile_flags = None
162
+ if hasattr(rec, "file") and rec.file:
163
+ compile_flags = self.extract_compile_flags(rec.file)
164
+ if compile_flags:
165
+ requirement_lines.extend(
166
+ [
167
+ "",
168
+ "C文件编译参数(来自 compile_commands.json):",
169
+ compile_flags,
170
+ "",
171
+ ]
172
+ )
173
+ prompt = "\n".join(requirement_lines)
174
+ return cast(str, self.append_additional_notes(prompt))
175
+
176
+ def extract_rust_fn_name_from_sig(self, rust_sig: str) -> str:
177
+ """
178
+ 从 rust 签名中提取函数名,支持生命周期参数和泛型参数。
179
+ 例如: 'pub fn foo(a: i32) -> i32 { ... }' -> 'foo'
180
+ 例如: 'pub fn foo<'a>(bzf: &'a mut BzFile) -> Result<&'a [u8], BzError>' -> 'foo'
181
+ """
182
+ # 支持生命周期参数和泛型参数:fn name<'a, T>(...)
183
+ m = re.search(
184
+ r"\bfn\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:<[^>]+>)?\s*\(", rust_sig or ""
185
+ )
186
+ return m.group(1) if m else ""
187
+
188
+ def codeagent_generate_impl(
189
+ self,
190
+ rec: FnRecord,
191
+ c_code: str,
192
+ module: str,
193
+ rust_sig: str,
194
+ unresolved: List[str],
195
+ ) -> None:
196
+ """
197
+ 使用 CodeAgent 生成/更新目标模块中的函数实现。
198
+ 约束:最小变更,生成可编译的占位实现,尽可能保留后续细化空间。
199
+ """
200
+ # 构建提示词
201
+ prompt = self.build_generate_impl_prompt(
202
+ rec, c_code, module, rust_sig, unresolved
203
+ )
204
+
205
+ # 确保目标模块文件存在(提高补丁应用与实现落盘的确定性)
206
+ try:
207
+ mp = Path(module)
208
+ if not mp.is_absolute():
209
+ mp = (self.crate_dir / module).resolve()
210
+ mp.parent.mkdir(parents=True, exist_ok=True)
211
+ if not mp.exists():
212
+ try:
213
+ mp.write_text(
214
+ "// Auto-created by c2rust transpiler\n", encoding="utf-8"
215
+ )
216
+ PrettyOutput.auto_print(
217
+ f"✅ [c2rust-transpiler][gen] auto-created module file: {mp}"
218
+ )
219
+ except Exception:
220
+ pass
221
+ except Exception:
222
+ pass
223
+
224
+ # 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
225
+ # 记录运行前的 commit
226
+ before_commit = self.get_crate_commit_hash()
227
+ # 使用生成 Agent(可以复用)
228
+ agent = self.get_generation_agent()
229
+ agent.run(
230
+ self.compose_prompt_with_context(prompt),
231
+ prefix="[c2rust-transpiler][gen]",
232
+ suffix="",
233
+ )
234
+
235
+ # 检测并处理测试代码删除
236
+ if self.check_and_handle_test_deletion(before_commit, agent):
237
+ # 如果回退了,需要重新运行 agent
238
+ PrettyOutput.auto_print(
239
+ "⚠️ [c2rust-transpiler][gen] 检测到测试代码删除问题,已回退,重新运行 agent"
240
+ )
241
+ before_commit = self.get_crate_commit_hash()
242
+ # 重试时使用相同的 prompt(已包含 C 源文件位置信息)
243
+ agent.run(
244
+ self.compose_prompt_with_context(prompt),
245
+ prefix="[c2rust-transpiler][gen][retry]",
246
+ suffix="",
247
+ )
248
+ # 再次检测
249
+ if self.check_and_handle_test_deletion(before_commit, agent):
250
+ PrettyOutput.auto_print(
251
+ "❌ [c2rust-transpiler][gen] 再次检测到测试代码删除问题,已回退"
252
+ )
253
+
254
+ # 如果是根符号,确保其模块在 lib.rs 中被暴露
255
+ if self.is_root_symbol(rec):
256
+ try:
257
+ mp = Path(module)
258
+ crate_root = self.crate_dir.resolve()
259
+ rel = (
260
+ mp.resolve().relative_to(crate_root)
261
+ if mp.is_absolute()
262
+ else Path(module)
263
+ )
264
+ rel_s = str(rel).replace("\\", "/")
265
+ if rel_s.startswith("./"):
266
+ rel_s = rel_s[2:]
267
+ if rel_s.startswith("src/"):
268
+ parts = rel_s[len("src/") :].strip("/").split("/")
269
+ if parts and parts[0]:
270
+ top_mod = parts[0]
271
+ # 过滤掉 "mod" 关键字和 .rs 文件
272
+ if top_mod != "mod" and not top_mod.endswith(".rs"):
273
+ self.ensure_top_level_pub_mod(top_mod)
274
+ PrettyOutput.auto_print(
275
+ f"📋 [c2rust-transpiler][gen] 根符号 {rec.qname or rec.name} 的模块 {top_mod} 已在 lib.rs 中暴露"
276
+ )
277
+ except Exception:
278
+ pass
@@ -0,0 +1,163 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Git 操作模块
4
+ """
5
+
6
+ import subprocess
7
+ from typing import Optional
8
+
9
+ from jarvis.jarvis_utils.git_utils import get_latest_commit_hash
10
+
11
+
12
+ class GitManager:
13
+ """Git 操作管理器"""
14
+
15
+ def __init__(self, crate_dir: str) -> None:
16
+ self.crate_dir = crate_dir
17
+
18
+ def get_crate_commit_hash(self) -> Optional[str]:
19
+ """获取 crate 目录的当前 commit id"""
20
+ try:
21
+ # 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
22
+ commit_hash = get_latest_commit_hash()
23
+ return commit_hash if commit_hash else None
24
+ except Exception:
25
+ return None
26
+
27
+ def get_git_diff(self, base_commit: Optional[str] = None) -> str:
28
+ """
29
+ 获取 git diff,显示从 base_commit 到当前工作区的变更
30
+
31
+ 参数:
32
+ base_commit: 基准 commit hash,如果为 None 则使用 HEAD
33
+
34
+ 返回:
35
+ str: git diff 内容,如果获取失败则返回空字符串
36
+ """
37
+ try:
38
+ # 检查是否是 git 仓库
39
+ check_git_result = subprocess.run(
40
+ ["git", "rev-parse", "--git-dir"],
41
+ capture_output=True,
42
+ text=True,
43
+ check=False,
44
+ cwd=self.crate_dir,
45
+ )
46
+ if check_git_result.returncode != 0:
47
+ # 不是 git 仓库,无法获取 diff
48
+ return ""
49
+
50
+ if base_commit:
51
+ # 先检查 base_commit 是否存在
52
+ check_result = subprocess.run(
53
+ ["git", "rev-parse", "--verify", base_commit],
54
+ capture_output=True,
55
+ text=True,
56
+ check=False,
57
+ cwd=self.crate_dir,
58
+ )
59
+ if check_result.returncode != 0:
60
+ # base_commit 不存在,使用 HEAD 作为基准
61
+ base_commit = None
62
+
63
+ # 检查是否有 HEAD
64
+ head_check = subprocess.run(
65
+ ["git", "rev-parse", "--verify", "HEAD"],
66
+ capture_output=True,
67
+ text=True,
68
+ check=False,
69
+ cwd=self.crate_dir,
70
+ )
71
+ has_head = head_check.returncode == 0
72
+
73
+ # 临时暂存新增文件以便获取完整的 diff
74
+ subprocess.run(
75
+ ["git", "add", "-N", "."],
76
+ check=False,
77
+ stdout=subprocess.DEVNULL,
78
+ stderr=subprocess.DEVNULL,
79
+ cwd=self.crate_dir,
80
+ )
81
+
82
+ try:
83
+ if base_commit:
84
+ # 获取从 base_commit 到当前工作区的差异
85
+ result = subprocess.run(
86
+ ["git", "diff", base_commit],
87
+ capture_output=True,
88
+ text=True,
89
+ encoding="utf-8",
90
+ errors="replace",
91
+ check=False,
92
+ cwd=self.crate_dir,
93
+ )
94
+ elif has_head:
95
+ # 获取从 HEAD 到当前工作区的差异
96
+ result = subprocess.run(
97
+ ["git", "diff", "HEAD"],
98
+ capture_output=True,
99
+ text=True,
100
+ encoding="utf-8",
101
+ errors="replace",
102
+ check=False,
103
+ cwd=self.crate_dir,
104
+ )
105
+ else:
106
+ # 空仓库,获取工作区差异
107
+ result = subprocess.run(
108
+ ["git", "diff"],
109
+ capture_output=True,
110
+ text=True,
111
+ encoding="utf-8",
112
+ errors="replace",
113
+ check=False,
114
+ cwd=self.crate_dir,
115
+ )
116
+
117
+ return result.stdout or "" if result.returncode == 0 else ""
118
+ finally:
119
+ # 重置暂存区
120
+ subprocess.run(
121
+ ["git", "reset"],
122
+ check=False,
123
+ stdout=subprocess.DEVNULL,
124
+ stderr=subprocess.DEVNULL,
125
+ cwd=self.crate_dir,
126
+ )
127
+ except Exception:
128
+ return ""
129
+
130
+ def reset_to_commit(self, commit_hash: str) -> bool:
131
+ """回退 crate 目录到指定的 commit"""
132
+ try:
133
+ # 由于 transpile() 开始时已切换到 crate 目录,此处无需再次切换
134
+ # 检查是否是 git 仓库
135
+ result = subprocess.run(
136
+ ["git", "rev-parse", "--git-dir"],
137
+ capture_output=True,
138
+ text=True,
139
+ check=False,
140
+ )
141
+ if result.returncode != 0:
142
+ # 不是 git 仓库,无法回退
143
+ return False
144
+
145
+ # 执行硬重置
146
+ result = subprocess.run(
147
+ ["git", "reset", "--hard", commit_hash],
148
+ capture_output=True,
149
+ text=True,
150
+ check=False,
151
+ )
152
+ if result.returncode == 0:
153
+ # 清理未跟踪的文件
154
+ subprocess.run(
155
+ ["git", "clean", "-fd"],
156
+ capture_output=True,
157
+ text=True,
158
+ check=False,
159
+ )
160
+ return True
161
+ return False
162
+ except Exception:
163
+ return False
@@ -0,0 +1,225 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from pathlib import Path
5
+
6
+ from jarvis.jarvis_utils.output import PrettyOutput
7
+
8
+
9
+ def ensure_top_level_pub_mod(crate_dir: Path, mod_name: str) -> None:
10
+ """
11
+ 在 src/lib.rs 中确保存在 `pub mod <mod_name>;`
12
+ - 如已存在 `pub mod`,不做改动
13
+ - 如存在 `mod <mod_name>;`,升级为 `pub mod <mod_name>;`
14
+ - 如都不存在,则在文件末尾追加一行 `pub mod <mod_name>;`
15
+ - 最小改动,不覆盖其他内容
16
+ """
17
+ try:
18
+ if not mod_name or mod_name in ("lib", "main", "mod"):
19
+ return
20
+ lib_rs = (crate_dir / "src" / "lib.rs").resolve()
21
+ lib_rs.parent.mkdir(parents=True, exist_ok=True)
22
+ if not lib_rs.exists():
23
+ try:
24
+ lib_rs.write_text(
25
+ "// Auto-generated by c2rust transpiler\n", encoding="utf-8"
26
+ )
27
+ PrettyOutput.auto_print(
28
+ f"✅ [c2rust-transpiler][mod] 已创建 src/lib.rs: {lib_rs}"
29
+ )
30
+ except Exception:
31
+ return
32
+ txt = lib_rs.read_text(encoding="utf-8", errors="replace")
33
+ pub_pat = re.compile(rf"(?m)^\s*pub\s+mod\s+{re.escape(mod_name)}\s*;\s*$")
34
+ mod_pat = re.compile(rf"(?m)^\s*mod\s+{re.escape(mod_name)}\s*;\s*$")
35
+ if pub_pat.search(txt):
36
+ return
37
+ if mod_pat.search(txt):
38
+ # 升级为 pub mod(保留原缩进)
39
+ def _repl(m):
40
+ line = m.group(0)
41
+ match = re.match(r"^(\s*)", line)
42
+ ws = match.group(1) if match is not None else ""
43
+ return f"{ws}pub mod {mod_name};"
44
+
45
+ new_txt = mod_pat.sub(_repl, txt, count=1)
46
+ else:
47
+ new_txt = txt.rstrip() + f"\npub mod {mod_name};\n"
48
+ lib_rs.write_text(new_txt, encoding="utf-8")
49
+ PrettyOutput.auto_print(
50
+ f"✅ [c2rust-transpiler][mod] updated src/lib.rs: ensured pub mod {mod_name}"
51
+ )
52
+ except Exception:
53
+ # 保持稳健,失败不阻塞主流程
54
+ pass
55
+
56
+
57
+ def ensure_mod_rs_decl(dir_path: Path, child_mod: str) -> None:
58
+ """
59
+ 在 dir_path/mod.rs 中确保存在 `pub mod <child_mod>;`
60
+ - 如存在 `mod <child_mod>;` 则升级为 `pub mod <child_mod>;`
61
+ - 如均不存在则在文件末尾追加 `pub mod <child_mod>;`
62
+ - 最小改动,不覆盖其他内容
63
+ """
64
+ try:
65
+ if not child_mod or child_mod in ("lib", "main", "mod"):
66
+ return
67
+ mod_rs = (dir_path / "mod.rs").resolve()
68
+ mod_rs.parent.mkdir(parents=True, exist_ok=True)
69
+ if not mod_rs.exists():
70
+ try:
71
+ mod_rs.write_text(
72
+ "// Auto-generated by c2rust transpiler\n", encoding="utf-8"
73
+ )
74
+ PrettyOutput.auto_print(f"✅ [c2rust-transpiler][mod] 已创建 {mod_rs}")
75
+ except Exception:
76
+ return
77
+ txt = mod_rs.read_text(encoding="utf-8", errors="replace")
78
+ pub_pat = re.compile(rf"(?m)^\s*pub\s+mod\s+{re.escape(child_mod)}\s*;\s*$")
79
+ mod_pat = re.compile(rf"(?m)^\s*mod\s+{re.escape(child_mod)}\s*;\s*$")
80
+ if pub_pat.search(txt):
81
+ return
82
+ if mod_pat.search(txt):
83
+ # 升级为 pub mod(保留原缩进)
84
+ def _repl(m):
85
+ line = m.group(0)
86
+ match = re.match(r"^(\s*)", line)
87
+ ws = match.group(1) if match is not None else ""
88
+ return f"{ws}pub mod {child_mod};"
89
+
90
+ new_txt = mod_pat.sub(_repl, txt, count=1)
91
+ else:
92
+ new_txt = txt.rstrip() + f"\npub mod {child_mod};\n"
93
+ mod_rs.write_text(new_txt, encoding="utf-8")
94
+ PrettyOutput.auto_print(
95
+ f"✅ [c2rust-transpiler][mod] updated {mod_rs}: ensured pub mod {child_mod}"
96
+ )
97
+ except Exception:
98
+ pass
99
+
100
+
101
+ def ensure_mod_chain_for_module(crate_dir: Path, module: str) -> None:
102
+ """
103
+ 根据目标模块文件,补齐从该文件所在目录向上的 mod.rs 声明链:
104
+ - 对于 src/foo/bar.rs:在 src/foo/mod.rs 确保 `pub mod bar;`
105
+ 并在上层 src/mod.rs(不修改)改为在 src/lib.rs 确保 `pub mod foo;`
106
+ - 对于 src/foo/bar/mod.rs:在 src/foo/mod.rs 确保 `pub mod bar;`
107
+ - 对于多级目录,逐级在上层 mod.rs 确保对子目录的 `pub mod <child>;`
108
+ """
109
+ try:
110
+ mp = Path(module)
111
+ base = mp
112
+ if not mp.is_absolute():
113
+ base = (crate_dir / module).resolve()
114
+ crate_root = crate_dir.resolve()
115
+ # 必须在 crate/src 下
116
+ rel = base.relative_to(crate_root)
117
+ rel_s = str(rel).replace("\\", "/")
118
+ if not rel_s.startswith("src/"):
119
+ return
120
+ # 计算起始目录与首个子模块名
121
+ inside = rel_s[len("src/") :].strip("/")
122
+ if not inside:
123
+ return
124
+ parts = [p for p in inside.split("/") if p] # 过滤空字符串
125
+ if parts[-1].endswith(".rs"):
126
+ if parts[-1] in ("lib.rs", "main.rs"):
127
+ return
128
+ child = parts[-1][:-3] # 去掉 .rs
129
+ # 过滤掉 "mod" 关键字
130
+ if child == "mod":
131
+ return
132
+ if len(parts) > 1:
133
+ start_dir = crate_root / "src" / "/".join(parts[:-1])
134
+ else:
135
+ start_dir = crate_root / "src"
136
+ # 确保 start_dir 在 crate/src 下
137
+ try:
138
+ start_dir_rel = start_dir.relative_to(crate_root)
139
+ if not str(start_dir_rel).replace("\\", "/").startswith("src/"):
140
+ return
141
+ except ValueError:
142
+ return
143
+ # 在当前目录的 mod.rs 确保 pub mod <child>
144
+ if start_dir.name != "src":
145
+ ensure_mod_rs_decl(start_dir, child)
146
+ # 向上逐级确保父目录对当前目录的 pub mod 声明
147
+ cur_dir = start_dir
148
+ else:
149
+ # 末尾为目录(mod.rs 情况):确保父目录对该目录 pub mod
150
+ if parts:
151
+ cur_dir = crate_root / "src" / "/".join(parts)
152
+ # 确保 cur_dir 在 crate/src 下
153
+ try:
154
+ cur_dir_rel = cur_dir.relative_to(crate_root)
155
+ if not str(cur_dir_rel).replace("\\", "/").startswith("src/"):
156
+ return
157
+ except ValueError:
158
+ return
159
+ else:
160
+ return
161
+ # 逐级向上到 src 根(不修改 src/mod.rs,顶层由 lib.rs 公开)
162
+ while True:
163
+ parent = cur_dir.parent
164
+ if not parent.exists():
165
+ break
166
+ # 确保不超过 crate 根目录
167
+ try:
168
+ parent.relative_to(crate_root)
169
+ except ValueError:
170
+ # parent 不在 crate_root 下,停止向上遍历
171
+ break
172
+ if parent.name == "src":
173
+ # 顶层由 ensure_top_level_pub_mod 负责
174
+ break
175
+ # 在 parent/mod.rs 确保 pub mod <cur_dir.name>
176
+ if cur_dir.name == "mod":
177
+ cur_dir = parent
178
+ continue
179
+ try:
180
+ parent_rel = parent.relative_to(crate_root)
181
+ if str(parent_rel).replace("\\", "/").startswith("src/"):
182
+ ensure_mod_rs_decl(parent, cur_dir.name)
183
+ except (ValueError, Exception):
184
+ # parent 不在 crate/src 下,跳过
185
+ break
186
+ cur_dir = parent
187
+ except Exception:
188
+ pass
189
+
190
+
191
+ def module_file_to_crate_path(crate_dir: Path, module: str) -> str:
192
+ """
193
+ 将模块文件路径转换为 crate 路径前缀:
194
+ - src/lib.rs -> crate
195
+ - src/foo/mod.rs -> crate::foo
196
+ - src/foo/bar.rs -> crate::foo::bar
197
+ 支持绝对路径:若 module 为绝对路径且位于 crate 根目录下,会自动转换为相对路径再解析;
198
+ 其它(无法解析为 crate/src 下的路径)统一返回 'crate'
199
+ """
200
+ mod = str(module).strip()
201
+ # 若传入绝对路径且在 crate_dir 下,转换为相对路径以便后续按 src/ 前缀解析
202
+ try:
203
+ mp = Path(mod)
204
+ if mp.is_absolute():
205
+ try:
206
+ rel = mp.resolve().relative_to(crate_dir.resolve())
207
+ mod = str(rel).replace("\\", "/")
208
+ except Exception:
209
+ # 绝对路径不在 crate_dir 下,保持原样
210
+ pass
211
+ except Exception:
212
+ pass
213
+ # 规范化 ./ 前缀
214
+ if mod.startswith("./"):
215
+ mod = mod[2:]
216
+ # 仅处理位于 src/ 下的模块文件
217
+ if not mod.startswith("src/"):
218
+ return "crate"
219
+ p = mod[len("src/") :]
220
+ if p.endswith("mod.rs"):
221
+ p = p[: -len("mod.rs")]
222
+ elif p.endswith(".rs"):
223
+ p = p[: -len(".rs")]
224
+ p = p.strip("/")
225
+ return "crate" if not p else "crate::" + p.replace("/", "::")