1
+ # main.py
2
+
1
3
import os
2
4
import re
3
5
import threading
6
+ from threading import Event
4
7
from collections import Counter
5
8
import matplotlib .pyplot as plt
6
9
from tkinter import Tk , filedialog , Label , Button , Text , Scrollbar , Frame , Menu
10
13
import queue
11
14
from concurrent .futures import ThreadPoolExecutor , as_completed
12
15
import ast
16
+ from stats_window import open_stats_window , analyze_project_structure , parse_python_files
17
+
18
+ from utils import read_gitignore , is_ignored
19
+
13
20
init (autoreset = True )
21
+ stop_event = Event ()
14
22
15
23
# Очередь для передачи данных между потоками
16
24
imports_count = {}
17
25
task_queue = queue .Queue ()
18
26
19
27
28
+
29
+
20
30
# =========================
21
31
# Функции для анализа файлов
22
32
# =========================
23
33
24
- def read_gitignore (directory ):
25
- gitignore_path = os .path .join (directory , '.gitignore' )
26
- ignored_paths = set ()
27
34
28
- if os .path .exists (gitignore_path ):
29
- with open (gitignore_path , 'r' ) as f :
30
- for line in f :
31
- line = line .strip ()
32
- if line and not line .startswith ('#' ):
33
- ignored_paths .add (line )
34
- return ignored_paths
35
-
36
-
37
- def is_ignored (file_path , ignored_paths ):
38
- relative_path = os .path .relpath (file_path )
39
- for ignored_path in ignored_paths :
40
- if relative_path .startswith (ignored_path ):
41
- return True
42
- return False
43
35
44
36
def get_gitignore_excluded_dirs (gitignore_path = '.gitignore' ):
45
37
excluded_dirs = []
@@ -93,7 +85,7 @@ def find_imports_in_file(file_path):
93
85
94
86
95
87
96
- def scan_directory_for_imports_parallel (directory , progress_label , output_text ):
88
+ def scan_directory_for_imports_parallel (directory , progress_label , output_text , task_queue , stop_event ):
97
89
global imports_count , total_imports
98
90
99
91
ignored_paths = read_gitignore (directory )
@@ -120,6 +112,8 @@ def scan_directory_for_imports_parallel(directory, progress_label, output_text):
120
112
imports_list = []
121
113
122
114
def process_file (file_path ):
115
+ if stop_event .is_set ():
116
+ return []
123
117
return find_imports_in_file (file_path )
124
118
125
119
with ThreadPoolExecutor (max_workers = 20 ) as executor :
@@ -139,6 +133,11 @@ def process_file(file_path):
139
133
140
134
task_queue .put (('stats' , imports_count , total_imports ))
141
135
136
+ progress_label .config (text = "Анализ структуры проекта..." )
137
+ progress_label .update ()
138
+ # После анализа импортов
139
+ analyze_project_structure (directory , task_queue )
140
+
142
141
143
142
144
143
@@ -151,7 +150,7 @@ def browse_directory():
151
150
if directory :
152
151
excluded_dirs = get_gitignore_excluded_dirs () # Получаем исключенные директории из .gitignore
153
152
threading .Thread (target = scan_directory_for_imports_parallel ,
154
- args = (directory , progress_label , output_text ), # Передаем только 3 аргумента
153
+ args = (directory , progress_label , output_text , task_queue , stop_event ), # Передаем только 3 аргумента
155
154
daemon = True ).start ()
156
155
157
156
@@ -274,27 +273,46 @@ def on_copy(event=None):
274
273
def update_gui ():
275
274
try :
276
275
message = task_queue .get_nowait ()
276
+
277
277
if isinstance (message , str ):
278
278
progress_label .config (text = message )
279
+
279
280
elif isinstance (message , tuple ) and message [0 ] == 'stats' :
280
281
imports_count , total_imports = message [1 ], message [2 ]
281
282
print_import_statistics (imports_count , total_imports , output_text )
282
283
plot_import_statistics (imports_count , total_imports )
284
+
285
+ elif isinstance (message , tuple ) and message [0 ] == 'project_stats' :
286
+ structure = message [1 ]
287
+ output_text .insert ("insert" , "\n --- Статистика проекта ---\n " )
288
+ output_text .insert ("insert" , f"Всего файлов: { structure ['total_files' ]} \n " )
289
+ output_text .insert ("insert" , f"Всего директорий: { structure ['total_dirs' ]} \n " )
290
+ output_text .insert ("insert" , f"Python файлов: { structure ['py_files' ]} (моего кода)\n " )
291
+ output_text .insert ("insert" , f"Python файлов в виртуальных/служебных папках: { structure ['py_files_venv' ]} \n " )
292
+ output_text .insert ("insert" , f"Прочих файлов: { structure ['other_files' ]} \n " )
293
+ output_text .insert ("insert" , f"\n Список директорий:\n " )
294
+ for folder in structure ['folders' ]:
295
+ output_text .insert ("insert" , f" - { folder } \n " )
296
+
283
297
except queue .Empty :
284
298
pass
285
- window .after (100 , update_gui ) # Обновляем GUI каждую сотую долю секунды
286
299
300
+ window .after (100 , update_gui )
287
301
288
302
# =========================
289
303
# GUI
290
304
# =========================
291
305
306
+ def on_closing ():
307
+ stop_event .set () # Сигнал остановки для потоков
308
+ window .destroy () # Закрытие окна
292
309
293
310
294
311
295
312
window = Tk ()
296
313
window .title ("Статистика импортов в проектах" )
297
314
window .geometry ("1200x800" )
315
+ window .protocol ("WM_DELETE_WINDOW" , on_closing )
298
316
299
317
frame = Frame (window )
300
318
frame .pack (pady = 20 )
@@ -333,6 +351,16 @@ def update_gui():
333
351
btn_others = Button (lib_frame , text = "Показать прочие библиотеки" , command = lambda : show_others (imports_count , total_imports , output_text ))
334
352
btn_others .pack (side = "left" , padx = 10 )
335
353
354
+ # Получаем данные о проектах для анализа
355
+ default_project_path = "E:/Code/PYTHON/projects" # <-- путь к проектам
356
+ project_data = list (parse_python_files (default_project_path ).values ())
357
+
358
+
359
+ # Новая кнопка: временной анализ проектов
360
+ btn_stats_by_date = Button (lib_frame , text = "Анализ проектов по дате" , command = lambda : open_stats_window (window , project_data ) )
361
+ btn_stats_by_date .pack (side = "left" , padx = 10 )
362
+
363
+
336
364
# Добавляем контекстное меню для копирования
337
365
context_menu = Menu (window , tearoff = 0 )
338
366
context_menu .add_command (label = "Копировать" , command = on_copy )
@@ -349,7 +377,10 @@ def show_context_menu(event):
349
377
progress_label .pack (pady = 10 )
350
378
351
379
# Запуск функции обновления GUI
352
- window . after ( 100 , update_gui )
353
-
354
- window .mainloop ()
380
+ def periodic_check ():
381
+ update_gui ()
382
+ window .after ( 100 , periodic_check ) # каждые 100 мс проверяем очередь
355
383
384
+ # Запуск Tkinter
385
+ periodic_check ()
386
+ window .mainloop ()
0 commit comments