Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
michael@0 | 1 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 4 | |
michael@0 | 5 | import re |
michael@0 | 6 | from distutils.version import LooseVersion |
michael@0 | 7 | from mozpack.errors import errors |
michael@0 | 8 | from collections import OrderedDict |
michael@0 | 9 | |
michael@0 | 10 | |
michael@0 | 11 | class Flag(object): |
michael@0 | 12 | ''' |
michael@0 | 13 | Class for flags in manifest entries in the form: |
michael@0 | 14 | "flag" (same as "flag=true") |
michael@0 | 15 | "flag=yes|true|1" |
michael@0 | 16 | "flag=no|false|0" |
michael@0 | 17 | ''' |
michael@0 | 18 | def __init__(self, name): |
michael@0 | 19 | ''' |
michael@0 | 20 | Initialize a Flag with the given name. |
michael@0 | 21 | ''' |
michael@0 | 22 | self.name = name |
michael@0 | 23 | self.value = None |
michael@0 | 24 | |
michael@0 | 25 | def add_definition(self, definition): |
michael@0 | 26 | ''' |
michael@0 | 27 | Add a flag value definition. Replaces any previously set value. |
michael@0 | 28 | ''' |
michael@0 | 29 | if definition == self.name: |
michael@0 | 30 | self.value = True |
michael@0 | 31 | return |
michael@0 | 32 | assert(definition.startswith(self.name)) |
michael@0 | 33 | if definition[len(self.name)] != '=': |
michael@0 | 34 | return errors.fatal('Malformed flag: %s' % definition) |
michael@0 | 35 | value = definition[len(self.name) + 1:] |
michael@0 | 36 | if value in ('yes', 'true', '1', 'no', 'false', '0'): |
michael@0 | 37 | self.value = value |
michael@0 | 38 | else: |
michael@0 | 39 | return errors.fatal('Unknown value in: %s' % definition) |
michael@0 | 40 | |
michael@0 | 41 | def matches(self, value): |
michael@0 | 42 | ''' |
michael@0 | 43 | Return whether the flag value matches the given value. The values |
michael@0 | 44 | are canonicalized for comparison. |
michael@0 | 45 | ''' |
michael@0 | 46 | if value in ('yes', 'true', '1', True): |
michael@0 | 47 | return self.value in ('yes', 'true', '1', True) |
michael@0 | 48 | if value in ('no', 'false', '0', False): |
michael@0 | 49 | return self.value in ('no', 'false', '0', False, None) |
michael@0 | 50 | raise RuntimeError('Invalid value: %s' % value) |
michael@0 | 51 | |
michael@0 | 52 | def __str__(self): |
michael@0 | 53 | ''' |
michael@0 | 54 | Serialize the flag value in the same form given to the last |
michael@0 | 55 | add_definition() call. |
michael@0 | 56 | ''' |
michael@0 | 57 | if self.value is None: |
michael@0 | 58 | return '' |
michael@0 | 59 | if self.value is True: |
michael@0 | 60 | return self.name |
michael@0 | 61 | return '%s=%s' % (self.name, self.value) |
michael@0 | 62 | |
michael@0 | 63 | |
michael@0 | 64 | class StringFlag(object): |
michael@0 | 65 | ''' |
michael@0 | 66 | Class for string flags in manifest entries in the form: |
michael@0 | 67 | "flag=string" |
michael@0 | 68 | "flag!=string" |
michael@0 | 69 | ''' |
michael@0 | 70 | def __init__(self, name): |
michael@0 | 71 | ''' |
michael@0 | 72 | Initialize a StringFlag with the given name. |
michael@0 | 73 | ''' |
michael@0 | 74 | self.name = name |
michael@0 | 75 | self.values = [] |
michael@0 | 76 | |
michael@0 | 77 | def add_definition(self, definition): |
michael@0 | 78 | ''' |
michael@0 | 79 | Add a string flag definition. |
michael@0 | 80 | ''' |
michael@0 | 81 | assert(definition.startswith(self.name)) |
michael@0 | 82 | value = definition[len(self.name):] |
michael@0 | 83 | if value.startswith('='): |
michael@0 | 84 | self.values.append(('==', value[1:])) |
michael@0 | 85 | elif value.startswith('!='): |
michael@0 | 86 | self.values.append(('!=', value[2:])) |
michael@0 | 87 | else: |
michael@0 | 88 | return errors.fatal('Malformed flag: %s' % definition) |
michael@0 | 89 | |
michael@0 | 90 | def matches(self, value): |
michael@0 | 91 | ''' |
michael@0 | 92 | Return whether one of the string flag definitions matches the given |
michael@0 | 93 | value. |
michael@0 | 94 | For example, |
michael@0 | 95 | flag = StringFlag('foo') |
michael@0 | 96 | flag.add_definition('foo!=bar') |
michael@0 | 97 | flag.matches('bar') returns False |
michael@0 | 98 | flag.matches('qux') returns True |
michael@0 | 99 | flag = StringFlag('foo') |
michael@0 | 100 | flag.add_definition('foo=bar') |
michael@0 | 101 | flag.add_definition('foo=baz') |
michael@0 | 102 | flag.matches('bar') returns True |
michael@0 | 103 | flag.matches('baz') returns True |
michael@0 | 104 | flag.matches('qux') returns False |
michael@0 | 105 | ''' |
michael@0 | 106 | if not self.values: |
michael@0 | 107 | return True |
michael@0 | 108 | for comparison, val in self.values: |
michael@0 | 109 | if eval('value %s val' % comparison): |
michael@0 | 110 | return True |
michael@0 | 111 | return False |
michael@0 | 112 | |
michael@0 | 113 | def __str__(self): |
michael@0 | 114 | ''' |
michael@0 | 115 | Serialize the flag definitions in the same form given to each |
michael@0 | 116 | add_definition() call. |
michael@0 | 117 | ''' |
michael@0 | 118 | res = [] |
michael@0 | 119 | for comparison, val in self.values: |
michael@0 | 120 | if comparison == '==': |
michael@0 | 121 | res.append('%s=%s' % (self.name, val)) |
michael@0 | 122 | else: |
michael@0 | 123 | res.append('%s!=%s' % (self.name, val)) |
michael@0 | 124 | return ' '.join(res) |
michael@0 | 125 | |
michael@0 | 126 | |
michael@0 | 127 | class VersionFlag(object): |
michael@0 | 128 | ''' |
michael@0 | 129 | Class for version flags in manifest entries in the form: |
michael@0 | 130 | "flag=version" |
michael@0 | 131 | "flag<=version" |
michael@0 | 132 | "flag<version" |
michael@0 | 133 | "flag>=version" |
michael@0 | 134 | "flag>version" |
michael@0 | 135 | ''' |
michael@0 | 136 | def __init__(self, name): |
michael@0 | 137 | ''' |
michael@0 | 138 | Initialize a VersionFlag with the given name. |
michael@0 | 139 | ''' |
michael@0 | 140 | self.name = name |
michael@0 | 141 | self.values = [] |
michael@0 | 142 | |
michael@0 | 143 | def add_definition(self, definition): |
michael@0 | 144 | ''' |
michael@0 | 145 | Add a version flag definition. |
michael@0 | 146 | ''' |
michael@0 | 147 | assert(definition.startswith(self.name)) |
michael@0 | 148 | value = definition[len(self.name):] |
michael@0 | 149 | if value.startswith('='): |
michael@0 | 150 | self.values.append(('==', LooseVersion(value[1:]))) |
michael@0 | 151 | elif len(value) > 1 and value[0] in ['<', '>']: |
michael@0 | 152 | if value[1] == '=': |
michael@0 | 153 | if len(value) < 3: |
michael@0 | 154 | return errors.fatal('Malformed flag: %s' % definition) |
michael@0 | 155 | self.values.append((value[0:2], LooseVersion(value[2:]))) |
michael@0 | 156 | else: |
michael@0 | 157 | self.values.append((value[0], LooseVersion(value[1:]))) |
michael@0 | 158 | else: |
michael@0 | 159 | return errors.fatal('Malformed flag: %s' % definition) |
michael@0 | 160 | |
michael@0 | 161 | def matches(self, value): |
michael@0 | 162 | ''' |
michael@0 | 163 | Return whether one of the version flag definitions matches the given |
michael@0 | 164 | value. |
michael@0 | 165 | For example, |
michael@0 | 166 | flag = VersionFlag('foo') |
michael@0 | 167 | flag.add_definition('foo>=1.0') |
michael@0 | 168 | flag.matches('1.0') returns True |
michael@0 | 169 | flag.matches('1.1') returns True |
michael@0 | 170 | flag.matches('0.9') returns False |
michael@0 | 171 | flag = VersionFlag('foo') |
michael@0 | 172 | flag.add_definition('foo>=1.0') |
michael@0 | 173 | flag.add_definition('foo<0.5') |
michael@0 | 174 | flag.matches('0.4') returns True |
michael@0 | 175 | flag.matches('1.0') returns True |
michael@0 | 176 | flag.matches('0.6') returns False |
michael@0 | 177 | ''' |
michael@0 | 178 | value = LooseVersion(value) |
michael@0 | 179 | if not self.values: |
michael@0 | 180 | return True |
michael@0 | 181 | for comparison, val in self.values: |
michael@0 | 182 | if eval('value %s val' % comparison): |
michael@0 | 183 | return True |
michael@0 | 184 | return False |
michael@0 | 185 | |
michael@0 | 186 | def __str__(self): |
michael@0 | 187 | ''' |
michael@0 | 188 | Serialize the flag definitions in the same form given to each |
michael@0 | 189 | add_definition() call. |
michael@0 | 190 | ''' |
michael@0 | 191 | res = [] |
michael@0 | 192 | for comparison, val in self.values: |
michael@0 | 193 | if comparison == '==': |
michael@0 | 194 | res.append('%s=%s' % (self.name, val)) |
michael@0 | 195 | else: |
michael@0 | 196 | res.append('%s%s%s' % (self.name, comparison, val)) |
michael@0 | 197 | return ' '.join(res) |
michael@0 | 198 | |
michael@0 | 199 | |
michael@0 | 200 | class Flags(OrderedDict): |
michael@0 | 201 | ''' |
michael@0 | 202 | Class to handle a set of flags definitions given on a single manifest |
michael@0 | 203 | entry. |
michael@0 | 204 | ''' |
michael@0 | 205 | FLAGS = { |
michael@0 | 206 | 'application': StringFlag, |
michael@0 | 207 | 'appversion': VersionFlag, |
michael@0 | 208 | 'platformversion': VersionFlag, |
michael@0 | 209 | 'contentaccessible': Flag, |
michael@0 | 210 | 'os': StringFlag, |
michael@0 | 211 | 'osversion': VersionFlag, |
michael@0 | 212 | 'abi': StringFlag, |
michael@0 | 213 | 'platform': Flag, |
michael@0 | 214 | 'xpcnativewrappers': Flag, |
michael@0 | 215 | 'tablet': Flag, |
michael@0 | 216 | } |
michael@0 | 217 | RE = re.compile(r'([!<>=]+)') |
michael@0 | 218 | |
michael@0 | 219 | def __init__(self, *flags): |
michael@0 | 220 | ''' |
michael@0 | 221 | Initialize a set of flags given in string form. |
michael@0 | 222 | flags = Flags('contentaccessible=yes', 'appversion>=3.5') |
michael@0 | 223 | ''' |
michael@0 | 224 | OrderedDict.__init__(self) |
michael@0 | 225 | for f in flags: |
michael@0 | 226 | name = self.RE.split(f) |
michael@0 | 227 | name = name[0] |
michael@0 | 228 | if not name in self.FLAGS: |
michael@0 | 229 | errors.fatal('Unknown flag: %s' % name) |
michael@0 | 230 | continue |
michael@0 | 231 | if not name in self: |
michael@0 | 232 | self[name] = self.FLAGS[name](name) |
michael@0 | 233 | self[name].add_definition(f) |
michael@0 | 234 | |
michael@0 | 235 | def __str__(self): |
michael@0 | 236 | ''' |
michael@0 | 237 | Serialize the set of flags. |
michael@0 | 238 | ''' |
michael@0 | 239 | return ' '.join(str(self[k]) for k in self) |
michael@0 | 240 | |
michael@0 | 241 | def match(self, **filter): |
michael@0 | 242 | ''' |
michael@0 | 243 | Return whether the set of flags match the set of given filters. |
michael@0 | 244 | flags = Flags('contentaccessible=yes', 'appversion>=3.5', |
michael@0 | 245 | 'application=foo') |
michael@0 | 246 | flags.match(application='foo') returns True |
michael@0 | 247 | flags.match(application='foo', appversion='3.5') returns True |
michael@0 | 248 | flags.match(application='foo', appversion='3.0') returns False |
michael@0 | 249 | ''' |
michael@0 | 250 | for name, value in filter.iteritems(): |
michael@0 | 251 | if not name in self: |
michael@0 | 252 | continue |
michael@0 | 253 | if not self[name].matches(value): |
michael@0 | 254 | return False |
michael@0 | 255 | return True |