orgmode-to-gemini-blog/linking_articles_prev_next.py

552 lines
27 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/python3
# trouver les articles précédents et suivants
from utils.utils import *
from website_config import configs_sites, global_config
import datetime as dt
import os
import json
import re
import argparse
import pypandoc
from jinja2 import Environment, FileSystemLoader
import time
import sys
# Démarrer le chronomètre
start_time = time.time()
# Configs pour tester
# Configurer argparse pour prendre le blog en argument
parser = argparse.ArgumentParser(description='Générer une liste des derniers articles de blog.')
parser.add_argument('blog', type=str, help='Nom du dossier du blog à traiter', default='cipherbliss_blog')
parser.add_argument('--run_gemini', type=str, help='Activer ou non la génération des billets gemini', default=True)
parser.add_argument('--run_pandoc', type=str, help='Activer ou non la génération des fichiers html', default=True)
parser.add_argument('--enable_roam_id_rewrite', type=str, help='Activer ou non la réécriture des liens roam', default=False)
parser.add_argument('--generate_html_pages', type=str, help='Activer ou non la génération des pages html', default=True)
parser.add_argument('--generate_linkings_json', type=str, help='Activer ou non la génération du json des liens entre articles', default=True)
parser.add_argument('--force_html_regen', action='store_true', help='Forcer la régénération des fichiers HTML même s\'ils existent déjà', default=False)
parser.add_argument('--rebuild_articles_info_json', type=str, help='Reconstruire le fichier de données JSON des articles', default=False)
args = parser.parse_args()
run_gemini = args.run_gemini
run_pandoc = args.run_pandoc
generate_linkings_json = args.generate_linkings_json
force_html_regen = args.force_html_regen
generate_html_pages = args.generate_html_pages
rebuild_articles_info_json = args.rebuild_articles_info_json
# TODO check cette fonctionnalité
enable_roam_id_rewrite = args.enable_roam_id_rewrite
# Fonction pour extraire le basename d'un fichier
def get_basename(file_name):
return os.path.splitext(file_name)[0]
# Chemin du dossier contenant les fichiers orgmode
directory_pages = f'sources/{args.blog}/'
lang_fr = f'sources/{args.blog}/lang_fr'
lang_en = f'sources/{args.blog}/lang_en'
# directories_to_scan = [directory_pages, lang_fr, lang_en]
directories_to_scan = [directory_pages]
destination_json = f'sources/{args.blog}/build'
destination_html = f'html-websites/{args.blog}/'
destination_gmi = f'gemini-capsules/{args.blog}/'
json_file = destination_json + '/articles_info.json'
# Si rebuild_articles_info_json est True, supprimer le fichier JSON existant
if rebuild_articles_info_json:
print(f"Suppression du fichier JSON existant: {json_file}")
try:
if os.path.exists(json_file):
os.remove(json_file)
print(f"Fichier JSON {json_file} supprimé avec succès")
except Exception as e:
print(f"Erreur lors de la suppression du fichier JSON: {e}")
# Dictionnaire pour stocker les informations des fichiers
# Vérifier si le fichier JSON existe déjà
files_dict = {}
if os.path.exists(json_file):
print(f" ============== Chargement du fichier JSON existant: {json_file}")
try:
with open(json_file, 'r', encoding='utf-8') as f:
files_dict = json.load(f)
print(f"Fichier JSON chargé avec succès, {len(files_dict)} articles trouvés")
except Exception as e:
print(f"Erreur lors du chargement du fichier JSON: {e}")
else:
print(f"\033[91m ============== Aucun fichier articles_info.json existant trouvé, reconstruction des informations du blog {args.blog}\033[0m")
# count_articles = count_files_in_directories(directories_to_scan)
counter=0
rebuild_counter = 0
pandoc_runs_counter = 0
lang_folder = global_config.get('lang_default', 'fr')
if generate_linkings_json :
#print(f"Génération des liens entre articles pour {count_articles} articles")
#print(f"run_pandoc: {run_pandoc}")
#print(f"run_gemini: {run_gemini}")
article_type = "article"
# Parcourir les fichiers du dossier
for index, directory in enumerate(directories_to_scan):
print(f"Traitement du dossier {directory}, {index+1}/{len(directories_to_scan)}")
try:
# Vérifier si le répertoire existe
if not os.path.exists(directory):
print(f"Le répertoire {directory} n'existe pas, on passe au suivant")
continue
subdirs = [d for d in os.listdir(directory)
if os.path.isdir(os.path.join(directory, d))
and d not in ["..", "build", "templates", "img"]]
for subdir_index, subdir in enumerate(subdirs):
print(f"Traitement du sous-dossier {subdir}, {subdir_index+1}/{len(subdirs)}")
subdir_path = os.path.join(directory, subdir)
try:
# Liste tous les fichiers du sous-dossier avec les extensions supportées
files = [f for f in os.listdir(subdir_path)
if f.endswith(('.org', '.md', '.gmi'))]
for file_index, file_name in enumerate(files):
print(f"Traitement du fichier {file_name}, {file_index+1}/{len(files)}")
# Vérifier si le fichier se termine par une extension supportée
if not (file_name.endswith('.org') or file_name.endswith('.md') or file_name.endswith('.gmi')):
print(f"Fichier {file_name} non supporté")
continue
if file_name.endswith('.org'):
counter+=1
# print(f"Traitement de l'article {counter}/{count_articles} {file_name}")
file_path = os.path.join(directory, subdir, file_name)
if force_html_regen and counter % 10 == 0:
print(f"{time.strftime('%H:%M:%S')} : Articles traités : {counter}")
# on ouvre chacun des fichiers interprétables
# pour déterminer les informations qu'il contient
# afin de les stocker dans un json pour la génération des pages html et gemini
with open(file_path, "r", encoding="utf-8") as f:
print(f"----- Traitement de l'article {counter}: {file_name}")
content = f.read()
# Convertir le contenu Org en HTML
title = find_first_level1_title(content)
date_modified = time.ctime(os.path.getmtime(file_path))
rebuild_this_article_gemini = False
rebuild_this_article_html = False
basename = get_basename(file_name)
date_str, annee, slug = find_year_and_slug_on_filename(basename)
slug = slugify_title(title)
slug_with_year = f"{annee}/{slug}"
tags = extract_tags_from_file(file_path, global_config['excluded_tags'])
# Convertir les tags en liste si c'est un set
if isinstance(tags, set):
tags = list(tags)
boom = basename.split('__')
# Désactiver les warning d'identifiant dupliqué dans la conversion pandoc
content_without_h1 = re.sub(r'^\*.*?$', '', content, count=1, flags=re.MULTILINE)
gemini_content = ''
html_content = ''
html_content_without_h1 = ''
# Vérifier l'existence du fichier HTML pour déterminer last_html_build
html_path = f"html-websites/{args.blog}/{annee}/{slug}/index.html"
print(f"html_path existe il? : {html_path}")
last_html_build_time = None
if os.path.exists(html_path):
# Obtenir la date de création du fichier HTML
last_html_build_time = os.path.getctime(html_path)
print(f"----- last_html_build EXISTE: {last_html_build_time} : {html_path}")
else:
print(f"html_path n'existe pas: on va le créer")
# Vérifier l'existence du fichier Gemini pour déterminer last_gemini_build
gemini_path = f"./gemini-capsules/{args.blog}/{annee}/{slug}.gmi"
last_gemini_build = None
if os.path.exists(gemini_path):
try:
# Obtenir directement le timestamp au lieu de la chaîne de date
last_gemini_build_time = os.path.getmtime(gemini_path)
last_gemini_build = time.ctime(last_gemini_build_time)
# Vérifier si l'article doit être reconstruit
file_modified_time = os.path.getmtime(file_path)
rebuild_this_article_gemini = file_modified_time > last_gemini_build_time
except Exception as e:
print(f"Erreur lors de la vérification des dates pour {gemini_path}: {e}")
rebuild_this_article_gemini = True
else:
rebuild_this_article_gemini = True
# print(f"rebuild_this_article_gemini: {rebuild_this_article_gemini}")
# Vérifier si l'article doit être reconstruit en comparant les dates de modification
if last_html_build_time:
file_modified_time = os.path.getmtime(file_path)
print(f"--------- file_modified_time: {file_path} : {file_modified_time}")
print(f"--------- last_html_build_time: {last_html_build_time}")
# Obtenir l'heure de dernière modification du fichier HTML
rebuild_this_article_html = file_modified_time > last_html_build_time
if rebuild_this_article_html:
print(f"\033[91m--------- article modifié après le build de son rendu html: {file_path}, {rebuild_this_article_html}\033[0m")
else:
print(f"\033[91m--------- article non modifié après le build de son rendu html: {file_path}, {rebuild_this_article_html}, on ne recrée pas\033[0m")
else:
# si il n'y a pas de fichier html, on le construit pour la première fois
print('on reconstruit le html de l\'article', file_name)
rebuild_this_article_html = True
if rebuild_this_article_html:
rebuild_counter += 1
print(f"slug_with_year: {slug_with_year}")
# Afficher les clés de files_dict pour débogage
# print("\nClés disponibles dans files_dict:")
# for key in files_dict.keys():
# print(f"- {key}")
# print("\n")
# Garder le contenu HTML existant si déjà présent
if not rebuild_this_article_html and slug_with_year in files_dict and 'html_content' in files_dict[slug_with_year]:
print('========= on reprend le contenu html existant')
len_html = len(files_dict[slug_with_year]['html_content'])
print(f"len_html: {len_html}")
if len_html > 0 :
html_content = files_dict[slug_with_year]['html_content']
html_content_without_h1 = re.sub(r'<h1>.*?</h1>', '', html_content)
if len(files_dict[slug_with_year]['html_content_without_h1']) > 0 :
html_content_without_h1 = files_dict[slug_with_year]['html_content_without_h1']
else:
print('========= pas de contenu html existant')
print(f"\033[91m {time.strftime('%H:%M:%S')} BRRRRRRRRRRRRR pandoc html_content : {title} en html\033[0m")
pandoc_runs_counter += 1
html_content = pypandoc.convert_text(content_without_h1, 'html', format='org')
html_content_without_h1 = re.sub(r'<h1>.*?</h1>', '', html_content)
if run_pandoc and rebuild_this_article_html or force_html_regen:
print(f"run_pandoc: {run_pandoc}")
print(f"rebuild_this_article_html: {rebuild_this_article_html}")
print(f"force_html_regen: {force_html_regen}")
# convertir le contenu d'article org vers html
print(f"\033[91m {time.strftime('%H:%M:%S')} BRRRRRRRRRRRRR pandoc : {title} en html\033[0m")
# print(f"\033[91m.\033[0m", end='', flush=True)
if not html_content:
html_content = pypandoc.convert_text(content_without_h1, 'html', format='org')
html_content_without_h1 = re.sub(r'<h1>.*?</h1>', '', html_content)
pandoc_runs_counter += 1
if rebuild_this_article_gemini:
print(f"\033[91m {time.strftime('%H:%M:%S')} BRRRRRRRRRRRRR gemini : {title} en gmi\033[0m")
pandoc_runs_counter += 1
gemini_content = org_to_gmi(content_without_h1)
files_dict[slug_with_year] = {
'path': file_path,
'basename': basename,
'roam_id': find_org_roam_id(content),
'slug': f"{slug}/",
'slug_with_year': slug_with_year,
'date': boom[0],
'lang': lang_folder,
'article_type': article_type,
'date_modified' : date_modified,
'first_picture_url' : get_first_picture_url(content),
'date_formattee': format_date_str(date_str),
'annee': annee,
'tags': tags,
'title': title,
'next': None,
'previous': None,
'last_html_build': last_html_build_time,
'last_gemini_build': last_gemini_build,
'org_content': content, # Contenu Org original
'html_content_without_h1': html_content_without_h1, # Contenu HTML converti sans le titre de premier niveau
'html_content': html_content, # Contenu first_picture_urlHTML converti
'gemini_content': gemini_content,
'gemini_file_path': f"{annee}/{slug}.gmi",
# Contenu gemini
}
except OSError as e:
print(f"Erreur lors de la lecture du sous-dossier {subdir_path}: {e}")
continue
except OSError as e:
print(f"Erreur lors de la lecture du dossier {directory}: {e}")
continue
print(f"======= Nombre d'articles reconstruits: {rebuild_counter}")
print(f"======= Nombre de runs de pandoc: {pandoc_runs_counter}")
else:
print(f"Pas de génération des liens entre articles")
# Trier les basenames par ordre décroissant
sorted_basenames = sorted(files_dict.keys(), reverse=True)
#print(len(sorted_basenames), 'articles trouvés')
template_content = get_blog_template_conf(args.blog)
# Dictionnaire des identifiants roam qui mène à un slug d'article pour réécrire les références
articles_roam_id_to_slugs = {info['roam_id']: slug for slug, info in files_dict.items()}
# Parcourir les articles de files_dict et ajouter une clé rewritten_roam_links_html là où un lien vers un identifiant roam est trouvé dans le html_content
if enable_roam_id_rewrite:
for slug, info in files_dict.items():
html_content = info['html_content']
rewritten_html_content = html_content
for roam_id, slug in articles_roam_id_to_slugs.items():
if roam_id is not None and isinstance(rewritten_html_content, str) and roam_id in rewritten_html_content:
#print(f'{roam_id} -> {slug}')
rewritten_html_content = rewritten_html_content.replace(f'href="#{roam_id}"', f'href="{template_content["NDD"]}/{slug}"')
info['rewritten_roam_links_html'] = rewritten_html_content
# Ajouter les infos des articles suivant et précédent dans la liste des articles
for i in range(len(sorted_basenames)):
basename = sorted_basenames[i]
if i > 0:
files_dict[basename]['previous'] = sorted_basenames[i - 1]
if i < len(sorted_basenames) - 1:
files_dict[basename]['next'] = sorted_basenames[i + 1]
os.makedirs(destination_json, exist_ok=True)
# sauver le json de tous les articles et pages
if pandoc_runs_counter > 0 or not os.path.exists(json_file) or rebuild_articles_info_json:
print(f"pandoc_runs_counter: {pandoc_runs_counter}")
print(f"rebuild_articles_info_json: {rebuild_articles_info_json}")
print(f"\033[94m Génération du json {json_file} \033[0m")
with open( json_file, 'w', encoding='utf-8') as json_file:
files_dict_serialized = json.dumps(files_dict, ensure_ascii=False, indent=4)
json_file.write(files_dict_serialized)
#print(f"Nombre d'articles trouvés : {len(sorted_basenames)}")
count_articles_updated = 0
current_year = dt.datetime.now().year
for basename, info in files_dict.items():
date_str = info['date']
if date_str > f'{current_year}0101':
count_articles_updated += 1
#print(f"Nombre d'articles mis à jour après le 01 01 {current_year} : {count_articles_updated}")
def generate_blog_index(json_file, template_file, output_file):
"""
Génère la page d'index du blog à partir des informations JSON et d'un template Jinja2.
"""
try:
# Charger les données JSON
with open(json_file, 'r', encoding='utf-8') as f:
articles_info = json.load(f)
# Trier les articles par date
sorted_articles = sorted(articles_info.values(), key=lambda x: x['date'], reverse=True)
# Séparer les articles récents et les autres
recent_articles = sorted_articles[:global_config['posts_per_page']]
articles_others = sorted_articles[global_config['posts_per_page']:]
# Configurer Jinja2
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template(template_file)
template_content = get_blog_template_conf(args.blog)
# Générer le HTML
output_index_html = template.render(
template_content=template_content,
articles=recent_articles,
articles_others=articles_others
)
# Générer le contenu GMI
gmi_list_articles = ''
current_year = None
for article in sorted_articles:
if article['annee'] != current_year:
current_year = article['annee']
gmi_list_articles += f"\n## {current_year}\n\n"
gmi_list_articles += f"=> {article['annee']}/{article['slug']}.gmi {article['title']}\n"
output_index_gmi = f"""
# {template_content['BLOG_TITLE']}
===============================================
{template_content['BANNIERE_ENTETE']}
Par {template_content['AUTHOR']}
===============================================
Dernière mise à jour: {dt.datetime.now().strftime('%Y-%m-%d, %H:%M:%S')}
===============================================
{template_content['DESCRIPTION']}
===============================================
{template_content['SITE_ICON']}
-----------------------------------------------
# Index des {len(sorted_articles)} articles:
{gmi_list_articles}
-----------------------------------------------
# Pages:
=> index.gmi Index
=> tags.gmi Tags
-----------------------------------------------
{template_content['SOUTIEN']}
-----------------------------------------------
{template_content['WEBSITE_GENERATOR_DESCRIPTION']}
{template_content['WEBSITE_GENERATOR']}
"""
# Écrire les fichiers de sortie
gmi_index_file = os.path.join(destination_gmi, 'index.gmi')
os.makedirs(os.path.dirname(output_file), exist_ok=True)
os.makedirs(os.path.dirname(gmi_index_file), exist_ok=True)
with open(output_file, 'w', encoding='utf-8') as f:
f.write(output_index_html)
with open(gmi_index_file, 'w', encoding='utf-8') as f:
f.write(output_index_gmi)
except IOError as e:
print(f"Erreur lors de la lecture/écriture des fichiers: {e}")
return False
except Exception as e:
print(f"Erreur inattendue: {e}")
return False
return True
# Générer la page d'index seulement si des articles ont été convertis
if pandoc_runs_counter > 0 or run_gemini or force_html_regen:
# Appel de la fonction pour générer la page d'index
generate_blog_index(destination_json + '/articles_info.json', 'templates/html/index.html.jinja', destination_html + 'index.html')
print(f"\033[94m index régénéré {destination_html}index.html \033[0m")
# else:
# #print("Aucun article n'a été converti, la page d'index n'est pas régénérée")
def generate_article_pages(json_file, template_file, output_dir):
"""
Génère les pages HTML pour chaque article à partir des informations JSON et d'un template Jinja2.
:param json_file: Chemin du fichier JSON contenant les informations des articles.
:param template_file: Chemin du fichier template Jinja2.
:param output_dir: Répertoire de sortie pour les fichiers HTML.
"""
counter_gemini = 0
print(f"generate_article_pages: ouverture du json {json_file}")
try:
with open(json_file, 'r', encoding='utf-8') as f:
articles_info = json.load(f)
# Configurer Jinja2
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template(template_file)
template_content = get_blog_template_conf(args.blog)
print(f"articles count: {len(articles_info.values())}")
# Générer les pages pour chaque article
for article in articles_info.values():
print('----------------------- article', article['title'])
if article['first_picture_url']:
template_content['OG_IMAGE'] = article['first_picture_url']
else:
template_content['OG_IMAGE'] = template_content['SITE_ICON']
output_html = template.render(
template_content=template_content,
article=article,
all_articles=articles_info
)
slug_to_use = article['slug_with_year']
# Déterminer le slug à utiliser selon le type d'article
if 'article_type' in article and article['article_type'] == 'article':
slug_to_use = article['slug_with_year']
else:
slug_to_use = article['slug']
# Construire le chemin de sortie html en fonction du slug avec l'année
output_subdir = os.path.join(output_dir, slug_to_use)
print(f"output_subdir: {output_subdir}")
os.makedirs(output_subdir, exist_ok=True)
output_file = os.path.join(output_subdir ,"index.html")
print(f"output_file: {output_file}")
# Écrire le fichier de sortie en HTML pour un article
with open(output_file, 'w', encoding='utf-8') as f:
f.write(output_html)
print(f"Génération de la page gemini pour {article['title']}")
if 'gemini_content' in article and len(article['gemini_content']) > 0:
# Construire le chemin de sortie gmi en fonction du slug avec l'année
save_gemini_file(args.blog, article, articles_info, template_content)
counter_gemini += 1
else:
print(f"----------- on ne génère pas le gemini pour {article['slug']}")
print(f"\033[94m Nombre d'articles gemini générés : {counter_gemini}\033[0m")
return
except IOError as e:
print(f"Erreur lors de la lecture du fichier {json_file}: {e}")
sys.exit(1)
except Exception as e:
print(f"Erreur inattendue lors de la lecture du fichier {json_file}: {e}")
sys.exit(1)
# if pandoc_runs_counter or run_gemini:
# Appel de la fonction pour générer les pages des articles
generate_article_pages(destination_json + '/articles_info.json', 'templates/html/article.html.jinja', destination_html)
# À la fin du script, calculer et afficher le temps d'exécution
execution_time = time.time() - start_time
#print(f"Temps d'exécution : {execution_time:.2f} secondes")