1# Copyright (C) 2011-2013 Kai Willadsen <kai.willadsen@gmail.com> 2# 3# This program is free software: you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation, either version 2 of the License, or (at 6# your option) any later version. 7# 8# This program is distributed in the hope that it will be useful, but 9# WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11# General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16import logging 17import re 18 19log = logging.getLogger(__name__) 20 21 22def try_compile(regex, flags=0): 23 try: 24 compiled = re.compile(regex, flags) 25 except re.error: 26 log.warning( 27 'Error compiling regex {!r} with flags {!r}'.format(regex, flags)) 28 compiled = None 29 return compiled 30 31 32class FilterEntry: 33 34 __slots__ = ("label", "active", "filter", "byte_filter", "filter_string") 35 36 REGEX, SHELL = 0, 1 37 38 def __init__(self, label, active, filter, byte_filter, filter_string): 39 self.label = label 40 self.active = active 41 self.filter = filter 42 self.byte_filter = byte_filter 43 self.filter_string = filter_string 44 45 @classmethod 46 def compile_regex(cls, regex, byte_regex=False): 47 if byte_regex and not isinstance(regex, bytes): 48 # TODO: Register a custom error handling function to replace 49 # encoding errors with '.'? 50 regex = regex.encode('utf8', 'replace') 51 return try_compile(regex, re.M) 52 53 @classmethod 54 def compile_shell_pattern(cls, pattern): 55 bits = pattern.split() 56 if not bits: 57 # An empty pattern would match everything, so skip it 58 return None 59 elif len(bits) > 1: 60 regexes = [shell_to_regex(b)[:-1] for b in bits] 61 regex = "(%s)$" % "|".join(regexes) 62 else: 63 regex = shell_to_regex(bits[0]) 64 return try_compile(regex) 65 66 @classmethod 67 def new_from_gsetting(cls, elements, filter_type): 68 name, active, filter_string = elements 69 if filter_type == cls.REGEX: 70 str_re = cls.compile_regex(filter_string) 71 bytes_re = cls.compile_regex(filter_string, byte_regex=True) 72 elif filter_type == cls.SHELL: 73 str_re = cls.compile_shell_pattern(filter_string) 74 bytes_re = None 75 else: 76 raise ValueError("Unknown filter type") 77 78 active = active and bool(str_re) 79 return cls(name, active, str_re, bytes_re, filter_string) 80 81 @classmethod 82 def check_filter(cls, filter_string, filter_type): 83 if filter_type == cls.REGEX: 84 compiled = cls.compile_regex(filter_string) 85 elif filter_type == cls.SHELL: 86 compiled = cls.compile_shell_pattern(filter_string) 87 return compiled is not None 88 89 def __copy__(self): 90 new = type(self)( 91 self.label, self.active, None, None, self.filter_string) 92 if self.filter is not None: 93 new.filter = re.compile(self.filter.pattern, self.filter.flags) 94 if self.byte_filter is not None: 95 new.byte_filter = re.compile( 96 self.byte_filter.pattern, self.byte_filter.flags) 97 return new 98 99 100def shell_to_regex(pat): 101 """Translate a shell PATTERN to a regular expression. 102 103 Based on fnmatch.translate(). 104 We also handle {a,b,c} where fnmatch does not. 105 """ 106 107 i, n = 0, len(pat) 108 res = '' 109 while i < n: 110 c = pat[i] 111 i += 1 112 if c == '\\': 113 try: 114 c = pat[i] 115 except IndexError: 116 pass 117 else: 118 i += 1 119 res += re.escape(c) 120 elif c == '*': 121 res += '.*' 122 elif c == '?': 123 res += '.' 124 elif c == '[': 125 try: 126 j = pat.index(']', i) 127 except ValueError: 128 res += r'\[' 129 else: 130 stuff = pat[i:j] 131 i = j + 1 132 if stuff[0] == '!': 133 stuff = '^%s' % stuff[1:] 134 elif stuff[0] == '^': 135 stuff = r'\^%s' % stuff[1:] 136 res += '[%s]' % stuff 137 elif c == '{': 138 try: 139 j = pat.index('}', i) 140 except ValueError: 141 res += '\\{' 142 else: 143 stuff = pat[i:j] 144 i = j + 1 145 res += '(%s)' % "|".join( 146 [shell_to_regex(p)[:-1] for p in stuff.split(",")] 147 ) 148 else: 149 res += re.escape(c) 150 return res + "$" 151