Add utility functions around filename matching

We add utils.fnmatch and utils.filenames_for in anticipation of our
checker manager creating file checkers for each file. We also include
tests for these functions and a couple previously untested utility
functions.
This commit is contained in:
Ian Cordasco 2016-02-22 21:47:43 -06:00
parent a21c328870
commit 8792c30872
2 changed files with 101 additions and 0 deletions

View file

@ -1,4 +1,5 @@
"""Utility methods for flake8."""
import fnmatch as _fnmatch
import io
import os
import sys
@ -89,3 +90,57 @@ def is_using_stdin(paths):
bool
"""
return '-' in paths
def _default_predicate(*args):
return False
def filenames_from(arg, predicate=None):
# type: (str) -> Generator
"""Generate filenames from an argument.
:param str arg:
Parameter from the command-line.
:param callable predicate:
Predicate to use to filter out filenames. If the predicate
returns ``True`` we will exclude the filename, otherwise we
will yield it.
:returns:
Generator of paths
"""
if predicate is None:
predicate = _default_predicate
if os.path.isdir(arg):
for root, sub_directories, files in os.walk(arg):
for filename in files:
joined = os.path.join(root, filename)
if predicate(filename) or predicate(joined):
continue
yield joined
# NOTE(sigmavirus24): os.walk() will skip a directory if you
# remove it from the list of sub-directories.
for directory in sub_directories:
if predicate(directory):
sub_directories.remove(directory)
else:
yield arg
def fnmatch(filename, patterns, default=True):
# type: (str, List[str], bool) -> bool
"""Wrap :func:`fnmatch.fnmatch` to add some functionality.
:param str filename:
Name of the file we're trying to match.
:param list patterns:
Patterns we're using to try to match the filename.
:param bool default:
The default value if patterns is empty
:returns:
True if a pattern matches the filename, False if it doesn't.
``default`` if patterns is empty.
"""
if not patterns:
return default
return any(_fnmatch.fnmatch(filename, pattern) for pattern in patterns)

View file

@ -1,5 +1,6 @@
"""Tests for flake8's utils module."""
import os
import mock
import pytest
@ -40,3 +41,48 @@ def test_normalize_path(value, expected):
def test_normalize_paths(value, expected):
"""Verify we normalize comma-separated paths provided to the tool."""
assert utils.normalize_paths(value) == expected
def test_is_windows_checks_for_nt():
"""Verify that we correctly detect Windows."""
with mock.patch.object(os, 'name', 'nt'):
assert utils.is_windows() is True
with mock.patch.object(os, 'name', 'posix'):
assert utils.is_windows() is False
@pytest.mark.parametrize('filename,patterns,expected', [
('foo.py', [], True),
('foo.py', ['*.pyc'], False),
('foo.pyc', ['*.pyc'], True),
('foo.pyc', ['*.swp', '*.pyc', '*.py'], True),
])
def test_fnmatch(filename, patterns, expected):
"""Verify that our fnmatch wrapper works as expected."""
assert utils.fnmatch(filename, patterns) is expected
def test_filenames_from_a_directory():
"""Verify that filenames_from walks a directory."""
filenames = list(utils.filenames_from('flake8/'))
assert len(filenames) > 2
assert 'flake8/__init__.py' in filenames
def test_filenames_from_a_directory_with_a_predicate():
"""Verify that predicates filter filenames_from."""
filenames = list(utils.filenames_from(
arg='flake8/',
predicate=lambda filename: filename == 'flake8/__init__.py',
))
assert len(filenames) > 2
assert 'flake8/__init__.py' not in filenames
def test_filenames_from_a_single_file():
"""Verify that we simply yield that filename."""
filenames = list(utils.filenames_from('flake8/__init__.py'))
assert len(filenames) == 1
assert ['flake8/__init__.py'] == filenames