Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/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 

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

6 

7 

8class Translator: 

9 """ 

10 >>> Translator('xyz') 

11 Traceback (most recent call last): 

12 ... 

13 AssertionError: Invalid separators 

14 

15 >>> Translator('') 

16 Traceback (most recent call last): 

17 ... 

18 AssertionError: Invalid separators 

19 """ 

20 

21 seps: str 

22 

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

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

25 self.seps = seps 

26 

27 def translate(self, pattern): 

28 """ 

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

30 """ 

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

32 

33 def extend(self, pattern): 

34 r""" 

35 Extend regex for pattern-wide concerns. 

36 

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

38 matches newlines (valid on Unix). 

39 

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

41 """ 

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

43 

44 def match_dirs(self, pattern): 

45 """ 

46 Ensure that zipfile.Path directory names are matched. 

47 

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

49 """ 

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

51 

52 def translate_core(self, pattern): 

53 r""" 

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

55 

56 >>> t = Translator() 

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

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

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

60 'a[^/]txt' 

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

62 '.*/[^/][^/]*' 

63 """ 

64 self.restrict_rglob(pattern) 

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

66 

67 def replace(self, match): 

68 """ 

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

70 """ 

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

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

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

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

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

76 ) 

77 

78 def restrict_rglob(self, pattern): 

79 """ 

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

81 

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

83 Traceback (most recent call last): 

84 ... 

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

86 """ 

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

88 segments = re.split(seps_pattern, pattern) 

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

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

91 

92 def star_not_empty(self, pattern): 

93 """ 

94 Ensure that * will not match an empty segment. 

95 """ 

96 

97 def handle_segment(match): 

98 segment = match.group(0) 

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

100 

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

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

103 

104 

105def separate(pattern): 

106 """ 

107 Separate out character sets to avoid translating their contents. 

108 

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

110 ['*.txt'] 

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

112 ['a', '[?]', 'txt'] 

113 """ 

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