From 45829da837d7447725e2a4b08d756b53cffd2fb8 Mon Sep 17 00:00:00 2001 From: cpburnz Date: Fri, 7 Nov 2014 08:29:29 -0500 Subject: [PATCH] Fixed matching Windows paths. --- CHANGES.rst | 6 ++++++ pathspec/__init__.py | 6 +++--- pathspec/compat.py | 17 ++++++++++++--- pathspec/gitignore.py | 2 +- pathspec/pathspec.py | 23 ++++++++++++++------ pathspec/pattern.py | 2 +- pathspec/tests/__init__.py | 7 +++++++ pathspec/tests/test_gitignore.py | 2 +- pathspec/tests/test_pathspec.py | 35 +++++++++++++++++++++++++++++++ pathspec/util.py | 36 +++++++++++++++++++++++++++++--- setup.py | 4 ++-- 11 files changed, 120 insertions(+), 20 deletions(-) create mode 100644 pathspec/tests/test_pathspec.py diff --git a/CHANGES.rst b/CHANGES.rst index 238a24d..281de0d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,12 @@ Change History ============== +0.3.2 (TBD) +----------- + +- Issue #6: Fixed matching Windows paths. + + 0.3.1 (2014-09-17) ------------------ diff --git a/pathspec/__init__.py b/pathspec/__init__.py index 007f292..1625a76 100644 --- a/pathspec/__init__.py +++ b/pathspec/__init__.py @@ -1,4 +1,4 @@ -# coding: utf-8 +# encoding: utf-8 """ The *pathspec* package provides pattern matching for file paths. So far this only includes gitignore style pattern matching. @@ -18,8 +18,8 @@ __license__ = "MPL 2.0" __project__ = "pathspec" __status__ = "Development" -__updated__ = "2014-09-17" -__version__ = "0.3.1" +__updated__ = "2014-11-07" +__version__ = "0.3.2b1" from .gitignore import GitIgnorePattern from .pathspec import PathSpec diff --git a/pathspec/compat.py b/pathspec/compat.py index d999283..4f9e450 100644 --- a/pathspec/compat.py +++ b/pathspec/compat.py @@ -1,4 +1,4 @@ -# coding: utf-8 +# encoding: utf-8 """ This module provides compatibility between Python 2 and 3. Hardly anything is used by this project to constitute including `six`_. @@ -6,7 +6,18 @@ .. _`six`: http://pythonhosted.org/six """ -try: +import sys + +if sys.version_info[0] < 3: + # Python 2. string_types = (basestring,) -except NameError: + + def viewkeys(mapping): + return mapping.viewkeys() + +else: + # Python 3. string_types = (str,) + + def viewkeys(mapping): + return mapping.keys() diff --git a/pathspec/gitignore.py b/pathspec/gitignore.py index 7dfec4e..1d14912 100644 --- a/pathspec/gitignore.py +++ b/pathspec/gitignore.py @@ -1,4 +1,4 @@ -# coding: utf-8 +# encoding: utf-8 """ This module implements gitignore style pattern matching which incorporates POSIX glob patterns. diff --git a/pathspec/pathspec.py b/pathspec/pathspec.py index abf42d1..100d5ec 100644 --- a/pathspec/pathspec.py +++ b/pathspec/pathspec.py @@ -1,4 +1,4 @@ -# coding: utf-8 +# encoding: utf-8 """ This module provides an object oriented interface for pattern matching of files. @@ -7,7 +7,7 @@ import collections from . import util -from .compat import string_types +from .compat import string_types, viewkeys class PathSpec(object): @@ -63,16 +63,27 @@ def from_lines(cls, pattern_factory, lines): lines = [pattern_factory(line) for line in lines if line] return cls(lines) - def match_files(self, files): + def match_files(self, files, separators=None): """ Matches the files to this path-spec. *files* (``Iterable`` of ``str``) contains the files to be matched against *patterns*. - Returns the matched files (``set`` of ``str``). + *separators* (``Container`` of ``str``) optionally contains the path + separators to normalize. This does not need to include the POSIX + path separator (`/`), but including it will not affect the results. + Default is ``None`` to determine the separators based upon the + current operating system by examining `os.sep` and `os.altsep`. To + prevent normalization, pass an empty container (e.g., an empty tuple + `()`). + + Returns the matched files (``Iterable`` of ``str``). """ - return util.match_files(self.patterns, files) + file_map = util.normalize_files(files, separators=separators) + matched_files = util.match_files(self.patterns, viewkeys(file_map)) + for path in matched_files: + yield file_map[path] def match_tree(self, root): """ @@ -81,7 +92,7 @@ def match_tree(self, root): *root* (``str``) is the root directory to search for files. - Returns the matched files (``set`` of ``str``). + Returns the matched files (``Iterable`` of ``str``). """ files = util.iter_tree(root) return self.match_files(files) diff --git a/pathspec/pattern.py b/pathspec/pattern.py index ab521b7..f46dbf1 100644 --- a/pathspec/pattern.py +++ b/pathspec/pattern.py @@ -1,4 +1,4 @@ -# coding: utf-8 +# encoding: utf-8 """ This module provides the base definition for patterns. """ diff --git a/pathspec/tests/__init__.py b/pathspec/tests/__init__.py index e69de29..1ffe460 100644 --- a/pathspec/tests/__init__.py +++ b/pathspec/tests/__init__.py @@ -0,0 +1,7 @@ +# encoding: utf-8 +""" +This package provides tests. +""" + +from .test_pathspec import PathSpecTest +from .test_gitignore import GitIgnoreTest diff --git a/pathspec/tests/test_gitignore.py b/pathspec/tests/test_gitignore.py index 551fe1d..80429c5 100644 --- a/pathspec/tests/test_gitignore.py +++ b/pathspec/tests/test_gitignore.py @@ -1,4 +1,4 @@ -# coding: utf-8 +# encoding: utf-8 """ This script tests ``GitIgnorePattern``. """ diff --git a/pathspec/tests/test_pathspec.py b/pathspec/tests/test_pathspec.py new file mode 100644 index 0000000..4628dd6 --- /dev/null +++ b/pathspec/tests/test_pathspec.py @@ -0,0 +1,35 @@ +# encoding: utf-8 +""" +This script tests ``PathSpec``. +""" + +import unittest + +import pathspec + +class PathSpecTest(unittest.TestCase): + """ + The ``PathSpecTest`` class tests the ``PathSpec`` class. + """ + + def test_01_windows_paths(self): + """ + Tests that Windows paths will be properly normalized and matched. + """ + spec = pathspec.PathSpec.from_lines('gitignore', [ + '*.txt', + '!test1/', + ]) + results = set(spec.match_files([ + 'src\\test1\\a.txt', + 'src\\test1\\b.txt', + 'src\\test1\\c\\c.txt', + 'src\\test2\\a.txt', + 'src\\test2\\b.txt', + 'src\\test2\\c\\c.txt', + ], separators=('\\',))) + self.assertEquals(results, { + 'src\\test2\\a.txt', + 'src\\test2\\b.txt', + 'src\\test2\\c\\c.txt', + }) diff --git a/pathspec/util.py b/pathspec/util.py index c34317c..3d14249 100644 --- a/pathspec/util.py +++ b/pathspec/util.py @@ -1,4 +1,4 @@ -# coding: utf-8 +# encoding: utf-8 """ This module provides utility methods for dealing with path-specs. """ @@ -9,6 +9,13 @@ from .compat import string_types +NORMALIZE_PATH_SEPS = [sep for sep in [os.sep, os.altsep] if sep and sep != '/'] +""" +*NORMALIZE_PATH_SEPS* (``list`` of ``str``) contains the path separators +that need to be normalized to the POSIX separator for the current +operating system. +""" + _registered_patterns = {} """ *_registered_patterns* (``dict``) maps a name (``str``) to the @@ -66,8 +73,8 @@ def match_files(patterns, files): *patterns* (``Iterable`` of ``pathspec.Pattern``) contains the patterns to use. - *files* (``Iterable`` of ``str``) contains the files to be matched - against *patterns*. + *files* (``Iterable`` of ``str``) contains the normalized files to be + matched against *patterns*. Returns the matched files (``set`` of ``str``). """ @@ -82,6 +89,29 @@ def match_files(patterns, files): return_files.difference_update(result_files) return return_files +def normalize_files(files, separators=None): + """ + Normalizes the file paths to use the POSIX path separator (i.e., `/`). + + *files* (``Iterable`` of ``str``) contains the file paths to be + normalized. + + *separators* (``Container`` of ``str``) optionally contains the path + separators to normalize. + + Returns a ``dict`` mapping the normalized file path (``str``) to the + original file path (``str``) + """ + if separators is None: + separators = NORMALIZE_PATH_SEPS + file_map = {} + for path in files: + norm = path + for sep in separators: + norm = norm.replace(sep, '/') + file_map[norm] = path + return file_map + def register_pattern(name, pattern_factory, override=None): """ Registers the specified pattern factory. diff --git a/setup.py b/setup.py index 2debb3a..ce22e91 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -# coding: utf-8 +# encoding: utf-8 import io from setuptools import setup, find_packages @@ -33,5 +33,5 @@ ], license=__license__, packages=find_packages(), - test_suite='pathspec.tests.test_gitignore.GitIgnoreTest', + test_suite='pathspec.tests', )