orgmode-to-gemini-blog/linking_articles_prev_next.py

496 lines
24 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 import *
from website_config import configs_sites
import os
import json
import re
import argparse
import pypandoc
from jinja2 import Environment, FileSystemLoader
import time # Importer le module time
# 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à')
parser.add_argument('--rebuild_articles_info_json', type=str, help='Chemin du fichier 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}/'
# 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à
json_file = destination_json + '/articles_info.json'
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"Aucun fichier articles_info.json existant trouvé, reconstruction des informations du blog {args.blog}")
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}/{len(directories_to_scan)}")
for index, subdir in enumerate(os.listdir(directory)):
print(f"Traitement du dossier {directory}/{subdir}, {index}/{len(os.listdir(directory))}")
if subdir == ".." or subdir == "build" or subdir == "templates" or subdir == "img":
continue
# Déterminer le type d'article en fonction du chemin
if directory == '/':
article_type = "page"
else:
article_type = "article"
# Extraire la langue du dossier si elle commence par "lang_"
if directory.split('/')[-1].startswith('lang_'):
lang_folder = directory.split('/')[-1][5:] # Prend les caractères après "lang_"
for index, file_name in enumerate(os.listdir(f'{directory}/{subdir}')):
print(f"directory: {subdir}, {article_type}, {file_name}, {index}/{len(os.listdir(f'{directory}/{subdir}'))}")
# 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')):
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}/{count_articles}")
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
date_modified = time.ctime(os.path.getmtime(file_path))
basename = get_basename(file_name)
date_str, annee, slug = find_year_and_slug_on_filename(basename)
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('__')
# Convertir le contenu Org en HTML
title = find_first_level1_title(content)
# 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"
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: {last_html_build_time} : {html_path}")
else:
# ----- print(f"----------- last_html_build html_path: {html_path} n'existe pas")
# Vérifier l'existence du fichier Gemini pour déterminer last_gemini_build
os.makedirs(os.path.dirname(f"./html_websites/{args.blog}/{annee}"), exist_ok=True)
os.makedirs(os.path.dirname(f"./gemini-capsules/{args.blog}/{annee}"), exist_ok=True)
gemini_path = f"./gemini-capsules/{args.blog}/{annee}/{slug}.gmi"
last_gemini_build = None
rebuild_this_article_gemini = False
if os.path.exists(gemini_path):
last_gemini_build = time.ctime(os.path.getmtime(gemini_path))
# Vérifier si l'article doit être reconstruit en comparant les dates de modification
if last_gemini_build:
file_modified_time = os.path.getmtime(file_path)
last_build_time = time.mktime(time.strptime(last_gemini_build))
rebuild_this_article_gemini = file_modified_time > last_build_time
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
rebuild_this_article_html = False
if last_html_build_time:
file_modified_time = os.path.getmtime(file_path)
# # ----- print(f"--------- file_modified_time: {file_path} : {file_modified_time}")
# Obtenir l'heure de dernière modification du fichier HTML
rebuild_this_article_html = file_modified_time > last_html_build_time
# # ----- print(f"--------- article modifié après le build de son rendu html: {file_path}, {rebuild_this_article_html}")
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
# Garder le contenu HTML existant si déjà présent
if f"{annee}/{slug}" in files_dict and 'html_content' in files_dict[f"{annee}/{slug}"]:
# ----- print('on reprend le contenu html existant')
if len(files_dict[f"{annee}/{slug}"]['html_content']) > 0:
html_content = files_dict[f"{annee}/{slug}"]['html_content']
if len(files_dict[f"{annee}/{slug}"]['html_content_without_h1']) > 0:
html_content_without_h1 = files_dict[f"{annee}/{slug}"]['html_content_without_h1']
else:
html_content_without_h1 = re.sub(r'<h1>.*?</h1>', '', html_content)
if run_pandoc and rebuild_this_article_html or force_html_regen:
# convertir le contenu d'article org vers html
# # ----- print(f"\033[91mBRRRRRRRRRRRRR pandoc time {time.strftime('%H:%M:%S')} : Conversion de {file_name} en html\033[0m")
# ----- print(f"\033[91m.\033[0m", end='', flush=True)
html_content = pypandoc.convert_text(content_without_h1, 'html', format='org')
pandoc_runs_counter += 1
else:
html_content = content_without_h1
# if run_gemini and rebuild_this_article_gemini:
# # ----- print('-----------on régénère le gemini')
# # convertir le contenu d'article org vers gmi pour la capsule gemini
# gemini_content = org_to_gmi(content_without_h1)
# # ----- print('len(gemini_content)', len(gemini_content))
# else:
# print('-----------on ne régénère pas le gemini')
if rebuild_this_article_gemini:
gemini_content = org_to_gmi(content_without_h1)
files_dict[f"{annee}/{slug}"] = {
'path': file_path,
'basename': basename,
'roam_id': find_org_roam_id(content),
'slug': f"{slug}/",
'slug_with_year': f"{annee}/{slug}",
'date': boom[0],
'lang': lang_folder,
'article_type': article_type,
'date_modified' : date_modified,
'first_picture_url' : get_first_picture_url(content),
'date_formattee': datetime.strptime(date_str, '%Y%m%d%H%M%S').strftime('%d %B %Y à %H:%M:%S') if len(date_str) == 14 else datetime.strptime(date_str, '%Y%m%dT%H%M%S').strftime('%d %B %Y à %H:%M:%S') if len(date_str) == 15 else datetime.strptime(date_str, '%Y-%m-%d').strftime('%d %B %Y'),
'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
}
# ----- 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):
# ----- print(f"\033[91m Les articles ont changé, 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 = 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.
:param json_file: Chemin du fichier JSON contenant les informations des articles.
:param template_file: Chemin du fichier template Jinja2.
:param output_file: Chemin du fichier HTML de sortie.
"""
# 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 (ou par slug) et prendre les 10 derniers
sorted_articles = sorted(articles_info.values(), key=lambda x: x['date'], reverse=True)[:global_config['posts_per_page']]
# Configurer Jinja2
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template(template_file)
articles_others = sorted(articles_info.values(), key=lambda x: x['date'], reverse=True)[10:]
template_content = get_blog_template_conf(args.blog)
# Rendre le template avec les données
output_index_html = template.render(
template_content=template_content,
articles=sorted_articles[:global_config['posts_per_page']],
articles_others=articles_others
)
gmi_list_articles = ''
for basename, article in files_dict.items():
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: {datetime.now().strftime('%Y-%m-%d, %H:%M:%S')}
===============================================
{template_content['DESCRIPTION']}
===============================================
{template_content['SITE_ICON']}
-----------------------------------------------
Index des {len(files_dict.items())} articles:
{gmi_list_articles}
-----------------------------------------------
Pages:
=> index.gmi Index
=> tags.gmi Tags
-----------------------------------------------
{template_content['SOUTIEN']}
-----------------------------------------------
{template_content['WEBSITE_GENERATOR_DESCRIPTION']}
{template_content['WEBSITE_GENERATOR']}
"""
gmi_index_file=destination_gmi+'index.gmi'
# Écrire le fichier de sortie en html et en gmi
with open(output_file, 'w', encoding='utf-8') as f:
f.write(output_index_html)
# ----- print(f"Page d'index générée dans {output_file}")
os.makedirs(os.path.dirname(gmi_index_file), exist_ok=True)
# ----- print('gmi_index_file', gmi_index_file)
with open(gmi_index_file, 'w', encoding='utf-8') as f:
f.write(output_index_gmi)
# ----- print(f"Page d'index gemini générée dans {gmi_index_file}")
# Générer la page d'index seulement si des articles ont été convertis
# if pandoc_runs_counter > 0:
# 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[91m 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('generate_article_pages: ouverture du json')
# Charger les données JSON
try:
# Charger les données JSON
with open(json_file, 'r', encoding='utf-8') as f:
print('----------------------- yay json chargé')
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)
# 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)
os.makedirs(output_subdir, exist_ok=True)
output_file = os.path.join(output_subdir ,"index.html")
# É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)
counter_gemini += 1
else:
print(f"----------- on ne génère pas le gemini pour {article['slug']}")
# ----- print('generate_article_pages: fin de génération de l index')
# ----- print(f"Nombre d'articles générés en gemini: {counter_gemini}")
except FileNotFoundError:
print(f"Erreur : Le fichier JSON {json_file} n'a pas été trouvé")
return
except json.JSONDecodeError:
print(f"Erreur : Le fichier JSON {json_file} est mal formaté")
return
except Exception as e:
print(f"Erreur lors de la lecture du fichier JSON : {json_file} {str(e)}")
return
with open(json_file, 'r', encoding='utf-8') as f:
articles_info = json.load(f)
# 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")