diff --git a/.claude/skills/meta-compile/scripts/meta-compile.ps1 b/.claude/skills/meta-compile/scripts/meta-compile.ps1
index c219e76a..4a93ed31 100644
--- a/.claude/skills/meta-compile/scripts/meta-compile.ps1
+++ b/.claude/skills/meta-compile/scripts/meta-compile.ps1
@@ -1068,6 +1068,42 @@ function Emit-Attribute {
X "$indent"
}
+# --- 8b. Command emitter ---
+# $cmd — объект свойств команды. Поля (omit-on-default): synonym/tooltip (ML), comment, group,
+# commandParameterType (тип), parameterUseMode (Single), modifiesData (false), representation (Auto),
+# picture, shortcut, onMainServerUnavalableBehavior (Auto).
+function Emit-Command {
+ param([string]$indent, [string]$cmdName, $cmd)
+ X "$indent"
+ X "$indent`t"
+ X "$indent`t`t$(Esc-Xml $cmdName)"
+ $syn = if ($null -ne $cmd.synonym) { $cmd.synonym } else { Split-CamelCase $cmdName }
+ Emit-MLText "$indent`t`t" "Synonym" $syn
+ if ($cmd.comment) { X "$indent`t`t$(Esc-XmlText "$($cmd.comment)")" } else { X "$indent`t`t" }
+ $group = if ($cmd.group) { "$($cmd.group)" } else { "" }
+ X "$indent`t`t$group"
+ if ($cmd.commandParameterType) {
+ X "$indent`t`t"
+ Emit-TypeContent "$indent`t`t`t" "$($cmd.commandParameterType)"
+ X "$indent`t`t"
+ } else {
+ X "$indent`t`t"
+ }
+ $pum = if ($cmd.parameterUseMode) { "$($cmd.parameterUseMode)" } else { "Single" }
+ X "$indent`t`t$pum"
+ $md = if ($cmd.modifiesData -eq $true) { "true" } else { "false" }
+ X "$indent`t`t$md"
+ $rep = if ($cmd.representation) { "$($cmd.representation)" } else { "Auto" }
+ X "$indent`t`t$rep"
+ Emit-MLText "$indent`t`t" "ToolTip" $cmd.tooltip
+ if ($cmd.picture) { X "$indent`t`t$(Esc-Xml "$($cmd.picture)")" } else { X "$indent`t`t" }
+ if ($cmd.shortcut) { X "$indent`t`t$(Esc-Xml "$($cmd.shortcut)")" } else { X "$indent`t`t" }
+ $osu = if ($cmd.onMainServerUnavalableBehavior) { "$($cmd.onMainServerUnavalableBehavior)" } else { "Auto" }
+ X "$indent`t`t$osu"
+ X "$indent`t"
+ X "$indent"
+}
+
# --- 9. TabularSection emitter ---
function Emit-TabularSection {
@@ -2903,7 +2939,16 @@ if ($objType -in $typesWithAttrTS) {
$addrAttrs = @($def.addressingAttributes)
}
- $childCount = $attrs.Count + $tsSections.Count + $acctFlags.Count + $extDimFlags.Count + $addrAttrs.Count
+ # Commands (map имя→объект ИЛИ array [{name,...}]) — генерируем блок + CommandModule.bsl-заготовку.
+ $commands = @()
+ if ($def.commands) {
+ if ($def.commands -is [array] -or $def.commands.GetType().Name -eq 'Object[]') {
+ foreach ($c in $def.commands) { $commands += @{ name = "$($c.name)"; def = $c } }
+ } else {
+ $def.commands.PSObject.Properties | ForEach-Object { $commands += @{ name = $_.Name; def = $_.Value } }
+ }
+ }
+ $childCount = $attrs.Count + $tsSections.Count + $acctFlags.Count + $extDimFlags.Count + $addrAttrs.Count + $commands.Count
if ($childCount -gt 0) {
$hasChildren = $true
X "`t`t"
@@ -2932,6 +2977,9 @@ if ($objType -in $typesWithAttrTS) {
foreach ($aa in $addrAttrs) {
Emit-AddressingAttribute "`t`t`t" $aa
}
+ foreach ($cmd in $commands) {
+ Emit-Command "`t`t`t" $cmd.name $cmd.def
+ }
X "`t`t"
} else {
X "`t`t"
@@ -3266,6 +3314,18 @@ if ($objType -eq "Catalog" -and $def.predefined -and @($def.predefined).Count -g
$modulesCreated += $predefPath
}
+# Модули команд (Commands/<Имя>/Ext/CommandModule.bsl) — заготовка обработчика.
+if ($commands -and $commands.Count -gt 0) {
+ $cmdModuleStub = "&НаКлиенте`r`nПроцедура ОбработкаКоманды(ПараметрКоманды, ПараметрыВыполненияКоманды)`r`n`r`n`t// Вставьте обработчик команды.`r`n`r`nКонецПроцедуры`r`n"
+ foreach ($cmd in $commands) {
+ $cmdDir = Join-Path (Join-Path (Join-Path $objSubDir "Commands") $cmd.name) "Ext"
+ if (-not (Test-Path $cmdDir)) { New-Item -ItemType Directory -Path $cmdDir -Force | Out-Null }
+ $cmdModPath = Join-Path $cmdDir "CommandModule.bsl"
+ [System.IO.File]::WriteAllText($cmdModPath, $cmdModuleStub, $enc)
+ $modulesCreated += $cmdModPath
+ }
+}
+
# --- 17. Register in Configuration.xml ---
$configXmlPath = Join-Path $OutputDir "Configuration.xml"
diff --git a/.claude/skills/meta-compile/scripts/meta-compile.py b/.claude/skills/meta-compile/scripts/meta-compile.py
index 1f87367e..42385adc 100644
--- a/.claude/skills/meta-compile/scripts/meta-compile.py
+++ b/.claude/skills/meta-compile/scripts/meta-compile.py
@@ -1061,6 +1061,39 @@ def emit_attribute(indent, parsed, context):
# 9. TabularSection emitter
# ---------------------------------------------------------------------------
+def emit_command(indent, cmd_name, cmd):
+ X(f'{indent}')
+ X(f'{indent}\t')
+ X(f'{indent}\t\t{esc_xml(cmd_name)}')
+ syn = cmd['synonym'] if cmd.get('synonym') is not None else split_camel_case(cmd_name)
+ emit_mltext(f'{indent}\t\t', 'Synonym', syn)
+ if cmd.get('comment'):
+ X(f'{indent}\t\t{esc_xml_text(str(cmd["comment"]))}')
+ else:
+ X(f'{indent}\t\t')
+ X(f'{indent}\t\t{cmd.get("group") or ""}')
+ if cmd.get('commandParameterType'):
+ X(f'{indent}\t\t')
+ emit_type_content(f'{indent}\t\t\t', str(cmd['commandParameterType']))
+ X(f'{indent}\t\t')
+ else:
+ X(f'{indent}\t\t')
+ X(f'{indent}\t\t{cmd.get("parameterUseMode") or "Single"}')
+ X(f'{indent}\t\t{"true" if cmd.get("modifiesData") is True else "false"}')
+ X(f'{indent}\t\t{cmd.get("representation") or "Auto"}')
+ emit_mltext(f'{indent}\t\t', 'ToolTip', cmd.get('tooltip'))
+ if cmd.get('picture'):
+ X(f'{indent}\t\t{esc_xml(str(cmd["picture"]))}')
+ else:
+ X(f'{indent}\t\t')
+ if cmd.get('shortcut'):
+ X(f'{indent}\t\t{esc_xml(str(cmd["shortcut"]))}')
+ else:
+ X(f'{indent}\t\t')
+ X(f'{indent}\t\t{cmd.get("onMainServerUnavalableBehavior") or "Auto"}')
+ X(f'{indent}\t')
+ X(f'{indent}')
+
def emit_tabular_section(indent, ts_name, columns, object_type, object_name, ts_synonym_arg=None, ts_tooltip=None, ts_comment=None):
uid = new_uuid()
X(f'{indent}')
@@ -2579,6 +2612,7 @@ types_with_attr_ts = [
'BusinessProcess', 'Task',
]
+commands = [] # заполняется внутри блока types_with_attr_ts; на уровне модуля для записи модулей команд
if obj_type in types_with_attr_ts:
def _as_list(val):
"""Normalize attributes: dict {"K":"V"} → ["K:V"], list/other → list."""
@@ -2623,7 +2657,17 @@ if obj_type in types_with_attr_ts:
addr_attrs = []
if obj_type == 'Task' and defn.get('addressingAttributes'):
addr_attrs = _as_list(defn['addressingAttributes'])
- child_count = len(attrs) + len(ts_sections) + len(acct_flags) + len(ext_dim_flags) + len(addr_attrs)
+ # Commands (map имя→объект ИЛИ array [{name,...}])
+ commands = []
+ if defn.get('commands'):
+ cd = defn['commands']
+ if isinstance(cd, list):
+ for c in cd:
+ commands.append({'name': str(c.get('name', '')), 'def': c})
+ else:
+ for k, v in cd.items():
+ commands.append({'name': k, 'def': v})
+ child_count = len(attrs) + len(ts_sections) + len(acct_flags) + len(ext_dim_flags) + len(addr_attrs) + len(commands)
if child_count > 0:
has_children = True
X('\t\t')
@@ -2650,6 +2694,8 @@ if obj_type in types_with_attr_ts:
emit_ext_dimension_accounting_flag('\t\t\t', edf_name)
for aa in addr_attrs:
emit_addressing_attribute('\t\t\t', aa)
+ for cmd in commands:
+ emit_command('\t\t\t', cmd['name'], cmd['def'])
X('\t\t')
else:
X('\t\t')
@@ -2935,6 +2981,16 @@ if obj_type == 'Catalog' and defn.get('predefined'):
write_utf8_bom(predef_path, predef_xml)
modules_created.append(predef_path)
+# Модули команд (Commands/<Имя>/Ext/CommandModule.bsl) — заготовка обработчика.
+if commands:
+ cmd_module_stub = '&НаКлиенте\r\nПроцедура ОбработкаКоманды(ПараметрКоманды, ПараметрыВыполненияКоманды)\r\n\r\n\t// Вставьте обработчик команды.\r\n\r\nКонецПроцедуры\r\n'
+ for cmd in commands:
+ cmd_dir = os.path.join(obj_sub_dir, 'Commands', cmd['name'], 'Ext')
+ os.makedirs(cmd_dir, exist_ok=True)
+ cmd_mod_path = os.path.join(cmd_dir, 'CommandModule.bsl')
+ write_utf8_bom(cmd_mod_path, cmd_module_stub)
+ modules_created.append(cmd_mod_path)
+
# ---------------------------------------------------------------------------
# 17. Register in Configuration.xml
# ---------------------------------------------------------------------------
diff --git a/.claude/skills/meta-decompile/scripts/meta-decompile.ps1 b/.claude/skills/meta-decompile/scripts/meta-decompile.ps1
index 33423711..0d4d6bb2 100644
--- a/.claude/skills/meta-decompile/scripts/meta-decompile.ps1
+++ b/.claude/skills/meta-decompile/scripts/meta-decompile.ps1
@@ -1,4 +1,4 @@
-# meta-decompile v0.10 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
+# meta-decompile v0.11 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
#
# Пилот: только Catalog. Инверс meta-compile (omit-on-default: ключ эмитим только
@@ -357,6 +357,32 @@ if ($childObjs) {
}
$dsl['tabularSections'] = $tsMap
}
+ # --- Commands (полноблочные в ChildObjects) → DSL commands (map имя→объект, omit-on-default).
+ # Тела модулей команд (CommandModule.bsl) — вне скоупа (как ObjectModule). ---
+ $cmdNodes = @($childObjs.SelectNodes('md:Command', $nsm))
+ if ($cmdNodes.Count -gt 0) {
+ $cmdMap = [ordered]@{}
+ foreach ($cm in $cmdNodes) {
+ $cp = $cm.SelectSingleNode('md:Properties', $nsm)
+ $cn = ($cp.SelectSingleNode('md:Name', $nsm)).InnerText
+ $o = [ordered]@{}
+ $syn = Get-MLValue ($cp.SelectSingleNode('md:Synonym', $nsm))
+ if ($syn -is [string]) { if ($syn -ne (Split-CamelWords $cn)) { $o['synonym'] = $syn } }
+ elseif ($null -ne $syn) { $o['synonym'] = $syn }
+ $cmtN = $cp.SelectSingleNode('md:Comment', $nsm); if ($cmtN -and $cmtN.InnerText) { $o['comment'] = $cmtN.InnerText }
+ $grpN = $cp.SelectSingleNode('md:Group', $nsm); if ($grpN -and $grpN.InnerText) { $o['group'] = $grpN.InnerText }
+ $cpt = Get-TypeShorthand ($cp.SelectSingleNode('md:CommandParameterType', $nsm)); if ($cpt) { $o['commandParameterType'] = $cpt }
+ $pumN = $cp.SelectSingleNode('md:ParameterUseMode', $nsm); if ($pumN -and $pumN.InnerText -ne 'Single') { $o['parameterUseMode'] = $pumN.InnerText }
+ $mdN = $cp.SelectSingleNode('md:ModifiesData', $nsm); if ($mdN -and $mdN.InnerText -eq 'true') { $o['modifiesData'] = $true }
+ $repN = $cp.SelectSingleNode('md:Representation', $nsm); if ($repN -and $repN.InnerText -ne 'Auto') { $o['representation'] = $repN.InnerText }
+ $ctt = Get-MLValue ($cp.SelectSingleNode('md:ToolTip', $nsm)); if ($null -ne $ctt) { $o['tooltip'] = $ctt }
+ $picN = $cp.SelectSingleNode('md:Picture', $nsm); if ($picN -and $picN.InnerText) { $o['picture'] = $picN.InnerText }
+ $scN = $cp.SelectSingleNode('md:Shortcut', $nsm); if ($scN -and $scN.InnerText) { $o['shortcut'] = $scN.InnerText }
+ $osuN = $cp.SelectSingleNode('md:OnMainServerUnavalableBehavior', $nsm); if ($osuN -and $osuN.InnerText -ne 'Auto') { $o['onMainServerUnavalableBehavior'] = $osuN.InnerText }
+ $cmdMap[$cn] = $o
+ }
+ $dsl['commands'] = $cmdMap
+ }
}
# --- Предопределённые (соседний Ext/Predefined.xml) → DSL predefined.
diff --git a/docs/meta-dsl-spec.md b/docs/meta-dsl-spec.md
index 0bcd84f3..f58158f1 100644
--- a/docs/meta-dsl-spec.md
+++ b/docs/meta-dsl-spec.md
@@ -355,6 +355,34 @@ JSON DSL для описания объектов метаданных конф
> Пока поддержано для **Catalog** (`CatalogPredefinedItems`). ChartOf*/ПВХ — при их проработке.
+#### 7.1.3 `commands` — команды объекта
+
+Команды объекта → ``-блоки в ChildObjects + заготовка `Commands/<Имя>/Ext/CommandModule.bsl` (обработчик `ОбработкаКоманды`). Ключ — имя команды, значение — объект свойств (map `имя→объект`, либо массив `[{name, …}]`).
+
+| Ключ | XML | Умолчание |
+|------|-----|-----------|
+| `synonym` | Synonym | авто из имени (ML) |
+| `tooltip` | ToolTip | пусто (ML) |
+| `comment` | Comment | пусто |
+| `group` | Group | пусто (`FormCommandBarImportant`, `FormNavigationPanelGoTo`, …) |
+| `commandParameterType` | CommandParameterType | пусто (тип, напр. `CatalogRef.Номенклатура`) |
+| `parameterUseMode` | ParameterUseMode | `Single` (`Single`/`Multiple`) |
+| `modifiesData` | ModifiesData | `false` (bool) |
+| `representation` | Representation | `Auto` |
+| `picture` / `shortcut` | Picture / Shortcut | пусто |
+| `onMainServerUnavalableBehavior` | OnMainServerUnavalableBehavior | `Auto` |
+
+```json
+"commands": {
+ "ПечатьЭтикеток": {
+ "synonym": "Печать этикеток", "group": "FormCommandBarImportant",
+ "commandParameterType": "CatalogRef.Номенклатура", "modifiesData": true
+ }
+}
+```
+
+> Тело модуля команды генерируется заготовкой (обработчик пустой). Раундтрип тела модуля не проверяет (как ObjectModule).
+
### 7.2 Document
| Поле JSON | Умолчание | XML элемент |
diff --git a/tests/skills/cases/meta-compile/catalog-command.json b/tests/skills/cases/meta-compile/catalog-command.json
new file mode 100644
index 00000000..30477d8d
--- /dev/null
+++ b/tests/skills/cases/meta-compile/catalog-command.json
@@ -0,0 +1,20 @@
+{
+ "name": "Справочник с командой",
+ "input": {
+ "type": "Catalog", "name": "Номенклатура",
+ "commands": {
+ "ПечатьЭтикеток": {
+ "synonym": { "ru": "Печать этикеток", "en": "Print labels" },
+ "group": "FormCommandBarImportant",
+ "commandParameterType": "CatalogRef.Номенклатура",
+ "parameterUseMode": "Multiple",
+ "modifiesData": true,
+ "tooltip": "Печать этикеток для выбранных товаров"
+ }
+ }
+ },
+ "validatePath": "Catalogs/Номенклатура",
+ "expect": {
+ "files": ["Catalogs/Номенклатура.xml", "Catalogs/Номенклатура/Commands/ПечатьЭтикеток/Ext/CommandModule.bsl"]
+ }
+}
diff --git a/tests/skills/cases/meta-compile/snapshots/catalog-command/Catalogs/Номенклатура.xml b/tests/skills/cases/meta-compile/snapshots/catalog-command/Catalogs/Номенклатура.xml
new file mode 100644
index 00000000..4002b9a5
--- /dev/null
+++ b/tests/skills/cases/meta-compile/snapshots/catalog-command/Catalogs/Номенклатура.xml
@@ -0,0 +1,124 @@
+
+
+
+
+
+ UUID-002
+ UUID-003
+
+
+ UUID-004
+ UUID-005
+
+
+ UUID-006
+ UUID-007
+
+
+ UUID-008
+ UUID-009
+
+
+ UUID-010
+ UUID-011
+
+
+
+ Номенклатура
+
+
+ ru
+ Номенклатура
+
+
+
+ false
+ HierarchyFoldersAndItems
+ false
+ 2
+ true
+ true
+
+ ToItems
+ 9
+ 25
+ String
+ Variable
+ WholeCatalog
+ false
+ true
+ AsDescription
+
+ Auto
+ InDialog
+ false
+ BothWays
+
+ Catalog.Номенклатура.StandardAttribute.Description
+ Catalog.Номенклатура.StandardAttribute.Code
+
+ Begin
+ DontUse
+ Directly
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+ Automatic
+ Use
+
+
+
+
+
+ DontUse
+ Auto
+ DontUse
+ false
+ false
+
+
+
+
+ ПечатьЭтикеток
+
+
+ ru
+ Печать этикеток
+
+
+ en
+ Print labels
+
+
+
+ FormCommandBarImportant
+
+ d5p1:CatalogRef.Номенклатура
+
+ Multiple
+ true
+ Auto
+
+
+ ru
+ Печать этикеток для выбранных товаров
+
+
+
+
+ Auto
+
+
+
+
+
diff --git a/tests/skills/cases/meta-compile/snapshots/catalog-command/Catalogs/Номенклатура/Commands/ПечатьЭтикеток/Ext/CommandModule.bsl b/tests/skills/cases/meta-compile/snapshots/catalog-command/Catalogs/Номенклатура/Commands/ПечатьЭтикеток/Ext/CommandModule.bsl
new file mode 100644
index 00000000..d7ec0aeb
--- /dev/null
+++ b/tests/skills/cases/meta-compile/snapshots/catalog-command/Catalogs/Номенклатура/Commands/ПечатьЭтикеток/Ext/CommandModule.bsl
@@ -0,0 +1,6 @@
+&НаКлиенте
+Процедура ОбработкаКоманды(ПараметрКоманды, ПараметрыВыполненияКоманды)
+
+ // Вставьте обработчик команды.
+
+КонецПроцедуры
diff --git a/tests/skills/cases/meta-compile/snapshots/catalog-command/Catalogs/Номенклатура/Ext/ObjectModule.bsl b/tests/skills/cases/meta-compile/snapshots/catalog-command/Catalogs/Номенклатура/Ext/ObjectModule.bsl
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/skills/cases/meta-compile/snapshots/catalog-command/Configuration.xml b/tests/skills/cases/meta-compile/snapshots/catalog-command/Configuration.xml
new file mode 100644
index 00000000..be3edd96
--- /dev/null
+++ b/tests/skills/cases/meta-compile/snapshots/catalog-command/Configuration.xml
@@ -0,0 +1,252 @@
+
+
+
+
+
+ UUID-002
+ UUID-003
+
+
+ UUID-004
+ UUID-005
+
+
+ UUID-006
+ UUID-007
+
+
+ UUID-008
+ UUID-009
+
+
+ UUID-010
+ UUID-011
+
+
+ UUID-012
+ UUID-013
+
+
+ UUID-014
+ UUID-015
+
+
+
+ TestConfig
+
+
+ ru
+ TestConfig
+
+
+
+
+ Version8_3_24
+ ManagedApplication
+
+ PlatformApplication
+
+ Russian
+
+
+
+
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Biometrics
+ true
+
+
+ Location
+ false
+
+
+ BackgroundLocation
+ false
+
+
+ BluetoothPrinters
+ false
+
+
+ WiFiPrinters
+ false
+
+
+ Contacts
+ false
+
+
+ Calendars
+ false
+
+
+ PushNotifications
+ false
+
+
+ LocalNotifications
+ false
+
+
+ InAppPurchases
+ false
+
+
+ PersonalComputerFileExchange
+ false
+
+
+ Ads
+ false
+
+
+ NumberDialing
+ false
+
+
+ CallProcessing
+ false
+
+
+ CallLog
+ false
+
+
+ AutoSendSMS
+ false
+
+
+ ReceiveSMS
+ false
+
+
+ SMSLog
+ false
+
+
+ Camera
+ false
+
+
+ Microphone
+ false
+
+
+ MusicLibrary
+ false
+
+
+ PictureAndVideoLibraries
+ false
+
+
+ AudioPlaybackAndVibration
+ false
+
+
+ BackgroundAudioPlaybackAndVibration
+ false
+
+
+ InstallPackages
+ false
+
+
+ OSBackup
+ true
+
+
+ ApplicationUsageStatistics
+ false
+
+
+ BarcodeScanning
+ false
+
+
+ BackgroundAudioRecording
+ false
+
+
+ AllFilesAccess
+ false
+
+
+ Videoconferences
+ false
+
+
+ NFC
+ false
+
+
+ DocumentScanning
+ false
+
+
+ SpeechToText
+ false
+
+
+ Geofences
+ false
+
+
+ IncomingShareRequests
+ false
+
+
+ AllIncomingShareRequestsTypesProcessing
+ false
+
+
+
+
+
+ Normal
+
+
+ Language.Русский
+
+
+
+
+
+ Managed
+ NotAutoFree
+ DontUse
+ DontUse
+ TaxiEnableVersion8_2
+ DontUse
+ Version8_3_24
+
+
+
+ Русский
+ Номенклатура
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/meta-compile/snapshots/catalog-command/Ext/ClientApplicationInterface.xml b/tests/skills/cases/meta-compile/snapshots/catalog-command/Ext/ClientApplicationInterface.xml
new file mode 100644
index 00000000..3c1161b2
--- /dev/null
+++ b/tests/skills/cases/meta-compile/snapshots/catalog-command/Ext/ClientApplicationInterface.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ UUID-002
+
+
+
+
+ UUID-004
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/meta-compile/snapshots/catalog-command/Languages/Русский.xml b/tests/skills/cases/meta-compile/snapshots/catalog-command/Languages/Русский.xml
new file mode 100644
index 00000000..37c60d78
--- /dev/null
+++ b/tests/skills/cases/meta-compile/snapshots/catalog-command/Languages/Русский.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ Русский
+
+
+ ru
+ Русский
+
+
+
+ ru
+
+
+
\ No newline at end of file