kcode-pi 0.1.0
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.
- package/README.md +358 -0
- package/dist/cli/kcode.d.ts +15 -0
- package/dist/cli/kcode.js +153 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +7 -0
- package/docs/KCODE_DISTRIBUTION.md +91 -0
- package/extensions/kingdee-harness.ts +180 -0
- package/extensions/kingdee-header.ts +122 -0
- package/extensions/kingdee-tools.ts +379 -0
- package/knowledge/.backup/v1.0.0/version.json +10 -0
- package/knowledge/cangqiong/product-notes.md +15 -0
- package/knowledge/common/business-flows.md +115 -0
- package/knowledge/common/config-guides.md +110 -0
- package/knowledge/common/error-patterns.md +170 -0
- package/knowledge/common/implementation.md +144 -0
- package/knowledge/cosmic/hard-constraints.md +38 -0
- package/knowledge/cosmic/ksql-datafix.md +34 -0
- package/knowledge/cosmic/platform-baseline.md +32 -0
- package/knowledge/cosmic/plugin-decision-matrix.md +40 -0
- package/knowledge/cosmic/review-checklist.md +40 -0
- package/knowledge/cosmic/unittest.md +35 -0
- package/knowledge/enterprise/api-reference.md +186 -0
- package/knowledge/enterprise/code-patterns.md +217 -0
- package/knowledge/enterprise/plugin-lifecycle.md +188 -0
- package/knowledge/enterprise/tables.json +159 -0
- package/knowledge/flagship/api-reference.md +237 -0
- package/knowledge/flagship/code-patterns.md +246 -0
- package/knowledge/flagship/cosmic-platform-note.md +15 -0
- package/knowledge/flagship/plugin-lifecycle.md +248 -0
- package/knowledge/flagship/tables.json +159 -0
- package/knowledge/version.json +10 -0
- package/knowledge/xinghan/product-notes.md +15 -0
- package/package.json +71 -0
- package/prompts/kd-discuss.md +11 -0
- package/prompts/kd-execute.md +12 -0
- package/prompts/kd-plan.md +12 -0
- package/prompts/kd-ship.md +12 -0
- package/prompts/kd-spec.md +12 -0
- package/prompts/kd-verify.md +12 -0
- package/skills/kd-check/SKILL.md +26 -0
- package/skills/kd-cosmic-dev/SKILL.md +82 -0
- package/skills/kd-cosmic-review/SKILL.md +90 -0
- package/skills/kd-cosmic-unittest/SKILL.md +92 -0
- package/skills/kd-debug/SKILL.md +30 -0
- package/skills/kd-discuss/SKILL.md +24 -0
- package/skills/kd-execute/SKILL.md +22 -0
- package/skills/kd-gen/SKILL.md +34 -0
- package/skills/kd-ksql/SKILL.md +86 -0
- package/skills/kd-plan/SKILL.md +24 -0
- package/skills/kd-ship/SKILL.md +22 -0
- package/skills/kd-spec/SKILL.md +24 -0
- package/skills/kd-verify/SKILL.md +22 -0
- package/themes/kcode-dark.json +81 -0
- package/vendor/kingdee-skills/cosmic-unittest/SKILL.md +788 -0
- package/vendor/kingdee-skills/cosmic-unittest/author-cache.json +5 -0
- package/vendor/kingdee-skills/cosmic-unittest/cosmic-unittest-skill-overview.html +746 -0
- package/vendor/kingdee-skills/cosmic-unittest/examples/business-test.md +205 -0
- package/vendor/kingdee-skills/cosmic-unittest/examples/common-test.md +257 -0
- package/vendor/kingdee-skills/cosmic-unittest/examples/formplugin-test.md +560 -0
- package/vendor/kingdee-skills/cosmic-unittest/examples/op-plugin-test.md +231 -0
- package/vendor/kingdee-skills/cosmic-unittest/examples/validator-test.md +232 -0
- package/vendor/kingdee-skills/cosmic-unittest/patterns/business-helper.md +184 -0
- package/vendor/kingdee-skills/cosmic-unittest/patterns/common-module.md +355 -0
- package/vendor/kingdee-skills/cosmic-unittest/patterns/convert-plugin.md +130 -0
- package/vendor/kingdee-skills/cosmic-unittest/patterns/formplugin.md +235 -0
- package/vendor/kingdee-skills/cosmic-unittest/patterns/op-plugin.md +226 -0
- package/vendor/kingdee-skills/cosmic-unittest/patterns/validator.md +206 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/SKILL.md +674 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/advanced-scenario-checklist.md +307 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/algox-performance-checklist.md +129 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/coding-standard-checklist.md +491 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/cosmic-api-checklist.md +285 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/data-access-checklist.md +261 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/data-transaction-checklist.md +390 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/domain-logic-checklist.md +295 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/form-plugin-checklist.md +508 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/infra-checklist.md +254 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/ksql-checklist.md +305 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/lifecycle-checklist.md +298 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/operation-plugin-checklist.md +442 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/test-mock-checklist.md +120 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/references/ui-performance-checklist.md +320 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/scripts/pattern-matcher.py +336 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/scripts/review-score-calculator.py +121 -0
- package/vendor/kingdee-skills/ok-cosmic/CHANGELOG.md +295 -0
- package/vendor/kingdee-skills/ok-cosmic/README.md +460 -0
- package/vendor/kingdee-skills/ok-cosmic/SKILL.md +287 -0
- package/vendor/kingdee-skills/ok-cosmic/agents/openai.yaml +17 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/BatchImportPluginTemplate.java +93 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/BillPlugInTemplate.java +156 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/ConvertPlugInTemplate.java +255 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/FormPluginTemplate.java +597 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/IWorkflowPluginTemplate.java +91 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/ListPluginTemplate.java +194 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/OpPluginTemplate.java +201 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/OpenApiControllerTemplate.java +103 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/PrintPluginTemplate.java +95 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/ReportFormPluginTemplate.java +257 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/ReportListDataPluginTemplate.java +70 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/StandardTreeListPluginTemplate.java +130 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/TaskTemplate.java +80 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/TreeListPluginTemplate.java +152 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/WriteBackPlugInTemplate.java +286 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/attachment/AttachmentUploadBindSample.java +93 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/botp/BotpTracePushSample.java +168 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/botp/SampleConvertPlugin.java +223 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/cache/SampleCacheUsage.java +218 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/concurrent/SampleThreadPoolBatch.java +156 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/data/DynamicObjectCrudSample.java +205 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/data/DynamicObjectOpsSample.java +100 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/BeforeOperationConfirmSample.java +217 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/ConfirmDialogSample.java +131 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/EntryRowCalculateSample.java +116 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/F7FilterSample.java +134 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/GetAndSetValueSample.java +176 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/HyperlinkJumpSample.java +124 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/OpenBillModalSample.java +253 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/ReturnParentDataSample.java +295 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/TreeControlSample.java +140 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/form/ViewControlOpsSample.java +132 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/list/ListPluginBasicSample.java +170 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/list/ListPreOpenFilterSample.java +68 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/message/MessageNotifySample.java +95 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/mq/SampleMQConsumer.java +198 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/mq/sample_mq.xml +15 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/operation/OpAddValidatorsSample.java +137 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/operation/OperationOptionBridgeSample.java +228 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/package-info.java +19 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/query/BaseDataQuerySample.java +194 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/query/BatchQuerySample.java +368 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/query/DataSetQueryStatSample.java +131 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/report/SampleReportFormPlugin.java +179 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/report/SampleReportListDataPlugin.java +616 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/snippets-guide.md +64 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/task/ScheduleTaskSample.java +160 -0
- package/vendor/kingdee-skills/ok-cosmic/assets/snippets/workflow/SampleWorkflowPlugin.java +302 -0
- package/vendor/kingdee-skills/ok-cosmic/manifest.json +78 -0
- package/vendor/kingdee-skills/ok-cosmic/ok-cosmic-intro.html +903 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/attachment-api.md +114 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/botp-convert.md +98 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/dynamic-object.md +113 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/entity-metadata.md +123 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/event-lifecycle.md +184 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/flex-prop.md +114 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/form-utils.md +133 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/operate-chain.md +159 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/plugin-base.md +218 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/query-dataset.md +149 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/request-context.md +88 -0
- package/vendor/kingdee-skills/ok-cosmic/references/adv/view-handler.md +157 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-bill.md +76 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-botp.md +70 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-form.md +165 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-import.md +69 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-list.md +227 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-openapi.md +112 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-operation.md +135 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-print.md +65 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-report-data.md +64 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-report-form.md +90 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-task.md +62 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-tree-list.md +71 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-workflow.md +82 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/plugin/plugin-writeback.md +71 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-algo.md +67 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-cache.md +63 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-dynamic-model-svc.md +82 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-dynamic-object.md +70 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-entity-model.md +61 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-exception.md +64 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-file.md +63 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-id.md +47 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-lock.md +61 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-log.md +63 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-network-control.md +70 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-orm-access.md +78 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-request-context.md +62 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-threadpool.md +63 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-tx.md +64 -0
- package/vendor/kingdee-skills/ok-cosmic/references/base/sdk/sdk-utils.md +67 -0
- package/vendor/kingdee-skills/ok-cosmic/requirements.txt +2 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/a-layer-rules.json +24 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/anti-patterns.md +48 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/cheat-sheet.md +256 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/coding-preferences.md +140 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/constraints.md +61 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/decision-matrix.md +222 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/intent-routing.md +94 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/platform-baseline.md +69 -0
- package/vendor/kingdee-skills/ok-cosmic/rules/post-check.md +109 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/config_loader.py +204 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-api-knowledge.py +910 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-basedata-query.py +359 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-config-check.py +181 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-extpoints-query.py +389 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-form-metadata.py +856 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-post-check.py +262 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-post-lint.py +293 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/__init__.py +2 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/base.py +393 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/resource_check.py +176 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/scene_check.py +375 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/style_check.py +434 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/verify_check.py +36 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/route_client.py +186 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/script_utils.py +40 -0
- package/vendor/kingdee-skills/ok-cosmic/scripts/sqlite_cache.py +142 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/cuslib/kd-cd-cosmic-commons.jar +0 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/cuslib/kd-cd-cosmic-features.jar +0 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/ok-cosmic-docs.db +0 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/ok-cosmic.json +13 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/setup-mac.sh +18 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/setup-windows.bat +53 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/setup.jar +0 -0
- package/vendor/kingdee-skills/ok-ksql/SKILL.md +81 -0
- package/vendor/kingdee-skills/ok-ksql/agents/openai.yaml +7 -0
- package/vendor/kingdee-skills/ok-ksql/manifest.json +14 -0
- package/vendor/kingdee-skills/ok-ksql/references/ksql-datafix.md +452 -0
- package/vendor/kingdee-skills/ok-ksql/scripts/ksql_lint.py +363 -0
|
@@ -0,0 +1,910 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# SPDX-License-Identifier: NOASSERTION
|
|
3
|
+
"""
|
|
4
|
+
cosmic-api-knowledge.py — Offline Cosmic API knowledge graph query tool.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python3 cosmic-api-knowledge.py --config ok-cosmic.json search <queryA>,<queryB> # batch search
|
|
8
|
+
python3 cosmic-api-knowledge.py --config ok-cosmic.json search <query...>
|
|
9
|
+
python3 cosmic-api-knowledge.py --config ok-cosmic.json search <query...> --class-prefix kd.bos.servicehelper
|
|
10
|
+
python3 cosmic-api-knowledge.py --config ok-cosmic.json search-method <queryA>,<queryB> # batch search-method
|
|
11
|
+
python3 cosmic-api-knowledge.py --config ok-cosmic.json search-method <query...>
|
|
12
|
+
python3 cosmic-api-knowledge.py --config ok-cosmic.json detail <full.class.Name1>,<full.class.Name2> # batch detail
|
|
13
|
+
python3 cosmic-api-knowledge.py --config ok-cosmic.json detail <full.class.Name>
|
|
14
|
+
python3 cosmic-api-knowledge.py --config ok-cosmic.json detail <full.class.Name> --method <keyword>
|
|
15
|
+
python3 cosmic-api-knowledge.py --config ok-cosmic.json detail <full.class.Name> --method <keyword> --declared-only
|
|
16
|
+
python3 cosmic-api-knowledge.py --config ok-cosmic.json detail <full.class.Name> --method <keyword> --compact
|
|
17
|
+
|
|
18
|
+
What it provides:
|
|
19
|
+
1. Fuzzy / regex class search against ok-cosmic-knowledge.db
|
|
20
|
+
2. Cross-class method search with ranking and package/category filters
|
|
21
|
+
3. Class detail lookup with signatures, return types, comments and inherited methods
|
|
22
|
+
4. Compact method fact output for downstream AI consumption
|
|
23
|
+
|
|
24
|
+
What it does NOT do:
|
|
25
|
+
- It does not inspect live jars directly; it only queries the built knowledge database
|
|
26
|
+
- It does not verify runtime availability of a class or method in the current environment
|
|
27
|
+
- It does not rebuild the knowledge database; use ok-cosmic-knowledge for that
|
|
28
|
+
|
|
29
|
+
Prerequisites:
|
|
30
|
+
- A valid ok-cosmic.json or equivalent graph config
|
|
31
|
+
- A built ok-cosmic-knowledge.db reachable from config / environment / project paths
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
import json
|
|
35
|
+
import sys
|
|
36
|
+
import os
|
|
37
|
+
import argparse
|
|
38
|
+
import re
|
|
39
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
from typing import Any, Dict, List, Optional, Sequence, Set, Tuple
|
|
42
|
+
|
|
43
|
+
from config_loader import load_project_config
|
|
44
|
+
from script_utils import FriendlyArgumentParser, run_cli
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
CHEAT_SHEET_PATH = Path(__file__).resolve().parent.parent / "rules" / "cheat-sheet.md"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _load_cheat_sheet_index() -> Tuple[Set[str], Set[Tuple[str, str]]]:
|
|
51
|
+
"""解析 cheat-sheet.md,提取已收录的类简名和 (类简名, 方法名) 对。"""
|
|
52
|
+
classes: Set[str] = set()
|
|
53
|
+
methods: Set[Tuple[str, str]] = set()
|
|
54
|
+
try:
|
|
55
|
+
text = CHEAT_SHEET_PATH.read_text(encoding="utf-8")
|
|
56
|
+
except (FileNotFoundError, OSError):
|
|
57
|
+
return classes, methods
|
|
58
|
+
|
|
59
|
+
in_code_block = False
|
|
60
|
+
for line in text.splitlines():
|
|
61
|
+
stripped = line.strip()
|
|
62
|
+
if stripped.startswith("```"):
|
|
63
|
+
in_code_block = not in_code_block
|
|
64
|
+
continue
|
|
65
|
+
if not in_code_block or stripped.startswith("//") or stripped.startswith("import "):
|
|
66
|
+
continue
|
|
67
|
+
# 匹配 ClassName.methodName( 模式
|
|
68
|
+
for m in re.finditer(r'\b([A-Z][A-Za-z0-9_]+)\.([a-z][A-Za-z0-9_]*)\s*\(', stripped):
|
|
69
|
+
cls_name, method_name = m.group(1), m.group(2)
|
|
70
|
+
classes.add(cls_name)
|
|
71
|
+
methods.add((cls_name, method_name))
|
|
72
|
+
# 匹配 new ClassName( 模式
|
|
73
|
+
for m in re.finditer(r'\bnew\s+([A-Z][A-Za-z0-9_]+)\s*\(', stripped):
|
|
74
|
+
classes.add(m.group(1))
|
|
75
|
+
return classes, methods
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
PRIMITIVE_DESCRIPTOR_TYPES = {
|
|
79
|
+
"V": "void",
|
|
80
|
+
"Z": "boolean",
|
|
81
|
+
"B": "byte",
|
|
82
|
+
"C": "char",
|
|
83
|
+
"S": "short",
|
|
84
|
+
"I": "int",
|
|
85
|
+
"F": "float",
|
|
86
|
+
"J": "long",
|
|
87
|
+
"D": "double",
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class ApiGraph:
|
|
92
|
+
def __init__(self, config: Dict[str, Any]):
|
|
93
|
+
graph_config = config.get("graph") if isinstance(config.get("graph"), dict) else config
|
|
94
|
+
self.config_dir = str(config.get("__config_dir__", "")).strip()
|
|
95
|
+
self.script_dir = str(Path(__file__).resolve().parent)
|
|
96
|
+
db_path = str(graph_config.get("dbPath", "")).strip()
|
|
97
|
+
|
|
98
|
+
self.db_path = ""
|
|
99
|
+
if db_path:
|
|
100
|
+
raw_db_path = os.path.expanduser(db_path)
|
|
101
|
+
if os.path.isabs(raw_db_path):
|
|
102
|
+
self.db_path = os.path.normpath(raw_db_path)
|
|
103
|
+
else:
|
|
104
|
+
base_dir = self.config_dir or os.getcwd()
|
|
105
|
+
self.db_path = os.path.normpath(os.path.abspath(os.path.join(base_dir, raw_db_path)))
|
|
106
|
+
|
|
107
|
+
self._cheat_classes, self._cheat_methods = _load_cheat_sheet_index()
|
|
108
|
+
|
|
109
|
+
self.sqlite_error: Optional[str] = None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _get_conn(self):
|
|
114
|
+
if hasattr(self, '_conn_cache') and self._conn_cache is not None:
|
|
115
|
+
return self._conn_cache
|
|
116
|
+
if self.sqlite_error:
|
|
117
|
+
return None
|
|
118
|
+
try:
|
|
119
|
+
import sqlite3
|
|
120
|
+
import re
|
|
121
|
+
except Exception as e:
|
|
122
|
+
self.sqlite_error = f"sqlite3 or re unavailable: {e}"
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
if not self.db_path or not os.path.exists(self.db_path):
|
|
126
|
+
return None
|
|
127
|
+
conn = sqlite3.connect(self.db_path, check_same_thread=False)
|
|
128
|
+
conn.execute("PRAGMA journal_mode=WAL")
|
|
129
|
+
conn.execute("PRAGMA busy_timeout=5000")
|
|
130
|
+
|
|
131
|
+
def regexp(expr, item):
|
|
132
|
+
try:
|
|
133
|
+
reg = re.compile(expr, re.IGNORECASE)
|
|
134
|
+
return reg.search(item) is not None
|
|
135
|
+
except Exception:
|
|
136
|
+
return False
|
|
137
|
+
conn.create_function("REGEXP", 2, regexp)
|
|
138
|
+
|
|
139
|
+
conn.row_factory = sqlite3.Row
|
|
140
|
+
self._conn_cache = conn
|
|
141
|
+
return conn
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def _decode_descriptor_type(descriptor: str, index: int) -> Tuple[str, int]:
|
|
145
|
+
array_depth = 0
|
|
146
|
+
while index < len(descriptor) and descriptor[index] == "[":
|
|
147
|
+
array_depth += 1
|
|
148
|
+
index += 1
|
|
149
|
+
|
|
150
|
+
if index >= len(descriptor):
|
|
151
|
+
return "Object" + "[]" * array_depth, index
|
|
152
|
+
|
|
153
|
+
token = descriptor[index]
|
|
154
|
+
if token == "L":
|
|
155
|
+
end = descriptor.find(";", index)
|
|
156
|
+
if end == -1:
|
|
157
|
+
return "Object" + "[]" * array_depth, len(descriptor)
|
|
158
|
+
class_name = descriptor[index + 1:end].replace("/", ".").replace("$", ".")
|
|
159
|
+
return class_name + "[]" * array_depth, end + 1
|
|
160
|
+
|
|
161
|
+
pretty = PRIMITIVE_DESCRIPTOR_TYPES.get(token, token)
|
|
162
|
+
return pretty + "[]" * array_depth, index + 1
|
|
163
|
+
|
|
164
|
+
@classmethod
|
|
165
|
+
def _normalize_type_name(cls, type_name: Optional[str], *, keep_full_path: bool = False) -> str:
|
|
166
|
+
raw = (type_name or "").strip()
|
|
167
|
+
if not raw:
|
|
168
|
+
return "void"
|
|
169
|
+
if raw.endswith("[]"):
|
|
170
|
+
return cls._normalize_type_name(raw[:-2], keep_full_path=keep_full_path) + "[]"
|
|
171
|
+
if raw.startswith("("):
|
|
172
|
+
return raw
|
|
173
|
+
normalized = raw.replace("$", ".")
|
|
174
|
+
if keep_full_path:
|
|
175
|
+
return normalized
|
|
176
|
+
return normalized.split(".")[-1]
|
|
177
|
+
|
|
178
|
+
@classmethod
|
|
179
|
+
def _format_method_signature(cls, method_name: str, method_signature: Optional[str], return_type: Optional[str] = None) -> str:
|
|
180
|
+
signature = (method_signature or "").strip()
|
|
181
|
+
if not signature:
|
|
182
|
+
return f"{method_name}()"
|
|
183
|
+
if signature.startswith("(") and ("/" not in signature and ";" not in signature):
|
|
184
|
+
suffix = f" -> {cls._normalize_type_name(return_type, keep_full_path=True)}" if return_type else ""
|
|
185
|
+
return f"{signature}{suffix}"
|
|
186
|
+
if not signature.startswith("("):
|
|
187
|
+
return signature
|
|
188
|
+
|
|
189
|
+
index = 1
|
|
190
|
+
params: List[str] = []
|
|
191
|
+
while index < len(signature) and signature[index] != ")":
|
|
192
|
+
param_type, index = cls._decode_descriptor_type(signature, index)
|
|
193
|
+
params.append(param_type)
|
|
194
|
+
|
|
195
|
+
if index >= len(signature) or signature[index] != ")":
|
|
196
|
+
return f"{method_name}{signature}"
|
|
197
|
+
|
|
198
|
+
decoded_return_type, _ = cls._decode_descriptor_type(signature, index + 1)
|
|
199
|
+
return f"{method_name}({', '.join(params)}) -> {decoded_return_type}"
|
|
200
|
+
|
|
201
|
+
@classmethod
|
|
202
|
+
def _format_method_signature_for_search(cls, method_name: str, method_signature: Optional[str], return_type: Optional[str] = None) -> str:
|
|
203
|
+
signature = cls._format_method_signature(method_name, method_signature, return_type)
|
|
204
|
+
if " -> " not in signature:
|
|
205
|
+
return signature
|
|
206
|
+
prefix, suffix = signature.rsplit(" -> ", 1)
|
|
207
|
+
if suffix.startswith("java."):
|
|
208
|
+
return f"{prefix} -> {cls._normalize_type_name(suffix)}"
|
|
209
|
+
return signature
|
|
210
|
+
|
|
211
|
+
@staticmethod
|
|
212
|
+
def _parse_method_comment(comment: Optional[str]) -> Tuple[str, List[Tuple[str, str]], Optional[str], List[str]]:
|
|
213
|
+
text = (comment or "").strip()
|
|
214
|
+
if not text:
|
|
215
|
+
return "", [], None, []
|
|
216
|
+
|
|
217
|
+
description_lines: List[str] = []
|
|
218
|
+
params: List[Tuple[str, str]] = []
|
|
219
|
+
return_desc: Optional[str] = None
|
|
220
|
+
throws_desc: List[str] = []
|
|
221
|
+
|
|
222
|
+
for raw_line in text.splitlines():
|
|
223
|
+
line = raw_line.strip()
|
|
224
|
+
if not line:
|
|
225
|
+
continue
|
|
226
|
+
if line.startswith("@param"):
|
|
227
|
+
match = re.match(r"@param\s+`?([A-Za-z_$][A-Za-z0-9_$]*)`?\s*(.*)", line)
|
|
228
|
+
if match:
|
|
229
|
+
params.append((match.group(1), match.group(2).strip()))
|
|
230
|
+
else:
|
|
231
|
+
params.append(("", line[len("@param"):].strip()))
|
|
232
|
+
continue
|
|
233
|
+
if line.startswith("@return"):
|
|
234
|
+
return_desc = line[len("@return"):].strip() or None
|
|
235
|
+
continue
|
|
236
|
+
if line.startswith("@throws"):
|
|
237
|
+
throws_desc.append(line[len("@throws"):].strip())
|
|
238
|
+
continue
|
|
239
|
+
description_lines.append(line)
|
|
240
|
+
|
|
241
|
+
description = "\n".join(description_lines).strip()
|
|
242
|
+
return description, params, return_desc, throws_desc
|
|
243
|
+
|
|
244
|
+
@classmethod
|
|
245
|
+
def _summarize_method_comment(cls, comment: Optional[str], limit: int = 48) -> str:
|
|
246
|
+
description, _, _, _ = cls._parse_method_comment(comment)
|
|
247
|
+
summary = description.replace("\n", " ").strip()
|
|
248
|
+
if len(summary) > limit:
|
|
249
|
+
summary = summary[:limit].rstrip() + "..."
|
|
250
|
+
return summary
|
|
251
|
+
|
|
252
|
+
@staticmethod
|
|
253
|
+
def _normalize_terms(query: Sequence[str] | str) -> List[str]:
|
|
254
|
+
if isinstance(query, str):
|
|
255
|
+
raw_terms = [query]
|
|
256
|
+
else:
|
|
257
|
+
raw_terms = list(query)
|
|
258
|
+
|
|
259
|
+
terms: List[str] = []
|
|
260
|
+
for item in raw_terms:
|
|
261
|
+
if item is None:
|
|
262
|
+
continue
|
|
263
|
+
text = str(item).strip()
|
|
264
|
+
if not text:
|
|
265
|
+
continue
|
|
266
|
+
for part in text.split():
|
|
267
|
+
part = part.strip()
|
|
268
|
+
if part:
|
|
269
|
+
terms.append(part)
|
|
270
|
+
return terms
|
|
271
|
+
|
|
272
|
+
@staticmethod
|
|
273
|
+
def _build_term_clause(
|
|
274
|
+
column_expr: str,
|
|
275
|
+
terms: List[str],
|
|
276
|
+
*,
|
|
277
|
+
match_all: bool,
|
|
278
|
+
use_regex: bool = False,
|
|
279
|
+
) -> Tuple[str, List[str]]:
|
|
280
|
+
if not terms:
|
|
281
|
+
return "", []
|
|
282
|
+
|
|
283
|
+
operator = "REGEXP" if use_regex else "LIKE"
|
|
284
|
+
glue = " AND " if match_all else " OR "
|
|
285
|
+
clause_parts: List[str] = []
|
|
286
|
+
params: List[str] = []
|
|
287
|
+
for term in terms:
|
|
288
|
+
clause_parts.append(f"{column_expr} {operator} ?")
|
|
289
|
+
params.append(term if use_regex else f"%{term}%")
|
|
290
|
+
return "(" + glue.join(clause_parts) + ")", params
|
|
291
|
+
|
|
292
|
+
@staticmethod
|
|
293
|
+
def _build_prefix_clause(column_expr: str, prefixes: Optional[Sequence[str]]) -> Tuple[str, List[str]]:
|
|
294
|
+
if not prefixes:
|
|
295
|
+
return "", []
|
|
296
|
+
clean_prefixes = [str(prefix).strip() for prefix in prefixes if str(prefix).strip()]
|
|
297
|
+
if not clean_prefixes:
|
|
298
|
+
return "", []
|
|
299
|
+
|
|
300
|
+
parts = [f"{column_expr} LIKE ?" for _ in clean_prefixes]
|
|
301
|
+
params = [f"{prefix}%" for prefix in clean_prefixes]
|
|
302
|
+
return "(" + " OR ".join(parts) + ")", params
|
|
303
|
+
|
|
304
|
+
@staticmethod
|
|
305
|
+
def _build_exact_keyword_clause(column_expr: str, values: Optional[Sequence[str]]) -> Tuple[str, List[str]]:
|
|
306
|
+
if not values:
|
|
307
|
+
return "", []
|
|
308
|
+
clean_values = [str(value).strip() for value in values if str(value).strip()]
|
|
309
|
+
if not clean_values:
|
|
310
|
+
return "", []
|
|
311
|
+
|
|
312
|
+
parts = [f"LOWER({column_expr}) LIKE ?" for _ in clean_values]
|
|
313
|
+
params = [f"%{value.lower()}%" for value in clean_values]
|
|
314
|
+
return "(" + " OR ".join(parts) + ")", params
|
|
315
|
+
|
|
316
|
+
def _compose_class_filters(
|
|
317
|
+
self,
|
|
318
|
+
*,
|
|
319
|
+
term_column_expr: str,
|
|
320
|
+
query_terms: Sequence[str] | str,
|
|
321
|
+
match_all_terms: bool = False,
|
|
322
|
+
use_regex: bool = False,
|
|
323
|
+
class_prefixes: Optional[Sequence[str]] = None,
|
|
324
|
+
class_regex: Optional[str] = None,
|
|
325
|
+
class_keywords: Optional[Sequence[str]] = None,
|
|
326
|
+
) -> Tuple[str, List[str]]:
|
|
327
|
+
clauses: List[str] = []
|
|
328
|
+
params: List[str] = []
|
|
329
|
+
|
|
330
|
+
normalized_terms = self._normalize_terms(query_terms)
|
|
331
|
+
term_clause, term_params = self._build_term_clause(
|
|
332
|
+
term_column_expr,
|
|
333
|
+
normalized_terms,
|
|
334
|
+
match_all=match_all_terms,
|
|
335
|
+
use_regex=use_regex,
|
|
336
|
+
)
|
|
337
|
+
if term_clause:
|
|
338
|
+
clauses.append(term_clause)
|
|
339
|
+
params.extend(term_params)
|
|
340
|
+
|
|
341
|
+
prefix_clause, prefix_params = self._build_prefix_clause("class_name", class_prefixes)
|
|
342
|
+
if prefix_clause:
|
|
343
|
+
clauses.append(prefix_clause)
|
|
344
|
+
params.extend(prefix_params)
|
|
345
|
+
|
|
346
|
+
if class_regex and str(class_regex).strip():
|
|
347
|
+
clauses.append("(class_name REGEXP ?)")
|
|
348
|
+
params.append(str(class_regex).strip())
|
|
349
|
+
|
|
350
|
+
keyword_clause, keyword_params = self._build_exact_keyword_clause("class_name", class_keywords)
|
|
351
|
+
if keyword_clause:
|
|
352
|
+
clauses.append(keyword_clause)
|
|
353
|
+
params.extend(keyword_params)
|
|
354
|
+
|
|
355
|
+
where_sql = " AND ".join(clauses) if clauses else "1=1"
|
|
356
|
+
return where_sql, params
|
|
357
|
+
|
|
358
|
+
@staticmethod
|
|
359
|
+
def _build_method_relevance_expressions(normalized_terms: Sequence[str], query_label: str) -> Tuple[str, str, List[str]]:
|
|
360
|
+
select_parts: List[str] = [
|
|
361
|
+
"CASE WHEN LOWER(method_name) = LOWER(?) THEN 0 ELSE 1 END AS exact_rank",
|
|
362
|
+
"CASE WHEN LOWER(method_name) LIKE LOWER(?) THEN 0 ELSE 1 END AS prefix_rank",
|
|
363
|
+
]
|
|
364
|
+
params: List[str] = [query_label, f"{query_label}%"]
|
|
365
|
+
|
|
366
|
+
if normalized_terms:
|
|
367
|
+
all_match_parts = ["LOWER(method_name) LIKE LOWER(?)" for _ in normalized_terms]
|
|
368
|
+
select_parts.append(
|
|
369
|
+
"CASE WHEN " + " AND ".join(all_match_parts) + " THEN 0 ELSE 1 END AS all_terms_rank"
|
|
370
|
+
)
|
|
371
|
+
params.extend([f"%{term}%" for term in normalized_terms])
|
|
372
|
+
|
|
373
|
+
ordered_pattern = "%" + "%".join(normalized_terms) + "%"
|
|
374
|
+
select_parts.append("CASE WHEN LOWER(method_name) LIKE LOWER(?) THEN 0 ELSE 1 END AS ordered_rank")
|
|
375
|
+
params.append(ordered_pattern)
|
|
376
|
+
|
|
377
|
+
comment_match_parts = ["LOWER(COALESCE(method_comment, '')) LIKE LOWER(?)" for _ in normalized_terms]
|
|
378
|
+
select_parts.append(
|
|
379
|
+
"CASE WHEN " + " AND ".join(comment_match_parts) + " THEN 0 ELSE 1 END AS comment_rank"
|
|
380
|
+
)
|
|
381
|
+
params.extend([f"%{term}%" for term in normalized_terms])
|
|
382
|
+
else:
|
|
383
|
+
select_parts.extend([
|
|
384
|
+
"0 AS all_terms_rank",
|
|
385
|
+
"0 AS ordered_rank",
|
|
386
|
+
"0 AS comment_rank",
|
|
387
|
+
])
|
|
388
|
+
|
|
389
|
+
order_sql = ", ".join([
|
|
390
|
+
"exact_rank ASC",
|
|
391
|
+
"all_terms_rank ASC",
|
|
392
|
+
"ordered_rank ASC",
|
|
393
|
+
"prefix_rank ASC",
|
|
394
|
+
"comment_rank ASC",
|
|
395
|
+
"LENGTH(method_name) ASC",
|
|
396
|
+
"class_name ASC",
|
|
397
|
+
"method_name ASC",
|
|
398
|
+
])
|
|
399
|
+
return ", ".join(select_parts), order_sql, params
|
|
400
|
+
|
|
401
|
+
def search_methods(
|
|
402
|
+
self,
|
|
403
|
+
method_query,
|
|
404
|
+
page=1,
|
|
405
|
+
page_size=20,
|
|
406
|
+
*,
|
|
407
|
+
match_all_terms: bool = False,
|
|
408
|
+
class_prefixes: Optional[Sequence[str]] = None,
|
|
409
|
+
class_regex: Optional[str] = None,
|
|
410
|
+
class_keywords: Optional[Sequence[str]] = None,
|
|
411
|
+
):
|
|
412
|
+
conn = self._get_conn()
|
|
413
|
+
if not conn: return f"✖️ 未配置 graph.dbPath,或未在路径 '{self.db_path}' 下找到有效数据库"
|
|
414
|
+
|
|
415
|
+
offset = (page - 1) * page_size
|
|
416
|
+
try:
|
|
417
|
+
with conn:
|
|
418
|
+
normalized_terms = self._normalize_terms(method_query)
|
|
419
|
+
where_sql, where_params = self._compose_class_filters(
|
|
420
|
+
term_column_expr="method_name",
|
|
421
|
+
query_terms=normalized_terms,
|
|
422
|
+
match_all_terms=match_all_terms,
|
|
423
|
+
class_prefixes=class_prefixes,
|
|
424
|
+
class_regex=class_regex,
|
|
425
|
+
class_keywords=class_keywords,
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
count_sql = f"SELECT COUNT(*) as total FROM method_node WHERE {where_sql}"
|
|
429
|
+
total = conn.execute(count_sql, tuple(where_params)).fetchone()['total']
|
|
430
|
+
query_label = " ".join(normalized_terms) or str(method_query).strip()
|
|
431
|
+
relevance_select_sql, order_sql, order_params = self._build_method_relevance_expressions(normalized_terms, query_label)
|
|
432
|
+
|
|
433
|
+
sql = """
|
|
434
|
+
SELECT class_name, method_name, method_signature, return_type, method_comment,
|
|
435
|
+
{relevance_select_sql}
|
|
436
|
+
FROM method_node
|
|
437
|
+
WHERE {where_sql}
|
|
438
|
+
ORDER BY {order_sql}
|
|
439
|
+
LIMIT ? OFFSET ?
|
|
440
|
+
"""
|
|
441
|
+
rows = conn.execute(
|
|
442
|
+
sql.format(where_sql=where_sql, order_sql=order_sql, relevance_select_sql=relevance_select_sql),
|
|
443
|
+
tuple(order_params + where_params + [page_size, offset]),
|
|
444
|
+
).fetchall()
|
|
445
|
+
|
|
446
|
+
if not rows:
|
|
447
|
+
extra = []
|
|
448
|
+
if class_prefixes:
|
|
449
|
+
extra.append(f"class-prefix={','.join(class_prefixes)}")
|
|
450
|
+
if class_keywords:
|
|
451
|
+
extra.append(f"kind={','.join(class_keywords)}")
|
|
452
|
+
if class_regex:
|
|
453
|
+
extra.append(f"class-regex={class_regex}")
|
|
454
|
+
suffix = f"(过滤: {'; '.join(extra)})" if extra else ""
|
|
455
|
+
return f"[Search] 未找到匹配方法 `{query_label}`{suffix}。"
|
|
456
|
+
|
|
457
|
+
def relevance_bucket(row):
|
|
458
|
+
simple_cls = row['class_name'].rsplit('.', 1)[-1]
|
|
459
|
+
cheat_bonus = 0 if (simple_cls, row['method_name']) in self._cheat_methods else 32
|
|
460
|
+
return (
|
|
461
|
+
cheat_bonus
|
|
462
|
+
+ int(row["exact_rank"]) * 16
|
|
463
|
+
+ int(row["all_terms_rank"]) * 8
|
|
464
|
+
+ int(row["ordered_rank"]) * 4
|
|
465
|
+
+ int(row["prefix_rank"]) * 2
|
|
466
|
+
+ int(row["comment_rank"])
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
if page == 1 and len(rows) >= 3 and all(relevance_bucket(row) <= 4 for row in rows[:3]):
|
|
470
|
+
strong_rows = [row for row in rows if relevance_bucket(row) <= 4]
|
|
471
|
+
if strong_rows and len(strong_rows) < len(rows):
|
|
472
|
+
rows = strong_rows
|
|
473
|
+
|
|
474
|
+
total_pages = (total + page_size - 1) // page_size
|
|
475
|
+
md = [f"### [Method] 方法搜索结果: {query_label} ({page}/{total_pages} 页, 共 {total} 条)\n"]
|
|
476
|
+
if class_prefixes or class_keywords or class_regex:
|
|
477
|
+
filter_desc: List[str] = []
|
|
478
|
+
if class_prefixes:
|
|
479
|
+
filter_desc.append(f"class-prefix={','.join(class_prefixes)}")
|
|
480
|
+
if class_keywords:
|
|
481
|
+
filter_desc.append(f"kind={','.join(class_keywords)}")
|
|
482
|
+
if class_regex:
|
|
483
|
+
filter_desc.append(f"class-regex={class_regex}")
|
|
484
|
+
md.append(f"> 过滤条件: {'; '.join(filter_desc)}")
|
|
485
|
+
md.append("")
|
|
486
|
+
full_match_rows = []
|
|
487
|
+
partial_match_rows = []
|
|
488
|
+
if len(normalized_terms) >= 2:
|
|
489
|
+
for row in rows:
|
|
490
|
+
if int(row["all_terms_rank"]) == 0:
|
|
491
|
+
full_match_rows.append(row)
|
|
492
|
+
else:
|
|
493
|
+
partial_match_rows.append(row)
|
|
494
|
+
else:
|
|
495
|
+
full_match_rows = list(rows)
|
|
496
|
+
|
|
497
|
+
def append_rows(title: Optional[str], result_rows):
|
|
498
|
+
if not result_rows:
|
|
499
|
+
return
|
|
500
|
+
if title:
|
|
501
|
+
md.append(title)
|
|
502
|
+
md.append("| 定义类 (Class) | 签名 (Signature) | 说明 (Comment) |")
|
|
503
|
+
md.append("| :--- | :--- | :--- |")
|
|
504
|
+
for row in result_rows:
|
|
505
|
+
comment = self._summarize_method_comment(row["method_comment"])
|
|
506
|
+
pretty_signature = self._format_method_signature_for_search(row["method_name"], row["method_signature"], row["return_type"])
|
|
507
|
+
simple_cls = row['class_name'].rsplit('.', 1)[-1]
|
|
508
|
+
cheat_tag = " ✔️" if (simple_cls, row['method_name']) in self._cheat_methods else ""
|
|
509
|
+
md.append(f"| `{row['class_name']}` | `#{row['method_name']}{pretty_signature}`{cheat_tag} | {comment or '-'} |")
|
|
510
|
+
md.append("")
|
|
511
|
+
|
|
512
|
+
if len(normalized_terms) >= 2 and full_match_rows:
|
|
513
|
+
append_rows("#### 全关键词命中", full_match_rows)
|
|
514
|
+
if partial_match_rows:
|
|
515
|
+
append_rows("#### 部分关键词命中", partial_match_rows)
|
|
516
|
+
else:
|
|
517
|
+
append_rows(None, rows)
|
|
518
|
+
|
|
519
|
+
md.append("\n*提示: 使用 `detail <class_name>` 查看类完整继承树和注释。*")
|
|
520
|
+
return "\n".join(md)
|
|
521
|
+
except Exception as e: return f"✖️ 搜索失败: {str(e)}"
|
|
522
|
+
|
|
523
|
+
def fuzzy_search_classes(
|
|
524
|
+
self,
|
|
525
|
+
query,
|
|
526
|
+
page=1,
|
|
527
|
+
page_size=20,
|
|
528
|
+
use_regex=False,
|
|
529
|
+
*,
|
|
530
|
+
match_all_terms: bool = False,
|
|
531
|
+
class_prefixes: Optional[Sequence[str]] = None,
|
|
532
|
+
class_regex: Optional[str] = None,
|
|
533
|
+
class_keywords: Optional[Sequence[str]] = None,
|
|
534
|
+
):
|
|
535
|
+
conn = self._get_conn()
|
|
536
|
+
if not conn:
|
|
537
|
+
if self.sqlite_error:
|
|
538
|
+
return f"✖️ 运行环境缺少依赖: {self.sqlite_error}"
|
|
539
|
+
return f"✖️ 未配置 graph.dbPath,或未在路径 '{self.db_path}' 下找到有效数据库"
|
|
540
|
+
|
|
541
|
+
offset = (page - 1) * page_size
|
|
542
|
+
|
|
543
|
+
try:
|
|
544
|
+
with conn:
|
|
545
|
+
where_sql, where_params = self._compose_class_filters(
|
|
546
|
+
term_column_expr="class_name",
|
|
547
|
+
query_terms=query,
|
|
548
|
+
match_all_terms=match_all_terms,
|
|
549
|
+
use_regex=use_regex,
|
|
550
|
+
class_prefixes=class_prefixes,
|
|
551
|
+
class_regex=class_regex,
|
|
552
|
+
class_keywords=class_keywords,
|
|
553
|
+
)
|
|
554
|
+
count_sql = f"SELECT COUNT(*) as total FROM class_node WHERE {where_sql}"
|
|
555
|
+
total_row = conn.execute(count_sql, tuple(where_params)).fetchone()
|
|
556
|
+
total = total_row['total']
|
|
557
|
+
|
|
558
|
+
data_sql = """
|
|
559
|
+
SELECT class_name, class_comment
|
|
560
|
+
FROM class_node
|
|
561
|
+
WHERE {where_sql}
|
|
562
|
+
ORDER BY class_name ASC LIMIT ? OFFSET ?
|
|
563
|
+
"""
|
|
564
|
+
rows = conn.execute(data_sql.format(where_sql=where_sql), tuple(where_params + [page_size, offset])).fetchall()
|
|
565
|
+
|
|
566
|
+
query_label = " ".join(self._normalize_terms(query)) or str(query).strip()
|
|
567
|
+
if not rows:
|
|
568
|
+
msg = f"[Search] 未找到匹配 `{query_label}` 的类名"
|
|
569
|
+
if use_regex: msg += " (使用正则模式)"
|
|
570
|
+
extra = []
|
|
571
|
+
if class_prefixes:
|
|
572
|
+
extra.append(f"class-prefix={','.join(class_prefixes)}")
|
|
573
|
+
if class_keywords:
|
|
574
|
+
extra.append(f"kind={','.join(class_keywords)}")
|
|
575
|
+
if class_regex:
|
|
576
|
+
extra.append(f"class-regex={class_regex}")
|
|
577
|
+
if extra:
|
|
578
|
+
msg += f"(过滤: {'; '.join(extra)})"
|
|
579
|
+
return msg + f"(第 {page} 页)。"
|
|
580
|
+
|
|
581
|
+
total_pages = (total + page_size - 1) // page_size
|
|
582
|
+
mode_str = "正则" if use_regex else "模糊"
|
|
583
|
+
md = [f"### [Search] {mode_str}搜索结果 (关键字: {query_label}, 第 {page}/{total_pages} 页, 共 {total} 条)\n"]
|
|
584
|
+
if class_prefixes or class_keywords or class_regex:
|
|
585
|
+
filter_desc: List[str] = []
|
|
586
|
+
if class_prefixes:
|
|
587
|
+
filter_desc.append(f"class-prefix={','.join(class_prefixes)}")
|
|
588
|
+
if class_keywords:
|
|
589
|
+
filter_desc.append(f"kind={','.join(class_keywords)}")
|
|
590
|
+
if class_regex:
|
|
591
|
+
filter_desc.append(f"class-regex={class_regex}")
|
|
592
|
+
md.append(f"> 过滤条件: {'; '.join(filter_desc)}")
|
|
593
|
+
md.append("")
|
|
594
|
+
for row in rows:
|
|
595
|
+
class_comment = (row['class_comment'] or '').strip()
|
|
596
|
+
simple_name = row['class_name'].rsplit('.', 1)[-1]
|
|
597
|
+
cheat_tag = " `[速查已收录]`" if simple_name in self._cheat_classes else ""
|
|
598
|
+
class_line = f"- **`{row['class_name']}`**{cheat_tag}"
|
|
599
|
+
if class_comment:
|
|
600
|
+
class_line += f" \n > {class_comment}"
|
|
601
|
+
md.append(class_line)
|
|
602
|
+
|
|
603
|
+
if page < total_pages:
|
|
604
|
+
md.append(f"\n*提示: 还有更多结果 (page={page+1})。使用 `detail <classname>` 查看详情。*")
|
|
605
|
+
else:
|
|
606
|
+
md.append("\n*提示: 已显示全部结果。使用 `detail <classname>` 查看详情。*")
|
|
607
|
+
|
|
608
|
+
return "\n".join(md)
|
|
609
|
+
except Exception as e: return f"✖️ 搜索失败: {str(e)}"
|
|
610
|
+
|
|
611
|
+
def get_class_details(self, class_name, method_filter: Optional[str] = None, declared_only: bool = False, compact: bool = False):
|
|
612
|
+
conn = self._get_conn()
|
|
613
|
+
if not conn:
|
|
614
|
+
if self.sqlite_error:
|
|
615
|
+
return f"✖️ 运行环境缺少 sqlite3 依赖: {self.sqlite_error}"
|
|
616
|
+
return f"✖️ 未配置 graph.dbPath,或未在路径 '{self.db_path}' 下找到有效数据库"
|
|
617
|
+
|
|
618
|
+
# SQL with optional method filtering
|
|
619
|
+
method_clause = ""
|
|
620
|
+
params = [class_name]
|
|
621
|
+
if method_filter:
|
|
622
|
+
method_clause = "AND m.method_name LIKE ?"
|
|
623
|
+
params.append(f"%{method_filter}%")
|
|
624
|
+
|
|
625
|
+
hierarchy_cte = """
|
|
626
|
+
WITH RECURSIVE hierarchy(class_name, super_class_name, depth) AS (
|
|
627
|
+
SELECT class_name, super_class_name, 0 FROM class_node WHERE class_name = ?
|
|
628
|
+
"""
|
|
629
|
+
if not declared_only:
|
|
630
|
+
hierarchy_cte += """
|
|
631
|
+
UNION ALL
|
|
632
|
+
SELECT c.class_name, c.super_class_name, h.depth + 1
|
|
633
|
+
FROM class_node c JOIN hierarchy h ON c.class_name = h.super_class_name
|
|
634
|
+
WHERE c.class_name != 'java.lang.Object' AND c.class_name IS NOT NULL
|
|
635
|
+
"""
|
|
636
|
+
hierarchy_cte += ")"
|
|
637
|
+
|
|
638
|
+
sql = f"""
|
|
639
|
+
{hierarchy_cte}
|
|
640
|
+
SELECT h.class_name, h.depth, c.class_comment, m.method_name, m.method_signature, m.return_type, m.method_comment
|
|
641
|
+
FROM hierarchy h JOIN class_node c ON h.class_name = c.class_name
|
|
642
|
+
LEFT JOIN method_node m ON h.class_name = m.class_name {method_clause}
|
|
643
|
+
ORDER BY h.depth ASC, m.method_name ASC;
|
|
644
|
+
"""
|
|
645
|
+
try:
|
|
646
|
+
with conn:
|
|
647
|
+
rows = conn.execute(sql, tuple(params)).fetchall()
|
|
648
|
+
if not rows:
|
|
649
|
+
return f"✖️ 未找到类 `{class_name}`" + (f" 或匹配的方法 `{method_filter}`" if method_filter else "") + "。"
|
|
650
|
+
|
|
651
|
+
# Check if any methods were found if a filter was provided
|
|
652
|
+
has_methods = any(r['method_name'] for r in rows)
|
|
653
|
+
if method_filter and not has_methods:
|
|
654
|
+
return f"✖️ 类 `{class_name}` 及其父类中未找到匹配 `{method_filter}` 的方法。"
|
|
655
|
+
|
|
656
|
+
chain = []
|
|
657
|
+
seen_classes = set()
|
|
658
|
+
for r in rows:
|
|
659
|
+
if r['class_name'] not in seen_classes:
|
|
660
|
+
chain.append(r['class_name'])
|
|
661
|
+
seen_classes.add(r['class_name'])
|
|
662
|
+
|
|
663
|
+
method_names = [r['method_name'] for r in rows if r['method_name']]
|
|
664
|
+
unique_method_names = []
|
|
665
|
+
seen_method_names = set()
|
|
666
|
+
for name in method_names:
|
|
667
|
+
if name not in seen_method_names:
|
|
668
|
+
unique_method_names.append(name)
|
|
669
|
+
seen_method_names.add(name)
|
|
670
|
+
|
|
671
|
+
md = [f"## `{class_name}`"]
|
|
672
|
+
class_comment = (rows[0]['class_comment'] or '').strip()
|
|
673
|
+
if class_comment:
|
|
674
|
+
md.append(f"> {class_comment}")
|
|
675
|
+
if not compact:
|
|
676
|
+
if declared_only:
|
|
677
|
+
md.append("范围: 仅当前类声明的方法")
|
|
678
|
+
elif len(chain) > 1:
|
|
679
|
+
md.append(f"继承链: `{' -> '.join(reversed(chain))}`")
|
|
680
|
+
if method_filter and len(unique_method_names) == 1:
|
|
681
|
+
md.append(f"方法: `{unique_method_names[0]}`")
|
|
682
|
+
|
|
683
|
+
last_class = None
|
|
684
|
+
single_class = len(chain) == 1
|
|
685
|
+
single_filtered_method = method_filter and len(unique_method_names) == 1
|
|
686
|
+
for row in rows:
|
|
687
|
+
if row['class_name'] != last_class:
|
|
688
|
+
if not compact and not single_class:
|
|
689
|
+
title = "### 当前类" if row['depth'] == 0 else f"### 父类 L{row['depth']}"
|
|
690
|
+
md.append(title)
|
|
691
|
+
if row['class_name'] != class_name:
|
|
692
|
+
class_comment = (row['class_comment'] or '').strip()
|
|
693
|
+
md.append(f"`{row['class_name']}`")
|
|
694
|
+
if class_comment:
|
|
695
|
+
md.append(f"> {class_comment}")
|
|
696
|
+
last_class = row['class_name']
|
|
697
|
+
if row['method_name']:
|
|
698
|
+
pretty_signature = self._format_method_signature(row["method_name"], row["method_signature"], row["return_type"])
|
|
699
|
+
detail_class_simple = row['class_name'].rsplit('.', 1)[-1]
|
|
700
|
+
cheat_tag = " `[速查已收录·免验证]`" if (detail_class_simple, row['method_name']) in self._cheat_methods else ""
|
|
701
|
+
if compact:
|
|
702
|
+
method_label = row['method_name'] if not (single_filtered_method and len(unique_method_names) == 1) else None
|
|
703
|
+
if method_label:
|
|
704
|
+
md.append(f"- `{method_label}{pretty_signature}`{cheat_tag}")
|
|
705
|
+
else:
|
|
706
|
+
md.append(f"- `{pretty_signature}`{cheat_tag}")
|
|
707
|
+
elif single_filtered_method:
|
|
708
|
+
md.append(f"- `{pretty_signature}`{cheat_tag}")
|
|
709
|
+
else:
|
|
710
|
+
md.append(f"- **{row['method_name']}** `{pretty_signature}`{cheat_tag}")
|
|
711
|
+
description, params, return_desc, throws_desc = self._parse_method_comment(row["method_comment"])
|
|
712
|
+
if description:
|
|
713
|
+
md.append(f" - 说明: {description}")
|
|
714
|
+
for param_name, param_desc in params:
|
|
715
|
+
if param_name and param_desc:
|
|
716
|
+
md.append(f" - 参数 `{param_name}`: {param_desc}")
|
|
717
|
+
elif param_name:
|
|
718
|
+
md.append(f" - 参数 `{param_name}`")
|
|
719
|
+
elif param_desc:
|
|
720
|
+
md.append(f" - 参数: {param_desc}")
|
|
721
|
+
if return_desc:
|
|
722
|
+
md.append(f" - 返回: {return_desc}")
|
|
723
|
+
for throws_item in throws_desc:
|
|
724
|
+
if throws_item:
|
|
725
|
+
md.append(f" - 异常: {throws_item}")
|
|
726
|
+
return "\n".join(md)
|
|
727
|
+
except Exception as e: return f"✖️ 查询失败: {str(e)}"
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
def _split_batch_queries(query_args: list) -> list:
|
|
731
|
+
"""检测逗号分隔的批量查询,返回查询组列表。
|
|
732
|
+
|
|
733
|
+
- 无逗号 → [["kw1", "kw2"]] (单次搜索)
|
|
734
|
+
- 有逗号 → [["kwA"], ["kwB", "kwC"]] (批量搜索)
|
|
735
|
+
"""
|
|
736
|
+
joined = " ".join(query_args)
|
|
737
|
+
if "," not in joined:
|
|
738
|
+
return [query_args]
|
|
739
|
+
groups = [g.strip() for g in joined.split(",") if g.strip()]
|
|
740
|
+
return [g.split() for g in groups]
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
def main():
|
|
744
|
+
parser = FriendlyArgumentParser(
|
|
745
|
+
description="Cosmic API Knowledge CLI — 苍穹 SDK 类名/方法签名离线查询",
|
|
746
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
747
|
+
epilog="""\
|
|
748
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
749
|
+
推荐用法
|
|
750
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
751
|
+
# 查某个领域的 helper
|
|
752
|
+
%(prog)s --config ok-cosmic.json search Helper --class-prefix kd.bos.servicehelper --kind helper
|
|
753
|
+
|
|
754
|
+
# 查某个包下的方法
|
|
755
|
+
%(prog)s --config ok-cosmic.json search-method send email --class-prefix kd.bos.servicehelper.message
|
|
756
|
+
|
|
757
|
+
# 查插件相关类(全部关键词都要命中)
|
|
758
|
+
%(prog)s --config ok-cosmic.json search plugin operation --kind plugin --all
|
|
759
|
+
|
|
760
|
+
# 精确确认某个类有没有目标方法
|
|
761
|
+
%(prog)s --config ok-cosmic.json detail "类全限定名" --method "关键词"
|
|
762
|
+
|
|
763
|
+
# 确认方法是否由当前类自己声明(而非继承)
|
|
764
|
+
%(prog)s --config ok-cosmic.json detail "类全限定名" --method "关键词" --declared-only
|
|
765
|
+
|
|
766
|
+
# 低噪音事实块,适合继续喂给 AI 生成代码
|
|
767
|
+
%(prog)s --config ok-cosmic.json detail "类全限定名" --method "关键词" --compact
|
|
768
|
+
|
|
769
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
770
|
+
AI 调用约束(摘要,完整约束见 SKILL.md 跨脚本硬约束)
|
|
771
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
772
|
+
- 搜索过宽时优先补 --class-prefix 或 --kind 收窄,不要连续无过滤重复搜索。
|
|
773
|
+
- BOS 核心包前缀: kd.bos.servicehelper / kd.bos.entity / kd.bos.form / kd.bd
|
|
774
|
+
""",
|
|
775
|
+
)
|
|
776
|
+
parser.add_argument("--config", help="Path to ok-cosmic.json config file")
|
|
777
|
+
|
|
778
|
+
subparsers = parser.add_subparsers(dest="command", help="Commands")
|
|
779
|
+
|
|
780
|
+
# Search class command
|
|
781
|
+
search_parser = subparsers.add_parser("search", help="Search for classes (fuzzy or regex)")
|
|
782
|
+
search_parser.add_argument("query", nargs="+", help="Search keyword(s) or regex pattern")
|
|
783
|
+
search_parser.add_argument("--regex", action="store_true", help="Use regex for searching")
|
|
784
|
+
search_parser.add_argument("--all", action="store_true", help="Require all keywords to match. Default is any keyword match")
|
|
785
|
+
search_parser.add_argument("--class-prefix", action="append", default=[], help="Only include classes under package/class prefix, e.g. kd.bos.servicehelper")
|
|
786
|
+
search_parser.add_argument("--class-regex", help="Further filter classes by regex on full class name")
|
|
787
|
+
search_parser.add_argument(
|
|
788
|
+
"--kind",
|
|
789
|
+
action="append",
|
|
790
|
+
default=[],
|
|
791
|
+
choices=["helper", "servicehelper", "plugin", "service", "utils", "runtime", "entity", "const", "enum", "controller"],
|
|
792
|
+
help="Common class category filter on class name",
|
|
793
|
+
)
|
|
794
|
+
search_parser.add_argument("--page", type=int, default=1, help="Page number")
|
|
795
|
+
search_parser.add_argument("--page-size", type=int, default=20, help="Items per page")
|
|
796
|
+
|
|
797
|
+
# Search method command
|
|
798
|
+
msearch_parser = subparsers.add_parser("search-method", help="Search for methods across all classes")
|
|
799
|
+
msearch_parser.add_argument("query", nargs="+", help="Method name keyword(s)")
|
|
800
|
+
msearch_parser.add_argument("--all", action="store_true", help="Require all keywords to match. Default is any keyword match")
|
|
801
|
+
msearch_parser.add_argument("--class-prefix", action="append", default=[], help="Only include methods defined in classes under this prefix")
|
|
802
|
+
msearch_parser.add_argument("--class-regex", help="Further filter method results by class-name regex")
|
|
803
|
+
msearch_parser.add_argument(
|
|
804
|
+
"--kind",
|
|
805
|
+
action="append",
|
|
806
|
+
default=[],
|
|
807
|
+
choices=["helper", "servicehelper", "plugin", "service", "utils", "runtime", "entity", "const", "enum", "controller"],
|
|
808
|
+
help="Common class category filter on class name",
|
|
809
|
+
)
|
|
810
|
+
msearch_parser.add_argument("--page", type=int, default=1, help="Page number")
|
|
811
|
+
msearch_parser.add_argument("--page-size", type=int, default=20, help="Items per page")
|
|
812
|
+
|
|
813
|
+
# Detail command
|
|
814
|
+
detail_parser = subparsers.add_parser("detail", help="Get class details")
|
|
815
|
+
detail_parser.add_argument("classname", help="Full class name(s), comma-separated for batch query")
|
|
816
|
+
detail_parser.add_argument("--method", help="Filter methods by name (fuzzy)")
|
|
817
|
+
detail_parser.add_argument("--declared-only", action="store_true", help="Only show methods declared on the target class")
|
|
818
|
+
detail_parser.add_argument("--compact", action="store_true", help="Compact output for AI consumption")
|
|
819
|
+
|
|
820
|
+
args = parser.parse_args()
|
|
821
|
+
|
|
822
|
+
config = load_project_config(args.config)
|
|
823
|
+
api = ApiGraph(config)
|
|
824
|
+
|
|
825
|
+
if args.command == "search":
|
|
826
|
+
query_groups = _split_batch_queries(args.query)
|
|
827
|
+
|
|
828
|
+
def _search_one(q):
|
|
829
|
+
return api.fuzzy_search_classes(
|
|
830
|
+
q, args.page, args.page_size,
|
|
831
|
+
use_regex=args.regex,
|
|
832
|
+
match_all_terms=args.all,
|
|
833
|
+
class_prefixes=args.class_prefix,
|
|
834
|
+
class_regex=args.class_regex,
|
|
835
|
+
class_keywords=args.kind,
|
|
836
|
+
)
|
|
837
|
+
|
|
838
|
+
if len(query_groups) == 1:
|
|
839
|
+
results = [_search_one(query_groups[0])]
|
|
840
|
+
else:
|
|
841
|
+
results = [None] * len(query_groups)
|
|
842
|
+
with ThreadPoolExecutor(max_workers=min(len(query_groups), 4)) as pool:
|
|
843
|
+
fut = {pool.submit(_search_one, q): i for i, q in enumerate(query_groups)}
|
|
844
|
+
for f in as_completed(fut):
|
|
845
|
+
idx = fut[f]
|
|
846
|
+
try:
|
|
847
|
+
results[idx] = f.result()
|
|
848
|
+
except Exception as e:
|
|
849
|
+
results[idx] = f"✖️ 搜索 `{' '.join(query_groups[idx])}` 失败: {e}"
|
|
850
|
+
print("\n\n---\n\n".join(results))
|
|
851
|
+
|
|
852
|
+
elif args.command == "search-method":
|
|
853
|
+
query_groups = _split_batch_queries(args.query)
|
|
854
|
+
|
|
855
|
+
def _msearch_one(q):
|
|
856
|
+
return api.search_methods(
|
|
857
|
+
q, args.page, args.page_size,
|
|
858
|
+
match_all_terms=args.all,
|
|
859
|
+
class_prefixes=args.class_prefix,
|
|
860
|
+
class_regex=args.class_regex,
|
|
861
|
+
class_keywords=args.kind,
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
if len(query_groups) == 1:
|
|
865
|
+
results = [_msearch_one(query_groups[0])]
|
|
866
|
+
else:
|
|
867
|
+
results = [None] * len(query_groups)
|
|
868
|
+
with ThreadPoolExecutor(max_workers=min(len(query_groups), 4)) as pool:
|
|
869
|
+
fut = {pool.submit(_msearch_one, q): i for i, q in enumerate(query_groups)}
|
|
870
|
+
for f in as_completed(fut):
|
|
871
|
+
idx = fut[f]
|
|
872
|
+
try:
|
|
873
|
+
results[idx] = f.result()
|
|
874
|
+
except Exception as e:
|
|
875
|
+
results[idx] = f"✖️ 搜索 `{' '.join(query_groups[idx])}` 失败: {e}"
|
|
876
|
+
print("\n\n---\n\n".join(results))
|
|
877
|
+
elif args.command == "detail":
|
|
878
|
+
class_names = [c.strip() for c in args.classname.split(",") if c.strip()]
|
|
879
|
+
|
|
880
|
+
def _detail_one(cn):
|
|
881
|
+
return api.get_class_details(
|
|
882
|
+
cn,
|
|
883
|
+
method_filter=args.method,
|
|
884
|
+
declared_only=args.declared_only,
|
|
885
|
+
compact=args.compact,
|
|
886
|
+
)
|
|
887
|
+
|
|
888
|
+
if len(class_names) == 1:
|
|
889
|
+
results = [_detail_one(class_names[0])]
|
|
890
|
+
else:
|
|
891
|
+
results = [None] * len(class_names)
|
|
892
|
+
with ThreadPoolExecutor(max_workers=min(len(class_names), 4)) as pool:
|
|
893
|
+
future_to_idx = {
|
|
894
|
+
pool.submit(_detail_one, cn): i
|
|
895
|
+
for i, cn in enumerate(class_names)
|
|
896
|
+
}
|
|
897
|
+
for future in as_completed(future_to_idx):
|
|
898
|
+
idx = future_to_idx[future]
|
|
899
|
+
try:
|
|
900
|
+
results[idx] = future.result()
|
|
901
|
+
except Exception as e:
|
|
902
|
+
results[idx] = f"✖️ 查询 `{class_names[idx]}` 失败: {e}"
|
|
903
|
+
|
|
904
|
+
print("\n\n---\n\n".join(results))
|
|
905
|
+
else:
|
|
906
|
+
parser.print_help()
|
|
907
|
+
|
|
908
|
+
|
|
909
|
+
if __name__ == "__main__":
|
|
910
|
+
sys.exit(run_cli(main))
|