Erminig/src/erminig/common/pakfile.py

225 lines
7.2 KiB
Python
Raw Normal View History

#
# Erminig - Librairie Pakfile
# Copyright (C) 2025 L0m1g
# Sous licence DOUARN - Voir le fichier LICENCE pour les détails
#
# Ce fichier fait partie du projet Erminig.
# Libre comme lair, stable comme un menhir, et salé comme le beurre.
#
import os
import toml
from .db import get_db_connection
class Pakfile:
"""
Représente un fichier de description de paquet (pakfile.toml)
dans le système de gestion de paquets Erminig.
Cette classe offre les opérations essentielles pour manipuler,
vérifier et modifier les pakfiles.
"""
BASE_PATH = "/var/govel"
def __init__(self, package: str):
"""
Initialise la gestion du pakfile pour un paquet donné.
:param package: Nom du paquet
"""
self.package = package
self.data = {}
def _load(self):
"""
Charge le contenu du pakfile en mémoire
Appelé automatiquement à l'initialisation si le fichier existe
"""
package_dir, pakfile_path = self._get_paths(package)
if not os.path.exists(pakfile_path):
raise FileNotFoundError(f"Pakfile introuvable pour {self.package} dans {pakfile_path}")
with open(pakfile_path, "r", encoding="utf-8") as f:
self.data = toml.load(f)
def _get_paths(self, package: str) -> tuple[str, str]:
"""
Retourne le chemin du dossier du paquet et du pakfile associé.
"""
package_dir = f"{self.BASE_PATH}/{package}"
pakfile_path = f"{package_dir}/pakfile.toml"
return package_dir, pakfile_path
def new(self, package: str):
"""
Crée un nouveau pakfile avec un squelette de base prêt à être édité.
Soulève une erreur si le pakfile existe déjà.
"""
package_dir, pakfile_path = self._get_paths(package)
if os.path.exists(pakfile_path):
raise FileExistsError(f"Le pakfile existe déjà pour {package}")
data = {
"header": {
"copyright": "Copyright (C) 2025 L0m1g",
"license": "Sous licence DOUARN - Voir le fichier LICENCE pour les détails",
"author": "L0m1g",
"maintainer": "L0m1g",
"description": "Description à compléter"
},
"name": package,
"ver": "",
"rev": 1,
"src": [],
"build": "# Ajouter les commandes de construction ici",
"check": "# Ajouter les commandes check ou équivalentes ici",
"install": "# Ajouter les commandes dinstallation ici"
}
os.makedirs(package_dir, exist_ok=True)
with open(pakfile_path, "w", encoding="utf-8") as f:
toml.dump(data, f)
self.update_db()
def set(self, key: str, value):
"""
Définit une valeur dans le pakfile et sauvegarde immédiatement
"""
self.data[key] = value
self._save()
self.update_db()
def _save(self):
"""
Ecrit le contenu actuel de self.data dans le pakfile correspondant
Créé le répertoire s'il n'existe pas.
"""
package_dir, pakfile_path = self._get_paths(self.package)
os.makedirs(package_dir, exist_ok=True)
with open(pakfile_path, "w", encoding="utf-8") as f:
toml.dump(self.data, f)
def get(self, key: str):
"""
Récupère la valeur d'une clé dans le pakfile
Redirige vers de getters spécifiques
:param key: Clé à récupérer
:return: Valeur associée ou None
"""
match key:
case "name" | "ver" | "rev":
return self._get(key)
case "src" | "deps" | "bdeps":
return self._get_list(key)
case _:
return None
def _get(self, key: str) -> str | None :
"""
Getter générique pour les chaines
"""
return self.data.get(key)
def _get_list(self, key: str) -> list[str]:
"""
Getter spécifique aux listes
"""
return self.data.get(key,[])
def delete(self, package: str) -> bool:
"""
Supprime un paquet non installé et son répertoire associé.
:param package: Nom du paquet à supprimer
:return: True si la suppression a réussi, False sinon
"""
package_dir, pakfile_path = self._get_paths(package)
if not os.path.exists(package_dir):
print(f"Le paquet '{package}' n'existe pas. Rien à supprimer.")
return False
try:
# On vire tout le répertoire et son contenu
for root, dirs, files in os.walk(package_dir, topdown=False):
for file in files:
os.remove(os.path.join(root, file))
for dir in dirs:
os.rmdir(os.path.join(root, dir))
os.rmdir(package_dir)
print(f"Paquet '{package}' supprimé avec succès.")
return True
except Exception as e:
print(f"Erreur lors de la suppression de '{package}': {e}")
return False
self.update_db()
def check(self):
"""
Vérifie la validité de la structure du pakfile.
Contrôle la présence et la cohérence des clés obligatoires.
Ne vérifie PAS la disponibilité des sources ou la qualité du code, juste la structure.
"""
required_keys = {"name", "ver", "rev", "src"}
optional_keys = {"deps", "bdeps", "build", "make", "install"}
all_keys = set(self.data.keys())
unknown_keys = all_keys - required_keys - optional_keys
missing_keys = required_keys - all_keys
if missing_keys:
print(f"Clés manquantes dans {self.package}: {', '.join(missing_keys)}")
return False
if unknown_keys:
print(f"Clés inconnues dans {self.package}: {', '.join(unknown_keys)}")
return False
return True
def update_db(self):
"""
Met à jour la base SQLite avec les informations actuelles du pakfile.
Si le paquet existe, il est mis à jour. Sinon, il est inséré.
"""
conn = get_db_connection()
cursor = conn.cursor()
# Récupère les données à jour
name = self.get("name")
version = self.get("ver")
revision = self.get("rev")
sources = self.get("src")
deps = self.get("deps")
bdeps = self.get("bdeps")
# Conversion des listes en string (on se complique pas la vie)
src_str = ",".join(sources) if sources else ""
deps_str = ",".join(deps) if deps else ""
bdeps_str = ",".join(bdeps) if bdeps else ""
cursor.execute("""
INSERT INTO paquets (name, version, revision, sources, deps, build_deps)
VALUES (?, ?, ?, ?, ?, ?)
ON CONFLICT(name) DO UPDATE SET
version = excluded.version,
revision = excluded.revision,
sources = excluded.sources,
deps = excluded.deps,
build_deps = excluded.build_deps
""", (name, version, revision, src_str, deps_str, bdeps_str))
conn.commit()
conn.close()