Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/zipp/glob.py: 47%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

34 statements  

1import os 

2import re 

3 

4from .compat.py313 import legacy_end_marker 

5 

6_default_seps = os.sep + str(os.altsep) * bool(os.altsep) 

7 

8 

9class Translator: 

10 """ 

11 >>> Translator('xyz') 

12 Traceback (most recent call last): 

13 ... 

14 AssertionError: Invalid separators 

15 

16 >>> Translator('') 

17 Traceback (most recent call last): 

18 ... 

19 AssertionError: Invalid separators 

20 """ 

21 

22 seps: str 

23 

24 def __init__(self, seps: str = _default_seps): 

25 assert seps and set(seps) <= set(_default_seps), "Invalid separators" 

26 self.seps = seps 

27 

28 def translate(self, pattern): 

29 """ 

30 Given a glob pattern, produce a regex that matches it. 

31 """ 

32 return self.extend(self.match_dirs(self.translate_core(pattern))) 

33 

34 @legacy_end_marker 

35 def extend(self, pattern): 

36 r""" 

37 Extend regex for pattern-wide concerns. 

38 

39 Apply '(?s:)' to create a non-matching group that 

40 matches newlines (valid on Unix). 

41 

42 Append '\z' to imply fullmatch even when match is used. 

43 """ 

44 return rf'(?s:{pattern})\z' 

45 

46 def match_dirs(self, pattern): 

47 """ 

48 Ensure that zipfile.Path directory names are matched. 

49 

50 zipfile.Path directory names always end in a slash. 

51 """ 

52 return rf'{pattern}[/]?' 

53 

54 def translate_core(self, pattern): 

55 r""" 

56 Given a glob pattern, produce a regex that matches it. 

57 

58 >>> t = Translator() 

59 >>> t.translate_core('*.txt').replace('\\\\', '') 

60 '[^/]*\\.txt' 

61 >>> t.translate_core('a?txt') 

62 'a[^/]txt' 

63 >>> t.translate_core('**/*').replace('\\\\', '') 

64 '.*/[^/][^/]*' 

65 """ 

66 self.restrict_rglob(pattern) 

67 return ''.join(map(self.replace, separate(self.star_not_empty(pattern)))) 

68 

69 def replace(self, match): 

70 """ 

71 Perform the replacements for a match from :func:`separate`. 

72 """ 

73 return match.group('set') or ( 

74 re.escape(match.group(0)) 

75 .replace('\\*\\*', r'.*') 

76 .replace('\\*', rf'[^{re.escape(self.seps)}]*') 

77 .replace('\\?', r'[^/]') 

78 ) 

79 

80 def restrict_rglob(self, pattern): 

81 """ 

82 Raise ValueError if ** appears in anything but a full path segment. 

83 

84 >>> Translator().translate('**foo') 

85 Traceback (most recent call last): 

86 ... 

87 ValueError: ** must appear alone in a path segment 

88 """ 

89 seps_pattern = rf'[{re.escape(self.seps)}]+' 

90 segments = re.split(seps_pattern, pattern) 

91 if any('**' in segment and segment != '**' for segment in segments): 

92 raise ValueError("** must appear alone in a path segment") 

93 

94 def star_not_empty(self, pattern): 

95 """ 

96 Ensure that * will not match an empty segment. 

97 """ 

98 

99 def handle_segment(match): 

100 segment = match.group(0) 

101 return '?*' if segment == '*' else segment 

102 

103 not_seps_pattern = rf'[^{re.escape(self.seps)}]+' 

104 return re.sub(not_seps_pattern, handle_segment, pattern) 

105 

106 

107def separate(pattern): 

108 """ 

109 Separate out character sets to avoid translating their contents. 

110 

111 >>> [m.group(0) for m in separate('*.txt')] 

112 ['*.txt'] 

113 >>> [m.group(0) for m in separate('a[?]txt')] 

114 ['a', '[?]', 'txt'] 

115 """ 

116 return re.finditer(r'([^\[]+)|(?P<set>[\[].*?[\]])|([\[][^\]]*$)', pattern)