@@ -123,6 +123,7 @@ class Walker(WalkerBase):
123123 be returned if the final component matches one of the patterns.
124124 exclude_dirs (list, optional): A list of patterns that will be used
125125 to filter out directories from the walk. e.g. ``['*.svn', '*.git']``.
126+ max_depth (int, optional): Maximum directory depth to walk.
126127
127128 """
128129
@@ -131,7 +132,8 @@ def __init__(self,
131132 on_error = None ,
132133 search = "breadth" ,
133134 filter = None ,
134- exclude_dirs = None ):
135+ exclude_dirs = None ,
136+ max_depth = None ):
135137 if search not in ('breadth' , 'depth' ):
136138 raise ValueError ("search must be 'breadth' or 'depth'" )
137139 self .ignore_errors = ignore_errors
@@ -153,6 +155,7 @@ def __init__(self,
153155 self .search = search
154156 self .filter = filter
155157 self .exclude_dirs = exclude_dirs
158+ self .max_depth = max_depth
156159 super (Walker , self ).__init__ ()
157160
158161 @classmethod
@@ -165,6 +168,14 @@ def _raise_errors(cls, path, error):
165168 """Callback to re-raise dir scan errors."""
166169 return False
167170
171+ @classmethod
172+ def _calculate_depth (cls , path ):
173+ """Calculate the 'depth' of a directory path (number of
174+ components).
175+ """
176+ _path = path .strip ('/' )
177+ return _path .count ('/' ) + 1 if _path else 0
178+
168179 @classmethod
169180 def bind (cls , fs ):
170181 """Bind a `Walker` instance to a given filesystem.
@@ -208,7 +219,8 @@ def __repr__(self):
208219 on_error = (self .on_error , None ),
209220 search = (self .search , 'breadth' ),
210221 filter = (self .filter , None ),
211- exclude_dirs = (self .exclude_dirs , None )
222+ exclude_dirs = (self .exclude_dirs , None ),
223+ max_depth = (self .max_depth , None )
212224 )
213225
214226 def filter_files (self , fs , infos ):
@@ -232,23 +244,53 @@ def filter_files(self, fs, infos):
232244 if _check_file (fs , info )
233245 ]
234246
247+ def _check_open_dir (self , fs , path , info ):
248+ """Check if a directory should be considered in the walk.
249+ """
250+ if (self .exclude_dirs is not None and
251+ fs .match (self .exclude_dirs , info .name )):
252+ return False
253+ return self .check_open_dir (fs , path , info )
235254
236- def check_open_dir (self , fs , info ):
255+ def check_open_dir (self , fs , path , info ):
237256 """Check if a directory should be opened.
238257
239258 Override to exclude directories from the walk.
240259
241260 Arguments:
242261 fs (FS): A filesystem instance.
243- info (Info): A resource info object.
262+ path (str): Path to directory.
263+ info (Info): A resource info object for the directory.
244264
245265 Returns:
246266 bool: `True` if the directory should be opened.
247267
248268 """
249- if self .exclude_dirs is None :
250- return True
251- return not fs .match (self .exclude_dirs , info .name )
269+ return True
270+
271+ def _check_scan_dir (self , fs , path , info , depth ):
272+ """Check if a directory contents should be scanned."""
273+ if self .max_depth is not None and depth >= self .max_depth :
274+ return False
275+ return self .check_scan_dir (fs , path , info )
276+
277+ def check_scan_dir (self , fs , path , info ):
278+ """Check if a directory should be scanned.
279+
280+ Override to omit scanning of certain directories. If a directory
281+ is omitted, it will appear in the walk but its files and
282+ sub-directories will not.
283+
284+ Arguments:
285+ fs (FS): A filesystem instance.
286+ path (str): Path to directory.
287+ info (Info): A resource info object for the directory.
288+
289+ Returns:
290+ bool: `True` if the directory should be scanned.
291+
292+ """
293+ return True
252294
253295 def check_file (self , fs , info ):
254296 """Check if a filename should be included.
@@ -329,19 +371,22 @@ def _walk_breadth(self, fs, path, namespaces=None):
329371 queue = deque ([path ])
330372 push = queue .appendleft
331373 pop = queue .pop
374+ depth = self ._calculate_depth (path )
332375
333376 while queue :
334377 dir_path = pop ()
335378 dirs = []
336379 files = []
337380 for info in self ._scan (fs , dir_path , namespaces = namespaces ):
338381 if info .is_dir :
339- if self .check_open_dir (fs , info ):
382+ _depth = self ._calculate_depth (dir_path ) - depth + 1
383+ if self ._check_open_dir (fs , dir_path , info ):
340384 dirs .append (info )
341- push (join (dir_path , info .name ))
385+ if self ._check_scan_dir (fs , dir_path , info , _depth ):
386+ push (join (dir_path , info .name ))
342387 else :
343388 files .append (info )
344- yield (
389+ yield Step (
345390 dir_path ,
346391 dirs ,
347392 self .filter_files (fs , files )
@@ -353,8 +398,10 @@ def _walk_depth(self, fs, path, namespaces=None):
353398 # No recursion!
354399
355400 def scan (path ):
401+ """Perform scan."""
356402 return self ._scan (fs , path , namespaces = namespaces )
357403
404+ depth = self ._calculate_depth (path )
358405 stack = [(
359406 path , scan (path ), [], []
360407 )]
@@ -365,20 +412,22 @@ def scan(path):
365412 try :
366413 info = next (iter_files )
367414 except StopIteration :
368- yield (
415+ yield Step (
369416 dir_path ,
370417 dirs ,
371418 self .filter_files (fs , files )
372419 )
373420 del stack [- 1 ]
374421 else :
375422 if info .is_dir :
376- if self .check_open_dir (fs , info ):
423+ _depth = self ._calculate_depth (dir_path ) - depth + 1
424+ if self ._check_open_dir (fs , dir_path , info ):
377425 dirs .append (info )
378- _path = join (dir_path , info .name )
379- push ((
380- _path , scan (_path ), [], []
381- ))
426+ if self ._check_scan_dir (fs , dir_path , info , _depth ):
427+ _path = join (dir_path , info .name )
428+ push ((
429+ _path , scan (_path ), [], []
430+ ))
382431 else :
383432 files .append (info )
384433
@@ -448,6 +497,7 @@ def walk(self,
448497 exclude_dirs (list): A list of patterns that will be used
449498 to filter out directories from the walk, e.g. ``['*.svn',
450499 '*.git']``.
500+ max_depth (int, optional): Maximum directory depth to walk.
451501
452502 Returns:
453503 ~collections.Iterator: an iterator of ``(<path>, <dirs>, <files>)``
@@ -495,6 +545,7 @@ def files(self, path='/', **kwargs):
495545 exclude_dirs (list): A list of patterns that will be used
496546 to filter out directories from the walk, e.g. ``['*.svn',
497547 '*.git']``.
548+ max_depth (int, optional): Maximum directory depth to walk.
498549
499550 Returns:
500551 ~collections.Iterable: An iterable of file paths (absolute
@@ -525,6 +576,7 @@ def dirs(self, path='/', **kwargs):
525576 exclude_dirs (list): A list of patterns that will be used
526577 to filter out directories from the walk, e.g. ``['*.svn',
527578 '*.git']``.
579+ max_depth (int, optional): Maximum directory depth to walk.
528580
529581 Returns:
530582 ~collections.iterable: an iterable of directory paths
@@ -562,6 +614,7 @@ def info(self, path='/', namespaces=None, **kwargs):
562614 exclude_dirs (list): A list of patterns that will be used
563615 to filter out directories from the walk, e.g. ``['*.svn',
564616 '*.git']``.
617+ max_depth (int, optional): Maximum directory depth to walk.
565618
566619 Returns:
567620 ~collections.Iterable: an iterable yielding tuples of
0 commit comments