Compare commits

...

2 Commits

11 changed files with 222 additions and 31 deletions

View File

@ -34,11 +34,14 @@ Afin d'avoir une structure qui aura visuellement du sens pour l'autrice du livre
Votre livre peut contenir des médias, nous vous invitons à les placer dans le dossier "assets". Votre livre peut contenir des médias, nous vous invitons à les placer dans le dossier "assets".
Le dossier "inspirations" est destiné à avoir des images, des médias, des documents divers, une bibliographie, c'est toujours utile de référencer ses inspirations pour clarifier ce que l'on aimerait raconter. Le dossier "inspirations" est destiné à avoir des images, des médias, des documents divers, une bibliographie, c'est toujours utile de référencer ses inspirations pour clarifier ce que l'on aimerait raconter.
## Personnages ## Personnages
Donnez des alias à vos personnages dans la ligne prévue à cet effet afin de comptabiliser leurs mentions dans le script find_characters_in_book.py
## Intrigues ## Intrigues
Les intrigues sont des arcs narratifs qui peuvent se superposer dans votre histoire. Les intrigues sont des arcs narratifs qui peuvent se superposer dans votre histoire.
Pour avoir cette vision des superpositions, le script `make_intrigues_to_csv.py` liste les entêtes et recherche si elles contiennent deux nombres séparés par un tiret. Pour avoir cette vision des superpositions, le script `make_intrigues_to_csv.py` liste les entêtes et recherche si elles contiennent deux nombres séparés par un tiret.
Par exemple : Par exemple :
** l'intrigue bidule chose 4-9 `** l'intrigue bidule chose 4-9`
Indique que l'on souhaite que cette intrigue débute dans la partie 4 et se termine dans la partie 9. Indique que l'on souhaite que cette intrigue débute dans la partie 4 et se termine dans la partie 9.
Sans information de numérotation, on part du principe qu'une intrigue dure 1 partie de l'histoire, dans l'ordre des intrigues. Sans information de numérotation, on part du principe qu'une intrigue dure 1 partie de l'histoire, dans l'ordre des intrigues.
@ -52,7 +55,8 @@ Permettent de transformer votre livre en produit distribuable: ebook, html, pdf,
### Génération de plan de livre ### Génération de plan de livre
`python structure_generator.py` `python structure_generator.py`
Génère un plan de chapitres selon les nombres de chapitres, de sous parties, et d'objectif de mots par section donnés. Génère un plan de chapitres selon les nombres de chapitres, de sous parties, et d'objectif de mots par section donnés.
Il ne reste plus qu'à copier le texte donné dans livre.org Il ne reste plus qu'à copier le texte donné dans livre.org ou a utliser la sortie du script pour écrire dans un fichier.
### Conversion du livre ### Conversion du livre
Conversion en epub, html, et pdf grâce à pandoc. Conversion en epub, html, et pdf grâce à pandoc.
@ -77,14 +81,21 @@ Vous pouvez modifier cet objectif dans `stats_chapitres.py` puis lancer la mise
Un tag ajouté aux entêtes de chapitre permet de définir des objectifs de mots. Un tag ajouté aux entêtes de chapitre permet de définir des objectifs de mots.
:target_500: définit une cible à 500 mots, :target_1200: défniit la cible à 1200. Cela permettra au générateur de statistiques d'affiner son avancée plus finement. Ce sont des indicateurs, dans la réalité les auteurs écrivent leurs chapitres avec des volumes très variables. :target_500: définit une cible à 500 mots, :target_1200: défniit la cible à 1200. Cela permettra au générateur de statistiques d'affiner son avancée plus finement. Ce sont des indicateurs, dans la réalité les auteurs écrivent leurs chapitres avec des volumes très variables.
## Suivi de progression de la rédaction ## Suivi de progression de la rédaction
Il est envisagé que chaque génération de mise à jour des statistiques remplisse un fichier csv de suivi daté afin de pouvoir voir sa progression quotidienne. Il est envisagé que chaque génération de mise à jour des statistiques remplisse un fichier csv de suivi daté afin de pouvoir voir sa progression quotidienne.
La génération de données statistiques peut être incluse dans une tâche cron pour ne pas avoir à faire de lancement de commande tous les jours. La génération de données statistiques peut être incluse dans une tâche cron pour ne pas avoir à faire de lancement de commande tous les jours.
Exemple de cronjob pour lancer le suivi toutes les heures, adaptez le chemin du script dans le dossier du livre concerné:
`0 * * * * /usr/bin/python3 /home/user/book_generator/mon_livre_exemple/follow_progress.py`
Ceci alimente un fichier csv de suivi des évolutions et présente les changements de mots du jour, ainsi que depuis la semaine dernière.
Le CSV contient les décomptes de mots pour livre.org, personnages.org, le nombre de personnages, de chapitres, et de sous chapitres.
# Licence # Licence
AGPLv3+ AGPLv3+
# Contacts: # Contacts:
contact+book_generator@cipherbliss.com contact+book_generator@cipherbliss.com
@tykayn@mastodon.cipherbliss.com @tykayn@mastodon.cipherbliss.com

View File

@ -21,14 +21,18 @@
** Préface :title: ** Préface :title:
** Introduction :title: ** Introduction :title:
** Chapitre 1 :title: ** Chapitre 1 :title:
présentation de bob.
*** Chapitre 1 - Partie 1 *** Chapitre 1 - Partie 1
*** Chapitre 1 - Partie 2 *** Chapitre 1 - Partie 2
*** Chapitre 1 - Partie 3 *** Chapitre 1 - Partie 3
** Chapitre 2 :title: ** Chapitre 2 :title:
présentation de chuck norris.
présentation de bobette.
*** Chapitre 2 - Partie 1 *** Chapitre 2 - Partie 1
*** Chapitre 2 - Partie 2 *** Chapitre 2 - Partie 2
*** Chapitre 2 - Partie 3 *** Chapitre 2 - Partie 3
** Chapitre 3 :title: ** Chapitre 3 :title:
bob et bobette sont bien plus stylés que le roux.
*** Chapitre 3 - Partie 1 *** Chapitre 3 - Partie 1
*** Chapitre 3 - Partie 2 *** Chapitre 3 - Partie 2
*** Chapitre 3 - Partie 3 *** Chapitre 3 - Partie 3

View File

@ -10,15 +10,18 @@
- objectifs: - objectifs:
- conflits: - conflits:
- évolution: - évolution:
- alias: Bob l'éponge, SpongeBob
** chuck norris ** chuck norris
- nom: - nom:
- personnalité: - personnalité:
- objectifs: - objectifs:
- conflits: - conflits:
- évolution: - évolution:
- alias: le roux; celui dont on ne doit pas prononcer le nom
** bobette ** bobette
- nom: - nom:
- personnalité: - personnalité:
- objectifs: - objectifs:
- conflits: - conflits:
- évolution: - évolution:
- alias:

1
config/books_path.txt Normal file
View File

@ -0,0 +1 @@

View File

@ -4,38 +4,68 @@
# les personnages que l'on recherche dans le livre ne sont pas mentionnés dans la ligne d'entête; mais dans les lignes entre deux entêtes, dans les corps de texte # les personnages que l'on recherche dans le livre ne sont pas mentionnés dans la ligne d'entête; mais dans les lignes entre deux entêtes, dans les corps de texte
import csv import csv
import re import re
import argparse
import os
# Ajouter un argument pour le chemin du dossier contenant le fichier livre.org
parser = argparse.ArgumentParser(description='Rechercher les occurrences de personnages dans un fichier Org-mode.')
parser.add_argument('dossier', nargs='?', help='Le chemin du dossier contenant le fichier livre.org. Si aucun dossier n\'est spécifié, le dossier courant sera utilisé.', default=os.getcwd())
args = parser.parse_args()
# Concaténer le chemin du dossier et le nom du fichier livre.org
fichier_livre = f"{args.dossier}/livre.org"
# Remplacer par les chemins vers les fichiers Org-mode # Remplacer par les chemins vers les fichiers Org-mode
fichier_personnages = 'personnages.org' fichier_personnages = f"{args.dossier}personnages.org"
fichier_livre = 'livre.org'
# Expressions régulières pour extraire les noms des personnages et les titres des chapitres # Expressions régulières pour extraire les noms des personnages, les alias et les titres des chapitres
regex_personnage = r"\*\* (.*)" regex_personnage = r"\*\* (.*)"
regex_alias = r"\s*- alias:\s*(.*?)\s*$"
regex_chapitre = r'\*\* (.+)' regex_chapitre = r'\*\* (.+)'
# Dictionnaire pour stocker les occurrences de personnages dans chaque chapitre # Dictionnaire pour stocker les occurrences de personnages dans chaque chapitre
occurrences_personnages = {} occurrences_personnages = {}
alias_separator=";"
# Ouvrir le fichier personnages.org et extraire les noms des personnages def extract_character_info(character_line):
with open(fichier_personnages, 'r', encoding='utf-8') as fichier_personnages: match = re.search(regex_personnage, character_line)
personnages = [re.sub( "\*\* ","",ligne.strip()) for ligne in fichier_personnages if re.match(regex_personnage, ligne)] if match:
character = match.group(1)
aliases = []
print('personnages: ', personnages) # Extraire les alias du caractère
match_alias = re.search(regex_alias, character_line)
if match_alias:
aliases = [alias.strip() for alias in match_alias.group(1).split(alias_separator)]
return character, aliases
return None, []
# Ouvrir le fichier personnages.org et extraire les noms des personnages et leurs alias
def contains_any_of_these_words(line: str, words: list[str]) -> bool: def contains_any_of_these_words(line: str, words: list[str]) -> bool:
for word in words: for word in words:
if word in line: if word in line:
return True return True
return False return False
with open(fichier_personnages, 'r', encoding='utf-8') as fichier_personnages:
personnages = {}
for ligne in fichier_personnages:
character, aliases = extract_character_info(ligne)
if character:
personnages[character] = aliases
print('personnages: ', personnages.keys())
# Ouvrir le fichier livre.org et le fichier CSV # Ouvrir le fichier livre.org et le fichier CSV
with open(fichier_livre, 'r', encoding='utf-8') as livre, open('occurrences_personnages.csv', 'w', newline='', encoding='utf-8') as fichier_csv: with open(fichier_livre, 'r', encoding='utf-8') as livre, open(f"{args.dossier}/occurrences_personnages.csv", 'w', newline='', encoding='utf-8') as fichier_csv:
content = livre.read() content = livre.read()
csv_writer = csv.writer(fichier_csv) csv_writer = csv.writer(fichier_csv)
# Écrire les en-têtes dans le fichier CSV # Écrire les en-têtes dans le fichier CSV
csv_writer.writerow(['Chapitre'] + personnages) csv_writer.writerow(['Chapitre'] + list(personnages.keys()))
occurrences_chapitre = {personnage: 0 for personnage in personnages} occurrences_chapitre = {personnage: 0 for personnage in personnages.keys()}
chapitre = '(chapitre not found)' chapitre = '(chapitre not found)'
# Parcourir chaque ligne du fichier livre.org # Parcourir chaque ligne du fichier livre.org
@ -47,17 +77,17 @@ with open(fichier_livre, 'r', encoding='utf-8') as livre, open('occurrences_pers
chapitre = re.sub( ":title:", "", chapitre) chapitre = re.sub( ":title:", "", chapitre)
print(chapitre) print(chapitre)
# Initialiser le dictionnaire d'occurrences pour chaque chapitre # Initialiser le dictionnaire d'occurrences pour chaque chapitre
occurrences_chapitre = {personnage: 0 for personnage in personnages} occurrences_chapitre = {personnage: 0 for personnage in personnages.keys()}
# Parcourir chaque personnage et rechercher son nom dans la ligne # Parcourir chaque personnage et rechercher son nom ou ses alias dans la ligne
for personnage in personnages: for personnage, aliases in personnages.items():
if personnage.lower() in ligne.lower() or any(alias.lower() in ligne.lower() for alias in aliases):
if personnage in ligne: occurrences_chapitre[personnage] += 1
occurrences_chapitre[personnage] += 1 print(chapitre,' - ',personnage,' : ', ligne)
print(chapitre,' - ',personnage,' : ', ligne)
# Ajouter les occurrences du chapitre au dictionnaire global # Ajouter les occurrences du chapitre au dictionnaire global
occurrences_personnages[chapitre] = occurrences_chapitre occurrences_personnages[chapitre] = occurrences_chapitre
# Écrire les occurrences des personnages dans le fichier CSV # Écrire les occurrences des personnages dans le fichier CSV
for chapitre, occurrences in occurrences_personnages.items(): for chapitre, occurrences in occurrences_personnages.items():
csv_writer.writerow([chapitre] + [occurrences[personnage] for personnage in personnages]) csv_writer.writerow([chapitre] + [occurrences[personnage] for personnage in personnages.keys()])

102
follow_progress.py Executable file
View File

@ -0,0 +1,102 @@
# met à jour un fichier de suivi pour chaque livre
# le suivi se fait dans un fichier csv où chaque ligne est un compte rendu de statistiques du livre
# colonnes du csv: date; mots; intrigues; personnages; personnages_mots;
import csv
from datetime import date, timedelta, datetime
def mise_a_jour_suivi(fichier_csv, fichier_livre, fichier_personnages):
# Lire le fichier livre.org
with open(fichier_livre, 'r') as f:
contenu_livre = f.read()
mots_livre = len(contenu_livre.split())
chapitres = contenu_livre.count('* ')
sous_chapitres = contenu_livre.count('** ') + contenu_livre.count('*** ') + contenu_livre.count('**** ')
# Lire le fichier personnages.org
with open(fichier_personnages, 'r') as f:
contenu_personnages = f.read()
personnages_mots = len(contenu_personnages.split())
personnages = contenu_personnages.count('* ')
# Récupérer les valeurs pour les autres colonnes
intrigues = 5
# Mettre à jour le fichier de suivi
with open(fichier_csv, 'a', newline='') as csvfile:
writer = csv.writer(csvfile, delimiter=';')
now = datetime.now()
ligne = [now.isoformat(), mots_livre, intrigues, personnages, personnages_mots, chapitres, sous_chapitres]
writer.writerow(ligne)
# Exemple d'utilisation
fichier_csv ='suivi_livre.csv'
fichier_livre = 'livre.org'
fichier_personnages = 'personnages.org'
def analyse_csv(fichier_csv):
with open(fichier_csv, 'r') as csvfile:
reader = csv.reader(csvfile, delimiter=';')
donnees = list(reader)
# Récupérer les dates et les nombres de mots
dates = [datetime.fromisoformat(donnee[0]).date() for donnee in donnees]
mots = [int(donnee[1]) for donnee in donnees]
# Récupérer la date du jour
aujourd_hui = date.today()
oldest_count=0
hier = aujourd_hui - timedelta(days=1)
semaine_derniere = aujourd_hui - timedelta(days=7)
mois_dernier = date(aujourd_hui.year, aujourd_hui.month - 1, aujourd_hui.day)
# Récupérer la valeur des mots la plus récente parmi celles de la date du jour
mots_aujourd_hui = 0
mots_aujourd_hui_start = 0
most_recent = 0
# print("suivis: ",len(dates))
most_recent = mots[len(dates)-1]
for i, date_ in enumerate(dates):
# trouver le premier décompte supérieur à 0
if mots[i] > 0 and oldest_count == 0:
oldest_count = mots[i]
# print('oldest: ',oldest_count, " mots")
if date_ == aujourd_hui:
if mots_aujourd_hui_start == 0:
mots_aujourd_hui_start = mots[i]
print('au début du jour:' , mots_aujourd_hui_start)
if date_ == aujourd_hui:
mots_aujourd_hui = mots[i]
# Récupérer la valeur des mots la plus récente du jour précédent
mots_hier = 0
for i, date_ in enumerate(dates):
if date_ == hier:
mots_hier = mots[i]
break
# Récupérer la valeur des mots la plus récente de la semaine dernière
mots_semaine_derniere = 0
for i, date_ in enumerate(dates):
if semaine_derniere <= date_ < aujourd_hui:
mots_semaine_derniere = mots[i]
break
# Compter le nombre de mots changés aujourd'hui
mots_changés_aujourd_hui = most_recent - mots_aujourd_hui_start
# Compter le nombre de mots changés depuis la date la plus récente dans la semaine dernière
mots_changés_semaine_derniere = mots_aujourd_hui_start - mots_semaine_derniere
# Afficher le résultat
print("Nombres de mots changés aujourd'hui : ", mots_changés_aujourd_hui)
print("Nombres de mots changés depuis la date la plus récente dans la semaine dernière : ", mots_changés_semaine_derniere)
print("Total : ", most_recent," mots")
mise_a_jour_suivi(fichier_csv, fichier_livre, fichier_personnages)
analyse_csv(fichier_csv)

View File

@ -10,6 +10,22 @@
extension="org" # ou md extension="org" # ou md
fi fi
add_path_to_config() {
local path=$1
local config_file="config/books_path.txt"
if [ -f "$config_file" ]; then
if grep -q "^${path}$" "$config_file"; then
echo "Le chemin '${path}' existe déjà dans le fichier de configuration."
else
echo "$(pwd)/$path" >> "$config_file"
echo "Le chemin '${path}' a été ajouté au fichier de configuration."
fi
else
echo "Le fichier de configuration '${config_file}' n'existe pas."
touch $config_file
fi
}
function generate_uuid() { function generate_uuid() {
uuid=$(cat /proc/sys/kernel/random/uuid) uuid=$(cat /proc/sys/kernel/random/uuid)
echo "$uuid"; echo "$uuid";
@ -56,5 +72,8 @@
echo $nom_du_livre | xargs -I{} sed -i 's|BOOK_TITLE|{}|g' $nom_du_livre/taches_$nom_du_livre.org echo $nom_du_livre | xargs -I{} sed -i 's|BOOK_TITLE|{}|g' $nom_du_livre/taches_$nom_du_livre.org
exa -l $nom_du_livre exa -l $nom_du_livre
add_path_to_config $nom_du_livre
echo "fichiers du livre $nom_du_livre créé" echo "fichiers du livre $nom_du_livre créé"
exit 0 exit 0

View File

@ -1,4 +1,5 @@
#!/bin/bash #!/bin/bash
# sauvegarde de tous les changements dans le dossier actuel # ajout du décompte des informations pour chaque livre
#sauvegarde de tous les changements dans le dossier actuel
git add . git add .
git commit -m ":zap: - sauvegarde automatique de l'avancement du livre" git commit -m ":zap: - sauvegarde automatique de l'avancement du livre"

View File

@ -1,5 +1,17 @@
# python script.py --number_chapters 7 --number_parts 2 --objective_words 600 --objective_chapter 1800 #####################
# génère une structure à copier dans un nouveau fichier livre.org
# ce script ne remplit pas directement un fichier pour éviter les écrasements trop soudains.
# Exemple d'exécution:
#
# python structure_generator.py --number_chapters 7 --number_parts 2 --objective_words 600 --objective_chapter 1800
#
#####################
#
# Si vous voulez créer directement un fichier livre sans le reste de ce que fabriquerait generate_book.sh:
#
# python structure_generator.py > mon_livre_généré.org
#
#####################
import argparse import argparse
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()

View File

@ -5,3 +5,4 @@ python3 find_characters_in_book.py
python3 stats_chapitres.py python3 stats_chapitres.py
python3 make_intrigues_to_csv.py python3 make_intrigues_to_csv.py
python3 gantt_parser.py python3 gantt_parser.py
python3 follow_progress.py

View File

@ -8,20 +8,27 @@ echo "mise à jour du dossier $1 sans modifier les contenus Org"
dossier=$1 dossier=$1
cp Makefile "$dossier/"
if ! [ -d $dossier ]; then if ! [ -d $dossier ]; then
echo "le dossier $dossier n'existe pas, oh noes!" echo "le dossier $dossier n'existe pas, oh noes! ajoutez le en argument à ce script: \n bash update_ebook.sh /$USER/Nextcloud/textes/livre_bidule"
else else
# on supprime les scripts existants
rm "$dossier/*.py"
rm "$dossier/*.sh"
rm "$dossier/*.css"
# on met les nouvelles versions
cp Makefile "$dossier/"
cp *.py "$dossier/" cp *.py "$dossier/"
cp *.sh "$dossier/" cp *.sh "$dossier/"
cp *.css "$dossier/" cp *.css "$dossier/"
cp README.md "$dossier/" cp README.md "$dossier/"
cp LICENSE "$dossier/" cp LICENSE "$dossier/"
if ! [ -d $dossier/.git ]; then if ! [ -d $dossier/.git ]; then
cd $dossier cd $dossier
git init git init
fi fi
fi fi
echo "dossier $dossier mis à jour" echo "dossier $dossier mis à jour"