Élie Gouzien

Élie Gouzien

#!/usr/bin/python3
# -*- coding: utf-8 -*-

"""
Ce script permet de construire les menus et certains éléments
variables (date,...) de ma (Élie Gouzien) page web.

La philosophie est de stoker les parties des fichiers html dans différents
fichiers et les assembler lors d'une compilation. Pour chaque fichier htmlc,
on charge le prototype trouvé dans le dossier parent le plus proche.
Puis on insère l'entête spécifique, les menus (trouvés dans le plus proche
dossier parent) et le contenu principal. Ensuite on inclut des fichiers
arbitraires supplémentaires puis on remplace les variables spéciales.

Extensions de fichiers réservées :
htmlc : contenu des pages (sans entetes ni menus, ...) ;
htmle : entête spécifique a la page htmlc de même nom ;
htmls : fichiers spéciaux : prototype.htmls est le prototype du site,
    menu1.htmls contient le menu principal, menu2.htmls contient un menu
    secondaire. Tout autre fichier htmls sert à être inséré à la balise
    correspondante sous forme <!--&nom_fichier&--> (insère nom_fichier.htmls)

Les fichiers .html et .htaccess sont traités en remplaçant simplement les
balises.

Balise pseudo-html :
Toute balise de type <!--€  €--> sont réservées. Il y a :
<!--€entete€--> : le fichier htmle de même nom que le fichier htmlc traite
    (et dans le même répertoire y est inséré).
<!--€menu1€--> : le fichier menu1.htmls trouve en remontant les répertoires
    est insère. Il est modifie pour ajouter class="actuel" au lien menant vers
    le fichier traite.
<!--€menu1€--> : idem menu1 mais pour le menu2
<!--€contenu€--> : insère le contenu du fichier actuellement traite.
Ces balises sont en principe utilisées uniquement dans prototype.htmls. Il y a
aussi :
<!--€date€--> : insère la date
<!--€racine€--> : insère la racine du site vue par le serveur http (en fait la
    varible python RACINE et rien d'autre).
Toute balise de type <!--&nom&--> se voit remplacée par le contenu du fichier
    nom.htmls trouve en premier dans la remontée des répertoires.
    Attention à ce que le fichier existe (aucune protection).

Attention au contenu des fichiers menu, le script n'est pas très fin et sera
vite perdu.

Numéro de version : 3.0

Crée le dimanche 8 décembre 2013
Mis à jour en mars 2019
@auteur: Élie Gouzien
Export vers html : dans vim ":TOhtml" puis séparation à la main du contenu et
des entêtes. Remplacement de "body" par "#contenu" et agrandissement de
"max-width".
"""

import os
import shutil
import datetime
import locale

# chemin de la racine pour le serveur web
# (comprendre chemin relatif de l'URL de la page d'accueil)
RACINE = "/~gouzien"  # phare

# Cas ou le script est lance depuis le dossier contenant conception_semiauto.
ORIGINAL = os.path.abspath("conception_semiauto")
COPIE = os.path.abspath("site_semiauto")


def tester_fichiers_necessaires():
    """Vérifie la présence des fichiers essentiels, ne fait rien s'ils y sont.

    Test la présence de menu1.htmls, menu2.htmls et prototype.htmls
    dans RACINE. Déclenche des erreurs en cas d'absence.
    A priori le site est non vide donc ils doivent exister !
    Cela prévient les recherches vaines de ces fichiers.
    L'utilité de cette fonction dépend des choix faits sur
    plus_proches_parents. Elle ne peut pas faire de mal.
    """
    os.chdir(ORIGINAL)

    if not os.access("prototype.htmls", os.R_OK):
        raise IOError("Erreur il manque le fichier prototype.htmls")
    if not os.access("menu1.htmls", os.R_OK):
        raise IOError("Erreur il manque le fichier menu1.htmls")
    if not os.access("menu2.htmls", os.R_OK):
        raise IOError("Erreur il manque le fichier menu2.htmls")


def plus_proche_parent(chemin_absolu, fichier_cherche):
    """Trouve le fichier_cherche dans le plus proche parent de l'emplacement.

    Les arguments et la valeur renvoyée sont de type str.
    Renvoie le chemin complet de fichier_cherche solution du problème.
    """
    # HINT : essayer d'indexer directement ce qu'on va chercher (non trivial)
    fichier_cherche = os.path.basename(fichier_cherche)
    if not os.path.isdir(chemin_absolu):
        chemin_absolu = os.path.dirname(chemin_absolu)
    os.chdir(chemin_absolu)

    while True:
        if os.access(fichier_cherche, os.R_OK):
            return os.path.abspath(fichier_cherche)
        os.chdir("..")


