улучшены коллбеки для считывания xml

улучшен парс структур и юнионов переменных
улучшен поиск
This commit is contained in:
2025-07-09 05:44:03 +03:00
parent 0ba81a5147
commit 4517087194
11 changed files with 137 additions and 56 deletions

581
.out/setupVars_out.py Normal file
View File

@@ -0,0 +1,581 @@
import sys
import os
import subprocess
import xml.etree.ElementTree as ET
from generateVars import map_type_to_pt, get_iq_define, type_map
from enum import IntEnum
from scanVars import run_scan
from generateVars import run_generate
from PySide6.QtWidgets import (
QApplication, QWidget, QTableWidget, QTableWidgetItem,
QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton,
QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit
)
from PySide6.QtGui import QTextCursor
from PySide6.QtCore import Qt, QProcess
class rows(IntEnum):
include = 0
name = 1
type = 2
pt_type = 3
iq_type = 4
ret_type = 5
short_name = 6
# 1. Парсим vars.xml
def make_absolute_path(path, base_path):
if not os.path.isabs(path):
return os.path.abspath(os.path.join(base_path, path))
return os.path.abspath(path)
def parse_vars(filename):
tree = ET.parse(filename)
root = tree.getroot()
vars_list = []
variables_elem = root.find('variables')
if variables_elem is not None:
for var in variables_elem.findall('var'):
name = var.attrib.get('name', '')
vars_list.append({
'name': name,
'enable': var.findtext('enable', 'false'),
'shortname': var.findtext('shortname', name),
'pt_type': var.findtext('pt_type', 'pt_unknown'),
'iq_type': var.findtext('iq_type', 'iq_none'),
'return_type': var.findtext('return_type', 'int'),
'type': var.findtext('type', 'unknown'),
'file': var.findtext('file', ''),
'extern': var.findtext('extern', 'false') == 'true',
'static': var.findtext('static', 'false') == 'true',
})
return vars_list
# 2. Парсим structSup.xml
def parse_structs(filename):
tree = ET.parse(filename)
root = tree.getroot()
structs = {}
typedef_map = {}
# --- Считываем структуры ---
structs_elem = root.find('structs')
if structs_elem is not None:
for struct in structs_elem.findall('struct'):
name = struct.attrib['name']
fields = {}
for field in struct.findall('field'):
fname = field.attrib.get('name')
ftype = field.attrib.get('type')
if fname and ftype:
fields[fname] = ftype
structs[name] = fields
# --- Считываем typedef-ы ---
typedefs_elem = root.find('typedefs')
if typedefs_elem is not None:
for typedef in typedefs_elem.findall('typedef'):
name = typedef.attrib.get('name')
target_type = typedef.attrib.get('type')
if name and target_type:
typedef_map[name.strip()] = target_type.strip()
return structs, typedef_map
class ProcessOutputWindow(QWidget):
def __init__(self, command, args):
super().__init__()
self.setWindowTitle("Поиск переменных...")
self.resize(600, 400)
self.layout = QVBoxLayout(self)
self.output_edit = QTextEdit()
self.output_edit.setReadOnly(True)
self.layout.addWidget(self.output_edit)
self.btn_close = QPushButton("Закрыть")
self.btn_close.setEnabled(False)
self.btn_close.clicked.connect(self.close)
self.layout.addWidget(self.btn_close)
self.process = QProcess(self)
self.process.setProcessChannelMode(QProcess.MergedChannels)
self.process.readyReadStandardOutput.connect(self.handle_stdout)
self.process.finished.connect(self.process_finished)
self.process.start(command, args)
def handle_stdout(self):
data = self.process.readAllStandardOutput()
text = data.data().decode('utf-8')
self.output_edit.append(text)
def process_finished(self):
self.output_edit.append("\n--- Процесс завершён ---")
self.btn_close.setEnabled(True)
# 3. UI: таблица с переменными
class VarEditor(QWidget):
def __init__(self):
super().__init__()
self.vars_list = []
self.structs = {}
self.typedef_map = {}
self.structs_xml_path = None # сюда запишем путь из <structs_xml>
self.initUI()
def initUI(self):
self.setWindowTitle("Variable Editor")
# --- Поля ввода пути проекта и XML ---
# XML Output
xml_layout = QHBoxLayout()
xml_layout.addWidget(QLabel("XML Output:"))
self.xml_output_edit = QLineEdit()
xml_layout.addWidget(self.xml_output_edit)
self.xml_output_edit.returnPressed.connect(self.read_xml_file)
btn_xml_browse = QPushButton("...")
btn_xml_browse.setFixedWidth(30)
xml_layout.addWidget(btn_xml_browse)
btn_xml_browse.clicked.connect(self.browse_xml_output)
# Project Path
proj_layout = QHBoxLayout()
proj_layout.addWidget(QLabel("Project Path:"))
self.proj_path_edit = QLineEdit()
proj_layout.addWidget(self.proj_path_edit)
btn_proj_browse = QPushButton("...")
btn_proj_browse.setFixedWidth(30)
proj_layout.addWidget(btn_proj_browse)
btn_proj_browse.clicked.connect(self.browse_proj_path)
# Makefile Path
makefile_layout = QHBoxLayout()
makefile_layout.addWidget(QLabel("Makefile Path (relative path):"))
self.makefile_edit = QLineEdit()
makefile_layout.addWidget(self.makefile_edit)
btn_makefile_browse = QPushButton("...")
btn_makefile_browse.setFixedWidth(30)
makefile_layout.addWidget(btn_makefile_browse)
btn_makefile_browse.clicked.connect(self.browse_makefile)
# Source Output File/Directory
source_output_layout = QHBoxLayout()
source_output_layout.addWidget(QLabel("Source Output File:"))
self.source_output_edit = QLineEdit()
source_output_layout.addWidget(self.source_output_edit)
btn_source_output_browse = QPushButton("...")
btn_source_output_browse.setFixedWidth(30)
source_output_layout.addWidget(btn_source_output_browse)
btn_source_output_browse.clicked.connect(self.browse_source_output)
self.btn_update_vars = QPushButton("Обновить данные о переменных")
self.btn_update_vars.clicked.connect(self.update_vars_data)
# Таблица переменных
self.table = QTableWidget(len(self.vars_list), 7)
self.table.setHorizontalHeaderLabels([
'Include',
'Name',
'Origin Type',
'Pointer Type',
'IQ Type',
'Return Type',
'Short Name'
])
self.table.setEditTriggers(QAbstractItemView.AllEditTriggers)
# Кнопка сохранения
btn_save = QPushButton("Build")
btn_save.clicked.connect(self.save_build)
# Основной layout
layout = QVBoxLayout()
layout.addLayout(xml_layout)
layout.addLayout(proj_layout)
layout.addLayout(makefile_layout)
layout.addWidget(self.btn_update_vars)
layout.addWidget(self.table)
layout.addWidget(btn_save)
layout.addLayout(source_output_layout)
self.setLayout(layout)
def update_vars_data(self):
proj_path = os.path.abspath(self.proj_path_edit.text().strip())
xml_path = os.path.abspath(self.xml_output_edit.text().strip())
makefile_path = make_absolute_path(self.makefile_edit.text().strip(), proj_path)
if not proj_path or not xml_path:
QMessageBox.warning(self, "Ошибка", "Пожалуйста, укажите пути проекта и XML.")
return
if not os.path.isfile(makefile_path):
QMessageBox.warning(self, "Ошибка", f"Makefile не найден по пути:\n{makefile_path}")
return
scanvars_exe = "scanVars.exe"
args = [proj_path, makefile_path, xml_path]
# Создаем и показываем окно с выводом процесса
self.proc_win = ProcessOutputWindow("python", [])
self.proc_win.show()
self.proc_win.output_edit.append("Запуск анализа переменных...")
# Запускаем в отдельном процессе через QProcess, если нужен live-вывод:
from threading import Thread
def run_scan_wrapper():
try:
run_scan(proj_path, makefile_path, xml_path)
self.proc_win.output_edit.append("\n--- Анализ завершён ---")
except Exception as e:
self.proc_win.output_edit.append(f"\n[ОШИБКА] {e}")
self.proc_win.btn_close.setEnabled(True)
self.after_scanvars_finished(0, 0)
Thread(target=run_scan_wrapper).start()
# Можно подписаться на сигнал завершения процесса, если хочешь обновить UI после
#self.proc_win.process.finished.connect(lambda exitCode, exitStatus: self.after_scanvars_finished(exitCode, exitStatus))
def save_build(self):
vars_out = []
for row in range(self.table.rowCount()):
include_cb = self.table.cellWidget(row, rows.include)
if not include_cb.isChecked():
continue
name_edit = self.table.cellWidget(row, rows.name)
pt_type_combo = self.table.cellWidget(row, rows.pt_type)
iq_combo = self.table.cellWidget(row, rows.iq_type)
ret_combo = self.table.cellWidget(row, rows.ret_type)
short_name_edit = self.table.cellWidget(row, rows.short_name)
var_data = {
'name': name_edit.text(),
'type': 'pt_' + pt_type_combo.currentText(),
'iq_type': iq_combo.currentText(),
'return_type': ret_combo.currentText() if ret_combo.currentText() else 'int',
'short_name': short_name_edit.text(),
}
vars_out.append(var_data)
# Здесь нужно указать абсолютные пути к проекту, xml и output (замени на свои)
proj_path = os.path.abspath(self.proj_path_edit.text().strip())
xml_path = os.path.abspath(self.xml_output_edit.text().strip())
output_dir_c_file = os.path.abspath(self.source_output_edit.text().strip())
if not proj_path or not xml_path or not output_dir_c_file:
QMessageBox.warning(self, "Ошибка", "Заполните все пути: проект, XML и output.")
return
try:
run_generate(proj_path, xml_path, output_dir_c_file)
QMessageBox.information(self, "Готово", "Файл debug_vars.c успешно сгенерирован.")
except Exception as e:
QMessageBox.critical(self, "Ошибка при генерации", str(e))
def browse_proj_path(self):
dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку проекта")
if dir_path:
self.proj_path_edit.setText(dir_path)
def read_xml_file(self):
file_path = self.xml_output_edit.text().strip()
if file_path and not os.path.isfile(file_path):
return
self.vars_list = parse_vars(file_path)
try:
tree = ET.parse(file_path)
root = tree.getroot()
proj_path = self.proj_path_edit.text().strip()
if not proj_path:
# Если в поле ничего нет, пробуем взять из XML
proj_path_from_xml = root.attrib.get('proj_path', '').strip()
if proj_path_from_xml and os.path.isdir(proj_path_from_xml):
proj_path = proj_path_from_xml
self.proj_path_edit.setText(proj_path_from_xml)
else:
QMessageBox.warning(
self,
"Внимание",
"Путь к проекту (proj_path) не найден или не существует.\n"
"Пожалуйста, укажите его вручную в поле 'Project Path'."
)
else:
if not os.path.isdir(proj_path):
QMessageBox.warning(
self,
"Внимание",
f"Указанный путь к проекту не существует:\n{proj_path}\n"
"Пожалуйста, исправьте путь в поле 'Project Path'."
)
# --- makefile_path из атрибута ---
makefile_path = root.attrib.get('makefile_path', '').strip()
makefile_path_full = make_absolute_path(makefile_path, proj_path)
if makefile_path_full and os.path.isfile(makefile_path_full):
self.makefile_edit.setText(makefile_path)
# --- structs_path из атрибута ---
structs_path = root.attrib.get('structs_path', '').strip()
structs_path_full = make_absolute_path(structs_path, proj_path)
if structs_path_full and os.path.isfile(structs_path_full):
self.structs_xml_path = structs_path_full
self.structs, self.typedef_map = parse_structs(structs_path_full)
else:
self.structs_xml_path = None
self.update_table()
except Exception as e:
QMessageBox.warning(self, "Ошибка", f"Ошибка при чтении XML:\n{e}")
def browse_xml_output(self):
file_path, _ = QFileDialog.getSaveFileName(
self,
"Выберите XML файл",
filter="XML files (*.xml);;All Files (*)"
)
self.xml_output_edit.setText(file_path)
self.read_xml_file()
def browse_xml_struct(self):
file_path, _ = QFileDialog.getSaveFileName(self, "Выберите XML файл", filter="XML files (*.xml);;All Files (*)")
if file_path:
self.xml_output_edit.setText(file_path)
if os.path.isfile(file_path):
self.structs, self.typedef_map = parse_structs(file_path)
def browse_makefile(self):
file_path, _ = QFileDialog.getOpenFileName(
self, "Выберите Makefile", filter="Makefile (makefile);;All Files (*)"
)
if file_path:
self.makefile_edit.setText(file_path)
def browse_source_output(self):
dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку для debug_vars.c")
if dir_path:
self.source_output_edit.setText(dir_path)
def after_scanvars_finished(self, exitCode, exitStatus):
xml_path = self.xml_output_edit.text().strip()
if not os.path.isfile(xml_path):
QMessageBox.critical(self, "Ошибка", f"Файл не найден: {xml_path}")
return
try:
# Читаем структуры, если задан путь
if self.structs_xml_path and os.path.isfile(self.structs_xml_path):
try:
self.structs, self.typedef_map = parse_structs(self.structs_xml_path)
# При необходимости обновите UI или сделайте что-то с self.structs
except Exception as e:
QMessageBox.warning(self, "Внимание", f"Не удалось загрузить структуры из {self.structs_xml_path}:\n{e}")
self.vars_list = parse_vars(xml_path)
self.update_table()
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить переменные:\n{e}")
def update_table(self):
self.type_options = list(dict.fromkeys(type_map.values()))
self.display_type_options = [t.replace('pt_', '') for t in self.type_options]
iq_types = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)]
self.table.setRowCount(len(self.vars_list))
for row, var in enumerate(self.vars_list):
cb = QCheckBox()
enable_str = var.get('enable', 'false')
cb.setChecked(enable_str.lower() == 'true')
self.table.setCellWidget(row, rows.include, cb)
name_edit = QLineEdit(var['name'])
if var['type'] in self.structs:
completer = QCompleter(self.structs[var['type']].keys())
completer.setCaseSensitivity(Qt.CaseInsensitive)
name_edit.setCompleter(completer)
self.table.setCellWidget(row, rows.name, name_edit)
# Type (origin)
origin_type = var.get('type', '').strip()
origin_item = QTableWidgetItem(origin_type)
origin_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) # read-only
self.table.setItem(row, rows.type, origin_item)
pt_type_combo = QComboBox()
pt_type_combo.addItems(self.display_type_options)
internal_type = map_type_to_pt(var['type'], var['name'], self.typedef_map)
display_type = internal_type.replace('pt_', '')
if display_type in self.display_type_options:
pt_type_combo.setCurrentText(display_type)
else:
pt_type_combo.addItem(display_type)
pt_type_combo.setCurrentText(display_type)
self.table.setCellWidget(row, rows.pt_type, pt_type_combo)
iq_combo = QComboBox()
iq_combo.addItems(iq_types)
iq_type = get_iq_define(var['type']) # Получаем IQ-тип, например 'iq24'
display_type = iq_type.replace('t_', '')
if iq_type in iq_types:
iq_combo.setCurrentText(display_type)
else:
iq_combo.addItem(display_type)
iq_combo.setCurrentText(display_type)
self.table.setCellWidget(row, rows.iq_type, iq_combo)
ret_combo = QComboBox()
ret_combo.addItems(iq_types)
self.table.setCellWidget(row, rows.ret_type, ret_combo)
short_name_edit = QLineEdit(var['name'])
self.table.setCellWidget(row, rows.short_name, short_name_edit)
cb.stateChanged.connect(self.save_table_to_xml)
name_edit.textChanged.connect(self.save_table_to_xml)
pt_type_combo.currentTextChanged.connect(self.save_table_to_xml)
iq_combo.currentTextChanged.connect(self.save_table_to_xml)
ret_combo.currentTextChanged.connect(self.save_table_to_xml)
short_name_edit.textChanged.connect(self.save_table_to_xml)
def save_table_to_xml(self):
def make_relative_path(abs_path, base_path):
try:
return os.path.relpath(abs_path, base_path).replace("\\", "/")
except ValueError:
return abs_path.replace("\\", "/")
xml_path = self.xml_output_edit.text().strip()
proj_path = self.proj_path_edit.text().strip()
makefile_path = self.makefile_edit.text().strip()
if not xml_path or not os.path.isfile(xml_path):
print("XML файл не найден или путь пустой")
return
try:
tree = ET.parse(xml_path)
root = tree.getroot()
# Обновим атрибуты с относительными путями
if os.path.isdir(proj_path):
root.set("proj_path", proj_path.replace("\\", "/"))
if os.path.isfile(makefile_path):
rel_makefile = make_relative_path(makefile_path, proj_path)
root.set("makefile_path", rel_makefile)
if self.structs_xml_path and os.path.isfile(self.structs_xml_path):
rel_struct_path = make_relative_path(self.structs_xml_path, proj_path)
root.set("structs_path", rel_struct_path)
vars_elem = root.find('variables')
if vars_elem is None:
# Если блока нет, создаём
vars_elem = ET.SubElement(root, 'variables')
original_info = {}
for var_elem in vars_elem.findall('var'):
name = var_elem.attrib.get('name')
if name:
original_info[name] = {
'type': var_elem.findtext('type', ''),
'file': var_elem.findtext('file', ''),
'extern': var_elem.findtext('extern', ''),
'static': var_elem.findtext('static', '')
}
# Собираем данные из таблицы
updated_vars = []
for row in range(self.table.rowCount()):
cb = self.table.cellWidget(row, 0)
name_edit = self.table.cellWidget(row, 1)
pt_type_combo = self.table.cellWidget(row, 3)
iq_combo = self.table.cellWidget(row, 4)
ret_combo = self.table.cellWidget(row, 5)
short_name_edit = self.table.cellWidget(row, 6)
var_name = name_edit.text()
# Берём оригинальные type и file из словаря, если есть
orig_type = original_info.get(var_name, {}).get('type', '')
orig_file = original_info.get(var_name, {}).get('file', '')
orig_extern = original_info.get(var_name, {}).get('extern', '')
orig_static = original_info.get(var_name, {}).get('static', '')
updated_vars.append({
'name': var_name,
'enable': cb.isChecked(),
'shortname': short_name_edit.text(),
'pt_type': 'pt_' + pt_type_combo.currentText(),
'iq_type': iq_combo.currentText(),
'return_type': ret_combo.currentText() or 'int',
'type': orig_type,
'file': orig_file,
'extern': orig_extern,
'static': orig_static,
})
# Обновляем или добавляем по одному var в XML
for v in updated_vars:
var_elem = None
for ve in vars_elem.findall('var'):
if ve.attrib.get('name') == v['name']:
var_elem = ve
break
if var_elem is None:
var_elem = ET.SubElement(vars_elem, 'var', {'name': v['name']})
def set_sub_elem_text(parent, tag, text):
el = parent.find(tag)
if el is None:
el = ET.SubElement(parent, tag)
el.text = str(text)
set_sub_elem_text(var_elem, 'enable', 'true' if v['enable'] else 'false')
set_sub_elem_text(var_elem, 'shortname', v['shortname'])
set_sub_elem_text(var_elem, 'pt_type', v['pt_type'])
set_sub_elem_text(var_elem, 'iq_type', v['iq_type'])
set_sub_elem_text(var_elem, 'return_type', v['return_type'])
set_sub_elem_text(var_elem, 'type', v['type'])
set_sub_elem_text(var_elem, 'file', v['file'])
set_sub_elem_text(var_elem, 'extern', v['extern'])
set_sub_elem_text(var_elem, 'static', v['static'])
# Сохраняем изменения
tree.write(xml_path, encoding='utf-8', xml_declaration=True)
except Exception as e:
print(f"Ошибка при сохранении XML: {e}")
if __name__ == "__main__":
app = QApplication(sys.argv)
editor = VarEditor()
editor.resize(900, 600)
editor.show()
sys.exit(app.exec())