impression_hp: first draft
This commit is contained in:
parent
dd369ca635
commit
8cd2622469
2 changed files with 534 additions and 0 deletions
503
impression/impression_hp.py
Normal file
503
impression/impression_hp.py
Normal file
|
@ -0,0 +1,503 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
.. codeauthor:: Daniel STAN <dstan@crans.org>
|
||||||
|
.. codeauthor:: Antoine Durand-Gasselin <adg@crans.org>
|
||||||
|
|
||||||
|
Pour envoyer des jobs d'impression à l'imprimante canon via son interface web.
|
||||||
|
|
||||||
|
License: GPLv3
|
||||||
|
"""
|
||||||
|
# impression_hp.py
|
||||||
|
#
|
||||||
|
# Classe impression pour HP MFP 880
|
||||||
|
#
|
||||||
|
# Copyright (c) 2006, 2007, 2008, 2009 by Cr@ns (http://www.crans.org)
|
||||||
|
# #############################################################
|
||||||
|
|
||||||
|
import sys, os
|
||||||
|
import logging
|
||||||
|
import logging.handlers
|
||||||
|
|
||||||
|
if '/usr/scripts' not in sys.path:
|
||||||
|
sys.path.append('/usr/scripts')
|
||||||
|
|
||||||
|
from base import FichierInvalide, SoldeInsuffisant, PrintError, SettingsError
|
||||||
|
|
||||||
|
from gestion.config import impression as config_impression
|
||||||
|
from subprocess import Popen, PIPE, check_output
|
||||||
|
import livret
|
||||||
|
|
||||||
|
##
|
||||||
|
logger = logging.getLogger('impression_hp')
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
formatter = logging.Formatter('%(name)s: [%(levelname)s] %(message)s')
|
||||||
|
handler = logging.handlers.SysLogHandler(address = '/dev/log')
|
||||||
|
try:
|
||||||
|
handler.addFormatter(formatter)
|
||||||
|
except AttributeError:
|
||||||
|
handler.formatter = formatter
|
||||||
|
logger.addHandler(handler)
|
||||||
|
|
||||||
|
DEBUG = bool(os.getenv('DEBUG', False))
|
||||||
|
DECOUVERT_AUTHORISE = config_impression.decouvert
|
||||||
|
|
||||||
|
class Option(object):
|
||||||
|
# Valeur par défaut
|
||||||
|
value = None
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
name = "option"
|
||||||
|
pretty_name = u"option quelconque"
|
||||||
|
|
||||||
|
def parse(self, v):
|
||||||
|
self.value = v
|
||||||
|
|
||||||
|
class BooleanOption(Option):
|
||||||
|
value = False
|
||||||
|
def parse(self, v):
|
||||||
|
self.value = bool(v)
|
||||||
|
if str(v).lower() == 'false':
|
||||||
|
self.value = False
|
||||||
|
|
||||||
|
class Couleur(BooleanOption):
|
||||||
|
name = "couleur"
|
||||||
|
pretty_name = u"impression couleur"
|
||||||
|
|
||||||
|
class Livret(BooleanOption):
|
||||||
|
name = "livret"
|
||||||
|
pretty_name = u"livret piqûres à cheval"
|
||||||
|
|
||||||
|
def nb(self):
|
||||||
|
return 2*int(self.value)
|
||||||
|
|
||||||
|
class Duplex(BooleanOption):
|
||||||
|
name = "recto_verso"
|
||||||
|
pretty_name = u"Impression recto verso"
|
||||||
|
|
||||||
|
class Copies(Option):
|
||||||
|
name = "copies"
|
||||||
|
pretty_name = u"nombre d'exemplaires"
|
||||||
|
|
||||||
|
value = 1
|
||||||
|
|
||||||
|
def parse(self, v):
|
||||||
|
self.value = int(v)
|
||||||
|
|
||||||
|
class SelectOption(Option):
|
||||||
|
possibilities={
|
||||||
|
None: u'Aucun',
|
||||||
|
}
|
||||||
|
|
||||||
|
value = None
|
||||||
|
|
||||||
|
def parse(self, v):
|
||||||
|
if v == 'None':
|
||||||
|
v = None
|
||||||
|
if v not in self.possibilities:
|
||||||
|
return
|
||||||
|
self.value = v
|
||||||
|
|
||||||
|
class Agrafage(SelectOption):
|
||||||
|
name = "agrafage"
|
||||||
|
pretty_name = u"agrafage"
|
||||||
|
|
||||||
|
possibilities = {
|
||||||
|
None : "aucune agrafe",
|
||||||
|
"TopLeft" : u"agrafe en haut à gauche",
|
||||||
|
"TopRight" : u"agrafe en haut à droite",
|
||||||
|
#"BottomLeft" : u"agrafe en bas à gauche", # n'existe pas
|
||||||
|
#"BottomRight" : u"agrafe en bas à droite", # n' existe pas
|
||||||
|
"Left": u"deux agrafes sur le bord gauche",
|
||||||
|
"Right" : u"deux agrafes sur le bord droit",
|
||||||
|
#"Top" : u"deux agrafes sur le bord supérieur", #pas faisable
|
||||||
|
#"Bottom" : u"deux agrafes sur le bord inférieur", #n'existe pas
|
||||||
|
}
|
||||||
|
|
||||||
|
def nb(self):
|
||||||
|
if not self.value:
|
||||||
|
return 0
|
||||||
|
if 'Left' in self.value or 'Right' in self.value:
|
||||||
|
return 2 - int('Top' in self.value or 'Bottom' in self.value)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
def HP_name(self):
|
||||||
|
if 'Top' in self.value:
|
||||||
|
return '1Staple%sAngled' % (self.value.replace('Top', ''))
|
||||||
|
else:
|
||||||
|
return '2Staples%s' % self.value
|
||||||
|
|
||||||
|
class Papier(SelectOption):
|
||||||
|
name = "papier"
|
||||||
|
pretty_name = u"format papier"
|
||||||
|
|
||||||
|
value = 'A4'
|
||||||
|
|
||||||
|
possibilities = {
|
||||||
|
'A4' : "Papier A4 ordinaire",
|
||||||
|
'A3' : "Papier A3 ordinaire",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Perforation(SelectOption):
|
||||||
|
name = "perforation"
|
||||||
|
pretty_name = u"perforation"
|
||||||
|
possibilities = {
|
||||||
|
None: u"Aucune",
|
||||||
|
"2HolePunchLeft": u"2HolePunchLeft",
|
||||||
|
"2HolePunchRight": u"2HolePunchRight",
|
||||||
|
"2HolePunchTop": u"2HolePunchTop",
|
||||||
|
"2HolePunchBottom": u"2HolePunchBottom",
|
||||||
|
"3HolePunchLeft": u"3HolePunchLeft",
|
||||||
|
"3HolePunchRight": u"3HolePunchRight",
|
||||||
|
"3HolePunchTop": u"3HolePunchTop",
|
||||||
|
"4HolePunchLeft": u"4HolePunchLeft",
|
||||||
|
"4HolePunchRight": u"4HolePunchRight",
|
||||||
|
"4HolePunchTop": u"4HolePunchTop",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ######################################################## #
|
||||||
|
# CLASSE IMPRESSION #
|
||||||
|
# ######################################################## #
|
||||||
|
#
|
||||||
|
#
|
||||||
|
class impression(object):
|
||||||
|
"""impression
|
||||||
|
|
||||||
|
Un objet impression correspond à un fichier pdf et un adhérent.
|
||||||
|
"""
|
||||||
|
# fichier (chemin)
|
||||||
|
_fichier = ""
|
||||||
|
# adhérent (instance)
|
||||||
|
_adh = None
|
||||||
|
# paramètres
|
||||||
|
_settings_list = [
|
||||||
|
Agrafage,
|
||||||
|
Papier,
|
||||||
|
Couleur,
|
||||||
|
Duplex,
|
||||||
|
Livret,
|
||||||
|
Copies,
|
||||||
|
Perforation,
|
||||||
|
]
|
||||||
|
|
||||||
|
_settings = {}
|
||||||
|
|
||||||
|
# le prix de l'impression
|
||||||
|
_prix = 0.0
|
||||||
|
_pages = 0
|
||||||
|
# le cout de base encre pour une impression en couleurs/n&b
|
||||||
|
# (prix pour papier A4)
|
||||||
|
_base_prix_nb = 0.0
|
||||||
|
_base_prix_couleurs = 0.0
|
||||||
|
|
||||||
|
# Format du pdf, tout droit sorti de pdfinfo
|
||||||
|
# (les dimensions sont donc en pts)
|
||||||
|
_format = '(A4)'
|
||||||
|
_width = 595.28
|
||||||
|
_height = 841.89
|
||||||
|
|
||||||
|
# Jid unique, à définir avant l'impression
|
||||||
|
_jid = 0
|
||||||
|
|
||||||
|
def __init__(self, path_to_pdf, adh = None):
|
||||||
|
"""impression(path_to_pdf [, adh])
|
||||||
|
|
||||||
|
Crée un nouvel objet impression à partir du fichier pdf pointé
|
||||||
|
par path_to_pdf. Si adh ext donné, il peut être soit une
|
||||||
|
instance d'un objet adhérent de crans_ldap soit le login de
|
||||||
|
l'adhérent. Lève l'exception FichierInvalide si le fichier
|
||||||
|
n'existe pas ou si ce n'est pas un pdf.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# On génère la liste des options
|
||||||
|
self._settings = {cls.name: cls() for cls in self._settings_list}
|
||||||
|
|
||||||
|
self._fichier = path_to_pdf
|
||||||
|
self._adh = adh
|
||||||
|
|
||||||
|
# on verifie que le fichier existe
|
||||||
|
if not os.path.isfile(path_to_pdf):
|
||||||
|
raise FichierInvalide, ("Fichier introuvable", path_to_pdf)
|
||||||
|
if not open(path_to_pdf).read().startswith("%PDF"):
|
||||||
|
raise FichierInvalide, ("Le fichier ne semble pas etre un PDF", path_to_pdf)
|
||||||
|
|
||||||
|
# on compte les pages et on regarde le format
|
||||||
|
pdfinfo = Popen(["pdfinfo",self._fichier],stdout=PIPE,stderr=PIPE).communicate()
|
||||||
|
if pdfinfo[1] <> '':
|
||||||
|
raise FichierInvalide(u"pdfinfo n'arrive pas a lire le fichier (il est peut-etre corrompu ou protege par un mot de passe), https://wiki.crans.org/VieCrans/ImpressionReseau#Format_des_fichiers",path_to_pdf)
|
||||||
|
self._pages = -1
|
||||||
|
for line in pdfinfo[0].split('\n'):
|
||||||
|
if line.startswith('Pages'):
|
||||||
|
self._pages = int(line.split()[1])
|
||||||
|
elif line.startswith('Page size'):
|
||||||
|
size = line.split()
|
||||||
|
if len(size) <= 6:
|
||||||
|
self._format = "Unknown"
|
||||||
|
else:
|
||||||
|
self._format = size[6]
|
||||||
|
|
||||||
|
self._width = float(size[2])
|
||||||
|
self._height = float(size[4])
|
||||||
|
# Hack pour mieux reconnaître les formats
|
||||||
|
w = min(self._width,self._height)
|
||||||
|
h = max(self._width,self._height)
|
||||||
|
err = 100
|
||||||
|
if abs(w - 595) < err and abs(h - 842) < err:
|
||||||
|
self._format = "(A4)"
|
||||||
|
elif abs(w - 842) < err and abs(h - 1180) < err:
|
||||||
|
self._format = "(A3)"
|
||||||
|
if self._pages <= 0:
|
||||||
|
raise FichierInvalide(u"Impossible de lire le nombre de pages",path_to_pdf)
|
||||||
|
|
||||||
|
if not self._format in ['(A4)','(A3)']:
|
||||||
|
pass
|
||||||
|
#raise FichierInvalide, u"Seuls les formats A3 et A4 sont supportes"
|
||||||
|
# calcule le prix de l'encre tout de suite
|
||||||
|
self._calcule_prix()
|
||||||
|
|
||||||
|
def _uniq_jid(self):
|
||||||
|
""" Alloue un jid unique """
|
||||||
|
fname = '/var/impression/jid'
|
||||||
|
## Maybe need a lock ?
|
||||||
|
try:
|
||||||
|
f = file(fname,'r+')
|
||||||
|
cur = int(f.read())+1
|
||||||
|
f.seek(0)
|
||||||
|
except (IOError,ValueError):
|
||||||
|
cur = 0
|
||||||
|
f = file(fname,'w')
|
||||||
|
f.write(str(cur))
|
||||||
|
f.close()
|
||||||
|
return cur
|
||||||
|
|
||||||
|
def _pdfbook(self):
|
||||||
|
"""Génère un pdf livret équivalent et renvoie le chemin"""
|
||||||
|
newfile = '/tmp/' + self.fileName()[-4] + '-book.pdf'
|
||||||
|
newfile = '/tmp/book.pdf'
|
||||||
|
check_output(
|
||||||
|
['/usr/bin/pdfjam', self._fichier,
|
||||||
|
livret.pdfjam_order(self._pages),
|
||||||
|
'-o', newfile,
|
||||||
|
])
|
||||||
|
return newfile
|
||||||
|
|
||||||
|
def changeSettings(self, **kw):
|
||||||
|
"""changeSettings([keyword=value])
|
||||||
|
|
||||||
|
Change les parametres de l'impression, recalcule et renvoie le nouveau prix.
|
||||||
|
"""
|
||||||
|
for name in kw:
|
||||||
|
if name not in self._settings:
|
||||||
|
raise Exception('unknown %s option' % name)
|
||||||
|
self._settings[name].parse(kw[name])
|
||||||
|
|
||||||
|
if self._settings['livret'].value:
|
||||||
|
self._settings['recto_verso'].parse(True)
|
||||||
|
self._settings['agrafage'].parse(None)
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# Check si l'agrafage et cie sont compatibles avec le nb de pages
|
||||||
|
|
||||||
|
return self._calcule_prix()
|
||||||
|
|
||||||
|
def printSettings(self):
|
||||||
|
"""printSettings()
|
||||||
|
|
||||||
|
Affiche les paramètres courants sur la sortie standard
|
||||||
|
"""
|
||||||
|
for k in self._settings:
|
||||||
|
opt = self._settings[k]
|
||||||
|
label = u"%s: %s" % (opt.pretty_name.capitalize(), opt.value)
|
||||||
|
print label.encode('UTF-8')
|
||||||
|
|
||||||
|
def prix(self):
|
||||||
|
"""prix()
|
||||||
|
|
||||||
|
Renvoie le prix courrant de l'impression
|
||||||
|
"""
|
||||||
|
return self._prix
|
||||||
|
|
||||||
|
def fileName(self):
|
||||||
|
"""fileName()
|
||||||
|
|
||||||
|
renvoie le nom du fichier pdf (exemple : monPdf.pdf)
|
||||||
|
"""
|
||||||
|
return os.path.basename(self._fichier)
|
||||||
|
|
||||||
|
def filePath(self):
|
||||||
|
"""filePath()
|
||||||
|
|
||||||
|
renvoie le chemin d'accès au fichier pdf.
|
||||||
|
"""
|
||||||
|
return self._fichier
|
||||||
|
|
||||||
|
def pages(self):
|
||||||
|
"""pages()
|
||||||
|
|
||||||
|
renvoie le nombre de pages du document (page au sens nombre de
|
||||||
|
faces à imprimer et non le nombre de feuilles)
|
||||||
|
"""
|
||||||
|
return self._pages
|
||||||
|
|
||||||
|
def imprime(self):
|
||||||
|
"""imprime()
|
||||||
|
|
||||||
|
imprime le document pdf. débite l'adhérent si adhérent il y a.
|
||||||
|
(si il a été indiqué à l'initialisation de l'objet)
|
||||||
|
"""
|
||||||
|
self._jid = self._uniq_jid()
|
||||||
|
|
||||||
|
# debite l'adhérent si adherent il y a
|
||||||
|
if (self._adh != None):
|
||||||
|
adh = self._adh.split('@')
|
||||||
|
if len(adh) > 1:
|
||||||
|
adh = adh[1:]
|
||||||
|
adh = self._get_adh(adh[0])
|
||||||
|
self._calcule_prix() # Normalement inutile, mais évite les races
|
||||||
|
if (self._prix > (adh.solde() - DECOUVERT_AUTHORISE)):
|
||||||
|
raise SoldeInsuffisant
|
||||||
|
adh.solde(-self._prix, "impression(%d): %s par %s" % (self._jid,self._fichier,self._adh))
|
||||||
|
adh.save()
|
||||||
|
del adh
|
||||||
|
# imprime le document
|
||||||
|
self._exec_imprime()
|
||||||
|
|
||||||
|
def _calcule_prix(self):
|
||||||
|
|
||||||
|
faces = self._pages
|
||||||
|
|
||||||
|
if self._settings['livret'].value:
|
||||||
|
feuilles = int((faces+3)/4)
|
||||||
|
faces = 2 * feuilles
|
||||||
|
elif self._settings['recto_verso'].value:
|
||||||
|
feuilles = int(faces/2.+0.5)
|
||||||
|
else:
|
||||||
|
feuilles = faces
|
||||||
|
|
||||||
|
if (self._settings['papier'].value == "A3"):
|
||||||
|
c_papier = config_impression.c_a3
|
||||||
|
pages = 2*faces
|
||||||
|
else:
|
||||||
|
pages = faces
|
||||||
|
c_papier = config_impression.c_a4
|
||||||
|
|
||||||
|
if self._settings['couleur'].value:
|
||||||
|
c_impression = c_papier * feuilles + config_impression.c_face_couleur * pages
|
||||||
|
else:
|
||||||
|
c_impression = c_papier * feuilles + config_impression.c_face_nb * pages
|
||||||
|
|
||||||
|
# Cout des agrafes
|
||||||
|
nb_agrafes = self._settings['agrafage'].nb() + self._settings['livret'].nb()
|
||||||
|
|
||||||
|
c_agrafes = nb_agrafes * config_impression.c_agrafe
|
||||||
|
|
||||||
|
c_total = int(self._settings['copies'].value * ( c_impression +
|
||||||
|
c_agrafes ) + 0.5) # arrondi et facture
|
||||||
|
|
||||||
|
self._prix = float(c_total)/100
|
||||||
|
return self._prix
|
||||||
|
|
||||||
|
def _get_adh(self, adh):
|
||||||
|
if type(adh) == str:
|
||||||
|
#from ldap_crans_test import crans_ldap
|
||||||
|
from gestion.ldap_crans import CransLdap
|
||||||
|
adh = CransLdap().getProprio(adh, 'w')
|
||||||
|
return adh
|
||||||
|
|
||||||
|
## ################################# ##
|
||||||
|
## fonction qui imprime pour de vrai ##
|
||||||
|
## ################################# ##
|
||||||
|
##
|
||||||
|
def _exec_imprime(self):
|
||||||
|
""" Envoie l'impression a l'imprimante avec les parametres actuels """
|
||||||
|
|
||||||
|
if (self._adh != None):
|
||||||
|
logger.info('Impression(%d) [%s] : %s' % (self._jid, self._adh, self._fichier))
|
||||||
|
else:
|
||||||
|
logger.info("Impression(%d) : %s" % (self._jid, self._fichier))
|
||||||
|
|
||||||
|
# Création de la liste d'options pour lp
|
||||||
|
options = list()
|
||||||
|
|
||||||
|
# TODO que se passe-t-il si le fichier commence par -o ou -- ?
|
||||||
|
if self._settings['livret'].value:
|
||||||
|
options.append(self._pdfbook())
|
||||||
|
else:
|
||||||
|
options.append(self._fichier)
|
||||||
|
|
||||||
|
|
||||||
|
# Pour spécifier l'imprimante
|
||||||
|
options += ['-d', 'MFPM880']
|
||||||
|
|
||||||
|
# Pour spécifier un jobname de la forme jid:adh:nom_du_fichier
|
||||||
|
jobname = '%d:%s:%s' % (self._jid, self._adh, self._fichier.split('/')[-1].replace("\"","\\\""))
|
||||||
|
options += ['-t', jobname]
|
||||||
|
|
||||||
|
#Pour le nombre de copies et specifie non assemblee
|
||||||
|
options += ['-n', str(self._settings['copies'].value)]
|
||||||
|
#Et on ne les veux pas assemblées (sic)
|
||||||
|
options += ['-o', 'Collate=True']
|
||||||
|
|
||||||
|
# Pour donner le login de l'adherent (TODO: useful ?)
|
||||||
|
options += ['-U', str(self._adh)]
|
||||||
|
|
||||||
|
if self._settings['papier'].value == 'A4':
|
||||||
|
options += ['-o', 'PageSize=A4']
|
||||||
|
else:
|
||||||
|
options += ['-o', 'pdf-expand',
|
||||||
|
'-o', 'pdf-paper=841x1190',
|
||||||
|
'-o', 'PageSize=A3']
|
||||||
|
|
||||||
|
options.append('-o')
|
||||||
|
# Quelle alimentation papier utiliser ?
|
||||||
|
if self._settings['papier'].value == 'A3':
|
||||||
|
options.append('InputSlot=Tray1')
|
||||||
|
elif self._settings['livret'].value:
|
||||||
|
options.append('InputSlot=Tray2')
|
||||||
|
else:
|
||||||
|
options.append('InputSlot=Tray3')
|
||||||
|
|
||||||
|
|
||||||
|
if not self._settings['couleur'].value:
|
||||||
|
options += ['-o', 'HPColorAsGray=True']
|
||||||
|
|
||||||
|
duplex = self._settings['recto_verso'].value or\
|
||||||
|
self._settings['livret'].value
|
||||||
|
paysage = self._width > self._height
|
||||||
|
# mode paysage: on indique que c'est landscape (ou livret)
|
||||||
|
if paysage:
|
||||||
|
options += ['-o', 'landscape']
|
||||||
|
|
||||||
|
# Livret: 2 pages par côté, et on inverse le bord duplex
|
||||||
|
if self._settings['livret'].value:
|
||||||
|
options += ['-o', 'number-up=2']
|
||||||
|
paysage = not paysage
|
||||||
|
|
||||||
|
if duplex:
|
||||||
|
if paysage:
|
||||||
|
options += ['-o', 'sides=two-sided-short-edge']
|
||||||
|
else:
|
||||||
|
options += ['-o', 'sides=two-sided-long-edge']
|
||||||
|
|
||||||
|
if not duplex:
|
||||||
|
options += ['-o', 'sides=one-sided']
|
||||||
|
|
||||||
|
if self._settings['livret'].value:
|
||||||
|
options += ['-o', 'HPStaplerOptions=FoldStitch']
|
||||||
|
elif self._settings['agrafage'].value:
|
||||||
|
v = self._settings['agrafage'].HP_name()
|
||||||
|
options += ['-o', 'HPStaplerOptions=%s' % v]
|
||||||
|
|
||||||
|
if not DEBUG:
|
||||||
|
logger.info("lp " + " ".join(options))
|
||||||
|
check_output(['lp'] + options)
|
||||||
|
else:
|
||||||
|
logger.info("pretend printing (debug): " + " ".join(options))
|
||||||
|
|
||||||
|
|
||||||
|
|
31
impression/livret.py
Executable file
31
impression/livret.py
Executable file
|
@ -0,0 +1,31 @@
|
||||||
|
#!/bin/bash /usr/scripts/python.sh
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import math
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
def booklet_order(n):
|
||||||
|
page = (lambda k: None if k > n else k)
|
||||||
|
|
||||||
|
recto = 2*int(math.ceil(n/4.))
|
||||||
|
verso = recto + 2
|
||||||
|
|
||||||
|
while recto > 1:
|
||||||
|
yield page(verso)
|
||||||
|
yield page(recto-1)
|
||||||
|
yield page(recto)
|
||||||
|
yield page(verso-1)
|
||||||
|
recto -= 2
|
||||||
|
verso += 2
|
||||||
|
|
||||||
|
def pdfjam_order(n):
|
||||||
|
return ",".join(itertools.imap(lambda k: str(k or '{}'), booklet_order(n)))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
n = int(sys.argv[1])
|
||||||
|
except:
|
||||||
|
print "Veuillez entrer un nombre"
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
print pdfjam_order(n)
|
Loading…
Add table
Add a link
Reference in a new issue