Skip to content

Commit

Permalink
Fix negate not evaluating full path (#25)
Browse files Browse the repository at this point in the history
* Fix negate not evaluating full path
Fixes #24

* Resolve test failure
We need to require a slash not "any" character when starting a globstar

* Resolve globmatch issue on Windows

* Add tests to catch original issue
  • Loading branch information
facelessuser authored Mar 20, 2019
1 parent eac64b1 commit 75dbbbf
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 14 deletions.
5 changes: 5 additions & 0 deletions docs/src/markdown/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 2.0.4

- **FIX**: Fix issue where negated patterns would trigger before end of path.
- **FIX**: Fix `GLOBSTAR` regular expression pattern issues.

## 2.0.3

- **FIX**: In `glob`, properly handle files in the current working directory when give a literal pattern that matches it.
Expand Down
14 changes: 9 additions & 5 deletions tests/test_fnmatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,11 @@ class TestFnMatchFilter:
0
],
[r'te\st[ma]', ['testm', 'test\\3', 'testa'], ['testm', 'testa'], fnmatch.I],
[r'te\st[ma]', ['testm', 'test\\3', 'testa'], ['testm', 'testa'], fnmatch.F]
[r'te\st[ma]', ['testm', 'test\\3', 'testa'], ['testm', 'testa'], fnmatch.F],

# Issue #24
['*.bar', ["goo.cfg", "foo.bar", "foo.bar.cfg", "foo.cfg.bar"], ["foo.bar", "foo.cfg.bar"], 0],
['!*.bar', ["goo.cfg", "foo.bar", "foo.bar.cfg", "foo.cfg.bar"], ["goo.cfg", "foo.bar.cfg"], fnmatch.N]
]

@classmethod
Expand Down Expand Up @@ -297,10 +301,10 @@ def test_split_parsing(self, mock__iscase_sensitive):
p1, p2 = self.split_translate('*test[a-z]?|*test2[a-z]?|!test[!a-z]|!test[!-|a-z]', flags | fnmatch.N)
if util.PY36:
self.assertEqual(p1, [r'^(?s:(?=.).*?test[a-z].)$', r'^(?s:(?=.).*?test2[a-z].)$'])
self.assertEqual(p2, [r'^(?!(?s:test[^a-z])).*?$', r'^(?!(?s:test[^\-\|a-z])).*?$'])
self.assertEqual(p2, [r'^(?!(?s:test[^a-z])$).*?$', r'^(?!(?s:test[^\-\|a-z])$).*?$'])
else:
self.assertEqual(p1, [r'(?s)^(?:(?=.).*?test[a-z].)$', r'(?s)^(?:(?=.).*?test2[a-z].)$'])
self.assertEqual(p2, [r'(?s)^(?!(?:test[^a-z])).*?$', r'(?s)^(?!(?:test[^\-\|a-z])).*?$'])
self.assertEqual(p2, [r'(?s)^(?!(?:test[^a-z])$).*?$', r'(?s)^(?!(?:test[^\-\|a-z])$).*?$'])

p1, p2 = self.split_translate('test[]][!][][]', flags | fnmatch.F)
if util.PY36:
Expand Down Expand Up @@ -332,10 +336,10 @@ def test_split_parsing(self, mock__iscase_sensitive):
p1, p2 = self.split_translate('-|-test|-', flags=flags | fnmatch.N | fnmatch.M)
if util.PY36:
self.assertEqual(p1, [])
self.assertEqual(p2, [r'^(?!(?s:)).*?$', r'^(?!(?s:test)).*?$', r'^(?!(?s:)).*?$'])
self.assertEqual(p2, [r'^(?!(?s:)$).*?$', r'^(?!(?s:test)$).*?$', r'^(?!(?s:)$).*?$'])
else:
self.assertEqual(p1, [])
self.assertEqual(p2, [r'(?s)^(?!(?:)).*?$', r'(?s)^(?!(?:test)).*?$', r'(?s)^(?!(?:)).*?$'])
self.assertEqual(p2, [r'(?s)^(?!(?:)$).*?$', r'(?s)^(?!(?:test)$).*?$', r'(?s)^(?!(?:)$).*?$'])

p1, p2 = self.split_translate('test[^chars]', flags)
if util.PY36:
Expand Down
9 changes: 5 additions & 4 deletions tests/test_glob.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,12 +366,12 @@ class Testglob(_TestGlob):

