nutools/lib/pyulib/src/uapps/pyuupdate_inc.py

716 lines
26 KiB
Python
Raw Normal View History

2013-08-27 15:14:44 +04:00
#!/usr/bin/env python
# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
import os, sys, re, string
from os import path
from ulib.all import *
##################################################
# fonctions diverses
verbosity = 1
verbose = 2
normal = 1
quiet = 0
def _print(level, text, min_verbosity=normal):
"""afficher un texte avec un niveau d'indentation, si le niveau de verbosité
est correct.
Retourner 1 si le texte a été affiché, 0 sinon
"""
global verbosity
if verbosity < min_verbosity: return 0
if level < 0: level = 0
print "%s%s" % (" " * level, text)
return 1
debug = 0
def _debug(text):
if debug: print text
def select_files(p, exclude_names=(), exclude_paths=(), basepath=None):
"""Construire récursivement la liste de tous les fichiers à partir du
répertoire p, en excluant les répertoires dont le nom est donné dans
excludes_names, ou dont le chemin relativement à p est donné dans
exclude_paths
Note: on exclue automatiquement les fichiers qui sont 'binaires'
"""
if basepath is None:
return select_files(p, exclude_names, exclude_paths, p)
else:
files = []
for file in os.listdir(p):
if fileext_is_binary(file): continue
pf = path.join(p, file) # chemin absolu
relpf = pf[len(basepath) + 1:] # chemin relativement à basepath. +1 pour le '/'
ignore = 0
for pattern in exclude_names:
if matches_name(pattern, file, p):
ignore = 1
else:
for pattern in exclude_paths:
if matches_name(pattern, relpf, basepath):
ignore = 1
if not ignore:
if path.isdir(pf):
files.extend(select_files(pf, exclude_names, exclude_paths, basepath))
else:
files.append(pf)
return files
##################################################
# Gestion des répertoires d'inclusion
incpaths = None
def init_incpaths(ignore_env=0):
"""Initialiser incpaths, la liste globale des répertoires dans lesquels on
cherche les fichiers d'inclusions.
Lire la valeur de la variable d'environnement UPDATEINCPATH, sauf si on est
dans un répertoire non déployé.
Si on est dans un répertoire non déployé, s'assurer que le répertoire qui
contient ce script est dans incpaths.
"""
global incpaths
if incpaths is None: incpaths = []
if path.isdir(path.join(scriptdir, "test")):
# ignorer UPDATEINCPATH si on est dans le répertoire de test
ignore_env = 1
if not ignore_env:
incpaths = filter(None, string.split(os.environ.get('UPDATEINCPATH', ''), ':'))
else:
if scriptdir not in incpaths:
incpaths.insert(0, scriptdir)
_debug("init_incpaths()\n incpaths=\n %s" % string.join(incpaths, "\n "))
def set_incpaths(p):
"""Soit un répertoire ou une liste de répertoires séparés par ':'
Si p est un répertoire, l'ajouter à la liste des répertoires de recherche.
Si p est une liste de répertoires, remplacer la liste actuelle par cette
nouvelle liste.
"""
global incpaths
if incpaths is None: init_incpaths()
if ':' in p:
incpaths[:] = map(path.abspath, filter(None, string.split(p, ':')))
else:
p = path.abspath(p)
if p not in incpaths:
incpaths.append(p)
_debug("set_incpaths()\n incpaths=\n %s" % string.join(incpaths, "\n "))
def inc_abspath(file, local_incpaths=()):
"""Retourner le chemin absolu vers un fichier.
Si file est un chemin relatif, il est recherché dans incpaths et dans
local_incpaths
"""
file = path.expanduser(path.expandvars(file))
if path.isabs(file): return file
if path.exists(file): return path.abspath(file)
for incpath in incpaths:
pf = path.join(incpath, file)
if path.exists(pf): return pf
else:
for incpath in local_incpaths:
pf = path.join(incpath, file)
if path.exists(pf): return pf
return path.abspath(file)
##################################################
# Gestion des lignes d'inclusion
class InterestingLine:
def __init__(self, c=None):
"""Initialiser l'objet
re_inc matche une ligne d'inclusion repliée
re_start_inc matche une ligne d'inclusion dépliée de type 'start'
re_end_inc match une ligne d'inclusion dépliée de type 'end'
re_require match une ligne d'inclusion de type 'require'
"""
self.re_comments = [
re.compile(r'[ \t]*(?:r|R)(?:e|E)(?:m|M)'),
re.compile(r'[ \t]*##'),
re.compile(r'[ \t]*;;'),
re.compile(r'[ \t]*//'),
re.compile(r"[ \t]*''"),
]
if c is None:
self.re_inc = re.compile(r'(.*)@include[ \t]+(.+)')
self.re_start_inc = re.compile(r'(.*)@inc\[(.+)')
self.re_end_inc = re.compile(r'(.*)@inc\](.+)')
self.re_require = re.compile(r'(.*)@require[ \t]+(.+)')
self.re_provide = re.compile(r'(.*)@provide[ \t]+(.+)')
self.c = '@'
elif c in ('*', '@'):
self.re_inc = re.compile(r'(.*)(?:@|\*)include[ \t]+(.+)')
self.re_start_inc = re.compile(r'(.*)(?:@|\*)inc\[(.+)')
self.re_end_inc = re.compile(r'(.*)(?:@|\*)inc\](.+)')
self.re_require = re.compile(r'(.*)(?:@|\*)require[ \t]+(.+)')
self.re_provide = re.compile(r'(.*)(?:@|\*)provide[ \t]+(.+)')
self.c = c
self.inc_templ = "%s%sinclude %s"
self.start_inc_templ = "%s%sinc[%s"
self.end_inc_templ = "%s%sinc]%s"
self.require_templ = "%s%srequire %s"
self.provide_templ = "%s%sprovide %s"
self.top_level = True
def copy(self):
"""faire une copie de cet objet
"""
il = self.__class__(self.c)
il.top_level = False
return il
def is_comment(self, s):
"""retourner vrai si s est un commentaire (matche l'une des expressions
régulières de re_comment)
"""
for re_comment in self.re_comments:
if re_comment.match(s):
return True
return False
def matches(self, line):
"""retourner vrai si line est une ligne 'intéressante': ligne
d'inclusion repliée, dépliée de type 'start', dépliée de type 'end', de
type 'require', ou de type 'provide'
initialiser les informations sur cette ligne: mo, spaces, file
"""
self.__is_inc = 0
self.__is_start_inc = 0
self.__is_end_inc = 0
self.__is_require = 0
self.__is_provide = 0
mo = self.re_inc.match(line)
if mo is not None:
self.__is_inc = 1
else:
mo = self.re_start_inc.match(line)
if mo is not None:
self.__is_start_inc = 1
else:
mo = self.re_end_inc.match(line)
if mo is not None:
self.__is_end_inc = 1
else:
mo = self.re_require.match(line)
if mo is not None:
self.__is_require = 1
else:
mo = self.re_provide.match(line)
if mo is not None:
self.__is_provide = 1
if mo is not None:
before = mo.group(1)
if self.is_comment(before):
self.before = before
self.file = mo.group(2)
self.pf = inc_abspath(self.file)
else:
self.__is_inc = 0
self.__is_start_inc = 0
self.__is_end_inc = 0
self.__is_require = 0
self.__is_provide = 0
mo = None
self.mo = mo
return mo is not None
def is_inc(self, line=None):
"""Si line==None, retourner vrai si la ligne matchée par matches() est
une ligne d'inclusion repliée.
si line!=None, retourner vrai si la ligne est une ligne d'inclusion
repliée dont le nom de fichier correspond à la ligne matchée par
matches()
"""
if line is None:
return self.__is_inc
else:
mo = self.re_inc.match(line)
if mo is not None:
if mo.group(2) == self.file:
return 1
return 0
def inc(self):
"""retourner une ligne d'inclusion repliée construite avec self.before
et self.file
"""
return self.inc_templ % (self.before, self.c, self.file)
def is_start_inc(self, line=None):
"""Si line==None, retourner vrai si la ligne matchée par matches() est
une ligne d'inclusion dépliée de type 'start'.
si line!=None, retourner vrai si la ligne est une ligne d'inclusion
dépliée de type 'start' dont le nom de fichier correspond à la ligne
matchée par matches()
"""
if line is None:
return self.__is_start_inc
else:
mo = self.re_start_inc.match(line)
if mo is not None:
if mo.group(2) == self.file:
return 1
return 0
def start_inc(self):
"""retourner une ligne d'inclusion dépliée de type 'start' construite
avec self.before et self.file
"""
return self.start_inc_templ % (self.before, self.c, self.file)
def is_end_inc(self, line=None):
"""Si line==None, retourner vrai si la ligne matchée par matches() est
une ligne d'inclusion dépliée de type 'end'.
si line!=None, retourner vrai si la ligne est une ligne d'inclusion
dépliée de type 'end' dont le nom de fichier correspond à la ligne
matchée par matches()
"""
if line is None:
return self.__is_end_inc
else:
mo = self.re_end_inc.match(line)
if mo is not None:
if mo.group(2) == self.file:
return 1
return 0
def end_inc(self):
"""retourner une ligne d'inclusion dépliée de type 'end' construite
avec self.before et self.file
"""
return self.end_inc_templ % (self.before, self.c, self.file)
def is_require(self, line=None):
"""Si line==None, retourner vrai si la ligne matchée par matches() est
une ligne d'inclusion de type 'require'.
si line!=None, retourner vrai si la ligne est une ligne d'inclusion de
type 'require' dont le nom de fichier correspond à la ligne matchée par
matches()
"""
if line is None:
return self.__is_require
else:
mo = self.re_require.match(line)
if mo is not None:
if mo.group(2) == self.file:
return 1
return 0
def require(self):
"""retourner une ligne d'inclusion de type 'require' construite avec
self.before et self.file
"""
return self.require_templ % (self.before, self.c, self.file)
def is_provide(self, line=None):
"""Si line==None, retourner vrai si la ligne matchée par matches() est
une ligne d'inclusion de type 'provide'.
si line!=None, retourner vrai si la ligne est une ligne d'inclusion de
type 'provide' dont le nom de fichier correspond à la ligne matchée par
matches()
"""
if line is None:
return self.__is_provide
else:
mo = self.re_provide.match(line)
if mo is not None:
if mo.group(2) == self.file:
return 1
return 0
def provide(self):
"""retourner une ligne d'inclusion de type 'provide' construite avec
self.before et self.file
"""
return self.provide_templ % (self.before, self.c, self.file)
##################################################
# replier un fichier
def fold(file, level=0, il=None, recursive_update=0, parent_print_processing_maybe=None, **ignored):
"""Replier un fichier: c'est l'opération inverse de unfold_or_update
"""
if il is None: il = InterestingLine()
pf = inc_abspath(file)
# vérifier la présence du fichier
if not path.exists(pf):
_print(level, "not found: %s" % pf)
return
# Il ne faut pas changer le répertoire courant en sortant de cette
# fonction. Faire une copie d'abord
cwd = os.getcwd()
# Se placer dans le répertoire du fichier
p, f = path.split(pf)
os.chdir(p)
try:
tf = TextFile(pf, lines=BLines())
try:
old = tf.readlines()
except:
# impossible de lire le fichier
_print(level, "unable to read: %s" % pf)
return
new = []
printed = [0]
def print_processing_maybe(level=level, file=file, printed=printed, parent_print_processing_maybe=parent_print_processing_maybe):
if parent_print_processing_maybe is not None:
parent_print_processing_maybe()
if not printed[0]:
_print(level, "processing: %s" % file)
printed[0] = 1
folding = 0 # est-on en train de replier?
was_modified = 0
for line in old:
if not folding:
if il.matches(line):
if il.is_require():
if recursive_update:
fold(il.file, level=level + 1,
il=il.copy(), recursive_update=recursive_update,
parent_print_processing_maybe=print_processing_maybe)
new.append(il.require())
if il.require() != line: was_modified = 1
elif il.is_provide():
if recursive_update:
fold(il.file, level=level + 1,
il=il.copy(), recursive_update=recursive_update,
parent_print_processing_maybe=print_processing_maybe)
new.append(il.provide())
if il.provide() != line: was_modified = 1
elif il.is_inc():
if recursive_update:
fold(il.file, level=level + 1,
il=il.copy(), recursive_update=recursive_update,
parent_print_processing_maybe=print_processing_maybe)
new.append(il.inc())
if il.inc() != line: was_modified = 1
elif il.is_start_inc():
if il.start_inc() != line: was_modified = 1
folding = 1
if was_modified: print_processing_maybe()
else:
new.append(line)
else:
if il.is_end_inc(line):
if recursive_update:
fold(il.file, level=level + 1,
il=il.copy(), recursive_update=recursive_update,
parent_print_processing_maybe=print_processing_maybe)
print_processing_maybe()
_print(level, "f %s" % il.file, verbose)
new.append(il.inc())
was_modified = 1
folding = 0
if folding:
# on a trouvé une inclusion non terminée. ne pas modifier le fichier
_print(level, "warning: %s include not properly ended, ignored" % il.file)
was_modified = 0
if was_modified:
tf.writelines(new)
finally:
os.chdir(cwd)
##################################################
# déplier un fichier
MODIFIED = 2
UNMODIFIED = 1
ERROR = 0
def unfold_or_update(file, level=0, il=None, recursive_update=0, files=None, provided=None, parent_print_processing_maybe=None, **ignored):
"""déplier un fichier. Seul le fichier file est modifié le cas échéant si
files!=None
retourner MODIFIED si le fichier a été lu depuis le disque ou s'il a été
modifié, UNMODIFIED s'il faut utiliser le cache de lecture files, ERROR si
erreur (aucune modification effectuée)
"""
if il is None: il = InterestingLine()
pf = inc_abspath(file)
# Si le fichier a déja été traité, nous pouvons retourner
if files is not None and files.has_key(pf):
return UNMODIFIED
# liste des fichiers déjà traités.
can_write = recursive_update # doit-on mettre à jour le fichier sur disque?
if files is None:
can_write = 1
files = {}
# liste des fichiers déjà inclus
if provided is None:
provided = {}
printed = [0]
def print_processing_maybe(level=level, file=file, printed=printed, parent_print_processing_maybe=parent_print_processing_maybe):
if parent_print_processing_maybe is not None:
parent_print_processing_maybe()
if not printed[0]:
_print(level, "processing: %s" % file)
printed[0] = 1
# vérifier la présence du fichier
if not path.exists(pf):
if level == 0:
_print(level, "not found: %s" % pf)
else:
print_processing_maybe()
_print(level - 1, "X %s (not found)" % file)
return ERROR
# Il ne faut pas changer le répertoire courant en sortant de cette
# fonction. Faire une copie d'abord
cwd = os.getcwd()
# Se placer dans le répertoire du fichier
p, f = path.split(pf)
os.chdir(p)
try:
tf = TextFile(pf, lines=BLines())
try:
old = tf.readlines()
except:
# impossible de lire le fichier
if level == 0:
_print(level, "unable to read: %s" % pf)
else:
_print(level - 1, "X %s (unable to read)" % file)
return ERROR
new = []
files[pf] = tf
provided[pf] = 1
unfolding = None # est-on en train de déplier?
was_modified = 0
for line in old:
if unfolding is None:
if il.matches(line):
if il.is_require():
# traiter le cas @require
_debug("require\n file=%s\n provided=\n %s" % (il.file, string.join(provided.keys(), '\n ')))
if not il.top_level and not provided.has_key(il.pf):
print_processing_maybe()
_print(level, "R %s (is required)" % il.file)
new.append(il.require())
if il.require() != line: was_modified = 1
elif il.is_provide():
# traiter le cas @provide
_debug("provide\n file=%s\n provided=\n %s" % (il.file, string.join(provided.keys(), '\n ')))
provided[il.pf] = 1
new.append(il.provide())
if il.provide() != line: was_modified = 1
elif il.is_inc():
# traiter le cas @include
if unfold_or_update(il.file, level=level + 1,
il=il.copy(), recursive_update=recursive_update,
files=files, provided=provided,
parent_print_processing_maybe=print_processing_maybe):
print_processing_maybe()
_print(level, "U %s" % il.file, verbose)
new.append(il.start_inc())
new.extend(files[il.pf].lines)
new.append(il.end_inc())
was_modified = 1
else:
new.append(il.inc())
if il.inc() != line: was_modified = 1
elif il.is_start_inc():
unfolding = []
if il.start_inc() != line: was_modified = 1
else:
new.append(line)
else:
if il.is_end_inc(line):
status = unfold_or_update(il.file, level=level + 1,
il=il.copy(), recursive_update=recursive_update,
files=files, provided=provided,
parent_print_processing_maybe=print_processing_maybe)
if status == MODIFIED:
if unfolding != files[il.pf].lines:
print_processing_maybe()
_print(level, "u %s" % il.file, verbose)
was_modified = 1
new.append(il.start_inc())
new.extend(files[il.pf].lines)
new.append(il.end_inc())
if il.end_inc() != line: was_modified = 1
elif status == UNMODIFIED:
new.append(il.start_inc())
new.extend(files[il.pf].lines)
new.append(il.end_inc())
if il.end_inc() != line: was_modified = 1
elif status == ERROR:
print_processing_maybe()
_print(level, "X %s (not found)" % il.file)
new.append(il.start_inc())
new.extend(unfolding)
new.append(il.end_inc())
if il.end_inc() != line: was_modified = 1
unfolding = None
else:
unfolding.append(line)
if unfolding is not None:
# on a trouvé une inclusion non terminée. ne pas modifier le fichier
print_processing_maybe()
_print(level, "warning: %s include not properly ended, ignored" % il.file)
was_modified = 0
if was_modified:
if new == tf.lines:
return UNMODIFIED
print_processing_maybe()
tf.lines[:] = new
if can_write:
tf.writelines()
return MODIFIED
finally:
os.chdir(cwd)
##################################################
# lecture des paramètres
def update_inc_params(args=None, basedir=None):
if args is None: args = sys.argv[1:]
global verbosity, debug, incpaths
pr = {'action': unfold_or_update,
'exclude_names': ['CVS/', '.svn/'],
'exclude_paths': [],
'il': None,
'recursive_update': 0,
}
args_read = False
while not args_read:
config_file = None
opts, args = get_args(args, 'qvDuRfx:X:I:*@C:',
['quiet', 'verbose', 'debug',
'update', 'unfold', 'recursive-update',
'fold',
'exclude-names=', 'exclude-paths', 'include-paths',
'config-file=', 'basedir=',
])
for opt, value in opts:
if opt in ('-q', '--quiet'):
verbosity = quiet
elif opt in ('-v', '--verbose'):
verbosity = verbose
elif opt in ('-D', '--debug'):
debug = 1
elif opt in ('-u', '--update', '--unfold'):
pr['action'] = unfold_or_update
elif opt in ('-R', '--recursive-update'):
pr['recursive_update'] = 1
elif opt in ('-f', '--fold'):
pr['action'] = fold
elif opt in ('-x', '--exclude-names'):
if ':' in value:
pr['exclude_names'] = filter(None, string.split(value, ':'))
else:
pr['exclude_names'].append(value)
elif opt in ('-X', '--exclude-paths'):
if ':' in value:
pr['exclude_paths'] = filter(None, string.split(value, ':'))
else:
pr['exclude_paths'].append(value)
elif opt in ('-I', '--include-paths'):
if incpaths is None: init_incpaths()
set_incpaths(value)
elif opt in ('-*',):
pr['il'] = InterestingLine('*')
elif opt in ('-@',):
pr['il'] = InterestingLine('@')
elif opt in ('-C', '--config-file'):
config_file = value
elif opt in ('--basedir',):
basedir = path.abspath(value)
if config_file is not None:
cf = ShConfigFile(config_file, raise_exception=False)
if cf.is_valid():
args = split_args(cf.get('update_inc_options', '')) + split_args(cf.get('update_inc_args', '')) + args
else:
ewarn("Impossible de lire le fichier de configuration: %s" % config_file)
args_read = config_file is None
# transformer tous les chemins de args en chemins absolus.
if basedir is None: basedir = os.getcwd()
for i in range(len(args)):
args[i] = path.abspath(path.join(basedir, args[i]))
return pr, args
def update_inc(pr, args=None, action=None):
global incpaths
if incpaths is None: init_incpaths()
if action is not None:
action = {'fold': fold,
'unfold': unfold_or_update,
'update': unfold_or_update,
'unfold_or_update': unfold_or_update,
}.get(action, None)
if action is None: action = pr['action']
if args is None: args = []
for arg in args:
if path.isdir(arg):
for file in select_files(arg, pr['exclude_names'], pr['exclude_paths']):
action(file, il=pr['il'], recursive_update=pr['recursive_update'])
else:
action(arg, il=pr['il'], recursive_update=pr['recursive_update'])
if __name__ == '__main__':
pr, args = update_inc_params()
update_inc(pr, args)