initial commit

main
ddenoncin 2 years ago
parent dff8006b77
commit 7f2aacdbd2

@ -1,11 +1,14 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
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.

@ -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 <math@denoncin.fr>
## Licence
[WTFPL](http://www.wtfpl.net/)

@ -0,0 +1,3 @@
# TODO LIST
Faire des tests ?

@ -0,0 +1 @@
from libcsv.libcsv import *

@ -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'

@ -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

@ -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"
]
)
Loading…
Cancel
Save