nutools/lib/ulib/support/deploydb.py

1714 lines
65 KiB
Python
Executable File

#!/usr/bin/env python
# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
import os, sys, re
from os import path
from argparse import ArgumentParser
from glob import glob
USER_CONFDIR = '~/etc/deploy'
SYSTEM_CONFDIR = '/var/local/deploy'
################################################################################
# Diverses fonctions
def isseq(t):
"""Tester si t est une séquence
"""
return isinstance(t, list) or isinstance(t, tuple) or isinstance(t, set)
def flatten(src, unique=True, clean=True, sep=','):
"""découper chaque élément du tableau src selon sep et les aplatir dans une
seule liste.
Si unique==True, supprimer les doublons.
Si clean==True, supprimer les valeurs vides et les espaces périphériques
e.g flatten(['a , b', 'c,']) --> ['a', 'b', 'c']
"""
if src is None: return None
if not isseq(src): src = [src]
dest = []
for items in src:
items = items.split(sep)
if clean: items = filter(None, map(lambda item: item.strip(), items))
if unique:
for item in items:
if item not in dest: dest.append(item)
else:
dest.extend(items)
return dest
def qshell(values):
if isseq(values): return map(qshell, values)
elif not values: return ''
else: return "'%s'" % values.replace("'", "'\\''")
def strip_domain(a):
if a.find('.') == -1: return a
else: return a[:a.find('.')]
def match_host(a, b):
"""Les hôtes sont matchés de façon particulière:
NAME matche NAME et NAME.DOMAIN
NAME. matche NAME
NAME.DOMAIN matche NAME.DOMAIN
"""
a = a.lower()
b = b.lower()
if a == b: return True
if a.find('.') == -1: # NAME
return a == strip_domain(b)
elif a.endswith('.'): # NAME.
return a[:-1] == b
else: # NAME.DOMAIN
return a == b
################################################################################
# Base de données
class GenericObject(object):
"""Un objet générique
Un objet a un identifiant (propriété id), une liste de valeurs (propriété
values) et des attributs (propriété attrs)
"""
OTYPE = 'object'
VALUESNAME = 'values'
ATTRPREFIX = 'object_'
_id = None
_values = None
_attrs = None
def __init__(self, id):
self._id = id
self._values = set()
self._attrs = {}
def get_id(self): return self._id
id = property(get_id)
def is_defaults(self):
"""Cet objet contient-il les attributs par défaut pour le type?
"""
return self._id is None
def get_values(self): return tuple(self._values)
values = property(get_values)
def add_value(self, value):
self._values.add(value)
def get_attrs(self): return self._attrs
attrs = property(get_attrs)
def get_attr(self, name):
"""Obtenir la valeur d'un attribut ou None si l'attribut n'existe pas
"""
return self._attrs.get(name, None)
def set_attr(self, name, value):
"""Ajouter une valeur à la liste des valeurs d'un attribut si elle n'y est pas
déjà.
"""
if name not in self._attrs: self._attrs[name] = []
if value not in self._attrs[name]: self._attrs[name].append(value)
def reset_attr(self, name, value=None):
"""Recommencer à zéro la valeur d'un attribut
"""
if name in self._attrs: del self._attrs[name]
if value is not None: self.set_attr(name, value)
def add_attr(self, name, value):
"""Ajouter une valeur à la liste des valeurs d'un attribut
"""
if name not in self._attrs: self._attrs[name] = []
self._attrs[name].append(value)
def del_attr(self, name, value):
"""Supprimer une valeur d'un attribut
"""
if name not in self._attrs: return
self._attrs[name].remove(value)
if not self._attrs[name]: del self._attrs[name]
def modify_attr(self, name, value, method='set'):
"""Méthode générique pour modifier les valeurs d'un attribut
method peut valoir set, reset, add, del
"""
if method == 'set': self.set_attr(name, value)
elif method == 'reset': self.reset_attr(name, value)
elif method == 'add': self.add_attr(name, value)
elif method == 'del': self.del_attr(name, value)
def merge_attrs(self, other, method='set'):
"""Fusionner dans cet objet les attributs d'un autre objet, avec la méthode
spécifiée.
"""
for name, values in other.attrs.items():
for value in values:
self.modify_attr(name, value, method)
def invalid_match(self, match):
return ValueError("%s: invalid match type")
def match_values(self, values, match='any', reverse=False):
"""Tester si cet objet a l'une des valeurs spécifiées (avec match=='any' qui est
la valeur par défaut)
"""
if not isseq(values): values = [values]
if reverse: Yes, No = False, True
else: Yes, No = True, False
if match == 'any':
for value in values:
if value in self._values: return Yes
return No
elif match == 'all':
for value in values:
if value not in self._values: return No
return Yes
else:
raise self.invalid_match(match)
def match_attrs(self, attrs, match='any', reverse=False):
"""Tester si cet objet a un des attributs correspondant aux valeurs spécifiées
(avec match=='any' qui est la valeur par défaut)
"""
if reverse: Yes, No = False, True
else: Yes, No = True, False
if match == 'any':
for name, value in attrs.items():
values = self._attrs.get(name, None)
if values is None: continue
if value in values: return Yes
return No
elif match == 'all':
for name, value in attrs.items():
values = self._attrs.get(name, None)
if values is None: return No
if value not in values: return No
return Yes
else:
raise self.invalid_match(match)
def _dump_id(self, indent):
print "%s%s" % (indent, self.id)
def _dump_values(self, indent, name):
print "%s %s: %s" % (indent, name, ' '.join(self.values))
def _dump_attrs(self, indent):
if self.attrs:
print "%s attrs:" % indent
for name, values in self.attrs.items():
if len(values) == 1:
print "%s %s=%s" % (indent, name, repr(values[0]))
else:
print "%s %s=(%s)" % (indent, name, ', '.join(map(repr, values)))
def dump(self, indent=''):
"""Afficher l'identifiant, les valeurs et les attributs de cet objet
"""
self._dump_id(indent)
self._dump_values(indent, self.VALUESNAME)
self._dump_attrs(indent)
class HostObject(GenericObject):
"""Un hôte
"""
OTYPE = 'host'
VALUESNAME = 'hosts'
ATTRPREFIX = 'host_'
@staticmethod
def genid(host):
"""Générer un identifiant à partir du nom d'hôte
e.g. MY-Host42.tld --> my_host42
"""
host = re.sub(r'\..*', '', host)
host = re.sub(r'[^a-zA-Z0-9]', '_', host)
host = host.lower()
return host
@staticmethod
def genib(id):
"""Générer un identifiant générique à partir d'un identifiant qualifié
e.g. my_host42 --> my_host
"""
if re.match(r'\d+$', id): return id
ib = re.sub(r'^(.*?)\d+$', r'\1', id)
return ib
def match_values(self, values, match='any', reverse=False):
if not isseq(values): values = [values]
if reverse: Yes, No = False, True
else: Yes, No = True, False
if match == 'any':
for value in values:
for host in self._values:
if match_host(value, host): return Yes
return No
elif match == 'all':
for value in values:
for host in self._values:
if not match_host(value, host): return No
return Yes
else:
raise self.invalid_match(match)
class ModuleObject(GenericObject):
"""Un module, uinst-allable
"""
OTYPE = 'module'
VALUESNAME = 'modules'
ATTRPREFIX = 'module_'
@staticmethod
def genid(module):
"""Générér un identifiant à partir du nom du module
e.g. MY-Module --> MY_Module
"""
module = re.sub(r'[^a-zA-Z0-9]', '_', module)
return module
class WobundleObject(GenericObject):
"""Un bundle Webobjects, woinst-allable
"""
OTYPE = 'wobundle'
VALUESNAME = 'wobundles'
ATTRPREFIX = 'wobundle_'
@staticmethod
def genid(wobundle):
"""Générér un identifiant à partir du nom du bundle
e.g. MY-App.woa --> MY_App
"""
wobundle = re.sub(r'\.(woa|framework)$', '', wobundle)
wobundle = re.sub(r'[^a-zA-Z0-9]', '_', wobundle)
return wobundle
class WebappObject(GenericObject):
"""Une webapp, toinst-allable
"""
OTYPE = 'webapp'
VALUESNAME = 'webapps'
ATTRPREFIX = 'webapp_'
@staticmethod
def genid(webapp):
"""Générér un identifiant à partir du nom de la webapp
e.g. MY-Webapp --> MY_Webapp
"""
webapp = re.sub(r'[^a-zA-Z0-9]', '_', webapp)
return webapp
class GenericLink(object):
"""Un lien générique
Un lien est valide dans un profil (propriété profile), a un type d'objets de
départ (propriété ftype), un type d'objet d'arrivée (propriété ttype), un
ensemble d'objets de départ (propriété fos), un ensemble d'objets d'arrivée
(propriété tos), et des attributs (propriété attrs).
"""
_profile = None
_ftype = None
_fos = None
_ttype = None
_tos = None
_attrs = None
def __init__(self, ftype, ttype):
self._ftype = ftype
self._fos = set()
self._ttype = ttype
self._tos = set()
self._attrs = {}
def is_defaults(self):
"""Cet objet contient-il les attributs par défaut pour les liens provenant du
type d'objet ftype
"""
fos = list(self._fos)
return len(fos) == 1 and fos[0] is None
def get_profile(self): return self._profile
def set_profile(self, profile): self._profile = profile
profile = property(get_profile, set_profile)
def get_ftype(self): return self._ftype
ftype = property(get_ftype)
def get_fos(self): return self._fos
fos = property(get_fos)
def add_fo(self, fo):
self._fos.add(fo)
def get_ttype(self): return self._ttype
ttype = property(get_ttype)
def get_tos(self): return self._tos
tos = property(get_tos)
def add_to(self, to):
self._tos.add(to)
def get_attrs(self): return self._attrs
attrs = property(get_attrs)
def get_attr(self, name):
"""Obtenir la valeur d'un attribut ou None si l'attribut n'existe pas
"""
return self._attrs.get(name, None)
def set_attr(self, name, value):
"""Ajouter une valeur à la liste des valeurs d'un attribut si elle n'y est pas
déjà.
"""
if name not in self._attrs: self._attrs[name] = []
if value not in self._attrs[name]: self._attrs[name].append(value)
def reset_attr(self, name, value=None):
"""Recommencer à zéro la valeur d'un attribut
"""
if name in self._attrs: del self._attrs[name]
if value is not None: self.set_attr(name, value)
def add_attr(self, name, value):
"""Ajouter une valeur à la liste des valeurs d'un attribut
"""
if name not in self._attrs: self._attrs[name] = []
self._attrs[name].append(value)
def del_attr(self, name, value):
"""Supprimer une valeur d'un attribut
"""
if name not in self._attrs: return
self._attrs[name].remove(value)
if not self._attrs[name]: del self._attrs[name]
def modify_attr(self, name, value, method='set'):
"""Méthode générique pour modifier les valeurs d'un attribut
method peut valoir set, reset, add, del
"""
if method == 'set': self.set_attr(name, value)
elif method == 'reset': self.reset_attr(name, value)
elif method == 'add': self.add_attr(name, value)
elif method == 'del': self.del_attr(name, value)
def merge_attrs(self, other, method='set'):
"""Fusionner dans ce lien les attributs d'un autre lien, avec la méthode
spécifiée.
"""
for name, values in other.attrs.items():
for value in values:
self.modify_attr(name, value, method)
def invalid_match(self, match):
return ValueError("%s: invalid match type")
def match_profiles(self, profiles):
"""Tester si ce lien est dans l'un des profils spécifiés
Si profiles == None, alors l'utilisateur ne demande aucun profil en
particulier. Dans ce cas, la réponse est OUI.
Si profiles == [], alors l'utilisateur demande tous les liens pour
lesquels aucun profil n'est défini. Dans ce cas, la réponse est OUI si
ce lien ne définit aucun profil.
Sinon, profiles est une liste des profils recherchés. Dans ce cas, la
réponse n'est OUI que si le profil de ce lien fait partie des profils
spécifiés.
"""
if profiles is None: return True
if not isseq(profiles): profiles = [profiles]
if profiles == []: return self._profile is None
for profile in profiles:
if profile == self._profile: return True
return False
def match_fos(self, fos, match='any', reverse=False):
"""Tester si ce lien a l'un des objets spécifiés dans ses objets de départ (avec
match=='any' qui est la valeur par défaut)
"""
if not isseq(fos): fos = [fos]
if reverse: Yes, No = False, True
else: Yes, No = True, False
if match == 'any':
for fo in fos:
if fo in self._fos: return Yes
return No
elif match == 'all':
for fo in fos:
if fo not in self._fos: return No
return Yes
else:
raise self.invalid_match(match)
def match_tos(self, tos, match='any', reverse=False):
"""Tester si ce lien a l'un des objets spécifiés dans ses objets d'arrivée (avec
match=='any' qui est la valeur par défaut)
"""
if not isseq(tos): tos = [tos]
if reverse: Yes, No = False, True
else: Yes, No = True, False
if match == 'any':
for to in tos:
if to in self._tos: return Yes
return No
elif match == 'all':
for to in tos:
if to not in self._tos: return No
return Yes
else:
raise self.invalid_match(match)
def match_attrs(self, attrs, match='any', reverse=False):
"""Tester si ce lien a un des attributs correspondant aux valeurs spécifiées
(avec match=='any' qui est la valeur par défaut)
"""
if reverse: Yes, No = False, True
else: Yes, No = True, False
if match == 'any':
for name, value in attrs.items():
values = self._attrs.get(name, None)
if values is None: continue
if value in values: return Yes
return No
elif match == 'all':
for name, value in attrs.items():
values = self._attrs.get(name, None)
if values is None: return No
if value not in values: return No
return Yes
else:
raise self.invalid_match(match)
def _dump_profile(self, indent):
profile = self.profile or 'ALL'
print "%s profile: %s" % (indent, profile)
def _dump_fos(self, indent):
fos = [fo or '(default)' for fo in self.fos]
print "%s from: %s" % (indent, ' '.join(fos))
def _dump_tos(self, indent):
print "%s to: %s" % (indent, ' '.join(self.tos))
def _dump_attrs(self, indent):
if self.attrs:
print "%s attrs:" % indent
for name, values in self.attrs.items():
if len(values) == 1:
print "%s %s=%s" % (indent, name, repr(values[0]))
else:
print "%s %s=(%s)" % (indent, name, ', '.join(map(repr, values)))
def dump(self, indent=''):
"""Afficher le profil, les objets de départ, les objets d'arrivée, et les
attributs de ce lien
"""
print "%slink" % indent
self._dump_profile(indent)
self._dump_fos(indent)
self._dump_tos(indent)
self._dump_attrs(indent)
class UinstLink(GenericLink):
"""Un module déployé sur un hôte avec le type uinst:rsync
"""
def __init__(self):
super(UinstLink, self).__init__('module', 'host')
def dump(self, indent=''):
print "%suinst" % indent
self._dump_profile(indent)
self._dump_fos(indent)
self._dump_tos(indent)
self._dump_attrs(indent)
class RuinstLink(GenericLink):
"""Un module déployé sur un hôte avec ruinst
"""
def __init__(self):
super(RuinstLink, self).__init__('module', 'host')
def dump(self, indent=''):
print "%sruinst" % indent
self._dump_profile(indent)
self._dump_fos(indent)
self._dump_tos(indent)
self._dump_attrs(indent)
class RwoinstBundleLink(GenericLink):
"""Un bundle déployé sur un hôte avec rwoinst
"""
def __init__(self):
super(RwoinstBundleLink, self).__init__('wobundle', 'host')
def dump(self, indent=''):
print "%srwoinst bundle" % indent
self._dump_profile(indent)
self._dump_fos(indent)
self._dump_tos(indent)
self._dump_attrs(indent)
class RwoinstWebresLink(GenericLink):
"""Les resources web d'un bundle déployées sur un hôte avec rwoinst
"""
def __init__(self):
super(RwoinstWebresLink, self).__init__('wobundle', 'host')
def dump(self, indent=''):
print "%srwoinst webres" % indent
self._dump_profile(indent)
self._dump_fos(indent)
self._dump_tos(indent)
self._dump_attrs(indent)
class RtoinstLink(GenericLink):
"""Une webapp déployée sur un hôte avec rtoinst
"""
def __init__(self):
super(RtoinstLink, self).__init__('webapp', 'host')
def dump(self, indent=''):
print "%srtoinst" % indent
self._dump_profile(indent)
self._dump_fos(indent)
self._dump_tos(indent)
self._dump_attrs(indent)
class UNDEF(object):
__repr__ = __string__ = lambda self: 'UNDEF'
UNDEF = UNDEF()
class Database(object):
"""La base de données des objets et des liens
"""
_default_profile = None
_default_domain = None
_objects_ids_otype = None
_objects_classes = None
_links_ltype = None
_links_classes = None
def __init__(self):
self._objects_ids_otype = {}
self._objects_classes = {}
self._links_ltype = {}
self._links_classes = {}
def has_default_profile(self):
"""Vérifier si un profil par défaut a été spécifié
"""
return self._default_profile is not None
def get_default_profile(self):
"""Obtenir le profil par défaut, ou None si aucun profil par défaut n'a été
défini
"""
return self._default_profile
def set_default_profile(self, profile):
"""Spécifier le profil par défaut
Si un lien est spécifié sans profil, il est réputé défini dans le
profil par défaut.
"""
self._default_profile = profile or None
default_profile = property(get_default_profile, set_default_profile)
def has_default_domain(self):
"""Vérifier si un domaine par défaut a été spécifié
"""
return self._default_domain is not None
def get_default_domain(self):
"""Obtenir le domaine par défaut, ou None si aucun domaine par défaut n'a été
défini
"""
return self._default_domain
def set_default_domain(self, domain):
"""Spécifier le domaine par défaut
Si un hôte est spécifié sans domaine, il est réputé défini avec le
domaine par défaut.
"""
if domain is not None:
#XXX si le domaine a été corrigé, l'indiquer en warning
if domain.startswith('.'): domain = domain[1:]
if domain.endswith('.'): domain = domain[:-1]
self._default_domain = domain or None
default_domain = property(get_default_domain, set_default_domain)
def register_object(self, otype, oclass=None):
"""Enregistrer un type d'objet et la classe utilisée pour l'instancier.
Par défaut, le classe utilisé est GenericObject.
"""
if not self._objects_ids_otype.has_key(otype):
self._objects_ids_otype[otype] = {}
if oclass is None: oclass = GenericObject
self._objects_classes[otype] = oclass
def get_known_otypes(self):
"""Obtenir la liste des types d'objets connus
"""
return self._objects_ids_otype.keys()
known_otypes = property(get_known_otypes)
def get_objects(self, otype, value=UNDEF, attrs=UNDEF):
"""Obtenir tous les objets définis du type spécifié ou None si le type d'objets
est invalide. Si value ou attrs sont définis, filtrer sur ces valeurs.
"""
if otype is None:
#XXX faire un traitement récursif avec get_known_otypes
objects = []
for objects_ids in self._objects_ids_otype.values():
objects.extend(objects_ids.values())
else:
objects = self._objects_ids_otype.get(otype, None)
if objects is None: return None
objects = [objects[id] for id in objects if id is not None]
return [
object for object in objects if
(value is UNDEF or object.match_values(value)) and
(attrs is UNDEF or object.match_attrs(attrs))
]
def has_object(self, otype, id):
"""Vérifier si l'objet avec l'identifiant spécifié est défini
"""
objects = self._objects_ids_otype.get(otype, None)
if objects is None: return False
return objects.has_key(id)
def get_object(self, otype, id, create=True):
"""Obtenir l'objet avec l'identifiant spécifié, ou None si l'objet n'existe pas.
Si create==True, l'objet est créé s'il n'existe pas, et cette méthode ne
retourne pas None.
"""
objects = self._objects_ids_otype.get(otype, None)
if objects is None: return None
object = objects.get(id, None)
if object is None and create:
object_class = self._objects_classes.get(otype, None)
if object_class is not None:
object = object_class(id)
self._objects_ids_otype[otype][id] = object
return object
def register_link(self, ltype, lclass):
"""Enregister un type de lien et la classe utilisée pour l'instancier.
Il n'y a pas de classe par défaut, il faut absolument spécifier une
classe valide.
"""
if not self._links_ltype.has_key(ltype):
self._links_ltype[ltype] = []
self._links_classes[ltype] = lclass
def get_known_ltypes(self):
"""Obtenir la liste des types de liens connus
"""
return self._links_ltype.keys()
known_ltypes = property(get_known_ltypes)
def get_links(self, ltype, profile=UNDEF, fo=UNDEF, to=UNDEF, attrs=UNDEF, create=False):
"""Obtenir les liens correspondant aux critères.
XXX compléter la doc
"""
#XXX si ltype is None, faire un traitement récursif avec get_known_ltypes
links = self._links_ltype.get(ltype, None)
if links is None: return None
found = None
for link in links:
if (profile is UNDEF or link.match_profiles(profile)) \
and (fo is UNDEF or link.match_fos(fo)) \
and (to is UNDEF or link.match_tos(to)) \
and (attrs is UNDEF or link.match_attrs(attrs)):
if found is None: found = []
found.append(link)
if found is not None: return found
if not create: return []
# ici, create==True
if profile is UNDEF: profile = None
link = self._links_classes[ltype]()
link.profile = profile
if fo is not UNDEF:
if isseq(fo): fos = fo
else: fos = [fo]
for fo in fos:
link.add_fo(fo)
if to is not UNDEF:
if isseq(to): tos = to
else: tos = [to]
for to in tos:
link.add_to(to)
if attrs is not UNDEF:
for name, value in attrs.items():
link.set_attr(name, value)
links.append(link)
return [link]
################################################################################
# Analyse des fichiers de configuration
class EOL(object):
__repr__ = __string__ = lambda self: 'EOL'
EOL = EOL()
class EOF(object):
__repr__ = __string__ = lambda self: 'EOF'
EOF = EOF()
class Lexer(object):
file = None
lexems = None
_inf = None
_lcount = None
_line = None
def __init__(self, file, parse=True):
self.file = file
if parse: self.parse()
def next_line(self):
line = self._inf.readline()
if line == '': return None
if line.endswith("\r\n"): line = line[:-2]
elif line.endswith("\n"): line = line[:-1]
elif line.endswith("\r"): line = line[:-1]
self._lcount += 1
self._line = line
return line
def is_empty(self): return self._line == ''
def isa_comment(self): return self._line[:1] == '#'
def isa_squote(self): return self._line[:1] == "'"
def isa_dquote(self): return self._line[:1] == '"'
RE_SPACE = re.compile(r'\s+')
RE_COMMENT = re.compile(r'#.*')
def parse_ws(self):
mo = self.RE_SPACE.match(self._line)
if mo is not None:
self._line = self._line[mo.end(0):]
mo = self.RE_COMMENT.match(self._line)
if mo is not None:
self._line = self._line[mo.end(0):]
def isa_space(self): return self.RE_SPACE.match(self._line) is not None
def isa_comment(self): return self.RE_COMMENT.match(self._line) is not None
RE_SQUOTE = re.compile(r"'")
def parse_sstring(self):
slos = self._lcount
lexem = ''
self._line = self._line[1:]
mo = self.RE_SQUOTE.search(self._line)
while mo is None:
lexem += self._line
if self.next_line() is None:
raise ValueError("unterminated quoted string starting at line %i" % slos)
lexem += "\n"
mo = self.RE_SQUOTE.search(self._line)
lexem += self._line[0:mo.start(0)]
self._line = self._line[mo.end(0):]
return lexem
RE_DQUOTE = re.compile(r'"')
def parse_dstring(self):
slos = self._lcount
lexem = ''
self._line = self._line[1:]
mo = self.RE_DQUOTE.search(self._line)
while mo is None:
lexem += self._line
if self.next_line() is None:
raise ValueError("unterminated double-quoted string starting at line %i" % slos)
lexem += "\n"
mo = self.RE_DQUOTE.search(self._line)
lexem += self._line[0:mo.start(0)]
self._line = self._line[mo.end(0):]
lexem = lexem.replace('\\"', '"')
lexem = lexem.replace("\\'", "'")
lexem = lexem.replace('\\\\', '\\')
return lexem
RE_EOS = re.compile(r'''\s|(?<!\\)['"]''')
def parse_string(self):
mo = self.RE_EOS.search(self._line)
if mo is not None:
lexem = self._line[0:mo.start(0)]
self._line = self._line[mo.start(0):]
else:
lexem = self._line
self._line = ''
lexem = lexem.replace('\\"', '"')
lexem = lexem.replace("\\'", "'")
lexem = lexem.replace('\\\\', '\\')
return lexem
def parse(self):
if self.lexems is not None: return self.lexems
lexems = self.lexems = []
self._inf = open(self.file, 'rb')
self._lcount = 0
self._line = ''
try:
SOL = False
while True:
# Ignorer lignes vides et commentaires
self.parse_ws()
stop = False
while self.is_empty():
if SOL:
lexems.append(EOL)
SOL = False
if self.next_line() is None:
stop = True
break
self.parse_ws()
if stop: break
SOL = True
# Construire une chaine
lexem = ''
while True:
if self.is_empty(): break
elif self.isa_space(): break
if self.isa_squote(): lexem += self.parse_sstring()
elif self.isa_dquote(): lexem += self.parse_dstring()
else: lexem += self.parse_string()
lexems.append(lexem)
lexems.append(EOF)
return lexems
finally:
self._inf.close()
self._inf = None
return lexems
def get_predicates(self):
predicates = []
predicate = []
for lexem in self.parse():
if lexem is EOF: break
elif lexem is EOL:
predicates.append(predicate)
predicate = []
else:
predicate.append(lexem)
return predicates
RE_NAMETYPE = re.compile(r'(\S+):(\w+)$')
def split_namev(arg):
"spliter name[:type][method][=value]"
if '=' in arg:
name, value = arg.split('=', 1)
else:
name = arg
value = None
if name.endswith('%'):
name = name[:-1]
method = 'reset'
elif name.endswith('+'):
name = name[:-1]
method = 'add'
elif name.endswith('-'):
name = name[:-1]
method = 'del'
else:
method = 'set'
mo = RE_NAMETYPE.match(name)
if mo is not None:
name, type = mo.groups()
else:
type = None
return name, value, type, method
def split_nvalue(arg):
"spliter [name=]value"
if '=' in arg:
name, value = arg.split('=', 1)
else:
name = None
value = arg
return name, value
def split_nlist(arg):
"spliter [name=]values"
if '=' in arg:
name, values = arg.split('=', 1)
values = values.split(',')
else:
name = None
values = arg.split(',')
return name, values
class Parser(object):
db = None
groups = None
attr_otype = None
commands = None
def __init__(self, db=None):
if db is None: db = Database()
self.db = db
self.groups = {}
self.commands = {
'default_profile': self.handle_default_profile,
'default_domain': self.handle_default_domain,
'group': self.handle_group,
'attr': self.handle_attr,
}
self.__setup_hosts()
self.__setup_uinst()
self.__setup_woinst()
self.__setup_toinst()
self.__setup_query()
def parse(self, predicates):
for p in predicates:
cmd = p[0]
args = p[1:]
if cmd in self.commands: self.commands[cmd](*p)
else: raise ValueError("%s: unknown command" % cmd)
return self
def register_command(self, name, method):
self.commands[name] = method
############################################################################
def reset_group(self, otype):
self.groups[otype]['current'] = set()
def reset_group_maybe(self, otype):
if self.groups[otype]['type'] in ('defaults', 'once'):
self.reset_group(otype)
self.groups[otype]['type'] = 'once'
def add_group(self, otype, id):
self.groups[otype]['current'].add(id)
def handle_group(self, cmd, *args):
"""group otype [gtype]
gtype peut valoir:
- defaults: revenir à l'état initial, permettant de spécifier les
attributs et liens pour tous les hôtes définis à partir de cette ligne
- once: un nouveau groupe est défini à chaque nouvelle définition
d'objet. en d'autres termes, ne font partie du groupe que les objets
faisant partie d'une même définition. c'est la valeur par défaut
- until: définir un groupe qui va jusqu'à la prochaine définition de
groupe ou de lien.
"""
otype = args[0:1] and args[0] or 'host'
if otype not in self.groups:
raise ValueError('%s: invalid object type' % otype)
gtype = args[1:2] and args[1] or 'once'
self.groups[otype]['type'] = gtype
if gtype == 'defaults':
self.groups[otype]['current'] = set([None])
elif gtype in ('once', 'until'):
self.groups[otype]['current'] = set()
else:
raise ValueError('%s: invalid group type' % gtype)
self.attr_otype = otype
############################################################################
def handle_attr(self, cmd, *args):
otype = self.attr_otype
assert otype is not None, "attr_otype should not be None"
for nv in args:
name, value, type, method = split_namev(nv)
if value is None:
method = 'set'
value = '1'
elif type == 'path':
value = path.expanduser(value)
for id in self.groups[otype]['current']:
self.db.get_object(otype, id).modify_attr(name, value, method)
############################################################################
def handle_default_profile(self, cmd, *args):
if not args or not args[0]: profile = None
else: profile = args[0]
self.db.default_profile = profile
############################################################################
def handle_default_domain(self, cmd, *args):
if not args or not args[0]: domain = None
else: domain = args[0]
self.db.default_domain = domain
############################################################################
def __setup_hosts(self):
self.db.register_object('host', HostObject)
self.db.get_object('host', None)
self.groups['host'] = {}
self.handle_group('group', 'host', 'defaults')
self.register_command('host', self.handle_host)
def __fix_host(self, host):
if host.endswith('.'):
host = host[:-1]
elif '.' not in host and self.db.has_default_domain():
host = '%s.%s' % (host, self.db.default_domain)
return host
def handle_host(self, otype, *args):
"""host [id=]host,...
host -b [id=]basedir
host -d [id=]dirspec
"""
AP = ArgumentParser()
AP.add_argument('-b', '--basedir', action='append', dest='basedirs')
AP.add_argument('-d', '--dirspec', action='append', dest='dirspecs')
AP.add_argument('nvss', nargs='*')
o = AP.parse_args(args)
# construire la liste des hôtes à traiter
nvss = []
if o.basedirs is not None:
for pb in o.basedirs:
p, b = split_nvalue(pb)
if p is None: prefix = ''
else: prefix = p + '='
basedir = path.expanduser(b)
nvss.extend([prefix + path.abspath(path.join(basedir, name))
for name in os.listdir(basedir)
if path.isdir(path.join(basedir, name))])
if o.dirspecs is not None:
for pd in o.dirspecs:
p, d = split_nvalue(pd)
if p is None: prefix = ''
else: prefix = p + '='
dirspec = path.expanduser(d)
nvss.extend([prefix + path.abspath(dir)
for dir in glob(dirspec)
if path.isdir(dir)])
nvss.extend(o.nvss)
# préparer la mise à jour du groupe courant
self.reset_group_maybe(otype)
self.attr_otype = otype
defaulto = self.db.get_object(otype, None)
# traiter les hôtes
for nvs in nvss:
name, values = split_nlist(nvs)
for value in values:
dir = None
if value:
if '/' in value:
dir, value = path.split(path.abspath(path.expanduser(value)))
value = self.__fix_host(value)
if name:
o = self.db.get_object(otype, name)
if value: o.add_value(value)
o.merge_attrs(defaulto)
if dir is not None: o.set_attr('dir', dir)
self.add_group(otype, name)
else:
id = HostObject.genid(value)
o = self.db.get_object(otype, id)
o.add_value(value)
o.merge_attrs(defaulto)
if dir is not None: o.set_attr('dir', dir)
self.add_group(otype, id)
ib = HostObject.genib(id)
o = self.db.get_object(otype, ib)
o.add_value(value)
o.merge_attrs(defaulto)
if dir is not None: o.set_attr('dir', dir)
self.add_group(otype, ib)
############################################################################
def __setup_uinst(self):
self.db.register_object('module', ModuleObject)
self.db.register_link('uinst', UinstLink)
self.db.register_link('ruinst', RuinstLink)
self.db.get_object('module', None)
self.groups['module'] = {}
self.handle_group('group', 'module', 'defaults')
self.register_command('module', self.handle_module)
self.register_command('uinst', self.handle_xuinst)
self.register_command('ruinst', self.handle_xuinst)
def __fix_module(self, module):
return module
def handle_module(self, otype, *args):
"""module [id=]path/to/module
module -b [id=]basedir
module -d [id=]dirspec
"""
AP = ArgumentParser()
AP.add_argument('-b', '--basedir', action='append', dest='basedirs')
AP.add_argument('-d', '--dirspec', action='append', dest='dirspecs')
AP.add_argument('nvss', nargs='*')
o = AP.parse_args(args)
# construire la liste des modules à traiter
nvss = []
if o.basedirs is not None:
for pb in o.basedirs:
p, b = split_nvalue(pb)
if p is None: prefix = ''
else: prefix = p + '='
basedir = path.expanduser(b)
nvss.extend([prefix + path.abspath(path.join(basedir, name))
for name in os.listdir(basedir)
if path.isdir(path.join(basedir, name))])
if o.dirspecs is not None:
for pd in o.dirspecs:
p, d = split_nvalue(pd)
if p is None: prefix = ''
else: prefix = p + '='
dirspec = path.expanduser(d)
nvss.extend([prefix + path.abspath(dir)
for dir in glob(dirspec)
if path.isdir(dir)])
nvss.extend(o.nvss)
# préparer la mise à jour du groupe courant
self.reset_group_maybe(otype)
self.attr_otype = otype
defaulto = self.db.get_object(otype, None)
# traiter les modules
for nvs in nvss:
name, values = split_nlist(nvs)
for value in values:
dir = None
valuep = None
if value:
if '/' in value:
dir, value = path.split(path.abspath(path.expanduser(value)))
value = self.__fix_module(value)
if dir is not None:
valuep = path.join(dir, value)
if name:
o = self.db.get_object(otype, name)
if value: o.add_value(value)
o.merge_attrs(defaulto)
if dir is not None: o.set_attr('dir', dir)
if valuep is not None: o.set_attr('path', valuep)
self.add_group(otype, name)
else:
id = ModuleObject.genid(value)
o = self.db.get_object(otype, id)
if value: o.add_value(value)
o.merge_attrs(defaulto)
if dir is not None: o.set_attr('dir', dir)
if valuep is not None: o.set_attr('path', valuep)
self.add_group(otype, id)
def handle_xuinst(self, ltype, *args):
"""uinst -p profile attrs*
ruinst -p profile attrs*
usage de l'option -p:
pas d'option -p
définir les liens dans le profil par défaut. équivalent à -p '' s'il
n'y a pas de profil par défaut.
-p ''
définir les liens en ne les associant à aucun profil
-p PROFILE,...
définir les liens dans les profils spécifiés
"""
AP = ArgumentParser()
AP.add_argument('-p', '--profile', action='append', dest='profiles')
AP.add_argument('nvss', nargs='*')
o = AP.parse_args(args)
if o.profiles is None: profiles = [self.db.default_profile]
else: profiles = flatten(o.profiles)
if profiles == []: profiles = [None]
for profile in profiles:
# préparer la mise à jour du groupe courant
currentls = self.db.get_links(ltype, profile, fo=self.groups['module']['current'], create=True)
if profile is not None:
globalls = self.db.get_links(ltype, None, fo=None, create=True)
for currentl in currentls:
for globall in globalls:
currentl.merge_attrs(globall)
defaultls = self.db.get_links(ltype, profile, fo=None, create=True)
for currentl in currentls:
for defaultl in defaultls:
currentl.merge_attrs(defaultl)
defaultl = defaultls[0]
# traiter les liens
for nvs in o.nvss:
name, value, type, method = split_namev(nvs)
if name == defaultl.ttype:
# définir des destinations du lien
if value is not None:
tos = split_nlist(nvs)[1]
for to in tos:
for currentl in currentls:
currentl.add_to(to)
else:
# définir un attribut du lien
#name, value, type, method = split_namev(nv)
if value is None:
method = 'set'
value = '1'
elif type == 'path':
value = path.expanduser(value)
for currentl in currentls:
currentl.modify_attr(name, value, method)
############################################################################
def __setup_woinst(self):
self.db.register_object('wobundle', WobundleObject)
self.db.register_link('rwoinst-bundle', RwoinstBundleLink)
self.db.register_link('rwoinst-webres', RwoinstWebresLink)
self.db.get_object('wobundle', None)
self.groups['wobundle'] = {}
self.handle_group('group', 'wobundle', 'defaults')
self.register_command('wobundle', self.handle_wobundle)
self.register_command('rwoinst', self.handle_rwoinst)
def __fix_wobundle(self, wobundle):
if path.splitext(wobundle)[1] not in ('.woa', '.framework'):
wobundle = wobundle + '.woa'
return wobundle
def handle_wobundle(self, otype, *args):
"""wobundle [id=]path/to/wobundle
wobundle -b [id=]basedir
wobundle -d [id=]dirspec
"""
AP = ArgumentParser()
AP.add_argument('-b', '--basedir', action='append', dest='basedirs')
AP.add_argument('-d', '--dirspec', action='append', dest='dirspecs')
AP.add_argument('nvss', nargs='*')
o = AP.parse_args(args)
# construire la liste des wobundles à traiter
nvss = []
if o.basedirs is not None:
for pb in o.basedirs:
p, b = split_nvalue(pb)
if p is None: prefix = ''
else: prefix = p + '='
basedir = path.expanduser(b)
nvss.extend([prefix + path.abspath(path.join(basedir, name))
for name in os.listdir(basedir)
if path.isdir(path.join(basedir, name)) and path.splitext(name)[1] in ('.woa', '.framework')])
if o.dirspecs is not None:
for pd in o.dirspecs:
p, d = split_nvalue(pd)
if p is None: prefix = ''
else: prefix = p + '='
dirspec = path.expanduser(d)
nvss.extend([prefix + path.abspath(dir)
for dir in glob(dirspec)
if path.isdir(dir) and path.splitext(dir)[1] in ('.woa', '.framework')])
nvss.extend(o.nvss)
# préparer la mise à jour du groupe courant
self.reset_group_maybe(otype)
self.attr_otype = otype
defaulto = self.db.get_object(otype, None)
# traiter les wobundles
for nvs in nvss:
name, values = split_nlist(nvs)
for value in values:
dir = None
valuep = None
if value:
if '/' in value:
dir, value = path.split(path.abspath(path.expanduser(value)))
value = self.__fix_wobundle(value)
if dir is not None:
valuep = path.join(dir, value)
if name:
o = self.db.get_object(otype, name)
if value: o.add_value(value)
o.merge_attrs(defaulto)
if dir is not None: o.set_attr('dir', dir)
if valuep is not None: o.set_attr('path', valuep)
self.add_group(otype, name)
else:
id = WobundleObject.genid(value)
o = self.db.get_object(otype, id)
if value: o.add_value(value)
o.merge_attrs(defaulto)
if dir is not None: o.set_attr('dir', dir)
if valuep is not None: o.set_attr('path', valuep)
self.add_group(otype, id)
def handle_rwoinst(self, ltype, *args):
"""rwoinst bundle -p profile attrs*
rwoinst webres -p profile attrs*
"""
AP = ArgumentParser()
AP.add_argument('-p', '--profile', action='append', dest='profiles')
AP.add_argument('stype', nargs=1)
AP.add_argument('nvss', nargs='*')
o = AP.parse_args(args)
if o.profiles is None: profiles = [self.db.default_profile]
else: profiles = flatten(o.profiles)
if profiles == []: profiles = [None]
ltype = "%s-%s" % (ltype, o.stype[0])
for profile in profiles:
# préparer la mise à jour du groupe courant
currentls = self.db.get_links(ltype, profile, fo=self.groups['wobundle']['current'], create=True)
if profile is not None:
globalls = self.db.get_links(ltype, None, fo=None, create=True)
for currentl in currentls:
for globall in globalls:
currentl.merge_attrs(globall)
defaultls = self.db.get_links(ltype, profile, fo=None, create=True)
for currentl in currentls:
for defaultl in defaultls:
currentl.merge_attrs(defaultl)
defaultl = defaultls[0]
# traiter les liens
for nvs in o.nvss:
name, value, type, method = split_namev(nvs)
if name == defaultl.ttype:
# définir des destinations du lien
if value is not None:
tos = split_nlist(nvs)[1]
for to in tos:
for currentl in currentls:
currentl.add_to(to)
else:
# définir un attribut du lien
#name, value, type, method = split_namev(nv)
if value is None:
method = 'set'
value = '1'
elif type == 'path':
value = path.expanduser(value)
for currentl in currentls:
currentl.modify_attr(name, value, method)
############################################################################
def __setup_toinst(self):
self.db.register_object('webapp', WebappObject)
self.db.register_link('rtoinst', RtoinstLink)
self.db.get_object('webapp', None)
self.groups['webapp'] = {}
self.handle_group('group', 'webapp', 'defaults')
self.register_command('webapp', self.handle_webapp)
self.register_command('rtoinst', self.handle_rtoinst)
def __fix_webapp(self, webapp):
return webapp
def handle_webapp(self, otype, *args):
"""webapp [id=]path/to/webapp
webapp -b [id=]basedir
webapp -d [id=]dirspec
"""
AP = ArgumentParser()
AP.add_argument('-b', '--basedir', action='append', dest='basedirs')
AP.add_argument('-d', '--dirspec', action='append', dest='dirspecs')
AP.add_argument('nvss', nargs='*')
o = AP.parse_args(args)
# construire la liste des webapps à traiter
nvss = []
if o.basedirs is not None:
for pb in o.basedirs:
p, b = split_nvalue(pb)
if p is None: prefix = ''
else: prefix = p + '='
basedir = path.expanduser(b)
nvss.extend([prefix + path.abspath(path.join(basedir, name))
for name in os.listdir(basedir)
if path.isdir(path.join(basedir, name))])
if o.dirspecs is not None:
for pd in o.dirspecs:
p, d = split_nvalue(pd)
if p is None: prefix = ''
else: prefix = p + '='
dirspec = path.expanduser(d)
nvss.extend([prefix + path.abspath(dir)
for dir in glob(dirspec)
if path.isdir(dir)])
nvss.extend(o.nvss)
# préparer la mise à jour du groupe courant
self.reset_group_maybe(otype)
self.attr_otype = otype
defaulto = self.db.get_object(otype, None)
# traiter les webapps
for nvs in nvss:
name, values = split_nlist(nvs)
for value in values:
dir = None
valuep = None
if value:
if '/' in value:
dir, value = path.split(path.abspath(path.expanduser(value)))
value = self.__fix_webapp(value)
if dir is not None:
valuep = path.join(dir, value)
if name:
o = self.db.get_object(otype, name)
if value: o.add_value(value)
o.merge_attrs(defaulto)
if dir is not None: o.set_attr('dir', dir)
if valuep is not None: o.set_attr('path', valuep)
self.add_group(otype, name)
else:
id = WebappObject.genid(value)
o = self.db.get_object(otype, id)
if value: o.add_value(value)
o.merge_attrs(defaulto)
if dir is not None: o.set_attr('dir', dir)
if valuep is not None: o.set_attr('path', valuep)
self.add_group(otype, id)
def handle_rtoinst(self, ltype, *args):
"""rtoinst -p profile attrs*
"""
AP = ArgumentParser()
AP.add_argument('-p', '--profile', action='append', dest='profiles')
AP.add_argument('nvss', nargs='*')
o = AP.parse_args(args)
if o.profiles is None: profiles = [self.db.default_profile]
else: profiles = flatten(o.profiles)
if profiles == []: profiles = [None]
for profile in profiles:
# préparer la mise à jour du groupe courant
currentls = self.db.get_links(ltype, profile, fo=self.groups['webapp']['current'], create=True)
if profile is not None:
globalls = self.db.get_links(ltype, None, fo=None, create=True)
for currentl in currentls:
for globall in globalls:
currentl.merge_attrs(globall)
defaultls = self.db.get_links(ltype, profile, fo=None, create=True)
for currentl in currentls:
for defaultl in defaultls:
currentl.merge_attrs(defaultl)
defaultl = defaultls[0]
# traiter les liens
for nvs in o.nvss:
name, value, type, method = split_namev(nvs)
if name == defaultl.ttype:
# définir des destinations du lien
if value is not None:
tos = split_nlist(nvs)[1]
for to in tos:
for currentl in currentls:
currentl.add_to(to)
else:
# définir un attribut du lien
#name, value, type, method = split_namev(nv)
if value is None:
method = 'set'
value = '1'
elif type == 'path':
value = path.expanduser(value)
for currentl in currentls:
currentl.modify_attr(name, value, method)
############################################################################
def __setup_query(self):
self.register_command('query', self.handle_query)
def handle_query(self, cmd, *args):
"""query config
"""
print "WARNING: query not implemented" #XXX
################################################################################
# Programme principal
VALID_QUERY_TYPES = ('object', 'source', 'dest', 'link')
def __fix_query_type(query_type):
if query_type is None: return None
query_type = query_type.lower()
if query_type == 'o': query_type = 'object'
elif query_type == 's': query_type = 'source'
elif query_type == 'd': query_type = 'dest'
elif query_type == 'l': query_type = 'link'
if query_type not in VALID_QUERY_TYPES:
raise ValuerError("%s: invalid query type")
return query_type
def __resolve_format(o, query_type):
format = o.format or 'shell'
ovars, lvars = o.object_vars, o.link_vars
if not ovars and not lvars:
if query_type == 'object':
ovars = True
lvars = False
elif query_type in ('source', 'dest'):
ovars = True
lvars = True
elif query_type == 'link':
ovars = False
lvars = True
ofunc, lfunc = o.object_func, o.link_func
avars = flatten(o.vars) or None
return format, ovars, lvars, ofunc, lfunc, avars
def print_var(name, values, format, avars, is_values=False):
if avars is not None and name not in avars: return
if not isseq(values): values = [values]
if format == 'shell':
params = (name, " ".join(qshell(values)))
if len(values) <= 1: print "%s=%s" % params
else: print "%s=(%s)" % params
return
if not is_values: return
if format == 'lines':
for value in values:
print value
elif format == 'spaces':
print " ".join(values)
elif format == 'path':
print ":".join(values)
elif format == 'comma':
print ",".join(values)
else:
raise ValueError("%s: invalid format" % format)
def print_objects(objects, format, ovars, ofunc, avars):
if objects is None: return
if not ovars: return
for o in objects:
if o.is_defaults(): continue
print_var('otype', o.OTYPE, format, avars)
print_var(o.VALUESNAME, o.values, format, avars, True)
for name, values in o.attrs.items():
print_var('%s%s' % (o.ATTRPREFIX, name), values, format, avars)
if ofunc and format == 'shell':
print ofunc
def run_qdd(o):
# fichier de configuration
confname = o.confname or 'deploy'
if '/' in confname:
conf = path.abspath(confname)
elif path.exists('%s.conf' % confname):
conf = '%s.conf' % confname
else:
conf = confname
if not conf.endswith('.conf'): conf = '%s.conf' % conf
user_conf = path.join(path.expanduser(USER_CONFDIR), conf)
system_conf = path.join(SYSTEM_CONFDIR, conf)
if path.exists(user_conf): conf = user_conf
elif path.exists(system_conf): conf = system_conf
lexer = Lexer(conf)
db = Database()
if o.action == 'nop':
pass
elif o.action == 'dump':
predicates = lexer.get_predicates()
parser = Parser(db).parse(predicates)
print "=== predicates"
for p in predicates:
print ' '.join(map(repr, p))
otypes = db.known_otypes[:]
otypes.remove('host')
otypes.insert(0, 'host')
for otype in otypes:
objects = db.get_objects(otype)
if objects:
print "=== %s" % otype
for o in objects:
o.dump()
for ltype in db.known_ltypes:
links = db.get_links(ltype)
if links:
print "=== %s" % ltype
for link in links:
if link.is_defaults(): continue
link.dump(" ")
elif o.action == 'query':
parser = Parser(db).parse(lexer.get_predicates())
query_type, object_type, link_type = __fix_query_type(o.query_type), o.object_type, o.link_type
if query_type is None and object_type is None and link_type is None:
query_type = 'dest'
object_type = 'host'
if o.profile is not None: profile = o.profile or None
else: profile = UNDEF
args = o.args or UNDEF
format, ovars, lvars, ofunc, lfunc, avars = __resolve_format(o, query_type)
if query_type == 'object':
objects = db.get_objects(object_type, args)
print_objects(objects, format, ovars, ofunc, avars)
elif query_type == 'source':
tos, attrs = part_filter_args(args)
links = db.get_links(link_type, profile, to=tos, attrs=attrs)
links = filter_links(object_type)
print_links(links, format, ovars, lvars, ofunc, lfunc, avars)
elif query_type == 'dest':
fos, attrs = part_filter_args(args)
links = db.get_links(link_type, profile, fo=fos, attrs=attrs)
links = filter_links(object_type)
print_links(links, format, ovars, lvars, ofunc, lfunc, avars)
elif query_type == 'link':
pass #XXX
if __name__ == '__main__':
from argparse import ArgumentParser, HelpFormatter
if sys.argv[1:2] == ['--compat']:
# Avec l'argument --compat, désactiver la classe FancyHelpFormatter qui
# se base sur une API non documentée
sys.argv = sys.argv[0:1] + sys.argv[2:]
FancyHelpFormatter = HelpFormatter
else:
class FancyHelpFormatter(HelpFormatter):
"""Comme HelpFormatter, mais ne touche pas aux lignes qui commencent par les
caractères '>>>'. Cela permet de mixer du texte formaté et du texte non
formaté.
"""
def _fill_text(self, text, width, indent):
return ''.join([indent + line for line in text.splitlines(True)])
def _split_lines(self, text, width):
lines = ['']
for line in text.splitlines():
if line.startswith('>>>'):
lines.append(line)
lines.append('')
else:
lines[-1] += '\n' + line
lines = filter(None, lines)
texts = []
for line in lines:
if line.startswith('>>>'):
line = line[3:]
if line: texts.append(line)
else:
texts.extend(super(FancyHelpFormatter, self)._split_lines(line, width))
return texts
AP = ArgumentParser(
usage=u"%(prog)s --query FILTER",
description=__doc__,
formatter_class=FancyHelpFormatter,
)
AP.set_defaults(action='query')
AP.add_argument('-Q', '--query-action', action='store_const', dest='action', const='query',
help=u"Interroger la base de données. C'est l'option par défaut")
AP.add_argument('-N', '--nop-action', action='store_const', dest='action', const='nop',
help=u"Ne rien faire. Utile pour vérifier si le fichier ne contient pas d'erreur de syntaxe.")
AP.add_argument('-P', '--dump-action', action='store_const', dest='action', const='dump',
help=u"Afficher le contenu de la base de données.")
AP.add_argument('-c', '--config', dest='confname',
help=u"Spécifier le nom de la configuration à utiliser. Par défaut, utiliser le nom générique deploy.")
AP.add_argument('-q', '--query-type', dest='query_type',
help=u"""\
Spécifier le type d'information à afficher et donc le type d'information à chercher:
>>>
* 'object' pour des informations sur les objets. le filtre est la valeur de l'objet ou d'un de ses attributs.
>>>
* 'source' pour des informations sur les objets sources d'un lien. le filtre est la valeur d'un objet destination ou de l'un de ses attributs.
>>>
* 'dest' pour des informations sur les objets destinations d'un lien. le filtre est la valeur d'un objet source et de l'un de ses attributs.
>>>
* 'link' pour des informations sur un lien.
>>>
La valeur par défaut est '-q dest -j host'""")
AP.add_argument('-j', '--object-type', dest='object_type',
help=u"Spécifier le type d'objet à chercher pour -q object|source|dest")
AP.add_argument('-t', '--link-type', dest='link_type',
help=u"Spécifier le type de lien à chercher pour -q source|dest|link")
AP.add_argument('-p', '--profile', dest='profile',
help=u"Spécifier le profil de déploiement pour -q source|dest|link. Utiliser une valeur vide pour ne sélectionner que les liens sans profils. Ne pas spécifier l'option pour afficher tous les profils définis.")
AP.add_argument('-F', '--format', dest='format',
help=u"""\
Spécifier le format pour la sortie:
>>>
* 'shell' affiche les définitions de variables
>>>
* 'lines' affiches les valeurs à raison d'une par lignes. les attributs ne sont pas affichés.
>>>
* 'spaces', 'path' et 'comma' affichent les valeurs séparées respectivement par un espace ' ', deux point ':' et une virgule ','. les attributs ne sont pas affichés.
>>>
La valeur par défaut est shell""")
AP.add_argument('-v', '--include-vars', action='append', dest='vars', metavar='VARS...',
help=u"Spécifier les variables qui doivent être affichées. Par défaut, toutes les variables sont affichées.")
AP.add_argument('-o', '--object-vars', action='store_true', dest='object_vars',
help=u"Afficher uniquement les variables associées aux objets.")
AP.add_argument('-l', '--link-vars', action='store_true', dest='link_vars',
help=u"Afficher uniquement les variables associées aux liens.")
AP.add_argument('--of', '--object-func', dest='object_func', metavar='FUNC',
help=u"Avec le format shell, spécifier le nom d'une fonction à afficher après les variables associées aux objets.")
AP.add_argument('--lf', '--link-func', dest='link_func', metavar='FUNC',
help=u"Avec le format shell, spécifier le nom d'une fonction à afficher après les variables associées aux liens.")
AP.add_argument('args', nargs='*')
o = AP.parse_args()
run_qdd(o)