commit
60f2469aea
@ -0,0 +1,10 @@
|
||||
venv/
|
||||
__pycache__/
|
||||
__pycache__/*.pyc
|
||||
__pycache__/*.pyo
|
||||
__pycache__/*.pyd
|
||||
.vscode/
|
||||
.vscode/*.json
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
@ -0,0 +1,51 @@
|
||||
# CodeMap : Générateur de Diagrammes de Classes PlantUML et Documentation
|
||||
|
||||
**CodeMap** est un outil simple qui vous permet de générer rapidement des diagrammes de classes UML ainsi que de la "documentation" pour vos projets en Java et C#. En quelques clics, vous pouvez visualiser la structure de votre code et générer toute la documentation nécessaire, le tout avec une interface intuitive.
|
||||
|
||||
## Fonctionnalités principales
|
||||
|
||||
- **Prise en charge des projets Java et C#** : Que vous travailliez sur un projet en Java ou en C#, CodeMap s'adapte pour en extraire les diagrammes UML avec précision.
|
||||
- **Interface conviviale** : Choisissez simplement le chemin de votre projet, sélectionnez votre langage (Java ou C#), et indiquez si vous souhaitez générer des diagrammes UML, de la "documentation", ou les deux.
|
||||
- **Génération rapide et simple** : En quelques clics, vous obtenez votre diagrammes et votre documentation.
|
||||
- **Précision et clarté** : Les diagrammes générés sont directement basés sur votre code, assurant une représentation fidèle et claire.
|
||||
|
||||
## Installation
|
||||
1. Clonez le dépôt du projet :
|
||||
```bash
|
||||
git clone https://codefirst.iut.uca.fr/git/zayd.nahli/CodeMap.git
|
||||
```
|
||||
2. Installez les dépendances requises :
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Utilisation
|
||||
1. Exécutez le script principal :
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
2. Une interface s'ouvrira vous permettant de :
|
||||
- Sélectionner le chemin de votre projet à l'aide de l'explorateur de fichiers.
|
||||
- Indiquer si le projet est en Java ou C#.
|
||||
- Choisir le type de génération souhaité : diagrammes UML, documentation, ou les deux.
|
||||
3. Cliquez sur **Générer** pour obtenir les résultats, le répertoire de sortie s'ouvrira automatiquement.
|
||||
|
||||
## Tâches en cours et futures
|
||||
Voici une liste des fonctionnalités déjà en place et des améliorations prévues :
|
||||
|
||||
### Fonctionnalités déjà implémentées
|
||||
- [x] Génération de diagrammes de classes UML pour les projets Java et C#.
|
||||
- [x] Interface utilisateur intuitive avec explorateur de fichiers intégré.
|
||||
|
||||
### Améliorations prévues
|
||||
- [ ] Support pour d'autres langages (Python, JavaScript, etc.).
|
||||
- [ ] Génération automatique de README pour les projets analysés.
|
||||
- [ ] Amélioration de l'interface utilisateur (apparence et ergonomie).
|
||||
- [ ] Optimisation des performances pour une génération plus rapide.
|
||||
- [ ] Ajout d'options de personnalisation pour les diagrammes générés.
|
||||
|
||||
## Contributions
|
||||
Pour contribuer, corriger des bugs ou proposer des fonctionnalités, soumettez un pr.
|
||||
|
||||
## Licence
|
||||
Ce projet est sous licence MIT. Vous êtes libre de l'utiliser, de le modifier et de le partager.
|
@ -0,0 +1,517 @@
|
||||
import tkinter as tk
|
||||
import customtkinter as ctk
|
||||
from tkinter import filedialog, messagebox, ttk
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
|
||||
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):
|
||||
super().__init__()
|
||||
|
||||
|
||||
self.title("Générateur UML et Documentation")
|
||||
self.geometry("800x600")
|
||||
|
||||
|
||||
ctk.set_appearance_mode("dark")
|
||||
ctk.set_default_color_theme("blue")
|
||||
|
||||
|
||||
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="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.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):
|
||||
"""Ajoute un message dans la zone de log avec auto-scroll"""
|
||||
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):
|
||||
"""Prévisualise et génère le diagramme UML si validé"""
|
||||
from src.uml_generator import UMLGenerator
|
||||
import tempfile
|
||||
|
||||
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
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):
|
||||
"""Prévisualise et génère le README si validé"""
|
||||
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()
|
||||
else:
|
||||
from src.code_analyzer import CSharpAnalyzer
|
||||
analyzer = CSharpAnalyzer()
|
||||
|
||||
|
||||
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 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__":
|
||||
app = UMLGeneratorApp()
|
||||
app.mainloop()
|
@ -0,0 +1,6 @@
|
||||
customtkinter
|
||||
plantuml
|
||||
javalang
|
||||
antlr4-python3-runtime
|
||||
pyparsing
|
||||
pillow
|
@ -0,0 +1,135 @@
|
||||
import os
|
||||
from abc import ABC, abstractmethod
|
||||
import javalang
|
||||
import re
|
||||
|
||||
class CodeAnalyzer(ABC):
|
||||
@abstractmethod
|
||||
def analyze_directory(self, directory_path):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_classes(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_relationships(self):
|
||||
pass
|
||||
|
||||
class JavaAnalyzer(CodeAnalyzer):
|
||||
def __init__(self):
|
||||
self.classes = []
|
||||
self.relationships = []
|
||||
|
||||
def analyze_directory(self, directory_path):
|
||||
for root, _, files in os.walk(directory_path):
|
||||
for file in files:
|
||||
if file.endswith('.java'):
|
||||
self._analyze_file(os.path.join(root, file))
|
||||
return self.classes, self.relationships
|
||||
|
||||
def _analyze_file(self, file_path):
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as file:
|
||||
content = file.read()
|
||||
|
||||
tree = javalang.parse.parse(content)
|
||||
for path, node in tree.filter(javalang.tree.ClassDeclaration):
|
||||
class_info = {
|
||||
'name': node.name,
|
||||
'methods': [],
|
||||
'attributes': [],
|
||||
'extends': node.extends.name if node.extends else None,
|
||||
'implements': [impl.name for impl in node.implements] if node.implements else []
|
||||
}
|
||||
|
||||
# Analyse des méthodes
|
||||
for method in node.methods:
|
||||
method_info = {
|
||||
'name': method.name,
|
||||
'return_type': method.return_type.name if method.return_type else 'void',
|
||||
'parameters': [(param.type.name, param.name) for param in method.parameters]
|
||||
}
|
||||
class_info['methods'].append(method_info)
|
||||
|
||||
# Analyse des attributs
|
||||
for field in node.fields:
|
||||
for declarator in field.declarators:
|
||||
attribute_info = {
|
||||
'name': declarator.name,
|
||||
'type': field.type.name
|
||||
}
|
||||
class_info['attributes'].append(attribute_info)
|
||||
|
||||
self.classes.append(class_info)
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de l'analyse du fichier {file_path}: {str(e)}")
|
||||
|
||||
def get_classes(self):
|
||||
return self.classes
|
||||
|
||||
def get_relationships(self):
|
||||
return self.relationships
|
||||
|
||||
class CSharpAnalyzer(CodeAnalyzer):
|
||||
def __init__(self):
|
||||
self.classes = []
|
||||
self.relationships = []
|
||||
|
||||
def analyze_directory(self, directory_path):
|
||||
for root, _, files in os.walk(directory_path):
|
||||
for file in files:
|
||||
if file.endswith('.cs'):
|
||||
self._analyze_file(os.path.join(root, file))
|
||||
return self.classes, self.relationships
|
||||
|
||||
def _analyze_file(self, file_path):
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as file:
|
||||
content = file.read()
|
||||
|
||||
# Analyse basique avec regex pour C#
|
||||
# Note: Une analyse plus robuste nécessiterait un parser C# complet
|
||||
class_pattern = r'class\s+(\w+)(?:\s*:\s*(\w+))?'
|
||||
method_pattern = r'(?:public|private|protected)\s+(?:static\s+)?(\w+)\s+(\w+)\s*\((.*?)\)'
|
||||
property_pattern = r'(?:public|private|protected)\s+(\w+)\s+(\w+)\s*{\s*get;\s*set;\s*}'
|
||||
|
||||
# Recherche des classes
|
||||
for match in re.finditer(class_pattern, content):
|
||||
class_name = match.group(1)
|
||||
base_class = match.group(2)
|
||||
|
||||
class_info = {
|
||||
'name': class_name,
|
||||
'methods': [],
|
||||
'attributes': [],
|
||||
'extends': base_class,
|
||||
'implements': []
|
||||
}
|
||||
|
||||
# Recherche des méthodes
|
||||
for method_match in re.finditer(method_pattern, content):
|
||||
method_info = {
|
||||
'name': method_match.group(2),
|
||||
'return_type': method_match.group(1),
|
||||
'parameters': method_match.group(3).split(',') if method_match.group(3) else []
|
||||
}
|
||||
class_info['methods'].append(method_info)
|
||||
|
||||
# Recherche des propriétés
|
||||
for prop_match in re.finditer(property_pattern, content):
|
||||
attribute_info = {
|
||||
'name': prop_match.group(2),
|
||||
'type': prop_match.group(1)
|
||||
}
|
||||
class_info['attributes'].append(attribute_info)
|
||||
|
||||
self.classes.append(class_info)
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de l'analyse du fichier {file_path}: {str(e)}")
|
||||
|
||||
def get_classes(self):
|
||||
return self.classes
|
||||
|
||||
def get_relationships(self):
|
||||
return self.relationships
|
@ -0,0 +1,104 @@
|
||||
import customtkinter as ctk
|
||||
import tkinter as tk
|
||||
from PIL import Image, ImageTk
|
||||
import os
|
||||
|
||||
class PreviewWindow(ctk.CTkToplevel):
|
||||
def __init__(self, parent, content_type="readme", content=None, image_path=None):
|
||||
super().__init__(parent)
|
||||
|
||||
# Configuration de la fenêtre
|
||||
self.title("Prévisualisation")
|
||||
self.geometry("800x600")
|
||||
|
||||
# Variables
|
||||
self.result = False
|
||||
|
||||
# Configuration de la grille principale
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
|
||||
# Frame principal
|
||||
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.main_frame.grid_rowconfigure(0, weight=1)
|
||||
|
||||
# Zone de prévisualisation
|
||||
if content_type == "readme":
|
||||
self.preview_text = ctk.CTkTextbox(
|
||||
self.main_frame,
|
||||
wrap="word",
|
||||
font=("Courier New", 12)
|
||||
)
|
||||
self.preview_text.grid(row=0, column=0, padx=10, pady=10, sticky="nsew")
|
||||
self.preview_text.insert("1.0", content)
|
||||
self.preview_text.configure(state="disabled")
|
||||
else: # UML
|
||||
self.preview_frame = ctk.CTkFrame(self.main_frame)
|
||||
self.preview_frame.grid(row=0, column=0, padx=10, pady=10, sticky="nsew")
|
||||
|
||||
if os.path.exists(image_path):
|
||||
# Charger et afficher l'image
|
||||
image = Image.open(image_path)
|
||||
|
||||
# Calculer les dimensions pour l'ajustement
|
||||
frame_width = 760 # 800 - 2*20 (padding)
|
||||
frame_height = 520 # 600 - 2*20 (padding) - 40 (boutons)
|
||||
|
||||
# Redimensionner l'image en conservant les proportions
|
||||
ratio = min(frame_width/image.width, frame_height/image.height)
|
||||
new_width = int(image.width * ratio)
|
||||
new_height = int(image.height * ratio)
|
||||
|
||||
image = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
||||
photo = ImageTk.PhotoImage(image)
|
||||
|
||||
self.image_label = ctk.CTkLabel(
|
||||
self.preview_frame,
|
||||
text=""
|
||||
)
|
||||
self.image_label.grid(row=0, column=0, padx=10, pady=10)
|
||||
self.image_label.configure(image=photo)
|
||||
self.image_label.image = photo # Garder une référence
|
||||
else:
|
||||
self.error_label = ctk.CTkLabel(
|
||||
self.preview_frame,
|
||||
text="Erreur : Image non trouvée",
|
||||
font=("Helvetica", 14)
|
||||
)
|
||||
self.error_label.grid(row=0, column=0, padx=10, pady=10)
|
||||
|
||||
# Frame pour les boutons
|
||||
self.button_frame = ctk.CTkFrame(self.main_frame)
|
||||
self.button_frame.grid(row=1, column=0, padx=10, pady=10, sticky="ew")
|
||||
self.button_frame.grid_columnconfigure((0, 1), weight=1)
|
||||
|
||||
# Boutons
|
||||
self.validate_button = ctk.CTkButton(
|
||||
self.button_frame,
|
||||
text="Valider",
|
||||
command=self.validate,
|
||||
font=("Helvetica", 14)
|
||||
)
|
||||
self.validate_button.grid(row=0, column=0, padx=10, pady=5)
|
||||
|
||||
self.cancel_button = ctk.CTkButton(
|
||||
self.button_frame,
|
||||
text="Annuler",
|
||||
command=self.cancel,
|
||||
font=("Helvetica", 14)
|
||||
)
|
||||
self.cancel_button.grid(row=0, column=1, padx=10, pady=5)
|
||||
|
||||
# Rendre la fenêtre modale
|
||||
self.transient(parent)
|
||||
self.grab_set()
|
||||
|
||||
def validate(self):
|
||||
self.result = True
|
||||
self.destroy()
|
||||
|
||||
def cancel(self):
|
||||
self.result = False
|
||||
self.destroy()
|
@ -0,0 +1,344 @@
|
||||
import os
|
||||
import subprocess
|
||||
import json
|
||||
from collections import defaultdict
|
||||
|
||||
class ProjectAnalyzer:
|
||||
def __init__(self):
|
||||
self.tech_patterns = {
|
||||
'python': ['.py', 'requirements.txt', 'setup.py', 'Pipfile'],
|
||||
'java': ['.java', 'pom.xml', 'build.gradle', '.jar'],
|
||||
'csharp': ['.cs', '.csproj', '.sln'],
|
||||
'javascript': ['.js', 'package.json', '.jsx'],
|
||||
'typescript': ['.ts', '.tsx', 'tsconfig.json'],
|
||||
'html': ['.html', '.htm'],
|
||||
'css': ['.css', '.scss', '.sass', '.less'],
|
||||
'react': ['react', '.jsx', '.tsx'],
|
||||
'angular': ['angular', '.ts'],
|
||||
'vue': ['.vue'],
|
||||
'docker': ['Dockerfile', 'docker-compose.yml'],
|
||||
'sql': ['.sql'],
|
||||
'markdown': ['.md'],
|
||||
'yaml': ['.yml', '.yaml'],
|
||||
'json': ['.json'],
|
||||
'xml': ['.xml'],
|
||||
'git': ['.git'],
|
||||
'maven': ['pom.xml'],
|
||||
'gradle': ['build.gradle'],
|
||||
'npm': ['package.json'],
|
||||
'pip': ['requirements.txt'],
|
||||
'dotnet': ['.csproj', '.sln']
|
||||
}
|
||||
|
||||
# Frameworks et bibliothèques spécifiques à détecter dans les fichiers
|
||||
self.framework_patterns = {
|
||||
'django': ['django'],
|
||||
'flask': ['flask'],
|
||||
'spring': ['@SpringBootApplication', '@Controller', '@Service'],
|
||||
'aspnet': ['Microsoft.AspNetCore'],
|
||||
'react': ['react'],
|
||||
'angular': ['@angular'],
|
||||
'vue': ['Vue'],
|
||||
'bootstrap': ['bootstrap'],
|
||||
'jquery': ['jquery'],
|
||||
'tailwind': ['tailwindcss'],
|
||||
'entity_framework': ['Microsoft.EntityFrameworkCore'],
|
||||
'hibernate': ['@Entity', '@Table'],
|
||||
'pytest': ['pytest'],
|
||||
'junit': ['@Test', 'junit'],
|
||||
'numpy': ['numpy'],
|
||||
'pandas': ['pandas'],
|
||||
'tensorflow': ['tensorflow'],
|
||||
'pytorch': ['torch']
|
||||
}
|
||||
|
||||
def analyze_technologies(self, project_path):
|
||||
"""Analyse les technologies utilisées dans le projet"""
|
||||
technologies = defaultdict(int)
|
||||
frameworks = defaultdict(int)
|
||||
|
||||
# Parcourir tous les fichiers du projet
|
||||
for root, _, files in os.walk(project_path):
|
||||
if '.git' in root: # Ignorer le dossier .git
|
||||
continue
|
||||
|
||||
for file in files:
|
||||
file_path = os.path.join(root, file)
|
||||
|
||||
# Détecter les technologies par extension/nom de fichier
|
||||
for tech, patterns in self.tech_patterns.items():
|
||||
for pattern in patterns:
|
||||
if file.endswith(pattern) or file == pattern:
|
||||
technologies[tech] += 1
|
||||
|
||||
# Analyser le contenu des fichiers pour détecter les frameworks
|
||||
try:
|
||||
if os.path.getsize(file_path) < 1000000: # Limiter aux fichiers < 1MB
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read().lower()
|
||||
for framework, patterns in self.framework_patterns.items():
|
||||
for pattern in patterns:
|
||||
if pattern.lower() in content:
|
||||
frameworks[framework] += 1
|
||||
except (UnicodeDecodeError, IOError):
|
||||
continue
|
||||
|
||||
# Filtrer les technologies et frameworks les plus utilisés
|
||||
significant_technologies = {k: v for k, v in technologies.items() if v > 0}
|
||||
significant_frameworks = {k: v for k, v in frameworks.items() if v > 0}
|
||||
|
||||
# Formater les résultats
|
||||
tech_list = []
|
||||
|
||||
# Ajouter les langages principaux
|
||||
for tech, count in significant_technologies.items():
|
||||
tech_list.append(f"{tech.capitalize()} - Langage/Technologie principale")
|
||||
|
||||
# Ajouter les frameworks
|
||||
for framework, count in significant_frameworks.items():
|
||||
tech_list.append(f"{framework.capitalize()} - Framework/Bibliothèque")
|
||||
|
||||
# Ajouter les outils de build/gestion de dépendances
|
||||
build_tools = {'maven', 'gradle', 'npm', 'pip', 'dotnet'}
|
||||
for tool in build_tools:
|
||||
if tool in significant_technologies:
|
||||
tech_list.append(f"{tool.capitalize()} - Gestion de dépendances")
|
||||
|
||||
return tech_list
|
||||
|
||||
def get_git_authors(self, project_path):
|
||||
"""Récupère les auteurs depuis l'historique Git avec leurs contributions"""
|
||||
try:
|
||||
# Vérifier si le projet est un dépôt git
|
||||
if not os.path.exists(os.path.join(project_path, '.git')):
|
||||
return []
|
||||
|
||||
# Récupérer les auteurs avec leurs contributions
|
||||
cmd_log = ['git', 'shortlog', '-sne', '--all']
|
||||
process = subprocess.Popen(cmd_log,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=project_path,
|
||||
text=True)
|
||||
output, _ = process.communicate()
|
||||
|
||||
authors = []
|
||||
for line in output.strip().split('\n'):
|
||||
if line.strip():
|
||||
# Format: "123\tAuthor Name <email@example.com>"
|
||||
parts = line.strip().split('\t')
|
||||
if len(parts) == 2:
|
||||
commits = parts[0].strip()
|
||||
author_info = parts[1].split('<')
|
||||
name = author_info[0].strip()
|
||||
email = author_info[1].rstrip('>')
|
||||
|
||||
# Récupérer les statistiques de contribution
|
||||
cmd_stat = ['git', 'log', '--author=' + email, '--pretty=tformat:', '--numstat']
|
||||
process = subprocess.Popen(cmd_stat,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=project_path,
|
||||
text=True)
|
||||
stat_output, _ = process.communicate()
|
||||
|
||||
# Calculer les lignes ajoutées/supprimées
|
||||
added = 0
|
||||
deleted = 0
|
||||
for stat_line in stat_output.strip().split('\n'):
|
||||
if stat_line.strip():
|
||||
try:
|
||||
add, delete, _ = stat_line.split('\t')
|
||||
if add != '-':
|
||||
added += int(add)
|
||||
if delete != '-':
|
||||
deleted += int(delete)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
# Récupérer la dernière contribution
|
||||
cmd_last = ['git', 'log', '-1', '--format=%ai', f'--author={email}']
|
||||
process = subprocess.Popen(cmd_last,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=project_path,
|
||||
text=True)
|
||||
last_date, _ = process.communicate()
|
||||
last_date = last_date.strip()
|
||||
|
||||
# Formater la date si disponible
|
||||
if last_date:
|
||||
from datetime import datetime
|
||||
date_obj = datetime.strptime(last_date.split()[0], '%Y-%m-%d')
|
||||
last_date = date_obj.strftime('%d/%m/%Y')
|
||||
author_line = f"**{name}**"
|
||||
author_line += f"\n - {commits} commits"
|
||||
author_line += f"\n - {added:,} lignes ajoutées, {deleted:,} lignes supprimées"
|
||||
author_line += f"\n - Dernière contribution : {last_date}"
|
||||
else:
|
||||
author_line = f"**{name}** ({commits} commits)"
|
||||
|
||||
authors.append(author_line)
|
||||
|
||||
return authors
|
||||
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de la récupération des auteurs Git: {str(e)}")
|
||||
return []
|
||||
|
||||
def get_prerequisites(self, project_path):
|
||||
"""Analyse le projet pour déterminer les prérequis"""
|
||||
prerequisites = []
|
||||
|
||||
# Vérifier les différents fichiers de dépendances
|
||||
req_file = os.path.join(project_path, 'requirements.txt')
|
||||
if os.path.exists(req_file):
|
||||
# Lire toutes les dépendances
|
||||
with open(req_file, 'r', encoding='utf-8') as f:
|
||||
dependencies = [line.strip() for line in f if line.strip() and not line.startswith('#')]
|
||||
|
||||
# Ajouter Python avec une version spécifique si trouvée
|
||||
python_version = "3.x" # Version par défaut
|
||||
for dep in dependencies:
|
||||
if dep.lower().startswith("python"):
|
||||
python_version = dep.split("==")[-1] if "==" in dep else "3.x"
|
||||
break
|
||||
prerequisites.append(f"- Python {python_version}")
|
||||
|
||||
# Grouper les dépendances par catégorie
|
||||
ui_deps = []
|
||||
parsing_deps = []
|
||||
diagram_deps = []
|
||||
other_deps = []
|
||||
|
||||
for dep in dependencies:
|
||||
# Extraire le nom et la version
|
||||
if "==" in dep:
|
||||
name, version = dep.split("==")
|
||||
name = name.strip().lower()
|
||||
version = f"v{version}"
|
||||
else:
|
||||
name = dep.strip().lower()
|
||||
version = "(dernière version)"
|
||||
|
||||
# Classifier la dépendance
|
||||
if name in ['customtkinter', 'tkinter', 'pillow']:
|
||||
ui_deps.append(f"{dep} - {version}")
|
||||
elif name in ['antlr4-python3-runtime', 'javalang', 'pyparsing']:
|
||||
parsing_deps.append(f"{dep} - {version}")
|
||||
elif name in ['plantuml']:
|
||||
diagram_deps.append(f"{dep} - {version}")
|
||||
else:
|
||||
other_deps.append(f"{dep} - {version}")
|
||||
|
||||
# Ajouter les dépendances groupées
|
||||
if ui_deps:
|
||||
prerequisites.append("\n### Interface graphique")
|
||||
for dep in ui_deps:
|
||||
prerequisites.append(f"- {dep}")
|
||||
|
||||
if parsing_deps:
|
||||
prerequisites.append("\n### Analyse de code")
|
||||
for dep in parsing_deps:
|
||||
prerequisites.append(f"- {dep}")
|
||||
|
||||
if diagram_deps:
|
||||
prerequisites.append("\n### Génération de diagrammes")
|
||||
for dep in diagram_deps:
|
||||
prerequisites.append(f"- {dep}")
|
||||
|
||||
if other_deps:
|
||||
prerequisites.append("\n### Autres dépendances")
|
||||
for dep in other_deps:
|
||||
prerequisites.append(f"- {dep}")
|
||||
|
||||
# Vérifier Java pour PlantUML
|
||||
if os.path.exists(req_file) and any('plantuml' in line.lower() for line in open(req_file)):
|
||||
prerequisites.insert(1, "- Java Runtime Environment (JRE) - Requis pour PlantUML")
|
||||
|
||||
# Ajouter les outils de développement recommandés
|
||||
prerequisites.append("\n### Outils de développement recommandés")
|
||||
prerequisites.append("- Un IDE Python (PyCharm, VSCode, etc.)")
|
||||
prerequisites.append("- Git pour le contrôle de version")
|
||||
|
||||
return prerequisites
|
||||
|
||||
def get_installation_steps(self, project_path):
|
||||
"""Génère les étapes d'installation en fonction du projet"""
|
||||
steps = []
|
||||
|
||||
# Étape 1 : Clonage du projet
|
||||
if os.path.exists(os.path.join(project_path, '.git')):
|
||||
try:
|
||||
# Récupérer l'URL du dépôt distant
|
||||
cmd = ['git', 'config', '--get', 'remote.origin.url']
|
||||
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
cwd=project_path, text=True)
|
||||
output, _ = process.communicate()
|
||||
repo_url = output.strip() if output.strip() else '[URL_DU_PROJET]'
|
||||
|
||||
# Récupérer le nom du projet
|
||||
project_name = os.path.basename(project_path)
|
||||
|
||||
steps.append(
|
||||
f"**Cloner le projet**\n ```bash\n git clone {repo_url}\n cd {project_name}\n ```"
|
||||
)
|
||||
except Exception:
|
||||
steps.append(
|
||||
"**Cloner le projet**\n ```bash\n git clone [URL_DU_PROJET]\n cd [NOM_DU_PROJET]\n ```"
|
||||
)
|
||||
else:
|
||||
steps.append(
|
||||
"**Télécharger le projet**\n Téléchargez et décompressez le projet dans un dossier de votre choix"
|
||||
)
|
||||
|
||||
# Étape 2 : Installation des dépendances
|
||||
if os.path.exists(os.path.join(project_path, 'requirements.txt')):
|
||||
steps.append(
|
||||
"**Installer les dépendances Python**\n ```bash\n pip install -r requirements.txt\n ```"
|
||||
)
|
||||
|
||||
if os.path.exists(os.path.join(project_path, 'package.json')):
|
||||
steps.append(
|
||||
"**Installer les dépendances Node.js**\n ```bash\n npm install\n ```"
|
||||
)
|
||||
|
||||
if os.path.exists(os.path.join(project_path, 'pom.xml')):
|
||||
steps.append(
|
||||
"**Compiler le projet avec Maven**\n ```bash\n mvn clean install\n ```"
|
||||
)
|
||||
|
||||
if os.path.exists(os.path.join(project_path, 'build.gradle')):
|
||||
steps.append(
|
||||
"**Compiler le projet avec Gradle**\n ```bash\n ./gradlew build\n ```"
|
||||
)
|
||||
|
||||
if any(f.endswith('.csproj') for f in os.listdir(project_path)):
|
||||
steps.append(
|
||||
"**Restaurer et compiler le projet .NET**\n ```bash\n dotnet restore\n dotnet build\n ```"
|
||||
)
|
||||
|
||||
# Étape 3 : Lancement de l'application
|
||||
main_files = {
|
||||
'main.py': "**Lancer l'application**\n ```bash\n python main.py\n ```",
|
||||
'app.py': "**Lancer l'application**\n ```bash\n python app.py\n ```",
|
||||
'manage.py': "**Lancer le serveur Django**\n ```bash\n python manage.py runserver\n ```"
|
||||
}
|
||||
|
||||
for file, command in main_files.items():
|
||||
if os.path.exists(os.path.join(project_path, file)):
|
||||
steps.append(command)
|
||||
break
|
||||
|
||||
if os.path.exists(os.path.join(project_path, 'package.json')):
|
||||
try:
|
||||
with open(os.path.join(project_path, 'package.json'), 'r') as f:
|
||||
package_data = json.load(f)
|
||||
if 'scripts' in package_data and 'start' in package_data['scripts']:
|
||||
steps.append("**Lancer l'application**\n ```bash\n npm start\n ```")
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
# Si aucune étape de lancement n'est détectée, ne pas ajouter d'étape par défaut
|
||||
|
||||
return steps
|
@ -0,0 +1,141 @@
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
class ReadmeGenerator:
|
||||
def __init__(self):
|
||||
self.template = '''<h1 align="center" id="title">{title}</h1>
|
||||
|
||||
<p id="description">{description}</p>
|
||||
|
||||
## 🛠️ Prérequis
|
||||
|
||||
{prerequisites}
|
||||
|
||||
## 🚀 Installation
|
||||
|
||||
{installation_steps}
|
||||
|
||||
## 💻 Technologies Utilisées
|
||||
|
||||
{technologies}
|
||||
|
||||
## 👥 Contributeurs
|
||||
|
||||
{authors}
|
||||
|
||||
## 📝 Licence
|
||||
|
||||
Ce projet est distribué sous la licence {license}.
|
||||
'''
|
||||
|
||||
def analyze_project(self, project_path):
|
||||
"""Analyse le projet pour détecter ses caractéristiques"""
|
||||
project_info = {}
|
||||
|
||||
# Détecter le nom du projet
|
||||
project_info['title'] = os.path.basename(project_path)
|
||||
|
||||
# Détecter la description à partir du README existant ou des commentaires dans les fichiers
|
||||
description = ""
|
||||
readme_files = ['README.md', 'README.txt', 'README']
|
||||
for readme in readme_files:
|
||||
readme_path = os.path.join(project_path, readme)
|
||||
if os.path.exists(readme_path):
|
||||
with open(readme_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
# Chercher une description entre les balises ou après le titre
|
||||
import re
|
||||
desc_match = re.search(r'description[>\n]+(.*?)\n\n', content, re.I | re.S)
|
||||
if desc_match:
|
||||
description = desc_match.group(1).strip()
|
||||
break
|
||||
|
||||
if not description:
|
||||
# Chercher dans les docstrings des fichiers Python
|
||||
for root, _, files in os.walk(project_path):
|
||||
if '.git' in root or '__pycache__' in root:
|
||||
continue
|
||||
for file in files:
|
||||
if file.endswith('.py'):
|
||||
try:
|
||||
with open(os.path.join(root, file), 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
# Chercher une docstring au début du fichier
|
||||
doc_match = re.search(r'"""(.*?)"""', content, re.S)
|
||||
if doc_match:
|
||||
description = doc_match.group(1).strip()
|
||||
break
|
||||
except:
|
||||
continue
|
||||
if description:
|
||||
break
|
||||
|
||||
project_info['description'] = description if description else "Description à remplir"
|
||||
|
||||
# Détecter la licence
|
||||
license_files = ['LICENSE', 'LICENSE.txt', 'LICENSE.md']
|
||||
license_type = "MIT" # Licence par défaut
|
||||
for license_file in license_files:
|
||||
license_path = os.path.join(project_path, license_file)
|
||||
if os.path.exists(license_path):
|
||||
with open(license_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read().lower()
|
||||
if 'apache' in content:
|
||||
license_type = 'Apache'
|
||||
elif 'gnu' in content or 'gpl' in content:
|
||||
license_type = 'GPL'
|
||||
elif 'mit' in content:
|
||||
license_type = 'MIT'
|
||||
elif 'bsd' in content:
|
||||
license_type = 'BSD'
|
||||
break
|
||||
project_info['license'] = license_type
|
||||
|
||||
return project_info
|
||||
|
||||
def generate_readme_content(self, project_info):
|
||||
"""Génère le contenu du README sans l'écrire dans un fichier"""
|
||||
# Formatage des prérequis (déjà formatés par l'analyseur)
|
||||
prerequisites = "\n".join(project_info.get('prerequisites', []))
|
||||
|
||||
# Formatage des étapes d'installation
|
||||
installation_steps = "\n\n".join(project_info.get('installation_steps', []))
|
||||
|
||||
# Formatage des technologies
|
||||
technologies = "Ce projet utilise les technologies suivantes :\n\n"
|
||||
technologies += "\n".join([f"* {tech}" for tech in project_info.get('technologies', [])])
|
||||
|
||||
# Formatage des auteurs avec leurs contributions
|
||||
authors = "\n\n".join([f"{author}" for author in project_info.get('authors', [])])
|
||||
if not authors:
|
||||
authors = "*Aucun contributeur listé*"
|
||||
|
||||
# Remplacement des placeholders
|
||||
return self.template.format(
|
||||
title=project_info.get('title', 'Projet Sans Titre'),
|
||||
description=project_info.get('description', ''),
|
||||
prerequisites=prerequisites,
|
||||
installation_steps=installation_steps,
|
||||
technologies=technologies,
|
||||
authors=authors,
|
||||
license=project_info.get('license', 'MIT')
|
||||
)
|
||||
|
||||
def generate_readme(self, project_info, output_path):
|
||||
"""Génère le fichier README.md basé sur les informations du projet"""
|
||||
# Analyser le projet pour compléter les informations manquantes
|
||||
project_analysis = self.analyze_project(output_path)
|
||||
|
||||
# Fusionner les informations fournies avec celles détectées
|
||||
merged_info = project_analysis.copy()
|
||||
merged_info.update(project_info) # Les infos fournies ont la priorité
|
||||
|
||||
# Générer le contenu
|
||||
content = self.generate_readme_content(merged_info)
|
||||
|
||||
# Sauvegarde du fichier README
|
||||
readme_file = os.path.join(output_path, "README.md")
|
||||
with open(readme_file, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
|
||||
return readme_file
|
@ -0,0 +1,88 @@
|
||||
import os
|
||||
from plantuml import PlantUML
|
||||
|
||||
class UMLGenerator:
|
||||
def __init__(self):
|
||||
self.url = "http://www.plantuml.com/plantuml/img/"
|
||||
self.plantuml = PlantUML(url=self.url)
|
||||
|
||||
def generate_class_diagram(self, classes, output_path):
|
||||
uml_content = "@startuml\n\n"
|
||||
|
||||
|
||||
uml_content += "hide circle\n"
|
||||
uml_content += "allowmixing\n"
|
||||
uml_content += "skinparam classAttributeIconSize 0\n"
|
||||
uml_content += "skinparam classBackgroundColor #ffffb9\n"
|
||||
uml_content += "skinparam classBorderColor #800000\n"
|
||||
uml_content += "skinparam classArrowColor #800000\n"
|
||||
uml_content += "skinparam classFontColor black\n"
|
||||
uml_content += "skinparam classFontName Tahoma\n\n"
|
||||
|
||||
|
||||
for class_info in classes:
|
||||
uml_content += f"class {class_info['name']} {{\n"
|
||||
|
||||
|
||||
for attr in class_info['attributes']:
|
||||
uml_content += f" +{attr['name']}: {attr['type']}\n"
|
||||
|
||||
|
||||
for method in class_info['methods']:
|
||||
params = ", ".join([f"{p[0]} {p[1]}" if isinstance(p, tuple) else p for p in method['parameters']])
|
||||
uml_content += f" +{method['name']}({params}): {method['return_type']}\n"
|
||||
|
||||
uml_content += "}\n\n"
|
||||
|
||||
|
||||
if class_info['extends']:
|
||||
uml_content += f"{class_info['extends']} <|-- {class_info['name']}\n"
|
||||
|
||||
|
||||
for interface in class_info['implements']:
|
||||
uml_content += f"{interface} <|.. {class_info['name']}\n"
|
||||
|
||||
uml_content += "@enduml"
|
||||
|
||||
|
||||
uml_file = os.path.join(output_path, "class_diagram.puml")
|
||||
with open(uml_file, "w", encoding="utf-8") as f:
|
||||
f.write(uml_content)
|
||||
|
||||
|
||||
png_file = os.path.join(output_path, "class_diagram.png")
|
||||
self.plantuml.processes_file(uml_file, outfile=png_file)
|
||||
|
||||
return png_file
|
||||
|
||||
def generate_documentation(self, classes, output_path):
|
||||
doc_content = "# Documentation du Projet\n\n"
|
||||
|
||||
for class_info in classes:
|
||||
doc_content += f"## Classe {class_info['name']}\n\n"
|
||||
|
||||
if class_info['extends']:
|
||||
doc_content += f"Hérite de: `{class_info['extends']}`\n\n"
|
||||
|
||||
if class_info['implements']:
|
||||
doc_content += f"Implémente: {', '.join(class_info['implements'])}\n\n"
|
||||
|
||||
doc_content += "### Attributs\n\n"
|
||||
for attr in class_info['attributes']:
|
||||
doc_content += f"- `{attr['name']}`: {attr['type']}\n"
|
||||
|
||||
doc_content += "\n### Méthodes\n\n"
|
||||
for method in class_info['methods']:
|
||||
params = ", ".join([f"{p[0]} {p[1]}" if isinstance(p, tuple) else p for p in method['parameters']])
|
||||
doc_content += f"#### {method['name']}\n"
|
||||
doc_content += f"- Retour: `{method['return_type']}`\n"
|
||||
doc_content += f"- Paramètres: `{params}`\n\n"
|
||||
|
||||
doc_content += "---\n\n"
|
||||
|
||||
|
||||
doc_file = os.path.join(output_path, "documentation.md")
|
||||
with open(doc_file, "w", encoding="utf-8") as f:
|
||||
f.write(doc_content)
|
||||
|
||||
return doc_file
|
Loading…
Reference in new issue