| '''Собирает MQL5.zip для четвёртой части из тега p4.
|
|
|
| Исходник сервиса берётся из рабочего дерева только после проверки, что от p4
|
| отличаются лишь комментарии, пробелы и версия. Личные файлы не читаются.
|
| Запуск: python docs/articles/part-04/publish/build_archive.py
|
| '''
|
|
|
| import json
|
| import re
|
| import subprocess
|
| from pathlib import Path
|
| from zipfile import ZIP_DEFLATED, ZipFile
|
|
|
| ROOT = Path(__file__).resolve().parents[4]
|
| OUTPUT = Path(__file__).resolve().parent / 'MQL5.zip'
|
| PREFIX = 'MQL5/Shared Projects/mt5-manager/'
|
| BASE = 'p4'
|
|
|
|
|
| def git(*args):
|
| return subprocess.run(['git', *args], cwd=ROOT, capture_output=True, check=True).stdout
|
|
|
|
|
| def executable(text):
|
| text = re.sub(r'^#property version[^\n]*', '', text, flags=re.M)
|
| parts = re.compile(r'"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])*\'|//[^\n]*|/\*[\s\S]*?\*/')
|
| text = parts.sub(lambda m: '' if m.group().startswith(('//', '/*')) else m.group(), text)
|
| return re.findall(r'"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])*\'|\w+|[^\s]', text)
|
|
|
|
|
| def build():
|
| baseline = git('show', f'{BASE}:ServiceStateWriter.mq5').decode('utf-8-sig')
|
| service = (ROOT / 'ServiceStateWriter.mq5').read_text(encoding='utf-8-sig')
|
| if executable(baseline) != executable(service):
|
| raise RuntimeError('Сервис отличается от p4 по логике. Для архива четвёртой части нужен её исходник.')
|
| paths = git('ls-tree', '-r', '--name-only', BASE).decode().splitlines()
|
| paths = [name for name in paths if name in {
|
| 'LICENSE', 'README.md', 'ServiceStateWriter.mq5', 'config.json', 'config.py',
|
| 'deploy_service.py', 'extracticon.py', 'main.py', 'mt5_control.py', 'run.py'}
|
| or name.startswith(('static/', 'templates/'))]
|
| files = {name: git('show', f'{BASE}:{name}') for name in paths}
|
| files['ServiceStateWriter.mq5'] = service.encode('utf-8')
|
| config = json.loads(files['config.json'].decode('utf-8-sig'))
|
| config['mt5_folder'] = 'C:/MT5'
|
| config['database'] = 'C:/Users/USERNAME/AppData/Roaming/MetaQuotes/Terminal/Common/Files/mt5-manager.sqlite'
|
| for name, record in config['terminals'].items():
|
| record['login'] = ''
|
| record['server'] = ''
|
| record.pop('password', None)
|
| record.pop('path', None)
|
| files['config.json'] = (json.dumps(config, ensure_ascii=False, indent=2) + '\n').encode('utf-8')
|
| note = '''# Архив к четвёртой части
|
|
|
| Распакуйте MQL5.zip в папку данных терминала. Проект появится в
|
| MQL5/Shared Projects/mt5-manager. Для portable-режима это папка установки MT5.
|
|
|
| Перед запуском исправьте database в config.json: замените USERNAME на имя
|
| пользователя Windows. Проверьте mt5_folder и имена уже установленных терминалов.
|
| Логины и серверы в примере оставлены пустыми; используются счета, сохранённые MT5.
|
|
|
| Откройте ServiceStateWriter.mq5 в MetaEditor и скомпилируйте его. Полученный EX5
|
| будет расположен в MQL5/Services/Shared Projects/mt5-manager. Скопируйте EX5 по
|
| этому же пути в остальные терминалы и static/services.ini в их папки config.
|
| Либо используйте deploy_service.py, как описано в статье.
|
|
|
| Архив содержит код версии p4 с исправленным оформлением сервиса 1.01.
|
| Возможности пятой части в него не включены. Исполняемые EX5 не распространяются.
|
|
|
| ---
|
|
|
| '''
|
| files['README.md'] = note.encode('utf-8') + files['README.md']
|
| with ZipFile(OUTPUT, 'w', compression=ZIP_DEFLATED) as archive:
|
| for name, data in sorted(files.items()):
|
| if name.lower().endswith('.ex5'):
|
| raise RuntimeError('EX5 запрещён в архиве публикации.')
|
| archive.writestr(PREFIX + name, data)
|
| with ZipFile(OUTPUT) as archive:
|
| assert archive.testzip() is None
|
| assert all(name.startswith('MQL5/') for name in archive.namelist())
|
| assert not any(name.lower().endswith('.ex5') for name in archive.namelist())
|
| print(f'MQL5.zip: {len(files)} файлов, {OUTPUT.stat().st_size} байт; база p4, без EX5 и личных реквизитов.')
|
|
|
|
|
| if __name__ == '__main__':
|
| build()
|