-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactored AppController and Enhanced Application Functionality- **Re…
…factored AppController**: Separated duties into ProjectController, ThreadController, and UIController following the SOLID principles, improving code maintainability and scalability. - **Improved Threading and Memory Management**: - Updated ThreadController to manage worker threads more efficiently. - Ensured background tasks do not block the UI or cause memory leaks.- **Enhanced CommentParser**: - Rewrote CommentParser to robustly handle single-line and multiline comments across various programming languages. - Implemented efficient scanning to extract descriptions starting with 'GynTree:' in any case and position within comments.- **Expanded Test Files**: - Added comprehensive unit tests for AutoExcludeManager, CommentParser, DirectoryAnalyzer, and SettingsManager. - Ensured all functionalities are fully tested and reliable.- **Improved Error Handling and Logging**: - Implemented global exception handling via ErrorHandler. - Added detailed logging throughout the application for better debugging and monitoring.- **Fixed Application Startup Issue**: - Resolved the immediate closure of the application by adjusting the event loop management in App.py. - Removed premature calls to cleanup methods to ensure the main window remains open.- **Optimized Directory Analysis**: - Modified DirectoryAnalyzer and related services to efficiently handle file parsing and comment extraction.- **Updated Documentation**: - Provided detailed project documentation outlining features, architecture, recent changes, and future work.- **Miscellaneous Improvements**: - Ensured cross-platform compatibility by normalizing file paths. - Handled edge cases in settings and project management.
- Loading branch information
Showing
51 changed files
with
2,706 additions
and
1,375 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,39 +1,29 @@ | ||
aiohttp==3.9.5 | ||
aiosignal==1.3.1 | ||
attrs==23.2.0 | ||
awscli==1.34.10 | ||
botocore==1.35.10 | ||
certifi==2024.6.2 | ||
charset-normalizer==3.3.2 | ||
altgraph==0.17.4 | ||
attrs==24.2.0 | ||
colorama==0.4.6 | ||
docutils==0.16 | ||
frozenlist==1.4.1 | ||
idna==3.7 | ||
execnet==2.1.1 | ||
iniconfig==2.0.0 | ||
jmespath==1.0.1 | ||
keyboard==0.13.5 | ||
MouseInfo==0.1.3 | ||
multidict==6.0.5 | ||
packaging==24.0 | ||
Jinja2==3.1.4 | ||
jsonschema==4.23.0 | ||
jsonschema-specifications==2023.12.1 | ||
MarkupSafe==2.1.5 | ||
packaging==24.1 | ||
pefile==2024.8.26 | ||
pluggy==1.5.0 | ||
psycopg2-binary==2.9.9 | ||
pyasn1==0.6.0 | ||
PyAutoGUI==0.9.54 | ||
PyGetWindow==0.0.9 | ||
PyMsgBox==1.0.9 | ||
pyperclip==1.8.2 | ||
PyRect==0.2.0 | ||
PyScreeze==0.1.30 | ||
pytest==8.2.2 | ||
python-dateutil==2.9.0.post0 | ||
python-dotenv==1.0.1 | ||
pytweening==1.2.0 | ||
PyYAML==6.0.2 | ||
requests==2.32.3 | ||
rsa==4.7.2 | ||
s3transfer==0.10.2 | ||
six==1.16.0 | ||
tabulate==0.9.0 | ||
urllib3==2.2.1 | ||
uuid==1.30 | ||
yarl==1.9.4 | ||
psutil==6.0.0 | ||
pyasn1==0.6.1 | ||
pyinstaller==6.10.0 | ||
pyinstaller-hooks-contrib==2024.8 | ||
PyQt5==5.15.11 | ||
PyQt5-Qt5==5.15.2 | ||
PyQt5_sip==12.15.0 | ||
pytest==8.3.3 | ||
pytest-html==4.1.1 | ||
pytest-metadata==3.1.1 | ||
pytest-mock==3.14.0 | ||
pytest-xdist==3.6.1 | ||
pywin32-ctypes==0.2.3 | ||
referencing==0.35.1 | ||
rpds-py==0.20.0 | ||
rsa==4.9 | ||
setuptools==75.1.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,37 @@ | ||
""" | ||
GynTree: This is the main entry point for the GynTree application. | ||
It initializes the core components and starts the user interface. | ||
The App module orchestrates the overall flow of the application, | ||
connecting various components and services. | ||
""" | ||
GynTree: Main entry point for the GynTree application. Initializes core components and starts the user interface. | ||
The app module orchestrates the overall flow of the application, connecting various components and services. | ||
""" | ||
|
||
import sys | ||
import logging | ||
from PyQt5.QtWidgets import QApplication | ||
from controllers.AppController import AppController | ||
from utilities.error_handler import ErrorHandler | ||
|
||
logging.basicConfig(level=logging.DEBUG, | ||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') | ||
logger = logging.getLogger(__name__) | ||
|
||
def main(): | ||
app = QApplication([]) | ||
# Set global exception handling | ||
sys.excepthook = ErrorHandler.global_exception_handler | ||
|
||
app = QApplication(sys.argv) | ||
controller = AppController() | ||
|
||
# Connect cleanup method to be called on application quit | ||
app.aboutToQuit.connect(controller.cleanup) | ||
|
||
# Start the application | ||
controller.run() | ||
|
||
app.exec_() | ||
# Start the event loop | ||
sys.exit(app.exec_()) | ||
|
||
if __name__ == '__main__': | ||
main() | ||
if __name__ == "__main__": | ||
try: | ||
main() | ||
except Exception as e: | ||
logger.critical(f"Fatal error in main: {str(e)}", exc_info=True) | ||
sys.exit(1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,115 +1,110 @@ | ||
# GynTree: Defines the user interface for managing automatic file and directory exclusions. | ||
|
||
from PyQt5.QtWidgets import (QMainWindow, QVBoxLayout, QLabel, QCheckBox, QPushButton, | ||
QScrollArea, QWidget, QHBoxLayout, QTextEdit) | ||
from PyQt5.QtCore import Qt | ||
from PyQt5.QtWidgets import QMainWindow, QVBoxLayout, QLabel, QPushButton, QScrollArea, QWidget, QTreeWidget, QTreeWidgetItem, QMessageBox, QHeaderView, QHBoxLayout | ||
from PyQt5.QtCore import Qt, QSize | ||
from PyQt5.QtGui import QIcon, QFont | ||
import os | ||
from utilities.resource_path import get_resource_path | ||
|
||
class AutoExcludeUI(QMainWindow): | ||
def __init__(self, auto_exclude_manager, settings_manager, formatted_recommendations): | ||
def __init__(self, auto_exclude_manager, settings_manager, formatted_recommendations, project_context): | ||
super().__init__() | ||
self.auto_exclude_manager = auto_exclude_manager | ||
self.settings_manager = settings_manager | ||
self.formatted_recommendations = formatted_recommendations | ||
self.checkboxes = {'directories': {}, 'files': {}} | ||
self.init_ui() | ||
|
||
def init_ui(self): | ||
self.project_context = project_context | ||
self.folder_icon = QIcon(get_resource_path("assets/images/folder_icon.png")) | ||
self.file_icon = QIcon(get_resource_path("assets/images/file_icon.png")) | ||
self.setWindowTitle('Auto-Exclude Recommendations') | ||
self.setWindowIcon(QIcon(get_resource_path('assets/images/GynTree_logo 64X64.ico'))) | ||
self.setStyleSheet(""" | ||
self.setWindowIcon(QIcon(get_resource_path('assets/images/gyntree_logo 64x64.ico'))) | ||
self.setStyleSheet(""" | ||
QMainWindow { background-color: #f0f0f0; } | ||
QLabel { font-size: 16px; color: #333; margin-bottom: 10px; } | ||
QCheckBox { font-size: 14px; color: #555; padding: 2px 0; } | ||
QCheckBox::indicator { width: 18px; height: 18px; } | ||
QPushButton { background-color: #4CAF50; color: white; padding: 10px 20px; | ||
font-size: 16px; margin: 4px 2px; border-radius: 8px; } | ||
QLabel { font-size: 20px; color: #333; margin-bottom: 10px; } | ||
QPushButton { background-color: #4caf50; color: white; padding: 8px 16px; font-size: 14px; margin: 4px 2px; border-radius: 6px; } | ||
QPushButton:hover { background-color: #45a049; } | ||
QTextEdit { font-size: 14px; color: #333; background-color: #fff; border: 1px solid #ddd; } | ||
QTreeWidget { font-size: 14px; color: #333; background-color: #fff; border: 1px solid #ddd; } | ||
""") | ||
self.init_ui() | ||
|
||
main_layout = QVBoxLayout() | ||
title = QLabel('Auto-Exclude Recommendations') | ||
title.setFont(QFont('Arial', 18, QFont.Bold)) | ||
main_layout.addWidget(title) | ||
|
||
recommendations_text = QTextEdit() | ||
recommendations_text.setPlainText(self.formatted_recommendations) | ||
recommendations_text.setReadOnly(True) | ||
recommendations_text.setFixedHeight(200) | ||
main_layout.addWidget(recommendations_text) | ||
|
||
scroll_area = QScrollArea() | ||
scroll_area.setWidgetResizable(True) | ||
scroll_widget = QWidget() | ||
scroll_layout = QVBoxLayout(scroll_widget) | ||
|
||
current_settings = { | ||
'excluded_dirs': self.settings_manager.get_excluded_dirs(), | ||
'excluded_files': self.settings_manager.get_excluded_files() | ||
} | ||
recommendations = self.auto_exclude_manager.get_grouped_recommendations(self.settings_manager.settings) | ||
|
||
for exclusion_type in ['directories', 'files']: | ||
if recommendations[exclusion_type]: | ||
group_widget = QWidget() | ||
group_layout = QVBoxLayout(group_widget) | ||
group_checkbox = QCheckBox(exclusion_type.capitalize()) | ||
group_checkbox.setFont(QFont('Arial', 14, QFont.Bold)) | ||
group_checkbox.setChecked(True) | ||
group_checkbox.stateChanged.connect(lambda state, g=exclusion_type: self.toggle_group(state, g)) | ||
self.checkboxes[exclusion_type]['group'] = group_checkbox | ||
group_layout.addWidget(group_checkbox) | ||
|
||
for item in recommendations[exclusion_type]: | ||
item_checkbox = QCheckBox(os.path.basename(item)) | ||
item_checkbox.setChecked(True) | ||
item_checkbox.setStyleSheet("margin-left: 20px;") | ||
self.checkboxes[exclusion_type][item] = item_checkbox | ||
group_layout.addWidget(item_checkbox) | ||
scroll_layout.addWidget(group_widget) | ||
|
||
scroll_area.setWidget(scroll_widget) | ||
main_layout.addWidget(scroll_area) | ||
|
||
apply_button = QPushButton('Apply') | ||
def init_ui(self): | ||
central_widget = QWidget() | ||
main_layout = QVBoxLayout(central_widget) | ||
main_layout.setContentsMargins(10, 10, 10, 10) | ||
main_layout.setSpacing(10) | ||
|
||
header_layout = QHBoxLayout() | ||
title_label = QLabel('Auto-Exclude Recommendations', font=QFont('Arial', 16, QFont.Bold)) | ||
header_layout.addWidget(title_label) | ||
collapse_btn = QPushButton('Collapse All') | ||
expand_btn = QPushButton('Expand All') | ||
header_layout.addWidget(collapse_btn) | ||
header_layout.addWidget(expand_btn) | ||
main_layout.addLayout(header_layout) | ||
|
||
self.tree_widget = QTreeWidget() | ||
self.tree_widget.setHeaderLabels(['Name', 'Type']) | ||
self.tree_widget.setColumnWidth(0, 300) | ||
self.tree_widget.setAlternatingRowColors(True) | ||
self.tree_widget.setIconSize(QSize(20, 20)) | ||
self.tree_widget.header().setSectionResizeMode(0, QHeaderView.ResizeToContents) | ||
self.tree_widget.header().setSectionResizeMode(1, QHeaderView.ResizeToContents) | ||
main_layout.addWidget(self.tree_widget) | ||
|
||
self.populate_tree() | ||
|
||
collapse_btn.clicked.connect(self.tree_widget.collapseAll) | ||
expand_btn.clicked.connect(self.tree_widget.expandAll) | ||
|
||
apply_button = QPushButton('Apply Exclusions') | ||
apply_button.clicked.connect(self.apply_exclusions) | ||
main_layout.addWidget(apply_button, alignment=Qt.AlignCenter) | ||
|
||
central_widget = QWidget() | ||
central_widget.setLayout(main_layout) | ||
self.setCentralWidget(central_widget) | ||
self.setGeometry(300, 300, 400, 600) | ||
self.setGeometry(300, 150, 800, 600) | ||
|
||
def populate_tree(self): | ||
"""Populates the tree with merged exclusions from both AutoExcludeManager and project folder.""" | ||
self.tree_widget.clear() | ||
root = self.tree_widget.invisibleRootItem() | ||
|
||
def toggle_group(self, state, group): | ||
is_checked = (state == Qt.Checked) | ||
for item, checkbox in self.checkboxes[group].items(): | ||
if item != 'group': | ||
checkbox.setChecked(is_checked) | ||
combined_exclusions = self.get_combined_exclusions() | ||
|
||
categories = ['root_exclusions', 'excluded_dirs', 'excluded_files'] | ||
for category in categories: | ||
category_item = QTreeWidgetItem(root, [category.replace('_', ' ').title(), '']) | ||
category_item.setFlags(category_item.flags() & ~Qt.ItemIsUserCheckable) | ||
|
||
for path in sorted(combined_exclusions.get(category, [])): | ||
item = QTreeWidgetItem(category_item, [path, category[:-1]]) | ||
item.setIcon(0, self.folder_icon if os.path.isdir(path) else self.file_icon) | ||
if category != 'root_exclusions': | ||
item.setFlags(item.flags() | Qt.ItemIsUserCheckable) | ||
item.setCheckState(0, Qt.Checked) | ||
|
||
self.tree_widget.expandAll() | ||
|
||
def get_combined_exclusions(self): | ||
"""Retrieve exclusions from AutoExcludeManager and merge with project exclusions.""" | ||
manager_recommendations = self.auto_exclude_manager.get_recommendations() | ||
|
||
root_exclusions = set(self.project_context.settings_manager.get_root_exclusions()) | ||
excluded_dirs = set(self.project_context.settings_manager.get_excluded_dirs()) | ||
excluded_files = set(self.project_context.settings_manager.get_excluded_files()) | ||
|
||
combined_exclusions = { | ||
'root_exclusions': manager_recommendations.get('root_exclusions', set()) | root_exclusions, | ||
'excluded_dirs': manager_recommendations.get('excluded_dirs', set()) | excluded_dirs, | ||
'excluded_files': manager_recommendations.get('excluded_files', set()) | excluded_files | ||
} | ||
|
||
return combined_exclusions | ||
|
||
def apply_exclusions(self): | ||
current_settings = self.settings_manager.settings | ||
excluded_dirs = set(current_settings.get('excluded_dirs', [])) | ||
excluded_files = set(current_settings.get('excluded_files', [])) | ||
|
||
for exclusion_type, items in self.checkboxes.items(): | ||
for item, checkbox in items.items(): | ||
if checkbox.isChecked() and item != 'group': | ||
if exclusion_type == 'directories': | ||
excluded_dirs.add(item) | ||
else: | ||
excluded_files.add(item) | ||
|
||
self.settings_manager.update_settings({ | ||
'excluded_dirs': list(excluded_dirs), | ||
'excluded_files': list(excluded_files) | ||
}) | ||
self.auto_exclude_manager.apply_recommendations() | ||
QMessageBox.information(self, "Exclusions Updated", "Exclusions have been successfully updated.") | ||
self.close() | ||
|
||
def update_recommendations(self, formatted_recommendations): | ||
self.formatted_recommendations = formatted_recommendations | ||
recommendations_text = self.findChild(QTextEdit) | ||
if recommendations_text: | ||
recommendations_text.setPlainText(self.formatted_recommendations) | ||
self.populate_tree() | ||
|
||
def closeEvent(self, event): | ||
super().closeEvent(event) |
Oops, something went wrong.