orgmode-to-gemini-blog/linking_articles_prev_next.py
2025-02-23 20:23:38 +01:00

408 lines
16 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
generate_linkings_json = True
generate_articles = True
# 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=False)
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('--force_html_regen', action='store_true', help='Forcer la régénération des fichiers HTML même s\'ils existent déjà')
args = parser.parse_args()
run_gemini = args.run_gemini
run_pandoc = args.run_pandoc
force_html_regen = args.force_html_regen
# 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 = f'sources/{args.blog}/lang_fr'
destination_json = f'sources/{args.blog}/build'
destination_html = f'html-websites/{args.blog}/'
destination_gmi = f'gemini-capsules/{args.blog}/'
# Dictionnaire pour stocker les informations des fichiers
files_dict = {}
def get_first_picture_url(content):
# Utiliser une expression régulière pour trouver la première URL d'image dans le contenu
pattern = r'\[\[(.*?)\]\]'
match = re.search(pattern, content)
if match:
return match.group(1)
else:
return None
def org_to_gmi(org_text: str, output_filename_slug: str) -> str:
"""
Convertit un texte au format Org en un fichier au format GMI (Gemini)
en utilisant pypandoc.
Args:
- org_text (str): Le texte au format Org à convertir.
- output_file (str): Chemin du fichier de sortie au format GMI, sans avoir à préciser l'extension.
"""
output = """
# mock land output
===========
blah blah blah
-----------------
Tykayn blog mock content
-----------------
Navigation:
=> accueil.gmi Accueil
=> a-propos.gmi à propos
"""
# Conversion du texte Org en GMI via Pandoc
try:
output = pypandoc.convert_text(org_text, 'markdown', format='org')
except RuntimeError as e:
print(f"Erreur de conversion : {e}")
return
# Sauvegarde du contenu GMI dans un fichier
try:
with open(destination_gmi+'/'+output_filename_slug+'.gmi', 'w', encoding='utf-8') as f:
f.write(output)
print(f"Fichier GMI sauvegardé avec succès : {output_filename_slug}")
except OSError as e:
print(f"Erreur lors de la sauvegarde du fichier : {e}")
return output
if generate_linkings_json :
count_articles = len(os.listdir(directory))
counter=0
rebuild_counter = 0
pandoc_runs_counter = 0
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}")
# Parcourir les fichiers du dossier
for file_name in os.listdir(directory):
if file_name.endswith('.org'):
counter+=1
if force_html_regen and counter % 10 == 0:
print(f"{time.strftime('%H:%M:%S')} : Articles traités : {counter}/{count_articles}")
file_path = os.path.join(directory, file_name)
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 = ''
# 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
gemini_path = f"gemini-capsules/{args.blog}/{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
# 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
if run_pandoc and rebuild_this_article_html or force_html_regen:
# convertir le contenu d'article org vers html
print(f"BRRRRRRRRRRRR pandoc time {time.strftime('%H:%M:%S')} : Conversion de {file_name} en html")
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:
os.makedirs(destination_gmi, exist_ok=True)
# convertir le contenu d'article org vers gmi pour la capsule gemini
gemini_content = org_to_gmi(content_without_h1, slug)
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],
'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': re.sub(r'<h1>.*?</h1>', '', html_content), # Contenu HTML converti sans le titre de premier niveau
'html_content': html_content # Contenu first_picture_urlHTML converti
}
print(f"======= Nombre d'articles reconstruits: {rebuild_counter}")
print(f"======= Nombre de runs de pandoc: {pandoc_runs_counter}")
# 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)
json_file=destination_json+'/articles_info.json'
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
for basename, info in files_dict.items():
date_str = info['date']
current_year = datetime.now().year
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"\n=> {article['slug']}.gmi "
output_index_gmi = f"""
# {template_content['BLOG_TITLE']}
===============================================
{template_content['BANNIERE_ENTETE']}
Par {template_content['AUTHOR']}
Dernière mise à jour: {datetime.now()}
***********************************************
{template_content['DESCRIPTION']}
**************************************************************
{template_content['SITE_ICON']}
---------------------
Index des {len(files_dict.items())} articles:
{gmi_list_articles}
---------------------
Pages:
=> index.gmi
"""
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}")
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}")
# 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')
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.
"""
print('generate_article_pages: ouverture du json')
# Charger les données JSON
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)
# Générer les pages pour chaque article
for article in articles_info.values():
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
)
# Construire le chemin de sortie en fonction du slug avec l'année
output_subdir = os.path.join(output_dir, article['slug_with_year'])
os.makedirs(output_subdir, exist_ok=True)
output_file = os.path.join(output_subdir ,"index.html")
# print(output_file)
# Écrire le fichier de sortie
with open(output_file, 'w', encoding='utf-8') as f:
f.write(output_html)
print('generate_article_pages: fin de génération de l index')
if generate_articles:
# 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")