# Glob inverse
[
('a*',),
('a*', '**'),
[
('EF',), ('ZZZ',), ('',)
] if not can_symlink() else [
('EF',), ('ZZZ',), ('',), ('sym1',), ('sym3',), ('sym2',),
('sym3', 'efg'), ('sym3', 'efg', 'ha'), ('sym3', 'EF')
('a',), ('aaa', ), ('aab', ), ('sym3', 'efg'), ('sym3', 'efg', 'ha'), ('sym3', 'EF')
],
glob.N
],
Expand Down Expand Up @@ -714,12 +714,13 @@ class Testglob(_TestGlob):
],
Options(just_negative=True),
[
('a*',),
('a*', '**'),
[
('EF',), ('ZZZ',)
] if not can_symlink() else [
('EF',), ('ZZZ',),
('sym1',), ('sym3',), ('sym2',), ('sym3', 'efg'), ('sym3', 'efg', 'ha'), ('sym3', 'EF')
('a',), ('aaa', ), ('aab', ), ('sym1',), ('sym3',), ('sym2',), ('sym3', 'efg'), ('sym3', 'efg', 'ha'),
('sym3', 'EF')
],
glob.N
],
Expand Down
9 changes: 8 additions & 1 deletion tests/test_globmatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,14 @@ class TestGlobFilter:
['@(test/test)', [], glob.F],
[r'@(test\/test)', [], glob.F],
['test[/]test', [], glob.F],
[r'test[\/]test', [], glob.F]
[r'test[\/]test', [], glob.F],

# Issue #24
GlobFiles(
["goo.cfg", "foo.bar", "foo.bar.cfg", "foo.cfg.bar"]
),
['*.bar', ["foo.bar", "foo.cfg.bar"]],
['!*.bar', ["goo.cfg", "foo.bar.cfg"]]
]

@classmethod
Expand Down
12 changes: 9 additions & 3 deletions wcmatch/_wcparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
_GLOBSTAR_DIV = r'(?:^|$|%s)+'
# Lookahead to see there is one character.
_NEED_CHAR = r'(?=.)'
_NEED_SEP = r'(?=%s)'
# Group that matches one or none
_QMARK_GROUP = r'(?:%s)?'
# Group that matches Zero or more
Expand Down Expand Up @@ -914,7 +915,7 @@ def _handle_star(self, i, current):
current[-1] = value
else:
# Replace the last path separator
current[-1] = _NEED_CHAR
current[-1] = _NEED_SEP % self.get_path_sep()
current.append(value)
self.consume_path_sep(i)
current.append(sep)
Expand Down Expand Up @@ -1095,7 +1096,12 @@ def root(self, pattern, current):
m = RE_WIN_PATH.match(pattern)
if m:
drive = m.group(0).replace('\\\\', '\\')
if drive.endswith('\\'):
slash = True
drive = drive[:-1]
current.append(re.escape(drive))
if slash:
current.append(self.get_path_sep() + _ONE_OR_MORE)
i.advance(m.end(0))
self.consume_path_sep(i)

Expand Down Expand Up @@ -1161,9 +1167,9 @@ def parse(self):

case_flag = 'i' if not self.case_sensitive else ''
if util.PY36:
pattern = (r'^(?!(?s%s:%s)).*?$' if negative else r'^(?s%s:%s)$') % (case_flag, ''.join(result))
pattern = (r'^(?!(?s%s:%s)$).*?$' if negative else r'^(?s%s:%s)$') % (case_flag, ''.join(result))
else:
pattern = (r'(?s%s)^(?!(?:%s)).*?$' if negative else r'(?s%s)^(?:%s)$') % (case_flag, ''.join(result))
pattern = (r'(?s%s)^(?!(?:%s)$).*?$' if negative else r'(?s%s)^(?:%s)$') % (case_flag, ''.join(result))

if self.is_bytes:
pattern = pattern.encode('latin-1')
Expand Down
2 changes: 1 addition & 1 deletion wcmatch/file_hidden.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def _test(fn): # pragma: no cover
cf.CFBooleanGetValue.restype = ctypes.c_int

# This one is a static `CFStringRef`.
kCFURLIsHiddenKey = ctypes.c_void_p.in_dll(cf, 'kCFURLIsHiddenKey')
kCFURLIsHiddenKey = ctypes.c_void_p.in_dll(cf, 'kCFURLIsHiddenKey') # noqa: N816

@contextlib.contextmanager
def cfreleasing(objects):
Expand Down

0 comments on commit 75dbbbf

Please sign in to comment.