diff --git a/docs/src/markdown/about/changelog.md b/docs/src/markdown/about/changelog.md index d92d41e..a0d6a1b 100644 --- a/docs/src/markdown/about/changelog.md +++ b/docs/src/markdown/about/changelog.md @@ -1,5 +1,10 @@ # Changelog +## 9.0 + +- **NEW**: Remove deprecated function `glob.raw_escape`. +- **NEW**: Officially support Python 3.13. + ## 8.5.2 - **FIX**: Fix `pathlib` issue with inheritance on Python versions greater than 3.12. diff --git a/docs/src/markdown/fnmatch.md b/docs/src/markdown/fnmatch.md index 3d0f114..f707a9f 100644 --- a/docs/src/markdown/fnmatch.md +++ b/docs/src/markdown/fnmatch.md @@ -203,7 +203,7 @@ Translate patterns now provide capturing groups for [`EXTMATCH`](#extmatch) grou def escape(pattern): ``` -The `escape` function will conservatively escape `-`, `!`, `*`, `?`, `(`, `)`, `[`, `]`, `|`, `{`, `}`. and `\` with +The `escape` function will conservatively escape `-`, `!`, `*`, `?`, `(`, `)`, `[`, `]`, `|`, `{`, `}`, and `\` with backslashes, regardless of what feature is or is not enabled. It is meant to escape filenames. ```pycon3 diff --git a/docs/src/markdown/glob.md b/docs/src/markdown/glob.md index 431af36..6e769d5 100644 --- a/docs/src/markdown/glob.md +++ b/docs/src/markdown/glob.md @@ -600,7 +600,7 @@ Translate patterns now provide capturing groups for [`EXTGLOB`](#extglob) groups def escape(pattern, unix=None): ``` -The `escape` function will conservatively escape `-`, `!`, `*`, `?`, `(`, `)`, `[`, `]`, `|`, `{`, `}`. and `\` with +The `escape` function will conservatively escape `-`, `!`, `*`, `?`, `(`, `)`, `[`, `]`, `|`, `{`, `}`, and `\` with backslashes, regardless of what feature is or is not enabled. It is meant to escape path parts (filenames, Windows drives, UNC sharepoints) or full paths. @@ -660,90 +660,6 @@ force Windows style escaping. drives manually in their match patterns as well. /// -#### `glob.raw_escape` {: #raw_escape} - -/// warning | Deprecated 8.1 -In 8.1, `raw_escape` has been deprecated. The same can be accomplished simply by using `codecs` and then using the -normal [`escape`](#escape): - -```pycon3 ->>> string = r"translate\\raw strings\\\u00c3\xc3\303\N{LATIN CAPITAL LETTER A WITH TILDE}" ->>> translated = codecs.decode(string, 'unicode_escape') ->>> glob.escape(translated) -'translate\\\\raw strings\\\\ÃÃÃÃ' ->>> glob.raw_escape(string) -'translate\\\\raw strings\\\\ÃÃÃÃ' -``` -/// - -```py3 -def raw_escape(pattern, unix=None, raw_chars=True): -``` - -`raw_escape` is kind of a niche function and 99% of the time, it is recommended to use [`escape`](#escape). - -The big difference between `raw_escape` and [`escape`](#escape) is how `\` are handled. `raw_escape` is mainly for paths -provided to Python via an interface that doesn't process Python strings like they normally are, for instance an input -in a GUI. - -To illustrate, you may have an interface to input path names, but may want to take advantage of Python Unicode -references. Normally, on a python command line, you can do this: - -```pycon3 ->>> 'folder\\El Ni\u00f1o' -'folder\\El Niño' -``` - -But when in a GUI interface, if a user inputs the same, it's like getting a raw string. - -```pycon3 ->>> r'folder\\El Ni\u00f1o' -'folder\\\\El Ni\\u00f1o' -``` - -`raw_escape` will take a raw string in the above format and resolve character escapes and escape the path as if it was -a normal string. Notice to do this, we must treat literal Windows' path backslashes as an escaped backslash. - -```pycon3 ->>> glob.escape('folder\\El Ni\u00f1o', unix=False) -'folder\\\\El Niño' ->>> glob.raw_escape(r'folder\\El Ni\u00f1o') -'folder\\\\El Niño' -``` - -Handling of raw character references can be turned off if desired: - -```pycon3 ->>> glob.raw_escape(r'my\\file-\x31.txt', unix=False) -'my\\\\file\\-1.txt' ->>> glob.raw_escape(r'my\\file-\x31.txt', unix=False, raw_chars=False) -'my\\\\file\\-\\\\x31.txt' -``` - -Outside of the treatment of `\`, `raw_escape` will function just like [`escape`](#escape): - -`raw_escape` will detect the system it is running on and pick Windows escape logic or Linux/Unix logic. Since -[`globmatch`](#globmatch) allows you to match Unix style paths on a Windows system, and vice versa, you can force -Unix style escaping or Windows style escaping via the `unix` parameter. When `unix` is `None`, the escape style will be -detected, when `unix` is `True` Linux/Unix style escaping will be used, and when `unix` is `False` Windows style -escaping will be used. - -```pycon3 ->>> glob.raw_escape(r'some/path?/\x2a\x2afile\x2a\x2a{}.txt', unix=True) -``` - -/// new | New 5.0 -The `unix` parameter is now `None` by default. Set to `True` to force Linux/Unix style escaping or set to `False` to -force Windows style escaping. -/// - -/// new | New 7.0 -`{`, `}`, and `|` will be escaped in Windows drives. Additionally, users can escape these characters in Windows -drives manually in their match patterns as well. - -`raw_chars` option was added. -/// - ### `glob.is_magic` {: #is_magic} ```py3 diff --git a/hatch_build.py b/hatch_build.py index 60bfb7e..4663a1d 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -33,6 +33,7 @@ def update(self, metadata): 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'Topic :: Software Development :: Libraries :: Python Modules', 'Typing :: Typed' ] diff --git a/pyproject.toml b/pyproject.toml index b8cfd4c..4c5dbf7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,7 +111,7 @@ legacy_tox_ini = """ isolated_build = true skipsdist=true envlist= - py38,py39,py310,py311,py312, + py38,py39,py310,py311,py312,py313, lint [testenv] diff --git a/tests/test_glob.py b/tests/test_glob.py index 1f46155..252e2c8 100644 --- a/tests/test_glob.py +++ b/tests/test_glob.py @@ -1373,7 +1373,7 @@ class TestGlobCornerCaseMarked(Testglob): class TestGlobEscapes(unittest.TestCase): """Test escaping.""" - def check_escape(self, arg, expected, raw=False, unix=None, raw_chars=True): + def check_escape(self, arg, expected, unix=None): """Verify escapes.""" flags = 0 @@ -1382,38 +1382,15 @@ def check_escape(self, arg, expected, raw=False, unix=None, raw_chars=True): elif unix is True: flags = glob.FORCEUNIX - if raw: - self.assertEqual(glob.raw_escape(arg, unix=unix, raw_chars=raw_chars), expected) - self.assertEqual(glob.raw_escape(os.fsencode(arg), unix=unix, raw_chars=raw_chars), os.fsencode(expected)) - file = (util.norm_pattern(arg, False, True) if raw_chars else arg).replace('\\\\', '\\') - self.assertTrue( - glob.globmatch( - file, - glob.raw_escape(arg, unix=unix, raw_chars=raw_chars), - flags=flags - ) - ) - else: - self.assertEqual(glob.escape(arg, unix=unix), expected) - self.assertEqual(glob.escape(os.fsencode(arg), unix=unix), os.fsencode(expected)) - self.assertTrue( - glob.globmatch( - arg, - glob.escape(arg, unix=unix), - flags=flags - ) + self.assertEqual(glob.escape(arg, unix=unix), expected) + self.assertEqual(glob.escape(os.fsencode(arg), unix=unix), os.fsencode(expected)) + self.assertTrue( + glob.globmatch( + arg, + glob.escape(arg, unix=unix), + flags=flags ) - - def test_raw_escape_deprecation(self): - """Test raw escape deprecation.""" - - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - - glob.raw_escape(r'test\\test') - - self.assertTrue(len(w) == 1) - self.assertTrue(issubclass(w[-1].category, DeprecationWarning)) + ) def test_escape(self): """Test path escapes.""" @@ -1426,34 +1403,6 @@ def test_escape(self): check('[[_/*?*/_]]', r'\[\[_/\*\?\*/_\]\]') check('/[[_/*?*/_]]/', r'/\[\[_/\*\?\*/_\]\]/') - def test_raw_escape(self): - """Test path escapes.""" - - check = self.check_escape - check(r'abc', 'abc', raw=True) - check(r'[', r'\[', raw=True) - check(r'?', r'\?', raw=True) - check(r'*', r'\*', raw=True) - check(r'[[_/*?*/_]]', r'\[\[_/\*\?\*/_\]\]', raw=True) - check(r'/[[_/*?*/_]]/', r'/\[\[_/\*\?\*/_\]\]/', raw=True) - check(r'\x3f', r'\?', raw=True) - check(r'\\\\[^what]\\name\\temp', r'\\\\[^what]\\name\\temp', raw=True, unix=False) - check('//[^what]/name/temp', r'//[^what]/name/temp', raw=True, unix=False) - - def test_raw_escape_no_raw_chars(self): - """Test path escapes with no raw character translations.""" - - check = self.check_escape - check(r'abc', 'abc', raw=True, raw_chars=False) - check(r'[', r'\[', raw=True, raw_chars=False) - check(r'?', r'\?', raw=True, raw_chars=False) - check(r'*', r'\*', raw=True, raw_chars=False) - check(r'[[_/*?*/_]]', r'\[\[_/\*\?\*/_\]\]', raw=True, raw_chars=False) - check(r'/[[_/*?*/_]]/', r'/\[\[_/\*\?\*/_\]\]/', raw=True, raw_chars=False) - check(r'\x3f', r'\\x3f', raw=True, raw_chars=False) - check(r'\\\\[^what]\\name\\temp', r'\\\\[^what]\\name\\temp', raw=True, raw_chars=False, unix=False) - check('//[^what]/name/temp', r'//[^what]/name/temp', raw=True, raw_chars=False, unix=False) - @unittest.skipUnless(sys.platform.startswith('win'), "Windows specific test") def test_escape_windows(self): """Test windows escapes.""" diff --git a/wcmatch/__meta__.py b/wcmatch/__meta__.py index 7d6b0c0..a3e3da4 100644 --- a/wcmatch/__meta__.py +++ b/wcmatch/__meta__.py @@ -1,6 +1,5 @@ """Meta related things.""" from __future__ import annotations -from __future__ import unicode_literals from collections import namedtuple import re @@ -94,7 +93,7 @@ def __new__( raise ValueError("All version parts except 'release' should be integers.") if release not in REL_MAP: - raise ValueError("'{}' is not a valid release type.".format(release)) + raise ValueError(f"'{release}' is not a valid release type.") # Ensure valid pre-release (we do not allow implicit pre-releases). if ".dev-candidate" < release < "final": @@ -119,7 +118,7 @@ def __new__( elif dev: raise ValueError("Version is not a development release.") - return super(Version, cls).__new__(cls, major, minor, micro, release, pre, post, dev) + return super().__new__(cls, major, minor, micro, release, pre, post, dev) def _is_pre(self) -> bool: """Is prerelease.""" @@ -146,15 +145,15 @@ def _get_canonical(self) -> str: # Assemble major, minor, micro version and append `pre`, `post`, or `dev` if needed.. if self.micro == 0: - ver = "{}.{}".format(self.major, self.minor) + ver = f"{self.major}.{self.minor}" else: - ver = "{}.{}.{}".format(self.major, self.minor, self.micro) + ver = f"{self.major}.{self.minor}.{self.micro}" if self._is_pre(): - ver += '{}{}'.format(REL_MAP[self.release], self.pre) + ver += f'{REL_MAP[self.release]}{self.pre}' if self._is_post(): - ver += ".post{}".format(self.post) + ver += f".post{self.post}" if self._is_dev(): - ver += ".dev{}".format(self.dev) + ver += f".dev{self.dev}" return ver @@ -165,7 +164,7 @@ def parse_version(ver: str) -> Version: m = RE_VER.match(ver) if m is None: - raise ValueError("'{}' is not a valid version".format(ver)) + raise ValueError(f"'{ver}' is not a valid version") # Handle major, minor, micro major = int(m.group('major')) @@ -194,5 +193,5 @@ def parse_version(ver: str) -> Version: return Version(major, minor, micro, release, pre, post, dev) -__version_info__ = Version(8, 5, 2, "final") +__version_info__ = Version(9, 0, 0, "final") __version__ = __version_info__._get_canonical() diff --git a/wcmatch/_wcmatch.py b/wcmatch/_wcmatch.py index cce7045..aff5ad5 100644 --- a/wcmatch/_wcmatch.py +++ b/wcmatch/_wcmatch.py @@ -5,7 +5,7 @@ import stat import copyreg from . import util -from typing import Pattern, AnyStr, Generic, Any, cast +from typing import Pattern, AnyStr, Generic, Any # `O_DIRECTORY` may not always be defined DIR_FLAGS = os.O_RDONLY | getattr(os, 'O_DIRECTORY', 0) @@ -192,7 +192,7 @@ def match(self, root_dir: AnyStr | None = None, dir_fd: int | None = None) -> bo ) ) - re_mount = cast(Pattern[AnyStr], (RE_WIN_MOUNT if util.platform() == "windows" else RE_MOUNT)[self.ptype]) + re_mount = (RE_WIN_MOUNT if util.platform() == "windows" else RE_MOUNT)[self.ptype] # type: Pattern[AnyStr] # type: ignore[assignment] is_abs = re_mount.match(self.filename) is not None if is_abs: diff --git a/wcmatch/_wcparse.py b/wcmatch/_wcparse.py index 267a13d..ba297f5 100644 --- a/wcmatch/_wcparse.py +++ b/wcmatch/_wcparse.py @@ -7,7 +7,7 @@ from . import util from . import posix from . _wcmatch import WcRegexp -from typing import AnyStr, Iterable, Pattern, Generic, Sequence, overload, cast +from typing import AnyStr, Iterable, Pattern, Generic, Sequence, overload UNICODE_RANGE = '\u0000-\U0010ffff' ASCII_RANGE = '\x00-\xff' @@ -205,7 +205,7 @@ _PATH_STAR_DOTMATCH = _NO_DIR + _PATH_STAR # Star for `PATHNAME` when `DOTMATCH` is disabled and start is at start of file. # Disallow . and .. and don't allow match to start with a dot. -_PATH_STAR_NO_DOTMATCH = _NO_DIR + r'(?:(?!\.){})?'.format(_PATH_STAR) +_PATH_STAR_NO_DOTMATCH = _NO_DIR + fr'(?:(?!\.){_PATH_STAR})?' # `GLOBSTAR` during `DOTMATCH`. Avoid directory match /./ or /../ _PATH_GSTAR_DOTMATCH = r'(?:(?!(?:[{sep}]|^)(?:\.{{1,2}})($|[{sep}])).)*?' # `GLOBSTAR` with `DOTMATCH` disabled. Don't allow a dot to follow / @@ -250,7 +250,7 @@ _EXCLA_GROUP = r'(?:(?!(?:{})' _EXCLA_CAPTURE_GROUP = r'((?#)(?!(?:{})' # Closing for inverse group -_EXCLA_GROUP_CLOSE = r'){})' +_EXCLA_GROUP_CLOSE = '){})' # Restrict root _NO_ROOT = r'(?!/)' _NO_WIN_ROOT = r'(?!(?:[\\/]|[a-zA-Z]:))' @@ -300,35 +300,32 @@ def iter_patterns(patterns: AnyStr | Sequence[AnyStr]) -> Iterable[AnyStr]: yield from patterns -def escape(pattern: AnyStr, unix: bool | None = None, pathname: bool = True, raw: bool = False) -> AnyStr: +def escape(pattern: AnyStr, unix: bool | None = None, pathname: bool = True) -> AnyStr: """ Escape. `unix`: use Unix style path logic. `pathname`: Use path logic. - `raw`: Handle raw strings (deprecated) - """ if isinstance(pattern, bytes): - drive_pat = cast(Pattern[AnyStr], RE_WIN_DRIVE[util.BYTES]) - magic = cast(Pattern[AnyStr], RE_MAGIC_ESCAPE[util.BYTES]) - drive_magic = cast(Pattern[AnyStr], RE_WIN_DRIVE_MAGIC[util.BYTES]) + drive_pat = RE_WIN_DRIVE[util.BYTES] # type: Pattern[AnyStr] # type: ignore[assignment] + magic = RE_MAGIC_ESCAPE[util.BYTES] # type: Pattern[AnyStr] # type: ignore[assignment] + drive_magic = RE_WIN_DRIVE_MAGIC[util.BYTES] # type: Pattern[AnyStr] # type: ignore[assignment] replace = br'\\\1' slash = b'\\' double_slash = b'\\\\' drive = b'' else: - drive_pat = cast(Pattern[AnyStr], RE_WIN_DRIVE[util.UNICODE]) - magic = cast(Pattern[AnyStr], RE_MAGIC_ESCAPE[util.UNICODE]) - drive_magic = cast(Pattern[AnyStr], RE_WIN_DRIVE_MAGIC[util.UNICODE]) + drive_pat = RE_WIN_DRIVE[util.UNICODE] # type: ignore[assignment] + magic = RE_MAGIC_ESCAPE[util.UNICODE] # type: ignore[assignment] + drive_magic = RE_WIN_DRIVE_MAGIC[util.UNICODE] # type: ignore[assignment] replace = r'\\\1' slash = '\\' double_slash = '\\\\' drive = '' - if not raw: - pattern = pattern.replace(slash, double_slash) + pattern = pattern.replace(slash, double_slash) # Handle windows drives special. # Windows drives are handled special internally. @@ -408,28 +405,27 @@ def _get_magic_symbols(pattern: AnyStr, unix: bool, flags: int) -> tuple[set[Any ptype = util.UNICODE slash = '\\' - magic = set() # type: set[AnyStr] if unix: magic_drive = set() # type: set[AnyStr] else: magic_drive = {slash} - magic |= cast('set[AnyStr]', MAGIC_DEF[ptype]) + magic = set(MAGIC_DEF[ptype]) # type: set[AnyStr] # type: ignore[arg-type] if flags & BRACE: - magic |= cast('set[AnyStr]', MAGIC_BRACE[ptype]) - magic_drive |= cast('set[AnyStr]', MAGIC_BRACE[ptype]) + magic |= MAGIC_BRACE[ptype] # type: ignore[arg-type] + magic_drive |= MAGIC_BRACE[ptype] # type: ignore[arg-type] if flags & SPLIT: - magic |= cast('set[AnyStr]', MAGIC_SPLIT[ptype]) - magic_drive |= cast('set[AnyStr]', MAGIC_SPLIT[ptype]) + magic |= MAGIC_SPLIT[ptype] # type: ignore[arg-type] + magic_drive |= MAGIC_SPLIT[ptype] # type: ignore[arg-type] if flags & GLOBTILDE: - magic |= cast('set[AnyStr]', MAGIC_TILDE[ptype]) + magic |= MAGIC_TILDE[ptype] # type: ignore[arg-type] if flags & EXTMATCH: - magic |= cast('set[AnyStr]', MAGIC_EXTMATCH[ptype]) + magic |= MAGIC_EXTMATCH[ptype] # type: ignore[arg-type] if flags & NEGATE: if flags & MINUSNEGATE: - magic |= cast('set[AnyStr]', MAGIC_MINUS_NEGATE[ptype]) + magic |= MAGIC_MINUS_NEGATE[ptype] # type: ignore[arg-type] else: - magic |= cast('set[AnyStr]', MAGIC_NEGATE[ptype]) + magic |= MAGIC_NEGATE[ptype] # type: ignore[arg-type] return magic, magic_drive @@ -445,7 +441,7 @@ def is_magic(pattern: AnyStr, flags: int = 0) -> bool: else: ptype = util.UNICODE - drive_pat = cast(Pattern[AnyStr], RE_WIN_DRIVE[ptype]) + drive_pat = RE_WIN_DRIVE[ptype] # type: Pattern[AnyStr] # type: ignore[assignment] magic, magic_drive = _get_magic_symbols(pattern, unix, flags) is_path = flags & PATHNAME @@ -524,8 +520,8 @@ def expand_tilde(pattern: AnyStr, is_unix: bool, flags: int) -> AnyStr: if pos > -1: string_type = util.BYTES if isinstance(pattern, bytes) else util.UNICODE - tilde = cast(AnyStr, TILDE_SYM[string_type]) - re_tilde = cast(Pattern[AnyStr], RE_WIN_TILDE[string_type] if not is_unix else RE_TILDE[string_type]) + tilde = TILDE_SYM[string_type] # type: AnyStr # type: ignore[assignment] + re_tilde = RE_WIN_TILDE[string_type] if not is_unix else RE_TILDE[string_type] # type: Pattern[AnyStr] # type: ignore[assignment] m = re_tilde.match(pattern, pos) if m: expanded = os.path.expanduser(m.group(0)) @@ -569,7 +565,7 @@ def get_case(flags: int) -> bool: def escape_drive(drive: str, case: bool) -> str: """Escape drive.""" - return '(?i:{})'.format(re.escape(drive)) if case else re.escape(drive) + return f'(?i:{re.escape(drive)})' if case else re.escape(drive) def is_unix_style(flags: int) -> bool: @@ -644,7 +640,7 @@ def translate( count += 1 total += 1 if 0 < limit < total: - raise PatternLimitException("Pattern limit exceeded the limit of {:d}".format(limit)) + raise PatternLimitException(f"Pattern limit exceeded the limit of {limit:d}") if expanded not in seen: seen.add(expanded) if is_negative(expanded, flags): @@ -656,7 +652,7 @@ def translate( if current_limit < 1: current_limit = 1 except bracex.ExpansionLimitException as e: - raise PatternLimitException("Pattern limit exceeded the limit of {:d}".format(limit)) from e + raise PatternLimitException(f"Pattern limit exceeded the limit of {limit:d}") from e if negative and not positive: if flags & NEGATEALL: @@ -667,7 +663,7 @@ def translate( if positive and flags & NODIR: index = util.BYTES if isinstance(positive[0], bytes) else util.UNICODE - negative.append(cast(AnyStr, _NO_NIX_DIR[index] if is_unix else _NO_WIN_DIR[index])) + negative.append(_NO_NIX_DIR[index] if is_unix else _NO_WIN_DIR[index]) # type: ignore[arg-type] return positive, negative @@ -730,7 +726,7 @@ def compile_pattern( count += 1 total += 1 if 0 < limit < total: - raise PatternLimitException("Pattern limit exceeded the limit of {:d}".format(limit)) + raise PatternLimitException(f"Pattern limit exceeded the limit of {limit:d}") if expanded not in seen: seen.add(expanded) if is_negative(expanded, flags): @@ -742,7 +738,7 @@ def compile_pattern( if current_limit < 1: current_limit = 1 except bracex.ExpansionLimitException as e: - raise PatternLimitException("Pattern limit exceeded the limit of {:d}".format(limit)) from e + raise PatternLimitException(f"Pattern limit exceeded the limit of {limit:d}") from e if negative and not positive: if flags & NEGATEALL: @@ -751,7 +747,7 @@ def compile_pattern( if positive and flags & NODIR: ptype = util.BYTES if isinstance(positive[0].pattern, bytes) else util.UNICODE - negative.append(cast(Pattern[AnyStr], RE_NO_DIR[ptype] if is_unix else RE_WIN_NO_DIR[ptype])) + negative.append(RE_NO_DIR[ptype] if is_unix else RE_WIN_NO_DIR[ptype]) # type: ignore[arg-type] return positive, negative @@ -966,7 +962,7 @@ def __init__(self, pattern: AnyStr, flags: int = 0) -> None: self.bslash_abort = False sep = {"sep": re.escape('/')} self.bare_sep = sep['sep'] - self.sep = '[{}]'.format(self.bare_sep) + self.sep = f'[{self.bare_sep}]' self.path_eop = _PATH_EOP.format(**sep) self.no_dir = _NO_DIR.format(**sep) self.seq_path = _PATH_NO_SLASH.format(**sep) @@ -1159,12 +1155,12 @@ def _sequence(self, i: util.StringIter) -> str: if value == '[]': # We specified some ranges, but they are all # out of reach. Create an impossible sequence to match. - result = ['[^{}]'.format(ASCII_RANGE if self.is_bytes else UNICODE_RANGE)] + result = [f'[^{ASCII_RANGE if self.is_bytes else UNICODE_RANGE}]'] elif value == '[^]': # We specified some range, but hey are all # out of reach. Since this is exclusive # that means we can match *anything*. - result = ['[{}]'.format(ASCII_RANGE if self.is_bytes else UNICODE_RANGE)] + result = [f'[{ASCII_RANGE if self.is_bytes else UNICODE_RANGE}]'] else: result = [value] @@ -1260,7 +1256,7 @@ def _handle_dot(self, i: util.StringIter, current: list[str]) -> None: i.rewind(i.index - index) if not is_current and not is_previous: - current.append(r'(?!\.[.]?{})\.'.format(self.path_eop)) + current.append(rf'(?!\.[.]?{self.path_eop})\.') else: current.append(re.escape('.')) @@ -1278,7 +1274,7 @@ def _handle_star(self, i: util.StringIter, current: list[str]) -> None: star = self.path_star globstar = self.path_gstar_dot1 if self.globstar_capture: - globstar = '({})'.format(globstar) + globstar = f'({globstar})' else: if self.after_start and not self.dot: star = _NO_DOT + _STAR @@ -1671,7 +1667,7 @@ def _parse(self, p: str) -> str: result = prepend + result case_flag = 'i' if not self.case_sensitive else '' - pattern = R'^(?s{}:{})$'.format(case_flag, ''.join(result)) + pattern = Rf'^(?s{case_flag}:{"".join(result)})$' if self.capture: # Strip out unnecessary regex comments diff --git a/wcmatch/glob.py b/wcmatch/glob.py index 195dde6..6612a67 100644 --- a/wcmatch/glob.py +++ b/wcmatch/glob.py @@ -13,7 +13,7 @@ from . import _wcparse from . import _wcmatch from . import util -from typing import Iterator, Iterable, AnyStr, Generic, Pattern, Callable, Any, Sequence, cast +from typing import Iterator, Iterable, AnyStr, Generic, Pattern, Callable, Any, Sequence __all__ = ( "CASE", "IGNORECASE", "RAWCHARS", "DOTGLOB", "DOTMATCH", @@ -21,7 +21,7 @@ "REALPATH", "FOLLOW", "MATCHBASE", "MARK", "NEGATEALL", "NODIR", "FORCEWIN", "FORCEUNIX", "GLOBTILDE", "NODOTDIR", "SCANDOTDIR", "SUPPORT_DIR_FD", "C", "I", "R", "D", "E", "G", "N", "M", "B", "P", "L", "S", "X", 'K', "O", "A", "W", "U", "T", "Q", "Z", "SD", - "iglob", "glob", "globmatch", "globfilter", "escape", "raw_escape", "is_magic" + "iglob", "glob", "globmatch", "globfilter", "escape", "is_magic" ) # We don't use `util.platform` only because we mock it in tests, @@ -280,7 +280,7 @@ def store(self, value: AnyStr, l: list[_GlobPart], dir_only: bool) -> None: globstar = value in (b'**', '**') and self.globstar magic = self.is_magic(value) if magic: - v = cast(Pattern[AnyStr], _wcparse._compile(value, self.flags)) # type: Pattern[AnyStr] | AnyStr + v = _wcparse._compile(value, self.flags) # type: Pattern[AnyStr] | AnyStr else: v = value if globstar and l and l[-1].is_globstar: @@ -348,13 +348,13 @@ def split(self) -> list[_GlobPart]: for split, offset in split_index: value = pattern[start + 1:split] - self.store(cast(AnyStr, value.encode('latin-1') if is_bytes else value), parts, True) + self.store(value.encode('latin-1') if is_bytes else value, parts, True) # type: ignore[arg-type] start = split + offset if start < len(pattern): value = pattern[start + 1:] if value: - self.store(cast(AnyStr, value.encode('latin-1') if is_bytes else value), parts, False) + self.store(value.encode('latin-1') if is_bytes else value, parts, False) # type: ignore[arg-type] if len(pattern) == 0: parts.append(_GlobPart(pattern.encode('latin-1') if is_bytes else pattern, False, False, False, False)) @@ -438,8 +438,8 @@ def __init__( self.stars = b'**' # type: AnyStr self.sep = b'\\' if forcewin else b'/' # type: AnyStr self.seps = (b'/', self.sep) if forcewin else (self.sep,) # type: tuple[AnyStr, ...] - self.re_pathlib_norm = cast(Pattern[AnyStr], _RE_WIN_PATHLIB_DOT_NORM[ptype]) # type: Pattern[AnyStr] - self.re_no_dir = cast(Pattern[AnyStr], _wcparse.RE_WIN_NO_DIR[ptype]) # type: Pattern[AnyStr] + self.re_pathlib_norm = _RE_WIN_PATHLIB_DOT_NORM[ptype] # type: Pattern[AnyStr] # type: ignore[assignment] + self.re_no_dir = _wcparse.RE_WIN_NO_DIR[ptype] # type: Pattern[AnyStr] # type: ignore[assignment] else: ptype = util.UNICODE self.current = '.' @@ -448,15 +448,13 @@ def __init__( self.stars = '**' self.sep = '\\' if forcewin else '/' self.seps = ('/', self.sep) if forcewin else (self.sep,) - self.re_pathlib_norm = cast(Pattern[AnyStr], _RE_WIN_PATHLIB_DOT_NORM[ptype]) - self.re_no_dir = cast(Pattern[AnyStr], _wcparse.RE_WIN_NO_DIR[ptype]) + self.re_pathlib_norm = _RE_WIN_PATHLIB_DOT_NORM[ptype] # type: ignore[assignment] + self.re_no_dir = _wcparse.RE_WIN_NO_DIR[ptype] # type: ignore[assignment] temp = os.fspath(root_dir) if root_dir is not None else self.current if not isinstance(temp, bytes if ptype else str): raise TypeError( - 'Pattern and root_dir should be of the same type, not {} and {}'.format( - type(pats[0]), type(temp) - ) + f'Pattern and root_dir should be of the same type, not {type(pats[0])} and {type(temp)}' ) self.root_dir = temp # type: AnyStr @@ -479,7 +477,7 @@ def _iter_patterns(self, patterns: Sequence[AnyStr], force_negate: bool = False) total += 1 if 0 < self.limit < total: raise _wcparse.PatternLimitException( - "Pattern limit exceeded the limit of {:d}".format(self.limit) + f"Pattern limit exceeded the limit of {self.limit:d}" ) # Filter out duplicate patterns. If `NOUNIQUE` is enabled, # we only want to filter on negative patterns as they are @@ -497,7 +495,7 @@ def _iter_patterns(self, patterns: Sequence[AnyStr], force_negate: bool = False) self.current_limit = 1 except bracex.ExpansionLimitException as e: raise _wcparse.PatternLimitException( - "Pattern limit exceeded the limit of {:d}".format(self.limit) + f"Pattern limit exceeded the limit of {self.limit:d}" ) from e def _parse_patterns(self, patterns: Sequence[AnyStr], force_negate: bool = False) -> None: @@ -508,7 +506,7 @@ def _parse_patterns(self, patterns: Sequence[AnyStr], force_negate: bool = False # Treat the inverse pattern as a normal pattern if it matches, we will exclude. # This is faster as compiled patterns usually compare the include patterns first, # and then the exclude, but glob will already know it wants to include the file. - self.npatterns.append(cast(Pattern[AnyStr], _wcparse._compile(p, self.negate_flags))) + self.npatterns.append(_wcparse._compile(p, self.negate_flags)) else: self.pattern.append(_GlobSplit(p, self.flags).split()) @@ -948,15 +946,6 @@ def globfilter( return matches -@util.deprecated("This function will be removed in 9.0.") -def raw_escape(pattern: AnyStr, unix: bool | None = None, raw_chars: bool = True) -> AnyStr: - """Apply raw character transform before applying escape.""" - - return _wcparse.escape( - util.norm_pattern(pattern, False, raw_chars, True), unix=unix, pathname=True, raw=True - ) - - def escape(pattern: AnyStr, unix: bool | None = None) -> AnyStr: """Escape.""" diff --git a/wcmatch/pathlib.py b/wcmatch/pathlib.py index f2935f7..13e77d6 100644 --- a/wcmatch/pathlib.py +++ b/wcmatch/pathlib.py @@ -172,13 +172,13 @@ def __new__(cls, *args: str, **kwargs: Any) -> 'Path': else: self = cls._from_parts(args, init=False) # type: ignore[attr-defined] if not self._flavour.is_supported: - raise NotImplementedError("Cannot instantiate {!r} on your system".format(cls.__name__)) + raise NotImplementedError(f"Cannot instantiate {cls.__name__!r} on your system") if not util.PY310: self._init() return self # type: ignore[no-any-return] else: if cls is WindowsPath and not win_host or cls is not WindowsPath and win_host: - raise NotImplementedError("Cannot instantiate {!r} on your system".format(cls.__name__)) + raise NotImplementedError(f"Cannot instantiate {cls.__name__!r} on your system") return object.__new__(cls) def glob( # type: ignore[override] diff --git a/wcmatch/posix.py b/wcmatch/posix.py index 2562d36..dac4b26 100644 --- a/wcmatch/posix.py +++ b/wcmatch/posix.py @@ -73,4 +73,4 @@ def get_posix_property(value: str, limit_ascii: bool = False) -> str: else: return unicode_posix_properties[value] except Exception as e: # pragma: no cover - raise ValueError("'{} is not a valid posix property".format(value)) from e + raise ValueError(f"'{value} is not a valid posix property") from e diff --git a/wcmatch/util.py b/wcmatch/util.py index 17fa173..ba95fee 100644 --- a/wcmatch/util.py +++ b/wcmatch/util.py @@ -7,7 +7,7 @@ import unicodedata from functools import wraps import warnings -from typing import Any, Callable, AnyStr, Match, Pattern, cast +from typing import Any, Callable, AnyStr, Match, Pattern PY310 = (3, 10) <= sys.version_info PY312 = (3, 12) <= sys.version_info @@ -78,7 +78,7 @@ def is_case_sensitive() -> bool: return CASE_FS -def norm_pattern(pattern: AnyStr, normalize: bool | None, is_raw_chars: bool, ignore_escape: bool = False) -> AnyStr: +def norm_pattern(pattern: AnyStr, normalize: bool | None, is_raw_chars: bool) -> AnyStr: r""" Normalize pattern. @@ -99,7 +99,7 @@ def norm_pattern(pattern: AnyStr, normalize: bool | None, is_raw_chars: bool, ig multi_slash = slash * 4 pat = RE_NORM - if not normalize and not is_raw_chars and not ignore_escape: + if not normalize and not is_raw_chars: return pattern def norm(m: Match[AnyStr]) -> AnyStr: @@ -110,21 +110,19 @@ def norm(m: Match[AnyStr]) -> AnyStr: if normalize and len(char) > 1: char = multi_slash elif m.group(2): - char = cast(AnyStr, BACK_SLASH_TRANSLATION[m.group(2)] if is_raw_chars else m.group(2)) + char = BACK_SLASH_TRANSLATION[m.group(2)] if is_raw_chars else m.group(2) elif is_raw_chars and m.group(4): - char = cast(AnyStr, bytes([int(m.group(4), 8) & 0xFF]) if is_bytes else chr(int(m.group(4), 8))) + char = bytes([int(m.group(4), 8) & 0xFF]) if is_bytes else chr(int(m.group(4), 8)) elif is_raw_chars and m.group(3): - char = cast(AnyStr, bytes([int(m.group(3)[2:], 16)]) if is_bytes else chr(int(m.group(3)[2:], 16))) + char = bytes([int(m.group(3)[2:], 16)]) if is_bytes else chr(int(m.group(3)[2:], 16)) elif is_raw_chars and not is_bytes and m.group(5): char = unicodedata.lookup(m.group(5)[3:-1]) elif not is_raw_chars or m.group(5 if is_bytes else 6): char = m.group(0) - if ignore_escape: - char = slash + char else: value = m.group(6) if is_bytes else m.group(7) pos = m.start(6) if is_bytes else m.start(7) - raise SyntaxError("Could not convert character value {!r} at position {:d}".format(value, pos)) + raise SyntaxError(f"Could not convert character value {value!r} at position {pos:d}") return char return pat.sub(norm, pattern)