diff --git a/LICENSE b/LICENSE index 7a3094a..eadf858 100644 --- a/LICENSE +++ b/LICENSE @@ -1,11 +1,14 @@ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 Copyright (C) 2004 Sam Hocevar -Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed. +Everyone is permitted to copy and distribute verbatim or modified copies of +this license document, and changing it is allowed as long as the name is changed. DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - 0. You just DO WHAT THE FUCK YOU WANT TO. + 0. You just DO WHAT THE FUCK YOU WANT TO. \ No newline at end of file diff --git a/README.md b/README.md index c0ee58d..19b1fff 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,10 @@ -# libcsv +# Libcurses +Regroupe des fonctions faites maison pour l'utilisation personnelle simplifiée de la bibliothèque curses. + +## Contributions + +Toute contribution est la bienvenue, et peut m'être adressée à l'adresse + +## Licence +[WTFPL](http://www.wtfpl.net/) diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..450577d --- /dev/null +++ b/TODO.md @@ -0,0 +1,3 @@ +# TODO LIST + +Faire des tests ? diff --git a/libcsv/__init__.py b/libcsv/__init__.py new file mode 100644 index 0000000..dd35f29 --- /dev/null +++ b/libcsv/__init__.py @@ -0,0 +1 @@ +from libcsv.libcsv import * diff --git a/libcsv/constantes_csv.py b/libcsv/constantes_csv.py new file mode 100644 index 0000000..8e3f8b4 --- /dev/null +++ b/libcsv/constantes_csv.py @@ -0,0 +1,9 @@ +version='1.1' + +DEBUG = False + ## Compilation en LaTeX +rapport_caractere = 1 # pour taille expérimentale d'un caractère : faire diminuer pour augmenter la taille, taille initiale 1.5 +taille_max = 42 #42em pour la taille max d'un tableau à compiler +nom_dossier_sources = "sources_tex" +compilateur_latex = 'pdflatex' +nom_dossier_latex = 'TEX' diff --git a/libcsv/libcsv.py b/libcsv/libcsv.py new file mode 100644 index 0000000..2e05067 --- /dev/null +++ b/libcsv/libcsv.py @@ -0,0 +1,391 @@ +import os +import glob +import subprocess +from . import constantes_csv as constantes +from pylatex import Document, Section, NoEscape, LongTable, Tabularx, Table + +def construction_nom_fichier(*args): + """À partir de la liste, construit un nom de fichier valable : les arguments sont séparés par des sous-tirets + """ + return '_'.join( [str(arg).lower() for arg in args ] ) + +def construction_ligne(*args,separator=';'): + """À partir de la liste, construit une ligne csv valable : le séparateur par défaut est le point-virgule + """ + return separator.join( [str(arg) for arg in args ] ) + +def arrondi(flottant,nb_chiffres=2): + """Retourne un arrondi à deux chiffres après la virgule d'un flottant + """ + return round(float(flottant),nb_chiffres) + +def taille_donnee(string): + """Retourne une valeur entière de la taille d'une chaîne de caractère, pour tenir compte des chaînes de caractères contenant du LaTeX + """ + taille_0 = [r'$',"\\frac",'{','}','\\left[','\\right]','\\left(','\\right)','\\left{','\\right}'] # Quelques symboles maths + taille_1 = ['\\alpha','\\beta','\\epsilon','\\gamma','\\delta','\\eta','\\phi','\\psi','\\epsilon','\\neq','\\leq','\\geq',r"\textcolor{green}{\checkmark}",r"\textcolor{red}{\text{X}}"] #alphabet grec, quelques symboles maths, quelques commandes latex + taille_2 = ['\\sum','\\prod','\\oplus'] # symboles sommes, produits, somme directe + liste_taille = [taille_0,taille_1,taille_2] + a_remplacer = '' + for liste in liste_taille: + for element in liste: + string = string.replace(element,a_remplacer) + string = string.replace(element.capitalize(),a_remplacer) # Pour tenir compte des lettres majuscules + a_remplacer += ' ' + return len(string) + +def taille_colonnes(donnees): + """Prend en argument la liste de liste des données d'un fichier csv pour extraire la taille maximale à affecter à chaque colonne. Les contraintes suivantes sont respectées : + - la somme des tailles fait 42em, la largeur maximale du pdf : on applique pour chaque maximum de chaque colonne la formule 42*max/(somme des max des colonnes). + - si la taille maximum d'une colonne est réalisée pour un seul mot, la largeur du mot est prise pour taille maximale (en gros, cette largeur étant donnée par len(string)/1.9) : la formule précédente est conservée uniquement sur les autres colonnes : la somme des tailles peut être alors plus petite ou plus grande que 42em. Si elle est plus grande on affiche un message d'erreur. + Retourne la liste des arguments à retourner à un environnement LaTeX de type Tabular + """ + + taille_max = constantes.taille_max #42em en LaTeX + + # Taille expérimentale d'une chaîne de caractère + def largeur_string(taille_string): + return taille_string/constantes.rapport_caractere + + nombre_colonne = len(donnees[0]) + liste_des_maximum = [max([ taille_donnee(ligne[i]) for ligne in donnees]) for i in range(nombre_colonne) ]#0 for _ in range(nombre_colonne) ] + + # Récupération de la liste des colonnes pour lesquelles le maximum est réalisée pour un mot unique + def unique_mot(string): + return len(string.split(' ')) <= 1 # True si moins d'un mot + + mot_unique = [ False for _ in range(nombre_colonne) ] # A priori les colonnes ont plus d'un mot + for i in range(nombre_colonne): + colonne_i = [ ligne[i] for ligne in donnees ] + for element in colonne_i: # On teste pour tous les max possibles si l'élément maximum n'a qu'un mot, cela évite les problèmes de doublons : dès qu'un max est réalisé pour un unique mot c'est cette contrainte que l'on choisi. + if taille_donnee(element) == liste_des_maximum[i]: + if unique_mot(element): + mot_unique[i] = True + + # Remplissage de la liste des tailles + liste_taille = [ 0 for _ in range(nombre_colonne) ] + + # Liste des tailles de colonnes dont le max est réalisée pour un mot unique + for i in range(nombre_colonne): + if mot_unique[i]: + liste_taille[i] = arrondi(largeur_string(liste_des_maximum[i]),1) + + # Liste des tailles des autres colonnes + caracteres_deja_pris = sum(liste_taille) + taille_cumulee = sum([liste_des_maximum[i] for i in range(nombre_colonne) if not(mot_unique[i]) ]) + for i in range(nombre_colonne): + if not(mot_unique[i]): + #liste_taille[i] = arrondi(liste_des_maximum[i]*(taille_max-caracteres_deja_pris)/(taille_cumulee-caracteres_deja_pris),1) + liste_taille[i] = arrondi((taille_max-caracteres_deja_pris)*liste_des_maximum[i]/taille_cumulee,1) + if constantes.DEBUG: + print(liste_taille) + # Message d'erreur si jamais la somme des tailles est trop grande. + if sum(liste_taille) > taille_max: + print("Les colonnes sont trop grandes, le document risque d'être bien moche") + return ">{\\centering\\arraybackslash}"+">{\\centering\\arraybackslash}".join([ 'm'+'{'+str(element)+'em'+'}' for element in liste_taille ]) + +#'|'+'|'.join([ 'p'+'{'+str(element)+'em'+'}' for element in liste_taille ])+'|' + +class Fichier(): + + """Classe de fichier, porte un nom et un emplacement de dossier (chemin absolu ?) + On peut : + - retourner le nom sans l'extension + - le créer physiquement sur la machine + - le copier + - l'imprimer tel quel + + """ + + def __init__(self,nom,path): + """Les attributs sont le nom et le chemin du dossier où se trouve le fichier """ + self.nom = nom + self.path = path + + def emplacement(self): + """Chemin du fichier complet """ + return os.path.join(self.path,self.nom) + + def splitext(self): + """Retourne le nom du fichier sans extension """ + if self.nom[0]=='.': # Ne prend pas les fichiers cachés ? + return '' + else: + return os.path.splitext(self.nom)[0] + + def exists(self): + """Test d'existence du fichier """ + return os.path.exists(self.emplacement()) + + def existserror(self): + """Message d'erreur en cas de non existence du fichier : utilisée dans le décorateur suivant """ + if not self.exists(): + print('le fichier '+self.emplacement()+" n'existe pas") + + def existswrap(func): + """Décorateur pour s'assurer que l'on applique la fonction à un fichier qui existe """ + def wrap(self,*args,**kwargs): + if self.exists(): + return func(self,*args,**kwargs) + else: + return self.existserror() + return wrap + + def create(self): + """Crée le fichier ainsi que tous les sous-dossiers nécessaires, si le fichier n'existe pas déjà """ + if not self.exists(): + try: + os.makedirs(self.path) + with open(self.emplacement(),'w') as csvfile: + pass + except: + with open(self.emplacement(),'w') as csvfile: + pass + + def returndir(func): + """Décorateur pour effectuer une action dans un sous-dossier puis revenir """ + def wrap(self,*args,**kwargs): + returnpath = os.getcwd() + f = func(self,*args,**kwargs) + os.chdir(returnpath) + return f + return wrap + + @existswrap + def ecrire(self,*args): + """Écrit dans un fichier chaque élément de la liste des arguments, un par ligne """ + with open(self.emplacement(),'a') as outfile: + for arg in args: + outfile.write(arg+'\n') + + @existswrap + def lire(self): + """Permet de lire le fichier en entier """ + with open(self.emplacement(),'r') as csvfile: + data = csvfile.read() + return data + + @existswrap + def copier(self,path): + """Copie le fichier à l'endroit désigné """ + data = self.lire() + copie = Fichier(self.nom,path) + copie.create() + copie.ecrire(data) + + @existswrap + def imprimer(self,*args): + """Imprimer les fichier de la liste des arguments """ + cmd = ['lpr']+[arg for arg in args]+[self.emplacement()] + try: + subprocess.call(cmd) + except: + print("Impossible d'imprimer le fichier") + + @returndir + @existswrap + def tex_to_pdf(self,path_sources_tex): + """Permet de compiler un fichier latex : on ne vérifie pas l'extension ! + """ + tex_path = self.path + fichiers_tex = sorted([fichier for fichier in glob.glob(os.path.join(path_sources_tex,'*.tex'))]) + fichiers_sources_tex = [Fichier(fichier,path_sources_tex) for fichier in fichiers_tex] + if len(fichiers_sources_tex) == 0: + print("Le dossier des sources LaTeX n'existe pas ") + return None + else: + a_copier = '' + for fichier in fichiers_sources_tex: + if 'tailer.tex' not in fichier.nom: + a_copier +=r'\input{'+fichier.splitext()+'}' + else: + tailer = r'\input{'+fichier.splitext()+'}' + data_tex = self.lire() + with open(self.emplacement(),'w') as texfile: + texfile.write(a_copier) + texfile.write('\n') + texfile.write(data_tex) + texfile.write('\n') + texfile.write(tailer) + try: + os.chdir(self.path) + cmd = [constantes.compilateur_latex]+[self.nom] + outfile = os.path.join(self.splitext()+'.logtex') + with open(outfile,'w') as out: + subprocess.call(cmd,stdout = out) + return Fichier(self.splitext()+'.pdf',self.path) + except: + print("Il y a eu une erreur durant la compilation (fichier csv non écrit en LaTeX ?), voir le fichier .logtex situé dans "+self.path) + return None + +class FichierCSV(Fichier): + + """Classe de fichier CSV héritant de la classe de fichier pour manipuler des fichiers csv. + + # Attributs : + - nom de fichier avec extension csv + - la liste des noms des colonnes, dans l'ordre (pourquoi ? Non implémenté pour le moment) + - emplacement du dossier parent + - séparateur + + ### IMPORTANT ### + Le séparateur par défaut est un point-virgule. + + Le fichier doit se terminer par une ligne commençant par le mot "terminer" pour pouvoir être utilisé par LaTeX (chelou et faux pour le moment !) + + """ + + def __init__(self,nom,path,separator=';'): # le séparateur par défaut est un point-virgule ! + nomcsv = nom+'.csv' + self.nom = nomcsv + self.path = path + self.separator = separator + + def create(self,nom_des_colonnes): + """Crée le fichier CSV ainsi que sa première ligne sur le même modèle que la méthode create sur les fichiers """ + if not self.exists(): + ligne = construction_ligne(*nom_des_colonnes,separator=self.separator) + try: + os.makedirs(self.path) + with open(self.emplacement(),'w') as csvfile: + csvfile.write(ligne+'\n') + except: + with open(self.emplacement(),'w') as csvfile: + csvfile.write(ligne+'\n') + + @Fichier.existswrap + def ecrire(self,*args): + """Écrit dans le fichier CSV la ligne correspondant à la liste des arguments au format voulu """ + ligne = construction_ligne(*args,separator=self.separator) + with open(self.emplacement(),'a') as csvfile: + csvfile.write(ligne+'\n') + + @Fichier.existswrap + def reinitialiser(self): + """Écrase le fichier et le remet à 0 + """ + nom_des_colonnes = self.nom_colonnes() + ligne = construction_ligne(*nom_des_colonnes,separator=self.separator) + with open(self.emplacement(),'w') as csvfile: + csvfile.write(ligne+'\n') + + @Fichier.existswrap + def lire(self): + """Récupère une liste de listes des données du fichier CSV. On ne prend évidemment pas la première ligne qui est la ligne des noms de colonnes """ + with open(self.emplacement(),'r') as csvfile: + lines = csvfile.readlines() + return [line.rstrip('\n').split(self.separator) for line in lines][1:] + + @Fichier.existswrap + def nom_colonnes(self): + """Récupère les noms des colonnes : c'est la première ligne """ + with open(self.emplacement(),'r') as csvfile: + lines = csvfile.readlines() + return [line.rstrip('\n').split(self.separator) for line in lines][0] + + @Fichier.existswrap + @Fichier.returndir + def csv_to_tex(self,titre='',commentaires='',conclusion=''): + """Crée dans le dossier courant un dossier TEX ainsi qu'un sous-dossier du nom du fichier csv dans lequel on construit une version tex du fichier CSV. Ces fichier TeX brut est à insérer avec les en-têtes et tailer pour compilation + Contrainte : le fichier CSV doit être écrit en TeX car on a mis des NoEscape partout + """ + infos = self.lire() + donnees = infos+[self.nom_colonnes()] + nom_des_colonnes = self.nom_colonnes() + path = self.path + + # Création du dossier TEX et du sous-dossier self.splitext() + tex_path = os.path.join(path,constantes.nom_dossier_latex) + fichier_tex_path = os.path.join(tex_path,self.splitext()) + if not os.path.exists(tex_path): + os.makedirs(tex_path) + if not os.path.exists(fichier_tex_path): + os.makedirs(fichier_tex_path) + + # Génération du fichier TeX dans le sous-dossier TEX/self.splitext() + os.chdir(fichier_tex_path) + doc = Section(NoEscape(titre),numbering=False) + doc.append(NoEscape(r'\begin{center}')) + doc.append(NoEscape(commentaires)) + doc.append(NoEscape(r'\end{center}')) + doc.append(NoEscape(r'\begin{center}')) + with doc.create(LongTable(table_spec=NoEscape(taille_colonnes(donnees)),pos=['H'])) as info: +#LongTable(table_spec=NoEscape(taille_colonnes(donnees)),pos=['H'])) as info: + info.add_hline() + info.add_row(*[NoEscape(element) for element in nom_des_colonnes]) + info.add_hline() + for element in infos: + info.add_row(*[NoEscape(item) for item in element ]) + info.add_hline() + doc.append(NoEscape(r'\end{center}')) + doc.append(NoEscape(r'\begin{center}')) + doc.append(NoEscape(conclusion)) + doc.append(NoEscape(r'\end{center}')) + doc.generate_tex(self.splitext()) + return Fichier(self.splitext()+'.tex',fichier_tex_path) + + @Fichier.existswrap + @Fichier.returndir + def csv_to_tex_to_pdf(self,path_sources_tex,titre='',commentaires='',conclusion=''): + """Appelle la méthode csv_to_tex, ajoute les en-têtes et tailer de path_sources_tex au fichier TeX du dossier TEX/self.splitext() et compile le fichier tex. + Les sources doivent être ordonnées selon l'ordre lexicographique pour préfixer et suffixer le fichier self.nom.tex pour ensuite le compiler. Le fichier à utiliser pour suffixer doit s'appeller "tailer". Ne fonctionnera que si pdflatex est présent et accessible par subprocess.call + """ + tex_fichier = self.csv_to_tex(titre=titre,commentaires=commentaires,conclusion=conclusion) + tex_path = tex_fichier.path + if tex_fichier.exists(): + fichiers_tex = sorted([fichier for fichier in glob.glob(os.path.join(path_sources_tex,'*.tex'))]) + fichiers_sources_tex = [Fichier(fichier,path_sources_tex) for fichier in fichiers_tex] + if len(fichiers_sources_tex) == 0: + print("Le dossier des sources LaTeX n'existe pas ") + return None + else: + a_copier = '' + for fichier in fichiers_sources_tex: + if 'tailer.tex' not in fichier.nom: + a_copier +=r'\input{'+fichier.splitext()+'}' + else: + tailer = r'\input{'+fichier.splitext()+'}' + data_tex = tex_fichier.lire() + with open(tex_fichier.emplacement(),'w') as texfile: + texfile.write(a_copier) + texfile.write('\n') + texfile.write(data_tex) + texfile.write('\n') + texfile.write(tailer) + try: + os.chdir(tex_fichier.path) + cmd = [constantes.compilateur_latex]+[tex_fichier.nom ]#emplacement()] + outfile = os.path.join(tex_fichier.splitext()+'.logtex')#tex_fichier.path,tex_fichier.splitext()+'.logtex') + with open(outfile,'w') as out: + subprocess.call(cmd,stdout = out) + return Fichier(tex_fichier.splitext()+'.pdf',tex_fichier.path) + except: + print("Il y a eu une erreur durant la compilation (fichier csv non écrit en LaTeX ?), voir le fichier .logtex situé dans "+tex_fichier.path) + return None + else: + print('le fichier '+tex_fichier.emplacement()+" n'existe pas, et ça c'est chelou ! ") + return None + +def fichier(path_complet_du_fichier): + """Construit un objet Fichier à partir de son nom complet + """ + return Fichier(os.path.basename(path_complet_du_fichier),os.path.dirname(path_complet_du_fichier)) + +def fichierCSV(nom_de_fichier,separator=';'): + """Construit un objet FichierCSV à partir de son nom. Le fichier csv doit être un fichier valable (la première ligne doit être les noms de colonnes), ceci n'est pas testé. + """ + nom_fichier = os.path.splitext(os.path.basename(nom_de_fichier))[0] + path_fichier = os.path.dirname(nom_de_fichier) + return FichierCSV(nom_fichier,path_fichier,separator=separator) + +class Bareme(FichierCSV): + def __init__(self,type_ecrit,matiere,numero,detail=True,separator=';'): + """Un barème se crée uniquement dans le dossier typeecrit_matiere_numero du dossier courant + """ + self.nom = construction_nom_fichier('bareme',type_ecrit,matiere,numero)+'.csv' + self.type_ecrit = type_ecrit + self.matiere = matiere + self.numero = numero + self.path = os.path.join(os.getcwd(),construction_nom_fichier(type_ecrit,matiere,numero.strip('/'))) # dossier dans lequel se trouve le barème + self.detail = detail + self.separator = separator diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..89e644a --- /dev/null +++ b/setup.py @@ -0,0 +1,17 @@ +from setuptools import setup, find_packages +from libcsv.constantes_csv import version + +setup( + name="libcsv", + version=version, + packages=find_packages(), + + install_requires=["pylatex"], + author="David Denoncin", + author_email="math@denoncin.fr", + url="math.denoncin.fr", + description="Mes fonctions perso pour faciliter l'utilisation de fichiers CSV", + classifiers=[ + "Licence :: WTFPL" + ] +)