@@ -146,12 +146,14 @@ def coerce_retention_period(value):
146146 return value
147147
148148
149- def load_config_file (configuration_file = None ):
149+ def load_config_file (configuration_file = None , expand = True ):
150150 """
151151 Load a configuration file with backup directories and rotation schemes.
152152
153153 :param configuration_file: Override the pathname of the configuration file
154154 to load (a string or :data:`None`).
155+ :param expand: :data:`True` to expand filename patterns to their matches,
156+ :data:`False` otherwise.
155157 :returns: A generator of tuples with four values each:
156158
157159 1. An execution context created using :mod:`executor.contexts`.
@@ -175,6 +177,7 @@ def load_config_file(configuration_file=None):
175177 above, so that sections in user-specific configuration files override
176178 sections by the same name in system-wide configuration files.
177179 """
180+ expand_notice_given = False
178181 if configuration_file :
179182 loader = ConfigLoader (available_files = [configuration_file ], strict = True )
180183 else :
@@ -195,7 +198,21 @@ def load_config_file(configuration_file=None):
195198 io_scheduling_class = items .get ('ionice' ),
196199 strict = coerce_boolean (items .get ('strict' , 'yes' )),
197200 prefer_recent = coerce_boolean (items .get ('prefer-recent' , 'no' )))
198- yield location , rotation_scheme , options
201+ # Expand filename patterns?
202+ if expand and location .have_wildcards :
203+ logger .verbose ("Expanding filename pattern %s on %s .." , location .directory , location .context )
204+ if location .is_remote and not expand_notice_given :
205+ logger .notice ("Expanding remote filename patterns (may be slow) .." )
206+ expand_notice_given = True
207+ for match in sorted (location .context .glob (location .directory )):
208+ if location .context .is_directory (match ):
209+ logger .verbose ("Matched directory: %s" , match )
210+ expanded = Location (context = location .context , directory = match )
211+ yield expanded , rotation_scheme , options
212+ else :
213+ logger .verbose ("Ignoring match (not a directory): %s" , match )
214+ else :
215+ yield location , rotation_scheme , options
199216
200217
201218def rotate_backups (directory , rotation_scheme , ** options ):
@@ -472,15 +489,27 @@ def load_config_file(self, location):
472489 :returns: The configured or given :class:`Location` object.
473490 """
474491 location = coerce_location (location )
475- for configured_location , rotation_scheme , options in load_config_file (self .config_file ):
476- if location == configured_location :
492+ for configured_location , rotation_scheme , options in load_config_file (self .config_file , expand = False ):
493+ if configured_location . match ( location ) :
477494 logger .verbose ("Loading configuration for %s .." , location )
478495 if rotation_scheme :
479496 self .rotation_scheme = rotation_scheme
480497 for name , value in options .items ():
481498 if value :
482499 setattr (self , name , value )
483- return configured_location
500+ # Create a new Location object based on the directory of the
501+ # given location and the execution context of the configured
502+ # location, because:
503+ #
504+ # 1. The directory of the configured location may be a filename
505+ # pattern whereas we are interested in the expanded name.
506+ #
507+ # 2. The execution context of the given location may lack some
508+ # details of the configured location.
509+ return Location (
510+ context = configured_location .context ,
511+ directory = location .directory ,
512+ )
484513 logger .verbose ("No configuration found for %s." , location )
485514 return location
486515
@@ -622,6 +651,11 @@ def have_ionice(self):
622651 """:data:`True` when ionice_ is available, :data:`False` otherwise."""
623652 return self .context .have_ionice
624653
654+ @lazy_property
655+ def have_wildcards (self ):
656+ """:data:`True` if :attr:`directory` is a filename pattern, :data:`False` otherwise."""
657+ return '*' in self .directory
658+
625659 @lazy_property
626660 def mount_point (self ):
627661 """
@@ -703,6 +737,27 @@ def ensure_writable(self):
703737 to permissions. Consider using the --use-sudo option.
704738 """ , location = self ))
705739
740+ def match (self , location ):
741+ """
742+ Check if the given location "matches".
743+
744+ :param location: The :class:`Location` object to try to match.
745+ :returns: :data:`True` if the two locations are on the same system and
746+ the :attr:`directory` can be matched as a filename pattern or
747+ a literal match on the normalized pathname.
748+ """
749+ if self .ssh_alias != location .ssh_alias :
750+ # Never match locations on other systems.
751+ return False
752+ elif self .have_wildcards :
753+ # Match filename patterns using fnmatch().
754+ return fnmatch .fnmatch (location .directory , self .directory )
755+ else :
756+ # Compare normalized directory pathnames.
757+ self = os .path .normpath (self .directory )
758+ other = os .path .normpath (location .directory )
759+ return self == other
760+
706761 def __str__ (self ):
707762 """Render a simple human readable representation of a location."""
708763 return '%s:%s' % (self .ssh_alias , self .directory ) if self .ssh_alias else self .directory
0 commit comments