@dforge-core/dforge-mcp 0.2.44 → 0.2.46-next
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/CHANGELOG.md +73 -0
- package/README.md +3 -2
- package/dist/server.js +109 -84
- package/package.json +48 -46
- package/resources/docs/conventions.md +17 -14
- package/resources/docs/dsl-reference.md +15 -5
- package/resources/schemas/entity.schema.json +1 -1
- package/resources/schemas/registers.schema.json +8 -3
- package/scripts/install-skills.mjs +1 -0
- package/skills/dforge-1c-import/SKILL.md +374 -0
- package/skills/dforge-1c-import/references/environment.md +97 -0
- package/skills/dforge-1c-import/references/loading.md +161 -0
- package/skills/dforge-1c-import/references/measurements.md +210 -0
- package/skills/dforge-1c-import/references/print-forms.md +148 -0
- package/skills/dforge-1c-import/scripts/1c.py +178 -0
- package/skills/dforge-1c-import/scripts/README.md +110 -0
- package/skills/dforge-1c-import/scripts/lib/analyze.py +83 -0
- package/skills/dforge-1c-import/scripts/lib/attrs.py +278 -0
- package/skills/dforge-1c-import/scripts/lib/autospec.py +964 -0
- package/skills/dforge-1c-import/scripts/lib/binding.py +257 -0
- package/skills/dforge-1c-import/scripts/lib/bsl.py +811 -0
- package/skills/dforge-1c-import/scripts/lib/callgraph.py +717 -0
- package/skills/dforge-1c-import/scripts/lib/cli.py +53 -0
- package/skills/dforge-1c-import/scripts/lib/columnmap.py +346 -0
- package/skills/dforge-1c-import/scripts/lib/coverage.py +242 -0
- package/skills/dforge-1c-import/scripts/lib/custom.py +83 -0
- package/skills/dforge-1c-import/scripts/lib/delta.py +741 -0
- package/skills/dforge-1c-import/scripts/lib/deps.py +136 -0
- package/skills/dforge-1c-import/scripts/lib/docusage.py +105 -0
- package/skills/dforge-1c-import/scripts/lib/dump1c.py +251 -0
- package/skills/dforge-1c-import/scripts/lib/explore.py +55 -0
- package/skills/dforge-1c-import/scripts/lib/external.py +201 -0
- package/skills/dforge-1c-import/scripts/lib/extract_code.py +251 -0
- package/skills/dforge-1c-import/scripts/lib/forms.py +239 -0
- package/skills/dforge-1c-import/scripts/lib/genmodule.py +1432 -0
- package/skills/dforge-1c-import/scripts/lib/handlers.py +202 -0
- package/skills/dforge-1c-import/scripts/lib/handmap.py +142 -0
- package/skills/dforge-1c-import/scripts/lib/i18n_coverage.py +87 -0
- package/skills/dforge-1c-import/scripts/lib/idioms.py +273 -0
- package/skills/dforge-1c-import/scripts/lib/integrity.py +264 -0
- package/skills/dforge-1c-import/scripts/lib/inventory.py +535 -0
- package/skills/dforge-1c-import/scripts/lib/layers.py +106 -0
- package/skills/dforge-1c-import/scripts/lib/meta.py +315 -0
- package/skills/dforge-1c-import/scripts/lib/migrate.py +1299 -0
- package/skills/dforge-1c-import/scripts/lib/mxl.py +368 -0
- package/skills/dforge-1c-import/scripts/lib/naming.py +251 -0
- package/skills/dforge-1c-import/scripts/lib/paths.py +90 -0
- package/skills/dforge-1c-import/scripts/lib/peek.py +70 -0
- package/skills/dforge-1c-import/scripts/lib/posted.py +218 -0
- package/skills/dforge-1c-import/scripts/lib/posting.py +655 -0
- package/skills/dforge-1c-import/scripts/lib/printgen.py +239 -0
- package/skills/dforge-1c-import/scripts/lib/rebuild.py +194 -0
- package/skills/dforge-1c-import/scripts/lib/recorded.py +170 -0
- package/skills/dforge-1c-import/scripts/lib/recorders.py +94 -0
- package/skills/dforge-1c-import/scripts/lib/reftargets.py +95 -0
- package/skills/dforge-1c-import/scripts/lib/reportgen.py +157 -0
- package/skills/dforge-1c-import/scripts/lib/roles.py +33 -0
- package/skills/dforge-1c-import/scripts/lib/seedstate.py +488 -0
- package/skills/dforge-1c-import/scripts/lib/sizing.py +184 -0
- package/skills/dforge-1c-import/scripts/lib/spec.py +400 -0
- package/skills/dforge-1c-import/scripts/lib/statements.py +774 -0
- package/skills/dforge-1c-import/scripts/lib/subsystems.py +480 -0
- package/skills/dforge-1c-import/scripts/lib/terms.py +451 -0
- package/skills/dforge-1c-import/scripts/lib/terms_uk.py +555 -0
- package/skills/dforge-1c-import/scripts/lib/totals.py +387 -0
- package/skills/dforge-1c-import/scripts/lib/typemap.py +73 -0
- package/skills/dforge-1c-import/scripts/lib/unpack.py +136 -0
- package/skills/dforge-1c-import/scripts/lib/validate.py +508 -0
- package/skills/dforge-1c-import/scripts/project-template/contours.py +47 -0
- package/skills/dforge-1c-import/scripts/project-template/sources.py +14 -0
- package/skills/dforge-mcp-author/SKILL.md +8 -2
- package/skills/dforge-mcp-author/examples/column-security/entities/product.json +1 -1
- package/skills/dforge-mcp-author/examples/column-security/manifest.json +0 -1
- package/skills/dforge-mcp-author/examples/column-security/security/roles.json +2 -2
- package/skills/dforge-mcp-author/references/action-dsl.md +20 -7
- package/skills/dforge-mcp-author/references/conventions.md +7 -1
- package/skills/dforge-mcp-author/references/field-types.md +3 -2
- package/skills/dforge-mcp-author/references/formulas.md +5 -1
- package/skills/dforge-mcp-author/references/print-templates.md +5 -2
- package/skills/dforge-mcp-author/references/queries.md +1 -1
- package/skills/dforge-mcp-author/references/registers.md +18 -3
- package/skills/dforge-mcp-author/references/reports.md +4 -1
- package/skills/dforge-mcp-author/references/schema-import.md +1 -1
- package/skills/dforge-mcp-author/references/security.md +1 -1
- package/skills/dforge-mcp-author/references/translations.md +38 -15
- package/skills/dforge-mcp-author/references/validation-checklist.md +2 -3
- package/skills/dforge-module-build/SKILL.md +10 -1
- package/skills/dforge-module-ship/SKILL.md +31 -3
- package/skills/dforge-mcp-author/examples/column-security/translations/en-US.json +0 -35
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
u"""The behaviour spec: what each document's posting writes, read out of the code.
|
|
4
|
+
|
|
5
|
+
Reading and generating are two steps with a file between them (docs/task-vendor-logic.md
|
|
6
|
+
§3). This is the reading step. For every movement 1C actually recorded - a (document,
|
|
7
|
+
register) pair in `recorded.json` - it takes the write sites the call graph found behind
|
|
8
|
+
it and writes down, per site, the requirement:
|
|
9
|
+
|
|
10
|
+
row `Запись = Набор.Добавить()`: every column assigned to that record before the next
|
|
11
|
+
`Добавить`, including analytics set through `УстановитьСубконто(Счет, Субконто,
|
|
12
|
+
Вид, Значение)` and whole-record copies through `ЗаполнитьЗначенияСвойств`;
|
|
13
|
+
table a table handed to the set (`Загрузить`, or `мТаблицаДвижений` + `ВыполнитьПриход`):
|
|
14
|
+
the columns filled from the header (`ЗаполнитьЗначения`), the collapse
|
|
15
|
+
(`Свернуть`), the table it was copied from by column name, and where that one
|
|
16
|
+
came from - followed one call up, to the query and the tables it reads.
|
|
17
|
+
|
|
18
|
+
Each entry also carries the conditions it sits under and the loop it runs in, because a
|
|
19
|
+
binding needs both: a condition becomes a filter or a computed column, a loop over a
|
|
20
|
+
tabular section is what "posts from lines" means.
|
|
21
|
+
|
|
22
|
+
Expressions are kept as compact text. They are the requirement as 1C stated it, for a person
|
|
23
|
+
and for the generator to read; none of it is meant to be run, and none of it goes into a
|
|
24
|
+
module (§8).
|
|
25
|
+
|
|
26
|
+
What the reader cannot place is listed in the summary, with counts, instead of dropped.
|
|
27
|
+
|
|
28
|
+
python tools/1c.py spec # every document; out/behaviour/*.json
|
|
29
|
+
python tools/1c.py spec ПриходныйКассовыйОрдер # one document, printed
|
|
30
|
+
"""
|
|
31
|
+
from __future__ import print_function
|
|
32
|
+
|
|
33
|
+
import codecs
|
|
34
|
+
import collections
|
|
35
|
+
import io
|
|
36
|
+
import json
|
|
37
|
+
import os
|
|
38
|
+
import re
|
|
39
|
+
import sys
|
|
40
|
+
|
|
41
|
+
import bsl
|
|
42
|
+
import callgraph
|
|
43
|
+
import idioms
|
|
44
|
+
import paths
|
|
45
|
+
|
|
46
|
+
TARGET = os.path.join(paths.OUT, "behaviour")
|
|
47
|
+
|
|
48
|
+
SUBCONTO = bsl.norm(u"УстановитьСубконто")
|
|
49
|
+
ADD_MOVEMENT = bsl.norm(u"ДобавитьДвижение")
|
|
50
|
+
COPY_PROPS = bsl.norm(u"ЗаполнитьЗначенияСвойств")
|
|
51
|
+
TEXT = bsl.norm(u"Текст")
|
|
52
|
+
RECEIPT = frozenset(bsl.norm(x) for x in (u"ВыполнитьПриход", u"ДобавитьПриход",
|
|
53
|
+
u"ВыполнитьДвижениеПриход"))
|
|
54
|
+
EXPENSE = frozenset(bsl.norm(x) for x in (u"ВыполнитьРасход", u"ДобавитьРасход",
|
|
55
|
+
u"ВыполнитьДвижениеРасход"))
|
|
56
|
+
QUERY_SOURCE = re.compile(u"(?:ИЗ|FROM|СОЕДИНЕНИЕ|JOIN)\\s+((?:Документ|Справочник|"
|
|
57
|
+
u"РегистрНакопления|РегистрСведений|РегистрБухгалтерии|"
|
|
58
|
+
u"Document|Catalog|AccumulationRegister|InformationRegister)"
|
|
59
|
+
u"\\.[\\w.]+)", re.U | re.I)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def conditions(context):
|
|
63
|
+
"""The conditions a statement sits under, as text, outermost first."""
|
|
64
|
+
found = []
|
|
65
|
+
for frame in context:
|
|
66
|
+
if frame[0] != "if":
|
|
67
|
+
continue
|
|
68
|
+
for earlier in frame[2]:
|
|
69
|
+
found.append(u"НЕ (%s)" % bsl.render(earlier))
|
|
70
|
+
if frame[1] is not None:
|
|
71
|
+
found.append(bsl.render(frame[1]))
|
|
72
|
+
return found
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def loop(context):
|
|
76
|
+
"""The innermost loop around a statement, as text."""
|
|
77
|
+
for frame in reversed(context):
|
|
78
|
+
if frame[0] == "foreach":
|
|
79
|
+
return u"Для каждого %s Из %s" % (frame[1], bsl.render(frame[2]))
|
|
80
|
+
if frame[0] == "while":
|
|
81
|
+
return u"Пока %s" % bsl.render(frame[1])
|
|
82
|
+
if frame[0] == "for":
|
|
83
|
+
return u"Для %s = %s По %s" % (frame[1], bsl.render(frame[2]), bsl.render(frame[3]))
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def calls_on(expr, receiver):
|
|
88
|
+
"""Calls `<receiver>.<method>(...)` anywhere inside an expression."""
|
|
89
|
+
for sub in bsl.subexpressions(expr):
|
|
90
|
+
if sub[0] == "callx" and sub[1][0] == "member" and sub[1][1][0] == "name" \
|
|
91
|
+
and bsl.norm(sub[1][1][1]) == receiver:
|
|
92
|
+
yield bsl.norm(sub[1][2]), sub
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def named_call(sub, name):
|
|
96
|
+
callee = sub[1]
|
|
97
|
+
if callee[0] == "name":
|
|
98
|
+
return bsl.norm(callee[1]) == name
|
|
99
|
+
return callee[0] == "member" and bsl.norm(callee[2]) == name
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class Reader(object):
|
|
103
|
+
|
|
104
|
+
def __init__(self, modules):
|
|
105
|
+
self.locator = idioms.Locator(modules)
|
|
106
|
+
self.recordsets = dict((bsl.norm(m.object), m) for m in modules if m.kind == "recordset")
|
|
107
|
+
self.missing = collections.Counter()
|
|
108
|
+
|
|
109
|
+
# -- row by row ---------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
def row(self, method, line, op):
|
|
112
|
+
"""The record added at `line` and every column set on it before the next add."""
|
|
113
|
+
statements = list(bsl.walk(method[5]))
|
|
114
|
+
if op == ADD_MOVEMENT:
|
|
115
|
+
return self.set_properties(statements, line)
|
|
116
|
+
start = None
|
|
117
|
+
record = None
|
|
118
|
+
for index, (st, context) in enumerate(statements):
|
|
119
|
+
if st[1] == line and st[0] == "assign" and st[2][0] == "name":
|
|
120
|
+
for sub in bsl.subexpressions(st[3]):
|
|
121
|
+
# `Набор.Добавить()`, or a bare `Добавить()` inside the set's own module
|
|
122
|
+
if sub[0] == "callx" and named_call(sub, op):
|
|
123
|
+
start, record = index, bsl.norm(st[2][1])
|
|
124
|
+
break
|
|
125
|
+
if start is not None:
|
|
126
|
+
break
|
|
127
|
+
if start is None:
|
|
128
|
+
self.missing["row: record variable not found"] += 1
|
|
129
|
+
return None
|
|
130
|
+
entry = self.row_from(statements, start, record)
|
|
131
|
+
if op in RECEIPT:
|
|
132
|
+
entry["kind"] = "receipt"
|
|
133
|
+
elif op in EXPENSE:
|
|
134
|
+
entry["kind"] = "expense"
|
|
135
|
+
return entry, statements[start][1]
|
|
136
|
+
|
|
137
|
+
def set_properties(self, statements, line):
|
|
138
|
+
"""`Набор.Колонка = …; Набор.ДобавитьДвижение()` - the 7.7-style call adds one
|
|
139
|
+
record from the values set on the set itself since its previous add."""
|
|
140
|
+
site = None
|
|
141
|
+
for index, (st, context) in enumerate(statements):
|
|
142
|
+
if st[1] == line and st[0] == "call" and st[2][1][0] == "member" \
|
|
143
|
+
and bsl.norm(st[2][1][2]) == ADD_MOVEMENT:
|
|
144
|
+
site = index
|
|
145
|
+
break
|
|
146
|
+
if site is None:
|
|
147
|
+
self.missing["row: ДобавитьДвижение call not found"] += 1
|
|
148
|
+
return None
|
|
149
|
+
receiver = bsl.render(statements[site][0][2][1][1])
|
|
150
|
+
site_context = statements[site][1]
|
|
151
|
+
begin = 0
|
|
152
|
+
for i in range(site - 1, -1, -1):
|
|
153
|
+
st = statements[i][0]
|
|
154
|
+
if st[0] == "call" and st[2][1][0] == "member" \
|
|
155
|
+
and bsl.norm(st[2][1][2]) == ADD_MOVEMENT \
|
|
156
|
+
and bsl.render(st[2][1][1]) == receiver:
|
|
157
|
+
begin = i + 1
|
|
158
|
+
break
|
|
159
|
+
columns = collections.OrderedDict()
|
|
160
|
+
for st, context in statements[begin:site]:
|
|
161
|
+
if st[0] == "assign" and st[2][0] == "member" and bsl.render(st[2][1]) == receiver:
|
|
162
|
+
extra = conditions(context[len(site_context):]) \
|
|
163
|
+
if context[:len(site_context)] == site_context else conditions(context)
|
|
164
|
+
self._column(columns, st[2][2], st[3], extra)
|
|
165
|
+
return {"shape": "row", "columns": columns, "via": "set properties"}, site_context
|
|
166
|
+
|
|
167
|
+
def row_from(self, statements, start, record, stop=None):
|
|
168
|
+
"""The columns set on `record` after statements[start], until it is reassigned."""
|
|
169
|
+
site_context = statements[start][1]
|
|
170
|
+
columns = collections.OrderedDict()
|
|
171
|
+
copies = []
|
|
172
|
+
for st, context in statements[start + 1:stop]:
|
|
173
|
+
if st[0] == "assign" and st[2][0] == "name" and bsl.norm(st[2][1]) == record:
|
|
174
|
+
break # the next record
|
|
175
|
+
extra = conditions(context[len(site_context):])
|
|
176
|
+
if st[0] == "assign" and st[2][0] == "member" and st[2][1][0] == "name" \
|
|
177
|
+
and bsl.norm(st[2][1][1]) == record:
|
|
178
|
+
self._column(columns, st[2][2], st[3], extra)
|
|
179
|
+
continue
|
|
180
|
+
for expr in bsl.expressions(st):
|
|
181
|
+
for sub in bsl.subexpressions(expr):
|
|
182
|
+
if sub[0] != "callx":
|
|
183
|
+
continue
|
|
184
|
+
args = sub[2]
|
|
185
|
+
if named_call(sub, SUBCONTO) and len(args) >= 4 and args[1] is not None \
|
|
186
|
+
and args[1][0] == "member" and args[1][1][0] == "name" \
|
|
187
|
+
and bsl.norm(args[1][1][1]) == record:
|
|
188
|
+
self._column(columns, u"%s[%s]" % (args[1][2], bsl.render(args[2])),
|
|
189
|
+
args[3], extra)
|
|
190
|
+
elif named_call(sub, COPY_PROPS) and args and args[0] is not None \
|
|
191
|
+
and args[0][0] == "name" and bsl.norm(args[0][1]) == record:
|
|
192
|
+
copies.append({"from": bsl.render(args[1]) if len(args) > 1 else u"",
|
|
193
|
+
"only": bsl.render(args[2]) if len(args) > 2 else None,
|
|
194
|
+
"when": extra or None})
|
|
195
|
+
entry = {"shape": "row", "columns": columns}
|
|
196
|
+
if copies:
|
|
197
|
+
entry["copies"] = copies
|
|
198
|
+
return entry
|
|
199
|
+
|
|
200
|
+
@staticmethod
|
|
201
|
+
def _column(columns, name, expr, extra):
|
|
202
|
+
value = {"value": bsl.render(expr)}
|
|
203
|
+
if extra:
|
|
204
|
+
value["when"] = extra
|
|
205
|
+
columns.setdefault(name, []).append(value)
|
|
206
|
+
|
|
207
|
+
# -- a table at once ----------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
def table(self, module, method, line, op):
|
|
210
|
+
table = self.locator.table_of(method, line, op)
|
|
211
|
+
entry = {"shape": "table"}
|
|
212
|
+
if op in RECEIPT:
|
|
213
|
+
entry["kind"] = "receipt"
|
|
214
|
+
elif op in EXPENSE:
|
|
215
|
+
entry["kind"] = "expense"
|
|
216
|
+
if table is None:
|
|
217
|
+
self.missing["table: no table found"] += 1
|
|
218
|
+
return entry
|
|
219
|
+
entry["table"] = bsl.render(table)
|
|
220
|
+
if table[0] != "name":
|
|
221
|
+
entry["source"] = self.source(module, method, table)
|
|
222
|
+
return entry
|
|
223
|
+
name = bsl.norm(table[1])
|
|
224
|
+
statements = list(bsl.walk(method[5]))
|
|
225
|
+
site = next((i for i, (st, _) in enumerate(statements) if st[1] >= line), len(statements))
|
|
226
|
+
begin = 0
|
|
227
|
+
for i in range(site - 1, -1, -1):
|
|
228
|
+
st = statements[i][0]
|
|
229
|
+
if st[0] == "assign" and st[2][0] == "name" and bsl.norm(st[2][1]) == name:
|
|
230
|
+
begin = i
|
|
231
|
+
break
|
|
232
|
+
window = statements[begin:site]
|
|
233
|
+
fills = collections.OrderedDict()
|
|
234
|
+
per_line = collections.OrderedDict()
|
|
235
|
+
rows = []
|
|
236
|
+
for offset, (st, context) in enumerate(window):
|
|
237
|
+
# a line added to the table and filled column by column
|
|
238
|
+
if st[0] == "assign" and st[2][0] == "name":
|
|
239
|
+
for called, sub in calls_on(st[3], name):
|
|
240
|
+
if called in idioms.ROW_OPS:
|
|
241
|
+
rows.append(self.row_from(statements, begin + offset,
|
|
242
|
+
bsl.norm(st[2][1]), stop=site))
|
|
243
|
+
for expr in bsl.expressions(st):
|
|
244
|
+
for called, sub in calls_on(expr, name):
|
|
245
|
+
args = sub[2]
|
|
246
|
+
if called == idioms.FILL and len(args) >= 2 and args[1] is not None and args[1][0] == "lit":
|
|
247
|
+
fill = {"value": bsl.render(args[0])}
|
|
248
|
+
if conditions(context):
|
|
249
|
+
fill["when"] = conditions(context)
|
|
250
|
+
fills.setdefault(args[1][1], []).append(fill)
|
|
251
|
+
elif called == idioms.COLLAPSE:
|
|
252
|
+
entry["collapse"] = {
|
|
253
|
+
"by": bsl.render(args[0]) if args else u"",
|
|
254
|
+
"sum": bsl.render(args[1]) if len(args) > 1 else u""}
|
|
255
|
+
for sub in bsl.subexpressions(expr):
|
|
256
|
+
if sub[0] == "callx" and named_call(sub, idioms.LOAD_BY_NAME) and len(sub[2]) >= 2 and sub[2][1] is not None and sub[2][1][0] == "name" and bsl.norm(sub[2][1][1]) == name:
|
|
257
|
+
entry.setdefault("from", []).append(
|
|
258
|
+
self.source(module, method, sub[2][0]))
|
|
259
|
+
# a loop over the table setting a column on each line
|
|
260
|
+
if st[0] == "assign" and st[2][0] == "member" and st[2][1][0] == "name":
|
|
261
|
+
for frame in context:
|
|
262
|
+
if frame[0] == "foreach" and bsl.norm(st[2][1][1]) == bsl.norm(frame[1]) and frame[2][0] == "name" and bsl.norm(frame[2][1]) == name:
|
|
263
|
+
per_line.setdefault(st[2][2], []).append({"value": bsl.render(st[3])})
|
|
264
|
+
if rows:
|
|
265
|
+
entry["rows"] = rows
|
|
266
|
+
if fills:
|
|
267
|
+
entry["header"] = fills
|
|
268
|
+
if per_line:
|
|
269
|
+
entry["per_line"] = per_line
|
|
270
|
+
if "from" not in entry and not rows:
|
|
271
|
+
entry["from"] = [self.source(module, method, table)]
|
|
272
|
+
return entry
|
|
273
|
+
|
|
274
|
+
def source(self, module, method, expr, up=True):
|
|
275
|
+
"""Where a table comes from: its origin, and for a query the tables it reads;
|
|
276
|
+
for a parameter, the same one call up, in the callers inside the module."""
|
|
277
|
+
origin = self.locator.origin(method, expr)
|
|
278
|
+
found = {"expr": bsl.render(expr), "origin": origin}
|
|
279
|
+
if "query" in origin:
|
|
280
|
+
tables = self.query_tables(method)
|
|
281
|
+
if tables:
|
|
282
|
+
found["reads"] = tables
|
|
283
|
+
if origin == "param" and up and expr is not None and expr[0] == "name":
|
|
284
|
+
index = [bsl.norm(p[0]) for p in method[3]].index(bsl.norm(expr[1]))
|
|
285
|
+
callers = []
|
|
286
|
+
for other in module.methods.values():
|
|
287
|
+
for st in bsl.statements(other[5]):
|
|
288
|
+
for e in bsl.expressions(st):
|
|
289
|
+
for sub in bsl.subexpressions(e):
|
|
290
|
+
if sub[0] == "callx" and sub[1][0] == "name" \
|
|
291
|
+
and bsl.norm(sub[1][1]) == bsl.norm(method[1]) \
|
|
292
|
+
and len(sub[2]) > index:
|
|
293
|
+
callers.append(dict(self.source(module, other, sub[2][index],
|
|
294
|
+
up=False),
|
|
295
|
+
caller=other[1]))
|
|
296
|
+
if callers:
|
|
297
|
+
found["callers"] = callers
|
|
298
|
+
else:
|
|
299
|
+
self.missing["source: parameter with no caller in the module"] += 1
|
|
300
|
+
return found
|
|
301
|
+
|
|
302
|
+
@staticmethod
|
|
303
|
+
def query_tables(method):
|
|
304
|
+
"""The tables named in the query texts this method sets."""
|
|
305
|
+
tables = []
|
|
306
|
+
for st in bsl.statements(method[5]):
|
|
307
|
+
if st[0] == "assign" and st[2][0] == "member" and bsl.norm(st[2][2]) == TEXT:
|
|
308
|
+
for sub in bsl.subexpressions(st[3]):
|
|
309
|
+
if sub[0] == "lit" and isinstance(sub[1], type(u"")):
|
|
310
|
+
for match in QUERY_SOURCE.findall(sub[1]):
|
|
311
|
+
if match not in tables:
|
|
312
|
+
tables.append(match)
|
|
313
|
+
return tables
|
|
314
|
+
|
|
315
|
+
# -- one site -----------------------------------------------------------------------
|
|
316
|
+
|
|
317
|
+
def site(self, path, line, op, register=None):
|
|
318
|
+
module, method = self.locator.method_at(path, line)
|
|
319
|
+
if method is None:
|
|
320
|
+
self.missing["site: method not found"] += 1
|
|
321
|
+
return None
|
|
322
|
+
kind = idioms.shape(op)
|
|
323
|
+
own = self.recordsets.get(bsl.norm(register)) if register else None
|
|
324
|
+
if own is not None and op in own.methods and kind != "load":
|
|
325
|
+
# the register's own module defines this method; УТП's write from the table
|
|
326
|
+
# handed over in `мТаблицаДвижений`
|
|
327
|
+
kind = "module"
|
|
328
|
+
where = {"site": u"%s:%d" % (path, line), "in": u"%s.%s" % (module.object, method[1])}
|
|
329
|
+
if kind == "row":
|
|
330
|
+
found = self.row(method, line, op)
|
|
331
|
+
if found is None:
|
|
332
|
+
return dict(where, shape="row", unread=True)
|
|
333
|
+
entry, context = found
|
|
334
|
+
elif kind in ("load", "module"):
|
|
335
|
+
entry = self.table(module, method, line, op)
|
|
336
|
+
context = self._context_at(method, line)
|
|
337
|
+
else:
|
|
338
|
+
return None # `Записать` alone, or a set method
|
|
339
|
+
entry.update(where)
|
|
340
|
+
guards = conditions(context)
|
|
341
|
+
if guards:
|
|
342
|
+
entry["when"] = guards
|
|
343
|
+
around = loop(context)
|
|
344
|
+
if around:
|
|
345
|
+
entry["loop"] = around
|
|
346
|
+
return entry
|
|
347
|
+
|
|
348
|
+
@staticmethod
|
|
349
|
+
def _context_at(method, line):
|
|
350
|
+
for st, context in bsl.walk(method[5]):
|
|
351
|
+
if st[1] == line:
|
|
352
|
+
return context
|
|
353
|
+
return ()
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def main():
|
|
357
|
+
args = sys.argv[1:]
|
|
358
|
+
out = codecs.getwriter("utf-8")(sys.stdout.buffer)
|
|
359
|
+
modules, _ = callgraph.load()
|
|
360
|
+
graph = callgraph.Graph(modules, callgraph.register_names())
|
|
361
|
+
graph.run()
|
|
362
|
+
graph.document_writes(callgraph.subscriptions(modules), callgraph.declared_movements())
|
|
363
|
+
with io.open(os.path.join(paths.OUT, "recorded.json"), encoding="utf-8") as f:
|
|
364
|
+
recorded = json.load(f)["documents"]
|
|
365
|
+
reader = Reader(modules)
|
|
366
|
+
|
|
367
|
+
if not os.path.isdir(TARGET):
|
|
368
|
+
os.makedirs(TARGET)
|
|
369
|
+
shapes = collections.Counter()
|
|
370
|
+
written = 0
|
|
371
|
+
for document in sorted(recorded):
|
|
372
|
+
if args and document != args[0]:
|
|
373
|
+
continue
|
|
374
|
+
movements = collections.OrderedDict()
|
|
375
|
+
for register, rows in sorted(recorded[document].items()):
|
|
376
|
+
key = bsl.norm(register)
|
|
377
|
+
entries = []
|
|
378
|
+
for event in ("posting", "on-write", "before-write"):
|
|
379
|
+
for path, line, op in sorted(graph.document_sites.get((document, event, key),
|
|
380
|
+
())):
|
|
381
|
+
entry = reader.site(path, line, op, register)
|
|
382
|
+
if entry is not None:
|
|
383
|
+
entry["event"] = event
|
|
384
|
+
entries.append(entry)
|
|
385
|
+
shapes[entry.get("shape")] += 1
|
|
386
|
+
movements[register] = {"rows_in_1c": rows, "writes": entries}
|
|
387
|
+
spec = {"document": document, "movements": movements}
|
|
388
|
+
with codecs.open(os.path.join(TARGET, document + ".json"), "w", "utf-8") as f:
|
|
389
|
+
json.dump(spec, f, ensure_ascii=False, indent="\t")
|
|
390
|
+
written += 1
|
|
391
|
+
if args:
|
|
392
|
+
out.write(json.dumps(spec, ensure_ascii=False, indent=2))
|
|
393
|
+
out.write(u"\n")
|
|
394
|
+
out.write(u"%d documents, %s entries; unread: %s\n" % (
|
|
395
|
+
written, dict(shapes), dict(reader.missing) or u"none"))
|
|
396
|
+
out.write(u"written %s\n" % TARGET)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
if __name__ == "__main__":
|
|
400
|
+
main()
|