def etendre_chemin_dans_menu(menu, chemin):
    """Étend la variable <!--€racine€--> et repère la position de la page.

    menu : chaine contenant tout le menu avec RACINE non étendue
    chemin : chemin du fichier relatif (a la racine du site) dans lequel on
        insère le menu
    sortie : chaine contenant le menu directement utilisable.

    La fonction remplace <!--€racine€--> par RACINE et ajoute class="actuel"
    dans l'entre de menu permettant d'accéder a la page en question.
    """
    position = menu.find("<!--€racine€-->/") + 16
    while position != 15:
        # adresse : chemin repéré dans le menu car <!--€racine€--> le précède
        # et /" le fini
        addresse = menu[position:position + menu[position:].find("/\"")]

        # A priori il faudrait utiliser posixpath plutôt que os.path
        # en pratique posixpath ne fonctionne pas sous Windows et on ne fait
        # qu'une comparaison : seule la cohérence entre les modèles suffit
        if os.path.normpath(chemin).startswith(os.path.normpath(addresse)):
            menu = menu.replace("href=\"<!--€racine€-->",
                                "class=\"actuel\" href=\"<!--€racine€-->", 1)
        menu = menu.replace("<!--€racine€-->", RACINE, 1)
        position = menu.find("<!--€racine€-->") + 16
    return menu


# Début du script

tester_fichiers_necessaires()

# supprime COPIE (si existe)
if os.access(COPIE, os.F_OK):
    shutil.rmtree(COPIE)

# Remarque pour la suite ; utiliser communprefix
# En cas de recodage de plus_proches_parents, il peut être utile de créer ici
# une liste exhaustive des fichiers ".hmls".

for emplacement, dossiers, fichiers in os.walk(ORIGINAL):  # Boucle principale
    emplacement = os.path.relpath(emplacement, ORIGINAL)  # Emplacement relatif
    for dossier in dossiers:  # Construction de l'arborescence
        os.makedirs(os.path.join(COPIE, emplacement, dossier))

    for fichier in fichiers:
        nom_nu, extension = os.path.splitext(fichier)

        # On passe les fichiers de constructions et de sauvegardes
        if extension in [".htmls", ".htmle"] or fichier[-1] in ['~', '#']:
            continue
        # On copie touts les fichiers normaux
        elif extension not in [".htmlc", ".html"] and fichier != ".htaccess":
            shutil.copyfile(os.path.join(ORIGINAL, emplacement, fichier),
                            os.path.join(COPIE, emplacement, fichier))
            continue

# ### Plus que les fichiers .htmlc, .html et .htacess à traiter ####

    # ## Chargement du contenu dans texte_html ###
        # #  Cas de fichier .htmlc : utiliser le modèle ##
        elif extension == ".htmlc":
            nom_sortie = nom_nu + '.html'
            with open(plus_proche_parent(os.path.join(ORIGINAL, emplacement),
                                         "prototype.htmls")) as source:
                texte_html = source.read()
            # Chargement des entêtes spécifiques
            with open(os.path.join(ORIGINAL, emplacement,
                                   nom_nu + ".htmle")) as source:
                contenu = source.read()
            texte_html = texte_html.replace("<!--€entete€-->", contenu)
            # HINT : pas de gestion d'indentation pour l'instant (idem suite)
            # Chargement du contenu principal (fichier htmlc)
            with open(os.path.join(ORIGINAL, emplacement, fichier)) as source:
                contenu = source.read()
            texte_html = texte_html.replace("<!--€contenu€-->", contenu)

        # # Cas de fichier .html ou .htaccess : le charger directement
        elif extension == ".html" or fichier == ".htaccess":
            nom_sortie = fichier
            with open(os.path.join(ORIGINAL, emplacement, fichier)) as source:
                texte_html = source.read()
        else:
            raise RuntimeError(
                "Le fichier {}/{} n'est rien de connu.".format(emplacement,
                                                               fichier))

    # ## Chargement des menus ###
        # menu1
        with open(plus_proche_parent(os.path.join(ORIGINAL, emplacement),
                                     "menu1.htmls"), 'r') as source:
            contenu = source.read()
        contenu = etendre_chemin_dans_menu(contenu, emplacement)
        texte_html = texte_html.replace("<!--€menu1€-->", contenu)

        # menu2
        with open(plus_proche_parent(os.path.join(ORIGINAL, emplacement),
                                     "menu2.htmls")) as source:
            contenu = source.read()
        contenu = etendre_chemin_dans_menu(contenu, emplacement)
        texte_html = texte_html.replace("<!--€menu2€-->", contenu)

        # # Boucle générique pour les inclusions de fichiers & & ##
        while -1 not in [texte_html.find("<!--&"), texte_html.find("&-->")]:
            nom_a_charger = texte_html[texte_html.find("<!--&") + 5:
                                       texte_html.find("&-->")]
            with open(plus_proche_parent(os.path.join(ORIGINAL, emplacement),
                                         nom_a_charger + ".htmls")) as source:
                contenu = source.read()
            texte_html = texte_html.replace("<!--&" + nom_a_charger + "&-->",
                                            contenu)

        # # Traitement des variables spéciales ##
        # date
        locale.setlocale(locale.LC_TIME, '')
        date = datetime.date.today().strftime("%A %d %B %Y")
        texte_html = texte_html.replace("<!--€date€-->", date)

        # racine
        texte_html = texte_html.replace("<!--€racine€-->", RACINE)

        # Écriture du fichier html de sortie
        with open(os.path.join(COPIE, emplacement, nom_sortie), 'w') as sortie:
            sortie.write(texte_html)

# Modifie la page d'accueil pour qu'elle soit référencée comme modifiée en
# dernier.
os.chdir(COPIE)
with open('index.html', 'a'):
    pass