86 lines
4.6 KiB
Python
'''Собирает 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()
|