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.
Files changed (34) hide show
  1. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/MANIFEST.in +1 -0
  2. {devops_analyzer-0.1.1/devops_analyzer.egg-info → devops_analyzer-0.1.3}/PKG-INFO +2 -3
  3. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/analysis.py +92 -28
  4. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/cli.py +2 -1
  5. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/config_cmd.py +2 -1
  6. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/extract.py +6 -16
  7. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/models/__init__.py +6 -4
  8. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/scan/scan.cpp +1 -1
  9. devops_analyzer-0.1.3/devops/tree_sitter/__init__.py +22 -0
  10. devops_analyzer-0.1.3/devops/tree_sitter/build_sources.py +84 -0
  11. devops_analyzer-0.1.3/devops/tree_sitter/languages.h +30 -0
  12. devops_analyzer-0.1.3/devops/ui.py +41 -0
  13. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3/devops_analyzer.egg-info}/PKG-INFO +2 -3
  14. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops_analyzer.egg-info/SOURCES.txt +4 -0
  15. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops_analyzer.egg-info/requires.txt +0 -1
  16. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/pyproject.toml +3 -4
  17. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/requirements.txt +0 -1
  18. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/setup.py +38 -0
  19. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/LICENSE +0 -0
  20. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/README.md +0 -0
  21. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/__init__.py +0 -0
  22. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/__main__.py +0 -0
  23. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/config.py +0 -0
  24. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/models/base.py +0 -0
  25. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/models/openai.py +0 -0
  26. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/models/zhipuai.py +0 -0
  27. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/scan/__init__.py +0 -0
  28. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/scan/scan.h +0 -0
  29. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops/scan/scan_bindings.cpp +0 -0
  30. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops_analyzer.egg-info/dependency_links.txt +0 -0
  31. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops_analyzer.egg-info/entry_points.txt +0 -0
  32. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops_analyzer.egg-info/not-zip-safe +0 -0
  33. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/devops_analyzer.egg-info/top_level.txt +0 -0
  34. {devops_analyzer-0.1.1 → devops_analyzer-0.1.3}/setup.cfg +0 -0
@@ -1,2 +1,3 @@
1
1
  include LICENSE README.md requirements.txt
2
2
  include devops/scan/scan.h
3
+ include devops/tree_sitter/languages.h
@@ -1,15 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-analyzer
3
- Version: 0.1.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.11
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
- return model.chat(SYSTEM_PROMPT, prompt, temperature=0.2)
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 | None = None,
54
- api_key: str | None = None,
55
- model_name: str | None = None,
56
- base_url: str | None = None,
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 | None = None,
131
- api_key: str | None = None,
132
- model_name: str | None = None,
133
- base_url: str | None = None,
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 | None = None,
150
- api_key: str | None = None,
151
- model_name: str | None = None,
152
- base_url: str | None = None,
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] | None = None) -> int:
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] | None = None) -> int:
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
- from tree_sitter_languages import get_parser
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 | Path) -> Optional[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 | Path) -> list[CodeBlock]:
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 = _get_parser(language).parse(source)
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 | None = None,
28
- api_key: str | None = None,
29
- model: str | None = None,
30
- base_url: str | None = None,
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", ".swift", ".m", ".mm",
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.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.11
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
@@ -1,5 +1,4 @@
1
1
  tree-sitter<0.22,>=0.21.0
2
- tree-sitter-languages>=1.10.0
3
2
  openai>=1.40.0
4
3
 
5
4
  [all]
@@ -4,16 +4,15 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devops-analyzer"
7
- version = "0.1.1"
7
+ version = "0.1.3"
8
8
  description = "扫描源码、提取代码块、大模型分析并生成 README.md"
9
9
  readme = "README.md"
10
- requires-python = ">=3.11"
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+ Language API,见 grantjenks/py-tree-sitter-languages#77
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,5 +1,4 @@
1
1
  # 推荐使用: pip install -e .
2
2
  tree-sitter>=0.21.0,<0.22
3
- tree-sitter-languages>=1.10.0
4
3
  openai>=1.40.0
5
4
  pybind11>=2.11
@@ -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