@dforge-core/dforge-mcp 0.2.45 → 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 +12 -0
- package/README.md +2 -1
- package/package.json +48 -46
- 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 +5 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""What the library needs from the project it is pointed at.
|
|
3
|
+
|
|
4
|
+
The library knows 1C and it knows dForge. What it cannot know is this infobase: which
|
|
5
|
+
object belongs to which module, which 1C column feeds which ported one, and what the
|
|
6
|
+
hand-written modules look like. That is the project's to say, and this is the whole of
|
|
7
|
+
what it has to say - five names, no more.
|
|
8
|
+
|
|
9
|
+
Everything here is imported by name from the directory `paths.PROJECT` names, so the
|
|
10
|
+
library never mentions a project module and a different infobase is a different
|
|
11
|
+
directory rather than a different library.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import print_function
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
import paths
|
|
19
|
+
|
|
20
|
+
# What a project must provide, and what for. A project missing one of these fails here,
|
|
21
|
+
# naming the file, rather than somewhere downstream as an AttributeError.
|
|
22
|
+
REQUIRED = [
|
|
23
|
+
("contours", ["CONTOURS", "CODES"], "which object belongs to which module"),
|
|
24
|
+
("sources", ["SOURCES"], "which 1C column feeds each authored column"),
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
# And what a project may provide. Absent, the library does that much less rather than fail.
|
|
28
|
+
OPTIONAL = [
|
|
29
|
+
("registers", ["REGISTERS"], "registers declared over the generated entities"),
|
|
30
|
+
("regulated", ["FORMS"], "the regulated statements this configuration holds"),
|
|
31
|
+
("vocabulary", ["TERMS", "LATIN"], "this company's own proper nouns"),
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
_loaded = {}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _module(name):
|
|
38
|
+
if name in _loaded:
|
|
39
|
+
return _loaded[name]
|
|
40
|
+
if paths.PROJECT not in sys.path:
|
|
41
|
+
sys.path.insert(0, paths.PROJECT)
|
|
42
|
+
try:
|
|
43
|
+
_loaded[name] = __import__(name)
|
|
44
|
+
except ImportError:
|
|
45
|
+
raise ImportError(
|
|
46
|
+
"the project at %s has no %s.py - see binding.REQUIRED for what a project"
|
|
47
|
+
" has to provide" % (paths.PROJECT, name))
|
|
48
|
+
return _loaded[name]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def contours():
|
|
52
|
+
"""The contour map: CONTOURS, CODES, EXTENSIONS, DONE, names()."""
|
|
53
|
+
return _module("contours")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def client_prefixes():
|
|
57
|
+
u"""The prefixes the integrator's own objects carry (`мсбЗаказ`, `рзТранспорт`).
|
|
58
|
+
|
|
59
|
+
Which objects are the client's rather than the vendor's is this configuration's
|
|
60
|
+
convention, so the project names it in `contours.CLIENT_PREFIXES`. Absent, nothing
|
|
61
|
+
counts as the client's - the reports then say so, rather than guessing a convention.
|
|
62
|
+
"""
|
|
63
|
+
return tuple(getattr(contours(), "CLIENT_PREFIXES", ()) or ())
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def toolkit_prefixes():
|
|
67
|
+
u"""The prefixes of a third-party toolkit embedded in the configuration (`ир`, `ит`):
|
|
68
|
+
neither the vendor's nor the client's, and dropped from names like the client's."""
|
|
69
|
+
return tuple(getattr(contours(), "TOOLKIT_PREFIXES", ()) or ())
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def hand_entities():
|
|
73
|
+
u"""1C object name -> `module.entity` for the objects an authored spec already owns.
|
|
74
|
+
|
|
75
|
+
A reference to one of them resolves to that entity rather than generating a second
|
|
76
|
+
copy of it. `contours.HAND_ENTITIES`; absent, everything is generated.
|
|
77
|
+
"""
|
|
78
|
+
return dict(getattr(contours(), "HAND_ENTITIES", {}) or {})
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def sources():
|
|
82
|
+
"""The column map for authored modules: SOURCES, ENUMS, QUERIES, TABLEREFS."""
|
|
83
|
+
return _module("sources")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def registers():
|
|
87
|
+
"""Registers declared over the GENERATED entities, module code -> overlay.
|
|
88
|
+
|
|
89
|
+
An authored spec says what its registers are inline. A generated module has no spec
|
|
90
|
+
to say it in, and the alternative - authoring all 61 entities of a contour by hand to
|
|
91
|
+
reach the two that are a register - is not one. So a project may name them separately,
|
|
92
|
+
and this is the whole of that overlay:
|
|
93
|
+
|
|
94
|
+
REGISTERS = {"production": {
|
|
95
|
+
"defs": {<register code>: <the registers.json declaration>},
|
|
96
|
+
"entities": {<entity>: <an entity spec, autospec's shape>},
|
|
97
|
+
"retype": {<entity>: {<column>: <field spec>}},
|
|
98
|
+
"fields": {<entity>: [(name, spec, label), ...]}, # the recorder column
|
|
99
|
+
"bind": {<document entity>: {<column>: <the A column>}},
|
|
100
|
+
"constraints": {<entity>: {<name>: <constraint>}},
|
|
101
|
+
"load": {<entity>: {<column>: {"from": <column>, "transform": <name>}}},
|
|
102
|
+
"uk": {<entity>: {<column>: <Ukrainian label>}},
|
|
103
|
+
}}
|
|
104
|
+
|
|
105
|
+
This list went stale once already - it named four of the sections while the generator
|
|
106
|
+
applied seven - so it is the whole of the overlay again, and `apply_registers` applies
|
|
107
|
+
them in this order.
|
|
108
|
+
|
|
109
|
+
`entities` introduces an entity the infobase has no original for. The case it exists
|
|
110
|
+
for is a BALANCE register's state: dForge keeps the running total in a table of its
|
|
111
|
+
own, and 1C keeps no such table, holding the movements and computing the balance from
|
|
112
|
+
them on every read. `retype` corrects a column the generator typed from the physical
|
|
113
|
+
schema and the register needs typed as what it means. `constraints` declares, as a real
|
|
114
|
+
CHECK, an invariant a 1C handler kept by refusing to post.
|
|
115
|
+
|
|
116
|
+
`load` is for a column the register needs that 1C does not hold in that shape - a
|
|
117
|
+
turnover register's direction, which the infobase keeps as the SIGN of the value. It
|
|
118
|
+
names the ported column whose already-resolved 1C source to read again, so DBNames
|
|
119
|
+
stays the only thing that resolves a physical column, and `migrate.TRANSFORMS` says
|
|
120
|
+
what each name does.
|
|
121
|
+
|
|
122
|
+
Optional: a project with no registers.py generates exactly what it did before.
|
|
123
|
+
"""
|
|
124
|
+
try:
|
|
125
|
+
module = _module("registers")
|
|
126
|
+
except ImportError:
|
|
127
|
+
return {}
|
|
128
|
+
return getattr(module, "REGISTERS", {}) or {}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def forms():
|
|
132
|
+
"""The regulated statements, as a list of no-argument builders, in reporting order.
|
|
133
|
+
|
|
134
|
+
Each builder reads one 1C regulated form and returns the `statements.Form` it filled -
|
|
135
|
+
so the library knows how such a form is STORED and read, and the project knows which
|
|
136
|
+
forms this configuration holds and what is peculiar about each. The peculiar part is
|
|
137
|
+
never small enough to guess: which module of the several editions is current, which of
|
|
138
|
+
its branches are dead, what its `2`-suffixed helpers mean, and whatever it does that no
|
|
139
|
+
regex should be asked to read.
|
|
140
|
+
|
|
141
|
+
Optional: a project with no regulated.py reads no statements, which is not an
|
|
142
|
+
error. The name is not `forms.py` because the library already has one of those,
|
|
143
|
+
for the 1C INTERFACE forms, and it would shadow this on the import path.
|
|
144
|
+
"""
|
|
145
|
+
try:
|
|
146
|
+
module = _module("regulated")
|
|
147
|
+
except ImportError:
|
|
148
|
+
return []
|
|
149
|
+
return list(getattr(module, "FORMS", ()) or ())
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def terms():
|
|
153
|
+
"""This company's own proper nouns in Ukrainian - abbreviations, plants, customers.
|
|
154
|
+
|
|
155
|
+
The library carries the language; a project carries the names that are not language.
|
|
156
|
+
Without this, another configuration's company names would be transliterated into the
|
|
157
|
+
first one's. The module is `vocabulary.py` and not `terms.py` because the library
|
|
158
|
+
already has a `terms.py`, which would shadow it on the import path.
|
|
159
|
+
"""
|
|
160
|
+
return _optional("vocabulary", "TERMS")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def vocabulary():
|
|
164
|
+
"""The same proper nouns in Latin, for the identifiers the port generates."""
|
|
165
|
+
return _optional("vocabulary", "LATIN")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _optional(module_name, attribute):
|
|
169
|
+
try:
|
|
170
|
+
module = _module(module_name)
|
|
171
|
+
except ImportError:
|
|
172
|
+
return {}
|
|
173
|
+
return getattr(module, attribute, {}) or {}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
AUTHORED = "authored"
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def authored(module):
|
|
180
|
+
"""The project's hand-written files for one module, or None.
|
|
181
|
+
|
|
182
|
+
A generated module is rewritten whole on every `genmodule`, so a file added to it by
|
|
183
|
+
hand - an action, its DSL script - is gone the next time it runs. What the generator
|
|
184
|
+
cannot derive from the infobase is kept in the project instead, under
|
|
185
|
+
`authored/<module>/`, laid out exactly as it is in the module, and copied in when the
|
|
186
|
+
module is emitted:
|
|
187
|
+
|
|
188
|
+
authored/production/ui/actions.json
|
|
189
|
+
authored/production/logic/actions/post_report.dsl
|
|
190
|
+
authored/production/translations/uk-UA.json # merged into the generated one
|
|
191
|
+
|
|
192
|
+
A translations file is MERGED into the one the generator writes, the authored value
|
|
193
|
+
winning, because the generated file already holds every entity's Ukrainian and the
|
|
194
|
+
authored one only adds to it - its actions and their messages. Every other file is
|
|
195
|
+
copied as it stands, and one the generator also writes is refused rather than
|
|
196
|
+
silently replaced.
|
|
197
|
+
|
|
198
|
+
Optional: a module with no such directory is emitted exactly as before.
|
|
199
|
+
"""
|
|
200
|
+
path = os.path.join(paths.PROJECT, AUTHORED, module)
|
|
201
|
+
return path if os.path.isdir(path) else None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def specs():
|
|
205
|
+
"""The authored modules, code -> SPEC, in the order the project lists them.
|
|
206
|
+
|
|
207
|
+
A project that authors nothing returns an empty map and everything is generated.
|
|
208
|
+
"""
|
|
209
|
+
found = {}
|
|
210
|
+
for name in sorted(os.listdir(paths.PROJECT)):
|
|
211
|
+
if not name.startswith("spec_") or not name.endswith(".py"):
|
|
212
|
+
continue
|
|
213
|
+
module = _module(name[:-3])
|
|
214
|
+
spec = getattr(module, "SPEC", None)
|
|
215
|
+
if spec:
|
|
216
|
+
found[spec.get("code") or name[len("spec_"):-3]] = spec
|
|
217
|
+
return found
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def check():
|
|
221
|
+
"""Is the project complete enough to run against? Returns what is missing."""
|
|
222
|
+
missing = []
|
|
223
|
+
for name, names, why in REQUIRED:
|
|
224
|
+
try:
|
|
225
|
+
module = _module(name)
|
|
226
|
+
except ImportError:
|
|
227
|
+
missing.append("%s.py is absent - %s" % (name, why))
|
|
228
|
+
continue
|
|
229
|
+
for attribute in names:
|
|
230
|
+
if not hasattr(module, attribute):
|
|
231
|
+
missing.append("%s.py declares no %s - %s" % (name, attribute, why))
|
|
232
|
+
return missing
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def main():
|
|
236
|
+
paths.show()
|
|
237
|
+
print("specs %s" % ", ".join(sorted(specs())) or "(none)")
|
|
238
|
+
overlay = registers()
|
|
239
|
+
print("registers %s" % ", ".join(
|
|
240
|
+
"%s:%d" % (module, len((o.get("defs") or {}))) for module, o in sorted(overlay.items()))
|
|
241
|
+
or "(none)")
|
|
242
|
+
root = os.path.join(paths.PROJECT, AUTHORED)
|
|
243
|
+
print("authored %s" % (", ".join(sorted(
|
|
244
|
+
name for name in os.listdir(root) if os.path.isdir(os.path.join(root, name))))
|
|
245
|
+
if os.path.isdir(root) else "(none)"))
|
|
246
|
+
builders = forms()
|
|
247
|
+
print("forms %d declared" % len(builders) if builders else "forms (none)")
|
|
248
|
+
print("vocabulary %d Ukrainian, %d Latin" % (len(terms()), len(vocabulary())))
|
|
249
|
+
problems = check()
|
|
250
|
+
for line in problems:
|
|
251
|
+
print(" %s" % line)
|
|
252
|
+
print("%d problem(s)" % len(problems))
|
|
253
|
+
return 1 if problems else 0
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
if __name__ == "__main__":
|
|
257
|
+
sys.exit(main() or 0)
|