Commit initale portage de ce projet de github a gitea apres amélioration

master
Zayd Nahli 1 week ago
commit 60f2469aea

10
.gitignore vendored

@ -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…
Cancel
Save