You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

558 lines
20 KiB

import tkinter as tk
import customtkinter as ctk
from tkinter import filedialog, messagebox, ttk
import os
import platform
import subprocess
import urllib.request
from src.uml_generator import UMLGenerator
from src.code_analyzer import JavaAnalyzer, CSharpAnalyzer
from src.project_analyzer import ProjectAnalyzer
from src.readme_generator import ReadmeGenerator
from src.preview_window import PreviewWindow
class UMLGeneratorApp(ctk.CTk):
def __init__(self, local_version):
super().__init__()
self.title(f"CodeMap[{local_version}] - Générateur UML et Documentation")
self.geometry("800x600")
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("green")
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
self.main_frame = ctk.CTkFrame(self)
self.main_frame.grid(row=0, column=0, padx=20, pady=20, sticky="nsew")
self.main_frame.grid_columnconfigure(0, weight=1)
self.title_label = ctk.CTkLabel(
self.main_frame,
text="CodeMap - Générateur UML et Documentation",
font=("Helvetica", 24, "bold")
)
self.title_label.grid(row=0, column=0, pady=20)
self.tabview = ctk.CTkTabview(self.main_frame)
self.tabview.grid(row=1, column=0, padx=20, pady=10, sticky="nsew")
self.main_frame.grid_rowconfigure(1, weight=1)
self.uml_tab = self.tabview.add("UML")
self.uml_tab.grid_columnconfigure(0, weight=1)
self.setup_uml_tab()
# Onglet README
# self.readme_tab = self.tabview.add("README")
# self.readme_tab.grid_columnconfigure(0, weight=1)
# self.setup_readme_tab()
# Onglet Sélection de projet
# self.project_tab = self.tabview.add("Sélection de projet")
# self.project_tab.grid_columnconfigure(0, weight=1)
# self.setup_project_tab()
def setup_uml_tab(self):
self.project_frame = ctk.CTkFrame(self.uml_tab)
self.project_frame.grid(row=0, column=0, padx=20, pady=10, sticky="ew")
self.project_label = ctk.CTkLabel(
self.project_frame,
text="Chemin du projet:",
font=("Helvetica", 14)
)
self.project_label.grid(row=0, column=0, padx=10, pady=5)
self.project_path = ctk.CTkEntry(self.project_frame, width=400)
self.project_path.grid(row=0, column=1, padx=10, pady=5)
self.browse_button = ctk.CTkButton(
self.project_frame,
text="Parcourir",
command=self.browse_directory
)
self.browse_button.grid(row=0, column=2, padx=10, pady=5)
self.options_frame = ctk.CTkFrame(self.uml_tab)
self.options_frame.grid(row=1, column=0, padx=20, pady=10, sticky="ew")
self.language_var = tk.StringVar(value="java")
self.java_radio = ctk.CTkRadioButton(
self.options_frame,
text="Java",
variable=self.language_var,
value="java"
)
self.java_radio.grid(row=0, column=0, padx=20, pady=10)
self.csharp_radio = ctk.CTkRadioButton(
self.options_frame,
text="C#",
variable=self.language_var,
value="csharp"
)
self.csharp_radio.grid(row=0, column=1, padx=20, pady=10)
self.php_radio = ctk.CTkRadioButton(
self.options_frame,
text="PHP",
variable=self.language_var,
value="php"
)
self.php_radio.grid(row=0, column=2, padx=20, pady=10)
self.generate_classes = tk.BooleanVar(value=True)
self.classes_check = ctk.CTkCheckBox(
self.options_frame,
text="Diagramme de classes",
variable=self.generate_classes
)
self.classes_check.grid(row=1, column=0, padx=20, pady=5)
self.generate_doc = tk.BooleanVar(value=True)
self.doc_check = ctk.CTkCheckBox(
self.options_frame,
text="Documentation",
variable=self.generate_doc
)
self.doc_check.grid(row=1, column=1, padx=20, pady=5)
self.generate_button = ctk.CTkButton(
self.uml_tab,
text="Générer UML",
command=self.generate,
font=("Helvetica", 16),
height=40
)
self.generate_button.grid(row=2, column=0, pady=20)
self.log_frame = ctk.CTkFrame(self.uml_tab)
self.log_frame.grid(row=3, column=0, padx=20, pady=10, sticky="nsew")
self.uml_tab.grid_rowconfigure(3, weight=1)
self.log_text = ctk.CTkTextbox(
self.log_frame,
height=200,
width=600,
wrap="word"
)
self.log_text.grid(row=0, column=0, sticky="nsew")
self.log_frame.grid_columnconfigure(0, weight=1)
self.log_frame.grid_rowconfigure(0, weight=1)
def setup_readme_tab(self):
self.readme_content_frame = ctk.CTkFrame(self.readme_tab)
self.readme_content_frame.grid(row=0, column=0, padx=20, pady=10, sticky="nsew")
self.readme_tab.grid_rowconfigure(0, weight=1)
self.title_label = ctk.CTkLabel(
self.readme_content_frame,
text="Titre du projet:",
font=("Helvetica", 14)
)
self.title_label.grid(row=0, column=0, padx=10, pady=5, sticky="w")
self.title_entry = ctk.CTkEntry(
self.readme_content_frame,
width=400
)
self.title_entry.grid(row=0, column=1, padx=10, pady=5, sticky="ew")
self.desc_label = ctk.CTkLabel(
self.readme_content_frame,
text="Description:",
font=("Helvetica", 14)
)
self.desc_label.grid(row=1, column=0, padx=10, pady=5, sticky="nw")
self.desc_text = ctk.CTkTextbox(
self.readme_content_frame,
height=100,
width=400
)
self.desc_text.grid(row=1, column=1, padx=10, pady=5, sticky="nsew")
self.tech_label = ctk.CTkLabel(
self.readme_content_frame,
text="Technologies détectées:",
font=("Helvetica", 14)
)
self.tech_label.grid(row=2, column=0, padx=10, pady=5, sticky="nw")
self.tech_text = ctk.CTkTextbox(
self.readme_content_frame,
height=80,
width=400,
state="disabled"
)
self.tech_text.grid(row=2, column=1, padx=10, pady=5, sticky="nsew")
self.authors_label = ctk.CTkLabel(
self.readme_content_frame,
text="Auteurs (Git):",
font=("Helvetica", 14)
)
self.authors_label.grid(row=3, column=0, padx=10, pady=5, sticky="nw")
self.authors_text = ctk.CTkTextbox(
self.readme_content_frame,
height=80,
width=400,
state="disabled"
)
self.authors_text.grid(row=3, column=1, padx=10, pady=5, sticky="nsew")
self.info_label = ctk.CTkLabel(
self.readme_content_frame,
text="Note: Les technologies et auteurs seront détectés automatiquement\nlors de la génération du README",
font=("Helvetica", 12),
text_color="gray"
)
self.info_label.grid(row=4, column=0, columnspan=2, pady=5)
self.license_label = ctk.CTkLabel(
self.readme_content_frame,
text="Licence:",
font=("Helvetica", 14)
)
self.license_label.grid(row=5, column=0, padx=10, pady=5, sticky="w")
self.license_entry = ctk.CTkEntry(
self.readme_content_frame,
width=400
)
self.license_entry.insert(0, "MIT")
self.license_entry.grid(row=5, column=1, padx=10, pady=5, sticky="ew")
self.readme_content_frame.grid_columnconfigure(1, weight=1)
for i in range(6):
if i == 1:
self.readme_content_frame.grid_rowconfigure(i, weight=1)
else:
self.readme_content_frame.grid_rowconfigure(i, weight=0)
self.generate_readme_button = ctk.CTkButton(
self.readme_tab,
text="Générer README",
command=self.generate_readme,
font=("Helvetica", 16),
height=40
)
self.generate_readme_button.grid(row=1, column=0, pady=20)
def setup_project_tab(self):
self.project_content_frame = ctk.CTkFrame(self.project_tab)
self.project_content_frame.grid(row=0, column=0, padx=20, pady=10, sticky="nsew")
self.project_tab.grid_rowconfigure(0, weight=1)
self.project_title_label = ctk.CTkLabel(
self.project_content_frame,
text="Titre du projet:",
font=("Helvetica", 14)
)
self.project_title_label.grid(row=0, column=0, padx=10, pady=5, sticky="w")
self.project_title_entry = ctk.CTkEntry(
self.project_content_frame,
width=400
)
self.project_title_entry.grid(row=0, column=1, padx=10, pady=5, sticky="ew")
self.project_desc_label = ctk.CTkLabel(
self.project_content_frame,
text="Description:",
font=("Helvetica", 14)
)
self.project_desc_label.grid(row=1, column=0, padx=10, pady=5, sticky="nw")
self.project_desc_text = ctk.CTkTextbox(
self.project_content_frame,
height=100,
width=400
)
self.project_desc_text.grid(row=1, column=1, padx=10, pady=5, sticky="nsew")
self.generate_project_button = ctk.CTkButton(
self.project_content_frame,
text="Générer README et diagramme",
command=self.generate_project,
font=("Helvetica", 16),
height=40
)
self.generate_project_button.grid(row=2, column=0, columnspan=2, pady=20)
def log_message(self, message):
self.log_text.insert(tk.END, message + "\n")
self.log_text.see(tk.END)
self.update_idletasks()
def browse_directory(self):
directory = filedialog.askdirectory()
if directory:
self.project_path.delete(0, tk.END)
self.project_path.insert(0, directory)
def preview_and_generate_uml(self, classes, output_dir):
from src.uml_generator import UMLGenerator
import tempfile
temp_dir = tempfile.gettempdir()
generator = UMLGenerator()
temp_diagram = generator.generate_class_diagram(classes, temp_dir)
from src.preview_window import PreviewWindow
preview = PreviewWindow(self, content_type="uml", image_path=temp_diagram)
self.wait_window(preview)
if preview.result:
return generator.generate_class_diagram(classes, output_dir)
return None
def preview_and_generate_readme(self, project_info, output_dir):
from src.readme_generator import ReadmeGenerator
generator = ReadmeGenerator()
preview_content = generator.generate_readme_content(project_info)
from src.preview_window import PreviewWindow
preview = PreviewWindow(self, content_type="readme", content=preview_content)
self.wait_window(preview)
if preview.result:
return generator.generate_readme(project_info, output_dir)
return None
def open_output_folder(self, output_dir):
if os.path.isdir(output_dir):
if platform.system() == "Windows":
os.startfile(output_dir)
elif platform.system() == "Darwin":
subprocess.call(["open", output_dir])
else:
subprocess.call(["xdg-open", output_dir])
else:
print(f"Error: Directory '{output_dir}' does not exist.")
def generate(self):
project_path = self.project_path.get()
if not project_path:
messagebox.showerror("Erreur", "Veuillez sélectionner un dossier de projet")
return
if not os.path.exists(project_path):
messagebox.showerror("Erreur", "Le dossier spécifié n'existe pas")
return
self.log_text.delete("1.0", tk.END)
self.log_message("=== Début de l'analyse ===\n")
self.log_message("📁 Dossier projet: " + project_path)
self.log_message("🔧 Langage: " + self.language_var.get())
self.log_message("📊 Options sélectionnées:")
self.log_message(f" - Diagramme de classes: {'' if self.generate_classes.get() else ''}")
self.log_message(f" - Documentation: {'' if self.generate_doc.get() else ''}\n")
try:
output_dir = os.path.join(project_path, "uml_output")
if not os.path.exists(output_dir):
os.makedirs(output_dir)
self.log_message("📁 Création du dossier de sortie: " + output_dir)
self.log_message("🔍 Initialisation de l'analyseur de code...")
if self.language_var.get() == "java":
from src.code_analyzer import JavaAnalyzer
analyzer = JavaAnalyzer()
elif self.language_var.get() == "csharp":
from src.code_analyzer import CSharpAnalyzer
analyzer = CSharpAnalyzer()
elif self.language_var.get() == "php":
from src.code_analyzer import PHPAnalyzer
analyzer = PHPAnalyzer()
self.log_message("🔍 Analyse du code source en cours...")
classes, relationships = analyzer.analyze_directory(project_path)
self.log_message(f"{len(classes)} classes trouvées")
self.log_message("\n📋 Classes détectées:")
for cls in classes:
self.log_message(f" - {cls['name']}")
if cls['extends']:
self.log_message(f" ↳ Hérite de: {cls['extends']}")
if cls['implements']:
self.log_message(f" ↳ Implémente: {', '.join(cls['implements'])}")
if 'associations' in cls and cls['associations']:
self.log_message(f" ↳ Associations: {', '.join(cls['associations'])}")
if 'dependencies' in cls and cls['dependencies']:
self.log_message(f" ↳ Dépendances: {', '.join(cls['dependencies'])}")
if 'aggregations' in cls and cls['aggregations']:
self.log_message(f" ↳ Agrégations: {', '.join(cls['aggregations'])}")
if 'compositions' in cls and cls['compositions']:
self.log_message(f" ↳ Compositions: {', '.join(cls['compositions'])}")
if self.generate_classes.get():
self.log_message("\n📊 Génération du diagramme de classes...")
diagram_file = self.preview_and_generate_uml(classes, output_dir)
if diagram_file:
self.log_message(f"✓ Diagramme généré: {diagram_file}")
else:
self.log_message("❌ Génération du diagramme annulée")
return
if self.generate_doc.get():
self.log_message("\n📝 Génération de la documentation...")
from src.uml_generator import UMLGenerator
generator = UMLGenerator()
doc_file = generator.generate_documentation(classes, output_dir)
self.log_message(f"✓ Documentation générée: {doc_file}")
self.log_message("\n=== Génération terminée avec succès ===")
self.log_message(f"📂 Les fichiers ont été générés dans: {output_dir}")
self.after(1000, lambda: self.open_output_folder(output_dir))
except Exception as e:
self.log_message(f"\n❌ ERREUR: {str(e)}")
self.log_message("=== Génération interrompue ===")
messagebox.showerror("Erreur", f"Une erreur est survenue: {str(e)}")
def generate_readme(self):
from src.readme_generator import ReadmeGenerator
from src.project_analyzer import ProjectAnalyzer
project_path = self.project_path.get()
if not project_path:
messagebox.showerror("Erreur", "Veuillez sélectionner un dossier de projet")
return
if not os.path.exists(project_path):
messagebox.showerror("Erreur", "Le dossier spécifié n'existe pas")
return
try:
analyzer = ProjectAnalyzer()
self.log_message("🔍 Analyse des technologies utilisées...")
technologies = analyzer.analyze_technologies(project_path)
self.log_message("🔍 Analyse des prérequis...")
prerequisites = analyzer.get_prerequisites(project_path)
self.log_message("🔍 Génération des étapes d'installation...")
installation_steps = analyzer.get_installation_steps(project_path)
self.log_message("🔍 Récupération des auteurs depuis Git...")
authors = analyzer.get_git_authors(project_path)
project_info = {
'title': self.title_entry.get(),
'description': self.desc_text.get("1.0", tk.END).strip(),
'license': self.license_entry.get(),
'technologies': technologies,
'prerequisites': prerequisites,
'installation_steps': installation_steps,
'authors': authors
}
if not project_info['title']:
messagebox.showerror("Erreur", "Le titre du projet est obligatoire")
return
if not project_info['description']:
messagebox.showerror("Erreur", "La description du projet est obligatoire")
return
readme_file = self.preview_and_generate_readme(project_info, project_path)
if readme_file:
messagebox.showinfo("Succès", f"README.md généré avec succès dans {project_path}")
self.open_output_folder(project_path)
except Exception as e:
messagebox.showerror("Erreur", f"Une erreur est survenue lors de la génération du README: {str(e)}")
def generate_project(self):
project_title = self.project_title_entry.get()
project_description = self.project_desc_text.get("1.0", tk.END).strip()
print(f'Generating README for: {project_title} - {project_description}')
if __name__ == "__main__":
url = 'https://codefirst.iut.uca.fr/git/zayd.nahli/CodeMap/raw/branch/master/version/version.txt'
with urllib.request.urlopen(url) as response:
remote_version = response.read().decode().strip()
local_version = "1.1.1-beta1"
if local_version != remote_version:
root = tk.Tk()
root.withdraw()
messagebox.showinfo("Mise à jour disponible",
f"Votre version de CodeMap ({local_version}) n'est pas à jour. La version la plus récente est {remote_version}. Veuillez mettre à jour votre application pour profiter de toutes les fonctionnalités.")
app = UMLGeneratorApp(local_version)
app.mainloop()