46 Commits

Author SHA1 Message Date
Razvalyaev
10e37a14b4 добавлен ifdef на присутствие BUILD_ дефайнов в проекте 2025-08-01 15:35:10 +03:00
Razvalyaev
10dd2a6987 Добавлено автообновление 2025-07-25 16:05:05 +03:00
Razvalyaev
910bf0a585 Обновлены readme 2025-07-23 18:18:19 +03:00
Razvalyaev
502046091c опять кууууча всего:
базово доделаны терминалки до более менее итогового состояния
2025-07-23 17:13:28 +03:00
Razvalyaev
e99de603e6 попытка сделать в parse_xml парсинг вложенных массивов [][]
определяет вложенные массивы но не определяет их размерности (нули)
2025-07-22 18:57:59 +03:00
Razvalyaev
788ad19464 кууууча всего по терминалке, надо резгребать и структурировать
базово:
+сделан lowlevel для кучи переменных (пока работает медленно)
+сделан сохранение принимаемых значений в лог
+ gui терминалок подогнаны под один стиль плюс минус
2025-07-22 18:05:12 +03:00
Razvalyaev
96496a0256 все неплохо работает.
сейв перед попыткой улучшить lowlevel debug
2025-07-21 13:40:52 +03:00
Razvalyaev
f89aff1b1c Улучшена скорость полла Watch (переделано формирование таблицы) 2025-07-19 18:17:00 +03:00
Razvalyaev
6830743477 Начата работа над lowlevel терминалкой (по адресам из xml) 2025-07-19 18:01:40 +03:00
Razvalyaev
171f176d63 добавлен exe для парса всех переменных из .out 2025-07-19 11:36:32 +03:00
Razvalyaev
f2c4b7b3cd структурирован код debug_tools
доработана демо-терминалка для считывания tms переменных и встроена в DebugVarEdit
2025-07-19 10:56:46 +03:00
Razvalyaev
c94a7e711c сделана бета терминалка для опроса переменных 2025-07-18 17:43:23 +03:00
Razvalyaev
5be6343c33 бета терминалка для опроса 2025-07-18 16:47:18 +03:00
Razvalyaev
043359fe66 + фикс кривых проверок на наличие uint8_t
+ фикс перевод float в iq
+ фикс поиска вручнуб добавленных переменных в debug_vars.c
2025-07-17 10:37:58 +03:00
Razvalyaev
c55f38ef1c + пример debug_vars.c
+ exe
+ опечатка в readme
+ коррекции по абзацам в комментах
2025-07-17 09:26:57 +03:00
Razvalyaev
ae2c90160e +библиотека .c/.h переписана под универсальные дефайны intX_t uintX_t
+сделано задание размера короткого имени
+ добавлена бета поддержка stm:+
   - парс переменных из файла преокта Keil или makefile CubeIDE
   - запись в utf-8 для STM, вместо cp1251 для TMS
   - другой размер int (32 бита, вместо 16 бит) для STM
2025-07-17 09:18:03 +03:00
Razvalyaev
4de53090a1 добавлены комменты к debug_tools.c/.h
начата работа над поддержкой stm32 и кейл проектов
2025-07-15 19:05:52 +03:00
Razvalyaev
742c4e9e1b readme для .c файлов 2025-07-15 15:56:47 +03:00
Razvalyaev
369cfa808c чистка для релиза
переструктурировано чуть

и всякие мелкие фиксы
2025-07-15 15:37:29 +03:00
Razvalyaev
c32dc161f8 переструктурирвоаны исходники проекта 2025-07-15 13:31:18 +03:00
Razvalyaev
abfc507e4e регулярнка не работает в clean_remp 2025-07-15 13:13:08 +03:00
Razvalyaev
cb496bca0f добавлен readme
другая библиотека для xml (более быстрая)
сделан выбор элементов в выпадающем списке типов
исправлено вроде кривое выставлениеп return_type
изменено окно выбора переменых
    - кнопки добавит, удалить заменены на применить
    - исправлены кривые подсказки в .exe версии
2025-07-15 13:03:11 +03:00
Razvalyaev
7b720cbdf4 криво работает return_type, надо разобратся
чет поделано в коде для тмс
2025-07-14 17:54:49 +03:00
Razvalyaev
6428e523df сделано ленивое раскрытие подпеременных в структурах
(баово заглушки и если они раскрываются,то подставляются реальнгые)
2025-07-14 14:57:32 +03:00
Razvalyaev
c738acd871 фикс мелочей в таблицах выбора
+exe
+3.13 python installer заменен на 3.9 (3.13 не имеет PySide2)
2025-07-14 07:35:28 +03:00
Razvalyaev
05bde87c38 +exe 2025-07-13 22:53:58 +03:00
Razvalyaev
02f3124224 ну вроде чуть поменьше тормозит, но все равно неприятно. надо еще подумать 2025-07-13 22:47:33 +03:00
Razvalyaev
21082a38e0 ну вроде работает, но опять тормозит 2025-07-13 22:17:45 +03:00
Razvalyaev
42ac3eb65d наконец-то вроде сделан поиск.
есть кривости в подскасках
начата работа надо двумя таблицами (всех и выбранных переменных)
2025-07-13 18:53:01 +03:00
Razvalyaev
69c0bf1574 почему-то внуки, правнуки и так далее в поиске на находятся... 2025-07-12 18:13:51 +03:00
Razvalyaev
4f949e9854 таблица выбора элементов вынесена в отдельный класс 2025-07-12 08:54:53 +03:00
Razvalyaev
0d54031dd5 исправлен поиск и фильтрация
плюс делание всякого по коду для мк
2025-07-11 17:57:29 +03:00
Razvalyaev
e4fcfd11d7 Доработана функции для считывания переменных
Добавлены бета-функции для считывания переменны по адресу

+фиксы багов

future:
- в селекторе сделать две таблички для всех переменных и для выборанных
- по кнопке переносить переменные из всех в выбранные
- переменные из выбранных и добавлять в основную табличку
- сделать отдельный класс для таблички - который будет принимать спиоск переменных для отображения
2025-07-11 16:46:51 +03:00
Razvalyaev
d3f1e824fa оптимизировано. на первом запуске Выбора переменных конечно подвисает, но поиск работает относительно шустро 2025-07-11 10:48:52 +03:00
Razvalyaev
ad7b9126b7 массивы вроде работают но тормозит. начата работа над оптимизацией 2025-07-11 09:44:33 +03:00
Razvalyaev
f271b2e82c убрано лишнее окно предупреждения о перезаписи
выровнен шрифт в таблице
+exe

надо как-то сделать парс массивов и подумать как лучше
2025-07-11 07:24:43 +03:00
Razvalyaev
a3850c2c8a гитингор + чистка
+ часть работы xml перенесена в отдельный файл
+ фикс бага с записыванием в xml полных путей, вместо относительных
+ фикс бага при поиске
2025-07-11 06:47:30 +03:00
Razvalyaev
0d59f88444 пре-релизный exe
исправлена иконка (добавлена в exe)
исправлен баг: xml не сохранлся при нажатии del на клаве
2025-07-10 19:01:42 +03:00
Razvalyaev
c44216a450 + блокировка добавления структур и юнионов в переменные
+ запись return iq type в debug_vars.c
+ переход на русский в gui
2025-07-10 18:48:27 +03:00
Razvalyaev
07e42c774a мерж + чистка build и перенос всякого в сурсы
+ добавлено подсвечивание предупреждений и ошибок в таблице выбранных переменных
2025-07-10 18:05:11 +03:00
Razvalyaev
a95a1535a9 Merge branch 'nuitka' 2025-07-10 13:56:08 +03:00
Razvalyaev
858e7de57d на win7 работает и Nuitka и PyInstaller
добавлен прогрессбар на сканирование переменных
исправлены мелкие баги
2025-07-10 13:49:13 +03:00
Razvalyaev
cd6645df98 скомпилилось с pyinstaller на win7 2025-07-10 10:32:04 +03:00
Razvalyaev
3343428796 попытка перехода на python 3.12 и nutika для компиляции без зависимостей 2025-07-09 19:07:27 +03:00
Razvalyaev
f881132fa8 добавлена кнопка для открытия выходного файла
+ exe
2025-07-09 15:56:18 +03:00
Razvalyaev
4962276760 работа с таблицей перенесена в отдельный файл
сделано автозаполнение при поиске переменых
сделано правильное формирование структур, через . или ->

пофиксены мелкие фиксы
2025-07-09 15:40:16 +03:00
72 changed files with 8055 additions and 61703 deletions

9
.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
/.out
/Src/__pycache__
/build/__pycache__
/build_temp
/DebugVarEdit_GUI.build
/DebugVarEdit_GUI.dist
/DebugVarEdit_GUI.onefile-build
/parse_xml/build/
/parse_xml/Src/__pycache__/

Binary file not shown.

View File

@@ -1,732 +0,0 @@
# pyinstaller --onefile --distpath . --workpath ./build --specpath ./build scanVars.py
import sys
import os
import re
import argparse
from pathlib import Path
# === Словарь соответствия типов XML → DebugVarType_t ===
type_map = dict([
*[(k, 'pt_int8') for k in ('signed char', 'char')],
*[(k, 'pt_int16') for k in ('int', 'int16', 'short')],
*[(k, 'pt_int32') for k in ('long', 'int32', '_iqx')],
*[(k, 'pt_int64') for k in ('long long', 'int64')],
*[(k, 'pt_uint8') for k in ('unsigned char',)],
*[(k, 'pt_uint16') for k in ('unsigned int', 'unsigned short', 'Uint16')],
*[(k, 'pt_uint32') for k in ('unsigned long', 'Uint32')],
*[(k, 'pt_uint64') for k in ('unsigned long long', 'Uint64')],
*[(k, 'pt_ptr_int8') for k in ('signed char*',)],
*[(k, 'pt_ptr_int16') for k in ('int*', 'short*')],
*[(k, 'pt_ptr_int32') for k in ('long*',)],
*[(k, 'pt_ptr_uint8') for k in ('unsigned char*',)],
*[(k, 'pt_ptr_uint16') for k in ('unsigned int*', 'unsigned short*')],
*[(k, 'pt_ptr_uint32') for k in ('unsigned long*',)],
('unsigned long long*', 'pt_int64'),
*[(k, 'pt_arr_int8') for k in ('signed char[]',)],
*[(k, 'pt_arr_int16') for k in ('int[]', 'short[]')],
*[(k, 'pt_arr_int32') for k in ('long[]',)],
*[(k, 'pt_arr_uint8') for k in ('unsigned char[]',)],
*[(k, 'pt_arr_uint16') for k in ('unsigned int[]', 'unsigned short[]')],
*[(k, 'pt_arr_uint32') for k in ('unsigned long[]',)],
*[(k, 'pt_float') for k in ('float', 'float32')],
('struct', 'pt_struct'),
('union', 'pt_union'),
])
def parse_makefile(makefile_path):
makefile_dir = os.path.dirname(makefile_path)
project_root = os.path.dirname(makefile_dir) # поднялись из Debug
with open(makefile_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
objs_lines = []
collecting = False
for line in lines:
stripped = line.strip()
if stripped.startswith("ORDERED_OBJS") and "+=" in stripped:
parts = stripped.split("\\")
first_part = parts[0]
idx = first_part.find("+=")
tail = first_part[idx+2:].strip()
if tail:
objs_lines.append(tail)
collecting = True
if len(parts) > 1:
for p in parts[1:]:
p = p.strip()
if p:
objs_lines.append(p)
continue
if collecting:
if stripped.endswith("\\"):
objs_lines.append(stripped[:-1].strip())
else:
objs_lines.append(stripped)
collecting = False
objs_str = ' '.join(objs_lines)
objs_str = re.sub(r"\$\([^)]+\)", "", objs_str)
objs = []
for part in objs_str.split():
part = part.strip()
if part.startswith('"') and part.endswith('"'):
part = part[1:-1]
if part:
objs.append(part)
c_files = []
include_dirs = set()
for obj_path in objs:
if "DebugTools" in obj_path:
continue
if "v120" in obj_path:
continue
if obj_path.startswith("Debug\\") or obj_path.startswith("Debug/"):
rel_path = obj_path.replace("Debug\\", "Src\\").replace("Debug/", "Src/")
else:
rel_path = obj_path
abs_path = os.path.normpath(os.path.join(project_root, rel_path))
root, ext = os.path.splitext(abs_path)
if ext.lower() == ".obj":
c_path = root + ".c"
else:
c_path = abs_path
# Сохраняем только .c файлы
if c_path.lower().endswith(".c"):
c_files.append(c_path)
dir_path = os.path.dirname(c_path)
if dir_path and "DebugTools" not in dir_path:
include_dirs.add(dir_path)
return c_files, sorted(include_dirs)
# Шаблон для поиска глобальных переменных
# Пример: int varname;
# Пропускаем строки с static и функции
VAR_PATTERN = re.compile(
r'^\s*(?!static)(\w[\w\s\*]+)\s+(\w+)\s*(=\s*[^;]+)?\s*;',
re.MULTILINE)
EXTERN_PATTERN = re.compile(
r'^\s*extern\s+[\w\s\*]+\s+(\w+)\s*;',
re.MULTILINE)
TYPEDEF_PATTERN = re.compile(
r'typedef\s+(struct|union)?\s*(\w+)?\s*{[^}]*}\s*(\w+)\s*;', re.DOTALL)
TYPEDEF_SIMPLE_PATTERN = re.compile(
r'typedef\s+(.+?)\s+(\w+)\s*;', re.DOTALL)
def map_type_to_pt(typename, varname):
typename = typename.strip()
# Убираем const и volatile, где бы они ни были (например, "const volatile int")
for qualifier in ('const', 'volatile'):
typename = typename.replace(qualifier, '')
typename = typename.strip() # снова убрать лишние пробелы после удаления
# Раскрутка через typedef
resolved_type = typedef_aliases.get(typename, typename)
# Прямая проверка
if resolved_type in type_map:
return type_map[resolved_type]
if resolved_type.startswith('struct'):
return type_map['struct']
if resolved_type.startswith('union'):
return type_map['union']
if '_iq' in resolved_type and '_iqx' in type_map:
return type_map['_iqx']
return 'pt_unknown'
def get_iq_define(vtype):
if '_iq' in vtype:
# Преобразуем _iqXX в t_iqXX
return 't' + vtype[vtype.index('_iq'):]
else:
return 't_iq_none'
def get_files_by_ext(roots, exts):
files = []
for root in roots:
for dirpath, _, filenames in os.walk(root):
for f in filenames:
if any(f.endswith(e) for e in exts):
files.append(os.path.join(dirpath, f))
return files
def read_file_try_encodings(filepath):
for enc in ['utf-8', 'cp1251']:
try:
with open(filepath, 'r', encoding=enc) as f:
content = f.read()
content = strip_single_line_comments(content) # <=== ВАЖНО
return content, enc
except UnicodeDecodeError:
continue
raise UnicodeDecodeError(f"Не удалось прочитать файл {filepath} с кодировками utf-8 и cp1251")
FUNC_PATTERN = re.compile(
r'\w[\w\s\*\(\),]*\([^;{)]*\)\s*\{(?:[^{}]*|\{[^}]*\})*?\}', re.DOTALL)
def strip_single_line_comments(code):
# Удалим // ... до конца строки
return re.sub(r'//.*?$', '', code, flags=re.MULTILINE)
def remove_function_bodies(code):
result = []
i = 0
length = len(code)
while i < length:
match = re.search(r'\b[\w\s\*\(\),]*\([^;{}]*\)\s*\{', code[i:])
if not match:
result.append(code[i:])
break
start = i + match.start()
brace_start = i + match.end() - 1
result.append(code[i:start]) # Добавляем всё до функции
# Ищем конец функции по уровню вложенности скобок
brace_level = 1
j = brace_start + 1
in_string = False
while j < length and brace_level > 0:
char = code[j]
if char == '"' or char == "'":
quote = char
j += 1
while j < length and code[j] != quote:
if code[j] == '\\':
j += 2
else:
j += 1
elif code[j] == '{':
brace_level += 1
elif code[j] == '}':
brace_level -= 1
j += 1
# Заменяем тело функции пробелами той же длины
result.append(' ' * (j - start))
i = j
return ''.join(result)
def extract_struct_definitions_from_file(filepath):
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# Удаляем комментарии
content = re.sub(r'//.*', '', content)
content = re.sub(r'/\*.*?\*/', '', content, flags=re.DOTALL)
type_definitions = {}
anonymous_counter = [0]
def split_fields(body):
"""
Разбивает тело struct/union на поля с учётом вложенных { }.
Возвращает список строк - полей (без ;).
"""
fields = []
start = 0
brace_level = 0
for i, ch in enumerate(body):
if ch == '{':
brace_level += 1
elif ch == '}':
brace_level -= 1
elif ch == ';' and brace_level == 0:
fields.append(body[start:i].strip())
start = i + 1
# если что-то осталось после последнего ;
tail = body[start:].strip()
if tail:
fields.append(tail)
return fields
def parse_fields(body):
fields = {}
body = body.strip('{} \n\t')
field_strings = split_fields(body)
for field_str in field_strings:
if field_str.startswith(('struct ', 'union ')):
# Вложенный struct/union
# Пытаемся найти имя и тело
m = re.match(r'(struct|union)\s*(\w*)\s*({.*})\s*(\w+)?', field_str, re.DOTALL)
if m:
kind, tag, inner_body, varname = m.groups()
if not varname:
# Анонимная вложенная структура/объединение
varname = f"__anon_{anonymous_counter[0]}"
anonymous_counter[0] += 1
type_definitions[varname] = parse_fields(inner_body)
fields[varname] = varname
else:
# Если есть имя переменной
anon_type_name = f"__anon_{anonymous_counter[0]}"
anonymous_counter[0] += 1
type_definitions[anon_type_name] = parse_fields(inner_body)
fields[varname] = anon_type_name if tag == '' else f"{kind} {tag}"
else:
# Не смогли распарсить вложенную структуру - кладём как есть
fields[field_str] = None
else:
# Обычное поле
# Нужно выделить тип и имя поля с учётом указателей и массивов
m = re.match(r'(.+?)\s+([\w\*\[\]]+)$', field_str)
if m:
typename, varname = m.groups()
fields[varname.strip()] = typename.strip()
else:
# не распарсили поле — кладём "как есть"
fields[field_str] = None
return fields
# Парсим typedef struct/union {...} Alias;
typedef_struct_pattern = re.compile(
r'\btypedef\s+(struct|union)\s*({.*?})\s*(\w+)\s*;', re.DOTALL)
for match in typedef_struct_pattern.finditer(content):
alias = match.group(3)
body = match.group(2)
type_definitions[alias] = parse_fields(body)
# Парсим struct/union Name {...};
named_struct_pattern = re.compile(
r'\b(struct|union)\s+(\w+)\s*({.*?})\s*;', re.DOTALL)
for match in named_struct_pattern.finditer(content):
name = match.group(2)
body = match.group(3)
if name not in type_definitions:
type_definitions[name] = parse_fields(body)
return type_definitions
def parse_vars_from_file(filepath):
content, encoding = read_file_try_encodings(filepath)
content_clean = remove_function_bodies(content)
vars_found = []
for m in VAR_PATTERN.finditer(content_clean):
typename = m.group(1).strip()
varlist = m.group(2)
for var in varlist.split(','):
varname = var.strip()
# Убираем указатели и массивы
varname = varname.strip('*').split('[')[0]
# Фильтрация мусора
if not re.match(r'^[_a-zA-Z][_a-zA-Z0-9]*$', varname):
continue
vars_found.append((varname, typename))
return vars_found, encoding
def parse_typedefs_from_file(filepath):
"""
Парсит typedef из файла C:
- typedef struct/union { ... } Alias;
- typedef simple_type Alias;
Возвращает словарь alias -> базовый тип (например, 'MyType' -> 'struct' или 'unsigned int').
"""
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# Убираем однострочные комментарии
content = re.sub(r'//.*?$', '', content, flags=re.MULTILINE)
# Убираем многострочные комментарии
content = re.sub(r'/\*.*?\*/', '', content, flags=re.DOTALL)
aliases = {}
# --- Парсим typedef struct/union {...} Alias;
# Используем стек для вложенных фигурных скобок
typedef_struct_union_pattern = re.compile(r'\btypedef\s+(struct|union)\b', re.IGNORECASE)
pos = 0
while True:
m = typedef_struct_union_pattern.search(content, pos)
if not m:
break
kind = m.group(1)
brace_open_pos = content.find('{', m.end())
if brace_open_pos == -1:
# Нет тела структуры, пропускаем
pos = m.end()
continue
# Ищем позицию закрывающей скобки с учётом вложенности
brace_level = 1
i = brace_open_pos + 1
while i < len(content) and brace_level > 0:
if content[i] == '{':
brace_level += 1
elif content[i] == '}':
brace_level -= 1
i += 1
if brace_level != 0:
# Некорректный синтаксис
pos = m.end()
continue
# Отрезок typedef структуры/объединения
typedef_block = content[m.start():i]
# После закрывающей скобки ожидаем имя алиаса и точку с запятой
rest = content[i:].lstrip()
alias_match = re.match(r'(\w+)\s*;', rest)
if alias_match:
alias_name = alias_match.group(1)
aliases[alias_name] = kind # например, "struct" или "union"
pos = i + alias_match.end()
else:
# Анонимный typedef? Просто пропускаем
pos = i
# --- Удаляем typedef struct/union {...} Alias; чтобы не мешали простым typedef
# Для этого удалим весь блок typedef struct/union {...} Alias;
def remove_typedef_struct_union_blocks(text):
result = []
last_pos = 0
for m in typedef_struct_union_pattern.finditer(text):
brace_open_pos = text.find('{', m.end())
if brace_open_pos == -1:
continue
brace_level = 1
i = brace_open_pos + 1
while i < len(text) and brace_level > 0:
if text[i] == '{':
brace_level += 1
elif text[i] == '}':
brace_level -= 1
i += 1
if brace_level != 0:
continue
# Ищем имя алиаса и точку с запятой после i
rest = text[i:].lstrip()
alias_match = re.match(r'\w+\s*;', rest)
if alias_match:
end_pos = i + alias_match.end()
result.append(text[last_pos:m.start()])
last_pos = end_pos
result.append(text[last_pos:])
return ''.join(result)
content_simple = remove_typedef_struct_union_blocks(content)
# --- Парсим простые typedef: typedef base_type alias;
simple_typedef_pattern = re.compile(
r'\btypedef\s+([^{};]+?)\s+(\w+)\s*;', re.MULTILINE)
for m in simple_typedef_pattern.finditer(content_simple):
base_type = m.group(1).strip()
alias = m.group(2).strip()
if alias not in aliases:
aliases[alias] = base_type
return aliases
def parse_externs_from_file(filepath):
content, encoding = read_file_try_encodings(filepath)
extern_vars = set(EXTERN_PATTERN.findall(content))
return extern_vars, encoding
def get_relpath_to_srcdirs(filepath, src_dirs):
# Ищем первый SRC_DIR, в котором лежит filepath, и возвращаем относительный путь
for d in src_dirs:
try:
rel = os.path.relpath(filepath, d)
# Проверим, что rel не уходит выше корня (например, не начинается с '..')
if not rel.startswith('..'):
return rel.replace('\\', '/') # Для единообразия в путях
except ValueError:
continue
# Если ни один SRC_DIR не подходит, вернуть basename
return os.path.basename(filepath)
def find_all_includes_recursive(c_files, include_dirs, processed_files=None):
"""
Рекурсивно ищет все include-файлы начиная с заданных c_files.
include_dirs — список директорий, в которых ищем include-файлы.
processed_files — множество уже обработанных файлов (для избежания циклов).
"""
if processed_files is None:
processed_files = set()
include_files = set()
include_pattern = re.compile(r'#include\s+"([^"]+)"')
for cfile in c_files:
norm_path = os.path.normpath(cfile)
if norm_path in processed_files:
continue
processed_files.add(norm_path)
content, _ = read_file_try_encodings(cfile)
includes = include_pattern.findall(content)
for inc in includes:
include_files.add(inc)
# Ищем полный путь к include-файлу в include_dirs
inc_full_path = None
for dir_ in include_dirs:
candidate = os.path.normpath(os.path.join(dir_, inc))
if os.path.isfile(candidate):
inc_full_path = candidate
break
# Если нашли include-файл и ещё не обработали — рекурсивно ищем include внутри него
if inc_full_path and inc_full_path not in processed_files:
nested_includes = find_all_includes_recursive(
[inc_full_path], include_dirs, processed_files
)
include_files.update(nested_includes)
return include_files
def file_uses_typedef_vars(filepath, missing_vars, typedefs):
"""
Проверяем, содержит ли файл typedef с одним из missing_vars.
typedefs — словарь alias->базовый тип, полученный parse_typedefs_from_file.
"""
# Здесь проще проверить, есть ли в typedefs ключи из missing_vars,
# но в условии — typedef переменных из missing_in_h,
# значит, нужно проверить typedef переменных с этими именами
# Для упрощения — прочитаем содержимое и проверим наличие typedef с именами из missing_vars
content, _ = read_file_try_encodings(filepath)
for var in missing_vars:
# Ищем в content что-то типа typedef ... var ...;
# Для простоты регулярка: typedef ... var;
pattern = re.compile(r'\btypedef\b[^;]*\b' + re.escape(var) + r'\b[^;]*;', re.DOTALL)
if pattern.search(content):
return True
return False
def file_contains_extern_vars(filepath, extern_vars):
content, _ = read_file_try_encodings(filepath)
for var in extern_vars:
pattern = re.compile(r'\bextern\b[^;]*\b' + re.escape(var) + r'\b\s*;')
if pattern.search(content):
return True
return False
def add_struct_fields(new_debug_vars, var_prefix, struct_type, all_structs, existing_debug_vars):
"""
Рекурсивно добавляет поля структуры в new_debug_vars.
var_prefix: имя переменной или путь к полю (например "myVar" или "myVar.subfield")
struct_type: имя типа структуры (например "MyStruct")
all_structs: словарь всех структур
existing_debug_vars: множество уже существующих имен переменных
"""
if struct_type not in all_structs:
# Типа нет в структуре, значит не структура или неизвестный тип — выходим
return
fields = all_structs[struct_type]
for field_name, field_type in fields.items():
full_var_name = f"{var_prefix}.{field_name}"
if full_var_name in existing_debug_vars or full_var_name in new_debug_vars:
continue
if field_type is None:
continue
iq_type = get_iq_define(field_type)
pt_type = map_type_to_pt(field_type, full_var_name)
formated_name = f'"{full_var_name}"'
line = f'\t{{(char *)&{full_var_name:<40} , {pt_type:<20} , {iq_type:<20} , {formated_name:<40}}}, \\'
new_debug_vars[full_var_name] = line
# Если поле — тоже структура, рекурсивно раскрываем
# При этом убираем указатели и массивы из типа, если они есть
base_field_type = field_type.split()[0] # например "struct" или "MyStruct*"
# Удаляем указатели и массивы из имени типа для поиска в all_structs
base_field_type = re.sub(r'[\*\[\]0-9]+', '', base_field_type)
base_field_type = base_field_type.strip()
if base_field_type in all_structs:
add_struct_fields(new_debug_vars, full_var_name, base_field_type, all_structs, existing_debug_vars)
else:
a=1
def main(make_path):
c_files, include_dirs = parse_makefile(make_path)
all_dirs = c_files + include_dirs
h_files = get_files_by_ext(include_dirs, ['.h'])
vars_in_c = {}
encodings_c = set()
for cf in c_files:
vars_found, enc = parse_vars_from_file(cf)
encodings_c.add(enc)
for vname, vtype in vars_found:
vars_in_c[vname] = (vtype, cf)
externs_in_h = set()
for hf in h_files:
externs, _ = parse_externs_from_file(hf)
externs_in_h |= externs
missing_in_h = {v: vars_in_c[v] for v in vars_in_c if v not in externs_in_h}
all_structs = {}
for fl in c_files + h_files:
structs = extract_struct_definitions_from_file(fl)
all_structs.update(structs)
# Подготовка typedef-ов
global typedef_aliases
typedef_aliases = {}
for f in h_files + c_files:
aliases = parse_typedefs_from_file(f)
typedef_aliases.update(aliases)
included_headers = find_all_includes_recursive(c_files, include_dirs) # все подключенные .h
include_files = []
for header in included_headers:
# Полный путь к файлу нужно получить
full_path = None
for d in all_dirs:
candidate = os.path.join(d, header)
if os.path.isfile(candidate):
full_path = candidate
break
if not full_path:
continue # файл не найден в SRC_DIRS — игнорируем
# Проверяем, что это строго .h
if not full_path.endswith('.h'):
continue
# Парсим typedef из файла
typedefs = parse_typedefs_from_file(full_path)
# Проверяем, использует ли typedef переменные из missing_in_h по их vtype
uses_typedef = any(vtype in typedefs for (vtype, path) in missing_in_h.values())
# Проверяем наличие extern переменных
has_extern = file_contains_extern_vars(full_path, externs_in_h)
if not has_extern and not uses_typedef:
continue
# Если прошло оба условия — добавляем
include_files.append(full_path)
# Путь к debug_vars.h
common_prefix = os.path.commonpath(include_dirs)
output_dir = os.path.join(common_prefix, 'DebugTools')
os.makedirs(output_dir, exist_ok=True)
output_path = os.path.join(output_dir, 'debug_vars.c')
# Считываем существующие переменные
existing_debug_vars = {}
if os.path.isfile(output_path):
with open(output_path, 'r', encoding='utf-8', errors='ignore') as f:
old_lines = f.readlines()
for line in old_lines:
m = re.match(r'\s*{.*?,\s+.*?,\s+.*?,\s+"([_a-zA-Z][_a-zA-Z0-9]*)"\s*},', line)
if m:
varname = m.group(1)
existing_debug_vars[varname] = line.strip()
# Генерируем новые переменные
new_debug_vars = {}
for vname, (vtype, path) in vars_in_c.items():
if vname in existing_debug_vars:
continue
iq_type = get_iq_define(vtype)
pt_type = map_type_to_pt(vtype, vname)
if pt_type not in ('pt_struct', 'pt_union'):
formated_name = f'"{vname}"'
line = f'{{(char *)&{vname:<41} , {pt_type:<21} , {iq_type:<21} , {formated_name:<42}}}, \\'
new_debug_vars[vname] = line
else:
continue
# Если тип переменной — структура, добавляем поля
base_type = vtype.split()[0]
# Удаляем символы указателей '*' и всю квадратную скобку с содержимым (например [10])
base_type = re.sub(r'\*|\[[^\]]*\]', '', base_type).strip()
if base_type in all_structs:
add_struct_fields(new_debug_vars, vname, base_type, all_structs, existing_debug_vars)
# Сортируем новые переменные по алфавиту по имени
sorted_new_debug_vars = dict(sorted(new_debug_vars.items()))
# Объединяем все переменные
all_debug_lines = list(existing_debug_vars.values()) + list(sorted_new_debug_vars.values())
# DebugVar_Numb теперь по всем переменным
out_lines = []
out_lines.append("// Этот файл сгенерирован автоматически")
out_lines.append(f'#include "debug_tools.h"')
out_lines.append('\n\n// Инклюды для доступа к переменным')
out_lines.append(f'#include "IQmathLib.h"')
for incf in include_files:
out_lines.append(f'#include "{incf}"')
out_lines.append('\n\n// Экстерны для доступа к переменным')
for vname, (vtype, path) in missing_in_h.items():
out_lines.append(f'extern {vtype} {vname};')
out_lines.append(f'\n\n// Определение массива с указателями на переменные для отладки')
out_lines.append(f'int DebugVar_Numb = {len(all_debug_lines)};')
out_lines.append('#pragma DATA_SECTION(dbg_vars,".dbgvar_info")')
out_lines.append('DebugVar_t dbg_vars[] = {\\')
out_lines.extend(all_debug_lines)
out_lines.append('};')
out_lines.append('')
# Выберем кодировку для записи файла
# Если встречается несколько, возьмем первую из set
enc_to_write = 'cp1251'
#print("== GLOBAL VARS FOUND ==")
#for vname, (vtype, path) in vars_in_c.items():
#print(f"{vtype:<20} {vname:<40} // {path}")
with open(output_path, 'w', encoding=enc_to_write) as f:
f.write('\n'.join(out_lines))
print(f'Файл debug_vars.c сгенерирован в кодировке, переменных: {len(all_debug_lines)}')
if __name__ == '__main__':
if len(sys.argv) < 2:
main('F:/Work/Projects/TMS/TMS_new_bus/Debug/makefile')
print("Usage: parse_makefile.py path/to/Makefile")
sys.exit(1)
else:
main(sys.argv[1])

View File

@@ -1,143 +0,0 @@
import xml.etree.ElementTree as ET
# === Словарь соответствия типов XML → DebugVarType_t ===
type_map = dict([
*[(k, 'pt_int8') for k in ('signed char', 'char')],
*[(k, 'pt_int16') for k in ('int', 'int16', 'short')],
*[(k, 'pt_int32') for k in ('long', 'int32', '_iqx')],
*[(k, 'pt_int64') for k in ('long long', 'int64')],
*[(k, 'pt_uint8') for k in ('unsigned char',)],
*[(k, 'pt_uint16') for k in ('unsigned int', 'unsigned short', 'Uint16')],
*[(k, 'pt_uint32') for k in ('unsigned long', 'Uint32')],
*[(k, 'pt_uint64') for k in ('unsigned long long', 'Uint64')],
*[(k, 'pt_ptr_int8') for k in ('signed char*',)],
*[(k, 'pt_ptr_int16') for k in ('int*', 'short*')],
*[(k, 'pt_ptr_int32') for k in ('long*',)],
*[(k, 'pt_ptr_uint8') for k in ('unsigned char*',)],
*[(k, 'pt_ptr_uint16') for k in ('unsigned int*', 'unsigned short*')],
*[(k, 'pt_ptr_uint32') for k in ('unsigned long*',)],
('unsigned long long*', 'pt_int64'),
*[(k, 'pt_arr_int8') for k in ('signed char[]',)],
*[(k, 'pt_arr_int16') for k in ('int[]', 'short[]')],
*[(k, 'pt_arr_int32') for k in ('long[]',)],
*[(k, 'pt_arr_uint8') for k in ('unsigned char[]',)],
*[(k, 'pt_arr_uint16') for k in ('unsigned int[]', 'unsigned short[]')],
*[(k, 'pt_arr_uint32') for k in ('unsigned long[]',)],
*[(k, 'pt_float') for k in ('float', 'float32')],
('struct', 'pt_struct'),
('union', 'pt_union'),
])
def is_anonymous(elem):
typ = elem.attrib.get('type', '')
return 'anonymous' in typ
def get_array_sizes(elem):
sizes = []
i = 0
while True:
attr = 'size' if i == 0 else f'size{i}'
if attr in elem.attrib:
sizes.append(elem.attrib[attr])
i += 1
else:
break
return ''.join(f'[{size}]' for size in sizes)
def collect_externs_and_vars(element, prefix, extern_vars, variables):
kind = element.attrib.get('kind', '')
name = element.attrib.get('name', prefix)
vtype = element.attrib.get('type', 'unknown')
# Пропускаем анонимные типы полностью
if is_anonymous(element):
return
# Для массивов учитываем все размеры size, size1, size2...
if kind == 'array':
array_dims = get_array_sizes(element)
extern_type = vtype # тип без размеров
extern_vars[name] = (extern_type, array_dims)
variables.append((name, vtype)) # В массив макросов кладём сам массив (без элементов)
return
# Для обычных структур (не анонимных) и переменных
if kind in ('struct', 'union'):
extern_vars[name] = (vtype, '')
variables.append((name, vtype))
return
# Простые переменные
extern_vars[name] = (vtype, '')
variables.append((name, vtype))
def parse_variables(xml_path):
tree = ET.parse(xml_path)
root = tree.getroot()
extern_vars = dict()
variables = []
for var in root.findall('variable'):
collect_externs_and_vars(var, var.attrib['name'], extern_vars, variables)
return variables, extern_vars
def format_extern_declaration(varname, vartype, dims):
base_type = vartype.replace('[]', '') # убираем [] из типа
return f"extern {base_type} {varname}{dims};"
def to_macro_entry(varname, vartype):
# pt_ и t_iq_none — пример, замените на вашу логику или словарь
ptr_type = type_map.get(vartype)
if ptr_type is None:
ptr_type = 'char *'
iq_type = "t_iq_none"
return f"\t{{(char *)&{varname:<40}, {ptr_type:<20}, {iq_type:<20}, \"{varname}\"}},"
def generate_code(variables, extern_vars):
# extern-блок
extern_lines = []
for var, (vtype, dims) in sorted(extern_vars.items()):
# фильтр анонимных структур по имени (пример)
if "anonymous" in var:
continue
extern_lines.append(format_extern_declaration(var, vtype, dims))
# define DebugVar_Numb
debugvar_numb = len(variables)
defines = [f"#define DebugVar_Numb\t{debugvar_numb}"]
# define DebugVar_Init
defines.append("#define DebugVar_Init\t\\")
defines.append("{\\")
for varname, vartype in variables:
defines.append(to_macro_entry(varname, vartype))
defines.append("}")
return "\n".join(extern_lines) + "\n\n" + "\n".join(defines)
if __name__ == "__main__":
xml_file = "F:/Work/Projects/TMS/TMS_new_bus/bin/New_bus_allVars.xml"
variables, extern_vars = parse_variables(xml_file)
code = generate_code(variables, extern_vars)
with open("debug_vars.h", "w") as f:
f.write(code)
print("Сгенерировано в debug_vars.h")

View File

@@ -1,581 +0,0 @@
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())

Binary file not shown.

282
README.md Normal file
View File

@@ -0,0 +1,282 @@
# DebugTools - Просмотр переменных по указателям
## Содержание
1. [Описание модуля](#программный-модуль-debugtools)
2. [Описание приложения для настройки](#debugvaredit---настройка-переменных)
3. [Описание терминалки для считывания](#debugvarterminal---считывание-переменных-для-tms)
4. [Для разработчиков](#для-разработчиков)
# Программный модуль DebugTools
Модуль состоит из трех файлов:
- **debug_tools.c** - реализация считывания переменных
- **debug_tools.h** - объявление всякого для считывания переменных
- **debug_vars.c** - определение массива считываемых переменных
Этот модуль предоставляет функциональность для чтения значений переменных во встроенной системе, включая работу с IQ-форматами, защиту доступа и проверку диапазонов памяти.
Для чтения переменных можно использовать функции:
```c
/* Читает значение переменной по индексу */
int Debug_ReadVar(int var_ind, int32_t *return_long);
/* Читает имя переменной по индексу */
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr, int *length);
/* Читает возвращаемый тип (IQ) переменной по индексу */
int Debug_ReadVarReturnType(int var_ind, int *vartype);
/* Читает тип переменной по индексу */
int Debug_ReadVarType(int var_ind, int *vartype);
/* Читает значение переменной с нижнего уровня */
int Debug_LowLevel_ReadVar(int32_t *return_long);
/* Инициализирует отладку нижнего уровня */
int Debug_LowLevel_Initialize(DateTime_t *external_date);
/* Читает возвращаемый тип (IQ) низкоуровнено заданной переменной */
int Debug_LowLevel_ReadVarReturnType(int *vartype);
/* Читает тип низкоуровнено заданной переменной.*/
int Debug_LowLevel_ReadVarType(int *vartype);
```
Переменные доступные для чтения определяются в **debug_vars.c** (их можно прописывать вручную или генерировать через **DebugVarEdit**):
```c
// Определение массива с указателями на переменные для отладки
int DebugVar_Qnt = 5;
#pragma DATA_SECTION(dbg_vars,".dbgvar_info")
// pointer_type iq_type return_iq_type short_name
DebugVar_t dbg_vars[] = {\
{(uint8_t *)&freqTerm, pt_float, t_iq_none, t_iq10, "freqT" }, \
{(uint8_t *)&ADC_sf[0][0], pt_int16, t_iq_none, t_iq_none, "ADC_sf00" }, \
{(uint8_t *)&ADC_sf[0][1], pt_int16, t_iq_none, t_iq_none, "ADC_sf01" }, \
{(uint8_t *)&ADC_sf[0][2], pt_int16, t_iq_none, t_iq_none, "ADC_sf02" }, \
{(uint8_t *)&ADC_sf[0][3], pt_int16, t_iq_none, t_iq_none, "ADC_sf03" }, \
{(uint8_t *)&Bender[0].KOhms, pt_uint16, t_iq, t_iq10, "Bend0.KOhm" }, \
{(uint8_t *)&Bender[0].Times, pt_uint16, t_iq_none, t_iq_none, "Bend0.Time" }, \
};
```
## LowLevel - Просмотр абсолютно любых переменных
Также присутствует утилита `parse_xml.exe`, которая генерирует `.xml` файл с всеми переменными в программе и их адрессами
Для её подключения:
- В Pre-Build Steps добавить следующую команду. Это удаление `debug_tools.obj` файла для его перекомпиялции с актуальной датой компиляции
```
cmd /c del /Q "${CWD}\Src\DebugTools\debug_tools.obj"
```
- В Post-Build Steps добавить следующую команду. Это:
- формирование с помощью утилиты компилятора `ofd2000` `.xml` файла с полной отладочной информацией.
- формирование отладочной информации в текстовом файле (для получения таймштампа)
- запуск утилиты `parse_xml.exe` для формирования итогового `<projname>_allVars.xml` с информацией о переменных и адресах
```
"${CG_TOOL_ROOT}/bin/ofd2000" --obj_display=symbols --dwarf --dwarf_display=all --xml --xml_indent=1 --output="${BuildArtifactFileBaseName}_ofd_dump.xml" "${BuildArtifactFilePath}"
"${CG_TOOL_ROOT}/bin/ofd2000" "${BuildArtifactFilePath}" > ${CCS_PROJECT_DIR}/bin/temp.txt
"${CCS_PROJECT_DIR}/Src/DebugTools/parse_xml/parse_xml.exe" ${BuildArtifactFileBaseName}_ofd_dump.xml ${CCS_PROJECT_DIR}/bin/temp.txt ${BuildArtifactFileBaseName}_allVars.xml"
```
После, с использованием терминалки можно прочитать любые переменные по адресам. (должен совпадать таймштапм в прошивке и `.xml` файла, иначе контроллер ответит ошибкой)
# DebugVarEdit - Настройка переменных
**DebugVarEdit** — графическое приложение для Windows, предназначенное для настройки и генерации отладочных переменных (`debug_vars.c`) на основе исходного C-проекта. Работает с `makefile` проекта, сохраняет изменения в XML и позволяет удобно редактировать переменные и их типы через интерфейс.
Программа — один исполняемый файл `DebugVarEdit.exe`, не требующий установки и дополнительных зависимостей.
> Требуется Windows 7 или новее.
---
## Как использовать приложение
1. Запустите **DebugVarEdit.exe.**
2. Укажите пути и контроллер:
- **Путь к XML** — новый или существующий файл настроек переменных;
- **Путь к проекту** — путь к корню проека (папка, которая является корнем для проекта в Code Composer);
- **Путь к makefile** — путь к `makefile` относительно корня проекта;
- **Путь для debug_vars.c** — папка, куда будет сгенерирован файл `debug_vars.c` с переменными.
- **МК** — TMS или STM. Они отличаются размером int, и также принятыми кодировками файлов (utf-8/cp1251)
3. Нажмите **Сканировать переменные**:
- Программа проанализирует исходники, указанные в makefile, и найдёт все переменные.
- Результат сохранится в двух XML-файлах:
- `structs.xml` — информация обо всех структурах и typedef;
- `<ваш_файл>.xml` — список всех найденных переменных.
4. Нажмите **Добавить переменные**:
- В **левой таблице** отображаются все найденные переменные.
Для добавления переменной в проект дважды кликните по ней или нажмите кнопку `>`, чтобы переместить в правую таблицу.
- В **правой таблице** находятся переменные, выбранные для использования.
Чтобы убрать переменную из проекта, переместите её обратно в левую таблицу двойным кликом или кнопкой `<`.
- После выбора переменных нажмите **Применить**, чтобы обновить основной список переменных и включить их в проект.
5. Настройте параметры выбранных переменных:
- **En** — включение или отключение переменной для генерации;
- **Base Type** — базовый тип переменной (например, int8, uint16 и т.д.);
- **IQ Type** — формат IQ, если применимо;
- **Return Type** — формат возвращаемого значения (IQ-тип или целочисленный);
- **Shortname** — короткое имя переменной для для терминалки.
Все параметры выбираются из выпадающих списков, которые можно настроить, чтобы отображались только нужные опции.
6. Нажмите **Сгенерировать файл** для создания файла `debug_vars.c` с выбранными переменными.
---
## Возможности
- Загрузка и сохранение настроек переменных в XML-файлах.
- Автоматическое определение исходных файлов с переменными для удобства работы.
- Редактирование переменных: включение, короткого имени и типов через удобные списки.
- Подсветка ошибок при вводе (неправильные имена, слишком длинные короткие имена).
- Быстрая фильтрация переменных по столбцам.
- Автоматическая генерация файла debug_vars.c с выбранными переменными.
- Возможность сразу открыть сгенерированный файл в редакторе.
- Умное автодополнение имён переменных и полей структур.
---
## Пример XML-файла
```xml
<project proj_path="C:/myproj" makefile_path="Debug/Makefile" structs_path="Src/DebugTools/structs.xml">
<variables>
<var name="g_myvar">
<enable>true</enable>
<show_var>true</show_var>
<shortname>myv</shortname>
<pt_type>pt_float</pt_type>
<iq_type>t_iq24</iq_type>
<return_type>t_iq24</return_type>
<type>float</type>
<file>Src/main/main.c</file>
<extern>true</extern>
<static>false</static>
</var>
</variables>
</project>
```
---
# DebugVarTerminal - Считывание переменных (для TMS)
**DebugVarTerminal** — терминалка для считывания переменных по RS-232 протоколу RS_Functions.
Программа — один исполняемый файл `DebugVarTerminal.exe`, не требующий установки и дополнительных зависимостей.
> Требуется Windows 7 или новее.
---
## Как использовать приложение
1. Запустите **DebugVarTerminal.exe.**
2. Выберите COM Port и скорость Baud. Нажмиите **Open**
3. Для считывания переменных заданных в `*debug_vars.c`:
- Откройте вкладку **Watch**.
- Выберите стартовый индекс переменной и количество переменных для считывания.
- Нажмите одну из кнопок:
- **Update Service** - считывает информацию об именах и возвращаемых IQ типах переемнных
- **Read Value(s)** - считывает и форматирует значения переменных в соответствии с IQ типами
- **Start Polling** - начать опрос выбранных переменных с заданным интервалом. При старте опроса, имя и тип переменных считываются автоматически
4. Для считывания переменных по адресам:
- Откройте вкладку **LowLevel**.
- Выберите `projname_allVars.xml` в папке bin рядом с бинарником. Из него подгрузятся все доступные для считывания переменные
- Выберите переменные кнопкной **Выбрать переменные**
- Задайте IQ тип и возвращаемый IQ тип если требуется
- Нажмите одну из кнопок:
- **Read Once** - считывает выбранные переменные один раз
- **Start Polling** - начать опрос выбранных переменных с заданным интервалом
5. Запись в CSV:
- Можно записывавать считываемые переменные в CSV файл, с разделителем `;`
- Нажмите кнопку **Начать запись в CSV**
- Когда нужная выборка будет накоплена нажмите **Остаовить запись в CSV**
- Нажмите **Выбрать файл CSV** для выбора пути для сохранения файла и нажмите **Сохранить данные в CSV** чтобы сохранить
- Генерируется совместимый с LogView `.csv` файл
Все параметры выбираются из выпадающих списков, которые можно настроить, чтобы отображались только нужные опции.
---
## Возможности
Режим "Watch":
- Быстрое и удобное чтение одной или нескольких переменных по их индексу.
- Автоматическое получение имен, типов и параметров масштабирования (IQ) переменных.
- Запуск постоянного опроса для отслеживания изменений значений в реальном времени.
- Наглядное представление данных в таблице с отображением как сырых, так и масштабированных значений.
Режим "LowLevel":
- Прямое чтение данных из памяти по заданным адресам.
- Возможность указания типов указателей, IQ-масштабирования и форматов возвращаемых данных.
- Аналогично режиму "Watch", поддерживается постоянный опрос выбранных низкоуровневых переменных.
- Умное автодополнение имён переменных и полей структур.
Логирование в CSV
- Записывайте все полученные значения в файл формата CSV для последующего анализа.
- Легкое управление записью: запуск, остановка и сохранение данных в любой момент.
---
---
# Для разработчиков
### Структура проекта:
```bash
Src
├── build/
│ └── build_and_clean.py # Билдинг проекта в .exe (через nuitka или pyinstaller)
├── DebugVarEdit_GUI.py # Главное окно
├── tms_debugvar_term.py # Терминал DebugVarTerminal
├── tms_debugvar_lowlevel.py # Виджет для выбора переменных для LowLevel Watch
├── var_table.py # Таблица выбранных переменных
├── var_selector_window.py # Окно выбора переменных
├── var_selector_table.py # Таблица переменных в окне выбора переменных
├── scan_progress_gui.py # Отображение процесса сканирования переменных
├── scan_vars.py # Сканирование переменных среди .c/.h файлов
├── generate_debug_vars.py # Генерация debug_vars.c
├── allvars_xml_parser.py # Парсинг XML со всеми переменными и структурами
├── csv_logger.py # Логирование переменных в CSV
├── myXML.py # Утилиты для XML
├── makefile_parser.py # Парсинг makefile на .c/.h файлы
├── var_setup.py # Подготовка переменных для окна выбора переменных
├── path_hints.py # Подсказки для автодополнения путей переменных
├── libclang.dll # Бибилиотека clang
├── icon.ico # Иконка
```
### Зависимости
Для запуска приложения:
- **Python 3.7+**
- **clang** — используется для парсинга C-кода (требуется `libclang.dll`, лежит в папке Src)
- **PySide2** — GUI-фреймворк
- **lxml** — работа с XML
> Python 3.7 и PySide2 рекомендуется для совместимости с Windows 7
Для сборки `.exe`:
- **Nuitka** — создает полностью автономный `.exe` без внешних зависимостей
- **PyInstaller** — создает `.exe` с зависимостью от `pythonXX.dll` (Python должен быть установлен для запуска `.exe`)
### Сборка:
Если вы хотите собрать `DebugVarEdit.exe` самостоятельно из исходников, используйте скрипт **build/build_and_clean.py**. Он автоматически собирает проект с помощью Nuitka или PyInstaller:
- Собирает проект в `DebugVarEdit.exe` в корневой папке.
- Включает:
- все необходимые `.dll` (например, `libclang.dll`),
- иконку (`icon.ico`),
- плагины PySide2.
- Очищает временные папки после сборки:
- `build_temp`
- `__pycache__`
- `<MAIN_SCRIPT_NAME>.*`
> Все пути, имена файлов, временные папки и выбор между Nuitka и PyInstaller можно настроить в начале файла `build_and_clean.py`.
### Установка зависимостей
```bash
pip install clang PySide2 lxml nuitka pyinstaller
```

732
Src/DebugVarEdit_GUI.py Normal file
View File

@@ -0,0 +1,732 @@
# build command
# pyinstaller --onefile --name DebugVarEdit --add-binary "build/libclang.dll;build" --distpath ./ --workpath ./build_temp --specpath ./build_temp var_setup_GUI.py
import sys
import os
import subprocess
import lxml.etree as ET
from generate_debug_vars import type_map, choose_type_map
from enum import IntEnum
from tms_debugvar_term import _DemoWindow
import threading
from generate_debug_vars import run_generate
import var_setup
from var_selector_window import VariableSelectorDialog
from var_table import VariableTableWidget, rows
from scan_progress_gui import ProcessOutputWindow
import scan_vars
import myXML
import time
import auto_updater
from PySide2.QtWidgets import (
QApplication, QWidget, QTableWidget, QTableWidgetItem,
QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton,
QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit,
QDialog, QTreeWidget, QTreeWidgetItem, QSizePolicy, QHeaderView,
QMenuBar, QMenu, QAction
)
from PySide2.QtGui import QTextCursor, QKeyEvent, QIcon, QFont
from PySide2.QtCore import Qt, QProcess, QObject, Signal, QSettings
var_edit_title = "Редактор переменных для отладки"
xml_path_title = "Путь к XML:"
proj_path_title = "Путь к проекту:"
makefile_path_title = "Пусть к makefile (относительно проекта)"
output_path_title = "Путь для для debug_vars.c:"
scan_title = "Сканировать переменные"
build_title = "Сгенерировать файл"
add_vars_title = "Добавить переменные"
open_output_title = "Открыть файл"
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)
# 3. UI: таблица с переменными
class VarEditor(QWidget):
def __init__(self):
super().__init__()
self.vars_list = []
self.structs = {}
self.typedef_map = {}
self.proj_path = None
self.xml_path = None
self.makefile_path = None
self.structs_path = None
self.output_path = None
self._updating = False # Флаг блокировки рекурсии
self._resizing = False # флаг блокировки повторного вызова
self.target = 'TMS'
self.initUI()
def initUI(self):
self.setWindowTitle(var_edit_title)
base_path = scan_vars.get_base_path()
icon_path = os.path.join(base_path, "icon.ico")
if os.path.exists(icon_path):
self.setWindowIcon(QIcon(icon_path))
# --- Поля ввода пути проекта и XML ---
# XML Output
xml_layout = QHBoxLayout()
xml_layout.addWidget(QLabel(xml_path_title))
self.xml_output_edit = QLineEdit()
self.xml_output_edit.returnPressed.connect(self.update)
self.xml_output_edit.textChanged.connect(self.__on_xml_path_changed)
xml_layout.addWidget(self.xml_output_edit)
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(proj_path_title))
self.proj_path_edit = QLineEdit()
self.proj_path_edit.returnPressed.connect(self.update)
self.proj_path_edit.textChanged.connect(self.__on_proj_path_changed)
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_title))
self.makefile_edit = QLineEdit()
self.makefile_edit.returnPressed.connect(self.update)
self.makefile_edit.textChanged.connect(self.__on_makefile_path_changed)
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(output_path_title))
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(scan_title)
self.btn_update_vars.clicked.connect(self.update_vars_data)
# Добавляем чекбокс для выбора типовой карты
# --- Создаем верхнее меню ---
menubar = QMenuBar(self)
menubar.setToolTip('Разные размеры int и кодировки файлов')
self.target_menu = QMenu("МК:", menubar)
# Создаем действия для выбора Target
self.action_tms = QAction("TMS", self, checkable=True)
self.action_stm = QAction("STM", self, checkable=True)
# Инициализируем QSettings с именем организации и приложения
self.settings = QSettings("SET", "DebugVarEdit_MainWindow")
# Восстанавливаем сохранённое состояние, если есть
mcu = self.settings.value("mcu_choosen", True, type=str)
self.on_target_selected(mcu)
self.target_menu.setToolTip(f'TMS: Размер int 16 бит. Кодировка cp1251\nSTM: Размер int 32 бита. Кодировка utf-8')
# Группируем действия чтобы выбирался только один
self.action_tms.triggered.connect(lambda: self.on_target_selected("TMS"))
self.action_tms.setToolTip('Размер int 16 бит. Кодировка cp1251')
self.action_stm.triggered.connect(lambda: self.on_target_selected("STM"))
self.action_stm.setToolTip('Размер int 32 бита. Кодировка utf-8')
self.target_menu.addAction(self.action_tms)
self.target_menu.addAction(self.action_stm)
self.terminal_menu = QMenu("Открыть Терминал", menubar)
self.action_terminal_tms = QAction("TMS DemoTerminal", self)
self.action_terminal_modbus = QAction("Modbus DemoTerminal", self)
self.action_terminal_tms.triggered.connect(lambda: self.open_terminal("TMS"))
self.action_terminal_modbus.triggered.connect(lambda: self.open_terminal("MODBUS"))
self.terminal_menu.addAction(self.action_terminal_tms)
#self.terminal_menu.addAction(self.action_terminal_modbus)
menubar.addMenu(self.target_menu)
menubar.addMenu(self.terminal_menu)
# Кнопка сохранения
btn_save = QPushButton(build_title)
btn_save.clicked.connect(self.save_build)
# Кнопка добавления переменных
self.btn_add_vars = QPushButton(add_vars_title)
self.btn_add_vars.clicked.connect(self.__open_variable_selector)
# Кнопка открыть output-файл с выбором программы
btn_open_output = QPushButton(open_output_title)
btn_open_output.clicked.connect(self.__open_output_file_with_program)
# Таблица
self.table = VariableTableWidget()
# Основной layout
layout = QVBoxLayout()
layout.setMenuBar(menubar) # прикрепляем menubar в layout сверху
layout.addLayout(xml_layout)
layout.addLayout(proj_layout)
layout.addLayout(makefile_layout)
layout.addWidget(self.btn_update_vars)
layout.addWidget(self.table)
layout.addWidget(self.btn_add_vars)
layout.addLayout(source_output_layout)
layout.addWidget(btn_save)
layout.addWidget(btn_open_output)
self.setLayout(layout)
def open_terminal(self, target):
target = target.lower()
if target == "tms":
exe_name = "DebugVarTerminal.exe"
# Путь к exe в текущей директории запуска программы
exe_path = os.path.join(os.getcwd(), exe_name)
if not os.path.isfile(exe_path):
# Файл не найден — попросим пользователя выбрать путь к exe
msg = QMessageBox()
msg.setIcon(QMessageBox.Warning)
msg.setWindowTitle("Файл не найден")
msg.setText(f"Файл {exe_name} не найден в текущей папке.\nВыберите путь к {exe_name}.")
msg.exec_()
# Открываем диалог выбора файла
selected_path, _ = QFileDialog.getOpenFileName(
None, "Выберите файл " + exe_name, os.getcwd(), "Executable Files (*.exe)"
)
if not selected_path:
# Пользователь отменил выбор — ничего не делаем
return
exe_path = selected_path
# Запускаем exe (отдельное окно терминала)
subprocess.Popen([exe_path], creationflags=subprocess.CREATE_NEW_CONSOLE)
elif target == "modbus":
a = 1
def on_target_selected(self, target):
self.target_menu.setTitle(f'МК: {target}')
self.settings.setValue("mcu_choosen", target)
self.target = target.lower()
if self.target == "stm":
choose_type_map(True)
self.action_stm.setChecked(True)
self.action_tms.setChecked(False)
else:
choose_type_map(False)
self.action_tms.setChecked(True)
self.action_stm.setChecked(False)
def get_xml_path(self):
xml_path = self.xml_output_edit.text().strip()
return xml_path
def get_proj_path(self):
proj_path = self.proj_path_edit.text().strip()
return proj_path
def get_makefile_path(self):
proj_path = self.get_proj_path()
rel_makefile_path = self.makefile_edit.text().strip()
if not rel_makefile_path:
return None
makefile_path = myXML.make_absolute_path(rel_makefile_path, proj_path)
return makefile_path
def get_struct_path(self):
proj_path = self.get_proj_path()
xml_path = self.get_xml_path()
root, tree = myXML.safe_parse_xml(xml_path)
if root is None:
return
# --- structs_path из атрибута ---
structs_path = root.attrib.get('structs_path', '').strip()
structs_path_full = myXML.make_absolute_path(structs_path, proj_path)
if structs_path_full and os.path.isfile(structs_path_full):
structs_path = structs_path_full
else:
structs_path = None
return structs_path
def get_output_path(self):
output_path = os.path.abspath(self.source_output_edit.text().strip())
return output_path
def update_all_paths(self):
self.proj_path = self.get_proj_path()
self.xml_path = self.get_xml_path()
self.makefile_path = self.get_makefile_path()
self.structs_path = self.get_struct_path()
self.output_path = self.get_output_path()
def update_vars_data(self):
self.update_all_paths()
if not self.proj_path or not self.xml_path:
QMessageBox.warning(self, "Ошибка", "Укажите пути проекта и XML.")
return
if not os.path.isfile(self.makefile_path):
QMessageBox.warning(self, "Ошибка", f"Makefile не найден:\n{self.makefile_path}")
return
# Создаём окно с кнопкой "Готово"
self.proc_win = ProcessOutputWindow(self.proj_path, self.makefile_path, self.xml_path,
self.__after_scan_vars_finished, self)
self.proc_win.start_scan()
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': 't_' + iq_combo.currentText(),
'return_type': 't_' + ret_combo.currentText() if ret_combo.currentText() else 't_iq_none',
'short_name': short_name_edit.text(),
}
vars_out.append(var_data)
self.update_all_paths()
if not self.proj_path or not self.xml_path or not self.output_path:
QMessageBox.warning(self, "Ошибка", f"Заполните: {xml_path_title[:-1]}, {proj_path_title[:-1]}, {output_path_title[:-1]}.")
return
try:
run_generate(self.proj_path, self.xml_path, self.output_path, self.table._shortname_size)
QMessageBox.information(self, "Готово", "Файл debug_vars.c успешно сгенерирован.")
self.update()
except Exception as e:
QMessageBox.critical(self, "Ошибка при генерации", str(e))
def update(self, force=0):
if self._updating and (force==0):
return # Уже в процессе обновления — выходим, чтобы избежать рекурсии
self._updating = True
self.update_all_paths()
try:
if self.xml_path and not os.path.isfile(self.xml_path):
return
try:
root, tree = myXML.safe_parse_xml(self.xml_path)
if root is None:
return
if not self.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):
self.proj_path = proj_path_from_xml
self.proj_path_edit.setText(proj_path_from_xml)
else:
QMessageBox.warning(
self,
"Внимание",
"Путь к проекту не найден или не существует.\n"
f"Пожалуйста, укажите его вручную в поле '{proj_path_title[:-1]}'."
)
else:
if not os.path.isdir(self.proj_path):
QMessageBox.warning(
self,
"Внимание",
f"Указанный путь к проекту не существует:\n{self.proj_path}\n"
"Пожалуйста, исправьте путь в поле '{proj_path_title[:-1]}'."
)
if not self.makefile_path and self.proj_path and os.path.isdir(self.proj_path):
makefile_path = root.attrib.get('makefile_path', '').strip()
makefile_path_full = myXML.make_absolute_path(makefile_path, self.proj_path)
if os.path.isfile(makefile_path_full):
# Обновляем edit-поле на относительный путь, абсолют сохраняем
self.makefile_path = makefile_path_full
self.makefile_edit.setText(makefile_path)
else:
self.makefile_path = None
self.makefile_edit.setText("")
# --- structs_path из атрибута ---
structs_path = root.attrib.get('structs_path', '').strip()
structs_path_full = myXML.make_absolute_path(structs_path, self.proj_path)
if structs_path_full and os.path.isfile(structs_path_full):
self.structs_path = structs_path_full
self.structs, self.typedef_map = var_setup.parse_structs(structs_path_full)
else:
self.structs_path = None
self.vars_list = var_setup.parse_vars(self.xml_path, self.typedef_map)
self.table.populate(self.vars_list, self.structs, self.write_to_xml)
except Exception as e:
QMessageBox.warning(self, "Ошибка", f"Ошибка при чтении XML:\n{e}")
finally:
self._updating = False # Снимаем блокировку при выходе из функции
def __browse_proj_path(self):
dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку проекта")
if dir_path:
self.proj_path_edit.setText(dir_path)
self.proj_path = dir_path
# Сброс makefile, если proj_path изменился
if not os.path.isdir(dir_path):
self.makefile_path = None
self.makefile_edit.setText("")
else:
if self.makefile_path and os.path.isfile(self.makefile_path):
rel_path = myXML.make_relative_path(self.makefile_path, dir_path)
self.makefile_edit.setText(rel_path)
self.update()
self.update()
if self.makefile_path and self.proj_path:
path = myXML.make_relative_path(self.makefile_path, self.proj_path)
self.makefile_edit.setText(path)
self.makefile_path = path
def __browse_xml_output(self):
dialog = QFileDialog(self, "Выберите или создайте XML-файл")
dialog.setAcceptMode(QFileDialog.AcceptSave)
dialog.setNameFilter("XML files (*.xml);;All Files (*)")
dialog.setDefaultSuffix("xml")
dialog.setOption(QFileDialog.DontConfirmOverwrite, True) # ⚠️ Не спрашивать про перезапись
if dialog.exec_():
file_path = dialog.selectedFiles()[0]
if not file_path.endswith(".xml"):
file_path += ".xml"
self.xml_output_edit.setText(file_path)
self.xml_path = file_path
def keyPressEvent(self, event: QKeyEvent):
if event.key() == Qt.Key_Delete:
self.delete_selected_rows()
else:
super().keyPressEvent(event)
def __browse_makefile(self):
if self.target == 'stm':
file_filter = "Makefile или Keil-проект (*.uvprojx *.uvproj makefile);;Все файлы (*)"
dialog_title = "Выберите Makefile или Keil-проект"
else: # 'TMS' или по умолчанию
file_filter = "Makefile (makefile);;Все файлы (*)"
dialog_title = "Выберите Makefile"
file_path, _ = QFileDialog.getOpenFileName(
self,
dialog_title,
filter=file_filter
)
if file_path:
if self.proj_path:
path = myXML.make_relative_path(file_path, self.proj_path)
else:
path = file_path
self.makefile_edit.setText(path)
self.makefile_path = path
def __browse_source_output(self):
dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку для debug_vars.c")
if dir_path:
self.source_output_edit.setText(dir_path)
self.output_path = dir_path
else:
self.output_path = ''
def __on_xml_path_changed(self, _):
self.xml_path = self.get_xml_path()
self.update()
def __on_proj_path_changed(self, _):
self.proj_path = self.get_proj_path()
if not os.path.isdir(self.proj_path):
self.makefile_path = None
self.makefile_edit.setText("")
return # Преждевременно выходим, если проект не существует
# Обновим путь к makefile, если он уже задан и абсолютен
if self.makefile_path and os.path.isfile(self.makefile_path):
rel_path = myXML.make_relative_path(self.makefile_path, self.proj_path)
self.makefile_edit.setText(rel_path)
self.update()
def __on_makefile_path_changed(self, _):
self.makefile_path = self.get_makefile_path()
if self.makefile_path and self.proj_path:
path = myXML.make_relative_path(self.makefile_path, self.proj_path)
self.makefile_edit.setText(path)
self.update()
def __after_scan_vars_finished(self):
if not os.path.isfile(self.xml_path):
self.makefile_path = None
self.structs_path = None
self.proj_path = None
QMessageBox.critical(self, "Ошибка", f"Файл не найден: {self.xml_path}")
return
try:
self.update(1)
except Exception as e:
self.makefile_path = None
self.structs_path = None
self.proj_path = None
QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить переменные:\n{e}")
def delete_selected_rows(self):
# Получаем имена всех выбранных переменных из первого столбца
selected_names = self.table.get_selected_var_names()
if not selected_names:
return
# Меняем флаг show_var по имени
for var in self.vars_list:
if var.get('name') in selected_names:
var['show_var'] = 'false'
self.table.populate(self.vars_list, self.structs, self.write_to_xml)
self.write_to_xml()
def __open_variable_selector(self):
self.update()
if not self.vars_list:
QMessageBox.warning(self, "Нет переменных", f"Сначала загрузите переменные ({scan_title}).")
return
dlg = VariableSelectorDialog(self.table, self.vars_list, self.structs, self.typedef_map, self.xml_path, self)
if dlg.exec_():
self.write_to_xml()
self.update()
def write_to_xml(self, dummy=None):
t0 = time.time()
self.update_all_paths()
def get_val(name, default=''):
return str(v_table[name] if v_table and name in v_table else v.get(name, default))
def element_differs(elem, values: dict):
for tag, new_val in values.items():
current_elem = elem.find(tag)
current_val = (current_elem.text or '').strip()
new_val_str = str(new_val or '').strip()
if current_val != new_val_str:
return True
return False
if not self.xml_path or not os.path.isfile(self.xml_path):
print("XML файл не найден или путь пустой")
return
if not self.proj_path or not os.path.isdir(self.proj_path):
print("Project path не найден или путь пустой")
return
if not self.makefile_path or not os.path.isfile(self.makefile_path):
print("makefile файл не найден или путь пустой")
return
if os.path.abspath(self.makefile_path) == os.path.abspath(self.proj_path):
print("makefile_path совпадает с proj_path — игнор")
return
try:
root, tree = myXML.safe_parse_xml(self.xml_path)
if root is None:
return
root.set("proj_path", self.proj_path.replace("\\", "/"))
if self.makefile_path and os.path.isfile(self.makefile_path):
rel_makefile = myXML.make_relative_path(self.makefile_path, self.proj_path)
# Если результат — абсолютный путь, не записываем
if not os.path.isabs(rel_makefile):
root.set("makefile_path", rel_makefile)
if self.structs_path and os.path.isfile(self.structs_path):
rel_struct = myXML.make_relative_path(self.structs_path, self.proj_path)
root.set("structs_path", rel_struct)
if not os.path.isabs(rel_struct):
root.set("structs_path", rel_struct)
t1 = time.time()
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] = {
'file': var_elem.findtext('file', ''),
'extern': var_elem.findtext('extern', ''),
'static': var_elem.findtext('static', '')
}
var_elements_by_name = {ve.attrib.get('name'): ve for ve in vars_elem.findall('var')}
# Читаем переменные из таблицы (активные/изменённые)
table_vars = {v['name']: v for v in self.table.read_data()}
# Все переменные (в том числе новые, которых нет в таблице)
all_vars_by_name = {v['name']: v for v in self.vars_list}
# Объединённый список переменных для записи
all_names = list(all_vars_by_name.keys())
t2 = time.time()
for name in all_names:
v = all_vars_by_name[name]
v_table = table_vars.get(name)
var_elem = None
pt_type_val = get_val('pt_type').lower()
if 'arr' in pt_type_val or 'struct' in pt_type_val or 'union' in pt_type_val:
continue
show_var_val = str(v.get('show_var', 'false')).lower()
enable_val = get_val('enable').lower()
# Тут подтягиваем из таблицы, если есть, иначе из v
shortname_val = get_val('shortname')
iq_type_val = get_val('iq_type')
ret_type_val = get_val('return_type')
# file/extern/static: из original_info, либо из v
file_val = v.get('file') or original_info.get(name, {}).get('file', '')
extern_val = v.get('extern') or original_info.get(name, {}).get('extern', '')
static_val = v.get('static') or original_info.get(name, {}).get('static', '')
values_to_write = {
'show_var': show_var_val,
'enable': enable_val,
'shortname': shortname_val,
'pt_type': pt_type_val,
'iq_type': iq_type_val,
'return_type': ret_type_val,
'type': v.get('type', ''),
'file': file_val,
'extern': extern_val,
'static': static_val
}
# Ищем уже существующий <var> в XML
var_elem = var_elements_by_name.get(name)
# Если элемента нет, это новая переменная — сразу пишем
if var_elem is None:
var_elem = ET.SubElement(vars_elem, 'var', {'name': name})
var_elements_by_name[name] = var_elem
write_all = True # обязательно записать все поля
else:
write_all = element_differs(var_elem, values_to_write)
if not write_all:
continue # Пропускаем, если нет изменений
for tag, text in values_to_write.items():
set_sub_elem_text(var_elem, tag, text)
t3 = time.time()
# Преобразуем дерево в строку
myXML.fwrite(root, self.xml_path)
self.table.check()
t4 = time.time()
'''print(f"[T1] parse + set paths: {t1 - t0:.3f} сек")
print(f"[T2] prepare variables: {t2 - t1:.3f} сек")
print(f"[T3] loop + updates: {t3 - t2:.3f} сек")
print(f"[T4] write to file: {t4 - t3:.3f} сек")
print(f"[TOTAL] write_to_xml total: {t4 - t0:.3f} сек")'''
except Exception as e:
print(f"Ошибка при сохранении XML: {e}")
def __open_output_file_with_program(self):
output_path = self.get_output_path()
if not output_path:
QMessageBox.warning(self,
"Ошибка",
"Путь к debug_var.c не задан."
f"Пожалуйста, укажите его в поле '{output_path_title[:-1]}'.")
return
output_file = os.path.join(output_path, "debug_vars.c")
if not os.path.isfile(output_file):
QMessageBox.warning(self, "Ошибка", f"Файл не найден:\n{output_file}")
return
try:
# Открыть стандартное окно Windows "Открыть с помощью..."
subprocess.run([
"rundll32.exe",
"shell32.dll,OpenAs_RunDLL",
output_file
], shell=False)
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Не удалось открыть диалог 'Открыть с помощью':\n{e}")
if __name__ == "__main__":
app = QApplication(sys.argv)
auto_updater.check_and_update(
current_version="1.2.1",
git_releases_url="https://git.arktika.cyou/Razvalyaev/debugVarTool/releases",
exe_name="DebugVarEdit.exe",
zip_name="DebugToolsRelease.rar",
parent_widget=None
)
editor = VarEditor()
editor.resize(900, 600)
editor.show()
sys.exit(app.exec_())

62
Src/README_DEVELOP.md Normal file
View File

@@ -0,0 +1,62 @@
# Для разработчиков
### Структура проекта:
```bash
Src
├── build/
│ └── build_and_clean.py # Билдинг проекта в .exe (через nuitka или pyinstaller)
├── DebugVarEdit_GUI.py # Главное окно
├── tms_debugvar_term.py # Терминал DebugVarTerminal
├── tms_debugvar_lowlevel.py # Виджет для выбора переменных для LowLevel Watch
├── var_table.py # Таблица выбранных переменных
├── var_selector_window.py # Окно выбора переменных
├── var_selector_table.py # Таблица переменных в окне выбора переменных
├── scan_progress_gui.py # Отображение процесса сканирования переменных
├── scan_vars.py # Сканирование переменных среди .c/.h файлов
├── generate_debug_vars.py # Генерация debug_vars.c
├── allvars_xml_parser.py # Парсинг XML со всеми переменными и структурами
├── csv_logger.py # Логирование переменных в CSV
├── myXML.py # Утилиты для XML
├── makefile_parser.py # Парсинг makefile на .c/.h файлы
├── var_setup.py # Подготовка переменных для окна выбора переменных
├── path_hints.py # Подсказки для автодополнения путей переменных
├── libclang.dll # Бибилиотека clang
├── icon.ico # Иконка
```
### Зависимости
Для запуска приложения:
- **Python 3.7+**
- **clang** — используется для парсинга C-кода (требуется `libclang.dll`, лежит в папке Src)
- **PySide2** — GUI-фреймворк
- **lxml** — работа с XML
> Python 3.7 и PySide2 рекомендуется для совместимости с Windows 7
Для сборки `.exe`:
- **Nuitka** — создает полностью автономный `.exe` без внешних зависимостей
- **PyInstaller** — создает `.exe` с зависимостью от `pythonXX.dll` (Python должен быть установлен для запуска `.exe`)
### Сборка:
Если вы хотите собрать `DebugVarEdit.exe` самостоятельно из исходников, используйте скрипт **build/build_and_clean.py**. Он автоматически собирает проект с помощью Nuitka или PyInstaller:
- Собирает проект в `DebugVarEdit.exe` в корневой папке.
- Включает:
- все необходимые `.dll` (например, `libclang.dll`),
- иконку (`icon.ico`),
- плагины PySide2.
- Очищает временные папки после сборки:
- `build_temp`
- `__pycache__`
- `<MAIN_SCRIPT_NAME>.*`
> Все пути, имена файлов, временные папки и выбор между Nuitka и PyInstaller можно настроить в начале файла `build_and_clean.py`.
### Установка зависимостей
```bash
pip install clang PySide2 lxml nuitka pyinstaller
```

View File

@@ -1,279 +0,0 @@
import re
from PySide6.QtWidgets import (
QDialog, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QPushButton,
QLineEdit, QLabel, QHeaderView
)
from PySide6.QtCore import Qt
from setupVars import *
from scanVars import *
array_re = re.compile(r'^(\w+)\[(\d+)\]$')
class VariableSelectorDialog(QDialog):
def __init__(self, all_vars, structs, typedefs, xml_path=None, parent=None):
super().__init__(parent)
self.setWindowTitle("Выбор переменных")
self.resize(600, 500)
self.selected_names = []
self.all_vars = all_vars
self.structs = structs
self.typedefs = typedefs
self.expanded_vars = []
self.var_map = {v['name']: v for v in all_vars}
self.xml_path = xml_path # сохраняем путь к xml
self.search_input = QLineEdit()
self.search_input.setPlaceholderText("Поиск по имени переменной...")
self.search_input.textChanged.connect(self.filter_tree)
self.tree = QTreeWidget()
self.tree.setHeaderLabels(["Имя переменной", "Тип"])
self.tree.setSelectionMode(QTreeWidget.ExtendedSelection)
self.tree.setRootIsDecorated(True)
self.tree.setUniformRowHeights(True)
self.tree.setStyleSheet("""
QTreeWidget::item:selected {
background-color: #87CEFA;
color: black;
}
QTreeWidget::item:hover {
background-color: #D3D3D3;
}
""")
self.btn_add = QPushButton("Добавить выбранные")
self.btn_add.clicked.connect(self.on_add_clicked)
self.btn_delete = QPushButton("Удалить выбранные")
self.btn_delete.clicked.connect(self.on_delete_clicked)
layout = QVBoxLayout()
layout.addWidget(QLabel("Поиск:"))
layout.addWidget(self.search_input)
layout.addWidget(self.tree)
layout.addWidget(self.btn_add)
layout.addWidget(self.btn_delete) # Кнопка удаления
self.setLayout(layout)
self.populate_tree()
def add_tree_item_recursively(self, parent, var):
"""
Рекурсивно добавляет переменную и её дочерние поля в дерево.
Если parent == None, добавляет на верхний уровень.
"""
name = var['name']
type_str = var.get('type', '')
show_var = var.get('show_var', 'false') == 'true'
item = QTreeWidgetItem([name, type_str])
item.setData(0, Qt.UserRole, name)
# Делаем bitfield-поля неактивными
if "(bitfield:" in type_str:
item.setDisabled(True)
self.set_tool(item, "Битовые поля недоступны для выбора")
for i, attr in enumerate(['file', 'extern', 'static']):
item.setData(0, Qt.UserRole + 1 + i, var.get(attr))
if show_var:
item.setForeground(0, Qt.gray)
item.setForeground(1, Qt.gray)
self.set_tool(item, "Уже добавлена")
if parent is None:
self.tree.addTopLevelItem(item)
else:
parent.addChild(item)
for child in var.get('children', []):
self.add_tree_item_recursively(item, child)
def populate_tree(self):
self.tree.clear()
expanded_vars = expand_vars(self.all_vars, self.structs, self.typedefs)
for var in expanded_vars:
self.add_tree_item_recursively(None, var)
header = self.tree.header()
header.setSectionResizeMode(QHeaderView.Interactive) # вручную можно менять
self.tree.setColumnWidth(0, 400)
self.tree.resizeColumnToContents(1)
""" header.setSectionResizeMode(0, QHeaderView.Stretch)
header.setSectionResizeMode(1, QHeaderView.ResizeToContents) """
def filter_tree(self):
text = self.search_input.text().strip().lower()
path_parts = text.split('.') if text else []
def hide_all(item):
item.setHidden(True)
for i in range(item.childCount()):
hide_all(item.child(i))
def path_matches_search(name, search_parts):
name_parts = name.lower().split('.')
if len(name_parts) < len(search_parts):
return False
for sp, np in zip(search_parts, name_parts):
if not np.startswith(sp):
return False
return True
def show_matching_path(item, level=0):
name = item.text(0).lower()
# Проверяем соответствие до длины path_parts
if not path_parts:
matched = True
else:
matched = False
# Проверяем совпадение по пути
if path_matches_search(name, path_parts[:level+1]):
matched = True
item.setHidden(not matched)
# Раскрываем, если это не последний уровень поиска
if matched and level < len(path_parts) - 1:
item.setExpanded(True)
else:
item.setExpanded(False)
matched_any_child = False
for i in range(item.childCount()):
child = item.child(i)
if show_matching_path(child, level + 1):
matched_any_child = True
return matched or matched_any_child
for i in range(self.tree.topLevelItemCount()):
item = self.tree.topLevelItem(i)
hide_all(item)
show_matching_path(item, 0)
def on_add_clicked(self):
self.selected_names = []
for item in self.tree.selectedItems():
name = item.text(0) # имя переменной (в колонке 1)
type_str = item.text(1) # тип переменной (в колонке 2)
if not name:
continue
self.selected_names.append((name, type_str))
if name in self.var_map:
# Если переменная уже есть, просто включаем её и показываем
var = self.var_map[name]
var['show_var'] = 'true'
var['enable'] = 'true'
else:
# Создаём новый элемент переменной
# Получаем родительские параметры
file_val = item.data(0, Qt.UserRole + 1)
extern_val = item.data(0, Qt.UserRole + 2)
static_val = item.data(0, Qt.UserRole + 3)
new_var = {
'name': name,
'type': type_str,
'show_var': 'true',
'enable': 'true',
'shortname': name,
'pt_type': '',
'iq_type': '',
'return_type': 'iq_none',
'file': file_val,
'extern': str(extern_val).lower() if extern_val else 'false',
'static': str(static_val).lower() if static_val else 'false',
}
# Добавляем в список переменных
self.all_vars.append(new_var)
self.var_map[name] = new_var # Чтобы в будущем не добавлялось повторно
self.accept()
def on_delete_clicked(self):
# Деактивируем (удаляем из видимых) выбранные переменные
for item in self.tree.selectedItems():
name = item.text(0)
if not name:
continue
if name in self.var_map:
var = self.var_map[name]
var['show_var'] = 'false'
var['enable'] = 'false'
self.accept()
def set_tool(self, item, text):
item.setToolTip(0, text)
item.setToolTip(1, text)
def keyPressEvent(self, event):
if event.key() == Qt.Key_Delete:
self.delete_selected_vars()
else:
super().keyPressEvent(event)
def delete_selected_vars(self):
# Деактивируем (удаляем из видимых) выбранные переменные
for item in self.tree.selectedItems():
name = item.text(0)
if not name:
continue
if name in self.var_map:
var = self.var_map[name]
var['show_var'] = 'false'
var['enable'] = 'false'
if not hasattr(self, 'xml_path') or not self.xml_path:
from PySide6.QtWidgets import QMessageBox
QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно удалить переменные.")
return
import xml.etree.ElementTree as ET
tree = ET.parse(self.xml_path)
root = tree.getroot()
if root is None:
return
vars_section = root.find('variables')
if vars_section is None:
return # Нет секции variables — ничего удалять
selected_names = [item.text(0) for item in self.tree.selectedItems() if item.text(0)]
removed_any = False
for var_elem in vars_section.findall('var'):
name = var_elem.attrib.get('name')
if name in selected_names:
vars_section.remove(var_elem)
removed_any = True
if name in self.var_map:
del self.var_map[name]
# Удаляем элементы из списка на месте
self.all_vars[:] = [v for v in self.all_vars if v['name'] != name]
if removed_any:
ET.indent(tree, space=" ", level=0)
tree.write(self.xml_path, encoding='utf-8', xml_declaration=True)
self.populate_tree()

500
Src/allvars_xml_parser.py Normal file
View File

@@ -0,0 +1,500 @@
"""
VariablesXML + get_all_vars_data
---------------------------------
Поддержка вложенных структур, указателей на структуры ("->"),
и многомерных массивов (индексация [i][j]...).
Требования пользователя:
- size (без индекса) = общий размер массива в байтах (НЕ измерение!).
- size1..sizeN = размеры измерений массива.
- В результирующем плоском списке (flattened) должны присутствовать ВСЕ промежуточные
пути: var, var[0], var[0][0], var[0][0].field, var[0][0].field->subfield, ...
- Аналогично для членов структур.
Пример желаемого формата:
project
project.adc
project.adc[0]
project.adc[0][0]
project.adc[0][0].bus
project.adc[0][0].bus->status
Данный модуль реализует:
- Разбор XML (parse) с извлечением размеров размерностей в поле `dims`.
- Генерацию плоского списка словарей `flattened()`.
- Построение иерархии словарей `get_all_vars_data()`.
"""
from __future__ import annotations
import re
import xml.etree.ElementTree as ET
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple, Any
import var_setup # ожидается split_path(...)
from generate_debug_vars import choose_type_map, type_map # используется для выбора карт типов
# --------------------------- константы ----------------------------
DATE_FIELD_SET = {"year", "month", "day", "hour", "minute"}
# --------------------------- dataclasses --------------------------
@dataclass
class MemberNode:
name: str
offset: int = 0
type_str: str = ""
size: Optional[int] = None # общий размер (байты), если известен
children: List["MemberNode"] = field(default_factory=list)
# --- доп.поля ---
kind: Optional[str] = None # 'array', 'union', ...
dims: Optional[List[int]] = None
def is_date_struct(self) -> bool:
if not self.children:
return False
child_names = {c.name for c in self.children}
return DATE_FIELD_SET.issubset(child_names)
@dataclass
class VariableNode:
name: str
address: int
type_str: str
size: Optional[int]
members: List[MemberNode] = field(default_factory=list)
# --- доп.поля ---
kind: Optional[str] = None # 'array'
dims: Optional[List[int]] = None # полный список размеров [size1, size2, ...]
def base_address_hex(self) -> str:
return f"0x{self.address:06X}"
# --------------------------- класс парсера -----------------------
class VariablesXML:
"""
Читает XML и предоставляет методы:
- flattened(): плоский список всех путей.
- date_struct_candidates(): как раньше.
Правила формирования путей:
* Структурные поля: '.'
* Поля через указатель на структуру: '->'
* Массивы: [index] (каждое измерение).
"""
# предполагаемые размеры примитивов (MCU: int=2)
_PRIM_SIZE = {
'char': 1, 'signed char': 1, 'unsigned char': 1,
'uint8_t': 1, 'int8_t': 1,
'short': 2, 'short int': 2, 'signed short': 2, 'unsigned short': 2,
'uint16_t': 2, 'int16_t': 2,
'int': 2, 'signed int': 2, 'unsigned int': 2,
'long': 4, 'unsigned long': 4, 'int32_t': 4, 'uint32_t': 4,
'float': 4,
'long long': 8, 'unsigned long long': 8, 'int64_t': 8, 'uint64_t': 8, 'double': 8,
}
def __init__(self, path: str):
self.path = path
self.timestamp: str = ""
self.variables: List[VariableNode] = []
choose_type_map(0) # инициализация карт типов (если требуется)
self._parse()
# ------------------ утилиты ------------------
@staticmethod
def _parse_int_guess(txt: Optional[str]) -> Optional[int]:
if not txt:
return None
txt = txt.strip()
if txt.startswith(('0x', '0X')):
return int(txt, 16)
# если в строке есть A-F, попробуем hex
if any(c in 'abcdefABCDEF' for c in txt):
try:
return int(txt, 16)
except ValueError:
pass
try:
return int(txt, 10)
except ValueError:
return None
@staticmethod
def _is_pointer_to_struct(t: str) -> bool:
if not t:
return False
low = t.replace('\t', ' ').replace('\n', ' ')
return 'struct ' in low and '*' in low
@staticmethod
def _is_struct_or_union(t: str) -> bool:
if not t:
return False
low = t.strip()
return low.startswith('struct ') or low.startswith('union ')
@staticmethod
def _is_union(t: str) -> bool:
if not t:
return False
low = t.strip()
return low.startswith('union ')
@staticmethod
def _strip_array_suffix(t: str) -> str:
t = t.strip()
while t.endswith('[]'):
t = t[:-2].strip()
return t
def _guess_primitive_size(self, type_str: str) -> Optional[int]:
if not type_str:
return None
base = type_str
for tok in ('volatile', 'const'):
base = base.replace(tok, '')
base = base.replace('*', ' ')
base = base.replace('[', ' ').replace(']', ' ')
base = ' '.join(base.split()).strip()
return self._PRIM_SIZE.get(base)
# ------------------ XML read ------------------
def _parse(self) -> None:
try:
tree = ET.parse(self.path)
root = tree.getroot()
ts = root.find('timestamp')
self.timestamp = ts.text.strip() if ts is not None and ts.text else ''
def parse_member(elem: ET.Element, base_offset=0) -> MemberNode:
name = elem.get('name', '')
offset = int(elem.get('offset', '0'), 16) if elem.get('offset') else 0
t = elem.get('type', '') or ''
size_attr = elem.get('size')
size = int(size_attr, 16) if size_attr else None
kind = elem.get('kind')
abs_offset = base_offset + offset
# Собираем размеры, если есть
dims: List[int] = []
i = 1
while True:
size_key = f"size{i}"
size_val = elem.get(size_key)
if size_val is None:
break
parsed = self._parse_int_guess(size_val) # предполагается твоя функция парсинга int
if parsed is not None:
dims.append(parsed)
i += 1
node = MemberNode(
name=name,
offset=abs_offset,
type_str=t,
size=size,
kind=kind,
dims=dims if dims else None,
)
# Для детей
for ch in elem.findall('member'):
if kind == 'union':
# Для union детей НЕ добавляем их offset, просто передаём abs_offset
child = parse_member(ch, base_offset=abs_offset)
child.offset = abs_offset # выравниваем offset, игнорируем offset детей
else:
# Для struct/array суммируем offset нормально
child = parse_member(ch, base_offset=abs_offset)
node.children.append(child)
# Аналогично для pointee
pointee_elem = elem.find('pointee')
if pointee_elem is not None:
for ch in pointee_elem.findall('member'):
if kind == 'union':
child = parse_member(ch, base_offset=abs_offset)
child.offset = abs_offset
else:
child = parse_member(ch, base_offset=abs_offset)
node.children.append(child)
size_p = pointee_elem.get('size')
if size_p:
node.size = int(size_p, 16)
return node
for var in root.findall('variable'):
addr = int(var.get('address', '0'), 16)
name = var.get('name', '')
t = var.get('type', '') or ''
size_attr = var.get('size') # общий размер байт
size = int(size_attr, 16) if size_attr else None
kind = var.get('kind')
dims: List[int] = []
i = 1
while True:
key = f'size{i}'
val = var.get(key)
if val is None:
break
parsed = self._parse_int_guess(val)
if parsed is not None:
dims.append(parsed)
i += 1
members = [parse_member(m) for m in var.findall('member')]
v = VariableNode(
name=name,
address=addr,
type_str=t,
size=size,
members=members,
kind=kind,
dims=dims if dims else None,
)
self.variables.append(v)
except FileNotFoundError:
self.variables = []
except ET.ParseError:
self.variables = []
# ------------------ helpers для flattened ---------------------
def _elem_size_bytes(self, total_size: Optional[int], dims: List[int], base_type: str, members: List[MemberNode]) -> int:
"""Оценка размера одного *листового* элемента (последнего измерения).
Если total_size и dims все известны — берём size / prod(dims).
Иначе — пробуем примитивный размер; иначе 1.
(Не учитываем выравнивание структур; при необходимости можно расширить.)
"""
if total_size is not None and dims:
prod = 1
for d in dims:
if d is None or d == 0:
prod = None
break
prod *= d
if prod and prod > 0:
return max(1, total_size // prod)
prim = self._guess_primitive_size(base_type)
if prim:
return prim
# Если структура и у неё есть size по детям? Пока fallback=1.
return 1
# ------------------ flattened ------------------
def flattened(self, max_array_elems: Optional[int] = None) -> List[Dict[str, Any]]:
"""Возвращает плоский список всех путей (каждый путь = dict).
Включает промежуточные узлы массивов (var[0], var[0][0], ...).
"""
out: List[Dict[str, Any]] = []
def mk(name: str, addr: Optional[int], type_str: str, size: Optional[int], kind: Optional[str], dims_for_node: Optional[List[int]]):
if 'Bender' in name:
a=1
out.append({
'name': name,
'address': addr,
'type': type_str,
'size': size,
'kind': kind,
'dims': dims_for_node[:] if dims_for_node else None,
})
def expand_members(prefix: str, base_addr: int, members: List[MemberNode], parent_is_ptr_struct: bool, parent_is_union: bool) -> None:
# Выбираем разделитель пути: '.' если обычный член, '->' если указатель на структуру
join = '->' if parent_is_ptr_struct else '.'
for m in members:
path_m = f"{prefix}{join}{m.name}" if prefix else m.name
is_union = m.kind == 'union' or parent_is_union
if is_union:
# Все поля union начинаются с одного адреса
addr_m = base_addr
else:
addr_m = base_addr + m.offset
dims = m.dims or []
mk(path_m, addr_m, m.type_str, m.size, m.kind, dims)
if m.kind == 'array' and dims:
base_t = self._strip_array_suffix(m.type_str)
elem_sz = m.size
# Для массива внутри структуры: первый уровень — '.' для доступа,
# внутри массива раскрываем по обычной логике с parent_is_ptr_struct=False
expand_dims(path_m, addr_m, dims, base_t, m.children, elem_sz, parent_is_ptr_struct=False)
else:
if m.children:
# Проверяем, является ли поле указателем на структуру
is_ptr = self._is_pointer_to_struct(m.type_str)
# Рекурсивно раскрываем дочерние поля, выбирая правильный разделитель
expand_members(path_m, addr_m, m.children, is_ptr, is_union)
def expand_dims(name: str, base_addr: int, dims: List[int], base_type: str, children: List[MemberNode], elem_size: int, parent_is_ptr_struct: bool) -> None:
prods: List[int] = []
acc = 1
for d in reversed(dims[1:]):
acc *= (d if d else 1)
prods.append(acc)
prods.reverse()
def rec(k: int, cur_name: str, cur_addr: int) -> None:
if k == len(dims):
# Листовой элемент массива
mk(cur_name, cur_addr, base_type, elem_size, None, None)
# Если элемент — структура или указатель на структуру, раскрываем вложения
if children and self._is_struct_or_union(base_type):
expand_members(cur_name, cur_addr, children, parent_is_ptr_struct=False, parent_is_union=self._is_union(base_type))
elif self._is_pointer_to_struct(base_type):
expand_members(cur_name, cur_addr, children, parent_is_ptr_struct=True, parent_is_union=self._is_union(base_type))
return
dim_sz = dims[k] or 0
if max_array_elems is not None:
dim_sz = min(dim_sz, max_array_elems)
stride = elem_size * prods[k] if k < len(prods) else elem_size
if len(dims) > 2:
a=1
for i in range(dim_sz):
child_name = f"{cur_name}[{i}]"
child_addr = (cur_addr + i * stride) if cur_addr is not None else None
remaining = dims[k+1:]
mk(child_name, child_addr, base_type + '[]' * len(remaining), stride if remaining else elem_size, 'array' if remaining else None, remaining)
rec(k + 1, child_name, child_addr)
rec(0, name, base_addr)
# --- цикл по топ‑левел переменным ---
for v in self.variables:
dims = v.dims or []
mk(v.name, v.address, v.type_str, v.size, v.kind, dims)
if (v.kind == 'array' or v.type_str.endswith('[]')) and dims:
base_t = self._strip_array_suffix(v.type_str)
elem_sz = v.size
expand_dims(v.name, v.address, dims, base_t, v.members, elem_sz, parent_is_ptr_struct=False)
else:
if v.members:
is_ptr = self._is_pointer_to_struct(v.type_str)
is_union = self._is_union(v.type_str)
expand_members(v.name, v.address, v.members, is_ptr, is_union)
return out
# -------------------- date candidates (как раньше) -------------
def date_struct_candidates(self) -> List[Tuple[str, int]]:
cands = []
for v in self.variables:
# top level (if all date fields are present)
direct_names = {mm.name for mm in v.members}
if DATE_FIELD_SET.issubset(direct_names):
cands.append((v.name, v.address))
# check first-level members
for m in v.members:
if m.is_date_struct():
cands.append((f"{v.name}.{m.name}", v.address + m.offset))
return cands
# ------------------------------------------------------------------
# Построение иерархического дерева из flattened()
# ------------------------------------------------------------------
def get_all_vars_data(self) -> List[Dict[str, Any]]:
"""
Строит иерархию словарей из плоского списка переменных.
Каждый узел = {
'name': <полный путь>,
'address': <адрес или None>,
'type': <тип>,
'size': <байты>,
'kind': <'array' | ...>,
'dims': [size1, size2, ...] или None,
'children': [...список дочерних узлов]
}
Возвращает список корневых узлов (top-level переменных).
"""
flat_data = self.flattened(max_array_elems=None)
# Быстрое отображение имя -> узел (словарь с детьми)
all_nodes: Dict[str, Dict[str, Any]] = {}
for item in flat_data:
node = dict(item)
node['children'] = []
all_nodes[item['name']] = node
def _parent_struct_split(path: str) -> Optional[str]:
# Ищем последний '.' или '->' для определения родителя
dot_idx = path.rfind('.')
arrow_idx = path.rfind('->')
cut_idx = max(dot_idx, arrow_idx)
if cut_idx == -1:
return None
# '->' занимает 2 символа, нужно взять срез до начала '->'
if arrow_idx > dot_idx:
return path[:arrow_idx]
else:
return path[:dot_idx]
def find_parent(path: str) -> Optional[str]:
"""
Возвращает полный путь родителя, учитывая '.', '->' и индексы [] в конце.
Если путь заканчивается индексом [k], удаляет последний индекс и проверяет наличие родителя.
Иначе пытается найти последний сепаратор '.' или '->'.
"""
# Если есть trailing индекс в конце, убираем его
m = re.search(r'\[[0-9]+\]$', path)
if m:
base = path[:m.start()] # убираем последний [k]
# Если базовый путь есть в узлах, считаем его родителем
if base in all_nodes:
return base
# Иначе пытаемся найти родителя от базового пути
return _parent_struct_split(base)
else:
# Если нет индекса, просто ищем последний разделитель
return _parent_struct_split(path)
# Строим иерархию: parent -> children
roots: List[Dict[str, Any]] = []
for full_name, node in all_nodes.items():
parent_name = find_parent(full_name)
if parent_name and parent_name in all_nodes:
all_nodes[parent_name]['children'].append(node)
else:
roots.append(node)
# Рекурсивно сортируем детей по имени для порядка
def sort_nodes(nodes: List[Dict[str, Any]]):
nodes.sort(key=lambda n: n['name'])
for n in nodes:
if n['children']:
sort_nodes(n['children'])
sort_nodes(roots)
return roots

239
Src/auto_updater.py Normal file
View File

@@ -0,0 +1,239 @@
import os
import sys
import shutil
import tempfile
import requests
import subprocess
from packaging.version import Version, InvalidVersion
from bs4 import BeautifulSoup
from PySide2.QtWidgets import (
QApplication, QMessageBox, QProgressDialog
)
from PySide2.QtCore import QThread, Signal
class DownloadThread(QThread):
progress = Signal(int)
finished = Signal(bool, str)
def __init__(self, url, dest_path):
super().__init__()
self.url = url
self.dest_path = dest_path
def run(self):
try:
with requests.get(self.url, stream=True) as r:
r.raise_for_status()
total = int(r.headers.get("content-length", 0))
downloaded = 0
with open(self.dest_path, 'wb') as f:
for chunk in r.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
downloaded += len(chunk)
if total > 0:
self.progress.emit(int(downloaded * 100 / total))
self.finished.emit(True, "")
except Exception as e:
self.finished.emit(False, str(e))
def check_and_update(
current_version: str,
git_releases_url: str,
exe_name: str,
zip_name: str = None,
parent_widget=None
):
print(f"[Updater] Текущая версия: {current_version}")
latest_ver, rel_page = _get_latest_release_info(git_releases_url)
if not latest_ver:
return
try:
current_ver_obj = Version(current_version)
except InvalidVersion:
return
if latest_ver <= current_ver_obj:
print(f"[Updater] Приложение актуально (v{current_version}).")
return
if not _ask_update_dialog(latest_ver, parent_widget):
return
file_url = _get_download_link(rel_page, exe_name, zip_name)
if not file_url:
_show_error("Не удалось найти файл для скачивания.", parent_widget)
return
temp_exe = os.path.join(tempfile.gettempdir(), f"new_{exe_name}")
if file_url.endswith(".zip") or file_url.endswith(".rar"):
# Выбираем расширение временного файла согласно скачиваемому файлу
ext = ".zip" if file_url.endswith(".zip") else ".rar"
temp_archive = temp_exe.replace(".exe", ext)
'''_start_download_gui(file_url, temp_archive, parent_widget, on_finished=lambda ok, err:
_handle_archive_download(ok, err, temp_archive, exe_name, temp_exe, parent_widget))'''
else:
_start_download_gui(file_url, temp_exe, parent_widget, on_finished=lambda ok, err:
_handle_exe_download(ok, err, temp_exe, parent_widget))
'''def _handle_archive_download(success, error, archive_path, exe_name, exe_dest, parent):
if not success:
_show_error(f"Ошибка при скачивании архива: {error}", parent)
return
ok = False
if archive_path.endswith(".zip"):
ok = _extract_exe_from_zip(archive_path, exe_name, exe_dest)
elif archive_path.endswith(".rar"):
ok = _extract_exe_from_rar(archive_path, exe_name, exe_dest)
if not ok:
_show_error(f"Не удалось извлечь {exe_name} из архива.", parent)
return
_update_self(exe_dest)'''
'''def _extract_exe_from_rar(rar_path, exe_name, dest_path):
try:
with rarfile.RarFile(rar_path) as rar_ref:
for file in rar_ref.namelist():
if file.endswith(exe_name):
rar_ref.extract(file, os.path.dirname(dest_path))
src = os.path.join(os.path.dirname(dest_path), file)
shutil.move(src, dest_path)
return True
except Exception as e:
print(f"[Updater] Ошибка при распаковке RAR архива: {e}")
return False'''
def _handle_exe_download(success, error, exe_path, parent):
if not success:
_show_error(f"Ошибка при скачивании файла: {error}", parent)
return
_update_self(exe_path)
def _start_download_gui(url, dest_path, parent, on_finished):
dialog = QProgressDialog("Скачивание обновления...", "Отмена", 0, 100, parent)
dialog.setWindowTitle("Обновление")
dialog.setMinimumDuration(0)
dialog.setAutoClose(False)
dialog.setAutoReset(False)
thread = DownloadThread(url, dest_path)
thread.progress.connect(dialog.setValue)
thread.finished.connect(lambda ok, err: (
dialog.close(),
on_finished(ok, err)
))
thread.start()
dialog.exec_()
def _ask_update_dialog(latest_ver, parent):
msg = QMessageBox(parent)
msg.setIcon(QMessageBox.Information)
msg.setWindowTitle("Доступно обновление")
msg.setText(f"Доступна новая версия: v{latest_ver}\nЖелаете обновиться?")
msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
return msg.exec_() == QMessageBox.Yes
def _show_error(text, parent):
msg = QMessageBox(parent)
msg.setIcon(QMessageBox.Critical)
msg.setWindowTitle("Ошибка обновления")
msg.setText(text)
msg.exec_()
def _get_latest_release_info(base_url):
try:
parts = base_url.strip("/").split("/")
owner, repo = parts[-3], parts[-2]
tags_url = f"https://git.arktika.cyou/api/v1/repos/{owner}/{repo}/tags"
response = requests.get(tags_url, timeout=10)
response.raise_for_status()
tags = response.json()
versions = []
for tag in tags:
raw_tag = tag.get("name", "")
norm_tag = raw_tag.lstrip("v")
try:
parsed_ver = Version(norm_tag)
versions.append((parsed_ver, raw_tag))
except InvalidVersion:
continue
if not versions:
return None, None
versions.sort(reverse=True)
latest_ver, latest_tag = versions[0]
release_url = f"https://git.arktika.cyou/{owner}/{repo}/releases/tag/{latest_tag}"
return latest_ver, release_url
except Exception as e:
print(f"[Updater] Ошибка при получении тега через API: {e}")
return None, None
def _get_download_link(release_page_url, exe_name, zip_name=None):
try:
resp = requests.get(release_page_url, timeout=10)
soup = BeautifulSoup(resp.text, "html.parser")
links = soup.select("a[href]")
def normalize(href):
if href.startswith("http"):
return href
return "https://git.arktika.cyou" + href
for link in links:
href = link["href"]
if href.endswith(exe_name):
return normalize(href)
if zip_name:
for link in links:
href = link["href"]
if href.endswith(zip_name):
return normalize(href)
except Exception as e:
print(f"[Updater] Ошибка при поиске файла обновления: {e}")
return None
'''def _extract_exe_from_zip(zip_path, exe_name, dest_path):
try:
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
for file in zip_ref.namelist():
if file.endswith(exe_name):
zip_ref.extract(file, os.path.dirname(dest_path))
src = os.path.join(os.path.dirname(dest_path), file)
shutil.move(src, dest_path)
return True
except Exception as e:
print(f"[Updater] Ошибка при распаковке архива: {e}")
return False'''
def _update_self(new_exe_path):
cur_path = os.path.abspath(sys.executable if getattr(sys, 'frozen', False) else sys.argv[0])
bat_path = os.path.join(tempfile.gettempdir(), "update.bat")
with open(bat_path, "w") as bat:
bat.write(f"""@echo off
timeout /t 2 > nul
taskkill /F /IM "{os.path.basename(cur_path)}" > nul 2>&1
move /Y "{new_exe_path}" "{cur_path}" > nul
start "" "{cur_path}"
del "%~f0"
""")
subprocess.Popen(["cmd", "/c", bat_path])
sys.exit(0)

View File

@@ -0,0 +1,113 @@
import subprocess
import shutil
import os
from pathlib import Path
import PySide2
from PyInstaller.utils.hooks import collect_data_files
# install: pip install PySide2 lxml nuitka pyinstaller
# - PyInstaller
# - nuitka
# - PySide2
# - clang
# === Конфигурация ===
USE_NUITKA = True # True — сборка через Nuitka, False — через PyInstaller
MAIN_SCRIPT_NAME = "DebugVarEdit_GUI"
OUTPUT_NAME = "DebugVarEdit"
SRC_PATH = Path("./Src/")
SCRIPT_PATH = SRC_PATH / (MAIN_SCRIPT_NAME + ".py")
DIST_PATH = Path("./").resolve()
WORK_PATH = Path("./build_temp").resolve()
SPEC_PATH = WORK_PATH
ICON_PATH = SRC_PATH / "icon.png"
ICON_ICO_PATH = SRC_PATH / "icon.ico"
TEMP_FOLDERS = [
"build_temp",
"__pycache__",
MAIN_SCRIPT_NAME + ".build",
MAIN_SCRIPT_NAME + ".onefile-build",
MAIN_SCRIPT_NAME + ".dist"
]
# === Пути к DLL и прочим зависимостям ===
LIBS = {
"libclang.dll": SRC_PATH / "libclang.dll"
}
# === PySide2 плагины ===
PySide2_path = Path(PySide2.__file__).parent
datas = []
datas += collect_data_files('PySide2', includes=['plugins/platforms/*'])
datas += collect_data_files('PySide2', includes=['plugins/styles/*'])
datas += collect_data_files('PySide2', includes=['plugins/imageformats/*'])
add_data_list = [f"{src};{dest}" for src, dest in datas]
# Проверка наличия DLL и добавление
add_binary_list = []
for name, path in LIBS.items():
if path.exists():
add_binary_list.append(f"{str(path)};{name}")
else:
print(f"WARNING: {path.name} не найден — он не будет включён в сборку")
def clean_temp():
for folder in TEMP_FOLDERS:
path = Path(folder)
if path.exists():
shutil.rmtree(path, ignore_errors=True)
if USE_NUITKA:
# Формируем include-data-file только для DLL
include_data_files = [f"--include-data-file={str(path)}={name}" for name, path in LIBS.items() if path.exists()]
# Добавляем icon.ico как встроенный ресурс
if ICON_ICO_PATH.exists():
include_data_files.append(f"--include-data-file={ICON_ICO_PATH}=icon.ico")
cmd = [
"python", "-m", "nuitka",
"--standalone",
"--onefile",
"--enable-plugin=pyside2",
"--windows-console-mode=disable",
f"--output-dir={DIST_PATH}",
f"--output-filename={OUTPUT_NAME}.exe",
f"--windows-icon-from-ico={ICON_ICO_PATH}",
*include_data_files,
str(SCRIPT_PATH)
]
else:
# PyInstaller
cmd = [
"pyinstaller",
"--name", OUTPUT_NAME,
"--distpath", str(DIST_PATH),
"--workpath", str(WORK_PATH),
"--specpath", str(SPEC_PATH),
"--windowed",
f"--icon={ICON_ICO_PATH}",
"--hidden-import=PySide2.QtWidgets",
"--hidden-import=PySide2.QtGui",
"--hidden-import=PySide2.QtCore",
*[arg for b in add_binary_list for arg in ("--add-binary", b)],
*[arg for d in add_data_list for arg in ("--add-data", d)],
str(SCRIPT_PATH)
]
# === Запуск сборки ===
print("Выполняется сборка с помощью " + ("Nuitka" if USE_NUITKA else "PyInstaller"))
print(" ".join(cmd))
try:
subprocess.run(cmd, check=True)
print("\nСборка успешно завершена!")
# Удаление временных папок после сборки
clean_temp()
except subprocess.CalledProcessError:
print("\nОшибка при сборке.")

224
Src/csv_logger.py Normal file
View File

@@ -0,0 +1,224 @@
import csv
import numbers
import time
from datetime import datetime
from PySide2 import QtWidgets
class CsvLogger:
"""
Логгер, совместимый по формату с C-реализацией CSV_AddTitlesLine / CSV_AddLogLine.
Публичный API сохранён:
set_titles(varnames)
set_value(timestamp, varname, varvalue)
select_file(parent=None) -> bool
write_to_csv()
Использование:
1) set_titles([...])
2) многократно set_value(ts, name, value)
3) select_file() (по желанию)
4) write_to_csv()
"""
def __init__(self, filename="log.csv", delimiter=';'):
self._filename = filename
self._delimiter = delimiter
# Пользовательские заголовки
self.variable_names_ordered = []
# Полные заголовки CSV (Ticks(X), Ticks(Y), Time(Y), ...)
self.headers = ['t'] # до вызова set_titles placeholder
# Данные: {timestamp_key: {varname: value, ...}}
# timestamp_key = то, что передано в set_value (float/int/etc)
self.data_rows = {}
# Внутренние структуры для генерации CSV-формата С
self._row_wall_dt = {} # {timestamp_key: datetime при первой записи}
self._base_ts = None # timestamp_key первой строки (число)
self._base_ts_val = 0.0 # float значение первой строки (для delta)
self._tick_x_start = 0 # начальный тик (можно менять вручную при необходимости)
# ---- Свойства ----
@property
def filename(self):
return self._filename
# ---- Публичные методы ----
def set_titles(self, varnames):
"""
Устанавливает имена переменных.
Формирует полные заголовки CSV в формате С-лога.
"""
if not isinstance(varnames, list):
raise TypeError("Varnames must be a list of strings.")
if not all(isinstance(name, str) for name in varnames):
raise ValueError("All variable names must be strings.")
self.variable_names_ordered = varnames
self.headers = ["Ticks(X)", "Ticks(Y)", "Time(Y)"] + self.variable_names_ordered
# Сброс данных (структура изменилась)
self.data_rows.clear()
self._row_wall_dt.clear()
self._base_ts = None
self._base_ts_val = 0.0
def set_value(self, timestamp, varname, varvalue):
"""
Установить ОДНО значение в ОДНУ колонку для заданного timestampа.
timestamp — float секунд с эпохи (time.time()).
"""
if varname not in self.variable_names_ordered:
return # игнор, как у тебя было
# Новая строка?
if timestamp not in self.data_rows:
# Инициализируем поля переменных значением None
self.data_rows[timestamp] = {vn: None for vn in self.variable_names_ordered}
# Дата/время строки из ПЕРЕДАННОГО timestamp (а не datetime.now()!)
try:
ts_float = float(timestamp)
except Exception:
# если какая-то дичь прилетела, пусть будет 0 (эпоха) чтобы не упасть
ts_float = 0.0
self._row_wall_dt[timestamp] = datetime.fromtimestamp(ts_float)
# База для расчёта Ticks(Y) — первая строка
if self._base_ts is None:
self._base_ts = timestamp
self._base_ts_val = ts_float
# Записываем значение
self.data_rows[timestamp][varname] = varvalue
def select_file(self, parent=None) -> bool:
"""
Диалог выбора файла.
"""
options = QtWidgets.QFileDialog.Options()
filename, _ = QtWidgets.QFileDialog.getSaveFileName(
parent,
"Сохранить данные CSV",
self._filename,
"CSV Files (*.csv);;All Files (*)",
options=options
)
if filename:
if not filename.lower().endswith('.csv'):
filename += '.csv'
self._filename = filename
return True
else:
return False
def write_to_csv(self):
"""
Формирует CSV в формате C:
Ticks(X);Ticks(Y);Time(Y);Var1;Var2;...
0;0,000000;22/07/2025 13:45:12:0123;...;...
Правила значений:
- Тик X: автоинкремент от 0 (или self._tick_x_start) по порядку сортировки timestamp.
- Ticks(Y): дельта (секунды,микросекунды) между текущим timestamp и первым timestamp.
- Time(Y): wallclock строки (datetime.now() при первом появлении timestamp).
- Значение < 0 -> пустая ячейка (как if(raw_data[i] >= 0) else ;)
- None -> пустая ячейка.
"""
if len(self.headers) <= 3: # только служебные поля без переменных
print("Ошибка: Заголовки не установлены или не содержат переменных. Вызовите set_titles() перед записью.")
return
if not self._filename:
print("Ошибка: Имя файла не определено. select_file() или задайте при инициализации.")
return
if not self.data_rows:
print("Предупреждение: Нет данных для записи.")
# всё равно создадим файл с одними заголовками
try:
with open(self._filename, 'w', newline='', encoding='utf-8') as csvfile:
# QUOTE_NONE + escapechar для чистого формата без кавычек (как в С-строке)
writer = csv.writer(
csvfile,
delimiter=self._delimiter,
quoting=csv.QUOTE_NONE,
escapechar='\\',
lineterminator='\r\n'
)
# Пишем заголовки
writer.writerow(self.headers)
if self.data_rows:
sorted_ts = sorted(self.data_rows.keys(), key=self._ts_sort_key)
# убедимся, что база была зафиксирована
if self._base_ts is None:
self._base_ts = sorted_ts[0]
self._base_ts_val = self._coerce_ts_to_float(self._base_ts)
tick_x = self._tick_x_start
for ts in sorted_ts:
row_dict = self.data_rows[ts]
# delta по timestamp
cur_ts_val = self._coerce_ts_to_float(ts)
delta_us = int(round((cur_ts_val - self._base_ts_val) * 1_000_000))
if delta_us < 0:
delta_us = 0 # защита
seconds = delta_us // 1_000_000
micros = delta_us % 1_000_000
# wallclock строки
dt = self._row_wall_dt.get(ts, datetime.now())
# Формат DD/MM/YYYY HH:MM:SS:мммм (4 цифры ms, как в C: us/1000)
time_str = dt.strftime("%d/%m/%Y %H:%M:%S") + f":{dt.microsecond // 1000:04d}"
# Значения
row_vals = []
for vn in self.variable_names_ordered:
v = row_dict.get(vn)
if v is None:
row_vals.append("") # нет данных
else:
# если числовое и <0 -> пусто (как в C: если raw_data[i] >= 0 else ;)
if isinstance(v, numbers.Number) and v < 0:
row_vals.append("")
else:
row_vals.append(v)
csv_row = [tick_x, f"{seconds},{micros:06d}", time_str] + row_vals
writer.writerow(csv_row)
tick_x += 1
print(f"Данные успешно записаны в '{self._filename}'")
except Exception as e:
print(f"Ошибка при записи в файл '{self._filename}': {e}")
# ---- Вспомогательные ----
def _coerce_ts_to_float(self, ts):
"""
Пробуем привести переданный timestamp к float.
Разрешаем int/float/str, остальное -> индекс по порядку (0).
"""
if isinstance(ts, numbers.Number):
return float(ts)
try:
return float(ts)
except Exception:
# fallback: нечисловой ключ -> используем порядковый индекс
# (таких почти не должно быть, но на всякий)
return 0.0
def _ts_sort_key(self, ts):
"""
Ключ сортировки timestampов — сначала попытка float, потом str.
"""
if isinstance(ts, numbers.Number):
return (0, float(ts))
try:
return (0, float(ts))
except Exception:
return (1, str(ts))

View File

@@ -1,18 +1,21 @@
# build command
# pyinstaller --onefile --distpath . --workpath ./build --specpath ./build generateVars.py
# pyinstaller --onefile --distpath . --workpath ./build --specpath ./build generate_debug_vars.py
# start script
# generateVars.exe F:\Work\Projects\TMS\TMS_new_bus\ Src/DebugTools/vars.xml Src/DebugTools
# generate_debug_vars.exe F:\Work\Projects\TMS\TMS_new_bus\ Src/DebugTools/vars.xml Src/DebugTools
import sys
import os
import re
import xml.etree.ElementTree as ET
import lxml.etree as ET
from pathlib import Path
from xml.dom import minidom
import myXML
import argparse
shortnameSize = 10
# === Словарь соответствия типов XML → DebugVarType_t ===
type_map = dict([
type_map_tms = dict([
*[(k, 'pt_int8') for k in ('signed char', 'char')],
*[(k, 'pt_int16') for k in ('int', 'int16', 'short')],
*[(k, 'pt_int32') for k in ('long', 'int32', '_iqx')],
@@ -50,6 +53,150 @@ type_map = dict([
('struct[]', 'pt_arr_struct'),
('union[]', 'pt_arr_union'),
])
# === Словарь соответствия типов XML → DebugVarType_t ===
type_map_stm32 = dict([
*[(k, 'pt_int8') for k in (
'int8_t', 'signed char', 'char'
)],
# --- 8-bit unsigned ---
*[(k, 'pt_uint8') for k in (
'uint8_t', 'unsigned char'
)],
# --- 16-bit signed ---
*[(k, 'pt_int16') for k in (
'int16_t', 'short', 'short int', 'signed short', 'signed short int'
)],
# --- 16-bit unsigned ---
*[(k, 'pt_uint16') for k in (
'uint16_t', 'unsigned short', 'unsigned short int'
)],
# --- 32-bit signed ---
*[(k, 'pt_int32') for k in (
'int32_t', 'int', 'signed', 'signed int'
)],
# --- 32-bit unsigned ---
*[(k, 'pt_uint32') for k in (
'uint32_t', 'unsigned', 'unsigned int'
)],
# --- 64-bit signed ---
*[(k, 'pt_int64') for k in (
'int64_t', 'long long', 'signed long long', 'signed long long int'
)],
# --- 64-bit unsigned ---
*[(k, 'pt_uint64') for k in (
'uint64_t', 'unsigned long long', 'unsigned long long int'
)],
# --- Float ---
*[(k, 'pt_float') for k in (
'float', 'float32_t'
)],
# --- Struct and Union ---
('struct', 'pt_struct'),
('union', 'pt_union'),
('struct*', 'pt_ptr_struct'),
('union*', 'pt_ptr_union'),
('struct[]', 'pt_arr_struct'),
('union[]', 'pt_arr_union'),
# === POINTERS ===
# 8-bit
*[(k, 'pt_ptr_int8') for k in (
'int8_t*', 'signed char*', 'char*'
)],
*[(k, 'pt_ptr_uint8') for k in (
'uint8_t*', 'unsigned char*'
)],
# 16-bit
*[(k, 'pt_ptr_int16') for k in (
'int16_t*', 'short*', 'short int*', 'signed short*', 'signed short int*'
)],
*[(k, 'pt_ptr_uint16') for k in (
'uint16_t*', 'unsigned short*', 'unsigned short int*'
)],
# 32-bit
*[(k, 'pt_ptr_int32') for k in (
'int32_t*', 'int*', 'signed*', 'signed int*'
)],
*[(k, 'pt_ptr_uint32') for k in (
'uint32_t*', 'unsigned*', 'unsigned int*'
)],
# 64-bit
*[(k, 'pt_ptr_int64') for k in (
'int64_t*', 'long long*', 'signed long long*', 'signed long long int*'
)],
*[(k, 'pt_ptr_uint64') for k in (
'uint64_t*', 'unsigned long long*', 'unsigned long long int*'
)],
# float*
*[(k, 'pt_ptr_float') for k in (
'float*', 'float32_t*'
)],
# === ARRAYS ===
# 8-bit
*[(k, 'pt_arr_int8') for k in (
'int8_t[]', 'signed char[]', 'char[]'
)],
*[(k, 'pt_arr_uint8') for k in (
'uint8_t[]', 'unsigned char[]'
)],
# 16-bit
*[(k, 'pt_arr_int16') for k in (
'int16_t[]', 'short[]', 'short int[]', 'signed short[]', 'signed short int[]'
)],
*[(k, 'pt_arr_uint16') for k in (
'uint16_t[]', 'unsigned short[]', 'unsigned short int[]'
)],
# 32-bit
*[(k, 'pt_arr_int32') for k in (
'int32_t[]', 'int[]', 'signed[]', 'signed int[]'
)],
*[(k, 'pt_arr_uint32') for k in (
'uint32_t[]', 'unsigned[]', 'unsigned int[]'
)],
# 64-bit
*[(k, 'pt_arr_int64') for k in (
'int64_t[]', 'long long[]', 'signed long long[]', 'signed long long int[]'
)],
*[(k, 'pt_arr_uint64') for k in (
'uint64_t[]', 'unsigned long long[]', 'unsigned long long int[]'
)],
# float[]
*[(k, 'pt_arr_float') for k in (
'float[]', 'float32_t[]'
)],
])
type_map = type_map_tms
stm_flag_global = 0
def choose_type_map(stm_flag):
global type_map # объявляем, что будем менять глобальную переменную
global stm_flag_global # объявляем, что будем менять глобальную переменную
if stm_flag:
type_map = type_map_stm32
stm_flag_global = 1
else:
type_map = type_map_tms
stm_flag_global = 0
def map_type_to_pt(typename, varname=None, typedef_map=None):
typename_orig = typename.strip()
@@ -138,20 +285,26 @@ def add_new_vars_to_xml(proj_path, xml_rel_path, output_path):
Возвращает True если что-то добавлено и XML перезаписан, иначе False.
"""
pattern = re.compile(
r'{\s*\(uint8_t\s*\*\)\s*&([a-zA-Z_][a-zA-Z0-9_]*(?:\[.*?\])?(?:(?:\.|->)[a-zA-Z_][a-zA-Z0-9_]*(?:\[.*?\])?)*)\s*,\s*'
r'(pt_\w+)\s*,\s*'
r'(t?_?iq\w+)\s*,\s*'
r'(t?_?iq\w+)\s*,\s*'
r'"([^"]+)"'
)
# Считываем существующие переменные
parsed_vars = {}
if os.path.isfile(output_path):
with open(output_path, 'r', encoding='utf-8', errors='ignore') as f:
for line in f:
# {(char *)&some.deep.var.name , pt_uint16 , t_iq15 , "ShortName"},
m = re.match(
r'{\s*\(char\s*\*\)\s*&([a-zA-Z_][a-zA-Z0-9_]*)\s*,\s*(pt_\w+)\s*,\s*(t?iq_\w+)\s*,\s*"([^"]+)"',
line)
# {(uint8_t *)&some.deep.var.name , pt_uint16 , t_iq15 , t_iq10, "ShortName"},
m = pattern.search(line)
if m:
full_varname = m.group(1) # e.g., some.deep.var.name
pt_type = m.group(2)
iq_type = m.group(3)
shortname = m.group(4)
return_type = m.group(4)
shortname = m.group(5)
parsed_vars[full_varname] = {
'pt_type': pt_type,
@@ -159,7 +312,7 @@ def add_new_vars_to_xml(proj_path, xml_rel_path, output_path):
'enable': True,
'show_var': True,
'shortname': shortname,
'return_type': 'int',
'return_type': return_type,
'type': '', # Можешь дополнить из externs
'file': '', # Можешь дополнить из externs
'extern': False,
@@ -214,8 +367,8 @@ def add_new_vars_to_xml(proj_path, xml_rel_path, output_path):
added_count += 1
if added_count > 0:
ET.indent(tree, space=" ", level=0)
tree.write(xml_full_path, encoding="utf-8", xml_declaration=True)
myXML.fwrite(root, xml_full_path)
print(f"[INFO] В XML добавлено новых переменных: {added_count}")
return True
else:
@@ -282,12 +435,27 @@ def read_vars_from_xml(proj_path, xml_rel_path):
return unique_vars, include_files, vars_need_extern
def read_file_try_encodings(filepath):
if not os.path.isfile(filepath):
# Файл не существует — просто вернуть пустую строку или None
return "", None
for enc in ['utf-8', 'cp1251']:
try:
with open(filepath, 'r', encoding=enc) as f:
content = f.read()
return content, enc
except UnicodeDecodeError:
continue
raise UnicodeDecodeError(f"Не удалось прочитать файл {filepath} с кодировками utf-8 и cp1251")
def generate_vars_file(proj_path, xml_path, output_dir):
output_dir = os.path.join(proj_path, output_dir)
os.makedirs(output_dir, exist_ok=True)
output_path = os.path.join(output_dir, 'debug_vars.c')
LIBC_path = os.path.join(output_dir, 'debug_tools.c')
LIBH_path = os.path.join(output_dir, 'debug_tools.h')
# Запись новых переменных для в XML
@@ -328,14 +496,24 @@ def generate_vars_file(proj_path, xml_path, output_dir):
if not pt_type:
pt_type = map_type_to_pt(vtype, vname)
ret_type = info.get('return_type')
if not ret_type:
pt_type = 't_iq_none'
# Дополнительные поля, например комментарий
comment = info.get("comment", "")
short_name = info.get("shortname", f'"{vname}"')
short_trimmed = short_name[:shortnameSize] # ограничиваем длину до 10
if pt_type not in ('pt_struct', 'pt_union'):
formated_name = f'"{vname}"'
f_name = f'{vname},'
f_type = f'{pt_type},'
f_iq = f'{iq_type},'
f_ret_iq = f'{ret_type},'
f_short_name = f'"{short_trimmed}"' # оборачиваем в кавычки
# Добавим комментарий после записи, если он есть
comment_str = f' // {comment}' if comment else ''
line = f'{{(char *)&{vname:<41} , {pt_type:<21} , {iq_type:<21} , {formated_name:<42}}}, \\{comment_str}'
line = f'{{(uint8_t *)&{f_name:<58} {f_type:<15} {f_iq:<15} {f_ret_iq:<15} {f_short_name:<21}}}, \\{comment_str}'
new_debug_vars[vname] = line
else:
@@ -381,14 +559,19 @@ def generate_vars_file(proj_path, xml_path, output_dir):
out_lines.append(f'\n\n// Определение массива с указателями на переменные для отладки')
out_lines.append(f'int DebugVar_Qnt = {len(all_debug_lines)};')
out_lines.append('#pragma DATA_SECTION(dbg_vars,".dbgvar_info")')
if stm_flag_global == 0:
out_lines.append('#pragma DATA_SECTION(dbg_vars,".dbgvar_info")')
out_lines.append('// pointer_type iq_type return_iq_type short_name')
out_lines.append('DebugVar_t dbg_vars[] = {\\')
out_lines.extend(all_debug_lines)
out_lines.append('};')
out_lines.append('')
# Выберем кодировку для записи файла
# Если встречается несколько, возьмем первую из set
enc_to_write = 'cp1251'
if stm_flag_global == 0:
enc_to_write = 'cp1251'
else:
enc_to_write = 'utf-8'
#print("== GLOBAL VARS FOUND ==")
#for vname, (vtype, path) in vars_in_c.items():
@@ -398,6 +581,16 @@ def generate_vars_file(proj_path, xml_path, output_dir):
with open(output_path, 'w', encoding=enc_to_write) as f:
f.write('\n'.join(out_lines))
if os.path.isfile(LIBC_path):
libc_code, _ = read_file_try_encodings(LIBC_path)
with open(LIBC_path, 'w', encoding=enc_to_write) as f:
f.write(libc_code)
if os.path.isfile(LIBH_path):
libh_code, _ = read_file_try_encodings(LIBH_path)
with open(LIBH_path, 'w', encoding=enc_to_write) as f:
f.write(libh_code)
print(f'Файл debug_vars.c сгенерирован в кодировке, переменных: {len(all_debug_lines)}')
@@ -459,16 +652,17 @@ Usage example:
print(f"Error: Project path '{proj_path}' не является директорией или не существует.")
sys.exit(1)
generate_vars_file(proj_path, xml_path_rel, output_dir_rel)
generate_vars_file(proj_path, xml_path_rel, output_dir_rel, 0)
if __name__ == "__main__":
main()
def run_generate(proj_path, xml_path, output_dir):
def run_generate(proj_path, xml_path, output_dir, shortname_size):
import os
global shortnameSize
shortnameSize = shortname_size
# Normalize absolute paths
proj_path = os.path.abspath(proj_path)
xml_path_abs = os.path.abspath(xml_path)

BIN
Src/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
Src/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

242
Src/makefile_parser.py Normal file
View File

@@ -0,0 +1,242 @@
import os
import re
from lxml import etree as ET
def strip_single_line_comments(code):
# Удалим // ... до конца строки
return re.sub(r'//.*?$', '', code, flags=re.MULTILINE)
def read_file_try_encodings(filepath):
if not os.path.isfile(filepath):
# Файл не существует — просто вернуть пустую строку или None
return "", None
for enc in ['utf-8', 'cp1251']:
try:
with open(filepath, 'r', encoding=enc) as f:
content = f.read()
content = strip_single_line_comments(content)
return content, enc
except UnicodeDecodeError:
continue
raise UnicodeDecodeError(f"Не удалось прочитать файл {filepath} с кодировками utf-8 и cp1251")
def find_all_includes_recursive(c_files, include_dirs, processed_files=None):
"""
Рекурсивно ищет все include-файлы начиная с заданных c_files.
Возвращает множество ПОЛНЫХ ПУТЕЙ к найденным include-файлам.
include_dirs — список директорий, в которых ищем include-файлы.
processed_files — множество уже обработанных файлов (для избежания циклов).
"""
if processed_files is None:
processed_files = set()
include_files = set()
include_pattern = re.compile(r'#include\s+"([^"]+)"')
for cfile in c_files:
norm_path = os.path.normpath(cfile)
if norm_path in processed_files:
continue
processed_files.add(norm_path)
content, _ = read_file_try_encodings(cfile)
if content is None:
continue
includes = include_pattern.findall(content)
for inc in includes:
# Ищем полный путь к include-файлу в include_dirs
inc_full_path = None
for dir_ in include_dirs:
candidate = os.path.normpath(os.path.join(dir_, inc))
if os.path.isfile(candidate):
inc_full_path = os.path.abspath(candidate)
break
if inc_full_path:
include_files.add(inc_full_path)
# Рекурсивный обход вложенных includes
if inc_full_path not in processed_files:
nested_includes = find_all_includes_recursive(
[inc_full_path], include_dirs, processed_files
)
include_files.update(nested_includes)
return include_files
def parse_objects_list(objects_list_path, project_root):
c_files = []
include_dirs = set()
if not os.path.isfile(objects_list_path):
return c_files, include_dirs
with open(objects_list_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
for line in lines:
line = line.strip().strip('"').replace("\\", "/")
if line.endswith(".o"):
c_file = re.sub(r"\.o$", ".c", line)
abs_path = os.path.normpath(os.path.join(project_root, c_file))
if os.path.isfile(abs_path):
if not any(x in abs_path for x in ["DebugTools", "v120", "v100"]):
c_files.append(abs_path)
include_dirs.add(os.path.dirname(abs_path))
return c_files, include_dirs
def parse_uvprojx(uvprojx_path):
import xml.etree.ElementTree as ET
import os
tree = ET.parse(uvprojx_path)
root = tree.getroot()
project_dir = os.path.dirname(os.path.abspath(uvprojx_path))
c_files = []
include_dirs = set()
defines = set()
# Найдём C-файлы и директории
for file_elem in root.findall(".//FilePath"):
file_path = file_elem.text
if file_path:
abs_path = os.path.normpath(os.path.join(project_dir, file_path))
if os.path.isfile(abs_path):
if abs_path.endswith(".c"):
c_files.append(abs_path)
include_dirs.add(os.path.dirname(abs_path))
# Включаем IncludePath
for inc_path_elem in root.findall(".//IncludePath"):
path_text = inc_path_elem.text
if path_text:
paths = path_text.split(';')
for p in paths:
p = p.strip()
if p:
abs_inc_path = os.path.normpath(os.path.join(project_dir, p))
if os.path.isdir(abs_inc_path):
include_dirs.add(abs_inc_path)
# Добавим <Define>
for define_elem in root.findall(".//Define"):
def_text = define_elem.text
if def_text:
for d in def_text.split(','):
d = d.strip()
if d:
defines.add(d)
h_files = find_all_includes_recursive(c_files, include_dirs)
return sorted(c_files), sorted(h_files), sorted(include_dirs), sorted(defines)
def parse_makefile(makefile_path, proj_path):
import os
import re
project_root = os.path.abspath(proj_path)
c_files = []
include_dirs = set()
defines = [] # Заглушка: нет define-параметров из Makefile
with open(makefile_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
raw_entries = []
collecting = False
for line in lines:
stripped = line.strip()
if (("ORDERED_OBJS" in stripped or "C_SOURCES" in stripped) and ("+=" in stripped or "=" in stripped)):
collecting = True
if collecting:
line_clean = stripped.rstrip("\\").strip()
if line_clean:
line_clean = re.sub(r"\$\([^)]+\)", "", line_clean)
line_clean = re.sub(r"\$\{[^}]+\}", "", line_clean)
raw_entries.append(line_clean)
if not stripped.endswith("\\"):
collecting = False
for entry in raw_entries:
for token in entry.split():
token = token.strip('"')
if not token:
continue
token = token.replace("\\", "/")
if token.endswith(".obj"):
token = re.sub(r"\.obj$", ".c", token)
elif token.endswith(".o"):
token = re.sub(r"\.o$", ".c", token)
if token.endswith(".c"):
abs_path = os.path.normpath(os.path.join(project_root, token))
if os.path.isfile(abs_path):
if not any(x in abs_path for x in ["DebugTools", "v120", "v100"]):
c_files.append(abs_path)
include_dirs.add(os.path.dirname(abs_path))
if not c_files:
makefile_dir = os.path.dirname(os.path.abspath(makefile_path))
objects_list_path = os.path.join(makefile_dir, "objects.list")
c_from_objects, inc_from_objects = parse_objects_list(objects_list_path, project_root)
c_files.extend(c_from_objects)
include_dirs.update(inc_from_objects)
for line in lines:
if "-I" in line or "C_INCLUDES" in line:
matches = re.findall(r"-I\s*([^\s\\]+)", line)
for match in matches:
match = match.strip('"').replace("\\", "/")
abs_include = os.path.normpath(os.path.join(project_root, match))
if os.path.isdir(abs_include):
include_dirs.add(abs_include)
# Добавляем пути с заменой 'Src' на 'Inc', если путь заканчивается на 'Src'
additional_includes = set()
for inc in include_dirs:
if inc.endswith(os.sep + "Src") or inc.endswith("/Src"):
inc_inc = inc[:-3] + "Inc" # заменяем 'Src' на 'Inc'
if os.path.isdir(inc_inc):
additional_includes.add(inc_inc)
include_dirs.update(additional_includes)
h_files = find_all_includes_recursive(c_files, include_dirs)
return sorted(c_files), sorted(h_files), sorted(include_dirs), sorted(defines)
def parse_project(project_file_path, project_root=None):
"""
Выбирает парсер в зависимости от расширения project_file_path:
- для *.uvprojx и *.uvproj вызывается парсер Keil
- для остальных - parse_makefile
project_root нужен для parse_makefile, если не передан - берется из project_file_path
"""
ext = os.path.splitext(project_file_path)[1].lower()
if ext in ['.uvprojx', '.uvproj']:
# Парсим Keil проект
return parse_uvprojx(project_file_path)
else:
# Парсим makefile
if project_root is None:
project_root = os.path.dirname(os.path.abspath(project_file_path))
return parse_makefile(project_file_path, project_root)

72
Src/myXML.py Normal file
View File

@@ -0,0 +1,72 @@
import os
from lxml import etree
def make_absolute_path(path, base_path):
if not os.path.isabs(path) and os.path.isdir(base_path):
try:
return os.path.abspath(os.path.join(base_path, path))
except Exception:
pass # На случай сбоя в os.path.join или abspath
elif os.path.isabs(path):
return os.path.abspath(path)
else:
return path
def make_relative_path(abs_path, base_path):
abs_path = os.path.abspath(abs_path)
base_path = os.path.abspath(base_path)
# Разбиваем на списки директорий
abs_parts = abs_path.split(os.sep)
base_parts = base_path.split(os.sep)
# Проверяем, является ли base_path настоящим префиксом пути (по папкам)
if abs_parts[:len(base_parts)] == base_parts:
rel_parts = abs_parts[len(base_parts):]
return "/".join(rel_parts)
# Иначе пробуем relpath
try:
return os.path.relpath(abs_path, base_path).replace("\\", "/")
except Exception:
return abs_path.replace("\\", "/")
def indent_xml(elem, level=0):
# Убираем strip() — они медленные, и текст мы всё равно перезаписываем
i = "\n" + level * " "
if len(elem):
elem.text = elem.text or i + " "
for child in elem:
indent_xml(child, level + 1)
elem[-1].tail = elem[-1].tail or i
else:
elem.tail = elem.tail or i
def fwrite(root, xml_full_path):
#indent_xml(root)
#ET.ElementTree(root).write(xml_full_path, encoding="utf-8", xml_declaration=True)
rough_string = etree.tostring(root, encoding="utf-8")
parsed = etree.fromstring(rough_string)
with open(xml_full_path, "wb") as f:
f.write(etree.tostring(parsed, pretty_print=True, encoding="utf-8", xml_declaration=True))
def safe_parse_xml(xml_path):
if not xml_path or not os.path.isfile(xml_path):
print(f"Файл '{xml_path}' не найден или путь пустой")
return None, None
try:
if os.path.getsize(xml_path) == 0:
return None, None
tree = etree.parse(xml_path)
root = tree.getroot()
return root, tree
except etree .XMLSyntaxError as e:
print(f"Ошибка парсинга XML файла '{xml_path}': {e}")
return None, None
except Exception as e:
print(f"Неожиданная ошибка при чтении XML файла '{xml_path}': {e}")
return None, None

View File

@@ -1,143 +0,0 @@
import os
import re
def strip_single_line_comments(code):
# Удалим // ... до конца строки
return re.sub(r'//.*?$', '', code, flags=re.MULTILINE)
def read_file_try_encodings(filepath):
for enc in ['utf-8', 'cp1251']:
try:
with open(filepath, 'r', encoding=enc) as f:
content = f.read()
content = strip_single_line_comments(content) # <=== ВАЖНО
return content, enc
except UnicodeDecodeError:
continue
raise UnicodeDecodeError(f"Не удалось прочитать файл {filepath} с кодировками utf-8 и cp1251")
def find_all_includes_recursive(c_files, include_dirs, processed_files=None):
"""
Рекурсивно ищет все include-файлы начиная с заданных c_files.
Возвращает множество ПОЛНЫХ ПУТЕЙ к найденным include-файлам.
include_dirs — список директорий, в которых ищем include-файлы.
processed_files — множество уже обработанных файлов (для избежания циклов).
"""
if processed_files is None:
processed_files = set()
include_files = set()
include_pattern = re.compile(r'#include\s+"([^"]+)"')
for cfile in c_files:
norm_path = os.path.normpath(cfile)
if norm_path in processed_files:
continue
processed_files.add(norm_path)
content, _ = read_file_try_encodings(cfile)
includes = include_pattern.findall(content)
for inc in includes:
# Ищем полный путь к include-файлу в include_dirs
inc_full_path = None
for dir_ in include_dirs:
candidate = os.path.normpath(os.path.join(dir_, inc))
if os.path.isfile(candidate):
inc_full_path = os.path.abspath(candidate)
break
if inc_full_path:
include_files.add(inc_full_path)
# Рекурсивный обход вложенных includes
if inc_full_path not in processed_files:
nested_includes = find_all_includes_recursive(
[inc_full_path], include_dirs, processed_files
)
include_files.update(nested_includes)
return include_files
def parse_makefile(makefile_path):
makefile_dir = os.path.dirname(makefile_path)
project_root = os.path.dirname(makefile_dir) # поднялись из Debug
with open(makefile_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
objs_lines = []
collecting = False
for line in lines:
stripped = line.strip()
if stripped.startswith("ORDERED_OBJS") and "+=" in stripped:
parts = stripped.split("\\")
first_part = parts[0]
idx = first_part.find("+=")
tail = first_part[idx+2:].strip()
if tail:
objs_lines.append(tail)
collecting = True
if len(parts) > 1:
for p in parts[1:]:
p = p.strip()
if p:
objs_lines.append(p)
continue
if collecting:
if stripped.endswith("\\"):
objs_lines.append(stripped[:-1].strip())
else:
objs_lines.append(stripped)
collecting = False
objs_str = ' '.join(objs_lines)
objs_str = re.sub(r"\$\([^)]+\)", "", objs_str)
objs = []
for part in objs_str.split():
part = part.strip()
if part.startswith('"') and part.endswith('"'):
part = part[1:-1]
if part:
objs.append(part)
c_files = []
include_dirs = set()
for obj_path in objs:
if "DebugTools" in obj_path:
continue
if "v120" in obj_path:
continue
if obj_path.startswith("Debug\\") or obj_path.startswith("Debug/"):
rel_path = obj_path.replace("Debug\\", "Src\\").replace("Debug/", "Src/")
else:
rel_path = obj_path
abs_path = os.path.normpath(os.path.join(project_root, rel_path))
root, ext = os.path.splitext(abs_path)
if ext.lower() == ".obj":
c_path = root + ".c"
else:
c_path = abs_path
# Сохраняем только .c файлы
if c_path.lower().endswith(".c"):
c_files.append(c_path)
dir_path = os.path.dirname(c_path)
if dir_path and "DebugTools" not in dir_path:
include_dirs.add(dir_path)
h_files = find_all_includes_recursive(c_files, include_dirs)
return sorted(c_files), sorted(h_files), sorted(include_dirs)

319
Src/path_hints.py Normal file
View File

@@ -0,0 +1,319 @@
# path_hints.py
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple
import re
# ---------------------- tokenization helpers ----------------------
def split_path_tokens(path: str) -> List[str]:
"""
Разбивает строку пути на логические части:
'foo[2].bar[1]->baz' -> ['foo', '[2]', 'bar', '[1]', 'baz']
Аналог твоей split_path(), но оставлена как чистая функция.
"""
tokens: List[str] = []
token = ''
i = 0
L = len(path)
while i < L:
c = path[i]
# '->'
if c == '-' and i + 1 < L and path[i:i+2] == '->':
if token:
tokens.append(token)
token = ''
i += 2
continue
# одиночный '-' в конце
if c == '-' and i == L - 1:
i += 1
continue
# '.'
if c == '.':
if token:
tokens.append(token)
token = ''
i += 1
continue
# '[' ... ']'
if c == '[':
if token:
tokens.append(token)
token = ''
idx = ''
while i < L and path[i] != ']':
idx += path[i]
i += 1
if i < L and path[i] == ']':
idx += ']'
i += 1
tokens.append(idx)
continue
# обычный символ
token += c
i += 1
if token:
tokens.append(token)
return tokens
def split_path_tokens_with_spans(path: str) -> List[Tuple[str, int, int]]:
"""
Возвращает список кортежей (токен, start_pos, end_pos)
Токены — так же, как в split_path_tokens, но с позициями в исходной строке.
"""
tokens = []
i = 0
L = len(path)
while i < L:
c = path[i]
start = i
# '->'
if c == '-' and i + 1 < L and path[i:i+2] == '->':
tokens.append(('->', start, start + 2))
i += 2
continue
if c == '.':
tokens.append(('.', start, start + 1))
i += 1
continue
if c == '[':
# захватим весь индекс с ']'
j = i
while j < L and path[j] != ']':
j += 1
if j < L and path[j] == ']':
j += 1
tokens.append((path[i:j], i, j))
i = j
continue
# иначе - обычное имя (до точки, стрелки или скобок)
j = i
while j < L and path[j] not in ['.', '-', '[']:
if path[j] == '-' and j + 1 < L and path[j:j+2] == '->':
break
j += 1
tokens.append((path[i:j], i, j))
i = j
# фильтруем из списка токены-разделители '.' и '->' чтобы оставить только логические части
filtered = [t for t in tokens if t[0] not in ['.', '->']]
return filtered
def canonical_key(path: str) -> str:
"""
Преобразует путь к канонической форме для индекса / поиска:
- '->' -> '.'
- '[' -> '.['
- lower()
"""
p = path.replace('->', '.')
p = p.replace('[', '.[')
return p.lower()
# ---------------------- индекс узлов ----------------------
@dataclass
class PathNode:
"""
Узел в логическом дереве путей.
Храним:
- собственное имя (локальное, напр. 'controller' или '[3]')
- полный путь (оригинальный, как его должен видеть пользователь)
- тип (опционально; widget может хранить отдельно)
- дети
"""
name: str
full_path: str
type_str: str = ''
children: Dict[str, "PathNode"] = field(default_factory=dict)
def add_child(self, child: "PathNode") -> None:
self.children[child.name] = child
def get_children(self) -> List["PathNode"]:
"""
Вернуть список дочерних узлов, отсортированных по имени.
"""
return sorted(self.children.values(), key=lambda n: n.name)
class PathHints:
"""
Движок автоподсказок / completion.
Работает с плоским списком ПОЛНЫХ имён (как показываются пользователю).
Сам восстанавливает иерархию и выдаёт подсказки по текущему вводу.
Qt-независим.
"""
def __init__(self) -> None:
self._paths: List[str] = []
self._types: Dict[str, str] = {} # full_path -> type_str (опционально)
self._index: Dict[str, PathNode] = {} # canonical full path -> node
self._root_children: Dict[str, PathNode] = {} # top-level по первому токену
# ------------ Подаём данные ------------
def set_paths(self,
paths: List[Tuple[str, Optional[str]]]
) -> None:
"""
paths: список кортежей (full_path, type_str|None).
Пример: ('project.controller.read.errors.bit.status_er0', 'unsigned int')
Поля могут содержать '->' и индексы, т.е. строки в пользовательском формате.
NOTE: порядок не важен; дерево строится автоматически.
"""
self._paths = []
self._types.clear()
self._index.clear()
self._root_children.clear()
for p, t in paths:
if t is None:
t = ''
self._add_path(p, t)
def _add_path(self, full_path: str, type_str: str) -> None:
self._paths.append(full_path)
self._types[full_path] = type_str
tokens_spans = split_path_tokens_with_spans(full_path)
if not tokens_spans:
return
cur_dict = self._root_children
cur_full = ''
parent_node: Optional[PathNode] = None
for i, (tok, start, end) in enumerate(tokens_spans):
cur_full = full_path[:end] # подстрока с начала до конца токена включительно
node = cur_dict.get(tok)
if node is None:
node = PathNode(name=tok, full_path=cur_full)
cur_dict[tok] = node
# Регистрируем все узлы, включая промежуточные
self._index[canonical_key(cur_full)] = node
parent_node = node
cur_dict = node.children
# В последний узел добавляем тип
if parent_node:
parent_node.type_str = type_str
# ------------ Поиск узла ------------
def find_node(self, path: str) -> Optional[PathNode]:
return self._index.get(canonical_key(path))
def get_children(self, full_path: str) -> List[PathNode]:
"""
Вернуть список дочерних узлов PathNode для заданного полного пути.
Если узел не найден — вернуть пустой список.
"""
node = self.find_node(full_path)
if node is None:
return []
return node.get_children()
# ------------ Подсказки ------------
def suggest(self,
text: str,
*,
include_partial: bool = True
) -> List[str]:
"""
Вернёт список *полных имён узлов*, подходящих под ввод.
Правила (упрощённо, повторяя твою update_completions()):
- Если текст пуст → top-level.
- Если заканчивается на '.' или '->' или '[' → вернуть детей текущего узла.
- Иначе → фильтр по последнему фрагменту (prefix substring match).
"""
text = text or ''
stripped = text.strip()
# пусто: top-level
if stripped == '':
return sorted(self._root_full_names())
# Завершение по разделителю?
if stripped.endswith('.') or stripped.endswith('->') or stripped.endswith('['):
base = stripped[:-1] if stripped.endswith('[') else stripped.rstrip('.').rstrip('>').rstrip('-')
node = self.find_node(base)
if node:
return self._children_full_names(node)
# не нашли базу — ничего
return []
# иначе: обычный поиск по последней части
toks = split_path_tokens(stripped)
prefix_last = toks[-1].lower() if toks else ''
parent_toks = toks[:-1]
if not parent_toks:
# фильтр top-level
res = []
for name, node in self._root_children.items():
if prefix_last == '' or prefix_last in name.lower():
res.append(node.full_path)
return sorted(res)
# есть родитель
parent_path = self._join_tokens(parent_toks)
parent_node = self.find_node(parent_path)
if not parent_node:
return []
res = []
for child in parent_node.children.values():
if prefix_last == '' or prefix_last in child.name.lower():
res.append(child.full_path)
return sorted(res)
def add_separator(self, full_path: str) -> str:
"""
Возвращает full_path с добавленным разделителем ('.' или '['),
если у узла есть дети и пользователь ещё не поставил разделитель.
Если первый ребёнок — массивный токен ('[0]') → добавляем '['.
Позже можно допилить '->' для указателей.
"""
node = self.find_node(full_path)
text = full_path
if node and node.children and not (
text.endswith('.') or text.endswith('->') or text.endswith('[')
):
first_child = next(iter(node.children.values()))
if first_child.name.startswith('['):
text += '[' # сразу начинаем индекс
else:
text += '.' # обычный переход
return text
# ------------ внутренние вспомогательные ------------
def _root_full_names(self) -> List[str]:
return [node.full_path for node in self._root_children.values()]
def _children_full_names(self, node: PathNode) -> List[str]:
return [ch.full_path for ch in node.children.values()]
@staticmethod
def _join_tokens(tokens: List[str]) -> str:
"""
Собираем путь обратно. Для внутренних нужд (поиск), формат не критичен —
всё равно canonical_key() нормализует.
"""
if not tokens:
return ''
out = tokens[0]
for t in tokens[1:]:
if t.startswith('['):
out += t
else:
out += '.' + t
return out

Binary file not shown.

219
Src/scan_progress_gui.py Normal file
View File

@@ -0,0 +1,219 @@
import sys
import re
import multiprocessing
import sys
import contextlib
import io
import json
from scan_vars import run_scan
from var_table import VariableTableWidget, rows
from PySide2.QtWidgets import (
QApplication, QWidget, QTableWidget, QTableWidgetItem,
QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton,
QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit,
QDialog, QTreeWidget, QTreeWidgetItem, QSizePolicy, QHeaderView, QProgressBar
)
from PySide2.QtGui import QTextCursor, QKeyEvent
from PySide2.QtCore import Qt, QProcess, QObject, Signal, QTimer
class EmittingStream(QObject):
text_written = Signal(str)
progress_updated = Signal(str, int, int) # bar_name, current, total
def __init__(self):
super().__init__()
self._buffer = ""
self._current_bar_name = None
def write(self, text):
self._buffer += text
while '\n' in self._buffer:
line, self._buffer = self._buffer.split('\n', 1)
if line.startswith('Progress: "') and line.endswith('"'):
bar_name = self._extract_bar_name(line)
if bar_name:
self._current_bar_name = bar_name
continue # не выводим строку
elif self._is_progress_line(line):
current, total = self._extract_progress(line)
if current is not None and total is not None:
name = self._current_bar_name if self._current_bar_name else "Progress"
self.progress_updated.emit(name, current, total)
continue # не выводим строку
# если не прогресс и не имя — выводим
self.text_written.emit(line)
def _extract_bar_name(self, line):
# точно обрезаем вручную
prefix = 'Progress: "'
suffix = '"'
if line.startswith(prefix) and line.endswith(suffix):
return line[len(prefix):-1]
return None
def _is_progress_line(self, line):
return re.match(r'^Progress:\s*\d+\s*/\s*\d+$', line) is not None
def _extract_progress(self, line):
match = re.match(r'^Progress:\s*(\d+)\s*/\s*(\d+)$', line)
if match:
return int(match.group(1)), int(match.group(2))
return None, None
def flush(self):
if self._buffer:
line = self._buffer.strip()
if not self._is_progress_line(line) and not self._extract_bar_name(line):
self.text_written.emit(line)
self._buffer = ""
class ProcessOutputWindow(QDialog):
def __init__(self, proj_path, makefile_path, xml_path, on_done_callback=None, parent=None):
super().__init__(parent)
self.setWindowTitle("Поиск переменных...")
self.resize(600, 480)
self.setModal(True)
self.setAttribute(Qt.WA_DeleteOnClose)
self.proj_path = proj_path
self.makefile_path = makefile_path
self.xml_path = xml_path
self._on_done_callback = on_done_callback
self.layout = QVBoxLayout(self)
self.output_edit = QTextEdit()
self.output_edit.setReadOnly(True)
self.layout.addWidget(self.output_edit)
self.progress_label = QLabel("Progress:")
self.layout.addWidget(self.progress_label)
self.progress_bar = QProgressBar()
self.progress_bar.setMinimum(0)
self.progress_bar.setValue(0)
self.layout.addWidget(self.progress_bar)
self.btn_close = QPushButton("Закрыть")
self.btn_close.setEnabled(False)
self.layout.addWidget(self.btn_close)
self.btn_close.clicked.connect(self.__handle_done)
self.queue = None
self.proc = None
def start_scan(self):
self.queue = multiprocessing.Queue()
self.proc = multiprocessing.Process(
target=run_scan_process,
args=(self.proj_path, self.makefile_path, self.xml_path, self.queue),
daemon=True)
self.proc.start()
self.timer = QTimer(self)
self.timer.timeout.connect(self.poll_queue)
self.timer.start(100)
self.show()
def poll_queue(self):
try:
while True:
msg = self.queue.get_nowait()
if msg is None:
# Конец процесса
self.btn_close.setEnabled(True)
self.append_text("\n--- Анализ завершён ---")
self.timer.stop()
return
# Пытаемся разобрать JSON-сообщение
if isinstance(msg, str) and msg.startswith("PROGRESS_MSG:"):
try:
data = json.loads(msg[len("PROGRESS_MSG:"):])
self.update_progress(data["bar_name"], data["current"], data["total"])
except Exception:
# Если не удалось распарсить, выводим как текст
self.append_text(msg)
else:
self.append_text(msg)
except Exception:
pass # Очередь пустая
def append_text(self, text):
cursor = self.output_edit.textCursor()
cursor.movePosition(QTextCursor.End)
if not text.endswith('\n'):
text += '\n'
cursor.insertText(text)
self.output_edit.setTextCursor(cursor)
self.output_edit.ensureCursorVisible()
def update_progress(self, bar_name, current, total):
self.progress_label.setText(f"{bar_name}")
self.progress_bar.setMaximum(total)
self.progress_bar.setValue(current)
def __handle_done(self):
self.close()
def closeEvent(self, event):
if self.proc and self.proc.is_alive():
self.proc.terminate()
self.proc.join()
self.btn_close.setEnabled(True)
self.append_text("Сканирование прервано.")
def run_scan_process(proj_path, makefile_path, xml_path, queue):
class QueueWriter(io.TextIOBase):
def __init__(self):
self._buffer = ""
self._current_bar_name = None
def write(self, txt):
self._buffer += txt
while '\n' in self._buffer:
line, self._buffer = self._buffer.split('\n', 1)
# Обработка прогресса
if line.startswith('Progress: "') and line.endswith('"'):
# Название прогресс-бара
bar_name = line[len('Progress: "'):-1]
self._current_bar_name = bar_name
elif re.match(r'^Progress:\s*\d+\s*/\s*\d+$', line):
m = re.match(r'^Progress:\s*(\d+)\s*/\s*(\d+)$', line)
if m:
current = int(m.group(1))
total = int(m.group(2))
bar_name = self._current_bar_name or "Progress"
# Отправляем специальное сообщение в очередь в формате JSON
msg = {
"bar_name": bar_name,
"current": current,
"total": total
}
queue.put("PROGRESS_MSG:" + json.dumps(msg))
else:
# Обычный вывод
queue.put(line)
def flush(self):
if self._buffer.strip():
queue.put(self._buffer.strip())
self._buffer = ""
sys.stdout = QueueWriter()
sys.stderr = sys.stdout
try:
run_scan(proj_path, makefile_path, xml_path)
except Exception as e:
queue.put(f"[ОШИБКА] {e}")
finally:
queue.put(None) # сигнал окончания

View File

@@ -1,29 +1,69 @@
# build command
# pyinstaller --onefile scanVars.py --add-binary "F:\Work\Projects\TMS\TMS_new_bus\Src\DebugTools/build/libclang.dll;." --distpath . --workpath ./build --specpath ./build
# pyinstaller --onefile scan_vars.py --add-binary "F:\Work\Projects\TMS\TMS_new_bus\Src\DebugTools/build/libclang.dll;." --distpath . --workpath ./build --specpath ./build
# start script
# scanVars.exe F:\Work\Projects\TMS\TMS_new_bus\ F:\Work\Projects\TMS\TMS_new_bus\Debug\makefile
# scan_vars.exe F:\Work\Projects\TMS\TMS_new_bus\ F:\Work\Projects\TMS\TMS_new_bus\Debug\makefile
import os
import sys
import re
import clang.cindex
from clang import cindex
import xml.etree.ElementTree as ET
from clang.cindex import Config
import lxml.etree as ET
from xml.dom import minidom
from parseMakefile import parse_makefile
from makefile_parser import parse_project
from collections import deque
import argparse
import myXML
BITFIELD_WIDTHS = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}
def is_frozen():
# Для Nuitka --onefile
return getattr(sys, 'frozen', False)
def get_base_path():
if is_frozen():
# В Nuitka onefile распаковывается в папку с самим exe во временной директории
return os.path.dirname(sys.executable)
else:
# Режим разработки
return os.path.dirname(os.path.abspath(__file__))
# Укажи полный путь к libclang.dll — поменяй на свой путь или оставь относительный
dll_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "build/libclang.dll")
def print_embedded_dlls():
# Папка временной распаковки для onefile приложений Nuitka/PyInstaller
base_path = get_base_path()
print(f"Scanning DLLs in temporary folder: {base_path}")
if hasattr(sys, '_MEIPASS'):
dll_path = os.path.join(sys._MEIPASS, "libclang.dll")
cindex.Config.set_library_file(dll_path)
dlls = []
for root, dirs, files in os.walk(base_path):
for file in files:
if file.lower().endswith('.dll'):
full_path = os.path.join(root, file)
rel_path = os.path.relpath(full_path, base_path)
dlls.append(rel_path)
if dlls:
print("DLL files found:")
for d in dlls:
print(" -", d)
else:
print("No DLL files found in temporary folder.")
# Вызов при старте
print_embedded_dlls()
base_path = get_base_path()
print("Base path:", base_path)
# Указываем полный путь к libclang.dll внутри распакованного каталога
dll_path = os.path.join(base_path, "libclang.dll")
if os.path.exists(dll_path):
print("Loading libclang.dll from:", dll_path)
Config.set_library_file(dll_path)
else:
cindex.Config.set_library_file(r"build\libclang.dll") # путь для запуска без упаковки
print("ERROR: libclang.dll not found at", dll_path)
index = cindex.Index.create()
PRINT_LEVEL = 2
@@ -78,11 +118,11 @@ def get_canonical_typedef_file(var_type, include_dirs):
break
return None
def analyze_variables_across_files(c_files, h_files, include_dirs):
def analyze_variables_across_files(c_files, h_files, include_dirs, global_defs):
optional_printf(PRINT_STATUS, "Starting analysis of variables across files...")
index = clang.cindex.Index.create()
args = [f"-I{inc}" for inc in include_dirs]
define_args = [f"-D{d}" for d in global_defs]
args = [f"-I{inc}" for inc in include_dirs] + define_args
unique_vars = {} # имя переменной → словарь с инфой
h_files_needed = set()
vars_need_extern = {} # имя переменной → словарь без поля 'extern'
@@ -114,6 +154,7 @@ def analyze_variables_across_files(c_files, h_files, include_dirs):
# Проверяем, начинается ли имя с "_" и содержит заглавные буквы или служебные символы
return bool(re.match(r"^_[_A-Z]", var_name))
if node.kind == clang.cindex.CursorKind.VAR_DECL:
if node.semantic_parent.kind == clang.cindex.CursorKind.TRANSLATION_UNIT:
is_extern = (node.storage_class == clang.cindex.StorageClass.EXTERN)
@@ -130,7 +171,12 @@ def analyze_variables_across_files(c_files, h_files, include_dirs):
return # игнорируем только явно известные служебные переменные
if node.spelling == 'HUGE': # еще одна служеюная, которую хз как выделять
return
if 'Drivers' in node.location.file.name:
return
if 'uint' in node.spelling:
a = 1
# Проверяем, является ли тип указателем на функцию
# Признак: в типе есть '(' и ')' и '*', например: "void (*)(int)"
if "(" in var_type and "*" in var_type and ")" in var_type:
@@ -163,7 +209,9 @@ def analyze_variables_across_files(c_files, h_files, include_dirs):
return vars_in_file
optional_printf(PRINT_STATUS, "Parsing header files (.h)...")
for h in h_files:
optional_printf(PRINT_STATUS, 'Progress: "Parsing variables from headers..."')
total_h = len(h_files)
for i, h in enumerate(h_files, 1):
vars_in_h = parse_file(h)
for v in vars_in_h:
name = v["name"]
@@ -174,9 +222,12 @@ def analyze_variables_across_files(c_files, h_files, include_dirs):
"static": v["static"],
"file": v["file"]
}
optional_printf(PRINT_STATUS, f"Progress: {i}/{total_h}")
optional_printf(PRINT_STATUS, "Parsing source files (.c)...")
for c in c_files:
optional_printf(PRINT_STATUS, 'Progress: "Parsing variables from sources files..."')
total_c = len(c_files)
for i, c in enumerate(c_files, 1):
vars_in_c = parse_file(c)
for v in vars_in_c:
name = v["name"]
@@ -194,9 +245,12 @@ def analyze_variables_across_files(c_files, h_files, include_dirs):
"static": v["static"],
"file": v["file"]
}
optional_printf(PRINT_STATUS, f"Progress: {i}/{total_c}")
optional_printf(PRINT_STATUS, "Checking which variables need explicit extern declaration...")
for name, info in unique_vars.items():
optional_printf(PRINT_STATUS, 'Progress: "Checking extern declarations..."')
total_vars = len(unique_vars)
for i, (name, info) in enumerate(unique_vars.items(), 1):
if not info["extern"] and not info["static"] and info["file"].endswith('.c'):
extern_declared = False
for h in h_files_needed:
@@ -208,6 +262,7 @@ def analyze_variables_across_files(c_files, h_files, include_dirs):
"type": info["type"],
"file": info["file"]
}
optional_printf(PRINT_STATUS, f"Progress: {i}/{total_vars}")
optional_printf(PRINT_STATUS, "Analysis complete.")
optional_printf(PRINT_STATUS, f"\tTotal unique variables found: {len(unique_vars)}")
@@ -246,6 +301,8 @@ def strip_ptr_and_array(typename):
return typename
def analyze_typedefs_and_struct(typedefs, structs):
optional_printf(PRINT_STATUS, "Resolving typedefs and expanding struct field types...")
@@ -314,9 +371,21 @@ def analyze_typedefs_and_struct(typedefs, structs):
substituted_fields[fname] = resolved_type
substituted_structs[resolved_sname] = substituted_fields """
# Раскрываем typedef'ы
optional_printf(PRINT_STATUS, 'Progress: "Resolving typedefs..."')
total_typedefs = len(typedefs)
resolved_typedefs = {}
for i, tname in enumerate(typedefs, 1):
resolved = resolve_typedef_rec(tname)
resolved_typedefs[tname] = resolved
optional_printf(4, f"\tTypedef {tname} resolved")
optional_printf(PRINT_STATUS, f"Progress: {i}/{total_typedefs}")
# Теперь раскрываем вложенные структуры
optional_printf(PRINT_STATUS, 'Progress: "Resolving structs..."')
total_structs = len(structs)
resolved_structs = {}
for sname, fields in structs.items():
for i, (sname, fields) in enumerate(structs.items(), 1):
if "(unnamed" in sname:
optional_printf(4, f" Skipping anonymous struct/union: {sname}")
continue
@@ -325,13 +394,7 @@ def analyze_typedefs_and_struct(typedefs, structs):
resolved_fields = resolve_struct_fields(fields)
resolved_structs[sname] = resolved_fields
optional_printf(PRINT_DEBUG, f"\tStruct {sname} resolved")
# Раскрываем typedef'ы в отдельном шаге
resolved_typedefs = {}
for tname in typedefs:
resolved = resolve_typedef_rec(tname)
resolved_typedefs[tname] = resolved
optional_printf(4, f"\tTypedef {tname} resolved")
optional_printf(PRINT_STATUS, f"Progress: {i}/{total_structs}")
return resolved_typedefs, resolved_structs
@@ -367,10 +430,28 @@ def contains_anywhere_in_node(node, target: str) -> bool:
return False
def analyze_typedefs_and_structs_across_files(c_files, include_dirs):
def try_guess_std_include():
# Популярные места, где может лежать stdint.h
guesses = [
r"C:\Keil_v5\ARM\ARMCLANG\include",
r"C:\Program Files (x86)\GNU Arm Embedded Toolchain",
r"C:\Program Files (x86)\Arm GNU Toolchain"
]
found = []
for base in guesses:
for root, dirs, files in os.walk(base):
if "stdint.h" in files:
found.append(root)
return found
def analyze_typedefs_and_structs_across_files(c_files, include_dirs, global_defs):
optional_printf(PRINT_STATUS, "Starting analysis of typedefs and structs across files...")
index = clang.cindex.Index.create()
args = [f"-I{inc}" for inc in include_dirs]
define_args = [f"-D{d}" for d in global_defs]
extra_std_include_dirs = try_guess_std_include()
args = [f"-I{inc}" for inc in include_dirs] + extra_std_include_dirs + define_args
unique_typedefs_raw = {}
unique_structs_raw = {}
@@ -397,7 +478,6 @@ def analyze_typedefs_and_structs_across_files(c_files, include_dirs):
raw_name = node.spelling
normalized_name = normalize_type_name(raw_name)
# struct_name всегда с префиксом
if node.spelling and "unnamed" not in normalized_name:
struct_name = f"{prefix}{normalized_name}"
@@ -439,7 +519,9 @@ def analyze_typedefs_and_structs_across_files(c_files, include_dirs):
visit(tu.cursor)
return typedefs, structs
for c_file in c_files:
optional_printf(PRINT_STATUS, 'Progress: "Resolving structs and typedefs..."')
total_files = len(c_files)
for i, c_file in enumerate(c_files, 1):
typedefs_in_file, structs_in_file = parse_file(c_file)
for name, underlying in typedefs_in_file.items():
if name not in unique_typedefs_raw:
@@ -447,6 +529,7 @@ def analyze_typedefs_and_structs_across_files(c_files, include_dirs):
for sname, fields in structs_in_file.items():
if sname not in unique_structs_raw:
unique_structs_raw[sname] = fields
optional_printf(PRINT_STATUS, f"Progress: {i}/{total_files}")
# Теперь раскроем typedef и структуры, учитывая вложения
resolved_typedefs, resolved_structs = analyze_typedefs_and_struct(unique_typedefs_raw, unique_structs_raw)
@@ -458,31 +541,6 @@ def analyze_typedefs_and_structs_across_files(c_files, include_dirs):
return resolved_typedefs, resolved_structs
def safe_parse_xml(xml_path):
"""
Безопасно парсит XML-файл.
Возвращает кортеж (root, tree) или (None, None) при ошибках.
"""
if not xml_path or not os.path.isfile(xml_path):
print(f"Файл '{xml_path}' не найден или путь пустой")
return None, None
try:
if os.path.getsize(xml_path) == 0:
return None, None
tree = ET.parse(xml_path)
root = tree.getroot()
return root, tree
except ET.ParseError as e:
print(f"Ошибка парсинга XML файла '{xml_path}': {e}")
return None, None
except Exception as e:
print(f"Неожиданная ошибка при чтении XML файла '{xml_path}': {e}")
return None, None
def read_vars_from_xml(xml_path):
xml_full_path = os.path.normpath(xml_path)
vars_data = {}
@@ -490,7 +548,7 @@ def read_vars_from_xml(xml_path):
if not os.path.exists(xml_full_path):
return vars_data # пусто, если файла нет
root, tree = safe_parse_xml(xml_full_path)
root, tree = myXML.safe_parse_xml(xml_full_path)
if root is None:
return vars_data
@@ -512,7 +570,7 @@ def read_vars_from_xml(xml_path):
'shortname': var_elem.findtext('shortname', name),
'pt_type': var_elem.findtext('pt_type', ''),
'iq_type': var_elem.findtext('iq_type', ''),
'return_type': var_elem.findtext('return_type', 'int'),
'return_type': var_elem.findtext('return_type', 't_iq_none'),
'type': var_elem.findtext('type', 'unknown'),
'file': var_elem.findtext('file', ''),
'extern': get_bool('extern'),
@@ -521,9 +579,19 @@ def read_vars_from_xml(xml_path):
return vars_data
def make_relative_if_possible(path, base):
if not path:
return ''
if not os.path.isabs(path):
return path # уже относительный
try:
rel = os.path.relpath(path, base)
return rel
except ValueError as e:
print(f"[WARNING] relpath error between '{path}' and '{base}': {e}")
return path # оставляем абсолютным
def generate_xml_output(proj_path, xml_path, unique_vars, h_files_needed, vars_need_extern, structs_xml_path=None, makefile_path=None):
xml_full_path = os.path.normpath(xml_path)
# Проверяем, существует ли файл, только тогда читаем из него
@@ -531,12 +599,22 @@ def generate_xml_output(proj_path, xml_path, unique_vars, h_files_needed, vars_n
if os.path.isfile(xml_full_path):
existing_vars_data = read_vars_from_xml(xml_full_path)
# --- Новый блок: формируем атрибуты корневого тега ---
analysis_attrs = {"proj_path": proj_path}
# --- Новый блок: формируем атрибуты корневого тега с относительными путями ---
proj_path = os.path.abspath(proj_path)
analysis_attrs = {
"proj_path": proj_path.replace("\\", "/")
}
if makefile_path:
analysis_attrs["makefile_path"] = makefile_path
rel_makefile = myXML.make_relative_path(makefile_path, proj_path)
if not os.path.isabs(rel_makefile):
analysis_attrs["makefile_path"] = rel_makefile.replace("\\", "/")
if structs_xml_path:
analysis_attrs["structs_path"] = structs_xml_path
rel_struct = myXML.make_relative_path(structs_xml_path, proj_path)
if not os.path.isabs(rel_struct):
analysis_attrs["structs_path"] = rel_struct.replace("\\", "/")
root = ET.Element("analysis", attrib=analysis_attrs)
@@ -555,7 +633,7 @@ def generate_xml_output(proj_path, xml_path, unique_vars, h_files_needed, vars_n
'shortname': info.get('shortname', name),
'pt_type': info.get('pt_type', ''),
'iq_type': info.get('iq_type', ''),
'return_type': info.get('return_type', 'int'),
'return_type': info.get('return_type', 't_iq_none'),
'type': info.get('type', 'unknown'),
'file': info.get('file', ''),
'extern': info.get('extern', False),
@@ -574,10 +652,10 @@ def generate_xml_output(proj_path, xml_path, unique_vars, h_files_needed, vars_n
ET.SubElement(var_elem, "shortname").text = info.get('shortname', name)
ET.SubElement(var_elem, "pt_type").text = info.get('pt_type', '')
ET.SubElement(var_elem, "iq_type").text = info.get('iq_type', '')
ET.SubElement(var_elem, "return_type").text = info.get('return_type', 'int')
ET.SubElement(var_elem, "return_type").text = info.get('return_type', 't_iq_none')
ET.SubElement(var_elem, "type").text = info.get('type', 'unknown')
rel_file = os.path.relpath(info.get('file', ''), proj_path) if info.get('file') else ''
rel_file = make_relative_if_possible(info.get('file', ''), proj_path).replace("\\", "/")
ET.SubElement(var_elem, "file").text = rel_file.replace("\\", "/") if rel_file else ''
ET.SubElement(var_elem, "extern").text = str(info.get('extern', False)).lower()
ET.SubElement(var_elem, "static").text = str(info.get('static', False)).lower()
@@ -598,12 +676,7 @@ def generate_xml_output(proj_path, xml_path, unique_vars, h_files_needed, vars_n
ET.SubElement(var_elem, "file").text = rel_file.replace("\\", "/")
# Форматирование с отступами
rough_string = ET.tostring(root, encoding="utf-8")
reparsed = minidom.parseString(rough_string)
pretty_xml = reparsed.toprettyxml(indent=" ")
with open(xml_full_path, "w", encoding="utf-8") as f:
f.write(pretty_xml)
myXML.fwrite(root, xml_full_path)
optional_printf(PRINT_STATUS, f"[XML] Variables saved to {xml_full_path}")
@@ -645,12 +718,7 @@ def write_typedefs_and_structs_to_xml(proj_path, xml_path, typedefs, structs):
ET.SubElement(typedefs_elem, "typedef", name=name, type=underlying)
# Преобразуем в красиво отформатированную XML-строку
rough_string = ET.tostring(root, encoding="utf-8")
reparsed = minidom.parseString(rough_string)
pretty_xml = reparsed.toprettyxml(indent=" ")
with open(xml_full_path, "w", encoding="utf-8") as f:
f.write(pretty_xml)
myXML.fwrite(root, xml_full_path)
print(f"[XML] Typedefs and structs saved to: {xml_full_path}")
@@ -812,10 +880,10 @@ Usage example:
print(f"Error: Makefile path '{makefile_path}' does not exist.")
sys.exit(1)
c_files, h_files, include_dirs = parse_makefile(makefile_path)
c_files, h_files, include_dirs, global_defs = parse_project(makefile_path, proj_path)
vars, includes, externs = analyze_variables_across_files(c_files, h_files, include_dirs)
typedefs, structs = analyze_typedefs_and_structs_across_files(c_files, include_dirs)
vars, includes, externs = analyze_variables_across_files(c_files, h_files, include_dirs, global_defs)
typedefs, structs = analyze_typedefs_and_structs_across_files(c_files, include_dirs, global_defs)
vars = dict(sorted(vars.items()))
includes = get_sorted_headers(c_files, includes, include_dirs)
@@ -855,10 +923,10 @@ def run_scan(proj_path, makefile_path, output_xml, verbose=2):
if not os.path.isfile(makefile_path):
raise FileNotFoundError(f"Makefile path '{makefile_path}' does not exist.")
c_files, h_files, include_dirs = parse_makefile(makefile_path)
c_files, h_files, include_dirs, global_defs = parse_project(makefile_path, proj_path)
vars, includes, externs = analyze_variables_across_files(c_files, h_files, include_dirs)
typedefs, structs = analyze_typedefs_and_structs_across_files(c_files, include_dirs)
vars, includes, externs = analyze_variables_across_files(c_files, h_files, include_dirs, global_defs)
typedefs, structs = analyze_typedefs_and_structs_across_files(c_files, include_dirs, global_defs)
vars = dict(sorted(vars.items()))
includes = get_sorted_headers(c_files, includes, include_dirs)
@@ -866,9 +934,14 @@ def run_scan(proj_path, makefile_path, output_xml, verbose=2):
typedefs = dict(sorted(typedefs.items()))
structs = dict(sorted(structs.items()))
print('Progress: "Writting XML..."')
print("Progress: 0/2")
print("[XML] Creating structs.xml...")
structs_xml = os.path.join(os.path.dirname(output_xml), "structs.xml")
write_typedefs_and_structs_to_xml(proj_path, structs_xml, typedefs, structs)
print("Progress: 1/2")
print("[XML] Creating vars.xml...")
generate_xml_output(proj_path, output_xml, vars, includes, externs, structs_xml, makefile_path)
print('Progress: "Done"')
print("Progress: 2/2")

View File

@@ -1,250 +0,0 @@
import sys
import os
import re
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 *
from generateVars import *
def make_absolute_path(path, base_path):
if not os.path.isabs(path) and os.path.isdir(base_path):
try:
return os.path.abspath(os.path.join(base_path, path))
except Exception:
pass # На случай сбоя в os.path.join или abspath
elif os.path.isabs(path):
return os.path.abspath(path)
else:
return path
def make_relative_path(abs_path, base_path):
abs_path = os.path.abspath(abs_path)
base_path = os.path.abspath(base_path)
# Разбиваем на списки директорий
abs_parts = abs_path.split(os.sep)
base_parts = base_path.split(os.sep)
# Проверяем, является ли base_path настоящим префиксом пути (по папкам)
if abs_parts[:len(base_parts)] == base_parts:
rel_parts = abs_parts[len(base_parts):]
return "/".join(rel_parts)
# Иначе пробуем relpath
try:
return os.path.relpath(abs_path, base_path).replace("\\", "/")
except Exception:
return abs_path.replace("\\", "/")
def parse_vars(filename, typedef_map=None):
root, tree = safe_parse_xml(filename)
if root is None:
return []
if typedef_map is None:
typedef_map = {}
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', '')
var_type = var.findtext('type', 'unknown').strip()
# Вычисляем pt_type и iq_type
pt_type = var.findtext('pt_type')
if not pt_type:
pt_type = map_type_to_pt(var_type, name, typedef_map)
iq_type = var.findtext('iq_type')
if not iq_type:
iq_type = get_iq_define(var_type)
vars_list.append({
'name': name,
'show_var': var.findtext('show_var', 'false'),
'enable': var.findtext('enable', 'false'),
'shortname': var.findtext('shortname', name),
'pt_type': pt_type,
'iq_type': iq_type,
'return_type': var.findtext('return_type', 'int'),
'type': var_type,
'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):
root, tree = safe_parse_xml(filename)
if root is None:
return {}, {}
structs = {}
typedef_map = {}
def parse_struct_element(elem):
fields = {}
for field in elem.findall("field"):
fname = field.attrib.get("name")
ftype = field.attrib.get("type", "")
# Проверка на вложенную структуру
nested_struct_elem = field.find("struct")
if nested_struct_elem is not None:
# Рекурсивно парсим вложенную структуру и вставляем её как подсловарь
nested_fields = parse_struct_element(nested_struct_elem)
# Оборачиваем в dict с ключом 'type' для хранения типа из XML
fields[fname] = {
'type': ftype, # здесь тип, например "BENDER_ERROR"
**nested_fields # развёрнутые поля вложенной структуры
}
else:
# Обычное поле
fields[fname] = ftype
return fields
structs_elem = root.find("structs")
if structs_elem is not None:
for struct in structs_elem.findall("struct"):
name = struct.attrib.get("name")
if name and name not in structs:
fields = parse_struct_element(struct)
structs[name] = fields
# typedefs без изменений
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
def safe_parse_xml(xml_path):
"""
Безопасно парсит XML-файл.
Возвращает кортеж (root, tree) или (None, None) при ошибках.
"""
if not xml_path or not os.path.isfile(xml_path):
#print(f"Файл '{xml_path}' не найден или путь пустой")
return None, None
try:
if os.path.getsize(xml_path) == 0:
return None, None
tree = ET.parse(xml_path)
root = tree.getroot()
return root, tree
except ET.ParseError as e:
print(f"Ошибка парсинга XML файла '{xml_path}': {e}")
return None, None
except Exception as e:
print(f"Неожиданная ошибка при чтении XML файла '{xml_path}': {e}")
return None, None
def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, depth=0):
if depth > 10:
return []
# Если type_str — словарь структуры
if isinstance(type_str, dict):
fields = type_str
else:
base_type = strip_ptr_and_array(type_str)
fields = structs.get(base_type)
if not isinstance(fields, dict):
return []
children = []
for field_name, field_value in fields.items():
# Пропускаем поле 'type', оно служит для хранения имени типа
if field_name == 'type':
continue
full_name = f"{prefix}.{field_name}"
if isinstance(field_value, dict):
# Если вложенная структура — берем её имя типа из поля 'type' или пустую строку
type_name = field_value.get('type', '')
child = {
'name': full_name,
'type': type_name,
'pt_type': '',
'file': var_attrs.get('file'),
'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'),
}
# Рекурсивно раскрываем вложенные поля
subchildren = expand_struct_recursively(full_name, field_value, structs, typedefs, var_attrs, depth + 1)
if subchildren:
child['children'] = subchildren
else:
# Простое поле — строка типа
# Пропускаем указатели на функции
if isinstance(field_value, str) and "(" in field_value and "*" in field_value and ")" in field_value:
continue
child = {
'name': full_name,
'type': field_value,
'pt_type': '',
'file': var_attrs.get('file'),
'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'),
}
children.append(child)
return children
def expand_vars(vars_list, structs, typedefs):
"""
Раскрывает структуры и массивы структур в деревья.
"""
expanded = []
for var in vars_list:
pt_type = var.get('pt_type', '')
raw_type = var.get('type', '')
base_type = strip_ptr_and_array(raw_type)
fields = structs.get(base_type)
if pt_type.startswith('pt_arr_') and isinstance(fields, dict):
new_var = var.copy()
new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var)
expanded.append(new_var)
elif pt_type == 'pt_struct' and isinstance(fields, dict):
new_var = var.copy()
new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var)
expanded.append(new_var)
elif pt_type == 'pt_union' and isinstance(fields, dict):
new_var = var.copy()
new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var)
expanded.append(new_var)
else:
expanded.append(var)
return expanded

View File

@@ -1,753 +0,0 @@
# build command
# pyinstaller --onefile --name DebugVarEdit --add-binary "build/libclang.dll;build" --distpath ./ --workpath ./build_temp --specpath ./build_temp setupVars_GUI.py
import sys
import os
import subprocess
import xml.etree.ElementTree as ET
from generateVars import type_map
from enum import IntEnum
import threading
from scanVars import run_scan
from generateVars import run_generate
from setupVars import *
from VariableSelector import *
from PySide6.QtWidgets import (
QApplication, QWidget, QTableWidget, QTableWidgetItem,
QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton,
QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit,
QDialog, QTreeWidget, QTreeWidgetItem, QSizePolicy
)
from PySide6.QtGui import QTextCursor, QKeyEvent
from PySide6.QtCore import Qt, QProcess, QObject, Signal, QTimer
class rows(IntEnum):
No = 0
include = 1
name = 2
type = 3
pt_type = 4
iq_type = 5
ret_type = 6
short_name = 7
class EmittingStream(QObject):
text_written = Signal(str)
def __init__(self):
super().__init__()
self._buffer = ""
def write(self, text):
self._buffer += text
while '\n' in self._buffer:
line, self._buffer = self._buffer.split('\n', 1)
# Отправляем строку без '\n'
self.text_written.emit(line)
def flush(self):
if self._buffer:
self.text_written.emit(self._buffer)
self._buffer = ""
class ProcessOutputWindowDummy(QWidget):
def __init__(self, on_done_callback):
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.layout.addWidget(self.btn_close)
self.btn_close.clicked.connect(self.__handle_done)
self._on_done_callback = on_done_callback
def __handle_done(self):
if self._on_done_callback:
self._on_done_callback()
self.close()
def append_text(self, text):
cursor = self.output_edit.textCursor()
cursor.movePosition(QTextCursor.End)
for line in text.splitlines():
self.output_edit.append(line)
self.output_edit.setTextCursor(cursor)
self.output_edit.ensureCursorVisible()
# 3. UI: таблица с переменными
class VarEditor(QWidget):
def __init__(self):
super().__init__()
self.vars_list = []
self.structs = {}
self.typedef_map = {}
self.proj_path = None
self.xml_path = None
self.makefile_path = None
self.structs_path = None
self.output_path = None
self._updating = False # Флаг блокировки рекурсии
self._resizing = False # флаг блокировки повторного вызова
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()
self.xml_output_edit.returnPressed.connect(self.update)
self.xml_output_edit.textChanged.connect(self.__on_xml_path_changed)
xml_layout.addWidget(self.xml_output_edit)
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()
self.proj_path_edit.returnPressed.connect(self.update)
self.proj_path_edit.textChanged.connect(self.__on_proj_path_changed)
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()
self.makefile_edit.returnPressed.connect(self.update)
self.makefile_edit.textChanged.connect(self.__on_makefile_path_changed)
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), 8)
self.table.setHorizontalHeaderLabels([
'№', # новый столбец
'En',
'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)
# Кнопка добавления переменных
self.btn_add_vars = QPushButton("Add Variables")
self.btn_add_vars.clicked.connect(self.__open_variable_selector)
# Основной 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(self.btn_add_vars)
layout.addLayout(source_output_layout)
layout.addWidget(btn_save)
header = self.table.horizontalHeader()
# Для остальных колонок — растяжение (Stretch), чтобы они заняли всю оставшуюся ширину
for col in range(self.table.columnCount()):
if col == self.table.columnCount() - 1:
header.setSectionResizeMode(col, QHeaderView.Stretch)
else:
header.setSectionResizeMode(col, QHeaderView.Interactive)
parent_widget = self.table.parentWidget()
if parent_widget:
w = parent_widget.width()
h = parent_widget.height()
viewport_width = self.table.viewport().width()
# Сделаем колонки с номерами фиксированной ширины
self.table.setColumnWidth(rows.No, 30)
self.table.setColumnWidth(rows.include, 30)
self.table.setColumnWidth(rows.pt_type, 85)
self.table.setColumnWidth(rows.iq_type, 85)
self.table.setColumnWidth(rows.ret_type, 85)
self.table.setColumnWidth(rows.name, 300)
self.table.setColumnWidth(rows.type, 100)
self.table.horizontalHeader().sectionResized.connect(self.on_section_resized)
self.setLayout(layout)
def on_section_resized(self, logicalIndex, oldSize, newSize):
if self._resizing:
return # предотвращаем рекурсию
min_width = 50
delta = newSize - oldSize
right_index = logicalIndex + 1
if right_index >= self.table.columnCount():
# Если правая колока - нет соседа, ограничиваем минимальную ширину
if newSize < min_width:
self._resizing = True
self.table.setColumnWidth(logicalIndex, min_width)
self._resizing = False
return
self._resizing = True
try:
right_width = self.table.columnWidth(right_index)
new_right_width = right_width - delta
# Если соседняя колонка станет уже минимальной - подкорректируем левую
if new_right_width < min_width:
new_right_width = min_width
newSize = oldSize + (right_width - min_width)
self.table.setColumnWidth(logicalIndex, newSize)
self.table.setColumnWidth(right_index, new_right_width)
finally:
self._resizing = False
def get_xml_path(self):
xml_path = self.xml_output_edit.text().strip()
return xml_path
def get_proj_path(self):
proj_path = self.proj_path_edit.text().strip()
return proj_path
def get_makefile_path(self):
proj_path = self.get_proj_path()
makefile_path = make_absolute_path(self.makefile_edit.text().strip(), proj_path)
return makefile_path
def get_struct_path(self):
proj_path = self.get_proj_path()
xml_path = self.get_xml_path()
root, tree = safe_parse_xml(xml_path)
if root is None:
return
# --- 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):
structs_path = structs_path_full
else:
structs_path = None
return structs_path
def get_output_path(self):
output_path = os.path.abspath(self.source_output_edit.text().strip())
return output_path
def update_all_paths(self):
self.proj_path = self.get_proj_path()
self.xml_path = self.get_xml_path()
self.makefile_path = self.get_makefile_path()
self.structs_path = self.get_struct_path()
self.output_path = self.get_output_path()
def update_vars_data(self):
self.update_all_paths()
if not self.proj_path or not self.xml_path:
QMessageBox.warning(self, "Ошибка", "Укажите пути проекта и XML.")
return
if not os.path.isfile(self.makefile_path):
QMessageBox.warning(self, "Ошибка", f"Makefile не найден:\n{self.makefile_path}")
return
# Создаём окно с кнопкой "Готово"
self.proc_win = ProcessOutputWindowDummy(self.__after_scanvars_finished)
self.proc_win.show()
self.emitting_stream = EmittingStream()
self.emitting_stream.text_written.connect(self.proc_win.append_text)
def run_scan_wrapper():
try:
old_stdout = sys.stdout
sys.stdout = self.emitting_stream
run_scan(self.proj_path, self.makefile_path, self.xml_path)
except Exception as e:
self.emitting_stream.text_written.emit(f"\n[ОШИБКА] {e}")
finally:
sys.stdout = old_stdout
self.emitting_stream.text_written.emit("\n--- Анализ завершён ---")
self.proc_win.btn_close.setEnabled(True)
threading.Thread(target=run_scan_wrapper, daemon=True).start()
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)
self.update_all_paths()
if not self.proj_path or not self.xml_path or not self.output_path:
QMessageBox.warning(self, "Ошибка", "Заполните все пути: проект, XML и output.")
return
try:
run_generate(self.proj_path, self.xml_path, self.output_path)
QMessageBox.information(self, "Готово", "Файл debug_vars.c успешно сгенерирован.")
self.update()
except Exception as e:
QMessageBox.critical(self, "Ошибка при генерации", str(e))
def update(self):
if self._updating:
return # Уже в процессе обновления — выходим, чтобы избежать рекурсии
self._updating = True
self.update_all_paths()
try:
if self.xml_path and not os.path.isfile(self.xml_path):
return
try:
root, tree = safe_parse_xml(self.xml_path)
if root is None:
return
if not self.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):
self.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(self.proj_path):
QMessageBox.warning(
self,
"Внимание",
f"Указанный путь к проекту не существует:\n{self.proj_path}\n"
"Пожалуйста, исправьте путь в поле 'Project Path'."
)
if not self.makefile_path:
# --- makefile_path из атрибута ---
makefile_path = root.attrib.get('makefile_path', '').strip()
makefile_path_full = make_absolute_path(makefile_path, self.proj_path)
if makefile_path_full and os.path.isfile(makefile_path_full):
self.makefile_edit.setText(makefile_path)
else:
self.makefile_path = None
if not self.structs_path:
# --- structs_path из атрибута ---
structs_path = root.attrib.get('structs_path', '').strip()
structs_path_full = make_absolute_path(structs_path, self.proj_path)
if structs_path_full and os.path.isfile(structs_path_full):
self.structs_path = structs_path_full
self.structs, self.typedef_map = parse_structs(structs_path_full)
else:
self.structs_path = None
self.vars_list = parse_vars(self.xml_path, self.typedef_map)
self.update_table()
except Exception as e:
QMessageBox.warning(self, "Ошибка", f"Ошибка при чтении XML:\n{e}")
finally:
self._updating = False # Снимаем блокировку при выходе из функции
def __browse_proj_path(self):
dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку проекта")
if dir_path:
self.proj_path_edit.setText(dir_path)
self.proj_path = dir_path
if self.makefile_path and self.proj_path:
path = make_relative_path(self.makefile_path, self.proj_path)
self.makefile_edit.setText(path)
self.makefile_path = path
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.xml_path = file_path
def keyPressEvent(self, event: QKeyEvent):
if event.key() == Qt.Key_Delete:
self.delete_selected_rows()
else:
super().keyPressEvent(event)
def __browse_makefile(self):
file_path, _ = QFileDialog.getOpenFileName(
self, "Выберите Makefile", filter="Makefile (makefile);;All Files (*)"
)
if file_path and self.proj_path:
path = make_relative_path(file_path, self.proj_path)
else:
path = file_path
self.makefile_edit.setText(path)
self.makefile_path = path
def __browse_source_output(self):
dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку для debug_vars.c")
if dir_path:
self.source_output_edit.setText(dir_path)
self.output_path = dir_path
else:
self.output_path = ''
def __on_xml_path_changed(self):
self.xml_path = self.get_xml_path()
self.update()
def __on_proj_path_changed(self):
self.proj_path = self.get_proj_path()
self.update()
def __on_makefile_path_changed(self):
self.makefile_path = self.get_makefile_path()
if self.makefile_path and self.proj_path:
path = make_relative_path(self.makefile_path, self.proj_path)
self.makefile_edit.setText(path)
self.update()
def __after_scanvars_finished(self):
self.update_all_paths()
if not os.path.isfile(self.xml_path):
QMessageBox.critical(self, "Ошибка", f"Файл не найден: {self.xml_path}")
return
try:
self.makefile_path = None
self.structs_path = None
self.proj_path = None
self.update()
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить переменные:\n{e}")
def delete_selected_rows(self):
selected_rows = sorted(set(index.row() for index in self.table.selectedIndexes()), reverse=True)
if not selected_rows:
return
# Удаляем из vars_list те, у кого show_var == true и имя совпадает
filtered_vars = [v for v in self.vars_list if v.get('show_var', 'false') == 'true']
for row in selected_rows:
if 0 <= row < len(filtered_vars):
var_to_remove = filtered_vars[row]
for v in self.vars_list:
if v['name'] == var_to_remove['name']:
v['show_var'] = 'false'
break
self.update_table()
def __open_variable_selector(self):
if not self.vars_list:
QMessageBox.warning(self, "Нет переменных", "Сначала загрузите или обновите переменные.")
return
dlg = VariableSelectorDialog(self.vars_list, self.structs, self.typedef_map, self.xml_path, self)
if dlg.exec():
self.write_to_xml()
self.update()
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)]
filtered_vars = [v for v in self.vars_list if v.get('show_var', 'false') == 'true']
self.table.setRowCount(len(filtered_vars))
self.table.verticalHeader().setVisible(False)
for row, var in enumerate(filtered_vars):
# Добавляем номер строки в колонку No (0)
no_item = QTableWidgetItem(str(row))
no_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) # readonly
self.table.setItem(row, rows.No, no_item)
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 = var['pt_type'].replace('pt_', '')
if internal_type in self.display_type_options:
pt_type_combo.setCurrentText(internal_type)
else:
pt_type_combo.addItem(internal_type)
pt_type_combo.setCurrentText(internal_type)
self.table.setCellWidget(row, rows.pt_type, pt_type_combo)
iq_combo = QComboBox()
iq_combo.addItems(iq_types)
iq_type = var['iq_type'].replace('t_', '')
if iq_type in iq_types:
iq_combo.setCurrentText(iq_type)
else:
iq_combo.addItem(iq_type)
iq_combo.setCurrentText(iq_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.write_to_xml)
name_edit.textChanged.connect(self.write_to_xml)
pt_type_combo.currentTextChanged.connect(self.write_to_xml)
iq_combo.currentTextChanged.connect(self.write_to_xml)
ret_combo.currentTextChanged.connect(self.write_to_xml)
short_name_edit.textChanged.connect(self.write_to_xml)
self.write_to_xml()
def read_table(self):
vars_data = []
for row in range(self.table.rowCount()):
cb = self.table.cellWidget(row, rows.include)
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)
origin_item = self.table.item(row, rows.type)
vars_data.append({
'show_var': True,
'enable': cb.isChecked() if cb else False,
'name': name_edit.text() if name_edit else '',
'pt_type': 'pt_' + pt_type_combo.currentText() if pt_type_combo else '',
'iq_type': iq_combo.currentText() if iq_combo else '',
'return_type': ret_combo.currentText() if ret_combo else '',
'shortname': short_name_edit.text() if short_name_edit else '',
'type': origin_item.text() if origin_item else '',
})
return vars_data
def write_to_xml(self):
self.update_all_paths()
if not self.xml_path or not os.path.isfile(self.xml_path):
print("XML файл не найден или путь пустой")
return
if not self.proj_path or not os.path.isdir(self.proj_path):
print("Project path не найден или путь пустой")
return
if not self.makefile_path or not os.path.isfile(self.makefile_path):
print("makefile файл не найден или путь пустой")
return
try:
root, tree = safe_parse_xml(self.xml_path)
if root is None:
return
root.set("proj_path", self.proj_path.replace("\\", "/"))
if self.makefile_path and os.path.isfile(self.makefile_path):
rel_makefile = make_relative_path(self.makefile_path, self.proj_path)
root.set("makefile_path", rel_makefile)
if self.structs_path and os.path.isfile(self.structs_path):
rel_struct = make_relative_path(self.structs_path, self.proj_path)
root.set("structs_path", rel_struct)
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] = {
'file': var_elem.findtext('file', ''),
'extern': var_elem.findtext('extern', ''),
'static': var_elem.findtext('static', '')
}
# Читаем переменные из таблицы (активные/изменённые)
table_vars = {v['name']: v for v in self.read_table()}
# Все переменные (в том числе новые, которых нет в таблице)
all_vars_by_name = {v['name']: v for v in self.vars_list}
# Объединённый список переменных для записи
all_names = list(all_vars_by_name.keys())
for name in all_names:
v = all_vars_by_name[name]
v_table = table_vars.get(name)
var_elem = None
# Ищем уже существующий <var> в XML
for ve in vars_elem.findall('var'):
if ve.attrib.get('name') == name:
var_elem = ve
break
if var_elem is None:
var_elem = ET.SubElement(vars_elem, 'var', {'name': 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, 'show_var', v.get('show_var', 'false'))
set_sub_elem_text(var_elem, 'enable', v.get('enable', 'false'))
# Тут подтягиваем из таблицы, если есть, иначе из v
shortname_val = v_table['shortname'] if v_table and 'shortname' in v_table else v.get('shortname', '')
pt_type_val = v_table['pt_type'] if v_table and 'pt_type' in v_table else v.get('pt_type', '')
iq_type_val = v_table['iq_type'] if v_table and 'iq_type' in v_table else v.get('iq_type', '')
ret_type_val = v_table['return_type'] if v_table and 'return_type' in v_table else v.get('return_type', '')
set_sub_elem_text(var_elem, 'shortname', shortname_val)
set_sub_elem_text(var_elem, 'pt_type', pt_type_val)
set_sub_elem_text(var_elem, 'iq_type', iq_type_val)
set_sub_elem_text(var_elem, 'return_type', ret_type_val)
set_sub_elem_text(var_elem, 'type', v.get('type', ''))
# file/extern/static: из original_info, либо из v
file_val = v.get('file') or original_info.get(name, {}).get('file', '')
extern_val = v.get('extern') or original_info.get(name, {}).get('extern', '')
static_val = v.get('static') or original_info.get(name, {}).get('static', '')
set_sub_elem_text(var_elem, 'file', file_val)
set_sub_elem_text(var_elem, 'extern', extern_val)
set_sub_elem_text(var_elem, 'static', static_val)
ET.indent(tree, space=" ", level=0)
tree.write(self.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())

View File

@@ -0,0 +1,504 @@
"""
LowLevelSelectorWidget (refactored)
-----------------------------------
Версия, использующая VariableTableWidget вместо самодельной таблицы selected_vars_table.
Ключевые изменения:
* Вместо QTableWidget с 6 колонками теперь встраивается VariableTableWidget (8 колонок: №, En, Name, Origin Type, Base Type, IQ Type, Return Type, Short Name).
* Логика sync <-> self._all_available_vars перенесена в _on_var_table_changed() и _pull_from_var_table().
* Поддержка политики хранения типов:
- ptr_type: строковое имя (без префикса `pt_`).
- ptr_type_enum: числовой индекс (см. PT_ENUM_ORDER).
- Для совместимости с VariableTableWidget: поле `pt_type` = 'pt_<name>'.
- IQ / Return: аналогично (`iq_type` / `iq_type_enum`, `return_type` / `return_type_enum`).
* Функции получения выбранных переменных теперь читают данные из VariableTableWidget.
* Убраны неиспользуемые методы, связанные с прежней таблицей (комбо‑боксы и т.п.).
Как интегрировать:
1. Поместите этот файл рядом с module VariableTableWidget (см. импорт ниже). Если класс VariableTableWidget находится в том же файле — удалите строку импорта и используйте напрямую.
2. Убедитесь, что VariablesXML предоставляет методы get_all_vars_data() (list[dict]) и, при наличии, get_struct_map() -> dict[type_name -> dict[field_name -> field_type]]. Если такого метода нет, передаём пустой {} и автодополнение по структурам будет недоступно.
3. Отметьте переменные в VariableSelectorDialog (как и раньше) — он обновит self._all_available_vars. После закрытия диалога вызывается self._populate_var_table().
4. Для чтения выбранных переменных используйте get_selected_variables_and_addresses(); она вернёт список словарей в унифицированном формате.
Примечание о совместимости: VariableTableWidget работает с ключами `pt_type`, `iq_type`, `return_type` (строки с префиксами). Мы поддерживаем дублирование этих полей с «новыми» полями без префикса и enumзначениями.
"""
from __future__ import annotations
import sys
import re
import datetime
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple, Any
from PySide2 import QtCore, QtGui
from PySide2.QtWidgets import (
QWidget, QVBoxLayout, QPushButton, QLabel, QHBoxLayout, QFileDialog, QMessageBox,
QMainWindow, QApplication, QSizePolicy, QSpinBox, QGroupBox, QSplitter, QFormLayout
)
# Локальные импорты
from path_hints import PathHints
from generate_debug_vars import choose_type_map, type_map
from var_selector_window import VariableSelectorDialog
from allvars_xml_parser import VariablesXML
# Импортируем готовую таблицу
# ЗАМЕТКА: замените на реальное имя файла/модуля, если отличается.
from var_table import VariableTableWidget, rows as VT_ROWS # noqa: F401
# ------------------------------------------------------------ Enumerations --
# Порядок фиксируем на основании предыдущей версии. При необходимости расширьте.
PT_ENUM_ORDER = [
'unknown','int8','int16','int32','int64',
'uint8','uint16','uint32','uint64','float',
'struct','union'
]
IQ_ENUM_ORDER = [
'iq_none','iq','iq1','iq2','iq3','iq4','iq5','iq6',
'iq7','iq8','iq9','iq10','iq11','iq12','iq13','iq14',
'iq15','iq16','iq17','iq18','iq19','iq20','iq21','iq22',
'iq23','iq24','iq25','iq26','iq27','iq28','iq29','iq30'
]
PT_ENUM_VALUE: Dict[str, int] = {name: idx for idx, name in enumerate(PT_ENUM_ORDER)}
IQ_ENUM_VALUE: Dict[str, int] = {name: idx for idx, name in enumerate(IQ_ENUM_ORDER)}
PT_ENUM_NAME_FROM_VAL: Dict[int, str] = {v: k for k, v in PT_ENUM_VALUE.items()}
IQ_ENUM_NAME_FROM_VAL: Dict[int, str] = {v: k for k, v in IQ_ENUM_VALUE.items()}
# ------------------------------------------- Address / validation helpers --
HEX_ADDR_MASK = QtCore.QRegExp(r"0x[0-9A-Fa-f]{0,6}")
class HexAddrValidator(QtGui.QRegExpValidator):
def __init__(self, parent=None):
super().__init__(HEX_ADDR_MASK, parent)
@staticmethod
def normalize(text: str) -> str:
if not text:
return '0x000000'
try:
val = int(text,16)
except ValueError:
return '0x000000'
return f"0x{val & 0xFFFFFF:06X}"
class LowLevelSelectorWidget(QWidget):
variablePrepared = QtCore.Signal(dict)
xmlLoaded = QtCore.Signal(str)
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle('LowLevel Variable Selector')
self._xml: Optional[VariablesXML] = None
self._paths: List[str] = []
self._path_info: Dict[str, Tuple[int, str]] = {}
self._addr_index: Dict[int, Optional[str]] = {}
self._hints = PathHints()
self._all_available_vars: List[Dict[str, Any]] = []
self.dt = None
self.flat_vars = None
# --- NEW ---
self.btn_read_once = QPushButton("Read Once")
self.btn_start_polling = QPushButton("Start Polling")
self.spin_interval = QSpinBox()
self.spin_interval.setRange(50, 10000)
self.spin_interval.setValue(500)
self.spin_interval.setSuffix(" ms")
self._build_ui()
self._connect()
def _build_ui(self):
tab = QWidget()
main_layout = QVBoxLayout(tab)
# --- Variable Selector ---
g_selector = QGroupBox("Variable Selector")
selector_layout = QVBoxLayout(g_selector)
form_selector = QFormLayout()
# --- XML File chooser ---
file_layout = QHBoxLayout()
self.btn_load = QPushButton('Load XML...')
self.lbl_file = QLabel('<no file>')
self.lbl_file.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
file_layout.addWidget(self.btn_load)
file_layout.addWidget(self.lbl_file, 1)
form_selector.addRow("XML File:", file_layout)
# --- Interval SpinBox ---
self.spin_interval = QSpinBox()
self.spin_interval.setRange(50, 10000)
self.spin_interval.setValue(500)
self.spin_interval.setSuffix(" ms")
form_selector.addRow("Interval:", self.spin_interval)
selector_layout.addLayout(form_selector)
# --- Buttons ---
self.btn_read_once = QPushButton("Read Once")
self.btn_start_polling = QPushButton("Start Polling")
btn_layout = QHBoxLayout()
btn_layout.addWidget(self.btn_read_once)
btn_layout.addWidget(self.btn_start_polling)
selector_layout.addLayout(btn_layout)
# --- Table ---
g_table = QGroupBox("Table")
table_layout = QVBoxLayout(g_table)
self.btn_open_var_selector = QPushButton("Выбрать переменные...")
table_layout.addWidget(self.btn_open_var_selector)
self.var_table = VariableTableWidget(self, show_value_instead_of_shortname=1)
self.var_table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
table_layout.addWidget(self.var_table)
# --- Timestamp (moved here) ---
self.lbl_timestamp = QLabel('Timestamp: -')
table_layout.addWidget(self.lbl_timestamp)
# --- Splitter (Selector + Table) ---
v_split = QSplitter(QtCore.Qt.Vertical)
v_split.addWidget(g_selector)
v_split.addWidget(g_table)
v_split.setStretchFactor(0, 1)
v_split.setStretchFactor(1, 3)
main_layout.addWidget(v_split)
self.setLayout(main_layout)
def _connect(self):
self.btn_load.clicked.connect(self._on_load_xml)
self.btn_open_var_selector.clicked.connect(self._on_open_variable_selector)
# ------------------------------------------------------ XML loading ----
def _on_load_xml(self):
path, _ = QFileDialog.getOpenFileName(
self, 'Select variables XML', '', 'XML Files (*.xml);;All Files (*)')
if not path:
return
try:
self._xml = VariablesXML(path)
self.flat_vars = {v['name']: v for v in self._xml.flattened()}
# Получаем сырые данные по переменным
self._all_available_vars = self._xml.get_all_vars_data()
except Exception as e:
QMessageBox.critical(self, 'Parse error', f'Ошибка парсинга:\n{e}')
return
self.lbl_file.setText(path)
self.lbl_timestamp.setText(f'Timestamp: {self._xml.timestamp or "-"}')
self._populate_internal_maps_from_all_vars()
self._apply_timestamp_to_date()
self.xmlLoaded.emit(path)
self._log(f'Loaded {path}, variables={len(self._all_available_vars)})')
def _apply_timestamp_to_date(self):
if not (self._xml and self._xml.timestamp):
return
try:
# Пример: "Sat Jul 19 15:27:59 2025"
self.dt = datetime.datetime.strptime(self._xml.timestamp, "%a %b %d %H:%M:%S %Y")
except Exception as e:
print(f"Ошибка разбора timestamp '{self._xml.timestamp}': {e}")
# ------------------------------------------ Variable selector dialog ----
def _on_open_variable_selector(self):
if not self._xml:
QMessageBox.warning(self, 'No XML', 'Сначала загрузите XML файл.')
return
dialog = VariableSelectorDialog(
table=None, # не используем встроенную таблицу
all_vars=self._all_available_vars,
structs=None, # при необходимости подайте реальные структуры из XML
typedefs=None, # ...
xml_path=None, # по запросу пользователя xml_path = None
parent=self
)
if dialog.exec_() == dialog.Accepted:
# Диалог обновил self._all_available_vars напрямую
self._populate_internal_maps_from_all_vars()
self._populate_var_table()
self._log("Variable selection updated.")
# ----------------------------------------------------- Populate table ----
def _populate_var_table(self):
"""Отобразить переменные (show_var == 'true') в VariableTableWidget."""
if not self._all_available_vars:
self.var_table.setRowCount(0)
return
# Нормализуем все записи перед передачей таблице.
for var in self._all_available_vars:
self._normalize_var_record(var)
# Карта структур для автодополнения (если VariablesXML предоставляет)
try:
structs_map = self._xml.get_struct_map() if self._xml else {}
except AttributeError:
structs_map = {}
# populate() принимает: (vars_list, structs, on_change_callback)
self.var_table.populate(self._all_available_vars, structs_map, self._on_var_table_changed)
# -------------------------------------------------- Table change slot ----
def _on_var_table_changed(self, *args, **kwargs): # noqa: D401 (неиспользуемые)
"""Вызывается при любом изменении в VariableTableWidget.
Читаем данные из таблицы, мержим в self._all_available_vars (по имени),
пересобираем служебные индексы.
"""
updated = self.var_table.read_data() # list[dict]
# создаём индекс по имени из master списка
idx_by_name = {v.get('name'): v for v in self._all_available_vars if v.get('name')}
for rec in updated:
nm = rec.get('name')
if not nm:
continue
dst = idx_by_name.get(nm)
if not dst:
# Новая запись; добавляем базовые поля
dst = {
'name': nm,
'address': 0,
'file': '', 'extern': 'false', 'static': 'false',
}
self._all_available_vars.append(dst)
idx_by_name[nm] = dst
# перенести видимые поля
dst['show_var'] = str(bool(rec.get('show_var'))).lower()
dst['enable'] = str(bool(rec.get('enable'))).lower()
dst['shortname']= rec.get('shortname', nm)
dst['type'] = rec.get('type', dst.get('type',''))
# типы (строковые, с префиксами) -> нормализуем
pt_pref = rec.get('pt_type','pt_unknown') # 'pt_int16'
iq_pref = rec.get('iq_type','t_iq_none') # 't_iq10' etc.
rt_pref = rec.get('return_type', iq_pref)
self._assign_types_from_prefixed(dst, pt_pref, iq_pref, rt_pref)
# Пересобрать карты путей/адресов
self._populate_internal_maps_from_all_vars()
# --------------------------------- Normalize var record (public-ish) ----
def _normalize_var_record(self, var: Dict[str, Any]):
"""Унифицирует записи переменной.
Требуемые поля после нормализации:
var['ptr_type'] -> str (напр. 'int16')
var['ptr_type_enum'] -> int
var['iq_type'] -> str ('iq10')
var['iq_type_enum'] -> int
var['return_type'] -> str ('iq10')
var['return_type_enum']-> int
var['pt_type'] -> 'pt_<ptr_type>' (для совместимости с VariableTableWidget)
var['return_type_pref']-> 't_<return_type>' (см. ниже) # не обяз.
Дополнительно корректируем show_var/enable и адрес.
"""
# --- show_var / enable
var['show_var'] = str(var.get('show_var', 'false')).lower()
var['enable'] = str(var.get('enable', 'true')).lower()
# --- address
if not var.get('address'):
var_name = var.get('name')
# Ищем в self.flat_vars
if hasattr(self, 'flat_vars') and isinstance(self.flat_vars, dict):
flat_entry = self.flat_vars.get(var_name)
if flat_entry and 'address' in flat_entry:
var['address'] = flat_entry['address']
else:
var['address'] = 0
else:
var['address'] = 0
else:
# Нормализация адреса (если строка типа '0x1234')
try:
if isinstance(var['address'], str):
var['address'] = int(var['address'], 16)
except ValueError:
var['address'] = 0
# --- ptr_type (строка)
name = None
if isinstance(var.get('ptr_type'), str):
name = var['ptr_type']
elif isinstance(var.get('ptr_type_name'), str):
name = var['ptr_type_name']
elif isinstance(var.get('pt_type'), str) and 'pt_' in var.get('pt_type'):
name = var['pt_type'].replace('pt_','')
elif isinstance(var.get('ptr_type'), int):
name = PT_ENUM_NAME_FROM_VAL.get(var['ptr_type'], 'unknown')
else:
name = self._map_type_to_ptr_enum(var.get('type'))
val = PT_ENUM_VALUE.get(name, 0)
var['ptr_type'] = name
var['ptr_type_enum'] = val
var['pt_type'] = f'pt_{name}'
# ---------------------------------------------- prefixed assign helper ----
def _assign_types_from_prefixed(self, dst: Dict[str, Any], pt_pref: str, iq_pref: str, rt_pref: str):
"""Парсит строки вида 'pt_int16', 't_iq10' и записывает нормализованные поля."""
pt_name = pt_pref.replace('pt_','') if pt_pref else 'unknown'
iq_name = iq_pref
if iq_name.startswith('t_'):
iq_name = iq_name[2:]
rt_name = rt_pref
if rt_name.startswith('t_'):
rt_name = rt_name[2:]
dst['ptr_type'] = pt_name
dst['ptr_type_enum'] = PT_ENUM_VALUE.get(pt_name, 0)
dst['pt_type'] = f'pt_{pt_name}'
dst['iq_type'] = iq_name
dst['iq_type_enum'] = IQ_ENUM_VALUE.get(iq_name, 0)
dst['return_type'] = rt_name
dst['return_type_enum'] = IQ_ENUM_VALUE.get(rt_name, dst['iq_type_enum'])
dst['return_type_pref'] = f't_{rt_name}'
# ------------------------------------------ Populate internal maps ----
def _populate_internal_maps_from_all_vars(self):
self._path_info.clear()
self._addr_index.clear()
self._paths.clear()
for var in self._all_available_vars:
nm = var.get('name')
tp = var.get('type')
addr = var.get('address')
if nm is None:
continue
if addr is None:
addr = 0
var['address'] = 0
self._paths.append(nm)
self._path_info[nm] = (addr, tp)
if addr in self._addr_index:
self._addr_index[addr] = None
else:
self._addr_index[addr] = nm
# Обновим подсказки
self._hints.set_paths([(p, self._path_info[p][1]) for p in self._paths])
# -------------------------------------------------- Public helpers ----
def get_selected_variables_and_addresses(self) -> List[Dict[str, Any]]:
"""Возвращает список выбранных переменных (show_var == true) с адресами и типами.
Чтение из VariableTableWidget + подстановка адресов/прочих служебных полей
из master списка.
"""
tbl_data = self.var_table.read_data() # список dict'ов в формате VariableTableWidget
idx_by_name = {v.get('name'): v for v in self._all_available_vars if v.get('name')}
out: List[Dict[str, Any]] = []
for rec in tbl_data:
nm = rec.get('name')
if not nm:
continue
src = idx_by_name.get(nm, {})
addr = src.get('address')
if addr is None or addr == '' or addr == 0:
src['address'] = self.flat_vars.get(nm, {}).get('address', 0)
else:
# если это строка "0x..." — конвертируем в int
if isinstance(addr, str) and addr.startswith('0x'):
try:
src['address'] = int(addr, 16)
except ValueError:
src['address'] = self.flat_vars.get(nm, {}).get('address', 0)
type_str = src.get('type', rec.get('type','N/A'))
# нормализация типов
tmp = dict(src) # copy src to preserve extra fields (file, extern, ...)
self._assign_types_from_prefixed(tmp,
rec.get('pt_type','pt_unknown'),
rec.get('iq_type','t_iq_none'),
rec.get('return_type', rec.get('iq_type','t_iq_none')))
tmp['show_var'] = str(bool(rec.get('show_var'))).lower()
tmp['enable'] = str(bool(rec.get('enable'))).lower()
tmp['name'] = nm
tmp['address'] = addr
tmp['type'] = type_str
out.append(tmp)
return out
def get_datetime(self):
return self.dt
def set_variable_value(self, var_name: str, value: Any):
# 1. Обновляем master-список переменных
found = None
for var in self._all_available_vars:
if var.get('name') == var_name:
var['value'] = value
found = var
break
if not found:
# Если переменной нет в списке, можно либо проигнорировать, либо добавить.
return False
# 2. Обновляем отображение в таблице
#self.var_table.populate(self._all_available_vars, {}, self._on_var_table_changed)
return True
# --------------- Address mapping / type mapping helpers ---------------
def _map_type_to_ptr_enum(self, type_str: Optional[str]) -> str:
if not type_str:
return 'unknown'
low = type_str.lower()
token = low.replace('*',' ').replace('[',' ')
return type_map.get(token, 'unknown').replace('pt_','')
# ----------------------------------------------------------- Logging --
def _log(self, msg: str):
print(f"[LowLevelSelectorWidget Log] {msg}")
# ---------------------------------------------------------------------------
# Тест‑прогоночка (ручной) --------------------------------------------------
# Запускать только вручную: python LowLevelSelectorWidget_refactored.py <xml>
# ---------------------------------------------------------------------------
# ----------------------------------------------------------- Demo window --
class _DemoWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('LowLevel Selector Demo')
self.selector = LowLevelSelectorWidget(self)
self.setCentralWidget(self.selector)
self.selector.variablePrepared.connect(self.on_var)
def on_var(self, data: dict):
print('Variable prepared ->', data)
def closeEvent(self, ev):
self.setCentralWidget(None)
super().closeEvent(ev)
# ----------------------------------------------------------------- main ---
if __name__ == '__main__':
app = QApplication(sys.argv)
w = _DemoWindow()
w.resize(640, 520)
w.show()
sys.exit(app.exec_())

1279
Src/tms_debugvar_term.py Normal file
View File

@@ -0,0 +1,1279 @@
from PySide2 import QtCore, QtWidgets, QtSerialPort, QtGui
from tms_debugvar_lowlevel import LowLevelSelectorWidget
import datetime
import time
import os
from csv_logger import CsvLogger
import auto_updater
# ------------------------------- Константы протокола ------------------------
WATCH_SERVICE_BIT = 0x8000
DEBUG_OK = 0 # ожидаемый код успешного чтения
SIGN_BIT_MASK = 0x80
FRAC_MASK_FULL = 0x7F # если используем 7 бит дробной части
# --- Debug status codes (из прошивки) ---
DEBUG_OK = 0x00
DEBUG_ERR = 0x80 # общий флаг ошибки (старший бит)
DEBUG_ERR_VAR_NUMB = DEBUG_ERR | (1 << 0)
DEBUG_ERR_INVALID_VAR = DEBUG_ERR | (1 << 1)
DEBUG_ERR_ADDR = DEBUG_ERR | (1 << 2)
DEBUG_ERR_ADDR_ALIGN = DEBUG_ERR | (1 << 3)
DEBUG_ERR_INTERNAL = DEBUG_ERR | (1 << 4)
DEBUG_ERR_DATATIME = DEBUG_ERR | (1 << 5)
DEBUG_ERR_RS = DEBUG_ERR | (1 << 5)
# для декодирования по битам
_DEBUG_ERR_BITS = (
(1 << 0, "Invalid Variable Index"),
(1 << 1, "Invalid Variable"),
(1 << 2, "Invalid Address"),
(1 << 3, "Invalid Address Align"),
(1 << 4, "Internal Code Error"),
(1 << 5, "Invalid Data or Time"),
(1 << 6, "Error with RS"),
)
# ---------------------------------------------------------------- CRC util ---
def crc16_ibm(data: bytes, *, init=0xFFFF) -> int:
"""CRC16-IBM (aka CRC-16/ANSI, polynomial 0xA001 reflected)."""
crc = init
for b in data:
crc ^= b
for _ in range(8):
if crc & 1:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
return crc & 0xFFFF
def is_frozen():
# Для Nuitka --onefile
return getattr(sys, 'frozen', False)
def get_base_path():
if is_frozen():
# В Nuitka onefile распаковывается в папку с самим exe во временной директории
return os.path.dirname(sys.executable)
else:
# Режим разработки
return os.path.dirname(os.path.abspath(__file__))
def _decode_debug_status(status: int) -> str:
"""Преобразует код статуса прошивки в строку.
Возвращает 'OK' или перечисление битов через '|'.
Не зависит от того, WATCH или LowLevel.
"""
if status == DEBUG_OK:
return "OK"
parts = []
if status & DEBUG_ERR:
for mask, name in _DEBUG_ERR_BITS:
if status & mask:
parts.append(name)
if not parts: # старший бит есть, но ни один из известных младших не выставлен
parts.append("ERR")
else:
# Неожиданно: статус !=0, но бит DEBUG_ERR не стоит
parts.append(f"0x{status:02X}")
return "|".join(parts)
class Spoiler(QtWidgets.QWidget):
def __init__(self, title="", animationDuration=300, parent=None):
super().__init__(parent)
self._animationDuration = animationDuration
self.state = False
# --- Toggle button ---
self.toggleButton = QtWidgets.QToolButton(self)
self.toggleButton.setStyleSheet("QToolButton { border: none; }")
self.toggleButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
self.toggleButton.setArrowType(QtCore.Qt.RightArrow)
self.toggleButton.setText(title)
self.toggleButton.setCheckable(True)
# --- Header line ---
self.headerLine = QtWidgets.QFrame(self)
self.headerLine.setFrameShape(QtWidgets.QFrame.HLine)
self.headerLine.setFrameShadow(QtWidgets.QFrame.Sunken)
# --- Content area ---
self.contentArea = QtWidgets.QScrollArea(self)
self.contentArea.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
self.contentArea.setFrameShape(QtWidgets.QFrame.NoFrame)
self.contentArea.setWidgetResizable(True)
self._contentWidget = QtWidgets.QWidget()
self.contentArea.setWidget(self._contentWidget)
self.contentArea.setMaximumHeight(0)
# --- Анимация только по контенту ---
self._ani_content = QtCore.QPropertyAnimation(self.contentArea, b"maximumHeight")
self._ani_content.setDuration(animationDuration)
self._ani_content.setEasingCurve(QtCore.QEasingCurve.InOutCubic)
# --- Layout ---
self.mainLayout = QtWidgets.QGridLayout(self)
self.mainLayout.setVerticalSpacing(0)
self.mainLayout.setContentsMargins(0, 0, 0, 0)
self.mainLayout.addWidget(self.toggleButton, 0, 0, 1, 1)
self.mainLayout.addWidget(self.headerLine, 0, 1, 1, 1)
self.mainLayout.addWidget(self.contentArea, 1, 0, 1, 2)
# --- Signals ---
self.toggleButton.clicked.connect(self._on_toggled)
def setContentLayout(self, contentLayout):
old = self._contentWidget.layout()
if old:
QtWidgets.QWidget().setLayout(old)
self._contentWidget.setLayout(contentLayout)
def getState(self):
return self.state
def _on_toggled(self, checked: bool):
self.state = checked
self.toggleButton.setArrowType(QtCore.Qt.DownArrow if checked else QtCore.Qt.RightArrow)
contentHeight = self._contentWidget.sizeHint().height()
self._ani_content.stop()
self._ani_content.setStartValue(self.contentArea.maximumHeight())
self._ani_content.setEndValue(contentHeight if checked else 0)
self._ani_content.start()
# --------------------------- DebugTerminalWidget ---------------------------
class DebugTerminalWidget(QtWidgets.QWidget):
# Существующие сигналы (Watch)
nameRead = QtCore.Signal(int, int, int, str)
valueRead = QtCore.Signal(int, int, int, int, float)
valuesRead = QtCore.Signal(int, int, list, list, list, list)
# Новые сигналы (LowLevel)
llValueRead = QtCore.Signal(int, int, int, int, float) # addr, status, rettype_raw, raw16_signed, scaled
portOpened = QtCore.Signal(str)
portClosed = QtCore.Signal(str)
txBytes = QtCore.Signal(bytes)
rxBytes = QtCore.Signal(bytes)
def __init__(self, parent=None, *,
start_byte=0x0A,
cmd_byte=0x46,
cmd_lowlevel=0x47,
iq_scaling=None,
read_timeout_ms=250,
auto_crc_check=True,
drop_if_busy=False,
replace_if_busy=True):
super().__init__(parent)
self.device_addr = start_byte
self.cmd_byte = cmd_byte
self.cmd_lowlevel = cmd_lowlevel
self.read_timeout_ms = read_timeout_ms
self.auto_crc_check = auto_crc_check
self._drop_if_busy = drop_if_busy
self._replace_if_busy = replace_if_busy
self._last_txn_timestamp = 0
self._ll_polling_active = False
if iq_scaling is None:
iq_scaling = {n: float(1 << n) for n in range(31)}
iq_scaling[0] = 1.0
self.iq_scaling = iq_scaling
# Serial
self.serial = QtSerialPort.QSerialPort(self)
self.serial.setBaudRate(115200)
self.serial.readyRead.connect(self._on_ready_read)
self.serial.errorOccurred.connect(self._on_serial_error)
# State
self._rx_buf = bytearray()
self._busy = False
self._pending_cmd = None # (frame, meta)
self._txn_meta = None # {'service':bool,'index':int,'varqnt':int,'chain':...,'lowlevel':bool}
self._txn_timer = QtCore.QTimer(self)
self._txn_timer.setSingleShot(True)
self._txn_timer.timeout.connect(self._on_txn_timeout)
# Watch polling
self._poll_timer = QtCore.QTimer(self)
self._poll_timer.timeout.connect(self._on_poll_timeout)
self._polling = False
# LowLevel polling
self._ll_poll_timer = QtCore.QTimer(self)
self._ll_poll_timer.timeout.connect(self._on_ll_poll_timeout)
self._ll_polling = False
self._ll_polling_variables = [] # List of selected variables for polling
self._ll_current_poll_index = -1 # Index of the variable currently being polled in the _ll_polling_variables list
self._ll_current_var_info = []
self.csv_logger = CsvLogger()
self._csv_logging_active = False
self._last_csv_timestamp = 0 # Для отслеживания времени записи
# Кэш: index -> (status, iq, name, is_signed, frac_bits)
self._name_cache = {}
# Очередь service индексов
self._service_queue = []
self._pending_data_after_services = None # (base, count)
self._build_ui()
self._connect_ui()
self.set_available_ports()
# ------------------------------ UI ----------------------------------
def _build_ui(self):
layout = QtWidgets.QVBoxLayout(self)
# --- Serial group ---
g_serial = QtWidgets.QGroupBox("Serial Port")
hs = QtWidgets.QHBoxLayout(g_serial)
self.cmb_port = QtWidgets.QComboBox()
self.btn_refresh = QtWidgets.QPushButton("Refresh")
self.cmb_baud = QtWidgets.QComboBox()
self.cmb_baud.addItems(["9600","19200","38400","57600","115200","230400"])
self.cmb_baud.setCurrentText("115200")
self.btn_open = QtWidgets.QPushButton("Open")
hs.addWidget(QtWidgets.QLabel("Port:"))
hs.addWidget(self.cmb_port, 1)
hs.addWidget(self.btn_refresh)
hs.addSpacing(10)
hs.addWidget(QtWidgets.QLabel("Baud:"))
hs.addWidget(self.cmb_baud)
hs.addWidget(self.btn_open)
# --- TabWidget ---
self.tabs = QtWidgets.QTabWidget()
self._build_watch_tab()
self._build_lowlevel_tab() # <-- Вызываем новый метод
g_control = QtWidgets.QGroupBox("Control / Status")
control_layout = QtWidgets.QHBoxLayout(g_control) # Используем QHBoxLayout
# Форма для статусов слева
form_control = QtWidgets.QFormLayout()
self.lbl_status = QtWidgets.QLabel("Idle")
self.lbl_status.setStyleSheet("font-weight: bold; color: grey;")
form_control.addRow("Status:", self.lbl_status)
self.lbl_actual_interval = QtWidgets.QLabel("-")
form_control.addRow("Actual Interval:", self.lbl_actual_interval)
control_layout.addLayout(form_control, 1) # Растягиваем форму
# Галочка Raw справа
self.chk_raw = QtWidgets.QCheckBox("Raw (no IQ scaling)")
control_layout.addWidget(self.chk_raw)
# Создаем QGroupBox для группировки элементов управления CSV
self.csv_log_groupbox = QtWidgets.QGroupBox("CSV Logging")
csv_log_layout = QtWidgets.QVBoxLayout(self.csv_log_groupbox) # Передаем groupbox как родительский layout
# Элементы управления CSV
h_file_select = QtWidgets.QHBoxLayout()
self.btn_select_csv_file = QtWidgets.QPushButton("Выбрать файл CSV")
# Убедитесь, что self.csv_logger инициализирован где-то до этого момента
self.lbl_csv_filename = QtWidgets.QLabel(self.csv_logger.filename)
h_file_select.addWidget(self.btn_select_csv_file)
h_file_select.addWidget(self.lbl_csv_filename, 1)
csv_log_layout.addLayout(h_file_select)
h_control_buttons = QtWidgets.QHBoxLayout()
self.btn_start_csv_logging = QtWidgets.QPushButton("Начать запись в CSV")
self.btn_stop_csv_logging = QtWidgets.QPushButton("Остановить запись в CSV")
self.btn_save_csv_data = QtWidgets.QPushButton("Сохранить данные в CSV")
self.btn_stop_csv_logging.setEnabled(False) # По умолчанию остановлена
h_control_buttons.addWidget(self.btn_start_csv_logging)
h_control_buttons.addWidget(self.btn_stop_csv_logging)
h_control_buttons.addWidget(self.btn_save_csv_data)
csv_log_layout.addLayout(h_control_buttons)
# Добавляем QGroupBox в основной лейаут
# --- UART Log ---
self.log_spoiler = Spoiler("UART Log", animationDuration=300, parent=self)
self.log_spoiler.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Minimum)
log_layout = QtWidgets.QVBoxLayout()
self.txt_log = QtWidgets.QTextEdit(); self.txt_log.setReadOnly(True)
self.txt_log.setFontFamily("Courier")
log_layout.addWidget(self.txt_log)
self.log_spoiler.setContentLayout(log_layout)
layout.addWidget(g_serial)
layout.addWidget(self.tabs, 1)
layout.addWidget(g_control)
layout.addWidget(self.csv_log_groupbox)
layout.addWidget(self.log_spoiler)
layout.setStretch(layout.indexOf(g_serial), 0)
layout.setStretch(layout.indexOf(self.tabs), 1)
def _build_watch_tab(self):
tab = QtWidgets.QWidget()
main_layout = QtWidgets.QVBoxLayout(tab)
# --- Variable Selector ---
g_selector = QtWidgets.QGroupBox("Variable Selector")
selector_layout = QtWidgets.QVBoxLayout(g_selector)
form_selector = QtWidgets.QFormLayout()
h_layout = QtWidgets.QHBoxLayout()
self.spin_index = QtWidgets.QSpinBox()
self.spin_index.setRange(0, 0x7FFF)
self.spin_index.setAccelerated(True)
self.chk_hex_index = QtWidgets.QCheckBox("Hex")
self.spin_count = QtWidgets.QSpinBox()
self.spin_count.setRange(1, 255)
self.spin_count.setValue(1)
# Первая группа: Base Index + spin + checkbox
base_index_layout = QtWidgets.QHBoxLayout()
base_index_label = QtWidgets.QLabel("Base Index")
base_index_layout.addWidget(base_index_label)
base_index_layout.addWidget(self.spin_index)
base_index_layout.addWidget(self.chk_hex_index)
base_index_layout.setSpacing(5)
# Вторая группа: spin_count + метка справа
count_layout = QtWidgets.QHBoxLayout()
count_layout.setSpacing(2) # минимальный отступ
count_layout.addWidget(self.spin_count)
count_label = QtWidgets.QLabel("Cnt")
count_layout.addWidget(count_label)
# Добавляем обе группы в общий горизонтальный лэйаут
h_layout.addLayout(base_index_layout)
h_layout.addSpacing(20)
h_layout.addLayout(count_layout)
form_selector.addRow(h_layout)
self.spin_interval = QtWidgets.QSpinBox()
self.spin_interval.setRange(50, 10000)
self.spin_interval.setValue(500)
self.spin_interval.setSuffix(" ms")
form_selector.addRow("Interval:", self.spin_interval)
selector_layout.addLayout(form_selector)
btn_layout = QtWidgets.QHBoxLayout()
self.btn_update_service = QtWidgets.QPushButton("Update Service")
self.btn_read_values = QtWidgets.QPushButton("Read Value(s)")
self.btn_poll = QtWidgets.QPushButton("Start Polling")
btn_layout.addWidget(self.btn_update_service)
btn_layout.addWidget(self.btn_read_values)
btn_layout.addWidget(self.btn_poll)
selector_layout.addLayout(btn_layout)
# --- Table ---
g_table = QtWidgets.QGroupBox("Table")
table_layout = QtWidgets.QVBoxLayout(g_table)
self.tbl_values = QtWidgets.QTableWidget(0, 5)
self.tbl_values.setHorizontalHeaderLabels(["Index", "Name", "IQ", "Raw", "Scaled"])
hh = self.tbl_values.horizontalHeader()
for i in range(4):
hh.setSectionResizeMode(i, QtWidgets.QHeaderView.ResizeToContents)
hh.setSectionResizeMode(4, QtWidgets.QHeaderView.Stretch)
self.tbl_values.verticalHeader().setVisible(False)
table_layout.addWidget(self.tbl_values)
# --- Вертикальный сплиттер ---
v_split = QtWidgets.QSplitter(QtCore.Qt.Vertical)
v_split.addWidget(g_selector)
v_split.addWidget(g_table)
v_split.setStretchFactor(0, 1)
v_split.setStretchFactor(1, 3)
v_split.setStretchFactor(2, 1)
main_layout.addWidget(v_split)
self.tabs.addTab(tab, "Watch")
table_layout.addWidget(self.tbl_values)
def _build_lowlevel_tab(self):
# создаём виджет LowLevelSelectorWidget
self.ll_selector = LowLevelSelectorWidget()
# добавляем как корневой виджет вкладки
self.tabs.addTab(self.ll_selector, "LowLevel")
def _connect_ui(self):
# Watch
self.btn_refresh.clicked.connect(self.set_available_ports)
self.btn_open.clicked.connect(self._open_close_port)
self.btn_update_service.clicked.connect(self.request_service_update_for_table)
self.btn_read_values.clicked.connect(self.request_values)
self.btn_poll.clicked.connect(self._toggle_polling)
self.chk_hex_index.stateChanged.connect(self._toggle_index_base)
# LowLevel (новые и переделанные)
self.ll_selector.xmlLoaded.connect(lambda p: self._log(f"[LL] XML loaded: {p}"))
self.ll_selector.btn_read_once.clicked.connect(self._start_ll_cycle)
self.ll_selector.btn_start_polling.clicked.connect(self._toggle_ll_polling)
# --- CSV Logging ---
self.btn_select_csv_file.clicked.connect(self._select_csv_file)
self.btn_start_csv_logging.clicked.connect(self._start_csv_logging)
self.btn_stop_csv_logging.clicked.connect(self._stop_csv_logging)
self.btn_save_csv_data.clicked.connect(self._save_csv_data)
def set_status(self, text: str, mode: str = "idle"):
colors = {
"idle": "gray",
"service": "blue",
"values": "green",
"error": "red"
}
color = colors.get(mode.lower(), "black")
self.lbl_status.setText(text)
self.lbl_status.setStyleSheet(f"font-weight: bold; color: {color};")
# ----------------------------- SERIAL MGMT ----------------------------
def set_available_ports(self):
cur = self.cmb_port.currentText()
self.cmb_port.blockSignals(True)
self.cmb_port.clear()
for info in QtSerialPort.QSerialPortInfo.availablePorts():
self.cmb_port.addItem(info.portName())
if cur:
ix = self.cmb_port.findText(cur)
if ix >= 0:
self.cmb_port.setCurrentIndex(ix)
self.cmb_port.blockSignals(False)
def _open_close_port(self):
if self.serial.isOpen():
name = self.serial.portName()
self.serial.close()
self.btn_open.setText("Open")
self._log(f"[PORT OK] Closed {name}")
self.portClosed.emit(name)
return
port = self.cmb_port.currentText()
if not port:
self._log("[ERR] No port selected")
return
self.serial.setPortName(port)
self.serial.setBaudRate(int(self.cmb_baud.currentText()))
if not self.serial.open(QtCore.QIODevice.ReadWrite):
self._log(f"[ERR] Open fail {port}: {self.serial.errorString()}")
return
self.btn_open.setText("Close")
self._log(f"[PORT OK] Opened {port}")
self.portOpened.emit(port)
# ---------------------------- FRAME BUILD -----------------------------
def _build_request(self, index: int, *, service: bool, varqnt: int) -> bytes:
dbg = index & 0x7FFF
if service:
dbg |= WATCH_SERVICE_BIT
hi = (dbg >> 8) & 0xFF
lo = dbg & 0xFF
q = varqnt & 0xFF
payload = bytes([self.device_addr & 0xFF, self.cmd_byte & 0xFF, hi, lo, q])
crc = crc16_ibm(payload)
return payload + bytes([crc & 0xFF, (crc >> 8) & 0xFF])
def _build_lowlevel_request(self, var_info: dict) -> bytes:
# Формат: [adr][cmd_lowlevel][year_hi][year_lo][month][day][hour][minute][addr2][addr1][addr0][pt_type][iq_type][return_type]
# Пытаемся получить время из переданной информации
dt_info = self.ll_selector.get_datetime()
if dt_info:
# Используем время из var_info
year = dt_info.year
month = dt_info.month
day = dt_info.day
hour = dt_info.hour
minute = dt_info.minute
self._log("[LL] Using time from selector.")
else:
return
addr = var_info.get('address', 0)
addr2 = (addr >> 16) & 0xFF
addr1 = (addr >> 8) & 0xFF
addr0 = addr & 0xFF
# Ensure 'ptr_type' and 'iq_type' from var_info are integers (enum values)
# Use a fallback to 0 if they are not found or not integers
pt_type = var_info.get('ptr_type_enum', 0) & 0xFF
iq_type = var_info.get('iq_type_enum', 0) & 0xFF
ret_type = var_info.get('return_type_enum', 0) & 0xFF
frame_wo_crc = bytes([
self.device_addr & 0xFF, self.cmd_lowlevel & 0xFF,
(year >> 8) & 0xFF, year & 0xFF,
month & 0xFF, day & 0xFF, hour & 0xFF, minute & 0xFF,
addr2, addr1, addr0, pt_type, iq_type, ret_type
])
crc = crc16_ibm(frame_wo_crc)
return frame_wo_crc + bytes([crc & 0xFF, (crc >> 8) & 0xFF])
# ----------------------------- PUBLIC API -----------------------------
def request_service_single(self):
idx = int(self.spin_index.value())
self._enqueue_or_start(idx, service=True, varqnt=0)
def request_service_update_for_table(self):
"""
Очищает кеш имен/типов для всех видимых в таблице переменных
и инициирует их повторное чтение.
"""
indices_to_update = []
for row in range(self.tbl_values.rowCount()):
item = self.tbl_values.item(row, 0)
if item and item.text().isdigit():
indices_to_update.append(int(item.text()))
if not indices_to_update:
self._log("[SERVICE] No variables in table to update.")
return
self._log(f"[SERVICE] Queuing name/type update for {len(indices_to_update)} variables.")
# Очищаем кеш для этих индексов, чтобы принудительно их перечитать
for index in indices_to_update:
if index in self._name_cache:
del self._name_cache[index]
# Запускаем стандартный запрос значений. Он автоматически обработает
# отсутствующую сервисную информацию (имена/типы) перед запросом данных.
if not (self._polling or self._ll_polling):
self.request_values()
def request_values(self):
self._update_interval()
base = int(self.spin_index.value())
count = int(self.spin_count.value())
needed = []
for i in range(base, base+count):
if i not in self._name_cache:
needed.append(i)
if needed:
self._service_queue = needed[:]
self._pending_data_after_services = (base, count)
self._log(f"[AUTO] Need service for {len(needed)} indices: {needed}")
self.set_status("Read service...", "service")
self._kick_service_queue()
else:
self.set_status("Read values...", "values")
self._enqueue_or_start(base, service=False, varqnt=count)
def request_lowlevel_once(self):
"""Запрашивает чтение выбранной LowLevel переменной (однократно)."""
if not self.serial.isOpen():
self._log("[LL] Port is not open.")
return
if self._busy:
self._log("[LL] Busy, request dropped.")
return
# Если переменная не подготовлена, или нет актуальной информации
if not hasattr(self, '_ll_current_var_info') or not self._ll_current_var_info:
self._log("[LL] No variable prepared/selected for single read!")
return
frame = self._build_lowlevel_request(self._ll_current_var_info)
# --- НОВОЕ: Передаем ll_var_info в метаданные транзакции ---
meta = {'lowlevel': True, 'll_polling': False, 'll_var_info': self._ll_current_var_info}
self.set_status("Read lowlevel...", "values")
self._enqueue_raw(frame, meta)
# -------------------------- SERVICE QUEUE FLOW ------------------------
# ... (код без изменений)
def _kick_service_queue(self):
if self._busy:
return
if self._service_queue:
nxt = self._service_queue.pop(0)
self._enqueue_or_start(nxt, service=True, varqnt=0, queue_mode=True)
elif self._pending_data_after_services:
base, count = self._pending_data_after_services
self._pending_data_after_services = None
self._enqueue_or_start(base, service=False, varqnt=count)
# ------------------------ TRANSACTION SCHEDULER -----------------------
# ... (код без изменений)
def _enqueue_raw(self, frame: bytes, meta: dict):
# Добавляем ll_var_info, если это LL запрос
if meta.get('lowlevel', False) and 'll_var_info' not in meta:
# Это должно быть установлено вызывающим кодом, но для безопасности
# или если LL polling не передал var_info явно
meta['ll_var_info'] = self._ll_current_var_info # Используем last prepared var info for single shots
if self._busy:
# ... существующий код ...
if self._replace_if_busy:
self._pending_cmd = (frame, meta)
self._log("[LOCKSTEP] Busy -> replaced pending")
else:
self._log("[LOCKSTEP] Busy -> ignore")
return
self._start_txn(frame, meta)
def _enqueue_or_start(self, index, service: bool, varqnt: int, chain_after=None, queue_mode=False):
frame = self._build_request(index, service=service, varqnt=varqnt)
meta = {'service': service, 'index': index, 'varqnt': varqnt, 'chain': chain_after, 'queue_mode': queue_mode, 'lowlevel': False}
if self._busy:
if self._drop_if_busy and not self._replace_if_busy:
self._log("[LOCKSTEP] Busy -> drop")
return
if self._replace_if_busy:
self._pending_cmd = (frame, meta)
self._log("[LOCKSTEP] Busy -> replaced pending")
else:
self._log("[LOCKSTEP] Busy -> ignore")
return
self._start_txn(frame, meta)
def _start_txn(self, frame: bytes, meta: dict):
if(meta.get('service')):
self._update_interval()
self._busy = True
self._txn_meta = meta
self._rx_buf.clear()
self._set_ui_busy(True)
self._send(frame)
self._txn_timer.start(self.read_timeout_ms)
def _end_txn(self):
self._txn_timer.stop()
queue_mode = False
chain = None
meta = self._txn_meta
if meta:
queue_mode = meta.get('queue_mode', False)
chain = meta.get('chain')
self._txn_meta = None
self._busy = False
self._rx_buf.clear()
self._set_ui_busy(False)
if chain:
base, serv, q = chain
self._enqueue_or_start(base, service=serv, varqnt=q)
return
if self._pending_cmd is not None:
frame, meta = self._pending_cmd
self._pending_cmd = None
QtCore.QTimer.singleShot(0, lambda f=frame, m=meta: self._start_txn(f, m))
return
if queue_mode:
QtCore.QTimer.singleShot(0, self._kick_service_queue)
# !!! Раньше тут было `return`, его убираем
# Если идёт LL polling — переходим сразу к следующей переменной
if self._ll_polling:
self._process_next_ll_variable_in_cycle()
return
def _on_txn_timeout(self):
if not self._busy: return
is_ll = self._txn_meta.get('lowlevel', False) if self._txn_meta else False
log_prefix = "[LL TIMEOUT]" if is_ll else "[TIMEOUT]"
self._log(f"{log_prefix} No response")
if self._rx_buf:
self._log_frame(bytes(self._rx_buf), tx=False)
self._end_txn()
self.set_status("Timeout", "error")
# ------------------------------- TX/RX ---------------------------------
# ... (код без изменений)
def _send(self, data: bytes):
w = self.serial.write(data)
if w != len(data):
self._log(f"[ERR] Write short {w}/{len(data)}")
self.txBytes.emit(data)
self._log_frame(data, tx=True)
def _on_ready_read(self):
self._rx_buf.extend(self.serial.readAll().data())
if not self._busy:
if self._rx_buf:
self._log("[WARN] Data while idle -> drop")
self._log_frame(bytes(self._rx_buf), tx=False)
self._rx_buf.clear()
return
self._try_parse()
if not (self._polling or self._ll_polling):
self.set_status("Idle", "idle")
# ------------------------------- PARSING -------------------------------
def _try_parse(self):
if not self._txn_meta:
return
if self._txn_meta.get('lowlevel', False):
self._try_parse_lowlevel()
else:
self._try_parse_watch()
def _try_parse_watch(self):
# ... (код без изменений)
service = self._txn_meta['service']
buf = self._rx_buf
trailer_len = 4
if service:
if len(buf) < 7 + trailer_len:
return
name_len = buf[6]
expected = 7 + name_len + trailer_len
if len(buf) < expected:
return
frame = bytes(buf[:expected]); del buf[:expected]
self.rxBytes.emit(frame); self._log_frame(frame, tx=False)
self._parse_service_frame(frame)
self._end_txn()
else:
if len(buf) < 6 + trailer_len:
return
varqnt = buf[4]; status = buf[5]
if status != DEBUG_OK:
expected = 8 + trailer_len
if len(buf) < expected: return
frame = bytes(buf[:expected]); del buf[:expected]
self.rxBytes.emit(frame); self._log_frame(frame, tx=False)
self._parse_data_frame(frame, error_mode=True)
self._end_txn()
else:
expected = 6 + varqnt*2 + trailer_len
if len(buf) < expected: return
frame = bytes(buf[:expected]); del buf[:expected]
self.rxBytes.emit(frame); self._log_frame(frame, tx=False)
self._parse_data_frame(frame, error_mode=False)
self._end_txn()
def _try_parse_lowlevel(self):
# Ожидаемая длина: Успех=13, Ошибка=10
buf = self._rx_buf
if len(buf) < 10: # Минимальная длина (ошибка)
return
# Проверяем, что ответ для нас
if buf[1] != self.cmd_lowlevel:
self._log("[LL] Unexpected cmd in lowlevel parser, flushing.")
self._log_frame(bytes(self._rx_buf), tx=False)
self._rx_buf.clear()
# Не завершаем транзакцию, ждём таймаута
return
status = buf[2]
expected_len = 13 if status == DEBUG_OK else 10
if len(buf) >= expected_len:
frame = bytes(buf[:expected_len])
del buf[:expected_len]
self.rxBytes.emit(frame)
self._log_frame(frame, tx=False)
self._parse_lowlevel_frame(frame, success=(status == DEBUG_OK))
self._end_txn()
def _check_crc(self, payload: bytes, crc_lo: int, crc_hi: int):
if not self.auto_crc_check:
return True
crc_rx = (crc_hi << 8) | crc_lo
crc_calc = crc16_ibm(payload)
if crc_calc != crc_rx:
self._log(f"[CRC FAIL] calc=0x{crc_calc:04X} rx=0x{crc_rx:04X}")
return False
self._log("[CRC OK]")
return True
@staticmethod
def _clear_service_bit(vhi, vlo):
return ((vhi & 0x7F) << 8) | vlo
def _parse_service_frame(self, frame: bytes):
# ... (код без изменений)
payload = frame[:-4]; crc_lo, crc_hi = frame[-4], frame[-3]
if len(payload) < 7:
self._log("[ERR] Service frame too short"); return
self._check_crc(payload, crc_lo, crc_hi)
adr, cmd, vhi, vlo, status, iq_raw, name_len = payload[:7]
status_desc = _decode_debug_status(status)
index = self._clear_service_bit(vhi, vlo)
if len(payload) < 7 + name_len:
self._log("[ERR] Service name truncated"); return
name_bytes = payload[7:7+name_len]; name = name_bytes.decode(errors='replace')
is_signed = (iq_raw & SIGN_BIT_MASK) != 0
frac_bits = iq_raw & FRAC_MASK_FULL
if status == DEBUG_OK:
self._name_cache[index] = (status, iq_raw, name, is_signed, frac_bits)
self.nameRead.emit(index, status, iq_raw, name)
self._log(f"[SERVICE] idx={index} status={status} iq_raw=0x{iq_raw:02X} sign={'S' if is_signed else 'U'} frac={frac_bits} name='{name}'")
def _parse_data_frame(self, frame: bytes, *, error_mode: bool):
payload = frame[:-4]; crc_lo, crc_hi = frame[-4], frame[-3]
if len(payload) < 6:
self._log("[ERR] Data frame too short"); return
self._check_crc(payload, crc_lo, crc_hi)
adr, cmd, vhi, vlo, varqnt, status = payload[:6]
base = self._clear_service_bit(vhi, vlo)
if error_mode:
self.set_status("Error", "error")
if len(payload) < 8:
self._log("[ERR] Error frame truncated"); return
err_hi, err_lo = payload[6:8]
bad_index = (err_hi << 8) | err_lo
desc = _decode_debug_status(status)
self._log(f"[DATA] ERROR status=0x{status:02X} ({desc}) bad_index={bad_index}")
# Обновим UI
self._populate_watch_error(bad_index, status)
# Сигналы (оставляем совместимость)
self.valueRead.emit(bad_index, status, 0, 0, float('nan'))
self.valuesRead.emit(base, 0, [], [], [], [])
return
if len(payload) < 6 + varqnt*2:
self._log("[ERR] Data payload truncated"); return
raw_vals = []
pos = 6
for _ in range(varqnt):
hi = payload[pos]; lo = payload[pos+1]; pos += 2
raw16 = (hi << 8) | lo
raw_vals.append(raw16)
idx_list = []; iq_list = []; name_list = []; scaled_list = []; display_raw_list = []
# Получаем текущее время один раз для всех переменных в этом фрейме
current_time = time.time()
for ofs, raw16 in enumerate(raw_vals):
idx = base + ofs
status_i, iq_raw, name_i, is_signed, frac_bits = self._name_cache.get(idx, (DEBUG_OK, 0, '', False, 0))
if is_signed and (raw16 & 0x8000):
value_int = raw16 - 0x10000
else:
value_int = raw16
if self.chk_raw.isChecked():
scale = 1.0
else:
scale = self.iq_scaling.get(frac_bits, 1.0 / (1 << frac_bits))
scaled = float(value_int) / scale if frac_bits > 0 else float(value_int)
idx_list.append(idx); iq_list.append(iq_raw); name_list.append(name_i)
scaled_list.append(scaled); display_raw_list.append(value_int)
# --- Здесь записываем имя и значение в csv_logger ---
self.csv_logger.set_value(current_time, name_i, scaled)
self._populate_table(idx_list, name_list, iq_list, display_raw_list, scaled_list)
if varqnt == 1:
if idx_list[0] == self.spin_index.value():
_, iq_raw0, name0, is_signed0, frac0 = self._name_cache.get(idx_list[0], (DEBUG_OK, 0, '', False, 0))
self.valueRead.emit(idx_list[0], status, iq_list[0], display_raw_list[0], scaled_list[0])
else:
self.valuesRead.emit(base, varqnt, idx_list, iq_list, display_raw_list, scaled_list)
self._log(f"[DATA] base={base} q={varqnt} values={[f'{v:.6g}' for v in scaled_list] if not self.chk_raw.isChecked() else raw_vals}")
def _parse_lowlevel_frame(self, frame: bytes, success: bool):
payload_len = 9 if success else 6
crc_pos = payload_len
payload = frame[:payload_len]
crc_lo, crc_hi = frame[crc_pos], frame[crc_pos+1]
self._check_crc(payload, crc_lo, crc_hi)
status = payload[2]
addr2, addr1, addr0 = payload[3], payload[4], payload[5]
addr24 = (addr2 << 16) | (addr1 << 8) | addr0
status_desc = _decode_debug_status(status)
if not success:
# Ошибка — в ответе нет ReturnType и данных, только статус
self._log(f"[LL] ERROR addr=0x{addr24:06X} status=0x{status:02X} ({status_desc})")
self.llValueRead.emit(addr24, status, None, None, None)
return None
# Если success == True, продолжаем парсить ReturnType и данные
return_type = payload[6]
data_hi, data_lo = payload[7], payload[8]
raw16 = (data_hi << 8) | data_lo
is_signed = (return_type & SIGN_BIT_MASK) != 0
frac_bits = return_type & FRAC_MASK_FULL
if is_signed and (raw16 & 0x8000):
value_int = raw16 - 0x10000
else:
value_int = raw16
if self.chk_raw.isChecked():
scale = 1.0
else:
scale = self.iq_scaling.get(frac_bits, 1.0 / (1 << frac_bits)) # 1 / 2^N
scaled = float(value_int) / scale
self.llValueRead.emit(addr24, status, return_type, value_int, scaled)
var_name = None
if self._ll_current_var_info.get("address") == addr24:
var_name = self._ll_current_var_info.get("name")
display_val = value_int if self.chk_raw.isChecked() else scaled
if var_name:
self.ll_selector.set_variable_value(var_name, display_val)
self._log(f"[LL] OK addr=0x{addr24:06X} type=0x{return_type:02X} raw={value_int} scaled={scaled:.6g}")
current_time = time.time()
self.csv_logger.set_value(current_time, var_name, display_val)
def _populate_watch_error(self, bad_index: int, status: int):
"""Отобразить строку ошибки при неудачном ответе WATCH."""
desc = _decode_debug_status(status)
self.tbl_values.setRowCount(1)
self.tbl_values.setItem(0, 0, QtWidgets.QTableWidgetItem(str(bad_index)))
self.tbl_values.setItem(0, 1, QtWidgets.QTableWidgetItem(f"<ERROR:{desc}>"))
self.tbl_values.setItem(0, 2, QtWidgets.QTableWidgetItem("-"))
self.tbl_values.setItem(0, 3, QtWidgets.QTableWidgetItem("-"))
self.tbl_values.setItem(0, 4, QtWidgets.QTableWidgetItem("<ERROR>"))\
def _populate_table(self, idxs, names, iqs, raws, scaled):
"""
Быстрое массовое обновление таблицы значений.
- Не пересоздаём QTableWidgetItem при каждом вызове: обновляем текст.
- Блокируем сортировку, сигналы и обновления на время заполнения.
- Предвычисляем отображаемые строки (особенно формат scaled).
"""
tbl = self.tbl_values
n = len(idxs)
# Заморозка UI на время массового обновления
prev_sorting = tbl.isSortingEnabled()
tbl.setSortingEnabled(False)
tbl.blockSignals(True)
tbl.setUpdatesEnabled(False)
# Подготовка размера
if tbl.rowCount() != n:
tbl.setRowCount(n)
# Предварительно решаем: показывать сырые или масштабированные значения
show_raw = self.chk_raw.isChecked()
# Готовим строки (ускоряет при больших объёмах)
# str() заранее, чтобы не повторять в цикле
idx_strs = [str(v) for v in idxs]
raw_strs = [str(v) for v in raws]
scaled_strs = raw_strs if show_raw else [f"{v:.6g}" for v in scaled]
# Флаги необновляемых ячеек (только выбор/просмотр)
flags_ro = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
# Локальный шорткат для быстрой установки текста в ячейку
def _set_text(row, col, text):
item = tbl.item(row, col)
if item is None:
item = QtWidgets.QTableWidgetItem(text)
item.setFlags(flags_ro)
tbl.setItem(row, col, item)
else:
# обновим текст только при изменении (немного экономит на больших данных)
if item.text() != text:
item.setText(text)
if item.flags() != flags_ro:
item.setFlags(flags_ro)
# Основной цикл
for row in range(n):
iq_raw = iqs[row]
is_signed = (iq_raw & SIGN_BIT_MASK) != 0
frac_bits = iq_raw & FRAC_MASK_FULL
iq_disp = f"{frac_bits}{'s' if is_signed else 'u'}"
_set_text(row, 0, idx_strs[row])
_set_text(row, 1, names[row])
_set_text(row, 2, iq_disp)
_set_text(row, 3, raw_strs[row])
_set_text(row, 4, scaled_strs[row])
# Разморозка
tbl.blockSignals(False)
tbl.setUpdatesEnabled(True)
tbl.setSortingEnabled(prev_sorting)
tbl.viewport().update()
# ------------------------------ POLLING --------------------------------
def _toggle_polling(self):
if self._polling:
self._poll_timer.stop()
self._polling = False
self.btn_poll.setText("Start Polling")
self.set_status("Idle", "idle")
self._log("[POLL] Stopped")
else:
interval = self.spin_interval.value()
self._poll_timer.start(interval)
self._polling = True
self.btn_poll.setText("Stop Polling")
self.set_status("Idle", "idle")
self._log(f"[POLL] Started interval={interval}ms")
self._set_ui_busy(False) # Обновить доступность кнопок
def _on_poll_timeout(self):
self.request_values()
def _toggle_ll_polling(self):
if self._ll_polling: # If currently polling, stop
self._ll_polling = False
self.ll_selector.btn_start_polling.setText("Start Polling")
self._ll_poll_timer.stop()
self._ll_polling_variables.clear()
self._ll_current_poll_index = -1
self._log("[LL Polling] Stopped.")
else: # If not polling, start
# Get all selected variables from the LowLevelSelectorWidget
self._ll_polling_variables = self.ll_selector.get_selected_variables_and_addresses()
if not self._ll_polling_variables:
self._log("[LL] No variables selected for polling. Aborting.")
self.set_status("Error.", "error")
return
self._ll_polling = True
self.ll_selector.btn_start_polling.setText("Stop Polling")
self._ll_current_poll_index = 0 # Start from the first variable
self._log(f"[LL Polling] Started. Polling {len(self._ll_polling_variables)} variables.")
# Start the timer. It will trigger _on_ll_poll_timeout, which starts the cycle.
# The first cycle starts immediately, subsequent cycles wait for the interval.
self._ll_poll_timer.setInterval(self.ll_selector.spin_interval.value())
self._ll_poll_timer.start() # Start the timer for recurrent cycles
# Immediately kick off the first variable read of the first cycle
self._start_ll_cycle()
self._set_ui_busy(False) # Обновить доступность кнопок
def _on_ll_poll_timeout(self):
"""Вызывается по таймеру для старта нового цикла."""
if self._ll_polling and not self._busy:
self._start_ll_cycle()
elif self._busy:
self._log("[LL Polling] Busy, skip cycle start.")
def _start_ll_cycle(self):
self._update_interval()
"""Запускает новый цикл опроса всех переменных."""
if not self._ll_polling or not self._ll_polling_variables:
return
self._ll_poll_index = 0
self._process_next_ll_variable_in_cycle()
def _on_ll_variable_prepared(self, var_info: dict):
"""Срабатывает при выборе переменной в селекторе."""
self._ll_current_var_info = var_info
def _process_next_ll_variable_in_cycle(self):
if not self._ll_polling: # Добавим проверку, чтобы избежать вызова, если LL polling отключен
return
if self._ll_poll_index < len(self._ll_polling_variables):
var_info = self._ll_polling_variables[self._ll_poll_index]
self._on_ll_variable_prepared(var_info)
self._ll_poll_index += 1
frame = self._build_lowlevel_request(var_info)
# --- НОВОЕ: Передаем var_info в метаданные транзакции для LL polling ---
meta = {'lowlevel': True, 'll_polling': True, 'll_var_info': var_info}
# Получаем адрес переменной, предполагаем что ключ называется 'addr' или 'address'
addr = var_info.get('addr') or var_info.get('address')
if addr is not None:
addr_str = f"0x{addr:06X}"
else:
addr_str = "addr unknown"
self.set_status(f"Polling LL: {addr_str} {var_info.get('name')}", "values")
self._enqueue_raw(frame, meta)
else:
# Цикл завершен, перезапускаем таймер для следующего полного цикла
self.ll_selector._populate_var_table()
# ------------------------------ HELPERS --------------------------------
def _toggle_index_base(self, st):
# ... (код без изменений)
val = self.spin_index.value()
if st == QtCore.Qt.Checked:
self.spin_index.setDisplayIntegerBase(16); self.spin_index.setPrefix("0x")
else:
self.spin_index.setDisplayIntegerBase(10); self.spin_index.setPrefix("")
self.spin_index.setValue(val)
def _set_ui_busy(self, busy: bool):
# Блокируем кнопки в зависимости от состояния 'busy' и 'polling'
# Watch tab
can_use_watch = not busy and not (self._polling or self._ll_polling)
#self.btn_update_service.setEnabled(can_use_watch)
self.btn_read_values.setEnabled(can_use_watch)
# LowLevel tab
can_use_ll = not busy and not (self._ll_polling or self._polling)
self.ll_selector.btn_read_once.setEnabled(can_use_ll)
def _on_serial_error(self, err):
# ... (код без изменений)
if err == QtSerialPort.QSerialPort.NoError: return
self._log(f"[SERIAL ERR] {self.serial.errorString()} ({err})")
if self._busy: self._end_txn()
# ------------------------------ LOGGING --------------------------------
def _select_csv_file(self):
"""Открывает диалог выбора файла для CSV и обновляет UI."""
if self.csv_logger.select_file(self): # Передаем self как parent для диалога
self.lbl_csv_filename.setText(self.csv_logger.filename)
self._log(f"CSV file set to: {self.csv_logger.filename}")
def _start_csv_logging(self):
"""Начинает запись данных в CSV. Устанавливает заголовки в зависимости от активной вкладки."""
if not self.serial.isOpen():
self._log("[CSV] Невозможно начать запись: COM порт не открыт.")
self.set_status("Port closed", "error")
return
# Определяем активную вкладку и устанавливаем заголовки
current_tab_index = self.tabs.currentIndex()
varnames_for_csv = []
if self.tabs.tabText(current_tab_index) == "Watch":
# Для вкладки Watch берем имена из кэша, если они есть, иначе используем Index_X
base_index = self.spin_index.value()
count = self.spin_count.value()
for i in range(base_index, base_index + count):
if i in self._name_cache and self._name_cache[i][2]: # status, iq_raw, name, is_signed, frac_bits
varnames_for_csv.append(self._name_cache[i][2])
else:
varnames_for_csv.append(f"Index_{i}")
self._log(f"[CSV] Начинается запись для Watch переменных: {varnames_for_csv}")
elif self.tabs.tabText(current_tab_index) == "LowLevel":
# Для вкладки LowLevel берем имена из ll_selector
selected_vars = self.ll_selector.get_selected_variables_and_addresses()
varnames_for_csv = [var['name'] for var in selected_vars if 'name' in var]
if not varnames_for_csv:
self._log("[CSV] Внимание: На вкладке LowLevel не выбраны переменные для записи.")
self._log(f"[CSV] Начинается запись для LowLevel переменных: {varnames_for_csv}")
else:
self._log("[CSV] Неизвестная активная вкладка. Невозможно определить заголовки CSV.")
return
if not varnames_for_csv:
self._log("[CSV] Нет переменных для записи в CSV. Запись не начата.")
return
self.csv_logger.set_titles(varnames_for_csv)
self._csv_logging_active = True
self.btn_start_csv_logging.setEnabled(False)
self.btn_stop_csv_logging.setEnabled(True)
self.set_status("CSV Logging ACTIVATED", "service")
self._log("[CSV] Запись данных в CSV началась.")
def _stop_csv_logging(self):
"""Останавливает запись данных в CSV."""
self._csv_logging_active = False
self.btn_start_csv_logging.setEnabled(True)
self.btn_stop_csv_logging.setEnabled(False)
self.set_status("CSV Logging STOPPED", "service")
self._log("[CSV] Запись данных в CSV остановлена.")
def _save_csv_data(self):
"""Сохраняет все собранные данные в CSV файл."""
if self._csv_logging_active:
self._log("[CSV] Запись активна. Сначала остановите запись.")
self.set_status("Stop logging first", "error")
return
self.csv_logger.write_to_csv()
self.set_status("CSV data saved", "service")
def _log(self, msg: str):
# ... (код без изменений)
if 'ERR' in msg:
self.set_status(msg, 'error')
if 'OK' in msg:
self.set_status('Idle', 'idle')
if not self.log_spoiler.getState():
return
ts = datetime.datetime.now().strftime('%H:%M:%S.%f')[:-3]
self.txt_log.append(f"{ts} {msg}")
def _log_frame(self, data: bytes, *, tx: bool):
# ... (код без изменений)
if not self.log_spoiler.getState():
return
tag = 'TX' if tx else 'RX'
hexs = ' '.join(f"{b:02X}" for b in data)
ascii_part = ''.join(chr(b) if 32 <= b < 127 else '.' for b in data)
self._log(f"[{tag}] {hexs} |{ascii_part}|")
def _update_interval(self):
now = time.perf_counter()
if self._last_txn_timestamp is not None:
delta_ms = (now - self._last_txn_timestamp) * 1000
# Обновляем UI только если он уже создан
if hasattr(self, 'lbl_actual_interval'):
self.lbl_actual_interval.setText(f"{delta_ms:.1f} ms")
self._last_txn_timestamp = now
# ---------------------------------------------------------- Demo harness ---
class _DemoWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
base_path = get_base_path()
icon_path = os.path.join(base_path, "icon.ico")
if os.path.exists(icon_path):
self.setWindowIcon(QtGui.QIcon(icon_path))
self.setWindowTitle("DebugVar Terminal")
self.term = DebugTerminalWidget(self)
self.setCentralWidget(self.term)
self.resize(1000, 600)
def closeEvent(self, event):
self.setCentralWidget(None)
if self.term:
self.term.deleteLater(); self.term = None
super().closeEvent(event)
# ------------------------------- Demo --------------------------------------
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
auto_updater.check_and_update(
current_version="1.2.1",
git_releases_url="https://git.arktika.cyou/Razvalyaev/debugVarTool/releases",
exe_name="DebugVarEdit.exe",
zip_name="DebugToolsRelease.rar",
parent_widget=None
)
win = _DemoWindow(); win.show()
sys.exit(app.exec_())

428
Src/var_selector_table.py Normal file
View File

@@ -0,0 +1,428 @@
# variable_select_widget.py
import pickle
import hashlib
from typing import List, Dict, Any, Optional
from PySide2.QtWidgets import (
QWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QLineEdit,
QHeaderView, QCompleter
)
from PySide2.QtGui import QKeyEvent
from PySide2.QtCore import Qt, QStringListModel
from path_hints import PathHints, canonical_key, split_path_tokens
# ------------------------------------------------------------------
# utils
# ------------------------------------------------------------------
def compute_vars_hash(vars_list):
return hashlib.sha1(pickle.dumps(vars_list)).hexdigest()
def is_lazy_item(item: QTreeWidgetItem) -> bool:
return item.childCount() == 1 and item.child(0).text(0) == 'lazy_marker'
# ------------------------------------------------------------------
# VariableSelectWidget
# ------------------------------------------------------------------
class VariableSelectWidget(QWidget):
"""
Виджет выбора переменных с деревом + строкой поиска + автодополнением.
Подсказки полностью через PathHints.
ВАЖНО: ожидается, что в данных (vars_list) каждое var['name'] — ПОЛНЫЙ ПУТЬ
(например: 'project.adc.status'), даже внутри children.
"""
ROLE_NAME = Qt.UserRole # локальный хвост (display)
ROLE_VAR_DICT = Qt.UserRole + 100 # исходный dict
ROLE_FULLPATH = Qt.UserRole + 200 # полный путь
def __init__(self, parent=None):
super().__init__(parent)
# данные
self.expanded_vars: List[Dict[str, Any]] = []
self.is_autocomplete_on = True
self.manual_completion_active = False
self._bckspc_pressed = False
self._vars_hash: Optional[str] = None
# индекс: canonical_full_path -> item
self._item_by_canon: Dict[str, QTreeWidgetItem] = {}
# подсказки
self.hints = PathHints()
# --- UI ---
self.search_input = QLineEdit(self)
self.search_input.setPlaceholderText("Поиск...")
self.tree = QTreeWidget(self)
self.tree.setHeaderLabels(["Имя переменной", "Тип"])
self.tree.setSelectionMode(QTreeWidget.ExtendedSelection)
self.tree.setRootIsDecorated(True)
self.tree.setUniformRowHeights(True)
self.tree.setStyleSheet("""
QTreeWidget::item:selected { background-color: #87CEFA; color: black; }
QTreeWidget::item:hover { background-color: #D3D3D3; }
""")
self.tree.itemExpanded.connect(self.on_item_expanded)
self.completer = QCompleter(self)
self.completer.setCompletionMode(QCompleter.PopupCompletion)
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
self.completer.setFilterMode(Qt.MatchContains)
self.completer.setWidget(self.search_input)
self.completer.activated[str].connect(self.insert_completion)
# layout
lay = QVBoxLayout(self)
lay.setContentsMargins(0, 0, 0, 0)
lay.addWidget(self.search_input)
lay.addWidget(self.tree)
# signals
self.search_input.textChanged.connect(self.on_search_text_changed)
self.search_input.installEventFilter(self)
# ------------------------------------------------------------------
# public api
# ------------------------------------------------------------------
def set_autocomplete(self, enabled: bool):
self.is_autocomplete_on = enabled
def set_data(self, vars_list: List[Dict[str, Any]]):
"""
Загружаем список переменных (формат: см. класс docstring).
"""
# deepcopy
self.expanded_vars = pickle.loads(pickle.dumps(vars_list, protocol=pickle.HIGHEST_PROTOCOL))
# rebuild hints из полного списка узлов (каждый узел уже с full_path)
self._rebuild_hints_from_vars(self.expanded_vars)
# rebuild tree
self.populate_tree(self.expanded_vars)
# ------------------------------------------------------------------
# hints builder: дети уже содержат ПОЛНЫЙ ПУТЬ
# ------------------------------------------------------------------
def _rebuild_hints_from_vars(self, vars_list: List[Dict[str, Any]]):
paths: List[tuple] = []
def walk(node: Dict[str, Any]):
full = node.get('name', '')
if full:
paths.append((full, node.get('type')))
for ch in node.get('children', []) or []:
walk(ch)
for v in vars_list:
walk(v)
self.hints.set_paths(paths)
# ------------------------------------------------------------------
# tree building
# ------------------------------------------------------------------
def populate_tree(self, vars_list=None):
if vars_list is None:
vars_list = self.expanded_vars
new_hash = compute_vars_hash(vars_list)
if self._vars_hash == new_hash:
return
self._vars_hash = new_hash
self.tree.setUpdatesEnabled(False)
self.tree.blockSignals(True)
self.tree.clear()
self._item_by_canon.clear()
# построим top-level из входного списка: определяем по глубине токенов
# (vars_list может содержать и глубокие узлы; выберем корни = те, чей full_path не имеет родителя в списке)
full_to_node = {v['name']: v for v in vars_list}
# но safer: просто добавляем все как top-level, если ты уже передаёшь только корни.
# Если в твоих данных vars_list == корни, просто сделаем:
for v in vars_list:
self._add_tree_item_lazy(None, v)
self.tree.setUpdatesEnabled(True)
self.tree.blockSignals(False)
header = self.tree.header()
header.setSectionResizeMode(QHeaderView.Interactive)
header.setSectionResizeMode(1, QHeaderView.Stretch)
self.tree.setColumnWidth(0, 400)
def on_item_expanded(self, item: QTreeWidgetItem):
if is_lazy_item(item):
item.removeChild(item.child(0))
var = item.data(0, self.ROLE_VAR_DICT)
if var:
for ch in var.get('children', []) or []:
self._add_tree_item_lazy(item, ch)
# ------------------------------------------------------------------
# item creation (var['name'] — ПОЛНЫЙ ПУТЬ)
# ------------------------------------------------------------------
def _add_tree_item_lazy(self, parent: Optional[QTreeWidgetItem], var: Dict[str, Any]):
full_path = var.get('name', '')
type_str = var.get('type', '')
# здесь оставляем полный путь для отображения
item = QTreeWidgetItem([full_path, type_str])
item.setData(0, self.ROLE_NAME, full_path) # теперь ROLE_NAME = полный путь
item.setData(0, self.ROLE_VAR_DICT, var)
item.setData(0, self.ROLE_FULLPATH, full_path)
if "(bitfield:" in type_str:
item.setDisabled(True)
self._set_tool(item, "Битовые поля недоступны для выбора")
# метаданные
for i, attr in enumerate(['file', 'extern', 'static']):
item.setData(0, Qt.UserRole + 1 + i, var.get(attr))
# в дерево
if parent is None:
self.tree.addTopLevelItem(item)
else:
parent.addChild(item)
# lazy children
if var.get('children'):
dummy = QTreeWidgetItem(["lazy_marker"])
item.addChild(dummy)
# индекс
self._item_by_canon[canonical_key(full_path)] = item
@staticmethod
def _tail_token(full_path: str) -> str:
toks = split_path_tokens(full_path)
return toks[-1] if toks else full_path
# ------------------------------------------------------------------
# filtering
# ------------------------------------------------------------------
def filter_tree(self):
"""
Быстрый фильтр:
- без разделителей → substring по ЛОКАЛЬНОМУ имени top-level
- с разделителями → структурный (по токенам full_path)
"""
text = (self.search_input.text() or '').strip()
low = text.lower()
parts = split_path_tokens(low) if low else []
# простой режим (нет ., ->, [):
if low and all(x not in low for x in ('.', '->', '[')):
for i in range(self.tree.topLevelItemCount()):
it = self.tree.topLevelItem(i)
full = (it.data(0, self.ROLE_FULLPATH) or '').lower()
it.setHidden(low not in full)
return
# структурный
for i in range(self.tree.topLevelItemCount()):
it = self.tree.topLevelItem(i)
self._show_matching_path(it, parts, 0)
def _show_matching_path(self, item: QTreeWidgetItem, path_parts: List[str], level: int = 0):
"""
Сравниваем введённый путь (разбитый на токены) с ПОЛНЫМ ПУТЁМ узла.
Алгоритм: берём полный путь узла, разбиваем в токены, берём уровень level,
и сравниваем с соответствующим токеном path_parts[level].
"""
full = (item.data(0, self.ROLE_FULLPATH) or '').lower()
node_parts = split_path_tokens(full)
if level >= len(path_parts):
item.setHidden(False)
item.setExpanded(False)
return True
if level >= len(node_parts):
item.setHidden(True)
return False
search_part = path_parts[level]
node_part = node_parts[level]
if search_part == node_part:
item.setHidden(False)
matched_any = False
self.on_item_expanded(item)
for i in range(item.childCount()):
ch = item.child(i)
if self._show_matching_path(ch, path_parts, level + 1):
matched_any = True
item.setExpanded(matched_any)
return matched_any or item.childCount() == 0
elif node_part.startswith(search_part):
item.setHidden(False)
item.setExpanded(False)
return True
elif search_part in node_part and (level == len(path_parts) - 1):
item.setHidden(False)
item.setExpanded(False)
return True
else:
item.setHidden(True)
return False
# ------------------------------------------------------------------
# completions (ONLY PathHints)
# ------------------------------------------------------------------
def update_completions(self, text: Optional[str] = None) -> List[str]:
if text is None:
text = self.search_input.text()
suggestions = self.hints.suggest(text)
self.completer.setModel(QStringListModel(suggestions))
if suggestions:
self.completer.complete()
else:
self.completer.popup().hide()
return suggestions
def insert_completion(self, full_path: str):
text = self.hints.add_separator(full_path)
if not self._bckspc_pressed:
self.search_input.setText(text)
self.search_input.setCursorPosition(len(text))
self.run_completions(text)
# ------------------------------------------------------------------
# events
# ------------------------------------------------------------------
def eventFilter(self, obj, event):
if obj == self.search_input and isinstance(event, QKeyEvent):
if event.key() == Qt.Key_Space and event.modifiers() & Qt.ControlModifier:
self.manual_completion_active = True
self.run_completions(self.search_input.text())
elif event.key() == Qt.Key_Escape:
if not self.is_autocomplete_on:
self.manual_completion_active = False
self.completer.popup().hide()
return True
if event.key() == Qt.Key_Backspace:
self._bckspc_pressed = True
else:
self._bckspc_pressed = False
return super().eventFilter(obj, event)
def run_completions(self, text: str):
if not self.is_autocomplete_on and not self.manual_completion_active:
self.completer.popup().hide()
return
self.update_completions(text)
def on_search_text_changed(self, text: str):
self.completer.setWidget(self.search_input)
self.filter_tree()
if text is None:
text = self.search_input.text()
if self.is_autocomplete_on:
self.run_completions(text)
else:
if self.manual_completion_active:
self.run_completions(text)
else:
self.completer.popup().hide()
def focusInEvent(self, event):
if self.completer.widget() != self.search_input:
self.completer.setWidget(self.search_input)
super().focusInEvent(event)
def closeEvent(self, event):
self.completer.setWidget(None)
self.completer.deleteLater()
super().closeEvent(event)
# ------------------------------------------------------------------
# lookup by full path
# ------------------------------------------------------------------
def find_item_by_fullpath(self, path: str) -> Optional[QTreeWidgetItem]:
return self._item_by_canon.get(canonical_key(path))
# ------------------------------------------------------------------
# tooltips
# ------------------------------------------------------------------
def _set_tool(self, item: QTreeWidgetItem, text: str):
item.setToolTip(0, text)
item.setToolTip(1, text)
# ------------------------------------------------------------------
# selection helpers
# ------------------------------------------------------------------
def get_all_items(self):
"""Все leaf-узлы (подгружаем lazy)."""
def collect_leaf(parent):
leaves = []
for i in range(parent.childCount()):
ch = parent.child(i)
if ch.isHidden():
continue
self.on_item_expanded(ch)
if ch.childCount() == 0:
t = ch.text(1)
if t and 'bitfield' in t.lower():
continue
leaves.append(ch)
else:
leaves.extend(collect_leaf(ch))
return leaves
out = []
for i in range(self.tree.topLevelItemCount()):
top = self.tree.topLevelItem(i)
self.on_item_expanded(top)
if top.childCount() == 0:
t = top.text(1)
if t and 'bitfield' in t.lower():
continue
out.append(top)
else:
out.extend(collect_leaf(top))
return out
def _get_internal_selected_items(self):
selected = self.tree.selectedItems()
all_items = []
def collect(item):
self.on_item_expanded(item)
res = [item]
for i in range(item.childCount()):
res.extend(collect(item.child(i)))
return res
for it in selected:
all_items.extend(collect(it))
return all_items
def get_selected_items(self):
selected = self.tree.selectedItems()
leaves = []
for it in selected:
self.on_item_expanded(it)
if all(it.child(i).isHidden() or not it.child(i).isSelected() for i in range(it.childCount())):
t = it.data(0, self.ROLE_NAME)
if t and isinstance(t, str) and 'bitfield' in t.lower():
continue
leaves.append(it)
return leaves
def get_all_var_names(self):
return [it.data(0, self.ROLE_FULLPATH) for it in self.get_all_items() if it.data(0, self.ROLE_FULLPATH)]
def _get_internal_selected_var_names(self):
return [it.data(0, self.ROLE_FULLPATH) for it in self._get_internal_selected_items() if it.data(0, self.ROLE_FULLPATH)]
def get_selected_var_names(self):
return [it.data(0, self.ROLE_FULLPATH) for it in self.get_selected_items() if it.data(0, self.ROLE_FULLPATH)]

392
Src/var_selector_window.py Normal file
View File

@@ -0,0 +1,392 @@
import re
import lxml.etree as ET
from PySide2.QtWidgets import (
QDialog, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QPushButton,
QLineEdit, QLabel, QHeaderView, QCompleter, QCheckBox, QHBoxLayout, QSizePolicy
)
from PySide2.QtGui import QKeySequence, QKeyEvent
from PySide2.QtCore import Qt, QStringListModel, QSettings
import var_table
import var_setup
import myXML
import time
import var_selector_table
array_re = re.compile(r'^(\w+)\[(\d+)\]$')
class VariableSelectorDialog(QDialog):
def __init__(self, table, all_vars, structs, typedefs, xml_path=None, parent=None):
super().__init__(parent)
self.setWindowTitle("Выбор переменных")
self.setAttribute(Qt.WA_DeleteOnClose)
self.resize(1200, 500)
self.selected_names = []
self._bckspc_pressed = False # флаг подавления добавления разделителя
self.table = table
self.all_vars = all_vars
self.structs = structs
self.typedefs = typedefs
self.expanded_vars = []
self.var_map = {v['name']: v for v in all_vars}
self.node_index = {}
self.xml_path = xml_path # сохраняем путь к xml
self.manual_completion_active = False
# --- Добавляем чекбокс для автодополнения ---
self.autocomplete_checkbox = QCheckBox("Включить автодополнение")
self.autocomplete_checkbox.setChecked(True)
# Инициализируем QSettings с именем организации и приложения
self.settings = QSettings("SET", "DebugVarEdit_VarsSelector")
# Восстанавливаем сохранённое состояние чекбокса, если есть
checked = self.settings.value("autocomplete_enabled", True, type=bool)
self.autocomplete_checkbox.setChecked(checked)
# При изменении состояния чекбокса сохраняем его
self.autocomplete_checkbox.stateChanged.connect(self.save_checkbox_state)
# Кнопки между таблицами
self.btn_right = QPushButton(">")
self.btn_right.clicked.connect(self.on_move_right)
self.btn_left = QPushButton("<")
self.btn_left.clicked.connect(self.on_move_left)
# Создаем кнопки, они остаются в диалоге
self.btn_accept = QPushButton("Применить")
# Создаем экземпляр вашего готового виджета
self.vars_widget = var_selector_table.VariableSelectWidget(self)
self.vars_widget.tree.itemDoubleClicked.connect(self.on_left_tree_double_click)
self.vars_widget.setObjectName("LeftTable")
self.selected_vars_widget = var_selector_table.VariableSelectWidget(self)
self.selected_vars_widget.tree.itemDoubleClicked.connect(self.on_rigth_tree_double_click)
self.selected_vars_widget.setObjectName("RightTable")
# Подписи над таблицами
label_all = QLabel("Все переменные")
label_all.setStyleSheet("font-weight: bold; font-size: 14px;")
label_selected = QLabel("Выбранные переменные")
label_selected.setStyleSheet("font-weight: bold; font-size: 14px;")
# --- Лэйауты ---
main_layout = QVBoxLayout(self) # главный вертикальный layout окна
# Чекбокс автодополнения — первый в главном layout
main_layout.addWidget(self.autocomplete_checkbox)
# Подписи над таблицами
labels_layout = QHBoxLayout()
labels_layout.addWidget(label_all)
labels_layout.addStretch()
labels_layout.addWidget(label_selected)
main_layout.addLayout(labels_layout)
# Горизонтальный layout с таблицами и кнопками
tables_layout = QHBoxLayout()
# Левая таблица
self.vars_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
tables_layout.addWidget(self.vars_widget)
# Кнопки ">" и "<" между таблицами
middle_buttons_layout = QVBoxLayout()
middle_buttons_layout.addStretch()
middle_buttons_layout.addWidget(self.btn_right)
middle_buttons_layout.addWidget(self.btn_left)
middle_buttons_layout.addStretch()
tables_layout.addLayout(middle_buttons_layout)
# Правая таблица
self.selected_vars_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
tables_layout.addWidget(self.selected_vars_widget)
# Добавляем горизонтальный layout с таблицами в главный вертикальный
main_layout.addLayout(tables_layout)
# Кнопки "Добавить выбранные" и "Удалить выбранные" под таблицами
buttons_layout = QVBoxLayout()
buttons_layout.addWidget(self.btn_accept)
main_layout.addLayout(buttons_layout)
# Важно, если окно — QDialog или QWidget, установи layout
self.setLayout(main_layout)
# Соединяем сигналы кнопок с методами диалога
self.btn_accept.clicked.connect(self.on_apply_clicked)
# Соединяем чекбокс с методом виджета
self.autocomplete_checkbox.stateChanged.connect(self.set_autocomplete_tables)
# Устанавливаем начальное состояние автодополнения в виджете
self.vars_widget.set_autocomplete(self.autocomplete_checkbox.isChecked())
self.selected_vars_widget.set_autocomplete(self.autocomplete_checkbox.isChecked())
# --- Код в конце __init__ ---
self.expanded_vars = var_setup.expand_vars(self.all_vars, self.structs, self.typedefs)
self.update_vars_widget()
def on_move_right(self):
# Устанавливаем show_var=True для всех выбранных переменных из ЛЕВОЙ таблицы
selected = self.vars_widget._get_internal_selected_var_names()
if not selected:
return
def mark_selected_show_var(data):
for var in data:
if var['name'] in selected:
var['show_var'] = 'true'
var['enable'] = 'true'
if 'children' in var:
mark_selected_show_var(var['children'])
mark_selected_show_var(self.expanded_vars)
self.update_vars_widget()
def on_move_left(self):
# Сбрасываем show_var=False для всех выбранных переменных из ПРАВОЙ таблицы
selected = self.selected_vars_widget._get_internal_selected_var_names()
if not selected:
return
def mark_selected_hide_var(data):
for var in data:
if var['name'] in selected:
var['show_var'] = 'false'
if 'children' in var:
mark_selected_hide_var(var['children'])
mark_selected_hide_var(self.expanded_vars)
self.update_vars_widget()
def update_vars_widget(self):
t_start = time.perf_counter()
t1 = time.perf_counter()
self.selected_vars, self.unselected_vars = var_setup.split_vars_by_show_flag(self.expanded_vars)
t2 = time.perf_counter()
self.vars_widget.set_data(self.unselected_vars)
t3 = time.perf_counter()
self.vars_widget.filter_tree()
t4 = time.perf_counter()
self.selected_vars_widget.set_data(self.selected_vars)
t5 = time.perf_counter()
self.selected_vars_widget.filter_tree()
def on_apply_clicked(self):
# Получаем имена всех переменных из правой таблицы (selected_vars_widget)
right_var_names = set(self.selected_vars_widget.get_all_var_names())
all_items = self.selected_vars_widget.get_all_items()
if not all_items:
return
# Устанавливаем show_var=true и enable=true для переменных из правой таблицы
def add_or_update_var(item):
name = item.text(0)
type_str = item.text(1)
if name in self.var_map:
var = self.var_map[name]
var['show_var'] = 'true'
var['enable'] = 'true'
else:
file_val = item.data(0, Qt.UserRole + 1)
extern_val = item.data(0, Qt.UserRole + 2)
static_val = item.data(0, Qt.UserRole + 3)
new_var = {
'name': name,
'type': type_str,
'show_var': 'true',
'enable': 'true',
'shortname': name,
'pt_type': '',
'iq_type': 't_iq_none',
'return_type': 't_iq_none',
'file': file_val,
'extern': str(extern_val).lower() if extern_val else 'false',
'static': str(static_val).lower() if static_val else 'false',
}
self.all_vars.append(new_var)
self.var_map[name] = new_var
for item in all_items:
add_or_update_var(item)
# Сбрасываем show_var и enable у всех переменных, которых нет в правой таблице
for var in self.all_vars:
if var['name'] not in right_var_names:
var['show_var'] = 'false'
var['enable'] = 'false'
# Обновляем expanded_vars чтобы отразить новые show_var и enable
def update_expanded_vars(data):
for v in data:
name = v['name']
if name in self.var_map:
v['show_var'] = self.var_map[name]['show_var']
v['enable'] = self.var_map[name]['enable']
if 'children' in v:
update_expanded_vars(v['children'])
update_expanded_vars(self.expanded_vars)
# Обновляем отображение в виджетах
self.update_vars_widget()
# Закрываем диалог
self.accept()
# Обнови on_left_tree_double_click:
def on_left_tree_double_click(self, item, column):
selected_names = [item.text(0)]
if not selected_names:
return
def mark_selected_show_var(data):
for var in data:
if var['name'] in selected_names:
var['show_var'] = 'true'
var['enable'] = 'true'
if 'children' in var:
mark_selected_show_var(var['children'])
mark_selected_show_var(self.expanded_vars)
self.update_vars_widget()
# Добавь обработчик двойного клика справа (если нужно):
def on_rigth_tree_double_click(self, item, column):
selected_names = [item.text(0)]
if not selected_names:
return
def mark_selected_hide_var(data):
for var in data:
if var['name'] in selected_names:
var['show_var'] = 'false'
if 'children' in var:
mark_selected_hide_var(var['children'])
mark_selected_hide_var(self.expanded_vars)
self.update_vars_widget()
def keyPressEvent(self, event):
if event.key() == Qt.Key_Delete:
self.delete_selected_vars()
else:
super().keyPressEvent(event)
def delete_selected_vars(self):
selected_names = self._get_selected_var_names()
if not selected_names:
print("nothing selected")
return
# Обновляем var_map и all_vars
for name in selected_names:
if name in self.var_map:
self.var_map[name]['show_var'] = 'false'
self.var_map[name]['enable'] = 'false'
for v in self.all_vars:
if v['name'] == name:
v['show_var'] = 'false'
v['enable'] = 'false'
break
# Проверка пути к XML
if not hasattr(self, 'xml_path') or not self.xml_path:
from PySide2.QtWidgets import QMessageBox
#QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно обновить переменные.")
return
root, tree = myXML.safe_parse_xml(self.xml_path)
if root is None:
return
vars_section = root.find('variables')
if vars_section is None:
return
for var_elem in vars_section.findall('var'):
name = var_elem.attrib.get('name')
if name in selected_names:
def set_text(tag, value):
el = var_elem.find(tag)
if el is None:
el = ET.SubElement(var_elem, tag)
el.text = value
set_text('show_var', 'false')
set_text('enable', 'false')
myXML.fwrite(root, self.xml_path)
self.table.populate(self.all_vars, self.structs, None)
# Проверка пути к XML
if not hasattr(self, 'xml_path') or not self.xml_path:
from PySide2.QtWidgets import QMessageBox
QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно удалить переменные.")
return
root, tree = myXML.safe_parse_xml(self.xml_path)
if root is None:
return
vars_section = root.find('variables')
if vars_section is None:
return
removed_any = False
for var_elem in list(vars_section.findall('var')):
name = var_elem.attrib.get('name')
if name in selected_names:
vars_section.remove(var_elem)
removed_any = True
self.var_map.pop(name, None)
# Удаляем из all_vars (глобально)
self.all_vars[:] = [v for v in self.all_vars if v['name'] not in selected_names]
# Удаляем из expanded_vars (тоже глобально)
def filter_out_selected(vars_list):
filtered = []
for v in vars_list:
if v['name'] not in selected_names:
# Рекурсивно фильтруем детей, если есть
if 'children' in v:
v = v.copy()
v['children'] = filter_out_selected(v['children'])
filtered.append(v)
return filtered
self.expanded_vars[:] = filter_out_selected(self.expanded_vars)
if removed_any:
myXML.fwrite(root, self.xml_path)
self.update_vars_widget()
def _get_selected_var_names(self):
focused = self.focusWidget()
if focused and focused is self.vars_widget.tree:
return self.vars_widget.get_selected_var_names()
elif focused and focused is self.selected_vars_widget.tree:
return self.selected_vars_widget.get_selected_var_names()
else:
return []
def save_checkbox_state(self):
self.settings.setValue("autocomplete_enabled", self.autocomplete_checkbox.isChecked())
def set_autocomplete_tables(self, state):
self.vars_widget.set_autocomplete(state)
self.selected_vars_widget.set_autocomplete(state)

619
Src/var_setup.py Normal file
View File

@@ -0,0 +1,619 @@
import sys
import os
import re
import lxml.etree as ET
from generate_debug_vars import map_type_to_pt, get_iq_define, type_map
from enum import IntEnum
import scan_vars
import myXML
import pickle
# Вспомогательные функции, которые теперь будут использоваться виджетом
def split_path(path):
"""
Разбивает путь на компоненты:
- 'foo[2].bar[1]->baz' → ['foo', '[2]', 'bar', '[1]', 'baz']
Если видит '-' в конце строки (без '>' после) — обрезает этот '-'
"""
tokens = []
token = ''
i = 0
length = len(path)
while i < length:
c = path[i]
# Разделители: '->' и '.'
if c == '-' and i + 1 < length and path[i:i+2] == '->':
if token:
tokens.append(token)
token = ''
i += 2
continue
elif c == '-' and i == length - 1:
# '-' на конце строки без '>' после — просто пропускаем его
i += 1
continue
elif c == '.':
if token:
tokens.append(token)
token = ''
i += 1
continue
elif c == '[':
if token:
tokens.append(token)
token = ''
idx = ''
while i < length and path[i] != ']':
idx += path[i]
i += 1
if i < length and path[i] == ']':
idx += ']'
i += 1
tokens.append(idx)
continue
else:
token += c
i += 1
if token:
tokens.append(token)
return tokens
def make_absolute_path(path, base_path):
if not os.path.isabs(path) and os.path.isdir(base_path):
try:
return os.path.abspath(os.path.join(base_path, path))
except Exception:
pass # На случай сбоя в os.path.join или abspath
elif os.path.isabs(path):
return os.path.abspath(path)
else:
return path
def make_relative_path(abs_path, base_path):
abs_path = os.path.abspath(abs_path)
base_path = os.path.abspath(base_path)
# Разбиваем на списки директорий
abs_parts = abs_path.split(os.sep)
base_parts = base_path.split(os.sep)
# Проверяем, является ли base_path настоящим префиксом пути (по папкам)
if abs_parts[:len(base_parts)] == base_parts:
rel_parts = abs_parts[len(base_parts):]
return "/".join(rel_parts)
# Иначе пробуем relpath
try:
return os.path.relpath(abs_path, base_path).replace("\\", "/")
except Exception:
return abs_path.replace("\\", "/")
def parse_vars(filename, typedef_map=None):
root, tree = myXML.safe_parse_xml(filename)
if root is None:
return []
if typedef_map is None:
typedef_map = {}
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', '')
var_type = var.findtext('type', 'unknown').strip()
# Вычисляем pt_type и iq_type
pt_type = var.findtext('pt_type')
if not pt_type:
pt_type = map_type_to_pt(var_type, name, typedef_map)
iq_type = var.findtext('iq_type')
if not iq_type:
iq_type = get_iq_define(var_type)
# Записываем iq_type в XML
iq_type_elem = var.find('iq_type')
if iq_type_elem is None:
iq_type_elem = ET.SubElement(var, 'iq_type')
iq_type_elem.text = iq_type
# Вычисляем pt_type и iq_type
pt_type = var.findtext('pt_type')
if not pt_type:
pt_type = map_type_to_pt(var_type, name, typedef_map)
# Записываем pt_type в XML
pt_type_elem = var.find('pt_type')
if pt_type_elem is None:
pt_type_elem = ET.SubElement(var, 'pt_type')
pt_type_elem.text = pt_type
vars_list.append({
'name': name,
'show_var': var.findtext('show_var', 'false'),
'enable': var.findtext('enable', 'false'),
'shortname': var.findtext('shortname', name),
'pt_type': pt_type,
'iq_type': iq_type,
'return_type': var.findtext('return_type', 't_iq_none'),
'type': var_type,
'file': var.findtext('file', ''),
'extern': var.findtext('extern', 'false') == 'true',
'static': var.findtext('static', 'false') == 'true',
})
myXML.fwrite(root, filename)
return vars_list
# 2. Парсим structSup.xml
def parse_structs(filename):
root, tree = myXML.safe_parse_xml(filename)
if root is None:
return {}, {}
structs = {}
typedef_map = {}
def parse_struct_element(elem):
fields = {}
for field in elem.findall("field"):
fname = field.attrib.get("name")
ftype = field.attrib.get("type", "")
# Проверка на вложенную структуру
nested_struct_elem = field.find("struct")
if nested_struct_elem is not None:
# Рекурсивно парсим вложенную структуру и вставляем её как подсловарь
nested_fields = parse_struct_element(nested_struct_elem)
# Оборачиваем в dict с ключом 'type' для хранения типа из XML
fields[fname] = {
'type': ftype, # здесь тип, например "BENDER_ERROR"
**nested_fields # развёрнутые поля вложенной структуры
}
else:
# Обычное поле
fields[fname] = ftype
return fields
structs_elem = root.find("structs")
if structs_elem is not None:
for struct in structs_elem.findall("struct"):
name = struct.attrib.get("name")
if name and name not in structs:
fields = parse_struct_element(struct)
structs[name] = fields
# typedefs без изменений
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
def parse_array_dims(type_str):
"""Возвращает базовый тип и список размеров массива"""
dims = list(map(int, re.findall(r'\[(\d+)\]', type_str)))
base_type = re.sub(r'\[\d+\]', '', type_str).strip()
return base_type, dims
def generate_array_names(prefix, dims, depth=0):
"""Рекурсивно генерирует имена для всех элементов многомерного массива"""
if not dims:
return [prefix]
result = []
for i in range(dims[0]):
new_prefix = f"{prefix}[{i}]"
children = generate_array_names(new_prefix, dims[1:], depth + 1)
result.append({
'name': new_prefix,
'children': children if len(dims) > 1 else None
})
return result
def flatten_array_tree(array_tree):
"""Разворачивает дерево массивов в линейный список с вложенными children"""
result = []
for node in array_tree:
entry = {'name': node['name']}
if node['children']:
entry['children'] = flatten_array_tree(node['children'])
result.append(entry)
return result
def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, depth=0):
if depth > 10:
return []
# Вспомогательная функция для обработки массивов
def process_array(prefix, type_str, structs, typedefs, var_attrs, depth=0):
base_type, array_dims = parse_array_dims(type_str)
if not array_dims:
return []
# На текущем уровне берем первый размер массива
current_dim = array_dims[0]
# Оставшиеся размеры — все, кроме первого
remaining_dims = array_dims[1:]
# Для создания типа с оставшимися размерами:
if remaining_dims:
# Формируем строку типа для оставшихся измерений массива, например int[16]
remaining_type_str = f"{base_type}{''.join(f'[{d}]' for d in remaining_dims)}"
else:
remaining_type_str = base_type
array_tree = []
for i in range(current_dim):
name = f"{prefix}[{i}]"
# Для каждого элемента передаем уже оставшийся тип массива
children = expand_struct_recursively(name, remaining_type_str, structs, typedefs, var_attrs, depth + 1)
node = {
'name': name,
'type': remaining_type_str if remaining_dims else base_type,
'pt_type': '',
'iq_type': '',
'return_type': '',
'file': var_attrs.get('file'),
'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'),
}
if children:
node['children'] = children
array_tree.append(node)
return array_tree
# Если type_str — уже распарсенная структура (dict)
if isinstance(type_str, dict):
fields = type_str
else:
# Проверяем, массив ли это
base_type, array_dims = parse_array_dims(type_str)
if array_dims:
return process_array(prefix, type_str, structs, typedefs, var_attrs, depth)
# Ищем структуру по имени типа
base_type = scan_vars.strip_ptr_and_array(type_str)
fields = structs.get(base_type)
if not isinstance(fields, dict):
# Не структура и не массив — просто возвращаем пустой список
return []
children = []
for field_name, field_value in fields.items():
if field_name == 'type':
continue
# Формируем полное имя поля
if prefix.endswith('*'):
separator = '->'
full_name = f"{prefix[:-1]}{separator}{field_name}"
else:
separator = '.'
full_name = f"{prefix}{separator}{field_name}"
# Определяем тип поля
if isinstance(field_value, dict) and isinstance(field_value.get('type'), str):
field_type_str = field_value['type']
elif isinstance(field_value, str):
field_type_str = field_value
else:
field_type_str = None
if '*' in field_type_str:
full_name_prefix = full_name + '*'
else:
full_name_prefix = full_name
# Обработка, если поле — строка (тип или массив)
if field_type_str:
base_subtype, sub_dims = parse_array_dims(field_type_str)
if sub_dims:
# Массив — раскрываем элементы
array_parent = {
'name': full_name,
'type': field_type_str,
'pt_type': '',
'iq_type': '',
'return_type': '',
'file': var_attrs.get('file'),
'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'),
}
array_children = []
flat_names = generate_array_names(full_name, sub_dims)
for node in flat_names:
# node — dict с ключом 'name' и (возможно) 'children'
sub_items = expand_struct_recursively(node['name'], base_subtype, structs, typedefs, var_attrs, depth + 1)
child_node = {
'name': node['name'],
'type': base_subtype,
'pt_type': '',
'iq_type': '',
'return_type': '',
'file': var_attrs.get('file'),
'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'),
}
if sub_items:
child_node['children'] = sub_items
array_children.append(child_node)
array_parent['children'] = array_children
children.append(array_parent)
continue
# Игнорируем указатели на функции
if "(" in field_type_str and "*" in field_type_str and ")" in field_type_str:
continue
if isinstance(field_value, dict):
# Это одиночная структура — раскрываем рекурсивно
sub_items = expand_struct_recursively(full_name_prefix, field_value, structs, typedefs, var_attrs, depth + 1)
child = {
'name': full_name,
'type': field_type_str,
'pt_type': '',
'iq_type': '',
'return_type': '',
'file': var_attrs.get('file'),
'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'),
}
if sub_items:
child['children'] = sub_items
children.append(child)
else:
# Обычное поле (int, float, etc.)
child = {
'name': full_name,
'type': field_type_str,
'pt_type': '',
'iq_type': '',
'return_type': '',
'file': var_attrs.get('file'),
'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'),
}
children.append(child)
continue
# Если поле — dict без 'type' или со сложной структурой, обрабатываем как вложенную структуру
if isinstance(field_value, dict):
type_name = field_value.get('type', '')
child = {
'name': full_name,
'type': type_name,
'pt_type': '',
'iq_type': '',
'return_type': '',
'file': var_attrs.get('file'),
'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'),
}
subchildren = expand_struct_recursively(full_name_prefix, field_value, structs, typedefs, var_attrs, depth + 1)
if subchildren:
child['children'] = subchildren
children.append(child)
return children
def expand_vars(vars_list, structs, typedefs):
"""
Раскрывает структуры и массивы структур в деревья.
"""
expanded = []
for var in vars_list:
pt_type = var.get('pt_type', '')
raw_type = var.get('type', '')
if var['name'] == 'project':
a = 1
if pt_type.startswith('pt_ptr_'):
new_var = var.copy()
new_var['children'] = expand_struct_recursively(var['name']+'*', raw_type, structs, typedefs, var)
expanded.append(new_var)
if pt_type.startswith('pt_arr_'):
new_var = var.copy()
new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var)
expanded.append(new_var)
elif pt_type == 'pt_struct':
new_var = var.copy()
new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var)
expanded.append(new_var)
elif pt_type == 'pt_union':
new_var = var.copy()
new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var)
expanded.append(new_var)
else:
expanded.append(var)
return expanded
def build_full_names(parts, full_name):
"""
Восстанавливает вложенные полные имена из списка частей,
ориентируясь на оригинальное полное имя (с '.', '->' и индексами).
Пример:
parts = ['arr', '[0]', '[1]', 'ptr', 'val']
full_name = 'arr[0][1].ptr->val'
→ [
'arr',
'arr[0]',
'arr[0][1]',
'arr[0][1].ptr',
'arr[0][1].ptr->val'
]
"""
names = []
acc = ''
idx = 0
for part in parts:
pos = full_name.find(part, idx)
if pos == -1:
acc += part
else:
acc = full_name[:pos + len(part)]
idx = pos + len(part)
names.append(acc)
return names
def find_var_by_name(tree, name):
for var in tree:
if var.get('name') == name:
return var
if 'children' in var:
found = find_var_by_name(var['children'], name)
if found:
return found
return None
def add_to_nested_tree(tree, var, path_parts, full_names=None, depth=0, source_tree=None):
if not path_parts:
return
if full_names is None:
full_names = build_full_names(path_parts, var['name'])
current_name = full_names[depth]
for child in tree:
if child.get('name') == current_name:
if depth == len(path_parts) - 1:
child.update(var)
return
if 'children' not in child:
child['children'] = []
add_to_nested_tree(child['children'], var, path_parts, full_names, depth + 1, source_tree)
return
# Ищем в source_tree (expanded_vars) родительский узел по current_name
parent_data = {}
if source_tree:
parent_var = find_var_by_name(source_tree, current_name)
if parent_var:
# Копируем все поля кроме детей (children)
parent_data = {k: v for k, v in parent_var.items() if k != 'children'}
new_node = {
'name': current_name,
'children': []
}
# Обновляем new_node данными родителя
new_node.update(parent_data)
if depth == len(path_parts) - 1:
new_node.update(var)
else:
add_to_nested_tree(new_node['children'], var, path_parts, full_names, depth + 1, source_tree)
tree.append(new_node)
def split_vars_by_show_flag(expanded_vars):
unselected_vars = pickle.loads(pickle.dumps(expanded_vars, protocol=pickle.HIGHEST_PROTOCOL))
selected_vars = []
def find_and_remove(var_list, target_name):
"""Удаляет элемент по полному имени и возвращает его"""
for i, var in enumerate(var_list):
if var.get("name") == target_name:
return var_list.pop(i)
if 'children' in var:
found = find_and_remove(var['children'], target_name)
if found:
return found
return None
def collect_selected_nodes(var):
"""Рекурсивно возвращает все show_var=true узлы (включая поддерево)"""
nodes = []
if var.get('show_var', 'false').lower() == 'true':
nodes.append(var)
for child in var.get('children', []):
nodes.extend(collect_selected_nodes(child))
return nodes
def exists_by_path(tree, full_name):
"""
Проверяет, существует ли переменная в дереве, следуя по частям пути (например: project → adc → status).
Каждая часть ('project', 'project.adc', ...) должна иметь точное совпадение с 'name' в узле.
"""
path_parts = split_path(full_name)
full_names = build_full_names(path_parts, full_name)
current_level = tree
for name in full_names:
found = False
for var in current_level:
if var.get('name') == name:
current_level = var.get('children', [])
found = True
break
if not found:
return False
return True
selected_nodes = []
for var in expanded_vars:
full_name = var['name']
# Проверка: если имя содержит вложенность, но целиком есть в корне — пропускаем
if ('.' in full_name or '[' in full_name or '->' in full_name):
path_parts = split_path(full_name)
if exists_by_path(expanded_vars, full_name):
# Удалим лишнюю копию из корня unselected_vars
find_and_remove(unselected_vars, full_name)
else:
add_to_nested_tree(unselected_vars, var, path_parts, source_tree=expanded_vars)
find_and_remove(unselected_vars, full_name)
selected_nodes.extend(collect_selected_nodes(var))
for node in selected_nodes:
full_name = node['name']
path_parts = split_path(full_name)
# Вырезать из unselected_vars
removed = find_and_remove(unselected_vars, full_name)
if removed:
add_to_nested_tree(selected_vars, removed, path_parts, source_tree=expanded_vars)
else:
# вдруг удалённый родитель — создаём вручную
add_to_nested_tree(selected_vars, node, path_parts, source_tree=expanded_vars)
return selected_vars, unselected_vars

498
Src/var_table.py Normal file
View File

@@ -0,0 +1,498 @@
from PySide2.QtWidgets import (
QTableWidget, QTableWidgetItem, QCheckBox, QComboBox, QLineEdit, QCompleter,
QAbstractItemView, QHeaderView, QLabel, QSpacerItem, QSizePolicy, QSpinBox,
QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QScrollArea, QWidget
)
from PySide2.QtGui import QColor, QBrush, QPalette
from PySide2.QtCore import Qt, QSettings
from enum import IntEnum
from generate_debug_vars import type_map
import time
from typing import Dict, List
class rows(IntEnum):
No = 0
include = 1
name = 2
type = 3
pt_type = 4
iq_type = 5
ret_type = 6
short_name = 7
class FilterDialog(QDialog):
def __init__(self, parent, options, selected, title="Выберите значения"):
super().__init__(parent)
self.setWindowTitle(title)
self.resize(250, 300)
self.selected = set(selected)
layout = QVBoxLayout(self)
scroll = QScrollArea(self)
scroll.setWidgetResizable(True)
container = QWidget()
scroll.setWidget(container)
self.checkboxes = []
vbox = QVBoxLayout(container)
for opt in options:
cb = QCheckBox(opt)
cb.setChecked(opt in self.selected)
vbox.addWidget(cb)
self.checkboxes.append(cb)
layout.addWidget(scroll)
btn_layout = QHBoxLayout()
btn_ok = QPushButton("OK")
btn_cancel = QPushButton("Отмена")
btn_layout.addWidget(btn_ok)
btn_layout.addWidget(btn_cancel)
layout.addLayout(btn_layout)
btn_ok.clicked.connect(self.accept)
btn_cancel.clicked.connect(self.reject)
def get_selected(self):
return [cb.text() for cb in self.checkboxes if cb.isChecked()]
class SetSizeDialog(QDialog):
"""
Диалоговое окно для выбора числового значения (размера).
"""
def __init__(self, parent=None, initial_value=10, min_value=1, max_value=50, title="Укажите размер короткого имени",
label_text="Количество символов:"):
super().__init__(parent)
self.setWindowTitle(title)
self.setFixedSize(320, 120) # Задаем фиксированный размер для аккуратного вида
# Основной вертикальный макет
main_layout = QVBoxLayout(self)
# Макет для ввода значения
input_layout = QHBoxLayout()
label = QLabel(label_text, self)
self.spin_box = QSpinBox(self)
self.spin_box.setRange(min_value, max_value) # Устанавливаем диапазон допустимых значений
initial_value = parent._shortname_size
self.spin_box.setValue(initial_value) # Устанавливаем начальное значение
self.spin_box.setFocus() # Устанавливаем фокус на поле ввода
input_layout.addWidget(label)
input_layout.addWidget(self.spin_box)
main_layout.addLayout(input_layout)
# Добавляем пустое пространство для лучшего разделения
main_layout.addSpacerItem(QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Expanding))
# Макет для кнопок
btn_layout = QHBoxLayout()
btn_layout.addStretch() # Добавляем растягивающийся элемент, чтобы кнопки были справа
btn_ok = QPushButton("OK")
btn_cancel = QPushButton("Отмена")
btn_layout.addWidget(btn_ok)
btn_layout.addWidget(btn_cancel)
main_layout.addLayout(btn_layout)
# Подключение сигналов к слотам
btn_ok.clicked.connect(self.accept) # При нажатии "OK" диалог закроется со статусом "Accepted"
btn_cancel.clicked.connect(self.reject) # При нажатии "Отмена" - со статусом "Rejected"
def get_selected_size(self):
"""
Возвращает значение, выбранное в QSpinBox.
"""
return self.spin_box.value()
class CtrlScrollComboBox(QComboBox):
def wheelEvent(self, event):
if event.modifiers() & Qt.ControlModifier:
super().wheelEvent(event)
else:
event.ignore()
class VariableTableWidget(QTableWidget):
def __init__(self, parent=None, show_value_instead_of_shortname=0):
# Таблица переменных
if show_value_instead_of_shortname:
super().__init__(0, 8, parent)
self.setHorizontalHeaderLabels([
'№',
'En',
'Name',
'Origin Type',
'Base Type',
'IQ Type',
'Return Type',
'Value'
])
self._show_value = True
else:
super().__init__(0, 8, parent)
self.setHorizontalHeaderLabels([
'№',
'En',
'Name',
'Origin Type',
'Base Type',
'IQ Type',
'Return Type',
'Short Name'
])
self._show_value = False
self.setEditTriggers(QAbstractItemView.AllEditTriggers)
self.var_list = []
# QSettings
self.settings = QSettings("SET", "DebugVarEdit_VarTable")
shortsize = self.settings.value("shortname_size", True, type=int)
self._shortname_size = shortsize
if(self._show_value):
self._shortname_size = 3
self.type_options = list(dict.fromkeys(type_map.values()))
self.pt_types_all = [t.replace('pt_', '') for t in self.type_options]
self.iq_types_all = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)]
self.iq_types = ['iq_none', 'iq', 'iq10', 'iq15', 'iq19', 'iq24']
type_options = [t for t in dict.fromkeys(type_map.values()) if 'arr' not in t and 'ptr' not in t
and 'struct' not in t and 'union' not in t and '64' not in t]
self.pt_types = [t.replace('pt_', '') for t in type_options]
self._iq_type_filter = list(self.iq_types)
self._pt_type_filter = list(self.pt_types)
self._ret_type_filter = list(self.iq_types)
header = self.horizontalHeader()
for col in range(self.columnCount()):
if col == self.columnCount() - 1:
header.setSectionResizeMode(col, QHeaderView.Stretch)
else:
header.setSectionResizeMode(col, QHeaderView.Interactive)
self.setColumnWidth(rows.No, 30)
self.setColumnWidth(rows.include, 30)
self.setColumnWidth(rows.pt_type, 85)
self.setColumnWidth(rows.iq_type, 85)
self.setColumnWidth(rows.ret_type, 85)
self.setColumnWidth(rows.name, 300)
self.setColumnWidth(rows.type, 100)
self._resizing = False
self.horizontalHeader().sectionResized.connect(self.on_section_resized)
self.horizontalHeader().sectionClicked.connect(self.on_header_clicked)
def populate(self, vars_list, structs, on_change_callback):
self.var_list = vars_list
self.setUpdatesEnabled(False)
self.blockSignals(True)
for var in vars_list:
pt_type = var.get('pt_type', '')
if 'struct' in pt_type or 'union' in pt_type:
var['show_var'] = 'false'
var['enable'] = 'false'
filtered_vars = [v for v in vars_list if v.get('show_var', 'false') == 'true']
self.setRowCount(len(filtered_vars))
self.verticalHeader().setVisible(False)
style_with_padding = "padding-left: 5px; padding-right: 5px; font-size: 14pt; font-family: 'Segoe UI';"
for row, var in enumerate(filtered_vars):
# №
no_item = QTableWidgetItem(str(row))
no_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
self.setItem(row, rows.No, no_item)
# Enable
cb = QCheckBox()
cb.setChecked(var.get('enable', 'false') == 'true')
cb.stateChanged.connect(on_change_callback)
cb.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.include, cb)
# Name
name_edit = QLineEdit(var['name'])
name_edit.textChanged.connect(on_change_callback)
name_edit.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.name, name_edit)
# Origin Type
origin_item = QTableWidgetItem(var.get('type', ''))
origin_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
origin_item.setToolTip(var.get('type', ''))
origin_item.setForeground(QBrush(Qt.black))
self.setItem(row, rows.type, origin_item)
# pt_type
pt_combo = CtrlScrollComboBox()
pt_combo.addItems(self.pt_types)
value = var.get('pt_type', 'unknown').replace('pt_', '')
if value not in self.pt_types:
pt_combo.addItem(value)
pt_combo.setCurrentText(value)
pt_combo.currentTextChanged.connect(on_change_callback)
pt_combo.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.pt_type, pt_combo)
# iq_type
iq_combo = CtrlScrollComboBox()
iq_combo.addItems(self.iq_types)
value = var.get('iq_type', 'iq_none').replace('t_', '')
if value not in self.iq_types:
iq_combo.addItem(value)
iq_combo.setCurrentText(value)
iq_combo.currentTextChanged.connect(on_change_callback)
iq_combo.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.iq_type, iq_combo)
# return_type
ret_combo = CtrlScrollComboBox()
ret_combo.addItems(self.iq_types)
value = var.get('return_type', 'iq_none').replace('t_', '')
if value not in self.iq_types:
ret_combo.addItem(value)
ret_combo.setCurrentText(value)
ret_combo.currentTextChanged.connect(on_change_callback)
ret_combo.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.ret_type, ret_combo)
# Последний столбец
if self._show_value:
if self._show_value:
val = var.get('value', '')
if val is None:
val = ''
else:
try:
f_val = float(val)
# Форматируем число с учетом self._shortname_size
if f_val.is_integer():
val = str(int(f_val))
else:
precision = getattr(self, "_shortname_size", 3) # по умолчанию 3
val = f"{f_val:.{precision}f}"
except ValueError:
# Если значение не число (строка и т.п.), оставляем как есть
val = str(val)
val_edit = QLineEdit(val)
val_edit.textChanged.connect(on_change_callback)
val_edit.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.short_name, val_edit)
else:
short_name_val = var.get('shortname', var['name'])
short_name_edit = QLineEdit(short_name_val)
short_name_edit.textChanged.connect(on_change_callback)
short_name_edit.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.short_name, short_name_edit)
self.blockSignals(False)
self.setUpdatesEnabled(True)
self.check()
def check(self):
warning_color = QColor("#FFFACD")
error_color = QColor("#FFB6C1")
tooltip_shortname = "Short Name длиннее 10 символов — будет обрезано при генерации"
tooltip_missing = 'Имя переменной не найдено среди переменных. Добавьте её через кнопку "Добавить переменные"'
var_names_set = {v.get('name') for v in self.var_list if v.get('name')}
t0 = time.time()
self.setUpdatesEnabled(False)
for row in range(self.rowCount()):
t1 = time.time()
name_widget = self.cellWidget(row, rows.name)
t2 = time.time()
name = name_widget.text() if name_widget else ""
short_name_edit = self.cellWidget(row, rows.short_name)
t3 = time.time()
shortname = short_name_edit.text() if short_name_edit else ""
long_shortname = len(shortname) > self._shortname_size
found = name in var_names_set
color = None
tooltip = ""
if not found:
color = error_color
tooltip = tooltip_missing
elif long_shortname:
if not self._show_value:
color = warning_color
tooltip = tooltip_shortname
self.highlight_row(row, color, tooltip)
t4 = time.time()
self.setUpdatesEnabled(True)
#print(f"Row {row}: cellWidget(name) {t2-t1:.4f}s, cellWidget(shortname) {t3-t2:.4f}s, highlight_row {t4-t3:.4f}s")
def read_data(self):
result = []
for row in range(self.rowCount()):
cb = self.cellWidget(row, rows.include)
name = self.cellWidget(row, rows.name).text()
pt = self.cellWidget(row, rows.pt_type).currentText()
iq = self.cellWidget(row, rows.iq_type).currentText()
ret = self.cellWidget(row, rows.ret_type).currentText()
shortname = self.cellWidget(row, rows.short_name).text()
origin_type = self.item(row, rows.type).text()
result.append({
'show_var': True,
'enable': cb.isChecked(),
'name': name,
'pt_type': f'pt_{pt}',
'iq_type': f't_{iq}',
'return_type': f't_{ret}',
'shortname': shortname,
'type': origin_type,
})
return result
def on_header_clicked(self, logicalIndex):
if logicalIndex == rows.pt_type:
dlg = FilterDialog(self, self.pt_types_all, self._pt_type_filter, "Выберите базовые типы")
if dlg.exec_():
self._pt_type_filter = dlg.get_selected()
self.update_comboboxes({rows.pt_type: self._pt_type_filter})
elif logicalIndex == rows.iq_type:
dlg = FilterDialog(self, self.iq_types_all, self._iq_type_filter, "Выберите IQ типы")
if dlg.exec_():
self._iq_type_filter = dlg.get_selected()
self.update_comboboxes({rows.iq_type: self._iq_type_filter})
elif logicalIndex == rows.ret_type:
dlg = FilterDialog(self, self.iq_types_all, self._ret_type_filter, "Выберите IQ типы")
if dlg.exec_():
self._ret_type_filter = dlg.get_selected()
self.update_comboboxes({rows.ret_type: self._ret_type_filter})
elif logicalIndex == rows.short_name:
if self._show_value:
dlg = SetSizeDialog(self, title="Укажите точность", label_text="Кол-во знаков после запятой", initial_value=3)
else:
dlg = SetSizeDialog(self)
if dlg.exec_():
self._shortname_size = dlg.get_selected_size()
if not self._show_value:
self.settings.setValue("shortname_size", self._shortname_size)
self.check()
def update_comboboxes(self, columns_filters: Dict[int, List[str]]):
"""
Обновляет combobox-ячейки в указанных столбцах таблицы.
:param columns_filters: dict, где ключ — индекс столбца,
значение — список допустимых вариантов для combobox.
"""
for row in range(self.rowCount()):
for col, allowed_items in columns_filters.items():
combo = self.cellWidget(row, col)
if combo:
current = combo.currentText()
combo.blockSignals(True)
combo.clear()
combo.addItems(allowed_items)
if current not in allowed_items:
combo.addItem(current)
combo.setCurrentText(current)
combo.blockSignals(False)
def on_section_resized(self, logicalIndex, oldSize, newSize):
if self._resizing:
return # предотвращаем рекурсию
min_width = 50
delta = newSize - oldSize
right_index = logicalIndex + 1
if right_index >= self.columnCount():
# Если правая колока - нет соседа, ограничиваем минимальную ширину
if newSize < min_width:
self._resizing = True
self.setColumnWidth(logicalIndex, min_width)
self._resizing = False
return
self._resizing = True
try:
right_width = self.columnWidth(right_index)
new_right_width = right_width - delta
# Если соседняя колонка станет уже минимальной - подкорректируем левую
if new_right_width < min_width:
new_right_width = min_width
newSize = oldSize + (right_width - min_width)
self.setColumnWidth(logicalIndex, newSize)
self.setColumnWidth(right_index, new_right_width)
finally:
self._resizing = False
def highlight_row(self, row: int, color: QColor = None, tooltip: str = ""):
"""
Подсвечивает строку таблицы цветом `color`, не меняя шрифт.
Работает с QLineEdit, QComboBox, QCheckBox (включая обёртки).
Если `color=None`, сбрасывает подсветку.
"""
css_reset = "background-color: none; font: inherit;"
css_color = f"background-color: {color.name()};" if color else css_reset
for col in range(self.columnCount()):
item = self.item(row, col)
widget = self.cellWidget(row, col)
if item is not None:
current_bg = item.background().color() if item.background() else None
if color and current_bg != color:
item.setBackground(QBrush(color))
item.setToolTip(tooltip)
elif not color and current_bg is not None:
item.setBackground(QBrush(Qt.NoBrush))
item.setToolTip("")
elif widget is not None:
if widget.styleSheet() != css_color:
widget.setStyleSheet(css_color)
current_tip = widget.toolTip()
if color and current_tip != tooltip:
widget.setToolTip(tooltip)
elif not color and current_tip:
widget.setToolTip("")
def get_selected_var_names(self):
selected_indexes = self.selectedIndexes()
selected_rows = set(index.row() for index in selected_indexes)
names = []
for row in selected_rows:
name_widget = self.cellWidget(row, rows.name)
if name_widget:
name = name_widget.text()
if name:
names.append(name)
return names

View File

@@ -1,34 +0,0 @@
import subprocess
import shutil
import os
# Пути
dist_path = os.path.abspath("./") # текущая папка — exe будет тут
work_path = os.path.abspath("./build_temp")
spec_path = os.path.abspath("./build_temp")
script_dir = os.path.dirname(os.path.abspath(__file__))
libclang_path = os.path.join(script_dir, "libclang.dll")
# Запуск PyInstaller с нужными параметрами
cmd = [
"pyinstaller",
"--onefile",
"--windowed",
"--name", "DebugVarEdit",
"--add-binary", f"{libclang_path};.",
"--distpath", dist_path,
"--workpath", work_path,
"--specpath", spec_path,
"./Src/setupVars_GUI.py"
]
result = subprocess.run(cmd)
if result.returncode == 0:
# Удаляем временные папки
for folder in ["build_temp", "__pycache__"]:
if os.path.exists(folder):
shutil.rmtree(folder)
print("Сборка успешно завершена!")
else:
print("Сборка завершилась с ошибкой.")

View File

@@ -1,38 +0,0 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['..\\generateVars.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='generateVars',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)

View File

@@ -1,1066 +0,0 @@
(['F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\generateVars.py'],
['F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools'],
[],
[('C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\_pyinstaller_hooks_contrib\\stdhooks',
-1000),
('C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\_pyinstaller_hooks_contrib',
-1000)],
{},
[],
[],
False,
{},
0,
[],
[],
'3.13.3 (tags/v3.13.3:6280bb5, Apr 8 2025, 14:47:33) [MSC v.1943 64 bit '
'(AMD64)]',
[('pyi_rth_inspect',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py',
'PYSOURCE'),
('generateVars',
'F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\generateVars.py',
'PYSOURCE')],
[('zipfile',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\zipfile\\__init__.py',
'PYMODULE'),
('zipfile._path',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\zipfile\\_path\\__init__.py',
'PYMODULE'),
('zipfile._path.glob',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\zipfile\\_path\\glob.py',
'PYMODULE'),
('contextlib',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\contextlib.py',
'PYMODULE'),
('py_compile',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\py_compile.py',
'PYMODULE'),
('importlib.machinery',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\machinery.py',
'PYMODULE'),
('importlib',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\__init__.py',
'PYMODULE'),
('importlib._bootstrap',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\_bootstrap.py',
'PYMODULE'),
('importlib._bootstrap_external',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\_bootstrap_external.py',
'PYMODULE'),
('importlib.metadata',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\metadata\\__init__.py',
'PYMODULE'),
('csv',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\csv.py',
'PYMODULE'),
('importlib.metadata._adapters',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\metadata\\_adapters.py',
'PYMODULE'),
('importlib.metadata._text',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\metadata\\_text.py',
'PYMODULE'),
('email.message',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\message.py',
'PYMODULE'),
('email.policy',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\policy.py',
'PYMODULE'),
('email.contentmanager',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\contentmanager.py',
'PYMODULE'),
('email.quoprimime',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\quoprimime.py',
'PYMODULE'),
('string',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\string.py',
'PYMODULE'),
('email.headerregistry',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\headerregistry.py',
'PYMODULE'),
('email._header_value_parser',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\_header_value_parser.py',
'PYMODULE'),
('urllib',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\urllib\\__init__.py',
'PYMODULE'),
('email.iterators',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\iterators.py',
'PYMODULE'),
('email.generator',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\generator.py',
'PYMODULE'),
('copy',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\copy.py',
'PYMODULE'),
('random',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\random.py',
'PYMODULE'),
('statistics',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\statistics.py',
'PYMODULE'),
('decimal',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\decimal.py',
'PYMODULE'),
('_pydecimal',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_pydecimal.py',
'PYMODULE'),
('contextvars',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\contextvars.py',
'PYMODULE'),
('fractions',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\fractions.py',
'PYMODULE'),
('numbers',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\numbers.py',
'PYMODULE'),
('hashlib',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\hashlib.py',
'PYMODULE'),
('logging',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\logging\\__init__.py',
'PYMODULE'),
('pickle',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\pickle.py',
'PYMODULE'),
('pprint',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\pprint.py',
'PYMODULE'),
('dataclasses',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\dataclasses.py',
'PYMODULE'),
('_compat_pickle',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_compat_pickle.py',
'PYMODULE'),
('bisect',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\bisect.py',
'PYMODULE'),
('email._encoded_words',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\_encoded_words.py',
'PYMODULE'),
('base64',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\base64.py',
'PYMODULE'),
('getopt',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\getopt.py',
'PYMODULE'),
('gettext',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\gettext.py',
'PYMODULE'),
('email.charset',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\charset.py',
'PYMODULE'),
('email.encoders',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\encoders.py',
'PYMODULE'),
('email.base64mime',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\base64mime.py',
'PYMODULE'),
('email._policybase',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\_policybase.py',
'PYMODULE'),
('email.header',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\header.py',
'PYMODULE'),
('email.errors',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\errors.py',
'PYMODULE'),
('email.utils',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\utils.py',
'PYMODULE'),
('socket',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\socket.py',
'PYMODULE'),
('selectors',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\selectors.py',
'PYMODULE'),
('email._parseaddr',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\_parseaddr.py',
'PYMODULE'),
('calendar',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\calendar.py',
'PYMODULE'),
('urllib.parse',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\urllib\\parse.py',
'PYMODULE'),
('ipaddress',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\ipaddress.py',
'PYMODULE'),
('datetime',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\datetime.py',
'PYMODULE'),
('_pydatetime',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_pydatetime.py',
'PYMODULE'),
('_strptime',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_strptime.py',
'PYMODULE'),
('quopri',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\quopri.py',
'PYMODULE'),
('typing',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\typing.py',
'PYMODULE'),
('importlib.abc',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\abc.py',
'PYMODULE'),
('importlib.resources.abc',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\resources\\abc.py',
'PYMODULE'),
('importlib.resources',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\resources\\__init__.py',
'PYMODULE'),
('importlib.resources._functional',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\resources\\_functional.py',
'PYMODULE'),
('importlib.resources._common',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\resources\\_common.py',
'PYMODULE'),
('importlib.resources._adapters',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\resources\\_adapters.py',
'PYMODULE'),
('tempfile',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\tempfile.py',
'PYMODULE'),
('importlib._abc',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\_abc.py',
'PYMODULE'),
('importlib.metadata._itertools',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\metadata\\_itertools.py',
'PYMODULE'),
('importlib.metadata._functools',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\metadata\\_functools.py',
'PYMODULE'),
('importlib.metadata._collections',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\metadata\\_collections.py',
'PYMODULE'),
('importlib.metadata._meta',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\metadata\\_meta.py',
'PYMODULE'),
('textwrap',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\textwrap.py',
'PYMODULE'),
('email',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\__init__.py',
'PYMODULE'),
('email.parser',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\parser.py',
'PYMODULE'),
('email.feedparser',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\feedparser.py',
'PYMODULE'),
('json',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\json\\__init__.py',
'PYMODULE'),
('json.encoder',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\json\\encoder.py',
'PYMODULE'),
('json.decoder',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\json\\decoder.py',
'PYMODULE'),
('json.scanner',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\json\\scanner.py',
'PYMODULE'),
('__future__',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\__future__.py',
'PYMODULE'),
('importlib.readers',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\readers.py',
'PYMODULE'),
('importlib.resources.readers',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\resources\\readers.py',
'PYMODULE'),
('importlib.resources._itertools',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\resources\\_itertools.py',
'PYMODULE'),
('tokenize',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\tokenize.py',
'PYMODULE'),
('token',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\token.py',
'PYMODULE'),
('lzma',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\lzma.py',
'PYMODULE'),
('_compression',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_compression.py',
'PYMODULE'),
('bz2',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\bz2.py',
'PYMODULE'),
('threading',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\threading.py',
'PYMODULE'),
('_threading_local',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_threading_local.py',
'PYMODULE'),
('struct',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\struct.py',
'PYMODULE'),
('shutil',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\shutil.py',
'PYMODULE'),
('tarfile',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\tarfile.py',
'PYMODULE'),
('gzip',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\gzip.py',
'PYMODULE'),
('fnmatch',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\fnmatch.py',
'PYMODULE'),
('importlib.util',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\util.py',
'PYMODULE'),
('inspect',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\inspect.py',
'PYMODULE'),
('dis',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\dis.py',
'PYMODULE'),
('opcode',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\opcode.py',
'PYMODULE'),
('_opcode_metadata',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_opcode_metadata.py',
'PYMODULE'),
('ast',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\ast.py',
'PYMODULE'),
('_py_abc',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_py_abc.py',
'PYMODULE'),
('stringprep',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\stringprep.py',
'PYMODULE'),
('tracemalloc',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\tracemalloc.py',
'PYMODULE'),
('_colorize',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_colorize.py',
'PYMODULE'),
('argparse',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\argparse.py',
'PYMODULE'),
('pathlib',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\pathlib\\__init__.py',
'PYMODULE'),
('pathlib._local',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\pathlib\\_local.py',
'PYMODULE'),
('glob',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\glob.py',
'PYMODULE'),
('pathlib._abc',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\pathlib\\_abc.py',
'PYMODULE'),
('xml.etree.ElementTree',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\etree\\ElementTree.py',
'PYMODULE'),
('xml.etree.cElementTree',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\etree\\cElementTree.py',
'PYMODULE'),
('xml.etree.ElementInclude',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\etree\\ElementInclude.py',
'PYMODULE'),
('xml.parsers.expat',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\parsers\\expat.py',
'PYMODULE'),
('xml.parsers',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\parsers\\__init__.py',
'PYMODULE'),
('xml',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\__init__.py',
'PYMODULE'),
('xml.sax.expatreader',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\sax\\expatreader.py',
'PYMODULE'),
('xml.sax.saxutils',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\sax\\saxutils.py',
'PYMODULE'),
('urllib.request',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\urllib\\request.py',
'PYMODULE'),
('getpass',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\getpass.py',
'PYMODULE'),
('nturl2path',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\nturl2path.py',
'PYMODULE'),
('ftplib',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\ftplib.py',
'PYMODULE'),
('netrc',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\netrc.py',
'PYMODULE'),
('mimetypes',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\mimetypes.py',
'PYMODULE'),
('http.cookiejar',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\http\\cookiejar.py',
'PYMODULE'),
('http',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\http\\__init__.py',
'PYMODULE'),
('ssl',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\ssl.py',
'PYMODULE'),
('urllib.response',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\urllib\\response.py',
'PYMODULE'),
('urllib.error',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\urllib\\error.py',
'PYMODULE'),
('http.client',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\http\\client.py',
'PYMODULE'),
('xml.sax',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\sax\\__init__.py',
'PYMODULE'),
('xml.sax.handler',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\sax\\handler.py',
'PYMODULE'),
('xml.sax._exceptions',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\sax\\_exceptions.py',
'PYMODULE'),
('xml.sax.xmlreader',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\sax\\xmlreader.py',
'PYMODULE'),
('xml.etree.ElementPath',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\etree\\ElementPath.py',
'PYMODULE'),
('xml.etree',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\etree\\__init__.py',
'PYMODULE'),
('subprocess',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\subprocess.py',
'PYMODULE'),
('signal',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\signal.py',
'PYMODULE')],
[('python313.dll',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\python313.dll',
'BINARY'),
('_decimal.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\_decimal.pyd',
'EXTENSION'),
('_hashlib.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\_hashlib.pyd',
'EXTENSION'),
('select.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\select.pyd',
'EXTENSION'),
('_socket.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\_socket.pyd',
'EXTENSION'),
('unicodedata.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\unicodedata.pyd',
'EXTENSION'),
('_lzma.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\_lzma.pyd',
'EXTENSION'),
('_bz2.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\_bz2.pyd',
'EXTENSION'),
('_elementtree.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\_elementtree.pyd',
'EXTENSION'),
('pyexpat.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\pyexpat.pyd',
'EXTENSION'),
('_ssl.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\_ssl.pyd',
'EXTENSION'),
('api-ms-win-crt-environment-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-environment-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-heap-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-heap-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-filesystem-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-filesystem-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-convert-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-convert-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-stdio-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-stdio-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-locale-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-locale-l1-1-0.dll',
'BINARY'),
('VCRUNTIME140.dll',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\VCRUNTIME140.dll',
'BINARY'),
('api-ms-win-crt-time-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-time-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-runtime-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-runtime-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-string-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-string-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-math-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-math-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-process-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-process-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-conio-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-conio-l1-1-0.dll',
'BINARY'),
('libcrypto-3.dll',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\libcrypto-3.dll',
'BINARY'),
('api-ms-win-crt-utility-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-utility-l1-1-0.dll',
'BINARY'),
('libssl-3.dll',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\libssl-3.dll',
'BINARY'),
('ucrtbase.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\ucrtbase.dll',
'BINARY'),
('api-ms-win-core-interlocked-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-interlocked-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-datetime-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-datetime-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-string-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-string-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-timezone-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-timezone-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-errorhandling-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-errorhandling-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-profile-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-profile-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-console-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-console-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-processthreads-l1-1-1.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-processthreads-l1-1-1.dll',
'BINARY'),
('api-ms-win-core-namedpipe-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-namedpipe-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-heap-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-heap-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-file-l1-2-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-file-l1-2-0.dll',
'BINARY'),
('api-ms-win-core-file-l2-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-file-l2-1-0.dll',
'BINARY'),
('api-ms-win-core-libraryloader-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-libraryloader-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-debug-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-debug-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-rtlsupport-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-rtlsupport-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-sysinfo-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-sysinfo-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-file-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-file-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-localization-l1-2-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-localization-l1-2-0.dll',
'BINARY'),
('api-ms-win-core-util-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-util-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-handle-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-handle-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-synch-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-synch-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-memory-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-memory-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-processenvironment-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-processenvironment-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-processthreads-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-processthreads-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-synch-l1-2-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-synch-l1-2-0.dll',
'BINARY')],
[],
[],
[('base_library.zip',
'F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\build\\generateVars\\base_library.zip',
'DATA')],
[('weakref',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\weakref.py',
'PYMODULE'),
('_weakrefset',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_weakrefset.py',
'PYMODULE'),
('copyreg',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\copyreg.py',
'PYMODULE'),
('abc',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\abc.py',
'PYMODULE'),
('heapq',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\heapq.py',
'PYMODULE'),
('codecs',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\codecs.py',
'PYMODULE'),
('linecache',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\linecache.py',
'PYMODULE'),
('sre_constants',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\sre_constants.py',
'PYMODULE'),
('keyword',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\keyword.py',
'PYMODULE'),
('ntpath',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\ntpath.py',
'PYMODULE'),
('locale',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\locale.py',
'PYMODULE'),
('posixpath',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\posixpath.py',
'PYMODULE'),
('genericpath',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\genericpath.py',
'PYMODULE'),
('reprlib',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\reprlib.py',
'PYMODULE'),
('collections',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\collections\\__init__.py',
'PYMODULE'),
('types',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\types.py',
'PYMODULE'),
('stat',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\stat.py',
'PYMODULE'),
('encodings.zlib_codec',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\zlib_codec.py',
'PYMODULE'),
('encodings.uu_codec',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\uu_codec.py',
'PYMODULE'),
('encodings.utf_8_sig',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\utf_8_sig.py',
'PYMODULE'),
('encodings.utf_8',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\utf_8.py',
'PYMODULE'),
('encodings.utf_7',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\utf_7.py',
'PYMODULE'),
('encodings.utf_32_le',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\utf_32_le.py',
'PYMODULE'),
('encodings.utf_32_be',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\utf_32_be.py',
'PYMODULE'),
('encodings.utf_32',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\utf_32.py',
'PYMODULE'),
('encodings.utf_16_le',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\utf_16_le.py',
'PYMODULE'),
('encodings.utf_16_be',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\utf_16_be.py',
'PYMODULE'),
('encodings.utf_16',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\utf_16.py',
'PYMODULE'),
('encodings.unicode_escape',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\unicode_escape.py',
'PYMODULE'),
('encodings.undefined',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\undefined.py',
'PYMODULE'),
('encodings.tis_620',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\tis_620.py',
'PYMODULE'),
('encodings.shift_jisx0213',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\shift_jisx0213.py',
'PYMODULE'),
('encodings.shift_jis_2004',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\shift_jis_2004.py',
'PYMODULE'),
('encodings.shift_jis',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\shift_jis.py',
'PYMODULE'),
('encodings.rot_13',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\rot_13.py',
'PYMODULE'),
('encodings.raw_unicode_escape',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\raw_unicode_escape.py',
'PYMODULE'),
('encodings.quopri_codec',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\quopri_codec.py',
'PYMODULE'),
('encodings.punycode',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\punycode.py',
'PYMODULE'),
('encodings.ptcp154',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\ptcp154.py',
'PYMODULE'),
('encodings.palmos',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\palmos.py',
'PYMODULE'),
('encodings.oem',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\oem.py',
'PYMODULE'),
('encodings.mbcs',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\mbcs.py',
'PYMODULE'),
('encodings.mac_turkish',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\mac_turkish.py',
'PYMODULE'),
('encodings.mac_romanian',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\mac_romanian.py',
'PYMODULE'),
('encodings.mac_roman',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\mac_roman.py',
'PYMODULE'),
('encodings.mac_latin2',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\mac_latin2.py',
'PYMODULE'),
('encodings.mac_iceland',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\mac_iceland.py',
'PYMODULE'),
('encodings.mac_greek',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\mac_greek.py',
'PYMODULE'),
('encodings.mac_farsi',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\mac_farsi.py',
'PYMODULE'),
('encodings.mac_cyrillic',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\mac_cyrillic.py',
'PYMODULE'),
('encodings.mac_croatian',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\mac_croatian.py',
'PYMODULE'),
('encodings.mac_arabic',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\mac_arabic.py',
'PYMODULE'),
('encodings.latin_1',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\latin_1.py',
'PYMODULE'),
('encodings.kz1048',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\kz1048.py',
'PYMODULE'),
('encodings.koi8_u',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\koi8_u.py',
'PYMODULE'),
('encodings.koi8_t',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\koi8_t.py',
'PYMODULE'),
('encodings.koi8_r',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\koi8_r.py',
'PYMODULE'),
('encodings.johab',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\johab.py',
'PYMODULE'),
('encodings.iso8859_9',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso8859_9.py',
'PYMODULE'),
('encodings.iso8859_8',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso8859_8.py',
'PYMODULE'),
('encodings.iso8859_7',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso8859_7.py',
'PYMODULE'),
('encodings.iso8859_6',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso8859_6.py',
'PYMODULE'),
('encodings.iso8859_5',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso8859_5.py',
'PYMODULE'),
('encodings.iso8859_4',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso8859_4.py',
'PYMODULE'),
('encodings.iso8859_3',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso8859_3.py',
'PYMODULE'),
('encodings.iso8859_2',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso8859_2.py',
'PYMODULE'),
('encodings.iso8859_16',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso8859_16.py',
'PYMODULE'),
('encodings.iso8859_15',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso8859_15.py',
'PYMODULE'),
('encodings.iso8859_14',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso8859_14.py',
'PYMODULE'),
('encodings.iso8859_13',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso8859_13.py',
'PYMODULE'),
('encodings.iso8859_11',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso8859_11.py',
'PYMODULE'),
('encodings.iso8859_10',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso8859_10.py',
'PYMODULE'),
('encodings.iso8859_1',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso8859_1.py',
'PYMODULE'),
('encodings.iso2022_kr',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso2022_kr.py',
'PYMODULE'),
('encodings.iso2022_jp_ext',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso2022_jp_ext.py',
'PYMODULE'),
('encodings.iso2022_jp_3',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso2022_jp_3.py',
'PYMODULE'),
('encodings.iso2022_jp_2004',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso2022_jp_2004.py',
'PYMODULE'),
('encodings.iso2022_jp_2',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso2022_jp_2.py',
'PYMODULE'),
('encodings.iso2022_jp_1',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso2022_jp_1.py',
'PYMODULE'),
('encodings.iso2022_jp',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\iso2022_jp.py',
'PYMODULE'),
('encodings.idna',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\idna.py',
'PYMODULE'),
('encodings.hz',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\hz.py',
'PYMODULE'),
('encodings.hp_roman8',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\hp_roman8.py',
'PYMODULE'),
('encodings.hex_codec',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\hex_codec.py',
'PYMODULE'),
('encodings.gbk',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\gbk.py',
'PYMODULE'),
('encodings.gb2312',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\gb2312.py',
'PYMODULE'),
('encodings.gb18030',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\gb18030.py',
'PYMODULE'),
('encodings.euc_kr',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\euc_kr.py',
'PYMODULE'),
('encodings.euc_jp',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\euc_jp.py',
'PYMODULE'),
('encodings.euc_jisx0213',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\euc_jisx0213.py',
'PYMODULE'),
('encodings.euc_jis_2004',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\euc_jis_2004.py',
'PYMODULE'),
('encodings.cp950',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp950.py',
'PYMODULE'),
('encodings.cp949',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp949.py',
'PYMODULE'),
('encodings.cp932',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp932.py',
'PYMODULE'),
('encodings.cp875',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp875.py',
'PYMODULE'),
('encodings.cp874',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp874.py',
'PYMODULE'),
('encodings.cp869',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp869.py',
'PYMODULE'),
('encodings.cp866',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp866.py',
'PYMODULE'),
('encodings.cp865',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp865.py',
'PYMODULE'),
('encodings.cp864',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp864.py',
'PYMODULE'),
('encodings.cp863',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp863.py',
'PYMODULE'),
('encodings.cp862',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp862.py',
'PYMODULE'),
('encodings.cp861',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp861.py',
'PYMODULE'),
('encodings.cp860',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp860.py',
'PYMODULE'),
('encodings.cp858',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp858.py',
'PYMODULE'),
('encodings.cp857',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp857.py',
'PYMODULE'),
('encodings.cp856',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp856.py',
'PYMODULE'),
('encodings.cp855',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp855.py',
'PYMODULE'),
('encodings.cp852',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp852.py',
'PYMODULE'),
('encodings.cp850',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp850.py',
'PYMODULE'),
('encodings.cp775',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp775.py',
'PYMODULE'),
('encodings.cp737',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp737.py',
'PYMODULE'),
('encodings.cp720',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp720.py',
'PYMODULE'),
('encodings.cp500',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp500.py',
'PYMODULE'),
('encodings.cp437',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp437.py',
'PYMODULE'),
('encodings.cp424',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp424.py',
'PYMODULE'),
('encodings.cp273',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp273.py',
'PYMODULE'),
('encodings.cp1258',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp1258.py',
'PYMODULE'),
('encodings.cp1257',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp1257.py',
'PYMODULE'),
('encodings.cp1256',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp1256.py',
'PYMODULE'),
('encodings.cp1255',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp1255.py',
'PYMODULE'),
('encodings.cp1254',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp1254.py',
'PYMODULE'),
('encodings.cp1253',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp1253.py',
'PYMODULE'),
('encodings.cp1252',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp1252.py',
'PYMODULE'),
('encodings.cp1251',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp1251.py',
'PYMODULE'),
('encodings.cp1250',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp1250.py',
'PYMODULE'),
('encodings.cp1140',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp1140.py',
'PYMODULE'),
('encodings.cp1125',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp1125.py',
'PYMODULE'),
('encodings.cp1026',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp1026.py',
'PYMODULE'),
('encodings.cp1006',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp1006.py',
'PYMODULE'),
('encodings.cp037',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\cp037.py',
'PYMODULE'),
('encodings.charmap',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\charmap.py',
'PYMODULE'),
('encodings.bz2_codec',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\bz2_codec.py',
'PYMODULE'),
('encodings.big5hkscs',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\big5hkscs.py',
'PYMODULE'),
('encodings.big5',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\big5.py',
'PYMODULE'),
('encodings.base64_codec',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\base64_codec.py',
'PYMODULE'),
('encodings.ascii',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\ascii.py',
'PYMODULE'),
('encodings.aliases',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\aliases.py',
'PYMODULE'),
('encodings',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\encodings\\__init__.py',
'PYMODULE'),
('operator',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\operator.py',
'PYMODULE'),
('functools',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\functools.py',
'PYMODULE'),
('re._parser',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\re\\_parser.py',
'PYMODULE'),
('re._constants',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\re\\_constants.py',
'PYMODULE'),
('re._compiler',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\re\\_compiler.py',
'PYMODULE'),
('re._casefix',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\re\\_casefix.py',
'PYMODULE'),
('_collections_abc',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_collections_abc.py',
'PYMODULE'),
('warnings',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\warnings.py',
'PYMODULE'),
('traceback',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\traceback.py',
'PYMODULE'),
('enum',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\enum.py',
'PYMODULE'),
('sre_parse',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\sre_parse.py',
'PYMODULE'),
('sre_compile',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\sre_compile.py',
'PYMODULE'),
('io',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\io.py',
'PYMODULE'),
('re',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\re\\__init__.py',
'PYMODULE'),
('os',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\os.py',
'PYMODULE')])

View File

@@ -1,230 +0,0 @@
('F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\generateVars.exe',
True,
False,
False,
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\PyInstaller\\bootloader\\images\\icon-console.ico',
None,
False,
False,
b'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<assembly xmlns='
b'"urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">\n <trustInfo x'
b'mlns="urn:schemas-microsoft-com:asm.v3">\n <security>\n <requested'
b'Privileges>\n <requestedExecutionLevel level="asInvoker" uiAccess='
b'"false"/>\n </requestedPrivileges>\n </security>\n </trustInfo>\n '
b'<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">\n <'
b'application>\n <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f'
b'0}"/>\n <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>\n '
b' <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>\n <s'
b'upportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>\n <supporte'
b'dOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>\n </application>\n <'
b'/compatibility>\n <application xmlns="urn:schemas-microsoft-com:asm.v3">'
b'\n <windowsSettings>\n <longPathAware xmlns="http://schemas.micros'
b'oft.com/SMI/2016/WindowsSettings">true</longPathAware>\n </windowsSett'
b'ings>\n </application>\n <dependency>\n <dependentAssembly>\n <ass'
b'emblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version='
b'"6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" langua'
b'ge="*"/>\n </dependentAssembly>\n </dependency>\n</assembly>',
True,
False,
None,
None,
None,
'F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\build\\generateVars\\generateVars.pkg',
[('pyi-contents-directory _internal', '', 'OPTION'),
('PYZ-00.pyz',
'F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\build\\generateVars\\PYZ-00.pyz',
'PYZ'),
('struct',
'F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\build\\generateVars\\localpycs\\struct.pyc',
'PYMODULE'),
('pyimod01_archive',
'F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\build\\generateVars\\localpycs\\pyimod01_archive.pyc',
'PYMODULE'),
('pyimod02_importers',
'F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\build\\generateVars\\localpycs\\pyimod02_importers.pyc',
'PYMODULE'),
('pyimod03_ctypes',
'F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\build\\generateVars\\localpycs\\pyimod03_ctypes.pyc',
'PYMODULE'),
('pyimod04_pywin32',
'F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\build\\generateVars\\localpycs\\pyimod04_pywin32.pyc',
'PYMODULE'),
('pyiboot01_bootstrap',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\PyInstaller\\loader\\pyiboot01_bootstrap.py',
'PYSOURCE'),
('pyi_rth_inspect',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py',
'PYSOURCE'),
('generateVars',
'F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\generateVars.py',
'PYSOURCE'),
('python313.dll',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\python313.dll',
'BINARY'),
('_decimal.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\_decimal.pyd',
'EXTENSION'),
('_hashlib.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\_hashlib.pyd',
'EXTENSION'),
('select.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\select.pyd',
'EXTENSION'),
('_socket.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\_socket.pyd',
'EXTENSION'),
('unicodedata.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\unicodedata.pyd',
'EXTENSION'),
('_lzma.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\_lzma.pyd',
'EXTENSION'),
('_bz2.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\_bz2.pyd',
'EXTENSION'),
('_elementtree.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\_elementtree.pyd',
'EXTENSION'),
('pyexpat.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\pyexpat.pyd',
'EXTENSION'),
('_ssl.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\_ssl.pyd',
'EXTENSION'),
('api-ms-win-crt-environment-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-environment-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-heap-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-heap-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-filesystem-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-filesystem-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-convert-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-convert-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-stdio-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-stdio-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-locale-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-locale-l1-1-0.dll',
'BINARY'),
('VCRUNTIME140.dll',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\VCRUNTIME140.dll',
'BINARY'),
('api-ms-win-crt-time-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-time-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-runtime-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-runtime-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-string-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-string-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-math-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-math-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-process-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-process-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-conio-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-conio-l1-1-0.dll',
'BINARY'),
('libcrypto-3.dll',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\libcrypto-3.dll',
'BINARY'),
('api-ms-win-crt-utility-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-utility-l1-1-0.dll',
'BINARY'),
('libssl-3.dll',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\libssl-3.dll',
'BINARY'),
('ucrtbase.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\ucrtbase.dll',
'BINARY'),
('api-ms-win-core-interlocked-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-interlocked-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-datetime-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-datetime-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-string-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-string-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-timezone-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-timezone-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-errorhandling-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-errorhandling-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-profile-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-profile-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-console-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-console-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-processthreads-l1-1-1.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-processthreads-l1-1-1.dll',
'BINARY'),
('api-ms-win-core-namedpipe-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-namedpipe-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-heap-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-heap-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-file-l1-2-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-file-l1-2-0.dll',
'BINARY'),
('api-ms-win-core-file-l2-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-file-l2-1-0.dll',
'BINARY'),
('api-ms-win-core-libraryloader-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-libraryloader-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-debug-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-debug-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-rtlsupport-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-rtlsupport-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-sysinfo-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-sysinfo-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-file-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-file-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-localization-l1-2-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-localization-l1-2-0.dll',
'BINARY'),
('api-ms-win-core-util-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-util-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-handle-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-handle-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-synch-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-synch-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-memory-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-memory-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-processenvironment-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-processenvironment-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-processthreads-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-processthreads-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-synch-l1-2-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-synch-l1-2-0.dll',
'BINARY'),
('base_library.zip',
'F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\build\\generateVars\\base_library.zip',
'DATA')],
[],
False,
False,
1751903262,
[('run.exe',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\PyInstaller\\bootloader\\Windows-64bit-intel\\run.exe',
'EXECUTABLE')],
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\python313.dll')

View File

@@ -1,208 +0,0 @@
('F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\build\\generateVars\\generateVars.pkg',
{'BINARY': True,
'DATA': True,
'EXECUTABLE': True,
'EXTENSION': True,
'PYMODULE': True,
'PYSOURCE': True,
'PYZ': False,
'SPLASH': True,
'SYMLINK': False},
[('pyi-contents-directory _internal', '', 'OPTION'),
('PYZ-00.pyz',
'F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\build\\generateVars\\PYZ-00.pyz',
'PYZ'),
('struct',
'F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\build\\generateVars\\localpycs\\struct.pyc',
'PYMODULE'),
('pyimod01_archive',
'F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\build\\generateVars\\localpycs\\pyimod01_archive.pyc',
'PYMODULE'),
('pyimod02_importers',
'F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\build\\generateVars\\localpycs\\pyimod02_importers.pyc',
'PYMODULE'),
('pyimod03_ctypes',
'F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\build\\generateVars\\localpycs\\pyimod03_ctypes.pyc',
'PYMODULE'),
('pyimod04_pywin32',
'F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\build\\generateVars\\localpycs\\pyimod04_pywin32.pyc',
'PYMODULE'),
('pyiboot01_bootstrap',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\PyInstaller\\loader\\pyiboot01_bootstrap.py',
'PYSOURCE'),
('pyi_rth_inspect',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py',
'PYSOURCE'),
('generateVars',
'F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\generateVars.py',
'PYSOURCE'),
('python313.dll',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\python313.dll',
'BINARY'),
('_decimal.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\_decimal.pyd',
'EXTENSION'),
('_hashlib.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\_hashlib.pyd',
'EXTENSION'),
('select.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\select.pyd',
'EXTENSION'),
('_socket.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\_socket.pyd',
'EXTENSION'),
('unicodedata.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\unicodedata.pyd',
'EXTENSION'),
('_lzma.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\_lzma.pyd',
'EXTENSION'),
('_bz2.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\_bz2.pyd',
'EXTENSION'),
('_elementtree.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\_elementtree.pyd',
'EXTENSION'),
('pyexpat.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\pyexpat.pyd',
'EXTENSION'),
('_ssl.pyd',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\_ssl.pyd',
'EXTENSION'),
('api-ms-win-crt-environment-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-environment-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-heap-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-heap-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-filesystem-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-filesystem-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-convert-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-convert-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-stdio-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-stdio-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-locale-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-locale-l1-1-0.dll',
'BINARY'),
('VCRUNTIME140.dll',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\VCRUNTIME140.dll',
'BINARY'),
('api-ms-win-crt-time-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-time-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-runtime-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-runtime-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-string-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-string-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-math-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-math-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-process-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-process-l1-1-0.dll',
'BINARY'),
('api-ms-win-crt-conio-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-conio-l1-1-0.dll',
'BINARY'),
('libcrypto-3.dll',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\libcrypto-3.dll',
'BINARY'),
('api-ms-win-crt-utility-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-crt-utility-l1-1-0.dll',
'BINARY'),
('libssl-3.dll',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\DLLs\\libssl-3.dll',
'BINARY'),
('ucrtbase.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\ucrtbase.dll',
'BINARY'),
('api-ms-win-core-interlocked-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-interlocked-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-datetime-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-datetime-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-string-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-string-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-timezone-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-timezone-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-errorhandling-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-errorhandling-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-profile-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-profile-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-console-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-console-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-processthreads-l1-1-1.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-processthreads-l1-1-1.dll',
'BINARY'),
('api-ms-win-core-namedpipe-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-namedpipe-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-heap-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-heap-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-file-l1-2-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-file-l1-2-0.dll',
'BINARY'),
('api-ms-win-core-file-l2-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-file-l2-1-0.dll',
'BINARY'),
('api-ms-win-core-libraryloader-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-libraryloader-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-debug-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-debug-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-rtlsupport-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-rtlsupport-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-sysinfo-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-sysinfo-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-file-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-file-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-localization-l1-2-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-localization-l1-2-0.dll',
'BINARY'),
('api-ms-win-core-util-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-util-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-handle-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-handle-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-synch-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-synch-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-memory-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-memory-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-processenvironment-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-processenvironment-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-processthreads-l1-1-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-processthreads-l1-1-0.dll',
'BINARY'),
('api-ms-win-core-synch-l1-2-0.dll',
'F:\\Work\\Programs\\Active-HDL-13-x64\\bin\\api-ms-win-core-synch-l1-2-0.dll',
'BINARY'),
('base_library.zip',
'F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\build\\generateVars\\base_library.zip',
'DATA')],
'python313.dll',
False,
False,
False,
[],
None,
None,
None)

Binary file not shown.

View File

@@ -1,415 +0,0 @@
('F:\\Work\\Projects\\TMS\\TMS_new_bus\\Src\\DebugTools\\build\\generateVars\\PYZ-00.pyz',
[('__future__',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\__future__.py',
'PYMODULE'),
('_colorize',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_colorize.py',
'PYMODULE'),
('_compat_pickle',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_compat_pickle.py',
'PYMODULE'),
('_compression',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_compression.py',
'PYMODULE'),
('_opcode_metadata',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_opcode_metadata.py',
'PYMODULE'),
('_py_abc',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_py_abc.py',
'PYMODULE'),
('_pydatetime',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_pydatetime.py',
'PYMODULE'),
('_pydecimal',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_pydecimal.py',
'PYMODULE'),
('_strptime',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_strptime.py',
'PYMODULE'),
('_threading_local',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\_threading_local.py',
'PYMODULE'),
('argparse',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\argparse.py',
'PYMODULE'),
('ast',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\ast.py',
'PYMODULE'),
('base64',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\base64.py',
'PYMODULE'),
('bisect',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\bisect.py',
'PYMODULE'),
('bz2',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\bz2.py',
'PYMODULE'),
('calendar',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\calendar.py',
'PYMODULE'),
('contextlib',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\contextlib.py',
'PYMODULE'),
('contextvars',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\contextvars.py',
'PYMODULE'),
('copy',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\copy.py',
'PYMODULE'),
('csv',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\csv.py',
'PYMODULE'),
('dataclasses',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\dataclasses.py',
'PYMODULE'),
('datetime',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\datetime.py',
'PYMODULE'),
('decimal',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\decimal.py',
'PYMODULE'),
('dis',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\dis.py',
'PYMODULE'),
('email',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\__init__.py',
'PYMODULE'),
('email._encoded_words',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\_encoded_words.py',
'PYMODULE'),
('email._header_value_parser',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\_header_value_parser.py',
'PYMODULE'),
('email._parseaddr',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\_parseaddr.py',
'PYMODULE'),
('email._policybase',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\_policybase.py',
'PYMODULE'),
('email.base64mime',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\base64mime.py',
'PYMODULE'),
('email.charset',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\charset.py',
'PYMODULE'),
('email.contentmanager',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\contentmanager.py',
'PYMODULE'),
('email.encoders',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\encoders.py',
'PYMODULE'),
('email.errors',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\errors.py',
'PYMODULE'),
('email.feedparser',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\feedparser.py',
'PYMODULE'),
('email.generator',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\generator.py',
'PYMODULE'),
('email.header',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\header.py',
'PYMODULE'),
('email.headerregistry',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\headerregistry.py',
'PYMODULE'),
('email.iterators',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\iterators.py',
'PYMODULE'),
('email.message',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\message.py',
'PYMODULE'),
('email.parser',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\parser.py',
'PYMODULE'),
('email.policy',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\policy.py',
'PYMODULE'),
('email.quoprimime',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\quoprimime.py',
'PYMODULE'),
('email.utils',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\email\\utils.py',
'PYMODULE'),
('fnmatch',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\fnmatch.py',
'PYMODULE'),
('fractions',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\fractions.py',
'PYMODULE'),
('ftplib',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\ftplib.py',
'PYMODULE'),
('getopt',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\getopt.py',
'PYMODULE'),
('getpass',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\getpass.py',
'PYMODULE'),
('gettext',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\gettext.py',
'PYMODULE'),
('glob',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\glob.py',
'PYMODULE'),
('gzip',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\gzip.py',
'PYMODULE'),
('hashlib',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\hashlib.py',
'PYMODULE'),
('http',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\http\\__init__.py',
'PYMODULE'),
('http.client',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\http\\client.py',
'PYMODULE'),
('http.cookiejar',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\http\\cookiejar.py',
'PYMODULE'),
('importlib',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\__init__.py',
'PYMODULE'),
('importlib._abc',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\_abc.py',
'PYMODULE'),
('importlib._bootstrap',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\_bootstrap.py',
'PYMODULE'),
('importlib._bootstrap_external',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\_bootstrap_external.py',
'PYMODULE'),
('importlib.abc',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\abc.py',
'PYMODULE'),
('importlib.machinery',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\machinery.py',
'PYMODULE'),
('importlib.metadata',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\metadata\\__init__.py',
'PYMODULE'),
('importlib.metadata._adapters',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\metadata\\_adapters.py',
'PYMODULE'),
('importlib.metadata._collections',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\metadata\\_collections.py',
'PYMODULE'),
('importlib.metadata._functools',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\metadata\\_functools.py',
'PYMODULE'),
('importlib.metadata._itertools',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\metadata\\_itertools.py',
'PYMODULE'),
('importlib.metadata._meta',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\metadata\\_meta.py',
'PYMODULE'),
('importlib.metadata._text',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\metadata\\_text.py',
'PYMODULE'),
('importlib.readers',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\readers.py',
'PYMODULE'),
('importlib.resources',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\resources\\__init__.py',
'PYMODULE'),
('importlib.resources._adapters',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\resources\\_adapters.py',
'PYMODULE'),
('importlib.resources._common',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\resources\\_common.py',
'PYMODULE'),
('importlib.resources._functional',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\resources\\_functional.py',
'PYMODULE'),
('importlib.resources._itertools',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\resources\\_itertools.py',
'PYMODULE'),
('importlib.resources.abc',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\resources\\abc.py',
'PYMODULE'),
('importlib.resources.readers',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\resources\\readers.py',
'PYMODULE'),
('importlib.util',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\importlib\\util.py',
'PYMODULE'),
('inspect',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\inspect.py',
'PYMODULE'),
('ipaddress',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\ipaddress.py',
'PYMODULE'),
('json',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\json\\__init__.py',
'PYMODULE'),
('json.decoder',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\json\\decoder.py',
'PYMODULE'),
('json.encoder',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\json\\encoder.py',
'PYMODULE'),
('json.scanner',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\json\\scanner.py',
'PYMODULE'),
('logging',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\logging\\__init__.py',
'PYMODULE'),
('lzma',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\lzma.py',
'PYMODULE'),
('mimetypes',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\mimetypes.py',
'PYMODULE'),
('netrc',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\netrc.py',
'PYMODULE'),
('nturl2path',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\nturl2path.py',
'PYMODULE'),
('numbers',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\numbers.py',
'PYMODULE'),
('opcode',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\opcode.py',
'PYMODULE'),
('pathlib',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\pathlib\\__init__.py',
'PYMODULE'),
('pathlib._abc',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\pathlib\\_abc.py',
'PYMODULE'),
('pathlib._local',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\pathlib\\_local.py',
'PYMODULE'),
('pickle',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\pickle.py',
'PYMODULE'),
('pprint',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\pprint.py',
'PYMODULE'),
('py_compile',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\py_compile.py',
'PYMODULE'),
('quopri',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\quopri.py',
'PYMODULE'),
('random',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\random.py',
'PYMODULE'),
('selectors',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\selectors.py',
'PYMODULE'),
('shutil',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\shutil.py',
'PYMODULE'),
('signal',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\signal.py',
'PYMODULE'),
('socket',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\socket.py',
'PYMODULE'),
('ssl',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\ssl.py',
'PYMODULE'),
('statistics',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\statistics.py',
'PYMODULE'),
('string',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\string.py',
'PYMODULE'),
('stringprep',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\stringprep.py',
'PYMODULE'),
('subprocess',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\subprocess.py',
'PYMODULE'),
('tarfile',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\tarfile.py',
'PYMODULE'),
('tempfile',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\tempfile.py',
'PYMODULE'),
('textwrap',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\textwrap.py',
'PYMODULE'),
('threading',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\threading.py',
'PYMODULE'),
('token',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\token.py',
'PYMODULE'),
('tokenize',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\tokenize.py',
'PYMODULE'),
('tracemalloc',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\tracemalloc.py',
'PYMODULE'),
('typing',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\typing.py',
'PYMODULE'),
('urllib',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\urllib\\__init__.py',
'PYMODULE'),
('urllib.error',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\urllib\\error.py',
'PYMODULE'),
('urllib.parse',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\urllib\\parse.py',
'PYMODULE'),
('urllib.request',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\urllib\\request.py',
'PYMODULE'),
('urllib.response',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\urllib\\response.py',
'PYMODULE'),
('xml',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\__init__.py',
'PYMODULE'),
('xml.etree',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\etree\\__init__.py',
'PYMODULE'),
('xml.etree.ElementInclude',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\etree\\ElementInclude.py',
'PYMODULE'),
('xml.etree.ElementPath',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\etree\\ElementPath.py',
'PYMODULE'),
('xml.etree.ElementTree',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\etree\\ElementTree.py',
'PYMODULE'),
('xml.etree.cElementTree',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\etree\\cElementTree.py',
'PYMODULE'),
('xml.parsers',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\parsers\\__init__.py',
'PYMODULE'),
('xml.parsers.expat',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\parsers\\expat.py',
'PYMODULE'),
('xml.sax',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\sax\\__init__.py',
'PYMODULE'),
('xml.sax._exceptions',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\sax\\_exceptions.py',
'PYMODULE'),
('xml.sax.expatreader',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\sax\\expatreader.py',
'PYMODULE'),
('xml.sax.handler',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\sax\\handler.py',
'PYMODULE'),
('xml.sax.saxutils',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\sax\\saxutils.py',
'PYMODULE'),
('xml.sax.xmlreader',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\xml\\sax\\xmlreader.py',
'PYMODULE'),
('zipfile',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\zipfile\\__init__.py',
'PYMODULE'),
('zipfile._path',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\zipfile\\_path\\__init__.py',
'PYMODULE'),
('zipfile._path.glob',
'C:\\Users\\I\\AppData\\Local\\Programs\\Python\\Python313\\Lib\\zipfile\\_path\\glob.py',
'PYMODULE')])

Binary file not shown.

Binary file not shown.

View File

@@ -1,27 +0,0 @@
This file lists modules PyInstaller was not able to find. This does not
necessarily mean this module is required for running your program. Python and
Python 3rd-party packages include a lot of conditional or optional modules. For
example the module 'ntpath' only exists on Windows, whereas the module
'posixpath' only exists on Posix systems.
Types if import:
* top-level: imported at the top-level - look at these first
* conditional: imported within an if-statement
* delayed: imported within a function
* optional: imported within a try-except-statement
IMPORTANT: Do NOT post this list to the issue-tracker. Use it as a basis for
tracking down the missing module yourself. Thanks!
missing module named _frozen_importlib_external - imported by importlib._bootstrap (delayed), importlib (optional), importlib.abc (optional)
excluded module named _frozen_importlib - imported by importlib (optional), importlib.abc (optional)
missing module named 'collections.abc' - imported by traceback (top-level), typing (top-level), inspect (top-level), logging (top-level), importlib.resources.readers (top-level), selectors (top-level), tracemalloc (top-level), xml.etree.ElementTree (top-level), http.client (top-level)
missing module named posix - imported by os (conditional, optional), posixpath (optional), shutil (conditional), importlib._bootstrap_external (conditional)
missing module named resource - imported by posix (top-level)
missing module named pwd - imported by posixpath (delayed, conditional, optional), shutil (delayed, optional), tarfile (optional), pathlib._local (optional), subprocess (delayed, conditional, optional), netrc (delayed, conditional), getpass (delayed, optional)
missing module named grp - imported by shutil (delayed, optional), tarfile (optional), pathlib._local (optional), subprocess (delayed, conditional, optional)
missing module named _scproxy - imported by urllib.request (conditional)
missing module named termios - imported by getpass (optional)
missing module named _posixsubprocess - imported by subprocess (conditional)
missing module named fcntl - imported by subprocess (optional)

Some files were not shown because too many files have changed in this diff Show More