Skip to content

Commit

Permalink
Refactored AppController and Enhanced Application Functionality- **Re…
Browse files Browse the repository at this point in the history
…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
dsj7419 committed Sep 20, 2024
1 parent 3983548 commit 057ec5f
Show file tree
Hide file tree
Showing 51 changed files with 2,706 additions and 1,375 deletions.
12 changes: 6 additions & 6 deletions assets/docs/README.md → README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

GynTree is a powerful Python application designed to analyze and visualize complex directory structures, providing deep insights into your project's architecture. Whether you're managing a large codebase, organizing a media library, or just trying to understand the layout of a new project, GynTree has got you covered.

![GynTree Logo](../images/GynTree_logo.png)
![GynTree Logo](./assets/images/GynTree_logo.png)

## 🌟 Key Features

Expand All @@ -17,17 +17,17 @@ GynTree is a powerful Python application designed to analyze and visualize compl

## 🚀 Getting Started

Ready to dive in? Check out our [Installation Guide](INSTALL.md) to get GynTree up and running on your system in no time!
Ready to dive in? Check out our [Installation Guide](./assets/docs/INSTALL.md) to get GynTree up and running on your system in no time!

## 📖 Documentation

- [User Guide](user_guide.md): Learn how to use GynTree effectively.
- [Configuration](configuration.md): Customize GynTree to suit your needs.
- [API Reference](api_reference.md): For developers looking to extend GynTree's functionality.
- [User Guide](./assets/docs/user_guide.md): Learn how to use GynTree effectively.
- [Configuration](./assets/docs/configuration.md): Customize GynTree to suit your needs.
- [API Reference](./assets/docs/api_reference.md): For developers looking to extend GynTree's functionality.

## 🤝 Contributing

We welcome contributions from the community! Whether it's bug reports, feature requests, or code contributions, check out our [Contributing Guide](CONTRIBUTING.md) to get started.
We welcome contributions from the community! Whether it's bug reports, feature requests, or code contributions, check out our [Contributing Guide](./assets/docs/CONTRIBUTING.md) to get started.

## 📜 License

Expand Down
2 changes: 1 addition & 1 deletion assets/docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ First off, thank you for considering contributing to GynTree! It's people like y

## Code of Conduct

By participating in this project, you are expected to uphold our [Code of Conduct](CODE_OF_CONDUCT.md). Please report unacceptable behavior to [project_email@example.com](mailto:project_email@example.com).
By participating in this project, you are expected to uphold our [Code of Conduct](CODE_OF_CONDUCT.md). Please report unacceptable behavior to [dsj7419@gmail.com](mailto:dsj7419@gmail.com).

## How Can I Contribute?

Expand Down
248 changes: 0 additions & 248 deletions config/projects/GynTree.json

This file was deleted.

62 changes: 26 additions & 36 deletions requirements.txt
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
35 changes: 26 additions & 9 deletions src/App.py
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)
175 changes: 85 additions & 90 deletions src/components/UI/AutoExcludeUI.py
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)
Loading

0 comments on commit 057ec5f

Please sign in to comment.