Co-authored-by: Claude Code <noreply@anthropic.com> GitOrigin-RevId: 604c29640462ec72350a87692886849d1aa40007
65 lines
2.3 KiB
Python
65 lines
2.3 KiB
Python
"""Filename globbing utility."""
|
|
|
|
import functools
|
|
import glob as _glob
|
|
import os.path
|
|
import re
|
|
from collections.abc import Iterator
|
|
|
|
_CONTAINS_GLOB_PATTERN = re.compile("[*?[]")
|
|
|
|
|
|
def is_glob_pattern(string: str) -> bool:
|
|
"""Return true if 'string' represents a glob pattern, and false otherwise."""
|
|
|
|
# Copied from glob.has_magic().
|
|
return _CONTAINS_GLOB_PATTERN.search(string) is not None
|
|
|
|
|
|
@functools.cache
|
|
def glob(globbed_pathname: str) -> list[str]:
|
|
"""Return a list of pathnames matching the 'globbed_pathname' pattern.
|
|
|
|
In addition to containing simple shell-style wildcards a la fnmatch,
|
|
the pattern may also contain globstars ("**"), which is recursively
|
|
expanded to match zero or more subdirectories.
|
|
"""
|
|
|
|
return list(iglob(globbed_pathname))
|
|
|
|
|
|
def iglob(globbed_pathname: str) -> Iterator[str]:
|
|
"""Emit a list of pathnames matching the 'globbed_pathname' pattern.
|
|
|
|
In addition to containing simple shell-style wildcards a la fnmatch,
|
|
the pattern may also contain globstars ("**"), which is recursively
|
|
expanded to match zero or more subdirectories.
|
|
"""
|
|
|
|
results = _glob.iglob(globbed_pathname, recursive=True)
|
|
|
|
# Python 3.13 changed glob behavior for recursive patterns.
|
|
# In Python 3.10, when pattern is "dir/**", _glob2() always yields empty string
|
|
# which becomes "dir" when joined. Python 3.13's _glob2() only yields if dir exists.
|
|
# Peek at first result to check if glob returned anything without consuming the iterator.
|
|
first_result = next(results, None)
|
|
|
|
if first_result is None:
|
|
# No results from glob - simulate Python 3.10 behavior for recursive patterns
|
|
dirname, basename = os.path.split(globbed_pathname)
|
|
# Check if basename is exactly "**" (recursive wildcard)
|
|
is_recursive = basename == "**" or (isinstance(basename, bytes) and basename == b"**")
|
|
if _glob.has_magic(basename) and is_recursive:
|
|
# Mimic Python 3.10: _glob2 yields empty string, joined with dirname = dirname
|
|
if dirname:
|
|
yield os.path.normpath(dirname)
|
|
return
|
|
|
|
# Yield the first result we peeked at
|
|
yield os.path.normpath(first_result)
|
|
|
|
# Then yield the rest
|
|
for pathname in results:
|
|
# Normalize 'pathname' so exact string comparison can be used later.
|
|
yield os.path.normpath(pathname)
|