Release 0.2.0: object tree, refs/modules options, docs

Add query object (--clear), modules -n, composite refs labels,
FillChecking/TypeSet parsing, VERSION/CHANGELOG and --version.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
mihailkudravcev
2026-07-16 16:37:41 +03:00
parent 88a3737078
commit a9d1215820
9 changed files with 837 additions and 117 deletions
+163 -8
View File
@@ -13,6 +13,8 @@
python tools/index/query_1c.py show Document.ЗаказКлиента -b crm3-26
python tools/index/query_1c.py modules \"РВС_Transfer\" -b ws-rhana
python tools/index/query_1c.py refs Catalog.Партнеры -b source
python tools/index/query_1c.py object ЗаказКлиента -b target
python tools/index/query_1c.py object Партнер -b crm3-26 --clear
python tools/index/query_1c.py module CommonModules/РВС_TransferAPIДокументы
"""
@@ -39,6 +41,7 @@ from index1c.cli import ( # noqa: E402
resolve_project_root,
)
from index1c.config import resolve_object_type # noqa: E402
from index1c import read_version_file # noqa: E402
from index1c.search import ( # noqa: E402
find_refs_to,
get_module,
@@ -47,6 +50,11 @@ from index1c.search import ( # noqa: E402
search_modules,
search_objects,
)
from index1c.object_view import ( # noqa: E402
find_structure_matches,
format_object_tree,
load_object_structure,
)
def _open_or_exit(root: Path):
@@ -95,14 +103,31 @@ def cmd_modules(args: argparse.Namespace) -> int:
baseconfs = get_baseconfs_for_query(args, conn, root)
except BaseconfError as e:
return handle_baseconf_error(e)
rows = search_modules(conn, args.query, configs=baseconfs, limit=args.limit)
names_only = bool(args.names_only)
rows = search_modules(
conn,
args.query,
configs=baseconfs,
limit=args.limit,
names_only=names_only,
)
if not rows:
print(format_not_found_modules(args.query, baseconfs=baseconfs, conn=conn))
return 2
for r in rows:
print(f"- [{r['config']}] `{r['rel_path']}` ({r['module_kind']}, owner={r['owner']})")
if r.get("snip"):
print(f" {r['snip']}")
syn = r.get("owner_synonym") or ""
if names_only:
# только имя модуля (owner) и синоним объекта-владельца
syn_s = f"{syn}" if syn else ""
print(f"- [{r['config']}] `{r['owner']}`{syn_s}")
else:
syn_s = f", {syn}" if syn else ""
print(
f"- [{r['config']}] `{r['rel_path']}` "
f"({r['module_kind']}, owner={r['owner']}{syn_s})"
)
if r.get("snip"):
print(f" {r['snip']}")
return 0
finally:
conn.close()
@@ -116,15 +141,41 @@ def cmd_refs(args: argparse.Namespace) -> int:
baseconfs = get_baseconfs_for_query(args, conn, root)
except BaseconfError as e:
return handle_baseconf_error(e)
rows = find_refs_to(conn, args.target, configs=baseconfs, limit=args.limit)
rows, totals = find_refs_to(
conn, args.target, configs=baseconfs, limit=args.limit
)
if not rows:
scope = f" в {', '.join(baseconfs)}" if baseconfs else ""
print(f"Ссылок на «{args.target}» не найдено{scope}.")
return 2
current_cfg = None
for r in rows:
if r["config"] != current_cfg:
current_cfg = r["config"]
total = totals.get(current_cfg, 0)
shown = sum(1 for x in rows if x["config"] == current_cfg)
trunc = f", показано {shown}" if shown < total else ""
print(f"## [{current_cfg}] — всего ссылок: {total}{trunc}")
arrow = f"{r['to_kind']}.{r['to_name']}"
if r.get("is_composite"):
others = r.get("other_types") or []
if others and len(others) <= 4:
arrow += f" (среди прочего: {', '.join(others)})"
elif others:
arrow += f" (среди прочего: {', '.join(others[:3])}, … ещё {len(others) - 3})"
else:
arrow += " (среди прочего)"
print(f"- `{r['from_full_name']}.{r['from_field']}` → {arrow}")
if len(totals) > 1:
grand = sum(totals.values())
shown = len(rows)
print()
print(
f"- [{r['config']}] `{r['from_full_name']}.{r['from_field']}` "
f"{r['to_kind']}.{r['to_name']}"
f"Итого: {grand} ссылок в {len(totals)} конфигурациях; "
f"выведено {shown} (лимит {args.limit} на каждую). "
f"См. -b / --limit."
)
return 0
finally:
@@ -149,6 +200,76 @@ def cmd_show(args: argparse.Namespace) -> int:
conn.close()
def cmd_object(args: argparse.Namespace) -> int:
root = resolve_project_root(args)
conn = _open_or_exit(root)
try:
try:
baseconfs = get_baseconfs_for_query(args, conn, root)
except BaseconfError as e:
return handle_baseconf_error(e)
otype = resolve_object_type(args.type) if args.type else None
matches = find_structure_matches(
conn,
args.query,
configs=baseconfs,
object_type=otype,
limit=args.limit,
)
if not matches:
print(format_not_found_object(args.query, baseconfs=baseconfs, conn=conn), file=sys.stderr)
return 2
for i, m in enumerate(matches):
if i:
print()
obj = load_object_structure(m)
# подтянуть FillChecking/типы из свежего XML в matched_fields
matched = list(m.get("matched_fields") or [])
if matched and obj.get("fields"):
by_f = {
(
f.get("tabular_section") or "",
f.get("name") or "",
f.get("kind") or "",
): f
for f in obj["fields"]
}
for mf in matched:
key = (
mf.get("tabular_section") or "",
mf.get("name") or "",
mf.get("kind") or "",
)
src = by_f.get(key)
if src:
mf["fill_checking"] = src.get("fill_checking") or ""
mf["types"] = src.get("types") or mf.get("types")
mf["synonym"] = src.get("synonym") or mf.get("synonym")
mf["refs"] = src.get("refs") or mf.get("refs")
# точное совпадение объекта (score 0) → clear без дерева;
# иначе при найденных реквизитах — clear только по ним
kind = m.get("match_kind") or "object"
if args.clear and matched and m.get("score", 100) > 0:
kind = "field"
elif args.clear and m.get("score", 100) == 0:
kind = "object"
print(
format_object_tree(
obj,
clear=bool(args.clear),
matched_fields=matched,
match_kind=kind,
)
)
if len(matches) > 1:
print()
print(f"Найдено объектов: {len(matches)}. Уточните -b / --type / --limit.")
return 0
finally:
conn.close()
def cmd_module(args: argparse.Namespace) -> int:
root = resolve_project_root(args)
conn = _open_or_exit(root)
@@ -177,6 +298,11 @@ def build_parser() -> argparse.ArgumentParser:
"Файл настроек утилиты — --config (-c): см. tools/index/config.example.json"
),
)
p.add_argument(
"--version",
action="version",
version=f"%(prog)s {read_version_file()}",
)
add_tool_args(p)
sub = p.add_subparsers(dest="cmd", required=True)
@@ -191,12 +317,23 @@ def build_parser() -> argparse.ArgumentParser:
m.add_argument("query")
add_baseconf_args(m)
m.add_argument("--limit", type=int, default=30)
m.add_argument(
"--names-only",
"-n",
action="store_true",
help="Только имена модулей (owner) и синонимы, без пути и фрагментов кода",
)
m.set_defaults(func=cmd_modules)
ref = sub.add_parser("refs", help="Обратный индекс: кто ссылается на объект")
ref.add_argument("target", help="Catalog.Партнеры | CatalogRef.Партнеры | Партнеры")
add_baseconf_args(ref)
ref.add_argument("--limit", type=int, default=100)
ref.add_argument(
"--limit",
type=int,
default=100,
help="Макс. строк на каждую конфигурацию 1С (по умолчанию 100)",
)
ref.set_defaults(func=cmd_refs)
sh = sub.add_parser("show", help="Карточка объекта (JSON: поля, типы, ссылки)")
@@ -204,6 +341,24 @@ def build_parser() -> argparse.ArgumentParser:
add_baseconf_args(sh)
sh.set_defaults(func=cmd_show)
ob = sub.add_parser(
"object",
help="Структура объекта: дерево реквизитов, обязательность, типы",
)
ob.add_argument("query", help="Имя/синоним объекта или реквизита")
add_baseconf_args(ob)
ob.add_argument("--type", help="Ограничить типом: Document, Catalog, …")
ob.add_argument("--limit", type=int, default=10, help="Макс. число объектов")
ob.add_argument(
"--clear",
action="store_true",
help=(
"Краткий вывод: только найденные реквизиты; "
"если совпал сам объект — только его имя без дерева"
),
)
ob.set_defaults(func=cmd_object)
md = sub.add_parser("module", help="Метаданные модуля по пути или owner")
md.add_argument("path", help="CommonModules/… или CommonModule.Имя")
add_baseconf_args(md)