![Firefly Description d’image pour l’article - L’image montre un exemple de mise en page d’un site Web](https://lmvi-conseil.fr/wp-content/uploads/2024/11/Firefly-Description-dimage-pour-larticle-Limage-montre-un-exemple-de-mise-en-page-dun-site-Web.jpg)
Quand extraire du code prend les airs d’une quête.
Parfois, même les projets les plus simples prennent des airs d’aventures héroïques ! C’est exactement ce qui m’est arrivé en créant ce script Python, destiné à extraire les blocs de code d’un fichier Markdown, et à les organiser proprement.
Si le script fonctionne désormais à merveille, il m’a fallu plus de deux heures de tests, de tâtonnements, et d’interactions avec plusieurs Grands Modèles de Langage (LLM), dont ChatGPT et Claude 3.5, pour en arriver là…
Aujourd’hui, je vous raconte ce voyage semé d’embûches et vous prouve en quoi il est devenu un outil précieux pour tout développeur cherchant à transformer des développements réalisés par des LLM en des modules isolés et bien organisés.
Spoiler : ça finit bien. Promis.
![Jean_Marc_Henry_LMVI](https://lmvi-conseil.fr/wp-content/uploads/2024/11/Jean_Marc_Henry_LMVI.png)
Bonjour !
Je suis Jean-Marc HENRY, ingénieur ESI, consultant IT/IS pour les entreprises depuis plus de 35 ans, et fondateur de LMVI Conseil.
À travers ce blog, je vous propose d’explorer ensemble tous les 15 jours les grands ou petits (!) sujets de l’informatique.
Ici, on parlera de sujets qui me servent quotidiennement et qui me tiennent à coeur, comme le Nocode, l’IA, l’IT, ou l’architecture logicielle et un peu WSO2.
D’ailleurs, je ne suis pas seul à rédiger ces billets !
Je suis accompagné de mon assistant IA prénommé Marius. C’est un bon pote d’Ollama et de ChatGPT (entre autres, car il a un sacré réseau !).
Il est assez secret et ne me dit pas tout sur la manière dont il m’aide à écrire mes articles. En revanche, je ne publie rien qui n’ait été validé par des sources sûres ou testé !
C’est parti, on vous embarque !
Une quête qui commence mal : 5 tentatives de shell scripts infructueuses.
Tout a commencé avec une tentative d’écrire ce programme en shell script avec l’aide de l’IA. Imaginez, vous avez un fichier Markdown bourré de blocs de code à extraire, et vous avez besoin de les organiser proprement.
Ça semble simple, non ? Eh bien, détrompez-vous, ça n’a pas été si facile…
J’ai lancé ChatGPT dans la bataille, pensant que ça allait être un jeu d’enfant. Après cinq versions inefficaces, je me suis rendu à l’évidence. Le shell script, aussi puissant soit-il pour les opérations système, s’est retrouvé limité par la complexité de la tâche : extraire des blocs de code dans tout types de langages et formats.
Frustré mais déterminé, j’ai décidé de reformuler ma demande et soumettre un prompt à son cousin Claude 3.5.
Et là… j’ai obtenu 20 versions différentes de code en Python, aucune n’était parfaite. La route était encore plus sinueuse que prévu ! Les ajustements et modifications n’avaient pas permis d’arriver à une version entièrement fonctionnelle, chaque solution proposée comportant de nouveaux défis à relever.
C’est alors que j’ai choisi de revenir vers ChatGPT pour déboguer le script existant. Après huit nouvelles versions, j’ai enfin obtenu un programme Python robuste et parfaitement adapté à mes besoins. Pfiou !
Le Programme : description et fonctionnalités
Le script Python développé a pour objectif de parcourir un fichier Markdown, d’en extraire les blocs de code, et de les organiser sous forme de fichiers distincts.
Pas si facile, car le script comporte des fonctionnalités cruciales pour gérer la variété et la complexité des langages de programmation. Je vous laisse découvrir le résultat !
Comment ça fonctionne ?
1. Extraction des Blocs de Code
Le programme ouvre le fichier Markdown et utilise une expression régulière pour détecter les blocs de code, identifiés par des backticks triple (« « `) et suivis du nom du langage. Les blocs sont alors extraits en tenant compte du langage spécifié (python, bash, etc.).
2.Création de Fichiers Temporaires
Les blocs de code extraits sont ensuite sauvegardés dans un répertoire temporaire (temp). Chaque bloc de code est stocké dans un fichier avec une extension correspondant au langage de programmation.
Chaque fichier prend l’extension qui correspond à son langage : .py pour Python, .sh pour Bash…
Le chaos commence à prendre forme !
3. Formatage spécial des scripts shell
Une particularité du programme est la gestion spécifique des scripts shell (bash ou sh). Chaque ligne de ces scripts est « trimée », c’est-à-dire qu’elle est nettoyée de tout espace superflu en début de ligne.
Cela permet de s’assurer que le code est clair, propre, et prêt à être exécuté sans espaces inutiles.
4. Organisation et destination finale : laisser la magie opérer !
Le programme traite ensuite les fichiers temporairement créés pour les organiser en fichiers définitifs.
Certains d’entre eux, comme les scripts shell, sont directement copiés dans le répertoire de sortie. Les autres sont organisés en fonction du contenu pour permettre de construire une arborescence parfaitement bien structurée.
Utilisation du programme : c’est du gâteau !
Pas de panique, l’utilisation du programme est simple. Tout ce que vous avez à faire, c’est d’exécuter le script en ligne de commande, en lui donnant le chemin de votre fichier Markdown comme argument :
python extract_code.py
Le tour est joué ! Le programme génère ensuite un répertoire output_YYYYMMDD_HHMMSS contenant les fichiers créés, chacun dans leur sous-répertoire approprié. Pratique, non ? Cela permet une gestion claire et ultra-organisée des blocs de code extraits.
Pourquoi du code en « extra » ?
Le programme va encore plus loin. Si un bloc de code contient des commentaires du type // path/to/file, il va utiliser cette info pour organiser les fichiers extraits dans une structure de répertoires correspondant.
Cette fonctionnalité permet de garder une organisation très proche des intentions initiales du développeur, offrant une certaine souplesse dans la gestion des fichiers extraits.
Pourquoi vous allez adorer ce programme ?
Alors, pourquoi ce programme est-il génial ?
Parce qu’il transforme n’importe quel développement réalisé avec l’aide de ChatGPT ou un autre LLM en sous-programmes parfaitement isolés et prêts à l’emploi.
Cela signifie que toute partie d’un développement, que ce soit des commandes shell ou des scripts de programmation dans divers langages, peut être extraite et sauvegardée dans des fichiers distincts, organisés par répertoire.
Cette approche permet non seulement une meilleure organisation, mais aussi un moyen efficace de transformer un code généré par un LLM en un projet véritablement utilisable dans la vraie vie.
Imaginez que vous ayez généré plusieurs fragments de codes lors d’une conversation avec ChatGPT. Et bien, ceux-ci peuvent être rapidement transformés en un ensemble de fichiers utilisables dans un dépôt Git, avec chaque morceau de code clairement identifié et enregistré sous forme de fichier autonome. Un vrai gain de temps !
Point important
Pour que les LLM puissent générer des programmes avec des informations de chemin, de nom de fichier, d’extension, ou de version en commentaire, vous pouvez ajouter des instructions spécifiques dans votre prompt dès le départ.
Cela garantira que chaque fichier généré contienne ces informations de manière standardisée.
Voici un exemple de commande à ajouter au prompt :
Prompt Général : lorsque vous générez un programme, veuillez suivre ces règles :
1- Ajoutez un commentaire à la première ligne contenant le nom du fichier, son extension, et son chemin.
- Pour un fichier Python, utilisez par exemple : # chemin/vers/fichier/nom_du_programme.py
- Pour un fichier JavaScript, utilisez // chemin/vers/fichier/nom_du_programme.js
- Utilisez la syntaxe de commentaire propre à chaque langage.
À la deuxième ligne, ajoutez la version du programme.
- Par exemple : # Version : 1.0.0 (ou utilisez la syntaxe de commentaire correspondant au langage).
Assurez-vous que le format soit respecté avec les informations exactes pour chaque fichier que vous générez.
Exemple concret d’utilisation pour un LLM :
Si vous demandez à un LLM de créer un programme Python, l’output devra ressembler à ceci :
# chemin/vers/fichier/mon_programme.py
# Version : 1.0.0
import os
# Le reste du code...
Pour un fichier JavaScript, le format serait :
// chemin/vers/fichier/mon_script.js
// Version : 1.0.0
console.log("Hello, world!");
Conclusion
Bien que je n’aie pas moi-même écrit chaque ligne de ce programme, sa création a été une aventure qui a nécessité de nombreuses heures d’interaction avec divers LLM.
Le processus a impliqué des essais, des erreurs, des ajustements constants et, finalement, une solution aboutie.
Ce programme est bien plus qu’un simple extracteur de code : il est une preuve que les modèles LLM, utilisés de manière créative et avec une dose de persévérance, peuvent produire des outils utiles pour améliorer la productivité des développeurs.
Transformer une idée en un script fonctionnel est une aventure qui demande de la patience et du courage, mais aussi une volonté de collaborer main dans la main avec la machine pour arriver à un résultat satisfaisant.
Grâce à ce script, je peux désormais transformer les réponses des LLM en modules exploitables, organisés et isolés pour un développement plus rapide et mieux structuré. Merci à mes fidèles alliés ChatGPT, Claude 3.5, et à Marius bien-sûr !
import sys
import os
import re
from datetime import datetime
# Dictionnaire centralisé pour les extensions de fichier et les syntaxes de commentaires
extensions = {
'python': ('.py', '#'),
'javascript': ('.js', '//'),
'js': ('.js', '//'),
'jsx': ('.jsx', '//'),
'typescript': ('.ts', '//'),
'ts': ('.ts', '//'),
'html': ('.html', '<!--'),
'css': ('.css', '/*'),
'sql': ('.sql', '--'),
'bash': ('.sh', '#'),
'sh': ('.sh', '#'),
'shell': ('.sh', '#'),
'c': ('.c', '//'),
'cpp': ('.cpp', '//'),
'c++': ('.cpp', '//'),
'cs': ('.cs', '//'),
'csharp': ('.cs', '//'),
'java': ('.java', '//'),
'go': ('.go', '//'),
'ruby': ('.rb', '#'),
'rb': ('.rb', '#'),
'php': ('.php', '//'),
'swift': ('.swift', '//'),
'kotlin': ('.kt', '//'),
'kt': ('.kt', '//'),
'r': ('.r', '#'),
'perl': ('.pl', '#'),
'pl': ('.pl', '#'),
'scala': ('.scala', '//'),
'groovy': ('.groovy', '//'),
'lua': ('.lua', '--'),
'dart': ('.dart', '//'),
'json': ('.json', None),
'yaml': ('.yaml', '#'),
'yml': ('.yml', '#'),
'markdown': ('.md', None),
'md': ('.md', None),
'makefile': ('.mk', '#'),
'dockerfile': ('.dockerfile', '#'),
'powershell': ('.ps1', '#'),
'ps1': ('.ps1', '#'),
'rust': ('.rs', '//'),
'rs': ('.rs', '//'),
}
def sanitize_path(path):
return re.sub(r'[<>:"|?*]', '_', path.strip())
def create_output_dir():
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_dir = f'output_{timestamp}'
temp_dir = os.path.join(output_dir, 'temp')
os.makedirs(temp_dir, exist_ok=True)
return output_dir, temp_dir
def extract_code_blocks(markdown_file, temp_dir):
with open(markdown_file, 'r', encoding='utf-8') as file:
content = file.read()
# Correction de la regex pour gérer les espaces et retours à la ligne après ```
code_block_pattern = re.compile(r'```(\w+)\s*\n(.*?)(?=```)', re.DOTALL)
code_blocks = code_block_pattern.findall(content)
counter = 1
sh_counter = 1
temp_files = []
for language, block in code_blocks:
# Identifier le nom et le chemin à partir du premier commentaire si présent
first_line = block.splitlines()[0].strip() if block.splitlines() else ''
comment_syntax = get_comment_syntax(language)
file_path = None
if comment_syntax and first_line.startswith(comment_syntax):
potential_path = first_line[len(comment_syntax):].strip()
file_path = sanitize_path(potential_path)
# Si le chemin est trouvé dans le commentaire, l'utiliser, sinon générer un nom par défaut
if file_path:
filename = file_path
else:
if language.lower() in ['bash', 'sh']:
filename = f"shellscript_{sh_counter:02d}.sh"
sh_counter += 1
else:
ext = get_extension(language)
filename = f"temp_{counter:03d}{ext}"
counter += 1
filepath = os.path.join(temp_dir, filename)
# Créer les répertoires nécessaires avant d'écrire le fichier
os.makedirs(os.path.dirname(filepath), exist_ok=True)
# Appliquer un trim sur chaque ligne du bloc pour les scripts shell
if language.lower() in ['bash', 'sh']:
block = "\n".join(line.lstrip() for line in block.splitlines())
with open(filepath, 'w', encoding='utf-8') as file:
file.write(block.strip())
temp_files.append((filepath, language))
print(f"Fichier créé dans temp : {filepath}")
return temp_files
def process_temp_files(temp_files, output_dir):
for temp_filepath, language in temp_files:
with open(temp_filepath, 'r', encoding='utf-8') as file:
lines = file.readlines()
# Vérifier si le premier commentaire contient le chemin du fichier
first_line = lines[0].strip() if lines else ''
comment_syntax = get_comment_syntax(language)
if comment_syntax and first_line.startswith(comment_syntax):
potential_path = first_line[len(comment_syntax):].strip()
file_path = sanitize_path(potential_path)
else:
file_path = os.path.basename(temp_filepath)
# Déterminer le chemin final du fichier
final_filepath = os.path.join(output_dir, file_path)
# Créer les répertoires nécessaires avant d'écrire le fichier
os.makedirs(os.path.dirname(final_filepath), exist_ok=True)
# Copier le contenu du fichier temporaire vers le fichier final
with open(final_filepath, 'w', encoding='utf-8') as dest:
dest.writelines(lines)
print(f"Fichier final créé : {final_filepath}")
def get_extension(language):
return extensions.get(language.lower(), ('.txt', None))[0]
def get_comment_syntax(language):
return extensions.get(language.lower(), (None, None))[1]
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python script.py ")
sys.exit(1)
markdown_file = sys.argv[1]
if not os.path.exists(markdown_file):
print(f"Le fichier {markdown_file} n'existe pas.")
sys.exit(1)
output_dir, temp_dir = create_output_dir()
temp_files = extract_code_blocks(markdown_file, temp_dir)
process_temp_files(temp_files, output_dir)
print(f"Extraction terminée. Les fichiers ont été créés dans le dossier '{output_dir}' et le sous-dossier '{temp_dir}'.")