#!/usr/bin/env python3
# stub-db-create v1.7 — Create temp 1C infobase with metadata stubs for EPF/ERF build
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import os
import random
import re
import subprocess
import sys
import tempfile
import uuid
IBCMD_NOUSER_HINT = (
"[ibcmd] No -UserName/-Password given; the infobase may require authentication. "
"On Windows ibcmd reads credentials from the console (stdin is ignored), so this "
"call may block instead of failing. If it does not return promptly, abort and "
"re-run with -UserName and -Password.\n"
)
def decode_platform_bytes(data):
"""ibcmd writes UTF-8 (checked on 8.3.24, 8.3.27, 8.5), a crashing 1cv8 may still emit
OEM text. Decode strictly as UTF-8 and fall back to cp866 on invalid bytes — the locale
code page (what text=True uses) mangles both."""
if not data:
return ""
try:
return data.decode("utf-8")
except UnicodeDecodeError:
return data.decode("cp866", errors="replace")
def clean_path(value, param=""):
"""Forgive what is unambiguous in a path the caller passed: surrounding whitespace,
surrounding quotes that survived shell parsing, a trailing separator. A quote left
inside afterwards cannot be part of a real path — reject it by name instead of letting
1C answer with its opaque "Неверные или отсутствующие параметры соединения"."""
if not value:
return value
v = value.strip()
if len(v) >= 2 and v[0] == v[-1] and v[0] in "\"'":
v = v[1:-1].strip()
if len(v) > 3 and v[-1] in "\\/":
v = v[:-1]
if '"' in v:
print(f"Error: {param or 'path'} contains a quote character: {value}", file=sys.stderr)
sys.exit(1)
return v
def quote_if_needed(token):
"""Extra arguments come from the caller unquoted; the 1cv8 command line is joined
verbatim, so a token with a space needs quotes of its own."""
if token and (" " in token or "\t" in token) and '"' not in token:
return f'"{token}"'
return token
def run_v8(v8path, arguments):
"""Run 1cv8 in batch mode and capture its console output.
The arguments carry their own quotes inside the value (File="C:\\a b") — that is where
1C's parser expects them, on Windows and on *nix alike. Windows list2cmdline would
escape those quotes, so there the command line is handed over ready-made.
"""
if os.name == "nt":
cmd = '"' + v8path + '" ' + " ".join(arguments)
else:
cmd = [v8path] + arguments
r = subprocess.run(cmd, input=b"", capture_output=True)
r.stdout = decode_platform_bytes(r.stdout)
r.stderr = decode_platform_bytes(r.stderr)
return r
def print_platform_output(result):
"""Print what the platform wrote to the console as its own labelled block. Silence stays
silent: in batch mode 1cv8 reports through /Out and prints nothing here."""
text = ((result.stdout or "") + (result.stderr or "")).rstrip()
if not text:
return
limit = 65536
if len(text) > limit:
text = f"[... обрезано, показаны последние {limit} символов ...]\n" + text[-limit:]
print("--- Вывод платформы ---")
print(text)
print("--- End ---")
def run_ibcmd(cmd, has_username=False, warn_no_user=True):
"""Run an ibcmd command non-interactively.
input="" closes stdin (EOF) so ibcmd's auth prompt fast-fails instead of hanging.
On Windows without -UserName ibcmd reads the console directly and may still block —
that residual case is flagged via IBCMD_NOUSER_HINT (model-facing).
"""
if warn_no_user and os.name == "nt" and not has_username:
sys.stderr.write(IBCMD_NOUSER_HINT)
sys.stderr.flush()
r = subprocess.run(cmd, input=b"", capture_output=True)
r.stdout = decode_platform_bytes(r.stdout)
r.stderr = decode_platform_bytes(r.stderr)
return r
# --- Additional platform arguments ---
V8_OWNED_KEYS = [
"DESIGNER", "ENTERPRISE", "CREATEINFOBASE", "CONFIG",
"/F", "/S", "/N", "/P", "/Out", "/DisableStartupDialogs",
"/UseTemplate", "/AddToList", "/Execute", "/C", "/URL", "/UC",
"/DumpIB", "/RestoreIB", "/DumpCfg", "/LoadCfg",
"/DumpConfigToFiles", "/LoadConfigFromFiles", "/UpdateDBCfg",
"/DumpExternalDataProcessorOrReportToFiles", "/LoadExternalDataProcessorOrReportFromFiles",
]
IBCMD_OWNED_KEYS = [
"--db-path", "--data", "--out", "--file", "--load", "--restore",
"--import", "--export", "--apply", "--force", "--create-database",
"--user", "--password",
]
V8_SECRET_KEYS = ["/P", "/UC", "/WSP", "/AWSP"]
IBCMD_SECRET_KEYS = ["--password", "--token", "--db-pwd"]
def arg_key_match(token, key):
"""Token matches a key when it equals it, or starts with it and the next character
is not a letter — catches glued /N"user" and --password=x, while keeping
/ClearCache distinct from /C."""
if len(token) < len(key):
return False
if token[: len(key)].lower() != key.lower():
return False
if len(token) == len(key):
return True
return not token[len(key)].isalpha()
def project_extra_args(name):
"""v8args / ibcmdargs from .v8-project.json — same upward walk as v8path."""
d = os.getcwd()
while True:
pf = os.path.join(d, ".v8-project.json")
if os.path.isfile(pf):
try:
with open(pf, encoding="utf-8-sig") as f:
data = json.load(f)
v = data.get(name)
if v:
return [str(x) for x in v]
except Exception:
pass
return []
parent = os.path.dirname(d)
if parent == d:
return []
d = parent
def assert_extra_args(extra, engine, hints):
"""The platform accepts only one batch operation, and a duplicate connection or
output key fails with an opaque 1C error — reject what the skill owns itself."""
param = "-AdditionalIbcmdArguments" if engine == "ibcmd" else "-AdditionalV8Arguments"
owned = IBCMD_OWNED_KEYS if engine == "ibcmd" else V8_OWNED_KEYS
for tok in extra:
if engine == "ibcmd" and not tok.startswith("-"):
print(
f"Error: '{tok}' is a positional token — pass values as --key=value "
f"({param} cannot extend the ibcmd command)",
file=sys.stderr,
)
sys.exit(1)
for k in owned:
if arg_key_match(tok, k):
hint = f" (use {hints[k]})" if hints and k in hints else ""
print(
f"Error: {k} is controlled by the skill and cannot be passed via {param}{hint}",
file=sys.stderr,
)
sys.exit(1)
def format_args_for_display(arglist, engine):
"""Redact values of secret-prone keys in glued, =-joined and separate forms.
Matching here is a plain prefix (no letter rule): over-masking costs nothing,
a leaked password does."""
keys = IBCMD_SECRET_KEYS if engine == "ibcmd" else V8_SECRET_KEYS
res = []
mask_next = False
for tok in arglist:
if mask_next:
res.append("***")
mask_next = False
continue
hit = None
for k in keys:
if tok[: len(k)].lower() == k.lower():
hit = k
break
if hit is None:
res.append(tok)
elif len(tok) == len(hit):
res.append(tok)
mask_next = True
elif tok[len(hit)] == "=":
res.append(hit + "=***")
else:
res.append(hit + "***")
return res
def extract_extra_args(argv, known_opts):
"""argparse refuses values that start with '-' (every ibcmd key does), so pull the two
escape-hatch lists out of argv by hand: after the flag, take everything up to the next
declared skill option. Returns (remaining_argv, v8_extra, ibcmd_extra)."""
rest, v8, ibcmd = [], [], []
i = 0
while i < len(argv):
low = argv[i].lower()
if low in ("-additionalv8arguments", "-additionalibcmdarguments"):
target = v8 if low == "-additionalv8arguments" else ibcmd
i += 1
while i < len(argv) and argv[i].lower() not in known_opts:
target.append(argv[i])
i += 1
continue
rest.append(argv[i])
i += 1
return rest, v8, ibcmd
def resolve_extra_args(engine, v8_extra, ibcmd_extra, hints):
"""Pick the argument list for the selected engine and validate it. An explicitly
passed parameter for the other engine is an error; the same keys coming from
.v8-project.json simply do not apply — a project may describe both engines.
Comma-separated elements are split apart: PowerShell's -File cannot bind an array,
so that form is the documented one and both ports must accept it. A value containing
a comma is not supported."""
v8_extra = [p for tok in v8_extra for p in str(tok).split(",") if p]
ibcmd_extra = [p for tok in ibcmd_extra for p in str(tok).split(",") if p]
if engine == "ibcmd" and v8_extra:
print(
"Error: -AdditionalV8Arguments applies to 1cv8 only; the selected engine is ibcmd "
"(use -AdditionalIbcmdArguments)",
file=sys.stderr,
)
sys.exit(1)
if engine != "ibcmd" and ibcmd_extra:
print(
"Error: -AdditionalIbcmdArguments applies to ibcmd only; the selected engine is 1cv8 "
"(use -AdditionalV8Arguments)",
file=sys.stderr,
)
sys.exit(1)
if engine == "ibcmd":
extra = project_extra_args("ibcmdargs") + list(ibcmd_extra)
else:
extra = project_extra_args("v8args") + list(v8_extra)
if extra:
assert_extra_args(extra, engine, hints)
return extra
def new_uuid():
return str(uuid.uuid4())
def scan_ref_types(source_dir):
"""Scan XML files for reference/object/recordset types. Returns {metaType: {name: True}}."""
type_map = {}
ref_pattern = re.compile(
r'(?:cfg:|d\dp1:)(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef'
r'|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef'
r'|ExchangePlanRef|BusinessProcessRef|TaskRef)'
r'\.([A-Za-z\u0400-\u04FF\d_]+)'
)
obj_pattern = re.compile(
r'(?:cfg:|d\dp1:)(CatalogObject|DocumentObject|ChartOfAccountsObject'
r'|ChartOfCharacteristicTypesObject|ChartOfCalculationTypesObject'
r'|ExchangePlanObject|BusinessProcessObject|TaskObject)'
r'\.([A-Za-z\u0400-\u04FF\d_]+)'
)
rs_pattern = re.compile(
r'(?:cfg:|d\dp1:)(InformationRegisterRecordSet|AccumulationRegisterRecordSet'
r'|AccountingRegisterRecordSet|CalculationRegisterRecordSet)'
r'\.([A-Za-z\u0400-\u04FF\d_]+)'
)
char_pattern = re.compile(r'cfg:Characteristic\.([A-Za-z\u0400-\u04FF\d_]+)')
dt_pattern = re.compile(r'cfg:DefinedType\.([A-Za-z\u0400-\u04FF\d_]+)')
ref_map = {
'CatalogRef': 'Catalog', 'DocumentRef': 'Document', 'EnumRef': 'Enum',
'ChartOfAccountsRef': 'ChartOfAccounts',
'ChartOfCharacteristicTypesRef': 'ChartOfCharacteristicTypes',
'ChartOfCalculationTypesRef': 'ChartOfCalculationTypes',
'ExchangePlanRef': 'ExchangePlan', 'BusinessProcessRef': 'BusinessProcess', 'TaskRef': 'Task',
}
obj_map = {
'CatalogObject': 'Catalog', 'DocumentObject': 'Document',
'ChartOfAccountsObject': 'ChartOfAccounts',
'ChartOfCharacteristicTypesObject': 'ChartOfCharacteristicTypes',
'ChartOfCalculationTypesObject': 'ChartOfCalculationTypes',
'ExchangePlanObject': 'ExchangePlan', 'BusinessProcessObject': 'BusinessProcess', 'TaskObject': 'Task',
}
rs_map = {
'InformationRegisterRecordSet': 'InformationRegister',
'AccumulationRegisterRecordSet': 'AccumulationRegister',
'AccountingRegisterRecordSet': 'AccountingRegister',
'CalculationRegisterRecordSet': 'CalculationRegister',
}
for dirpath, _, filenames in os.walk(source_dir):
for fn in filenames:
if not fn.endswith('.xml'):
continue
fp = os.path.join(dirpath, fn)
try:
with open(fp, 'r', encoding='utf-8-sig') as f:
content = f.read()
except Exception:
continue
for m in ref_pattern.finditer(content):
mt = ref_map[m.group(1)]
type_map.setdefault(mt, {})[m.group(2)] = True
for m in obj_pattern.finditer(content):
mt = obj_map[m.group(1)]
type_map.setdefault(mt, {})[m.group(2)] = True
for m in rs_pattern.finditer(content):
mt = rs_map[m.group(1)]
type_map.setdefault(mt, {})[m.group(2)] = True
for m in char_pattern.finditer(content):
type_map.setdefault('ChartOfCharacteristicTypes', {})[m.group(1)] = True
for m in dt_pattern.finditer(content):
type_map.setdefault('DefinedType', {})[m.group(1)] = True
return type_map
def scan_register_columns(source_dir):
"""Scan Form.xml for register record set columns referenced via DataPath.
Returns {"RegisterType.RegisterName": {"col1": True, "col2": True}}."""
import xml.etree.ElementTree as ET
register_columns = {}
std_cols = {'LineNumber', 'Period', 'Recorder', 'Active', 'RecordType'}
rs_type_map = {
'InformationRegisterRecordSet': 'InformationRegister',
'AccumulationRegisterRecordSet': 'AccumulationRegister',
'AccountingRegisterRecordSet': 'AccountingRegister',
'CalculationRegisterRecordSet': 'CalculationRegister',
}
rs_pattern = re.compile(
r'^(?:cfg:|d\dp1:)(InformationRegisterRecordSet|AccumulationRegisterRecordSet'
r'|AccountingRegisterRecordSet|CalculationRegisterRecordSet)\.(.+)$'
)
dp_pattern = re.compile(r'([A-Za-z\u0400-\u04FF\d_]+)\.([A-Za-z\u0400-\u04FF\d_]+)')
ns = {
'v8': 'http://v8.1c.ru/8.1/data/core',
'f': 'http://v8.1c.ru/8.3/xcf/logform',
}
for dirpath, _, filenames in os.walk(source_dir):
for fn in filenames:
if fn != 'Form.xml':
continue
fp = os.path.join(dirpath, fn)
try:
with open(fp, 'r', encoding='utf-8-sig') as fh:
content = fh.read()
except Exception:
continue
if '' not in content:
continue
# Parse form attributes to find register recordset types
reg_attr_map = {} # formAttrName -> "RegisterType.RegisterName"
try:
root = ET.fromstring(content)
for attr_node in root.iter('{http://v8.1c.ru/8.3/xcf/logform}Attribute'):
attr_name = attr_node.get('name', '')
for type_node in attr_node.iter('{http://v8.1c.ru/8.1/data/core}Type'):
m = rs_pattern.match(type_node.text or '')
if m:
reg_type = rs_type_map[m.group(1)]
reg_key = f"{reg_type}.{m.group(2)}"
reg_attr_map[attr_name] = reg_key
register_columns.setdefault(reg_key, {})
except Exception:
continue
# Find DataPath references like "AttrName.ColumnName"
for m in dp_pattern.finditer(content):
attr_name, col_name = m.group(1), m.group(2)
if attr_name in reg_attr_map and col_name not in std_cols:
register_columns[reg_attr_map[attr_name]][col_name] = True
return register_columns
NS = (
'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" '
'xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" '
'xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" '
'xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" '
'xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" '
'xmlns:style="http://v8.1c.ru/8.1/data/ui/style" '
'xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" '
'xmlns:v8="http://v8.1c.ru/8.1/data/core" '
'xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" '
'xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" '
'xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" '
'xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" '
'xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" '
'xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" '
'xmlns:xs="http://www.w3.org/2001/XMLSchema" '
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17"'
)
CLASS_IDS = [
"9cd510cd-abfc-11d4-9434-004095e12fc7",
"9fcd25a0-4822-11d4-9414-008048da11f9",
"e3687481-0a87-462c-a166-9f34594f9bba",
"9de14907-ec23-4a07-96f0-85521cb6b53b",
"51f2d5d8-ea4d-4064-8892-82951750031e",
"e68182ea-4237-4383-967f-90c1e3370bc7",
"fb282519-d103-4dd3-bc12-cb271d631dfc",
]
GT_DEFS = {
'Catalog': [('CatalogObject','Object'),('CatalogRef','Ref'),('CatalogSelection','Selection'),('CatalogList','List'),('CatalogManager','Manager')],
'Document': [('DocumentObject','Object'),('DocumentRef','Ref'),('DocumentSelection','Selection'),('DocumentList','List'),('DocumentManager','Manager')],
'Enum': [('EnumRef','Ref'),('EnumManager','Manager'),('EnumList','List')],
'ChartOfAccounts': [('ChartOfAccountsObject','Object'),('ChartOfAccountsRef','Ref'),('ChartOfAccountsSelection','Selection'),('ChartOfAccountsList','List'),('ChartOfAccountsManager','Manager')],
'ChartOfCharacteristicTypes': [('ChartOfCharacteristicTypesObject','Object'),('ChartOfCharacteristicTypesRef','Ref'),('ChartOfCharacteristicTypesSelection','Selection'),('ChartOfCharacteristicTypesList','List'),('Characteristic','Characteristic'),('ChartOfCharacteristicTypesManager','Manager')],
'ChartOfCalculationTypes': [('ChartOfCalculationTypesObject','Object'),('ChartOfCalculationTypesRef','Ref'),('ChartOfCalculationTypesSelection','Selection'),('ChartOfCalculationTypesList','List'),('ChartOfCalculationTypesManager','Manager')],
'ExchangePlan': [('ExchangePlanObject','Object'),('ExchangePlanRef','Ref'),('ExchangePlanSelection','Selection'),('ExchangePlanList','List'),('ExchangePlanManager','Manager')],
'BusinessProcess': [('BusinessProcessObject','Object'),('BusinessProcessRef','Ref'),('BusinessProcessSelection','Selection'),('BusinessProcessList','List'),('BusinessProcessManager','Manager')],
'Task': [('TaskObject','Object'),('TaskRef','Ref'),('TaskSelection','Selection'),('TaskList','List'),('TaskManager','Manager')],
'InformationRegister': [('InformationRegisterRecord','Record'),('InformationRegisterManager','Manager'),('InformationRegisterSelection','Selection'),('InformationRegisterList','List'),('InformationRegisterRecordSet','RecordSet'),('InformationRegisterRecordKey','RecordKey'),('InformationRegisterRecordManager','RecordManager')],
'AccumulationRegister': [('AccumulationRegisterRecord','Record'),('AccumulationRegisterManager','Manager'),('AccumulationRegisterSelection','Selection'),('AccumulationRegisterList','List'),('AccumulationRegisterRecordSet','RecordSet'),('AccumulationRegisterRecordKey','RecordKey')],
'AccountingRegister': [('AccountingRegisterRecord','Record'),('AccountingRegisterManager','Manager'),('AccountingRegisterSelection','Selection'),('AccountingRegisterExtDimensions','ExtDimensions'),('AccountingRegisterList','List'),('AccountingRegisterRecordSet','RecordSet'),('AccountingRegisterRecordKey','RecordKey')],
'CalculationRegister': [('CalculationRegisterRecord','Record'),('CalculationRegisterManager','Manager'),('CalculationRegisterSelection','Selection'),('CalculationRegisterList','List'),('CalculationRegisterRecordSet','RecordSet'),('CalculationRegisterRecordKey','RecordKey')],
'DefinedType': [('DefinedType','DefinedType')],
}
META_INFO = {
'Catalog': ('Catalog', 'Catalogs'),
'Document': ('Document', 'Documents'),
'Enum': ('Enum', 'Enums'),
'ChartOfAccounts': ('ChartOfAccounts', 'ChartsOfAccounts'),
'ChartOfCharacteristicTypes': ('ChartOfCharacteristicTypes', 'ChartsOfCharacteristicTypes'),
'ChartOfCalculationTypes': ('ChartOfCalculationTypes', 'ChartsOfCalculationTypes'),
'ExchangePlan': ('ExchangePlan', 'ExchangePlans'),
'BusinessProcess': ('BusinessProcess', 'BusinessProcesses'),
'Task': ('Task', 'Tasks'),
'InformationRegister': ('InformationRegister', 'InformationRegisters'),
'AccumulationRegister': ('AccumulationRegister', 'AccumulationRegisters'),
'AccountingRegister': ('AccountingRegister', 'AccountingRegisters'),
'CalculationRegister': ('CalculationRegister', 'CalculationRegisters'),
'DefinedType': ('DefinedType', 'DefinedTypes'),
}
STD_ATTRS_BY_TYPE = {
'Catalog': ['PredefinedDataName','Predefined','Ref','DeletionMark','IsFolder','Owner','Parent','Description','Code'],
'Document': ['Posted','Ref','DeletionMark','Date','Number'],
'Enum': ['Order','Ref'],
'ChartOfAccounts': ['PredefinedDataName','Predefined','Ref','DeletionMark','Description','Code','Parent','Order','Type','OffBalance'],
'ChartOfCharacteristicTypes': ['PredefinedDataName','Predefined','Ref','DeletionMark','Description','Code','Parent','ValueType'],
'ChartOfCalculationTypes': ['PredefinedDataName','Predefined','Ref','DeletionMark','Description','Code','ActionPeriodIsBasic'],
'ExchangePlan': ['Ref','DeletionMark','Code','Description','ThisNode','SentNo','ReceivedNo'],
'BusinessProcess': ['Ref','DeletionMark','Date','Number','Started','Completed','HeadTask'],
'Task': ['Ref','DeletionMark','Date','Number','Executed','Description','RoutePoint','BusinessProcess'],
'InformationRegister': ['Active','LineNumber','Recorder','Period'],
'AccumulationRegister': ['Active','LineNumber','Recorder','Period'],
'AccountingRegister': ['Active','Period','Recorder','LineNumber','Account'],
'CalculationRegister': ['Active','Recorder','LineNumber','RegistrationPeriod','CalculationType','ReversingEntry'],
}
STD_ATTR_BODY = """\t\t\t\t
\t\t\t\tDontCheck
\t\t\t\tfalse
\t\t\t\tfalse
\t\t\t\tAuto
\t\t\t\t
\t\t\t\t
\t\t\t\tfalse
\t\t\t\t
\t\t\t\t
\t\t\t\tAuto
\t\t\t\tAuto
\t\t\t\t
\t\t\t\tfalse
\t\t\t\tUse
\t\t\t\tfalse
\t\t\t\t
\t\t\t\t
\t\t\t\t
\t\t\t\tUse
\t\t\t\t
\t\t\t\t
\t\t\t\t
\t\t\t\t"""
def build_std_attrs(meta_type):
attrs = STD_ATTRS_BY_TYPE.get(meta_type)
if not attrs:
return ''
lines = ['\t\t\t']
for a in attrs:
lines.append(f'\t\t\t\t')
lines.append(STD_ATTR_BODY)
lines.append(f'\t\t\t\t')
lines.append('\t\t\t')
return '\n'.join(lines) + '\n'
def build_internal_info(meta_type, obj_name):
gts = GT_DEFS.get(meta_type)
if not gts:
return ''
lines = ['\t\t']
if meta_type == 'ExchangePlan':
lines.append(f'\t\t\t{new_uuid()}')
for prefix, cat in gts:
full = f'{prefix}.{obj_name}'
lines.append(f'\t\t\t')
lines.append(f'\t\t\t\t{new_uuid()}')
lines.append(f'\t\t\t\t{new_uuid()}')
lines.append(f'\t\t\t')
lines.append('\t\t')
return '\n'.join(lines)
# Properties templates per type — returns the Properties content (without tags)
PROPS = {}
PROPS['Catalog'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\tHierarchyFoldersAndItems
\t\t\tfalse
\t\t\t2
\t\t\ttrue
\t\t\tfalse
\t\t\t
\t\t\tToItems
\t\t\t9
\t\t\t25
\t\t\tString
\t\t\tVariable
\t\t\tWholeCatalog
\t\t\tfalse
\t\t\ttrue
\t\t\tAsDescription
{sa}\t\t\t
\t\t\tAuto
\t\t\tInDialog
\t\t\ttrue
\t\t\tBothWays
\t\t\t
\t\t\tBegin
\t\t\tDontUse
\t\t\tDirectly
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\tAutomatic
\t\t\tUse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tDontUse
\t\t\tAuto
\t\t\tDontUse
\t\t\tfalse
\t\t\tfalse"""
PROPS['Enum'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
{sa}\t\t\t
\t\t\ttrue
\t\t\tBothWays
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tAuto"""
PROPS['InformationRegister'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\tInDialog
\t\t\t
\t\t\t
\t\t\t
\t\t\t
{sa}\t\t\tNonperiodical
\t\t\tIndependent
\t\t\tfalse
\t\t\tfalse
\t\t\tAutomatic
\t\t\tUse
\t\t\tfalse
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tDontUse
\t\t\tfalse
\t\t\tfalse"""
PROPS['AccumulationRegister'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\tBalance
\t\t\tfalse
{sa}\t\t\tAutomatic
\t\t\tUse
\t\t\ttrue
\t\t\t
\t\t\t
\t\t\t"""
PROPS['AccountingRegister'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\tfalse
{sa}\t\t\tAutomatic
\t\t\tUse
\t\t\ttrue
\t\t\t
\t\t\t
\t\t\t"""
PROPS['CalculationRegister'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
{sa}\t\t\tAutomatic
\t\t\tUse
\t\t\t
\t\t\t
\t\t\t"""
PROPS['ChartOfAccounts'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t20
\t\t\t100
\t\t\tWholeCatalog
\t\t\tfalse
\t\t\ttrue
\t\t\tAsDescription
{sa}\t\t\t
\t\t\tAuto
\t\t\tInDialog
\t\t\ttrue
\t\t\tBothWays
\t\t\t
\t\t\tBegin
\t\t\tDontUse
\t\t\tDirectly
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\ttrue
\t\t\t5
\t\t\t0
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\tAutomatic
\t\t\tUse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tDontUse
\t\t\tAuto
\t\t\tDontUse
\t\t\tfalse
\t\t\tfalse"""
PROPS['ChartOfCharacteristicTypes'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t9
\t\t\tVariable
\t\t\t25
\t\t\tfalse
\t\t\ttrue
\t\t\tAsDescription
\t\t\t
\t\t\t
\t\t\t\txs:boolean
\t\t\t\txs:string
\t\t\t\t
\t\t\t\t\t0
\t\t\t\t\tVariable
\t\t\t\t
\t\t\t\txs:decimal
\t\t\t\t
\t\t\t\t\t15
\t\t\t\t\t2
\t\t\t\t\tAny
\t\t\t\t
\t\t\t\txs:dateTime
\t\t\t\t
\t\t\t\t\tDateTime
\t\t\t\t
\t\t\t
\t\t\tfalse
\t\t\ttrue
{sa}\t\t\t
\t\t\tAuto
\t\t\tInDialog
\t\t\ttrue
\t\t\tBothWays
\t\t\t
\t\t\tBegin
\t\t\tDontUse
\t\t\tDirectly
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\tAutomatic
\t\t\tUse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tDontUse
\t\t\tAuto
\t\t\tDontUse
\t\t\tfalse
\t\t\tfalse"""
PROPS['ChartOfCalculationTypes'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t9
\t\t\t25
\t\t\tString
\t\t\tVariable
\t\t\tWholeCatalog
\t\t\tfalse
\t\t\ttrue
\t\t\tAsDescription
{sa}\t\t\t
\t\t\tAuto
\t\t\tInDialog
\t\t\ttrue
\t\t\tBothWays
\t\t\t
\t\t\tBegin
\t\t\tDontUse
\t\t\tDirectly
\t\t\tNotDepend
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\tAutomatic
\t\t\tUse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tDontUse
\t\t\tAuto
\t\t\tDontUse
\t\t\tfalse
\t\t\tfalse"""
PROPS['ExchangePlan'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t9
\t\t\t25
\t\t\tVariable
{sa}\t\t\tAsDescription
\t\t\t
\t\t\tAuto
\t\t\tInDialog
\t\t\ttrue
\t\t\tBothWays
\t\t\t
\t\t\tBegin
\t\t\tDontUse
\t\t\tDirectly
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\tAutomatic
\t\t\tUse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tDontUse
\t\t\tAuto
\t\t\tfalse
\t\t\tDontUse
\t\t\tfalse
\t\t\tfalse"""
PROPS['BusinessProcess'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\tString
\t\t\t11
\t\t\tVariable
\t\t\tYear
\t\t\tfalse
\t\t\ttrue
{sa}\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\tAutomatic
\t\t\tUse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tAuto
\t\t\tDontUse
\t\t\tfalse
\t\t\tfalse"""
PROPS['Task'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\tString
\t\t\t11
\t\t\tVariable
\t\t\tYear
\t\t\tfalse
\t\t\ttrue
\t\t\t25
{sa}\t\t\t
\t\t\t
\t\t\tBegin
\t\t\tDontUse
\t\t\tDirectly
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\tAutomatic
\t\t\tUse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tAuto
\t\t\tDontUse
\t\t\tfalse
\t\t\tfalse"""
PROPS['DefinedType'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\t
\t\t\t\txs:string
\t\t\t\t
\t\t\t\t\t0
\t\t\t\t\tVariable
\t\t\t\t
\t\t\t"""
def build_doc_props(obj_name, std_attrs, reg_records_xml):
return f"""\t\t\t{obj_name}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\tString
\t\t\t11
\t\t\tVariable
\t\t\tYear
\t\t\tfalse
\t\t\ttrue
{std_attrs}\t\t\t
\t\t\t
\t\t\t
\t\t\tDontUse
\t\t\tBegin
\t\t\tDontUse
\t\t\tDirectly
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tAllow
\t\t\tDeny
\t\t\tAutoDelete
\t\t\tWriteModified
\t\t\tAutoFill
\t\t\t{reg_records_xml}
\t\t\ttrue
\t\t\ttrue
\t\t\tfalse
\t\t\t
\t\t\tAutomatic
\t\t\tUse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tAuto
\t\t\tDontUse
\t\t\tfalse
\t\t\tfalse"""
def write_bom(path, content):
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
f.write(content)
def main():
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
parser = argparse.ArgumentParser(description='Create temp 1C infobase with metadata stubs')
parser.add_argument('-SourceDir', required=True)
parser.add_argument('-V8Path', required=True)
parser.add_argument('-TempBasePath', default='')
parser.add_argument('-AdditionalV8Arguments', nargs='*', default=[],
help='Extra 1cv8 arguments, e.g. /UseHwLicenses+')
parser.add_argument('-AdditionalIbcmdArguments', nargs='*', default=[],
help='Extra ibcmd arguments in --key=value form')
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
args = parser.parse_args(argv)
args.SourceDir = clean_path(args.SourceDir, "-SourceDir")
args.V8Path = clean_path(args.V8Path, "-V8Path")
args.TempBasePath = clean_path(args.TempBasePath, "-TempBasePath")
type_map = scan_ref_types(args.SourceDir)
register_columns = scan_register_columns(args.SourceDir)
has_ref_types = len(type_map) > 0
temp_base = args.TempBasePath or os.path.join(tempfile.gettempdir(), f'epf_stub_db_{random.randint(0,999999)}')
# Add registrator stub document if needed
registrator_types = ['AccumulationRegister', 'AccountingRegister', 'CalculationRegister']
needs_registrator = any(rt in type_map and len(type_map[rt]) > 0 for rt in registrator_types)
if needs_registrator:
type_map.setdefault('Document', {})['\u0417\u0430\u0433\u043b\u0443\u0448\u043a\u0430\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430'] = True # ЗаглушкаРегистратора
if has_ref_types:
cfg_dir = os.path.join(temp_base, 'cfg')
os.makedirs(cfg_dir, exist_ok=True)
# Configuration.xml
uuid_cfg = new_uuid()
uuid_lang = new_uuid()
co_ids = [new_uuid() for _ in range(7)]
co_xml = ''
for i in range(7):
co_xml += f'\n\t\t\t\n\t\t\t\t{CLASS_IDS[i]}\n\t\t\t\t{co_ids[i]}\n\t\t\t'
child_xml = '\n\t\t\t\u0420\u0443\u0441\u0441\u043a\u0438\u0439' # Русский
for meta_type, names in type_map.items():
if meta_type not in META_INFO:
continue
tag = META_INFO[meta_type][0]
for name in names:
child_xml += f'\n\t\t\t<{tag}>{name}{tag}>'
cfg_xml = f"""
\t
\t\t{co_xml}
\t\t
\t\t
\t\t\tStubConfig
\t\t\t
\t\t\t
\t\t\t
\t\t\tVersion8_3_24
\t\t\tManagedApplication
\t\t\t
\t\t\t\tPlatformApplication
\t\t\t
\t\t\tRussian
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\tfalse
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tNormal
\t\t\t
\t\t\t
\t\t\tLanguage.\u0420\u0443\u0441\u0441\u043a\u0438\u0439
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tManaged
\t\t\tNotAutoFree
\t\t\tDontUse
\t\t\tDontUse
\t\t\tTaxi
\t\t\tDontUse
\t\t\tVersion8_3_24
\t\t\t
\t\t
\t\t{child_xml}
\t\t
\t
"""
write_bom(os.path.join(cfg_dir, 'Configuration.xml'), cfg_xml)
# Language
lang_dir = os.path.join(cfg_dir, 'Languages')
os.makedirs(lang_dir, exist_ok=True)
lang_xml = f"""
\t
\t\t
\t\t\t\u0420\u0443\u0441\u0441\u043a\u0438\u0439
\t\t\t
\t\t\t\t
\t\t\t\t\tru
\t\t\t\t\t\u0420\u0443\u0441\u0441\u043a\u0438\u0439
\t\t\t\t
\t\t\t
\t\t\t
\t\t\tru
\t\t
\t
"""
write_bom(os.path.join(lang_dir, '\u0420\u0443\u0441\u0441\u043a\u0438\u0439.xml'), lang_xml)
# Metadata stubs
for meta_type, names in type_map.items():
if meta_type not in META_INFO:
continue
tag, dirname = META_INFO[meta_type]
obj_dir = os.path.join(cfg_dir, dirname)
os.makedirs(obj_dir, exist_ok=True)
for obj_name in names:
obj_uuid = new_uuid()
internal_xml = build_internal_info(meta_type, obj_name)
if internal_xml:
internal_xml = '\n' + internal_xml
sa = build_std_attrs(meta_type)
if meta_type == 'Document':
rr_xml = ''
if obj_name == '\u0417\u0430\u0433\u043b\u0443\u0448\u043a\u0430\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430': # ЗаглушкаРегистратора
rr_lines = []
for rt in registrator_types:
if rt in type_map:
for rn in type_map[rt]:
rr_lines.append(f'\t\t\t\t{rt}.{rn}')
if rr_lines:
rr_xml = '\n' + '\n'.join(rr_lines) + '\n\t\t\t'
props_xml = build_doc_props(obj_name, sa, rr_xml)
elif meta_type in PROPS:
props_xml = PROPS[meta_type](obj_name, sa)
else:
props_xml = f'\t\t\t{obj_name}\n\t\t\t\n\t\t\t'
# ChildObjects — varies by type
if meta_type == 'DefinedType':
child_obj_xml = ''
elif meta_type == 'InformationRegister':
reg_key = f'InformationRegister.{obj_name}'
cols = list(register_columns.get(reg_key, {}).keys()) or ['\u0417\u0430\u0433\u043b\u0443\u0448\u043a\u0430']
parts = []
for i, col in enumerate(cols):
u = new_uuid()
if i == 0:
parts.append(f"""\t\t\t
\t\t\t\t
\t\t\t\t\t{col}
\t\t\t\t\t
\t\t\t\t\txs:string10Variable
\t\t\t\t\tfalsefalse
\t\t\t\t\tfalsefalse
\t\t\t\t\t
\t\t\t\t\tfalseDontCheck
\t\t\t\t\tItems
\t\t\t\t\tAutoAutoAuto
\t\t\t\t\tfalsetruefalse
\t\t\t\t\tDontIndexUseUse
\t\t\t\t
\t\t\t""")
else:
parts.append(f"""\t\t\t
\t\t\t\t
\t\t\t\t\t{col}
\t\t\t\t\t
\t\t\t\t\txs:string10Variable
\t\t\t\t\tfalsefalse
\t\t\t\t\tfalsefalse
\t\t\t\t\t
\t\t\t\t\tfalseDontCheck
\t\t\t\t\tItems
\t\t\t\t\tAutoAutoAuto
\t\t\t\t\tDontIndexUseUse
\t\t\t\t
\t\t\t""")
child_obj_xml = '\n\t\t\n' + '\n'.join(parts) + '\n\t\t'
elif meta_type in ('AccumulationRegister', 'AccountingRegister', 'CalculationRegister'):
reg_key = f'{meta_type}.{obj_name}'
cols = list(register_columns.get(reg_key, {}).keys())
parts = []
# Required stub Resource
parts.append(f"""\t\t\t
\t\t\t\t
\t\t\t\t\t\u0417\u0430\u0433\u043b\u0443\u0448\u043a\u0430
\t\t\t\t\t
\t\t\t\t\txs:decimal152Any
\t\t\t\t\tfalsefalse
\t\t\t\t\tfalsefalse
\t\t\t\t\tDontCheck
\t\t\t\t\tItems
\t\t\t\t\tAutoAutoAuto
\t\t\t\t\tUse
\t\t\t\t
\t\t\t""")
# Form-referenced columns as Dimensions
for col in cols:
parts.append(f"""\t\t\t
\t\t\t\t
\t\t\t\t\t{col}
\t\t\t\t\t
\t\t\t\t\txs:string10Variable
\t\t\t\t\tfalsefalse
\t\t\t\t\tfalsefalse
\t\t\t\t\tDontCheck
\t\t\t\t\tItems
\t\t\t\t\tAutoAutoAuto
\t\t\t\t\tUse
\t\t\t\t
\t\t\t""")
child_obj_xml = '\n\t\t\n' + '\n'.join(parts) + '\n\t\t'
else:
child_obj_xml = '\n\t\t'
obj_xml = f"""
\t<{tag} uuid="{obj_uuid}">{internal_xml}
\t\t
{props_xml}
\t\t{child_obj_xml}
\t{tag}>
"""
write_bom(os.path.join(obj_dir, f'{obj_name}.xml'), obj_xml)
print(f'Generated stub configuration with {len(type_map)} metadata types')
if register_columns:
print('WARNING: Register column categories (Dimension/Resource/Attribute) are guessed. Form field bindings may not survive round-trip through a real database.')
# Stub via ibcmd (one call: create [--import --apply])
stub_engine = "ibcmd" if os.path.basename(args.V8Path).lower().startswith("ibcmd") else "1cv8"
# --- Resolve additional arguments for the selected engine ---
arg_hints = {"/F": "-TempBasePath", "--db-path": "-TempBasePath"}
extra_args = resolve_extra_args(stub_engine, v8_extra, ibcmd_extra, arg_hints)
if stub_engine == "ibcmd":
import shutil
print(f'Creating infobase (ibcmd): {temp_base}')
ib_data = tempfile.mkdtemp(prefix="stub_data_")
ib_args = [args.V8Path, 'infobase', 'create', f'--db-path={temp_base}', '--create-database']
if has_ref_types:
ib_args += [f'--import={os.path.join(temp_base, "cfg")}', '--apply', '--force']
ib_args.append(f'--data={ib_data}')
ib_args.extend(extra_args)
result = run_ibcmd(ib_args, warn_no_user=False)
shutil.rmtree(ib_data, ignore_errors=True)
if result.returncode != 0:
if result.stdout:
print(result.stdout)
if result.stderr:
print(result.stderr, file=sys.stderr)
print(f'Failed to create stub infobase (code: {result.returncode})', file=sys.stderr)
sys.exit(1)
if has_ref_types:
import shutil
shutil.rmtree(os.path.join(temp_base, 'cfg'), ignore_errors=True)
print(f'[OK] Stub database created: {temp_base}')
print(temp_base)
sys.exit(0)
# Create infobase
print(f'Creating infobase: {temp_base}')
result = run_v8(args.V8Path, ['CREATEINFOBASE', f'File="{temp_base}"', '/DisableStartupDialogs']
+ [quote_if_needed(a) for a in extra_args])
if result.returncode != 0:
print_platform_output(result)
print(f'Failed to create infobase (code: {result.returncode})', file=sys.stderr)
sys.exit(1)
if has_ref_types:
cfg_dir = os.path.join(temp_base, 'cfg')
# LoadConfigFromFiles
print('Loading configuration from files...')
result = run_v8(args.V8Path, ['DESIGNER', f'/F"{temp_base}"', '/LoadConfigFromFiles', f'"{cfg_dir}"',
'/DisableStartupDialogs'] + [quote_if_needed(a) for a in extra_args])
if result.returncode != 0:
print_platform_output(result)
print(f'Failed to load config (code: {result.returncode})', file=sys.stderr)
sys.exit(1)
# UpdateDBCfg
print('Updating database configuration...')
update_log = os.path.join(tempfile.gettempdir(), 'stub_update_log.txt')
result = run_v8(args.V8Path, ['DESIGNER', f'/F"{temp_base}"', '/UpdateDBCfg', '/Out', f'"{update_log}"',
'/DisableStartupDialogs'] + [quote_if_needed(a) for a in extra_args])
if result.returncode != 0:
if os.path.isfile(update_log):
try:
with open(update_log, 'r', encoding='utf-8-sig') as f:
print(f.read())
except Exception:
pass
print_platform_output(result)
print(f'Failed to update DB config (code: {result.returncode})', file=sys.stderr)
sys.exit(1)
# Cleanup cfg dir
import shutil
shutil.rmtree(cfg_dir, ignore_errors=True)
print(f'[OK] Stub database created: {temp_base}')
print(temp_base)
if __name__ == '__main__':
main()