génération de stats pour un seul fichier org

This commit is contained in:
Tykayn 2025-11-02 17:38:52 +01:00 committed by tykayn
parent d404c6a7c8
commit 2e561080a9
4 changed files with 534 additions and 53 deletions

View file

@ -18,6 +18,8 @@ help:
@echo " make stats Génère les statistiques combinées pour tykayn_blog, cipherbliss_blog et qzine_blog"
@echo " make stats-all Génère les statistiques pour tous les blogs"
@echo " make stats-custom Génère les statistiques combinées personnalisées (modifiez la cible dans le Makefile)"
@echo " make stats-file Génère les statistiques d'un fichier unique (exemple: sources/tykayn_blog/lang_fr/2024-01-article.org)"
@echo " Utilisez: make stats-file FILE=chemin/vers/fichier.org [DATE_DEBUT=YYYY-MM-DD]"
@echo ""
@echo "Export EPUB:"
@echo " make epub-tykayn Exporte tykayn_blog en EPUB"
@ -27,13 +29,31 @@ help:
@echo " make epub Exporte tous les blogs en EPUB (modifiez la cible dans le Makefile)"
stats:
python3 generate_blog_stats.py tykayn_blog cipherbliss_blog qzine_blog --output tk_combinaison_stats.html
python3 generate_blog_stats.py cipherbliss_blog
stats-all:
python3 generate_blog_stats.py
python3 generate_blog_stats.py tykayn_blog cipherbliss_blog qzine_blog --output tk_combinaison_stats.html
stats-custom:
python3 generate_blog_stats.py tykayn_blog cipherbliss_blog qzine_blog --output tk_combinaison_stats.html --objectif-articles 10
python3 generate_blog_stats.py tykayn_blog cipherbliss_blog qzine_blog --output tk_combinaison_stats.html --objectif-articles 3
stats-file:
@if [ -z "$(FILE)" ]; then \
echo "Erreur: Veuillez spécifier un fichier avec FILE=chemin/vers/fichier.org"; \
echo ""; \
echo "Exemples:"; \
echo " make stats-file FILE=sources/tykayn_blog/lang_fr/2024-01-article.org"; \
echo " make stats-file FILE=sources/qzine_blog/lang_fr/20120114T183335__fetichisme-des-poignees-de-porte-zetes-prevenus.org"; \
echo ""; \
echo "Avec date de début d'écriture:"; \
echo " make stats-file FILE=sources/qzine_blog/lang_fr/20120114T183335__fetichisme-des-poignees-de-porte-zetes-prevenus.org DATE_DEBUT=2012-01-14"; \
exit 1; \
fi
@if [ -n "$(DATE_DEBUT)" ]; then \
python3 generate_blog_stats.py "$(FILE)" --date-debut "$(DATE_DEBUT)"; \
else \
python3 generate_blog_stats.py "$(FILE)"; \
fi
epub-tykayn:
python3 export_to_epub.py tykayn_blog

View file

