devops-analyzer 0.1.1__tar.gz → 0.1.3__tar.gz
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.
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/MANIFEST.in +1 -0
- {devops_analyzer-0.1.1/devops_analyzer.egg-info → devops_analyzer-0.1.3}/PKG-INFO +2 -3
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/analysis.py +92 -28
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/cli.py +2 -1
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/config_cmd.py +2 -1
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/extract.py +6 -16
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/models/__init__.py +6 -4
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/scan/scan.cpp +1 -1
- devops_analyzer-0.1.3/devops/tree_sitter/__init__.py +22 -0
- devops_analyzer-0.1.3/devops/tree_sitter/build_sources.py +84 -0
- devops_analyzer-0.1.3/devops/tree_sitter/languages.h +30 -0
- devops_analyzer-0.1.3/devops/ui.py +41 -0
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3/devops_analyzer.egg-info}/PKG-INFO +2 -3
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops_analyzer.egg-info/SOURCES.txt +4 -0
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops_analyzer.egg-info/requires.txt +0 -1
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/pyproject.toml +3 -4
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/requirements.txt +0 -1
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/setup.py +38 -0
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/LICENSE +0 -0
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/README.md +0 -0
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/__init__.py +0 -0
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/__main__.py +0 -0
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/config.py +0 -0
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/models/base.py +0 -0
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/models/openai.py +0 -0
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/models/zhipuai.py +0 -0
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/scan/__init__.py +0 -0
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/scan/scan.h +0 -0
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/scan/scan_bindings.cpp +0 -0
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops_analyzer.egg-info/dependency_links.txt +0 -0
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops_analyzer.egg-info/entry_points.txt +0 -0
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops_analyzer.egg-info/not-zip-safe +0 -0
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops_analyzer.egg-info/top_level.txt +0 -0
- {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/setup.cfg +0 -0
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devops-analyzer
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: 扫描源码、提取代码块、大模型分析并生成 README.md
|
|
5
5
|
Author: ZHUHK
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/dadaozhichen/OpenDevOps
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Requires-Dist: tree-sitter<0.22,>=0.21.0
|
|
12
|
-
Requires-Dist: tree-sitter-languages>=1.10.0
|
|
13
12
|
Requires-Dist: openai>=1.40.0
|
|
14
13
|
Provides-Extra: zhipuai
|
|
15
14
|
Requires-Dist: zhipuai>=2.0.0; extra == "zhipuai"
|
|
@@ -5,22 +5,90 @@ from __future__ import annotations
|
|
|
5
5
|
from collections import defaultdict
|
|
6
6
|
from datetime import datetime, timezone
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Any
|
|
8
|
+
from typing import Any, Optional
|
|
9
9
|
|
|
10
10
|
from devops.extract import CodeBlock, extract_blocks_from_paths
|
|
11
11
|
from devops.models import BaseChatModel, get_chat_model
|
|
12
|
+
from devops.ui import thinking
|
|
12
13
|
|
|
13
14
|
MAX_CODE_LINES = 80
|
|
14
15
|
MAX_CHARS_PER_REQUEST = 24_000
|
|
15
16
|
|
|
16
|
-
SYSTEM_PROMPT = """
|
|
17
|
-
请根据给出的代码块,输出简洁、结构化的中文分析,包含:
|
|
18
|
-
1. 文件/模块职责概述
|
|
19
|
-
2. 主要函数/类的作用
|
|
20
|
-
3. 潜在问题或改进建议(如有)
|
|
21
|
-
4. 关键依赖或调用关系(如能看出)
|
|
17
|
+
SYSTEM_PROMPT = """你是一名资深技术文档工程师,擅长从代码和项目结构中提炼出清晰、实用的技术文档。
|
|
22
18
|
|
|
23
|
-
|
|
19
|
+
## 输入说明
|
|
20
|
+
- 用户提供的是 Tree-sitter 提取的代码块(函数、类、方法等),通常不是完整仓库。
|
|
21
|
+
- 单块源码可能被截断(约 80 行以内),省略处会标注。
|
|
22
|
+
- 可能缺少完整目录树、部署脚本或配置文件正文;不得编造,无法确认时写「需结合仓库补充」或「根据代码推断(待确认)」。
|
|
23
|
+
|
|
24
|
+
## 通用输出要求
|
|
25
|
+
1. 使用 Markdown,简体中文,专业、客观、条理清晰。
|
|
26
|
+
2. 不要大段粘贴源码;最多引用函数/方法签名(例如 `def create_user(name: str) -> int`)。
|
|
27
|
+
3. 对公共 API(对外导出的函数、类、CLI 入口)优先用表格:名称 | 参数 | 返回值 | 说明。
|
|
28
|
+
4. 区分「事实」(代码中可见)与「推断」(架构意图、部署方式)。
|
|
29
|
+
5. 具体章节结构以用户消息中的任务说明为准;单文件分析不写整篇项目 README,项目级总览再写完整章节。
|
|
30
|
+
|
|
31
|
+
## 禁止
|
|
32
|
+
- 虚构不存在的模块、环境变量、命令或依赖。"""
|
|
33
|
+
|
|
34
|
+
FILE_ANALYSIS_PROMPT_PREFIX = """请分析以下源文件中的代码块,输出 Markdown(不要外层 # 标题,从 ### 小节开始)。
|
|
35
|
+
|
|
36
|
+
必须包含:
|
|
37
|
+
### 职责概述
|
|
38
|
+
本文件在项目中的作用(1–3 句)。
|
|
39
|
+
|
|
40
|
+
### 主要符号
|
|
41
|
+
列出重要的类、函数、常量;对公共接口用表格(名称 | 参数 | 返回值 | 说明)。
|
|
42
|
+
|
|
43
|
+
### 依赖与调用
|
|
44
|
+
本文件依赖的项目内模块或第三方库(仅从代码块可见部分归纳)。
|
|
45
|
+
|
|
46
|
+
### 配置与安全
|
|
47
|
+
若出现配置、密钥、路径相关逻辑则简要说明;否则写「本文件未体现」。
|
|
48
|
+
|
|
49
|
+
### 注意事项
|
|
50
|
+
至少 1 条边界情况、潜在风险或改进点;无明显问题时写「未发现明显问题」。
|
|
51
|
+
|
|
52
|
+
若本文件几乎是项目唯一核心文件,可在末尾增加 ### 项目补充,用 3–5 条要点概括安装/运行/架构(能推断则写,否则标待补充)。
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
OVERVIEW_PROMPT_PREFIX = """以下是多个源文件的逐项分析。请生成项目级 Markdown 总览(将写入 README 的「概览」一节)。
|
|
58
|
+
|
|
59
|
+
必须按顺序包含以下章节(使用 ## 标题):
|
|
60
|
+
|
|
61
|
+
## 项目概述
|
|
62
|
+
一句话定位 + 核心能力列表(3–6 条)。
|
|
63
|
+
|
|
64
|
+
## 快速开始
|
|
65
|
+
安装、配置、最小运行示例;信息不足时列出「待补充」项。
|
|
66
|
+
|
|
67
|
+
## 架构设计
|
|
68
|
+
模块划分与核心数据流(可用 Mermaid 代码块)。
|
|
69
|
+
|
|
70
|
+
## 模块与文件关系
|
|
71
|
+
各目录/文件如何协作;归纳即可,勿重复粘贴各文件分析全文。
|
|
72
|
+
|
|
73
|
+
## 配置说明
|
|
74
|
+
汇总配置项、环境变量、配置文件路径。
|
|
75
|
+
|
|
76
|
+
## 依赖关系
|
|
77
|
+
关键第三方库与项目内模块依赖(归纳,不罗列依赖文件全文)。
|
|
78
|
+
|
|
79
|
+
## 部署与运维
|
|
80
|
+
构建、启动、日志/输出要点;无法推断则标明「需补充」。
|
|
81
|
+
|
|
82
|
+
## 常见问题与注意事项
|
|
83
|
+
至少 2 条潜在坑点、扫描/分析范围限制或工具约束。
|
|
84
|
+
|
|
85
|
+
## 架构与质量建议
|
|
86
|
+
共性问题或改进建议(3–5 条,可选)。
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
逐项分析如下:
|
|
90
|
+
|
|
91
|
+
"""
|
|
24
92
|
|
|
25
93
|
|
|
26
94
|
def _truncate_code(code: str, max_lines: int = MAX_CODE_LINES) -> str:
|
|
@@ -44,16 +112,17 @@ def _format_blocks_for_prompt(blocks: list[CodeBlock]) -> str:
|
|
|
44
112
|
|
|
45
113
|
|
|
46
114
|
def _chat(model: BaseChatModel, prompt: str) -> str:
|
|
47
|
-
|
|
115
|
+
with thinking():
|
|
116
|
+
return model.chat(SYSTEM_PROMPT, prompt, temperature=0.2)
|
|
48
117
|
|
|
49
118
|
|
|
50
119
|
def analyze_blocks(
|
|
51
120
|
blocks: list[CodeBlock],
|
|
52
121
|
*,
|
|
53
|
-
provider: str
|
|
54
|
-
api_key: str
|
|
55
|
-
model_name: str
|
|
56
|
-
base_url: str
|
|
122
|
+
provider: Optional[str] = None,
|
|
123
|
+
api_key: Optional[str] = None,
|
|
124
|
+
model_name: Optional[str] = None,
|
|
125
|
+
base_url: Optional[str] = None,
|
|
57
126
|
) -> dict[str, Any]:
|
|
58
127
|
"""按文件分批调用大模型,并生成总览。"""
|
|
59
128
|
chat_model = get_chat_model(
|
|
@@ -83,6 +152,7 @@ def analyze_blocks(
|
|
|
83
152
|
body = body[:MAX_CHARS_PER_REQUEST] + "\n\n...(内容过长已截断)"
|
|
84
153
|
|
|
85
154
|
prompt = (
|
|
155
|
+
f"{FILE_ANALYSIS_PROMPT_PREFIX}"
|
|
86
156
|
f"文件路径: {file_path}\n"
|
|
87
157
|
f"语言: {file_blocks[0].language}\n"
|
|
88
158
|
f"代码块数量: {len(file_blocks)}\n\n"
|
|
@@ -98,13 +168,7 @@ def analyze_blocks(
|
|
|
98
168
|
)
|
|
99
169
|
file_summaries.append(f"【{file_path}】\n{analysis}")
|
|
100
170
|
|
|
101
|
-
overview_prompt = (
|
|
102
|
-
"以下是多个源文件的逐项分析,请生成一份项目级总览(中文):\n"
|
|
103
|
-
"1. 项目整体结构\n"
|
|
104
|
-
"2. 各文件之间的关系\n"
|
|
105
|
-
"3. 共性问题或架构建议\n\n"
|
|
106
|
-
+ "\n\n---\n\n".join(file_summaries)
|
|
107
|
-
)
|
|
171
|
+
overview_prompt = OVERVIEW_PROMPT_PREFIX + "\n\n---\n\n".join(file_summaries)
|
|
108
172
|
if len(overview_prompt) > MAX_CHARS_PER_REQUEST:
|
|
109
173
|
overview_prompt = overview_prompt[:MAX_CHARS_PER_REQUEST] + "\n...(已截断)"
|
|
110
174
|
|
|
@@ -127,10 +191,10 @@ def analyze_blocks(
|
|
|
127
191
|
def analyze_files(
|
|
128
192
|
paths: list[str],
|
|
129
193
|
*,
|
|
130
|
-
provider: str
|
|
131
|
-
api_key: str
|
|
132
|
-
model_name: str
|
|
133
|
-
base_url: str
|
|
194
|
+
provider: Optional[str] = None,
|
|
195
|
+
api_key: Optional[str] = None,
|
|
196
|
+
model_name: Optional[str] = None,
|
|
197
|
+
base_url: Optional[str] = None,
|
|
134
198
|
) -> dict[str, Any]:
|
|
135
199
|
"""从文件路径列表提取代码块并分析。"""
|
|
136
200
|
blocks = extract_blocks_from_paths(paths)
|
|
@@ -146,10 +210,10 @@ def analyze_files(
|
|
|
146
210
|
def analyze_folder(
|
|
147
211
|
folder: str,
|
|
148
212
|
*,
|
|
149
|
-
provider: str
|
|
150
|
-
api_key: str
|
|
151
|
-
model_name: str
|
|
152
|
-
base_url: str
|
|
213
|
+
provider: Optional[str] = None,
|
|
214
|
+
api_key: Optional[str] = None,
|
|
215
|
+
model_name: Optional[str] = None,
|
|
216
|
+
base_url: Optional[str] = None,
|
|
153
217
|
) -> dict[str, Any]:
|
|
154
218
|
"""扫描目录、提取代码块并分析。"""
|
|
155
219
|
from devops.scan import scan_code_files
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import argparse
|
|
6
6
|
import sys
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
from typing import Optional
|
|
8
9
|
|
|
9
10
|
from devops import __version__
|
|
10
11
|
from devops.analysis import analyze_folder, write_project_readme
|
|
@@ -159,7 +160,7 @@ def _run_analyze(args: argparse.Namespace) -> int:
|
|
|
159
160
|
return 1
|
|
160
161
|
|
|
161
162
|
|
|
162
|
-
def main(argv: list[str]
|
|
163
|
+
def main(argv: Optional[list[str]] = None) -> int:
|
|
163
164
|
argv = list(argv) if argv is not None else sys.argv[1:]
|
|
164
165
|
|
|
165
166
|
# devops /path/to/project -> 自动当作 analyze
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import getpass
|
|
6
6
|
import sys
|
|
7
|
+
from typing import Optional
|
|
7
8
|
|
|
8
9
|
from devops.config import (
|
|
9
10
|
CONFIG_FILE,
|
|
@@ -81,7 +82,7 @@ def run_config_interactive() -> int:
|
|
|
81
82
|
return 0
|
|
82
83
|
|
|
83
84
|
|
|
84
|
-
def run_config(argv: list[str]
|
|
85
|
+
def run_config(argv: Optional[list[str]] = None) -> int:
|
|
85
86
|
args = argv or []
|
|
86
87
|
if "--show" in args or "-s" in args:
|
|
87
88
|
return run_config_show()
|
|
@@ -4,10 +4,11 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from dataclasses import asdict, dataclass
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Iterator, Optional
|
|
7
|
+
from typing import Iterator, Optional, Union
|
|
8
8
|
|
|
9
9
|
from tree_sitter import Node, Tree
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
from devops.tree_sitter import get_parser
|
|
11
12
|
|
|
12
13
|
from devops.scan import scan_code_files
|
|
13
14
|
|
|
@@ -34,7 +35,6 @@ EXT_TO_LANGUAGE: dict[str, str] = {
|
|
|
34
35
|
".rb": "ruby",
|
|
35
36
|
".php": "php",
|
|
36
37
|
".cs": "c_sharp",
|
|
37
|
-
".swift": "swift",
|
|
38
38
|
".kt": "kotlin",
|
|
39
39
|
".scala": "scala",
|
|
40
40
|
".sh": "bash",
|
|
@@ -64,12 +64,8 @@ LANGUAGE_NODE_TYPES: dict[str, tuple[str, ...]] = {
|
|
|
64
64
|
"kotlin": ("function_declaration", "class_declaration"),
|
|
65
65
|
"scala": ("function_definition", "class_definition", "object_definition"),
|
|
66
66
|
"bash": ("function_definition",),
|
|
67
|
-
"swift": ("function_declaration", "class_declaration", "struct_declaration"),
|
|
68
67
|
}
|
|
69
68
|
|
|
70
|
-
_parser_cache: dict[str, object] = {}
|
|
71
|
-
|
|
72
|
-
|
|
73
69
|
@dataclass
|
|
74
70
|
class CodeBlock:
|
|
75
71
|
file_path: str
|
|
@@ -84,16 +80,10 @@ class CodeBlock:
|
|
|
84
80
|
return asdict(self)
|
|
85
81
|
|
|
86
82
|
|
|
87
|
-
def language_for_path(path: str
|
|
83
|
+
def language_for_path(path: Union[str, Path]) -> Optional[str]:
|
|
88
84
|
return EXT_TO_LANGUAGE.get(Path(path).suffix.lower())
|
|
89
85
|
|
|
90
86
|
|
|
91
|
-
def _get_parser(language: str):
|
|
92
|
-
if language not in _parser_cache:
|
|
93
|
-
_parser_cache[language] = get_parser(language)
|
|
94
|
-
return _parser_cache[language]
|
|
95
|
-
|
|
96
|
-
|
|
97
87
|
def _read_source(path: Path) -> Optional[bytes]:
|
|
98
88
|
try:
|
|
99
89
|
return path.read_bytes()
|
|
@@ -186,7 +176,7 @@ def _extract_from_tree(
|
|
|
186
176
|
return blocks
|
|
187
177
|
|
|
188
178
|
|
|
189
|
-
def extract_blocks_from_file(path: str
|
|
179
|
+
def extract_blocks_from_file(path: Union[str, Path]) -> list[CodeBlock]:
|
|
190
180
|
p = Path(path)
|
|
191
181
|
language = language_for_path(p)
|
|
192
182
|
if language is None:
|
|
@@ -197,7 +187,7 @@ def extract_blocks_from_file(path: str | Path) -> list[CodeBlock]:
|
|
|
197
187
|
return []
|
|
198
188
|
|
|
199
189
|
try:
|
|
200
|
-
tree =
|
|
190
|
+
tree = get_parser(language).parse(source)
|
|
201
191
|
except Exception:
|
|
202
192
|
return []
|
|
203
193
|
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
5
7
|
from devops.config import ModelConfig, list_providers
|
|
6
8
|
from devops.models.base import BaseChatModel
|
|
7
9
|
from devops.models.openai import OpenAIChatModel
|
|
@@ -24,10 +26,10 @@ def create_chat_model(config: ModelConfig) -> BaseChatModel:
|
|
|
24
26
|
|
|
25
27
|
def get_chat_model(
|
|
26
28
|
*,
|
|
27
|
-
provider: str
|
|
28
|
-
api_key: str
|
|
29
|
-
model: str
|
|
30
|
-
base_url: str
|
|
29
|
+
provider: Optional[str] = None,
|
|
30
|
+
api_key: Optional[str] = None,
|
|
31
|
+
model: Optional[str] = None,
|
|
32
|
+
base_url: Optional[str] = None,
|
|
31
33
|
) -> BaseChatModel:
|
|
32
34
|
from devops.config import load_config
|
|
33
35
|
|
|
@@ -63,7 +63,7 @@ bool isCodeFile(const fs::path& path) {
|
|
|
63
63
|
".c", ".cc", ".cpp", ".cxx", ".h", ".hpp",
|
|
64
64
|
".py", ".js", ".ts", ".tsx", ".jsx",
|
|
65
65
|
".java", ".go", ".rs", ".rb", ".php",
|
|
66
|
-
".cs", ".
|
|
66
|
+
".cs", ".m", ".mm",
|
|
67
67
|
".sh", ".sql", ".html", ".css",
|
|
68
68
|
".vue", ".kt", ".scala",
|
|
69
69
|
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from devops.tree_sitter.tree_sitter_native import get_parser as _native_get_parser
|
|
7
|
+
except ImportError as exc:
|
|
8
|
+
raise ImportError(
|
|
9
|
+
"devops.tree_sitter 原生扩展未安装。请先执行 "
|
|
10
|
+
"bash scripts/vendor_tree_sitter.sh,再运行 pip install -e ."
|
|
11
|
+
) from exc
|
|
12
|
+
|
|
13
|
+
_parser_cache: dict[str, object] = {}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_parser(language: str):
|
|
17
|
+
if language not in _parser_cache:
|
|
18
|
+
_parser_cache[language] = _native_get_parser(language)
|
|
19
|
+
return _parser_cache[language]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
__all__ = ["get_parser"]
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
# 与 setup.py 同级的项目根目录;setuptools 要求源路径为相对路径
|
|
7
|
+
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
8
|
+
VENDOR_ROOT = Path(__file__).resolve().parent / "vendor"
|
|
9
|
+
TREE_SITTER_CORE = VENDOR_ROOT / "tree-sitter"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _rel(path: Path) -> str:
|
|
13
|
+
return path.relative_to(PROJECT_ROOT).as_posix()
|
|
14
|
+
|
|
15
|
+
GRAMMAR_DIRS = (
|
|
16
|
+
"tree-sitter-bash",
|
|
17
|
+
"tree-sitter-c",
|
|
18
|
+
"tree-sitter-c-sharp",
|
|
19
|
+
"tree-sitter-cpp",
|
|
20
|
+
"tree-sitter-go",
|
|
21
|
+
"tree-sitter-java",
|
|
22
|
+
"tree-sitter-javascript",
|
|
23
|
+
"tree-sitter-kotlin",
|
|
24
|
+
"tree-sitter-php",
|
|
25
|
+
"tree-sitter-python",
|
|
26
|
+
"tree-sitter-ruby",
|
|
27
|
+
"tree-sitter-rust",
|
|
28
|
+
"tree-sitter-scala",
|
|
29
|
+
"tree-sitter-typescript",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# 部分 grammar 仓库将 src 放在子目录(如新版 php/typescript)
|
|
33
|
+
GRAMMAR_SRC_SUBDIRS: dict[str, tuple[str, ...]] = {
|
|
34
|
+
"tree-sitter-php": ("php/src", "src"),
|
|
35
|
+
"tree-sitter-typescript": ("typescript/src", "src"),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _grammar_src_dirs(grammar: str) -> tuple[Path, ...]:
|
|
40
|
+
root = VENDOR_ROOT / grammar
|
|
41
|
+
subdirs = GRAMMAR_SRC_SUBDIRS.get(grammar, ("src",))
|
|
42
|
+
return tuple(root / sub for sub in subdirs)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def vendor_ready() -> bool:
|
|
46
|
+
return (TREE_SITTER_CORE / "lib" / "include" / "tree_sitter" / "api.h").is_file()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def collect_sources() -> tuple[list[str], list[str]]:
|
|
50
|
+
if not vendor_ready():
|
|
51
|
+
raise FileNotFoundError(
|
|
52
|
+
"tree-sitter vendor 未就绪。请先运行: bash scripts/vendor_tree_sitter.sh"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
sources: list[str] = []
|
|
56
|
+
include_dirs: list[str] = [_rel(TREE_SITTER_CORE / "lib" / "include")]
|
|
57
|
+
|
|
58
|
+
def add_include(path: Path) -> None:
|
|
59
|
+
s = _rel(path)
|
|
60
|
+
if s not in include_dirs:
|
|
61
|
+
include_dirs.append(s)
|
|
62
|
+
|
|
63
|
+
for grammar in GRAMMAR_DIRS:
|
|
64
|
+
parser_c = None
|
|
65
|
+
src_dir = None
|
|
66
|
+
for candidate in _grammar_src_dirs(grammar):
|
|
67
|
+
path = candidate / "parser.c"
|
|
68
|
+
if path.is_file():
|
|
69
|
+
parser_c = path
|
|
70
|
+
src_dir = candidate
|
|
71
|
+
break
|
|
72
|
+
if parser_c is None:
|
|
73
|
+
raise FileNotFoundError(
|
|
74
|
+
f"缺少 grammar 源文件 parser.c,已检查: {_grammar_src_dirs(grammar)}"
|
|
75
|
+
)
|
|
76
|
+
sources.append(_rel(parser_c))
|
|
77
|
+
if (src_dir / "tree_sitter").is_dir():
|
|
78
|
+
add_include(src_dir)
|
|
79
|
+
for scanner_name in ("scanner.c", "scanner.cc"):
|
|
80
|
+
scanner = src_dir / scanner_name
|
|
81
|
+
if scanner.is_file():
|
|
82
|
+
sources.append(_rel(scanner))
|
|
83
|
+
|
|
84
|
+
return sources, include_dirs
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <string>
|
|
4
|
+
|
|
5
|
+
#include "tree_sitter/api.h"
|
|
6
|
+
|
|
7
|
+
#ifdef __cplusplus
|
|
8
|
+
extern "C" {
|
|
9
|
+
#endif
|
|
10
|
+
|
|
11
|
+
TSLanguage* tree_sitter_bash(void);
|
|
12
|
+
TSLanguage* tree_sitter_c(void);
|
|
13
|
+
TSLanguage* tree_sitter_c_sharp(void);
|
|
14
|
+
TSLanguage* tree_sitter_cpp(void);
|
|
15
|
+
TSLanguage* tree_sitter_go(void);
|
|
16
|
+
TSLanguage* tree_sitter_java(void);
|
|
17
|
+
TSLanguage* tree_sitter_javascript(void);
|
|
18
|
+
TSLanguage* tree_sitter_kotlin(void);
|
|
19
|
+
TSLanguage* tree_sitter_php(void);
|
|
20
|
+
TSLanguage* tree_sitter_python(void);
|
|
21
|
+
TSLanguage* tree_sitter_ruby(void);
|
|
22
|
+
TSLanguage* tree_sitter_rust(void);
|
|
23
|
+
TSLanguage* tree_sitter_scala(void);
|
|
24
|
+
TSLanguage* tree_sitter_typescript(void);
|
|
25
|
+
|
|
26
|
+
#ifdef __cplusplus
|
|
27
|
+
}
|
|
28
|
+
#endif
|
|
29
|
+
|
|
30
|
+
const TSLanguage* languageForName(const std::string& language);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""终端 UI 辅助(等待动画等)。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
import threading
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
from typing import Iterator
|
|
9
|
+
|
|
10
|
+
_SPINNER = ("⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@contextmanager
|
|
14
|
+
def thinking(message: str = "thinking") -> Iterator[None]:
|
|
15
|
+
"""在阻塞操作期间于 stderr 显示转圈与文案。"""
|
|
16
|
+
if not sys.stderr.isatty():
|
|
17
|
+
print(f"{message}...", file=sys.stderr, flush=True)
|
|
18
|
+
yield
|
|
19
|
+
return
|
|
20
|
+
|
|
21
|
+
stop = threading.Event()
|
|
22
|
+
prefix = f"{message} "
|
|
23
|
+
|
|
24
|
+
def _spin() -> None:
|
|
25
|
+
i = 0
|
|
26
|
+
while not stop.wait(0.08):
|
|
27
|
+
frame = _SPINNER[i % len(_SPINNER)]
|
|
28
|
+
sys.stderr.write(f"\r{frame} {prefix}")
|
|
29
|
+
sys.stderr.flush()
|
|
30
|
+
i += 1
|
|
31
|
+
|
|
32
|
+
thread = threading.Thread(target=_spin, daemon=True)
|
|
33
|
+
thread.start()
|
|
34
|
+
try:
|
|
35
|
+
yield
|
|
36
|
+
finally:
|
|
37
|
+
stop.set()
|
|
38
|
+
thread.join(timeout=0.5)
|
|
39
|
+
clear = "\r" + " " * (len(prefix) + 2) + "\r"
|
|
40
|
+
sys.stderr.write(clear)
|
|
41
|
+
sys.stderr.flush()
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devops-analyzer
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: 扫描源码、提取代码块、大模型分析并生成 README.md
|
|
5
5
|
Author: ZHUHK
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/dadaozhichen/OpenDevOps
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Requires-Dist: tree-sitter<0.22,>=0.21.0
|
|
12
|
-
Requires-Dist: tree-sitter-languages>=1.10.0
|
|
13
12
|
Requires-Dist: openai>=1.40.0
|
|
14
13
|
Provides-Extra: zhipuai
|
|
15
14
|
Requires-Dist: zhipuai>=2.0.0; extra == "zhipuai"
|
|
@@ -11,6 +11,7 @@ devops/cli.py
|
|
|
11
11
|
devops/config.py
|
|
12
12
|
devops/config_cmd.py
|
|
13
13
|
devops/extract.py
|
|
14
|
+
devops/ui.py
|
|
14
15
|
devops/models/__init__.py
|
|
15
16
|
devops/models/base.py
|
|
16
17
|
devops/models/openai.py
|
|
@@ -19,6 +20,9 @@ devops/scan/__init__.py
|
|
|
19
20
|
devops/scan/scan.cpp
|
|
20
21
|
devops/scan/scan.h
|
|
21
22
|
devops/scan/scan_bindings.cpp
|
|
23
|
+
devops/tree_sitter/__init__.py
|
|
24
|
+
devops/tree_sitter/build_sources.py
|
|
25
|
+
devops/tree_sitter/languages.h
|
|
22
26
|
devops_analyzer.egg-info/PKG-INFO
|
|
23
27
|
devops_analyzer.egg-info/SOURCES.txt
|
|
24
28
|
devops_analyzer.egg-info/dependency_links.txt
|
|
@@ -4,16 +4,15 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "devops-analyzer"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.3"
|
|
8
8
|
description = "扫描源码、提取代码块、大模型分析并生成 README.md"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
11
|
license = "MIT"
|
|
12
12
|
authors = [{ name = "ZHUHK" }]
|
|
13
13
|
dependencies = [
|
|
14
|
-
# tree-sitter-languages 尚未适配 py-tree-sitter 0.22
|
|
14
|
+
# tree-sitter-languages 尚未适配 py-tree-sitter 0.22+;grammar 由 devops.tree_sitter 原生扩展提供
|
|
15
15
|
"tree-sitter>=0.21.0,<0.22",
|
|
16
|
-
"tree-sitter-languages>=1.10.0",
|
|
17
16
|
"openai>=1.40.0",
|
|
18
17
|
]
|
|
19
18
|
|
|
@@ -1,10 +1,22 @@
|
|
|
1
|
+
import importlib.util
|
|
1
2
|
import shutil
|
|
2
3
|
import subprocess
|
|
3
4
|
import sys
|
|
5
|
+
from pathlib import Path
|
|
4
6
|
|
|
5
7
|
from setuptools import setup
|
|
6
8
|
from pybind11.setup_helpers import Pybind11Extension, build_ext
|
|
7
9
|
|
|
10
|
+
_spec = importlib.util.spec_from_file_location(
|
|
11
|
+
"tree_sitter_build_sources",
|
|
12
|
+
Path(__file__).parent / "devops/tree_sitter/build_sources.py",
|
|
13
|
+
)
|
|
14
|
+
_ts_build = importlib.util.module_from_spec(_spec)
|
|
15
|
+
assert _spec.loader is not None
|
|
16
|
+
_spec.loader.exec_module(_ts_build)
|
|
17
|
+
collect_sources = _ts_build.collect_sources
|
|
18
|
+
vendor_ready = _ts_build.vendor_ready
|
|
19
|
+
|
|
8
20
|
|
|
9
21
|
def platform_compile_args():
|
|
10
22
|
compile_args = []
|
|
@@ -42,6 +54,32 @@ ext_modules = [
|
|
|
42
54
|
),
|
|
43
55
|
]
|
|
44
56
|
|
|
57
|
+
if vendor_ready():
|
|
58
|
+
_ts_sources, _ts_include_dirs = collect_sources()
|
|
59
|
+
_ts_compile = list(_compile_args)
|
|
60
|
+
if sys.platform != "win32":
|
|
61
|
+
_ts_compile.append("-fvisibility=hidden")
|
|
62
|
+
ext_modules.append(
|
|
63
|
+
Pybind11Extension(
|
|
64
|
+
"devops.tree_sitter.tree_sitter_native",
|
|
65
|
+
[
|
|
66
|
+
"devops/tree_sitter/tree_sitter_bindings.cpp",
|
|
67
|
+
"devops/tree_sitter/languages.cpp",
|
|
68
|
+
*_ts_sources,
|
|
69
|
+
],
|
|
70
|
+
include_dirs=_ts_include_dirs,
|
|
71
|
+
cxx_std=17,
|
|
72
|
+
extra_compile_args=_ts_compile,
|
|
73
|
+
extra_link_args=_link_args,
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
else:
|
|
77
|
+
print(
|
|
78
|
+
"warning: tree-sitter vendor 缺失,跳过 devops.tree_sitter 扩展。"
|
|
79
|
+
"运行 bash scripts/vendor_tree_sitter.sh 后重新 pip install -e .",
|
|
80
|
+
file=sys.stderr,
|
|
81
|
+
)
|
|
82
|
+
|
|
45
83
|
setup(
|
|
46
84
|
cmdclass={"build_ext": build_ext},
|
|
47
85
|
ext_modules=ext_modules,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops_analyzer.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|