New Upstream Release - python-wcmatch

Ready changes

Summary

Merged new upstream version: 8.4.1 (was: 8.4).

Resulting package

Built on 2022-10-20T11:32 (took 2m13s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases python3-wcmatch

Lintian Result

Diff

diff --git a/debian/changelog b/debian/changelog
index b45c50e..30252af 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+python-wcmatch (8.4.1-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Thu, 20 Oct 2022 11:30:54 -0000
+
 python-wcmatch (8.4-2) unstable; urgency=medium
 
   [ Debian Janitor ]
diff --git a/docs/src/markdown/about/changelog.md b/docs/src/markdown/about/changelog.md
index df2082d..2ef5859 100644
--- a/docs/src/markdown/about/changelog.md
+++ b/docs/src/markdown/about/changelog.md
@@ -1,5 +1,10 @@
 # Changelog
 
+## 8.4.1
+
+- **FIX**: Windows drive path separators should normalize like other path separators.
+- **FIX**: Fix a Windows pattern parsing issue that caused absolute paths with ambiguous drives to not parse correctly.
+
 ## 8.4
 
 - **NEW**: Drop support for Python 3.6.
diff --git a/docs/src/markdown/fnmatch.md b/docs/src/markdown/fnmatch.md
index 576da39..a68901a 100644
--- a/docs/src/markdown/fnmatch.md
+++ b/docs/src/markdown/fnmatch.md
@@ -22,7 +22,7 @@ Pattern           | Meaning
 `[seq]`           | Matches any character in seq.
 `[!seq]`          | Matches any character not in seq. Will also accept character exclusions in the form of `[^seq]`.
 `[[:alnum:]]`     | POSIX style character classes inside sequences. See [POSIX Character Classes](#posix-character-classes) for more info.
-`\`               | Escapes characters. If applied to a meta character, it will be treated as a normal character.
+`\`               | Escapes characters. If applied to a meta character or non-meta characters, the character will be treated as a literal character. If applied to another escape, the backslash will be a literal backslash.
 `!`               | When used at the start of a pattern, the pattern will be an exclusion pattern. Requires the [`NEGATE`](#negate) flag. If also using the [`MINUSNEGATE`](#minusnegate) flag, `-` will be used instead of `!`.
 `?(pattern_list)` | The pattern matches if zero or one occurrences of any of the patterns in the `pattern_list` match the input string. Requires the [`EXTMATCH`](#extmatch) flag.
 `*(pattern_list)` | The pattern matches if zero or more occurrences of any of the patterns in the `pattern_list` match the input string. Requires the [`EXTMATCH`](#extmatch) flag.
@@ -63,7 +63,7 @@ a list of patterns. It will return a boolean indicating whether the file name wa
 
 ```pycon3
 >>> from wcmatch import fnmatch
->>> fnmatch.fnmatch('test.txt', r'@(*.txt|*.py)', flags=fnmatch.EXTMATCH)
+>>> fnmatch.fnmatch('test.txt', '@(*.txt|*.py)', flags=fnmatch.EXTMATCH)
 True
 ```
 
@@ -71,7 +71,7 @@ When applying multiple patterns, a file matches if it matches any of the pattern
 
 ```pycon3
 >>> from wcmatch import fnmatch
->>> fnmatch.fnmatch('test.txt', [r'*.txt', r'*.py'], flags=fnmatch.EXTMATCH)
+>>> fnmatch.fnmatch('test.txt', ['*.txt', '*.py'], flags=fnmatch.EXTMATCH)
 True
 ```
 
@@ -93,13 +93,13 @@ meant to filter other patterns, not match anything by themselves.
 
 ```pycon3
 >>> from wcmatch import fnmatch
->>> fnmatch.fnmatch('test.py', r'*|!*.py', flags=fnmatch.NEGATE | fnmatch.SPLIT)
+>>> fnmatch.fnmatch('test.py', '*|!*.py', flags=fnmatch.NEGATE | fnmatch.SPLIT)
 False
->>> fnmatch.fnmatch('test.txt', r'*|!*.py', flags=fnmatch.NEGATE | fnmatch.SPLIT)
+>>> fnmatch.fnmatch('test.txt', '*|!*.py', flags=fnmatch.NEGATE | fnmatch.SPLIT)
 True
->>> fnmatch.fnmatch('test.txt', [r'*.txt', r'!avoid.txt'], flags=fnmatch.NEGATE)
+>>> fnmatch.fnmatch('test.txt', ['*.txt', '!avoid.txt'], flags=fnmatch.NEGATE)
 True
->>> fnmatch.fnmatch('avoid.txt', [r'*.txt', r'!avoid.txt'], flags=fnmatch.NEGATE)
+>>> fnmatch.fnmatch('avoid.txt', ['*.txt', '!avoid.txt'], flags=fnmatch.NEGATE)
 False
 ```
 
@@ -110,9 +110,9 @@ pattern were given: `*` and `!*.md`.
 
 ```pycon3
 >>> from wcmatch import fnmatch
->>> fnmatch.fnmatch('test.py', r'!*.py', flags=fnmatch.NEGATE | fnmatch.NEGATEALL)
+>>> fnmatch.fnmatch('test.py', '!*.py', flags=fnmatch.NEGATE | fnmatch.NEGATEALL)
 False
->>> fnmatch.fnmatch('test.txt', r'!*.py', flags=fnmatch.NEGATE | fnmatch.NEGATEALL)
+>>> fnmatch.fnmatch('test.txt', '!*.py', flags=fnmatch.NEGATE | fnmatch.NEGATEALL)
 True
 ```
 
@@ -135,7 +135,7 @@ pattern or a list of patterns.It returns a list of all files that matched the pa
 
 ```pycon3
 >>> from wcmatch import fnmatch
->>> fnmatch.filter(['a.txt', 'b.txt', 'c.py'], r'*.txt')
+>>> fnmatch.filter(['a.txt', 'b.txt', 'c.py'], '*.txt')
 ['a.txt', 'b.txt']
 ```
 
@@ -159,9 +159,9 @@ matches at least one inclusion pattern and matches **none** of the exclusion pat
 
 ```pycon3
 >>> from wcmatch import fnmatch
->>> fnmatch.translate(r'*.{a,{b,c}}', flags=fnmatch.BRACE)
+>>> fnmatch.translate('*.{a,{b,c}}', flags=fnmatch.BRACE)
 (['^(?s:(?=.)(?![.]).*?\\.a)$', '^(?s:(?=.)(?![.]).*?\\.b)$', '^(?s:(?=.)(?![.]).*?\\.c)$'], [])
->>> fnmatch.translate(r'**|!*.{a,{b,c}}', flags=fnmatch.BRACE | fnmatch.NEGATE | fnmatch.SPLIT)
+>>> fnmatch.translate('**|!*.{a,{b,c}}', flags=fnmatch.BRACE | fnmatch.NEGATE | fnmatch.SPLIT)
 (['^(?s:(?=.)(?![.]).*?)$'], ['^(?s:(?=.).*?\\.a)$', '^(?s:(?=.).*?\\.b)$', '^(?s:(?=.).*?\\.c)$'])
 ```
 
@@ -346,9 +346,9 @@ it's warnings related to pairing it with `SPLIT`.
 
 ```pycon3
 >>> from wcmatch import fnmatch
->>> fnmatch.fnmatch('test.txt', r'*.txt|*.py', flags=fnmatch.SPLIT)
+>>> fnmatch.fnmatch('test.txt', '*.txt|*.py', flags=fnmatch.SPLIT)
 True
->>> fnmatch.fnmatch('test.py', r'*.txt|*.py', flags=fnmatch.SPLIT)
+>>> fnmatch.fnmatch('test.py', '*.txt|*.py', flags=fnmatch.SPLIT)
 True
 ```
 
diff --git a/docs/src/markdown/glob.md b/docs/src/markdown/glob.md
index d324acf..cd088d2 100644
--- a/docs/src/markdown/glob.md
+++ b/docs/src/markdown/glob.md
@@ -23,7 +23,7 @@ Pattern           | Meaning
 `[seq]`           | Matches any character in seq.
 `[!seq]`          | Matches any character not in seq. Will also accept character exclusions in the form of `[^seq]`.
 `[[:alnum:]]`     | POSIX style character classes inside sequences. See [POSIX Character Classes](#posix-character-classes) for more info.
-`\`               | Escapes characters. If applied to a meta character, it will be treated as a normal character.
+`\`               | Escapes characters. If applied to a meta character or non-meta characters, the character will be treated as a literal character. If applied to another escape, the backslash will be a literal backslash.
 `!`               | When used at the start of a pattern, the pattern will be an exclusion pattern. Requires the [`NEGATE`](#negate) flag. If also using the [`MINUSNEGATE`](#minusnegate) flag, `-` will be used instead of `!`.
 `?(pattern_list)` | The pattern matches if zero or one occurrences of any of the patterns in the `pattern_list` match the input string. Requires the [`EXTGLOB`](#extglob) flag.
 `*(pattern_list)` | The pattern matches if zero or more occurrences of any of the patterns in the `pattern_list` match the input string. Requires the [`EXTGLOB`](#extglob) flag.
@@ -152,6 +152,24 @@ Pattern           | Meaning
 
 --8<-- "posix.md"
 
+## Windows Separators
+
+On Windows, it is not required to use backslashes for path separators as `/` will match path separators for all systems.
+The following will work on Windows and Linux/Unix systems.
+
+```python
+glob.glob('docs/.*')
+```
+
+With that said, you can match Windows separators with backslashes as well. Keep in mind that Wildcard Match allows
+escaped characters in patterns, so to match a literal backslash separator, you must escape the backslash. It is advised
+to use raw strings when using backslashes to make the patterns more readable, but either of the below will work.
+
+```python
+glob.glob(r'docs\\.*')
+glob.glob('docs\\\\.*')
+```
+
 ## Multi-Pattern Limits
 
 Many of the API functions allow passing in multiple patterns or using either [`BRACE`](#brace) or
@@ -180,7 +198,7 @@ file system returning matching files.
 
 ```pycon3
 >>> from wcmatch import glob
->>> glob.glob(r'**/*.md')
+>>> glob.glob('**/*.md')
 ['docs/src/markdown/_snippets/abbr.md', 'docs/src/markdown/_snippets/links.md', 'docs/src/markdown/_snippets/refs.md', 'docs/src/markdown/changelog.md', 'docs/src/markdown/fnmatch.md', 'docs/src/markdown/glob.md', 'docs/src/markdown/index.md', 'docs/src/markdown/installation.md', 'docs/src/markdown/license.md', 'README.md']
 ```
 
@@ -188,7 +206,7 @@ Using a list, we can add exclusion patterns and also exclude directories and/or
 
 ```pycon3
 >>> from wcmatch import glob
->>> glob.glob([r'**/*.md', r'!README.md', r'!**/_snippets'], flags=glob.NEGATE)
+>>> glob.glob(['**/*.md', '!README.md', '!**/_snippets'], flags=glob.NEGATE)
 ['docs/src/markdown/changelog.md', 'docs/src/markdown/fnmatch.md', 'docs/src/markdown/glob.md', 'docs/src/markdown/index.md', 'docs/src/markdown/installation.md', 'docs/src/markdown/license.md']
 ```
 
@@ -196,7 +214,7 @@ When a glob pattern ends with a slash, it will only return directories:
 
 ```pycon3
 >>> from wcmatch import glob
->>> glob.glob(r'**/')
+>>> glob.glob('**/')
 ['__pycache__/', 'docs/', 'docs/src/', 'docs/src/markdown/', 'docs/src/markdown/_snippets/', 'docs/theme/', 'requirements/', 'stuff/', 'tests/', 'tests/__pycache__/', 'wcmatch/', 'wcmatch/__pycache__/']
 ```
 
@@ -313,7 +331,7 @@ def iglob(patterns, *, flags=0, root_dir=None, dir_fd=None, limit=1000, exclude=
 
 ```pycon3
 >>> from wcmatch import glob
->>> list(glob.iglob(r'**/*.md'))
+>>> list(glob.iglob('**/*.md'))
 ['docs/src/markdown/_snippets/abbr.md', 'docs/src/markdown/_snippets/links.md', 'docs/src/markdown/_snippets/refs.md', 'docs/src/markdown/changelog.md', 'docs/src/markdown/fnmatch.md', 'docs/src/markdown/glob.md', 'docs/src/markdown/index.md', 'docs/src/markdown/installation.md', 'docs/src/markdown/license.md', 'README.md']
 ```
 
@@ -342,7 +360,7 @@ boolean indicating whether the file path was matched by the pattern(s).
 
 ```pycon3
 >>> from wcmatch import glob
->>> glob.globmatch('some/path/test.txt', r'**/*/@(*.txt|*.py)', flags=glob.EXTGLOB)
+>>> glob.globmatch('some/path/test.txt', '**/*/@(*.txt|*.py)', flags=glob.EXTGLOB)
 True
 ```
 
@@ -350,7 +368,7 @@ When applying multiple patterns, a file path matches if it matches any of the pa
 
 ```pycon3
 >>> from wcmatch import glob
->>> glob.globmatch('some/path/test.txt', [r'**/*/*.txt', r'**/*/*.py'])
+>>> glob.globmatch('some/path/test.txt', ['**/*/*.txt', '**/*/*.py'])
 True
 ```
 
@@ -361,13 +379,13 @@ to filter other patterns, not match anything by themselves.
 
 ```pycon3
 >>> from wcmatch import glob
->>> glob.globmatch('some/path/test.py', r'**|!**/*.txt', flags=glob.NEGATE | glob.GLOBSTAR | glob.SPLIT)
+>>> glob.globmatch('some/path/test.py', '**|!**/*.txt', flags=glob.NEGATE | glob.GLOBSTAR | glob.SPLIT)
 True
->>> glob.globmatch('some/path/test.txt', r'**|!**/*.txt', flags=glob.NEGATE | glob.GLOBSTAR | glob.SPLIT)
+>>> glob.globmatch('some/path/test.txt', '**|!**/*.txt', flags=glob.NEGATE | glob.GLOBSTAR | glob.SPLIT)
 False
->>> glob.globmatch('some/path/test.txt', [r'*/*/*.txt', r'!*/*/avoid.txt'], flags=glob.NEGATE)
+>>> glob.globmatch('some/path/test.txt', ['*/*/*.txt', '!*/*/avoid.txt'], flags=glob.NEGATE)
 True
->>> glob.globmatch('some/path/avoid.txt', [r'*/*/*.txt', r'!*/*/avoid.txt'], flags=glob.NEGATE)
+>>> glob.globmatch('some/path/avoid.txt', ['*/*/*.txt', '!*/*/avoid.txt'], flags=glob.NEGATE)
 False
 ```
 
@@ -379,9 +397,9 @@ if [`GLOBSTAR`](#globstar) was enabled).
 
 ```pycon3
 >>> from wcmatch import glob
->>> glob.globmatch('some/path/test.py', r'!**/*.txt', flags=glob.NEGATE | glob.GLOBSTAR | glob.NEGATEALL)
+>>> glob.globmatch('some/path/test.py', '!**/*.txt', flags=glob.NEGATE | glob.GLOBSTAR | glob.NEGATEALL)
 True
->>> glob.globmatch('some/path/test.txt', r'!**/*.txt', flags=glob.NEGATE | glob.GLOBSTAR | glob.NEGATEALL)
+>>> glob.globmatch('some/path/test.txt', '!**/*.txt', flags=glob.NEGATE | glob.GLOBSTAR | glob.NEGATEALL)
 False
 ```
 
@@ -485,7 +503,7 @@ for [`globmatch`](#globmatch) is used for `globfilter`, albeit more efficient fo
 
 ```pycon3
 >>> from wcmatch import glob
->>> glob.globfilter(['some/path/a.txt', 'b.txt', 'another/path/c.py'], r'**/*.txt')
+>>> glob.globfilter(['some/path/a.txt', 'b.txt', 'another/path/c.py'], '**/*.txt')
 ['some/path/a.txt', 'b.txt']
 ```
 
@@ -521,9 +539,9 @@ matches at least one inclusion pattern and matches **none** of the exclusion pat
 
 ```pycon3
 >>> from wcmatch import glob
->>> glob.translate(r'**/*.{py,txt}')
+>>> glob.translate('**/*.{py,txt}')
 (['^(?s:(?=[^/])(?!(?:\\.{1,2})(?:$|[/]))(?:(?!\\.)[^/]*?)?[/]+(?=[^/])(?!(?:\\.{1,2})(?:$|[/]))(?:(?!\\.)[^/]*?)?\\.\\{py,txt\\}[/]*?)$'], [])
->>> glob.translate(r'**|!**/*.{py,txt}', flags=glob.NEGATE | glob.SPLIT)
+>>> glob.translate('**|!**/*.{py,txt}', flags=glob.NEGATE | glob.SPLIT)
 (['^(?s:(?=[^/])(?!(?:\\.{1,2})(?:$|[/]))(?:(?!\\.)[^/]*?)?[/]*?)$'], ['^(?s:(?=[^/])(?!(?:\\.{1,2})(?:$|[/]))[^/]*?[/]+(?=[^/])(?!(?:\\.{1,2})(?:$|[/]))[^/]*?\\.\\{py,txt\\}[/]*?)$'])
 ```
 
@@ -967,9 +985,9 @@ related to pairing it with `SPLIT`.
 
 ```pycon3
 >>> from wcmatch import glob
->>> glob.globmatch('test.txt', r'*.txt|*.py', flags=fnmatch.SPLIT)
+>>> glob.globmatch('test.txt', '*.txt|*.py', flags=fnmatch.SPLIT)
 True
->>> glob.globmatch('test.py', r'*.txt|*.py', flags=fnmatch.SPLIT)
+>>> glob.globmatch('test.py', '*.txt|*.py', flags=fnmatch.SPLIT)
 True
 ```
 
diff --git a/mkdocs.yml b/mkdocs.yml
index bee28cc..6b05815 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -109,7 +109,8 @@ extra:
 
 plugins:
   - search
-  - git-revision-date-localized
+  - git-revision-date-localized:
+      fallback_to_build_date: true
   - mkdocs_pymdownx_material_extras
   - minify:
       minify_html: true
diff --git a/tests/test_glob.py b/tests/test_glob.py
index 9d626be..57331f2 100644
--- a/tests/test_glob.py
+++ b/tests/test_glob.py
@@ -1591,13 +1591,29 @@ class TestSymlinkLoopGlob(unittest.TestCase):
 class TestGlobPaths(unittest.TestCase):
     """Test `glob` paths."""
 
-    def test_root(self):
+    @unittest.skipUnless(not sys.platform.startswith('win'), "Linux/Unix specific test")
+    def test_root_unix(self):
         """Test that `glob` translates the root properly."""
 
-        # On Windows, this should translate to the current drive.
         # On Linux/Unix, this should translate to the root.
         # Basically, we should not return an empty set.
-        self.assertTrue(len(glob.glob('/*')) > 0)
+        results = glob.glob('/*')
+        self.assertTrue(len(results) > 0)
+        self.assertTrue('/' not in results)
+
+    @unittest.skipUnless(sys.platform.startswith('win'), "Windows specific test")
+    def test_root_win(self):
+        """Test that `glob` translates the root properly."""
+
+        # On Windows, this should translate to the current drive.
+        # Basically, we should not return an empty set.
+        results = glob.glob('/*')
+        self.assertTrue(len(results) > 0)
+        self.assertTrue('\\' not in results)
+
+        results = glob.glob(r'\\*')
+        self.assertTrue(len(results) > 0)
+        self.assertTrue('\\' not in results)
 
     def test_start(self):
         """Test that starting directory/files are handled properly."""
diff --git a/wcmatch/__meta__.py b/wcmatch/__meta__.py
index 939221b..c273b6e 100644
--- a/wcmatch/__meta__.py
+++ b/wcmatch/__meta__.py
@@ -193,5 +193,5 @@ def parse_version(ver: str) -> Version:
     return Version(major, minor, micro, release, pre, post, dev)
 
 
-__version_info__ = Version(8, 4, 0, "final")
+__version_info__ = Version(8, 4, 1, "final")
 __version__ = __version_info__._get_canonical()
diff --git a/wcmatch/_wcparse.py b/wcmatch/_wcparse.py
index 08aea80..59d6d60 100644
--- a/wcmatch/_wcparse.py
+++ b/wcmatch/_wcparse.py
@@ -362,9 +362,9 @@ def _get_win_drive(
         end = m.end(0)
         if m.group(3) and RE_WIN_DRIVE_LETTER.match(m.group(0)):
             if regex:
-                drive = escape_drive(RE_WIN_DRIVE_UNESCAPE.sub(r'\1', m.group(3)), case_sensitive)
+                drive = escape_drive(RE_WIN_DRIVE_UNESCAPE.sub(r'\1', m.group(3)).replace('/', '\\'), case_sensitive)
             else:
-                drive = RE_WIN_DRIVE_UNESCAPE.sub(r'\1', m.group(0))
+                drive = RE_WIN_DRIVE_UNESCAPE.sub(r'\1', m.group(0)).replace('/', '\\')
             slash = bool(m.group(4))
             root_specified = True
         elif m.group(2):
diff --git a/wcmatch/glob.py b/wcmatch/glob.py
index a8249d6..4560e72 100644
--- a/wcmatch/glob.py
+++ b/wcmatch/glob.py
@@ -314,8 +314,12 @@ class _GlobSplit(Generic[AnyStr]):
                 i.advance(start)
             elif drive is None and root_specified:
                 parts.append(_GlobPart(b'\\' if is_bytes else '\\', False, False, True, True))
-                start = 1
-                i.advance(2)
+                if pattern.startswith('/'):
+                    start = 0
+                    i.advance(1)
+                else:
+                    start = 1
+                    i.advance(2)
         elif not self.win_drive_detect and pattern.startswith('/'):
             parts.append(_GlobPart(b'/' if is_bytes else '/', False, False, True, True))
             start = 0

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/wcmatch-8.4.1.dist-info/METADATA
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/wcmatch-8.4.1.dist-info/RECORD
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/wcmatch-8.4.1.dist-info/WHEEL

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/wcmatch-8.4.dist-info/METADATA
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/wcmatch-8.4.dist-info/RECORD
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/wcmatch-8.4.dist-info/WHEEL

No differences were encountered in the control files

More details

Full run details