@ -99,11 +99,26 @@ def extraire_date_du_fichier(filename):
return None
def compter_mots(contenu):
"""Compte le nombre de mots dans le contenu (sans les métadonnées)."""
# Utiliser find_extract_in_content_org pour nettoyer le contenu
contenu_clean = find_extract_in_content_org(contenu)
def nettoyer_contenu(contenu, est_markdown=False):
"""Nettoie le contenu en supprimant les métadonnées."""
if est_markdown:
# Supprimer le frontmatter YAML si présent
contenu = re.sub(r'^---\n.*?\n---\n', '', contenu, flags=re.DOTALL | re.MULTILINE)
return contenu.strip()
else:
# Utiliser find_extract_in_content_org pour nettoyer le contenu org
return find_extract_in_content_org(contenu)
def compter_mots(contenu, est_markdown=False):
"""Compte le nombre de mots dans le contenu (sans les métadonnées)."""
# Nettoyer le contenu
contenu_clean = nettoyer_contenu(contenu, est_markdown)
if est_markdown:
# Supprimer les liens markdown [texte](url) pour ne compter que le texte
contenu_clean = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', contenu_clean)
else:
# Supprimer les liens org-mode pour ne compter que le texte
contenu_clean = re.sub(r'\[\[([^\]]+)\]\[([^\]]+)\]\]', r'\2', contenu_clean)
contenu_clean = re.sub(r'\[\[([^\]]+)\]\]', r'\1', contenu_clean)
@ -113,28 +128,51 @@ def compter_mots(contenu):
return len([m for m in mots if len(m.strip()) > 0])
def compter_signes(contenu):
def compter_signes(contenu, est_markdown=False):
"""Compte le nombre de signes espaces compris dans le contenu."""
# Utiliser find_extract_in_content_org pour nettoyer le contenu
contenu_clean = find_extract_in_content_org(contenu)
# Nettoyer le contenu
contenu_clean = nettoyer_contenu(contenu, est_markdown)
return len(contenu_clean)
def compter_liens(contenu):
def compter_liens(contenu, est_markdown=False):
"""
Compte le nombre de liens dans le contenu (format [[url]] ou [[url][texte]]).
Compte le nombre de liens dans le contenu.
Pour org: format [[url]] ou [[url][texte]]
Pour markdown: format [texte](url) ou ![alt](url) pour images
Distingue les liens vers des images des autres liens.
Retourne un tuple (nb_liens_images, nb_liens_autres)
"""
# Compter les liens org-mode
liens = re.findall(r'\[\[([^\]]+)\](\[[^\]]+\])?\]', contenu)
nb_images = 0
nb_autres = 0
# Extensions d'images courantes
extensions_images = ['.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp', '.bmp', '.ico']
if est_markdown:
# Compter les liens markdown: [texte](url) ou ![alt](url)
liens_images = re.findall(r'!\[([^\]]*)\]\(([^\)]+)\)', contenu)
liens_autres = re.findall(r'(?<!\!)\[([^\]]+)\]\(([^\)]+)\)', contenu)
for url in [l[1] for l in liens_images]:
is_image = any(url.lower().endswith(ext) for ext in extensions_images) or \
'/image' in url.lower() or \
'img' in url.lower()
if is_image:
nb_images += 1
for url in [l[1] for l in liens_autres]:
is_image = any(url.lower().endswith(ext) for ext in extensions_images) or \
'/image' in url.lower() or \
'img' in url.lower()
if is_image:
nb_images += 1
else:
nb_autres += 1
else:
# Compter les liens org-mode
liens = re.findall(r'\[\[([^\]]+)\](\[[^\]]+\])?\]', contenu)
for lien_match in liens:
url = lien_match[0]
# Vérifier si c'est une image
@ -152,14 +190,29 @@ def compter_liens(contenu):
def analyser_article(filepath):
"""
Analyse un fichier article et retourne ses statistiques.
Analyse un fichier article (org ou markdown) et retourne ses statistiques.
"""
try:
with open(filepath, 'r', encoding='utf-8') as f:
contenu = f.read()
# Détecter le type de fichier
ext = os.path.splitext(filepath)[1].lower()
est_markdown = ext in ['.md', '.markdown']
# Extraire la date
date_pub = None
if not est_markdown:
date_pub = extraire_date_du_contenu(contenu)
else:
# Pour markdown, chercher dans le frontmatter YAML
match = re.search(r'^---\n.*?date:\s*(.+?)\n', contenu, re.DOTALL | re.MULTILINE)
if match:
try:
date_pub = datetime.strptime(match.group(1).strip(), '%Y-%m-%d')
except ValueError:
pass
if not date_pub:
date_pub = extraire_date_du_fichier(os.path.basename(filepath))
if not date_pub:
@ -167,9 +220,9 @@ def analyser_article(filepath):
date_pub = datetime.fromtimestamp(os.path.getmtime(filepath))
# Calculer les statistiques
nb_mots = compter_mots(contenu)
nb_signes = compter_signes(contenu)
nb_liens_images, nb_liens_autres = compter_liens(contenu)
nb_mots = compter_mots(contenu, est_markdown)
nb_signes = compter_signes(contenu, est_markdown)
nb_liens_images, nb_liens_autres = compter_liens(contenu, est_markdown)
# Temps de lecture (en minutes)
temps_lecture = nb_mots / LECTURE_MOTS_PAR_MINUTE if nb_mots > 0 else 0
@ -338,6 +391,9 @@ def calculer_nanowrimo_mois(mois_cle, stats_mois, aujourdhui, objectif_quotidien
else:
signes_par_jour_moyen = 0
# Récupérer les articles du mois (triés par date)
articles_du_mois = sorted(stats_mois.get('articles', []), key=lambda x: x['date'])
return {
'mois_cle': mois_cle,
'mois_formate': mois_date.strftime('%B %Y'),
@ -354,7 +410,8 @@ def calculer_nanowrimo_mois(mois_cle, stats_mois, aujourdhui, objectif_quotidien
'est_mois_futur': mois_date > aujourdhui,
'objectif_quotidien': objectif_quotidien_utilise,
'reste_a_faire': reste_a_faire,
'signes_par_jour_moyen': signes_par_jour_moyen
'signes_par_jour_moyen': signes_par_jour_moyen,
'articles_du_mois': articles_du_mois
}
@ -469,9 +526,23 @@ def generer_graphiques(blog_name, stats_par_mois, output_dir):
ax.set_title(f'Articles publiés par mois - {blog_name}', fontsize=14, fontweight='bold')
ax.legend(loc='upper left')
ax.grid(True, alpha=0.3, axis='y')
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=max(1, len(mois)//12)))
# Améliorer le formatage des dates pour éviter la superposition
nb_mois = len(mois)
if nb_mois > 24:
interval = max(3, nb_mois // 12)
format_str = '%Y'
elif nb_mois > 12:
interval = max(2, nb_mois // 12)
format_str = '%Y-%m'
else:
interval = 1
format_str = '%Y-%m'
ax.xaxis.set_major_formatter(mdates.DateFormatter(format_str))
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=interval))
plt.xticks(rotation=45, ha='right')
plt.setp(ax.xaxis.get_majorticklabels(), rotation=45, ha='right')
plt.tight_layout()
graph1_path = os.path.join(graph_dir, 'articles_par_mois.png')
plt.savefig(graph1_path, dpi=150, bbox_inches='tight')
@ -502,9 +573,23 @@ def generer_graphiques(blog_name, stats_par_mois, output_dir):
ax.set_title(f'Mots totaux par mois - {blog_name}', fontsize=14, fontweight='bold')
ax.legend(loc='upper left')
ax.grid(True, alpha=0.3)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=max(1, len(mois)//12)))
# Améliorer le formatage des dates pour éviter la superposition
nb_mois = len(mois)
if nb_mois > 24:
interval = max(3, nb_mois // 12)
format_str = '%Y'
elif nb_mois > 12:
interval = max(2, nb_mois // 12)
format_str = '%Y-%m'
else:
interval = 1
format_str = '%Y-%m'
ax.xaxis.set_major_formatter(mdates.DateFormatter(format_str))
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=interval))
plt.xticks(rotation=45, ha='right')
plt.setp(ax.xaxis.get_majorticklabels(), rotation=45, ha='right')
plt.tight_layout()
graph2_path = os.path.join(graph_dir, 'mots_par_mois.png')
plt.savefig(graph2_path, dpi=150, bbox_inches='tight')
@ -518,9 +603,23 @@ def generer_graphiques(blog_name, stats_par_mois, output_dir):
ax.set_ylabel('Temps de lecture (minutes)', fontsize=12)
ax.set_title(f'Temps de lecture par mois - {blog_name}', fontsize=14, fontweight='bold')
ax.grid(True, alpha=0.3, axis='y')
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=max(1, len(mois)//12)))
# Améliorer le formatage des dates pour éviter la superposition
nb_mois = len(mois)
if nb_mois > 24:
interval = max(3, nb_mois // 12)
format_str = '%Y'
elif nb_mois > 12:
interval = max(2, nb_mois // 12)
format_str = '%Y-%m'
else:
interval = 1
format_str = '%Y-%m'
ax.xaxis.set_major_formatter(mdates.DateFormatter(format_str))
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=interval))
plt.xticks(rotation=45, ha='right')
plt.setp(ax.xaxis.get_majorticklabels(), rotation=45, ha='right')
plt.tight_layout()
graph3_path = os.path.join(graph_dir, 'temps_lecture_par_mois.png')
plt.savefig(graph3_path, dpi=150, bbox_inches='tight')
@ -804,9 +903,22 @@ def generer_graphiques_combines(blogs_data, output_dir):
ax.set_title('Articles publiés par mois - Comparaison des blogs', fontsize=14, fontweight='bold')
ax.legend(loc='upper left')
ax.grid(True, alpha=0.3, axis='y')
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=max(1, len(mois_dates)//12)))
plt.xticks([mdates.date2num(m) for m in mois_dates], rotation=45, ha='right')
# Améliorer le formatage des dates pour éviter la superposition
nb_mois = len(mois_dates)
if nb_mois > 24:
interval = max(3, nb_mois // 12)
format_str = '%Y'
elif nb_mois > 12:
interval = max(2, nb_mois // 12)
format_str = '%Y-%m'
else:
interval = 1
format_str = '%Y-%m'
ax.xaxis.set_major_formatter(mdates.DateFormatter(format_str))
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=interval))
plt.setp(ax.xaxis.get_majorticklabels(), rotation=45, ha='right')
plt.tight_layout()
graph1_path = os.path.join(graph_dir, 'articles_par_mois_combines.png')
@ -857,9 +969,22 @@ def generer_graphiques_combines(blogs_data, output_dir):
ax.set_title('Mots publiés par mois - Comparaison des blogs', fontsize=14, fontweight='bold')
ax.legend(loc='upper left')
ax.grid(True, alpha=0.3, axis='y')
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=max(1, len(mois_dates)//12)))
plt.xticks([mdates.date2num(m) for m in mois_dates], rotation=45, ha='right')
# Améliorer le formatage des dates pour éviter la superposition
nb_mois = len(mois_dates)
if nb_mois > 24:
interval = max(3, nb_mois // 12)
format_str = '%Y'
elif nb_mois > 12:
interval = max(2, nb_mois // 12)
format_str = '%Y-%m'
else:
interval = 1
format_str = '%Y-%m'
ax.xaxis.set_major_formatter(mdates.DateFormatter(format_str))
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=interval))
plt.setp(ax.xaxis.get_majorticklabels(), rotation=45, ha='right')
plt.tight_layout()
graph2_path = os.path.join(graph_dir, 'mots_par_mois_combines.png')
@ -910,9 +1035,22 @@ def generer_graphiques_combines(blogs_data, output_dir):
ax.set_title('Signes publiés par mois - Comparaison des blogs', fontsize=14, fontweight='bold')
ax.legend(loc='upper left')
ax.grid(True, alpha=0.3, axis='y')
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m'))
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=max(1, len(mois_dates)//12)))
plt.xticks([mdates.date2num(m) for m in mois_dates], rotation=45, ha='right')
# Améliorer le formatage des dates pour éviter la superposition
nb_mois = len(mois_dates)
if nb_mois > 24:
interval = max(3, nb_mois // 12)
format_str = '%Y'
elif nb_mois > 12:
interval = max(2, nb_mois // 12)
format_str = '%Y-%m'
else:
interval = 1
format_str = '%Y-%m'
ax.xaxis.set_major_formatter(mdates.DateFormatter(format_str))
ax.xaxis.set_major_locator(mdates.MonthLocator(interval=interval))
plt.setp(ax.xaxis.get_majorticklabels(), rotation=45, ha='right')
plt.tight_layout()
graph3_path = os.path.join(graph_dir, 'signes_par_mois_combines.png')
@ -1052,17 +1190,197 @@ def generer_page_combinee(blogs_data, output_file, html_websites_dir, env, objec
print(f"\n✓ Page combinée générée: {html_path}")
def generer_statistiques_fichier(filepath, html_websites_dir, env, template, date_debut_ecriture=None, objectif_quotidien=None, objectif_mensuel=None):
"""
Génère les statistiques pour un seul fichier org ou markdown.
Si date_debut_ecriture est None, utilise le 1er du mois courant.
"""
import calendar
# Vérifier que le fichier existe
if not os.path.exists(filepath):
print(f"Erreur: Le fichier {filepath} n'existe pas")
return None
# Vérifier l'extension
ext = os.path.splitext(filepath)[1].lower()
if ext not in ['.org', '.md', '.markdown']:
print(f"Erreur: Le fichier doit être en .org, .md ou .markdown (reçu: {ext})")
return None
print(f"Analyse du fichier {filepath}...")
# Analyser le fichier
article = analyser_article(filepath)
if not article:
print(f"Impossible d'analyser le fichier {filepath}")
return None
# Déterminer la date de début d'écriture
aujourdhui = datetime.now()
if date_debut_ecriture is None:
# Par défaut : 1er du mois courant
date_debut_ecriture = aujourdhui.replace(day=1)
# Calculer les statistiques par jour, mois, année depuis la date de début
date_article = article['date']
if date_article < date_debut_ecriture:
date_debut_ecriture = date_article # Ajuster si l'article est plus ancien
# Calculer le nombre de jours/mois/années depuis le début
delta = aujourdhui - date_debut_ecriture
nb_jours_ecriture = max(1, delta.days + 1) # Au moins 1 jour
# Calculer les moyennes par jour, mois, année
articles_par_jour_moyen = 1 / nb_jours_ecriture
articles_par_mois_moyen = 1 / max(1, nb_jours_ecriture / 30)
articles_par_annee_moyen = 1 / max(1, nb_jours_ecriture / 365)
mots_par_jour_moyen = article['mots'] / nb_jours_ecriture
mots_par_mois_moyen = article['mots'] / max(1, nb_jours_ecriture / 30)
mots_par_annee_moyen = article['mots'] / max(1, nb_jours_ecriture / 365)
signes_par_jour_moyen = article['signes'] / nb_jours_ecriture
signes_par_mois_moyen = article['signes'] / max(1, nb_jours_ecriture / 30)
signes_par_annee_moyen = article['signes'] / max(1, nb_jours_ecriture / 365)
temps_lecture_par_jour_moyen = article['temps_lecture'] / nb_jours_ecriture
temps_lecture_par_mois_moyen = article['temps_lecture'] / max(1, nb_jours_ecriture / 30)
temps_lecture_par_annee_moyen = article['temps_lecture'] / max(1, nb_jours_ecriture / 365)
liens_par_jour_moyen = article['liens'] / nb_jours_ecriture
liens_par_mois_moyen = article['liens'] / max(1, nb_jours_ecriture / 30)
liens_par_annee_moyen = article['liens'] / max(1, nb_jours_ecriture / 365)
# Créer une structure de données compatible avec les templates
articles = [article]
stats_par_mois = calculer_statistiques_par_mois(articles)
# Calculer les stats NaNoWriMo pour le mois courant (depuis date_debut_ecriture)
mois_courant_cle = date_debut_ecriture.strftime('%Y-%m')
stats_mois = stats_par_mois.get(mois_courant_cle, {
'articles': articles,
'signes_total': article['signes'],
'nb_articles': 1,
'mots_total': article['mots'],
'liens_total': article['liens'],
'liens_images_total': article['liens_images'],
'liens_autres_total': article['liens_autres'],
'temps_lecture_total': article['temps_lecture']
})
# Calculer NaNoWriMo en considérant que l'écriture a commencé à date_debut_ecriture
stats_nanowrimo_mois = calculer_nanowrimo_mois(mois_courant_cle, stats_mois, aujourdhui, objectif_quotidien, objectif_mensuel)
# Ajuster jour_actuel pour refléter les jours depuis date_debut_ecriture
jours_ecoules = (aujourdhui - date_debut_ecriture).days + 1
stats_nanowrimo_mois['jour_actuel'] = min(jours_ecoules, stats_nanowrimo_mois['jours_dans_mois'])
# Recalculer objectif_jusqu_aujourdhui
stats_nanowrimo_mois['objectif_jusqu_aujourdhui'] = stats_nanowrimo_mois['objectif_quotidien'] * jours_ecoules
# Recalculer signes_par_jour_moyen
if jours_ecoules > 0:
stats_nanowrimo_mois['signes_par_jour_moyen'] = article['signes'] / jours_ecoules
else:
stats_nanowrimo_mois['signes_par_jour_moyen'] = 0
# Recalculer reste_a_faire
stats_nanowrimo_mois['reste_a_faire'] = max(0, stats_nanowrimo_mois['objectif_jusqu_aujourdhui'] - article['signes'])
# Ajouter les propriétés manquantes pour compatibilité avec le template
stats_nanowrimo_mois['objectif_articles'] = None # Pas d'objectif d'articles pour un fichier unique
stats_nanowrimo_mois['articles_realises'] = 1 # Un seul article (le fichier)
stats_nanowrimo = [stats_nanowrimo_mois]
# Créer le dossier de sortie
filename_base = os.path.splitext(os.path.basename(filepath))[0]
output_dir = os.path.join(html_websites_dir, 'fichiers_analyses')
os.makedirs(output_dir, exist_ok=True)
# Copier le fichier CSS
css_source = 'templates/styles/stats.css'
css_dest = os.path.join(output_dir, 'stats.css')
if os.path.exists(css_source):
shutil.copy2(css_source, css_dest)
# Calculer les statistiques globales
nb_articles_total = 1
mots_total = article['mots']
signes_total = article['signes']
liens_total = article['liens']
liens_images_total = article['liens_images']
liens_autres_total = article['liens_autres']
temps_lecture_total = article['temps_lecture']
# Générer le HTML
date_gen = datetime.now().strftime('%d/%m/%Y à %H:%M:%S')
date_article_str = date_article.strftime('%d/%m/%Y') if date_article else 'N/A'
html_content = template.render(
blog_title=f"Analyse: {os.path.basename(filepath)}",
author='',
nb_articles_total=nb_articles_total,
mots_total=mots_total,
signes_total=signes_total,
liens_total=liens_total,
liens_images_total=liens_images_total,
liens_autres_total=liens_autres_total,
temps_lecture_total=temps_lecture_total,
mots_moyen=mots_total,
liens_moyen=liens_total,
liens_images_moyen=liens_images_total,
liens_autres_moyen=liens_autres_total,
signes_moyen=signes_total,
temps_lecture_par_article=temps_lecture_total,
frequence=0,
premiere_date_str=date_article_str,
derniere_date_str=date_article_str,
stats_par_mois=stats_par_mois,
stats_nanowrimo=stats_nanowrimo,
graphiques=[],
date_gen=date_gen,
lecture_mots_par_minute=LECTURE_MOTS_PAR_MINUTE,
css_path='stats.css',
fichier_source=filepath,
date_article=date_article_str,
date_debut_ecriture=date_debut_ecriture.strftime('%d/%m/%Y'),
articles_par_jour_moyen=articles_par_jour_moyen,
articles_par_mois_moyen=articles_par_mois_moyen,
articles_par_annee_moyen=articles_par_annee_moyen,
mots_par_jour_moyen=mots_par_jour_moyen,
mots_par_mois_moyen=mots_par_mois_moyen,
mots_par_annee_moyen=mots_par_annee_moyen,
signes_par_jour_moyen=signes_par_jour_moyen,
signes_par_mois_moyen=signes_par_mois_moyen,
signes_par_annee_moyen=signes_par_annee_moyen,
temps_lecture_par_jour_moyen=temps_lecture_par_jour_moyen,
temps_lecture_par_mois_moyen=temps_lecture_par_mois_moyen,
temps_lecture_par_annee_moyen=temps_lecture_par_annee_moyen,
liens_par_jour_moyen=liens_par_jour_moyen,
liens_par_mois_moyen=liens_par_mois_moyen,
liens_par_annee_moyen=liens_par_annee_moyen,
nb_jours_avec_articles=1,
nb_mois_avec_articles=1,
nb_annees_avec_articles=1
)
# Sauvegarder le HTML
html_path = os.path.join(output_dir, f'{filename_base}_stats.html')
with open(html_path, 'w', encoding='utf-8') as f:
f.write(html_content)
print(f" ✓ Statistiques générées: {html_path}")
return html_path
def main():
"""
Fonction principale qui analyse les blogs et génère les pages de statistiques.
"""
parser = argparse.ArgumentParser(
description='Génère des statistiques détaillées sur les blogs'
description='Génère des statistiques détaillées sur les blogs ou un fichier unique'
)
parser.add_argument(
'blogs',
nargs='*',
help='Noms des blogs à analyser (si vide, analyse tous les blogs)'
help='Noms des blogs à analyser ou chemin vers un fichier .org/.md/.markdown (si vide, analyse tous les blogs)'
)
parser.add_argument(
'--output',
@ -1084,6 +1402,11 @@ def main():
type=int,
help='Objectif de signes par mois (prend le pas sur l\'objectif quotidien et NaNoWriMo)'
)
parser.add_argument(
'--date-debut',
type=str,
help='Date de début d\'écriture au format YYYY-MM-DD (pour l\'analyse d\'un fichier unique, défaut: 1er du mois courant)'
)
args = parser.parse_args()
@ -1099,6 +1422,29 @@ def main():
template = env.get_template('templates/html/stats.html.j2')
# Vérifier si un seul fichier est fourni
if args.blogs and len(args.blogs) == 1:
filepath = args.blogs[0]
# Vérifier si c'est un fichier (et pas un dossier de blog)
if os.path.isfile(filepath):
# Parser la date de début si fournie
date_debut_ecriture = None
if args.date_debut:
try:
date_debut_ecriture = datetime.strptime(args.date_debut, '%Y-%m-%d')
except ValueError:
print(f"Erreur: Format de date invalide pour --date-debut. Utilisez YYYY-MM-DD (ex: 2024-01-15)")
return
# Récupérer les objectifs de signes
objectif_quotidien = args.objectif_signes_quotidien
objectif_mensuel = args.objectif_signes_mensuel
generer_statistiques_fichier(filepath, html_websites_dir, env, template,
date_debut_ecriture, objectif_quotidien, objectif_mensuel)
print("Terminé!")
return
# Lister tous les dossiers de blogs si aucun n'est spécifié
if not os.path.exists(sources_dir):
print(f"Erreur: Le dossier {sources_dir} n'existe pas")

View file

@ -14,26 +14,66 @@
{% endif %}
<h2>Vue d'ensemble</h2>
{% if date_debut_ecriture %}
<p style="color: #666; margin-bottom: 15px;">
<strong>Date de début d'écriture:</strong> {{date_debut_ecriture}}
</p>
{% endif %}
<div class="stats-grid">
<div class="stat-card">
<h3>Articles publiés</h3>
<div class="value">{{nb_articles_total|int|format_number}}</div>
{% if articles_par_jour_moyen is defined %}
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">
Par jour: {{articles_par_jour_moyen|round(3)}} |
Par mois: {{articles_par_mois_moyen|round(2)}} |
Par année: {{articles_par_annee_moyen|round(2)}}
</div>
{% endif %}
</div>
<div class="stat-card blue">
<h3>Mots totaux</h3>
<div class="value">{{mots_total|int|format_number}}</div>
{% if mots_par_jour_moyen is defined %}
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">
Par jour: {{mots_par_jour_moyen|int|format_number}} |
Par mois: {{mots_par_mois_moyen|int|format_number}} |
Par année: {{mots_par_annee_moyen|int|format_number}}
</div>
{% endif %}
</div>
<div class="stat-card green">
<h3>Signes (espaces inclus)</h3>
<div class="value">{{signes_total|int|format_number}}</div>
{% if signes_par_jour_moyen is defined %}
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">
Par jour: {{signes_par_jour_moyen|int|format_number}} |
Par mois: {{signes_par_mois_moyen|int|format_number}} |
Par année: {{signes_par_annee_moyen|int|format_number}}
</div>
{% endif %}
</div>
<div class="stat-card orange">
<h3>Temps de lecture total</h3>
<div class="value">{{temps_lecture_total|format_duree}}</div>
{% if temps_lecture_par_jour_moyen is defined %}
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">
Par jour: {{temps_lecture_par_jour_moyen|format_duree}} |
Par mois: {{temps_lecture_par_mois_moyen|format_duree}} |
Par année: {{temps_lecture_par_annee_moyen|format_duree}}
</div>
{% endif %}
</div>
<div class="stat-card purple">
<h3>Liens totaux</h3>
<div class="value">{{liens_total|int|format_number}}</div>
{% if liens_par_jour_moyen is defined %}
<div style="font-size: 0.9em; color: #666; margin-top: 5px;">
Par jour: {{liens_par_jour_moyen|round(2)}} |
Par mois: {{liens_par_mois_moyen|round(1)}} |
Par année: {{liens_par_annee_moyen|round(1)}}
</div>
{% endif %}
</div>
</div>
@ -115,7 +155,12 @@
<p><strong>Objectif quotidien:</strong> 1667 signes (espaces compris) par jour</p>
{% for mois_stats in stats_nanowrimo %}
<div class="nanowrimo-month">
<h3>{{mois_stats.mois_formate}}</h3>
<h3>
{{mois_stats.mois_formate}}
{% if mois_stats.depassement > 0 %}
⭐ <span style="color: #FFC107; font-size: 0.9em;">+{{mois_stats.depassement|int|format_number}} signes</span>
{% endif %}
</h3>
{% if mois_stats.objectif_articles is not none %}
<div class="progress-info">
@ -198,6 +243,26 @@
(objectif jusqu'à aujourd'hui: {{mois_stats.objectif_jusqu_aujourdhui|int|format_number}} signes)
</div>
{% endif %}
{% if mois_stats.articles_du_mois %}
<div style="margin-top: 15px;">
<strong>Articles du mois ({{mois_stats.articles_du_mois|length}}):</strong>
<ul style="margin-top: 5px; padding-left: 20px;">
{% for article in mois_stats.articles_du_mois %}
<li>
<strong>{{article.date.strftime('%d/%m/%Y')}}</strong> - {{article.fichier}}
<span style="color: #666; font-size: 0.9em;">
({{article.mots|int|format_number}} mots, {{article.signes|int|format_number}} signes)
</span>
</li>
{% endfor %}
</ul>
</div>
{% else %}
<div style="margin-top: 15px; color: #999; font-style: italic;">
Aucun article ce mois
</div>
{% endif %}
</div>
{% endfor %}
</div>

View file

@ -118,7 +118,12 @@
{% endif %}
{% for mois_stats in stats_nanowrimo_combines %}
<div class="nanowrimo-month">
<h3>{{mois_stats.mois_formate}}</h3>
<h3>
{{mois_stats.mois_formate}}
{% if mois_stats.depassement > 0 %}
⭐ <span style="color: #FFC107; font-size: 0.9em;">+{{mois_stats.depassement|int|format_number}} signes</span>
{% endif %}
</h3>
<div class="progress-info">
<strong>Signes réalisés (tous blogs):</strong> {{mois_stats.signes_realises|int|format_number}}
@ -192,6 +197,26 @@
(objectif jusqu'à aujourd'hui: {{mois_stats.objectif_jusqu_aujourdhui|int|format_number}} signes)
</div>
{% endif %}
{% if mois_stats.articles_du_mois %}
<div style="margin-top: 15px;">
<strong>Articles du mois (tous blogs, {{mois_stats.articles_du_mois|length}}):</strong>
<ul style="margin-top: 5px; padding-left: 20px;">
{% for article in mois_stats.articles_du_mois %}
<li>
<strong>{{article.date.strftime('%d/%m/%Y')}}</strong> - {{article.fichier}}
<span style="color: #666; font-size: 0.9em;">
({{article.mots|int|format_number}} mots, {{article.signes|int|format_number}} signes)
</span>
</li>
{% endfor %}
</ul>
</div>
{% else %}
<div style="margin-top: 15px; color: #999; font-style: italic;">
Aucun article ce mois
</div>
{% endif %}
</div>
{% endfor %}
</div>
@ -286,7 +311,12 @@
<p><strong>Objectif quotidien:</strong> 1667 signes (espaces compris) par jour</p>
{% for mois_stats in blog_data.stats_nanowrimo %}
<div class="nanowrimo-month">
<h3>{{mois_stats.mois_formate}}</h3>
<h3>
{{mois_stats.mois_formate}}
{% if mois_stats.depassement > 0 %}
⭐ <span style="color: #FFC107; font-size: 0.9em;">+{{mois_stats.depassement|int|format_number}} signes</span>
{% endif %}
</h3>
{% if mois_stats.objectif_articles is not none %}
<div class="progress-info">
@ -369,6 +399,26 @@
(objectif jusqu'à aujourd'hui: {{mois_stats.objectif_jusqu_aujourdhui|int|format_number}} signes)
</div>
{% endif %}
{% if mois_stats.articles_du_mois %}
<div style="margin-top: 15px;">
<strong>Articles du mois ({{mois_stats.articles_du_mois|length}}):</strong>
<ul style="margin-top: 5px; padding-left: 20px;">
{% for article in mois_stats.articles_du_mois %}
<li>
<strong>{{article.date.strftime('%d/%m/%Y')}}</strong> - {{article.fichier}}
<span style="color: #666; font-size: 0.9em;">
({{article.mots|int|format_number}} mots, {{article.signes|int|format_number}} signes)
</span>
</li>
{% endfor %}
</ul>
</div>
{% else %}
<div style="margin-top: 15px; color: #999; font-style: italic;">
Aucun article ce mois
</div>
{% endif %}
</div>
{% endfor %}
</div>