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
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()
|
|
|
|
|