Compare commits
46 Commits
0b50c31aa8
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10e37a14b4 | ||
|
|
10dd2a6987 | ||
|
|
910bf0a585 | ||
|
|
502046091c | ||
|
|
e99de603e6 | ||
|
|
788ad19464 | ||
|
|
96496a0256 | ||
|
|
f89aff1b1c | ||
|
|
6830743477 | ||
|
|
171f176d63 | ||
|
|
f2c4b7b3cd | ||
|
|
c94a7e711c | ||
|
|
5be6343c33 | ||
|
|
043359fe66 | ||
|
|
c55f38ef1c | ||
|
|
ae2c90160e | ||
|
|
4de53090a1 | ||
|
|
742c4e9e1b | ||
|
|
369cfa808c | ||
|
|
c32dc161f8 | ||
|
|
abfc507e4e | ||
|
|
cb496bca0f | ||
|
|
7b720cbdf4 | ||
|
|
6428e523df | ||
|
|
c738acd871 | ||
|
|
05bde87c38 | ||
|
|
02f3124224 | ||
|
|
21082a38e0 | ||
|
|
42ac3eb65d | ||
|
|
69c0bf1574 | ||
|
|
4f949e9854 | ||
|
|
0d54031dd5 | ||
|
|
e4fcfd11d7 | ||
|
|
d3f1e824fa | ||
|
|
ad7b9126b7 | ||
|
|
f271b2e82c | ||
|
|
a3850c2c8a | ||
|
|
0d59f88444 | ||
|
|
c44216a450 | ||
|
|
07e42c774a | ||
|
|
a95a1535a9 | ||
|
|
858e7de57d | ||
|
|
cd6645df98 | ||
|
|
3343428796 | ||
|
|
f881132fa8 | ||
|
|
4962276760 |
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal 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.
@@ -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])
|
||||
@@ -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")
|
||||
@@ -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())
|
||||
|
||||
|
||||
BIN
DebugVarEdit.exe
BIN
DebugVarEdit.exe
Binary file not shown.
Binary file not shown.
282
README.md
Normal file
282
README.md
Normal 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
732
Src/DebugVarEdit_GUI.py
Normal 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
62
Src/README_DEVELOP.md
Normal 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
|
||||
```
|
||||
@@ -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
500
Src/allvars_xml_parser.py
Normal 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
239
Src/auto_updater.py
Normal 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)
|
||||
113
Src/build/build_and_clean.py
Normal file
113
Src/build/build_and_clean.py
Normal 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
224
Src/csv_logger.py
Normal 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))
|
||||
|
||||
@@ -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
BIN
Src/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
BIN
Src/icon.png
Normal file
BIN
Src/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
242
Src/makefile_parser.py
Normal file
242
Src/makefile_parser.py
Normal 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
72
Src/myXML.py
Normal 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
|
||||
@@ -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
319
Src/path_hints.py
Normal 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
|
||||
BIN
Src/pythonInstaller/python-3.7.9-amd64.exe
Normal file
BIN
Src/pythonInstaller/python-3.7.9-amd64.exe
Normal file
Binary file not shown.
Binary file not shown.
219
Src/scan_progress_gui.py
Normal file
219
Src/scan_progress_gui.py
Normal 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) # сигнал окончания
|
||||
@@ -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")
|
||||
250
Src/setupVars.py
250
Src/setupVars.py
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
504
Src/tms_debugvar_lowlevel.py
Normal file
504
Src/tms_debugvar_lowlevel.py
Normal 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
1279
Src/tms_debugvar_term.py
Normal 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
428
Src/var_selector_table.py
Normal 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
392
Src/var_selector_window.py
Normal 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
619
Src/var_setup.py
Normal 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
498
Src/var_table.py
Normal 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
|
||||
@@ -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("Сборка завершилась с ошибкой.")
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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')])
|
||||
@@ -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')
|
||||
@@ -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.
@@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
Reference in New Issue
Block a user