Skip to content

Commit

Permalink
fix for filterdir
Browse files Browse the repository at this point in the history
  • Loading branch information
willmcgugan committed Dec 4, 2016
1 parent 1fc0f9d commit 3b1cb2c
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 52 deletions.
87 changes: 41 additions & 46 deletions fs/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import os
import threading
import time
from functools import partial

from contextlib import closing
import itertools
Expand Down Expand Up @@ -370,27 +371,26 @@ def exists(self, path):

def filterdir(self,
path,
exclude_dirs=False,
exclude_files=False,
files=None,
dirs=None,
exclude_dirs=None,
exclude_files=None,
namespaces=None,
page=None):
"""
Get an iterator of resource info, filtered by wildcards.
Get an iterator of resource info, filtered by file patterns.
:param str path: A path to a directory on the filesystem.
:param bool exclude_dirs: Exclude directories.
:param bool exclude_files: Exclude files.
:param files: A list of unix shell-style patterns to filter
:param list files: A list of unix shell-style patterns to filter
file names, e.g. ``['*.py']``.
:type wildcards: list or None
:param dirs: A list of unix shell-style wildcards to
:param list dirs: A list of unix shell-style wildcards to
filter directory names.
:type dirs: list or None
:param namespaces: A list of info namespaces to include in
results.
:type namespaces: list or None
:param list exclude_dirs: An optional list of patterns used to
exclude directories
:param list exclude_files: An optional list of patterns used to
exclude files.
:param list namespaces: A list of namespaces to include in
the resource information.
:param page: May be a tuple of ``(<start>, <end>)`` indexes to
return an iterator of a subset of the resource info, or
``None`` to iterate over the entire directory. Paging a
Expand All @@ -399,51 +399,44 @@ def filterdir(self,
:return: An iterator of :class:`~fs.info.Info` objects.
:rtype: iterator
This method enhances the :meth:`~fs.base.FS.scandir` method with
additional filtering functionality.
This method enhances :meth:`~fs.base.FS.scandir` with additional
filtering functionality.
"""

case_sensitive = self.getmeta().get('case_sensitive', True)

resources = self.scandir(path, namespaces=namespaces)
filters = []

if exclude_dirs:
resources = (
info
for info in resources
if not info.is_dir
)
def match_dir(patterns, info):
"""Pattern match info.name"""
return info.is_file or self.match(patterns, info.name)

if exclude_files:
resources = (
info
for info in resources
if info.is_dir
)
def match_file(patterns, info):
"""Pattern match info.name"""
return info.is_dir or self.match(patterns, info.name)

if files is not None:
if isinstance(files, six.text_type):
raise ValueError(
'wildcards must be a sequence, not a string'
)
match = wildcard.get_matcher(files, case_sensitive)
resources = (
info
for info in resources
if info.is_dir or match(info.name)
)
def exclude_dir(patterns, info):
"""Pattern match info.name"""
return info.is_file or not self.match(patterns, info.name)

if dirs is not None:
if isinstance(dirs, six.text_type):
raise ValueError(
'dir_wildcards must be a sequence, not a string'
)
match = wildcard.get_matcher(dirs, case_sensitive)
def exclude_file(patterns, info):
"""Pattern match info.name"""
return info.is_dir or not self.match(patterns, info.name)

if files:
filters.append(partial(match_file, files))
if dirs:
filters.append(partial(match_dir, dirs))
if exclude_dirs:
filters.append(partial(exclude_dir, exclude_dirs))
if exclude_files:
filters.append(partial(exclude_file, exclude_files))

if filters:
resources = (
info
for info in resources
if not info.is_dir or match(info.name)
if all(_filter(info) for _filter in filters)
)

iter_info = iter(resources)
Expand Down Expand Up @@ -1211,6 +1204,8 @@ def match(self, patterns, name):
"""
if patterns is None:
return True
if isinstance(patterns, six.text_type):
raise ValueError('patterns must be a list or sequence')
case_sensitive = self.getmeta().get('case_sensitive', True)
matcher = wildcard.get_matcher(patterns, case_sensitive)
return matcher(name)
Expand Down
10 changes: 10 additions & 0 deletions fs/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,16 @@ def is_dir(self):
"""
return self.get('basic', 'is_dir')

@property
def is_file(self):
"""
Check if a resource references a file.
:rtype: bool
"""
return not self.get('basic', 'is_dir')

@property
def type(self):
"""
Expand Down
8 changes: 4 additions & 4 deletions fs/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1122,23 +1122,23 @@ def test_filterdir(self):
self.assertEqual(set(dir_list), {'bar', 'foo.py', 'foo.pyc'})

# Check excluding dirs
dir_list = [info.name for info in self.fs.filterdir('/', exclude_dirs=True, files=['*.py', '*.pyc'])]
dir_list = [info.name for info in self.fs.filterdir('/', exclude_dirs=['*'], files=['*.py', '*.pyc'])]
self.assertEqual(set(dir_list), {'foo.py', 'foo.pyc'})

# Check excluding files
dir_list = [info.name for info in self.fs.filterdir('/', exclude_files=True)]
dir_list = [info.name for info in self.fs.filterdir('/', exclude_files=['*'])]
self.assertEqual(set(dir_list), {'bar'})

# Check wildcards must be a list
with self.assertRaises(ValueError):
dir_list = [info.name for info in self.fs.filterdir('/', files="*.py")]

self.fs.makedir('baz')
dir_list = [info.name for info in self.fs.filterdir('/', exclude_files=True, dirs=['??z'])]
dir_list = [info.name for info in self.fs.filterdir('/', exclude_files=['*'], dirs=['??z'])]
self.assertEqual(set(dir_list), {'baz'})

with self.assertRaises(ValueError):
dir_list = [info.name for info in self.fs.filterdir('/', exclude_files=True, dirs="*.py")]
dir_list = [info.name for info in self.fs.filterdir('/', exclude_files=['*'], dirs="*.py")]

def test_getbytes(self):
# Test getbytes method.
Expand Down
10 changes: 8 additions & 2 deletions fs/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ def render(fs,
encoding=None,
max_levels=5,
with_color=None,
dirs_first=True):
dirs_first=True,
exclude=None,
filter=None):
"""
Render a directory structure in to a pretty tree.
Expand All @@ -35,6 +37,10 @@ def render(fs,
:param bool with_color: Enable terminal color output, or None to
auto-detect terminal.
:param bool dirs_first: Show directories first.
:param list exclude: Option list of directory patterns to exclude
from the tree render.
:param filter: Optional list of files patterns to match in the tree
render.
:rtype: tuple
:returns: A tuple of ``(<directory count>, <file count>)``.
Expand Down Expand Up @@ -106,7 +112,7 @@ def format_directory(path, levels=[]):
"""Recursive directory function."""
try:
directory = sorted(
fs.filterdir(path),
fs.filterdir(path, exclude_dirs=exclude, files=filter),
key=sort_key_dirs_first if dirs_first else sort_key
)
except Exception as error:
Expand Down

0 comments on commit 3b1cb2c

Please sign in to comment.