diff --git a/b2sdk/bucket.py b/b2sdk/bucket.py index 4a6b4be83..57a8a87dd 100644 --- a/b2sdk/bucket.py +++ b/b2sdk/bucket.py @@ -9,12 +9,8 @@ ###################################################################### from __future__ import annotations -import fnmatch import logging from contextlib import suppress -from functools import partial - -from wcmatch import glob as wcglob from b2sdk.session import B2Session @@ -53,7 +49,7 @@ limit_trace_arguments, validate_b2_file_name, ) -from .utils.wildcards import WildcardStyle, get_solid_prefix +from .utils.wildcards import WildcardStyle, get_solid_prefix, get_wildcard_matcher logger = logging.getLogger(__name__) @@ -374,21 +370,7 @@ def ls( if with_wildcard: prefix = get_solid_prefix(prefix, folder_to_list, wildcard_style) - if wildcard_style == WildcardStyle.GLOB: - wildcard_matcher = partial( - lambda file_name: fnmatch.fnmatchcase(file_name, folder_to_list) - ) - else: - wc_flags = ( - wcglob.CASE # case sensitive - | wcglob.BRACE # support {} for multiple options - | wcglob.GLOBSTAR # support ** for recursive matching - | wcglob.NEGATE # support [!] for negation - ) - wildcard_matcher = partial( - lambda file_name: wcglob. - globmatch(file_name, folder_to_list, flags=wc_flags, limit=100) - ) + wildcard_matcher = get_wildcard_matcher(folder_to_list, wildcard_style) elif prefix != '' and not prefix.endswith('/'): # we don't assume that this is folder that we're searching through. # It could be an exact file, e.g. 'a/b.txt' that we're trying to locate. diff --git a/b2sdk/utils/wildcards.py b/b2sdk/utils/wildcards.py index 35858a2ed..27695b910 100644 --- a/b2sdk/utils/wildcards.py +++ b/b2sdk/utils/wildcards.py @@ -9,8 +9,13 @@ ###################################################################### from __future__ import annotations +import fnmatch import pathlib from enum import Enum +from functools import partial +from typing import Callable + +from wcmatch import glob as wcglob class WildcardStyle(str, Enum): @@ -88,3 +93,24 @@ def get_solid_prefix( # We could receive paths in different stage, e.g. 'a/*/result.[ct]sv' has two # possible parent paths: 'a/' and 'a/*/', with the first one being the correct one return min(parent_path, current_prefix, key=len) + + +def get_wildcard_matcher(match_pattern: str, + wildcard_style: WildcardStyle) -> Callable[[str], bool]: + """Return a wildcard matcher for chosen style and pattern.""" + if wildcard_style == WildcardStyle.SHELL: + wc_flags = ( + wcglob.CASE # case sensitive + | wcglob.BRACE # support {} for multiple options + | wcglob.GLOBSTAR # support ** for recursive matching + | wcglob.NEGATE # support [!] for negation + ) + wildcard_matcher = partial( + lambda file_name: wcglob.globmatch(file_name, match_pattern, flags=wc_flags, limit=100) + ) + elif wildcard_style == WildcardStyle.GLOB: + wildcard_matcher = partial(lambda file_name: fnmatch.fnmatchcase(file_name, match_pattern)) + else: + raise ValueError(f"Unknown wildcard style: {wildcard_style}") + + return wildcard_matcher