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

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

32 statements  

1import os 

2import re 

3 

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

5 

6 

7class Translator: 

8 """ 

9 >>> Translator('xyz') 

10 Traceback (most recent call last): 

11 ... 

12 AssertionError: Invalid separators 

13 

14 >>> Translator('') 

15 Traceback (most recent call last): 

16 ... 

17 AssertionError: Invalid separators 

18 """ 

19 

20 seps: str 

21 

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

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

24 self.seps = seps 

25 

26 def translate(self, pattern): 

27 """ 

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

29 """ 

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

31 

32 def extend(self, pattern): 

33 r""" 

34 Extend regex for pattern-wide concerns. 

35 

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

37 matches newlines (valid on Unix). 

38 

39 Append '\Z' to imply fullmatch even when match is used. 

40 """ 

41 return rf'(?s:{pattern})\Z' 

42 

43 def match_dirs(self, pattern): 

44 """ 

45 Ensure that zipfile.Path directory names are matched. 

46 

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

48 """ 

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

50 

51 def translate_core(self, pattern): 

52 r""" 

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

54 

55 >>> t = Translator() 

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

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

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

59 'a[^/]txt' 

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

61 '.*/[^/][^/]*' 

62 """ 

63 self.restrict_rglob(pattern) 

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

65 

66 def replace(self, match): 

67 """ 

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

69 """ 

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

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

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

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

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

75 ) 

76 

77 def restrict_rglob(self, pattern): 

78 """ 

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

80 

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

82 Traceback (most recent call last): 

83 ... 

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

85 """ 

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

87 segments = re.split(seps_pattern, pattern) 

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

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

90 

91 def star_not_empty(self, pattern): 

92 """ 

93 Ensure that * will not match an empty segment. 

94 """ 

95 

96 def handle_segment(match): 

97 segment = match.group(0) 

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

99 

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

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

102 

103 

104def separate(pattern): 

105 """ 

106 Separate out character sets to avoid translating their contents. 

107 

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

109 ['*.txt'] 

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

111 ['a', '[?]', 'txt'] 

112 """ 

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