Created
September 29, 2024 07:59
-
-
Save apeyroux/422f26a2ef94def68f7ecf0cfd438955 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import shutil | |
import re | |
import logging | |
import argparse | |
from urllib.parse import unquote | |
from datetime import datetime # Importation de datetime pour la gestion des dates | |
# Configuration du logger | |
logging.basicConfig(filename='migration_log.txt', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
# Extensions d'images prises en charge | |
IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.svg'] | |
# Chemins par défaut pour les dossiers Obsidian et NotePlan | |
obsidian_folder_default = "/chemin/vers/ton/dossier/Obsidian" | |
noteplan_folder_default = "/chemin/vers/ton/dossier/NotePlan" | |
obsidian_assets_folder_default = os.path.join(obsidian_folder_default, "assets") | |
class FileHandler: | |
@staticmethod | |
def list_markdown_files(folder): | |
"""Liste tous les fichiers Markdown dans un dossier""" | |
return [f for f in os.listdir(folder) if f.endswith(".md")] | |
@staticmethod | |
def is_image(file_name): | |
"""Détermine si un fichier est une image en fonction de son extension""" | |
return any(file_name.lower().endswith(ext) for ext in IMAGE_EXTENSIONS) | |
@staticmethod | |
def copy_attachment(attachment_name, obsidian_assets_folder, noteplan_attachments_folder): | |
"""Recherche et copie une pièce jointe du dossier Obsidian (y compris les sous-dossiers) vers NotePlan""" | |
# Décoder le nom de fichier encodé en URL | |
decoded_attachment_name = unquote(attachment_name) | |
for root, _, files in os.walk(obsidian_assets_folder): | |
if decoded_attachment_name in files: | |
source_path = os.path.join(root, decoded_attachment_name) | |
destination_path = os.path.join(noteplan_attachments_folder, decoded_attachment_name) | |
shutil.copy(source_path, destination_path) | |
logging.info(f"Pièce jointe copiée : {decoded_attachment_name} vers {noteplan_attachments_folder}") | |
return True | |
logging.warning(f"Pièce jointe manquante : {decoded_attachment_name} non trouvée dans {obsidian_assets_folder}") | |
return False | |
class ContentConverter: | |
@staticmethod | |
def remove_yaml_header(content): | |
"""Supprime les en-têtes YAML dans les fichiers daily notes""" | |
# Recherche et suppression de l'en-tête entre les `---` au début | |
return re.sub(r'^---.*?---\n', '', content, flags=re.DOTALL) | |
@staticmethod | |
def convert_links(content, attachments_folder_name): | |
"""Convertit les liens internes Obsidian vers le format NotePlan, en tenant compte des fichiers avec espaces""" | |
def replace_link(match): | |
file_name = match.group(1).strip() | |
# Décoder le nom du fichier encodé en URL | |
file_name_decoded = unquote(file_name) | |
return f'' | |
return re.sub(r'!\[\[(.+?)\]\]', replace_link, content) | |
class Migration: | |
def __init__(self, obsidian_folder, noteplan_folder, obsidian_assets_folder, batch_size, migrate_attachments, is_daily): | |
self.obsidian_folder = obsidian_folder | |
self.noteplan_folder = noteplan_folder | |
self.obsidian_assets_folder = obsidian_assets_folder | |
self.batch_size = batch_size | |
self.migrate_attachments = migrate_attachments | |
self.is_daily = is_daily | |
@staticmethod | |
def convert_daily_filename(filename): | |
"""Convertit les noms de fichiers daily notes du format dd-mm-yyyy.md au format yyyymmdd.md""" | |
try: | |
# Extraire la date du nom de fichier avec le format dd-mm-yyyy | |
date_obj = datetime.strptime(filename[:10], '%d-%m-%Y') | |
# Reformater la date au format yyyymmdd | |
return date_obj.strftime('%Y%m%d') + ".md" | |
except ValueError: | |
# Si le fichier ne correspond pas au format attendu, retourner le nom original | |
return filename | |
def migrate_files_in_batches(self): | |
"""Migration par lots des fichiers Markdown et pièces jointes d'Obsidian vers NotePlan""" | |
markdown_files = FileHandler.list_markdown_files(self.obsidian_folder) | |
total_files = len(markdown_files) | |
logging.info(f"Nombre total de fichiers Markdown à migrer : {total_files}") | |
if not os.path.exists(self.noteplan_folder): | |
os.makedirs(self.noteplan_folder) | |
for i in range(0, total_files, self.batch_size): | |
batch_files = markdown_files[i:i + self.batch_size] | |
logging.info(f"Traitement du lot {i//self.batch_size + 1} : {len(batch_files)} fichiers") | |
for file in batch_files: | |
self.migrate_single_file(file) | |
def migrate_single_file(self, file): | |
"""Gère la migration d'un fichier Markdown unique""" | |
title = os.path.splitext(file)[0] | |
if self.is_daily: | |
# Si c'est une daily note, convertit le nom du fichier au format yyyymmdd.md | |
new_file_name = self.convert_daily_filename(file) | |
else: | |
new_file_name = file | |
obsidian_file_path = os.path.join(self.obsidian_folder, file) | |
noteplan_file_path = os.path.join(self.noteplan_folder, new_file_name) | |
# Lire le contenu du fichier Markdown | |
with open(obsidian_file_path, 'r', encoding='utf-8') as f: | |
content = f.read() | |
if self.is_daily: | |
# Retirer l'en-tête YAML dans les daily notes | |
content = ContentConverter.remove_yaml_header(content) | |
attachments_copied = 0 | |
if self.migrate_attachments: | |
attachments = re.findall(r'!\[\[(.+?)\]\]', content) | |
if attachments: | |
# Utiliser le nouveau nom de fichier pour les daily notes | |
noteplan_attachments_folder = os.path.join(self.noteplan_folder, f"{os.path.splitext(new_file_name)[0]}_attachments") | |
os.makedirs(noteplan_attachments_folder, exist_ok=True) | |
for attachment in attachments: | |
if FileHandler.copy_attachment(attachment, self.obsidian_assets_folder, noteplan_attachments_folder): | |
attachments_copied += 1 | |
# Convertir les liens, mais ne pas ajouter de titre pour les daily notes | |
if not self.is_daily: | |
content = f"# {title}\n\n" + content # Ajouter le titre pour les fichiers non daily | |
converted_content = ContentConverter.convert_links(content, f"{os.path.splitext(new_file_name)[0]}_attachments") | |
# Sauvegarder le fichier converti | |
with open(noteplan_file_path, 'w', encoding='utf-8') as f: | |
f.write(converted_content) | |
logging.info(f"Fichier migré : {file} avec {attachments_copied}/{len(attachments)} pièces jointes copiées") | |
def main(): | |
# Interface CLI | |
parser = argparse.ArgumentParser(description="Migration de fichiers Markdown d'Obsidian vers NotePlan") | |
parser.add_argument('--obsidian_folder', type=str, default=obsidian_folder_default, help="Dossier source Obsidian") | |
parser.add_argument('--noteplan_folder', type=str, default=noteplan_folder_default, help="Dossier destination NotePlan") | |
parser.add_argument('--obsidian_assets_folder', type=str, default=obsidian_assets_folder_default, help="Dossier des assets (pièces jointes) Obsidian") | |
parser.add_argument('--batch_size', type=int, default=10, help="Nombre de fichiers à traiter par lot") | |
parser.add_argument('--migrate_attachments', type=bool, default=True, help="Activer ou désactiver la migration des pièces jointes") | |
parser.add_argument('--daily', action='store_true', help="Traiter les notes quotidiennes et convertir les fichiers au format yyyymmdd.md") | |
args = parser.parse_args() | |
# Initialisation et lancement de la migration | |
migration = Migration( | |
obsidian_folder=args.obsidian_folder, | |
noteplan_folder=args.noteplan_folder, | |
obsidian_assets_folder=args.obsidian_assets_folder, | |
batch_size=args.batch_size, | |
migrate_attachments=args.migrate_attachments, | |
is_daily=args.daily | |
) | |
migration.migrate_files_in_batches() | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment