possibilité de matcher des sous-répertoires dans l'expression --spec. Support du match de type --glob

This commit is contained in:
Jephté Clain 2016-06-10 03:57:07 +04:00
parent a49d6e2a24
commit b7600aa43b
1 changed files with 102 additions and 23 deletions

View File

@ -6,7 +6,7 @@ u"""Ce script est prévu pour être utilisé dans un script CGI.
Il permet de lister le contenu d'un répertoire au format XML, et de télécharger les fichiers trouvés.
"""
import os, sys, re, shutil, mimetypes, urlparse, urllib
import os, sys, re, shutil, mimetypes, urlparse, urllib, fnmatch, glob
from os import path
from time import time, localtime
from types import UnicodeType, StringTypes
@ -35,13 +35,23 @@ RE_SORT_FIELD_TYPE = re.compile(r'([_a-zA-Z][-_a-zA-Z0-9]*)(?::([aAdDcCnN]+))?')
class Filter:
re_spec = None
glob = None
caret_anchor = None
deep_scan = None
re_allows = None
ex_vars = None
ex_group = None
ex_break_on = None
def __init__(self, re_spec):
self.re_spec = re.compile(re_spec)
def __init__(self, spec=None, glob=None):
if spec is not None:
self.re_spec = re.compile(spec)
self.caret_anchor = spec.startswith('^')
self.deep_scan = '/' in spec
elif glob is not None:
self.glob = glob
self.deep_scan = '/' in glob
else: raise ValueError("either spec or glob must be specified")
self.re_allows = []
self.ex_vars = []
self.ex_group = None
@ -63,14 +73,34 @@ class Filter:
self.ex_break_on = ex_break_on
def __repr__(self):
return 'Filter<%s,%s,%s,%s,%s>' % \
(self.re_spec, repr(self.re_allows),
return 'Filter<%s||%s,%s,%s,%s,%s>' % \
(self.re_spec, self.glob, repr(self.re_allows),
repr(self.ex_vars),
self.ex_group, self.ex_break_on,
)
def match_fill(self, file):
mo = self.re_spec.match(file.name)
def match_fill(self, file, basedir=None):
if self.glob:
if self.deep_scan:
# la correspondance a déjà été faite avant avec glob.glob(). on
# se contente d'initialiser l'objet
pass
elif not fnmatch.fnmatch(file.name, self.glob):
return None
file.group = None
file.break_on = None
return True
if self.deep_scan:
pf = file.pf
if not self.caret_anchor:
blen = len(basedir)
if pf[:blen] != basedir: return None
pf = pf[blen:]
if not pf.startswith('/'): return None
pf = pf[1:]
mo = self.re_spec.match(pf)
else: mo = self.re_spec.match(file.name)
if mo is None: return None
for name, expr in self.ex_vars:
value = mo.expand(expr)
@ -102,7 +132,11 @@ class File:
vars = None
dontlist = None
def __init__(self, dir, name, dontlist=False):
def __init__(self, dir=None, name=None, pf=None, dontlist=False):
if pf is None:
if dir is None or name is None: raise ValueError("either dir/name or pf must be specified")
pf = path.join(dir, name)
dir, name = path.split(pf) # il est possible que name soit de la forme subdir/name
dir = path.abspath(dir)
pf = path.join(dir, name)
self.__dict__['pf'] = pf
@ -137,19 +171,49 @@ class File:
def __repr__(self): return 'File<%s>' % (self.pf)
def find_files(basedir, filters):
basedir = path.abspath(basedir)
files = []
for name in os.listdir(basedir):
file = File(basedir, name)
if not file.isfile(): continue
matched = False
allowed = False
for filter in filters:
matched = filter.match_fill(file) or matched
allowed = filter.match_allow(file) or allowed
if matched: pass
elif allowed: file.dontlist = True
else: continue
files.append(file)
deep_filters = [filter for filter in filters if filter.re_spec is not None and filter.deep_scan]
if deep_filters:
for dirpath, dirnames, filenames in os.walk(basedir):
for filename in filenames:
file = File(dirpath, filename)
matched = False
allowed = False
for filter in deep_filters:
matched = filter.match_fill(file, basedir) or matched
allowed = filter.match_allow(file) or allowed
if matched: pass
elif allowed: file.dontlist = True
else: continue
files.append(file)
simple_filters = [filter for filter in filters if filter.re_spec is not None and not filter.deep_scan]
if simple_filters:
for name in os.listdir(basedir):
file = File(basedir, name)
if not file.isfile(): continue
matched = False
allowed = False
for filter in simple_filters:
matched = filter.match_fill(file) or matched
allowed = filter.match_allow(file) or allowed
if matched: pass
elif allowed: file.dontlist = True
else: continue
files.append(file)
glob_filters = [filter for filter in filters if filter.glob is not None]
if glob_filters:
for filter in glob_filters:
if filter.deep_scan:
candidates = [File(pf=pf) for pf in glob.glob(path.join(basedir, filter.glob))]
else:
candidates = [File(basedir, name) for name in os.listdir(basedir)]
for file in candidates:
if not file.isfile(): continue
elif filter.match_fill(file): pass
elif filter.match_allow(file): file.dontlist = True
else: continue
files.append(file)
return files
def build_sortfunc(sortby):
@ -318,7 +382,10 @@ def run_cgilsxml():
default_filter = Filter(r'(.*)')
def add_spec(option, opt, value, parser, *args, **kw):
if env['filter'] is not None: env['filters'].append(env['filter'])
env['filter'] = Filter(value)
env['filter'] = Filter(spec=value)
def add_glob(option, opt, value, parser, *args, **kw):
if env['filter'] is not None: env['filters'].append(env['filter'])
env['filter'] = Filter(glob=value)
def allow_spec(option, opt, value, parser, *args, **kw):
if env['filter'] is None: env['filter'] = default_filter
env['filter'].allow_spec(value)
@ -335,9 +402,21 @@ def run_cgilsxml():
from optparse import OptionParser
OP = OptionParser(usage=u"\n\t%prog [options] /path/to/dir", description=__doc__)
OP.add_option('-e', '--spec', dest='spec', action='callback', callback=add_spec, type='string',
help=u"Spécifier l'expression régulière permettant de sélectionner les fichiers à lister. L'expression régulière peut définir des groupes qui sont utilisées pour l'extraction des variables."
help=u"Spécifier l'expression régulière permettant de sélectionner les fichiers à lister. "
+ u"La correspondance est tentée sur le nom du fichier avec la fonction match(). "
+ u"Penser à inclure l'ancre $ pour matcher le nom entier du fichier. "
+ u"Si l'expression régulière contient un caractère '/', la correspondance peut se faire sur le chemin complet du fichier. "
+ u"Pour simplifier, par défaut seule la partie située après le chemin de base est matchée, sauf si l'expression commence par '^', auquel cas la correspondance se fait sur le chemin complet du fichier. "
+ u"L'expression régulière peut définir des groupes qui sont utilisées pour l'extraction des variables."
+ u"\n Il est possible de spécifier cette option plusieurs fois."
+ u"\n Note: TOUTES les expressions régulières sont testées par rapport au nom du fichier, et pour celles qui correspondent, les variables correspondantes sont définies. Il faut donc ordonner les expressions régulières de la plus générale à la plus spécifique, contrairement à ce qui se fait d'habitude.")
+ u"\n Note: TOUTES les expressions régulières sont testées par rapport au nom du fichier, et pour celles qui correspondent, les variables correspondantes sont définies. "
+ u"Il faut donc ordonner les expressions régulières de la plus générale à la plus spécifique, contrairement à ce qui se fait d'habitude.")
OP.add_option('--glob', dest='pattern', action='callback', callback=add_glob, type='string',
help=u"Comme --spec, mais en utilisant les patterns du shell *, ?, [seq], [!seq]. "
+ u"Si l'expression contient un caractère '/', la correspondance se fait sur le chemin complet du fichier avec le module glob. "
+ u"Cette précision est importante notamment pour la recherche de fichiers dont le nom commence par un point. "
+ u"Si l'expression ne contient pas le caractère '/', la correspondance est faite uniquement sur le nom de fichier avec le module fnmatch, et le point n'a plus de signification particulière. "
+ u"Cette méthode de correspondance ne permet pas de définir des groupes et ne permet donc pas d'extraire des variables.")
OP.add_option('-E', '--allow-spec', dest='spec', action='callback', callback=allow_spec, type='string',
help=u"Ajouter une spécification de fichier qui peut être demandé avec --cgi-path-info. Ces fichiers ne sont pas inclus dans la liste.")
OP.add_option('-v', '--var', dest='var', action='callback', callback=add_var, type='string',